From noreply at nginx.com Sat Feb 1 01:57:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Sat, 1 Feb 2025 01:57:02 +0000 (UTC) Subject: [njs] HTTP: reading r.requestText or r.requestBuffer from a temp file. Message-ID: <20250201015702.A0D10489D5@pubserv1.nginx> details: https://github.com/nginx/njs/commit/2d97e80486d7d2554e58cdaca4f2389b836600c8 branches: master commit: 2d97e80486d7d2554e58cdaca4f2389b836600c8 user: Dmitry Volyntsev date: Tue, 28 Jan 2025 19:11:48 -0800 description: HTTP: reading r.requestText or r.requestBuffer from a temp file. Previously, an exception was thrown when accessing r.requestText or r.requestBuffer if a client request body size exceeded client_body_buffer_size. --- nginx/ngx_http_js_module.c | 60 +++++++++++++++++++++++++++++++++++++++------- nginx/t/js_request_body.t | 29 +++++++++++++++++++--- 2 files changed, 77 insertions(+), 12 deletions(-) diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c index 04d06fb2..66cb97c0 100644 --- a/nginx/ngx_http_js_module.c +++ b/nginx/ngx_http_js_module.c @@ -2916,6 +2916,7 @@ ngx_http_js_ext_get_request_body(njs_vm_t *vm, njs_object_prop_t *prop, { u_char *p, *body; size_t len; + ssize_t n; uint32_t buffer_type; ngx_buf_t *buf; njs_int_t ret; @@ -2948,14 +2949,35 @@ ngx_http_js_ext_get_request_body(njs_vm_t *vm, njs_object_prop_t *prop, return NJS_DECLINED; } - if (r->request_body->temp_file) { - njs_vm_error(vm, "request body is in a file"); - return NJS_ERROR; - } - cl = r->request_body->bufs; buf = cl->buf; + if (r->request_body->temp_file) { + ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, + "http js reading request body from a temporary file"); + + if (buf == NULL || !buf->in_file) { + njs_vm_internal_error(vm, "cannot find request body"); + return NJS_ERROR; + } + + len = buf->file_last - buf->file_pos; + + body = ngx_pnalloc(r->pool, len); + if (body == NULL) { + njs_vm_memory_error(vm); + return NJS_ERROR; + } + + n = ngx_read_file(buf->file, body, len, buf->file_pos); + if (n != (ssize_t) len) { + njs_vm_internal_error(vm, "failed to read request body"); + return NJS_ERROR; + } + + goto done; + } + if (cl->next == NULL) { len = buf->last - buf->pos; body = buf->pos; @@ -5259,6 +5281,7 @@ ngx_http_qjs_ext_request_body(JSContext *cx, JSValueConst this_val, int type) { u_char *p, *data; size_t len; + ssize_t n; JSValue body; uint32_t buffer_type; ngx_buf_t *buf; @@ -5287,13 +5310,32 @@ ngx_http_qjs_ext_request_body(JSContext *cx, JSValueConst this_val, int type) return JS_UNDEFINED; } - if (r->request_body->temp_file) { - return JS_ThrowTypeError(cx, "request body is in a file"); - } - cl = r->request_body->bufs; buf = cl->buf; + if (r->request_body->temp_file) { + ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, + "http js reading request body from a temporary file"); + + if (buf == NULL || !buf->in_file) { + return JS_ThrowInternalError(cx, "cannot find body file"); + } + + len = buf->file_last - buf->file_pos; + + data = ngx_pnalloc(r->pool, len); + if (data == NULL) { + return JS_ThrowOutOfMemory(cx); + } + + n = ngx_read_file(buf->file, data, len, buf->file_pos); + if (n != (ssize_t) len) { + return JS_ThrowInternalError(cx, "failed to read request body"); + } + + goto done; + } + if (cl->next == NULL) { len = buf->last - buf->pos; data = buf->pos; diff --git a/nginx/t/js_request_body.t b/nginx/t/js_request_body.t index 360e4565..3980ea73 100644 --- a/nginx/t/js_request_body.t +++ b/nginx/t/js_request_body.t @@ -47,11 +47,25 @@ http { js_content test.body; } + location /body_4k { + client_body_buffer_size 4k; + js_content test.body; + } + location /in_file { + js_content test.body; + } + + location /in_file_on { client_body_in_file_only on; js_content test.body; } + location /in_file_clean { + client_body_in_file_only clean; + js_content test.body; + } + location /read_body_from_temp_file { client_body_in_file_only clean; js_content test.read_body_from_temp_file; @@ -93,20 +107,29 @@ $t->write_file('test.js', <try_run('no njs request body')->plan(5); +$t->try_run('no njs request body')->plan(9); ############################################################################### like(http_post('/body'), qr/REQ-BODY/, 'request body'); -like(http_post('/in_file'), qr/request body is in a file/, - 'request body in file'); +like(http_post('/in_file'), qr/REQ-BODY/, 'request body in a file'); +like(http_post('/in_file_on'), qr/REQ-BODY/, 'request body in a file on'); +like(http_post('/in_file_clean'), qr/REQ-BODY/, 'request body in a file clean'); like(http_post_big('/body'), qr/200.*^(1234567890){1024}$/ms, 'request body big'); +like(http_post_big('/body_4k'), qr/200.*^(1234567890){1024}$/ms, + 'request body big with 4k buffer'); like(http_post_big('/read_body_from_temp_file'), qr/200.*^(1234567890){1024}$/ms, 'request body big from temp file'); like(http_post('/request_body_cache'), qr/requestText:string requestBuffer:buffer$/s, 'request body cache'); +$t->stop(); + +ok(index($t->read_file('error.log'), + 'http js reading request body from a temporary file') > 0, + 'http request body is in a file warning'); + ############################################################################### sub http_post { From osa at freebsd.org.ru Sun Feb 2 14:50:02 2025 From: osa at freebsd.org.ru (Sergey A. Osokin) Date: Sun, 2 Feb 2025 17:50:02 +0300 Subject: [njs] HTTP: reading r.requestText or r.requestBuffer from a temp file. In-Reply-To: <20250201015702.A0D10489D5@pubserv1.nginx> References: <20250201015702.A0D10489D5@pubserv1.nginx> Message-ID: This is great! Thank you! -- Sergey A. Osokin On Sat, Feb 01, 2025 at 01:57:02AM +0000, noreply at nginx.com wrote: > details: https://github.com/nginx/njs/commit/2d97e80486d7d2554e58cdaca4f2389b836600c8 > branches: master > commit: 2d97e80486d7d2554e58cdaca4f2389b836600c8 > user: Dmitry Volyntsev > date: Tue, 28 Jan 2025 19:11:48 -0800 > description: > HTTP: reading r.requestText or r.requestBuffer from a temp file. > > Previously, an exception was thrown when accessing r.requestText or > r.requestBuffer if a client request body size exceeded > client_body_buffer_size. > > --- > nginx/ngx_http_js_module.c | 60 +++++++++++++++++++++++++++++++++++++++------- > nginx/t/js_request_body.t | 29 +++++++++++++++++++--- > 2 files changed, 77 insertions(+), 12 deletions(-) > > diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c > index 04d06fb2..66cb97c0 100644 > --- a/nginx/ngx_http_js_module.c > +++ b/nginx/ngx_http_js_module.c > @@ -2916,6 +2916,7 @@ ngx_http_js_ext_get_request_body(njs_vm_t *vm, njs_object_prop_t *prop, > { > u_char *p, *body; > size_t len; > + ssize_t n; > uint32_t buffer_type; > ngx_buf_t *buf; > njs_int_t ret; > @@ -2948,14 +2949,35 @@ ngx_http_js_ext_get_request_body(njs_vm_t *vm, njs_object_prop_t *prop, > return NJS_DECLINED; > } > > - if (r->request_body->temp_file) { > - njs_vm_error(vm, "request body is in a file"); > - return NJS_ERROR; > - } > - > cl = r->request_body->bufs; > buf = cl->buf; > > + if (r->request_body->temp_file) { > + ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, > + "http js reading request body from a temporary file"); > + > + if (buf == NULL || !buf->in_file) { > + njs_vm_internal_error(vm, "cannot find request body"); > + return NJS_ERROR; > + } > + > + len = buf->file_last - buf->file_pos; > + > + body = ngx_pnalloc(r->pool, len); > + if (body == NULL) { > + njs_vm_memory_error(vm); > + return NJS_ERROR; > + } > + > + n = ngx_read_file(buf->file, body, len, buf->file_pos); > + if (n != (ssize_t) len) { > + njs_vm_internal_error(vm, "failed to read request body"); > + return NJS_ERROR; > + } > + > + goto done; > + } > + > if (cl->next == NULL) { > len = buf->last - buf->pos; > body = buf->pos; > @@ -5259,6 +5281,7 @@ ngx_http_qjs_ext_request_body(JSContext *cx, JSValueConst this_val, int type) > { > u_char *p, *data; > size_t len; > + ssize_t n; > JSValue body; > uint32_t buffer_type; > ngx_buf_t *buf; > @@ -5287,13 +5310,32 @@ ngx_http_qjs_ext_request_body(JSContext *cx, JSValueConst this_val, int type) > return JS_UNDEFINED; > } > > - if (r->request_body->temp_file) { > - return JS_ThrowTypeError(cx, "request body is in a file"); > - } > - > cl = r->request_body->bufs; > buf = cl->buf; > > + if (r->request_body->temp_file) { > + ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, > + "http js reading request body from a temporary file"); > + > + if (buf == NULL || !buf->in_file) { > + return JS_ThrowInternalError(cx, "cannot find body file"); > + } > + > + len = buf->file_last - buf->file_pos; > + > + data = ngx_pnalloc(r->pool, len); > + if (data == NULL) { > + return JS_ThrowOutOfMemory(cx); > + } > + > + n = ngx_read_file(buf->file, data, len, buf->file_pos); > + if (n != (ssize_t) len) { > + return JS_ThrowInternalError(cx, "failed to read request body"); > + } > + > + goto done; > + } > + > if (cl->next == NULL) { > len = buf->last - buf->pos; > data = buf->pos; > diff --git a/nginx/t/js_request_body.t b/nginx/t/js_request_body.t > index 360e4565..3980ea73 100644 > --- a/nginx/t/js_request_body.t > +++ b/nginx/t/js_request_body.t > @@ -47,11 +47,25 @@ http { > js_content test.body; > } > > + location /body_4k { > + client_body_buffer_size 4k; > + js_content test.body; > + } > + > location /in_file { > + js_content test.body; > + } > + > + location /in_file_on { > client_body_in_file_only on; > js_content test.body; > } > > + location /in_file_clean { > + client_body_in_file_only clean; > + js_content test.body; > + } > + > location /read_body_from_temp_file { > client_body_in_file_only clean; > js_content test.read_body_from_temp_file; > @@ -93,20 +107,29 @@ $t->write_file('test.js', < > EOF > > -$t->try_run('no njs request body')->plan(5); > +$t->try_run('no njs request body')->plan(9); > > ############################################################################### > > like(http_post('/body'), qr/REQ-BODY/, 'request body'); > -like(http_post('/in_file'), qr/request body is in a file/, > - 'request body in file'); > +like(http_post('/in_file'), qr/REQ-BODY/, 'request body in a file'); > +like(http_post('/in_file_on'), qr/REQ-BODY/, 'request body in a file on'); > +like(http_post('/in_file_clean'), qr/REQ-BODY/, 'request body in a file clean'); > like(http_post_big('/body'), qr/200.*^(1234567890){1024}$/ms, > 'request body big'); > +like(http_post_big('/body_4k'), qr/200.*^(1234567890){1024}$/ms, > + 'request body big with 4k buffer'); > like(http_post_big('/read_body_from_temp_file'), > qr/200.*^(1234567890){1024}$/ms, 'request body big from temp file'); > like(http_post('/request_body_cache'), > qr/requestText:string requestBuffer:buffer$/s, 'request body cache'); > > +$t->stop(); > + > +ok(index($t->read_file('error.log'), > + 'http js reading request body from a temporary file') > 0, > + 'http request body is in a file warning'); > + > ############################################################################### > > sub http_post { > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel From noreply at nginx.com Wed Feb 5 10:09:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 5 Feb 2025 10:09:02 +0000 (UTC) Subject: [nginx] Added "keepalive_min_timeout" directive. Message-ID: <20250205100902.79FDC477F7@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/22a2a225ba87029f0e7bbc09a80ff7cdad23399d branches: master commit: 22a2a225ba87029f0e7bbc09a80ff7cdad23399d user: Roman Arutyunyan date: Wed, 15 Jan 2025 12:42:39 +0400 description: Added "keepalive_min_timeout" directive. The directive sets a timeout during which a keepalive connection will not be closed by nginx for connection reuse or graceful shutdown. The change allows clients that send multiple requests over the same connection without delay or with a small delay between them, to avoid receiving a TCP RST in response to one of them. This excludes network issues and non-graceful shutdown. As a side-effect, it also addresses the TCP reset problem described in RFC 9112, Section 9.6, when the last sent HTTP response could be damaged by a followup TCP RST. It is important for non-idempotent requests, which cannot be retried by client. It is not recommended to set keepalive_min_timeout to large values as this can introduce an additional delay during graceful shutdown and may restrict nginx from effective connection reuse. --- src/http/ngx_http_core_module.c | 10 +++++++++ src/http/ngx_http_core_module.h | 1 + src/http/ngx_http_request.c | 50 +++++++++++++++++++++++++++++++++++------ src/http/ngx_http_request.h | 2 ++ 4 files changed, 56 insertions(+), 7 deletions(-) diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c index 033a3bf64..a1540c018 100644 --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -509,6 +509,13 @@ static ngx_command_t ngx_http_core_commands[] = { 0, NULL }, + { ngx_string("keepalive_min_timeout"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_core_loc_conf_t, keepalive_min_timeout), + NULL }, + { ngx_string("keepalive_requests"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, @@ -3606,6 +3613,7 @@ ngx_http_core_create_loc_conf(ngx_conf_t *cf) clcf->keepalive_time = NGX_CONF_UNSET_MSEC; clcf->keepalive_timeout = NGX_CONF_UNSET_MSEC; clcf->keepalive_header = NGX_CONF_UNSET; + clcf->keepalive_min_timeout = NGX_CONF_UNSET_MSEC; clcf->keepalive_requests = NGX_CONF_UNSET_UINT; clcf->lingering_close = NGX_CONF_UNSET_UINT; clcf->lingering_time = NGX_CONF_UNSET_MSEC; @@ -3844,6 +3852,8 @@ ngx_http_core_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) prev->keepalive_timeout, 75000); ngx_conf_merge_sec_value(conf->keepalive_header, prev->keepalive_header, 0); + ngx_conf_merge_msec_value(conf->keepalive_min_timeout, + prev->keepalive_min_timeout, 0); ngx_conf_merge_uint_value(conf->keepalive_requests, prev->keepalive_requests, 1000); ngx_conf_merge_uint_value(conf->lingering_close, diff --git a/src/http/ngx_http_core_module.h b/src/http/ngx_http_core_module.h index 765e7ff60..e7e266bf8 100644 --- a/src/http/ngx_http_core_module.h +++ b/src/http/ngx_http_core_module.h @@ -370,6 +370,7 @@ struct ngx_http_core_loc_conf_s { ngx_msec_t send_timeout; /* send_timeout */ ngx_msec_t keepalive_time; /* keepalive_time */ ngx_msec_t keepalive_timeout; /* keepalive_timeout */ + ngx_msec_t keepalive_min_timeout; /* keepalive_min_timeout */ ngx_msec_t lingering_time; /* lingering_time */ ngx_msec_t lingering_timeout; /* lingering_timeout */ ngx_msec_t resolver_timeout; /* resolver_timeout */ diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index f44c9e79b..0be9da95e 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -2799,6 +2799,13 @@ ngx_http_finalize_connection(ngx_http_request_t *r) r->lingering_close = 1; } + if (r->keepalive + && clcf->keepalive_min_timeout > 0) + { + ngx_http_set_keepalive(r); + return; + } + if (!ngx_terminate && !ngx_exiting && r->keepalive @@ -3301,10 +3308,22 @@ ngx_http_set_keepalive(ngx_http_request_t *r) r->http_state = NGX_HTTP_KEEPALIVE_STATE; #endif - c->idle = 1; - ngx_reusable_connection(c, 1); + if (clcf->keepalive_min_timeout == 0) { + c->idle = 1; + ngx_reusable_connection(c, 1); + } + + if (clcf->keepalive_min_timeout > 0 + && clcf->keepalive_timeout > clcf->keepalive_min_timeout) + { + hc->keepalive_timeout = clcf->keepalive_timeout + - clcf->keepalive_min_timeout; + + } else { + hc->keepalive_timeout = 0; + } - ngx_add_timer(rev, clcf->keepalive_timeout); + ngx_add_timer(rev, clcf->keepalive_timeout - hc->keepalive_timeout); if (rev->ready) { ngx_post_event(rev, &ngx_posted_events); @@ -3315,15 +3334,32 @@ ngx_http_set_keepalive(ngx_http_request_t *r) static void ngx_http_keepalive_handler(ngx_event_t *rev) { - size_t size; - ssize_t n; - ngx_buf_t *b; - ngx_connection_t *c; + size_t size; + ssize_t n; + ngx_buf_t *b; + ngx_connection_t *c; + ngx_http_connection_t *hc; c = rev->data; + hc = c->data; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http keepalive handler"); + if (!ngx_terminate + && !ngx_exiting + && rev->timedout + && hc->keepalive_timeout > 0) + { + c->idle = 1; + ngx_reusable_connection(c, 1); + + ngx_add_timer(rev, hc->keepalive_timeout); + + hc->keepalive_timeout = 0; + rev->timedout = 0; + return; + } + if (rev->timedout || c->close) { ngx_http_close_connection(c); return; diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h index 65c8333f8..9407f46ae 100644 --- a/src/http/ngx_http_request.h +++ b/src/http/ngx_http_request.h @@ -329,6 +329,8 @@ typedef struct { ngx_chain_t *free; + ngx_msec_t keepalive_timeout; + unsigned ssl:1; unsigned proxy_protocol:1; } ngx_http_connection_t; From noreply at nginx.com Wed Feb 5 16:12:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 5 Feb 2025 16:12:02 +0000 (UTC) Subject: [nginx] SNI: added restriction for TLSv1.3 cross-SNI session resumption. Message-ID: <20250205161202.1467D47804@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/46b9f5d389447b3b822ea71f5ac86ebc316c2975 branches: master commit: 46b9f5d389447b3b822ea71f5ac86ebc316c2975 user: Sergey Kandaurov date: Wed, 22 Jan 2025 18:55:44 +0400 description: SNI: added restriction for TLSv1.3 cross-SNI session resumption. In OpenSSL, session resumption always happens in the default SSL context, prior to invoking the SNI callback. Further, unlike in TLSv1.2 and older protocols, SSL_get_servername() returns values received in the resumption handshake, which may be different from the value in the initial handshake. Notably, this makes the restriction added in b720f650b insufficient for sessions resumed with different SNI server name. Considering the example from b720f650b, previously, a client was able to request example.org by presenting a certificate for example.org, then to resume and request example.com. The fix is to reject handshakes resumed with a different server name, if verification of client certificates is enabled in a corresponding server configuration. --- src/http/ngx_http_request.c | 27 +++++++++++++++++++++++++-- src/stream/ngx_stream_ssl_module.c | 27 +++++++++++++++++++++++++-- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 0be9da95e..ceac8d307 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -932,6 +932,31 @@ ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg) goto done; } + sscf = ngx_http_get_module_srv_conf(cscf->ctx, ngx_http_ssl_module); + +#if (defined TLS1_3_VERSION \ + && !defined LIBRESSL_VERSION_NUMBER && !defined OPENSSL_IS_BORINGSSL) + + /* + * SSL_SESSION_get0_hostname() is only available in OpenSSL 1.1.1+, + * but servername being negotiated in every TLSv1.3 handshake + * is only returned in OpenSSL 1.1.1+ as well + */ + + if (sscf->verify) { + const char *hostname; + + hostname = SSL_SESSION_get0_hostname(SSL_get0_session(ssl_conn)); + + if (hostname != NULL && ngx_strcmp(hostname, servername) != 0) { + c->ssl->handshake_rejected = 1; + *ad = SSL_AD_ACCESS_DENIED; + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + } + +#endif + hc->ssl_servername = ngx_palloc(c->pool, sizeof(ngx_str_t)); if (hc->ssl_servername == NULL) { goto error; @@ -945,8 +970,6 @@ ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg) ngx_set_connection_log(c, clcf->error_log); - sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_ssl_module); - c->ssl->buffer_size = sscf->buffer_size; if (sscf->ssl.ctx) { diff --git a/src/stream/ngx_stream_ssl_module.c b/src/stream/ngx_stream_ssl_module.c index b84995d61..2f1b99624 100644 --- a/src/stream/ngx_stream_ssl_module.c +++ b/src/stream/ngx_stream_ssl_module.c @@ -589,12 +589,35 @@ ngx_stream_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg) goto done; } + sscf = ngx_stream_get_module_srv_conf(cscf->ctx, ngx_stream_ssl_module); + +#if (defined TLS1_3_VERSION \ + && !defined LIBRESSL_VERSION_NUMBER && !defined OPENSSL_IS_BORINGSSL) + + /* + * SSL_SESSION_get0_hostname() is only available in OpenSSL 1.1.1+, + * but servername being negotiated in every TLSv1.3 handshake + * is only returned in OpenSSL 1.1.1+ as well + */ + + if (sscf->verify) { + const char *hostname; + + hostname = SSL_SESSION_get0_hostname(SSL_get0_session(ssl_conn)); + + if (hostname != NULL && ngx_strcmp(hostname, servername) != 0) { + c->ssl->handshake_rejected = 1; + *ad = SSL_AD_ACCESS_DENIED; + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + } + +#endif + s->srv_conf = cscf->ctx->srv_conf; ngx_set_connection_log(c, cscf->error_log); - sscf = ngx_stream_get_module_srv_conf(s, ngx_stream_ssl_module); - if (sscf->ssl.ctx) { if (SSL_set_SSL_CTX(ssl_conn, sscf->ssl.ctx) == NULL) { goto error; From noreply at nginx.com Wed Feb 5 16:14:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 5 Feb 2025 16:14:02 +0000 (UTC) Subject: [nginx] nginx-1.27.4-RELEASE Message-ID: <20250205161402.3991E47883@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/ecb809305e54ed15be9f620d56b19ff4e4be7db5 branches: master commit: ecb809305e54ed15be9f620d56b19ff4e4be7db5 user: Sergey Kandaurov date: Wed, 5 Feb 2025 14:24:20 +0400 description: nginx-1.27.4-RELEASE --- docs/xml/nginx/changes.xml | 104 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/docs/xml/nginx/changes.xml b/docs/xml/nginx/changes.xml index b46961704..b55177d80 100644 --- a/docs/xml/nginx/changes.xml +++ b/docs/xml/nginx/changes.xml @@ -5,6 +5,110 @@ + + + + +недостаточная проверка в обработке виртуальных серверов +при использовании SNI в TLSv1.3 позволяла повторно использовать +SSL-сессию в контексте другого виртуального сервера, +чтобы обойти проверку клиентских SSL-сертификатов (CVE-2025-23419). + + +insufficient check in virtual servers handling with TLSv1.3 SNI +allowed to reuse SSL sessions in a different virtual server, +to bypass client SSL certificates verification (CVE-2025-23419). + + + + + +директивы ssl_object_cache_inheritable, ssl_certificate_cache, +proxy_ssl_certificate_cache, grpc_ssl_certificate_cache +и uwsgi_ssl_certificate_cache. + + +the "ssl_object_cache_inheritable", "ssl_certificate_cache", +"proxy_ssl_certificate_cache", "grpc_ssl_certificate_cache", +and "uwsgi_ssl_certificate_cache" directives. + + + + + +директива keepalive_min_timeout. + + +the "keepalive_min_timeout" directive. + + + + + +при использовании zlib-ng +в логах появлялись сообщения "gzip filter failed to use preallocated memory". + + +"gzip filter failed to use preallocated memory" alerts appeared in logs +when using zlib-ng. + + + + + +nginx не мог собрать библиотеку libatomic из исходных текстов, +если использовался параметр --with-libatomic=DIR. + + +nginx could not build libatomic library using the library sources +if the --with-libatomic=DIR option was used. + + + + + +могла происходить ошибка установления соединения +при использовании 0-RTT в QUIC; +ошибка появилась в 1.27.1. + + +QUIC connection might not be established when using 0-RTT; +the bug had appeared in 1.27.1. + + + + + +теперь nginx игнорирует пакеты согласования версий QUIC от клиентов. + + +nginx now ignores QUIC version negotiation packets from clients. + + + + + +nginx не собирался на Solaris 10 и более ранних +с модулем ngx_http_v3_module. + + +nginx could not be built on Solaris 10 and earlier +with the ngx_http_v3_module. + + + + + +Исправления в HTTP/3. + + +Bugfixes in HTTP/3. + + + + + + From noreply at nginx.com Wed Feb 5 16:41:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 5 Feb 2025 16:41:02 +0000 (UTC) Subject: [nginx] Switched GNUmakefile from hg to git. Message-ID: <20250205164102.6BEFB48F57@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/bfe0a1fd6ea96b75c9a4c8b05131f2886dcc7e91 branches: stable-1.26 commit: bfe0a1fd6ea96b75c9a4c8b05131f2886dcc7e91 user: Roman Arutyunyan date: Thu, 29 Aug 2024 16:03:58 +0400 description: Switched GNUmakefile from hg to git. --- misc/GNUmakefile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/misc/GNUmakefile b/misc/GNUmakefile index fa4c36d49..7d4b657de 100644 --- a/misc/GNUmakefile +++ b/misc/GNUmakefile @@ -30,12 +30,12 @@ release: export export: rm -rf $(TEMP) - hg archive -X '.hg*' $(TEMP)/$(NGINX) + git archive --prefix=$(TEMP)/$(NGINX)/ HEAD | tar -x -f - --exclude '.git*' RELEASE: - hg ci -m nginx-$(VER)-RELEASE - hg tag -m "release-$(VER) tag" release-$(VER) + git commit -m nginx-$(VER)-RELEASE + git tag -m "release-$(VER) tag" release-$(VER) $(MAKE) -f misc/GNUmakefile release @@ -93,8 +93,8 @@ zip: export sed -i '' -e "s/$$/`printf '\r'`/" $(TEMP)/$(NGINX)/conf/* - mv $(TEMP)/$(NGINX)/docs/text/LICENSE $(TEMP)/$(NGINX)/docs.new - mv $(TEMP)/$(NGINX)/docs/text/README $(TEMP)/$(NGINX)/docs.new + mv $(TEMP)/$(NGINX)/LICENSE $(TEMP)/$(NGINX)/docs.new + mv $(TEMP)/$(NGINX)/README $(TEMP)/$(NGINX)/docs.new mv $(TEMP)/$(NGINX)/docs/html $(TEMP)/$(NGINX) rm -r $(TEMP)/$(NGINX)/docs From noreply at nginx.com Wed Feb 5 16:41:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 5 Feb 2025 16:41:02 +0000 (UTC) Subject: [nginx] Mp4: fixed handling an empty run of chunks in stsc atom. Message-ID: <20250205164102.79A9048F5A@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/4712dee8820cf6af417b1932d9ef65774a1ee1b3 branches: stable-1.26 commit: 4712dee8820cf6af417b1932d9ef65774a1ee1b3 user: Roman Arutyunyan date: Mon, 23 Sep 2024 15:51:30 +0400 description: Mp4: fixed handling an empty run of chunks in stsc atom. A specially crafted mp4 file with an empty run of chunks in the stsc atom and a large value for samples per chunk for that run, combined with a specially crafted request, allowed to store that large value in prev_samples and later in trak->end_chunk_samples while in ngx_http_mp4_crop_stsc_data(). Later in ngx_http_mp4_update_stsz_atom() this could result in buffer overread while calculating trak->end_chunk_samples_size. Now the value of samples per chunk specified for an empty run is ignored. --- src/http/modules/ngx_http_mp4_module.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/http/modules/ngx_http_mp4_module.c b/src/http/modules/ngx_http_mp4_module.c index 041ad263b..2ca059136 100644 --- a/src/http/modules/ngx_http_mp4_module.c +++ b/src/http/modules/ngx_http_mp4_module.c @@ -3176,7 +3176,10 @@ ngx_http_mp4_crop_stsc_data(ngx_http_mp4_file_t *mp4, start_sample -= n; - prev_samples = samples; + if (next_chunk > chunk) { + prev_samples = samples; + } + chunk = next_chunk; samples = ngx_mp4_get_32value(entry->samples); id = ngx_mp4_get_32value(entry->id); From noreply at nginx.com Wed Feb 5 16:41:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 5 Feb 2025 16:41:02 +0000 (UTC) Subject: [nginx] Moved LICENSE and README to root. Message-ID: <20250205164102.701B048F58@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/35a14205600fb64f412a453627ac382ebc16a404 branches: stable-1.26 commit: 35a14205600fb64f412a453627ac382ebc16a404 user: Roman Arutyunyan date: Thu, 29 Aug 2024 16:24:03 +0400 description: Moved LICENSE and README to root. --- docs/text/LICENSE => LICENSE | 0 docs/text/README => README | 0 misc/GNUmakefile | 2 -- 3 files changed, 2 deletions(-) diff --git a/docs/text/LICENSE b/LICENSE similarity index 100% rename from docs/text/LICENSE rename to LICENSE diff --git a/docs/text/README b/README similarity index 100% rename from docs/text/README rename to README diff --git a/misc/GNUmakefile b/misc/GNUmakefile index 7d4b657de..4b6a12759 100644 --- a/misc/GNUmakefile +++ b/misc/GNUmakefile @@ -15,8 +15,6 @@ release: export mv $(TEMP)/$(NGINX)/auto/configure $(TEMP)/$(NGINX) - mv $(TEMP)/$(NGINX)/docs/text/LICENSE $(TEMP)/$(NGINX) - mv $(TEMP)/$(NGINX)/docs/text/README $(TEMP)/$(NGINX) mv $(TEMP)/$(NGINX)/docs/html $(TEMP)/$(NGINX) mv $(TEMP)/$(NGINX)/docs/man $(TEMP)/$(NGINX) From noreply at nginx.com Wed Feb 5 16:41:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 5 Feb 2025 16:41:02 +0000 (UTC) Subject: [nginx] Version bump. Message-ID: <20250205164102.6928348F50@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/11b890d66d58efaeaec1f8cd2ddb6227ec5e7798 branches: stable-1.26 commit: 11b890d66d58efaeaec1f8cd2ddb6227ec5e7798 user: Sergey Kandaurov date: Wed, 5 Feb 2025 14:40:21 +0400 description: Version bump. --- src/core/nginx.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/nginx.h b/src/core/nginx.h index d29edd24c..ef000e02a 100644 --- a/src/core/nginx.h +++ b/src/core/nginx.h @@ -9,8 +9,8 @@ #define _NGINX_H_INCLUDED_ -#define nginx_version 1026002 -#define NGINX_VERSION "1.26.2" +#define nginx_version 1026003 +#define NGINX_VERSION "1.26.3" #define NGINX_VER "nginx/" NGINX_VERSION #ifdef NGX_BUILD From noreply at nginx.com Wed Feb 5 16:41:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 5 Feb 2025 16:41:02 +0000 (UTC) Subject: [nginx] Configure: MSVC compatibility with PCRE2 10.43. Message-ID: <20250205164102.7440048F59@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/cfd68334d8ba39b5fa016fe323aa8cdbd540cbe6 branches: stable-1.26 commit: cfd68334d8ba39b5fa016fe323aa8cdbd540cbe6 user: Thierry Bastian date: Wed, 9 Oct 2024 09:18:49 +0200 description: Configure: MSVC compatibility with PCRE2 10.43. --- auto/lib/pcre/make | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/auto/lib/pcre/make b/auto/lib/pcre/make index 839ef294b..182590ac5 100644 --- a/auto/lib/pcre/make +++ b/auto/lib/pcre/make @@ -36,7 +36,8 @@ if [ $PCRE_LIBRARY = PCRE2 ]; then pcre2_valid_utf.c \ pcre2_xclass.c" - ngx_pcre_test="pcre2_convert.c \ + ngx_pcre_test="pcre2_chkdint.c \ + pcre2_convert.c \ pcre2_extuni.c \ pcre2_find_bracket.c \ pcre2_script_run.c \ From noreply at nginx.com Wed Feb 5 16:41:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 5 Feb 2025 16:41:02 +0000 (UTC) Subject: [nginx] Mp4: unordered stsc chunks error for the final chunk. Message-ID: <20250205164102.7F2F648F5B@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/1ebe58a02e90a65458de0a38c84165b2a37574ed branches: stable-1.26 commit: 1ebe58a02e90a65458de0a38c84165b2a37574ed user: Roman Arutyunyan date: Wed, 2 Oct 2024 16:22:15 +0400 description: Mp4: unordered stsc chunks error for the final chunk. Currently an error is triggered if any of the chunk runs in stsc are unordered. This however does not include the final chunk run, which ends with trak->chunks + 1. The previous chunk index can be larger leading to a 32-bit overflow. This could allow to skip the validity check "if (start_sample > n)". This could later lead to a large trak->start_chunk/trak->end_chunk, which would be caught later in ngx_http_mp4_update_stco_atom() or ngx_http_mp4_update_co64_atom(). While there are no implications of the validity check being avoided, the change still adds a check to ensure the final chunk run is ordered, to produce a meaningful error and avoid a potential integer overflow. --- src/http/modules/ngx_http_mp4_module.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/http/modules/ngx_http_mp4_module.c b/src/http/modules/ngx_http_mp4_module.c index 2ca059136..49b0999cf 100644 --- a/src/http/modules/ngx_http_mp4_module.c +++ b/src/http/modules/ngx_http_mp4_module.c @@ -3189,6 +3189,13 @@ ngx_http_mp4_crop_stsc_data(ngx_http_mp4_file_t *mp4, next_chunk = trak->chunks + 1; + if (next_chunk < chunk) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "unordered mp4 stsc chunks in \"%s\"", + mp4->file.name.data); + return NGX_ERROR; + } + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "sample:%uD, chunk:%uD, chunks:%uD, samples:%uD", start_sample, chunk, next_chunk - chunk, samples); From noreply at nginx.com Wed Feb 5 16:41:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 5 Feb 2025 16:41:02 +0000 (UTC) Subject: [nginx] QUIC: prevented BIO leak in case of error. Message-ID: <20250205164102.92C3748F61@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/977824010f0bb8e2b54963fd4532a6167e6a0ada branches: stable-1.26 commit: 977824010f0bb8e2b54963fd4532a6167e6a0ada user: Roman Arutyunyan date: Fri, 22 Nov 2024 11:38:06 +0400 description: QUIC: prevented BIO leak in case of error. --- src/event/quic/ngx_event_quic_openssl_compat.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/event/quic/ngx_event_quic_openssl_compat.c b/src/event/quic/ngx_event_quic_openssl_compat.c index c7412e82b..6052bc683 100644 --- a/src/event/quic/ngx_event_quic_openssl_compat.c +++ b/src/event/quic/ngx_event_quic_openssl_compat.c @@ -391,6 +391,7 @@ SSL_set_quic_method(SSL *ssl, const SSL_QUIC_METHOD *quic_method) wbio = BIO_new(BIO_s_null()); if (wbio == NULL) { + BIO_free(rbio); return 0; } From noreply at nginx.com Wed Feb 5 16:41:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 5 Feb 2025 16:41:02 +0000 (UTC) Subject: [nginx] QUIC: ignore version negotiation packets. Message-ID: <20250205164102.89AE548F5F@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/0d11f2885eab99924dbe40d7effb91c80b00d9bf branches: stable-1.26 commit: 0d11f2885eab99924dbe40d7effb91c80b00d9bf user: Roman Arutyunyan date: Fri, 13 Dec 2024 13:25:26 +0400 description: QUIC: ignore version negotiation packets. Previously, such packets were treated as long header packets with unknown version 0, and a version negotiation packet was sent in response. This could be used to set up an infinite traffic reflect loop with another nginx instance. Now version negotiation packets are ignored. As per RFC 9000, Section 6.1: An endpoint MUST NOT send a Version Negotiation packet in response to receiving a Version Negotiation packet. --- src/event/quic/ngx_event_quic_transport.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/event/quic/ngx_event_quic_transport.c b/src/event/quic/ngx_event_quic_transport.c index fba098caa..bb13447b5 100644 --- a/src/event/quic/ngx_event_quic_transport.c +++ b/src/event/quic/ngx_event_quic_transport.c @@ -295,6 +295,11 @@ ngx_quic_parse_packet(ngx_quic_header_t *pkt) return NGX_ERROR; } + if (pkt->version == 0) { + /* version negotiation */ + return NGX_ERROR; + } + if (!ngx_quic_supported_version(pkt->version)) { return NGX_ABORT; } From noreply at nginx.com Wed Feb 5 16:41:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 5 Feb 2025 16:41:02 +0000 (UTC) Subject: [nginx] QUIC: fixed accessing a released stream. Message-ID: <20250205164102.8E66C48F60@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/5c8a92f1f0e482028504e5340f0ba455423df336 branches: stable-1.26 commit: 5c8a92f1f0e482028504e5340f0ba455423df336 user: Roman Arutyunyan date: Tue, 10 Dec 2024 18:19:27 +0400 description: QUIC: fixed accessing a released stream. While trying to close a stream in ngx_quic_close_streams() by calling its read event handler, the next stream saved prior to that could be destroyed recursively. This caused a segfault while trying to access the next stream. The way the next stream could be destroyed in HTTP/3 is the following. A request stream read event handler ngx_http_request_handler() could end up calling ngx_http_v3_send_cancel_stream() to report a cancelled request stream in the decoder stream. If sending stream cancellation decoder instruction fails for any reason, and the decoder stream is the next in order after the request stream, the issue is triggered. The fix is to postpone calling read event handlers for all streams being closed to avoid closing a released stream. --- src/event/quic/ngx_event_quic_streams.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 178b805e4..a9a21f578 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -174,7 +174,7 @@ ngx_int_t ngx_quic_close_streams(ngx_connection_t *c, ngx_quic_connection_t *qc) { ngx_pool_t *pool; - ngx_queue_t *q; + ngx_queue_t *q, posted_events; ngx_rbtree_t *tree; ngx_connection_t *sc; ngx_rbtree_node_t *node; @@ -197,6 +197,8 @@ ngx_quic_close_streams(ngx_connection_t *c, ngx_quic_connection_t *qc) return NGX_OK; } + ngx_queue_init(&posted_events); + node = ngx_rbtree_min(tree->root, tree->sentinel); while (node) { @@ -213,15 +215,21 @@ ngx_quic_close_streams(ngx_connection_t *c, ngx_quic_connection_t *qc) } sc->read->error = 1; + sc->read->ready = 1; sc->write->error = 1; - - ngx_quic_set_event(sc->read); - ngx_quic_set_event(sc->write); + sc->write->ready = 1; sc->close = 1; - sc->read->handler(sc->read); + + if (sc->read->posted) { + ngx_delete_posted_event(sc->read); + } + + ngx_post_event(sc->read, &posted_events); } + ngx_event_process_posted((ngx_cycle_t *) ngx_cycle, &posted_events); + if (tree->root == tree->sentinel) { return NGX_OK; } From noreply at nginx.com Wed Feb 5 16:41:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 5 Feb 2025 16:41:02 +0000 (UTC) Subject: [nginx] Gzip: compatibility with recent zlib-ng 2.2.x versions. Message-ID: <20250205164102.9722548F62@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/9cda58178bf6d552829d20fedee1d4df564f5629 branches: stable-1.26 commit: 9cda58178bf6d552829d20fedee1d4df564f5629 user: Sergey Kandaurov date: Mon, 23 Dec 2024 17:57:45 +0400 description: Gzip: compatibility with recent zlib-ng 2.2.x versions. It now uses 5/4 times more memory for the pending buffer. Further, a single allocation is now used, which takes additional 56 bytes for deflate_allocs in 64-bit mode aligned to 16, to store sub-allocation pointers, and the total allocation size now padded up to 128 bytes, which takes theoretically 200 additional bytes in total. This fits though into "4 * (64 + sizeof(void*))" additional space for ZALLOC used in zlib-ng 2.1.x versions. The comment was updated to reflect this. --- src/http/modules/ngx_http_gzip_filter_module.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/http/modules/ngx_http_gzip_filter_module.c b/src/http/modules/ngx_http_gzip_filter_module.c index b55527845..7113df695 100644 --- a/src/http/modules/ngx_http_gzip_filter_module.c +++ b/src/http/modules/ngx_http_gzip_filter_module.c @@ -516,8 +516,10 @@ ngx_http_gzip_filter_memory(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx) /* * Another zlib variant, https://github.com/zlib-ng/zlib-ng. * It used to force window bits to 13 for fast compression level, - * uses (64 + sizeof(void*)) additional space on all allocations - * for alignment, 16-byte padding in one of window-sized buffers, + * used (64 + sizeof(void*)) additional space on all allocations + * for alignment and 16-byte padding in one of window-sized buffers, + * uses a single allocation with up to 200 bytes for alignment and + * internal pointers, 5/4 times more memory for the pending buffer, * and 128K hash. */ @@ -526,7 +528,7 @@ ngx_http_gzip_filter_memory(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx) } ctx->allocated = 8192 + 16 + (1 << (wbits + 2)) - + 131072 + (1 << (memlevel + 8)) + + 131072 + (5 << (memlevel + 6)) + 4 * (64 + sizeof(void*)); ctx->zlib_ng = 1; } From noreply at nginx.com Wed Feb 5 16:41:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 5 Feb 2025 16:41:02 +0000 (UTC) Subject: [nginx] Mp4: prevent chunk index underflow. Message-ID: <20250205164102.8488E48F5D@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/e9e83dbb697c17b7ad51d1dd8536ad1c601fdd0e branches: stable-1.26 commit: e9e83dbb697c17b7ad51d1dd8536ad1c601fdd0e user: Roman Arutyunyan date: Tue, 22 Oct 2024 18:34:13 +0400 description: Mp4: prevent chunk index underflow. When cropping stsc atom, it's assumed that chunk index is never 0. Based on this assumption, start_chunk and end_chunk are calculated by subtracting 1 from it. If chunk index is zero, start_chunk or end_chunk may underflow, which will later trigger "start/end time is out mp4 stco chunks" error. The change adds an explicit check for zero chunk index to avoid underflow and report a proper error. Zero chunk index is explicitly banned in ISO/IEC 14496-12, 8.7.4 Sample To Chunk Box. It's also implicitly banned in QuickTime File Format specification. Description of chunk offset table references "Chunk 1" as the first table element. --- src/http/modules/ngx_http_mp4_module.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/http/modules/ngx_http_mp4_module.c b/src/http/modules/ngx_http_mp4_module.c index 49b0999cf..b7bd192df 100644 --- a/src/http/modules/ngx_http_mp4_module.c +++ b/src/http/modules/ngx_http_mp4_module.c @@ -3221,6 +3221,12 @@ found: return NGX_ERROR; } + if (chunk == 0) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "zero chunk in \"%s\"", mp4->file.name.data); + return NGX_ERROR; + } + target_chunk = chunk - 1; target_chunk += start_sample / samples; chunk_samples = start_sample % samples; From noreply at nginx.com Wed Feb 5 16:41:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 5 Feb 2025 16:41:02 +0000 (UTC) Subject: [nginx] Configure: fixed --with-libatomic=DIR with recent libatomic_ops. Message-ID: <20250205164102.9FB5348F65@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/a43f1272c38ad1c85f6d0919a5588ad47a5322e1 branches: stable-1.26 commit: a43f1272c38ad1c85f6d0919a5588ad47a5322e1 user: Sergey Kandaurov date: Fri, 17 Jan 2025 17:55:21 +0400 description: Configure: fixed --with-libatomic=DIR with recent libatomic_ops. The build location of the resulting libatomic_ops.a was changed in v7.4.0 after converting libatomic_ops to use libtool. The fix is to use library from the install path, this allows building with both old and new versions. Initially reported here: https://mailman.nginx.org/pipermail/nginx/2018-April/056054.html --- auto/lib/libatomic/conf | 4 ++-- auto/lib/libatomic/make | 11 ++++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/auto/lib/libatomic/conf b/auto/lib/libatomic/conf index 8c8cb438b..dfdc1a622 100644 --- a/auto/lib/libatomic/conf +++ b/auto/lib/libatomic/conf @@ -7,8 +7,8 @@ if [ $NGX_LIBATOMIC != YES ]; then have=NGX_HAVE_LIBATOMIC . auto/have CORE_INCS="$CORE_INCS $NGX_LIBATOMIC/src" - LINK_DEPS="$LINK_DEPS $NGX_LIBATOMIC/src/libatomic_ops.a" - CORE_LIBS="$CORE_LIBS $NGX_LIBATOMIC/src/libatomic_ops.a" + LINK_DEPS="$LINK_DEPS $NGX_LIBATOMIC/build/lib/libatomic_ops.a" + CORE_LIBS="$CORE_LIBS $NGX_LIBATOMIC/build/lib/libatomic_ops.a" else diff --git a/auto/lib/libatomic/make b/auto/lib/libatomic/make index c90318ea1..530c746a6 100644 --- a/auto/lib/libatomic/make +++ b/auto/lib/libatomic/make @@ -3,14 +3,19 @@ # Copyright (C) Nginx, Inc. + case $NGX_LIBATOMIC in + /*) ngx_prefix="$NGX_LIBATOMIC/build" ;; + *) ngx_prefix="$PWD/$NGX_LIBATOMIC/build" ;; + esac + cat << END >> $NGX_MAKEFILE -$NGX_LIBATOMIC/src/libatomic_ops.a: $NGX_LIBATOMIC/Makefile - cd $NGX_LIBATOMIC && \$(MAKE) +$NGX_LIBATOMIC/build/lib/libatomic_ops.a: $NGX_LIBATOMIC/Makefile + cd $NGX_LIBATOMIC && \$(MAKE) && \$(MAKE) install $NGX_LIBATOMIC/Makefile: $NGX_MAKEFILE cd $NGX_LIBATOMIC \\ && if [ -f Makefile ]; then \$(MAKE) distclean; fi \\ - && ./configure + && ./configure --prefix=$ngx_prefix END From noreply at nginx.com Wed Feb 5 16:41:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 5 Feb 2025 16:41:02 +0000 (UTC) Subject: [nginx] QUIC: added missing casts in iov_base assignments. Message-ID: <20250205164102.9BB4348F63@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/2e42c1e29e447c6716802bf62a7a40a390444e6b branches: stable-1.26 commit: 2e42c1e29e447c6716802bf62a7a40a390444e6b user: Aleksei Bavshin date: Mon, 27 Jan 2025 10:33:25 -0800 description: QUIC: added missing casts in iov_base assignments. This is consistent with the rest of the code and fixes build on systems with non-standard definition of struct iovec (Solaris, Illumos). --- src/event/quic/ngx_event_quic_output.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c index ce6aaab22..f087e2bfa 100644 --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -411,7 +411,7 @@ ngx_quic_send_segments(ngx_connection_t *c, u_char *buf, size_t len, ngx_memzero(msg_control, sizeof(msg_control)); iov.iov_len = len; - iov.iov_base = buf; + iov.iov_base = (void *) buf; msg.msg_iov = &iov; msg.msg_iovlen = 1; @@ -699,7 +699,7 @@ ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len, ngx_memzero(&msg, sizeof(struct msghdr)); iov.iov_len = len; - iov.iov_base = buf; + iov.iov_base = (void *) buf; msg.msg_iov = &iov; msg.msg_iovlen = 1; From noreply at nginx.com Wed Feb 5 16:41:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 5 Feb 2025 16:41:02 +0000 (UTC) Subject: [nginx] Updated OpenSSL used for win32 builds. Message-ID: <20250205164102.A384B48F66@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/95f9116128dfe531b054e740cfc4b10a3a1f58d5 branches: stable-1.26 commit: 95f9116128dfe531b054e740cfc4b10a3a1f58d5 user: Sergey Kandaurov date: Tue, 1 Oct 2024 19:23:45 +0400 description: Updated OpenSSL used for win32 builds. --- misc/GNUmakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/GNUmakefile b/misc/GNUmakefile index 4b6a12759..a1553eea4 100644 --- a/misc/GNUmakefile +++ b/misc/GNUmakefile @@ -6,7 +6,7 @@ TEMP = tmp CC = cl OBJS = objs.msvc8 -OPENSSL = openssl-3.0.14 +OPENSSL = openssl-3.0.15 ZLIB = zlib-1.3.1 PCRE = pcre2-10.39 From noreply at nginx.com Wed Feb 5 16:41:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 5 Feb 2025 16:41:02 +0000 (UTC) Subject: [nginx] SNI: added restriction for TLSv1.3 cross-SNI session resumption. Message-ID: <20250205164102.A965A48F67@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/13935cf9fdc3c8d8278c70716417d3b71c36140e branches: stable-1.26 commit: 13935cf9fdc3c8d8278c70716417d3b71c36140e user: Sergey Kandaurov date: Wed, 22 Jan 2025 18:55:44 +0400 description: SNI: added restriction for TLSv1.3 cross-SNI session resumption. In OpenSSL, session resumption always happens in the default SSL context, prior to invoking the SNI callback. Further, unlike in TLSv1.2 and older protocols, SSL_get_servername() returns values received in the resumption handshake, which may be different from the value in the initial handshake. Notably, this makes the restriction added in b720f650b insufficient for sessions resumed with different SNI server name. Considering the example from b720f650b, previously, a client was able to request example.org by presenting a certificate for example.org, then to resume and request example.com. The fix is to reject handshakes resumed with a different server name, if verification of client certificates is enabled in a corresponding server configuration. --- src/http/ngx_http_request.c | 27 +++++++++++++++++++++++++-- src/stream/ngx_stream_ssl_module.c | 27 +++++++++++++++++++++++++-- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 3cca57cf5..9593b7fb5 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -932,6 +932,31 @@ ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg) goto done; } + sscf = ngx_http_get_module_srv_conf(cscf->ctx, ngx_http_ssl_module); + +#if (defined TLS1_3_VERSION \ + && !defined LIBRESSL_VERSION_NUMBER && !defined OPENSSL_IS_BORINGSSL) + + /* + * SSL_SESSION_get0_hostname() is only available in OpenSSL 1.1.1+, + * but servername being negotiated in every TLSv1.3 handshake + * is only returned in OpenSSL 1.1.1+ as well + */ + + if (sscf->verify) { + const char *hostname; + + hostname = SSL_SESSION_get0_hostname(SSL_get0_session(ssl_conn)); + + if (hostname != NULL && ngx_strcmp(hostname, servername) != 0) { + c->ssl->handshake_rejected = 1; + *ad = SSL_AD_ACCESS_DENIED; + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + } + +#endif + hc->ssl_servername = ngx_palloc(c->pool, sizeof(ngx_str_t)); if (hc->ssl_servername == NULL) { goto error; @@ -945,8 +970,6 @@ ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg) ngx_set_connection_log(c, clcf->error_log); - sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_ssl_module); - c->ssl->buffer_size = sscf->buffer_size; if (sscf->ssl.ctx) { diff --git a/src/stream/ngx_stream_ssl_module.c b/src/stream/ngx_stream_ssl_module.c index ba444776a..6dee106de 100644 --- a/src/stream/ngx_stream_ssl_module.c +++ b/src/stream/ngx_stream_ssl_module.c @@ -521,12 +521,35 @@ ngx_stream_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg) goto done; } + sscf = ngx_stream_get_module_srv_conf(cscf->ctx, ngx_stream_ssl_module); + +#if (defined TLS1_3_VERSION \ + && !defined LIBRESSL_VERSION_NUMBER && !defined OPENSSL_IS_BORINGSSL) + + /* + * SSL_SESSION_get0_hostname() is only available in OpenSSL 1.1.1+, + * but servername being negotiated in every TLSv1.3 handshake + * is only returned in OpenSSL 1.1.1+ as well + */ + + if (sscf->verify) { + const char *hostname; + + hostname = SSL_SESSION_get0_hostname(SSL_get0_session(ssl_conn)); + + if (hostname != NULL && ngx_strcmp(hostname, servername) != 0) { + c->ssl->handshake_rejected = 1; + *ad = SSL_AD_ACCESS_DENIED; + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + } + +#endif + s->srv_conf = cscf->ctx->srv_conf; ngx_set_connection_log(c, cscf->error_log); - sscf = ngx_stream_get_module_srv_conf(s, ngx_stream_ssl_module); - if (sscf->ssl.ctx) { if (SSL_set_SSL_CTX(ssl_conn, sscf->ssl.ctx) == NULL) { goto error; From noreply at nginx.com Wed Feb 5 16:41:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 5 Feb 2025 16:41:02 +0000 (UTC) Subject: [nginx] nginx-1.26.3-RELEASE Message-ID: <20250205164102.B63B648F68@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/1be0fb0c9f9bc3489c7b40576efd6afe6b2eccd5 branches: stable-1.26 commit: 1be0fb0c9f9bc3489c7b40576efd6afe6b2eccd5 user: Sergey Kandaurov date: Wed, 5 Feb 2025 14:38:01 +0400 description: nginx-1.26.3-RELEASE --- docs/xml/nginx/changes.xml | 81 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/docs/xml/nginx/changes.xml b/docs/xml/nginx/changes.xml index e68c656fa..91e27f8fa 100644 --- a/docs/xml/nginx/changes.xml +++ b/docs/xml/nginx/changes.xml @@ -5,6 +5,87 @@ + + + + +недостаточная проверка в обработке виртуальных серверов +при использовании SNI в TLSv1.3 позволяла повторно использовать +SSL-сессию в контексте другого виртуального сервера, +чтобы обойти проверку клиентских SSL-сертификатов (CVE-2025-23419). + + +insufficient check in virtual servers handling with TLSv1.3 SNI +allowed to reuse SSL sessions in a different virtual server, +to bypass client SSL certificates verification (CVE-2025-23419). + + + + + +в модуле ngx_http_mp4_module.
+Спасибо Nils Bars. +
+ +in the ngx_http_mp4_module.
+Thanks to Nils Bars. +
+
+ + + +при использовании zlib-ng +в логах появлялись сообщения "gzip filter failed to use preallocated memory". + + +"gzip filter failed to use preallocated memory" alerts appeared in logs +when using zlib-ng. + + + + + +nginx не мог собрать библиотеку libatomic из исходных текстов, +если использовался параметр --with-libatomic=DIR. + + +nginx could not build libatomic library using the library sources +if the --with-libatomic=DIR option was used. + + + + + +теперь nginx игнорирует пакеты согласования версий QUIC от клиентов. + + +nginx now ignores QUIC version negotiation packets from clients. + + + + + +nginx не собирался на Solaris 10 и более ранних +с модулем ngx_http_v3_module. + + +nginx could not be built on Solaris 10 and earlier +with the ngx_http_v3_module. + + + + + +Исправления в HTTP/3. + + +Bugfixes in HTTP/3. + + + +
+ + From noreply at nginx.com Wed Feb 5 16:46:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 5 Feb 2025 16:46:02 +0000 (UTC) Subject: [nginx] Annotated tag created: release-1.26.3 Message-ID: <20250205164602.B25EE48F58@pubserv1.nginx> details: https://github.com/nginx/nginx/releases/tag/release-1.26.3 branches: commit: 1be0fb0c9f9bc3489c7b40576efd6afe6b2eccd5 user: Sergey Kandaurov date: Wed Feb 5 20:44:40 2025 +0400 description: release-1.26.3 tag From noreply at nginx.com Wed Feb 5 16:46:03 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 5 Feb 2025 16:46:03 +0000 (UTC) Subject: [nginx] Annotated tag created: release-1.27.4 Message-ID: <20250205164603.684B848F59@pubserv1.nginx> details: https://github.com/nginx/nginx/releases/tag/release-1.27.4 branches: commit: ecb809305e54ed15be9f620d56b19ff4e4be7db5 user: Sergey Kandaurov date: Wed Feb 5 20:44:18 2025 +0400 description: release-1.27.4 tag From noreply at nginx.com Sat Feb 8 02:15:01 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Sat, 8 Feb 2025 02:15:01 +0000 (UTC) Subject: [njs] Tests262: fixed merge() with null values. Message-ID: <20250208021501.F13C048F6D@pubserv1.nginx> details: https://github.com/nginx/njs/commit/a5e4a75604135149c6e9d616378372c664033dc9 branches: master commit: a5e4a75604135149c6e9d616378372c664033dc9 user: Dmitry Volyntsev date: Thu, 30 Jan 2025 19:10:29 -0800 description: Tests262: fixed merge() with null values. --- test/harness/runTsuite.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/harness/runTsuite.js b/test/harness/runTsuite.js index 5ba0ef46..1823e4a2 100644 --- a/test/harness/runTsuite.js +++ b/test/harness/runTsuite.js @@ -63,7 +63,7 @@ function merge(to, from) { if (typeof r[v] == 'object' && typeof from[v] == 'object') { r[v] = merge(r[v], from[v]); - } else if (typeof from[v] == 'object') { + } else if (typeof from[v] == 'object' && from[v] !== null) { if (Buffer.isBuffer(from[v])) { r[v] = Buffer.from(from[v]); From noreply at nginx.com Sat Feb 8 02:15:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Sat, 8 Feb 2025 02:15:02 +0000 (UTC) Subject: [njs] QuickJS: fixed shared dict in stream module. Message-ID: <20250208021502.011B048F73@pubserv1.nginx> details: https://github.com/nginx/njs/commit/246f4ca082f125fd1fd20ddc7cb45e40f19de04d branches: master commit: 246f4ca082f125fd1fd20ddc7cb45e40f19de04d user: Dmitry Volyntsev date: Mon, 3 Feb 2025 09:07:22 -0800 description: QuickJS: fixed shared dict in stream module. The change was missed in 352c2e59 (0.8.8). --- nginx/ngx_stream_js_module.c | 1 + 1 file changed, 1 insertion(+) diff --git a/nginx/ngx_stream_js_module.c b/nginx/ngx_stream_js_module.c index 5f4c6a04..21f65165 100644 --- a/nginx/ngx_stream_js_module.c +++ b/nginx/ngx_stream_js_module.c @@ -836,6 +836,7 @@ static JSClassDef ngx_stream_qjs_variables_class = { qjs_module_t *njs_stream_qjs_addon_modules[] = { &ngx_qjs_ngx_module, + &ngx_qjs_ngx_shared_dict_module, /* * Shared addons should be in the same order and the same positions * in all nginx modules. From noreply at nginx.com Sat Feb 8 02:15:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Sat, 8 Feb 2025 02:15:02 +0000 (UTC) Subject: [njs] QuickJS: added querystring module. Message-ID: <20250208021502.0D5A048F74@pubserv1.nginx> details: https://github.com/nginx/njs/commit/f289dcb99a9e4c9b72ca8d1c60659a43e58547cd branches: master commit: f289dcb99a9e4c9b72ca8d1c60659a43e58547cd user: Dmitry Volyntsev date: Wed, 29 Jan 2025 21:35:08 -0800 description: QuickJS: added querystring module. --- auto/qjs_modules | 6 + external/qjs_query_string_module.c | 1009 ++++++++++++++++++++++++++++++++++++ src/test/njs_unit_test.c | 354 ------------- test/harness/compareObjects.js | 10 +- test/querystring.t.mjs | 253 +++++++++ 5 files changed, 1277 insertions(+), 355 deletions(-) diff --git a/auto/qjs_modules b/auto/qjs_modules index 50ce9476..d82f9d9f 100644 --- a/auto/qjs_modules +++ b/auto/qjs_modules @@ -13,6 +13,12 @@ njs_module_srcs=external/qjs_fs_module.c . auto/qjs_module +njs_module_name=qjs_query_string_module +njs_module_incs= +njs_module_srcs=external/qjs_query_string_module.c + +. auto/qjs_module + if [ $NJS_OPENSSL = YES -a $NJS_HAVE_OPENSSL = YES ]; then njs_module_name=qjs_webcrypto_module njs_module_incs= diff --git a/external/qjs_query_string_module.c b/external/qjs_query_string_module.c new file mode 100644 index 00000000..85d2fcb3 --- /dev/null +++ b/external/qjs_query_string_module.c @@ -0,0 +1,1009 @@ + +/* + * Copyright (C) Dmitry Volyntsev + * Copyright (C) F5, Inc. + */ + + +#include + +static JSValue qjs_query_string_parse(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv); +static JSValue qjs_query_string_stringify(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv); +static JSValue qjs_query_string_escape(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv); +static JSValue qjs_query_string_unescape(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv); +static JSValue qjs_query_string_decode(JSContext *cx, const u_char *start, + size_t size); +static int qjs_query_string_encode(njs_chb_t *chain, njs_str_t *str); +static JSValue qjs_query_string_parser(JSContext *cx, u_char *query, + u_char *end, njs_str_t *sep, njs_str_t *eq, JSValue decode, + unsigned max_keys); +static JSModuleDef *qjs_querystring_init(JSContext *ctx, const char *name); + + +static const JSCFunctionListEntry qjs_querystring_export[] = { + JS_CFUNC_DEF("decode", 4, qjs_query_string_parse), + JS_CFUNC_DEF("encode", 4, qjs_query_string_stringify), + JS_CFUNC_DEF("escape", 1, qjs_query_string_escape), + JS_CFUNC_DEF("parse", 4, qjs_query_string_parse), + JS_CFUNC_DEF("stringify", 4, qjs_query_string_stringify), + JS_CFUNC_DEF("unescape", 1, qjs_query_string_unescape), +}; + + +qjs_module_t qjs_query_string_module = { + .name = "querystring", + .init = qjs_querystring_init, +}; + + +static JSValue +qjs_query_string_parse(JSContext *cx, JSValueConst this_val, int argc, + JSValueConst *argv) +{ + int64_t max_keys; + JSValue options, ret, decode, native; + njs_str_t str, sep, eq; + + sep.start = NULL; + eq.start = NULL; + str.start = NULL; + + max_keys = 1000; + decode = JS_UNDEFINED; + + if (!JS_IsNullOrUndefined(argv[1])) { + sep.start = (u_char *) JS_ToCStringLen(cx, &sep.length, argv[1]); + if (sep.start == NULL) { + return JS_EXCEPTION; + } + } + + if (!JS_IsNullOrUndefined(argv[2])) { + eq.start = (u_char *) JS_ToCStringLen(cx, &eq.length, argv[2]); + if (eq.start == NULL) { + JS_FreeCString(cx, (char *) sep.start); + return JS_EXCEPTION; + } + } + + options = argv[3]; + if (JS_IsObject(options)) { + ret = JS_GetPropertyStr(cx, options, "maxKeys"); + if (JS_IsException(ret)) { + goto fail; + } + + if (!JS_IsUndefined(ret)) { + if (JS_ToInt64(cx, &max_keys, ret) < 0) { + JS_FreeValue(cx, ret); + goto fail; + } + + JS_FreeValue(cx, ret); + + if (max_keys < 0) { + max_keys = INT64_MAX; + } + } + + decode = JS_GetPropertyStr(cx, options, "decodeURIComponent"); + if (JS_IsException(decode)) { + goto fail; + } + + if (!JS_IsUndefined(decode) && !JS_IsFunction(cx, decode)) { + JS_ThrowTypeError(cx, "option decodeURIComponent is not " + "a function"); + goto fail; + } + } + + if (JS_IsNullOrUndefined(decode)) { + decode = JS_GetPropertyStr(cx, this_val, "unescape"); + if (JS_IsException(decode)) { + goto fail; + } + + if (!JS_IsFunction(cx, decode)) { + JS_ThrowTypeError(cx, "QueryString.unescape is not a function"); + goto fail; + } + + native = JS_GetPropertyStr(cx, decode, "native"); + if (JS_IsException(native)) { + goto fail; + } + + if (JS_IsBool(native)) { + JS_FreeValue(cx, decode); + decode = JS_NULL; + } + } + + str.start = (u_char *) JS_ToCStringLen(cx, &str.length, argv[0]); + if (str.start == NULL) { + goto fail; + } + + ret = qjs_query_string_parser(cx, str.start, str.start + str.length, + sep.start ? &sep : NULL, + eq.start ? &eq : NULL, decode, max_keys); + + JS_FreeValue(cx, decode); + + if (sep.start != NULL) { + JS_FreeCString(cx, (char *) sep.start); + } + + if (eq.start != NULL) { + JS_FreeCString(cx, (char *) eq.start); + } + + JS_FreeCString(cx, (char *) str.start); + + return ret; + +fail: + + JS_FreeValue(cx, decode); + + if (sep.start != NULL) { + JS_FreeCString(cx, (char *) sep.start); + } + + if (eq.start != NULL) { + JS_FreeCString(cx, (char *) eq.start); + } + + if (str.start != NULL) { + JS_FreeCString(cx, (char *) str.start); + } + + return JS_EXCEPTION; +} + + +static JSValue +qjs_query_string_decode(JSContext *cx, const u_char *start, size_t size) +{ + u_char *dst; + JSValue ret; + uint32_t cp; + njs_chb_t chain; + const u_char *p, *end; + njs_unicode_decode_t ctx; + + static const int8_t hex[256] + njs_aligned(32) = + { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + }; + + NJS_CHB_CTX_INIT(&chain, cx); + njs_utf8_decode_init(&ctx); + + cp = 0; + + p = start; + end = p + size; + + while (p < end) { + if (*p == '%' && end - p > 2 && hex[p[1]] >= 0 && hex[p[2]] >= 0) { + cp = njs_utf8_consume(&ctx, (hex[p[1]] << 4) | hex[p[2]]); + p += 3; + + } else { + if (*p == '+') { + cp = ' '; + p++; + + } else { + cp = njs_utf8_decode(&ctx, &p, end); + } + } + + if (cp > NJS_UNICODE_MAX_CODEPOINT) { + if (cp == NJS_UNICODE_CONTINUE) { + continue; + } + + cp = NJS_UNICODE_REPLACEMENT; + } + + dst = njs_chb_reserve(&chain, 4); + if (dst == NULL) { + JS_ThrowOutOfMemory(cx); + return JS_EXCEPTION; + } + + njs_chb_written(&chain, njs_utf8_encode(dst, cp) - dst); + } + + if (cp == NJS_UNICODE_CONTINUE) { + dst = njs_chb_reserve(&chain, 3); + if (dst == NULL) { + JS_ThrowOutOfMemory(cx); + return JS_EXCEPTION; + } + + njs_chb_written(&chain, + njs_utf8_encode(dst, NJS_UNICODE_REPLACEMENT) - dst); + } + + + ret = qjs_string_create_chb(cx, &chain); + + njs_chb_destroy(&chain); + + return ret; +} + + +static JSValue +qjs_query_string_escape(JSContext *cx, JSValueConst this_val, int argc, + JSValueConst *argv) +{ + JSValue ret; + njs_str_t str; + njs_chb_t chain; + + str.start = (u_char *) JS_ToCStringLen(cx, &str.length, argv[0]); + if (str.start == NULL) { + return JS_EXCEPTION; + } + + NJS_CHB_CTX_INIT(&chain, cx); + + if (qjs_query_string_encode(&chain, &str) < 0) { + JS_FreeCString(cx, (char *) str.start); + njs_chb_destroy(&chain); + return JS_EXCEPTION; + } + + ret = qjs_string_create_chb(cx, &chain); + + njs_chb_destroy(&chain); + + JS_FreeCString(cx, (char *) str.start); + + return ret; +} + + +static JSValue +qjs_query_string_unescape(JSContext *cx, JSValueConst this_val, int argc, + JSValueConst *argv) +{ + u_char *p, *end; + JSValue ret; + njs_str_t str; + + str.start = (u_char *) JS_ToCStringLen(cx, &str.length, argv[0]); + if (str.start == NULL) { + return JS_EXCEPTION; + } + + p = str.start; + end = p + str.length; + + ret = qjs_query_string_decode(cx, p, end - p); + + JS_FreeCString(cx, (char *) str.start); + + return ret; +} + + +static u_char * +qjs_query_string_match(u_char *p, u_char *end, const njs_str_t *v) +{ + size_t length; + + length = v->length; + + if (length == 1) { + p = njs_strlchr(p, end, v->start[0]); + + if (p == NULL) { + p = end; + } + + return p; + } + + while (p <= (end - length)) { + if (memcmp(p, v->start, length) == 0) { + return p; + } + + p++; + } + + return end; +} + + +static int +qjs_query_string_append(JSContext *cx, JSValue object, const u_char *key, + size_t key_size, const u_char *val, size_t val_size, JSValue decoder) +{ + JSAtom prop; + JSValue name, value, prev, length, ret; + uint32_t len; + + if (JS_IsNullOrUndefined(decoder)) { + name = qjs_query_string_decode(cx, key, key_size); + if (JS_IsException(name)) { + return -1; + } + + value = qjs_query_string_decode(cx, val, val_size); + if (JS_IsException(value)) { + JS_FreeValue(cx, name); + return -1; + } + + } else { + name = JS_NewStringLen(cx, (const char *) key, key_size); + if (JS_IsException(name)) { + return -1; + } + + ret = JS_Call(cx, decoder, JS_UNDEFINED, 1, &name); + JS_FreeValue(cx, name); + if (JS_IsException(ret)) { + return -1; + } + + name = ret; + + value = JS_NewStringLen(cx, (const char *) val, val_size); + if (JS_IsException(value)) { + return -1; + } + + ret = JS_Call(cx, decoder, JS_UNDEFINED, 1, &value); + JS_FreeValue(cx, value); + if (JS_IsException(ret)) { + JS_FreeValue(cx, name); + return -1; + } + + value = ret; + } + + prop = JS_ValueToAtom(cx, name); + JS_FreeValue(cx, name); + if (prop == JS_ATOM_NULL) { + JS_FreeValue(cx, value); + return -1; + } + + prev = JS_GetProperty(cx, object, prop); + if (JS_IsException(prev)) { + JS_FreeAtom(cx, prop); + JS_FreeValue(cx, value); + return -1; + } + + if (JS_IsUndefined(prev)) { + if (JS_SetProperty(cx, object, prop, value) < 0) { + goto exception; + } + + } else if (JS_IsArray(cx, prev)) { + length = JS_GetPropertyStr(cx, prev, "length"); + + if (JS_ToUint32(cx, &len, length) < 0) { + JS_FreeValue(cx, length); + goto exception; + } + + JS_FreeValue(cx, length); + + if (JS_SetPropertyUint32(cx, prev, len, value) < 0) { + goto exception; + } + + JS_FreeValue(cx, prev); + + } else { + ret = JS_NewArray(cx); + if (JS_IsException(ret)) { + goto exception; + } + + if (JS_SetPropertyUint32(cx, ret, 0, prev) < 0) { + JS_FreeValue(cx, ret); + goto exception; + } + + prev = JS_UNDEFINED; + + if (JS_SetPropertyUint32(cx, ret, 1, value) < 0) { + JS_FreeValue(cx, ret); + goto exception; + } + + value = JS_UNDEFINED; + + if (JS_SetProperty(cx, object, prop, ret) < 0) { + JS_FreeValue(cx, ret); + goto exception; + } + } + + JS_FreeAtom(cx, prop); + + return 0; + +exception: + + JS_FreeAtom(cx, prop); + JS_FreeValue(cx, prev); + JS_FreeValue(cx, value); + + return -1; +} + + +static JSValue +qjs_query_string_parser(JSContext *cx, u_char *query, u_char *end, + njs_str_t *sep, njs_str_t *eq, JSValue decode, unsigned max_keys) +{ + size_t size; + u_char *part, *key, *val; + JSValue obj; + unsigned count; + njs_str_t sep_val, eq_val; + + if (sep == NULL || sep->length == 0) { + sep = &sep_val; + sep->start = (u_char *) "&"; + sep->length = 1; + } + + if (eq == NULL || eq->length == 0) { + eq = &eq_val; + eq->start = (u_char *) "="; + eq->length = 1; + } + + obj = JS_NewObject(cx); + if (JS_IsException(obj)) { + return JS_EXCEPTION; + } + + count = 0; + + key = query; + + while (key < end) { + if (count++ == max_keys) { + break; + } + + part = qjs_query_string_match(key, end, sep); + + if (part == key) { + goto next; + } + + val = qjs_query_string_match(key, part, eq); + + size = val - key; + + if (val != part) { + val += eq->length; + } + + if (qjs_query_string_append(cx, obj, key, size, val, part - val, + decode) < 0) + { + JS_FreeValue(cx, obj); + return JS_EXCEPTION; + } + +next: + + key = part + sep->length; + } + + return obj; +} + + +static inline int +qjs_need_escape(const uint32_t *escape, uint32_t byte) +{ + return ((escape[byte >> 5] & ((uint32_t) 1 << (byte & 0x1f))) != 0); +} + + +static inline u_char * +qjs_string_encode(const uint32_t *escape, size_t size, const u_char *src, + u_char *dst) +{ + uint8_t byte; + static const u_char hex[16] = "0123456789ABCDEF"; + + do { + byte = *src++; + + if (qjs_need_escape(escape, byte)) { + *dst++ = '%'; + *dst++ = hex[byte >> 4]; + *dst++ = hex[byte & 0xf]; + + } else { + *dst++ = byte; + } + + size--; + + } while (size != 0); + + return dst; +} + + +static int +qjs_query_string_encode(njs_chb_t *chain, njs_str_t *str) +{ + size_t size; + u_char *p, *start, *end; + + static const uint32_t escape[] = { + 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ + + /* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */ + 0xfc00987d, /* 1111 1100 0000 0000 1001 1000 0111 1101 */ + + /* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */ + 0x78000001, /* 0111 1000 0000 0000 0000 0000 0000 0001 */ + + /* ~}| {zyx wvut srqp onml kjih gfed cba` */ + 0xb8000001, /* 1011 1000 0000 0000 0000 0000 0000 0001 */ + + 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ + 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ + 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ + 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ + }; + + if (chain->error) { + return -1; + } + + if (str->length == 0) { + return 0; + } + + p = str->start; + end = p + str->length; + + size = str->length; + while (p < end) { + if (qjs_need_escape(escape, *p++)) { + size += 2; + } + } + + start = njs_chb_reserve(chain, size); + if (start == NULL) { + return -1; + } + + if (size == str->length) { + memcpy(start, str->start, str->length); + njs_chb_written(chain, str->length); + return 0; + } + + qjs_string_encode(escape, str->length, str->start, start); + + njs_chb_written(chain, size); + + return 0; +} + + +static inline int +qjs_query_string_encoder_call(JSContext *cx, njs_chb_t *chain, + JSValue encoder, JSValue value) +{ + int rc; + JSValue ret; + njs_str_t str; + + if (JS_IsNullOrUndefined(encoder)) { + str.start = (u_char *) JS_ToCStringLen(cx, &str.length, value); + if (str.start == NULL) { + return -1; + } + + rc = qjs_query_string_encode(chain, &str); + JS_FreeCString(cx, (char *) str.start); + return rc; + } + + ret = JS_Call(cx, encoder, JS_UNDEFINED, 1, &value); + if (JS_IsException(ret)) { + return -1; + } + + str.start = (u_char *) JS_ToCStringLen(cx, &str.length, ret); + JS_FreeValue(cx, ret); + if (str.start == NULL) { + return -1; + } + + njs_chb_append_str(chain, &str); + + JS_FreeCString(cx, (char *) str.start); + + return 0; +} + + +static inline int +qjs_query_string_push(JSContext *cx, njs_chb_t *chain, JSValue key, + JSValue value, njs_str_t *eq, JSValue encoder) +{ + if (qjs_query_string_encoder_call(cx, chain, encoder, key) < 0) { + return -1; + } + + njs_chb_append(chain, eq->start, eq->length); + + if (JS_IsNumber(value) + || JS_IsBool(value) + || JS_IsString(value)) + { + return qjs_query_string_encoder_call(cx, chain, encoder, value); + } + + return 0; +} + + +static inline int +qjs_query_string_push_array(JSContext *cx, njs_chb_t *chain, JSValue key, + JSValue array, njs_str_t *eq, njs_str_t *sep, JSValue encoder) +{ + int rc; + JSValue val, len; + uint32_t i, length; + + len = JS_GetPropertyStr(cx, array, "length"); + if (JS_IsException(len)) { + return -1; + } + + if (JS_ToUint32(cx, &length, len) < 0) { + JS_FreeValue(cx, len); + return -1; + } + + JS_FreeValue(cx, len); + + for (i = 0; i < length; i++) { + if (chain->last != NULL) { + njs_chb_append(chain, sep->start, sep->length); + } + + val = JS_GetPropertyUint32(cx, array, i); + if (JS_IsException(val)) { + return -1; + } + + rc = qjs_query_string_push(cx, chain, key, val, eq, encoder); + JS_FreeValue(cx, val); + if (rc != 0) { + return -1; + } + } + + return 0; +} + + +static void +qjs_free_prop_enum(JSContext *cx, JSPropertyEnum *tab, uint32_t len) +{ + uint32_t i; + + for (i = 0; i < len; i++) { + JS_FreeAtom(cx, tab[i].atom); + } + + js_free(cx, tab); +} + + +static JSValue +qjs_query_string_stringify_internal(JSContext *cx, JSValue obj, njs_str_t *sep, + njs_str_t *eq, JSValue encoder) +{ + int rc; + uint32_t n, length; + JSValue key, val, ret; + njs_str_t sep_val, eq_val; + njs_chb_t chain; + JSPropertyEnum *ptab; + + if (!JS_IsObject(obj)) { + return JS_NewString(cx, ""); + } + + if (JS_GetOwnPropertyNames(cx, &ptab, &length, obj, + JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY) + < 0) + { + return JS_EXCEPTION; + } + + if (sep == NULL || sep->length == 0) { + sep = &sep_val; + sep->start = (u_char *) "&"; + sep->length = 1; + } + + if (eq == NULL || eq->length == 0) { + eq = &eq_val; + eq->start = (u_char *) "="; + eq->length = 1; + } + + NJS_CHB_CTX_INIT(&chain, cx); + + for (n = 0; n < length; n++) { + val = JS_GetProperty(cx, obj, ptab[n].atom); + if (JS_IsException(val)) { + goto fail; + } + + if (JS_IsArray(cx, val)) { + key = JS_AtomToString(cx, ptab[n].atom); + if (JS_IsException(key)) { + JS_FreeValue(cx, val); + goto fail; + } + + rc = qjs_query_string_push_array(cx, &chain, key, val, eq, sep, + encoder); + JS_FreeValue(cx, key); + JS_FreeValue(cx, val); + if (rc != 0) { + goto fail; + } + + continue; + } + + if (n != 0) { + njs_chb_append(&chain, sep->start, sep->length); + } + + key = JS_AtomToString(cx, ptab[n].atom); + if (JS_IsException(key)) { + JS_FreeValue(cx, val); + goto fail; + } + + rc = qjs_query_string_push(cx, &chain, key, val, eq, encoder); + JS_FreeValue(cx, key); + JS_FreeValue(cx, val); + if (rc != 0) { + goto fail; + } + } + + if (ptab != NULL) { + qjs_free_prop_enum(cx, ptab, length); + } + + ret = qjs_string_create_chb(cx, &chain); + + njs_chb_destroy(&chain); + + return ret; + +fail: + + if (ptab != NULL) { + qjs_free_prop_enum(cx, ptab, length); + } + + njs_chb_destroy(&chain); + + return JS_EXCEPTION; +} + + +static JSValue +qjs_query_string_stringify(JSContext *cx, JSValueConst this_val, int argc, + JSValueConst *argv) +{ + JSValue options, ret, encode, native; + njs_str_t sep, eq; + + sep.start = NULL; + eq.start = NULL; + + encode = JS_UNDEFINED; + + if (!JS_IsNullOrUndefined(argv[1])) { + sep.start = (u_char *) JS_ToCStringLen(cx, &sep.length, argv[1]); + if (sep.start == NULL) { + return JS_EXCEPTION; + } + } + + if (!JS_IsNullOrUndefined(argv[2])) { + eq.start = (u_char *) JS_ToCStringLen(cx, &eq.length, argv[2]); + if (eq.start == NULL) { + JS_FreeCString(cx, (char *) sep.start); + return JS_EXCEPTION; + } + } + + options = argv[3]; + if (JS_IsObject(options)) { + encode = JS_GetPropertyStr(cx, options, "encodeURIComponent"); + if (JS_IsException(encode)) { + return JS_EXCEPTION; + } + + if (!JS_IsUndefined(encode) && !JS_IsFunction(cx, encode)) { + JS_ThrowTypeError(cx, "option encodeURIComponent is not " + "a function"); + goto fail; + } + } + + if (JS_IsNullOrUndefined(encode)) { + encode = JS_GetPropertyStr(cx, this_val, "escape"); + if (JS_IsException(encode)) { + goto fail; + } + + if (!JS_IsFunction(cx, encode)) { + JS_ThrowTypeError(cx, "QueryString.escape is not a function"); + goto fail; + } + + native = JS_GetPropertyStr(cx, encode, "native"); + if (JS_IsException(native)) { + goto fail; + } + + if (JS_IsBool(native)) { + JS_FreeValue(cx, encode); + encode = JS_NULL; + } + } + + ret = qjs_query_string_stringify_internal(cx, argv[0], + sep.start ? &sep : NULL, + eq.start ? &eq : NULL, encode); + + JS_FreeValue(cx, encode); + + if (sep.start != NULL) { + JS_FreeCString(cx, (char *) sep.start); + } + + if (eq.start != NULL) { + JS_FreeCString(cx, (char *) eq.start); + } + + return ret; + +fail: + + JS_FreeValue(cx, encode); + + if (sep.start != NULL) { + JS_FreeCString(cx, (char *) sep.start); + } + + if (eq.start != NULL) { + JS_FreeCString(cx, (char *) eq.start); + } + + return JS_EXCEPTION; +} + + +static int +qjs_querystring_module_init(JSContext *ctx, JSModuleDef *m) +{ + int rc; + JSValue proto, method; + + proto = JS_NewObject(ctx); + if (JS_IsException(proto)) { + return -1; + } + + JS_SetPropertyFunctionList(ctx, proto, qjs_querystring_export, + njs_nitems(qjs_querystring_export)); + + method = JS_GetPropertyStr(ctx, proto, "escape"); + if (JS_IsException(method)) { + return -1; + } + + /* Marking the default "escape" function for the fast path. */ + + if (JS_SetPropertyStr(ctx, method, "native", JS_NewBool(ctx, 1)) < 0) { + JS_FreeValue(ctx, method); + return -1; + } + + JS_FreeValue(ctx, method); + + method = JS_GetPropertyStr(ctx, proto, "unescape"); + if (JS_IsException(method)) { + return -1; + } + + /* Marking the default "unescape" function for the fast path. */ + + if (JS_SetPropertyStr(ctx, method, "native", JS_NewBool(ctx, 1)) < 0) { + JS_FreeValue(ctx, method); + return -1; + } + + JS_FreeValue(ctx, method); + + rc = JS_SetModuleExport(ctx, m, "default", proto); + if (rc != 0) { + return -1; + } + + return JS_SetModuleExportList(ctx, m, qjs_querystring_export, + njs_nitems(qjs_querystring_export)); +} + + +static JSModuleDef * +qjs_querystring_init(JSContext *ctx, const char *name) +{ + int rc; + JSModuleDef *m; + + m = JS_NewCModule(ctx, name, qjs_querystring_module_init); + if (m == NULL) { + return NULL; + } + + JS_AddModuleExport(ctx, m, "default"); + rc = JS_AddModuleExportList(ctx, m, qjs_querystring_export, + njs_nitems(qjs_querystring_export)); + if (rc != 0) { + return NULL; + } + + return m; +} diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index 81fee436..e99a6d8a 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -20491,354 +20491,6 @@ static njs_unit_test_t njs_crypto_module_test[] = njs_str("TypeError: \"this\" is not a hash object") }, }; -static njs_unit_test_t njs_querystring_module_test[] = -{ - { njs_str("var qs = require('querystring');" - "var obj = qs.parse('baz=fuz');" - "njs.dump(obj)"), - njs_str("{baz:'fuz'}") }, - - { njs_str("var qs = require('querystring');" - "var obj = qs.parse('baz=');" - "njs.dump(obj)"), - njs_str("{baz:''}") }, - - { njs_str("var qs = require('querystring');" - "var obj = qs.parse('baz=fuz&muz=tax');" - "njs.dump(obj)"), - njs_str("{baz:'fuz',muz:'tax'}") }, - - { njs_str("var qs = require('querystring');" - "var obj = qs.parse('baz=fuz&');" - "njs.dump(obj)"), - njs_str("{baz:'fuz'}") }, - - { njs_str("var qs = require('querystring');" - "var obj = qs.parse('&baz=fuz');" - "njs.dump(obj)"), - njs_str("{baz:'fuz'}") }, - - { njs_str("var qs = require('querystring');" - "var obj = qs.parse('&&&&&baz=fuz');" - "njs.dump(obj)"), - njs_str("{baz:'fuz'}") }, - - { njs_str("var qs = require('querystring');" - "var obj = qs.parse('=fuz');" - "njs.dump(obj)"), - njs_str("{:'fuz'}") }, - - { njs_str("var qs = require('querystring');" - "var obj = qs.parse('=fuz=');" - "njs.dump(obj)"), - njs_str("{:'fuz='}") }, - - { njs_str("var qs = require('querystring');" - "var obj = qs.parse('===fu=z');" - "njs.dump(obj)"), - njs_str("{:'==fu=z'}") }, - - { njs_str("var qs = require('querystring');" - "var obj = qs.parse('baz=fuz&baz=tax');" - "njs.dump(obj)"), - njs_str("{baz:['fuz','tax']}") }, - - { njs_str("var qs = require('querystring');" - "var obj = qs.parse('freespace');" - "njs.dump(obj)"), - njs_str("{freespace:''}") }, - - { njs_str("var qs = require('querystring');" - "var obj = qs.parse('name&value=12');" - "njs.dump(obj)"), - njs_str("{name:'',value:'12'}") }, - - { njs_str("var qs = require('querystring');" - "var obj = qs.parse('baz=fuz&muz=tax', 'fuz');" - "njs.dump(obj)"), - njs_str("{baz:'',&muz:'tax'}") }, - - { njs_str("var qs = require('querystring');" - "var obj = qs.parse('baz=fuz&muz=tax', '');" - "njs.dump(obj)"), - njs_str("{baz:'fuz',muz:'tax'}") }, - - { njs_str("var qs = require('querystring');" - "var obj = qs.parse('baz=fuz&muz=tax', null);" - "njs.dump(obj)"), - njs_str("{baz:'fuz',muz:'tax'}") }, - - { njs_str("var qs = require('querystring');" - "var obj = qs.parse('baz=fuz&muz=tax', undefined);" - "njs.dump(obj)"), - njs_str("{baz:'fuz',muz:'tax'}") }, - - { njs_str("var qs = require('querystring');" - "var obj = qs.parse('baz=fuz123muz=tax', 123);" - "njs.dump(obj)"), - njs_str("{baz:'fuz',muz:'tax'}") }, - - { njs_str("var qs = require('querystring');" - "var obj = qs.parse('baz=fuzαααmuz=tax', 'ααα');" - "njs.dump(obj)"), - njs_str("{baz:'fuz',muz:'tax'}") }, - - { njs_str("var qs = require('querystring');" - "var obj = qs.parse('baz=fuz&muz=tax', '=');" - "njs.dump(obj)"), - njs_str("{baz:'',fuz&muz:'',tax:''}") }, - - { njs_str("var qs = require('querystring');" - "var obj = qs.parse('baz=fuz&muz=tax', null, 'fuz');" - "njs.dump(obj)"), - njs_str("{baz=:'',muz=tax:''}") }, - - { njs_str("var qs = require('querystring');" - "var obj = qs.parse('baz=fuz&muz=tax', null, '&');" - "njs.dump(obj)"), - njs_str("{baz=fuz:'',muz=tax:''}") }, - - { njs_str("var qs = require('querystring');" - "var obj = qs.parse('baz123fuz&muz123tax', null, 123);" - "njs.dump(obj)"), - njs_str("{baz:'fuz',muz:'tax'}") }, - - { njs_str("var qs = require('querystring');" - "var obj = qs.parse('bazαααfuz&muzαααtax', null, 'ααα');" - "njs.dump(obj)"), - njs_str("{baz:'fuz',muz:'tax'}") }, - - { njs_str("var qs = require('querystring');" - "var obj = qs.parse('baz=fuz&muz=tax', null, null, {maxKeys: 1});" - "njs.dump(obj)"), - njs_str("{baz:'fuz'}") }, - - { njs_str("var qs = require('querystring'); var out = [];" - "var obj = qs.parse('baz=fuz&muz=tax', null, null, {decodeURIComponent: (key) => {out.push(key)}});" - "out.join('; ');"), - njs_str("baz; fuz; muz; tax") }, - - { njs_str("var qs = require('querystring'); var i = 0;" - "var obj = qs.parse('baz=fuz&muz=tax', null, null, {decodeURIComponent: (key) => 'α' + i++});" - "njs.dump(obj);"), - njs_str("{α0:'α1',α2:'α3'}") }, - - { njs_str("var qs = require('querystring');" - "qs.parse('baz=fuz&muz=tax', null, null, {decodeURIComponent: 123});"), - njs_str("TypeError: option decodeURIComponent is not a function") }, - - { njs_str("var qs = require('querystring');" - "qs.unescape = 123;" - "qs.parse('baz=fuz&muz=tax');"), - njs_str("TypeError: QueryString.unescape is not a function") }, - - { njs_str("var qs = require('querystring'); var out = [];" - "qs.unescape = (key) => {out.push(key)};" - "qs.parse('baz=fuz&muz=tax');" - "out.join('; ');"), - njs_str("baz; fuz; muz; tax") }, - - { njs_str("var qs = require('querystring');" - "var obj = qs.parse('ba%32z=f%32uz');" - "njs.dump(obj)"), - njs_str("{ba2z:'f2uz'}") }, - - { njs_str("var qs = require('querystring');" - "var obj = qs.parse('ba%32z=f%32uz');" - "njs.dump(obj)"), - njs_str("{ba2z:'f2uz'}") }, - - { njs_str("var qs = require('querystring');" - "var obj = qs.parse('ba%F0%9F%92%A9z=f%F0%9F%92%A9uz');" - "njs.dump(obj)"), - njs_str("{ba💩z:'f💩uz'}") }, - - { njs_str("var qs = require('querystring');" - "var obj = qs.parse('======');" - "njs.dump(obj)"), - njs_str("{:'====='}") }, - - { njs_str("var qs = require('querystring');" - "var obj = qs.parse('baz=%F0%9F%A9');" - "njs.dump(obj)"), - njs_str("{baz:'�'}") }, - - { njs_str("var qs = require('querystring');" - "var obj = qs.parse('baz=αααααα%\x00\x01\x02αααα');" - "njs.dump(obj)"), - njs_str("{baz:'αααααα%\\u0000\\u0001\\u0002αααα'}") }, - - { njs_str("var qs = require('querystring');" - "var obj = qs.parse('baz=%F6α');" - "njs.dump(obj)"), - njs_str("{baz:'�α'}") }, - - { njs_str("var qs = require('querystring');" - "var obj = qs.parse('baz=%F6');" - "njs.dump(obj)"), - njs_str("{baz:'�'}") }, - - { njs_str("var qs = require('querystring');" - "var obj = qs.parse('baz=%FG');" - "njs.dump(obj)"), - njs_str("{baz:'%FG'}") }, - - { njs_str("var qs = require('querystring');" - "var obj = qs.parse('baz=%F');" - "njs.dump(obj)"), - njs_str("{baz:'%F'}") }, - - { njs_str("var qs = require('querystring');" - "var obj = qs.parse('baz=%');" - "njs.dump(obj)"), - njs_str("{baz:'%'}") }, - - { njs_str("var qs = require('querystring');" - "var obj = qs.parse('ba+z=f+uz');" - "njs.dump(obj)"), - njs_str("{ba z:'f uz'}") }, - - - { njs_str("var qs = require('querystring');" - "qs.parse('X='+'α'.repeat(33)).X.length"), - njs_str("33") }, - - { njs_str("var qs = require('querystring');" - "var x = qs.parse('X='+'α1'.repeat(33)).X;" - "[x.length, x[33], x[34]]"), - njs_str("66,1,α") }, - - { njs_str("var qs = require('querystring');" - "qs.stringify({'baz': 'fuz'})"), - njs_str("baz=fuz") }, - - { njs_str("var qs = require('querystring');" - "qs.stringify({'baz': 'fuz', 'muz': 'tax'})"), - njs_str("baz=fuz&muz=tax") }, - - { njs_str("var qs = require('querystring');" - "qs.stringify({'baαz': 'fαuz', 'muαz': 'tαax'});"), - njs_str("ba%CE%B1z=f%CE%B1uz&mu%CE%B1z=t%CE%B1ax") }, - - { njs_str("var qs = require('querystring');" - "qs.stringify({'baz': ['fuz', 'tax']})"), - njs_str("baz=fuz&baz=tax") }, - - { njs_str("var qs = require('querystring');" - njs_declare_sparse_array("arr", 2) - "arr[0] = 0; arr[1] = 1.5;" - "qs.stringify({'baz': arr})"), - njs_str("baz=0&baz=1.5") }, - - { njs_str("var qs = require('querystring'); var out = [];" - "qs.stringify({'baz': 'fuz', 'muz': 'tax'}, null, null, {encodeURIComponent: (key) => {out.push(key)}});" - "out.join('; ')"), - njs_str("baz; fuz; muz; tax") }, - - { njs_str("var qs = require('querystring');" - "qs.stringify({'baz': 'fuz', 'muz': 'tax'}, null, null, {encodeURIComponent: 123});" - "out.join('; ')"), - njs_str("TypeError: option encodeURIComponent is not a function") }, - - { njs_str("var qs = require('querystring');" - "qs.escape = 123;" - "qs.stringify({'baz': 'fuz', 'muz': 'tax'})"), - njs_str("TypeError: QueryString.escape is not a function") }, - - { njs_str("var qs = require('querystring'); var out = [];" - "qs.escape = (key) => {out.push(key)};" - "qs.stringify({'baz': 'fuz', 'muz': 'tax'});" - "out.join('; ')"), - njs_str("baz; fuz; muz; tax") }, - - { njs_str("var qs = require('querystring');" - "qs.stringify({'baz': 'fuz', 'muz': 'tax'}, '****')"), - njs_str("baz=fuz****muz=tax") }, - - { njs_str("var qs = require('querystring');" - "qs.stringify({'baz': 'fuz', 'muz': 'tax'}, null, '^^^^')"), - njs_str("baz^^^^fuz&muz^^^^tax") }, - - { njs_str("var qs = require('querystring');" - "var obj = {A:'α'}; obj['δ'] = 'D';" - "var s = qs.stringify(obj,'γ=','&β'); [s, s.length]"), - njs_str("A&β%CE%B1γ=%CE%B4&βD,20") }, - - { njs_str("var qs = require('querystring');" - "qs.stringify({'baz': 'fuz', 'muz': 'tax'}, '', '')"), - njs_str("baz=fuz&muz=tax") }, - - { njs_str("var qs = require('querystring');" - "qs.stringify({'baz': 'fuz', 'muz': 'tax'}, undefined, undefined)"), - njs_str("baz=fuz&muz=tax") }, - - { njs_str("var qs = require('querystring');" - "qs.stringify({'baz': 'fuz', 'muz': 'tax'}, '?', '/')"), - njs_str("baz/fuz?muz/tax") }, - - { njs_str("var qs = require('querystring');" - "qs.stringify('123')"), - njs_str("") }, - - { njs_str("var qs = require('querystring');" - "qs.stringify(123)"), - njs_str("") }, - - { njs_str("var qs = require('querystring');" - "qs.stringify({X:{toString(){return 3}}})"), - njs_str("X=") }, - - { njs_str("var qs = require('querystring');" - "qs.stringify({ name: undefined, age: 12 })"), - njs_str("name=&age=12") }, - - { njs_str("var qs = require('querystring');" - "qs.stringify(Object.create({ name: undefined, age: 12 }))"), - njs_str("") }, - - { njs_str("var qs = require('querystring');" - "qs.stringify([])"), - njs_str("") }, - - { njs_str("var qs = require('querystring');" - "qs.stringify(['','',''])"), - njs_str("0=&1=&2=") }, - - { njs_str("var qs = require('querystring');" - "qs.stringify([undefined, null, Symbol(), Object(0), Object('test'), Object(false),,,])"), - njs_str("0=&1=&2=&3=&4=&5=") }, - -#if 0 - { njs_str("var qs = require('querystring');" - "qs.stringify([NaN, Infinity, -Infinity, 2**69, 2**70])"), - njs_str("0=&1=&2=&3=590295810358705700000&4=1.1805916207174113e%2B21") }, -#else - { njs_str("var qs = require('querystring');" - "qs.stringify([NaN, Infinity, -Infinity, 2**69, 2**70])"), - njs_str("0=&1=&2=&3=590295810358705700000&4=1.1805916207174114e%2B21") }, -#endif - - { njs_str("var qs = require('querystring');" - "qs.stringify([[1,2,3],[4,5,6]])"), - njs_str("0=1&0=2&0=3&1=4&1=5&1=6") }, - - { njs_str("var qs = require('querystring');" - "qs.stringify([['a',,,],['b',,,]])"), - njs_str("0=a&0=&0=&1=b&1=&1=") }, - - { njs_str("var qs = require('querystring');" - "qs.stringify([[,'a','b',,]])"), - njs_str("0=&0=a&0=b&0=") }, - - { njs_str("var qs = require('querystring');" - "qs.escape('abcααααdef')"), - njs_str("abc%CE%B1%CE%B1%CE%B1%CE%B1def") }, - - { njs_str("var qs = require('querystring');" - "qs.unescape('abc%CE%B1%CE%B1%CE%B1%CE%B1def')"), - njs_str("abcααααdef") }, -}; - #define NJS_XML_DOC "const xml = require('xml');" \ "let data = `ToveJani`;" \ @@ -23918,12 +23570,6 @@ static njs_test_suite_t njs_suites[] = njs_nitems(njs_crypto_module_test), njs_unit_test }, - { njs_str("querystring module"), - { .repeat = 1, .unsafe = 1 }, - njs_querystring_module_test, - njs_nitems(njs_querystring_module_test), - njs_unit_test }, - { njs_str("externals"), { .externals = 1, .repeat = 1, .unsafe = 1 }, njs_externals_test, diff --git a/test/harness/compareObjects.js b/test/harness/compareObjects.js index d4a20c15..ea0dad18 100644 --- a/test/harness/compareObjects.js +++ b/test/harness/compareObjects.js @@ -4,6 +4,14 @@ function compareObjects(ref, obj) { } for (const key in ref) { + if (!Object.prototype.hasOwnProperty.call(ref, key)) { + continue; + } + + if (!Object.prototype.hasOwnProperty.call(obj, key)) { + return false; + } + if (!compareObjects(ref[key], obj[key])) { return false; } @@ -13,5 +21,5 @@ function compareObjects(ref, obj) { } function isObject(object) { - return object != null && typeof object === 'object'; + return object !== null && typeof object === 'object'; } diff --git a/test/querystring.t.mjs b/test/querystring.t.mjs new file mode 100644 index 00000000..b382d10d --- /dev/null +++ b/test/querystring.t.mjs @@ -0,0 +1,253 @@ +/*--- +includes: [runTsuite.js, compareObjects.js] +flags: [async] +---*/ + +import qs from 'querystring'; + +let escape_tsuite = { + name: "querystring.escape() tests", + T: async (params) => { + let r = qs.escape(params.value); + + if (r !== params.expected) { + throw Error(`unexpected output "${r}" != "${params.expected}"`); + } + + return 'SUCCESS'; + }, + prepare_args: (args, default_opts) => { + let params = merge({}, default_opts); + params = merge(params, args); + + return params; + }, + opts: { }, + + tests: [ + { value: '', expected: '' }, + { value: 'baz=fuz', expected: 'baz%3Dfuz' }, + { value: 'abcαdef', expected: 'abc%CE%B1def' }, +]}; + +let parse_tsuite = { + name: "querystring.parse() tests", + T: async (params) => { + let r; + let unescape = qs.unescape; + + if (params.unescape) { + qs.unescape = params.unescape; + } + + try { + if (params.options !== undefined) { + r = qs.parse(params.value, params.sep, params.eq, params.options); + + } else if (params.eq !== undefined) { + r = qs.parse(params.value, params.sep, params.eq); + + } else if (params.sep !== undefined) { + r = qs.parse(params.value, params.sep); + + } else { + r = qs.parse(params.value); + } + + } finally { + if (params.unescape) { + qs.unescape = unescape; + } + } + + if (!compareObjects(r, params.expected)) { + throw Error(`unexpected output "${JSON.stringify(r)}" != "${JSON.stringify(params.expected)}"`); + } + + return 'SUCCESS'; + }, + prepare_args: (args, default_opts) => { + let params = merge({}, default_opts); + params = merge(params, args); + + return params; + }, + opts: { }, + + tests: [ + { value: '', expected: {} }, + { value: 'baz=fuz', expected: { baz:'fuz' } }, + { value: 'baz=fuz', expected: { baz:'fuz' } }, + { value: 'baz=fuz&', expected: { baz:'fuz' } }, + { value: '&baz=fuz', expected: { baz:'fuz' } }, + { value: '&&baz=fuz', expected: { baz:'fuz' } }, + { value: 'baz=fuz&muz=tax', expected: { baz:'fuz', muz:'tax' } }, + { value: 'baz=fuz&baz=bar', expected: { baz:['fuz', 'bar'] } }, + + { value: qs.encode({ baz:'fuz', muz:'tax' }), expected: { baz:'fuz', muz:'tax' } }, + + { value: 'baz=fuz&baz=bar', sep: '&', eq: '=', expected: { baz:['fuz', 'bar'] } }, + { value: 'baz=fuz&baz=bar&baz=zap', expected: { baz:['fuz', 'bar', 'zap'] } }, + { value: 'baz=', expected: { baz:'' } }, + { value: '=fuz', expected: { '':'fuz' } }, + { value: '=fuz=', expected: { '':'fuz=' } }, + { value: '==fu=z', expected: { '':'=fu=z' } }, + { value: '===fu=z&baz=bar', expected: { baz:'bar', '':'==fu=z' } }, + { value: 'freespace', expected: { freespace:'' } }, + { value: 'name&value=12', expected: { name:'', value:'12' } }, + { value: 'baz=fuz&muz=tax', sep: 'fuz', expected: { baz:'', '&muz':'tax' } }, + { value: 'baz=fuz&muz=tax', sep: '', expected: { baz:'fuz', 'muz':'tax' } }, + { value: 'baz=fuz&muz=tax', sep: null, expected: { baz:'fuz', 'muz':'tax' } }, + { value: 'baz=fuz123muz=tax', sep: 123, expected: { baz:'fuz', 'muz':'tax' } }, + { value: 'baz=fuzαααmuz=tax', sep: 'ααα', expected: { baz:'fuz', 'muz':'tax' } }, + { value: 'baz=fuz&muz=tax', sep: '=', expected: { baz:'', 'fuz&muz':'', 'tax':'' } }, + + { value: 'baz=fuz&muz=tax', sep: '', eq: '', expected: { baz:'fuz', muz:'tax' } }, + { value: 'baz=fuz&muz=tax', sep: null, eq: 'fuz', expected: { 'baz=':'','muz=tax':'' } }, + { value: 'baz123fuz&muz123tax', sep: null, eq: '123', expected: { baz:'fuz', 'muz':'tax' } }, + { value: 'bazαααfuz&muzαααtax', sep: null, eq: 'ααα', expected: { baz:'fuz', 'muz':'tax' } }, + + { value: 'baz=fuz&muz=tax', sep: null, eq: null, options: { maxKeys: 1 }, expected: { baz:'fuz' } }, + { value: 'baz=fuz&muz=tax', sep: null, eq: null, options: { maxKeys: -1 }, + expected: { baz:'fuz', muz:'tax' } }, + { value: 'baz=fuz&muz=tax', sep: null, eq: null, options: { maxKeys: { valueOf: () => { throw 'Oops'; } }}, + exception: 'Oops' }, + { value: 'baz=fuz&muz=tax', sep: null, eq: null, + options: { decodeURIComponent: (s) => `|${s}|` }, expected: { '|baz|':'|fuz|', '|muz|':'|tax|' } }, + { value: 'baz=fuz&muz=tax', sep: null, eq: null, + options: { decodeURIComponent: 123 }, + exception: 'TypeError: option decodeURIComponent is not a function' }, + { value: 'baz=fuz&muz=tax', unescape: (s) => `|${s}|`, expected: { '|baz|':'|fuz|', '|muz|':'|tax|' } }, + { value: 'baz=fuz&muz=tax', unescape: 123, + exception: 'TypeError: QueryString.unescape is not a function' }, + + { value: 'ba%32z=f%32uz', expected: { ba2z:'f2uz' } }, + { value: 'ba%F0%9F%92%A9z=f%F0%9F%92%A9uz', expected: { 'ba💩z':'f💩uz' } }, + { value: '==', expected: { '':'=' } }, + { value: 'baz=%F0%9F%A9', expected: { baz:'�' } }, + { value: 'baz=α%00%01%02α', expected: { baz:'α' + String.fromCharCode(0, 1, 2) + 'α' } }, + { value: 'baz=%F6', expected: { baz:'�' } }, + { value: 'baz=%FG', expected: { baz:'%FG' } }, + { value: 'baz=%F', expected: { baz:'%F' } }, + { value: 'baz=%', expected: { baz:'%' } }, + { value: 'ba+z=f+uz', expected: { 'ba z':'f uz' } }, + { value: 'X=' + 'α'.repeat(33), expected: { X:'α'.repeat(33) } }, + { value: 'X=' + 'α1'.repeat(33), expected: { X:'α1'.repeat(33) } }, + + { value: {toString: () => { throw 'Oops'; }}, sep: "&", eq: "=", + exception: 'TypeError: Cannot convert object to primitive value' }, +]}; + +let stringify_tsuite = { + name: "querystring.stringify() tests", + T: async (params) => { + let r; + let escape = qs.escape; + + if (params.escape) { + qs.escape = params.escape; + } + + try { + if (params.options !== undefined) { + r = qs.stringify(params.obj, params.sep, params.eq, params.options); + + } else if (params.eq !== undefined) { + r = qs.stringify(params.obj, params.sep, params.eq); + + } else if (params.sep !== undefined) { + r = qs.stringify(params.obj, params.sep); + + } else { + r = qs.stringify(params.obj); + } + + } finally { + if (params.escape) { + qs.escape = escape; + } + } + + if (r !== params.expected) { + throw Error(`unexpected output "${r}" != "${params.expected}"`); + } + + return 'SUCCESS'; + }, + prepare_args: (args, default_opts) => { + let params = merge({}, default_opts); + params = merge(params, args); + + return params; + }, + opts: { }, + + tests: [ + { obj: {}, expected: '' }, + { obj: { baz:'fuz', muz:'tax' }, expected: 'baz=fuz&muz=tax' }, + { obj: { baz:['fuz', 'tax'] }, expected: 'baz=fuz&baz=tax' }, + { obj: {'baαz': 'fαuz', 'muαz': 'tαax' }, expected: 'ba%CE%B1z=f%CE%B1uz&mu%CE%B1z=t%CE%B1ax' }, + { obj: {A:'α', 'δ': 'D' }, expected: 'A=%CE%B1&%CE%B4=D' }, + { obj: { baz:'fuz', muz:'tax' }, sep: '*', expected: 'baz=fuz*muz=tax' }, + { obj: { baz:'fuz', muz:'tax' }, sep: null, eq: '^', expected: 'baz^fuz&muz^tax' }, + { obj: { baz:'fuz', muz:'tax' }, sep: '', eq: '', expected: 'baz=fuz&muz=tax' }, + { obj: { baz:'fuz', muz:'tax' }, sep: '?', eq: '/', expected: 'baz/fuz?muz/tax' }, + { obj: { baz:'fuz', muz:'tax' }, sep: null, eq: null, options: { encodeURIComponent: (key) => `|${key}|` }, + expected: '|baz|=|fuz|&|muz|=|tax|' }, + { obj: { baz:'fuz', muz:'tax' }, sep: null, eq: null, options: { encodeURIComponent: 123 }, + exception: 'TypeError: option encodeURIComponent is not a function' }, + { obj: { baz:'fuz', muz:'tax' }, escape: (key) => `|${key}|`, expected: '|baz|=|fuz|&|muz|=|tax|' }, + { obj: { '':'' }, escape: (s) => s.length == 0 ? '#' : s, expected: '#=#' }, + { obj: { baz:'fuz', muz:'tax' }, escape: 123, + exception: 'TypeError: QueryString.escape is not a function' }, + + { obj: qs.decode('baz=fuz&muz=tax'), expected: 'baz=fuz&muz=tax' }, + + { obj: '123', expected: '' }, + { obj: 123, expected: '' }, + { obj: { baz:'fuz' }, expected: 'baz=fuz' }, + { obj: { baz:undefined }, expected: 'baz=' }, + { obj: Object.create({ baz:'fuz' }), expected: '' }, + { obj: [], expected: '' }, + { obj: ['a'], expected: '0=a' }, + { obj: ['a', 'b'], expected: '0=a&1=b' }, + { obj: ['', ''], expected: '0=&1=' }, + { obj: [undefined, null, Symbol(), Object(0), Object('test'), Object(false),,,], + expected: '0=&1=&2=&3=&4=&5=' }, + { obj: [['a', 'b'], ['c', 'd']], expected: '0=a&0=b&1=c&1=d' }, + { obj: [['a',,,], ['b',,,]], expected: '0=a&0=&0=&1=b&1=&1=' }, + { obj: [[,'a','b',,]], expected: '0=&0=a&0=b&0=' }, +]}; + +let unescape_tsuite = { + name: "querystring.unescape() tests", + T: async (params) => { + let r = qs.unescape(params.value); + + if (r !== params.expected) { + throw Error(`unexpected output "${r}" != "${params.expected}"`); + } + + return 'SUCCESS'; + }, + prepare_args: (args, default_opts) => { + let params = merge({}, default_opts); + params = merge(params, args); + + return params; + }, + opts: { }, + + tests: [ + { value: '', expected: '' }, + { value: 'baz%3Dfuz', expected: 'baz=fuz' }, + { value: 'abc%CE%B1def', expected: 'abcαdef' }, +]}; + +run([ + escape_tsuite, + parse_tsuite, + stringify_tsuite, + unescape_tsuite, +]) +.then($DONE, $DONE); From noreply at nginx.com Tue Feb 11 01:51:01 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Tue, 11 Feb 2025 01:51:01 +0000 (UTC) Subject: [njs] Modules: fixed name corruption in variable and header processing. Message-ID: <20250211015101.E427347897@pubserv1.nginx> details: https://github.com/nginx/njs/commit/ae7d4f42d5d7497e6e8d3d30ff5aebfba228d27c branches: master commit: ae7d4f42d5d7497e6e8d3d30ff5aebfba228d27c user: Dmitry Volyntsev date: Fri, 7 Feb 2025 17:23:09 -0800 description: Modules: fixed name corruption in variable and header processing. The HTTP and Stream JS modules were performing in-place lowercasing of variable and header names, which could inadvertently overwrite the original data. In the NJS engine, the problem did not manifest itself for strings up to 14 bytes long because they are inlined into the value. --- nginx/ngx_http_js_module.c | 106 ++++++++++++++++++++++++++++++++++--------- nginx/ngx_stream_js_module.c | 72 ++++++++++++++++++++++++----- nginx/t/js_headers.t | 16 ++++++- nginx/t/js_variables.t | 26 ++++++++++- nginx/t/stream_js_var.t | 33 +++++++++++++- 5 files changed, 213 insertions(+), 40 deletions(-) diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c index 66cb97c0..7cd87401 100644 --- a/nginx/ngx_http_js_module.c +++ b/nginx/ngx_http_js_module.c @@ -3222,6 +3222,7 @@ ngx_http_js_request_variables(njs_vm_t *vm, njs_object_prop_t *prop, ngx_http_variable_t *v; ngx_http_core_main_conf_t *cmcf; ngx_http_variable_value_t *vv; + u_char storage[64]; rc = njs_vm_prop_name(vm, prop, &val); if (rc != NJS_OK) { @@ -3229,20 +3230,17 @@ ngx_http_js_request_variables(njs_vm_t *vm, njs_object_prop_t *prop, return NJS_DECLINED; } - name.data = val.start; - name.len = val.length; - if (setval == NULL) { is_capture = 1; - for (i = 0; i < name.len; i++) { - if (name.data[i] < '0' || name.data[i] > '9') { + for (i = 0; i < val.length; i++) { + if (val.start[i] < '0' || val.start[i] > '9') { is_capture = 0; break; } } if (is_capture) { - key = ngx_atoi(name.data, name.len) * 2; + key = ngx_atoi(val.start, val.length) * 2; if (r->captures == NULL || r->captures_data == NULL || r->ncaptures <= key) { @@ -3258,7 +3256,21 @@ ngx_http_js_request_variables(njs_vm_t *vm, njs_object_prop_t *prop, } /* Lookup the variable in nginx variables */ - key = ngx_hash_strlow(name.data, name.data, name.len); + + if (val.length < sizeof(storage)) { + name.data = storage; + + } else { + name.data = ngx_pnalloc(r->pool, val.length); + if (name.data == NULL) { + njs_vm_memory_error(vm); + return NJS_ERROR; + } + } + + name.len = val.length; + + key = ngx_hash_strlow(name.data, val.start, val.length); vv = ngx_http_get_variable(r, &name, key); if (vv == NULL || vv->not_found) { @@ -3272,9 +3284,20 @@ ngx_http_js_request_variables(njs_vm_t *vm, njs_object_prop_t *prop, cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); - key = ngx_hash_strlow(name.data, name.data, name.len); + if (val.length < sizeof(storage)) { + name.data = storage; + + } else { + name.data = ngx_pnalloc(r->pool, val.length); + if (name.data == NULL) { + njs_vm_memory_error(vm); + return NJS_ERROR; + } + } + + key = ngx_hash_strlow(name.data, val.start, val.length); - v = ngx_hash_find(&cmcf->variables_hash, key, name.data, name.len); + v = ngx_hash_find(&cmcf->variables_hash, key, name.data, val.length); if (v == NULL) { njs_vm_error(vm, "variable not found"); @@ -3783,6 +3806,7 @@ ngx_http_js_header_in(njs_vm_t *vm, ngx_http_request_t *r, unsigned flags, ngx_table_elt_t **ph; ngx_http_header_t *hh; ngx_http_core_main_conf_t *cmcf; + u_char storage[128]; if (retval == NULL) { return NJS_OK; @@ -3790,10 +3814,15 @@ ngx_http_js_header_in(njs_vm_t *vm, ngx_http_request_t *r, unsigned flags, /* look up hashed headers */ - lowcase_key = ngx_pnalloc(r->pool, name->length); - if (lowcase_key == NULL) { - njs_vm_memory_error(vm); - return NJS_ERROR; + if (name->length < sizeof(storage)) { + lowcase_key = storage; + + } else { + lowcase_key = ngx_pnalloc(r->pool, name->length); + if (lowcase_key == NULL) { + njs_vm_memory_error(vm); + return NJS_ERROR; + } } hash = ngx_hash_strlow(lowcase_key, name->start, name->length); @@ -6130,10 +6159,11 @@ ngx_http_qjs_variables_own_property(JSContext *cx, JSPropertyDescriptor *pdesc, JSValueConst obj, JSAtom prop) { uint32_t buffer_type; - ngx_str_t name; + ngx_str_t name, name_lc; ngx_uint_t i, key, start, length, is_capture; ngx_http_request_t *r; ngx_http_variable_value_t *vv; + u_char storage[64]; r = JS_GetOpaque(obj, NGX_QJS_CLASS_ID_HTTP_VARS); @@ -6184,9 +6214,22 @@ ngx_http_qjs_variables_own_property(JSContext *cx, JSPropertyDescriptor *pdesc, return 1; } - key = ngx_hash_strlow(name.data, name.data, name.len); + if (name.len < sizeof(storage)) { + name_lc.data = storage; + + } else { + name_lc.data = ngx_pnalloc(r->pool, name.len); + if (name_lc.data == NULL) { + (void) JS_ThrowOutOfMemory(cx); + return -1; + } + } + + name_lc.len = name.len; + + key = ngx_hash_strlow(name_lc.data, name.data, name.len); - vv = ngx_http_get_variable(r, &name, key); + vv = ngx_http_get_variable(r, &name_lc, key); JS_FreeCString(cx, (char *) name.data); if (vv == NULL || vv->not_found) { return 0; @@ -6207,6 +6250,7 @@ static int ngx_http_qjs_variables_set_property(JSContext *cx, JSValueConst obj, JSAtom prop, JSValueConst value, JSValueConst receiver, int flags) { + u_char *lowcase_key; ngx_str_t name, s; ngx_uint_t key; ngx_http_js_ctx_t *ctx; @@ -6214,6 +6258,7 @@ ngx_http_qjs_variables_set_property(JSContext *cx, JSValueConst obj, ngx_http_variable_t *v; ngx_http_variable_value_t *vv; ngx_http_core_main_conf_t *cmcf; + u_char storage[64]; r = JS_GetOpaque(obj, NGX_QJS_CLASS_ID_HTTP_VARS); @@ -6231,11 +6276,22 @@ ngx_http_qjs_variables_set_property(JSContext *cx, JSValueConst obj, name.len = ngx_strlen(name.data); - key = ngx_hash_strlow(name.data, name.data, name.len); + if (name.len < sizeof(storage)) { + lowcase_key = storage; + + } else { + lowcase_key = ngx_pnalloc(r->pool, name.len); + if (lowcase_key == NULL) { + (void) JS_ThrowOutOfMemory(cx); + return -1; + } + } + + key = ngx_hash_strlow(lowcase_key, name.data, name.len); cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); - v = ngx_hash_find(&cmcf->variables_hash, key, name.data, name.len); + v = ngx_hash_find(&cmcf->variables_hash, key, lowcase_key, name.len); JS_FreeCString(cx, (char *) name.data); if (v == NULL) { @@ -6500,13 +6556,19 @@ ngx_http_qjs_header_in(JSContext *cx, ngx_http_request_t *r, unsigned flags, ngx_table_elt_t **ph; ngx_http_header_t *hh; ngx_http_core_main_conf_t *cmcf; + u_char storage[128]; /* look up hashed headers */ - lowcase_key = ngx_pnalloc(r->pool, name->len); - if (lowcase_key == NULL) { - (void) JS_ThrowOutOfMemory(cx); - return -1; + if (name->len < sizeof(storage)) { + lowcase_key = storage; + + } else { + lowcase_key = ngx_pnalloc(r->pool, name->len); + if (lowcase_key == NULL) { + (void) JS_ThrowOutOfMemory(cx); + return -1; + } } hash = ngx_hash_strlow(lowcase_key, name->data, name->len); diff --git a/nginx/ngx_stream_js_module.c b/nginx/ngx_stream_js_module.c index 21f65165..a7a923ad 100644 --- a/nginx/ngx_stream_js_module.c +++ b/nginx/ngx_stream_js_module.c @@ -1717,6 +1717,7 @@ ngx_stream_js_session_variables(njs_vm_t *vm, njs_object_prop_t *prop, ngx_stream_variable_t *v; ngx_stream_core_main_conf_t *cmcf; ngx_stream_variable_value_t *vv; + u_char storage[64]; rc = njs_vm_prop_name(vm, prop, &val); if (rc != NJS_OK) { @@ -1724,11 +1725,21 @@ ngx_stream_js_session_variables(njs_vm_t *vm, njs_object_prop_t *prop, return NJS_DECLINED; } - name.data = val.start; - name.len = val.length; - if (setval == NULL) { - key = ngx_hash_strlow(name.data, name.data, name.len); + if (val.length < sizeof(storage)) { + name.data = storage; + + } else { + name.data = ngx_pnalloc(s->connection->pool, val.length); + if (name.data == NULL) { + njs_vm_error(vm, "internal error"); + return NJS_ERROR; + } + } + + name.len = val.length; + + key = ngx_hash_strlow(name.data, val.start, val.length); vv = ngx_stream_get_variable(s, &name, key); if (vv == NULL || vv->not_found) { @@ -1742,9 +1753,20 @@ ngx_stream_js_session_variables(njs_vm_t *vm, njs_object_prop_t *prop, cmcf = ngx_stream_get_module_main_conf(s, ngx_stream_core_module); - key = ngx_hash_strlow(name.data, name.data, name.len); + if (val.length < sizeof(storage)) { + name.data = storage; - v = ngx_hash_find(&cmcf->variables_hash, key, name.data, name.len); + } else { + name.data = ngx_pnalloc(s->connection->pool, val.length); + if (name.data == NULL) { + njs_vm_error(vm, "internal error"); + return NJS_ERROR; + } + } + + key = ngx_hash_strlow(name.data, val.start, val.length); + + v = ngx_hash_find(&cmcf->variables_hash, key, name.data, val.length); if (v == NULL) { njs_vm_error(vm, "variable not found"); @@ -2445,10 +2467,11 @@ ngx_stream_qjs_variables_own_property(JSContext *cx, JSPropertyDescriptor *pdesc, JSValueConst obj, JSAtom prop) { uint32_t buffer_type; - ngx_str_t name; + ngx_str_t name, name_lc; ngx_uint_t key; ngx_stream_session_t *s; ngx_stream_variable_value_t *vv; + u_char storage[64]; s = JS_GetOpaque(obj, NGX_QJS_CLASS_ID_STREAM_VARS); @@ -2467,9 +2490,22 @@ ngx_stream_qjs_variables_own_property(JSContext *cx, name.len = ngx_strlen(name.data); - key = ngx_hash_strlow(name.data, name.data, name.len); + if (name.len < sizeof(storage)) { + name_lc.data = storage; - vv = ngx_stream_get_variable(s, &name, key); + } else { + name_lc.data = ngx_pnalloc(s->connection->pool, name.len); + if (name_lc.data == NULL) { + (void) JS_ThrowOutOfMemory(cx); + return -1; + } + } + + name_lc.len = name.len; + + key = ngx_hash_strlow(name_lc.data, name.data, name.len); + + vv = ngx_stream_get_variable(s, &name_lc, key); JS_FreeCString(cx, (char *) name.data); if (vv == NULL || vv->not_found) { return 0; @@ -2490,13 +2526,14 @@ static int ngx_stream_qjs_variables_set_property(JSContext *cx, JSValueConst obj, JSAtom prop, JSValueConst value, JSValueConst receiver, int flags) { - ngx_str_t name, val; + ngx_str_t name, name_lc, val; ngx_uint_t key; ngx_js_ctx_t *ctx; ngx_stream_session_t *s; ngx_stream_variable_t *v; ngx_stream_variable_value_t *vv; ngx_stream_core_main_conf_t *cmcf; + u_char storage[64]; s = JS_GetOpaque(obj, NGX_QJS_CLASS_ID_STREAM_VARS); @@ -2514,11 +2551,22 @@ ngx_stream_qjs_variables_set_property(JSContext *cx, JSValueConst obj, name.len = ngx_strlen(name.data); - key = ngx_hash_strlow(name.data, name.data, name.len); + if (name.len < sizeof(storage)) { + name_lc.data = storage; + + } else { + name_lc.data = ngx_pnalloc(s->connection->pool, name.len); + if (name_lc.data == NULL) { + (void) JS_ThrowOutOfMemory(cx); + return -1; + } + } + + key = ngx_hash_strlow(name_lc.data, name.data, name.len); cmcf = ngx_stream_get_module_main_conf(s, ngx_stream_core_module); - v = ngx_hash_find(&cmcf->variables_hash, key, name.data, name.len); + v = ngx_hash_find(&cmcf->variables_hash, key, name_lc.data, name.len); JS_FreeCString(cx, (char *) name.data); if (v == NULL) { diff --git a/nginx/t/js_headers.t b/nginx/t/js_headers.t index 2cb8c660..8030a4fd 100644 --- a/nginx/t/js_headers.t +++ b/nginx/t/js_headers.t @@ -116,6 +116,10 @@ http { return 200 $test_ifoo_in; } + location /in_lowkey { + js_content test.in_lowkey; + } + location /hdr_in { js_content test.hdr_in; } @@ -355,6 +359,12 @@ $t->write_file('test.js', <write_file('test.js', <try_run('no njs')->plan(49); +$t->try_run('no njs')->plan(50); ############################################################################### @@ -572,6 +582,8 @@ like(http( . 'Host: localhost' . CRLF . CRLF ), qr/foo: bar1,\s?bar2/, 'r.headersIn duplicate generic'); +like(http_get('/in_lowkey'), qr/X{16}/, 'r.headersIn name is not overwritten'); + like(http( 'GET /raw_hdr_in?filter=foo HTTP/1.0' . CRLF . 'foo: bar1' . CRLF diff --git a/nginx/t/js_variables.t b/nginx/t/js_variables.t index f2481e0b..6f1eb173 100644 --- a/nginx/t/js_variables.t +++ b/nginx/t/js_variables.t @@ -44,6 +44,7 @@ http { server_name localhost; set $foo test.foo_orig; + set $XXXXXXXXXXXXXXXX 1; location /var_set { return 200 $test_var$foo; @@ -56,6 +57,10 @@ http { location /not_found_set { js_content test.not_found_set; } + + location /variable_lowkey { + js_content test.variable_lowkey; + } } } @@ -80,16 +85,33 @@ $t->write_file('test.js', <try_run('no njs')->plan(3); +$t->try_run('no njs')->plan(5); ############################################################################### like(http_get('/var_set?a=bar'), qr/test_varbar/, 'var set'); like(http_get('/content_set?a=bar'), qr/bar/, 'content set'); like(http_get('/not_found_set'), qr/variable not found/, 'not found exception'); +like(http_get('/variable_lowkey'), qr/X{16}/, + 'variable name is not overwritten while reading'); +like(http_get('/variable_lowkey?set=1'), qr/X{16}/, + 'variable name is not overwritten while setting'); ############################################################################### diff --git a/nginx/t/stream_js_var.t b/nginx/t/stream_js_var.t index cde2dc9f..2ab02e2c 100644 --- a/nginx/t/stream_js_var.t +++ b/nginx/t/stream_js_var.t @@ -39,8 +39,11 @@ stream { js_import test.js; js_var $foo; + js_var $XXXXXXXXXXXXXXXX; js_var $bar a:$remote_addr; js_set $var test.varr; + js_set $lowkey test.lowkey; + js_set $lowkey_set test.lowkey_set; server { listen 127.0.0.1:8081; @@ -51,6 +54,16 @@ stream { listen 127.0.0.1:8082; return $var$foo; } + + server { + listen 127.0.0.1:8083; + return $lowkey; + } + + server { + listen 127.0.0.1:8084; + return $lowkey_set; + } } EOF @@ -61,15 +74,31 @@ $t->write_file('test.js', <try_run('no stream js_var')->plan(2); +$t->try_run('no stream js_var')->plan(4); ############################################################################### is(stream('127.0.0.1:' . port(8081))->io('###'), 'a:127.0.0.1', 'default value'); is(stream('127.0.0.1:' . port(8082))->io('###'), 'xxx', 'value set'); +is(stream('127.0.0.1:' . port(8083))->io('###'), 'XXXXXXXXXXXXXXXX', + 'variable name is not overwritten while reading'); +is(stream('127.0.0.1:' . port(8084))->io('###'), 'XXXXXXXXXXXXXXXX', + 'variable name is not overwritten while writing'); ############################################################################### From noreply at nginx.com Tue Feb 18 11:50:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Tue, 18 Feb 2025 11:50:02 +0000 (UTC) Subject: [nginx] Version bump. Message-ID: <20250218115002.88677489CB@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/f274b3f72fa9aa3b2ed9b32817ed4a88eb2256b3 branches: master commit: f274b3f72fa9aa3b2ed9b32817ed4a88eb2256b3 user: Sergey Kandaurov date: Tue, 18 Feb 2025 15:38:33 +0400 description: Version bump. --- src/core/nginx.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/nginx.h b/src/core/nginx.h index 29f2c949f..090777853 100644 --- a/src/core/nginx.h +++ b/src/core/nginx.h @@ -9,8 +9,8 @@ #define _NGINX_H_INCLUDED_ -#define nginx_version 1027004 -#define NGINX_VERSION "1.27.4" +#define nginx_version 1027005 +#define NGINX_VERSION "1.27.5" #define NGINX_VER "nginx/" NGINX_VERSION #ifdef NGX_BUILD From noreply at nginx.com Tue Feb 18 13:19:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Tue, 18 Feb 2025 13:19:02 +0000 (UTC) Subject: [nginx] Core: fix build without libcrypt. Message-ID: <20250218131902.59066489CC@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/9a4090f02ab438c47178b3b5a4c15a3c769d5027 branches: master commit: 9a4090f02ab438c47178b3b5a4c15a3c769d5027 user: Piotr Sikora date: Wed, 12 Feb 2025 10:40:58 +0200 description: Core: fix build without libcrypt. libcrypt is no longer part of glibc, so it might not be available. Signed-off-by: Piotr Sikora --- auto/unix | 4 ++-- src/os/unix/ngx_linux_config.h | 6 +++++- src/os/unix/ngx_user.c | 10 +++++++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/auto/unix b/auto/unix index f29e69c61..8bd1b1370 100644 --- a/auto/unix +++ b/auto/unix @@ -150,7 +150,7 @@ fi ngx_feature="crypt()" -ngx_feature_name= +ngx_feature_name="NGX_HAVE_CRYPT" ngx_feature_run=no ngx_feature_incs= ngx_feature_path= @@ -162,7 +162,7 @@ ngx_feature_test="crypt(\"test\", \"salt\");" if [ $ngx_found = no ]; then ngx_feature="crypt() in libcrypt" - ngx_feature_name= + ngx_feature_name="NGX_HAVE_CRYPT" ngx_feature_run=no ngx_feature_incs= ngx_feature_path= diff --git a/src/os/unix/ngx_linux_config.h b/src/os/unix/ngx_linux_config.h index 88fef47ce..d99358c93 100644 --- a/src/os/unix/ngx_linux_config.h +++ b/src/os/unix/ngx_linux_config.h @@ -52,7 +52,6 @@ #include /* memalign() */ #include /* IOV_MAX */ #include -#include #include /* uname() */ #include @@ -61,6 +60,11 @@ #include +#if (NGX_HAVE_CRYPT_H) +#include +#endif + + #if (NGX_HAVE_POSIX_SEM) #include #endif diff --git a/src/os/unix/ngx_user.c b/src/os/unix/ngx_user.c index b3d81d07b..8c769ed93 100644 --- a/src/os/unix/ngx_user.c +++ b/src/os/unix/ngx_user.c @@ -41,7 +41,7 @@ ngx_libc_crypt(ngx_pool_t *pool, u_char *key, u_char *salt, u_char **encrypted) return NGX_ERROR; } -#else +#elif (NGX_HAVE_CRYPT) ngx_int_t ngx_libc_crypt(ngx_pool_t *pool, u_char *key, u_char *salt, u_char **encrypted) @@ -71,6 +71,14 @@ ngx_libc_crypt(ngx_pool_t *pool, u_char *key, u_char *salt, u_char **encrypted) return NGX_ERROR; } +#else + +ngx_int_t +ngx_libc_crypt(ngx_pool_t *pool, u_char *key, u_char *salt, u_char **encrypted) +{ + return NGX_ERROR; +} + #endif #endif /* NGX_CRYPT */ From noreply at nginx.com Tue Feb 18 15:08:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Tue, 18 Feb 2025 15:08:02 +0000 (UTC) Subject: [nginx] Configure: MSVC compatibility with PCRE2 10.45. Message-ID: <20250218150802.3CF5948F74@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/3327353ec05f32bf4ef227fcd67bf40efafa04f8 branches: master commit: 3327353ec05f32bf4ef227fcd67bf40efafa04f8 user: Thierry Bastian date: Mon, 17 Feb 2025 09:01:27 +0100 description: Configure: MSVC compatibility with PCRE2 10.45. --- auto/lib/pcre/make | 1 + 1 file changed, 1 insertion(+) diff --git a/auto/lib/pcre/make b/auto/lib/pcre/make index 182590ac5..2a987d152 100644 --- a/auto/lib/pcre/make +++ b/auto/lib/pcre/make @@ -37,6 +37,7 @@ if [ $PCRE_LIBRARY = PCRE2 ]; then pcre2_xclass.c" ngx_pcre_test="pcre2_chkdint.c \ + pcre2_compile_class.c \ pcre2_convert.c \ pcre2_extuni.c \ pcre2_find_bracket.c \ From noreply at nginx.com Tue Feb 18 17:35:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Tue, 18 Feb 2025 17:35:02 +0000 (UTC) Subject: [njs] Add missed syntax error for await in template literal. Message-ID: <20250218173502.A7CA248F49@pubserv1.nginx> details: https://github.com/nginx/njs/commit/2abc2a123fd510f6945bc94670f93db2f0aaf659 branches: master commit: 2abc2a123fd510f6945bc94670f93db2f0aaf659 user: Vadim Zhestikov date: Tue, 11 Feb 2025 15:39:29 -0800 description: Add missed syntax error for await in template literal. This fixes issues #836 on github. --- src/njs_parser.c | 20 +++++++++++++++++++- src/njs_parser.h | 1 + src/test/njs_unit_test.c | 6 ++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/njs_parser.c b/src/njs_parser.c index 1f16336f..7fe596c2 100644 --- a/src/njs_parser.c +++ b/src/njs_parser.c @@ -2254,6 +2254,15 @@ njs_parser_initializer_assign(njs_parser_t *parser, njs_token_type_t type) } +static njs_int_t +njs_parser_tagged_template_literal_after(njs_parser_t *parser, + njs_lexer_token_t *token, njs_queue_link_t *current) +{ + parser->scope->in_tagged_template--; + + return njs_parser_stack_pop(parser); +} + /* * 12.3 Left-Hand-Side Expressions. */ @@ -2334,9 +2343,12 @@ njs_parser_property(njs_parser_t *parser, njs_lexer_token_t *token, parser->node = node; + parser->scope->in_tagged_template++; + njs_parser_next(parser, njs_parser_template_literal); - break; + return njs_parser_after(parser, current, node, 1, + njs_parser_tagged_template_literal_after); default: return NJS_DONE; @@ -3580,6 +3592,12 @@ njs_parser_await(njs_parser_t *parser, njs_lexer_token_t *token, return NJS_ERROR; } + if (parser->scope->in_tagged_template > 0) { + njs_parser_syntax_error(parser, + "await in tagged template not supported"); + return NJS_ERROR; + } + node = njs_parser_node_new(parser, NJS_TOKEN_AWAIT); if (njs_slow_path(node == NULL)) { return NJS_ERROR; diff --git a/src/njs_parser.h b/src/njs_parser.h index c685d32c..db4db02d 100644 --- a/src/njs_parser.h +++ b/src/njs_parser.h @@ -27,6 +27,7 @@ struct njs_parser_scope_s { uint8_t dest_disable; uint8_t async; uint32_t in_args; + uint32_t in_tagged_template; }; diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index e99a6d8a..4f5e5c91 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -19821,6 +19821,12 @@ static njs_unit_test_t njs_test[] = { njs_str("async function f1() {try {f(await f1)} catch(e) {}}"), njs_str("SyntaxError: await in arguments not supported in 1") }, + { njs_str("(async () => (function (){}) `${(async () => 1)(await 1)}`)()"), + njs_str("SyntaxError: await in arguments not supported in 1") }, + + { njs_str("(async () => (function (){}) `${await 1}`)()"), + njs_str("SyntaxError: await in tagged template not supported in 1") }, + { njs_str("async function af() {await encrypt({},}"), njs_str("SyntaxError: Unexpected token \"}\" in 1") }, From noreply at nginx.com Wed Feb 19 00:31:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 19 Feb 2025 00:31:02 +0000 (UTC) Subject: [njs] QuickJS: fixed non-NULL terminated strings formatting in exceptions. Message-ID: <20250219003102.D8B4F48F49@pubserv1.nginx> details: https://github.com/nginx/njs/commit/b6d108c812c07b11d4c3f65a48a5898139ac1ae4 branches: master commit: b6d108c812c07b11d4c3f65a48a5898139ac1ae4 user: Dmitry Volyntsev date: Wed, 12 Feb 2025 19:11:08 -0800 description: QuickJS: fixed non-NULL terminated strings formatting in exceptions. When "%*s" is specified, the first integer is interpreted as width. Width specifies *minimum* number of characters to output. The next string is expected to be NULL-terminated. When "%.*s" is specified, the first integer is interpreted as precision. Precision specifies *maximum* number of characters to output. --- external/njs_shell.c | 6 +++--- external/qjs_webcrypto_module.c | 2 +- nginx/ngx_js.c | 4 ++-- nginx/ngx_stream_js_module.c | 2 +- src/qjs_buffer.c | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/external/njs_shell.c b/external/njs_shell.c index 776c2536..015c930f 100644 --- a/external/njs_shell.c +++ b/external/njs_shell.c @@ -2590,7 +2590,7 @@ njs_qjs_module_loader(JSContext *ctx, const char *module_name, void *opaque) (void) close(info.fd); if (njs_slow_path(ret != NJS_OK)) { - JS_ThrowInternalError(ctx, "while reading \"%*s\" module", + JS_ThrowInternalError(ctx, "while reading \"%.*s\" module", (int) info.file.length, info.file.start); return NULL; } @@ -2599,7 +2599,7 @@ njs_qjs_module_loader(JSContext *ctx, const char *module_name, void *opaque) ret = njs_console_set_cwd(console, &info.file); if (njs_slow_path(ret != NJS_OK)) { - JS_ThrowInternalError(ctx, "while setting cwd for \"%*s\" module", + JS_ThrowInternalError(ctx, "while setting cwd for \"%.*s\" module", (int) info.file.length, info.file.start); return NULL; } @@ -2827,7 +2827,7 @@ njs_engine_qjs_unhandled_rejection(njs_engine_t *engine) return -1; } - JS_ThrowTypeError(ctx, "unhandled promise rejection: %*s", (int) len, str); + JS_ThrowTypeError(ctx, "unhandled promise rejection: %.*s", (int) len, str); JS_FreeCString(ctx, str); for (i = 0; i < console->rejected_promises->items; i++) { diff --git a/external/qjs_webcrypto_module.c b/external/qjs_webcrypto_module.c index 64098f4e..cc654c0a 100644 --- a/external/qjs_webcrypto_module.c +++ b/external/qjs_webcrypto_module.c @@ -4802,7 +4802,7 @@ qjs_webcrypto_error(JSContext *cx, const char *fmt, ...) } } - JS_ThrowTypeError(cx, "%*s", (int) (p - errstr), errstr); + JS_ThrowTypeError(cx, "%.*s", (int) (p - errstr), errstr); } diff --git a/nginx/ngx_js.c b/nginx/ngx_js.c index 5c2a44cb..7f7b7362 100644 --- a/nginx/ngx_js.c +++ b/nginx/ngx_js.c @@ -1988,7 +1988,7 @@ ngx_qjs_module_loader(JSContext *cx, const char *module_name, void *opaque) (void) close(info.fd); if (ret != NJS_OK) { - JS_ThrowInternalError(cx, "while reading \"%*s\" module", + JS_ThrowInternalError(cx, "while reading \"%.*s\" module", (int) info.file.length, info.file.start); return NULL; } @@ -2057,7 +2057,7 @@ ngx_qjs_unhandled_rejection(ngx_js_ctx_t *ctx) return -1; } - JS_ThrowTypeError(cx, "unhandled promise rejection: %*s", (int) len, str); + JS_ThrowTypeError(cx, "unhandled promise rejection: %.*s", (int) len, str); JS_FreeCString(cx, str); for (i = 0; i < ctx->rejected_promises->items; i++) { diff --git a/nginx/ngx_stream_js_module.c b/nginx/ngx_stream_js_module.c index a7a923ad..59a3e9d2 100644 --- a/nginx/ngx_stream_js_module.c +++ b/nginx/ngx_stream_js_module.c @@ -2126,7 +2126,7 @@ ngx_stream_qjs_event(ngx_stream_session_t *s, JSContext *cx, ngx_str_t *event) } if (i == n) { - (void) JS_ThrowInternalError(cx, "unknown event \"%*s\"", + (void) JS_ThrowInternalError(cx, "unknown event \"%.*s\"", (int) event->len, event->data); return NULL; } diff --git a/src/qjs_buffer.c b/src/qjs_buffer.c index 8fc296ad..638d273c 100644 --- a/src/qjs_buffer.c +++ b/src/qjs_buffer.c @@ -2099,7 +2099,7 @@ qjs_buffer_encoding(JSContext *ctx, JSValueConst value, JS_BOOL thrw) JS_FreeCString(ctx, (char *) name.start); if (thrw) { - JS_ThrowTypeError(ctx, "\"%*s\" encoding is not supported", + JS_ThrowTypeError(ctx, "\"%.*s\" encoding is not supported", (int) name.length, name.start); } From noreply at nginx.com Wed Feb 19 00:31:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 19 Feb 2025 00:31:02 +0000 (UTC) Subject: [njs] Fixed access to uninitialized alg in SubtleCrypto.import(). Message-ID: <20250219003102.CF85F489C5@pubserv1.nginx> details: https://github.com/nginx/njs/commit/0b6eca0a123000297d4e18d46770bad7416d5056 branches: master commit: 0b6eca0a123000297d4e18d46770bad7416d5056 user: Dmitry Volyntsev date: Tue, 11 Feb 2025 16:41:56 -0800 description: Fixed access to uninitialized alg in SubtleCrypto.import(). Found by GCC: In function ‘qjs_import_jwk_oct’, external/qjs_webcrypto_module.c:3116:13: error: ‘alg.start’ may be used uninitialized [-Werror=maybe-uninitialized] 3116 | JS_ThrowTypeError(cx, "key size and \"alg\" value \"%s\" mismatch", The similar place in the NJS module was also fixed. --- external/njs_webcrypto_module.c | 47 +++++++++++++++++--------------- external/qjs_webcrypto_module.c | 59 ++++++++++++++++++++--------------------- 2 files changed, 54 insertions(+), 52 deletions(-) diff --git a/external/njs_webcrypto_module.c b/external/njs_webcrypto_module.c index d4725d89..59d02c1d 100644 --- a/external/njs_webcrypto_module.c +++ b/external/njs_webcrypto_module.c @@ -3207,37 +3207,40 @@ njs_import_jwk_oct(njs_vm_t *vm, njs_value_t *jwk, njs_webcrypto_key_t *key) njs_decode_base64url(&key->u.s.raw, &b64); - size = 16; - val = njs_vm_object_prop(vm, jwk, &string_alg, &value); - if (val != NULL && njs_value_is_string(val)) { - njs_value_string_get(val, &alg); + if (njs_slow_path(val == NULL || !njs_value_is_string(val))) { + njs_vm_type_error(vm, "Invalid JWK oct alg"); + return NJS_ERROR; + } - if (key->alg->type == NJS_ALGORITHM_HMAC) { - for (w = &hashes[0]; w->name.length != 0; w++) { - if (njs_strstr_eq(&alg, &w->name)) { - key->hash = w->value; - goto done; - } - } + njs_value_string_get(val, &alg); - } else { - type = key->alg->type; - a = &njs_webcrypto_alg_aes_name[type - NJS_ALGORITHM_AES_GCM][0]; - for (; a->length != 0; a++) { - if (njs_strstr_eq(&alg, a)) { - goto done; - } + size = 16; - size += 8; + if (key->alg->type == NJS_ALGORITHM_HMAC) { + for (w = &hashes[0]; w->name.length != 0; w++) { + if (njs_strstr_eq(&alg, &w->name)) { + key->hash = w->value; + goto done; } } - njs_vm_type_error(vm, "unexpected \"alg\" value \"%V\" for JWK key", - &alg); - return NJS_ERROR; + } else { + type = key->alg->type; + a = &njs_webcrypto_alg_aes_name[type - NJS_ALGORITHM_AES_GCM][0]; + for (; a->length != 0; a++) { + if (njs_strstr_eq(&alg, a)) { + goto done; + } + + size += 8; + } } + njs_vm_type_error(vm, "unexpected \"alg\" value \"%V\" for JWK key", + &alg); + return NJS_ERROR; + done: if (key->alg->type != NJS_ALGORITHM_HMAC) { diff --git a/external/qjs_webcrypto_module.c b/external/qjs_webcrypto_module.c index 22f5d108..64098f4e 100644 --- a/external/qjs_webcrypto_module.c +++ b/external/qjs_webcrypto_module.c @@ -3064,50 +3064,49 @@ qjs_import_jwk_oct(JSContext *cx, JSValue jwk, qjs_webcrypto_key_t *key) qjs_base64url_decode(cx, &b64, &key->u.s.raw); JS_FreeCString(cx, (char *) b64.start); - size = 16; - val = JS_GetPropertyStr(cx, jwk, "alg"); if (JS_IsException(val)) { return JS_EXCEPTION; } - if (JS_IsString(val)) { - alg.start = (u_char *) JS_ToCStringLen(cx, &alg.length, val); + if (!JS_IsString(val)) { JS_FreeValue(cx, val); - val = JS_UNDEFINED; - - if (alg.start == NULL) { - JS_ThrowOutOfMemory(cx); - return JS_EXCEPTION; - } + return JS_ThrowTypeError(cx, "Invalid JWK oct alg"); + } - if (key->alg->type == QJS_ALGORITHM_HMAC) { - for (w = &hashes[0]; w->name.length != 0; w++) { - if (njs_strstr_eq(&alg, &w->name)) { - key->hash = w->value; - goto done; - } - } + alg.start = (u_char *) JS_ToCStringLen(cx, &alg.length, val); + JS_FreeValue(cx, val); + if (alg.start == NULL) { + JS_ThrowOutOfMemory(cx); + return JS_EXCEPTION; + } - } else { - type = key->alg->type; - a = &qjs_webcrypto_alg_aes_name[type - QJS_ALGORITHM_AES_GCM][0]; - for (; a->length != 0; a++) { - if (njs_strstr_eq(&alg, a)) { - goto done; - } + size = 16; - size += 8; + if (key->alg->type == QJS_ALGORITHM_HMAC) { + for (w = &hashes[0]; w->name.length != 0; w++) { + if (njs_strstr_eq(&alg, &w->name)) { + key->hash = w->value; + goto done; } } - JS_ThrowTypeError(cx, "unexpected \"alg\" value \"%s\" for JWK key", - alg.start); - JS_FreeCString(cx, (char *) alg.start); - return JS_EXCEPTION; + } else { + type = key->alg->type; + a = &qjs_webcrypto_alg_aes_name[type - QJS_ALGORITHM_AES_GCM][0]; + for (; a->length != 0; a++) { + if (njs_strstr_eq(&alg, a)) { + goto done; + } + + size += 8; + } } - JS_FreeValue(cx, val); + JS_ThrowTypeError(cx, "unexpected \"alg\" value \"%s\" for JWK key", + alg.start); + JS_FreeCString(cx, (char *) alg.start); + return JS_EXCEPTION; done: From noreply at nginx.com Wed Feb 19 00:31:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 19 Feb 2025 00:31:02 +0000 (UTC) Subject: [njs] QuickJS: fixed key usage processing with invalid values in WebCrypto. Message-ID: <20250219003102.DECF548F4A@pubserv1.nginx> details: https://github.com/nginx/njs/commit/b065b41142cdf059cc5ace9e3c2495c624b8aee1 branches: master commit: b065b41142cdf059cc5ace9e3c2495c624b8aee1 user: Dmitry Volyntsev date: Wed, 12 Feb 2025 18:12:38 -0800 description: QuickJS: fixed key usage processing with invalid values in WebCrypto. --- external/qjs_webcrypto_module.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/external/qjs_webcrypto_module.c b/external/qjs_webcrypto_module.c index cc654c0a..6e820887 100644 --- a/external/qjs_webcrypto_module.c +++ b/external/qjs_webcrypto_module.c @@ -4637,10 +4637,17 @@ qjs_key_usage(JSContext *cx, JSValue value, unsigned *mask) for (e = &qjs_webcrypto_usage[0]; e->name.length != 0; e++) { if (njs_strstr_eq(&s, &e->name)) { *mask |= e->value; - break; + goto done; } } + JS_ThrowTypeError(cx, "unknown key usage: \"%.*s\"", (int) s.length, + s.start); + JS_FreeCString(cx, (char *) s.start); + return JS_EXCEPTION; + +done: + JS_FreeCString(cx, (char *) s.start); } From noreply at nginx.com Wed Feb 19 00:31:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 19 Feb 2025 00:31:02 +0000 (UTC) Subject: [njs] QuickJS: added missed OPENSSL context for errors in WebCrypto. Message-ID: <20250219003102.E4D8648F82@pubserv1.nginx> details: https://github.com/nginx/njs/commit/74ef8d95ff9c1f4b2362436e6b12094c9e0b5299 branches: master commit: 74ef8d95ff9c1f4b2362436e6b12094c9e0b5299 user: Dmitry Volyntsev date: Wed, 12 Feb 2025 18:13:54 -0800 description: QuickJS: added missed OPENSSL context for errors in WebCrypto. --- external/qjs_webcrypto_module.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/external/qjs_webcrypto_module.c b/external/qjs_webcrypto_module.c index 6e820887..3cb3d2b2 100644 --- a/external/qjs_webcrypto_module.c +++ b/external/qjs_webcrypto_module.c @@ -3242,14 +3242,14 @@ qjs_webcrypto_import_key(JSContext *cx, JSValueConst this_val, int argc, case QJS_KEY_FORMAT_PKCS8: bio = BIO_new_mem_buf(key_data.start, key_data.length); if (bio == NULL) { - JS_ThrowTypeError(cx, "BIO_new_mem_buf() failed"); + qjs_webcrypto_error(cx, "BIO_new_mem_buf() failed"); goto fail; } pkcs8 = d2i_PKCS8_PRIV_KEY_INFO_bio(bio, NULL); if (pkcs8 == NULL) { BIO_free(bio); - JS_ThrowTypeError(cx, "d2i_PKCS8_PRIV_KEY_INFO_bio() failed"); + qjs_webcrypto_error(cx, "d2i_PKCS8_PRIV_KEY_INFO_bio() failed"); goto fail; } @@ -3257,7 +3257,7 @@ qjs_webcrypto_import_key(JSContext *cx, JSValueConst this_val, int argc, if (pkey == NULL) { PKCS8_PRIV_KEY_INFO_free(pkcs8); BIO_free(bio); - JS_ThrowTypeError(cx, "EVP_PKCS82PKEY() failed"); + qjs_webcrypto_error(cx, "EVP_PKCS82PKEY() failed"); goto fail; } @@ -3272,7 +3272,7 @@ qjs_webcrypto_import_key(JSContext *cx, JSValueConst this_val, int argc, start = key_data.start; pkey = d2i_PUBKEY(NULL, &start, key_data.length); if (pkey == NULL) { - JS_ThrowTypeError(cx, "d2i_PUBKEY() failed"); + qjs_webcrypto_error(cx, "d2i_PUBKEY() failed"); goto fail; } @@ -3720,7 +3720,7 @@ qjs_convert_p1363_to_der(JSContext *cx, EVP_PKEY *pkey, u_char *p1363, if (len < 0) { js_free(cx, data); - JS_ThrowTypeError(cx, "i2d_ECDSA_SIG() failed"); + qjs_webcrypto_error(cx, "i2d_ECDSA_SIG() failed"); goto fail; } From noreply at nginx.com Wed Feb 19 00:31:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 19 Feb 2025 00:31:02 +0000 (UTC) Subject: [njs] QuickJS: added WebCrypto import tests forgotten in 75ca26f. Message-ID: <20250219003102.ED35B48F85@pubserv1.nginx> details: https://github.com/nginx/njs/commit/54f18a1049e86a1f2d7ace387c99b38d7f3f6875 branches: master commit: 54f18a1049e86a1f2d7ace387c99b38d7f3f6875 user: Dmitry Volyntsev date: Wed, 12 Feb 2025 18:07:26 -0800 description: QuickJS: added WebCrypto import tests forgotten in 75ca26f. Import related tests are consolidated from other WebCrypto tests. --- external/qjs_webcrypto_module.c | 17 +- test/webcrypto/aes.t.mjs | 3 - test/webcrypto/import.t.mjs | 646 ++++++++++++++++++++++++++++++++++++++ test/webcrypto/rsa.pkcs8.broken2 | 16 + test/webcrypto/rsa.t.mjs | 2 - test/webcrypto/rsa_decoding.t.mjs | 2 - test/webcrypto/sign.t.mjs | 135 -------- 7 files changed, 664 insertions(+), 157 deletions(-) diff --git a/external/qjs_webcrypto_module.c b/external/qjs_webcrypto_module.c index 3cb3d2b2..4816031c 100644 --- a/external/qjs_webcrypto_module.c +++ b/external/qjs_webcrypto_module.c @@ -4167,20 +4167,7 @@ qjs_webcrypto_key_algorithm(JSContext *cx, JSValueConst this_val) return JS_EXCEPTION; } - ret = JS_NewObject(cx); - if (JS_IsException(ret)) { - JS_FreeValue(cx, obj); - return JS_EXCEPTION; - } - - if (JS_DefinePropertyValueStr(cx, ret, "name", hash, JS_PROP_C_W_E) - < 0) - { - JS_FreeValue(cx, obj); - return JS_EXCEPTION; - } - - if (JS_DefinePropertyValueStr(cx, obj, "hash", ret, JS_PROP_C_W_E) + if (JS_DefinePropertyValueStr(cx, obj, "hash", hash, JS_PROP_C_W_E) < 0) { JS_FreeValue(cx, obj); @@ -4377,7 +4364,7 @@ qjs_jwk_kty(JSContext *cx, JSValueConst value) } } - JS_ThrowTypeError(cx, "invalid JWK key type: %s", kty.start); + JS_ThrowTypeError(cx, "invalid JWK key type: \"%s\"", kty.start); JS_FreeCString(cx, (char *) kty.start); return QJS_KEY_JWK_KTY_UNKNOWN; diff --git a/test/webcrypto/aes.t.mjs b/test/webcrypto/aes.t.mjs index 80cddabb..7c482583 100644 --- a/test/webcrypto/aes.t.mjs +++ b/test/webcrypto/aes.t.mjs @@ -65,7 +65,6 @@ let aes_tsuite = { { name: "AES-GCM", data: "aabbcc", tagLength: 96 }, { name: "AES-GCM", data: "aabbcc", tagLength: 112 }, { name: "AES-GCM", data: "aabbcc", tagLength: 113, exception: "TypeError: AES-GCM Invalid tagLength" }, - { name: "AES-GCM", data: "aabbcc", key: "aabbcc", exception: "TypeError: Invalid key length" }, { name: "AES-GCM", data: "aabbcc", key: "001122330011223300112233001122330011223300112233" }, { name: "AES-GCM", data: "aabbccdd".repeat(4096) }, @@ -88,14 +87,12 @@ let aes_tsuite = { { name: "AES-CTR", data: "aabbcc", key: "001122330011223300112233001122330011223300112233" }, { name: "AES-CTR", data: "aabbccdd", length: 129, exception: "TypeError: AES-CTR algorithm.length must be between 1 and 128" }, - { name: "AES-CTR", data: "aabbcc", key: "aabbcc", exception: "TypeError: Invalid key length" }, { name: "AES-CBC", data: "aa" }, { name: "AES-CBC", data: "aabbccdd".repeat(4) }, { name: "AES-CBC", data: "aabbccdd".repeat(4096) }, { name: "AES-CBC", data: "aabbccdd".repeat(5), iv: "ffffffffffffffffffffffffffffffff" }, { name: "AES-CBC", data: "aabbcc", key: "001122330011223300112233001122330011223300112233" }, - { name: "AES-CBC", data: "aabbcc", key: "aabbcc", exception: "TypeError: Invalid key length" }, ]}; run([aes_tsuite]) diff --git a/test/webcrypto/import.t.mjs b/test/webcrypto/import.t.mjs new file mode 100644 index 00000000..1b804006 --- /dev/null +++ b/test/webcrypto/import.t.mjs @@ -0,0 +1,646 @@ +/*--- +includes: [compatNjs.js, compatFs.js, compatWebcrypto.js, runTsuite.js, webCryptoUtils.js, compareArray.js] +flags: [async] +---*/ + +async function test(params) { + let key = await crypto.subtle.importKey(params.key.fmt, + params.key.key, + params.key.alg, + params.key.extractable, + params.key.usage); + + if (params.expected && !validate_key(key, params)) { + throw Error(`failed validate`); + } + + return 'SUCCESS'; +} + +function p(args, default_opts) { + let key, pem; + let params = merge({}, default_opts); + params = merge(params, args); + + switch (params.key.fmt) { + case "spki": + pem = fs.readFileSync(`test/webcrypto/${params.key.key}`); + key = pem_to_der(pem, "PUBLIC"); + break; + case "pkcs8": + pem = fs.readFileSync(`test/webcrypto/${params.key.key}`); + key = pem_to_der(pem, "PRIVATE"); + break; + case "jwk": + key = load_jwk(params.key.key); + break; + case "raw": + key = Buffer.from(params.key.key, "base64url"); + break; + default: + throw Error("Unknown encoding key format"); + } + + params.key.key = key; + + return params; +} + +function validate_key(key, params) { + let expected = params.expected; + if (expected.algorithm) { + if (!key.algorithm) { + throw Error(`missing import key algorithm`); + } + + if (expected.algorithm.name !== key.algorithm.name) { + throw Error(`unexpected import key algorithm name: ${key.algorithm.name} != ${expected.algorithm.name}`); + } + + if (has_njs() + && expected.algorithm.name == "HMAC" + && (expected.algorithm.hash !== key.algorithm.hash)) + { + throw Error(`unexpected import key algorithm hash: ${JSON.stringify(key.algorithm.hash)} != ${expected.algorithm.hash}`); + } + } + + if (expected.type !== key.type) { + throw Error(`unexpected import key type: ${key.type} != ${expected.type}`); + } + + if (expected.extractable !== key.extractable) { + throw Error(`unexpected import key extractable: ${key.extractable} != ${expected.extractable}`); + } + + if (expected.usages && !compareArray(expected.usages, key.usages)) { + throw Error(`unexpected import key usages: ${key.usages} != ${expected.usages}`); + } + + return true; +} + +let aes_tsuite = { + name: "AES importing", + skip: () => (!has_webcrypto()), + T: test, + prepare_args: p, + opts: {}, + + tests: [ + { key: { fmt: "jwk", + key: { alg: 'A128CBC', ext: true, k: 'AAAAAAAAAAAAAAAAAAAAAA', key_ops: [ 'decrypt', 'encrypt' ], kty: 'oct' }, + alg: { name: "AES-CBC" }, + extractable: true, + usage: [ "decrypt", "encrypt" ] }, + expected: { algorithm: { name: "AES-CBC" }, + extractable: true, + type: "secret", + usages: [ "decrypt", "encrypt" ] } }, + { key: { fmt: "jwk", + key: { alg: 'A128CBC', ext: true, k: 'AAAAAAAAAAAAAAAAAAAAAA', key_ops: [ 'encrypt' ], kty: 'oct' }, + alg: { name: "AES-CBC" }, + extractable: true, + usage: [ "encrypt" ] }, + expected: { algorithm: { name: "AES-CBC" }, + extractable: true, + type: "secret", + usages: [ "encrypt" ] } }, + { key: { fmt: "jwk", + key: { alg: 'A128CBC', ext: true, k: 'AAAAAAAAAAAAAAAAAAAAAA', kty: 'oct' }, + alg: { name: "AES-CBC" }, + extractable: true, + usage: [ "encrypt" ] }, + expected: { algorithm: { name: "AES-CBC" }, + extractable: true, + type: "secret", + usages: [ "encrypt" ] } }, + { key: { fmt: "jwk", + key: { alg: 'A128CBC', k: 'AAAAAAAAAAAAAAAAAAAAAA', kty: 'oct' }, + alg: { name: "AES-CBC" }, + extractable: true, + usage: [ "encrypt" ] }, + expected: { algorithm: { name: "AES-CBC" }, + extractable: true, + type: "secret", + usages: [ "encrypt" ] } }, + { key: { fmt: "raw", + key: 'AAAAAAAAAAAAAAAAAAAAAA', + alg: { name: "AES-CBC" }, + extractable: true, + usage: [ "decrypt", "encrypt" ] }, + expected: { algorithm: { name: "AES-CBC" }, + extractable: true, + type: "secret", + usages: [ "decrypt", "encrypt" ] } }, + { key: { fmt: "raw", + key: 'AAAAAAAAAAAAAAAAAAAAAA', + alg: { name: "AES-CTR" }, + extractable: false, + usage: [ "decrypt" ] }, + expected: { algorithm: { name: "AES-CTR" }, + extractable: false, + type: "secret", + usages: [ "decrypt" ] } }, + { key: { fmt: "raw", + key: 'AAAAAAAAAAAAAAAAAAAAAA', + alg: { name: "AES-GCM" }, + extractable: true, + usage: [ "decrypt", "encrypt" ] }, + expected: { algorithm: { name: "AES-GCM" }, + extractable: true, + type: "secret", + usages: [ "decrypt", "encrypt" ] } }, + + { key: { fmt: "raw", + key: 'AA', + alg: { name: "AES-CBC" }, + extractable: true, + usage: [ "decrypt", "encrypt" ] }, + exception: "TypeError: AES Invalid key length" }, + { key: { fmt: "jwk", + key: { alg: 'A128CBC', ext: true, k: 'AA', key_ops: [ 'encrypt', 'decrypt' ], kty: 'oct' }, + alg: { name: "AES-CBC" }, + usage: [ "encrypt", "decrypt" ] }, + exception: "TypeError: key size and \"alg\" value \"A128CBC\" mismatch" }, + { key: { fmt: "jwk", + key: { alg: 'A129CBC', ext: true, k: 'AA', key_ops: [ 'encrypt', 'decrypt' ], kty: 'oct' }, + alg: { name: "AES-CBC" }, + usage: [ "encrypt", "decrypt" ] }, + exception: "TypeError: unexpected \"alg\" value \"A129CBC\" for JWK key" }, + { key: { fmt: "jwk", + key: { alg: 'A128CBC', ext: false, k: 'AAAAAAAAAAAAAAAAAAAAAA', kty: 'oct' }, + alg: { name: "AES-CBC" }, + extractable: true, + usage: [ "encrypt" ] }, + exception: "TypeError: JWK oct is not extractable" }, + { key: { fmt: "jwk", + key: { alg: 1, ext: true, k: 'AA', key_ops: ['encrypt', 'decrypt'], kty: 'oct' }, + alg: { name: "AES-CBC" }, + usage: [ "encrypt", "decrypt" ] }, + exception: "TypeError: Invalid JWK oct alg" }, +]}; + +let ec_tsuite = { + name: "EC importing", + skip: () => (!has_webcrypto()), + T: test, + prepare_args: p, + opts: {}, + + tests: [ + { key: { fmt: "jwk", + key: "ec.jwk", + alg: { name: "ECDSA", namedCurve: "P-256" }, + extractable: true, + usage: [ "sign" ] }, + expected: { algorithm: { name: "ECDSA", namedCurve: "SHA-256" }, + extractable: true, + type: "private", + usages: [ "sign" ] } }, + { key: { fmt: "spki", + key: "ec.spki", + alg: { name: "ECDSA", namedCurve: "P-256" }, + extractable: true, + usage: [ "verify" ] }, + expected: { algorithm: { name: "ECDSA", namedCurve: "SHA-256" }, + extractable: true, + type: "public", + usages: [ "verify" ] } }, + { key: { fmt: "pkcs8", + key: "ec.pkcs8", + alg: { name: "ECDSA", namedCurve: "P-256" }, + extractable: true, + usage: [ "sign" ] }, + expected: { algorithm: { name: "ECDSA", namedCurve: "SHA-256" }, + extractable: true, + type: "private", + usages: [ "sign" ] } }, + { key: { fmt: "raw", + key: "BHFFLGURrlWEXhok0JfTKke4q-nWSIMPvKTPhdKYSVnc4Nzn_7uNz0AA3U4fhpfVxSD4U5QciGyEoM4r3jC7bjI", + alg: { name: "ECDSA", namedCurve: "P-256" }, + extractable: true, + usage: [ "verify" ] }, + expected: { algorithm: { name: "ECDSA", namedCurve: "SHA-256" }, + extractable: true, + type: "public", + usages: [ "verify" ] } }, + + { key: { fmt: "jwk", + key: "ec.jwk", + alg: { name: "ECDSA", namedCurve: "P-384" }, + extractable: true, + usage: [ "sign" ] }, + exception: "TypeError: Invalid JWK EC key" }, + { key: { fmt: "jwk", + key: 1, + alg: { name: "ECDSA", namedCurve: "P-256" }, + extractable: true, + usage: [ "sign" ] }, + exception: "TypeError: invalid JWK key data" }, + { key: { fmt: "jwk", + key: { kty: "EC" }, + alg: { name: "ECDSA", namedCurve: "P-256" }, + extractable: true, + usage: [ "sign" ] }, + exception: "TypeError: Invalid JWK EC key" }, + { key: { fmt: "jwk", + key: { kty: "EC", + x: "cUUsZRGuVYReGiTQl9MqR7ir6dZIgw-8pM-F0phJWdw" }, + alg: { name: "ECDSA", namedCurve: "P-256" }, + extractable: true, + usage: [ "sign" ] }, + exception: "TypeError: Invalid JWK EC key" }, + { key: { fmt: "jwk", + key: { kty: "EC", + x: "cUUsZRGuVYReGiTQl9MqR7ir6dZIgw-8pM-F0phJWdw", + y: "4Nzn_7uNz0AA3U4fhpfVxSD4U5QciGyEoM4r3jC7bjI" }, + alg: { name: "ECDSA", namedCurve: "P-256" }, + extractable: true, + usage: [ "sign" ] }, + exception: "TypeError: Invalid JWK EC key" }, + { key: { fmt: "jwk", + key: { kty: "EC", + x: "cUUsZRGuVYReGiTQl9MqR7ir6dZIgw-8pM-F0phJWdw", + y: "4Nzn_7uNz0AA3U4fhpfVxSD4U5QciGyEoM4r3jC7bjI", + d: "E2sW0_4a3QXaSTJ0JKbSUbieKTD1UFtr7i_2CuetP6A" }, + alg: { name: "ECDSA", namedCurve: "P-256" }, + extractable: true, + usage: [ "sign" ] }, + exception: "JWK EC curve mismatch" }, + { key: { fmt: "jwk", + key: { kty: "EC", + x: "cUUsZRGuVYReGiTQl9MqR7ir6dZIgw-8pM-F0phJWdw", + y: "4Nzn_7uNz0AA3U4fhpfVxSD4U5QciGyEoM4r3jC7bjI", + d: "E2sW0_4a3QXaSTJ0JKbSUbieKTD1UFtr7i_2CuetP6A", + crv: "P-384" }, + alg: { name: "ECDSA", namedCurve: "P-256" }, + extractable: true, + usage: [ "sign" ] }, + exception: "JWK EC curve mismatch" }, + { key: { fmt: "jwk", + key: { kty: "EC", + x: "_BROKEN_", + y: "4Nzn_7uNz0AA3U4fhpfVxSD4U5QciGyEoM4r3jC7bjI", + d: "E2sW0_4a3QXaSTJ0JKbSUbieKTD1UFtr7i_2CuetP6A", + crv: "P-256" }, + alg: { name: "ECDSA", namedCurve: "P-256" }, + extractable: true, + usage: [ "sign" ] }, + exception: "TypeError: EC_KEY_set_public_key_affine_coordinates()" }, + { key: { fmt: "jwk", + key: { kty: "EC", + x: "cUUsZRGuVYReGiTQl9MqR7ir6dZIgw-8pM-F0phJWdw", + y: "4Nzn_7uNz0AA3U4fhpfVxSD4U5QciGyEoM4r3jC7bjI", + d: "E2sW0_4a3QXaSTJ0JKbSUbieKTD1UFtr7i_2CuetP6A", + crv: "P-256", + key_ops: [ "verify" ] }, + alg: { name: "ECDSA", namedCurve: "P-256" }, + extractable: true, + usage: [ "sign" ] }, + exception: "TypeError: Key operations and usage mismatch" }, + { key: { fmt: "jwk", + key: { kty: "EC", + x: "cUUsZRGuVYReGiTQl9MqR7ir6dZIgw-8pM-F0phJWdw", + y: "4Nzn_7uNz0AA3U4fhpfVxSD4U5QciGyEoM4r3jC7bjI", + d: "E2sW0_4a3QXaSTJ0JKbSUbieKTD1UFtr7i_2CuetP6A", + ext: false }, + alg: { name: "ECDSA", namedCurve: "P-256" }, + extractable: true, + usage: [ "sign" ] }, + exception: "JWK EC is not extractable" }, + { key: { fmt: "jwk", + key: { kty: "EC", + x: "cUUsZRGuVYReGiTQl9MqR7ir6dZIgw-8pM-F0phJWdw", + y: "4Nzn_7uNz0AA3U4fhpfVxSD4U5QciGyEoM4r3jC7bjI", + d: "E2sW0_4a3QXaSTJ0JKbSUbieKTD1UFtr7i_2CuetP6A", + crv: "P-256" }, + alg: { name: "ECDSA", namedCurve: "P-256" }, + extractable: true, + usage: [ "sign", "verify" ] }, + exception: "TypeError: key usage mismatch for \"ECDSA\" key" }, + { key: { fmt: "pkcs8", + key: "ec.pkcs8", + alg: { name: "ECDSA", namedCurve: "unknown_named_curve" }, + extractable: true, + usage: [ "sign" ] }, + exception: "TypeError: unknown namedCurve: \"unknown_named_curve\"" }, + { key: { fmt: "spki", + key: "rsa.spki", + alg: { name: "ECDSA", namedCurve: "P-256" }, + extractable: true, + usage: [ "verify" ] }, + exception: "TypeError: EC key is not found" }, + { key: { fmt: "spki", + key: "ec.spki", + alg: { name: "ECDSA", namedCurve: "P-384" }, + extractable: true, + usage: [ "verify" ] }, + exception: "TypeError: name curve mismatch" }, + { key: { fmt: "raw", + key: "CHFFLGURrlWEXhok0JfTKke4q-nWSIMPvKTPhdKYSVnc4Nzn_7uNz0AA3U4fhpfVxSD4U5QciGyEoM4r3jC7bjI", + alg: { name: "ECDSA", namedCurve: "P-256" }, + extractable: true, + usage: [ "verify" ] }, + exception: "TypeError: EC_POINT_oct2point()" }, +]}; + +let hmac_tsuite = { + name: "HMAC importing", + skip: () => (!has_webcrypto()), + T: test, + prepare_args: p, + opts: {}, + + tests: [ + { key: { fmt: "raw", + key: "AA", + alg: { name: "HMAC", hash: "SHA-256" }, + extractable: true, + usage: [ "sign", "verify" ] }, + expected: { algorithm: { name: "HMAC", hash: "SHA-256" }, + extractable: true, + type: "secret", + usages: [ "sign", "verify" ] } }, + { key: { fmt: "raw", + key: "AA", + alg: { name: "HMAC", hash: "SHA-384" }, + extractable: true, + usage: [ "sign", "verify" ] }, + expected: { algorithm: { name: "HMAC", hash: "SHA-384" }, + extractable: true, + type: "secret", + usages: [ "sign", "verify" ] } }, + { key: { fmt: "raw", + key: "AA", + alg: { name: "HMAC", hash: "SHA-384" }, + extractable: false, + usage: [ "sign", "verify" ] }, + expected: { algorithm: { name: "HMAC", hash: "SHA-384" }, + extractable: false, + type: "secret", + usages: [ "sign", "verify" ] } }, + + { key: { fmt: "raw", + key: "AA", + alg: { name: "HMAC", hash: "SHA-385" }, + extractable: true, + usage: [ "sign", "verify" ] }, + exception: "TypeError: unknown hash name: \"SHA-385\"" }, + { key: { fmt: "spki", + key: "ec.spki", + alg: { name: "HMAC" }, + extractable: true, + usage: [ "sign" ] }, + exception: "TypeError: unsupported key fmt \"spki\" for \"HMAC\" key" }, + { key: { fmt: "raw", + key: "AA", + alg: { name: "HMAC", hash: "SHA-384" }, + extractable: true, + usage: [ "encrypt" ] }, + exception: "TypeError: unsupported key usage for \"HMAC\" key" }, + { key: { fmt: "raw", + key: "AA", + alg: { name: "HMAC", hash: "SHA-384" }, + extractable: true, + usage: [ "invalid_usage" ] }, + exception: "TypeError: unknown key usage: \"invalid_usage\"" }, + + { key: { fmt: "jwk", + key: { alg: 'HS384', ext: true, k: 'AA', key_ops: [ 'sign', 'verify' ], kty: 'oct' }, + alg: { name: "HMAC", hash: "SHA-384" }, + extractable: true, + usage: [ "sign", "verify" ] }, + expected: { algorithm: { name: "HMAC", hash: "SHA-384" }, + extractable: true, + type: "secret", + usages: [ "sign", "verify" ] } }, + { key: { fmt: "jwk", + key: { alg: 'HS256', ext: true, k: 'AA', key_ops: [ 'sign' ], kty: 'oct' }, + alg: { name: "HMAC", hash: "SHA-256" }, + extractable: true, + usage: [ "sign" ]}, + expected: { algorithm: { name: "HMAC", hash: "SHA-256" }, + extractable: true, + type: "secret", + usages: [ "sign" ] } }, + { key: { fmt: "jwk", + key: { alg: 'HS256', k: 'AA', kty: 'oct' }, + alg: { name: "HMAC", hash: "SHA-256" }, + extractable: true, + usage: [ "sign" ]}, + expected: { algorithm: { name: "HMAC", hash: "SHA-256" }, + extractable: true, + type: "secret", + usages: [ "sign" ] } }, + + { key: { fmt: "jwk", + key: { alg: 'HS385', ext: true, k: 'AA', key_ops: [ 'sign', 'verify' ], kty: 'oct' }, + alg: { name: "HMAC", hash: "SHA-385" }, + extractable: true, + usage: [ "sign", "verify" ] }, + exception: "TypeError: unexpected \"alg\" value \"HS385\" for JWK key" }, + { key: { fmt: "jwk", + key: { alg: 'HS384', ext: true, k: 'AA', key_ops: [ 'sign', 'verify' ], kty: 'invalid_kty' }, + alg: { name: "HMAC", hash: "SHA-384" }, + extractable: true, + usage: [ "sign", "verify" ] }, + exception: "TypeError: invalid JWK key type: \"invalid_kty\"" }, + { key: { fmt: "jwk", + key: { alg: 'HS256', ext: true, key_ops: [ 'sign' ], kty: 'oct' }, + alg: { name: "HMAC", hash: "SHA-256" }, + extractable: true, + usage: [ "sign" ]}, + exception: "TypeError: Invalid JWK oct key" }, + { key: { fmt: "jwk", + key: { alg: 'HS256', k: 'AA', key_ops: [ 'sign' ], kty: 'oct' }, + alg: { name: "HMAC", hash: "SHA-256" }, + extractable: true, + usage: [ "verify" ]}, + exception: "TypeError: Key operations and usage mismatch" }, +]}; + +let rsa_tsuite = { + name: "RSA importing", + skip: () => (!has_webcrypto()), + T: test, + prepare_args: p, + opts: {}, + + tests: [ + { key: { fmt: "jwk", + key: "rsa.jwk", + alg: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, + extractable: true, + usage: [ "sign" ] }, + expected: { algorithm: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, + extractable: true, + type: "private", + usages: [ "sign" ] } }, + { key: { fmt: "pkcs8", + key: "rsa.pkcs8", + alg: { name: "RSA-OAEP", hash: "SHA-1" }, + extractable: true, + usage: [ "decrypt" ] }, + expected: { algorithm: { name: "RSA-OAEP", hash: "SHA-1" }, + extractable: true, + type: "private", + usages: [ "decrypt" ] } }, + { key: { fmt: "spki", + key: "rsa.spki", + alg: { name: "RSA-OAEP", hash: "SHA-256" }, + extractable: true, + usage: [ "encrypt" ] }, + expected: { algorithm: { name: "RSA-OAEP", hash: "SHA-256" }, + extractable: true, + type: "public", + usages: [ "encrypt" ] } }, + + { key: { fmt: "jwk", + key: 1, + alg: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, + extractable: true, + usage: [ "sign" ] }, + exception: "TypeError: invalid JWK key data" }, + { key: { fmt: "jwk", + key: "rsa.jwk", + alg: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, + extractable: true, + usage: [ "verify" ] }, + exception: "TypeError: Key operations and usage mismatch" }, + { key: { fmt: "jwk", + key: "rsa.jwk", + alg: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-384" }, + extractable: true, + usage: [ "sign" ] }, + exception: "TypeError: RSA JWK hash mismatch" }, + { key: { fmt: "jwk", + key: { kty: "RSA", + n: "yUmxoJC8VAM5hyYZa-XUBZg1N1ywFMPUpWsF1kaSGed98P3XUgPzgX80wpyzd5qdGuALqnf2lMc7O8PrGBtO5YrvQlI96NX0jUo5bc5wz220ob3AUCeQnTfx-UFqM4pCwjoDSo2PlphJdWgFYymGBaBCJgnENQL9H1N_8_yNiN8" }, + alg: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, + extractable: true, + usage: [ "sign" ] }, + exception: "TypeError: Invalid JWK RSA key" }, + { key: { fmt: "jwk", + key: { kty: "RSA", + n: "yUmxoJC8VAM5hyYZa-XUBZg1N1ywFMPUpWsF1kaSGed98P3XUgPzgX80wpyzd5qdGuALqnf2lMc7O8PrGBtO5YrvQlI96NX0jUo5bc5wz220ob3AUCeQnTfx-UFqM4pCwjoDSo2PlphJdWgFYymGBaBCJgnENQL9H1N_8_yNiN8", + e: "AQAB" }, + alg: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, + extractable: true, + usage: [ "sign" ] }, + exception: "TypeError: key usage mismatch for \"RSASSA-PKCS1-v1_5\" key" }, + { key: { fmt: "jwk", + key: { kty: "RSA", + n: "yUmxoJC8VAM5hyYZa-XUBZg1N1ywFMPUpWsF1kaSGed98P3XUgPzgX80wpyzd5qdGuALqnf2lMc7O8PrGBtO5YrvQlI96NX0jUo5bc5wz220ob3AUCeQnTfx-UFqM4pCwjoDSo2PlphJdWgFYymGBaBCJgnENQL9H1N_8_yNiN8", + e: "AQAB", + d: "j06DQyCopFujYoASi0oWmGEUSjUYO8BsrdSzVCnsLLsuZBwlZ4Peouyw4Hl2IIoYniCyzYwZJzVtC5Dh2MjgcrJTG5nX3FfheuabGl4in0583C51ZYWlVpDvBWw8kJTfXjiKH4z6ZA9dWdT5Y3aH_kOf-znUc7eTvuzISs61x_k" }, + alg: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, + extractable: true, + usage: [ "sign" ] }, + exception: "TypeError: Invalid JWK RSA key" }, + { key: { fmt: "jwk", + key: { kty: "RSA", + n: "yUmxoJC8VAM5hyYZa-XUBZg1N1ywFMPUpWsF1kaSGed98P3XUgPzgX80wpyzd5qdGuALqnf2lMc7O8PrGBtO5YrvQlI96NX0jUo5bc5wz220ob3AUCeQnTfx-UFqM4pCwjoDSo2PlphJdWgFYymGBaBCJgnENQL9H1N_8_yNiN8", + e: "AQAB", + d: "j06DQyCopFujYoASi0oWmGEUSjUYO8BsrdSzVCnsLLsuZBwlZ4Peouyw4Hl2IIoYniCyzYwZJzVtC5Dh2MjgcrJTG5nX3FfheuabGl4in0583C51ZYWlVpDvBWw8kJTfXjiKH4z6ZA9dWdT5Y3aH_kOf-znUc7eTvuzISs61x_k", + p: "9ASb2yw5b8d7unrFuOyy4EDcPbnzEpbuVGASeHPqkORwHsqeGbfwGlhDYSYrY0HCwUsSBSFcO3SDeu0Z0zSvFQ" }, + alg: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, + extractable: true, + usage: [ "sign" ] }, + exception: "TypeError: Invalid JWK RSA key" }, + { key: { fmt: "jwk", + key: { kty: "RSA", + n: "yUmxoJC8VAM5hyYZa-XUBZg1N1ywFMPUpWsF1kaSGed98P3XUgPzgX80wpyzd5qdGuALqnf2lMc7O8PrGBtO5YrvQlI96NX0jUo5bc5wz220ob3AUCeQnTfx-UFqM4pCwjoDSo2PlphJdWgFYymGBaBCJgnENQL9H1N_8_yNiN8", + e: "AQAB", + d: "j06DQyCopFujYoASi0oWmGEUSjUYO8BsrdSzVCnsLLsuZBwlZ4Peouyw4Hl2IIoYniCyzYwZJzVtC5Dh2MjgcrJTG5nX3FfheuabGl4in0583C51ZYWlVpDvBWw8kJTfXjiKH4z6ZA9dWdT5Y3aH_kOf-znUc7eTvuzISs61x_k", + p: "9ASb2yw5b8d7unrFuOyy4EDcPbnzEpbuVGASeHPqkORwHsqeGbfwGlhDYSYrY0HCwUsSBSFcO3SDeu0Z0zSvFQ", + q: "0yvzzgHo_PGYSlVj-M3965AwQF2wTXz82MZHv6EfcCHKuBfCSecr-igqLHhzfynAQjjf39VrXuPuRL23REF1Iw" }, + alg: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, + extractable: true, + usage: [ "sign" ] }, + exception: "TypeError: Invalid JWK RSA key" }, + { key: { fmt: "jwk", + key: { kty: "RSA", + n: "yUmxoJC8VAM5hyYZa-XUBZg1N1ywFMPUpWsF1kaSGed98P3XUgPzgX80wpyzd5qdGuALqnf2lMc7O8PrGBtO5YrvQlI96NX0jUo5bc5wz220ob3AUCeQnTfx-UFqM4pCwjoDSo2PlphJdWgFYymGBaBCJgnENQL9H1N_8_yNiN8", + e: "AQAB", + d: "j06DQyCopFujYoASi0oWmGEUSjUYO8BsrdSzVCnsLLsuZBwlZ4Peouyw4Hl2IIoYniCyzYwZJzVtC5Dh2MjgcrJTG5nX3FfheuabGl4in0583C51ZYWlVpDvBWw8kJTfXjiKH4z6ZA9dWdT5Y3aH_kOf-znUc7eTvuzISs61x_k", + p: "9ASb2yw5b8d7unrFuOyy4EDcPbnzEpbuVGASeHPqkORwHsqeGbfwGlhDYSYrY0HCwUsSBSFcO3SDeu0Z0zSvFQ", + q: "0yvzzgHo_PGYSlVj-M3965AwQF2wTXz82MZHv6EfcCHKuBfCSecr-igqLHhzfynAQjjf39VrXuPuRL23REF1Iw", + dp: "pUXJ2jSl4lOWNcOZz5phvQmxIg2j2N9pJLS9TeAU63YNio1pb7npYa6OVGpp0JxlsE2MMvVZZtuPgd69MxPn0Q" }, + alg: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, + extractable: true, + usage: [ "sign" ] }, + exception: "TypeError: Invalid JWK RSA key" }, + { key: { fmt: "jwk", + key: { kty: "RSA", + n: "yUmxoJC8VAM5hyYZa-XUBZg1N1ywFMPUpWsF1kaSGed98P3XUgPzgX80wpyzd5qdGuALqnf2lMc7O8PrGBtO5YrvQlI96NX0jUo5bc5wz220ob3AUCeQnTfx-UFqM4pCwjoDSo2PlphJdWgFYymGBaBCJgnENQL9H1N_8_yNiN8", + e: "AQAB", + d: "j06DQyCopFujYoASi0oWmGEUSjUYO8BsrdSzVCnsLLsuZBwlZ4Peouyw4Hl2IIoYniCyzYwZJzVtC5Dh2MjgcrJTG5nX3FfheuabGl4in0583C51ZYWlVpDvBWw8kJTfXjiKH4z6ZA9dWdT5Y3aH_kOf-znUc7eTvuzISs61x_k", + p: "9ASb2yw5b8d7unrFuOyy4EDcPbnzEpbuVGASeHPqkORwHsqeGbfwGlhDYSYrY0HCwUsSBSFcO3SDeu0Z0zSvFQ", + q: "0yvzzgHo_PGYSlVj-M3965AwQF2wTXz82MZHv6EfcCHKuBfCSecr-igqLHhzfynAQjjf39VrXuPuRL23REF1Iw", + dp: "pUXJ2jSl4lOWNcOZz5phvQmxIg2j2N9pJLS9TeAU63YNio1pb7npYa6OVGpp0JxlsE2MMvVZZtuPgd69MxPn0Q", + dq: "RZoqDM-iXKTA3ldQ0TQMKnVnAgAfWRsGN-j6wxW3R_1LVOw31KYGX7iXVfsJjnNTdEBMwfkVH7yezzd8zVmJ4w" }, + alg: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, + extractable: true, + usage: [ "sign" ] }, + exception: "TypeError: Invalid JWK RSA key" }, + { key: { fmt: "jwk", + key: { kty: "RSA", + n: "yUmxoJC8VAM5hyYZa-XUBZg1N1ywFMPUpWsF1kaSGed98P3XUgPzgX80wpyzd5qdGuALqnf2lMc7O8PrGBtO5YrvQlI96NX0jUo5bc5wz220ob3AUCeQnTfx-UFqM4pCwjoDSo2PlphJdWgFYymGBaBCJgnENQL9H1N_8_yNiN8", + e: "AQAB", + d: "j06DQyCopFujYoASi0oWmGEUSjUYO8BsrdSzVCnsLLsuZBwlZ4Peouyw4Hl2IIoYniCyzYwZJzVtC5Dh2MjgcrJTG5nX3FfheuabGl4in0583C51ZYWlVpDvBWw8kJTfXjiKH4z6ZA9dWdT5Y3aH_kOf-znUc7eTvuzISs61x_k", + p: "9ASb2yw5b8d7unrFuOyy4EDcPbnzEpbuVGASeHPqkORwHsqeGbfwGlhDYSYrY0HCwUsSBSFcO3SDeu0Z0zSvFQ", + q: "0yvzzgHo_PGYSlVj-M3965AwQF2wTXz82MZHv6EfcCHKuBfCSecr-igqLHhzfynAQjjf39VrXuPuRL23REF1Iw", + dp: "pUXJ2jSl4lOWNcOZz5phvQmxIg2j2N9pJLS9TeAU63YNio1pb7npYa6OVGpp0JxlsE2MMvVZZtuPgd69MxPn0Q", + dq: "RZoqDM-iXKTA3ldQ0TQMKnVnAgAfWRsGN-j6wxW3R_1LVOw31KYGX7iXVfsJjnNTdEBMwfkVH7yezzd8zVmJ4w", + qi: "2REPnRQIaLsya5wlwFw0whwPaAbTZp2jfguhtg5gou_Yru7Cxz_b83YFPgoI6xuGE1OXsWkRTToS8FuIWCrNBQ", + ext: false }, + alg: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, + extractable: true, + usage: [ "sign" ] }, + exception: "JWK RSA is not extractable" }, + { key: { fmt: "jwk", + key: { kty: "RSA" }, + alg: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, + extractable: true, + usage: [ "sign" ] }, + exception: "TypeError: Invalid JWK RSA key" }, + { key: { fmt: "pkcs8", + key: "ec.pkcs8", + alg: { name: "RSA-OAEP", hash: "SHA-1" }, + extractable: true, + usage: [ "decrypt" ] }, + exception: "TypeError: RSA key is not found" }, + { key: { fmt: "pkcs8", + key: "rsa.pkcs8.broken", + alg: { name: "RSA-OAEP", hash: "SHA-1" }, + extractable: true, + usage: [ "decrypt" ] }, + exception: "TypeError: d2i_PKCS8_PRIV_KEY_INFO_bio() failed" }, + { key: { fmt: "pkcs8", + key: "rsa.pkcs8.broken2", + alg: { name: "RSA-OAEP", hash: "SHA-1" }, + extractable: true, + usage: [ "decrypt" ] }, + exception: "TypeError: EVP_PKCS82PKEY() failed" }, + { key: { fmt: "pkcs8", + key: "rsa.pkcs8", + alg: { name: "RSA-OAEP", hash: "XXX" }, + extractable: true, + usage: [ "decrypt" ] }, + exception: "TypeError: unknown hash name: \"XXX\"" }, + { key: { fmt: "spki", + key: "rsa.spki.broken", + alg: { name: "RSA-OAEP", hash: "SHA-256" }, + extractable: true, + usage: [ "encrypt" ] }, + exception: "TypeError: d2i_PUBKEY() failed" }, +]}; + +run([ + aes_tsuite, + ec_tsuite, + hmac_tsuite, + rsa_tsuite, +]) +.then($DONE, $DONE); diff --git a/test/webcrypto/rsa.pkcs8.broken2 b/test/webcrypto/rsa.pkcs8.broken2 new file mode 100644 index 00000000..1fdec72a --- /dev/null +++ b/test/webcrypto/rsa.pkcs8.broken2 @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGAAMlJsaCQvFQDOYcm +GWvl1AWYNTdcsBTD1KVrBdZGkhnnffD911ID84F/NMKcs3eanRrgC6p39pTHOzvD +6xgbTuWK70JSPejV9I1KOW3OcM9ttKG9wFAnkJ038flBajOKQsI6A0qNj5aYSXVo +BWMphgWgQiYJxDUC/R9Tf/P8jYjfAgMBAAECgYEAj06DQyCopFujYoASi0oWmGEU +SjUYO8BsrdSzVCnsLLsuZBwlZ4Peouyw4Hl2IIoYniCyzYwZJzVtC5Dh2MjgcrJT +G5nX3FfheuabGl4in0583C51ZYWlVpDvBWw8kJTfXjiKH4z6ZA9dWdT5Y3aH/kOf ++znUc7eTvuzISs61x/kCQQD0BJvbLDlvx3u6esW47LLgQNw9ufMSlu5UYBJ4c+qQ +5HAeyp4Zt/AaWENhJitjQcLBSxIFIVw7dIN67RnTNK8VAkEA0yvzzgHo/PGYSlVj ++M3965AwQF2wTXz82MZHv6EfcCHKuBfCSecr+igqLHhzfynAQjjf39VrXuPuRL23 +REF1IwJBAKVFydo0peJTljXDmc+aYb0JsSINo9jfaSS0vU3gFOt2DYqNaW+56WGu +jlRqadCcZbBNjDL1WWbbj4HevTMT59ECQEWaKgzPolykwN5XUNE0DCp1ZwIAH1kb +Bjfo+sMVt0f9S1TsN9SmBl+4l1X7CY5zU3RATMH5FR+8ns83fM1ZieMCQQDZEQ+d +FAhouzJrnCXAXDTCHA9oBtNmnaN+C6G2DmCi79iu7sLHP9vzdgU+CgjrG4YTU5ex +aRFNOhLwW4hYKs0F +-----END PRIVATE KEY----- diff --git a/test/webcrypto/rsa.t.mjs b/test/webcrypto/rsa.t.mjs index 977537e0..372535b1 100644 --- a/test/webcrypto/rsa.t.mjs +++ b/test/webcrypto/rsa.t.mjs @@ -127,8 +127,6 @@ let rsa_tsuite = { { data: "aabbccdd".repeat(7), enc: { hash: "SHA-384" }, dec: { hash: "SHA-384" } }, { data: "aabbcc", enc: { hash: "SHA-256" }, dec: { hash: "SHA-384" }, exception: "Error: EVP_PKEY_decrypt() failed" }, - { data: "aabbcc", enc: { hash: "XXX" }, exception: "TypeError: unknown hash name: \"XXX\"" }, - { data: "aabbcc", dec: { key: "rsa.spki.broken" }, exception: "Error: d2i_PUBKEY() failed" }, { data: "aabbcc", dec: { key: "rsa2.spki" }, exception: "Error: EVP_PKEY_decrypt() failed" }, { data: "aabbcc", enc: { fmt: "jwk", key: "rsa.enc.pub.jwk" }, dec: { fmt: "jwk", key: "rsa.dec.jwk" } }, diff --git a/test/webcrypto/rsa_decoding.t.mjs b/test/webcrypto/rsa_decoding.t.mjs index 24733279..08874491 100644 --- a/test/webcrypto/rsa_decoding.t.mjs +++ b/test/webcrypto/rsa_decoding.t.mjs @@ -33,8 +33,6 @@ let rsa_tsuite = { tests: [ { pem: "rsa.pkcs8", src: "text.base64.rsa-oaep.enc", expected: "WAKAWAKA" }, - { pem: "ec.pkcs8", src: "text.base64.rsa-oaep.enc", exception: "Error: RSA key is not found" }, - { pem: "rsa.pkcs8.broken", src: "text.base64.rsa-oaep.enc", exception: "Error: d2i_PKCS8_PRIV_KEY_INFO_bio() failed" }, ]}; run([rsa_tsuite]) diff --git a/test/webcrypto/sign.t.mjs b/test/webcrypto/sign.t.mjs index c2d52a6f..2999569f 100644 --- a/test/webcrypto/sign.t.mjs +++ b/test/webcrypto/sign.t.mjs @@ -194,12 +194,6 @@ let hmac_tsuite = { k: "c2VjcmV0S0VZ" } }, verify: true, expected: true }, - { sign_key: { fmt: "jwk", - key: { kty: "oct", - alg: "HS256", - key_ops: [ "verify" ], - k: "c2VjcmV0S0VZ" } }, - exception: "TypeError: Key operations and usage mismatch" }, { verify: true, expected: true }, { verify: true, import_alg: { hash: "SHA-384" }, expected: true }, @@ -250,59 +244,6 @@ let rsassa_pkcs1_v1_5_tsuite = { extractable: true, usage: [ "sign", "verify" ] }, expected: true }, - - { sign_key: { key: 1, fmt: "jwk" }, exception: "TypeError: invalid JWK key data" }, - { sign_key: { key: { kty: "RSA" }, fmt: "jwk" }, - exception: "TypeError: Invalid JWK RSA key" }, - { sign_key: { key: { kty: "RSA", - n: "yUmxoJC8VAM5hyYZa-XUBZg1N1ywFMPUpWsF1kaSGed98P3XUgPzgX80wpyzd5qdGuALqnf2lMc7O8PrGBtO5YrvQlI96NX0jUo5bc5wz220ob3AUCeQnTfx-UFqM4pCwjoDSo2PlphJdWgFYymGBaBCJgnENQL9H1N_8_yNiN8" }, - fmt: "jwk" }, - exception: "TypeError: Invalid JWK RSA key" }, - { sign_key: { key: { kty: "RSA", - n: "yUmxoJC8VAM5hyYZa-XUBZg1N1ywFMPUpWsF1kaSGed98P3XUgPzgX80wpyzd5qdGuALqnf2lMc7O8PrGBtO5YrvQlI96NX0jUo5bc5wz220ob3AUCeQnTfx-UFqM4pCwjoDSo2PlphJdWgFYymGBaBCJgnENQL9H1N_8_yNiN8", - e: "AQAB" }, - fmt: "jwk" }, - exception: "TypeError: key usage mismatch for a RSASSA-PKCS1-v1_5" }, - { sign_key: { key: { kty: "RSA", - n: "yUmxoJC8VAM5hyYZa-XUBZg1N1ywFMPUpWsF1kaSGed98P3XUgPzgX80wpyzd5qdGuALqnf2lMc7O8PrGBtO5YrvQlI96NX0jUo5bc5wz220ob3AUCeQnTfx-UFqM4pCwjoDSo2PlphJdWgFYymGBaBCJgnENQL9H1N_8_yNiN8", - e: "AQAB", - d: "j06DQyCopFujYoASi0oWmGEUSjUYO8BsrdSzVCnsLLsuZBwlZ4Peouyw4Hl2IIoYniCyzYwZJzVtC5Dh2MjgcrJTG5nX3FfheuabGl4in0583C51ZYWlVpDvBWw8kJTfXjiKH4z6ZA9dWdT5Y3aH_kOf-znUc7eTvuzISs61x_k" }, - fmt: "jwk" }, - exception: "TypeError: Invalid JWK RSA key" }, - { sign_key: { key: { kty: "RSA", - n: "yUmxoJC8VAM5hyYZa-XUBZg1N1ywFMPUpWsF1kaSGed98P3XUgPzgX80wpyzd5qdGuALqnf2lMc7O8PrGBtO5YrvQlI96NX0jUo5bc5wz220ob3AUCeQnTfx-UFqM4pCwjoDSo2PlphJdWgFYymGBaBCJgnENQL9H1N_8_yNiN8", - e: "AQAB", - d: "j06DQyCopFujYoASi0oWmGEUSjUYO8BsrdSzVCnsLLsuZBwlZ4Peouyw4Hl2IIoYniCyzYwZJzVtC5Dh2MjgcrJTG5nX3FfheuabGl4in0583C51ZYWlVpDvBWw8kJTfXjiKH4z6ZA9dWdT5Y3aH_kOf-znUc7eTvuzISs61x_k", - p: "9ASb2yw5b8d7unrFuOyy4EDcPbnzEpbuVGASeHPqkORwHsqeGbfwGlhDYSYrY0HCwUsSBSFcO3SDeu0Z0zSvFQ" }, - fmt: "jwk" }, - exception: "TypeError: Invalid JWK RSA key" }, - { sign_key: { key: { kty: "RSA", - n: "yUmxoJC8VAM5hyYZa-XUBZg1N1ywFMPUpWsF1kaSGed98P3XUgPzgX80wpyzd5qdGuALqnf2lMc7O8PrGBtO5YrvQlI96NX0jUo5bc5wz220ob3AUCeQnTfx-UFqM4pCwjoDSo2PlphJdWgFYymGBaBCJgnENQL9H1N_8_yNiN8", - e: "AQAB", - d: "j06DQyCopFujYoASi0oWmGEUSjUYO8BsrdSzVCnsLLsuZBwlZ4Peouyw4Hl2IIoYniCyzYwZJzVtC5Dh2MjgcrJTG5nX3FfheuabGl4in0583C51ZYWlVpDvBWw8kJTfXjiKH4z6ZA9dWdT5Y3aH_kOf-znUc7eTvuzISs61x_k", - p: "9ASb2yw5b8d7unrFuOyy4EDcPbnzEpbuVGASeHPqkORwHsqeGbfwGlhDYSYrY0HCwUsSBSFcO3SDeu0Z0zSvFQ", - q: "0yvzzgHo_PGYSlVj-M3965AwQF2wTXz82MZHv6EfcCHKuBfCSecr-igqLHhzfynAQjjf39VrXuPuRL23REF1Iw" }, - fmt: "jwk" }, - exception: "TypeError: Invalid JWK RSA key" }, - { sign_key: { key: { kty: "RSA", - n: "yUmxoJC8VAM5hyYZa-XUBZg1N1ywFMPUpWsF1kaSGed98P3XUgPzgX80wpyzd5qdGuALqnf2lMc7O8PrGBtO5YrvQlI96NX0jUo5bc5wz220ob3AUCeQnTfx-UFqM4pCwjoDSo2PlphJdWgFYymGBaBCJgnENQL9H1N_8_yNiN8", - e: "AQAB", - d: "j06DQyCopFujYoASi0oWmGEUSjUYO8BsrdSzVCnsLLsuZBwlZ4Peouyw4Hl2IIoYniCyzYwZJzVtC5Dh2MjgcrJTG5nX3FfheuabGl4in0583C51ZYWlVpDvBWw8kJTfXjiKH4z6ZA9dWdT5Y3aH_kOf-znUc7eTvuzISs61x_k", - p: "9ASb2yw5b8d7unrFuOyy4EDcPbnzEpbuVGASeHPqkORwHsqeGbfwGlhDYSYrY0HCwUsSBSFcO3SDeu0Z0zSvFQ", - q: "0yvzzgHo_PGYSlVj-M3965AwQF2wTXz82MZHv6EfcCHKuBfCSecr-igqLHhzfynAQjjf39VrXuPuRL23REF1Iw", - dp: "pUXJ2jSl4lOWNcOZz5phvQmxIg2j2N9pJLS9TeAU63YNio1pb7npYa6OVGpp0JxlsE2MMvVZZtuPgd69MxPn0Q" }, - fmt: "jwk" }, - exception: "TypeError: Invalid JWK RSA key" }, - { sign_key: { key: { kty: "RSA", - n: "yUmxoJC8VAM5hyYZa-XUBZg1N1ywFMPUpWsF1kaSGed98P3XUgPzgX80wpyzd5qdGuALqnf2lMc7O8PrGBtO5YrvQlI96NX0jUo5bc5wz220ob3AUCeQnTfx-UFqM4pCwjoDSo2PlphJdWgFYymGBaBCJgnENQL9H1N_8_yNiN8", - e: "AQAB", - d: "j06DQyCopFujYoASi0oWmGEUSjUYO8BsrdSzVCnsLLsuZBwlZ4Peouyw4Hl2IIoYniCyzYwZJzVtC5Dh2MjgcrJTG5nX3FfheuabGl4in0583C51ZYWlVpDvBWw8kJTfXjiKH4z6ZA9dWdT5Y3aH_kOf-znUc7eTvuzISs61x_k", - p: "9ASb2yw5b8d7unrFuOyy4EDcPbnzEpbuVGASeHPqkORwHsqeGbfwGlhDYSYrY0HCwUsSBSFcO3SDeu0Z0zSvFQ", - q: "0yvzzgHo_PGYSlVj-M3965AwQF2wTXz82MZHv6EfcCHKuBfCSecr-igqLHhzfynAQjjf39VrXuPuRL23REF1Iw", - dp: "pUXJ2jSl4lOWNcOZz5phvQmxIg2j2N9pJLS9TeAU63YNio1pb7npYa6OVGpp0JxlsE2MMvVZZtuPgd69MxPn0Q", - dq: "RZoqDM-iXKTA3ldQ0TQMKnVnAgAfWRsGN-j6wxW3R_1LVOw31KYGX7iXVfsJjnNTdEBMwfkVH7yezzd8zVmJ4w" }, - fmt: "jwk" }, - exception: "TypeError: Invalid JWK RSA key" }, { verify: true, sign_key: { key: { kty: "RSA", n: "yUmxoJC8VAM5hyYZa-XUBZg1N1ywFMPUpWsF1kaSGed98P3XUgPzgX80wpyzd5qdGuALqnf2lMc7O8PrGBtO5YrvQlI96NX0jUo5bc5wz220ob3AUCeQnTfx-UFqM4pCwjoDSo2PlphJdWgFYymGBaBCJgnENQL9H1N_8_yNiN8", @@ -315,43 +256,6 @@ let rsassa_pkcs1_v1_5_tsuite = { qi: "2REPnRQIaLsya5wlwFw0whwPaAbTZp2jfguhtg5gou_Yru7Cxz_b83YFPgoI6xuGE1OXsWkRTToS8FuIWCrNBQ" }, fmt: "jwk" }, expected: true }, - { sign_key: { key: { kty: "RSA", - n: "yUmxoJC8VAM5hyYZa-XUBZg1N1ywFMPUpWsF1kaSGed98P3XUgPzgX80wpyzd5qdGuALqnf2lMc7O8PrGBtO5YrvQlI96NX0jUo5bc5wz220ob3AUCeQnTfx-UFqM4pCwjoDSo2PlphJdWgFYymGBaBCJgnENQL9H1N_8_yNiN8", - e: "AQAB", - d: "j06DQyCopFujYoASi0oWmGEUSjUYO8BsrdSzVCnsLLsuZBwlZ4Peouyw4Hl2IIoYniCyzYwZJzVtC5Dh2MjgcrJTG5nX3FfheuabGl4in0583C51ZYWlVpDvBWw8kJTfXjiKH4z6ZA9dWdT5Y3aH_kOf-znUc7eTvuzISs61x_k", - p: "9ASb2yw5b8d7unrFuOyy4EDcPbnzEpbuVGASeHPqkORwHsqeGbfwGlhDYSYrY0HCwUsSBSFcO3SDeu0Z0zSvFQ", - q: "0yvzzgHo_PGYSlVj-M3965AwQF2wTXz82MZHv6EfcCHKuBfCSecr-igqLHhzfynAQjjf39VrXuPuRL23REF1Iw", - dp: "pUXJ2jSl4lOWNcOZz5phvQmxIg2j2N9pJLS9TeAU63YNio1pb7npYa6OVGpp0JxlsE2MMvVZZtuPgd69MxPn0Q", - dq: "RZoqDM-iXKTA3ldQ0TQMKnVnAgAfWRsGN-j6wxW3R_1LVOw31KYGX7iXVfsJjnNTdEBMwfkVH7yezzd8zVmJ4w", - qi: "2REPnRQIaLsya5wlwFw0whwPaAbTZp2jfguhtg5gou_Yru7Cxz_b83YFPgoI6xuGE1OXsWkRTToS8FuIWCrNBQ", - key_ops: [ "verify" ] }, - fmt: "jwk" }, - exception: "TypeError: Key operations and usage mismatch" }, - { sign_key: { key: { kty: "RSA", - n: "yUmxoJC8VAM5hyYZa-XUBZg1N1ywFMPUpWsF1kaSGed98P3XUgPzgX80wpyzd5qdGuALqnf2lMc7O8PrGBtO5YrvQlI96NX0jUo5bc5wz220ob3AUCeQnTfx-UFqM4pCwjoDSo2PlphJdWgFYymGBaBCJgnENQL9H1N_8_yNiN8", - e: "AQAB", - d: "j06DQyCopFujYoASi0oWmGEUSjUYO8BsrdSzVCnsLLsuZBwlZ4Peouyw4Hl2IIoYniCyzYwZJzVtC5Dh2MjgcrJTG5nX3FfheuabGl4in0583C51ZYWlVpDvBWw8kJTfXjiKH4z6ZA9dWdT5Y3aH_kOf-znUc7eTvuzISs61x_k", - p: "9ASb2yw5b8d7unrFuOyy4EDcPbnzEpbuVGASeHPqkORwHsqeGbfwGlhDYSYrY0HCwUsSBSFcO3SDeu0Z0zSvFQ", - q: "0yvzzgHo_PGYSlVj-M3965AwQF2wTXz82MZHv6EfcCHKuBfCSecr-igqLHhzfynAQjjf39VrXuPuRL23REF1Iw", - dp: "pUXJ2jSl4lOWNcOZz5phvQmxIg2j2N9pJLS9TeAU63YNio1pb7npYa6OVGpp0JxlsE2MMvVZZtuPgd69MxPn0Q", - dq: "RZoqDM-iXKTA3ldQ0TQMKnVnAgAfWRsGN-j6wxW3R_1LVOw31KYGX7iXVfsJjnNTdEBMwfkVH7yezzd8zVmJ4w", - qi: "2REPnRQIaLsya5wlwFw0whwPaAbTZp2jfguhtg5gou_Yru7Cxz_b83YFPgoI6xuGE1OXsWkRTToS8FuIWCrNBQ", - ext: false }, - fmt: "jwk", - extractable: true }, - exception: "TypeError: JWK RSA is not extractable" }, - { sign_key: { key: { kty: "RSA", - alg: "RS384", - n: "yUmxoJC8VAM5hyYZa-XUBZg1N1ywFMPUpWsF1kaSGed98P3XUgPzgX80wpyzd5qdGuALqnf2lMc7O8PrGBtO5YrvQlI96NX0jUo5bc5wz220ob3AUCeQnTfx-UFqM4pCwjoDSo2PlphJdWgFYymGBaBCJgnENQL9H1N_8_yNiN8", - e: "AQAB", - d: "j06DQyCopFujYoASi0oWmGEUSjUYO8BsrdSzVCnsLLsuZBwlZ4Peouyw4Hl2IIoYniCyzYwZJzVtC5Dh2MjgcrJTG5nX3FfheuabGl4in0583C51ZYWlVpDvBWw8kJTfXjiKH4z6ZA9dWdT5Y3aH_kOf-znUc7eTvuzISs61x_k", - p: "9ASb2yw5b8d7unrFuOyy4EDcPbnzEpbuVGASeHPqkORwHsqeGbfwGlhDYSYrY0HCwUsSBSFcO3SDeu0Z0zSvFQ", - q: "0yvzzgHo_PGYSlVj-M3965AwQF2wTXz82MZHv6EfcCHKuBfCSecr-igqLHhzfynAQjjf39VrXuPuRL23REF1Iw", - dp: "pUXJ2jSl4lOWNcOZz5phvQmxIg2j2N9pJLS9TeAU63YNio1pb7npYa6OVGpp0JxlsE2MMvVZZtuPgd69MxPn0Q", - dq: "RZoqDM-iXKTA3ldQ0TQMKnVnAgAfWRsGN-j6wxW3R_1LVOw31KYGX7iXVfsJjnNTdEBMwfkVH7yezzd8zVmJ4w", - qi: "2REPnRQIaLsya5wlwFw0whwPaAbTZp2jfguhtg5gou_Yru7Cxz_b83YFPgoI6xuGE1OXsWkRTToS8FuIWCrNBQ" }, - fmt: "jwk" }, - exception: "TypeError: JWK hash mismatch" }, ]}; let rsa_pss_tsuite = { @@ -422,8 +326,6 @@ let ecdsa_tsuite = { { verify: true, import_alg: { hash: "SHA-512" }, expected: true }, { verify: true, import_alg: { hash: "SHA-1" }, expected: true }, { verify: true, verify_key: { key: "ec2.spki" }, expected: false }, - { verify: true, verify_key: { key: "rsa.spki" }, exception: "Error: EC key is not found" }, - { verify: true, import_alg: { namedCurve: "P-384" }, exception: "Error: name curve mismatch" }, { verify: true, verify_key: { key: "BHFFLGURrlWEXhok0JfTKke4q-nWSIMPvKTPhdKYSVnc4Nzn_7uNz0AA3U4fhpfVxSD4U5QciGyEoM4r3jC7bjI", @@ -433,49 +335,12 @@ let ecdsa_tsuite = { { verify: true, sign_key: { key: "ec.jwk", fmt: "jwk" }, expected: true }, { verify: true, sign_key: { key: "ec.jwk", fmt: "jwk" }, verify_key: { key: "ec.pub.jwk", fmt: "jwk" }, expected: true }, - { verify: true, sign_key: { key: "ec.jwk", fmt: "jwk" }, - import_alg: { namedCurve: "P-384" }, exception: "Error: JWK EC curve mismatch" }, - { sign_key: { key: 1, fmt: "jwk" }, exception: "TypeError: Invalid JWK EC key" }, - { sign_key: { key: { kty: "EC" }, fmt: "jwk" }, exception: "TypeError: Invalid JWK EC key" }, - { sign_key: { key: { kty: "EC", x: "cUUsZRGuVYReGiTQl9MqR7ir6dZIgw-8pM-F0phJWdw"}, fmt: "jwk" }, - exception: "TypeError: Invalid JWK EC key" }, - { sign_key: { key: { kty: "EC", x: "cUUsZRGuVYReGiTQl9MqR7ir6dZIgw-8pM-F0phJWdw", - y: "4Nzn_7uNz0AA3U4fhpfVxSD4U5QciGyEoM4r3jC7bjI" }, - fmt: "jwk" }, - exception: "TypeError: Invalid JWK EC key" }, - { sign_key: { key: { kty: "EC", x: "cUUsZRGuVYReGiTQl9MqR7ir6dZIgw-8pM-F0phJWdw", - y: "4Nzn_7uNz0AA3U4fhpfVxSD4U5QciGyEoM4r3jC7bjI", - d: "E2sW0_4a3QXaSTJ0JKbSUbieKTD1UFtr7i_2CuetP6A" }, - fmt: "jwk" }, - exception: "TypeError: JWK EC curve mismatch" }, - { sign_key: { key: { kty: "EC", x: "cUUsZRGuVYReGiTQl9MqR7ir6dZIgw-8pM-F0phJWdw", - y: "4Nzn_7uNz0AA3U4fhpfVxSD4U5QciGyEoM4r3jC7bjI", - d: "E2sW0_4a3QXaSTJ0JKbSUbieKTD1UFtr7i_2CuetP6A", crv: "P-384" }, - fmt: "jwk" }, - exception: "TypeError: JWK EC curve mismatch" }, { verify: true, sign_key: { key: { kty: "EC", x: "cUUsZRGuVYReGiTQl9MqR7ir6dZIgw-8pM-F0phJWdw", y: "4Nzn_7uNz0AA3U4fhpfVxSD4U5QciGyEoM4r3jC7bjI", d: "E2sW0_4a3QXaSTJ0JKbSUbieKTD1UFtr7i_2CuetP6A", crv: "P-256" }, fmt: "jwk" }, expected: true }, - { sign_key: { key: { kty: "EC", x: "_BROKEN_", - y: "4Nzn_7uNz0AA3U4fhpfVxSD4U5QciGyEoM4r3jC7bjI", - d: "E2sW0_4a3QXaSTJ0JKbSUbieKTD1UFtr7i_2CuetP6A", crv: "P-256" }, - fmt: "jwk" }, - exception: "Error: EC_KEY_set_public_key_affine_coordinates() failed" }, - { sign_key: { key: { kty: "EC", x: "cUUsZRGuVYReGiTQl9MqR7ir6dZIgw-8pM-F0phJWdw", - y: "4Nzn_7uNz0AA3U4fhpfVxSD4U5QciGyEoM4r3jC7bjI", - d: "E2sW0_4a3QXaSTJ0JKbSUbieKTD1UFtr7i_2CuetP6A", crv: "P-256", - key_ops: [ "verify" ]}, - fmt: "jwk" }, - exception: "TypeError: Key operations and usage mismatch" }, - { sign_key: { key: { kty: "EC", x: "cUUsZRGuVYReGiTQl9MqR7ir6dZIgw-8pM-F0phJWdw", - y: "4Nzn_7uNz0AA3U4fhpfVxSD4U5QciGyEoM4r3jC7bjI", ext: false, - d: "E2sW0_4a3QXaSTJ0JKbSUbieKTD1UFtr7i_2CuetP6A", crv: "P-256" }, - extractable: true, - fmt: "jwk" }, - exception: "TypeError: JWK is not extractable" }, { sign_key: { key: { kty: "EC", x: "cUUsZRGuVYReGiTQl9MqR7ir6dZIgw-8pM-F0phJWdw", y: "4Nzn_7uNz0AA3U4fhpfVxSD4U5QciGyEoM4r3jC7bjI", d: "E2sW0_4a3QXaSTJ0JKbSUbieKTD1UFtr7i_2CuetP6A", crv: "P-256" }, From noreply at nginx.com Thu Feb 20 11:43:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Thu, 20 Feb 2025 11:43:02 +0000 (UTC) Subject: [nginx] Add gitignore file. Message-ID: <20250220114302.5FE7A4872F@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/f51e2de6fe303506538ba939e9071a11387d8275 branches: master commit: f51e2de6fe303506538ba939e9071a11387d8275 user: Orgad Shaneh date: Thu, 13 Feb 2025 22:35:17 +0200 description: Add gitignore file. --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..7e5088682 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/Makefile +/objs/ +/tmp/ From noreply at nginx.com Thu Feb 20 18:18:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Thu, 20 Feb 2025 18:18:02 +0000 (UTC) Subject: [njs] QuickJS: fixed SharedDict.incr() with empty init argument. Message-ID: <20250220181802.8ED5148F8A@pubserv1.nginx> details: https://github.com/nginx/njs/commit/13f0787fc52e36ae5ed67add0a14c412b3c7b716 branches: master commit: 13f0787fc52e36ae5ed67add0a14c412b3c7b716 user: Dmitry Volyntsev date: Wed, 19 Feb 2025 16:50:15 -0800 description: QuickJS: fixed SharedDict.incr() with empty init argument. --- nginx/ngx_js_shared_dict.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/nginx/ngx_js_shared_dict.c b/nginx/ngx_js_shared_dict.c index e317211e..06f940e0 100644 --- a/nginx/ngx_js_shared_dict.c +++ b/nginx/ngx_js_shared_dict.c @@ -2219,7 +2219,10 @@ ngx_qjs_ext_shared_dict_incr(JSContext *cx, JSValueConst this_val, return JS_EXCEPTION; } - if (JS_ToFloat64(cx, &init, argv[2]) < 0) { + if (JS_IsUndefined(argv[2])) { + init = 0; + + } else if (JS_ToFloat64(cx, &init, argv[2]) < 0) { return JS_EXCEPTION; } From noreply at nginx.com Thu Feb 20 18:18:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Thu, 20 Feb 2025 18:18:02 +0000 (UTC) Subject: [njs] QuickJS: fixed memory leak in js_periodic handler. Message-ID: <20250220181802.9564648F8B@pubserv1.nginx> details: https://github.com/nginx/njs/commit/1b451958979d38dc162cfa2872197fcba9381f7d branches: master commit: 1b451958979d38dc162cfa2872197fcba9381f7d user: Dmitry Volyntsev date: Wed, 19 Feb 2025 17:36:46 -0800 description: QuickJS: fixed memory leak in js_periodic handler. --- nginx/ngx_http_js_module.c | 17 ++++++++++++++++- nginx/ngx_stream_js_module.c | 17 ++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c index 7cd87401..cdc668c5 100644 --- a/nginx/ngx_http_js_module.c +++ b/nginx/ngx_http_js_module.c @@ -355,6 +355,7 @@ static ngx_http_request_t *ngx_http_qjs_request(JSValueConst val); static JSValue ngx_http_qjs_request_make(JSContext *cx, ngx_int_t proto_id, ngx_http_request_t *r); static void ngx_http_qjs_request_finalizer(JSRuntime *rt, JSValue val); +static void ngx_http_qjs_periodic_finalizer(JSRuntime *rt, JSValue val); #endif static ngx_pool_t *ngx_http_js_pool(ngx_http_request_t *r); @@ -1097,7 +1098,7 @@ static JSClassDef ngx_http_qjs_request_class = { static JSClassDef ngx_http_qjs_periodic_class = { "PeriodicSession", - .finalizer = NULL, + .finalizer = ngx_http_qjs_periodic_finalizer, }; @@ -7553,6 +7554,20 @@ ngx_http_qjs_request_finalizer(JSRuntime *rt, JSValue val) } +static void +ngx_http_qjs_periodic_finalizer(JSRuntime *rt, JSValue val) +{ + ngx_http_qjs_request_t *req; + + req = JS_GetOpaque(val, NGX_QJS_CLASS_ID_HTTP_PERIODIC); + if (req == NULL) { + return; + } + + js_free_rt(rt, req); +} + + static ngx_engine_t * ngx_engine_qjs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf, njs_int_t proto_id, void *external) diff --git a/nginx/ngx_stream_js_module.c b/nginx/ngx_stream_js_module.c index 59a3e9d2..db00b922 100644 --- a/nginx/ngx_stream_js_module.c +++ b/nginx/ngx_stream_js_module.c @@ -191,6 +191,7 @@ static ngx_stream_session_t *ngx_stream_qjs_session(JSValueConst val); static JSValue ngx_stream_qjs_session_make(JSContext *cx, ngx_int_t proto_id, ngx_stream_session_t *s); static void ngx_stream_qjs_session_finalizer(JSRuntime *rt, JSValue val); +static void ngx_stream_qjs_periodic_finalizer(JSRuntime *rt, JSValue val); #endif @@ -813,7 +814,7 @@ static JSClassDef ngx_stream_qjs_session_class = { static JSClassDef ngx_stream_qjs_periodic_class = { "Periodic", - .finalizer = NULL, + .finalizer = ngx_stream_qjs_periodic_finalizer, }; @@ -2812,6 +2813,20 @@ ngx_stream_qjs_session_finalizer(JSRuntime *rt, JSValue val) } +static void +ngx_stream_qjs_periodic_finalizer(JSRuntime *rt, JSValue val) +{ + ngx_stream_qjs_session_t *ses; + + ses = JS_GetOpaque(val, NGX_QJS_CLASS_ID_STREAM_PERIODIC); + if (ses == NULL) { + return; + } + + js_free_rt(rt, ses); +} + + static ngx_engine_t * ngx_engine_qjs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf, njs_int_t proto_id, void *external) From noreply at nginx.com Thu Feb 20 18:18:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Thu, 20 Feb 2025 18:18:02 +0000 (UTC) Subject: [njs] Tests: splitting js_periodic tests into multiple files. Message-ID: <20250220181802.9B46848F8C@pubserv1.nginx> details: https://github.com/nginx/njs/commit/89634204c4b12f17bbec826768d0d4e9dc5fae6c branches: master commit: 89634204c4b12f17bbec826768d0d4e9dc5fae6c user: Dmitry Volyntsev date: Tue, 18 Feb 2025 22:53:54 -0800 description: Tests: splitting js_periodic tests into multiple files. --- nginx/t/js_periodic.t | 92 +---------------- nginx/t/js_periodic_fetch.t | 138 +++++++++++++++++++++++++ nginx/t/js_periodic_file.t | 91 +++++++++++++++++ nginx/t/stream_js_periodic.t | 103 +------------------ nginx/t/stream_js_periodic_fetch.t | 201 +++++++++++++++++++++++++++++++++++++ nginx/t/stream_js_periodic_file.t | 144 ++++++++++++++++++++++++++ 6 files changed, 580 insertions(+), 189 deletions(-) diff --git a/nginx/t/js_periodic.t b/nginx/t/js_periodic.t index 63afe379..d6868935 100644 --- a/nginx/t/js_periodic.t +++ b/nginx/t/js_periodic.t @@ -59,46 +59,18 @@ http { js_periodic test.tick interval=30ms jitter=1ms; js_periodic test.timer interval=1s worker_affinity=all; js_periodic test.overrun interval=30ms; - js_periodic test.file interval=1s; - js_periodic test.fetch interval=40ms; - js_periodic test.multiple_fetches interval=1s; js_periodic test.affinity interval=50ms worker_affinity=0101; js_periodic test.vars interval=10s; - js_periodic test.fetch_exception interval=1s; js_periodic test.tick_exception interval=1s; js_periodic test.timer_exception interval=1s; js_periodic test.timeout_exception interval=30ms; } - location /engine { - js_content test.engine; - } - - location /fetch_ok { - return 200 'ok'; - } - - location /fetch_foo { - return 200 'foo'; - } - location /test_affinity { js_content test.test_affinity; } - location /test_fetch { - js_content test.test_fetch; - } - - location /test_file { - js_content test.test_file; - } - - location /test_multiple_fetches { - js_content test.test_multiple_fetches; - } - location /test_tick { js_content test.test_tick; } @@ -119,51 +91,15 @@ http { EOF -my $p0 = port(8080); - $t->write_file('test.js', < {}, 100000); } @@ -214,20 +150,6 @@ $t->write_file('test.js', <= 3); } @@ -244,19 +166,14 @@ $t->write_file('test.js', <try_run('no js_periodic'); -plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m; - -$t->plan(9); +$t->plan(6); ############################################################################### @@ -265,9 +182,6 @@ select undef, undef, undef, 0.1; like(http_get('/test_affinity'), qr/\[1,3]/, 'affinity test'); like(http_get('/test_tick'), qr/true/, '3x tick test'); like(http_get('/test_timer'), qr/true/, 'timer test'); -like(http_get('/test_file'), qr/true/, 'file test'); -like(http_get('/test_fetch'), qr/true/, 'periodic fetch test'); -like(http_get('/test_multiple_fetches'), qr/true/, 'multiple fetch test'); like(http_get('/test_timeout_exception'), qr/true/, 'timeout exception test'); like(http_get('/test_vars'), qr/JS-VAR\|JS-SET\|MAP-VAR/, 'vars test'); diff --git a/nginx/t/js_periodic_fetch.t b/nginx/t/js_periodic_fetch.t new file mode 100644 index 00000000..d7bcfb76 --- /dev/null +++ b/nginx/t/js_periodic_fetch.t @@ -0,0 +1,138 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for js_periodic directive. + +############################################################################### + +use warnings; +use strict; + +use Test::More; +use Socket qw/ CRLF /; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http rewrite/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; +worker_processes 4; + +events { +} + +worker_shutdown_timeout 100ms; + +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + js_shared_dict_zone zone=strings:32k; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location @periodic { + js_periodic test.fetch interval=40ms; + js_periodic test.multiple_fetches interval=1s; + + js_periodic test.fetch_exception interval=1s; + } + + location /engine { + js_content test.engine; + } + + location /fetch_ok { + return 200 'ok'; + } + + location /fetch_foo { + return 200 'foo'; + } + + location /test_fetch { + js_content test.test_fetch; + } + + location /test_multiple_fetches { + js_content test.test_multiple_fetches; + } + } +} + +EOF + +my $p0 = port(8080); + +$t->write_file('test.js', <try_run('no js_periodic with fetch'); + +plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m; + +$t->plan(3); + +############################################################################### + +select undef, undef, undef, 0.1; + +like(http_get('/test_fetch'), qr/true/, 'periodic fetch test'); +like(http_get('/test_multiple_fetches'), qr/true/, 'multiple fetch test'); + +$t->stop(); + +unlike($t->read_file('error.log'), qr/\[error\].*should not be seen/, + 'check for not discadred events'); diff --git a/nginx/t/js_periodic_file.t b/nginx/t/js_periodic_file.t new file mode 100644 index 00000000..e8eb3070 --- /dev/null +++ b/nginx/t/js_periodic_file.t @@ -0,0 +1,91 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for js_periodic directive. + +############################################################################### + +use warnings; +use strict; + +use Test::More; +use Socket qw/ CRLF /; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http rewrite/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; +worker_processes 4; + +events { +} + +worker_shutdown_timeout 100ms; + +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location @periodic { + js_periodic test.file interval=1s; + } + + location /test_file { + js_content test.test_file; + } + } +} + +EOF + +$t->write_file('test.js', <try_run('no js_periodic with fs support'); + +$t->plan(2); + +############################################################################### + +select undef, undef, undef, 0.1; + +like(http_get('/test_file'), qr/true/, 'file test'); + +$t->stop(); + +unlike($t->read_file('error.log'), qr/\[error\].*should not be seen/, + 'check for not discadred events'); diff --git a/nginx/t/stream_js_periodic.t b/nginx/t/stream_js_periodic.t index 4b9e319b..ac64045e 100644 --- a/nginx/t/stream_js_periodic.t +++ b/nginx/t/stream_js_periodic.t @@ -58,13 +58,9 @@ stream { js_periodic test.tick interval=30ms jitter=1ms; js_periodic test.timer interval=1s worker_affinity=all; js_periodic test.overrun interval=30ms; - js_periodic test.file interval=1s; - js_periodic test.fetch interval=40ms; - js_periodic test.multiple_fetches interval=1s; js_periodic test.affinity interval=50ms worker_affinity=0101; js_periodic test.vars interval=10s; - js_periodic test.fetch_exception interval=1s; js_periodic test.tick_exception interval=1s; js_periodic test.timer_exception interval=1s; js_periodic test.timeout_exception interval=30ms; @@ -75,76 +71,17 @@ stream { } } -http { - %%TEST_GLOBALS_HTTP%% - - js_import test.js; - - server { - listen 127.0.0.1:8080; - server_name localhost; - - location /engine { - js_content test.engine; - } - - location /fetch_ok { - return 200 'ok'; - } - - location /fetch_foo { - return 200 'foo'; - } - } -} - EOF -my $p1 = port(8080); - $t->write_file('test.js', < {}, 100000); } @@ -202,34 +139,6 @@ $t->write_file('test.js', <write_file('test.js', <run_daemon(\&stream_daemon, port(8090)); $t->try_run('no js_periodic'); -plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m; -$t->plan(9); +$t->plan(6); $t->waitforsocket('127.0.0.1:' . port(8090)); ############################################################################### @@ -293,10 +200,6 @@ is(stream('127.0.0.1:' . port(8081))->io('affinity'), 'affinity', 'affinity test'); is(stream('127.0.0.1:' . port(8081))->io('tick'), 'tick', '3x tick test'); is(stream('127.0.0.1:' . port(8081))->io('timer'), 'timer', 'timer test'); -is(stream('127.0.0.1:' . port(8081))->io('file'), 'file', 'file test'); -is(stream('127.0.0.1:' . port(8081))->io('fetch'), 'fetch', 'fetch test'); -is(stream('127.0.0.1:' . port(8081))->io('multiple_fetches'), - 'multiple_fetches', 'muliple fetches test'); is(stream('127.0.0.1:' . port(8081))->io('timeout_exception'), 'timeout_exception', 'timeout exception test'); is(stream('127.0.0.1:' . port(8081))->io('vars'), 'vars', 'vars test'); diff --git a/nginx/t/stream_js_periodic_fetch.t b/nginx/t/stream_js_periodic_fetch.t new file mode 100644 index 00000000..e88d69d5 --- /dev/null +++ b/nginx/t/stream_js_periodic_fetch.t @@ -0,0 +1,201 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for stream njs module, js_periodic directive. + +############################################################################### + +use warnings; +use strict; + +use Test::More; +use Socket qw/ CRLF /; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; +use Test::Nginx::Stream qw/ stream /; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http rewrite stream/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; +worker_processes 4; + +events { +} + +worker_shutdown_timeout 100ms; + +stream { + %%TEST_GLOBALS_STREAM%% + + js_import test.js; + + js_shared_dict_zone zone=strings:32k; + + server { + listen 127.0.0.1:8081; + + js_periodic test.fetch interval=40ms; + js_periodic test.multiple_fetches interval=1s; + + js_periodic test.fetch_exception interval=1s; + + js_preread test.test; + + proxy_pass 127.0.0.1:8090; + } +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /engine { + js_content test.engine; + } + + location /fetch_ok { + return 200 'ok'; + } + + location /fetch_foo { + return 200 'foo'; + } + } +} + +EOF + +my $p1 = port(8080); + +$t->write_file('test.js', < 0) { + switch (data) { + case 'fetch': + if (ngx.shared.strings.get('fetch').startsWith('okok')) { + s.done(); + return; + } + + break; + + case 'multiple_fetches': + if (ngx.shared.strings.get('multiple_fetches') + .startsWith('ok\@foo')) + { + s.done(); + return; + } + + break; + + default: + throw new Error(`Unknown test "\${data}"`); + } + + throw new Error(`Test "\${data}" failed`); + } + }); + } + + export default { engine, fetch, fetch_exception, test, multiple_fetches }; +EOF + +$t->run_daemon(\&stream_daemon, port(8090)); +$t->try_run('no js_periodic with fetch'); +plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m; +$t->plan(3); +$t->waitforsocket('127.0.0.1:' . port(8090)); + +############################################################################### + +select undef, undef, undef, 0.2; + +is(stream('127.0.0.1:' . port(8081))->io('fetch'), 'fetch', 'fetch test'); +is(stream('127.0.0.1:' . port(8081))->io('multiple_fetches'), + 'multiple_fetches', 'muliple fetches test'); + +$t->stop(); + +unlike($t->read_file('error.log'), qr/\[error\].*should not be seen/, + 'check for not discadred events'); + +############################################################################### + +sub stream_daemon { + my $server = IO::Socket::INET->new( + Proto => 'tcp', + LocalAddr => '127.0.0.1:' . port(8090), + Listen => 5, + Reuse => 1 + ) + or die "Can't create listening socket: $!\n"; + + local $SIG{PIPE} = 'IGNORE'; + + while (my $client = $server->accept()) { + $client->autoflush(1); + + log2c("(new connection $client)"); + + $client->sysread(my $buffer, 65536) or next; + + log2i("$client $buffer"); + + log2o("$client $buffer"); + + $client->syswrite($buffer); + + close $client; + } +} + +sub log2i { Test::Nginx::log_core('|| <<', @_); } +sub log2o { Test::Nginx::log_core('|| >>', @_); } +sub log2c { Test::Nginx::log_core('||', @_); } + +############################################################################### diff --git a/nginx/t/stream_js_periodic_file.t b/nginx/t/stream_js_periodic_file.t new file mode 100644 index 00000000..b41ce04c --- /dev/null +++ b/nginx/t/stream_js_periodic_file.t @@ -0,0 +1,144 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for stream njs module, js_periodic directive. + +############################################################################### + +use warnings; +use strict; + +use Test::More; +use Socket qw/ CRLF /; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; +use Test::Nginx::Stream qw/ stream /; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http rewrite stream/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; +worker_processes 4; + +events { +} + +worker_shutdown_timeout 100ms; + +stream { + %%TEST_GLOBALS_STREAM%% + + js_import test.js; + + server { + listen 127.0.0.1:8081; + js_periodic test.file interval=1s; + + js_preread test.test; + + proxy_pass 127.0.0.1:8090; + } +} + +EOF + +$t->write_file('test.js', < 0) { + switch (data) { + case 'file': + let file_data = fs.readFileSync(ngx.conf_prefix + 'file') + .toString(); + + if (file_data == 'abc') { + s.done(); + return; + } + + break; + + default: + throw new Error(`Unknown test "\${data}"`); + } + + throw new Error(`Test "\${data}" failed`); + } + }); + } + + export default { file, test }; +EOF + +$t->run_daemon(\&stream_daemon, port(8090)); +$t->try_run('no js_periodic with fs support'); +$t->plan(2); +$t->waitforsocket('127.0.0.1:' . port(8090)); + +############################################################################### + +select undef, undef, undef, 0.2; + +is(stream('127.0.0.1:' . port(8081))->io('file'), 'file', 'file test'); + +$t->stop(); + +unlike($t->read_file('error.log'), qr/\[error\].*should not be seen/, + 'check for not discadred events'); + +############################################################################### + +sub stream_daemon { + my $server = IO::Socket::INET->new( + Proto => 'tcp', + LocalAddr => '127.0.0.1:' . port(8090), + Listen => 5, + Reuse => 1 + ) + or die "Can't create listening socket: $!\n"; + + local $SIG{PIPE} = 'IGNORE'; + + while (my $client = $server->accept()) { + $client->autoflush(1); + + log2c("(new connection $client)"); + + $client->sysread(my $buffer, 65536) or next; + + log2i("$client $buffer"); + + log2o("$client $buffer"); + + $client->syswrite($buffer); + + close $client; + } +} + +sub log2i { Test::Nginx::log_core('|| <<', @_); } +sub log2o { Test::Nginx::log_core('|| >>', @_); } +sub log2c { Test::Nginx::log_core('||', @_); } + +############################################################################### From noreply at nginx.com Thu Feb 20 20:05:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Thu, 20 Feb 2025 20:05:02 +0000 (UTC) Subject: [nginx] Improved ngx_http_subrequest() error handling. Message-ID: <20250220200502.920B048F8B@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/d25139db01b636a8212c13e1feeca37eaadad0b5 branches: master commit: d25139db01b636a8212c13e1feeca37eaadad0b5 user: Sergey Kandaurov date: Tue, 11 Feb 2025 22:54:04 +0400 description: Improved ngx_http_subrequest() error handling. Previously, request might be left in inconsistent state in case of error, which manifested in "http request count is zero" alerts when used by SSI filter. The fix is to reshuffle initialization order to postpone committing state changes until after any potentially failing parts. Found by bad memory allocator simulation. --- src/http/ngx_http_core_module.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c index a1540c018..92c3eae8a 100644 --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -2327,6 +2327,7 @@ ngx_http_subrequest(ngx_http_request_t *r, ngx_connection_t *c; ngx_http_request_t *sr; ngx_http_core_srv_conf_t *cscf; + ngx_http_posted_request_t *posted; ngx_http_postponed_request_t *pr, *p; if (r->subrequests == 0) { @@ -2380,6 +2381,11 @@ ngx_http_subrequest(ngx_http_request_t *r, return NGX_ERROR; } + posted = ngx_palloc(r->pool, sizeof(ngx_http_posted_request_t)); + if (posted == NULL) { + return NGX_ERROR; + } + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); sr->main_conf = cscf->ctx->main_conf; sr->srv_conf = cscf->ctx->srv_conf; @@ -2438,10 +2444,6 @@ ngx_http_subrequest(ngx_http_request_t *r, } if (!sr->background) { - if (c->data == r && r->postponed == NULL) { - c->data = sr; - } - pr = ngx_palloc(r->pool, sizeof(ngx_http_postponed_request_t)); if (pr == NULL) { return NGX_ERROR; @@ -2451,6 +2453,10 @@ ngx_http_subrequest(ngx_http_request_t *r, pr->out = NULL; pr->next = NULL; + if (c->data == r && r->postponed == NULL) { + c->data = sr; + } + if (r->postponed) { for (p = r->postponed; p->next; p = p->next) { /* void */ } p->next = pr; @@ -2498,7 +2504,7 @@ ngx_http_subrequest(ngx_http_request_t *r, ngx_http_update_location_config(sr); } - return ngx_http_post_request(sr, NULL); + return ngx_http_post_request(sr, posted); } From yesin.iv at gmail.com Mon Feb 24 18:42:58 2025 From: yesin.iv at gmail.com (Ilya) Date: Mon, 24 Feb 2025 19:42:58 +0100 Subject: unsubscribe In-Reply-To: <20250220181802.9B46848F8C@pubserv1.nginx> References: <20250220181802.9B46848F8C@pubserv1.nginx> Message-ID: On Thu, Feb 20, 2025 at 7:18 PM wrote: > > details: https://github.com/nginx/njs/commit/89634204c4b12f17bbec826768d0d4e9dc5fae6c > branches: master > commit: 89634204c4b12f17bbec826768d0d4e9dc5fae6c > user: Dmitry Volyntsev > date: Tue, 18 Feb 2025 22:53:54 -0800 > description: > Tests: splitting js_periodic tests into multiple files. > > > --- > nginx/t/js_periodic.t | 92 +---------------- > nginx/t/js_periodic_fetch.t | 138 +++++++++++++++++++++++++ > nginx/t/js_periodic_file.t | 91 +++++++++++++++++ > nginx/t/stream_js_periodic.t | 103 +------------------ > nginx/t/stream_js_periodic_fetch.t | 201 +++++++++++++++++++++++++++++++++++++ > nginx/t/stream_js_periodic_file.t | 144 ++++++++++++++++++++++++++ > 6 files changed, 580 insertions(+), 189 deletions(-) > > diff --git a/nginx/t/js_periodic.t b/nginx/t/js_periodic.t > index 63afe379..d6868935 100644 > --- a/nginx/t/js_periodic.t > +++ b/nginx/t/js_periodic.t > @@ -59,46 +59,18 @@ http { > js_periodic test.tick interval=30ms jitter=1ms; > js_periodic test.timer interval=1s worker_affinity=all; > js_periodic test.overrun interval=30ms; > - js_periodic test.file interval=1s; > - js_periodic test.fetch interval=40ms; > - js_periodic test.multiple_fetches interval=1s; > js_periodic test.affinity interval=50ms worker_affinity=0101; > js_periodic test.vars interval=10s; > > - js_periodic test.fetch_exception interval=1s; > js_periodic test.tick_exception interval=1s; > js_periodic test.timer_exception interval=1s; > js_periodic test.timeout_exception interval=30ms; > } > > - location /engine { > - js_content test.engine; > - } > - > - location /fetch_ok { > - return 200 'ok'; > - } > - > - location /fetch_foo { > - return 200 'foo'; > - } > - > location /test_affinity { > js_content test.test_affinity; > } > > - location /test_fetch { > - js_content test.test_fetch; > - } > - > - location /test_file { > - js_content test.test_file; > - } > - > - location /test_multiple_fetches { > - js_content test.test_multiple_fetches; > - } > - > location /test_tick { > js_content test.test_tick; > } > @@ -119,51 +91,15 @@ http { > > EOF > > -my $p0 = port(8080); > - > $t->write_file('test.js', < - import fs from 'fs'; > - > - function engine(r) { > - r.return(200, njs.engine); > - } > - > function affinity() { > ngx.shared.workers.set(ngx.worker_id, 1); > } > > - async function fetch() { > - let reply = await ngx.fetch('http://127.0.0.1:$p0/fetch_ok'); > - let body = await reply.text(); > - > - let v = ngx.shared.strings.get('fetch') || ''; > - ngx.shared.strings.set('fetch', v + body); > - } > - > function js_set() { > return 'JS-SET'; > } > > - async function multiple_fetches() { > - let reply = await ngx.fetch('http://127.0.0.1:$p0/fetch_ok'); > - let reply2 = await ngx.fetch('http://127.0.0.1:$p0/fetch_foo'); > - let body = await reply.text(); > - let body2 = await reply2.text(); > - > - ngx.shared.strings.set('multiple_fetches', body + '\@' + body2); > - } > - > - async function fetch_exception() { > - let reply = await ngx.fetch('garbage'); > - } > - > - async function file() { > - let fh = await fs.promises.open(ngx.conf_prefix + 'file', 'a+'); > - > - await fh.write('abc'); > - await fh.close(); > - } > - > async function overrun() { > setTimeout(() => {}, 100000); > } > @@ -214,20 +150,6 @@ $t->write_file('test.js', < r.return(200, `[\${ngx.shared.workers.keys().toSorted()}]`); > } > > - function test_fetch(r) { > - r.return(200, ngx.shared.strings.get('fetch').startsWith('okok')); > - } > - > - function test_file(r) { > - r.return(200, > - fs.readFileSync(ngx.conf_prefix + 'file').toString() == 'abc'); > - } > - > - function test_multiple_fetches(r) { > - r.return(200, ngx.shared.strings.get('multiple_fetches') > - .startsWith('ok\@foo')); > - } > - > function test_tick(r) { > r.return(200, ngx.shared.nums.get('tick') >= 3); > } > @@ -244,19 +166,14 @@ $t->write_file('test.js', < r.return(200, ngx.shared.strings.get('vars')); > } > > - export default { affinity, fetch, fetch_exception, file, js_set, > - multiple_fetches, overrun, vars, test_affinity, test_fetch, > - test_file, test_multiple_fetches, test_tick, > + export default { affinity, js_set, overrun, vars, test_affinity, test_tick, > test_timeout_exception, test_timer, test_vars, tick, > - tick_exception, timer, timer_exception, > - timeout_exception, engine }; > + tick_exception, timer, timer_exception, timeout_exception }; > EOF > > $t->try_run('no js_periodic'); > > -plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m; > - > -$t->plan(9); > +$t->plan(6); > > ############################################################################### > > @@ -265,9 +182,6 @@ select undef, undef, undef, 0.1; > like(http_get('/test_affinity'), qr/\[1,3]/, 'affinity test'); > like(http_get('/test_tick'), qr/true/, '3x tick test'); > like(http_get('/test_timer'), qr/true/, 'timer test'); > -like(http_get('/test_file'), qr/true/, 'file test'); > -like(http_get('/test_fetch'), qr/true/, 'periodic fetch test'); > -like(http_get('/test_multiple_fetches'), qr/true/, 'multiple fetch test'); > > like(http_get('/test_timeout_exception'), qr/true/, 'timeout exception test'); > like(http_get('/test_vars'), qr/JS-VAR\|JS-SET\|MAP-VAR/, 'vars test'); > diff --git a/nginx/t/js_periodic_fetch.t b/nginx/t/js_periodic_fetch.t > new file mode 100644 > index 00000000..d7bcfb76 > --- /dev/null > +++ b/nginx/t/js_periodic_fetch.t > @@ -0,0 +1,138 @@ > +#!/usr/bin/perl > + > +# (C) Dmitry Volyntsev > +# (C) Nginx, Inc. > + > +# Tests for js_periodic directive. > + > +############################################################################### > + > +use warnings; > +use strict; > + > +use Test::More; > +use Socket qw/ CRLF /; > + > +BEGIN { use FindBin; chdir($FindBin::Bin); } > + > +use lib 'lib'; > +use Test::Nginx; > + > +############################################################################### > + > +select STDERR; $| = 1; > +select STDOUT; $| = 1; > + > +my $t = Test::Nginx->new()->has(qw/http rewrite/) > + ->write_file_expand('nginx.conf', <<'EOF'); > + > +%%TEST_GLOBALS%% > + > +daemon off; > +worker_processes 4; > + > +events { > +} > + > +worker_shutdown_timeout 100ms; > + > +http { > + %%TEST_GLOBALS_HTTP%% > + > + js_import test.js; > + > + js_shared_dict_zone zone=strings:32k; > + > + server { > + listen 127.0.0.1:8080; > + server_name localhost; > + > + location @periodic { > + js_periodic test.fetch interval=40ms; > + js_periodic test.multiple_fetches interval=1s; > + > + js_periodic test.fetch_exception interval=1s; > + } > + > + location /engine { > + js_content test.engine; > + } > + > + location /fetch_ok { > + return 200 'ok'; > + } > + > + location /fetch_foo { > + return 200 'foo'; > + } > + > + location /test_fetch { > + js_content test.test_fetch; > + } > + > + location /test_multiple_fetches { > + js_content test.test_multiple_fetches; > + } > + } > +} > + > +EOF > + > +my $p0 = port(8080); > + > +$t->write_file('test.js', < + function engine(r) { > + r.return(200, njs.engine); > + } > + > + async function fetch() { > + let reply = await ngx.fetch('http://127.0.0.1:$p0/fetch_ok'); > + let body = await reply.text(); > + > + let v = ngx.shared.strings.get('fetch') || ''; > + ngx.shared.strings.set('fetch', v + body); > + } > + > + async function multiple_fetches() { > + let reply = await ngx.fetch('http://127.0.0.1:$p0/fetch_ok'); > + let reply2 = await ngx.fetch('http://127.0.0.1:$p0/fetch_foo'); > + let body = await reply.text(); > + let body2 = await reply2.text(); > + > + ngx.shared.strings.set('multiple_fetches', body + '\@' + body2); > + } > + > + async function fetch_exception() { > + let reply = await ngx.fetch('garbage'); > + } > + > + function test_fetch(r) { > + r.return(200, ngx.shared.strings.get('fetch').startsWith('okok')); > + } > + > + function test_multiple_fetches(r) { > + r.return(200, ngx.shared.strings.get('multiple_fetches') > + .startsWith('ok\@foo')); > + } > + > + export default { fetch, fetch_exception, multiple_fetches, test_fetch, > + test_multiple_fetches, engine }; > +EOF > + > +$t->try_run('no js_periodic with fetch'); > + > +plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m; > + > +$t->plan(3); > + > +############################################################################### > + > +select undef, undef, undef, 0.1; > + > +like(http_get('/test_fetch'), qr/true/, 'periodic fetch test'); > +like(http_get('/test_multiple_fetches'), qr/true/, 'multiple fetch test'); > + > +$t->stop(); > + > +unlike($t->read_file('error.log'), qr/\[error\].*should not be seen/, > + 'check for not discadred events'); > diff --git a/nginx/t/js_periodic_file.t b/nginx/t/js_periodic_file.t > new file mode 100644 > index 00000000..e8eb3070 > --- /dev/null > +++ b/nginx/t/js_periodic_file.t > @@ -0,0 +1,91 @@ > +#!/usr/bin/perl > + > +# (C) Dmitry Volyntsev > +# (C) Nginx, Inc. > + > +# Tests for js_periodic directive. > + > +############################################################################### > + > +use warnings; > +use strict; > + > +use Test::More; > +use Socket qw/ CRLF /; > + > +BEGIN { use FindBin; chdir($FindBin::Bin); } > + > +use lib 'lib'; > +use Test::Nginx; > + > +############################################################################### > + > +select STDERR; $| = 1; > +select STDOUT; $| = 1; > + > +my $t = Test::Nginx->new()->has(qw/http rewrite/) > + ->write_file_expand('nginx.conf', <<'EOF'); > + > +%%TEST_GLOBALS%% > + > +daemon off; > +worker_processes 4; > + > +events { > +} > + > +worker_shutdown_timeout 100ms; > + > +http { > + %%TEST_GLOBALS_HTTP%% > + > + js_import test.js; > + > + server { > + listen 127.0.0.1:8080; > + server_name localhost; > + > + location @periodic { > + js_periodic test.file interval=1s; > + } > + > + location /test_file { > + js_content test.test_file; > + } > + } > +} > + > +EOF > + > +$t->write_file('test.js', < + import fs from 'fs'; > + > + async function file() { > + let fh = await fs.promises.open(ngx.conf_prefix + 'file', 'a+'); > + > + await fh.write('abc'); > + await fh.close(); > + } > + > + function test_file(r) { > + r.return(200, > + fs.readFileSync(ngx.conf_prefix + 'file').toString() == 'abc'); > + } > + > + export default { file, test_file }; > +EOF > + > +$t->try_run('no js_periodic with fs support'); > + > +$t->plan(2); > + > +############################################################################### > + > +select undef, undef, undef, 0.1; > + > +like(http_get('/test_file'), qr/true/, 'file test'); > + > +$t->stop(); > + > +unlike($t->read_file('error.log'), qr/\[error\].*should not be seen/, > + 'check for not discadred events'); > diff --git a/nginx/t/stream_js_periodic.t b/nginx/t/stream_js_periodic.t > index 4b9e319b..ac64045e 100644 > --- a/nginx/t/stream_js_periodic.t > +++ b/nginx/t/stream_js_periodic.t > @@ -58,13 +58,9 @@ stream { > js_periodic test.tick interval=30ms jitter=1ms; > js_periodic test.timer interval=1s worker_affinity=all; > js_periodic test.overrun interval=30ms; > - js_periodic test.file interval=1s; > - js_periodic test.fetch interval=40ms; > - js_periodic test.multiple_fetches interval=1s; > js_periodic test.affinity interval=50ms worker_affinity=0101; > js_periodic test.vars interval=10s; > > - js_periodic test.fetch_exception interval=1s; > js_periodic test.tick_exception interval=1s; > js_periodic test.timer_exception interval=1s; > js_periodic test.timeout_exception interval=30ms; > @@ -75,76 +71,17 @@ stream { > } > } > > -http { > - %%TEST_GLOBALS_HTTP%% > - > - js_import test.js; > - > - server { > - listen 127.0.0.1:8080; > - server_name localhost; > - > - location /engine { > - js_content test.engine; > - } > - > - location /fetch_ok { > - return 200 'ok'; > - } > - > - location /fetch_foo { > - return 200 'foo'; > - } > - } > -} > - > EOF > > -my $p1 = port(8080); > - > $t->write_file('test.js', < - import fs from 'fs'; > - > - function engine(r) { > - r.return(200, njs.engine); > - } > - > function affinity() { > ngx.shared.workers.set(ngx.worker_id, 1); > } > > - async function fetch() { > - let reply = await ngx.fetch('http://127.0.0.1:$p1/fetch_ok'); > - let body = await reply.text(); > - > - let v = ngx.shared.strings.get('fetch') || ''; > - ngx.shared.strings.set('fetch', v + body); > - } > - > - async function fetch_exception() { > - let reply = await ngx.fetch('garbage'); > - } > - > function js_set() { > return 'JS-SET'; > } > > - async function multiple_fetches() { > - let reply = await ngx.fetch('http://127.0.0.1:$p1/fetch_ok'); > - let reply2 = await ngx.fetch('http://127.0.0.1:$p1/fetch_foo'); > - let body = await reply.text(); > - let body2 = await reply2.text(); > - > - ngx.shared.strings.set('multiple_fetches', body + '\@' + body2); > - } > - > - async function file() { > - let fh = await fs.promises.open(ngx.conf_prefix + 'file', 'a+'); > - > - await fh.write('abc'); > - await fh.close(); > - } > - > async function overrun() { > setTimeout(() => {}, 100000); > } > @@ -202,34 +139,6 @@ $t->write_file('test.js', < return; > } > > - break; > - case 'fetch': > - if (ngx.shared.strings.get('fetch').startsWith('okok')) { > - s.done(); > - return; > - } > - > - break; > - > - case 'multiple_fetches': > - if (ngx.shared.strings.get('multiple_fetches') > - .startsWith('ok\@foo')) > - { > - s.done(); > - return; > - } > - > - break; > - > - case 'file': > - let file_data = fs.readFileSync(ngx.conf_prefix + 'file') > - .toString(); > - > - if (file_data == 'abc') { > - s.done(); > - return; > - } > - > break; > > case 'tick': > @@ -274,15 +183,13 @@ $t->write_file('test.js', < }); > } > > - export default { affinity, fetch, fetch_exception, js_set, multiple_fetches, > - file, overrun, test, tick, tick_exception, timer, > - timer_exception, timeout_exception, vars, engine }; > + export default { affinity, js_set, overrun, test, tick, tick_exception, > + timer, timer_exception, timeout_exception, vars }; > EOF > > $t->run_daemon(\&stream_daemon, port(8090)); > $t->try_run('no js_periodic'); > -plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m; > -$t->plan(9); > +$t->plan(6); > $t->waitforsocket('127.0.0.1:' . port(8090)); > > ############################################################################### > @@ -293,10 +200,6 @@ is(stream('127.0.0.1:' . port(8081))->io('affinity'), 'affinity', > 'affinity test'); > is(stream('127.0.0.1:' . port(8081))->io('tick'), 'tick', '3x tick test'); > is(stream('127.0.0.1:' . port(8081))->io('timer'), 'timer', 'timer test'); > -is(stream('127.0.0.1:' . port(8081))->io('file'), 'file', 'file test'); > -is(stream('127.0.0.1:' . port(8081))->io('fetch'), 'fetch', 'fetch test'); > -is(stream('127.0.0.1:' . port(8081))->io('multiple_fetches'), > - 'multiple_fetches', 'muliple fetches test'); > is(stream('127.0.0.1:' . port(8081))->io('timeout_exception'), > 'timeout_exception', 'timeout exception test'); > is(stream('127.0.0.1:' . port(8081))->io('vars'), 'vars', 'vars test'); > diff --git a/nginx/t/stream_js_periodic_fetch.t b/nginx/t/stream_js_periodic_fetch.t > new file mode 100644 > index 00000000..e88d69d5 > --- /dev/null > +++ b/nginx/t/stream_js_periodic_fetch.t > @@ -0,0 +1,201 @@ > +#!/usr/bin/perl > + > +# (C) Dmitry Volyntsev > +# (C) Nginx, Inc. > + > +# Tests for stream njs module, js_periodic directive. > + > +############################################################################### > + > +use warnings; > +use strict; > + > +use Test::More; > +use Socket qw/ CRLF /; > + > +BEGIN { use FindBin; chdir($FindBin::Bin); } > + > +use lib 'lib'; > +use Test::Nginx; > +use Test::Nginx::Stream qw/ stream /; > + > +############################################################################### > + > +select STDERR; $| = 1; > +select STDOUT; $| = 1; > + > +my $t = Test::Nginx->new()->has(qw/http rewrite stream/) > + ->write_file_expand('nginx.conf', <<'EOF'); > + > +%%TEST_GLOBALS%% > + > +daemon off; > +worker_processes 4; > + > +events { > +} > + > +worker_shutdown_timeout 100ms; > + > +stream { > + %%TEST_GLOBALS_STREAM%% > + > + js_import test.js; > + > + js_shared_dict_zone zone=strings:32k; > + > + server { > + listen 127.0.0.1:8081; > + > + js_periodic test.fetch interval=40ms; > + js_periodic test.multiple_fetches interval=1s; > + > + js_periodic test.fetch_exception interval=1s; > + > + js_preread test.test; > + > + proxy_pass 127.0.0.1:8090; > + } > +} > + > +http { > + %%TEST_GLOBALS_HTTP%% > + > + js_import test.js; > + > + server { > + listen 127.0.0.1:8080; > + server_name localhost; > + > + location /engine { > + js_content test.engine; > + } > + > + location /fetch_ok { > + return 200 'ok'; > + } > + > + location /fetch_foo { > + return 200 'foo'; > + } > + } > +} > + > +EOF > + > +my $p1 = port(8080); > + > +$t->write_file('test.js', < + function engine(r) { > + r.return(200, njs.engine); > + } > + > + async function fetch() { > + let reply = await ngx.fetch('http://127.0.0.1:$p1/fetch_ok'); > + let body = await reply.text(); > + > + let v = ngx.shared.strings.get('fetch') || ''; > + ngx.shared.strings.set('fetch', v + body); > + } > + > + async function fetch_exception() { > + let reply = await ngx.fetch('garbage'); > + } > + > + async function multiple_fetches() { > + let reply = await ngx.fetch('http://127.0.0.1:$p1/fetch_ok'); > + let reply2 = await ngx.fetch('http://127.0.0.1:$p1/fetch_foo'); > + let body = await reply.text(); > + let body2 = await reply2.text(); > + > + ngx.shared.strings.set('multiple_fetches', body + '\@' + body2); > + } > + > + function test(s) { > + s.on('upload', function (data) { > + if (data.length > 0) { > + switch (data) { > + case 'fetch': > + if (ngx.shared.strings.get('fetch').startsWith('okok')) { > + s.done(); > + return; > + } > + > + break; > + > + case 'multiple_fetches': > + if (ngx.shared.strings.get('multiple_fetches') > + .startsWith('ok\@foo')) > + { > + s.done(); > + return; > + } > + > + break; > + > + default: > + throw new Error(`Unknown test "\${data}"`); > + } > + > + throw new Error(`Test "\${data}" failed`); > + } > + }); > + } > + > + export default { engine, fetch, fetch_exception, test, multiple_fetches }; > +EOF > + > +$t->run_daemon(\&stream_daemon, port(8090)); > +$t->try_run('no js_periodic with fetch'); > +plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m; > +$t->plan(3); > +$t->waitforsocket('127.0.0.1:' . port(8090)); > + > +############################################################################### > + > +select undef, undef, undef, 0.2; > + > +is(stream('127.0.0.1:' . port(8081))->io('fetch'), 'fetch', 'fetch test'); > +is(stream('127.0.0.1:' . port(8081))->io('multiple_fetches'), > + 'multiple_fetches', 'muliple fetches test'); > + > +$t->stop(); > + > +unlike($t->read_file('error.log'), qr/\[error\].*should not be seen/, > + 'check for not discadred events'); > + > +############################################################################### > + > +sub stream_daemon { > + my $server = IO::Socket::INET->new( > + Proto => 'tcp', > + LocalAddr => '127.0.0.1:' . port(8090), > + Listen => 5, > + Reuse => 1 > + ) > + or die "Can't create listening socket: $!\n"; > + > + local $SIG{PIPE} = 'IGNORE'; > + > + while (my $client = $server->accept()) { > + $client->autoflush(1); > + > + log2c("(new connection $client)"); > + > + $client->sysread(my $buffer, 65536) or next; > + > + log2i("$client $buffer"); > + > + log2o("$client $buffer"); > + > + $client->syswrite($buffer); > + > + close $client; > + } > +} > + > +sub log2i { Test::Nginx::log_core('|| <<', @_); } > +sub log2o { Test::Nginx::log_core('|| >>', @_); } > +sub log2c { Test::Nginx::log_core('||', @_); } > + > +############################################################################### > diff --git a/nginx/t/stream_js_periodic_file.t b/nginx/t/stream_js_periodic_file.t > new file mode 100644 > index 00000000..b41ce04c > --- /dev/null > +++ b/nginx/t/stream_js_periodic_file.t > @@ -0,0 +1,144 @@ > +#!/usr/bin/perl > + > +# (C) Dmitry Volyntsev > +# (C) Nginx, Inc. > + > +# Tests for stream njs module, js_periodic directive. > + > +############################################################################### > + > +use warnings; > +use strict; > + > +use Test::More; > +use Socket qw/ CRLF /; > + > +BEGIN { use FindBin; chdir($FindBin::Bin); } > + > +use lib 'lib'; > +use Test::Nginx; > +use Test::Nginx::Stream qw/ stream /; > + > +############################################################################### > + > +select STDERR; $| = 1; > +select STDOUT; $| = 1; > + > +my $t = Test::Nginx->new()->has(qw/http rewrite stream/) > + ->write_file_expand('nginx.conf', <<'EOF'); > + > +%%TEST_GLOBALS%% > + > +daemon off; > +worker_processes 4; > + > +events { > +} > + > +worker_shutdown_timeout 100ms; > + > +stream { > + %%TEST_GLOBALS_STREAM%% > + > + js_import test.js; > + > + server { > + listen 127.0.0.1:8081; > + js_periodic test.file interval=1s; > + > + js_preread test.test; > + > + proxy_pass 127.0.0.1:8090; > + } > +} > + > +EOF > + > +$t->write_file('test.js', < + import fs from 'fs'; > + > + async function file() { > + let fh = await fs.promises.open(ngx.conf_prefix + 'file', 'a+'); > + > + await fh.write('abc'); > + await fh.close(); > + } > + > + function test(s) { > + s.on('upload', function (data) { > + if (data.length > 0) { > + switch (data) { > + case 'file': > + let file_data = fs.readFileSync(ngx.conf_prefix + 'file') > + .toString(); > + > + if (file_data == 'abc') { > + s.done(); > + return; > + } > + > + break; > + > + default: > + throw new Error(`Unknown test "\${data}"`); > + } > + > + throw new Error(`Test "\${data}" failed`); > + } > + }); > + } > + > + export default { file, test }; > +EOF > + > +$t->run_daemon(\&stream_daemon, port(8090)); > +$t->try_run('no js_periodic with fs support'); > +$t->plan(2); > +$t->waitforsocket('127.0.0.1:' . port(8090)); > + > +############################################################################### > + > +select undef, undef, undef, 0.2; > + > +is(stream('127.0.0.1:' . port(8081))->io('file'), 'file', 'file test'); > + > +$t->stop(); > + > +unlike($t->read_file('error.log'), qr/\[error\].*should not be seen/, > + 'check for not discadred events'); > + > +############################################################################### > + > +sub stream_daemon { > + my $server = IO::Socket::INET->new( > + Proto => 'tcp', > + LocalAddr => '127.0.0.1:' . port(8090), > + Listen => 5, > + Reuse => 1 > + ) > + or die "Can't create listening socket: $!\n"; > + > + local $SIG{PIPE} = 'IGNORE'; > + > + while (my $client = $server->accept()) { > + $client->autoflush(1); > + > + log2c("(new connection $client)"); > + > + $client->sysread(my $buffer, 65536) or next; > + > + log2i("$client $buffer"); > + > + log2o("$client $buffer"); > + > + $client->syswrite($buffer); > + > + close $client; > + } > +} > + > +sub log2i { Test::Nginx::log_core('|| <<', @_); } > +sub log2o { Test::Nginx::log_core('|| >>', @_); } > +sub log2c { Test::Nginx::log_core('||', @_); } > + > +############################################################################### > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel From noreply at nginx.com Tue Feb 25 00:50:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Tue, 25 Feb 2025 00:50:02 +0000 (UTC) Subject: [njs] Fetch: accepting response headers with underscore characters. Message-ID: <20250225005002.1C4CB478D9@pubserv1.nginx> details: https://github.com/nginx/njs/commit/18977e022b2a9f9f03c2dc5ae4bbc930a2f30855 branches: master commit: 18977e022b2a9f9f03c2dc5ae4bbc930a2f30855 user: Dmitry Volyntsev date: Fri, 21 Feb 2025 23:03:46 -0800 description: Fetch: accepting response headers with underscore characters. This fixes #856 on Github. --- nginx/ngx_js_fetch.c | 2 +- nginx/t/js_fetch.t | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/nginx/ngx_js_fetch.c b/nginx/ngx_js_fetch.c index 1c5a961d..cd6e54f6 100644 --- a/nginx/ngx_js_fetch.c +++ b/nginx/ngx_js_fetch.c @@ -2868,7 +2868,7 @@ ngx_js_http_parse_header_line(ngx_js_http_parse_t *hp, ngx_buf_t *b) break; } - if (ch == '-') { + if (ch == '-' || ch == '_') { break; } diff --git a/nginx/t/js_fetch.t b/nginx/t/js_fetch.t index 0e107c89..ae9d1f61 100644 --- a/nginx/t/js_fetch.t +++ b/nginx/t/js_fetch.t @@ -413,7 +413,7 @@ $t->try_run('no njs.fetch'); plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m; -$t->plan(36); +$t->plan(37); $t->run_daemon(\&http_daemon, port(8082)); $t->waitforsocket('127.0.0.1:' . port(8082)); @@ -479,6 +479,8 @@ like(http_get('/chain'), qr/200 OK.*SUCCESS$/s, 'fetch chain'); like(http_get('/header_iter?loc=duplicate_header_large'), qr/\["A:a","B:a","C:a","D:a","E:a","F:a","G:a","H:a","Moo:a, ?b"]$/s, 'fetch header duplicate large'); +like(http_get('/header_iter?loc=underscore_header'), + qr/\["F_O_O:b","Foo:a"]$/s, 'fetch header underscore'); TODO: { local $TODO = 'not yet' unless has_version('0.7.7'); @@ -621,6 +623,14 @@ sub http_daemon { "Connection: close" . CRLF . CRLF; + } elsif ($uri eq '/underscore_header') { + print $client + "HTTP/1.1 200 OK" . CRLF . + "Foo: a" . CRLF . + "F_O_O: b" . CRLF . + "Connection: close" . CRLF . + CRLF; + } elsif ($uri eq '/headers') { print $client "HTTP/1.1 200 OK" . CRLF . From noreply at nginx.com Tue Feb 25 00:50:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Tue, 25 Feb 2025 00:50:02 +0000 (UTC) Subject: [njs] Tests: making fetch test portable by removing njs.dump(). Message-ID: <20250225005002.16C10477EE@pubserv1.nginx> details: https://github.com/nginx/njs/commit/3045f31298c2b18f1c16c9414c91429bbfb9281d branches: master commit: 3045f31298c2b18f1c16c9414c91429bbfb9281d user: Dmitry Volyntsev date: Fri, 21 Feb 2025 22:54:00 -0800 description: Tests: making fetch test portable by removing njs.dump(). --- nginx/t/js_fetch.t | 16 ++++++++-------- nginx/t/js_fetch_objects.t | 4 ++-- nginx/t/js_fetch_timeout.t | 8 ++++---- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/nginx/t/js_fetch.t b/nginx/t/js_fetch.t index 320e06f5..0e107c89 100644 --- a/nginx/t/js_fetch.t +++ b/nginx/t/js_fetch.t @@ -303,7 +303,7 @@ $t->write_file('test.js', < { var h = reply.headers[method](r.args.h); - r.return(200, njs.dump(h)); + r.return(200, JSON.stringify(h)); }) .catch(e => r.return(501, e.message)) } @@ -342,7 +342,7 @@ $t->write_file('test.js', <write_file('test.js', < { var h = new Headers({a: 'X', A: 'Z'}); - return njs.dump(h.getAll('a')); - }, "['X','Z']"], + return JSON.stringify(h.getAll('a')); + }, '["X","Z"]'], ['inherit', () => { var h = new Headers({a: 'X', b: 'Y'}); var h2 = new Headers(h); diff --git a/nginx/t/js_fetch_timeout.t b/nginx/t/js_fetch_timeout.t index 1ac1c7aa..5b207b90 100644 --- a/nginx/t/js_fetch_timeout.t +++ b/nginx/t/js_fetch_timeout.t @@ -95,9 +95,9 @@ $t->write_file('test.js', < ngx.fetch(v))); let bs = rs.map(v => ({s: v.status, v: v.value ? v.value.headers.X - : v.reason})); + : v.reason.message})); - r.return(200, njs.dump(bs)); + r.return(200, JSON.stringify(bs)); } function normal_reply(r) { @@ -123,10 +123,10 @@ $t->plan(2); ############################################################################### like(http_get('/normal_timeout'), - qr/\[\{s:'fulfilled',v:'N'},\{s:'fulfilled',v:'D'}]$/s, + qr/\[\{"s":"fulfilled","v":"N"},\{"s":"fulfilled","v":"D"}]$/s, 'normal timeout'); like(http_get('/short_timeout'), - qr/\[\{s:'fulfilled',v:'N'},\{s:'rejected',v:Error: read timed out}]$/s, + qr/\[\{"s":"fulfilled","v":"N"},\{"s":"rejected","v":"read timed out"}]$/s, 'short timeout'); ############################################################################### From noreply at nginx.com Tue Feb 25 00:50:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Tue, 25 Feb 2025 00:50:02 +0000 (UTC) Subject: [njs] Modules: improved reporting of unhandled promise rejections. Message-ID: <20250225005002.0F0A8476E3@pubserv1.nginx> details: https://github.com/nginx/njs/commit/24633b3766df99de3c138ca6dc37b0cb403e619d branches: master commit: 24633b3766df99de3c138ca6dc37b0cb403e619d user: Dmitry Volyntsev date: Fri, 21 Feb 2025 20:40:14 -0800 description: Modules: improved reporting of unhandled promise rejections. Previously, some promise rejections were not reported. For example: async function timeout(ms) { return new Promise((resolve, reject) => { setTimeout(() => { reject(new Error('timeout')); }, ms); }); } async function handler(r) { let v = await timeout(1000); r.return(200); } --- nginx/ngx_js.c | 72 +++++++++++++++++------------------------------------- nginx/t/js_async.t | 24 ++++++++++++++++-- 2 files changed, 45 insertions(+), 51 deletions(-) diff --git a/nginx/ngx_js.c b/nginx/ngx_js.c index 7f7b7362..383d2304 100644 --- a/nginx/ngx_js.c +++ b/nginx/ngx_js.c @@ -731,13 +731,6 @@ ngx_engine_njs_call(ngx_js_ctx_t *ctx, ngx_str_t *fname, } } - if (ngx_js_unhandled_rejection(ctx)) { - ngx_js_exception(vm, &exception); - - ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js exception: %V", &exception); - return NGX_ERROR; - } - return njs_rbtree_is_empty(&ctx->waiting_events) ? NGX_OK : NGX_AGAIN; } @@ -775,6 +768,7 @@ static void ngx_engine_njs_destroy(ngx_engine_t *e, ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *conf) { + ngx_str_t exception; ngx_js_event_t *event; njs_rbtree_node_t *node; @@ -791,6 +785,12 @@ ngx_engine_njs_destroy(ngx_engine_t *e, ngx_js_ctx_t *ctx, node = njs_rbtree_node_successor(&ctx->waiting_events, node); } + + if (ngx_js_unhandled_rejection(ctx)) { + ngx_js_exception(e->u.njs.vm, &exception); + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, + "js unhandled rejection: %V", &exception); + } } njs_vm_destroy(e->u.njs.vm); @@ -1071,13 +1071,6 @@ ngx_engine_qjs_call(ngx_js_ctx_t *ctx, ngx_str_t *fname, } } - if (ngx_qjs_unhandled_rejection(ctx)) { - ngx_qjs_exception(ctx->engine, &exception); - - ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js exception: %V", &exception); - return NGX_ERROR; - } - return njs_rbtree_is_empty(&ctx->waiting_events) ? NGX_OK : NGX_AGAIN; } @@ -1129,16 +1122,16 @@ void ngx_engine_qjs_destroy(ngx_engine_t *e, ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *conf) { - uint32_t i, length; - JSRuntime *rt; - JSContext *cx; - JSClassID class_id; - ngx_qjs_event_t *event; - ngx_js_opaque_t *opaque; - njs_rbtree_node_t *node; - ngx_pool_cleanup_t *cln; - ngx_js_code_entry_t *pc; - ngx_js_rejected_promise_t *rejected_promise; + uint32_t i, length; + ngx_str_t exception; + JSRuntime *rt; + JSContext *cx; + JSClassID class_id; + ngx_qjs_event_t *event; + ngx_js_opaque_t *opaque; + njs_rbtree_node_t *node; + ngx_pool_cleanup_t *cln; + ngx_js_code_entry_t *pc; cx = e->u.qjs.ctx; @@ -1156,13 +1149,10 @@ ngx_engine_qjs_destroy(ngx_engine_t *e, ngx_js_ctx_t *ctx, node = njs_rbtree_node_successor(&ctx->waiting_events, node); } - if (ctx->rejected_promises != NULL) { - rejected_promise = ctx->rejected_promises->start; - - for (i = 0; i < ctx->rejected_promises->items; i++) { - JS_FreeValue(cx, ngx_qjs_arg(rejected_promise[i].promise)); - JS_FreeValue(cx, ngx_qjs_arg(rejected_promise[i].message)); - } + if (ngx_qjs_unhandled_rejection(ctx)) { + ngx_qjs_exception(ctx->engine, &exception); + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, + "js unhandled rejection: %V", &exception); } class_id = JS_GetClassID(ngx_qjs_arg(ctx->args[0])); @@ -2037,10 +2027,8 @@ ngx_qjs_module_loader(JSContext *cx, const char *module_name, void *opaque) static int ngx_qjs_unhandled_rejection(ngx_js_ctx_t *ctx) { - size_t len; uint32_t i; JSContext *cx; - const char *str; ngx_js_rejected_promise_t *rejected_promise; if (ctx->rejected_promises == NULL @@ -2052,13 +2040,7 @@ ngx_qjs_unhandled_rejection(ngx_js_ctx_t *ctx) cx = ctx->engine->u.qjs.ctx; rejected_promise = ctx->rejected_promises->start; - str = JS_ToCStringLen(cx, &len, ngx_qjs_arg(rejected_promise->message)); - if (njs_slow_path(str == NULL)) { - return -1; - } - - JS_ThrowTypeError(cx, "unhandled promise rejection: %.*s", (int) len, str); - JS_FreeCString(cx, str); + JS_Throw(cx, JS_DupValue(cx, ngx_qjs_arg(rejected_promise->message))); for (i = 0; i < ctx->rejected_promises->items; i++) { JS_FreeValue(cx, ngx_qjs_arg(rejected_promise[i].promise)); @@ -3890,8 +3872,6 @@ static njs_int_t ngx_js_unhandled_rejection(ngx_js_ctx_t *ctx) { njs_vm_t *vm; - njs_int_t ret; - njs_str_t message; ngx_js_rejected_promise_t *rejected_promise; if (ctx->rejected_promises == NULL @@ -3903,13 +3883,7 @@ ngx_js_unhandled_rejection(ngx_js_ctx_t *ctx) vm = ctx->engine->u.njs.vm; rejected_promise = ctx->rejected_promises->start; - ret = njs_vm_value_to_string(vm, &message, - njs_value_arg(&rejected_promise->message)); - if (njs_slow_path(ret != NJS_OK)) { - return -1; - } - - njs_vm_error(vm, "unhandled promise rejection: %V", &message); + njs_vm_throw(vm, njs_value_arg(&rejected_promise->message)); njs_arr_destroy(ctx->rejected_promises); ctx->rejected_promises = NULL; diff --git a/nginx/t/js_async.t b/nginx/t/js_async.t index 73e71851..32d1e0a4 100644 --- a/nginx/t/js_async.t +++ b/nginx/t/js_async.t @@ -84,6 +84,10 @@ http { location /set_rv_var { return 200 $test_set_rv_var; } + + location /await_reject { + js_content test.await_reject; + } } } @@ -194,13 +198,26 @@ $t->write_file('test.js', < { + setTimeout(() => { + reject(new Error('timeout')); + }, ms); + }); + } + + async function await_reject(r) { + let v = await timeout(1); + r.return(200); + } + export default {njs:test_njs, set_timeout, set_timeout_data, set_timeout_many, context_var, shared_ctx, limit_rate, - async_content, set_rv_var}; + async_content, set_rv_var, await_reject}; EOF -$t->try_run('no njs available')->plan(9); +$t->try_run('no njs available')->plan(10); ############################################################################### @@ -214,6 +231,7 @@ like(http_get('/async_content'), qr/retval: AB/, 'async content'); like(http_get('/set_rv_var'), qr/retval: 30/, 'set return value variable'); http_get('/async_var'); +http_get('/await_reject'); $t->stop(); @@ -221,5 +239,7 @@ ok(index($t->read_file('error.log'), 'pending events') > 0, 'pending js events'); ok(index($t->read_file('error.log'), 'async operation inside') > 0, 'async op in var handler'); +ok(index($t->read_file('error.log'), 'js unhandled rejection') > 0, + 'unhandled rejection'); ############################################################################### From noreply at nginx.com Tue Feb 25 00:50:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Tue, 25 Feb 2025 00:50:02 +0000 (UTC) Subject: [njs] Fixed typo introduced in 75ca26f. Message-ID: <20250225005002.11CF4476E6@pubserv1.nginx> details: https://github.com/nginx/njs/commit/62f863a80ca226b62bf650643806b653a3d6c34f branches: master commit: 62f863a80ca226b62bf650643806b653a3d6c34f user: Dmitry Volyntsev date: Fri, 21 Feb 2025 22:10:07 -0800 description: Fixed typo introduced in 75ca26f. --- src/qjs.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/qjs.h b/src/qjs.h index 2d3c289c..7c560d5f 100644 --- a/src/qjs.h +++ b/src/qjs.h @@ -33,9 +33,6 @@ #include -#define QJS_CORE_CLASS_ID_OFFSET 64 -#define QJS_CORE_CLASS_ID_BUFFER (QJS_CORE_CLASS_ID_OFFSET) -#define QJS_CORE_CLASS_ID_UINT8_ARRAY_CTOR (QJS_CORE_CLASS_ID_OFFSET + 1) #define QJS_CORE_CLASS_ID_OFFSET 64 #define QJS_CORE_CLASS_ID_BUFFER (QJS_CORE_CLASS_ID_OFFSET) #define QJS_CORE_CLASS_ID_UINT8_ARRAY_CTOR (QJS_CORE_CLASS_ID_OFFSET + 1) From noreply at nginx.com Tue Feb 25 19:15:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Tue, 25 Feb 2025 19:15:02 +0000 (UTC) Subject: [njs] QuickJS: removed unused variable casts introduced in 75ca26f. Message-ID: <20250225191502.5451248F9F@pubserv1.nginx> details: https://github.com/nginx/njs/commit/d145e033cb6b402d0198562831cccfa9050e53bb branches: master commit: d145e033cb6b402d0198562831cccfa9050e53bb user: Dmitry Volyntsev date: Wed, 5 Feb 2025 16:26:40 -0800 description: QuickJS: removed unused variable casts introduced in 75ca26f. These casts were added to suppress compiler warnings during development but are no longer needed. --- external/qjs_webcrypto_module.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/external/qjs_webcrypto_module.c b/external/qjs_webcrypto_module.c index 4816031c..560b2dd7 100644 --- a/external/qjs_webcrypto_module.c +++ b/external/qjs_webcrypto_module.c @@ -4827,9 +4827,6 @@ qjs_webcrypto_init(JSContext *cx, const char *name) JSValue crypto, proto, global_obj; JSModuleDef *m; - (void) qjs_webcrypto_alg_name; - (void) qjs_algorithm_hash_name; - if (!JS_IsRegisteredClass(JS_GetRuntime(cx), QJS_CORE_CLASS_ID_WEBCRYPTO_KEY)) { From noreply at nginx.com Tue Feb 25 19:15:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Tue, 25 Feb 2025 19:15:02 +0000 (UTC) Subject: [njs] QuickJS: using helper to declare Symbol.toStringTag properties. Message-ID: <20250225191502.5F4D348FA1@pubserv1.nginx> details: https://github.com/nginx/njs/commit/18d31701cef4af1925f6702e591ca77761541b7e branches: master commit: 18d31701cef4af1925f6702e591ca77761541b7e user: Dmitry Volyntsev date: Wed, 5 Feb 2025 16:47:03 -0800 description: QuickJS: using helper to declare Symbol.toStringTag properties. --- external/njs_shell.c | 10 ++------- external/qjs_fs_module.c | 34 ++++-------------------------- external/qjs_webcrypto_module.c | 13 ++---------- nginx/ngx_http_js_module.c | 27 +++--------------------- nginx/ngx_js_shared_dict.c | 14 +++---------- nginx/ngx_stream_js_module.c | 27 ++++-------------------- src/qjs.c | 46 ++++++----------------------------------- 7 files changed, 24 insertions(+), 147 deletions(-) diff --git a/external/njs_shell.c b/external/njs_shell.c index 015c930f..1228b374 100644 --- a/external/njs_shell.c +++ b/external/njs_shell.c @@ -1901,13 +1901,6 @@ njs_qjs_clear_timeout(JSContext *ctx, JSValueConst this_val, int argc, } -static JSValue -njs_qjs_console_to_string_tag(JSContext *ctx, JSValueConst this_val) -{ - return JS_NewString(ctx, "Console"); -} - - static JSValue njs_qjs_process_getter(JSContext *ctx, JSValueConst this_val) { @@ -2487,7 +2480,8 @@ static const JSCFunctionListEntry njs_qjs_global_proto[] = { static const JSCFunctionListEntry njs_qjs_console_proto[] = { - JS_CGETSET_DEF("[Symbol.toStringTag]", njs_qjs_console_to_string_tag, NULL), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Console", + JS_PROP_CONFIGURABLE), JS_CFUNC_MAGIC_DEF("error", 0, njs_qjs_console_log, NJS_LOG_ERROR), JS_CFUNC_MAGIC_DEF("info", 0, njs_qjs_console_log, NJS_LOG_INFO), JS_CFUNC_MAGIC_DEF("log", 0, njs_qjs_console_log, NJS_LOG_INFO), diff --git a/external/qjs_fs_module.c b/external/qjs_fs_module.c index 2adeef20..9d1f7687 100644 --- a/external/qjs_fs_module.c +++ b/external/qjs_fs_module.c @@ -167,7 +167,6 @@ static JSValue qjs_fs_write_file(JSContext *cx, JSValueConst this_val, int argc, static JSValue qjs_fs_unlink(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv, int calltype); -static JSValue qjs_fs_stats_to_string_tag(JSContext *cx, JSValueConst this_val); static JSValue qjs_fs_stats_test(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv, int testtype); static int qjs_fs_stats_get_own_property(JSContext *cx, @@ -176,15 +175,11 @@ static int qjs_fs_stats_get_own_property_names(JSContext *cx, JSPropertyEnum **ptab, uint32_t *plen, JSValueConst obj); static void qjs_fs_stats_finalizer(JSRuntime *rt, JSValue val); -static JSValue qjs_fs_dirent_to_string_tag(JSContext *cx, - JSValueConst this_val); static JSValue qjs_fs_dirent_ctor(JSContext *cx, JSValueConst new_target, int argc, JSValueConst *argv); static JSValue qjs_fs_dirent_test(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv, int testtype); -static JSValue qjs_fs_filehandle_to_string_tag(JSContext *cx, - JSValueConst this_val); static JSValue qjs_fs_filehandle_fd(JSContext *cx, JSValueConst this_val); static JSValue qjs_fs_filehandle_value_of(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv); @@ -222,7 +217,7 @@ static qjs_fs_entry_t qjs_flags_table[] = { static const JSCFunctionListEntry qjs_fs_stats_proto[] = { - JS_CGETSET_DEF("[Symbol.toStringTag]", qjs_fs_stats_to_string_tag, NULL), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Stats", JS_PROP_CONFIGURABLE), JS_CFUNC_MAGIC_DEF("isBlockDevice", 0, qjs_fs_stats_test, DT_BLK), JS_CFUNC_MAGIC_DEF("isCharacterDevice", 0, qjs_fs_stats_test, DT_CHR), JS_CFUNC_MAGIC_DEF("isDirectory", 0, qjs_fs_stats_test, DT_DIR), @@ -234,7 +229,7 @@ static const JSCFunctionListEntry qjs_fs_stats_proto[] = { static const JSCFunctionListEntry qjs_fs_dirent_proto[] = { - JS_CGETSET_DEF("[Symbol.toStringTag]", qjs_fs_dirent_to_string_tag, NULL), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Dirent", JS_PROP_CONFIGURABLE), JS_CFUNC_MAGIC_DEF("isBlockDevice", 0, qjs_fs_dirent_test, DT_BLK), JS_CFUNC_MAGIC_DEF("isCharacterDevice", 0, qjs_fs_dirent_test, DT_CHR), JS_CFUNC_MAGIC_DEF("isDirectory", 0, qjs_fs_dirent_test, DT_DIR), @@ -247,8 +242,8 @@ static const JSCFunctionListEntry qjs_fs_dirent_proto[] = { static const JSCFunctionListEntry qjs_fs_filehandle_proto[] = { - JS_CGETSET_DEF("[Symbol.toStringTag]", qjs_fs_filehandle_to_string_tag, - NULL), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "FileHandle", + JS_PROP_CONFIGURABLE), JS_CFUNC_MAGIC_DEF("close", 0, qjs_fs_close, QJS_FS_PROMISE), JS_CGETSET_DEF("fd", qjs_fs_filehandle_fd, NULL), JS_CFUNC_MAGIC_DEF("stat", 4, qjs_fs_stat, @@ -2341,13 +2336,6 @@ qjs_fs_unlink(JSContext *cx, JSValueConst this_val, int argc, } -static JSValue -qjs_fs_stats_to_string_tag(JSContext *cx, JSValueConst this_val) -{ - return JS_NewString(cx, "Stats"); -} - - static JSValue qjs_fs_stats_test(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv, int testtype) @@ -2629,13 +2617,6 @@ qjs_fs_stats_finalizer(JSRuntime *rt, JSValue val) } -static JSValue -qjs_fs_dirent_to_string_tag(JSContext *cx, JSValueConst this_val) -{ - return JS_NewString(cx, "Dirent"); -} - - static JSValue qjs_fs_dirent_test(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv, int testtype) @@ -2666,13 +2647,6 @@ qjs_fs_dirent_test(JSContext *cx, JSValueConst this_val, int argc, } -static JSValue -qjs_fs_filehandle_to_string_tag(JSContext *cx, JSValueConst this_val) -{ - return JS_NewString(cx, "FileHandle"); -} - - static JSValue qjs_fs_filehandle_fd(JSContext *cx, JSValueConst thisval) { diff --git a/external/qjs_webcrypto_module.c b/external/qjs_webcrypto_module.c index 560b2dd7..730feb6e 100644 --- a/external/qjs_webcrypto_module.c +++ b/external/qjs_webcrypto_module.c @@ -128,8 +128,6 @@ static JSValue qjs_webcrypto_import_key(JSContext *cx, JSValueConst this_val, static JSValue qjs_webcrypto_sign(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv, int verify); -static JSValue qjs_webcrypto_key_to_string_tag(JSContext *cx, - JSValueConst this_val); static JSValue qjs_webcrypto_key_algorithm(JSContext *cx, JSValueConst this_val); static JSValue qjs_webcrypto_key_extractable(JSContext *cx, @@ -444,8 +442,8 @@ static const JSCFunctionListEntry qjs_webcrypto_subtle[] = { static const JSCFunctionListEntry qjs_webcrypto_key_proto[] = { - JS_CGETSET_DEF("[Symbol.toStringTag]", qjs_webcrypto_key_to_string_tag, - NULL), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "CryptoKey", + JS_PROP_CONFIGURABLE), JS_CGETSET_DEF("algorithm", qjs_webcrypto_key_algorithm, NULL), JS_CGETSET_DEF("extractable", qjs_webcrypto_key_extractable, NULL), JS_CGETSET_DEF("type", qjs_webcrypto_key_type, NULL), @@ -4000,13 +3998,6 @@ fail: } -static JSValue -qjs_webcrypto_key_to_string_tag(JSContext *cx, JSValueConst this_val) -{ - return JS_NewString(cx, "CryptoKey"); -} - - static JSValue qjs_webcrypto_key_algorithm(JSContext *cx, JSValueConst this_val) { diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c index cdc668c5..71fd92ba 100644 --- a/nginx/ngx_http_js_module.c +++ b/nginx/ngx_http_js_module.c @@ -276,8 +276,6 @@ static njs_int_t ngx_http_js_server(njs_vm_t *vm, ngx_http_request_t *r, njs_value_t *retval); #if (NJS_HAVE_QUICKJS) -static JSValue ngx_http_qjs_ext_to_string_tag(JSContext *cx, - JSValueConst this_val); static JSValue ngx_http_qjs_ext_args(JSContext *cx, JSValueConst this_val); static JSValue ngx_http_qjs_ext_done(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv); @@ -294,8 +292,6 @@ static JSValue ngx_http_qjs_ext_internal_redirect(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv); static JSValue ngx_http_qjs_ext_log(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv, int level); -static JSValue ngx_http_qjs_ext_periodic_to_string_tag(JSContext *cx, - JSValueConst this_val); static JSValue ngx_http_qjs_ext_periodic_variables(JSContext *cx, JSValueConst this_val, int type); static JSValue ngx_http_qjs_ext_parent(JSContext *cx, JSValueConst this_val); @@ -1035,8 +1031,7 @@ static ngx_http_js_entry_t ngx_http_methods[] = { #if (NJS_HAVE_QUICKJS) static const JSCFunctionListEntry ngx_http_qjs_ext_request[] = { - JS_CGETSET_DEF("[Symbol.toStringTag]", ngx_http_qjs_ext_to_string_tag, - NULL), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Request", JS_PROP_CONFIGURABLE), JS_CGETSET_DEF("args", ngx_http_qjs_ext_args, NULL), JS_CFUNC_DEF("done", 0, ngx_http_qjs_ext_done), JS_CFUNC_MAGIC_DEF("error", 1, ngx_http_qjs_ext_log, NGX_LOG_ERR), @@ -1081,8 +1076,8 @@ static const JSCFunctionListEntry ngx_http_qjs_ext_request[] = { static const JSCFunctionListEntry ngx_http_qjs_ext_periodic[] = { - JS_CGETSET_DEF("[Symbol.toStringTag]", - ngx_http_qjs_ext_periodic_to_string_tag, NULL), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "PeriodicSession", + JS_PROP_CONFIGURABLE), JS_CGETSET_MAGIC_DEF("rawVariables", ngx_http_qjs_ext_periodic_variables, NULL, NGX_JS_BUFFER), JS_CGETSET_MAGIC_DEF("variables", ngx_http_qjs_ext_periodic_variables, @@ -4825,14 +4820,6 @@ ngx_http_qjs_query_string_decode(njs_chb_t *chain, const u_char *start, } -static JSValue -ngx_http_qjs_ext_to_string_tag(JSContext *cx, - JSValueConst this_val) -{ - return JS_NewString(cx, "Request"); -} - - static JSValue ngx_http_qjs_ext_args(JSContext *cx, JSValueConst this_val) { @@ -5181,14 +5168,6 @@ ngx_http_qjs_ext_log(JSContext *cx, JSValueConst this_val, int argc, } -static JSValue -ngx_http_qjs_ext_periodic_to_string_tag(JSContext *cx, - JSValueConst this_val) -{ - return JS_NewString(cx, "PeriodicSession"); -} - - static JSValue ngx_http_qjs_ext_periodic_variables(JSContext *cx, JSValueConst this_val, int type) diff --git a/nginx/ngx_js_shared_dict.c b/nginx/ngx_js_shared_dict.c index 06f940e0..6ae12d84 100644 --- a/nginx/ngx_js_shared_dict.c +++ b/nginx/ngx_js_shared_dict.c @@ -146,9 +146,7 @@ static JSValue ngx_qjs_ext_shared_dict_set(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv, int flags); static JSValue ngx_qjs_ext_shared_dict_size(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv); -static JSValue ngx_qjs_ext_shared_dict_tag(JSContext *cx, - JSValueConst this_val); -static JSValue ngx_qjs_ext_shared_dict_type(JSContext *cx, + static JSValue ngx_qjs_ext_shared_dict_type(JSContext *cx, JSValueConst this_val); static JSValue ngx_qjs_dict_copy_value_locked(JSContext *cx, @@ -425,7 +423,8 @@ static const JSCFunctionListEntry ngx_qjs_ext_ngx[] = { }; static const JSCFunctionListEntry ngx_qjs_ext_shared_dict[] = { - JS_CGETSET_DEF("[Symbol.toStringTag]", ngx_qjs_ext_shared_dict_tag, NULL), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "SharedDict", + JS_PROP_CONFIGURABLE), JS_CFUNC_MAGIC_DEF("add", 3, ngx_qjs_ext_shared_dict_set, NGX_JS_DICT_FLAG_MUST_NOT_EXIST), JS_CGETSET_DEF("capacity", ngx_qjs_ext_shared_dict_capacity, NULL), @@ -2604,13 +2603,6 @@ ngx_qjs_ext_shared_dict_size(JSContext *cx, JSValueConst this_val, } -static JSValue -ngx_qjs_ext_shared_dict_tag(JSContext *cx, JSValueConst this_val) -{ - return JS_NewString(cx, "SharedDict"); -} - - static JSValue ngx_qjs_ext_shared_dict_type(JSContext *cx, JSValueConst this_val) { diff --git a/nginx/ngx_stream_js_module.c b/nginx/ngx_stream_js_module.c index db00b922..7e93b237 100644 --- a/nginx/ngx_stream_js_module.c +++ b/nginx/ngx_stream_js_module.c @@ -145,8 +145,6 @@ static njs_int_t ngx_stream_js_periodic_variables(njs_vm_t *vm, #if (NJS_HAVE_QUICKJS) -static JSValue ngx_stream_qjs_ext_to_string_tag(JSContext *cx, - JSValueConst this_val); static JSValue ngx_stream_qjs_ext_done(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv, int magic); static JSValue ngx_stream_qjs_ext_log(JSContext *cx, JSValueConst this_val, @@ -155,8 +153,6 @@ static JSValue ngx_stream_qjs_ext_on(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv); static JSValue ngx_stream_qjs_ext_off(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv); -static JSValue ngx_stream_qjs_ext_periodic_to_string_tag(JSContext *cx, - JSValueConst this_val); static JSValue ngx_stream_qjs_ext_periodic_variables(JSContext *cx, JSValueConst this_val, int type); static JSValue ngx_stream_qjs_ext_remote_address(JSContext *cx, @@ -762,8 +758,8 @@ njs_module_t *njs_stream_js_addon_modules[] = { #if (NJS_HAVE_QUICKJS) static const JSCFunctionListEntry ngx_stream_qjs_ext_session[] = { - JS_CGETSET_DEF("[Symbol.toStringTag]", ngx_stream_qjs_ext_to_string_tag, - NULL), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Stream Session", + JS_PROP_CONFIGURABLE), JS_CFUNC_MAGIC_DEF("allow", 1, ngx_stream_qjs_ext_done, NGX_OK), JS_CFUNC_MAGIC_DEF("decline", 1, ngx_stream_qjs_ext_done, -NGX_DECLINED), JS_CFUNC_MAGIC_DEF("deny", 1, ngx_stream_qjs_ext_done, -NGX_DONE), @@ -790,8 +786,8 @@ static const JSCFunctionListEntry ngx_stream_qjs_ext_session[] = { static const JSCFunctionListEntry ngx_stream_qjs_ext_periodic[] = { - JS_CGETSET_DEF("[Symbol.toStringTag]", - ngx_stream_qjs_ext_periodic_to_string_tag, NULL), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "PeriodicSession", + JS_PROP_CONFIGURABLE), JS_CGETSET_MAGIC_DEF("rawVariables", ngx_stream_qjs_ext_periodic_variables, NULL, NGX_JS_BUFFER), JS_CGETSET_MAGIC_DEF("variables", ngx_stream_qjs_ext_periodic_variables, @@ -1998,13 +1994,6 @@ ngx_engine_njs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf, #if (NJS_HAVE_QUICKJS) -static JSValue -ngx_stream_qjs_ext_to_string_tag(JSContext *cx, JSValueConst this_val) -{ - return JS_NewString(cx, "Stream Session"); -} - - static JSValue ngx_stream_qjs_ext_done(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) @@ -2222,14 +2211,6 @@ ngx_stream_qjs_ext_off(JSContext *cx, JSValueConst this_val, int argc, } -static JSValue -ngx_stream_qjs_ext_periodic_to_string_tag(JSContext *cx, - JSValueConst this_val) -{ - return JS_NewString(cx, "PeriodicSession"); -} - - static JSValue ngx_stream_qjs_ext_periodic_variables(JSContext *cx, JSValueConst this_val, int type) diff --git a/src/qjs.c b/src/qjs.c index 56c6a3ba..ed9a6178 100644 --- a/src/qjs.c +++ b/src/qjs.c @@ -43,8 +43,6 @@ extern char **environ; static JSValue qjs_njs_getter(JSContext *ctx, JSValueConst this_val); -static JSValue qjs_njs_to_string_tag(JSContext *ctx, JSValueConst this_val); -static JSValue qjs_process_to_string_tag(JSContext *ctx, JSValueConst this_val); static JSValue qjs_process_env(JSContext *ctx, JSValueConst this_val); static JSValue qjs_process_kill(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv); @@ -52,8 +50,6 @@ static JSValue qjs_process_pid(JSContext *ctx, JSValueConst this_val); static JSValue qjs_process_ppid(JSContext *ctx, JSValueConst this_val); static int qjs_add_intrinsic_text_decoder(JSContext *cx, JSValueConst global); -static JSValue qjs_text_decoder_to_string_tag(JSContext *ctx, - JSValueConst this_val); static JSValue qjs_text_decoder_decode(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv); static JSValue qjs_text_decoder_encoding(JSContext *ctx, JSValueConst this_val); @@ -63,8 +59,6 @@ static JSValue qjs_text_decoder_ignore_bom(JSContext *ctx, static void qjs_text_decoder_finalizer(JSRuntime *rt, JSValue val); static int qjs_add_intrinsic_text_encoder(JSContext *cx, JSValueConst global); -static JSValue qjs_text_encoder_to_string_tag(JSContext *ctx, - JSValueConst this_val); static JSValue qjs_text_encoder_encode(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv); static JSValue qjs_text_encoder_encode_into(JSContext *ctx, @@ -110,8 +104,8 @@ static const JSCFunctionListEntry qjs_global_proto[] = { }; static const JSCFunctionListEntry qjs_text_decoder_proto[] = { - JS_CGETSET_DEF("[Symbol.toStringTag]", qjs_text_decoder_to_string_tag, - NULL), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "TextDecoder", + JS_PROP_CONFIGURABLE), JS_CFUNC_DEF("decode", 1, qjs_text_decoder_decode), JS_CGETSET_DEF("encoding", qjs_text_decoder_encoding, NULL), JS_CGETSET_DEF("fatal", qjs_text_decoder_fatal, NULL), @@ -119,15 +113,15 @@ static const JSCFunctionListEntry qjs_text_decoder_proto[] = { }; static const JSCFunctionListEntry qjs_text_encoder_proto[] = { - JS_CGETSET_DEF("[Symbol.toStringTag]", qjs_text_encoder_to_string_tag, - NULL), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "TextEncoder", + JS_PROP_CONFIGURABLE), JS_CFUNC_DEF("encode", 1, qjs_text_encoder_encode), JS_CFUNC_DEF("encodeInto", 1, qjs_text_encoder_encode_into), JS_CGETSET_DEF("encoding", qjs_text_encoder_encoding, NULL), }; static const JSCFunctionListEntry qjs_njs_proto[] = { - JS_CGETSET_DEF("[Symbol.toStringTag]", qjs_njs_to_string_tag, NULL), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "njs", JS_PROP_CONFIGURABLE), JS_PROP_STRING_DEF("version", NJS_VERSION, JS_PROP_C_W_E), JS_PROP_INT32_DEF("version_number", NJS_VERSION_NUMBER, JS_PROP_C_W_E), @@ -135,7 +129,7 @@ static const JSCFunctionListEntry qjs_njs_proto[] = { }; static const JSCFunctionListEntry qjs_process_proto[] = { - JS_CGETSET_DEF("[Symbol.toStringTag]", qjs_process_to_string_tag, NULL), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "process", JS_PROP_CONFIGURABLE), JS_CGETSET_DEF("env", qjs_process_env, NULL), JS_CFUNC_DEF("kill", 2, qjs_process_kill), JS_CGETSET_DEF("pid", qjs_process_pid, NULL), @@ -246,20 +240,6 @@ qjs_njs_getter(JSContext *ctx, JSValueConst this_val) } -static JSValue -qjs_njs_to_string_tag(JSContext *ctx, JSValueConst this_val) -{ - return JS_NewString(ctx, "njs"); -} - - -static JSValue -qjs_process_to_string_tag(JSContext *ctx, JSValueConst this_val) -{ - return JS_NewString(ctx, "process"); -} - - static JSValue qjs_process_env(JSContext *ctx, JSValueConst this_val) { @@ -583,13 +563,6 @@ qjs_add_intrinsic_text_decoder(JSContext *cx, JSValueConst global) } -static JSValue -qjs_text_decoder_to_string_tag(JSContext *ctx, JSValueConst this_val) -{ - return JS_NewString(ctx, "TextDecoder"); -} - - static JSValue qjs_text_decoder_decode(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv) @@ -762,13 +735,6 @@ qjs_add_intrinsic_text_encoder(JSContext *cx, JSValueConst global) } -static JSValue -qjs_text_encoder_to_string_tag(JSContext *ctx, JSValueConst this_val) -{ - return JS_NewString(ctx, "TextEncoder"); -} - - static JSValue qjs_text_encoder_encoding(JSContext *ctx, JSValueConst this_val) { From noreply at nginx.com Wed Feb 26 13:41:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 26 Feb 2025 13:41:02 +0000 (UTC) Subject: [nginx] SSL: style. Message-ID: <20250226134102.8EA1A475AE@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/b11ae4cfc9483006f67d92850dc520abe659d880 branches: master commit: b11ae4cfc9483006f67d92850dc520abe659d880 user: Sergey Kandaurov date: Tue, 25 Feb 2025 19:40:22 +0400 description: SSL: style. --- src/event/ngx_event_openssl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index 0681ca3a2..2446219a7 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -4034,12 +4034,12 @@ ngx_ssl_get_cached_session(ngx_ssl_conn_t *ssl_conn, const u_char *p; ngx_shm_zone_t *shm_zone; ngx_slab_pool_t *shpool; + ngx_connection_t *c; ngx_rbtree_node_t *node, *sentinel; ngx_ssl_session_t *sess; ngx_ssl_sess_id_t *sess_id; ngx_ssl_session_cache_t *cache; u_char buf[NGX_SSL_MAX_SESSION_SIZE]; - ngx_connection_t *c; hash = ngx_crc32_short((u_char *) (uintptr_t) id, (size_t) len); *copy = 0; From noreply at nginx.com Wed Feb 26 13:41:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 26 Feb 2025 13:41:02 +0000 (UTC) Subject: [nginx] SSL: improved logging of saving sessions from upstream servers. Message-ID: <20250226134102.9DB4748FA0@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/311c39037734df89b56325091e9435bc542308f4 branches: master commit: 311c39037734df89b56325091e9435bc542308f4 user: Sergey Kandaurov date: Fri, 21 Feb 2025 15:41:33 +0400 description: SSL: improved logging of saving sessions from upstream servers. This makes it easier to understand why sessions may not be saved in shared memory due to size. --- src/http/ngx_http_upstream_round_robin.c | 6 +++--- src/stream/ngx_stream_upstream_round_robin.c | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/http/ngx_http_upstream_round_robin.c b/src/http/ngx_http_upstream_round_robin.c index 6b4ff97f2..25741869e 100644 --- a/src/http/ngx_http_upstream_round_robin.c +++ b/src/http/ngx_http_upstream_round_robin.c @@ -952,11 +952,11 @@ ngx_http_upstream_save_round_robin_peer_session(ngx_peer_connection_t *pc, return; } - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, - "save session: %p", ssl_session); - len = i2d_SSL_SESSION(ssl_session, NULL); + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0, + "save session: %p:%d", ssl_session, len); + /* do not cache too big session */ if (len > NGX_SSL_MAX_SESSION_SIZE) { diff --git a/src/stream/ngx_stream_upstream_round_robin.c b/src/stream/ngx_stream_upstream_round_robin.c index 27db0851e..1dae4e538 100644 --- a/src/stream/ngx_stream_upstream_round_robin.c +++ b/src/stream/ngx_stream_upstream_round_robin.c @@ -985,11 +985,11 @@ ngx_stream_upstream_save_round_robin_peer_session(ngx_peer_connection_t *pc, return; } - ngx_log_debug1(NGX_LOG_DEBUG_STREAM, pc->log, 0, - "save session: %p", ssl_session); - len = i2d_SSL_SESSION(ssl_session, NULL); + ngx_log_debug2(NGX_LOG_DEBUG_STREAM, pc->log, 0, + "save session: %p:%d", ssl_session, len); + /* do not cache too big session */ if (len > NGX_SSL_MAX_SESSION_SIZE) { From noreply at nginx.com Wed Feb 26 13:41:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 26 Feb 2025 13:41:02 +0000 (UTC) Subject: [nginx] SSL: raised limit for sessions stored in shared memory. Message-ID: <20250226134102.98F3248F9F@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/91245922027767c64e4e6661bf5e7623365c2328 branches: master commit: 91245922027767c64e4e6661bf5e7623365c2328 user: Sergey Kandaurov date: Tue, 25 Feb 2025 19:50:44 +0400 description: SSL: raised limit for sessions stored in shared memory. Upstream SSL sessions may be of a noticeably larger size with tickets in TLSv1.2 and older versions, or with "stateless" tickets in TLSv1.3, if a client certificate is saved into the session. Further, certain stateless session resumption implemetations may store additional data. Such one is JDK, known to also include server certificates in session ticket data, which roughly doubles a decoded session size to slightly beyond the previous limit. While it's believed to be an issue on the JDK side, this change allows to save such sessions. Another, innocent case is using RSA certificates with 8192 key size. --- src/event/ngx_event_openssl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h index 25e023b01..b7aaaca75 100644 --- a/src/event/ngx_event_openssl.h +++ b/src/event/ngx_event_openssl.h @@ -142,7 +142,7 @@ struct ngx_ssl_connection_s { #define NGX_SSL_DFLT_BUILTIN_SCACHE -5 -#define NGX_SSL_MAX_SESSION_SIZE 4096 +#define NGX_SSL_MAX_SESSION_SIZE 8192 typedef struct ngx_ssl_sess_id_s ngx_ssl_sess_id_t; From noreply at nginx.com Wed Feb 26 13:41:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 26 Feb 2025 13:41:02 +0000 (UTC) Subject: [nginx] SSL: removed stale comments. Message-ID: <20250226134102.A279948FA1@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/d16251969bf113272b577920940f020524d5fceb branches: master commit: d16251969bf113272b577920940f020524d5fceb user: Sergey Kandaurov date: Fri, 21 Feb 2025 15:54:04 +0400 description: SSL: removed stale comments. It appears to be a relic from prototype locking removed in b0b7b5a35. --- src/http/ngx_http_upstream_round_robin.c | 2 -- src/stream/ngx_stream_upstream_round_robin.c | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/http/ngx_http_upstream_round_robin.c b/src/http/ngx_http_upstream_round_robin.c index 25741869e..4637318d1 100644 --- a/src/http/ngx_http_upstream_round_robin.c +++ b/src/http/ngx_http_upstream_round_robin.c @@ -1021,8 +1021,6 @@ ngx_http_upstream_save_round_robin_peer_session(ngx_peer_connection_t *pc, ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, "old session: %p", old_ssl_session); - /* TODO: may block */ - ngx_ssl_free_session(old_ssl_session); } } diff --git a/src/stream/ngx_stream_upstream_round_robin.c b/src/stream/ngx_stream_upstream_round_robin.c index 1dae4e538..dd80322c8 100644 --- a/src/stream/ngx_stream_upstream_round_robin.c +++ b/src/stream/ngx_stream_upstream_round_robin.c @@ -1054,8 +1054,6 @@ ngx_stream_upstream_save_round_robin_peer_session(ngx_peer_connection_t *pc, ngx_log_debug1(NGX_LOG_DEBUG_STREAM, pc->log, 0, "old session: %p", old_ssl_session); - /* TODO: may block */ - ngx_ssl_free_session(old_ssl_session); } } From noreply at nginx.com Wed Feb 26 13:41:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 26 Feb 2025 13:41:02 +0000 (UTC) Subject: [nginx] SSL: using static storage for NGX_SSL_MAX_SESSION_SIZE buffers. Message-ID: <20250226134102.94BC448F9C@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/3d7304b527d1fb6eb697eb8719f286ba7b8e90de branches: master commit: 3d7304b527d1fb6eb697eb8719f286ba7b8e90de user: Sergey Kandaurov date: Fri, 21 Feb 2025 13:49:41 +0400 description: SSL: using static storage for NGX_SSL_MAX_SESSION_SIZE buffers. All such transient buffers are converted to the single storage in BSS. In preparation to raise the limit. --- src/event/ngx_event_openssl.c | 13 +++++++------ src/event/ngx_event_openssl.h | 3 +++ src/http/ngx_http_upstream_round_robin.c | 10 ++++------ src/stream/ngx_stream_upstream_round_robin.c | 10 ++++------ 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index 2446219a7..865c78540 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -132,6 +132,9 @@ int ngx_ssl_index; int ngx_ssl_certificate_name_index; +u_char ngx_ssl_session_buffer[NGX_SSL_MAX_SESSION_SIZE]; + + ngx_int_t ngx_ssl_init(ngx_log_t *log) { @@ -3889,7 +3892,6 @@ ngx_ssl_new_session(ngx_ssl_conn_t *ssl_conn, ngx_ssl_session_t *sess) ngx_slab_pool_t *shpool; ngx_ssl_sess_id_t *sess_id; ngx_ssl_session_cache_t *cache; - u_char buf[NGX_SSL_MAX_SESSION_SIZE]; #ifdef TLS1_3_VERSION @@ -3916,7 +3918,7 @@ ngx_ssl_new_session(ngx_ssl_conn_t *ssl_conn, ngx_ssl_session_t *sess) return 0; } - p = buf; + p = ngx_ssl_session_buffer; i2d_SSL_SESSION(sess, &p); session_id = (u_char *) SSL_SESSION_get_id(sess, &session_id_length); @@ -3980,7 +3982,7 @@ ngx_ssl_new_session(ngx_ssl_conn_t *ssl_conn, ngx_ssl_session_t *sess) #endif - ngx_memcpy(sess_id->session, buf, len); + ngx_memcpy(sess_id->session, ngx_ssl_session_buffer, len); ngx_memcpy(sess_id->id, session_id, session_id_length); hash = ngx_crc32_short(session_id, session_id_length); @@ -4039,7 +4041,6 @@ ngx_ssl_get_cached_session(ngx_ssl_conn_t *ssl_conn, ngx_ssl_session_t *sess; ngx_ssl_sess_id_t *sess_id; ngx_ssl_session_cache_t *cache; - u_char buf[NGX_SSL_MAX_SESSION_SIZE]; hash = ngx_crc32_short((u_char *) (uintptr_t) id, (size_t) len); *copy = 0; @@ -4087,11 +4088,11 @@ ngx_ssl_get_cached_session(ngx_ssl_conn_t *ssl_conn, if (sess_id->expire > ngx_time()) { slen = sess_id->len; - ngx_memcpy(buf, sess_id->session, slen); + ngx_memcpy(ngx_ssl_session_buffer, sess_id->session, slen); ngx_shmtx_unlock(&shpool->mutex); - p = buf; + p = ngx_ssl_session_buffer; sess = d2i_SSL_SESSION(NULL, &p, slen); return sess; diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h index 9ad4d177b..25e023b01 100644 --- a/src/event/ngx_event_openssl.h +++ b/src/event/ngx_event_openssl.h @@ -362,4 +362,7 @@ extern int ngx_ssl_index; extern int ngx_ssl_certificate_name_index; +extern u_char ngx_ssl_session_buffer[NGX_SSL_MAX_SESSION_SIZE]; + + #endif /* _NGX_EVENT_OPENSSL_H_INCLUDED_ */ diff --git a/src/http/ngx_http_upstream_round_robin.c b/src/http/ngx_http_upstream_round_robin.c index 304494b3c..6b4ff97f2 100644 --- a/src/http/ngx_http_upstream_round_robin.c +++ b/src/http/ngx_http_upstream_round_robin.c @@ -878,7 +878,6 @@ ngx_http_upstream_set_round_robin_peer_session(ngx_peer_connection_t *pc, int len; const u_char *p; ngx_http_upstream_rr_peers_t *peers; - u_char buf[NGX_SSL_MAX_SESSION_SIZE]; #endif peer = rrp->current; @@ -898,12 +897,12 @@ ngx_http_upstream_set_round_robin_peer_session(ngx_peer_connection_t *pc, len = peer->ssl_session_len; - ngx_memcpy(buf, peer->ssl_session, len); + ngx_memcpy(ngx_ssl_session_buffer, peer->ssl_session, len); ngx_http_upstream_rr_peer_unlock(peers, peer); ngx_http_upstream_rr_peers_unlock(peers); - p = buf; + p = ngx_ssl_session_buffer; ssl_session = d2i_SSL_SESSION(NULL, &p, len); rc = ngx_ssl_set_session(pc->connection, ssl_session); @@ -940,7 +939,6 @@ ngx_http_upstream_save_round_robin_peer_session(ngx_peer_connection_t *pc, int len; u_char *p; ngx_http_upstream_rr_peers_t *peers; - u_char buf[NGX_SSL_MAX_SESSION_SIZE]; #endif #if (NGX_HTTP_UPSTREAM_ZONE) @@ -965,7 +963,7 @@ ngx_http_upstream_save_round_robin_peer_session(ngx_peer_connection_t *pc, return; } - p = buf; + p = ngx_ssl_session_buffer; (void) i2d_SSL_SESSION(ssl_session, &p); peer = rrp->current; @@ -995,7 +993,7 @@ ngx_http_upstream_save_round_robin_peer_session(ngx_peer_connection_t *pc, peer->ssl_session_len = len; } - ngx_memcpy(peer->ssl_session, buf, len); + ngx_memcpy(peer->ssl_session, ngx_ssl_session_buffer, len); ngx_http_upstream_rr_peer_unlock(peers, peer); ngx_http_upstream_rr_peers_unlock(peers); diff --git a/src/stream/ngx_stream_upstream_round_robin.c b/src/stream/ngx_stream_upstream_round_robin.c index 5b5f20db7..27db0851e 100644 --- a/src/stream/ngx_stream_upstream_round_robin.c +++ b/src/stream/ngx_stream_upstream_round_robin.c @@ -911,7 +911,6 @@ ngx_stream_upstream_set_round_robin_peer_session(ngx_peer_connection_t *pc, int len; const u_char *p; ngx_stream_upstream_rr_peers_t *peers; - u_char buf[NGX_SSL_MAX_SESSION_SIZE]; #endif peer = rrp->current; @@ -931,12 +930,12 @@ ngx_stream_upstream_set_round_robin_peer_session(ngx_peer_connection_t *pc, len = peer->ssl_session_len; - ngx_memcpy(buf, peer->ssl_session, len); + ngx_memcpy(ngx_ssl_session_buffer, peer->ssl_session, len); ngx_stream_upstream_rr_peer_unlock(peers, peer); ngx_stream_upstream_rr_peers_unlock(peers); - p = buf; + p = ngx_ssl_session_buffer; ssl_session = d2i_SSL_SESSION(NULL, &p, len); rc = ngx_ssl_set_session(pc->connection, ssl_session); @@ -973,7 +972,6 @@ ngx_stream_upstream_save_round_robin_peer_session(ngx_peer_connection_t *pc, int len; u_char *p; ngx_stream_upstream_rr_peers_t *peers; - u_char buf[NGX_SSL_MAX_SESSION_SIZE]; #endif #if (NGX_STREAM_UPSTREAM_ZONE) @@ -998,7 +996,7 @@ ngx_stream_upstream_save_round_robin_peer_session(ngx_peer_connection_t *pc, return; } - p = buf; + p = ngx_ssl_session_buffer; (void) i2d_SSL_SESSION(ssl_session, &p); peer = rrp->current; @@ -1028,7 +1026,7 @@ ngx_stream_upstream_save_round_robin_peer_session(ngx_peer_connection_t *pc, peer->ssl_session_len = len; } - ngx_memcpy(peer->ssl_session, buf, len); + ngx_memcpy(peer->ssl_session, ngx_ssl_session_buffer, len); ngx_stream_upstream_rr_peer_unlock(peers, peer); ngx_stream_upstream_rr_peers_unlock(peers);