[PATCH 2 of 4] Tests: tests with variables which change between accesses

Maxim Dounin mdounin at mdounin.ru
Mon Jun 8 17:46:40 UTC 2026


# HG changeset patch
# User Maxim Dounin <mdounin at mdounin.ru>
# Date 1780936528 -10800
#      Mon Jun 08 19:35:28 2026 +0300
# Node ID 4af2fea518e2389a44c483ace19f7b4e645989a7
# Parent  29d3b09bcce5c912235a0552db2cb1a3c1996881
Tests: tests with variables which change between accesses.

One example is a named capture, which is changed by a side effect of
a map.  Another example is a non-cacheable (volatile) map which uses
captures from its previous evaluation as input, and therefore becomes
longer on each evaluation.

diff --git a/rewrite.t b/rewrite.t
--- a/rewrite.t
+++ b/rewrite.t
@@ -21,7 +21,7 @@ use Test::Nginx;
 select STDERR; $| = 1;
 select STDOUT; $| = 1;
 
-my $t = Test::Nginx->new()->has(qw/http rewrite proxy/)->plan(25)
+my $t = Test::Nginx->new()->has(qw/http rewrite proxy/)->plan(26)
 	->write_file_expand('nginx.conf', <<'EOF');
 
 %%TEST_GLOBALS%%
@@ -34,6 +34,10 @@ events {
 http {
     %%TEST_GLOBALS_HTTP%%
 
+    map $uri $map_capture {
+       ~(?<capture>.*) $capture;
+    }
+
     server {
         listen       127.0.0.1:8080;
         server_name  localhost;
@@ -142,6 +146,10 @@ http {
             return 200 "uri:$uri args:$args";
         }
 
+        location /map/ {
+            rewrite ^ $capture$map_capture redirect;
+        }
+
         location /break {
             rewrite ^ /return200;
             break;
@@ -274,6 +282,17 @@ like(http_get('/capture_nested/%25?a=b')
 
 }
 
+TODO: {
+todo_skip 'might coredump', 1
+	unless $t->has_version('1.31.3')
+	or $ENV{TEST_NGINX_UNSAFE};
+local $TODO = 'not yet', $t->todo_alerts();
+
+like(http_get('/map/test-long-uri'), qr!Location: .*/map/test-long-uri!ms,
+	'rewrite and map with side effects');
+
+}
+
 # break
 
 like(http_get('/break'), qr/200/, 'valid_location reset');
diff --git a/rewrite_set.t b/rewrite_set.t
--- a/rewrite_set.t
+++ b/rewrite_set.t
@@ -1,5 +1,6 @@
 #!/usr/bin/perl
 
+# (C) Maxim Dounin
 # (C) Sergey Kandaurov
 # (C) Nginx, Inc.
 
@@ -22,7 +23,7 @@ use Test::Nginx;
 select STDERR; $| = 1;
 select STDOUT; $| = 1;
 
-my $t = Test::Nginx->new()->has(qw/http rewrite ssi/)->plan(10);
+my $t = Test::Nginx->new()->has(qw/http rewrite ssi map/)->plan(16);
 
 $t->write_file_expand('nginx.conf', <<'EOF');
 
@@ -36,6 +37,21 @@ events {
 http {
     %%TEST_GLOBALS_HTTP%%
 
+    map $uri $map_capture {
+        ~(?<capture>.*) $capture;
+    }
+
+    map prefix:$capture $map_volatile {
+        volatile;
+        ~(?<capture>.*) $capture;
+    }
+
+    map $args $map_flush {
+        volatile;
+        default wrong;
+        secret  good;
+    }
+
     server {
         listen       127.0.0.1:8080;
         server_name  localhost;
@@ -78,6 +94,40 @@ http {
             return 200 "X${temp}X";
         }
 
+        location /map {
+            set $temp "$capture $map_capture";
+            return 200 "X${temp}X";
+        }
+
+        location /map_volatile {
+            set $temp "$map_volatile";
+            return 200 "X${temp}X";
+        }
+
+        location /map_root {
+            root html/$pid;
+            return 200 "X${map_volatile}X${document_root}X";
+        }
+
+        location /map_root_root {
+            root html/$pid;
+            return 200 "X${map_volatile}X${document_root}${realpath_root}X";
+        }
+
+        location /map_root_overflow {
+            root html/$capture/$map_capture;
+            set $temp "$document_root";
+            return 200 "X${temp}X";
+        }
+
+        location /map_flush {
+            set $args "wrong";
+            set $temp "$map_flush";
+            set $args "secret";
+            set $temp "$temp:$map_flush";
+            return 200 "X${temp}X";
+        }
+
         location /t1 {
             set $http_foo "set_foo";
             ssi on;
@@ -147,6 +197,48 @@ like(http_get('/args/%20x'), qr!Xset_/ar
 
 }
 
+TODO: {
+todo_skip 'might coredump', 5
+	unless $t->has_version('1.31.3')
+	or $ENV{TEST_NGINX_UNSAFE};
+local $TODO = 'not yet', $t->todo_alerts();
+
+# map can change other variables via named captures,
+# resulting in invalid buffer length calculations
+
+like(http_get('/map'), qr!X.*/mapX!, 'set and map side effects');
+
+# non-cacheable variable can change its length on each evaluation,
+# resulting in invalid buffer length calculations
+
+like(http_get('/map_volatile'), qr!Xprefix:.*X!,
+	'set and volatile map');
+
+# even if a separate flush step is used, such as with return,
+# which uses ngx_http_complex_value(), an additional flush might happen
+# as a side effect of a variable lookup (notably $document_root and
+# $realpath_root when using root with variables)
+
+like(http_get('/map_root'), qr!Xprefix:X.*X!,
+	'return and volatile map with $document_root');
+
+like(http_get('/map_root_root'), qr!Xprefix:X.*X!,
+	'return and volatile map with $document_root and $realpath_root');
+
+# similarly, map with side effects can cause invalid buffer length
+# during evaluation of $document_root, which uses ngx_http_script_run()
+
+like(http_get('/map_root_overflow'), qr!X.*/map_root_overflowX!,
+	'$document_root with map side effects');
+
+}
+
+# non-cacheable map can be derived from a non-cacheable variable,
+# which also needs to be flushed before getting the map value
+
+like(http_get('/map_flush'), qr!Xwrong:goodX!,
+	'set and volatile map source flush');
+
 # non-indexed access of prefixed variables
 
 like(http_get_extra('/t1.html', 'Foo: http_foo'), qr/Xset_fooX/,
diff --git a/stream_set.t b/stream_set.t
--- a/stream_set.t
+++ b/stream_set.t
@@ -24,7 +24,8 @@ select STDERR; $| = 1;
 select STDOUT; $| = 1;
 
 my $t = Test::Nginx->new()
-	->has(qw/stream stream_return stream_map stream_set/);
+	->has(qw/stream stream_return stream_map stream_set http rewrite/)
+	->plan(3);
 
 $t->write_file_expand('nginx.conf', <<'EOF');
 
@@ -42,6 +43,10 @@ stream {
         default "original";
     }
 
+    map 0 $map_capture {
+        ~(?<capture>.*) $capture;
+    }
+
     server {
         listen  127.0.0.1:8082;
         return  $map_var:$set_var;
@@ -54,15 +59,31 @@ stream {
         listen  127.0.0.1:8083;
         return  $set_var;
     }
+
+    server {
+        listen  127.0.0.1:8084;
+        return  "$capture $map_capture";
+    }
 }
 
 EOF
 
-$t->run()->plan(2);
+$t->run();
 
 ###############################################################################
 
 is(stream('127.0.0.1:' . port(8082))->read(), 'new:original', 'set');
 is(stream('127.0.0.1:' . port(8083))->read(), '', 'uninitialized variable');
 
+TODO: {
+todo_skip 'might coredump', 1
+	unless $t->has_version('1.31.3')
+	or $ENV{TEST_NGINX_UNSAFE};
+local $TODO = 'not yet', $t->todo_alerts();
+
+is(stream('127.0.0.1:' . port(8084))->read(), '0 0',
+	'set and map with side effects');
+
+}
+
 ###############################################################################



More information about the nginx-devel mailing list