Module: monitoring-plugins
 Branch: master
 Commit: f7df5579ab1d029eeaad785a6d016884c8f36c7d
 Author: Ahmet Oeztuerk <[email protected]>
   Date: Tue Dec  9 00:11:20 2025 +0100
    URL: 
https://www.monitoring-plugins.org/repositories/monitoring-plugins/commit/?id=f7df5579

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.

---

 plugins/tests/check_curl.t | 194 ++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 193 insertions(+), 1 deletion(-)

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;
 use NPTest;
 use FindBin qw($Bin);
 
+use URI;
+use URI::QueryParam;
+use HTTP::Daemon;
+use HTTP::Daemon::SSL;
+
 $ENV{'LC_TIME'} = "C";
 
 my $common_tests = 75;
@@ -186,6 +191,149 @@ sub run_server {
                                $c->send_response('moved to /redirect2');
                        } elsif ($r->url->path eq "/redir_timeout") {
                                $c->send_redirect( "/timeout" );
+            } elsif ($r->url->path =~ m{^/redirect_with_increment}) {
+                               # 
<scheme>://<username>:<password>@<host>:<port>/<path>;<parameters>?<query>#<fragment>
+                               # Find every parameter, query , and fragment 
keys and increment them
+
+                               my $content = "";
+
+                # Use URI to help with query/fragment; parse path params 
manually.
+                my $original_url = $r->url->as_string;
+                               $content .= " original_url: ${original_url}\n";
+                my $uri = URI->new($original_url);
+                               $content .= " uri: ${uri}\n";
+
+                               my $path = $uri->path // '';
+                               my $query = $uri->query // '';
+                               my $fragment = $uri->fragment // '';
+
+                               $content .= " path: ${path}\n";
+                               $content .= " query: ${query}\n";
+                               $content .= " fragment: ${fragment}\n";
+
+                               # Sets and returns the scheme-specific part of 
the $uri (everything between the scheme and the fragment) as an escaped string.
+                               #my $opaque = $uri->opaque;
+                               #$content .= " opaque: ${opaque}\n";
+
+                               # group 1 is captured: anything that is not '/' 
: ([^/]*)
+                               # / matches the / directly
+                               # group 2 is captured: anything : (.*)
+                               #my ($before_slash, $after_slash) = $opaque =~ 
m{^/([^/]*)/(.*)$};
+                               #$before_slash //= '';
+                               #$after_slash //= '';
+                               #$content .= " before_slash: ${before_slash}\n";
+                               #$content .= " after_slash: ${after_slash}\n";
+
+                # split the uri part and parameters. uri package cannot do this
+                               # group 1 is captured: anything without a 
semicolon: ([^;])
+                               # group 2 is uncaptured: (?:;(.*))?
+                               # (? )? prevents the capture
+                               # in between the ';' matches the first ever 
semicolon
+                               # group3 is captured: any character stirng : 
(.*)
+                my ($before_params, $params) = $uri =~ m{^([^;]*)(?:;(.*))?\?};
+                               $before_params //= '';
+                               $params //= '';
+                               $content .= " before_params: 
${before_params}\n";
+                               $content .= " params: ${params}\n";
+                my @parameter_pairs;
+                if (defined $params && length $params) {
+                    for my $p (split /;/, $params) {
+                        my ($key,$value) = split /=/, $p, 2;
+                        $value //= '';
+                        push @parameter_pairs, [ $key, $value ];
+                                               $content .= " parameter: ${key} 
-> ${value}\n";
+                    }
+                }
+
+                # query parameters are offered directly from the library
+                my @query_form = $uri->query_form;
+                my @query_parameter_pairs;
+                while (@query_form) {
+                    my $key = shift @query_form;
+                    my $value = shift @query_form;
+                                       $value //= ''; # there can be valueless 
keys
+                    push @query_parameter_pairs, [ $key, $value ];
+                                       $content .= " query: ${key} -> 
${value}\n";
+                }
+
+                # fragment: try to split into key=value pairs on ';' or '&' if 
present
+                my @fragment_pairs;
+                               my $fragment_seperator = '';
+                if ($fragment ne '') {
+                                       $fragment_seperator = ($fragment =~ /&/ 
? '&' : ';');
+                    for my $f (split /[&;]/, $fragment) {
+                                               next unless length $f;
+                        my ($key,$value) = split /=/, $f, 2;
+                        $value //= '';
+                        push @fragment_pairs, [ $key, $value ];
+                                               $content .= " fragment: ${key} 
-> ${value}\n";
+                    }
+                }
+
+                # helper to increment value
+                my $increment = sub {
+                    my ($v) = @_;
+                    return $v if !defined $v || $v eq '';
+                    # numeric integer
+                    if ($v =~ /^-?\d+$/) {
+                        return $v + 1;
+                    }
+                    # otherwise -> increment as if its an ascii character
+                                       # sed replacement syntax, but the $& 
holds the matched character
+                    if (length($v)) {
+                        (my $new_v = $v) =~ s/./chr(ord($&) + 1)/ge;
+                                       return $new_v;
+                    }
+                };
+
+                # increment values in pairs
+                for my $pair (@parameter_pairs) {
+                    $pair->[1] = $increment->($pair->[1]);
+                                       $content .= " parameter new: " . 
$pair->[0] . " -> " . $pair->[1] . "\n";
+                }
+                for my $pair (@query_parameter_pairs) {
+                    $pair->[1] = $increment->($pair->[1]);
+                                       $content .= " query parameter new: " . 
$pair->[0] . " -> " . $pair->[1] . "\n";
+                }
+                for my $pair (@fragment_pairs) {
+                    $pair->[1] = $increment->($pair->[1]);
+                                       $content .= " fragment new: " . 
$pair->[0] . " -> " . $pair->[1] . "\n";
+                }
+
+                # rebuild strings
+                my $new_parameter_str = join(';', map { $_->[0] . '=' . 
$_->[1] } @parameter_pairs);
+                               $content .= " new_parameter_str: 
${new_parameter_str}\n";
+
+                               # library can rebuild from an array
+                my @new_query_form;
+                for my $p (@query_parameter_pairs) { push @new_query_form, 
$p->[0], $p->[1] }
+
+                my $new_fragment_str = '';
+                if (@fragment_pairs) {
+                    $new_fragment_str = join($fragment_seperator, map { 
$_->[0] . '=' . $_->[1] } @fragment_pairs);
+                }
+                               $content .= " new_fragment_str: 
${new_fragment_str}\n";
+
+                # construct new URI using the library
+                my $new_uri = URI->new('');
+                $new_uri->path( $before_params . ($new_parameter_str ? ';' . 
$new_parameter_str : '') );
+                $new_uri->query_form( \@new_query_form ) if @new_query_form;
+                $new_uri->fragment( $new_fragment_str ) if $new_fragment_str 
ne '';
+                               $content .= " new_uri: ${new_uri}\n";
+
+                               # Redirect until fail_count or redirect_count 
reaches 3
+                               if ($new_uri =~ /fail_count=3/){
+                                       
$c->send_error(HTTP::Status->RC_FORBIDDEN, "fail count reached 3, url path:" . 
$r->url->path );
+                               } elsif ($new_uri =~ /redirect_count=3/){
+                                       $c->send_response(HTTP::Response->new( 
200, 'OK', undef , $content ));
+                               } elsif ($new_uri =~ 
/location_redirect_count=3/){
+                                       $c->send_basic_header(302);
+                                       $c->send_header("Location", "$new_uri" 
);
+                                       $c->send_crlf;
+                                       $c->send_response("$content \n moved to 
$new_uri");
+                               } else {
+                       $c->send_redirect( $new_uri->as_string, 301, $content );
+                               }
                        } elsif ($r->url->path eq "/timeout") {
                                # Keep $c from being destroyed, but prevent 
severe leaks
                                unshift @persist, $c;
@@ -215,7 +363,7 @@ sub run_server {
                                        return($chunk);
                                }));
                        } else {
-                               $c->send_error(HTTP::Status->RC_FORBIDDEN);
+                               $c->send_error(HTTP::Status->RC_FORBIDDEN, 
"unknown url path:" . $r->url->path );
                        }
                        $c->close;
                }
@@ -482,6 +630,50 @@ sub run_common_tests {
        is( $result->return_code, 0, $cmd);
        like( $result->output, '/.*HTTP/1.1 200 OK - \d+ bytes in [\d\.]+ 
second.*/', "Output correct: ".$result->output );
 
+       # Redirect with increment tests. These are for checking if the url 
parameters, query parameters and fragment are parsed.
+       # The server at this point has dynamic redirection. It tries to 
increment values that it sees in these fields, then redirects.
+       # It also appends some debug log and writes it into HTTP content, pass 
the -vvv parameter to see them.
+
+       $cmd = "$command -p $port_http -u 
'/redirect_with_increment/path1/path2/path3/path4' --onredirect=follow -vvv";
+       $result = NPTest->testCmd( "$cmd" );
+       is( $result->return_code, 1, $cmd);
+       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 );
+
+       $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";
+       $result = NPTest->testCmd( "$cmd" );
+       is( $result->return_code, 0, $cmd);
+       like( $result->output, '/.*HTTP/1.1 200 OK - \d+ bytes in [\d\.]+ 
second.*/', "Output correct, redirect_count went up to 3: ".$result->output );
+
+       $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";
+       $result = NPTest->testCmd( "$cmd" );
+       is( $result->return_code, 0, $cmd);
+       like( $result->output, '/.*HTTP/1.1 200 OK - \d+ bytes in [\d\.]+ 
second.*/', "Output correct, location_redirect_count went up to 3: 
".$result->output );
+
+       $cmd = "$command -p $port_http -u 
'/redirect_with_increment/path1/path2;redirect_count=0;fail_count=2' 
--onredirect=follow -vvv";
+       $result = NPTest->testCmd( "$cmd" );
+       is( $result->return_code, 1, $cmd);
+       like( $result->output, '/.*HTTP/1.1 403 Forbidden - \d+ bytes in 
[\d\.]+ second.*/', "Output correct, early due to fail_count reaching 3: 
".$result->output );
+
+       $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";
+       $result = NPTest->testCmd( "$cmd" );
+       is( $result->return_code, 0, $cmd);
+       like( $result->output, '/.*;p1=3;p2=cd\?*/', "Output correct, parsed 
and incremented both parameters p1 and p2 : ".$result->output );
+
+       $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";
+       $result = NPTest->testCmd( "$cmd" );
+       is( $result->return_code, 0, $cmd);
+       like( $result->output, '/.*\?qp1=12&qp2=mn*/', "Output correct, parsed 
and incremented both query parameters qp1 and qp2 : ".$result->output );
+
+       $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";
+       $result = NPTest->testCmd( "$cmd" );
+       is( $result->return_code, 0, $cmd);
+       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 );
+
+       $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";
+       $result = NPTest->testCmd( "$cmd" );
+       is( $result->return_code, 0, $cmd);
+       like( $result->output, '/.*#f1=vguv*/', "Output correct, parsed and 
incremented fragment f1 : ".$result->output );
+
        # These tests may block
        # stickyport - on full urlS port is set back to 80 otherwise
        $cmd = "$command -f stickyport -u /redir_external -t 5 -s redirected";

Reply via email to