]> git.kaiwu.me - njs.git/commitdiff
Fetch: validate response body size
authorDmitry Volyntsev <xeioex@nginx.com>
Tue, 15 Sep 2026 22:45:50 +0000 (15:45 -0700)
committerDmitry Volyntsev <xeioexception@gmail.com>
Wed, 16 Sep 2026 21:17:38 +0000 (14:17 -0700)
Previously, a negative max_response_body_size could make a close-delimited
response appear as a successful empty body.

The fix is to reject the invalid option while parsing fetch arguments.

nginx/ngx_js_fetch.c
nginx/ngx_qjs_fetch.c
nginx/t/js_fetch.t

index 06dff3856043b1a71e70fefe31896fc6b4cdd6e6..9af568214a698d811ebf8b1c40288763edbe26f5 100644 (file)
@@ -586,6 +586,11 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
 #endif
     }
 
+    if (http->max_response_body_size < 0) {
+        njs_vm_type_error(vm, "max_response_body_size must be non-negative");
+        goto fail;
+    }
+
 #if (NGX_SSL)
     if (http->ssl != NULL && !http->ssl_verify) {
         http->keepalive = 0;
index 3373c9a98572c275bf7ebc8b692e8a2db85f8c80..d4b2d536cd7fcb50c91df41ca14fc226d7d690f2 100644 (file)
@@ -336,6 +336,11 @@ ngx_qjs_ext_fetch(JSContext *cx, JSValueConst this_val, int argc,
 #endif
     }
 
+    if (http->max_response_body_size < 0) {
+        JS_ThrowTypeError(cx, "max_response_body_size must be non-negative");
+        goto fail;
+    }
+
 #if (NGX_SSL)
     if (http->ssl != NULL && !http->ssl_verify) {
         http->keepalive = 0;
index 4c23fcfb3958824431d56da1b3e4bb4c4a5b6482..76370ab4b944c93bea88dd16cb67ca68ade551f4 100644 (file)
@@ -64,6 +64,10 @@ http {
             js_content test.broken_catch;
         }
 
+        location /body_size {
+            js_content test.body_size;
+        }
+
         location /body {
             js_content test.body;
         }
@@ -273,6 +277,19 @@ $t->write_file('test.js', <<EOF);
         return process_errors_catch(r, tests);
     }
 
+    async function body_size(r) {
+        try {
+            let reply = await ngx.fetch(
+                'http://127.0.0.1:$p2/no_content_length',
+                {max_response_body_size: -1});
+            let body = await reply.text();
+            r.return(200, `ok:\${body.length}`);
+
+        } catch (e) {
+            r.return(501, e.message);
+        }
+    }
+
     function chain(r) {
         var results = [];
         var reqs = [
@@ -467,15 +484,16 @@ $t->write_file('test.js', <<EOF);
         r.return(c, `\${v.request_method}:\${bar}:\${body}`);
     }
 
-     export default {njs: test_njs, body, broken, broken_response, broken_catch,
-                     body_special,chain, chunked_ok, chunked_fail, header,
-                     header_iter, host_header, multi, loc, property,
-                     body_content_length, user_agent_header };
+    export default {njs: test_njs, body, body_size, broken, broken_response,
+                    broken_catch, body_special, chain, chunked_ok,
+                    chunked_fail, header, header_iter, host_header,
+                    multi, loc, property, body_content_length,
+                    user_agent_header };
 EOF
 
 $t->try_run('no njs.fetch');
 
-$t->plan(45);
+$t->plan(46);
 
 $t->run_daemon(\&http_daemon, port(8082));
 $t->waitforsocket('127.0.0.1:' . port(8082));
@@ -544,6 +562,9 @@ like(http_get('/broken'), qr/200/s, 'fetch broken');
 like(http_get('/broken_response'), qr/200/s, 'fetch broken response');
 like(http_get('/broken_catch'), qr/\["async","async","async"]$/s,
         'fetch broken catch');
+like(http_get('/body_size'),
+       qr/501.*max_response_body_size must be non-negative/s,
+       'fetch negative response body size');
 like(http_get('/chunked_ok'), qr/200/s, 'fetch chunked ok');
 like(http_get('/chunked_fail'), qr/200/s, 'fetch chunked fail');
 like(http_get('/chain'), qr/200 OK.*SUCCESS$/s, 'fetch chain');