]> git.kaiwu.me - njs.git/commitdiff
Fetch: preserve QuickJS header names
authorDmitry Volyntsev <xeioex@nginx.com>
Thu, 13 Aug 2026 00:22:55 +0000 (17:22 -0700)
committerDmitry Volyntsev <xeioexception@gmail.com>
Thu, 13 Aug 2026 14:00:21 +0000 (07:00 -0700)
Previously, Headers.set() converted the header name with
JS_ToCStringLen().  When a new header was added, the list retained that
pointer after it was released with JS_FreeCString(), leaving the header
with a dangling name.

The fix is to copy the name into the request pool only when set() creates
a new list entry.  Replacing an existing entry requires no additional
allocation, and the temporary QuickJS string can always be released
after the mutation.

nginx/ngx_js_fetch.c
nginx/ngx_js_http.c
nginx/ngx_js_http.h
nginx/ngx_qjs_fetch.c
nginx/t/js_fetch_objects.t

index e2ce03207a58a870f7c8f95f18660030312ef6ca..06dff3856043b1a71e70fefe31896fc6b4cdd6e6 100644 (file)
@@ -1552,7 +1552,7 @@ ngx_js_headers_append(njs_vm_t *vm, ngx_js_headers_t *headers,
 {
     ngx_js_headers_rc_t  rc;
 
-    rc = ngx_js_headers_modify(headers, name, len, value, vlen, 0);
+    rc = ngx_js_headers_modify(NULL, headers, name, len, value, vlen, 0);
 
     return ngx_js_headers_result(vm, rc);
 }
@@ -1990,8 +1990,8 @@ ngx_headers_js_ext_set(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
         return NJS_ERROR;
     }
 
-    rc = ngx_js_headers_modify(headers, name.start, name.length, value.start,
-                               value.length, 1);
+    rc = ngx_js_headers_modify(NULL, headers, name.start, name.length,
+                               value.start, value.length, 1);
     if (ngx_js_headers_result(vm, rc) != NJS_OK) {
         return NJS_ERROR;
     }
index db8b0732fc66f97e5be2fa2b48b7cb472881beee..d2b7323a8f3b3cb03969c600dd5272c4f71b77b4 100644 (file)
@@ -1834,9 +1834,10 @@ ngx_js_headers_error(ngx_js_headers_rc_t rc)
 
 
 ngx_js_headers_rc_t
-ngx_js_headers_modify(ngx_js_headers_t *headers, u_char *name, size_t len,
-    u_char *value, size_t vlen, njs_bool_t replace)
+ngx_js_headers_modify(ngx_pool_t *pool, ngx_js_headers_t *headers,
+    u_char *name, size_t len, u_char *value, size_t vlen, njs_bool_t replace)
 {
+    u_char           *p;
     ngx_uint_t        i;
     ngx_list_part_t  *part;
     ngx_js_tb_elt_t  *first, *h, **ph;
@@ -1906,6 +1907,20 @@ ngx_js_headers_modify(ngx_js_headers_t *headers, u_char *name, size_t len,
         while (*ph) { ph = &(*ph)->next; }
     }
 
+    /*
+     * A pool is needed for QuickJS because the provided name is freed after
+     * this call.
+     */
+    if (pool != NULL) {
+        p = ngx_pnalloc(pool, len);
+        if (p == NULL) {
+            return NGX_JS_HEADERS_NOMEM;
+        }
+
+        ngx_memcpy(p, name, len);
+        name = p;
+    }
+
     h = ngx_list_push(&headers->header_list);
     if (h == NULL) {
         return NGX_JS_HEADERS_NOMEM;
index fc4276d03ff1c8760d8da33607081823ed79d932..c3ebe15ad61662acf695d3fc5f831f326735921e 100644 (file)
@@ -193,8 +193,9 @@ ngx_int_t ngx_js_check_header_name(u_char *name, size_t len);
 ngx_int_t ngx_js_check_request_line_component(u_char *value, size_t len);
 ngx_int_t ngx_js_check_header_value(u_char *value, size_t len);
 const char *ngx_js_headers_error(ngx_js_headers_rc_t rc);
-ngx_js_headers_rc_t ngx_js_headers_modify(ngx_js_headers_t *headers,
-    u_char *name, size_t len, u_char *value, size_t vlen, njs_bool_t replace);
+ngx_js_headers_rc_t ngx_js_headers_modify(ngx_pool_t *pool,
+    ngx_js_headers_t *headers, u_char *name, size_t len, u_char *value,
+    size_t vlen, njs_bool_t replace);
 ngx_js_headers_rc_t ngx_js_headers_remove(ngx_js_headers_t *headers,
     u_char *name, size_t len);
 njs_bool_t ngx_js_headers_has(ngx_js_headers_t *headers, u_char *name,
index 1e6f551b67bbff7d2ec6b663d3f4c82c88a8a10a..2c67f630ba58e164a9078e518808b91824e9539b 100644 (file)
@@ -1384,7 +1384,7 @@ ngx_qjs_headers_append(JSContext *cx, ngx_js_headers_t *headers,
 {
     ngx_js_headers_rc_t  rc;
 
-    rc = ngx_js_headers_modify(headers, name, len, value, vlen, 0);
+    rc = ngx_js_headers_modify(NULL, headers, name, len, value, vlen, 0);
 
     return ngx_qjs_headers_result(cx, rc);
 }
@@ -1914,7 +1914,7 @@ ngx_qjs_ext_fetch_headers_set(JSContext *cx, JSValueConst this_val,
         return JS_EXCEPTION;
     }
 
-    rc = ngx_js_headers_modify(headers, name.data, name.len, value.data,
+    rc = ngx_js_headers_modify(pool, headers, name.data, name.len, value.data,
                                value.len, 1);
     JS_FreeCString(cx, (const char *) name.data);
     if (ngx_qjs_headers_result(cx, rc) != NGX_OK) {
index 1e39d1c4cb8693d03b6abb066d0a550858f6073c..6591a8f5b3301e9e629c1416455b8f914527ee8f 100644 (file)
@@ -298,6 +298,21 @@ $t->write_file('test.js', <<EOF);
                     }
                 }
 
+                return 'OK';
+             }, 'OK'],
+            ['set computed names', () => {
+                var h = new Headers();
+
+                for (var i = 0; i < 2; i++) {
+                    h.set('x-computed-header-name-' + i, 'value-' + i);
+                }
+
+                for (var i = 0; i < 2; i++) {
+                    if (h.get('x-computed-header-name-' + i) != 'value-' + i) {
+                        throw new Error('header name is corrupted');
+                    }
+                }
+
                 return 'OK';
              }, 'OK'],
         ];