details: https://hg.nginx.org/njs/rev/c43261bad627 branches: changeset: 2015:c43261bad627 user: Dmitry Volyntsev <xei...@nginx.com> date: Mon Dec 12 22:00:23 2022 -0800 description: Modules: added Request, Response and Headers ctors in Fetch API.
Added Headers method and properties: append(), delete(), get(), forEach(), has(), set(). Added Request method and properties: arrayBuffer(), bodyUsed, cache, credentials, json(), method, mode, text(), url. Added Headers, Request, Response constructors. This closes #425 issue on Github. diffstat: nginx/ngx_js_fetch.c | 2258 ++++++++++++++++++++++++++++++++++++++++++------- 1 files changed, 1936 insertions(+), 322 deletions(-) diffs (truncated from 2786 to 1000 lines): diff -r f23c541c02ad -r c43261bad627 nginx/ngx_js_fetch.c --- a/nginx/ngx_js_fetch.c Mon Dec 12 21:55:47 2022 -0800 +++ b/nginx/ngx_js_fetch.c Mon Dec 12 22:00:23 2022 -0800 @@ -18,6 +18,12 @@ typedef struct ngx_js_http_s ngx_js_htt typedef struct { + njs_str_t name; + njs_int_t value; +} ngx_js_entry_t; + + +typedef struct { ngx_uint_t state; ngx_uint_t code; u_char *status_text; @@ -41,6 +47,59 @@ typedef struct { } ngx_js_http_chunk_parse_t; +typedef struct { + enum { + GUARD_NONE = 0, + GUARD_REQUEST, + GUARD_IMMUTABLE, + GUARD_RESPONSE, + } guard; + ngx_list_t header_list; +} ngx_js_headers_t; + + +typedef struct { + enum { + CACHE_MODE_DEFAULT = 0, + CACHE_MODE_NO_STORE, + CACHE_MODE_RELOAD, + CACHE_MODE_NO_CACHE, + CACHE_MODE_FORCE_CACHE, + CACHE_MODE_ONLY_IF_CACHED, + } cache_mode; + enum { + CREDENTIALS_SAME_ORIGIN = 0, + CREDENTIALS_INCLUDE, + CREDENTIALS_OMIT, + } credentials; + enum { + MODE_NO_CORS = 0, + MODE_SAME_ORIGIN, + MODE_CORS, + MODE_NAVIGATE, + MODE_WEBSOCKET, + } mode; + njs_str_t url; + njs_str_t method; + u_char m[8]; + uint8_t body_used; + njs_str_t body; + ngx_js_headers_t headers; + njs_opaque_value_t header_value; +} ngx_js_request_t; + + +typedef struct { + njs_str_t url; + ngx_int_t code; + njs_str_t status_text; + uint8_t body_used; + njs_chb_t chain; + ngx_js_headers_t headers; + njs_opaque_value_t header_value; +} ngx_js_response_t; + + struct ngx_js_http_s { ngx_log_t *log; ngx_pool_t *pool; @@ -63,9 +122,6 @@ struct ngx_js_http_s { ngx_int_t buffer_size; ngx_int_t max_response_body_size; - njs_str_t url; - ngx_array_t headers; - unsigned header_only; #if (NGX_SSL) @@ -78,26 +134,35 @@ struct ngx_js_http_s { ngx_buf_t *chunk; njs_chb_t chain; - njs_opaque_value_t reply; + ngx_js_response_t response; + njs_opaque_value_t response_value; + njs_opaque_value_t promise; njs_opaque_value_t promise_callbacks[2]; uint8_t done; - uint8_t body_used; ngx_js_http_parse_t http_parse; ngx_js_http_chunk_parse_t http_chunk_parse; ngx_int_t (*process)(ngx_js_http_t *http); }; + + #define ngx_js_http_error(http, err, fmt, ...) \ do { \ - njs_vm_value_error_set((http)->vm, njs_value_arg(&(http)->reply), \ + njs_vm_value_error_set((http)->vm, \ + njs_value_arg(&(http)->response_value), \ fmt, ##__VA_ARGS__); \ - ngx_js_http_fetch_done(http, &(http)->reply, NJS_ERROR); \ + ngx_js_http_fetch_done(http, &(http)->response_value, NJS_ERROR); \ } while (0) +static njs_int_t ngx_js_method_process(njs_vm_t *vm, ngx_js_request_t *r); +static njs_int_t ngx_js_headers_inherit(njs_vm_t *vm, ngx_js_headers_t *headers, + ngx_js_headers_t *orig); +static njs_int_t ngx_js_headers_fill(njs_vm_t *vm, ngx_js_headers_t *headers, + njs_value_t *init); static ngx_js_http_t *ngx_js_http_alloc(njs_vm_t *vm, ngx_pool_t *pool, ngx_log_t *log); static void njs_js_http_destructor(njs_external_ptr_t external, @@ -113,6 +178,14 @@ static void ngx_js_http_connect(ngx_js_h static void ngx_js_http_next(ngx_js_http_t *http); static void ngx_js_http_write_handler(ngx_event_t *wev); static void ngx_js_http_read_handler(ngx_event_t *rev); + +static njs_int_t ngx_js_request_constructor(njs_vm_t *vm, + ngx_js_request_t *request, ngx_url_t *u, njs_external_ptr_t external, + njs_value_t *args, njs_uint_t nargs); + +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); + static ngx_int_t ngx_js_http_process_status_line(ngx_js_http_t *http); static ngx_int_t ngx_js_http_process_headers(ngx_js_http_t *http); static ngx_int_t ngx_js_http_process_body(ngx_js_http_t *http); @@ -124,15 +197,39 @@ static ngx_int_t ngx_js_http_parse_chunk ngx_buf_t *b, njs_chb_t *chain); static void ngx_js_http_dummy_handler(ngx_event_t *ev); -static njs_int_t ngx_response_js_ext_headers_get(njs_vm_t *vm, - njs_value_t *args, njs_uint_t nargs, njs_index_t as_array); -static njs_int_t ngx_response_js_ext_headers_has(njs_vm_t *vm, - njs_value_t *args, njs_uint_t nargs, njs_index_t unused); -static njs_int_t ngx_response_js_ext_header(njs_vm_t *vm, +static njs_int_t ngx_headers_js_ext_append(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused); +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); +static njs_int_t ngx_headers_js_ext_for_each(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t as_array); +static njs_int_t ngx_headers_js_ext_get(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t as_array); +static njs_int_t ngx_headers_js_ext_has(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused); +static njs_int_t ngx_headers_js_ext_prop(njs_vm_t *vm, njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval, njs_value_t *retval); -static njs_int_t ngx_response_js_ext_keys(njs_vm_t *vm, njs_value_t *value, +static njs_int_t ngx_headers_js_ext_keys(njs_vm_t *vm, njs_value_t *value, njs_value_t *keys); +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); +static njs_int_t ngx_request_js_ext_body(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused); +static njs_int_t ngx_request_js_ext_body_used(njs_vm_t *vm, + njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval, + njs_value_t *retval); +static njs_int_t ngx_request_js_ext_cache(njs_vm_t *vm, + njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval, + njs_value_t *retval); +static njs_int_t ngx_request_js_ext_credentials(njs_vm_t *vm, + njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval, + njs_value_t *retval); +static njs_int_t ngx_request_js_ext_headers(njs_vm_t *vm, + njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval, + njs_value_t *retval); +static njs_int_t ngx_request_js_ext_mode(njs_vm_t *vm, njs_object_prop_t *prop, + njs_value_t *value, njs_value_t *setval, njs_value_t *retval); static njs_int_t ngx_response_js_ext_status(njs_vm_t *vm, njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval, njs_value_t *retval); @@ -145,6 +242,9 @@ static njs_int_t ngx_response_js_ext_ok( static njs_int_t ngx_response_js_ext_body_used(njs_vm_t *vm, njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval, njs_value_t *retval); +static njs_int_t ngx_response_js_ext_headers(njs_vm_t *vm, + njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval, + njs_value_t *retval); static njs_int_t ngx_response_js_ext_type(njs_vm_t *vm, njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval, njs_value_t *retval); @@ -158,7 +258,44 @@ static void ngx_js_http_ssl_handshake(ng static njs_int_t ngx_js_http_ssl_name(ngx_js_http_t *http); #endif -static njs_external_t ngx_js_ext_http_response_headers[] = { +static void ngx_js_http_trim(u_char **value, size_t *len, + njs_bool_t trim_c0_control_or_space); +static njs_int_t ngx_fetch_flag(njs_vm_t *vm, const ngx_js_entry_t *entries, + njs_int_t value, njs_value_t *retval); +static njs_int_t ngx_fetch_flag_set(njs_vm_t *vm, const ngx_js_entry_t *entries, + njs_value_t *value, const char *type); + + +static const ngx_js_entry_t ngx_js_fetch_credentials[] = { + { njs_str("same-origin"), CREDENTIALS_SAME_ORIGIN }, + { njs_str("omit"), CREDENTIALS_OMIT }, + { njs_str("include"), CREDENTIALS_INCLUDE }, + { njs_null_str, 0 }, +}; + + +static const ngx_js_entry_t ngx_js_fetch_cache_modes[] = { + { njs_str("default"), CACHE_MODE_DEFAULT }, + { njs_str("no-store"), CACHE_MODE_NO_STORE }, + { njs_str("reload"), CACHE_MODE_RELOAD }, + { njs_str("no-cache"), CACHE_MODE_NO_CACHE }, + { njs_str("force-cache"), CACHE_MODE_FORCE_CACHE }, + { njs_str("only-if-cached"), CACHE_MODE_ONLY_IF_CACHED }, + { njs_null_str, 0 }, +}; + + +static const ngx_js_entry_t ngx_js_fetch_modes[] = { + { njs_str("no-cors"), MODE_NO_CORS }, + { njs_str("cors"), MODE_CORS }, + { njs_str("same-origin"), MODE_SAME_ORIGIN }, + { njs_str("navigate"), MODE_NAVIGATE }, + { njs_str("websocket"), MODE_WEBSOCKET }, + { njs_null_str, 0 }, +}; + + +static njs_external_t ngx_js_ext_http_headers[] = { { .flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL, @@ -169,13 +306,55 @@ static njs_external_t ngx_js_ext_http_r }, { + .flags = NJS_EXTERN_SELF, + .u.object = { + .enumerable = 1, + .prop_handler = ngx_headers_js_ext_prop, + .keys = ngx_headers_js_ext_keys, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("append"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = ngx_headers_js_ext_append, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("delete"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = ngx_headers_js_ext_delete, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("forEach"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = ngx_headers_js_ext_for_each, + } + }, + + { .flags = NJS_EXTERN_METHOD, .name.string = njs_str("get"), .writable = 1, .configurable = 1, .enumerable = 1, .u.method = { - .native = ngx_response_js_ext_headers_get, + .native = ngx_headers_js_ext_get, } }, @@ -186,7 +365,7 @@ static njs_external_t ngx_js_ext_http_r .configurable = 1, .enumerable = 1, .u.method = { - .native = ngx_response_js_ext_headers_get, + .native = ngx_headers_js_ext_get, .magic8 = 1 } }, @@ -198,7 +377,135 @@ static njs_external_t ngx_js_ext_http_r .configurable = 1, .enumerable = 1, .u.method = { - .native = ngx_response_js_ext_headers_has, + .native = ngx_headers_js_ext_has, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("set"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = ngx_headers_js_ext_set, + } + }, + +}; + + +static njs_external_t ngx_js_ext_http_request[] = { + + { + .flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL, + .name.symbol = NJS_SYMBOL_TO_STRING_TAG, + .u.property = { + .value = "Request", + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("arrayBuffer"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = ngx_request_js_ext_body, +#define NGX_JS_BODY_ARRAY_BUFFER 0 +#define NGX_JS_BODY_JSON 1 +#define NGX_JS_BODY_TEXT 2 + .magic8 = NGX_JS_BODY_ARRAY_BUFFER + } + }, + + { + .flags = NJS_EXTERN_PROPERTY, + .name.string = njs_str("bodyUsed"), + .enumerable = 1, + .u.property = { + .handler = ngx_request_js_ext_body_used, + } + }, + + { + .flags = NJS_EXTERN_PROPERTY, + .name.string = njs_str("cache"), + .enumerable = 1, + .u.property = { + .handler = ngx_request_js_ext_cache, + } + }, + + { + .flags = NJS_EXTERN_PROPERTY, + .name.string = njs_str("credentials"), + .enumerable = 1, + .u.property = { + .handler = ngx_request_js_ext_credentials, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("json"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = ngx_request_js_ext_body, + .magic8 = NGX_JS_BODY_JSON + } + }, + + { + .flags = NJS_EXTERN_PROPERTY, + .name.string = njs_str("headers"), + .enumerable = 1, + .u.property = { + .handler = ngx_request_js_ext_headers, + } + }, + + { + .flags = NJS_EXTERN_PROPERTY, + .name.string = njs_str("method"), + .enumerable = 1, + .u.property = { + .handler = ngx_js_ext_string, + .magic32 = offsetof(ngx_js_request_t, method), + } + }, + + { + .flags = NJS_EXTERN_PROPERTY, + .name.string = njs_str("mode"), + .enumerable = 1, + .u.property = { + .handler = ngx_request_js_ext_mode, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("text"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = ngx_request_js_ext_body, + .magic8 = NGX_JS_BODY_TEXT + } + }, + + { + .flags = NJS_EXTERN_PROPERTY, + .name.string = njs_str("url"), + .enumerable = 1, + .u.property = { + .handler = ngx_js_ext_string, + .magic32 = offsetof(ngx_js_request_t, url), } }, @@ -223,9 +530,6 @@ static njs_external_t ngx_js_ext_http_r .enumerable = 1, .u.method = { .native = ngx_response_js_ext_body, -#define NGX_JS_BODY_ARRAY_BUFFER 0 -#define NGX_JS_BODY_JSON 1 -#define NGX_JS_BODY_TEXT 2 .magic8 = NGX_JS_BODY_ARRAY_BUFFER } }, @@ -240,15 +544,11 @@ static njs_external_t ngx_js_ext_http_r }, { - .flags = NJS_EXTERN_OBJECT, + .flags = NJS_EXTERN_PROPERTY, .name.string = njs_str("headers"), .enumerable = 1, - .u.object = { - .enumerable = 1, - .properties = ngx_js_ext_http_response_headers, - .nproperties = njs_nitems(ngx_js_ext_http_response_headers), - .prop_handler = ngx_response_js_ext_header, - .keys = ngx_response_js_ext_keys, + .u.property = { + .handler = ngx_response_js_ext_headers, } }, @@ -329,47 +629,42 @@ static njs_external_t ngx_js_ext_http_r .enumerable = 1, .u.property = { .handler = ngx_js_ext_string, - .magic32 = offsetof(ngx_js_http_t, url), + .magic32 = offsetof(ngx_js_response_t, url), } }, }; -static njs_int_t ngx_http_js_fetch_proto_id; +static njs_int_t ngx_http_js_fetch_request_proto_id; +static njs_int_t ngx_http_js_fetch_response_proto_id; +static njs_int_t ngx_http_js_fetch_headers_proto_id; njs_int_t ngx_js_ext_fetch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused) { - int64_t i, length; njs_int_t ret; - njs_str_t method, body, name, header; ngx_url_t u; - njs_bool_t has_host; + ngx_uint_t i; ngx_pool_t *pool; - njs_value_t *init, *value, *headers, *keys; + njs_value_t *init, *value; ngx_js_http_t *http; + ngx_list_part_t *part; + ngx_table_elt_t *h; + ngx_js_request_t request; ngx_connection_t *c; ngx_resolver_ctx_t *ctx; njs_external_ptr_t external; - njs_opaque_value_t *start, lvalue, headers_value; - - static const njs_str_t body_key = njs_str("body"); - static const njs_str_t headers_key = njs_str("headers"); + njs_opaque_value_t lvalue; + static const njs_str_t buffer_size_key = njs_str("buffer_size"); static const njs_str_t body_size_key = njs_str("max_response_body_size"); - static const njs_str_t method_key = njs_str("method"); #if (NGX_SSL) static const njs_str_t verify_key = njs_str("verify"); #endif - external = njs_vm_external(vm, NJS_PROTO_ID_ANY, njs_argument(args, 0)); - if (external == NULL) { - njs_vm_error(vm, "\"this\" is not an external"); - return NJS_ERROR; - } - + external = njs_vm_external_ptr(vm); c = ngx_external_connection(vm, external); pool = ngx_external_pool(vm, external); @@ -379,76 +674,29 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value } http->external = external; + http->event_handler = ngx_external_event_handler(vm, external); + + ret = ngx_js_request_constructor(vm, &request, &u, external, args, nargs); + if (ret != NJS_OK) { + goto fail; + } + + http->response.url = request.url; http->timeout = ngx_external_fetch_timeout(vm, external); - http->event_handler = ngx_external_event_handler(vm, external); http->buffer_size = ngx_external_buffer_size(vm, external); http->max_response_body_size = ngx_external_max_response_buffer_size(vm, external); - ret = ngx_js_string(vm, njs_arg(args, nargs, 1), &http->url); - if (ret != NJS_OK) { - njs_vm_error(vm, "failed to convert url arg"); - goto fail; - } - - ngx_memzero(&u, sizeof(ngx_url_t)); - - u.url.len = http->url.length; - u.url.data = http->url.start; - u.default_port = 80; - u.uri_part = 1; - u.no_resolve = 1; - - if (u.url.len > 7 - && ngx_strncasecmp(u.url.data, (u_char *) "http://", 7) == 0) - { - u.url.len -= 7; - u.url.data += 7; - #if (NGX_SSL) - } else if (u.url.len > 8 - && ngx_strncasecmp(u.url.data, (u_char *) "https://", 8) == 0) - { - u.url.len -= 8; - u.url.data += 8; - u.default_port = 443; + if (u.default_port == 443) { http->ssl = ngx_external_ssl(vm, external); http->ssl_verify = ngx_external_ssl_verify(vm, external); + } #endif - } else { - njs_vm_error(vm, "unsupported URL prefix"); - goto fail; - } - - if (ngx_parse_url(pool, &u) != NGX_OK) { - njs_vm_error(vm, "invalid url"); - goto fail; - } - init = njs_arg(args, nargs, 2); - method = njs_str_value("GET"); - body = njs_str_value(""); - headers = NULL; - if (njs_value_is_object(init)) { - value = njs_vm_object_prop(vm, init, &method_key, &lvalue); - if (value != NULL && ngx_js_string(vm, value, &method) != NGX_OK) { - goto fail; - } - - headers = njs_vm_object_prop(vm, init, &headers_key, &headers_value); - if (headers != NULL && !njs_value_is_object(headers)) { - njs_vm_error(vm, "headers is not an object"); - goto fail; - } - - value = njs_vm_object_prop(vm, init, &body_key, &lvalue); - if (value != NULL && ngx_js_string(vm, value, &body) != NGX_OK) { - goto fail; - } - value = njs_vm_object_prop(vm, init, &buffer_size_key, &lvalue); if (value != NULL && ngx_js_integer(vm, value, &http->buffer_size) @@ -473,11 +721,11 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value #endif } + http->header_only = njs_strstr_eq(&request.method, &njs_str_value("HEAD")); + njs_chb_init(&http->chain, njs_vm_memory_pool(vm)); - http->header_only = njs_strstr_case_eq(&method, &njs_str_value("HEAD")); - - njs_chb_append(&http->chain, method.start, method.length); + njs_chb_append(&http->chain, request.method.start, request.method.length); njs_chb_append_literal(&http->chain, " "); if (u.uri.len == 0 || u.uri.data[0] != '/') { @@ -486,59 +734,34 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value njs_chb_append(&http->chain, u.uri.data, u.uri.len); njs_chb_append_literal(&http->chain, " HTTP/1.1" CRLF); + + njs_chb_append_literal(&http->chain, "Host: "); + njs_chb_append(&http->chain, u.host.data, u.host.len); + njs_chb_append_literal(&http->chain, CRLF); njs_chb_append_literal(&http->chain, "Connection: close" CRLF); - has_host = 0; - - if (headers != NULL) { - keys = njs_vm_object_keys(vm, headers, njs_value_arg(&lvalue)); - if (keys == NULL) { - goto fail; - } - - start = (njs_opaque_value_t *) njs_vm_array_start(vm, keys); - if (start == NULL) { - goto fail; - } - - (void) njs_vm_array_length(vm, keys, &length); - - for (i = 0; i < length; i++) { - if (ngx_js_string(vm, njs_value_arg(start), &name) != NGX_OK) { - goto fail; + part = &request.headers.header_list.part; + h = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; } - start++; - - value = njs_vm_object_prop(vm, headers, &name, &lvalue); - if (value == NULL) { - goto fail; - } - - if (njs_value_is_null_or_undefined(value)) { - continue; - } - - if (ngx_js_string(vm, value, &header) != NGX_OK) { - goto fail; - } - - if (name.length == 4 - && ngx_strncasecmp(name.start, (u_char *) "Host", 4) == 0) - { - has_host = 1; - } - - njs_chb_append(&http->chain, name.start, name.length); - njs_chb_append_literal(&http->chain, ": "); - njs_chb_append(&http->chain, header.start, header.length); - njs_chb_append_literal(&http->chain, CRLF); + part = part->next; + h = part->elts; + i = 0; } - } - - if (!has_host) { - njs_chb_append_literal(&http->chain, "Host: "); - njs_chb_append(&http->chain, u.host.data, u.host.len); + + if (h[i].hash == 0) { + continue; + } + + njs_chb_append(&http->chain, h[i].key.data, h[i].key.len); + njs_chb_append_literal(&http->chain, ": "); + njs_chb_append(&http->chain, h[i].value.data, h[i].value.len); njs_chb_append_literal(&http->chain, CRLF); } @@ -547,10 +770,10 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value http->tls_name.len = u.host.len; #endif - if (body.length != 0) { + if (request.body.length != 0) { njs_chb_sprintf(&http->chain, 32, "Content-Length: %uz" CRLF CRLF, - body.length); - njs_chb_append(&http->chain, body.start, body.length); + request.body.length); + njs_chb_append(&http->chain, request.body.start, request.body.length); } else { njs_chb_append_literal(&http->chain, CRLF); @@ -609,6 +832,377 @@ fail: } +static njs_int_t +ngx_js_ext_headers_constructor(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused) +{ + ngx_int_t rc; + njs_int_t ret; + njs_value_t *init; + ngx_pool_t *pool; + ngx_js_headers_t *headers; + + pool = ngx_external_pool(vm, njs_vm_external_ptr(vm)); + + headers = ngx_palloc(pool, sizeof(ngx_js_headers_t)); + if (headers == NULL) { + njs_vm_memory_error(vm); + return NJS_ERROR; + } + + rc = ngx_list_init(&headers->header_list, pool, 4, sizeof(ngx_table_elt_t)); + if (rc != NGX_OK) { + njs_vm_memory_error(vm); + return NJS_ERROR; + } + + init = njs_arg(args, nargs, 1); + + if (njs_value_is_object(init)) { + ret = ngx_js_headers_fill(vm, headers, init); + if (ret != NJS_OK) { + return NJS_ERROR; + } + } + + return njs_vm_external_create(vm, njs_vm_retval(vm), + ngx_http_js_fetch_headers_proto_id, headers, + 0); +} + + +static njs_int_t +ngx_js_ext_request_constructor(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused) +{ + njs_int_t ret; + ngx_url_t u; + ngx_js_request_t *request; + + request = njs_mp_alloc(njs_vm_memory_pool(vm), sizeof(ngx_js_request_t)); + if (request == NULL) { + njs_vm_memory_error(vm); + return NJS_ERROR; + } + + ret = ngx_js_request_constructor(vm, request, &u, njs_vm_external_ptr(vm), + args, nargs); + if (ret != NJS_OK) { + return NJS_ERROR; + } + + return njs_vm_external_create(vm, njs_vm_retval(vm), + ngx_http_js_fetch_request_proto_id, request, + 0); +} + + +static njs_int_t +ngx_js_ext_response_constructor(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused) +{ + u_char *p, *end; + ngx_int_t rc; + njs_int_t ret; + njs_str_t bd; + ngx_pool_t *pool; + njs_value_t *body, *init, *value; + ngx_js_response_t *response; + njs_opaque_value_t lvalue; + + static const njs_str_t headers = njs_str("headers"); + static const njs_str_t status = njs_str("status"); + static const njs_str_t status_text = njs_str("statusText"); + + response = njs_mp_zalloc(njs_vm_memory_pool(vm), sizeof(ngx_js_response_t)); + if (response == NULL) { + njs_vm_memory_error(vm); + return NJS_ERROR; + } + + /* + * set by njs_mp_zalloc(): + * + * request->url.length = 0; + * request->status_text.length = 0; + */ + + response->code = 200; + response->headers.guard = GUARD_RESPONSE; + + pool = ngx_external_pool(vm, njs_vm_external_ptr(vm)); + + rc = ngx_list_init(&response->headers.header_list, pool, 4, + sizeof(ngx_table_elt_t)); + if (rc != NGX_OK) { + njs_vm_memory_error(vm); + return NJS_ERROR; + } + + init = njs_arg(args, nargs, 2); + + if (njs_value_is_object(init)) { + value = njs_vm_object_prop(vm, init, &status, &lvalue); + if (value != NULL) { + if (ngx_js_integer(vm, value, &response->code) != NGX_OK) { + njs_vm_error(vm, "invalid Response status"); + return NJS_ERROR; + } + + if (response->code < 200 || response->code > 599) { + njs_vm_error(vm, "status provided (%i) is outside of " + "[200, 599] range", response->code); + return NJS_ERROR; + } + } + + value = njs_vm_object_prop(vm, init, &status_text, &lvalue); + if (value != NULL) { + if (ngx_js_string(vm, value, &response->status_text) != NGX_OK) { + njs_vm_error(vm, "invalid Response statusText"); + return NJS_ERROR; + } + + p = response->status_text.start; + end = p + response->status_text.length; + + while (p < end) { + if (*p != '\t' && *p < ' ') { + njs_vm_error(vm, "invalid Response statusText"); + return NJS_ERROR; + } + + p++; + } + } + + value = njs_vm_object_prop(vm, init, &headers, &lvalue); + if (value != NULL) { + if (!njs_value_is_object(value)) { + njs_vm_error(vm, "Headers is not an object"); + return NJS_ERROR; + } + + ret = ngx_js_headers_fill(vm, &response->headers, value); + if (ret != NJS_OK) { + return NJS_ERROR; + } + } + } + + njs_chb_init(&response->chain, njs_vm_memory_pool(vm)); + + body = njs_arg(args, nargs, 1); + + if (!njs_value_is_null_or_undefined(body)) { + if (ngx_js_string(vm, body, &bd) != NGX_OK) { + njs_vm_error(vm, "invalid Response body"); + return NJS_ERROR; + } + + njs_chb_append(&response->chain, bd.start, bd.length); + + if (njs_value_is_string(body)) { + ret = ngx_js_headers_append(vm, &response->headers, + (u_char *) "Content-Type", + njs_length("Content-Type"), + (u_char *) "text/plain;charset=UTF-8", + njs_length("text/plain;charset=UTF-8")); + if (ret != NJS_OK) { + return NJS_ERROR; + } + } + } + + return njs_vm_external_create(vm, njs_vm_retval(vm), + ngx_http_js_fetch_response_proto_id, response, + 0); +} + + +static njs_int_t +ngx_js_method_process(njs_vm_t *vm, ngx_js_request_t *request) +{ + u_char *s, *p; + const njs_str_t *m; + + static const njs_str_t forbidden[] = { + njs_str("CONNECT"), + njs_str("TRACE"), + njs_str("TRACK"), + njs_null_str, + }; + + static const njs_str_t to_normalize[] = { + njs_str("DELETE"), + njs_str("GET"), + njs_str("HEAD"), + njs_str("OPTIONS"), + njs_str("POST"), + njs_str("PUT"), + njs_null_str, + }; + + for (m = &forbidden[0]; m->length != 0; m++) { + if (njs_strstr_case_eq(&request->method, m)) { + njs_vm_error(vm, "forbidden method: %V", m); + return NJS_ERROR; + } + } + + for (m = &to_normalize[0]; m->length != 0; m++) { + if (njs_strstr_case_eq(&request->method, m)) { + s = &request->m[0]; + p = m->start; + + while (*p != '\0') { + *s++ = njs_upper_case(*p++); + } + + request->method.start = &request->m[0]; + request->method.length = m->length; + break; + } + } + + return NJS_OK; +} + + +static njs_int_t +ngx_js_headers_inherit(njs_vm_t *vm, ngx_js_headers_t *headers, + ngx_js_headers_t *orig) +{ + njs_int_t ret; + ngx_uint_t i; + ngx_list_part_t *part; + ngx_table_elt_t *h; + + part = &orig->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) { _______________________________________________ nginx-devel mailing list nginx-devel@nginx.org https://mailman.nginx.org/mailman/listinfo/nginx-devel