]> git.kaiwu.me - njs.git/commitdiff
Fetch: reject invalid header values
authorDmitry Volyntsev <xeioex@nginx.com>
Fri, 29 May 2026 22:26:15 +0000 (15:26 -0700)
committerDmitry Volyntsev <xeioexception@gmail.com>
Mon, 1 Jun 2026 15:44:59 +0000 (08:44 -0700)
Reject control characters and DEL in Fetch Headers values while
preserving OWS trimming and obs-text.

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 3b6db4a278f9e632aa403b3476a6060d273cf894..ab1ea0c5e658bd148ddbf94cde7f2202771cd273 100644 (file)
@@ -1520,13 +1520,12 @@ static njs_int_t
 ngx_js_headers_append(njs_vm_t *vm, ngx_js_headers_t *headers,
     u_char *name, size_t len, u_char *value, size_t vlen)
 {
-    u_char           *p, *end;
     ngx_int_t         ret;
     ngx_uint_t        i;
     ngx_js_tb_elt_t  *h, **ph;
     ngx_list_part_t  *part;
 
-    ngx_js_http_trim(&value, &vlen, 0);
+    ngx_js_http_trim_ows(&value, &vlen);
 
     ret = ngx_js_check_header_name(name, len);
     if (ret != NGX_OK) {
@@ -1534,16 +1533,10 @@ ngx_js_headers_append(njs_vm_t *vm, ngx_js_headers_t *headers,
         return NJS_ERROR;
     }
 
-    p = value;
-    end = p + vlen;
-
-    while (p < end) {
-        if (*p == '\0') {
-            njs_vm_error(vm, "invalid header value");
-            return NJS_ERROR;
-        }
-
-        p++;
+    ret = ngx_js_check_header_value(value, vlen);
+    if (ret != NGX_OK) {
+        njs_vm_error(vm, "invalid header value");
+        return NJS_ERROR;
     }
 
     if (headers->guard == GUARD_IMMUTABLE) {
index 32c59461312c14192d4bd812f58dc3e312f9ff3b..28084a58a599cb4038fcbaf835e54d555504f531 100644 (file)
@@ -1696,6 +1696,32 @@ ngx_js_http_trim(u_char **value, size_t *len, int trim_c0_control_or_space)
 }
 
 
+void
+ngx_js_http_trim_ows(u_char **value, size_t *len)
+{
+    u_char  *start, *end;
+
+    start = *value;
+    end = start + *len;
+
+    while (start < end && (*start == ' ' || *start == '\t')) {
+        start++;
+    }
+
+    while (start < end) {
+        end--;
+
+        if (*end != ' ' && *end != '\t') {
+            end++;
+            break;
+        }
+    }
+
+    *value = start;
+    *len = end - start;
+}
+
+
 static const uint32_t  token_map[] = {
     0x00000000,  /* 0000 0000 0000 0000  0000 0000 0000 0000 */
 
@@ -1742,6 +1768,26 @@ ngx_js_check_header_name(u_char *name, size_t len)
 }
 
 
+ngx_int_t
+ngx_js_check_header_value(u_char *value, size_t len)
+{
+    u_char  *p, *end;
+
+    p = value;
+    end = p + len;
+
+    while (p < end) {
+        if (*p <= 0x08 || (*p >= 0x0a && *p <= 0x1f) || *p == 0x7f) {
+            return NGX_ERROR;
+        }
+
+        p++;
+    }
+
+    return NGX_OK;
+}
+
+
 static ngx_int_t
 ngx_js_http_get_keepalive_connection(ngx_js_http_t *http)
 {
index 93fa1966d3e520b239d7fdbe11c8e42a92fd394a..0e88b68a5cb52a611505e9da8c1392f3ce70556e 100644 (file)
@@ -180,7 +180,9 @@ void ngx_js_http_resolve_done(ngx_js_http_t *http);
 void ngx_js_http_close_peer(ngx_js_http_t *http);
 void ngx_js_http_trim(u_char **value, size_t *len,
     int trim_c0_control_or_space);
+void ngx_js_http_trim_ows(u_char **value, size_t *len);
 ngx_int_t ngx_js_check_header_name(u_char *name, size_t len);
+ngx_int_t ngx_js_check_header_value(u_char *value, size_t len);
 
 ngx_buf_t *ngx_js_chain_to_buf(ngx_pool_t *pool, njs_chb_t *chain);
 
index 0d510adb1c3ab130c40c48e089007e0b915a762c..d63e98cbe0009573b81a13bf91c679d0a181755b 100644 (file)
@@ -1323,13 +1323,12 @@ static ngx_int_t
 ngx_qjs_headers_append(JSContext *cx, ngx_js_headers_t *headers,
     u_char *name, size_t len, u_char *value, size_t vlen)
 {
-    u_char           *p, *end;
     ngx_int_t         ret;
     ngx_uint_t        i;
     ngx_list_part_t  *part;
     ngx_js_tb_elt_t  *h, **ph;
 
-    ngx_js_http_trim(&value, &vlen, 0);
+    ngx_js_http_trim_ows(&value, &vlen);
 
     ret = ngx_js_check_header_name(name, len);
     if (ret != NGX_OK) {
@@ -1337,16 +1336,10 @@ ngx_qjs_headers_append(JSContext *cx, ngx_js_headers_t *headers,
         return NGX_ERROR;
     }
 
-    p = value;
-    end = p + vlen;
-
-    while (p < end) {
-        if (*p == '\0') {
-            JS_ThrowInternalError(cx, "invalid header value");
-            return NGX_ERROR;
-        }
-
-        p++;
+    ret = ngx_js_check_header_value(value, vlen);
+    if (ret != NGX_OK) {
+        JS_ThrowInternalError(cx, "invalid header value");
+        return NGX_ERROR;
     }
 
     if (headers->guard == GUARD_IMMUTABLE) {
index c9d04c4970ae3acac3dd2ba2e93cd1d7dbcd1d28..0f2fec08cb0fe06cda4b6841a2611af12552a3af 100644 (file)
@@ -156,8 +156,42 @@ $t->write_file('test.js', <<EOF);
 
              }, 'OK'],
             ['invalid header value', () => {
-                var h = new Headers({A: 'aa\x00a'});
-             }, 'invalid header value'],
+                const invalid = [0, 1, 8, 10, 13, 31, 127];
+
+                for (var i = 0; i < invalid.length; i++) {
+                    var c = String.fromCharCode(invalid[i]);
+                    var values = [
+                        c, c + 'aa', 'aa' + c, 'aa' + c + 'a'
+                    ];
+
+                    for (var j = 0; j < values.length; j++) {
+                        try {
+                            new Headers({A: values[j]});
+                            throw new Error('no error');
+
+                        } catch (e) {
+                            if (e.message != 'invalid header value') {
+                                throw e;
+                            }
+                        }
+                    }
+                }
+
+                return 'OK';
+
+             }, 'OK'],
+            ['valid header value', () => {
+                var obs = String.fromCharCode(0x80, 0xff);
+                var h = new Headers({A: '\t a\tb \t'});
+
+                h.append('A', obs);
+
+                if (h.get('a') != 'a\tb, ' + obs) {
+                    throw new Error('invalid header value normalization');
+                }
+
+                return 'OK';
+             }, 'OK'],
             ['combine', () => {
                 var h = new Headers({a: 'X', A: 'Z'});
                 return h.get('a');