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);
* 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));
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,
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);
}
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));
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);
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));
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;
{
u_char *p, *end;
+ if (len == 0) {
+ return NGX_ERROR;
+ }
+
p = name;
end = p + 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)
{
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,
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);
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);
* 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));
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,
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);
}
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) {
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;
}
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) {
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;
}
return 'OK';
}, 'OK'],
['set', () => {
- 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);
var body = await r.text();
return `\${r.status} \${body}`;
}, '200 /target%0D%0A?q=%00%20'],
+ ['immutable headers', async () => {
+ 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);
var body = await r.text();
return `\${r.url}: \${r.status} \${body}`;
}, 'http://127.0.0.1:$p0/header: 200 X, Z'],
+ ['set request multi header', async () => {
+ 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);