From: Dmitry Volyntsev Date: Thu, 13 Aug 2026 00:22:31 +0000 (-0700) Subject: Fetch: centralize Headers mutations X-Git-Tag: 1.0.1~18 X-Git-Url: https://git.kaiwu.me/%7B@url%7D?a=commitdiff_plain;h=138c184ef47b6b9405f1fb60fa28a87b5b701830;p=njs.git Fetch: centralize Headers mutations Previously, Headers.set() validated its arguments only when the header did not already exist. Existing values were overwritten without trimming or validating the new value and without checking the immutable guard. It also detached duplicate values only from the first header's chain, while leaving the corresponding list entries active. The stale values could reappear when the Headers object was copied or serialized. The fix is to move append(), set(), and delete() validation and list updates into engine-independent helpers, deactivate every duplicate on replacement, reject empty names, and enforce immutable guards consistently. Content-Type presence is now derived from the header list instead of a separate cached pointer. Reported by vladimirelitokarev@gmail.com. --- diff --git a/nginx/ngx_js_fetch.c b/nginx/ngx_js_fetch.c index 554f14f4..e2ce0320 100644 --- a/nginx/ngx_js_fetch.c +++ b/nginx/ngx_js_fetch.c @@ -55,6 +55,8 @@ static ngx_int_t ngx_js_fetch_append_headers(ngx_js_http_t *http, ngx_js_headers_t *headers, u_char *name, size_t len, u_char *value, size_t vlen); static void ngx_js_fetch_process_done(ngx_js_http_t *http); +static njs_int_t ngx_js_headers_result(njs_vm_t *vm, + ngx_js_headers_rc_t rc); 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); @@ -1309,7 +1311,6 @@ ngx_js_request_constructor(njs_vm_t *vm, ngx_js_request_t *request, * request->cache_mode = CACHE_MODE_DEFAULT; * request->credentials = CREDENTIALS_SAME_ORIGIN; * request->mode = MODE_NO_CORS; - * request->headers.content_type = NULL; */ ngx_memzero(request, sizeof(ngx_js_request_t)); @@ -1473,7 +1474,9 @@ ngx_js_request_constructor(njs_vm_t *vm, ngx_js_request_t *request, return NJS_ERROR; } - if (request->headers.content_type == NULL + if (!ngx_js_headers_has(&request->headers, + (u_char *) "Content-Type", + njs_length("Content-Type")) && njs_value_is_string(value)) { ret = ngx_js_headers_append(vm, &request->headers, @@ -1526,86 +1529,32 @@ ngx_js_fetch_process_done(ngx_js_http_t *http) 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) +ngx_js_headers_result(njs_vm_t *vm, ngx_js_headers_rc_t rc) { - ngx_int_t ret; - ngx_uint_t i; - ngx_js_tb_elt_t *h, **ph; - ngx_list_part_t *part; - - ngx_js_http_trim_ows(&value, &vlen); - - ret = ngx_js_check_header_name(name, len); - if (ret != NGX_OK) { - njs_vm_type_error(vm, "invalid header name"); - return NJS_ERROR; - } - - ret = ngx_js_check_header_value(value, vlen); - if (ret != NGX_OK) { - njs_vm_type_error(vm, "invalid header value"); - return NJS_ERROR; - } - - if (headers->guard == GUARD_IMMUTABLE) { - njs_vm_type_error(vm, "cannot append to immutable object"); - return NJS_ERROR; + if (rc == NGX_JS_HEADERS_OK) { + return NJS_OK; } - ph = NULL; - part = &headers->header_list.part; - h = part->elts; - - for (i = 0; /* void */; i++) { - - if (i >= part->nelts) { - if (part->next == NULL) { - break; - } - - part = part->next; - h = part->elts; - i = 0; - } - - if (h[i].hash == 0) { - continue; - } + if (rc == NGX_JS_HEADERS_NOMEM) { + njs_vm_memory_error(vm); - if (len == h[i].key.len - && (njs_strncasecmp(name, h[i].key.data, len) == 0)) - { - ph = &h[i].next; - while (*ph) { ph = &(*ph)->next; } - break; - } + } else { + njs_vm_type_error(vm, "%s", ngx_js_headers_error(rc)); } - h = ngx_list_push(&headers->header_list); - if (h == NULL) { - njs_vm_memory_error(vm); - return NJS_ERROR; - } + return NJS_ERROR; +} - if (ph != NULL) { - *ph = h; - } - h->hash = 1; - h->key.data = name; - h->key.len = len; - h->value.data = value; - h->value.len = vlen; - h->next = NULL; +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) +{ + ngx_js_headers_rc_t rc; - if (len == njs_strlen("Content-Type") - && ngx_strncasecmp(name, (u_char *) "Content-Type", len) == 0) - { - headers->content_type = h; - } + rc = ngx_js_headers_modify(headers, name, len, value, vlen, 0); - return NJS_OK; + return ngx_js_headers_result(vm, rc); } @@ -1749,12 +1698,10 @@ static njs_int_t ngx_headers_js_ext_delete(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) { - njs_int_t ret; - njs_str_t name; - ngx_uint_t i; - ngx_list_part_t *part; - ngx_js_tb_elt_t *h; - ngx_js_headers_t *headers; + njs_int_t ret; + njs_str_t name; + ngx_js_headers_t *headers; + ngx_js_headers_rc_t rc; headers = njs_vm_external(vm, ngx_http_js_fetch_headers_proto_id, njs_argument(args, 0)); @@ -1768,37 +1715,9 @@ ngx_headers_js_ext_delete(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, return NJS_ERROR; } - part = &headers->header_list.part; - h = part->elts; - - for (i = 0; /* void */; i++) { - - if (i >= part->nelts) { - if (part->next == NULL) { - break; - } - - part = part->next; - h = part->elts; - i = 0; - } - - if (h[i].hash == 0) { - continue; - } - - if (name.length == h[i].key.len - && (njs_strncasecmp(name.start, h[i].key.data, name.length) == 0)) - { - h[i].hash = 0; - } - } - - if (name.length == njs_strlen("Content-Type") - && ngx_strncasecmp(name.start, (u_char *) "Content-Type", name.length) - == 0) - { - headers->content_type = NULL; + rc = ngx_js_headers_remove(headers, name.start, name.length); + if (ngx_js_headers_result(vm, rc) != NJS_OK) { + return NJS_ERROR; } njs_value_undefined_set(retval); @@ -2049,12 +1968,10 @@ static njs_int_t ngx_headers_js_ext_set(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) { - njs_int_t ret; - njs_str_t name, value; - ngx_uint_t i; - ngx_list_part_t *part; - ngx_js_tb_elt_t *h, **ph, **pp; - ngx_js_headers_t *headers; + njs_int_t ret; + njs_str_t name, value; + ngx_js_headers_t *headers; + ngx_js_headers_rc_t rc; headers = njs_vm_external(vm, ngx_http_js_fetch_headers_proto_id, njs_argument(args, 0)); @@ -2073,51 +1990,12 @@ ngx_headers_js_ext_set(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, return NJS_ERROR; } - part = &headers->header_list.part; - h = part->elts; - - for (i = 0; /* void */; i++) { - - if (i >= part->nelts) { - if (part->next == NULL) { - break; - } - - part = part->next; - h = part->elts; - i = 0; - } - - if (h[i].hash == 0) { - continue; - } - - if (name.length == h[i].key.len - && (njs_strncasecmp(name.start, h[i].key.data, name.length) == 0)) - { - h[i].value.len = value.length; - h[i].value.data = value.start; - - ph = &h[i].next; - - while (*ph) { - pp = ph; - ph = &(*ph)->next; - *pp = NULL; - } - - goto done; - } - } - - ret = ngx_js_headers_append(vm, headers, name.start, name.length, - value.start, value.length); - if (ret != NJS_OK) { + rc = ngx_js_headers_modify(headers, name.start, name.length, value.start, + value.length, 1); + if (ngx_js_headers_result(vm, rc) != NJS_OK) { return NJS_ERROR; } -done: - njs_value_undefined_set(retval); return NJS_OK; diff --git a/nginx/ngx_js_http.c b/nginx/ngx_js_http.c index e5d367e9..db8b0732 100644 --- a/nginx/ngx_js_http.c +++ b/nginx/ngx_js_http.c @@ -1755,6 +1755,10 @@ ngx_js_check_header_name(u_char *name, size_t len) { u_char *p, *end; + if (len == 0) { + return NGX_ERROR; + } + p = name; end = p + len; @@ -1810,6 +1814,193 @@ ngx_js_check_header_value(u_char *value, size_t len) } +const char * +ngx_js_headers_error(ngx_js_headers_rc_t rc) +{ + switch (rc) { + case NGX_JS_HEADERS_INVALID_NAME: + return "invalid header name"; + + case NGX_JS_HEADERS_INVALID_VALUE: + return "invalid header value"; + + case NGX_JS_HEADERS_IMMUTABLE: + return "cannot modify immutable object"; + + default: + return "unknown headers error"; + } +} + + +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_uint_t i; + ngx_list_part_t *part; + ngx_js_tb_elt_t *first, *h, **ph; + + ngx_js_http_trim_ows(&value, &vlen); + + if (ngx_js_check_header_name(name, len) != NGX_OK) { + return NGX_JS_HEADERS_INVALID_NAME; + } + + if (ngx_js_check_header_value(value, vlen) != NGX_OK) { + return NGX_JS_HEADERS_INVALID_VALUE; + } + + if (headers->guard == GUARD_IMMUTABLE) { + return NGX_JS_HEADERS_IMMUTABLE; + } + + first = NULL; + part = &headers->header_list.part; + h = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + h = part->elts; + i = 0; + } + + if (h[i].hash == 0 || len != h[i].key.len + || ngx_strncasecmp(name, h[i].key.data, len) != 0) + { + continue; + } + + if (first == NULL) { + first = &h[i]; + + if (replace) { + first->value.data = value; + first->value.len = vlen; + } + + continue; + } + + if (replace) { + h[i].hash = 0; + h[i].next = NULL; + } + } + + if (replace && first != NULL) { + first->next = NULL; + return NGX_JS_HEADERS_OK; + } + + ph = NULL; + + if (first != NULL) { + ph = &first->next; + while (*ph) { ph = &(*ph)->next; } + } + + h = ngx_list_push(&headers->header_list); + if (h == NULL) { + return NGX_JS_HEADERS_NOMEM; + } + + if (ph != NULL) { + *ph = h; + } + + h->hash = 1; + h->key.data = name; + h->key.len = len; + h->value.data = value; + h->value.len = vlen; + h->next = NULL; + + return NGX_JS_HEADERS_OK; +} + + +ngx_js_headers_rc_t +ngx_js_headers_remove(ngx_js_headers_t *headers, u_char *name, size_t len) +{ + ngx_uint_t i; + ngx_list_part_t *part; + ngx_js_tb_elt_t *h; + + if (ngx_js_check_header_name(name, len) != NGX_OK) { + return NGX_JS_HEADERS_INVALID_NAME; + } + + if (headers->guard == GUARD_IMMUTABLE) { + return NGX_JS_HEADERS_IMMUTABLE; + } + + part = &headers->header_list.part; + h = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + h = part->elts; + i = 0; + } + + if (h[i].hash != 0 && len == h[i].key.len + && ngx_strncasecmp(name, h[i].key.data, len) == 0) + { + h[i].hash = 0; + h[i].next = NULL; + } + } + + return NGX_JS_HEADERS_OK; +} + + +njs_bool_t +ngx_js_headers_has(ngx_js_headers_t *headers, u_char *name, size_t len) +{ + ngx_uint_t i; + ngx_list_part_t *part; + ngx_js_tb_elt_t *h; + + part = &headers->header_list.part; + h = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + h = part->elts; + i = 0; + } + + if (h[i].hash != 0 && len == h[i].key.len + && ngx_strncasecmp(name, h[i].key.data, len) == 0) + { + return 1; + } + } + + return 0; +} + + static ngx_int_t ngx_js_http_get_keepalive_connection(ngx_js_http_t *http) { diff --git a/nginx/ngx_js_http.h b/nginx/ngx_js_http.h index b9e4666c..fc4276d0 100644 --- a/nginx/ngx_js_http.h +++ b/nginx/ngx_js_http.h @@ -63,10 +63,18 @@ typedef struct { GUARD_RESPONSE, } guard; ngx_list_t header_list; - ngx_js_tb_elt_t *content_type; } ngx_js_headers_t; +typedef enum { + NGX_JS_HEADERS_OK = 0, + NGX_JS_HEADERS_INVALID_NAME, + NGX_JS_HEADERS_INVALID_VALUE, + NGX_JS_HEADERS_IMMUTABLE, + NGX_JS_HEADERS_NOMEM, +} ngx_js_headers_rc_t; + + typedef struct { enum { CACHE_MODE_DEFAULT = 0, @@ -184,6 +192,13 @@ 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_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_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, + size_t len); ngx_buf_t *ngx_js_chain_to_buf(ngx_pool_t *pool, njs_chb_t *chain); diff --git a/nginx/ngx_qjs_fetch.c b/nginx/ngx_qjs_fetch.c index 693fb48d..1e6f551b 100644 --- a/nginx/ngx_qjs_fetch.c +++ b/nginx/ngx_qjs_fetch.c @@ -48,6 +48,8 @@ static ngx_int_t ngx_qjs_fetch_append_headers(ngx_js_http_t *http, ngx_js_headers_t *headers, u_char *name, size_t len, u_char *value, size_t vlen); static void ngx_qjs_fetch_process_done(ngx_js_http_t *http); +static ngx_int_t ngx_qjs_headers_result(JSContext *cx, + ngx_js_headers_rc_t rc); 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); @@ -538,7 +540,6 @@ ngx_qjs_request_ctor(JSContext *cx, ngx_js_request_t *request, * request->cache_mode = CACHE_MODE_DEFAULT; * request->credentials = CREDENTIALS_SAME_ORIGIN; * request->mode = MODE_NO_CORS; - * request->headers.content_type = NULL; */ ngx_memzero(request, sizeof(ngx_js_request_t)); @@ -725,7 +726,11 @@ ngx_qjs_request_ctor(JSContext *cx, ngx_js_request_t *request, return NGX_ERROR; } - if (request->headers.content_type == NULL && JS_IsString(value)) { + if (!ngx_js_headers_has(&request->headers, + (u_char *) "Content-Type", + sizeof("Content-Type") - 1) + && JS_IsString(value)) + { rc = ngx_qjs_headers_append(cx, &request->headers, (u_char *) "Content-Type", sizeof("Content-Type") - 1, @@ -1356,86 +1361,32 @@ ngx_qjs_fetch_process_done(ngx_js_http_t *http) 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) +ngx_qjs_headers_result(JSContext *cx, ngx_js_headers_rc_t rc) { - ngx_int_t ret; - ngx_uint_t i; - ngx_list_part_t *part; - ngx_js_tb_elt_t *h, **ph; - - ngx_js_http_trim_ows(&value, &vlen); - - ret = ngx_js_check_header_name(name, len); - if (ret != NGX_OK) { - JS_ThrowTypeError(cx, "invalid header name"); - return NGX_ERROR; - } - - ret = ngx_js_check_header_value(value, vlen); - if (ret != NGX_OK) { - JS_ThrowTypeError(cx, "invalid header value"); - return NGX_ERROR; - } - - if (headers->guard == GUARD_IMMUTABLE) { - JS_ThrowTypeError(cx, "cannot append to immutable object"); - return NGX_ERROR; + if (rc == NGX_JS_HEADERS_OK) { + return NGX_OK; } - ph = NULL; - part = &headers->header_list.part; - h = part->elts; - - for (i = 0; /* void */; i++) { - - if (i >= part->nelts) { - if (part->next == NULL) { - break; - } - - part = part->next; - h = part->elts; - i = 0; - } - - if (h[i].hash == 0) { - continue; - } + if (rc == NGX_JS_HEADERS_NOMEM) { + JS_ThrowOutOfMemory(cx); - if (len == h[i].key.len - && (ngx_strncasecmp(name, h[i].key.data, len) == 0)) - { - ph = &h[i].next; - while (*ph) { ph = &(*ph)->next; } - break; - } + } else { + JS_ThrowTypeError(cx, "%s", ngx_js_headers_error(rc)); } - h = ngx_list_push(&headers->header_list); - if (h == NULL) { - JS_ThrowOutOfMemory(cx); - return NGX_ERROR; - } + return NGX_ERROR; +} - if (ph != NULL) { - *ph = h; - } - h->hash = 1; - h->key.data = name; - h->key.len = len; - h->value.data = value; - h->value.len = vlen; - h->next = NULL; +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) +{ + ngx_js_headers_rc_t rc; - if (len == (sizeof("Content-Type") - 1) - && ngx_strncasecmp(name, (u_char *) "Content-Type", len) == 0) - { - headers->content_type = h; - } + rc = ngx_js_headers_modify(headers, name, len, value, vlen, 0); - return NGX_OK; + return ngx_qjs_headers_result(cx, rc); } @@ -1785,11 +1736,9 @@ static JSValue ngx_qjs_ext_fetch_headers_delete(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv) { - ngx_str_t name; - ngx_uint_t i; - ngx_list_part_t *part; - ngx_js_tb_elt_t *h; - ngx_js_headers_t *headers; + ngx_str_t name; + ngx_js_headers_t *headers; + ngx_js_headers_rc_t rc; headers = JS_GetOpaque(this_val, NGX_QJS_CLASS_ID_FETCH_HEADERS); if (headers == NULL) { @@ -1802,40 +1751,11 @@ ngx_qjs_ext_fetch_headers_delete(JSContext *cx, JSValueConst this_val, return JS_EXCEPTION; } - part = &headers->header_list.part; - h = part->elts; - - for (i = 0; /* void */; i++) { - - if (i >= part->nelts) { - if (part->next == NULL) { - break; - } - - part = part->next; - h = part->elts; - i = 0; - } - - if (h[i].hash == 0) { - continue; - } - - if (name.len == h[i].key.len - && (ngx_strncasecmp(name.data, h[i].key.data, name.len) == 0)) - { - h[i].hash = 0; - } - } - - if (name.len == (sizeof("Content-Type") - 1) - && ngx_strncasecmp(name.data, (u_char *) "Content-Type", name.len) - == 0) - { - headers->content_type = NULL; - } - + rc = ngx_js_headers_remove(headers, name.data, name.len); JS_FreeCString(cx, (const char *) name.data); + if (ngx_qjs_headers_result(cx, rc) != NGX_OK) { + return JS_EXCEPTION; + } return JS_UNDEFINED; } @@ -1964,13 +1884,11 @@ static JSValue ngx_qjs_ext_fetch_headers_set(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv) { - ngx_int_t rc; - ngx_str_t name, value; - ngx_uint_t i; - ngx_pool_t *pool; - ngx_list_part_t *part; - ngx_js_tb_elt_t *h, **ph, **pp; - ngx_js_headers_t *headers; + ngx_int_t ret; + ngx_str_t name, value; + ngx_pool_t *pool; + ngx_js_headers_t *headers; + ngx_js_headers_rc_t rc; headers = JS_GetOpaque(this_val, NGX_QJS_CLASS_ID_FETCH_HEADERS); if (headers == NULL) { @@ -1985,55 +1903,21 @@ ngx_qjs_ext_fetch_headers_set(JSContext *cx, JSValueConst this_val, pool = ngx_qjs_external_pool(cx, JS_GetContextOpaque(cx)); - rc = ngx_qjs_string(cx, pool, argv[1], &value); - if (rc != NGX_OK) { + ret = ngx_qjs_string(cx, pool, argv[1], &value); + if (ret != NGX_OK) { JS_FreeCString(cx, (const char *) name.data); - return JS_EXCEPTION; - } - - part = &headers->header_list.part; - h = part->elts; - - for (i = 0; /* void */; i++) { - - if (i >= part->nelts) { - if (part->next == NULL) { - break; - } - part = part->next; - h = part->elts; - i = 0; - } - - if (h[i].hash == 0) { - continue; + if (!JS_HasException(cx)) { + JS_ThrowOutOfMemory(cx); } - if (name.len == h[i].key.len - && (ngx_strncasecmp(name.data, h[i].key.data, name.len) == 0)) - { - h[i].value.len = value.len; - h[i].value.data = value.data; - - ph = &h[i].next; - - while (*ph) { - pp = ph; - ph = &(*ph)->next; - *pp = NULL; - } - - JS_FreeCString(cx, (const char *) name.data); - - return JS_UNDEFINED; - } + return JS_EXCEPTION; } - rc = ngx_qjs_headers_append(cx, headers, name.data, name.len, - value.data, value.len); + rc = ngx_js_headers_modify(headers, name.data, name.len, value.data, + value.len, 1); JS_FreeCString(cx, (const char *) name.data); - if (rc != NGX_OK) { + if (ngx_qjs_headers_result(cx, rc) != NGX_OK) { return JS_EXCEPTION; } diff --git a/nginx/t/js_fetch_objects.t b/nginx/t/js_fetch_objects.t index 8a6806ac..1e39d1c4 100644 --- a/nginx/t/js_fetch_objects.t +++ b/nginx/t/js_fetch_objects.t @@ -255,15 +255,51 @@ $t->write_file('test.js', < { - var h = new Headers([['A', 'x'], ['a', 'y'], ['a', 'z']]); - h.set('a', '#'); - return h.get('a'); - }, '#'], - ['set on empty', () => { + var h = new Headers(); + + for (var i = 0; i < 10; i++) { + h.append(i == 0 ? 'A' : 'a', String(i)); + } + + h.set('a', ' \t#\t '); + var h2 = new Headers(h); + var key; + h.forEach((k) => key = k); + return `\${key} \${JSON.stringify(h.getAll('a'))} ` + + JSON.stringify(h2.getAll('a')); + }, 'A ["#"] ["#"]'], + ['set invalid value', () => { + var h = new Headers({a: 'x'}); + h.set('a', 'a\\r\\nb'); + }, 'invalid header value'], + ['set on empty', () => { var h = new Headers([]); h.set('x-test', '1234'); return h.get('x-test'); }, '1234'], + ['invalid empty name', () => { + var h = new Headers({a: 'x'}); + var tests = [ + () => new Headers({'': 'x'}), + () => h.append('', 'x'), + () => h.delete(''), + () => h.set('', 'x'), + ]; + + for (var i = 0; i < tests.length; i++) { + try { + tests[i](); + throw new Error('no error'); + + } catch (e) { + if (e.message != 'invalid header name') { + throw e; + } + } + } + + return 'OK'; + }, 'OK'], ]; run(r, tests); @@ -629,6 +665,35 @@ $t->write_file('test.js', < { + var r = await ngx.fetch('http://127.0.0.1:$p0/method'); + var errors = []; + + try { + r.headers.append('X-Test', 'test'); + + } catch (e) { + errors.push(e.message); + } + + try { + r.headers.set('Content-Type', 'text/html'); + + } catch (e) { + errors.push(e.message); + } + + try { + r.headers.delete('Content-Type'); + + } catch (e) { + errors.push(e.message); + } + + return errors.join('|'); + }, 'cannot modify immutable object|' + + 'cannot modify immutable object|' + + 'cannot modify immutable object'], ]; run(r, tests); @@ -645,6 +710,14 @@ $t->write_file('test.js', < { + var h = new Headers([['a', 'X'], ['a', 'Y'], ['a', 'Z']]); + h.set('a', '#'); + var r = await ngx.fetch('http://127.0.0.1:$p0/header', + {headers: h}); + var body = await r.text(); + return `\${r.url}: \${r.status} \${body}`; + }, 'http://127.0.0.1:$p0/header: 200 #'], ]; run(r, tests);