From b3f6a499163b430dc74b8278b7b9f383e7ad5df6 Mon Sep 17 00:00:00 2001 From: Zhidao HONG Date: Tue, 22 Apr 2025 01:04:31 +0800 Subject: [PATCH 1/3] Fetch: separated ngx_js_http_* from ngx_js_fetch_*. --- nginx/config | 2 + nginx/ngx_js_fetch.c | 2239 +++++++----------------------------------- nginx/ngx_js_http.c | 1425 +++++++++++++++++++++++++++ nginx/ngx_js_http.h | 157 +++ 4 files changed, 1963 insertions(+), 1860 deletions(-) create mode 100644 nginx/ngx_js_http.c create mode 100644 nginx/ngx_js_http.h diff --git a/nginx/config b/nginx/config index 03ec03d0c..b994f97f3 100644 --- a/nginx/config +++ b/nginx/config @@ -6,9 +6,11 @@ NJS_ZLIB=${NJS_ZLIB:-YES} NJS_QUICKJS=${NJS_QUICKJS:-YES} NJS_DEPS="$ngx_addon_dir/ngx_js.h \ + $ngx_addon_dir/ngx_js_http.h \ $ngx_addon_dir/ngx_js_fetch.h \ $ngx_addon_dir/ngx_js_shared_dict.h" NJS_SRCS="$ngx_addon_dir/ngx_js.c \ + $ngx_addon_dir/ngx_js_http.c \ $ngx_addon_dir/ngx_js_fetch.c \ $ngx_addon_dir/ngx_js_regex.c \ $ngx_addon_dir/ngx_js_shared_dict.c" diff --git a/nginx/ngx_js_fetch.c b/nginx/ngx_js_fetch.c index 4902fe4fa..dcf8566f0 100644 --- a/nginx/ngx_js_fetch.c +++ b/nginx/ngx_js_fetch.c @@ -12,9 +12,7 @@ #include #include #include "ngx_js.h" - - -typedef struct ngx_js_http_s ngx_js_http_t; +#include "ngx_js_http.h" typedef struct { @@ -24,136 +22,16 @@ typedef struct { typedef struct { - ngx_uint_t state; - ngx_uint_t code; - u_char *status_text; - u_char *status_text_end; - ngx_uint_t count; - ngx_flag_t chunked; - off_t content_length_n; - - u_char *header_name_start; - u_char *header_name_end; - u_char *header_start; - u_char *header_end; -} ngx_js_http_parse_t; - - -typedef struct { - u_char *pos; - uint64_t chunk_size; - uint8_t state; - uint8_t last; -} ngx_js_http_chunk_parse_t; - - -typedef struct ngx_js_tb_elt_s ngx_js_tb_elt_t; - -struct ngx_js_tb_elt_s { - ngx_uint_t hash; - ngx_str_t key; - ngx_str_t value; - ngx_js_tb_elt_t *next; -}; - - -typedef struct { - enum { - GUARD_NONE = 0, - GUARD_REQUEST, - GUARD_IMMUTABLE, - GUARD_RESPONSE, - } guard; - ngx_list_t header_list; - ngx_js_tb_elt_t *content_type; -} 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; + ngx_js_http_t http; njs_vm_t *vm; ngx_js_event_t *event; - ngx_resolver_ctx_t *ctx; - ngx_addr_t addr; - ngx_addr_t *addrs; - ngx_uint_t naddrs; - ngx_uint_t naddr; - in_port_t port; - - ngx_peer_connection_t peer; - ngx_msec_t timeout; - - ngx_int_t buffer_size; - ngx_int_t max_response_body_size; - - unsigned header_only; - -#if (NGX_SSL) - ngx_str_t tls_name; - ngx_ssl_t *ssl; - njs_bool_t ssl_verify; -#endif - - ngx_buf_t *buffer; - ngx_buf_t *chunk; - njs_chb_t chain; - - 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; - 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); -}; +} ngx_js_fetch_t; static njs_int_t ngx_js_method_process(njs_vm_t *vm, ngx_js_request_t *r); @@ -161,44 +39,29 @@ 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, +static ngx_js_fetch_t *ngx_js_fetch_alloc(njs_vm_t *vm, ngx_pool_t *pool, ngx_log_t *log); -static void ngx_js_http_resolve_done(ngx_js_http_t *http); -static void ngx_js_http_close_peer(ngx_js_http_t *http); -static void ngx_js_http_destructor(ngx_js_event_t *event); -static ngx_resolver_ctx_t *ngx_js_http_resolve(ngx_js_http_t *http, - ngx_resolver_t *r, ngx_str_t *host, in_port_t port, ngx_msec_t timeout); -static void ngx_js_http_resolve_handler(ngx_resolver_ctx_t *ctx); +static void ngx_js_fetch_error(ngx_js_http_t *http, const char *err); +static void ngx_js_fetch_destructor(ngx_js_event_t *event); static njs_int_t ngx_js_fetch_promissified_result(njs_vm_t *vm, njs_value_t *result, njs_int_t rc, njs_value_t *retval); -static void ngx_js_http_fetch_done(ngx_js_http_t *http, - njs_opaque_value_t *retval, njs_int_t rc); +static void ngx_js_fetch_done(ngx_js_fetch_t *fetch, njs_opaque_value_t *retval, + njs_int_t rc); static njs_int_t ngx_js_http_promise_trampoline(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); -static void ngx_js_http_connect(ngx_js_http_t *http); -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 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_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); -static ngx_int_t ngx_js_http_parse_status_line(ngx_js_http_parse_t *hp, - ngx_buf_t *b); -static ngx_int_t ngx_js_http_parse_header_line(ngx_js_http_parse_t *hp, - ngx_buf_t *b); -static ngx_int_t ngx_js_http_parse_chunked(ngx_js_http_chunk_parse_t *hcp, - ngx_buf_t *b, njs_chb_t *chain); -static void ngx_js_http_dummy_handler(ngx_event_t *ev); - 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, njs_value_t *retval); static njs_int_t ngx_headers_js_ext_delete(njs_vm_t *vm, njs_value_t *args, @@ -254,13 +117,6 @@ static njs_int_t ngx_response_js_ext_type(njs_vm_t *vm, static njs_int_t ngx_response_js_ext_body(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); -#if (NGX_SSL) -static void ngx_js_http_ssl_init_connection(ngx_js_http_t *http); -static void ngx_js_http_ssl_handshake_handler(ngx_connection_t *c); -static void ngx_js_http_ssl_handshake(ngx_js_http_t *http); -static njs_int_t ngx_js_http_ssl_name(ngx_js_http_t *http); -#endif - 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, @@ -663,6 +519,7 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, ngx_pool_t *pool; njs_value_t *init, *value; ngx_js_http_t *http; + ngx_js_fetch_t *fetch; ngx_list_part_t *part; ngx_js_tb_elt_t *h; ngx_js_request_t request; @@ -681,11 +538,13 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, c = ngx_external_connection(vm, external); pool = ngx_external_pool(vm, external); - http = ngx_js_http_alloc(vm, pool, c->log); - if (http == NULL) { + fetch = ngx_js_fetch_alloc(vm, pool, c->log); + if (fetch == NULL) { return NJS_ERROR; } + http = &fetch->http; + ret = ngx_js_request_constructor(vm, &request, &u, external, args, nargs); if (ret != NJS_OK) { goto fail; @@ -734,6 +593,7 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, http->header_only = njs_strstr_eq(&request.method, &njs_str_value("HEAD")); NJS_CHB_MP_INIT(&http->chain, njs_vm_memory_pool(vm)); + NJS_CHB_MP_INIT(&http->response.chain, njs_vm_memory_pool(vm)); njs_chb_append(&http->chain, request.method.start, request.method.length); njs_chb_append_literal(&http->chain, " "); @@ -848,7 +708,7 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, goto fail; } - njs_value_assign(retval, njs_value_arg(&http->promise)); + njs_value_assign(retval, njs_value_arg(&fetch->promise)); return NJS_OK; } @@ -859,18 +719,17 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, ngx_js_http_connect(http); - njs_value_assign(retval, njs_value_arg(&http->promise)); + njs_value_assign(retval, njs_value_arg(&fetch->promise)); return NJS_OK; fail: - njs_vm_exception_get(vm, njs_value_arg(&lvalue)); - ngx_js_http_fetch_done(http, &lvalue, NJS_ERROR); + ngx_js_fetch_done(fetch, &lvalue, NJS_ERROR); - njs_value_assign(retval, njs_value_arg(&http->promise)); + njs_value_assign(retval, njs_value_arg(&fetch->promise)); return NJS_OK; } @@ -1249,30 +1108,36 @@ 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 ngx_js_fetch_t * +ngx_js_fetch_alloc(njs_vm_t *vm, ngx_pool_t *pool, ngx_log_t *log) { njs_int_t ret; ngx_js_ctx_t *ctx; ngx_js_http_t *http; + ngx_js_fetch_t *fetch; ngx_js_event_t *event; njs_function_t *callback; - http = ngx_pcalloc(pool, sizeof(ngx_js_http_t)); - if (http == NULL) { + fetch = ngx_pcalloc(pool, sizeof(ngx_js_fetch_t)); + if (fetch == NULL) { goto failed; } + http = &fetch->http; + http->pool = pool; http->log = log; - http->vm = vm; http->timeout = 10000; http->http_parse.content_length_n = -1; - ret = njs_vm_promise_create(vm, njs_value_arg(&http->promise), - njs_value_arg(&http->promise_callbacks)); + http->append_headers = ngx_js_fetch_append_headers; + http->ready_handler = ngx_js_fetch_process_done; + http->error_handler = ngx_js_fetch_error; + + ret = njs_vm_promise_create(vm, njs_value_arg(&fetch->promise), + njs_value_arg(&fetch->promise_callbacks)); if (ret != NJS_OK) { goto failed; } @@ -1291,17 +1156,18 @@ ngx_js_http_alloc(njs_vm_t *vm, ngx_pool_t *pool, ngx_log_t *log) event->ctx = vm; njs_value_function_set(njs_value_arg(&event->function), callback); - event->destructor = ngx_js_http_destructor; + event->destructor = ngx_js_fetch_destructor; event->fd = ctx->event_id++; - event->data = http; + event->data = fetch; ngx_js_add_event(ctx, event); - http->event = event; + fetch->vm = vm; + fetch->event = event; - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "js fetch alloc:%p", http); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "js fetch alloc:%p", fetch); - return http; + return fetch; failed: @@ -1312,194 +1178,31 @@ ngx_js_http_alloc(njs_vm_t *vm, ngx_pool_t *pool, ngx_log_t *log) static void -ngx_js_http_error(ngx_js_http_t *http, const char *fmt, ...) -{ - u_char *p, *end; - va_list args; - u_char err[NGX_MAX_ERROR_STR]; - - end = err + NGX_MAX_ERROR_STR - 1; - - va_start(args, fmt); - p = njs_vsprintf(err, end, fmt, args); - *p = '\0'; - va_end(args); - - njs_vm_error(http->vm, (const char *) err); - njs_vm_exception_get(http->vm, njs_value_arg(&http->response_value)); - ngx_js_http_fetch_done(http, &http->response_value, NJS_ERROR); -} - - -static ngx_resolver_ctx_t * -ngx_js_http_resolve(ngx_js_http_t *http, ngx_resolver_t *r, ngx_str_t *host, - in_port_t port, ngx_msec_t timeout) -{ - ngx_int_t ret; - ngx_resolver_ctx_t *ctx; - - ctx = ngx_resolve_start(r, NULL); - if (ctx == NULL) { - return NULL; - } - - if (ctx == NGX_NO_RESOLVER) { - return ctx; - } - - http->ctx = ctx; - http->port = port; - - ctx->name = *host; - ctx->handler = ngx_js_http_resolve_handler; - ctx->data = http; - ctx->timeout = timeout; - - ret = ngx_resolve_name(ctx); - if (ret != NGX_OK) { - http->ctx = NULL; - return NULL; - } - - return ctx; -} - - -static void -ngx_js_http_resolve_handler(ngx_resolver_ctx_t *ctx) -{ - u_char *p; - size_t len; - socklen_t socklen; - ngx_uint_t i; - ngx_js_http_t *http; - struct sockaddr *sockaddr; - - http = ctx->data; - - if (ctx->state) { - ngx_js_http_error(http, "\"%V\" could not be resolved (%i: %s)", - &ctx->name, ctx->state, - ngx_resolver_strerror(ctx->state)); - return; - } - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, http->log, 0, - "http fetch resolved: \"%V\"", &ctx->name); - -#if (NGX_DEBUG) - { - u_char text[NGX_SOCKADDR_STRLEN]; - ngx_str_t addr; - ngx_uint_t i; - - addr.data = text; - - for (i = 0; i < ctx->naddrs; i++) { - addr.len = ngx_sock_ntop(ctx->addrs[i].sockaddr, ctx->addrs[i].socklen, - text, NGX_SOCKADDR_STRLEN, 0); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, http->log, 0, - "name was resolved to \"%V\"", &addr); - } - } -#endif - - http->naddrs = ctx->naddrs; - http->addrs = ngx_pcalloc(http->pool, http->naddrs * sizeof(ngx_addr_t)); - - if (http->addrs == NULL) { - goto failed; - } - - for (i = 0; i < ctx->naddrs; i++) { - socklen = ctx->addrs[i].socklen; - - sockaddr = ngx_palloc(http->pool, socklen); - if (sockaddr == NULL) { - goto failed; - } - - ngx_memcpy(sockaddr, ctx->addrs[i].sockaddr, socklen); - ngx_inet_set_port(sockaddr, http->port); - - http->addrs[i].sockaddr = sockaddr; - http->addrs[i].socklen = socklen; - - p = ngx_pnalloc(http->pool, NGX_SOCKADDR_STRLEN); - if (p == NULL) { - goto failed; - } - - len = ngx_sock_ntop(sockaddr, socklen, p, NGX_SOCKADDR_STRLEN, 1); - http->addrs[i].name.len = len; - http->addrs[i].name.data = p; - } - - ngx_js_http_resolve_done(http); - - ngx_js_http_connect(http); - - return; - -failed: - - ngx_js_http_error(http, "memory error"); -} - - -static void -ngx_js_http_close_connection(ngx_connection_t *c) +ngx_js_fetch_error(ngx_js_http_t *http, const char *err) { - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "js fetch close connection: %d", c->fd); - -#if (NGX_SSL) - if (c->ssl) { - c->ssl->no_wait_shutdown = 1; - - if (ngx_ssl_shutdown(c) == NGX_AGAIN) { - c->ssl->handler = ngx_js_http_close_connection; - return; - } - } -#endif - - c->destroyed = 1; - - ngx_close_connection(c); -} + ngx_js_fetch_t *fetch; + fetch = (ngx_js_fetch_t *) http; -static void -ngx_js_http_resolve_done(ngx_js_http_t *http) -{ - if (http->ctx != NULL) { - ngx_resolve_name_done(http->ctx); - http->ctx = NULL; - } -} + njs_vm_error(fetch->vm, err); + njs_vm_exception_get(fetch->vm, njs_value_arg(&fetch->response_value)); -static void -ngx_js_http_close_peer(ngx_js_http_t *http) -{ - if (http->peer.connection != NULL) { - ngx_js_http_close_connection(http->peer.connection); - http->peer.connection = NULL; - } + ngx_js_fetch_done(fetch, &fetch->response_value, NJS_ERROR); } static void -ngx_js_http_destructor(ngx_js_event_t *event) +ngx_js_fetch_destructor(ngx_js_event_t *event) { - ngx_js_http_t *http; + ngx_js_http_t *http; + ngx_js_fetch_t *fetch; - http = event->data; + fetch = event->data; + http = &fetch->http; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, http->log, 0, "js fetch destructor:%p", - http); + fetch); ngx_js_http_resolve_done(http); ngx_js_http_close_peer(http); @@ -1552,26 +1255,29 @@ ngx_js_fetch_promissified_result(njs_vm_t *vm, njs_value_t *result, static void -ngx_js_http_fetch_done(ngx_js_http_t *http, njs_opaque_value_t *retval, +ngx_js_fetch_done(ngx_js_fetch_t *fetch, njs_opaque_value_t *retval, njs_int_t rc) { njs_vm_t *vm; ngx_js_ctx_t *ctx; + ngx_js_http_t *http; ngx_js_event_t *event; njs_opaque_value_t arguments[2], *action; + http = &fetch->http; + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, http->log, 0, - "js fetch done http:%p rc:%i", http, (ngx_int_t) rc); + "js fetch done fetch:%p rc:%i", fetch, (ngx_int_t) rc); ngx_js_http_close_peer(http); - if (http->event != NULL) { - action = &http->promise_callbacks[(rc != NJS_OK)]; + if (fetch->event != NULL) { + action = &fetch->promise_callbacks[(rc != NJS_OK)]; njs_value_assign(&arguments[0], action); njs_value_assign(&arguments[1], retval); - vm = http->vm; - event = http->event; + vm = fetch->vm; + event = fetch->event; rc = ngx_js_call(vm, njs_value_function(njs_value_arg(&event->function)), &arguments[0], 2); @@ -1600,1627 +1306,440 @@ ngx_js_http_promise_trampoline(njs_vm_t *vm, njs_value_t *args, } -static void -ngx_js_http_connect(ngx_js_http_t *http) +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) { - ngx_int_t rc; - ngx_addr_t *addr; - - addr = &http->addrs[http->naddr]; - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, http->log, 0, - "js fetch connect %ui/%ui", http->naddr, http->naddrs); - - http->peer.sockaddr = addr->sockaddr; - http->peer.socklen = addr->socklen; - http->peer.name = &addr->name; - http->peer.get = ngx_event_get_peer; - http->peer.log = http->log; - http->peer.log_error = NGX_ERROR_ERR; - - rc = ngx_event_connect_peer(&http->peer); + njs_int_t ret; + ngx_uint_t rc; + ngx_pool_t *pool; + njs_value_t *input, *init, *value, *headers; + ngx_js_request_t *orig; + njs_opaque_value_t lvalue; - if (rc == NGX_ERROR) { - ngx_js_http_error(http, "connect failed"); - return; - } + static const njs_str_t body_key = njs_str("body"); + static const njs_str_t cache_key = njs_str("cache"); + static const njs_str_t cred_key = njs_str("credentials"); + static const njs_str_t headers_key = njs_str("headers"); + static const njs_str_t mode_key = njs_str("mode"); + static const njs_str_t method_key = njs_str("method"); - if (rc == NGX_BUSY || rc == NGX_DECLINED) { - ngx_js_http_next(http); - return; + input = njs_arg(args, nargs, 1); + if (njs_value_is_undefined(input)) { + njs_vm_error(vm, "1st argument is required"); + return NJS_ERROR; } - http->peer.connection->data = http; - http->peer.connection->pool = http->pool; + /* + * set by ngx_memzero(): + * + * request->url.length = 0; + * request->body.length = 0; + * request->cache_mode = CACHE_MODE_DEFAULT; + * request->credentials = CREDENTIALS_SAME_ORIGIN; + * request->mode = MODE_NO_CORS; + * request->headers.content_type = NULL; + */ - http->peer.connection->write->handler = ngx_js_http_write_handler; - http->peer.connection->read->handler = ngx_js_http_read_handler; + ngx_memzero(request, sizeof(ngx_js_request_t)); - http->process = ngx_js_http_process_status_line; + request->method = njs_str_value("GET"); + request->body = njs_str_value(""); + request->headers.guard = GUARD_REQUEST; - ngx_add_timer(http->peer.connection->read, http->timeout); - ngx_add_timer(http->peer.connection->write, http->timeout); + pool = ngx_external_pool(vm, external); -#if (NGX_SSL) - if (http->ssl != NULL && http->peer.connection->ssl == NULL) { - ngx_js_http_ssl_init_connection(http); - return; + rc = ngx_list_init(&request->headers.header_list, pool, 4, + sizeof(ngx_js_tb_elt_t)); + if (rc != NGX_OK) { + njs_vm_memory_error(vm); + return NJS_ERROR; } -#endif - if (rc == NGX_OK) { - ngx_js_http_write_handler(http->peer.connection->write); - } -} + if (njs_value_is_string(input)) { + ret = ngx_js_string(vm, input, &request->url); + if (ret != NJS_OK) { + njs_vm_error(vm, "failed to convert url arg"); + return NJS_ERROR; + } + } else { + orig = njs_vm_external(vm, ngx_http_js_fetch_request_proto_id, input); + if (orig == NULL) { + njs_vm_error(vm, "input is not string or a Request object"); + return NJS_ERROR; + } -#if (NGX_SSL) + request->url = orig->url; + request->method = orig->method; + request->body = orig->body; + request->body_used = orig->body_used; + request->cache_mode = orig->cache_mode; + request->credentials = orig->credentials; + request->mode = orig->mode; -static void -ngx_js_http_ssl_init_connection(ngx_js_http_t *http) -{ - ngx_int_t rc; - ngx_connection_t *c; + ret = ngx_js_headers_inherit(vm, &request->headers, &orig->headers); + if (ret != NJS_OK) { + return NJS_ERROR; + } + } - c = http->peer.connection; + ngx_js_http_trim(&request->url.start, &request->url.length, 1); - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, http->log, 0, - "js fetch secure connect %ui/%ui", http->naddr, - http->naddrs); + ngx_memzero(u, sizeof(ngx_url_t)); + + u->url.len = request->url.length; + u->url.data = request->url.start; + u->default_port = 80; + u->uri_part = 1; + u->no_resolve = 1; - if (ngx_ssl_create_connection(http->ssl, c, NGX_SSL_BUFFER|NGX_SSL_CLIENT) - != NGX_OK) + if (u->url.len > 7 + && njs_strncasecmp(u->url.data, (u_char *) "http://", 7) == 0) { - ngx_js_http_error(http, "failed to create ssl connection"); - return; - } + u->url.len -= 7; + u->url.data += 7; - c->sendfile = 0; +#if (NGX_SSL) + } else if (u->url.len > 8 + && njs_strncasecmp(u->url.data, (u_char *) "https://", 8) == 0) + { + u->url.len -= 8; + u->url.data += 8; + u->default_port = 443; +#endif - if (ngx_js_http_ssl_name(http) != NGX_OK) { - ngx_js_http_error(http, "failed to create ssl connection"); - return; + } else { + njs_vm_error(vm, "unsupported URL schema (only http or https are" + " supported)"); + return NJS_ERROR; } - c->log->action = "SSL handshaking to fetch target"; - - rc = ngx_ssl_handshake(c); - - if (rc == NGX_AGAIN) { - c->data = http; - c->ssl->handler = ngx_js_http_ssl_handshake_handler; - return; + if (ngx_parse_url(pool, u) != NGX_OK) { + njs_vm_error(vm, "invalid url"); + return NJS_ERROR; } - ngx_js_http_ssl_handshake(http); -} - - -static void -ngx_js_http_ssl_handshake_handler(ngx_connection_t *c) -{ - ngx_js_http_t *http; - - http = c->data; + init = njs_arg(args, nargs, 2); - http->peer.connection->write->handler = ngx_js_http_write_handler; - http->peer.connection->read->handler = ngx_js_http_read_handler; + if (njs_value_is_object(init)) { + value = njs_vm_object_prop(vm, init, &method_key, &lvalue); + if (value != NULL && ngx_js_string(vm, value, &request->method) + != NGX_OK) + { + njs_vm_error(vm, "invalid Request method"); + return NJS_ERROR; + } - ngx_js_http_ssl_handshake(http); -} + ret = ngx_js_method_process(vm, request); + if (ret != NJS_OK) { + return NJS_ERROR; + } - -static void -ngx_js_http_ssl_handshake(ngx_js_http_t *http) -{ - long rc; - ngx_connection_t *c; - - c = http->peer.connection; - - if (c->ssl->handshaked) { - if (http->ssl_verify) { - rc = SSL_get_verify_result(c->ssl->connection); - - if (rc != X509_V_OK) { - ngx_log_error(NGX_LOG_ERR, c->log, 0, - "js fetch SSL certificate verify error: (%l:%s)", - rc, X509_verify_cert_error_string(rc)); - goto failed; + value = njs_vm_object_prop(vm, init, &cache_key, &lvalue); + if (value != NULL) { + ret = ngx_fetch_flag_set(vm, ngx_js_fetch_cache_modes, value, + "cache"); + if (ret == NJS_ERROR) { + return NJS_ERROR; } - if (ngx_ssl_check_host(c, &http->tls_name) != NGX_OK) { - ngx_log_error(NGX_LOG_ERR, c->log, 0, - "js fetch SSL certificate does not match \"%V\"", - &http->tls_name); - goto failed; - } + request->cache_mode = ret; } - c->write->handler = ngx_js_http_write_handler; - c->read->handler = ngx_js_http_read_handler; + value = njs_vm_object_prop(vm, init, &cred_key, &lvalue); + if (value != NULL) { + ret = ngx_fetch_flag_set(vm, ngx_js_fetch_credentials, value, + "credentials"); + if (ret == NJS_ERROR) { + return NJS_ERROR; + } - if (c->read->ready) { - ngx_post_event(c->read, &ngx_posted_events); + request->credentials = ret; } - http->process = ngx_js_http_process_status_line; - ngx_js_http_write_handler(c->write); - - return; - } - -failed: - - ngx_js_http_next(http); - } - - -static njs_int_t -ngx_js_http_ssl_name(ngx_js_http_t *http) -{ -#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME - u_char *p; - - /* as per RFC 6066, literal IPv4 and IPv6 addresses are not permitted */ - ngx_str_t *name = &http->tls_name; + value = njs_vm_object_prop(vm, init, &mode_key, &lvalue); + if (value != NULL) { + ret = ngx_fetch_flag_set(vm, ngx_js_fetch_modes, value, "mode"); + if (ret == NJS_ERROR) { + return NJS_ERROR; + } - if (name->len == 0 || *name->data == '[') { - goto done; - } + request->mode = ret; + } - if (ngx_inet_addr(name->data, name->len) != INADDR_NONE) { - goto done; - } + headers = njs_vm_object_prop(vm, init, &headers_key, &lvalue); + if (headers != NULL) { + if (!njs_value_is_object(headers)) { + njs_vm_error(vm, "Headers is not an object"); + return NJS_ERROR; + } - /* - * SSL_set_tlsext_host_name() needs a null-terminated string, - * hence we explicitly null-terminate name here - */ + /* + * There are no API to reset or destroy ngx_list, + * just allocating a new one. + */ - p = ngx_pnalloc(http->pool, name->len + 1); - if (p == NULL) { - return NGX_ERROR; - } + ngx_memset(&request->headers, 0, sizeof(ngx_js_headers_t)); + request->headers.guard = GUARD_REQUEST; - (void) ngx_cpystrn(p, name->data, name->len + 1); + rc = ngx_list_init(&request->headers.header_list, pool, 4, + sizeof(ngx_js_tb_elt_t)); + if (rc != NGX_OK) { + njs_vm_memory_error(vm); + return NJS_ERROR; + } - name->data = p; + ret = ngx_js_headers_fill(vm, &request->headers, headers); + if (ret != NJS_OK) { + return NJS_ERROR; + } + } - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, http->log, 0, - "js fetch SSL server name: \"%s\"", name->data); + value = njs_vm_object_prop(vm, init, &body_key, &lvalue); + if (value != NULL) { + if (ngx_js_string(vm, value, &request->body) != NGX_OK) { + njs_vm_error(vm, "invalid Request body"); + return NJS_ERROR; + } - if (SSL_set_tlsext_host_name(http->peer.connection->ssl->connection, - (char *) name->data) - == 0) - { - ngx_ssl_error(NGX_LOG_ERR, http->log, 0, - "SSL_set_tlsext_host_name(\"%s\") failed", name->data); - return NGX_ERROR; + if (request->headers.content_type == NULL + && njs_value_is_string(value)) + { + ret = ngx_js_headers_append(vm, &request->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; + } + } + } } -#endif -done: - return NJS_OK; } -#endif - -static void -ngx_js_http_next(ngx_js_http_t *http) +njs_inline njs_int_t +ngx_js_http_whitespace(u_char c) { - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, http->log, 0, "js fetch next addr"); - - if (++http->naddr >= http->naddrs) { - ngx_js_http_error(http, "connect failed"); - return; - } + switch (c) { + case 0x09: /* */ + case 0x0A: /* */ + case 0x0D: /* */ + case 0x20: /* */ + return 1; - if (http->peer.connection != NULL) { - ngx_js_http_close_connection(http->peer.connection); - http->peer.connection = NULL; + default: + return 0; } - - http->buffer = NULL; - - ngx_js_http_connect(http); } static void -ngx_js_http_write_handler(ngx_event_t *wev) +ngx_js_http_trim(u_char **value, size_t *len, + njs_bool_t trim_c0_control_or_space) { - ssize_t n, size; - ngx_buf_t *b; - ngx_js_http_t *http; - ngx_connection_t *c; + u_char *start, *end; - c = wev->data; - http = c->data; + start = *value; + end = start + *len; - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, wev->log, 0, "js fetch write handler"); + for ( ;; ) { + if (start == end) { + break; + } - if (wev->timedout) { - ngx_js_http_error(http, "write timed out"); - return; - } + if (ngx_js_http_whitespace(*start) + || (trim_c0_control_or_space && *start <= ' ')) + { + start++; + continue; + } -#if (NGX_SSL) - if (http->ssl != NULL && http->peer.connection->ssl == NULL) { - ngx_js_http_ssl_init_connection(http); - return; + break; } -#endif - - b = http->buffer; - if (b == NULL) { - size = njs_chb_size(&http->chain); - if (size < 0) { - ngx_js_http_error(http, "memory error"); - return; + for ( ;; ) { + if (start == end) { + break; } - b = ngx_create_temp_buf(http->pool, size); - if (b == NULL) { - ngx_js_http_error(http, "memory error"); - return; - } + end--; - njs_chb_join_to(&http->chain, b->last); - b->last += size; + if (ngx_js_http_whitespace(*end) + || (trim_c0_control_or_space && *end <= ' ')) + { + continue; + } - http->buffer = b; + end++; + break; } - size = b->last - b->pos; + *value = start; + *len = end - start; +} - n = c->send(c, b->pos, size); - if (n == NGX_ERROR) { - ngx_js_http_next(http); - return; - } +static const uint32_t token_map[] = { + 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ - if (n > 0) { - b->pos += n; + /* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */ + 0x03ff6cfa, /* 0000 0011 1111 1111 0110 1100 1111 1010 */ - if (n == size) { - wev->handler = ngx_js_http_dummy_handler; + /* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */ + 0xc7fffffe, /* 1100 0111 1111 1111 1111 1111 1111 1110 */ - http->buffer = NULL; + /* ~}| {zyx wvut srqp onml kjih gfed cba` */ + 0x57ffffff, /* 0101 0111 1111 1111 1111 1111 1111 1111 */ - if (wev->timer_set) { - ngx_del_timer(wev); - } + 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ + 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ + 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ + 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ +}; - if (ngx_handle_write_event(wev, 0) != NGX_OK) { - ngx_js_http_error(http, "write failed"); - } - return; - } - } +njs_inline njs_bool_t +njs_is_token(uint32_t byte) +{ + return ((token_map[byte >> 5] & ((uint32_t) 1 << (byte & 0x1f))) != 0); +} - if (!wev->timer_set) { - ngx_add_timer(wev, http->timeout); - } + +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) +{ + ngx_js_fetch_t *fetch; + + fetch = (ngx_js_fetch_t *) http; + + return ngx_js_headers_append(fetch->vm, headers, name, len, value, vlen); } static void -ngx_js_http_read_handler(ngx_event_t *rev) +ngx_js_fetch_process_done(ngx_js_http_t *http) { - ssize_t n, size; - ngx_int_t rc; - ngx_buf_t *b; - ngx_js_http_t *http; - ngx_connection_t *c; - - c = rev->data; - http = c->data; + njs_int_t ret; + ngx_js_fetch_t *fetch; - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, rev->log, 0, "js fetch read handler"); + fetch = (ngx_js_fetch_t *) http; - if (rev->timedout) { - ngx_js_http_error(http, "read timed out"); + ret = njs_vm_external_create(fetch->vm, + njs_value_arg(&fetch->response_value), + ngx_http_js_fetch_response_proto_id, + &fetch->http.response, 0); + if (ret != NJS_OK) { + ngx_js_fetch_error(http, "fetch response creation failed"); return; } - if (http->buffer == NULL) { - b = ngx_create_temp_buf(http->pool, http->buffer_size); - if (b == NULL) { - ngx_js_http_error(http, "memory error"); - return; + ngx_js_fetch_done(fetch, &fetch->response_value, NJS_OK); +} + + +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_uint_t i; + ngx_js_tb_elt_t *h, **ph; + ngx_list_part_t *part; + + ngx_js_http_trim(&value, &vlen, 0); + + p = name; + end = p + len; + + while (p < end) { + if (!njs_is_token(*p)) { + njs_vm_error(vm, "invalid header name"); + return NJS_ERROR; } - http->buffer = b; + p++; } - for ( ;; ) { - b = http->buffer; - size = b->end - b->last; + p = value; + end = p + vlen; - n = c->recv(c, b->last, size); + while (p < end) { + if (*p == '\0') { + njs_vm_error(vm, "invalid header value"); + return NJS_ERROR; + } - if (n > 0) { - b->last += n; + p++; + } - rc = http->process(http); + if (headers->guard == GUARD_IMMUTABLE) { + njs_vm_error(vm, "cannot append to immutable object"); + return NJS_ERROR; + } - if (rc == NGX_ERROR) { - return; - } + ph = NULL; + part = &headers->header_list.part; + h = part->elts; - continue; - } + for (i = 0; /* void */; i++) { - if (n == NGX_AGAIN) { - if (ngx_handle_read_event(rev, 0) != NGX_OK) { - ngx_js_http_error(http, "read failed"); + if (i >= part->nelts) { + if (part->next == NULL) { + break; } - return; + part = part->next; + h = part->elts; + i = 0; } - if (n == NGX_ERROR) { - ngx_js_http_next(http); - return; + if (h[i].hash == 0) { + continue; } - break; + 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; + } } - http->done = 1; - - rc = http->process(http); - - if (rc == NGX_DONE) { - /* handler was called */ - return; + h = ngx_list_push(&headers->header_list); + if (h == NULL) { + njs_vm_memory_error(vm); + return NJS_ERROR; } - if (rc == NGX_AGAIN) { - ngx_js_http_error(http, "prematurely closed connection"); + 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_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) -{ - njs_int_t ret; - ngx_uint_t rc; - ngx_pool_t *pool; - njs_value_t *input, *init, *value, *headers; - ngx_js_request_t *orig; - njs_opaque_value_t lvalue; - - static const njs_str_t body_key = njs_str("body"); - static const njs_str_t cache_key = njs_str("cache"); - static const njs_str_t cred_key = njs_str("credentials"); - static const njs_str_t headers_key = njs_str("headers"); - static const njs_str_t mode_key = njs_str("mode"); - static const njs_str_t method_key = njs_str("method"); - - input = njs_arg(args, nargs, 1); - if (njs_value_is_undefined(input)) { - njs_vm_error(vm, "1st argument is required"); - return NJS_ERROR; - } - - /* - * set by ngx_memzero(): - * - * request->url.length = 0; - * request->body.length = 0; - * 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)); - - request->method = njs_str_value("GET"); - request->body = njs_str_value(""); - request->headers.guard = GUARD_REQUEST; - - pool = ngx_external_pool(vm, external); - - rc = ngx_list_init(&request->headers.header_list, pool, 4, - sizeof(ngx_js_tb_elt_t)); - if (rc != NGX_OK) { - njs_vm_memory_error(vm); - return NJS_ERROR; - } - - if (njs_value_is_string(input)) { - ret = ngx_js_string(vm, input, &request->url); - if (ret != NJS_OK) { - njs_vm_error(vm, "failed to convert url arg"); - return NJS_ERROR; - } - - } else { - orig = njs_vm_external(vm, ngx_http_js_fetch_request_proto_id, input); - if (orig == NULL) { - njs_vm_error(vm, "input is not string or a Request object"); - return NJS_ERROR; - } - - request->url = orig->url; - request->method = orig->method; - request->body = orig->body; - request->body_used = orig->body_used; - request->cache_mode = orig->cache_mode; - request->credentials = orig->credentials; - request->mode = orig->mode; - - ret = ngx_js_headers_inherit(vm, &request->headers, &orig->headers); - if (ret != NJS_OK) { - return NJS_ERROR; - } - } - - ngx_js_http_trim(&request->url.start, &request->url.length, 1); - - ngx_memzero(u, sizeof(ngx_url_t)); - - u->url.len = request->url.length; - u->url.data = request->url.start; - u->default_port = 80; - u->uri_part = 1; - u->no_resolve = 1; - - if (u->url.len > 7 - && njs_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 - && njs_strncasecmp(u->url.data, (u_char *) "https://", 8) == 0) + if (len == njs_strlen("Content-Type") + && ngx_strncasecmp(name, (u_char *) "Content-Type", len) == 0) { - u->url.len -= 8; - u->url.data += 8; - u->default_port = 443; -#endif - - } else { - njs_vm_error(vm, "unsupported URL schema (only http or https are" - " supported)"); - return NJS_ERROR; - } - - if (ngx_parse_url(pool, u) != NGX_OK) { - njs_vm_error(vm, "invalid url"); - return NJS_ERROR; - } - - init = njs_arg(args, nargs, 2); - - if (njs_value_is_object(init)) { - value = njs_vm_object_prop(vm, init, &method_key, &lvalue); - if (value != NULL && ngx_js_string(vm, value, &request->method) - != NGX_OK) - { - njs_vm_error(vm, "invalid Request method"); - return NJS_ERROR; - } - - ret = ngx_js_method_process(vm, request); - if (ret != NJS_OK) { - return NJS_ERROR; - } - - value = njs_vm_object_prop(vm, init, &cache_key, &lvalue); - if (value != NULL) { - ret = ngx_fetch_flag_set(vm, ngx_js_fetch_cache_modes, value, - "cache"); - if (ret == NJS_ERROR) { - return NJS_ERROR; - } - - request->cache_mode = ret; - } - - value = njs_vm_object_prop(vm, init, &cred_key, &lvalue); - if (value != NULL) { - ret = ngx_fetch_flag_set(vm, ngx_js_fetch_credentials, value, - "credentials"); - if (ret == NJS_ERROR) { - return NJS_ERROR; - } - - request->credentials = ret; - } - - value = njs_vm_object_prop(vm, init, &mode_key, &lvalue); - if (value != NULL) { - ret = ngx_fetch_flag_set(vm, ngx_js_fetch_modes, value, "mode"); - if (ret == NJS_ERROR) { - return NJS_ERROR; - } - - request->mode = ret; - } - - headers = njs_vm_object_prop(vm, init, &headers_key, &lvalue); - if (headers != NULL) { - if (!njs_value_is_object(headers)) { - njs_vm_error(vm, "Headers is not an object"); - return NJS_ERROR; - } - - /* - * There are no API to reset or destroy ngx_list, - * just allocating a new one. - */ - - ngx_memset(&request->headers, 0, sizeof(ngx_js_headers_t)); - request->headers.guard = GUARD_REQUEST; - - rc = ngx_list_init(&request->headers.header_list, pool, 4, - sizeof(ngx_js_tb_elt_t)); - if (rc != NGX_OK) { - njs_vm_memory_error(vm); - return NJS_ERROR; - } - - ret = ngx_js_headers_fill(vm, &request->headers, headers); - if (ret != NJS_OK) { - return NJS_ERROR; - } - } - - value = njs_vm_object_prop(vm, init, &body_key, &lvalue); - if (value != NULL) { - if (ngx_js_string(vm, value, &request->body) != NGX_OK) { - njs_vm_error(vm, "invalid Request body"); - return NJS_ERROR; - } - - if (request->headers.content_type == NULL - && njs_value_is_string(value)) - { - ret = ngx_js_headers_append(vm, &request->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; - } - } - } + headers->content_type = h; } return NJS_OK; } -njs_inline njs_int_t -ngx_js_http_whitespace(u_char c) -{ - switch (c) { - case 0x09: /* */ - case 0x0A: /* */ - case 0x0D: /* */ - case 0x20: /* */ - return 1; - - default: - return 0; - } -} - - -static void -ngx_js_http_trim(u_char **value, size_t *len, - njs_bool_t trim_c0_control_or_space) -{ - u_char *start, *end; - - start = *value; - end = start + *len; - - for ( ;; ) { - if (start == end) { - break; - } - - if (ngx_js_http_whitespace(*start) - || (trim_c0_control_or_space && *start <= ' ')) - { - start++; - continue; - } - - break; - } - - for ( ;; ) { - if (start == end) { - break; - } - - end--; - - if (ngx_js_http_whitespace(*end) - || (trim_c0_control_or_space && *end <= ' ')) - { - continue; - } - - end++; - break; - } - - *value = start; - *len = end - start; -} - - -static const uint32_t token_map[] = { - 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ - - /* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */ - 0x03ff6cfa, /* 0000 0011 1111 1111 0110 1100 1111 1010 */ - - /* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */ - 0xc7fffffe, /* 1100 0111 1111 1111 1111 1111 1111 1110 */ - - /* ~}| {zyx wvut srqp onml kjih gfed cba` */ - 0x57ffffff, /* 0101 0111 1111 1111 1111 1111 1111 1111 */ - - 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ - 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ - 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ - 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ -}; - - -njs_inline njs_bool_t -njs_is_token(uint32_t byte) -{ - return ((token_map[byte >> 5] & ((uint32_t) 1 << (byte & 0x1f))) != 0); -} - - -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_uint_t i; - ngx_js_tb_elt_t *h, **ph; - ngx_list_part_t *part; - - ngx_js_http_trim(&value, &vlen, 0); - - p = name; - end = p + len; - - while (p < end) { - if (!njs_is_token(*p)) { - njs_vm_error(vm, "invalid header name"); - return NJS_ERROR; - } - - p++; - } - - p = value; - end = p + vlen; - - while (p < end) { - if (*p == '\0') { - njs_vm_error(vm, "invalid header value"); - return NJS_ERROR; - } - - p++; - } - - if (headers->guard == GUARD_IMMUTABLE) { - njs_vm_error(vm, "cannot append to immutable object"); - return NJS_ERROR; - } - - 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 (len == h[i].key.len - && (njs_strncasecmp(name, h[i].key.data, len) == 0)) - { - ph = &h[i].next; - while (*ph) { ph = &(*ph)->next; } - break; - } - } - - h = ngx_list_push(&headers->header_list); - if (h == NULL) { - njs_vm_memory_error(vm); - 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; - - if (len == njs_strlen("Content-Type") - && ngx_strncasecmp(name, (u_char *) "Content-Type", len) == 0) - { - headers->content_type = h; - } - - return NJS_OK; -} - - -static ngx_int_t -ngx_js_http_process_status_line(ngx_js_http_t *http) -{ - ngx_int_t rc; - ngx_js_http_parse_t *hp; - - hp = &http->http_parse; - - rc = ngx_js_http_parse_status_line(hp, http->buffer); - - if (rc == NGX_OK) { - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, http->log, 0, "js fetch status %ui", - hp->code); - - http->response.code = hp->code; - http->response.status_text.start = hp->status_text; - http->response.status_text.length = hp->status_text_end - - hp->status_text; - http->process = ngx_js_http_process_headers; - - return http->process(http); - } - - if (rc == NGX_AGAIN) { - return NGX_AGAIN; - } - - /* rc == NGX_ERROR */ - - ngx_js_http_error(http, "invalid fetch status line"); - - return NGX_ERROR; -} - - -static ngx_int_t -ngx_js_http_process_headers(ngx_js_http_t *http) -{ - size_t len, vlen; - ngx_int_t rc; - njs_int_t ret; - ngx_js_http_parse_t *hp; - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, http->log, 0, - "js fetch process headers"); - - hp = &http->http_parse; - - if (http->response.headers.header_list.size == 0) { - rc = ngx_list_init(&http->response.headers.header_list, http->pool, 4, - sizeof(ngx_js_tb_elt_t)); - if (rc != NGX_OK) { - ngx_js_http_error(http, "alloc failed"); - return NGX_ERROR; - } - } - - for ( ;; ) { - rc = ngx_js_http_parse_header_line(hp, http->buffer); - - if (rc == NGX_OK) { - len = hp->header_name_end - hp->header_name_start; - vlen = hp->header_end - hp->header_start; - - ret = ngx_js_headers_append(http->vm, &http->response.headers, - hp->header_name_start, len, - hp->header_start, vlen); - - if (ret == NJS_ERROR) { - ngx_js_http_error(http, "cannot add respose header"); - return NGX_ERROR; - } - - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, http->log, 0, - "js fetch header \"%*s: %*s\"", - len, hp->header_name_start, vlen, hp->header_start); - - if (len == njs_strlen("Transfer-Encoding") - && vlen == njs_strlen("chunked") - && ngx_strncasecmp(hp->header_name_start, - (u_char *) "Transfer-Encoding", len) == 0 - && ngx_strncasecmp(hp->header_start, (u_char *) "chunked", - vlen) == 0) - { - hp->chunked = 1; - } - - if (len == njs_strlen("Content-Length") - && ngx_strncasecmp(hp->header_name_start, - (u_char *) "Content-Length", len) == 0) - { - hp->content_length_n = ngx_atoof(hp->header_start, vlen); - if (hp->content_length_n == NGX_ERROR) { - ngx_js_http_error(http, "invalid fetch content length"); - return NGX_ERROR; - } - - if (!http->header_only - && hp->content_length_n - > (off_t) http->max_response_body_size) - { - ngx_js_http_error(http, - "fetch content length is too large"); - return NGX_ERROR; - } - } - - continue; - } - - if (rc == NGX_DONE) { - http->response.headers.guard = GUARD_IMMUTABLE; - break; - } - - if (rc == NGX_AGAIN) { - return NGX_AGAIN; - } - - /* rc == NGX_ERROR */ - - ngx_js_http_error(http, "invalid fetch header"); - - return NGX_ERROR; - } - - njs_chb_destroy(&http->chain); - - NJS_CHB_MP_INIT(&http->response.chain, njs_vm_memory_pool(http->vm)); - - http->process = ngx_js_http_process_body; - - return http->process(http); -} - - -static ngx_int_t -ngx_js_http_process_body(ngx_js_http_t *http) -{ - ssize_t size, chsize, need; - ngx_int_t rc; - njs_int_t ret; - ngx_buf_t *b; - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, http->log, 0, - "js fetch process body done:%ui", (ngx_uint_t) http->done); - - if (http->done) { - size = njs_chb_size(&http->response.chain); - if (size < 0) { - ngx_js_http_error(http, "memory error"); - return NGX_ERROR; - } - - if (!http->header_only - && http->http_parse.chunked - && http->http_parse.content_length_n == -1) - { - ngx_js_http_error(http, "invalid fetch chunked response"); - return NGX_ERROR; - } - - if (http->header_only - || http->http_parse.content_length_n == -1 - || size == http->http_parse.content_length_n) - { - ret = njs_vm_external_create(http->vm, - njs_value_arg(&http->response_value), - ngx_http_js_fetch_response_proto_id, - &http->response, 0); - if (ret != NJS_OK) { - ngx_js_http_error(http, "fetch response creation failed"); - return NGX_ERROR; - } - - ngx_js_http_fetch_done(http, &http->response_value, NJS_OK); - return NGX_DONE; - } - - if (size < http->http_parse.content_length_n) { - return NGX_AGAIN; - } - - ngx_js_http_error(http, "fetch trailing data"); - return NGX_ERROR; - } - - b = http->buffer; - - if (http->http_parse.chunked) { - rc = ngx_js_http_parse_chunked(&http->http_chunk_parse, b, - &http->response.chain); - if (rc == NGX_ERROR) { - ngx_js_http_error(http, "invalid fetch chunked response"); - return NGX_ERROR; - } - - size = njs_chb_size(&http->response.chain); - - if (rc == NGX_OK) { - http->http_parse.content_length_n = size; - } - - if (size > http->max_response_body_size * 10) { - ngx_js_http_error(http, "very large fetch chunked response"); - return NGX_ERROR; - } - - b->pos = http->http_chunk_parse.pos; - - } else { - size = njs_chb_size(&http->response.chain); - - if (http->header_only) { - need = 0; - - } else if (http->http_parse.content_length_n == -1) { - need = http->max_response_body_size - size; - - } else { - need = http->http_parse.content_length_n - size; - } - - chsize = ngx_min(need, b->last - b->pos); - - if (size + chsize > http->max_response_body_size) { - ngx_js_http_error(http, "fetch response body is too large"); - return NGX_ERROR; - } - - if (chsize > 0) { - njs_chb_append(&http->response.chain, b->pos, chsize); - b->pos += chsize; - } - - rc = (need > chsize) ? NGX_AGAIN : NGX_DONE; - } - - if (b->pos == b->end) { - if (http->chunk == NULL) { - b = ngx_create_temp_buf(http->pool, http->buffer_size); - if (b == NULL) { - ngx_js_http_error(http, "memory error"); - return NGX_ERROR; - } - - http->buffer = b; - http->chunk = b; - - } else { - b->last = b->start; - b->pos = b->start; - } - } - - return rc; -} - - -static ngx_int_t -ngx_js_http_parse_status_line(ngx_js_http_parse_t *hp, ngx_buf_t *b) -{ - u_char ch; - u_char *p; - enum { - sw_start = 0, - sw_H, - sw_HT, - sw_HTT, - sw_HTTP, - sw_first_major_digit, - sw_major_digit, - sw_first_minor_digit, - sw_minor_digit, - sw_status, - sw_space_after_status, - sw_status_text, - sw_almost_done - } state; - - state = hp->state; - - for (p = b->pos; p < b->last; p++) { - ch = *p; - - switch (state) { - - /* "HTTP/" */ - case sw_start: - switch (ch) { - case 'H': - state = sw_H; - break; - default: - return NGX_ERROR; - } - break; - - case sw_H: - switch (ch) { - case 'T': - state = sw_HT; - break; - default: - return NGX_ERROR; - } - break; - - case sw_HT: - switch (ch) { - case 'T': - state = sw_HTT; - break; - default: - return NGX_ERROR; - } - break; - - case sw_HTT: - switch (ch) { - case 'P': - state = sw_HTTP; - break; - default: - return NGX_ERROR; - } - break; - - case sw_HTTP: - switch (ch) { - case '/': - state = sw_first_major_digit; - break; - default: - return NGX_ERROR; - } - break; - - /* the first digit of major HTTP version */ - case sw_first_major_digit: - if (ch < '1' || ch > '9') { - return NGX_ERROR; - } - - state = sw_major_digit; - break; - - /* the major HTTP version or dot */ - case sw_major_digit: - if (ch == '.') { - state = sw_first_minor_digit; - break; - } - - if (ch < '0' || ch > '9') { - return NGX_ERROR; - } - - break; - - /* the first digit of minor HTTP version */ - case sw_first_minor_digit: - if (ch < '0' || ch > '9') { - return NGX_ERROR; - } - - state = sw_minor_digit; - break; - - /* the minor HTTP version or the end of the request line */ - case sw_minor_digit: - if (ch == ' ') { - state = sw_status; - break; - } - - if (ch < '0' || ch > '9') { - return NGX_ERROR; - } - - break; - - /* HTTP status code */ - case sw_status: - if (ch == ' ') { - break; - } - - if (ch < '0' || ch > '9') { - return NGX_ERROR; - } - - hp->code = hp->code * 10 + (ch - '0'); - - if (++hp->count == 3) { - state = sw_space_after_status; - } - - break; - - /* space or end of line */ - case sw_space_after_status: - switch (ch) { - case ' ': - state = sw_status_text; - break; - case '.': /* IIS may send 403.1, 403.2, etc */ - state = sw_status_text; - break; - case CR: - break; - case LF: - goto done; - default: - return NGX_ERROR; - } - break; - - /* any text until end of line */ - case sw_status_text: - switch (ch) { - case CR: - hp->status_text_end = p; - state = sw_almost_done; - break; - case LF: - hp->status_text_end = p; - goto done; - } - - if (hp->status_text == NULL) { - hp->status_text = p; - } - - break; - - /* end of status line */ - case sw_almost_done: - switch (ch) { - case LF: - goto done; - default: - return NGX_ERROR; - } - } - } - - b->pos = p; - hp->state = state; - - return NGX_AGAIN; - -done: - - b->pos = p + 1; - hp->state = sw_start; - - return NGX_OK; -} - - -static ngx_int_t -ngx_js_http_parse_header_line(ngx_js_http_parse_t *hp, ngx_buf_t *b) -{ - u_char c, ch, *p; - enum { - sw_start = 0, - sw_name, - sw_space_before_value, - sw_value, - sw_space_after_value, - sw_almost_done, - sw_header_almost_done - } state; - - state = hp->state; - - for (p = b->pos; p < b->last; p++) { - ch = *p; - - switch (state) { - - /* first char */ - case sw_start: - - switch (ch) { - case CR: - hp->header_end = p; - state = sw_header_almost_done; - break; - case LF: - hp->header_end = p; - goto header_done; - default: - state = sw_name; - hp->header_name_start = p; - - c = (u_char) (ch | 0x20); - if (c >= 'a' && c <= 'z') { - break; - } - - if (ch >= '0' && ch <= '9') { - break; - } - - return NGX_ERROR; - } - break; - - /* header name */ - case sw_name: - c = (u_char) (ch | 0x20); - if (c >= 'a' && c <= 'z') { - break; - } - - if (ch == ':') { - hp->header_name_end = p; - state = sw_space_before_value; - break; - } - - if (ch == '-' || ch == '_') { - break; - } - - if (ch >= '0' && ch <= '9') { - break; - } - - if (ch == CR) { - hp->header_name_end = p; - hp->header_start = p; - hp->header_end = p; - state = sw_almost_done; - break; - } - - if (ch == LF) { - hp->header_name_end = p; - hp->header_start = p; - hp->header_end = p; - goto done; - } - - return NGX_ERROR; - - /* space* before header value */ - case sw_space_before_value: - switch (ch) { - case ' ': - break; - case CR: - hp->header_start = p; - hp->header_end = p; - state = sw_almost_done; - break; - case LF: - hp->header_start = p; - hp->header_end = p; - goto done; - default: - hp->header_start = p; - state = sw_value; - break; - } - break; - - /* header value */ - case sw_value: - switch (ch) { - case ' ': - hp->header_end = p; - state = sw_space_after_value; - break; - case CR: - hp->header_end = p; - state = sw_almost_done; - break; - case LF: - hp->header_end = p; - goto done; - } - break; - - /* space* before end of header line */ - case sw_space_after_value: - switch (ch) { - case ' ': - break; - case CR: - state = sw_almost_done; - break; - case LF: - goto done; - default: - state = sw_value; - break; - } - break; - - /* end of header line */ - case sw_almost_done: - switch (ch) { - case LF: - goto done; - default: - return NGX_ERROR; - } - - /* end of header */ - case sw_header_almost_done: - switch (ch) { - case LF: - goto header_done; - default: - return NGX_ERROR; - } - } - } - - b->pos = p; - hp->state = state; - - return NGX_AGAIN; - -done: - - b->pos = p + 1; - hp->state = sw_start; - - return NGX_OK; - -header_done: - - b->pos = p + 1; - hp->state = sw_start; - - return NGX_DONE; -} - - -#define \ -ngx_size_is_sufficient(cs) \ - (cs < ((__typeof__(cs)) 1 << (sizeof(cs) * 8 - 4))) - - -#define NGX_JS_HTTP_CHUNK_MIDDLE 0 -#define NGX_JS_HTTP_CHUNK_ON_BORDER 1 -#define NGX_JS_HTTP_CHUNK_END 2 - - -static ngx_int_t -ngx_js_http_chunk_buffer(ngx_js_http_chunk_parse_t *hcp, ngx_buf_t *b, - njs_chb_t *chain) -{ - size_t size; - - size = b->last - hcp->pos; - - if (hcp->chunk_size < size) { - njs_chb_append(chain, hcp->pos, hcp->chunk_size); - hcp->pos += hcp->chunk_size; - - return NGX_JS_HTTP_CHUNK_END; - } - - njs_chb_append(chain, hcp->pos, size); - hcp->pos += size; - - hcp->chunk_size -= size; - - if (hcp->chunk_size == 0) { - return NGX_JS_HTTP_CHUNK_ON_BORDER; - } - - return NGX_JS_HTTP_CHUNK_MIDDLE; -} - - -static ngx_int_t -ngx_js_http_parse_chunked(ngx_js_http_chunk_parse_t *hcp, - ngx_buf_t *b, njs_chb_t *chain) -{ - u_char c, ch; - ngx_int_t rc; - - enum { - sw_start = 0, - sw_chunk_size, - sw_chunk_size_linefeed, - sw_chunk_end_newline, - sw_chunk_end_linefeed, - sw_chunk, - } state; - - state = hcp->state; - - hcp->pos = b->pos; - - while (hcp->pos < b->last) { - /* - * The sw_chunk state is tested outside the switch - * to preserve hcp->pos and to not touch memory. - */ - if (state == sw_chunk) { - rc = ngx_js_http_chunk_buffer(hcp, b, chain); - if (rc == NGX_ERROR) { - return rc; - } - - if (rc == NGX_JS_HTTP_CHUNK_MIDDLE) { - break; - } - - state = sw_chunk_end_newline; - - if (rc == NGX_JS_HTTP_CHUNK_ON_BORDER) { - break; - } - - /* rc == NGX_JS_HTTP_CHUNK_END */ - } - - ch = *hcp->pos++; - - switch (state) { - - case sw_start: - state = sw_chunk_size; - - c = ch - '0'; - - if (c <= 9) { - hcp->chunk_size = c; - continue; - } - - c = (ch | 0x20) - 'a'; - - if (c <= 5) { - hcp->chunk_size = 0x0A + c; - continue; - } - - return NGX_ERROR; - - case sw_chunk_size: - - c = ch - '0'; - - if (c > 9) { - c = (ch | 0x20) - 'a'; - - if (c <= 5) { - c += 0x0A; - - } else if (ch == '\r') { - state = sw_chunk_size_linefeed; - continue; - - } else { - return NGX_ERROR; - } - } - - if (ngx_size_is_sufficient(hcp->chunk_size)) { - hcp->chunk_size = (hcp->chunk_size << 4) + c; - continue; - } - - return NGX_ERROR; - - case sw_chunk_size_linefeed: - if (ch == '\n') { - - if (hcp->chunk_size != 0) { - state = sw_chunk; - continue; - } - - hcp->last = 1; - state = sw_chunk_end_newline; - continue; - } - - return NGX_ERROR; - - case sw_chunk_end_newline: - if (ch == '\r') { - state = sw_chunk_end_linefeed; - continue; - } - - return NGX_ERROR; - - case sw_chunk_end_linefeed: - if (ch == '\n') { - - if (!hcp->last) { - state = sw_start; - continue; - } - - return NGX_OK; - } - - return NGX_ERROR; - - case sw_chunk: - /* - * This state is processed before the switch. - * It added here just to suppress a warning. - */ - continue; - } - } - - hcp->state = state; - - return NGX_AGAIN; -} - - -static void -ngx_js_http_dummy_handler(ngx_event_t *ev) -{ - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "js fetch dummy handler"); -} - - static njs_int_t ngx_headers_js_get(njs_vm_t *vm, njs_value_t *value, njs_str_t *name, njs_value_t *retval, njs_bool_t as_array) diff --git a/nginx/ngx_js_http.c b/nginx/ngx_js_http.c new file mode 100644 index 000000000..917a28215 --- /dev/null +++ b/nginx/ngx_js_http.c @@ -0,0 +1,1425 @@ + +/* + * Copyright (C) Dmitry Volyntsev + * Copyright (C) hongzhidao + * Copyright (C) Antoine Bonavita + * Copyright (C) NGINX, Inc. + */ + + +#include +#include +#include +#include +#include "ngx_js.h" +#include "ngx_js_http.h" + + +static void ngx_js_http_resolve_handler(ngx_resolver_ctx_t *ctx); +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 void ngx_js_http_dummy_handler(ngx_event_t *ev); + +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); +static ngx_int_t ngx_js_http_parse_status_line(ngx_js_http_parse_t *hp, + ngx_buf_t *b); +static ngx_int_t ngx_js_http_parse_header_line(ngx_js_http_parse_t *hp, + ngx_buf_t *b); +static ngx_int_t ngx_js_http_parse_chunked(ngx_js_http_chunk_parse_t *hcp, + ngx_buf_t *b, njs_chb_t *chain); + +#if (NGX_SSL) +static void ngx_js_http_ssl_init_connection(ngx_js_http_t *http); +static void ngx_js_http_ssl_handshake_handler(ngx_connection_t *c); +static void ngx_js_http_ssl_handshake(ngx_js_http_t *http); +static njs_int_t ngx_js_http_ssl_name(ngx_js_http_t *http); +#endif + + +static void +ngx_js_http_error(ngx_js_http_t *http, const char *fmt, ...) +{ + u_char *p, *end; + va_list args; + u_char err[NGX_MAX_ERROR_STR]; + + end = err + NGX_MAX_ERROR_STR - 1; + + va_start(args, fmt); + p = njs_vsprintf(err, end, fmt, args); + *p = '\0'; + va_end(args); + + http->error_handler(http, (const char *) err); +} + + +ngx_resolver_ctx_t * +ngx_js_http_resolve(ngx_js_http_t *http, ngx_resolver_t *r, ngx_str_t *host, + in_port_t port, ngx_msec_t timeout) +{ + ngx_int_t ret; + ngx_resolver_ctx_t *ctx; + + ctx = ngx_resolve_start(r, NULL); + if (ctx == NULL) { + return NULL; + } + + if (ctx == NGX_NO_RESOLVER) { + return ctx; + } + + http->ctx = ctx; + http->port = port; + + ctx->name = *host; + ctx->handler = ngx_js_http_resolve_handler; + ctx->data = http; + ctx->timeout = timeout; + + ret = ngx_resolve_name(ctx); + if (ret != NGX_OK) { + http->ctx = NULL; + return NULL; + } + + return ctx; +} + + +static void +ngx_js_http_resolve_handler(ngx_resolver_ctx_t *ctx) +{ + u_char *p; + size_t len; + socklen_t socklen; + ngx_uint_t i; + ngx_js_http_t *http; + struct sockaddr *sockaddr; + + http = ctx->data; + + if (ctx->state) { + ngx_js_http_error(http, "\"%V\" could not be resolved (%i: %s)", + &ctx->name, ctx->state, + ngx_resolver_strerror(ctx->state)); + return; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, http->log, 0, + "http resolved: \"%V\"", &ctx->name); + +#if (NGX_DEBUG) + { + u_char text[NGX_SOCKADDR_STRLEN]; + ngx_str_t addr; + ngx_uint_t i; + + addr.data = text; + + for (i = 0; i < ctx->naddrs; i++) { + addr.len = ngx_sock_ntop(ctx->addrs[i].sockaddr, ctx->addrs[i].socklen, + text, NGX_SOCKADDR_STRLEN, 0); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, http->log, 0, + "name was resolved to \"%V\"", &addr); + } + } +#endif + + http->naddrs = ctx->naddrs; + http->addrs = ngx_pcalloc(http->pool, http->naddrs * sizeof(ngx_addr_t)); + + if (http->addrs == NULL) { + goto failed; + } + + for (i = 0; i < ctx->naddrs; i++) { + socklen = ctx->addrs[i].socklen; + + sockaddr = ngx_palloc(http->pool, socklen); + if (sockaddr == NULL) { + goto failed; + } + + ngx_memcpy(sockaddr, ctx->addrs[i].sockaddr, socklen); + ngx_inet_set_port(sockaddr, http->port); + + http->addrs[i].sockaddr = sockaddr; + http->addrs[i].socklen = socklen; + + p = ngx_pnalloc(http->pool, NGX_SOCKADDR_STRLEN); + if (p == NULL) { + goto failed; + } + + len = ngx_sock_ntop(sockaddr, socklen, p, NGX_SOCKADDR_STRLEN, 1); + http->addrs[i].name.len = len; + http->addrs[i].name.data = p; + } + + ngx_js_http_resolve_done(http); + + ngx_js_http_connect(http); + + return; + +failed: + + ngx_js_http_error(http, "memory error"); +} + + +static void +ngx_js_http_close_connection(ngx_connection_t *c) +{ + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "js http close connection: %d", c->fd); + +#if (NGX_SSL) + if (c->ssl) { + c->ssl->no_wait_shutdown = 1; + + if (ngx_ssl_shutdown(c) == NGX_AGAIN) { + c->ssl->handler = ngx_js_http_close_connection; + return; + } + } +#endif + + c->destroyed = 1; + + ngx_close_connection(c); +} + + +void +ngx_js_http_resolve_done(ngx_js_http_t *http) +{ + if (http->ctx != NULL) { + ngx_resolve_name_done(http->ctx); + http->ctx = NULL; + } +} + + +void +ngx_js_http_close_peer(ngx_js_http_t *http) +{ + if (http->peer.connection != NULL) { + ngx_js_http_close_connection(http->peer.connection); + http->peer.connection = NULL; + } +} + + +void +ngx_js_http_connect(ngx_js_http_t *http) +{ + ngx_int_t rc; + ngx_addr_t *addr; + + addr = &http->addrs[http->naddr]; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, http->log, 0, + "js http connect %ui/%ui", http->naddr, http->naddrs); + + http->peer.sockaddr = addr->sockaddr; + http->peer.socklen = addr->socklen; + http->peer.name = &addr->name; + http->peer.get = ngx_event_get_peer; + http->peer.log = http->log; + http->peer.log_error = NGX_ERROR_ERR; + + rc = ngx_event_connect_peer(&http->peer); + + if (rc == NGX_ERROR) { + ngx_js_http_error(http, "connect failed"); + return; + } + + if (rc == NGX_BUSY || rc == NGX_DECLINED) { + ngx_js_http_next(http); + return; + } + + http->peer.connection->data = http; + http->peer.connection->pool = http->pool; + + http->peer.connection->write->handler = ngx_js_http_write_handler; + http->peer.connection->read->handler = ngx_js_http_read_handler; + + http->process = ngx_js_http_process_status_line; + + ngx_add_timer(http->peer.connection->read, http->timeout); + ngx_add_timer(http->peer.connection->write, http->timeout); + +#if (NGX_SSL) + if (http->ssl != NULL && http->peer.connection->ssl == NULL) { + ngx_js_http_ssl_init_connection(http); + return; + } +#endif + + if (rc == NGX_OK) { + ngx_js_http_write_handler(http->peer.connection->write); + } +} + + +#if (NGX_SSL) + +static void +ngx_js_http_ssl_init_connection(ngx_js_http_t *http) +{ + ngx_int_t rc; + ngx_connection_t *c; + + c = http->peer.connection; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, http->log, 0, + "js http secure connect %ui/%ui", http->naddr, + http->naddrs); + + if (ngx_ssl_create_connection(http->ssl, c, NGX_SSL_BUFFER|NGX_SSL_CLIENT) + != NGX_OK) + { + ngx_js_http_error(http, "failed to create ssl connection"); + return; + } + + c->sendfile = 0; + + if (ngx_js_http_ssl_name(http) != NGX_OK) { + ngx_js_http_error(http, "failed to create ssl connection"); + return; + } + + c->log->action = "SSL handshaking to http target"; + + rc = ngx_ssl_handshake(c); + + if (rc == NGX_AGAIN) { + c->data = http; + c->ssl->handler = ngx_js_http_ssl_handshake_handler; + return; + } + + ngx_js_http_ssl_handshake(http); +} + + +static void +ngx_js_http_ssl_handshake_handler(ngx_connection_t *c) +{ + ngx_js_http_t *http; + + http = c->data; + + http->peer.connection->write->handler = ngx_js_http_write_handler; + http->peer.connection->read->handler = ngx_js_http_read_handler; + + ngx_js_http_ssl_handshake(http); +} + + +static void +ngx_js_http_ssl_handshake(ngx_js_http_t *http) +{ + long rc; + ngx_connection_t *c; + + c = http->peer.connection; + + if (c->ssl->handshaked) { + if (http->ssl_verify) { + rc = SSL_get_verify_result(c->ssl->connection); + + if (rc != X509_V_OK) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "js http SSL certificate verify error: (%l:%s)", + rc, X509_verify_cert_error_string(rc)); + goto failed; + } + + if (ngx_ssl_check_host(c, &http->tls_name) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "js http SSL certificate does not match \"%V\"", + &http->tls_name); + goto failed; + } + } + + c->write->handler = ngx_js_http_write_handler; + c->read->handler = ngx_js_http_read_handler; + + if (c->read->ready) { + ngx_post_event(c->read, &ngx_posted_events); + } + + http->process = ngx_js_http_process_status_line; + ngx_js_http_write_handler(c->write); + + return; + } + +failed: + + ngx_js_http_next(http); +} + + +static njs_int_t +ngx_js_http_ssl_name(ngx_js_http_t *http) +{ +#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + u_char *p; + + /* as per RFC 6066, literal IPv4 and IPv6 addresses are not permitted */ + ngx_str_t *name = &http->tls_name; + + if (name->len == 0 || *name->data == '[') { + goto done; + } + + if (ngx_inet_addr(name->data, name->len) != INADDR_NONE) { + goto done; + } + + /* + * SSL_set_tlsext_host_name() needs a null-terminated string, + * hence we explicitly null-terminate name here + */ + + p = ngx_pnalloc(http->pool, name->len + 1); + if (p == NULL) { + return NGX_ERROR; + } + + (void) ngx_cpystrn(p, name->data, name->len + 1); + + name->data = p; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, http->log, 0, + "js http SSL server name: \"%s\"", name->data); + + if (SSL_set_tlsext_host_name(http->peer.connection->ssl->connection, + (char *) name->data) + == 0) + { + ngx_ssl_error(NGX_LOG_ERR, http->log, 0, + "SSL_set_tlsext_host_name(\"%s\") failed", name->data); + return NGX_ERROR; + } + +#endif +done: + + return NJS_OK; +} + +#endif + + +static void +ngx_js_http_next(ngx_js_http_t *http) +{ + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, http->log, 0, "js http next addr"); + + if (++http->naddr >= http->naddrs) { + ngx_js_http_error(http, "connect failed"); + return; + } + + if (http->peer.connection != NULL) { + ngx_js_http_close_connection(http->peer.connection); + http->peer.connection = NULL; + } + + http->buffer = NULL; + + ngx_js_http_connect(http); +} + + +static void +ngx_js_http_write_handler(ngx_event_t *wev) +{ + ssize_t n, size; + ngx_buf_t *b; + ngx_js_http_t *http; + ngx_connection_t *c; + + c = wev->data; + http = c->data; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, wev->log, 0, "js http write handler"); + + if (wev->timedout) { + ngx_js_http_error(http, "write timed out"); + return; + } + +#if (NGX_SSL) + if (http->ssl != NULL && http->peer.connection->ssl == NULL) { + ngx_js_http_ssl_init_connection(http); + return; + } +#endif + + b = http->buffer; + + if (b == NULL) { + size = njs_chb_size(&http->chain); + if (size < 0) { + ngx_js_http_error(http, "memory error"); + return; + } + + b = ngx_create_temp_buf(http->pool, size); + if (b == NULL) { + ngx_js_http_error(http, "memory error"); + return; + } + + njs_chb_join_to(&http->chain, b->last); + b->last += size; + + http->buffer = b; + } + + size = b->last - b->pos; + + n = c->send(c, b->pos, size); + + if (n == NGX_ERROR) { + ngx_js_http_next(http); + return; + } + + if (n > 0) { + b->pos += n; + + if (n == size) { + wev->handler = ngx_js_http_dummy_handler; + + http->buffer = NULL; + + if (wev->timer_set) { + ngx_del_timer(wev); + } + + if (ngx_handle_write_event(wev, 0) != NGX_OK) { + ngx_js_http_error(http, "write failed"); + } + + return; + } + } + + if (!wev->timer_set) { + ngx_add_timer(wev, http->timeout); + } +} + + +static void +ngx_js_http_read_handler(ngx_event_t *rev) +{ + ssize_t n, size; + ngx_int_t rc; + ngx_buf_t *b; + ngx_js_http_t *http; + ngx_connection_t *c; + + c = rev->data; + http = c->data; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, rev->log, 0, "js http read handler"); + + if (rev->timedout) { + ngx_js_http_error(http, "read timed out"); + return; + } + + if (http->buffer == NULL) { + b = ngx_create_temp_buf(http->pool, http->buffer_size); + if (b == NULL) { + ngx_js_http_error(http, "memory error"); + return; + } + + http->buffer = b; + } + + for ( ;; ) { + b = http->buffer; + size = b->end - b->last; + + n = c->recv(c, b->last, size); + + if (n > 0) { + b->last += n; + + rc = http->process(http); + + if (rc == NGX_ERROR) { + return; + } + + continue; + } + + if (n == NGX_AGAIN) { + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_js_http_error(http, "read failed"); + } + + return; + } + + if (n == NGX_ERROR) { + ngx_js_http_next(http); + return; + } + + break; + } + + http->done = 1; + + rc = http->process(http); + + if (rc == NGX_DONE) { + /* handler was called */ + return; + } + + if (rc == NGX_AGAIN) { + ngx_js_http_error(http, "prematurely closed connection"); + } +} + + +static void +ngx_js_http_dummy_handler(ngx_event_t *ev) +{ + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "js http dummy handler"); +} + + +static ngx_int_t +ngx_js_http_process_status_line(ngx_js_http_t *http) +{ + ngx_int_t rc; + ngx_js_http_parse_t *hp; + + hp = &http->http_parse; + + rc = ngx_js_http_parse_status_line(hp, http->buffer); + + if (rc == NGX_OK) { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, http->log, 0, "js http status %ui", + hp->code); + + http->response.code = hp->code; + http->response.status_text.start = hp->status_text; + http->response.status_text.length = hp->status_text_end + - hp->status_text; + http->process = ngx_js_http_process_headers; + + return http->process(http); + } + + if (rc == NGX_AGAIN) { + return NGX_AGAIN; + } + + /* rc == NGX_ERROR */ + + ngx_js_http_error(http, "invalid http status line"); + + return NGX_ERROR; +} + + +static ngx_int_t +ngx_js_http_process_headers(ngx_js_http_t *http) +{ + size_t len, vlen; + ngx_int_t rc; + njs_int_t ret; + ngx_js_http_parse_t *hp; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, http->log, 0, + "js http process headers"); + + hp = &http->http_parse; + + if (http->response.headers.header_list.size == 0) { + rc = ngx_list_init(&http->response.headers.header_list, http->pool, 4, + sizeof(ngx_js_tb_elt_t)); + if (rc != NGX_OK) { + ngx_js_http_error(http, "alloc failed"); + return NGX_ERROR; + } + } + + for ( ;; ) { + rc = ngx_js_http_parse_header_line(hp, http->buffer); + + if (rc == NGX_OK) { + len = hp->header_name_end - hp->header_name_start; + vlen = hp->header_end - hp->header_start; + + ret = http->append_headers(http, &http->response.headers, + hp->header_name_start, len, + hp->header_start, vlen); + + if (ret == NJS_ERROR) { + ngx_js_http_error(http, "cannot add respose header"); + return NGX_ERROR; + } + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, http->log, 0, + "js http header \"%*s: %*s\"", + len, hp->header_name_start, vlen, hp->header_start); + + if (len == njs_strlen("Transfer-Encoding") + && vlen == njs_strlen("chunked") + && ngx_strncasecmp(hp->header_name_start, + (u_char *) "Transfer-Encoding", len) == 0 + && ngx_strncasecmp(hp->header_start, (u_char *) "chunked", + vlen) == 0) + { + hp->chunked = 1; + } + + if (len == njs_strlen("Content-Length") + && ngx_strncasecmp(hp->header_name_start, + (u_char *) "Content-Length", len) == 0) + { + hp->content_length_n = ngx_atoof(hp->header_start, vlen); + if (hp->content_length_n == NGX_ERROR) { + ngx_js_http_error(http, "invalid http content length"); + return NGX_ERROR; + } + + if (!http->header_only + && hp->content_length_n + > (off_t) http->max_response_body_size) + { + ngx_js_http_error(http, + "http content length is too large"); + return NGX_ERROR; + } + } + + continue; + } + + if (rc == NGX_DONE) { + http->response.headers.guard = GUARD_IMMUTABLE; + break; + } + + if (rc == NGX_AGAIN) { + return NGX_AGAIN; + } + + /* rc == NGX_ERROR */ + + ngx_js_http_error(http, "invalid http header"); + + return NGX_ERROR; + } + + njs_chb_destroy(&http->chain); + + http->process = ngx_js_http_process_body; + + return http->process(http); +} + + +static ngx_int_t +ngx_js_http_process_body(ngx_js_http_t *http) +{ + ssize_t size, chsize, need; + ngx_int_t rc; + ngx_buf_t *b; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, http->log, 0, + "js http process body done:%ui", (ngx_uint_t) http->done); + + if (http->done) { + size = njs_chb_size(&http->response.chain); + if (size < 0) { + ngx_js_http_error(http, "memory error"); + return NGX_ERROR; + } + + if (!http->header_only + && http->http_parse.chunked + && http->http_parse.content_length_n == -1) + { + ngx_js_http_error(http, "invalid http chunked response"); + return NGX_ERROR; + } + + if (http->header_only + || http->http_parse.content_length_n == -1 + || size == http->http_parse.content_length_n) + { + http->ready_handler(http); + return NGX_DONE; + } + + if (size < http->http_parse.content_length_n) { + return NGX_AGAIN; + } + + ngx_js_http_error(http, "http trailing data"); + return NGX_ERROR; + } + + b = http->buffer; + + if (http->http_parse.chunked) { + rc = ngx_js_http_parse_chunked(&http->http_chunk_parse, b, + &http->response.chain); + if (rc == NGX_ERROR) { + ngx_js_http_error(http, "invalid http chunked response"); + return NGX_ERROR; + } + + size = njs_chb_size(&http->response.chain); + + if (rc == NGX_OK) { + http->http_parse.content_length_n = size; + } + + if (size > http->max_response_body_size * 10) { + ngx_js_http_error(http, "very large http chunked response"); + return NGX_ERROR; + } + + b->pos = http->http_chunk_parse.pos; + + } else { + size = njs_chb_size(&http->response.chain); + + if (http->header_only) { + need = 0; + + } else if (http->http_parse.content_length_n == -1) { + need = http->max_response_body_size - size; + + } else { + need = http->http_parse.content_length_n - size; + } + + chsize = ngx_min(need, b->last - b->pos); + + if (size + chsize > http->max_response_body_size) { + ngx_js_http_error(http, "http response body is too large"); + return NGX_ERROR; + } + + if (chsize > 0) { + njs_chb_append(&http->response.chain, b->pos, chsize); + b->pos += chsize; + } + + rc = (need > chsize) ? NGX_AGAIN : NGX_DONE; + } + + if (b->pos == b->end) { + if (http->chunk == NULL) { + b = ngx_create_temp_buf(http->pool, http->buffer_size); + if (b == NULL) { + ngx_js_http_error(http, "memory error"); + return NGX_ERROR; + } + + http->buffer = b; + http->chunk = b; + + } else { + b->last = b->start; + b->pos = b->start; + } + } + + return rc; +} + + +static ngx_int_t +ngx_js_http_parse_status_line(ngx_js_http_parse_t *hp, ngx_buf_t *b) +{ + u_char ch; + u_char *p; + enum { + sw_start = 0, + sw_H, + sw_HT, + sw_HTT, + sw_HTTP, + sw_first_major_digit, + sw_major_digit, + sw_first_minor_digit, + sw_minor_digit, + sw_status, + sw_space_after_status, + sw_status_text, + sw_almost_done + } state; + + state = hp->state; + + for (p = b->pos; p < b->last; p++) { + ch = *p; + + switch (state) { + + /* "HTTP/" */ + case sw_start: + switch (ch) { + case 'H': + state = sw_H; + break; + default: + return NGX_ERROR; + } + break; + + case sw_H: + switch (ch) { + case 'T': + state = sw_HT; + break; + default: + return NGX_ERROR; + } + break; + + case sw_HT: + switch (ch) { + case 'T': + state = sw_HTT; + break; + default: + return NGX_ERROR; + } + break; + + case sw_HTT: + switch (ch) { + case 'P': + state = sw_HTTP; + break; + default: + return NGX_ERROR; + } + break; + + case sw_HTTP: + switch (ch) { + case '/': + state = sw_first_major_digit; + break; + default: + return NGX_ERROR; + } + break; + + /* the first digit of major HTTP version */ + case sw_first_major_digit: + if (ch < '1' || ch > '9') { + return NGX_ERROR; + } + + state = sw_major_digit; + break; + + /* the major HTTP version or dot */ + case sw_major_digit: + if (ch == '.') { + state = sw_first_minor_digit; + break; + } + + if (ch < '0' || ch > '9') { + return NGX_ERROR; + } + + break; + + /* the first digit of minor HTTP version */ + case sw_first_minor_digit: + if (ch < '0' || ch > '9') { + return NGX_ERROR; + } + + state = sw_minor_digit; + break; + + /* the minor HTTP version or the end of the request line */ + case sw_minor_digit: + if (ch == ' ') { + state = sw_status; + break; + } + + if (ch < '0' || ch > '9') { + return NGX_ERROR; + } + + break; + + /* HTTP status code */ + case sw_status: + if (ch == ' ') { + break; + } + + if (ch < '0' || ch > '9') { + return NGX_ERROR; + } + + hp->code = hp->code * 10 + (ch - '0'); + + if (++hp->count == 3) { + state = sw_space_after_status; + } + + break; + + /* space or end of line */ + case sw_space_after_status: + switch (ch) { + case ' ': + state = sw_status_text; + break; + case '.': /* IIS may send 403.1, 403.2, etc */ + state = sw_status_text; + break; + case CR: + break; + case LF: + goto done; + default: + return NGX_ERROR; + } + break; + + /* any text until end of line */ + case sw_status_text: + switch (ch) { + case CR: + hp->status_text_end = p; + state = sw_almost_done; + break; + case LF: + hp->status_text_end = p; + goto done; + } + + if (hp->status_text == NULL) { + hp->status_text = p; + } + + break; + + /* end of status line */ + case sw_almost_done: + switch (ch) { + case LF: + goto done; + default: + return NGX_ERROR; + } + } + } + + b->pos = p; + hp->state = state; + + return NGX_AGAIN; + +done: + + b->pos = p + 1; + hp->state = sw_start; + + return NGX_OK; +} + + +static ngx_int_t +ngx_js_http_parse_header_line(ngx_js_http_parse_t *hp, ngx_buf_t *b) +{ + u_char c, ch, *p; + enum { + sw_start = 0, + sw_name, + sw_space_before_value, + sw_value, + sw_space_after_value, + sw_almost_done, + sw_header_almost_done + } state; + + state = hp->state; + + for (p = b->pos; p < b->last; p++) { + ch = *p; + + switch (state) { + + /* first char */ + case sw_start: + + switch (ch) { + case CR: + hp->header_end = p; + state = sw_header_almost_done; + break; + case LF: + hp->header_end = p; + goto header_done; + default: + state = sw_name; + hp->header_name_start = p; + + c = (u_char) (ch | 0x20); + if (c >= 'a' && c <= 'z') { + break; + } + + if (ch >= '0' && ch <= '9') { + break; + } + + return NGX_ERROR; + } + break; + + /* header name */ + case sw_name: + c = (u_char) (ch | 0x20); + if (c >= 'a' && c <= 'z') { + break; + } + + if (ch == ':') { + hp->header_name_end = p; + state = sw_space_before_value; + break; + } + + if (ch == '-' || ch == '_') { + break; + } + + if (ch >= '0' && ch <= '9') { + break; + } + + if (ch == CR) { + hp->header_name_end = p; + hp->header_start = p; + hp->header_end = p; + state = sw_almost_done; + break; + } + + if (ch == LF) { + hp->header_name_end = p; + hp->header_start = p; + hp->header_end = p; + goto done; + } + + return NGX_ERROR; + + /* space* before header value */ + case sw_space_before_value: + switch (ch) { + case ' ': + break; + case CR: + hp->header_start = p; + hp->header_end = p; + state = sw_almost_done; + break; + case LF: + hp->header_start = p; + hp->header_end = p; + goto done; + default: + hp->header_start = p; + state = sw_value; + break; + } + break; + + /* header value */ + case sw_value: + switch (ch) { + case ' ': + hp->header_end = p; + state = sw_space_after_value; + break; + case CR: + hp->header_end = p; + state = sw_almost_done; + break; + case LF: + hp->header_end = p; + goto done; + } + break; + + /* space* before end of header line */ + case sw_space_after_value: + switch (ch) { + case ' ': + break; + case CR: + state = sw_almost_done; + break; + case LF: + goto done; + default: + state = sw_value; + break; + } + break; + + /* end of header line */ + case sw_almost_done: + switch (ch) { + case LF: + goto done; + default: + return NGX_ERROR; + } + + /* end of header */ + case sw_header_almost_done: + switch (ch) { + case LF: + goto header_done; + default: + return NGX_ERROR; + } + } + } + + b->pos = p; + hp->state = state; + + return NGX_AGAIN; + +done: + + b->pos = p + 1; + hp->state = sw_start; + + return NGX_OK; + +header_done: + + b->pos = p + 1; + hp->state = sw_start; + + return NGX_DONE; +} + + +#define \ +ngx_size_is_sufficient(cs) \ + (cs < ((__typeof__(cs)) 1 << (sizeof(cs) * 8 - 4))) + + +#define NGX_JS_HTTP_CHUNK_MIDDLE 0 +#define NGX_JS_HTTP_CHUNK_ON_BORDER 1 +#define NGX_JS_HTTP_CHUNK_END 2 + + +static ngx_int_t +ngx_js_http_chunk_buffer(ngx_js_http_chunk_parse_t *hcp, ngx_buf_t *b, + njs_chb_t *chain) +{ + size_t size; + + size = b->last - hcp->pos; + + if (hcp->chunk_size < size) { + njs_chb_append(chain, hcp->pos, hcp->chunk_size); + hcp->pos += hcp->chunk_size; + + return NGX_JS_HTTP_CHUNK_END; + } + + njs_chb_append(chain, hcp->pos, size); + hcp->pos += size; + + hcp->chunk_size -= size; + + if (hcp->chunk_size == 0) { + return NGX_JS_HTTP_CHUNK_ON_BORDER; + } + + return NGX_JS_HTTP_CHUNK_MIDDLE; +} + + +static ngx_int_t +ngx_js_http_parse_chunked(ngx_js_http_chunk_parse_t *hcp, + ngx_buf_t *b, njs_chb_t *chain) +{ + u_char c, ch; + ngx_int_t rc; + + enum { + sw_start = 0, + sw_chunk_size, + sw_chunk_size_linefeed, + sw_chunk_end_newline, + sw_chunk_end_linefeed, + sw_chunk, + } state; + + state = hcp->state; + + hcp->pos = b->pos; + + while (hcp->pos < b->last) { + /* + * The sw_chunk state is tested outside the switch + * to preserve hcp->pos and to not touch memory. + */ + if (state == sw_chunk) { + rc = ngx_js_http_chunk_buffer(hcp, b, chain); + if (rc == NGX_ERROR) { + return rc; + } + + if (rc == NGX_JS_HTTP_CHUNK_MIDDLE) { + break; + } + + state = sw_chunk_end_newline; + + if (rc == NGX_JS_HTTP_CHUNK_ON_BORDER) { + break; + } + + /* rc == NGX_JS_HTTP_CHUNK_END */ + } + + ch = *hcp->pos++; + + switch (state) { + + case sw_start: + state = sw_chunk_size; + + c = ch - '0'; + + if (c <= 9) { + hcp->chunk_size = c; + continue; + } + + c = (ch | 0x20) - 'a'; + + if (c <= 5) { + hcp->chunk_size = 0x0A + c; + continue; + } + + return NGX_ERROR; + + case sw_chunk_size: + + c = ch - '0'; + + if (c > 9) { + c = (ch | 0x20) - 'a'; + + if (c <= 5) { + c += 0x0A; + + } else if (ch == '\r') { + state = sw_chunk_size_linefeed; + continue; + + } else { + return NGX_ERROR; + } + } + + if (ngx_size_is_sufficient(hcp->chunk_size)) { + hcp->chunk_size = (hcp->chunk_size << 4) + c; + continue; + } + + return NGX_ERROR; + + case sw_chunk_size_linefeed: + if (ch == '\n') { + + if (hcp->chunk_size != 0) { + state = sw_chunk; + continue; + } + + hcp->last = 1; + state = sw_chunk_end_newline; + continue; + } + + return NGX_ERROR; + + case sw_chunk_end_newline: + if (ch == '\r') { + state = sw_chunk_end_linefeed; + continue; + } + + return NGX_ERROR; + + case sw_chunk_end_linefeed: + if (ch == '\n') { + + if (!hcp->last) { + state = sw_start; + continue; + } + + return NGX_OK; + } + + return NGX_ERROR; + + case sw_chunk: + /* + * This state is processed before the switch. + * It added here just to suppress a warning. + */ + continue; + } + } + + hcp->state = state; + + return NGX_AGAIN; +} diff --git a/nginx/ngx_js_http.h b/nginx/ngx_js_http.h new file mode 100644 index 000000000..26148c8db --- /dev/null +++ b/nginx/ngx_js_http.h @@ -0,0 +1,157 @@ + +/* + * Copyright (C) Dmitry Volyntsev + * Copyright (C) hongzhidao + * Copyright (C) Antoine Bonavita + * Copyright (C) NGINX, Inc. + */ + + +#ifndef _NGX_JS_HTTP_H_INCLUDED_ +#define _NGX_JS_HTTP_H_INCLUDED_ + + +typedef struct ngx_js_http_s ngx_js_http_t; + + +typedef struct { + ngx_uint_t state; + ngx_uint_t code; + u_char *status_text; + u_char *status_text_end; + ngx_uint_t count; + ngx_flag_t chunked; + off_t content_length_n; + + u_char *header_name_start; + u_char *header_name_end; + u_char *header_start; + u_char *header_end; +} ngx_js_http_parse_t; + + +typedef struct { + u_char *pos; + uint64_t chunk_size; + uint8_t state; + uint8_t last; +} ngx_js_http_chunk_parse_t; + + +typedef struct ngx_js_tb_elt_s ngx_js_tb_elt_t; + +struct ngx_js_tb_elt_s { + ngx_uint_t hash; + ngx_str_t key; + ngx_str_t value; + ngx_js_tb_elt_t *next; +}; + + +typedef struct { + enum { + GUARD_NONE = 0, + GUARD_REQUEST, + GUARD_IMMUTABLE, + GUARD_RESPONSE, + } guard; + ngx_list_t header_list; + ngx_js_tb_elt_t *content_type; +} 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; + + ngx_resolver_ctx_t *ctx; + ngx_addr_t addr; + ngx_addr_t *addrs; + ngx_uint_t naddrs; + ngx_uint_t naddr; + in_port_t port; + + ngx_peer_connection_t peer; + ngx_msec_t timeout; + + ngx_int_t buffer_size; + ngx_int_t max_response_body_size; + + unsigned header_only; + +#if (NGX_SSL) + ngx_str_t tls_name; + ngx_ssl_t *ssl; + njs_bool_t ssl_verify; +#endif + + ngx_buf_t *buffer; + ngx_buf_t *chunk; + njs_chb_t chain; + + ngx_js_response_t response; + + uint8_t done; + 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); + ngx_int_t (*append_headers)(ngx_js_http_t *http, + ngx_js_headers_t *headers, + u_char *name, size_t len, + u_char *value, size_t vlen); + void (*ready_handler)(ngx_js_http_t *http); + void (*error_handler)(ngx_js_http_t *http, + const char *err); +}; + + +ngx_resolver_ctx_t *ngx_js_http_resolve(ngx_js_http_t *http, ngx_resolver_t *r, + ngx_str_t *host, in_port_t port, ngx_msec_t timeout); +void ngx_js_http_connect(ngx_js_http_t *http); +void ngx_js_http_resolve_done(ngx_js_http_t *http); +void ngx_js_http_close_peer(ngx_js_http_t *http); + + +#endif /* _NGX_JS_HTTP_H_INCLUDED_ */ From 56355426f9195565c3529bc6a35f7c76fc7d11c4 Mon Sep 17 00:00:00 2001 From: Zhidao HONG Date: Tue, 22 Apr 2025 01:09:15 +0800 Subject: [PATCH 2/3] Fetch: unify string type to support both njs and QuickJS. --- nginx/ngx_js.c | 16 ++++++++++++ nginx/ngx_js.h | 2 ++ nginx/ngx_js_fetch.c | 62 +++++++++++++++++++++++++------------------- nginx/ngx_js_http.c | 5 ++-- nginx/ngx_js_http.h | 10 +++---- 5 files changed, 61 insertions(+), 34 deletions(-) diff --git a/nginx/ngx_js.c b/nginx/ngx_js.c index f91fcd993..5c71d1ae4 100644 --- a/nginx/ngx_js.c +++ b/nginx/ngx_js.c @@ -4493,3 +4493,19 @@ ngx_js_queue_pop(ngx_js_queue_t *queue) return item; } + + +ngx_int_t +ngx_njs_string(njs_vm_t *vm, njs_value_t *value, ngx_str_t *str) +{ + njs_str_t s; + + if (ngx_js_string(vm, value, &s) != NGX_OK) { + return NGX_ERROR; + } + + str->data = s.start; + str->len = s.length; + + return NGX_OK; +} diff --git a/nginx/ngx_js.h b/nginx/ngx_js.h index 0e811f44f..1dbb4c36b 100644 --- a/nginx/ngx_js.h +++ b/nginx/ngx_js.h @@ -422,6 +422,8 @@ ngx_js_queue_t *ngx_js_queue_create(ngx_pool_t *pool, ngx_uint_t capacity); ngx_int_t ngx_js_queue_push(ngx_js_queue_t *queue, void *item); void *ngx_js_queue_pop(ngx_js_queue_t *queue); +ngx_int_t ngx_njs_string(njs_vm_t *vm, njs_value_t *value, ngx_str_t *str); + extern njs_module_t ngx_js_ngx_module; extern njs_module_t njs_webcrypto_module; diff --git a/nginx/ngx_js_fetch.c b/nginx/ngx_js_fetch.c index dcf8566f0..00bf7663f 100644 --- a/nginx/ngx_js_fetch.c +++ b/nginx/ngx_js_fetch.c @@ -513,6 +513,7 @@ ngx_js_ext_fetch(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 str; ngx_url_t u; ngx_uint_t i; njs_bool_t has_host; @@ -590,12 +591,15 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, #endif } - http->header_only = njs_strstr_eq(&request.method, &njs_str_value("HEAD")); + str.start = request.method.data; + str.length = request.method.len; + + http->header_only = njs_strstr_eq(&str, &njs_str_value("HEAD")); NJS_CHB_MP_INIT(&http->chain, njs_vm_memory_pool(vm)); NJS_CHB_MP_INIT(&http->response.chain, njs_vm_memory_pool(vm)); - njs_chb_append(&http->chain, request.method.start, request.method.length); + njs_chb_append(&http->chain, request.method.data, request.method.len); njs_chb_append_literal(&http->chain, " "); if (u.uri.len == 0 || u.uri.data[0] != '/') { @@ -685,10 +689,10 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, http->tls_name.len = u.host.len; #endif - if (request.body.length != 0) { + if (request.body.len != 0) { njs_chb_sprintf(&http->chain, 32, "Content-Length: %uz" CRLF CRLF, - request.body.length); - njs_chb_append(&http->chain, request.body.start, request.body.length); + request.body.len); + njs_chb_append(&http->chain, request.body.data, request.body.len); } else { njs_chb_append_literal(&http->chain, CRLF); @@ -863,13 +867,13 @@ ngx_js_ext_response_constructor(njs_vm_t *vm, njs_value_t *args, value = njs_vm_object_prop(vm, init, &status_text, &lvalue); if (value != NULL) { - if (ngx_js_string(vm, value, &response->status_text) != NGX_OK) { + if (ngx_njs_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; + p = response->status_text.data; + end = p + response->status_text.len; while (p < end) { if (*p != '\t' && *p < ' ') { @@ -929,6 +933,7 @@ static njs_int_t ngx_js_method_process(njs_vm_t *vm, ngx_js_request_t *request) { u_char *s, *p; + njs_str_t str; const njs_str_t *m; static const njs_str_t forbidden[] = { @@ -948,15 +953,18 @@ ngx_js_method_process(njs_vm_t *vm, ngx_js_request_t *request) njs_null_str, }; + str.start = request->method.data; + str.length = request->method.len; + for (m = &forbidden[0]; m->length != 0; m++) { - if (njs_strstr_case_eq(&request->method, m)) { + if (njs_strstr_case_eq(&str, 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)) { + if (njs_strstr_case_eq(&str, m)) { s = &request->m[0]; p = m->start; @@ -964,8 +972,8 @@ ngx_js_method_process(njs_vm_t *vm, ngx_js_request_t *request) *s++ = njs_upper_case(*p++); } - request->method.start = &request->m[0]; - request->method.length = m->length; + request->method.data = &request->m[0]; + request->method.len = m->length; break; } } @@ -1344,8 +1352,10 @@ ngx_js_request_constructor(njs_vm_t *vm, ngx_js_request_t *request, ngx_memzero(request, sizeof(ngx_js_request_t)); - request->method = njs_str_value("GET"); - request->body = njs_str_value(""); + request->method.data = (u_char *) "GET"; + request->method.len = 3; + request->body.data = (u_char *) ""; + request->body.len = 0; request->headers.guard = GUARD_REQUEST; pool = ngx_external_pool(vm, external); @@ -1358,7 +1368,7 @@ ngx_js_request_constructor(njs_vm_t *vm, ngx_js_request_t *request, } if (njs_value_is_string(input)) { - ret = ngx_js_string(vm, input, &request->url); + ret = ngx_njs_string(vm, input, &request->url); if (ret != NJS_OK) { njs_vm_error(vm, "failed to convert url arg"); return NJS_ERROR; @@ -1385,12 +1395,12 @@ ngx_js_request_constructor(njs_vm_t *vm, ngx_js_request_t *request, } } - ngx_js_http_trim(&request->url.start, &request->url.length, 1); + ngx_js_http_trim(&request->url.data, &request->url.len, 1); ngx_memzero(u, sizeof(ngx_url_t)); - u->url.len = request->url.length; - u->url.data = request->url.start; + u->url.len = request->url.len; + u->url.data = request->url.data; u->default_port = 80; u->uri_part = 1; u->no_resolve = 1; @@ -1425,7 +1435,7 @@ ngx_js_request_constructor(njs_vm_t *vm, ngx_js_request_t *request, if (njs_value_is_object(init)) { value = njs_vm_object_prop(vm, init, &method_key, &lvalue); - if (value != NULL && ngx_js_string(vm, value, &request->method) + if (value != NULL && ngx_njs_string(vm, value, &request->method) != NGX_OK) { njs_vm_error(vm, "invalid Request method"); @@ -1499,7 +1509,7 @@ ngx_js_request_constructor(njs_vm_t *vm, ngx_js_request_t *request, value = njs_vm_object_prop(vm, init, &body_key, &lvalue); if (value != NULL) { - if (ngx_js_string(vm, value, &request->body) != NGX_OK) { + if (ngx_njs_string(vm, value, &request->body) != NGX_OK) { njs_vm_error(vm, "invalid Request body"); return NJS_ERROR; } @@ -2280,8 +2290,8 @@ ngx_request_js_ext_body(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, switch (type) { case NGX_JS_BODY_ARRAY_BUFFER: ret = njs_vm_value_array_buffer_set(vm, njs_value_arg(&result), - request->body.start, - request->body.length); + request->body.data, + request->body.len); if (ret != NJS_OK) { njs_vm_memory_error(vm); return NJS_ERROR; @@ -2293,8 +2303,8 @@ ngx_request_js_ext_body(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, case NGX_JS_BODY_TEXT: default: ret = njs_vm_value_string_create(vm, njs_value_arg(&result), - request->body.start, - request->body.length); + request->body.data, + request->body.len); if (ret != NJS_OK) { njs_vm_memory_error(vm); return NJS_ERROR; @@ -2575,8 +2585,8 @@ ngx_response_js_ext_status_text(njs_vm_t *vm, njs_object_prop_t *prop, return NJS_DECLINED; } - njs_vm_value_string_create(vm, retval, response->status_text.start, - response->status_text.length); + njs_vm_value_string_create(vm, retval, response->status_text.data, + response->status_text.len); return NJS_OK; } diff --git a/nginx/ngx_js_http.c b/nginx/ngx_js_http.c index 917a28215..8b2942764 100644 --- a/nginx/ngx_js_http.c +++ b/nginx/ngx_js_http.c @@ -627,9 +627,8 @@ ngx_js_http_process_status_line(ngx_js_http_t *http) hp->code); http->response.code = hp->code; - http->response.status_text.start = hp->status_text; - http->response.status_text.length = hp->status_text_end - - hp->status_text; + http->response.status_text.data = hp->status_text; + http->response.status_text.len = hp->status_text_end - hp->status_text; http->process = ngx_js_http_process_headers; return http->process(http); diff --git a/nginx/ngx_js_http.h b/nginx/ngx_js_http.h index 26148c8db..42d2f5a33 100644 --- a/nginx/ngx_js_http.h +++ b/nginx/ngx_js_http.h @@ -81,20 +81,20 @@ typedef struct { MODE_NAVIGATE, MODE_WEBSOCKET, } mode; - njs_str_t url; - njs_str_t method; + ngx_str_t url; + ngx_str_t method; u_char m[8]; uint8_t body_used; - njs_str_t body; + ngx_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_str_t url; ngx_int_t code; - njs_str_t status_text; + ngx_str_t status_text; uint8_t body_used; njs_chb_t chain; ngx_js_headers_t headers; From 84a8f451c02ee43346299b6c98063b9499cacc4a Mon Sep 17 00:00:00 2001 From: Zhidao HONG Date: Tue, 22 Apr 2025 01:12:12 +0800 Subject: [PATCH 3/3] Fetch: expose ngx_js_http_trim() and ngx_js_check_header_name(). --- nginx/ngx_js_fetch.c | 105 ++--------------------------------------- nginx/ngx_js_http.c | 108 +++++++++++++++++++++++++++++++++++++++++++ nginx/ngx_js_http.h | 3 ++ 3 files changed, 116 insertions(+), 100 deletions(-) diff --git a/nginx/ngx_js_fetch.c b/nginx/ngx_js_fetch.c index 00bf7663f..4b6f753d7 100644 --- a/nginx/ngx_js_fetch.c +++ b/nginx/ngx_js_fetch.c @@ -117,8 +117,6 @@ static njs_int_t ngx_response_js_ext_type(njs_vm_t *vm, static njs_int_t ngx_response_js_ext_body(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); -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, @@ -1533,94 +1531,6 @@ ngx_js_request_constructor(njs_vm_t *vm, ngx_js_request_t *request, } -njs_inline njs_int_t -ngx_js_http_whitespace(u_char c) -{ - switch (c) { - case 0x09: /* */ - case 0x0A: /* */ - case 0x0D: /* */ - case 0x20: /* */ - return 1; - - default: - return 0; - } -} - - -static void -ngx_js_http_trim(u_char **value, size_t *len, - njs_bool_t trim_c0_control_or_space) -{ - u_char *start, *end; - - start = *value; - end = start + *len; - - for ( ;; ) { - if (start == end) { - break; - } - - if (ngx_js_http_whitespace(*start) - || (trim_c0_control_or_space && *start <= ' ')) - { - start++; - continue; - } - - break; - } - - for ( ;; ) { - if (start == end) { - break; - } - - end--; - - if (ngx_js_http_whitespace(*end) - || (trim_c0_control_or_space && *end <= ' ')) - { - continue; - } - - end++; - break; - } - - *value = start; - *len = end - start; -} - - -static const uint32_t token_map[] = { - 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ - - /* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */ - 0x03ff6cfa, /* 0000 0011 1111 1111 0110 1100 1111 1010 */ - - /* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */ - 0xc7fffffe, /* 1100 0111 1111 1111 1111 1111 1111 1110 */ - - /* ~}| {zyx wvut srqp onml kjih gfed cba` */ - 0x57ffffff, /* 0101 0111 1111 1111 1111 1111 1111 1111 */ - - 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ - 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ - 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ - 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ -}; - - -njs_inline njs_bool_t -njs_is_token(uint32_t byte) -{ - return ((token_map[byte >> 5] & ((uint32_t) 1 << (byte & 0x1f))) != 0); -} - - 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) @@ -1659,22 +1569,17 @@ 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); - p = name; - end = p + len; - - while (p < end) { - if (!njs_is_token(*p)) { - njs_vm_error(vm, "invalid header name"); - return NJS_ERROR; - } - - p++; + ret = ngx_js_check_header_name(name, len); + if (ret != NGX_OK) { + njs_vm_error(vm, "invalid header name"); + return NJS_ERROR; } p = value; diff --git a/nginx/ngx_js_http.c b/nginx/ngx_js_http.c index 8b2942764..5be5cc804 100644 --- a/nginx/ngx_js_http.c +++ b/nginx/ngx_js_http.c @@ -1422,3 +1422,111 @@ ngx_js_http_parse_chunked(ngx_js_http_chunk_parse_t *hcp, return NGX_AGAIN; } + + +njs_inline njs_int_t +ngx_js_http_whitespace(u_char c) +{ + switch (c) { + case 0x09: /* */ + case 0x0A: /* */ + case 0x0D: /* */ + case 0x20: /* */ + return 1; + + default: + return 0; + } +} + + +void +ngx_js_http_trim(u_char **value, size_t *len, + njs_bool_t trim_c0_control_or_space) +{ + u_char *start, *end; + + start = *value; + end = start + *len; + + for ( ;; ) { + if (start == end) { + break; + } + + if (ngx_js_http_whitespace(*start) + || (trim_c0_control_or_space && *start <= ' ')) + { + start++; + continue; + } + + break; + } + + for ( ;; ) { + if (start == end) { + break; + } + + end--; + + if (ngx_js_http_whitespace(*end) + || (trim_c0_control_or_space && *end <= ' ')) + { + continue; + } + + end++; + break; + } + + *value = start; + *len = end - start; +} + + +static const uint32_t token_map[] = { + 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ + + /* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */ + 0x03ff6cfa, /* 0000 0011 1111 1111 0110 1100 1111 1010 */ + + /* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */ + 0xc7fffffe, /* 1100 0111 1111 1111 1111 1111 1111 1110 */ + + /* ~}| {zyx wvut srqp onml kjih gfed cba` */ + 0x57ffffff, /* 0101 0111 1111 1111 1111 1111 1111 1111 */ + + 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ + 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ + 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ + 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ +}; + + +njs_inline njs_bool_t +njs_is_token(uint32_t byte) +{ + return ((token_map[byte >> 5] & ((uint32_t) 1 << (byte & 0x1f))) != 0); +} + + +ngx_int_t +ngx_js_check_header_name(u_char *name, size_t len) +{ + u_char *p, *end; + + p = name; + end = p + len; + + while (p < end) { + if (!njs_is_token(*p)) { + return NGX_ERROR; + } + + p++; + } + + return NGX_OK; +} diff --git a/nginx/ngx_js_http.h b/nginx/ngx_js_http.h index 42d2f5a33..089517f2b 100644 --- a/nginx/ngx_js_http.h +++ b/nginx/ngx_js_http.h @@ -152,6 +152,9 @@ ngx_resolver_ctx_t *ngx_js_http_resolve(ngx_js_http_t *http, ngx_resolver_t *r, void ngx_js_http_connect(ngx_js_http_t *http); 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, + njs_bool_t trim_c0_control_or_space); +ngx_int_t ngx_js_check_header_name(u_char *name, size_t len); #endif /* _NGX_JS_HTTP_H_INCLUDED_ */