diff options
| author | Ahmet Oeztuerk <Ahmet.Oeztuerk@consol.de> | 2025-12-09 00:11:20 +0100 |
|---|---|---|
| committer | Ahmet Oeztuerk <Ahmet.Oeztuerk@consol.de> | 2025-12-09 00:13:15 +0100 |
| commit | f7df5579ab1d029eeaad785a6d016884c8f36c7d (patch) | |
| tree | 7250f60a8a3491bbe626006160993aacb9a3d4bb /plugins | |
| parent | e0b30cc6e82113491261c872a371a2fa0b4db3ba (diff) | |
| download | monitoring-plugins-f7df5579ab1d029eeaad785a6d016884c8f36c7d.tar.gz | |
check_curl add tests for uri field parsing
plugins/tests/check_curl.t forks and runs a http(s) server that responds
to specific uri endpoints. Added another endpoint under
/redirect_with_increment with dynamic redirection points.
This endpoint will parse different parts of the uri that come after the
path: parameters, query and the fragment. If applicable, seperate
elements within each field are parsed into key/value pairs. value is
incremented in redirected URI.
Tests if check_url redirection logic retains different parts of the url
when parsing the uri and building the new redirected URL. Current tests
show that it ignores the fragment part.
Diffstat (limited to 'plugins')
| -rwxr-xr-x | plugins/tests/check_curl.t | 194 |
1 files changed, 193 insertions, 1 deletions
diff --git a/plugins/tests/check_curl.t b/plugins/tests/check_curl.t index 52c5ad1c..bc0101a2 100755 --- a/plugins/tests/check_curl.t +++ b/plugins/tests/check_curl.t | |||
| @@ -20,6 +20,11 @@ use Test::More; | |||
| 20 | use NPTest; | 20 | use NPTest; |
| 21 | use FindBin qw($Bin); | 21 | use FindBin qw($Bin); |
| 22 | 22 | ||
| 23 | use URI; | ||
| 24 | use URI::QueryParam; | ||
| 25 | use HTTP::Daemon; | ||
| 26 | use HTTP::Daemon::SSL; | ||
| 27 | |||
| 23 | $ENV{'LC_TIME'} = "C"; | 28 | $ENV{'LC_TIME'} = "C"; |
| 24 | 29 | ||
| 25 | my $common_tests = 75; | 30 | my $common_tests = 75; |
| @@ -186,6 +191,149 @@ sub run_server { | |||
| 186 | $c->send_response('moved to /redirect2'); | 191 | $c->send_response('moved to /redirect2'); |
| 187 | } elsif ($r->url->path eq "/redir_timeout") { | 192 | } elsif ($r->url->path eq "/redir_timeout") { |
| 188 | $c->send_redirect( "/timeout" ); | 193 | $c->send_redirect( "/timeout" ); |
| 194 | } elsif ($r->url->path =~ m{^/redirect_with_increment}) { | ||
| 195 | # <scheme>://<username>:<password>@<host>:<port>/<path>;<parameters>?<query>#<fragment> | ||
| 196 | # Find every parameter, query , and fragment keys and increment them | ||
| 197 | |||
| 198 | my $content = ""; | ||
| 199 | |||
| 200 | # Use URI to help with query/fragment; parse path params manually. | ||
| 201 | my $original_url = $r->url->as_string; | ||
| 202 | $content .= " original_url: ${original_url}\n"; | ||
| 203 | my $uri = URI->new($original_url); | ||
| 204 | $content .= " uri: ${uri}\n"; | ||
| 205 | |||
| 206 | my $path = $uri->path // ''; | ||
| 207 | my $query = $uri->query // ''; | ||
| 208 | my $fragment = $uri->fragment // ''; | ||
| 209 | |||
| 210 | $content .= " path: ${path}\n"; | ||
| 211 | $content .= " query: ${query}\n"; | ||
| 212 | $content .= " fragment: ${fragment}\n"; | ||
| 213 | |||
| 214 | # Sets and returns the scheme-specific part of the $uri (everything between the scheme and the fragment) as an escaped string. | ||
| 215 | #my $opaque = $uri->opaque; | ||
| 216 | #$content .= " opaque: ${opaque}\n"; | ||
| 217 | |||
| 218 | # group 1 is captured: anything that is not '/' : ([^/]*) | ||
| 219 | # / matches the / directly | ||
| 220 | # group 2 is captured: anything : (.*) | ||
| 221 | #my ($before_slash, $after_slash) = $opaque =~ m{^/([^/]*)/(.*)$}; | ||
| 222 | #$before_slash //= ''; | ||
| 223 | #$after_slash //= ''; | ||
| 224 | #$content .= " before_slash: ${before_slash}\n"; | ||
| 225 | #$content .= " after_slash: ${after_slash}\n"; | ||
| 226 | |||
| 227 | # split the uri part and parameters. uri package cannot do this | ||
| 228 | # group 1 is captured: anything without a semicolon: ([^;]) | ||
| 229 | # group 2 is uncaptured: (?:;(.*))? | ||
| 230 | # (? )? prevents the capture | ||
| 231 | # in between the ';' matches the first ever semicolon | ||
| 232 | # group3 is captured: any character stirng : (.*) | ||
| 233 | my ($before_params, $params) = $uri =~ m{^([^;]*)(?:;(.*))?\?}; | ||
| 234 | $before_params //= ''; | ||
| 235 | $params //= ''; | ||
| 236 | $content .= " before_params: ${before_params}\n"; | ||
| 237 | $content .= " params: ${params}\n"; | ||
| 238 | my @parameter_pairs; | ||
| 239 | if (defined $params && length $params) { | ||
| 240 | for my $p (split /;/, $params) { | ||
| 241 | my ($key,$value) = split /=/, $p, 2; | ||
| 242 | $value //= ''; | ||
| 243 | push @parameter_pairs, [ $key, $value ]; | ||
| 244 | $content .= " parameter: ${key} -> ${value}\n"; | ||
| 245 | } | ||
| 246 | } | ||
| 247 | |||
| 248 | # query parameters are offered directly from the library | ||
| 249 | my @query_form = $uri->query_form; | ||
| 250 | my @query_parameter_pairs; | ||
| 251 | while (@query_form) { | ||
| 252 | my $key = shift @query_form; | ||
| 253 | my $value = shift @query_form; | ||
| 254 | $value //= ''; # there can be valueless keys | ||
| 255 | push @query_parameter_pairs, [ $key, $value ]; | ||
| 256 | $content .= " query: ${key} -> ${value}\n"; | ||
| 257 | } | ||
| 258 | |||
| 259 | # fragment: try to split into key=value pairs on ';' or '&' if present | ||
| 260 | my @fragment_pairs; | ||
| 261 | my $fragment_seperator = ''; | ||
| 262 | if ($fragment ne '') { | ||
| 263 | $fragment_seperator = ($fragment =~ /&/ ? '&' : ';'); | ||
| 264 | for my $f (split /[&;]/, $fragment) { | ||
| 265 | next unless length $f; | ||
| 266 | my ($key,$value) = split /=/, $f, 2; | ||
| 267 | $value //= ''; | ||
| 268 | push @fragment_pairs, [ $key, $value ]; | ||
| 269 | $content .= " fragment: ${key} -> ${value}\n"; | ||
| 270 | } | ||
| 271 | } | ||
| 272 | |||
| 273 | # helper to increment value | ||
| 274 | my $increment = sub { | ||
| 275 | my ($v) = @_; | ||
| 276 | return $v if !defined $v || $v eq ''; | ||
| 277 | # numeric integer | ||
| 278 | if ($v =~ /^-?\d+$/) { | ||
| 279 | return $v + 1; | ||
| 280 | } | ||
| 281 | # otherwise -> increment as if its an ascii character | ||
| 282 | # sed replacement syntax, but the $& holds the matched character | ||
| 283 | if (length($v)) { | ||
| 284 | (my $new_v = $v) =~ s/./chr(ord($&) + 1)/ge; | ||
| 285 | return $new_v; | ||
| 286 | } | ||
| 287 | }; | ||
| 288 | |||
| 289 | # increment values in pairs | ||
| 290 | for my $pair (@parameter_pairs) { | ||
| 291 | $pair->[1] = $increment->($pair->[1]); | ||
| 292 | $content .= " parameter new: " . $pair->[0] . " -> " . $pair->[1] . "\n"; | ||
| 293 | } | ||
| 294 | for my $pair (@query_parameter_pairs) { | ||
| 295 | $pair->[1] = $increment->($pair->[1]); | ||
| 296 | $content .= " query parameter new: " . $pair->[0] . " -> " . $pair->[1] . "\n"; | ||
| 297 | } | ||
| 298 | for my $pair (@fragment_pairs) { | ||
| 299 | $pair->[1] = $increment->($pair->[1]); | ||
| 300 | $content .= " fragment new: " . $pair->[0] . " -> " . $pair->[1] . "\n"; | ||
| 301 | } | ||
| 302 | |||
| 303 | # rebuild strings | ||
| 304 | my $new_parameter_str = join(';', map { $_->[0] . '=' . $_->[1] } @parameter_pairs); | ||
| 305 | $content .= " new_parameter_str: ${new_parameter_str}\n"; | ||
| 306 | |||
| 307 | # library can rebuild from an array | ||
| 308 | my @new_query_form; | ||
| 309 | for my $p (@query_parameter_pairs) { push @new_query_form, $p->[0], $p->[1] } | ||
| 310 | |||
| 311 | my $new_fragment_str = ''; | ||
| 312 | if (@fragment_pairs) { | ||
| 313 | $new_fragment_str = join($fragment_seperator, map { $_->[0] . '=' . $_->[1] } @fragment_pairs); | ||
| 314 | } | ||
| 315 | $content .= " new_fragment_str: ${new_fragment_str}\n"; | ||
| 316 | |||
| 317 | # construct new URI using the library | ||
| 318 | my $new_uri = URI->new(''); | ||
| 319 | $new_uri->path( $before_params . ($new_parameter_str ? ';' . $new_parameter_str : '') ); | ||
| 320 | $new_uri->query_form( \@new_query_form ) if @new_query_form; | ||
| 321 | $new_uri->fragment( $new_fragment_str ) if $new_fragment_str ne ''; | ||
| 322 | $content .= " new_uri: ${new_uri}\n"; | ||
| 323 | |||
| 324 | # Redirect until fail_count or redirect_count reaches 3 | ||
| 325 | if ($new_uri =~ /fail_count=3/){ | ||
| 326 | $c->send_error(HTTP::Status->RC_FORBIDDEN, "fail count reached 3, url path:" . $r->url->path ); | ||
| 327 | } elsif ($new_uri =~ /redirect_count=3/){ | ||
| 328 | $c->send_response(HTTP::Response->new( 200, 'OK', undef , $content )); | ||
| 329 | } elsif ($new_uri =~ /location_redirect_count=3/){ | ||
| 330 | $c->send_basic_header(302); | ||
| 331 | $c->send_header("Location", "$new_uri" ); | ||
| 332 | $c->send_crlf; | ||
| 333 | $c->send_response("$content \n moved to $new_uri"); | ||
| 334 | } else { | ||
| 335 | $c->send_redirect( $new_uri->as_string, 301, $content ); | ||
| 336 | } | ||
| 189 | } elsif ($r->url->path eq "/timeout") { | 337 | } elsif ($r->url->path eq "/timeout") { |
| 190 | # Keep $c from being destroyed, but prevent severe leaks | 338 | # Keep $c from being destroyed, but prevent severe leaks |
| 191 | unshift @persist, $c; | 339 | unshift @persist, $c; |
| @@ -215,7 +363,7 @@ sub run_server { | |||
| 215 | return($chunk); | 363 | return($chunk); |
| 216 | })); | 364 | })); |
| 217 | } else { | 365 | } else { |
| 218 | $c->send_error(HTTP::Status->RC_FORBIDDEN); | 366 | $c->send_error(HTTP::Status->RC_FORBIDDEN, "unknown url path:" . $r->url->path ); |
| 219 | } | 367 | } |
| 220 | $c->close; | 368 | $c->close; |
| 221 | } | 369 | } |
| @@ -482,6 +630,50 @@ sub run_common_tests { | |||
| 482 | is( $result->return_code, 0, $cmd); | 630 | is( $result->return_code, 0, $cmd); |
| 483 | like( $result->output, '/.*HTTP/1.1 200 OK - \d+ bytes in [\d\.]+ second.*/', "Output correct: ".$result->output ); | 631 | like( $result->output, '/.*HTTP/1.1 200 OK - \d+ bytes in [\d\.]+ second.*/', "Output correct: ".$result->output ); |
| 484 | 632 | ||
| 633 | # Redirect with increment tests. These are for checking if the url parameters, query parameters and fragment are parsed. | ||
| 634 | # The server at this point has dynamic redirection. It tries to increment values that it sees in these fields, then redirects. | ||
| 635 | # It also appends some debug log and writes it into HTTP content, pass the -vvv parameter to see them. | ||
| 636 | |||
| 637 | $cmd = "$command -p $port_http -u '/redirect_with_increment/path1/path2/path3/path4' --onredirect=follow -vvv"; | ||
| 638 | $result = NPTest->testCmd( "$cmd" ); | ||
| 639 | is( $result->return_code, 1, $cmd); | ||
| 640 | like( $result->output, '/.*HTTP/1.1 403 Forbidden - \d+ bytes in [\d\.]+ second.*/', "Output correct, redirect_count was not present, got redirected to / : ".$result->output ); | ||
| 641 | |||
| 642 | $cmd = "$command -p $port_http -u '/redirect_with_increment/path1/path2;redirect_count=0;p1=1;p2=ab?qp1=10&qp2=kl#f1=test' --onredirect=follow -vvv"; | ||
| 643 | $result = NPTest->testCmd( "$cmd" ); | ||
| 644 | is( $result->return_code, 0, $cmd); | ||
| 645 | like( $result->output, '/.*HTTP/1.1 200 OK - \d+ bytes in [\d\.]+ second.*/', "Output correct, redirect_count went up to 3: ".$result->output ); | ||
| 646 | |||
| 647 | $cmd = "$command -p $port_http -u '/redirect_with_increment/path1/path2;location_redirect_count=0;p1=1;p2=ab?qp1=10&qp2=kl#f1=test' --onredirect=follow -vvv"; | ||
| 648 | $result = NPTest->testCmd( "$cmd" ); | ||
| 649 | is( $result->return_code, 0, $cmd); | ||
| 650 | like( $result->output, '/.*HTTP/1.1 200 OK - \d+ bytes in [\d\.]+ second.*/', "Output correct, location_redirect_count went up to 3: ".$result->output ); | ||
| 651 | |||
| 652 | $cmd = "$command -p $port_http -u '/redirect_with_increment/path1/path2;redirect_count=0;fail_count=2' --onredirect=follow -vvv"; | ||
| 653 | $result = NPTest->testCmd( "$cmd" ); | ||
| 654 | is( $result->return_code, 1, $cmd); | ||
| 655 | like( $result->output, '/.*HTTP/1.1 403 Forbidden - \d+ bytes in [\d\.]+ second.*/', "Output correct, early due to fail_count reaching 3: ".$result->output ); | ||
| 656 | |||
| 657 | $cmd = "$command -p $port_http -u '/redirect_with_increment/path1/path2;redirect_count=0;p1=1;p2=ab?qp1=10&qp2=kl#f1=test' --onredirect=follow -vvv"; | ||
| 658 | $result = NPTest->testCmd( "$cmd" ); | ||
| 659 | is( $result->return_code, 0, $cmd); | ||
| 660 | like( $result->output, '/.*;p1=3;p2=cd\?*/', "Output correct, parsed and incremented both parameters p1 and p2 : ".$result->output ); | ||
| 661 | |||
| 662 | $cmd = "$command -p $port_http -u '/redirect_with_increment/path1/path2;redirect_count=0;p1=1;p2=ab?qp1=10&qp2=kl#f1=test' --onredirect=follow -vvv"; | ||
| 663 | $result = NPTest->testCmd( "$cmd" ); | ||
| 664 | is( $result->return_code, 0, $cmd); | ||
| 665 | like( $result->output, '/.*\?qp1=12&qp2=mn*/', "Output correct, parsed and incremented both query parameters qp1 and qp2 : ".$result->output ); | ||
| 666 | |||
| 667 | $cmd = "$command -p $port_http -u '/redirect_with_increment;redirect_count=0;?qp0=0&qp1=1&qp2=2&qp3=3&qp4=4&qp5=5' --onredirect=follow -vvv"; | ||
| 668 | $result = NPTest->testCmd( "$cmd" ); | ||
| 669 | is( $result->return_code, 0, $cmd); | ||
| 670 | like( $result->output, '/.*\?qp0=2&qp1=3&qp2=4&qp3=5&qp4=6&qp5=7*/', "Output correct, parsed and incremented query parameters qp1,qp2,qp3,qp4,qp5 in order : ".$result->output ); | ||
| 671 | |||
| 672 | $cmd = "$command -p $port_http -u '/redirect_with_increment/path1/path2;redirect_count=0;p1=1;p2=ab?qp1=10&qp2=kl#f1=test' --onredirect=follow -vvv"; | ||
| 673 | $result = NPTest->testCmd( "$cmd" ); | ||
| 674 | is( $result->return_code, 0, $cmd); | ||
| 675 | like( $result->output, '/.*#f1=vguv*/', "Output correct, parsed and incremented fragment f1 : ".$result->output ); | ||
| 676 | |||
| 485 | # These tests may block | 677 | # These tests may block |
| 486 | # stickyport - on full urlS port is set back to 80 otherwise | 678 | # stickyport - on full urlS port is set back to 80 otherwise |
| 487 | $cmd = "$command -f stickyport -u /redir_external -t 5 -s redirected"; | 679 | $cmd = "$command -f stickyport -u /redir_external -t 5 -s redirected"; |
