From sorin.v.manole at gmail.com Sat Mar 1 14:41:15 2025 From: sorin.v.manole at gmail.com (Sorin Manole) Date: Sat, 1 Mar 2025 16:41:15 +0200 Subject: Development guide sentence wording Message-ID: Hello, Didn't know where to report it, just a small improvement of the development guide wording: The function ngx_event_process_posted() is called to process an event queue. It calls event handlers until the queue is not empty. This means that a posted event handler can post more events to be processed within the current event loop iteration. should probably be: The function ngx_event_process_posted() is called to process an event queue. It calls event handlers until the queue *is empty*. This means that a posted event handler can post more events to be processed within the current event loop iteration. -------------- next part -------------- An HTML attachment was scrubbed... URL: From osa at freebsd.org.ru Sat Mar 1 15:09:12 2025 From: osa at freebsd.org.ru (Sergey A. Osokin) Date: Sat, 1 Mar 2025 18:09:12 +0300 Subject: Development guide sentence wording In-Reply-To: References: Message-ID: Hi Sorin, thanks for the report! On Sat, Mar 01, 2025 at 04:41:15PM +0200, Sorin Manole wrote: > Hello, > > Didn't know where to report it, just a small improvement of the development > guide wording: > > The function ngx_event_process_posted() is called to process an event > queue. It calls event handlers until the queue is not empty. This means > that a posted event handler can post more events to be processed within the > current event loop iteration. > > should probably be: > > The function ngx_event_process_posted() is called to process an event > queue. It calls event handlers until the queue *is empty*. This means that > a posted event handler can post more events to be processed within the > current event loop iteration. We'll review and update our documentation. Thank you. -- Sergey A. Osokin From pluknet at nginx.com Tue Mar 4 15:44:14 2025 From: pluknet at nginx.com (Sergey Kandaurov) Date: Tue, 4 Mar 2025 19:44:14 +0400 Subject: Development guide sentence wording In-Reply-To: References: Message-ID: <3A2DF92D-E091-4060-A5E1-9CA883229B2F@nginx.com> > On 1 Mar 2025, at 18:41, Sorin Manole wrote: > > Hello, > > Didn't know where to report it, just a small improvement of the development guide wording: > > The function ngx_event_process_posted() is called to process an event queue. It calls event handlers until the queue is not empty. This means that a posted event handler can post more events to be processed within the current event loop iteration. > > should probably be: > > The function ngx_event_process_posted() is called to process an event queue. It calls event handlers until the queue is empty. This means that a posted event handler can post more events to be processed within the current event loop iteration. The site should be now updated, thanks for report. -- Sergey Kandaurov From noreply at nginx.com Mon Mar 10 16:33:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Mon, 10 Mar 2025 16:33:02 +0000 (UTC) Subject: [nginx] Slice filter: improved memory allocation error handling. Message-ID: <20250310163302.C2E72477EF@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/d31305653701bd99e8e5e6aa48094599a08f9f12 branches: master commit: d31305653701bd99e8e5e6aa48094599a08f9f12 user: Sergey Kandaurov date: Thu, 27 Feb 2025 16:09:50 +0400 description: Slice filter: improved memory allocation error handling. As uncovered by recent addition in slice.t, a partially initialized context, coupled with HTTP 206 response from stub backend, might be accessed in the next slice subrequest. Found by bad memory allocator simulation. --- src/http/modules/ngx_http_slice_filter_module.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/http/modules/ngx_http_slice_filter_module.c b/src/http/modules/ngx_http_slice_filter_module.c index 3b0bef629..67dc14c82 100644 --- a/src/http/modules/ngx_http_slice_filter_module.c +++ b/src/http/modules/ngx_http_slice_filter_module.c @@ -419,13 +419,13 @@ ngx_http_slice_range_variable(ngx_http_request_t *r, return NGX_ERROR; } - ngx_http_set_ctx(r, ctx, ngx_http_slice_filter_module); - p = ngx_pnalloc(r->pool, sizeof("bytes=-") - 1 + 2 * NGX_OFF_T_LEN); if (p == NULL) { return NGX_ERROR; } + ngx_http_set_ctx(r, ctx, ngx_http_slice_filter_module); + ctx->start = slcf->size * (ngx_http_slice_get_start(r) / slcf->size); ctx->range.data = p; From zyrotnet at gmail.com Wed Mar 12 15:04:24 2025 From: zyrotnet at gmail.com (Zyrot System) Date: Wed, 12 Mar 2025 10:04:24 -0500 Subject: Use directive code from other module inside my custom ngx_http_mycontrol_module Message-ID: Hello everyone, first I want to say I already searched mailman.nginx.org for a topic relating to my question but I cannot find it. ngx_http_rewrite_module has directives rewrite, return, etc. I want to use directives code from other module inside my module so can do code reuse "DRY", for example create my own directive like: server { # important to work in this Context mycontrol * { myrewrite ^(/download/.*)/media/(.*)\..*$ $1/mp3/$2.mp3 last; # using the core ngx_http_rewrite_module.c } mycontrol * { # optional using the original ngx_http_rewrite_module.c rewrite ^(/download/.*)/media/(.*)\..*$ $1/mp3/$2.mp3 last; } } So can avoid replicate the already functionality (I want to extend the ngx_http_rewrite_module, and others modules in my module mycontrol with more features). Any ideas ? I just want to be sure for what route to go, maybe instead I should used the PCRE library directly in ngx_http_mycontrol_module like does other modules ? Thanks for reading Note repost in: stackoverflow.com/questions/79503527/use-directive-code-from-other-module-inside-my-module-nginx -------------- next part -------------- An HTML attachment was scrubbed... URL: From teward at thomas-ward.net Sat Mar 15 21:55:40 2025 From: teward at thomas-ward.net (Thomas Ward) Date: Sat, 15 Mar 2025 17:55:40 -0400 Subject: Proposal: Change `ssl_client_certificate` to `ssl_client_ca_certificate` Message-ID: <3545d0b5-b53f-4d45-b82c-23bb593c22f2@thomas-ward.net> In line with a recent nginx mailing list thread I had with a user about how to properly secure a site with SSL/TLS Client Certificates, the user indicated that "ssl_client_certificate" is a confusing misnomer.  It implies that the certificate(s) provided are a bundle of certs that are *individual client certificates* not the Certification Authority (CA) certificate and chain that issued the certificiates. It's always annoyed me slightly that it has been "ssl_client_certificate" and has no mention of it being a CA cert. I'm guessing that's because you could theoretically use a self-signed certificate and verify it against itself, thus not needing a CA certificate, however that's not the primary use case nor is that how it's really explained in the NGINX documentation of the command. Has there been any discussion or consideration of renaming ssl_client_certificate to something that is less confusing to people new to the process, to show that this is supposed to be the CA certificate of the authority that is issuing the client certificates? Thomas From noreply at nginx.com Tue Mar 18 02:00:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Tue, 18 Mar 2025 02:00:02 +0000 (UTC) Subject: [njs] QuickJS: making ngx_qjs_*() functions consistent with ngx_js_*(). Message-ID: <20250318020002.D0AD748373@pubserv1.nginx> details: https://github.com/nginx/njs/commit/9010aeea7807a6398e5d5ea89794f94ca0e4cb1e branches: master commit: 9010aeea7807a6398e5d5ea89794f94ca0e4cb1e user: hongzhidao date: Mon, 17 Mar 2025 14:40:49 +0800 description: QuickJS: making ngx_qjs_*() functions consistent with ngx_js_*(). --- nginx/ngx_http_js_module.c | 71 ++++++++++++++++--------------------- nginx/ngx_js.c | 84 ++++++++++++++++++++++++-------------------- nginx/ngx_js.h | 8 ++--- nginx/ngx_js_shared_dict.c | 30 ++++------------ nginx/ngx_stream_js_module.c | 16 ++++----- 5 files changed, 91 insertions(+), 118 deletions(-) diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c index 71fd92ba..ae970eb1 100644 --- a/nginx/ngx_http_js_module.c +++ b/nginx/ngx_http_js_module.c @@ -5133,7 +5133,7 @@ ngx_http_qjs_ext_internal_redirect(JSContext *cx, JSValueConst this_val, "internalRedirect cannot be called while filtering"); } - if (ngx_qjs_string(ctx->engine, argv[0], &ctx->redirect_uri) != NGX_OK) { + if (ngx_qjs_string(cx, argv[0], &ctx->redirect_uri) != NGX_OK) { return JS_EXCEPTION; } @@ -5412,7 +5412,7 @@ ngx_http_qjs_ext_return(JSContext *cx, JSValueConst this_val, ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); if (status < NGX_HTTP_BAD_REQUEST || !JS_IsNullOrUndefined(argv[1])) { - if (ngx_qjs_string(ctx->engine, argv[1], &body) != NGX_OK) { + if (ngx_qjs_string(cx, argv[1], &body) != NGX_OK) { return JS_ThrowOutOfMemory(cx); } @@ -5515,7 +5515,7 @@ ngx_http_qjs_ext_send(JSContext *cx, JSValueConst this_val, ll = &out; for (n = 0; n < (ngx_uint_t) argc; n++) { - if (ngx_qjs_string(ctx->engine, argv[n], &s) != NGX_OK) { + if (ngx_qjs_string(cx, argv[n], &s) != NGX_OK) { return JS_ThrowTypeError(cx, "failed to convert arg"); } @@ -5578,7 +5578,7 @@ ngx_http_qjs_ext_send_buffer(JSContext *cx, JSValueConst this_val, return JS_ThrowTypeError(cx, "cannot send buffer while not filtering"); } - if (ngx_qjs_string(ctx->engine, argv[0], &buffer) != NGX_OK) { + if (ngx_qjs_string(cx, argv[0], &buffer) != NGX_OK) { return JS_ThrowTypeError(cx, "failed get buffer arg"); } @@ -5735,7 +5735,7 @@ ngx_http_qjs_subrequest_done(ngx_http_request_t *r, void *data, ngx_int_t rc) reply = JS_DupValue(cx, ngx_qjs_arg(sctx->args[0])); } - rc = ngx_qjs_call((ngx_js_ctx_t *) ctx, event->function, &reply, 1); + rc = ngx_qjs_call(cx, event->function, &reply, 1); JS_FreeValue(cx, reply); ngx_js_del_event(ctx, event); @@ -5786,7 +5786,7 @@ ngx_http_qjs_ext_subrequest(JSContext *cx, JSValueConst this_val, "the primary request"); } - if (ngx_qjs_string(ctx->engine, argv[0], &uri) != NGX_OK) { + if (ngx_qjs_string(cx, argv[0], &uri) != NGX_OK) { return JS_ThrowTypeError(cx, "failed to convert uri arg"); } @@ -5812,7 +5812,7 @@ ngx_http_qjs_ext_subrequest(JSContext *cx, JSValueConst this_val, arg = argv[1]; if (JS_IsString(arg)) { - if (ngx_qjs_string(ctx->engine, arg, &args) != NGX_OK) { + if (ngx_qjs_string(cx, arg, &args) != NGX_OK) { return JS_ThrowTypeError(cx, "failed to convert args"); } @@ -5833,7 +5833,7 @@ ngx_http_qjs_ext_subrequest(JSContext *cx, JSValueConst this_val, } if (!JS_IsUndefined(value)) { - rc = ngx_qjs_string(ctx->engine, value, &args); + rc = ngx_qjs_string(cx, value, &args); JS_FreeValue(cx, value); if (rc != NGX_OK) { @@ -5857,7 +5857,7 @@ ngx_http_qjs_ext_subrequest(JSContext *cx, JSValueConst this_val, } if (!JS_IsUndefined(value)) { - rc = ngx_qjs_string(ctx->engine, value, &method_name); + rc = ngx_qjs_string(cx, value, &method_name); JS_FreeValue(cx, value); if (rc != NGX_OK) { @@ -5884,7 +5884,7 @@ ngx_http_qjs_ext_subrequest(JSContext *cx, JSValueConst this_val, } if (!JS_IsUndefined(value)) { - rc = ngx_qjs_string(ctx->engine, value, &body_arg); + rc = ngx_qjs_string(cx, value, &body_arg); JS_FreeValue(cx, value); if (rc != NGX_OK) { @@ -6233,7 +6233,6 @@ ngx_http_qjs_variables_set_property(JSContext *cx, JSValueConst obj, u_char *lowcase_key; ngx_str_t name, s; ngx_uint_t key; - ngx_http_js_ctx_t *ctx; ngx_http_request_t *r; ngx_http_variable_t *v; ngx_http_variable_value_t *vv; @@ -6279,9 +6278,7 @@ ngx_http_qjs_variables_set_property(JSContext *cx, JSValueConst obj, return -1; } - ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); - - if (ngx_qjs_string(ctx->engine, value, &s) != NGX_OK) { + if (ngx_qjs_string(cx, value, &s) != NGX_OK) { return -1; } @@ -6692,15 +6689,14 @@ ngx_http_qjs_headers_out_handler(JSContext *cx, ngx_http_request_t *r, ngx_str_t *name, JSPropertyDescriptor *pdesc, JSValue *value, unsigned flags) { - u_char *p; - int64_t length; - uint32_t i; - ngx_int_t rc; - ngx_str_t s; - JSValue v; - ngx_list_part_t *part; - ngx_table_elt_t *header, *h, **ph; - ngx_http_js_ctx_t *ctx; + u_char *p; + int64_t length; + uint32_t i; + ngx_int_t rc; + ngx_str_t s; + JSValue v; + ngx_list_part_t *part; + ngx_table_elt_t *header, *h, **ph; if (flags & NJS_HEADER_GET) { return ngx_http_qjs_header_generic(cx, r, &r->headers_out.headers, NULL, @@ -6758,7 +6754,6 @@ ngx_http_qjs_headers_out_handler(JSContext *cx, ngx_http_request_t *r, } ph = &header; - ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); for (i = 0; i < (uint32_t) length; i++) { if (JS_IsArray(cx, *value)) { @@ -6768,7 +6763,7 @@ ngx_http_qjs_headers_out_handler(JSContext *cx, ngx_http_request_t *r, } } - rc = ngx_qjs_string(ctx->engine, v, &s); + rc = ngx_qjs_string(cx, v, &s); if (JS_IsArray(cx, *value)) { JS_FreeValue(cx, v); @@ -6828,15 +6823,14 @@ ngx_http_qjs_headers_out_special_handler(JSContext *cx, ngx_http_request_t *r, ngx_str_t *name, JSPropertyDescriptor *pdesc, JSValue *value, unsigned flags, ngx_table_elt_t **hh) { - u_char *p; - uint32_t length; - JSValue len, setval; - ngx_str_t s; - ngx_uint_t i, rc; - ngx_list_t *headers; - ngx_list_part_t *part; - ngx_table_elt_t *header, *h; - ngx_http_js_ctx_t *ctx; + u_char *p; + uint32_t length; + JSValue len, setval; + ngx_str_t s; + ngx_uint_t i, rc; + ngx_list_t *headers; + ngx_list_part_t *part; + ngx_table_elt_t *header, *h; if (flags & NJS_HEADER_GET) { return ngx_http_qjs_headers_out_handler(cx, r, name, pdesc, NULL, @@ -6870,9 +6864,7 @@ ngx_http_qjs_headers_out_special_handler(JSContext *cx, ngx_http_request_t *r, setval = JS_UNDEFINED; } - ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); - - rc = ngx_qjs_string(ctx->engine, setval, &s); + rc = ngx_qjs_string(cx, setval, &s); if (value != NULL && JS_IsArray(cx, *value)) { JS_FreeValue(cx, setval); @@ -7054,7 +7046,6 @@ ngx_http_qjs_headers_out_content_type(JSContext *cx, ngx_http_request_t *r, JSValue len, setval; ngx_int_t rc; ngx_str_t *hdr, s; - ngx_http_js_ctx_t *ctx; if (flags & NJS_HEADER_GET) { hdr = &r->headers_out.content_type; @@ -7108,9 +7099,7 @@ ngx_http_qjs_headers_out_content_type(JSContext *cx, ngx_http_request_t *r, setval = *value; } - ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); - - rc = ngx_qjs_string(ctx->engine, setval, &s); + rc = ngx_qjs_string(cx, setval, &s); if (JS_IsArray(cx, *value)) { JS_FreeValue(cx, setval); diff --git a/nginx/ngx_js.c b/nginx/ngx_js.c index 383d2304..84cdcc2e 100644 --- a/nginx/ngx_js.c +++ b/nginx/ngx_js.c @@ -101,7 +101,7 @@ static void ngx_qjs_rejection_tracker(JSContext *ctx, JSValueConst promise, JSValueConst reason, JS_BOOL is_handled, void *opaque); static JSValue ngx_qjs_value(JSContext *cx, const ngx_str_t *path); -static ngx_int_t ngx_qjs_dump_obj(ngx_engine_t *e, JSValueConst val, +static ngx_int_t ngx_qjs_dump_obj(JSContext *cx, JSValueConst val, ngx_str_t *dst); static JSModuleDef *ngx_qjs_core_init(JSContext *cx, const char *name); @@ -849,7 +849,7 @@ ngx_engine_qjs_compile(ngx_js_loc_conf_t *conf, ngx_log_t *log, u_char *start, JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY); if (JS_IsException(code)) { - ngx_qjs_exception(engine, &text); + ngx_qjs_exception(cx, &text); ngx_log_error(NGX_LOG_EMERG, log, 0, "js compile %V", &text); return NGX_ERROR; } @@ -972,7 +972,7 @@ ngx_qjs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf, void *external) rv = JS_ReadObject(cx, pc[i].code, pc[i].code_size, JS_READ_OBJ_BYTECODE); if (JS_IsException(rv)) { - ngx_qjs_exception(engine, &exception); + ngx_qjs_exception(cx, &exception); ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js load module exception: %V", &exception); @@ -988,7 +988,7 @@ ngx_qjs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf, void *external) rv = JS_EvalFunction(cx, rv); if (JS_IsException(rv)) { - ngx_qjs_exception(engine, &exception); + ngx_qjs_exception(cx, &exception); ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js eval exception: %V", &exception); @@ -997,7 +997,7 @@ ngx_qjs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf, void *external) rv = js_std_await(cx, rv); if (JS_IsException(rv)) { - ngx_qjs_exception(engine, &exception); + ngx_qjs_exception(cx, &exception); ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js eval exception: %V", &exception); @@ -1042,7 +1042,7 @@ ngx_engine_qjs_call(ngx_js_ctx_t *ctx, ngx_str_t *fname, val = JS_Call(cx, fn, JS_UNDEFINED, nargs, &ngx_qjs_arg(args[0])); JS_FreeValue(cx, fn); if (JS_IsException(val)) { - ngx_qjs_exception(ctx->engine, &exception); + ngx_qjs_exception(cx, &exception); ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js call exception: %V", &exception); @@ -1059,7 +1059,7 @@ ngx_engine_qjs_call(ngx_js_ctx_t *ctx, ngx_str_t *fname, rc = JS_ExecutePendingJob(rt, &cx1); if (rc <= 0) { if (rc == -1) { - ngx_qjs_exception(ctx->engine, &exception); + ngx_qjs_exception(cx, &exception); ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js job exception: %V", &exception); @@ -1093,7 +1093,7 @@ static ngx_int_t ngx_engine_qjs_string(ngx_engine_t *e, njs_opaque_value_t *value, ngx_str_t *str) { - return ngx_qjs_dump_obj(e, ngx_qjs_arg(*value), str); + return ngx_qjs_dump_obj(e->u.qjs.ctx, ngx_qjs_arg(*value), str); } @@ -1150,7 +1150,7 @@ ngx_engine_qjs_destroy(ngx_engine_t *e, ngx_js_ctx_t *ctx, } if (ngx_qjs_unhandled_rejection(ctx)) { - ngx_qjs_exception(ctx->engine, &exception); + ngx_qjs_exception(cx, &exception); ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js unhandled rejection: %V", &exception); } @@ -1255,13 +1255,17 @@ ngx_qjs_value(JSContext *cx, const ngx_str_t *path) static ngx_int_t -ngx_qjs_dump_obj(ngx_engine_t *e, JSValueConst val, ngx_str_t *dst) +ngx_qjs_dump_obj(JSContext *cx, JSValueConst val, ngx_str_t *dst) { - size_t len, byte_offset, byte_length; - u_char *start, *p; - JSValue buffer, stack; - ngx_str_t str, stack_str; - JSContext *cx; + size_t len, byte_offset, byte_length; + u_char *start, *p; + JSValue buffer, stack; + ngx_str_t str, stack_str; + ngx_js_ctx_t *ctx; + ngx_engine_t *e; + + ctx = ngx_qjs_external_ctx(cx, JS_GetContextOpaque(cx)); + e = ctx->engine; if (JS_IsNullOrUndefined(val)) { dst->data = NULL; @@ -1350,19 +1354,20 @@ ngx_qjs_dump_obj(ngx_engine_t *e, JSValueConst val, ngx_str_t *dst) ngx_int_t -ngx_qjs_call(ngx_js_ctx_t *ctx, JSValue fn, JSValue *argv, int argc) +ngx_qjs_call(JSContext *cx, JSValue fn, JSValue *argv, int argc) { - int rc; - JSValue ret; - ngx_str_t exception; - JSRuntime *rt; - JSContext *cx, *cx1; + int rc; + JSValue ret; + ngx_str_t exception; + JSRuntime *rt; + JSContext *cx1; + ngx_js_ctx_t *ctx; - cx = ctx->engine->u.qjs.ctx; + ctx = ngx_qjs_external_ctx(cx, JS_GetContextOpaque(cx)); ret = JS_Call(cx, fn, JS_UNDEFINED, argc, argv); if (JS_IsException(ret)) { - ngx_qjs_exception(ctx->engine, &exception); + ngx_qjs_exception(cx, &exception); ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js call exception: %V", &exception); @@ -1378,7 +1383,7 @@ ngx_qjs_call(ngx_js_ctx_t *ctx, JSValue fn, JSValue *argv, int argc) rc = JS_ExecutePendingJob(rt, &cx1); if (rc <= 0) { if (rc == -1) { - ngx_qjs_exception(ctx->engine, &exception); + ngx_qjs_exception(cx, &exception); ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js job exception: %V", &exception); @@ -1395,16 +1400,16 @@ ngx_qjs_call(ngx_js_ctx_t *ctx, JSValue fn, JSValue *argv, int argc) ngx_int_t -ngx_qjs_exception(ngx_engine_t *e, ngx_str_t *s) +ngx_qjs_exception(JSContext *cx, ngx_str_t *s) { JSValue exception; - exception = JS_GetException(e->u.qjs.ctx); - if (ngx_qjs_dump_obj(e, exception, s) != NGX_OK) { + exception = JS_GetException(cx); + if (ngx_qjs_dump_obj(cx, exception, s) != NGX_OK) { return NGX_ERROR; } - JS_FreeValue(e->u.qjs.ctx, exception); + JS_FreeValue(cx, exception); return NGX_OK; } @@ -1431,13 +1436,17 @@ ngx_qjs_integer(JSContext *cx, JSValueConst val, ngx_int_t *n) ngx_int_t -ngx_qjs_string(ngx_engine_t *e, JSValueConst val, ngx_str_t *dst) +ngx_qjs_string(JSContext *cx, JSValueConst val, ngx_str_t *dst) { - size_t len, byte_offset, byte_length; - u_char *start; - JSValue buffer; - JSContext *cx; - const char *str; + size_t len, byte_offset, byte_length; + u_char *start; + JSValue buffer; + const char *str; + ngx_js_ctx_t *ctx; + ngx_engine_t *e; + + ctx = ngx_qjs_external_ctx(cx, JS_GetContextOpaque(cx)); + e = ctx->engine; if (JS_IsNullOrUndefined(val)) { dst->data = NULL; @@ -1512,8 +1521,7 @@ ngx_qjs_timer_handler(ngx_event_t *ev) external = JS_GetContextOpaque(cx); ctx = ngx_qjs_external_ctx(cx, external); - rc = ngx_qjs_call((ngx_js_ctx_t *) ctx, event->function, event->args, - event->nargs); + rc = ngx_qjs_call(cx, event->function, event->args, event->nargs); ngx_js_del_event(ctx, event); @@ -1740,7 +1748,6 @@ ngx_qjs_ext_log(JSContext *cx, JSValueConst this_val, int argc, char *p; uint32_t level; ngx_str_t msg; - ngx_js_ctx_t *ctx; ngx_connection_t *c; p = JS_GetContextOpaque(cx); @@ -1759,11 +1766,10 @@ ngx_qjs_ext_log(JSContext *cx, JSValueConst this_val, int argc, argv++; } - ctx = ngx_qjs_external_ctx(cx, p); c = ngx_qjs_external_connection(cx, p); for ( ; argc > 0; argc--, argv++) { - if (ngx_qjs_dump_obj(ctx->engine, argv[0], &msg) != NGX_OK) { + if (ngx_qjs_dump_obj(cx, argv[0], &msg) != NGX_OK) { return JS_EXCEPTION; } diff --git a/nginx/ngx_js.h b/nginx/ngx_js.h index 833c1490..6fac6994 100644 --- a/nginx/ngx_js.h +++ b/nginx/ngx_js.h @@ -340,11 +340,11 @@ ngx_engine_t *ngx_qjs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf, void *external); void ngx_engine_qjs_destroy(ngx_engine_t *e, ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *conf); -ngx_int_t ngx_qjs_call(ngx_js_ctx_t *ctx, JSValue function, - JSValue *argv, int argc); -ngx_int_t ngx_qjs_exception(ngx_engine_t *e, ngx_str_t *s); +ngx_int_t ngx_qjs_call(JSContext *cx, JSValue function, JSValue *argv, + int argc); +ngx_int_t ngx_qjs_exception(JSContext *cx, ngx_str_t *s); ngx_int_t ngx_qjs_integer(JSContext *cx, JSValueConst val, ngx_int_t *n); -ngx_int_t ngx_qjs_string(ngx_engine_t *e, JSValueConst val, ngx_str_t *str); +ngx_int_t ngx_qjs_string(JSContext *cx, JSValueConst val, ngx_str_t *str); #define ngx_qjs_prop(cx, type, start, len) \ ((type == NGX_JS_STRING) ? qjs_string_create(cx, start, len) \ diff --git a/nginx/ngx_js_shared_dict.c b/nginx/ngx_js_shared_dict.c index 6ae12d84..7f8c808e 100644 --- a/nginx/ngx_js_shared_dict.c +++ b/nginx/ngx_js_shared_dict.c @@ -2078,7 +2078,6 @@ ngx_qjs_ext_shared_dict_delete(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv) { ngx_str_t key; - ngx_js_ctx_t *ctx; ngx_shm_zone_t *shm_zone; shm_zone = JS_GetOpaque(this_val, NGX_QJS_CLASS_ID_SHARED_DICT); @@ -2086,9 +2085,7 @@ ngx_qjs_ext_shared_dict_delete(JSContext *cx, JSValueConst this_val, return JS_ThrowTypeError(cx, "\"this\" is not a shared dict"); } - ctx = ngx_qjs_external_ctx(cx, JS_GetContextOpaque(cx)); - - if (ngx_qjs_string(ctx->engine, argv[0], &key) != NGX_OK) { + if (ngx_qjs_string(cx, argv[0], &key) != NGX_OK) { return JS_EXCEPTION; } @@ -2124,7 +2121,6 @@ ngx_qjs_ext_shared_dict_get(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv) { ngx_str_t key; - ngx_js_ctx_t *ctx; ngx_shm_zone_t *shm_zone; shm_zone = JS_GetOpaque(this_val, NGX_QJS_CLASS_ID_SHARED_DICT); @@ -2132,9 +2128,7 @@ ngx_qjs_ext_shared_dict_get(JSContext *cx, JSValueConst this_val, return JS_ThrowTypeError(cx, "\"this\" is not a shared dict"); } - ctx = ngx_qjs_external_ctx(cx, JS_GetContextOpaque(cx)); - - if (ngx_qjs_string(ctx->engine, argv[0], &key) != NGX_OK) { + if (ngx_qjs_string(cx, argv[0], &key) != NGX_OK) { return JS_EXCEPTION; } @@ -2149,7 +2143,6 @@ ngx_qjs_ext_shared_dict_has(JSContext *cx, JSValueConst this_val, ngx_str_t key; ngx_msec_t now; ngx_time_t *tp; - ngx_js_ctx_t *ctx; ngx_js_dict_t *dict; ngx_shm_zone_t *shm_zone; ngx_js_dict_node_t *node; @@ -2159,9 +2152,7 @@ ngx_qjs_ext_shared_dict_has(JSContext *cx, JSValueConst this_val, return JS_ThrowTypeError(cx, "\"this\" is not a shared dict"); } - ctx = ngx_qjs_external_ctx(cx, JS_GetContextOpaque(cx)); - - if (ngx_qjs_string(ctx->engine, argv[0], &key) != NGX_OK) { + if (ngx_qjs_string(cx, argv[0], &key) != NGX_OK) { return JS_EXCEPTION; } @@ -2193,7 +2184,6 @@ ngx_qjs_ext_shared_dict_incr(JSContext *cx, JSValueConst this_val, double delta, init; uint32_t timeout; ngx_str_t key; - ngx_js_ctx_t *ctx; ngx_js_dict_t *dict; ngx_shm_zone_t *shm_zone; @@ -2208,9 +2198,7 @@ ngx_qjs_ext_shared_dict_incr(JSContext *cx, JSValueConst this_val, return JS_ThrowTypeError(cx, "shared dict is not a number dict"); } - ctx = ngx_qjs_external_ctx(cx, JS_GetContextOpaque(cx)); - - if (ngx_qjs_string(ctx->engine, argv[0], &key) != NGX_OK) { + if (ngx_qjs_string(cx, argv[0], &key) != NGX_OK) { return JS_EXCEPTION; } @@ -2464,7 +2452,6 @@ ngx_qjs_ext_shared_dict_pop(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv) { ngx_str_t key; - ngx_js_ctx_t *ctx; ngx_shm_zone_t *shm_zone; shm_zone = JS_GetOpaque(this_val, NGX_QJS_CLASS_ID_SHARED_DICT); @@ -2472,9 +2459,7 @@ ngx_qjs_ext_shared_dict_pop(JSContext *cx, JSValueConst this_val, return JS_ThrowTypeError(cx, "\"this\" is not a shared dict"); } - ctx = ngx_qjs_external_ctx(cx, JS_GetContextOpaque(cx)); - - if (ngx_qjs_string(ctx->engine, argv[0], &key) != NGX_OK) { + if (ngx_qjs_string(cx, argv[0], &key) != NGX_OK) { return JS_EXCEPTION; } @@ -2489,7 +2474,6 @@ ngx_qjs_ext_shared_dict_set(JSContext *cx, JSValueConst this_val, JSValue ret; uint32_t timeout; ngx_str_t key; - ngx_js_ctx_t *ctx; ngx_js_dict_t *dict; ngx_shm_zone_t *shm_zone; @@ -2498,9 +2482,7 @@ ngx_qjs_ext_shared_dict_set(JSContext *cx, JSValueConst this_val, return JS_ThrowTypeError(cx, "\"this\" is not a shared dict"); } - ctx = ngx_qjs_external_ctx(cx, JS_GetContextOpaque(cx)); - - if (ngx_qjs_string(ctx->engine, argv[0], &key) != NGX_OK) { + if (ngx_qjs_string(cx, argv[0], &key) != NGX_OK) { return JS_EXCEPTION; } diff --git a/nginx/ngx_stream_js_module.c b/nginx/ngx_stream_js_module.c index 7e93b237..a7dddd04 100644 --- a/nginx/ngx_stream_js_module.c +++ b/nginx/ngx_stream_js_module.c @@ -2152,7 +2152,7 @@ ngx_stream_qjs_ext_on(JSContext *cx, JSValueConst this_val, int argc, ctx = ngx_stream_get_module_ctx(ses->session, ngx_stream_js_module); - if (ngx_qjs_string(ctx->engine, argv[0], &name) != NGX_OK) { + if (ngx_qjs_string(cx, argv[0], &name) != NGX_OK) { return JS_EXCEPTION; } @@ -2195,7 +2195,7 @@ ngx_stream_qjs_ext_off(JSContext *cx, JSValueConst this_val, int argc, ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module); - if (ngx_qjs_string(ctx->engine, argv[0], &name) != NGX_OK) { + if (ngx_qjs_string(cx, argv[0], &name) != NGX_OK) { return JS_EXCEPTION; } @@ -2278,7 +2278,7 @@ ngx_stream_qjs_ext_send(JSContext *cx, JSValueConst this_val, int argc, return JS_ThrowInternalError(cx, "cannot send buffer in this handler"); } - if (ngx_qjs_string(ctx->engine, argv[0], &buffer) != NGX_OK) { + if (ngx_qjs_string(cx, argv[0], &buffer) != NGX_OK) { return JS_EXCEPTION; } @@ -2510,7 +2510,6 @@ ngx_stream_qjs_variables_set_property(JSContext *cx, JSValueConst obj, { 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; @@ -2556,9 +2555,7 @@ ngx_stream_qjs_variables_set_property(JSContext *cx, JSValueConst obj, return -1; } - ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module); - - if (ngx_qjs_string(ctx->engine, value, &val) != NGX_OK) { + if (ngx_qjs_string(cx, value, &val) != NGX_OK) { return -1; } @@ -2669,14 +2666,13 @@ ngx_stream_qjs_run_event(ngx_stream_session_t *s, ngx_stream_js_ctx_t *ctx, JS_SetOpaque(argv[1], (void *) flags); - rc = ngx_qjs_call((ngx_js_ctx_t *) ctx, ngx_qjs_arg(event->function), - &argv[0], 2); + rc = ngx_qjs_call(cx, ngx_qjs_arg(event->function), &argv[0], 2); JS_FreeValue(cx, argv[0]); JS_FreeValue(cx, argv[1]); if (rc == NGX_ERROR) { error: - ngx_qjs_exception(ctx->engine, &exception); + ngx_qjs_exception(cx, &exception); ngx_log_error(NGX_LOG_ERR, c->log, 0, "js exception: %V", &exception); From noreply at nginx.com Tue Mar 18 21:46:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Tue, 18 Mar 2025 21:46:02 +0000 (UTC) Subject: [njs] Test262: using default prepare_args() where appropriate. Message-ID: <20250318214602.C4D8C48379@pubserv1.nginx> details: https://github.com/nginx/njs/commit/93e73cf1678038726b6f7fda927b932eb3e0d9d3 branches: master commit: 93e73cf1678038726b6f7fda927b932eb3e0d9d3 user: Dmitry Volyntsev date: Tue, 25 Feb 2025 22:35:55 -0800 description: Test262: using default prepare_args() where appropriate. --- test/buffer.t.js | 36 ++++-------------------------------- test/harness/runTsuite.js | 12 +++++++++++- test/querystring.t.mjs | 20 -------------------- test/text_decoder.t.js | 3 --- test/text_encoder.t.js | 9 --------- test/xml/saml_verify.t.mjs | 8 -------- test/zlib.t.mjs | 11 ++--------- 7 files changed, 17 insertions(+), 82 deletions(-) diff --git a/test/buffer.t.js b/test/buffer.t.js index 8e3f4ca9..01e25bed 100644 --- a/test/buffer.t.js +++ b/test/buffer.t.js @@ -3,13 +3,6 @@ includes: [compatBuffer.js, runTsuite.js, compareArray.js] flags: [async] ---*/ -function p(args, default_opts) { - let params = merge({}, default_opts); - params = merge(params, args); - - return params; -} - let alloc_tsuite = { name: "Buffer.alloc() tests", skip: () => (!has_buffer()), @@ -23,7 +16,6 @@ let alloc_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: { encoding: 'utf-8' }, tests: [ @@ -56,7 +48,6 @@ let byteLength_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: { encoding: 'utf-8' }, tests: [ @@ -95,7 +86,6 @@ let concat_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ { buffers: [ Buffer.from('abc'), @@ -122,7 +112,6 @@ let compare_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ @@ -161,7 +150,6 @@ let comparePrototype_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ @@ -212,7 +200,6 @@ let copy_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ @@ -249,7 +236,6 @@ let equals_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ @@ -285,7 +271,6 @@ let fill_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ { buf: Buffer.from('abc'), value: 0x61, expected: 'aaa' }, @@ -336,7 +321,7 @@ let from_tsuite = { return 'SUCCESS'; }, - prepare_args: p, + opts: { fmt: 'utf-8' }, tests: [ @@ -445,7 +430,6 @@ let includes_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ @@ -471,7 +455,6 @@ let indexOf_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ @@ -519,7 +502,7 @@ let isBuffer_tsuite = { return 'SUCCESS'; }, - prepare_args: p, + opts: {}, tests: [ @@ -543,7 +526,6 @@ let isEncoding_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ @@ -594,7 +576,6 @@ let lastIndexOf_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ @@ -659,7 +640,6 @@ let readXIntXX_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ @@ -719,7 +699,6 @@ let readFloat_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ @@ -748,7 +727,6 @@ let readGeneric_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ @@ -789,7 +767,6 @@ let slice_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ @@ -818,7 +795,6 @@ let subarray_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ @@ -841,7 +817,6 @@ let swap_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: { swap: 'swap16' }, tests: [ @@ -867,8 +842,8 @@ let toJSON_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, + tests: [ { value: '', expected: { type: 'Buffer', data: [] } }, { value: 'αβγ', expected: { type: 'Buffer', data: [0xCE, 0xB1, 0xCE, 0xB2, 0xCE, 0xB3] } }, @@ -893,7 +868,7 @@ let toString_tsuite = { return 'SUCCESS'; }, - prepare_args: p, + opts: { fmt: 'utf-8' }, tests: [ @@ -937,7 +912,6 @@ let write_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ @@ -981,7 +955,6 @@ let writeXIntXX_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ @@ -1022,7 +995,6 @@ let writeGeneric_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ diff --git a/test/harness/runTsuite.js b/test/harness/runTsuite.js index 1823e4a2..068a19e1 100644 --- a/test/harness/runTsuite.js +++ b/test/harness/runTsuite.js @@ -26,7 +26,10 @@ async function run(tlist) { return Promise.resolve("SKIPPED"); } - return ts.T(ts.prepare_args(t, ts.opts)); + let prepare_args = ts.prepare_args ? ts.prepare_args + : default_prepare_args; + + return ts.T(prepare_args(t, ts.opts)); } catch (e) { return Promise.reject(e); @@ -57,6 +60,13 @@ async function run(tlist) { } } +function default_prepare_args(args, default_opts) { + let params = merge({}, default_opts); + params = merge(params, args); + + return params; +} + function merge(to, from) { let r = Object.assign(Array.isArray(to) ? [] : {}, to); Object.keys(from).forEach(v => { diff --git a/test/querystring.t.mjs b/test/querystring.t.mjs index b382d10d..cb42305e 100644 --- a/test/querystring.t.mjs +++ b/test/querystring.t.mjs @@ -16,12 +16,7 @@ let escape_tsuite = { return 'SUCCESS'; }, - prepare_args: (args, default_opts) => { - let params = merge({}, default_opts); - params = merge(params, args); - return params; - }, opts: { }, tests: [ @@ -66,12 +61,7 @@ let parse_tsuite = { return 'SUCCESS'; }, - prepare_args: (args, default_opts) => { - let params = merge({}, default_opts); - params = merge(params, args); - return params; - }, opts: { }, tests: [ @@ -174,12 +164,7 @@ let stringify_tsuite = { return 'SUCCESS'; }, - prepare_args: (args, default_opts) => { - let params = merge({}, default_opts); - params = merge(params, args); - return params; - }, opts: { }, tests: [ @@ -230,12 +215,7 @@ let unescape_tsuite = { return 'SUCCESS'; }, - prepare_args: (args, default_opts) => { - let params = merge({}, default_opts); - params = merge(params, args); - return params; - }, opts: { }, tests: [ diff --git a/test/text_decoder.t.js b/test/text_decoder.t.js index 2eb879c0..a6fced2b 100644 --- a/test/text_decoder.t.js +++ b/test/text_decoder.t.js @@ -40,7 +40,6 @@ let stream_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ @@ -96,7 +95,6 @@ let fatal_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ @@ -126,7 +124,6 @@ let ignoreBOM_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ diff --git a/test/text_encoder.t.js b/test/text_encoder.t.js index e790ae37..10324bca 100644 --- a/test/text_encoder.t.js +++ b/test/text_encoder.t.js @@ -4,13 +4,6 @@ includes: [runTsuite.js, compareArray.js] flags: [async] ---*/ -function p(args, default_opts) { - let params = merge({}, default_opts); - params = merge(params, args); - - return params; -} - let encode_tsuite = { name: "TextEncoder() encode tests", T: async (params) => { @@ -33,7 +26,6 @@ let encode_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ @@ -66,7 +58,6 @@ let encodeinto_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ diff --git a/test/xml/saml_verify.t.mjs b/test/xml/saml_verify.t.mjs index 875fbd40..d6f06a14 100644 --- a/test/xml/saml_verify.t.mjs +++ b/test/xml/saml_verify.t.mjs @@ -271,18 +271,10 @@ async function signatureSAML(signature, key_data, produce) { signedInfoC14n); } -function p(args, default_opts) { - let params = merge({}, default_opts); - params = merge(params, args); - - return params; -} - let saml_verify_tsuite = { name: "SAML verify", skip: () => (!has_njs() || !has_webcrypto() || !has_xml()), T: verify, - prepare_args: p, opts: { key: { fmt: "spki", file: "rsa.spki" }, }, diff --git a/test/zlib.t.mjs b/test/zlib.t.mjs index d972f6f8..faef1fb0 100644 --- a/test/zlib.t.mjs +++ b/test/zlib.t.mjs @@ -5,13 +5,6 @@ flags: [async] import zlib from 'zlib'; -function p(args, default_opts) { - let params = merge({}, default_opts); - params = merge(params, args); - - return params; -} - let deflateSync_tsuite = { name: "deflateSync()/deflateRawSync() tests", skip: () => !zlib.deflateRawSync, @@ -29,7 +22,7 @@ let deflateSync_tsuite = { return 'SUCCESS'; }, - prepare_args: p, + opts: { raw: true }, tests: [ @@ -72,7 +65,7 @@ let inflateSync_tsuite = { return 'SUCCESS'; }, - prepare_args: p, + opts: { raw: true }, tests: [ From noreply at nginx.com Tue Mar 18 21:46:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Tue, 18 Mar 2025 21:46:02 +0000 (UTC) Subject: [njs] Test262: allowing to omit empty default option argument. Message-ID: <20250318214602.C9EEC4837A@pubserv1.nginx> details: https://github.com/nginx/njs/commit/3ad475cb9a0b24fc7bc49460ede80f4ec104c3fd branches: master commit: 3ad475cb9a0b24fc7bc49460ede80f4ec104c3fd user: Dmitry Volyntsev date: Tue, 25 Feb 2025 22:44:39 -0800 description: Test262: allowing to omit empty default option argument. --- test/buffer.t.js | 37 ------------------------------------- test/fs/methods.t.mjs | 4 ---- test/harness/runTsuite.js | 2 +- test/querystring.t.mjs | 8 -------- test/text_decoder.t.js | 6 ------ test/text_encoder.t.js | 4 ---- test/webcrypto/digest.t.mjs | 1 - test/webcrypto/import.t.mjs | 4 ---- test/webcrypto/rsa_decoding.t.mjs | 1 - 9 files changed, 1 insertion(+), 66 deletions(-) diff --git a/test/buffer.t.js b/test/buffer.t.js index 01e25bed..37821104 100644 --- a/test/buffer.t.js +++ b/test/buffer.t.js @@ -86,7 +86,6 @@ let concat_tsuite = { return 'SUCCESS'; }, - opts: {}, tests: [ { buffers: [ Buffer.from('abc'), Buffer.from(new Uint8Array([0x64, 0x65, 0x66]).buffer, 1) ], @@ -112,8 +111,6 @@ let compare_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { buf1: Buffer.from('abc'), buf2: Buffer.from('abc'), expected: 0 }, { buf1: Buffer.from('abc'), @@ -150,8 +147,6 @@ let comparePrototype_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { buf: Buffer.from('abc'), target: Buffer.from('abc'), expected: 0 }, { buf: Buffer.from('abc'), @@ -200,8 +195,6 @@ let copy_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { buf: Buffer.from('abcdef'), target: Buffer.from('123456'), expected: 6, expected_buf: 'abcdef' }, @@ -236,7 +229,6 @@ let equals_tsuite = { return 'SUCCESS'; }, - opts: {}, tests: [ { buf1: Buffer.from('abc'), buf2: Buffer.from('abc'), expected: true }, @@ -271,7 +263,6 @@ let fill_tsuite = { return 'SUCCESS'; }, - opts: {}, tests: [ { buf: Buffer.from('abc'), value: 0x61, expected: 'aaa' }, { buf: Buffer.from('abc'), value: 0x61, expected: 'aaa', offset: 0, end: 3 }, @@ -430,8 +421,6 @@ let includes_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { buf: Buffer.from('abcdef'), value: 'abc', expected: true }, { buf: Buffer.from('abcdef'), value: 'def', expected: true }, @@ -455,8 +444,6 @@ let indexOf_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { buf: Buffer.from('abcdef'), value: 'abc', expected: 0 }, { buf: Buffer.from('abcdef'), value: 'def', expected: 3 }, @@ -503,8 +490,6 @@ let isBuffer_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { value: Buffer.from('α'), expected: true }, { value: new Uint8Array(10), expected: false }, @@ -526,8 +511,6 @@ let isEncoding_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { value: 'utf-8', expected: true }, { value: 'utf8', expected: true }, @@ -576,8 +559,6 @@ let lastIndexOf_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { buf: Buffer.from('abcdef'), value: 'abc', expected: 0 }, { buf: Buffer.from('abcabc'), value: 'abc', expected: 3 }, @@ -640,8 +621,6 @@ let readXIntXX_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { buf: Buffer.from([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]), offset: 0, expected: [ -86,170,-17494,-21829,48042,43707,-573785174,-1430532899,3721182122,2864434397 ] }, @@ -699,8 +678,6 @@ let readFloat_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ {} ], @@ -727,8 +704,6 @@ let readGeneric_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { buf: Buffer.from([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]), offset: 0, length: 1, expected: [ 170, 170, -86, -86 ] }, @@ -767,8 +742,6 @@ let slice_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { buf: Buffer.from('abcdef'), start: 1, expected: 'bcdef' }, { buf: Buffer.from('abcdef'), start: 1, end: 3, expected: 'bc' }, @@ -795,8 +768,6 @@ let subarray_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { buf: Buffer.from('abcdef'), start: 0, end: 3, expected: 'Zbc' }, { buf: Buffer.from('abcdef'), start: 1, expected: 'bcdef' }, @@ -842,8 +813,6 @@ let toJSON_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { value: '', expected: { type: 'Buffer', data: [] } }, { value: 'αβγ', expected: { type: 'Buffer', data: [0xCE, 0xB1, 0xCE, 0xB2, 0xCE, 0xB3] } }, @@ -912,8 +881,6 @@ let write_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { value: 'abc', expected: 3, expected_buf: 'abcZZZZZZZ' }, { value: 'abc', offset: 1, expected: 3, expected_buf: 'ZabcZZZZZZ' }, @@ -955,8 +922,6 @@ let writeXIntXX_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { write: 'writeInt8', value: 0xaa, exception: 'RangeError: Index out of range' }, { write: 'writeInt8', value: 0x00, offset: 3, expected: 4, expected_buf: '5a5a5a005a5a5a5a5a5a' }, @@ -995,8 +960,6 @@ let writeGeneric_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { write: 'writeUIntLE', value: 0xaa, length: 1, exception: 'RangeError: Index out of range' }, diff --git a/test/fs/methods.t.mjs b/test/fs/methods.t.mjs index 928d1682..705c1b5a 100644 --- a/test/fs/methods.t.mjs +++ b/test/fs/methods.t.mjs @@ -1008,7 +1008,6 @@ let readSync_tsuite = { skip: () => (!has_buffer()), T: read_test, prepare_args: p, - opts: {}, get tests() { return read_tests() }, }; @@ -1017,7 +1016,6 @@ let readFh_tsuite = { skip: () => (!has_buffer()), T: readFh_test, prepare_args: p, - opts: {}, get tests() { return read_tests() }, }; @@ -1213,7 +1211,6 @@ let writeSync_tsuite = { skip: () => (!has_buffer()), T: write_test, prepare_args: p, - opts: {}, get tests() { return write_tests() }, }; @@ -1222,7 +1219,6 @@ let writeFh_tsuite = { skip: () => (!has_buffer()), T: writeFh_test, prepare_args: p, - opts: {}, get tests() { return write_tests() }, }; diff --git a/test/harness/runTsuite.js b/test/harness/runTsuite.js index 068a19e1..2a324fbe 100644 --- a/test/harness/runTsuite.js +++ b/test/harness/runTsuite.js @@ -29,7 +29,7 @@ async function run(tlist) { let prepare_args = ts.prepare_args ? ts.prepare_args : default_prepare_args; - return ts.T(prepare_args(t, ts.opts)); + return ts.T(prepare_args(t, ts.opts ? ts.opts : {})); } catch (e) { return Promise.reject(e); diff --git a/test/querystring.t.mjs b/test/querystring.t.mjs index cb42305e..8ea14050 100644 --- a/test/querystring.t.mjs +++ b/test/querystring.t.mjs @@ -17,8 +17,6 @@ let escape_tsuite = { return 'SUCCESS'; }, - opts: { }, - tests: [ { value: '', expected: '' }, { value: 'baz=fuz', expected: 'baz%3Dfuz' }, @@ -62,8 +60,6 @@ let parse_tsuite = { return 'SUCCESS'; }, - opts: { }, - tests: [ { value: '', expected: {} }, { value: 'baz=fuz', expected: { baz:'fuz' } }, @@ -165,8 +161,6 @@ let stringify_tsuite = { return 'SUCCESS'; }, - opts: { }, - tests: [ { obj: {}, expected: '' }, { obj: { baz:'fuz', muz:'tax' }, expected: 'baz=fuz&muz=tax' }, @@ -216,8 +210,6 @@ let unescape_tsuite = { return 'SUCCESS'; }, - opts: { }, - tests: [ { value: '', expected: '' }, { value: 'baz%3Dfuz', expected: 'baz=fuz' }, diff --git a/test/text_decoder.t.js b/test/text_decoder.t.js index a6fced2b..afc91fc4 100644 --- a/test/text_decoder.t.js +++ b/test/text_decoder.t.js @@ -40,8 +40,6 @@ let stream_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { chunks: [new Uint8Array([0xF0, 0x9F, 0x8C, 0x9F])], expected: ['🌟'] }, @@ -95,8 +93,6 @@ let fatal_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { chunks: [new Uint8Array([0xF0, 0xA0, 0xAE, 0xB7])], expected: ['𠮷'] }, @@ -124,8 +120,6 @@ let ignoreBOM_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { value: new Uint8Array([239, 187, 191, 50]), opts: {ignoreBOM: true}, diff --git a/test/text_encoder.t.js b/test/text_encoder.t.js index 10324bca..25173cd1 100644 --- a/test/text_encoder.t.js +++ b/test/text_encoder.t.js @@ -26,8 +26,6 @@ let encode_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { value: "", expected: [] }, { value: "abc", expected: [97, 98, 99] }, @@ -58,8 +56,6 @@ let encodeinto_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { value: "", dest: new Uint8Array(4), expected: [], read: 0 }, { value: "aα", dest: new Uint8Array(3), expected: [97, 206, 177], read: 2 }, diff --git a/test/webcrypto/digest.t.mjs b/test/webcrypto/digest.t.mjs index 4318fe16..f07f0346 100644 --- a/test/webcrypto/digest.t.mjs +++ b/test/webcrypto/digest.t.mjs @@ -25,7 +25,6 @@ let digest_tsuite = { skip: () => (!has_buffer() || !has_webcrypto()), T: test, prepare_args: p, - opts: { }, tests: [ { name: "XXX", data: "", diff --git a/test/webcrypto/import.t.mjs b/test/webcrypto/import.t.mjs index 1b804006..9ec0fb60 100644 --- a/test/webcrypto/import.t.mjs +++ b/test/webcrypto/import.t.mjs @@ -85,7 +85,6 @@ let aes_tsuite = { skip: () => (!has_webcrypto()), T: test, prepare_args: p, - opts: {}, tests: [ { key: { fmt: "jwk", @@ -186,7 +185,6 @@ let ec_tsuite = { skip: () => (!has_webcrypto()), T: test, prepare_args: p, - opts: {}, tests: [ { key: { fmt: "jwk", @@ -350,7 +348,6 @@ let hmac_tsuite = { skip: () => (!has_webcrypto()), T: test, prepare_args: p, - opts: {}, tests: [ { key: { fmt: "raw", @@ -465,7 +462,6 @@ let rsa_tsuite = { skip: () => (!has_webcrypto()), T: test, prepare_args: p, - opts: {}, tests: [ { key: { fmt: "jwk", diff --git a/test/webcrypto/rsa_decoding.t.mjs b/test/webcrypto/rsa_decoding.t.mjs index 08874491..179072ff 100644 --- a/test/webcrypto/rsa_decoding.t.mjs +++ b/test/webcrypto/rsa_decoding.t.mjs @@ -29,7 +29,6 @@ let rsa_tsuite = { name: "RSA-OAEP decoding", T: test, prepare_args: (v) => v, - opts: { }, tests: [ { pem: "rsa.pkcs8", src: "text.base64.rsa-oaep.enc", expected: "WAKAWAKA" }, From noreply at nginx.com Tue Mar 18 21:46:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Tue, 18 Mar 2025 21:46:02 +0000 (UTC) Subject: [njs] QuickJS: crypto module. Message-ID: <20250318214602.D78D34837C@pubserv1.nginx> details: https://github.com/nginx/njs/commit/d5359d17f151bf172697e2ac353377b469b64c0f branches: master commit: d5359d17f151bf172697e2ac353377b469b64c0f user: Vadim Zhestikov date: Wed, 5 Mar 2025 18:54:41 -0800 description: QuickJS: crypto module. --- auto/qjs_modules | 6 + external/qjs_crypto_module.c | 659 ++++++++++++++++++++++++++++++++++++++++ external/qjs_webcrypto_module.c | 23 -- src/qjs.c | 103 +++++++ src/qjs.h | 21 +- src/test/njs_unit_test.c | 265 ---------------- test/crypto.t.mjs | 447 +++++++++++++++++++++++++++ 7 files changed, 1235 insertions(+), 289 deletions(-) diff --git a/auto/qjs_modules b/auto/qjs_modules index d82f9d9f..1381d2c5 100644 --- a/auto/qjs_modules +++ b/auto/qjs_modules @@ -7,6 +7,12 @@ njs_module_srcs=src/qjs_buffer.c . auto/qjs_module +njs_module_name=qjs_crypto_module +njs_module_incs= +njs_module_srcs=external/qjs_crypto_module.c + +. auto/qjs_module + njs_module_name=qjs_fs_module njs_module_incs= njs_module_srcs=external/qjs_fs_module.c diff --git a/external/qjs_crypto_module.c b/external/qjs_crypto_module.c new file mode 100644 index 00000000..2b1bb26b --- /dev/null +++ b/external/qjs_crypto_module.c @@ -0,0 +1,659 @@ + +/* + * Copyright (C) Vadim Zhestkov + * Copyright (C) F5, Inc. + */ + +#include +#include "njs_hash.h" + +typedef void (*qjs_hash_init)(njs_hash_t *ctx); +typedef void (*qjs_hash_update)(njs_hash_t *ctx, const void *data, size_t size); +typedef void (*qjs_hash_final)(u_char result[32], njs_hash_t *ctx); + +typedef JSValue (*qjs_digest_encode)(JSContext *cx, const njs_str_t *src); + + +typedef struct { + njs_str_t name; + + size_t size; + qjs_hash_init init; + qjs_hash_update update; + qjs_hash_final final; +} qjs_hash_alg_t; + +typedef struct { + njs_hash_t ctx; + qjs_hash_alg_t *alg; +} qjs_digest_t; + +typedef struct { + u_char opad[64]; + njs_hash_t ctx; + qjs_hash_alg_t *alg; +} qjs_hmac_t; + + +typedef struct { + njs_str_t name; + + qjs_digest_encode encode; +} qjs_crypto_enc_t; + + +static qjs_hash_alg_t *qjs_crypto_algorithm(JSContext *cx, JSValueConst val); +static qjs_crypto_enc_t *qjs_crypto_encoding(JSContext *cx, JSValueConst val); +static JSValue qjs_buffer_digest(JSContext *cx, const njs_str_t *src); +static JSValue qjs_crypto_create_hash(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv); +static JSValue qjs_hash_prototype_update(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv, int hmac); +static JSValue qjs_hash_prototype_digest(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv, int hmac); +static JSValue qjs_hash_prototype_copy(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv); +static JSValue qjs_crypto_create_hmac(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv); +static void qjs_hash_finalizer(JSRuntime *rt, JSValue val); +static void qjs_hmac_finalizer(JSRuntime *rt, JSValue val); +static int qjs_crypto_module_init(JSContext *cx, JSModuleDef *m); +static JSModuleDef * qjs_crypto_init(JSContext *cx, const char *module_name); + + +static qjs_hash_alg_t qjs_hash_algorithms[] = { + + { + njs_str("md5"), + 16, + njs_md5_init, + njs_md5_update, + njs_md5_final + }, + + { + njs_str("sha1"), + 20, + njs_sha1_init, + njs_sha1_update, + njs_sha1_final + }, + + { + njs_str("sha256"), + 32, + njs_sha2_init, + njs_sha2_update, + njs_sha2_final + }, + + { + njs_null_str, + 0, + NULL, + NULL, + NULL + } + +}; + + +static qjs_crypto_enc_t qjs_encodings[] = { + + { + njs_str("buffer"), + qjs_buffer_digest + }, + + { + njs_str("hex"), + qjs_string_hex + }, + + { + njs_str("base64"), + qjs_string_base64 + }, + + { + njs_str("base64url"), + qjs_string_base64url + }, + + { + njs_null_str, + NULL + } + +}; + + +static const JSCFunctionListEntry qjs_crypto_export[] = { + JS_CFUNC_DEF("createHash", 1, qjs_crypto_create_hash), + JS_CFUNC_DEF("createHmac", 2, qjs_crypto_create_hmac), +}; + + +static const JSCFunctionListEntry qjs_hash_proto_proto[] = { + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Hash", JS_PROP_CONFIGURABLE), + JS_CFUNC_MAGIC_DEF("update", 2, qjs_hash_prototype_update, 0), + JS_CFUNC_MAGIC_DEF("digest", 1, qjs_hash_prototype_digest, 0), + JS_CFUNC_DEF("copy", 0, qjs_hash_prototype_copy), + JS_CFUNC_DEF("constructor", 1, qjs_crypto_create_hash), +}; + + +static const JSCFunctionListEntry qjs_hmac_proto_proto[] = { + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Hmac", JS_PROP_CONFIGURABLE), + JS_CFUNC_MAGIC_DEF("update", 2, qjs_hash_prototype_update, 1), + JS_CFUNC_MAGIC_DEF("digest", 1, qjs_hash_prototype_digest, 1), + JS_CFUNC_DEF("constructor", 2, qjs_crypto_create_hmac), +}; + + +static JSClassDef qjs_hash_class = { + "Hash", + .finalizer = qjs_hash_finalizer, +}; + + +static JSClassDef qjs_hmac_class = { + "Hmac", + .finalizer = qjs_hmac_finalizer, +}; + + +qjs_module_t qjs_crypto_module = { + .name = "crypto", + .init = qjs_crypto_init, +}; + + +static JSValue +qjs_crypto_create_hash(JSContext *cx, JSValueConst this_val, int argc, + JSValueConst *argv) +{ + JSValue obj; + qjs_digest_t *dgst; + qjs_hash_alg_t *alg; + + alg = qjs_crypto_algorithm(cx, argv[0]); + if (alg == NULL) { + return JS_EXCEPTION; + } + + dgst = js_malloc(cx, sizeof(qjs_digest_t)); + if (dgst == NULL) { + return JS_ThrowOutOfMemory(cx); + } + + dgst->alg = alg; + alg->init(&dgst->ctx); + + obj = JS_NewObjectClass(cx, QJS_CORE_CLASS_CRYPTO_HASH); + if (JS_IsException(obj)) { + js_free(cx, dgst); + return obj; + } + + JS_SetOpaque(obj, dgst); + + return obj; +} + + +static JSValue +qjs_hash_prototype_update(JSContext *cx, JSValueConst this_val, int argc, + JSValueConst *argv, int hmac) +{ + njs_str_t str, content; + njs_hash_t *uctx; + qjs_hmac_t *hctx; + qjs_bytes_t bytes; + qjs_digest_t *dgst; + const qjs_buffer_encoding_t *enc; + + void (*update)(njs_hash_t *ctx, const void *data, size_t size); + + if (!hmac) { + dgst = JS_GetOpaque2(cx, this_val, QJS_CORE_CLASS_CRYPTO_HASH); + if (dgst == NULL) { + return JS_ThrowTypeError(cx, "\"this\" is not a hash object"); + } + + if (dgst->alg == NULL) { + return JS_ThrowTypeError(cx, "Digest already called"); + } + + update = dgst->alg->update; + uctx = &dgst->ctx; + + } else { + hctx = JS_GetOpaque2(cx, this_val, QJS_CORE_CLASS_CRYPTO_HMAC); + if (hctx == NULL) { + return JS_ThrowTypeError(cx, "\"this\" is not a hmac object"); + } + + if (hctx->alg == NULL) { + return JS_ThrowTypeError(cx, "Digest already called"); + } + + update = hctx->alg->update; + uctx = &hctx->ctx; + } + + if (JS_IsString(argv[0])) { + enc = qjs_buffer_encoding(cx, argv[1], 1); + if (enc == NULL) { + return JS_EXCEPTION; + } + + str.start = (u_char *) JS_ToCStringLen(cx, &str.length, argv[0]); + if (str.start == NULL) { + return JS_EXCEPTION; + } + + if (enc->decode_length != NULL) { + content.length = enc->decode_length(cx, &str); + content.start = js_malloc(cx, content.length); + if (content.start == NULL) { + JS_FreeCString(cx, (const char *) str.start); + return JS_ThrowOutOfMemory(cx); + } + + if (enc->decode(cx, &str, &content) != 0) { + JS_FreeCString(cx, (const char *) str.start); + JS_FreeCString(cx, (const char *) content.start); + return JS_EXCEPTION; + } + + JS_FreeCString(cx, (const char *) str.start); + + update(uctx, content.start, content.length); + js_free(cx, content.start); + + } else { + update(uctx, str.start, str.length); + JS_FreeCString(cx, (const char *) str.start); + } + + } else if (qjs_is_typed_array(cx, argv[0])) { + if (qjs_to_bytes(cx, &bytes, argv[0]) != 0) { + return JS_EXCEPTION; + } + + update(uctx, bytes.start, bytes.length); + + } else { + return JS_ThrowTypeError(cx, + "data is not a string or Buffer-like object"); + } + + return JS_DupValue(cx, this_val); +} + + +static JSValue +qjs_hash_prototype_digest(JSContext *cx, JSValueConst this_val, int argc, + JSValueConst *argv, int hmac) +{ + njs_str_t str; + qjs_hmac_t *hctx; + qjs_digest_t *dgst; + qjs_hash_alg_t *alg; + qjs_crypto_enc_t *enc; + u_char hash1[32],digest[32]; + + if (!hmac) { + dgst = JS_GetOpaque2(cx, this_val, QJS_CORE_CLASS_CRYPTO_HASH); + if (dgst == NULL) { + return JS_ThrowTypeError(cx, "\"this\" is not a hash object"); + } + + alg = dgst->alg; + if (alg == NULL) { + return JS_ThrowTypeError(cx, "Digest already called"); + } + + dgst->alg = NULL; + + alg->final(digest, &dgst->ctx); + + } else { + hctx = JS_GetOpaque2(cx, this_val, QJS_CORE_CLASS_CRYPTO_HMAC); + if (hctx == NULL) { + return JS_ThrowTypeError(cx, "\"this\" is not a hmac object"); + } + + alg = hctx->alg; + if (alg == NULL) { + return JS_ThrowTypeError(cx, "Digest already called"); + } + + hctx->alg = NULL; + + alg->final(hash1, &hctx->ctx); + + alg->init(&hctx->ctx); + alg->update(&hctx->ctx, hctx->opad, 64); + alg->update(&hctx->ctx, hash1, alg->size); + alg->final(digest, &hctx->ctx); + } + + str.start = digest; + str.length = alg->size; + + if (argc == 0) { + return qjs_buffer_digest(cx, &str); + } + + enc = qjs_crypto_encoding(cx, argv[0]); + if (enc == NULL) { + return JS_EXCEPTION; + } + + return enc->encode(cx, &str); +} + + +static JSValue +qjs_hash_prototype_copy(JSContext *cx, JSValueConst this_val, int argc, + JSValueConst *argv) +{ + JSValue obj; + qjs_digest_t *dgst, *copy; + + dgst = JS_GetOpaque2(cx, this_val, QJS_CORE_CLASS_CRYPTO_HASH); + if (dgst == NULL) { + return JS_EXCEPTION; + } + + if (dgst->alg == NULL) { + return JS_ThrowTypeError(cx, "Digest already called"); + } + + copy = js_malloc(cx, sizeof(qjs_digest_t)); + if (copy == NULL) { + return JS_ThrowOutOfMemory(cx); + } + + memcpy(copy, dgst, sizeof(qjs_digest_t)); + + obj = JS_NewObjectClass(cx, QJS_CORE_CLASS_CRYPTO_HASH); + if (JS_IsException(obj)) { + js_free(cx, copy); + return obj; + } + + JS_SetOpaque(obj, copy); + + return obj; +} + + +static JSValue +qjs_crypto_create_hmac(JSContext *cx, JSValueConst this_val, int argc, + JSValueConst *argv) +{ + int i; + JS_BOOL key_is_string; + JSValue obj; + njs_str_t key; + qjs_hmac_t *hmac; + qjs_bytes_t bytes; + qjs_hash_alg_t *alg; + u_char digest[32], key_buf[64]; + + alg = qjs_crypto_algorithm(cx, argv[0]); + if (alg == NULL) { + return JS_EXCEPTION; + } + + key_is_string = JS_IsString(argv[1]); + if (key_is_string) { + key.start = (u_char *) JS_ToCStringLen(cx, &key.length, argv[1]); + if (key.start == NULL) { + return JS_EXCEPTION; + } + + } else if (qjs_is_typed_array(cx, argv[1])) { + if (qjs_to_bytes(cx, &bytes, argv[1]) != 0) { + return JS_EXCEPTION; + } + + key.start = bytes.start; + key.length = bytes.length; + + } else { + return JS_ThrowTypeError(cx, + "key is not a string or Buffer-like object"); + } + + hmac = js_malloc(cx, sizeof(qjs_hmac_t)); + if (hmac == NULL) { + if (key_is_string) { + JS_FreeCString(cx, (const char *) key.start); + } + + return JS_ThrowOutOfMemory(cx); + } + + hmac->alg = alg; + + if (key.length > sizeof(key_buf)) { + alg->init(&hmac->ctx); + alg->update(&hmac->ctx, key.start, key.length); + alg->final(digest, &hmac->ctx); + + memcpy(key_buf, digest, alg->size); + memset(key_buf + alg->size, 0, sizeof(key_buf) - alg->size); + + } else { + memcpy(key_buf, key.start, key.length); + memset(key_buf + key.length, 0, sizeof(key_buf) - key.length); + } + + if (key_is_string) { + JS_FreeCString(cx, (const char *) key.start); + } + + for (i = 0; i < 64; i++) { + hmac->opad[i] = key_buf[i] ^ 0x5c; + } + + for (i = 0; i < 64; i++) { + key_buf[i] ^= 0x36; + } + + alg->init(&hmac->ctx); + alg->update(&hmac->ctx, key_buf, 64); + + obj = JS_NewObjectClass(cx, QJS_CORE_CLASS_CRYPTO_HMAC); + if (JS_IsException(obj)) { + js_free(cx, hmac); + return obj; + } + + JS_SetOpaque(obj, hmac); + + return obj; +} + + +static void +qjs_hash_finalizer(JSRuntime *rt, JSValue val) +{ + qjs_digest_t *dgst; + + dgst = JS_GetOpaque(val, QJS_CORE_CLASS_CRYPTO_HASH); + if (dgst != NULL) { + js_free_rt(rt, dgst); + } +} + + +static void +qjs_hmac_finalizer(JSRuntime *rt, JSValue val) +{ + qjs_hmac_t *hmac; + + hmac = JS_GetOpaque(val, QJS_CORE_CLASS_CRYPTO_HMAC); + if (hmac != NULL) { + js_free_rt(rt, hmac); + } +} + + +static qjs_hash_alg_t * +qjs_crypto_algorithm(JSContext *cx, JSValueConst val) +{ + njs_str_t name; + qjs_hash_alg_t *a, *alg; + + name.start = (u_char *) JS_ToCStringLen(cx, &name.length, val); + if (name.start == NULL) { + JS_ThrowTypeError(cx, "algorithm must be a string"); + return NULL; + } + + alg = NULL; + + for (a = &qjs_hash_algorithms[0]; a->name.start != NULL; a++) { + if (njs_strstr_eq(&name, &a->name)) { + alg = a; + break; + } + } + + JS_FreeCString(cx, (const char *) name.start); + + if (alg == NULL) { + JS_ThrowTypeError(cx, "not supported algorithm"); + } + + return alg; +} + + +static qjs_crypto_enc_t * +qjs_crypto_encoding(JSContext *cx, JSValueConst val) +{ + njs_str_t name; + qjs_crypto_enc_t *e, *enc; + + if (JS_IsNullOrUndefined(val)) { + return &qjs_encodings[0]; + } + + name.start = (u_char *) JS_ToCStringLen(cx, &name.length, val); + if (name.start == NULL) { + return NULL; + } + + enc = NULL; + + for (e = &qjs_encodings[1]; e->name.start != NULL; e++) { + if (njs_strstr_eq(&name, &e->name)) { + enc = e; + break; + } + } + + JS_FreeCString(cx, (const char *) name.start); + + if (enc == NULL) { + JS_ThrowTypeError(cx, "Unknown digest encoding"); + } + + return enc; +} + + +static JSValue +qjs_buffer_digest(JSContext *cx, const njs_str_t *src) +{ + return qjs_buffer_create(cx, src->start, src->length); +} + + +static int +qjs_crypto_module_init(JSContext *cx, JSModuleDef *m) +{ + JSValue crypto_obj; + + crypto_obj = JS_NewObject(cx); + if (JS_IsException(crypto_obj)) { + return -1; + } + + JS_SetPropertyFunctionList(cx, crypto_obj, qjs_crypto_export, + njs_nitems(qjs_crypto_export)); + + if (JS_SetModuleExport(cx, m, "default", crypto_obj) != 0) { + return -1; + } + + return JS_SetModuleExportList(cx, m, qjs_crypto_export, + njs_nitems(qjs_crypto_export)); +} + + +static JSModuleDef * +qjs_crypto_init(JSContext *cx, const char *module_name) +{ + JSValue proto; + JSModuleDef *m; + + if (!JS_IsRegisteredClass(JS_GetRuntime(cx), + QJS_CORE_CLASS_CRYPTO_HASH)) + { + if (JS_NewClass(JS_GetRuntime(cx), QJS_CORE_CLASS_CRYPTO_HASH, + &qjs_hash_class) < 0) + { + return NULL; + } + + proto = JS_NewObject(cx); + if (JS_IsException(proto)) { + return NULL; + } + + JS_SetPropertyFunctionList(cx, proto, qjs_hash_proto_proto, + njs_nitems(qjs_hash_proto_proto)); + + JS_SetClassProto(cx, QJS_CORE_CLASS_CRYPTO_HASH, proto); + + if (JS_NewClass(JS_GetRuntime(cx), QJS_CORE_CLASS_CRYPTO_HMAC, + &qjs_hmac_class) < 0) + { + return NULL; + } + + proto = JS_NewObject(cx); + if (JS_IsException(proto)) { + return NULL; + } + + JS_SetPropertyFunctionList(cx, proto, qjs_hmac_proto_proto, + njs_nitems(qjs_hmac_proto_proto)); + + JS_SetClassProto(cx, QJS_CORE_CLASS_CRYPTO_HMAC, proto); + } + + m = JS_NewCModule(cx, module_name, qjs_crypto_module_init); + if (m == NULL) { + return NULL; + } + + if (JS_AddModuleExport(cx, m, "default") < 0) { + return NULL; + } + + if (JS_AddModuleExportList(cx, m, qjs_crypto_export, + njs_nitems(qjs_crypto_export)) != 0) + { + return NULL; + } + + return m; +} diff --git a/external/qjs_webcrypto_module.c b/external/qjs_webcrypto_module.c index 730feb6e..a1983651 100644 --- a/external/qjs_webcrypto_module.c +++ b/external/qjs_webcrypto_module.c @@ -1238,29 +1238,6 @@ fail: } -static JSValue -qjs_string_base64url(JSContext *cx, const njs_str_t *src) -{ - size_t padding; - njs_str_t dst; - u_char buf[/* qjs_base64_encoded_length(512) */ 686]; - - if (src->length == 0) { - return JS_NewStringLen(cx, "", 0); - } - - padding = src->length % 3; - padding = (4 >> padding) & 0x03; - - dst.start = buf; - dst.length = qjs_base64_encode_length(cx, src) - padding; - - qjs_base64url_encode(cx, src, &dst); - - return JS_NewStringLen(cx, (const char *) dst.start, dst.length); -} - - static JSValue qjs_export_base64url_bignum(JSContext *cx, const BIGNUM *v, size_t size) { diff --git a/src/qjs.c b/src/qjs.c index ed9a6178..15a575a2 100644 --- a/src/qjs.c +++ b/src/qjs.c @@ -1025,3 +1025,106 @@ qjs_string_create_chb(JSContext *cx, njs_chb_t *chain) return val; } + + +JSValue +qjs_string_hex(JSContext *cx, const njs_str_t *src) +{ + JSValue ret; + njs_str_t dst; + u_char buf[1024]; + + if (src->length == 0) { + return JS_NewStringLen(cx, "", 0); + } + + dst.start = buf; + dst.length = qjs_hex_encode_length(cx, src); + + if (dst.length <= sizeof(buf)) { + qjs_hex_encode(cx, src, &dst); + ret = JS_NewStringLen(cx, (const char *) dst.start, dst.length); + + } else { + dst.start = js_malloc(cx, dst.length); + if (dst.start == NULL) { + return JS_ThrowOutOfMemory(cx); + } + + qjs_hex_encode(cx, src, &dst); + ret = JS_NewStringLen(cx, (const char *) dst.start, dst.length); + js_free(cx, dst.start); + } + + return ret; +} + + +JSValue +qjs_string_base64(JSContext *cx, const njs_str_t *src) +{ + JSValue ret; + njs_str_t dst; + u_char buf[1024]; + + if (src->length == 0) { + return JS_NewStringLen(cx, "", 0); + } + + dst.start = buf; + dst.length = qjs_base64_encode_length(cx, src); + + if (dst.length <= sizeof(buf)) { + qjs_base64_encode(cx, src, &dst); + ret = JS_NewStringLen(cx, (const char *) dst.start, dst.length); + + } else { + dst.start = js_malloc(cx, dst.length); + if (dst.start == NULL) { + return JS_ThrowOutOfMemory(cx); + } + + qjs_base64_encode(cx, src, &dst); + ret = JS_NewStringLen(cx, (const char *) dst.start, dst.length); + js_free(cx, dst.start); + } + + return ret; +} + + +JSValue +qjs_string_base64url(JSContext *cx, const njs_str_t *src) +{ + size_t padding; + JSValue ret; + njs_str_t dst; + u_char buf[1024]; + + if (src->length == 0) { + return JS_NewStringLen(cx, "", 0); + } + + padding = src->length % 3; + padding = (4 >> padding) & 0x03; + + dst.start = buf; + dst.length = qjs_base64_encode_length(cx, src) - padding; + + if (dst.length <= sizeof(buf)) { + qjs_base64url_encode(cx, src, &dst); + ret = JS_NewStringLen(cx, (const char *) dst.start, dst.length); + + } else { + dst.start = js_malloc(cx, dst.length); + if (dst.start == NULL) { + return JS_ThrowOutOfMemory(cx); + } + + qjs_base64url_encode(cx, src, &dst); + ret = JS_NewStringLen(cx, (const char *) dst.start, dst.length); + js_free(cx, dst.start); + } + + return ret; +} diff --git a/src/qjs.h b/src/qjs.h index 7c560d5f..54f96dfe 100644 --- a/src/qjs.h +++ b/src/qjs.h @@ -42,7 +42,9 @@ #define QJS_CORE_CLASS_ID_FS_DIRENT (QJS_CORE_CLASS_ID_OFFSET + 5) #define QJS_CORE_CLASS_ID_FS_FILEHANDLE (QJS_CORE_CLASS_ID_OFFSET + 6) #define QJS_CORE_CLASS_ID_WEBCRYPTO_KEY (QJS_CORE_CLASS_ID_OFFSET + 7) -#define QJS_CORE_CLASS_ID_LAST (QJS_CORE_CLASS_ID_OFFSET + 8) +#define QJS_CORE_CLASS_CRYPTO_HASH (QJS_CORE_CLASS_ID_OFFSET + 8) +#define QJS_CORE_CLASS_CRYPTO_HMAC (QJS_CORE_CLASS_ID_OFFSET + 9) +#define QJS_CORE_CLASS_ID_LAST (QJS_CORE_CLASS_ID_OFFSET + 10) typedef JSModuleDef *(*qjs_addon_init_pt)(JSContext *ctx, const char *name); @@ -102,6 +104,20 @@ typedef struct { u_char *start; } qjs_bytes_t; + +njs_inline int +qjs_is_typed_array(JSContext *cx, JSValue val) +{ + JS_BOOL exception; + + val = JS_GetTypedArrayBuffer(cx, val, NULL, NULL, NULL); + exception = JS_IsException(val); + JS_FreeValue(cx, val); + + return !exception; +} + + int qjs_to_bytes(JSContext *ctx, qjs_bytes_t *data, JSValueConst value); void qjs_bytes_free(JSContext *ctx, qjs_bytes_t *data); JSValue qjs_typed_array_data(JSContext *ctx, JSValueConst value, @@ -111,6 +127,9 @@ JSValue qjs_typed_array_data(JSContext *ctx, JSValueConst value, JS_NewStringLen(ctx, (const char *) (data), len) JSValue qjs_string_create_chb(JSContext *cx, njs_chb_t *chain); +JSValue qjs_string_hex(JSContext *cx, const njs_str_t *src); +JSValue qjs_string_base64(JSContext *cx, const njs_str_t *src); +JSValue qjs_string_base64url(JSContext *cx, const njs_str_t *src); static inline JS_BOOL JS_IsNullOrUndefined(JSValueConst v) { diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index 4f5e5c91..f39660f5 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -20239,265 +20239,6 @@ static njs_unit_test_t njs_fs_module_test[] = }; -static njs_unit_test_t njs_crypto_module_test[] = -{ - { njs_str("import x from 'crypto'"), - njs_str("undefined") }, - - { njs_str("import x from 'crypto' 1"), - njs_str("SyntaxError: Unexpected token \"1\" in 1") }, - - { njs_str("if (1) {import x from 'crypto'}"), - njs_str("SyntaxError: Illegal import statement in 1") }, - - { njs_str("var h = require('crypto').createHash('sha1');" - "[Object.prototype.toString.call(h), njs.dump(h),h]"), - njs_str("[object Hash],Hash {},[object Hash]") }, - - { njs_str("var h = require('crypto').createHash('sha1');" - "var Hash = h.constructor; " - "Hash('sha1').update('AB').digest('hex')"), - njs_str("06d945942aa26a61be18c3e22bf19bbca8dd2b5d") }, - - { njs_str("var hash = require('crypto').createHash.bind(undefined, 'md5');" - "['hex', 'base64', 'base64url'].map(e => {" - " var h = hash().update('AB').digest().toString(e);" - " var h2 = hash().update(Buffer.from('XABX').subarray(1,3)).digest(e);" - " var h3 = hash().update('A').update('B').digest(e);" - " if (h !== h2) {throw new Error(`digest().toString($e):$h != digest($e):$h2`)};" - " if (h !== h3) {throw new Error(`digest().toString($e):$h != update('A').update('B').digest($e):$h3`)};" - " return h;" - "})"), - njs_str("b86fc6b051f63d73de262d4c34e3a0a9," - "uG/GsFH2PXPeJi1MNOOgqQ==," - "uG_GsFH2PXPeJi1MNOOgqQ") }, - - { njs_str("var hash = require('crypto').createHash.bind(undefined, 'sha1');" - "['hex', 'base64', 'base64url'].map(e => {" - " var h = hash().update('4142', 'hex').digest().toString(e);" - " var h2 = hash().update(Buffer.from('XABX').subarray(1,3)).digest(e);" - " var h3 = hash().update('A').update('B').digest(e);" - " if (h !== h2) {throw new Error(`digest().toString($e):$h != digest($e):$h2`)};" - " if (h !== h3) {throw new Error(`digest().toString($e):$h != update('A').update('B').digest($e):$h3`)};" - " return h;" - "})"), - njs_str("06d945942aa26a61be18c3e22bf19bbca8dd2b5d," - "BtlFlCqiamG+GMPiK/GbvKjdK10=," - "BtlFlCqiamG-GMPiK_GbvKjdK10") }, - - { njs_str("var hash = require('crypto').createHash.bind(undefined, 'sha1');" - "['hex', 'base64', 'base64url'].every(e => {" - " var h = hash().digest(e);" - " var h2 = hash().update('').digest(e);" - " if (h !== h2) {throw new Error(`digest($e):$h != update('').digest($e):$h2`)};" - " return true;" - "})"), - njs_str("true") }, - - { njs_str("var hash = require('crypto').createHash.bind(undefined, 'sha1');" - "[" - " ['AB']," - " ['4142', 'hex']," - " ['QUI=', 'base64']," - " ['QUI', 'base64url']" - "].every(args => {" - " return hash().update(args[0], args[1]).digest('hex') === '06d945942aa26a61be18c3e22bf19bbca8dd2b5d';" - "})"), - njs_str("true") }, - - { njs_str("var hash = require('crypto').createHash.bind(undefined, 'sha256');" - "['hex', 'base64', 'base64url'].map(e => {" - " var h = hash().update('AB').digest().toString(e);" - " var h2 = hash().update(Buffer.from('XABX').subarray(1,3)).digest(e);" - " var h3 = hash().update('A').update('B').digest(e);" - " if (h !== h2) {throw new Error(`digest().toString($e):$h != digest($e):$h2`)};" - " if (h !== h3) {throw new Error(`digest().toString($e):$h != update('A').update('B').digest($e):$h3`)};" - " return h;" - "})"), - njs_str("38164fbd17603d73f696b8b4d72664d735bb6a7c88577687fd2ae33fd6964153," - "OBZPvRdgPXP2lri01yZk1zW7anyIV3aH/SrjP9aWQVM=," - "OBZPvRdgPXP2lri01yZk1zW7anyIV3aH_SrjP9aWQVM") }, - - { njs_str("const crypto = require('crypto');" - "let hash = crypto.createHash('sha256');" - "let digests = [];" - "hash.update('one');" - "digests.push(hash.copy().digest('hex'));" - "hash.update('two');" - "digests.push(hash.copy().digest('hex'));" - "hash.update('three');" - "digests.push(hash.copy().digest('hex'));" - "digests"), - njs_str("7692c3ad3540bb803c020b3aee66cd8887123234ea0c6e7143c0add73ff431ed," - "25b6746d5172ed6352966a013d93ac846e1110d5a25e8f183b5931f4688842a1," - "4592092e1061c7ea85af2aed194621cc17a2762bae33a79bf8ce33fd0168b801") }, - - { njs_str("const crypto = require('crypto');" - "let hash = crypto.createHash('sha256');" - "hash.update('one').digest();" - "hash.copy()"), - njs_str("Error: Digest already called") }, - - { njs_str("var hash = require('crypto').createHash;" - "njs.dump(['', 'abc'.repeat(100)].map(v => {" - " return ['md5', 'sha1', 'sha256'].map(h => {" - " return hash(h).update(v).digest('hex');" - " })" - "}))"), - njs_str("[['d41d8cd98f00b204e9800998ecf8427e'," - "'da39a3ee5e6b4b0d3255bfef95601890afd80709'," - "'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855']," - "['f571117acbd8153c8dc3c81b8817773a'," - "'c95466320eaae6d19ee314ae4f135b12d45ced9a'," - "'d9f5aeb06abebb3be3f38adec9a2e3b94228d52193be923eb4e24c9b56ee0930']]") }, - - { njs_str("var h = require('crypto').createHash()"), - njs_str("TypeError: algorithm must be a string") }, - - { njs_str("var h = require('crypto').createHash([])"), - njs_str("TypeError: algorithm must be a string") }, - - { njs_str("var h = require('crypto').createHash('sha512')"), - njs_str("TypeError: not supported algorithm: \"sha512\"") }, - - { njs_str("var h = require('crypto').createHash('sha1');" - "h.update()"), - njs_str("TypeError: data is not a string or Buffer-like object") }, - - { njs_str("var h = require('crypto').createHash('sha1');" - "h.update({})"), - njs_str("TypeError: data is not a string or Buffer-like object") }, - - { njs_str("var h = require('crypto').createHash('sha1');" - "h.update('A').digest('latin1')"), - njs_str("TypeError: Unknown digest encoding: \"latin1\"") }, - - { njs_str("require('crypto').createHash('sha1').digest() instanceof Buffer"), - njs_str("true") }, - - { njs_str("var h = require('crypto').createHash('sha1');" - "h.update('A').digest('hex'); h.digest('hex')"), - njs_str("Error: Digest already called") }, - - { njs_str("var h = require('crypto').createHash('sha1');" - "h.update('A').digest('hex'); h.update('B')"), - njs_str("Error: Digest already called") }, - - { njs_str("typeof require('crypto').createHash('md5')"), - njs_str("object") }, - - { njs_str("var h = require('crypto').createHmac('sha1', '');" - "[Object.prototype.toString.call(h), njs.dump(h),h]"), - njs_str("[object Hmac],Hmac {},[object Hmac]") }, - - { njs_str("var hmac = require('crypto').createHmac.bind(undefined, 'md5', '');" - "['hex', 'base64', 'base64url'].map(e => {" - " var h = hmac().update('AB').digest().toString(e);" - " var h2 = hmac().update(Buffer.from('XABX').subarray(1,3)).digest(e);" - " var h3 = hmac().update('A').update('B').digest(e);" - " if (h !== h2) {throw new Error(`digest().toString($e):$h != digest($e):$h2`)};" - " if (h !== h3) {throw new Error(`digest().toString($e):$h != update('A').update('B').digest($e):$h3`)};" - " return h;" - "})"), - njs_str("9e0e9e545ef63d41dfb653daecf8ebc7," - "ng6eVF72PUHftlPa7Pjrxw==," - "ng6eVF72PUHftlPa7Pjrxw") }, - - { njs_str("var hmac = require('crypto').createHmac.bind(undefined, 'sha1', '');" - "['hex', 'base64', 'base64url'].map(e => {" - " var h = hmac().update('AB').digest().toString(e);" - " var h2 = hmac().update(Buffer.from('XABX').subarray(1,3)).digest(e);" - " var h3 = hmac().update('A').update('B').digest(e);" - " if (h !== h2) {throw new Error(`digest().toString($e):$h != digest($e):$h2`)};" - " if (h !== h3) {throw new Error(`digest().toString($e):$h != update('A').update('B').digest($e):$h3`)};" - " return h;" - "})"), - njs_str("d32c0b6637cc2dfe4670f3fe48ef4434123c4810," - "0ywLZjfMLf5GcPP+SO9ENBI8SBA=," - "0ywLZjfMLf5GcPP-SO9ENBI8SBA") }, - - { njs_str("var hash = require('crypto').createHmac.bind(undefined, 'sha1', '');" - "[" - " ['AB']," - " ['4142', 'hex']," - " ['QUI=', 'base64']," - " ['QUI', 'base64url']" - "].every(args => {" - " return hash().update(args[0], args[1]).digest('hex') === 'd32c0b6637cc2dfe4670f3fe48ef4434123c4810';" - "})"), - njs_str("true") }, - - { njs_str("var hmac = require('crypto').createHmac.bind(undefined, 'sha256', '');" - "['hex', 'base64', 'base64url'].map(e => {" - " var h = hmac().update('AB').digest().toString(e);" - " var h2 = hmac().update(Buffer.from('AB')).digest(e);" - " var h3 = hmac().update('A').update('B').digest(e);" - " if (h !== h2) {throw new Error(`digest().toString($e):$h != digest($e):$h2`)};" - " if (h !== h3) {throw new Error(`digest().toString($e):$h != update('A').update('B').digest($e):$h3`)};" - " return h;" - "})"), - njs_str("d53400095496267cf02e5dbd4b0bf9fbfb5f36f311ea7d9809af5487421743e3," - "1TQACVSWJnzwLl29Swv5+/tfNvMR6n2YCa9Uh0IXQ+M=," - "1TQACVSWJnzwLl29Swv5-_tfNvMR6n2YCa9Uh0IXQ-M") }, - - { njs_str("var hmac = require('crypto').createHmac;" - "njs.dump(['', 'abc'.repeat(100)].map(v => {" - " return ['md5', 'sha1', 'sha256'].map(h => {" - " return hmac(h, Buffer.from('secret')).update(v).digest('hex');" - " })" - "}))"), - njs_str("[['5c8db03f04cec0f43bcb060023914190'," - "'25af6174a0fcecc4d346680a72b7ce644b9a88e8'," - "'f9e66e179b6747ae54108f82f8ade8b3c25d76fd30afde6c395822c530196169']," - "['91eb74a225cdd3bbfccc34396c6e3ac5'," - "'0aac71e3a813a7acc4a809cfdedb2ecba04ffc5e'," - "'8660d2d51d6f20f61d5aadfb6c43df7fd05fc2fc4967d8aec1846f3d9ec03987']]") }, - - { njs_str("var h = require('crypto').createHmac('sha1', '');" - "var Hmac = h.constructor; " - "Hmac('sha1', '').digest('hex')"), - njs_str("fbdb1d1b18aa6c08324b7d64b71fb76370690e1d") }, - - { njs_str("require('crypto').createHmac('sha1', '').digest() instanceof Buffer"), - njs_str("true") }, - - { njs_str("var h = require('crypto').createHmac('sha256', 'A'.repeat(64));" - "h.update('AB').digest('hex')"), - njs_str("ee9dce43b12eb3e865614ad9c1a8d4fad4b6eac2b64647bd24cd192888d3f367") }, - - { njs_str("var h = require('crypto').createHmac('sha256', 'A'.repeat(100));" - "h.update('AB').digest('hex')"), - njs_str("5647b6c429701ff512f0f18232b4507065d2376ca8899a816a0a6e721bf8ddcc") }, - - { njs_str("var h = require('crypto').createHmac()"), - njs_str("TypeError: algorithm must be a string") }, - - { njs_str("var h = require('crypto').createHmac([])"), - njs_str("TypeError: algorithm must be a string") }, - - { njs_str("var h = require('crypto').createHmac('sha512', '')"), - njs_str("TypeError: not supported algorithm: \"sha512\"") }, - - { njs_str("var h = require('crypto').createHmac('sha1', [])"), - njs_str("TypeError: key is not a string or Buffer-like object") }, - - { njs_str("var h = require('crypto').createHmac('sha1', 'secret key');" - "h.update('A').digest('hex'); h.digest('hex')"), - njs_str("Error: Digest already called") }, - - { njs_str("var h = require('crypto').createHmac('sha1', 'secret key');" - "h.update('A').digest('hex'); h.update('B')"), - njs_str("Error: Digest already called") }, - - { njs_str("typeof require('crypto').createHmac('md5', 'a')"), - njs_str("object") }, - - { njs_str("var cr = require('crypto'); var h = cr.createHash('sha1');" - "h.update.call(cr.createHmac('sha1', 's'), '')"), - njs_str("TypeError: \"this\" is not a hash object") }, -}; - - #define NJS_XML_DOC "const xml = require('xml');" \ "let data = `ToveJani`;" \ "let doc = xml.parse(data);" @@ -23570,12 +23311,6 @@ static njs_test_suite_t njs_suites[] = njs_nitems(njs_fs_module_test), njs_unit_test }, - { njs_str("crypto module"), - { .repeat = 1, .unsafe = 1 }, - njs_crypto_module_test, - njs_nitems(njs_crypto_module_test), - njs_unit_test }, - { njs_str("externals"), { .externals = 1, .repeat = 1, .unsafe = 1 }, njs_externals_test, diff --git a/test/crypto.t.mjs b/test/crypto.t.mjs new file mode 100644 index 00000000..ad237d88 --- /dev/null +++ b/test/crypto.t.mjs @@ -0,0 +1,447 @@ +/*--- +includes: [runTsuite.js] +flags: [async] +---*/ + +import cr from 'crypto'; + +let createHash_tsuite = { + name: "createHash tests", + skip: () => !cr.createHash, + T: async (params) => { + var h = params.hash_value ? params.hash_value(params.hash) + : cr.createHash(params.hash); + + if (typeof h !== 'object') { + throw Error(`unexpected result "${h}" is not object`); + } + + for (let i = 0; i < params.data.length; i++) { + let args = params.data[i]; + + if (Array.isArray(args)) { + h.update(args[0], args[1]); + + } else { + h.update(args); + } + } + + let r = h.digest(params.digest); + + if (!params.digest) { + if (!(r instanceof Buffer)) { + throw Error(`unexpected result "${r}" is not Buffer`); + } + + if (r.compare(params.expected) !== 0) { + throw Error(`unexpected output "${r}" != "${params.expected}"`); + } + + } else { + if (typeof r !== 'string') { + throw Error(`unexpected result "${r}" is not string`); + } + + if (r !== params.expected) { + throw Error(`unexpected output "${r}" != "${params.expected}"`); + } + } + + return 'SUCCESS'; + }, + + tests: [ + { hash: 'md5', data: [], digest: 'hex', + expected: "d41d8cd98f00b204e9800998ecf8427e" }, + { hash: 'md5', data: [''], digest: 'hex', + expected: "d41d8cd98f00b204e9800998ecf8427e" }, + { hash: 'md5', data: [''], + expected: Buffer.from("d41d8cd98f00b204e9800998ecf8427e", "hex") }, + + { hash: 'md5', data: ['AB'], digest: 'hex', + expected: "b86fc6b051f63d73de262d4c34e3a0a9" }, + { hash: 'md5', data: ['A', 'B'], digest: 'hex', + expected: "b86fc6b051f63d73de262d4c34e3a0a9" }, + { hash: 'md5', data: [Buffer.from('XABX').subarray(1,3)], digest: 'hex', + expected: "b86fc6b051f63d73de262d4c34e3a0a9" }, + + { hash: 'md5', data: [['4142', 'hex']], digest: 'hex', + expected: "b86fc6b051f63d73de262d4c34e3a0a9" }, + { hash: 'md5', data: [['QUI=', 'base64']], digest: 'hex', + expected: "b86fc6b051f63d73de262d4c34e3a0a9" }, + { hash: 'md5', data: [['QUI', 'base64url']], digest: 'hex', + expected: "b86fc6b051f63d73de262d4c34e3a0a9" }, + + { hash: 'md5', data: ['abc'.repeat(100)], digest: 'hex', + expected: "f571117acbd8153c8dc3c81b8817773a" }, + + { hash: 'md5', data: ['AB'], digest: 'base64', + expected: "uG/GsFH2PXPeJi1MNOOgqQ==" }, + { hash: 'md5', data: ['A', 'B'], digest: 'base64', + expected: "uG/GsFH2PXPeJi1MNOOgqQ==" }, + { hash: 'md5', data: [Buffer.from('XABX').subarray(1,3)], digest: 'base64', + expected: "uG/GsFH2PXPeJi1MNOOgqQ==" }, + + { hash: 'md5', data: ['AB'], digest: 'base64url', + expected: "uG_GsFH2PXPeJi1MNOOgqQ" }, + { hash: 'md5', data: ['A', 'B'], digest: 'base64url', + expected: "uG_GsFH2PXPeJi1MNOOgqQ" }, + { hash: 'md5', data: [Buffer.from('XABX').subarray(1,3)], digest: 'base64url', + expected: "uG_GsFH2PXPeJi1MNOOgqQ" }, + + { hash: 'sha1', data: [], digest: 'hex', + expected: "da39a3ee5e6b4b0d3255bfef95601890afd80709" }, + { hash: 'sha1', data: [''], digest: 'hex', + expected: "da39a3ee5e6b4b0d3255bfef95601890afd80709" }, + { hash: 'sha1', data: [''], + expected: Buffer.from("da39a3ee5e6b4b0d3255bfef95601890afd80709", "hex") }, + + { hash: 'sha1', data: ['AB'], digest: 'hex', + expected: "06d945942aa26a61be18c3e22bf19bbca8dd2b5d" }, + { hash: 'sha1', data: ['A', 'B'], digest: 'hex', + expected: "06d945942aa26a61be18c3e22bf19bbca8dd2b5d" }, + { hash: 'sha1', data: [Buffer.from('XABX').subarray(1,3)], digest: 'hex', + expected: "06d945942aa26a61be18c3e22bf19bbca8dd2b5d" }, + + { hash: 'sha1', data: [['4142', 'hex']], digest: 'hex', + expected: "06d945942aa26a61be18c3e22bf19bbca8dd2b5d" }, + { hash: 'sha1', data: [['QUI=', 'base64']], digest: 'hex', + expected: "06d945942aa26a61be18c3e22bf19bbca8dd2b5d" }, + { hash: 'sha1', data: [['QUI', 'base64url']], digest: 'hex', + expected: "06d945942aa26a61be18c3e22bf19bbca8dd2b5d" }, + + { hash: 'sha1', data: ['abc'.repeat(100)], digest: 'hex', + expected: "c95466320eaae6d19ee314ae4f135b12d45ced9a" }, + + { hash: 'sha1', data: ['AB'], digest: 'base64', + expected: "BtlFlCqiamG+GMPiK/GbvKjdK10=" }, + { hash: 'sha1', data: ['A', 'B'], digest: 'base64', + expected: "BtlFlCqiamG+GMPiK/GbvKjdK10=" }, + { hash: 'sha1', data: [Buffer.from('XABX').subarray(1,3)], digest: 'base64', + expected: "BtlFlCqiamG+GMPiK/GbvKjdK10=" }, + + { hash: 'sha1', data: ['AB'], digest: 'base64url', + expected: "BtlFlCqiamG-GMPiK_GbvKjdK10" }, + { hash: 'sha1', data: ['A', 'B'], digest: 'base64url', + expected: "BtlFlCqiamG-GMPiK_GbvKjdK10" }, + { hash: 'sha1', data: [Buffer.from('XABX').subarray(1,3)], digest: 'base64url', + expected: "BtlFlCqiamG-GMPiK_GbvKjdK10" }, + + { hash: 'sha256', data: [], digest: 'hex', + expected: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" }, + { hash: 'sha256', data: [''], digest: 'hex', + expected: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" }, + { hash: 'sha256', data: [''], + expected: Buffer.from("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "hex") }, + + { hash: 'sha256', data: ['AB'], digest: 'hex', + expected: "38164fbd17603d73f696b8b4d72664d735bb6a7c88577687fd2ae33fd6964153" }, + { hash: 'sha256', data: ['A', 'B'], digest: 'hex', + expected: "38164fbd17603d73f696b8b4d72664d735bb6a7c88577687fd2ae33fd6964153" }, + { hash: 'sha256', data: [Buffer.from('XABX').subarray(1,3)], digest: 'hex', + expected: "38164fbd17603d73f696b8b4d72664d735bb6a7c88577687fd2ae33fd6964153" }, + + { hash: 'sha256', data: [['4142', 'hex']], digest: 'hex', + expected: "38164fbd17603d73f696b8b4d72664d735bb6a7c88577687fd2ae33fd6964153" }, + { hash: 'sha256', data: [['QUI=', 'base64']], digest: 'hex', + expected: "38164fbd17603d73f696b8b4d72664d735bb6a7c88577687fd2ae33fd6964153" }, + { hash: 'sha256', data: [['QUI', 'base64url']], digest: 'hex', + expected: "38164fbd17603d73f696b8b4d72664d735bb6a7c88577687fd2ae33fd6964153" }, + + { hash: 'sha256', data: ['abc'.repeat(100)], digest: 'hex', + expected: "d9f5aeb06abebb3be3f38adec9a2e3b94228d52193be923eb4e24c9b56ee0930" }, + + { hash: 'sha256', data: ['AB'], digest: 'base64', + expected: "OBZPvRdgPXP2lri01yZk1zW7anyIV3aH/SrjP9aWQVM=" }, + { hash: 'sha256', data: ['A', 'B'], digest: 'base64', + expected: "OBZPvRdgPXP2lri01yZk1zW7anyIV3aH/SrjP9aWQVM=" }, + { hash: 'sha256', data: [Buffer.from('XABX').subarray(1,3)], digest: 'base64', + expected: "OBZPvRdgPXP2lri01yZk1zW7anyIV3aH/SrjP9aWQVM=" }, + + { hash: 'sha256', data: ['AB'], digest: 'base64url', + expected: "OBZPvRdgPXP2lri01yZk1zW7anyIV3aH_SrjP9aWQVM" }, + { hash: 'sha256', data: ['A', 'B'], digest: 'base64url', + expected: "OBZPvRdgPXP2lri01yZk1zW7anyIV3aH_SrjP9aWQVM" }, + { hash: 'sha256', data: [Buffer.from('XABX').subarray(1,3)], digest: 'base64url', + expected: "OBZPvRdgPXP2lri01yZk1zW7anyIV3aH_SrjP9aWQVM" }, + + { hash: 'sha1', + hash_value(hash) { + var Hash = cr.createHash(hash).constructor; + return Hash(hash); + }, + data: [], digest: 'hex', + expected: "da39a3ee5e6b4b0d3255bfef95601890afd80709" }, + + { hash: 'sha1', + hash_value(hash) { + var h = cr.createHash(hash); + h.copy().digest('hex'); + return h; + }, + data: [], digest: 'hex', + expected: "da39a3ee5e6b4b0d3255bfef95601890afd80709" }, + + { hash: 'sha1', + hash_value(hash) { + var h = cr.createHash(hash); + h.digest('hex'); + return h; + }, + data: [], digest: 'hex', + exception: "TypeError: Digest already called" }, + + { hash: 'sha1', + hash_value(hash) { + var h = cr.createHash(hash); + h.digest('hex'); + h.copy(); + return h; + }, + data: [], digest: 'hex', + exception: "TypeError: Digest already called" }, + + { hash: 'sha1', + hash_value(hash) { + var h = cr.createHash(hash); + h.update('AB'); + return h; + }, + data: [], digest: 'hex', + exception: "TypeError: Digest already called" }, + + { hash: 'sha1', data: [undefined], digest: 'hex', + exception: "TypeError: data is not a string or Buffer-like object" }, + { hash: 'sha1', data: [{}], digest: 'hex', + exception: "TypeError: data is not a string or Buffer-like object" }, + + { hash: 'unknown', data: [], digest: 'hex', + exception: 'TypeError: not supported algorithm: "unknown"' }, + { hash: 'sha1', data: [], digest: 'unknown', + exception: 'TypeError: unknown digest type: "unknown"' }, +]}; + + +let createHmac_tsuite = { + name: "createHmac tests", + skip: () => !cr.createHmac, + T: async (params) => { + var h = params.hmac_value ? params.hmac_value(params.hash, params.key) + : cr.createHmac(params.hash, params.key || ''); + + if (typeof h !== 'object') { + throw Error(`unexpected result "${h}" is not object`); + } + + for (let i = 0; i < params.data.length; i++) { + let args = params.data[i]; + + if (Array.isArray(args)) { + h.update(args[0], args[1]); + + } else { + h.update(args); + } + } + + let r = h.digest(params.digest); + + if (!params.digest) { + if (!(r instanceof Buffer)) { + throw Error(`unexpected result "${r}" is not Buffer`); + } + + if (r.compare(params.expected) !== 0) { + throw Error(`unexpected output "${r}" != "${params.expected}"`); + } + + } else { + if (typeof r !== 'string') { + throw Error(`unexpected result "${r}" is not string`); + } + + if (r !== params.expected) { + throw Error(`unexpected output "${r}" != "${params.expected}"`); + } + } + + return 'SUCCESS'; + }, + + tests: [ + { hash: 'md5', key: '', data: [], digest: 'hex', + expected: "74e6f7298a9c2d168935f58c001bad88" }, + { hash: 'md5', key: '', data: [''], digest: 'hex', + expected: "74e6f7298a9c2d168935f58c001bad88" }, + { hash: 'md5', key: '', data: [''], + expected: Buffer.from("74e6f7298a9c2d168935f58c001bad88", "hex") }, + + { hash: 'md5', key: '', data: ['AB'], digest: 'hex', + expected: "9e0e9e545ef63d41dfb653daecf8ebc7" }, + { hash: 'md5', key: '', data: ['A', 'B'], digest: 'hex', + expected: "9e0e9e545ef63d41dfb653daecf8ebc7" }, + { hash: 'md5', key: '', data: [Buffer.from('XABX').subarray(1,3)], digest: 'hex', + expected: "9e0e9e545ef63d41dfb653daecf8ebc7" }, + + { hash: 'md5', key: '', data: [['4142', 'hex']], digest: 'hex', + expected: "9e0e9e545ef63d41dfb653daecf8ebc7" }, + { hash: 'md5', key: '', data: [['QUI=', 'base64']], digest: 'hex', + expected: "9e0e9e545ef63d41dfb653daecf8ebc7" }, + { hash: 'md5', key: '', data: [['QUI', 'base64url']], digest: 'hex', + expected: "9e0e9e545ef63d41dfb653daecf8ebc7" }, + + { hash: 'md5', key: Buffer.from('secret'), data: ['abc'.repeat(100)], digest: 'hex', + expected: "91eb74a225cdd3bbfccc34396c6e3ac5" }, + + { hash: 'md5', key: '', data: ['AB'], digest: 'base64', + expected: "ng6eVF72PUHftlPa7Pjrxw==" }, + { hash: 'md5', key: '', data: ['A', 'B'], digest: 'base64', + expected: "ng6eVF72PUHftlPa7Pjrxw==" }, + { hash: 'md5', key: '', data: [Buffer.from('XABX').subarray(1,3)], digest: 'base64', + expected: "ng6eVF72PUHftlPa7Pjrxw==" }, + + { hash: 'md5', key: '', data: ['AB'], digest: 'base64url', + expected: "ng6eVF72PUHftlPa7Pjrxw" }, + { hash: 'md5', key: '', data: ['A', 'B'], digest: 'base64url', + expected: "ng6eVF72PUHftlPa7Pjrxw" }, + { hash: 'md5', key: '', data: [Buffer.from('XABX').subarray(1,3)], digest: 'base64url', + expected: "ng6eVF72PUHftlPa7Pjrxw" }, + + { hash: 'sha1', key: '', data: [], digest: 'hex', + expected: "fbdb1d1b18aa6c08324b7d64b71fb76370690e1d" }, + { hash: 'sha1', key: '', data: [''], digest: 'hex', + expected: "fbdb1d1b18aa6c08324b7d64b71fb76370690e1d" }, + { hash: 'sha1', key: '', data: [''], + expected: Buffer.from("fbdb1d1b18aa6c08324b7d64b71fb76370690e1d", "hex") }, + + { hash: 'sha1', key: '', data: ['AB'], digest: 'hex', + expected: "d32c0b6637cc2dfe4670f3fe48ef4434123c4810" }, + { hash: 'sha1', key: '', data: ['A', 'B'], digest: 'hex', + expected: "d32c0b6637cc2dfe4670f3fe48ef4434123c4810" }, + { hash: 'sha1', key: '', data: [Buffer.from('XABX').subarray(1,3)], digest: 'hex', + expected: "d32c0b6637cc2dfe4670f3fe48ef4434123c4810" }, + + { hash: 'sha1', key: '', data: [['4142', 'hex']], digest: 'hex', + expected: "d32c0b6637cc2dfe4670f3fe48ef4434123c4810" }, + { hash: 'sha1', key: '', data: [['QUI=', 'base64']], digest: 'hex', + expected: "d32c0b6637cc2dfe4670f3fe48ef4434123c4810" }, + { hash: 'sha1', key: '', data: [['QUI', 'base64url']], digest: 'hex', + expected: "d32c0b6637cc2dfe4670f3fe48ef4434123c4810" }, + + { hash: 'sha1', key: Buffer.from('secret'), data: ['abc'.repeat(100)], digest: 'hex', + expected: "0aac71e3a813a7acc4a809cfdedb2ecba04ffc5e" }, + + { hash: 'sha1', key: '', data: ['AB'], digest: 'base64', + expected: "0ywLZjfMLf5GcPP+SO9ENBI8SBA=" }, + { hash: 'sha1', key: '', data: ['A', 'B'], digest: 'base64', + expected: "0ywLZjfMLf5GcPP+SO9ENBI8SBA=" }, + { hash: 'sha1', key: '', data: [Buffer.from('XABX').subarray(1,3)], digest: 'base64', + expected: "0ywLZjfMLf5GcPP+SO9ENBI8SBA=" }, + + { hash: 'sha1', key: '', data: ['AB'], digest: 'base64url', + expected: "0ywLZjfMLf5GcPP-SO9ENBI8SBA" }, + { hash: 'sha1', key: '', data: ['A', 'B'], digest: 'base64url', + expected: "0ywLZjfMLf5GcPP-SO9ENBI8SBA" }, + { hash: 'sha1', key: '', data: [Buffer.from('XABX').subarray(1,3)], digest: 'base64url', + expected: "0ywLZjfMLf5GcPP-SO9ENBI8SBA" }, + + { hash: 'sha256', key: '', data: [], digest: 'hex', + expected: "b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad" }, + { hash: 'sha256', key: '', data: [''], digest: 'hex', + expected: "b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad" }, + { hash: 'sha256', key: '', data: [''], + expected: Buffer.from("b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad", "hex") }, + + { hash: 'sha256', key: '', data: ['AB'], digest: 'hex', + expected: "d53400095496267cf02e5dbd4b0bf9fbfb5f36f311ea7d9809af5487421743e3" }, + { hash: 'sha256', key: '', data: ['A', 'B'], digest: 'hex', + expected: "d53400095496267cf02e5dbd4b0bf9fbfb5f36f311ea7d9809af5487421743e3" }, + { hash: 'sha256', key: '', data: [Buffer.from('XABX').subarray(1,3)], digest: 'hex', + expected: "d53400095496267cf02e5dbd4b0bf9fbfb5f36f311ea7d9809af5487421743e3" }, + + { hash: 'sha256', key: '', data: [['4142', 'hex']], digest: 'hex', + expected: "d53400095496267cf02e5dbd4b0bf9fbfb5f36f311ea7d9809af5487421743e3" }, + { hash: 'sha256', key: '', data: [['QUI=', 'base64']], digest: 'hex', + expected: "d53400095496267cf02e5dbd4b0bf9fbfb5f36f311ea7d9809af5487421743e3" }, + { hash: 'sha256', key: '', data: [['QUI', 'base64url']], digest: 'hex', + expected: "d53400095496267cf02e5dbd4b0bf9fbfb5f36f311ea7d9809af5487421743e3" }, + + { hash: 'sha256', key: Buffer.from('secret'), data: ['abc'.repeat(100)], digest: 'hex', + expected: "8660d2d51d6f20f61d5aadfb6c43df7fd05fc2fc4967d8aec1846f3d9ec03987" }, + + { hash: 'sha256', key: '', data: ['AB'], digest: 'base64', + expected: "1TQACVSWJnzwLl29Swv5+/tfNvMR6n2YCa9Uh0IXQ+M=" }, + { hash: 'sha256', key: '', data: ['A', 'B'], digest: 'base64', + expected: "1TQACVSWJnzwLl29Swv5+/tfNvMR6n2YCa9Uh0IXQ+M=" }, + { hash: 'sha256', key: '', data: [Buffer.from('XABX').subarray(1,3)], digest: 'base64', + expected: "1TQACVSWJnzwLl29Swv5+/tfNvMR6n2YCa9Uh0IXQ+M=" }, + + { hash: 'sha256', key: '', data: ['AB'], digest: 'base64url', + expected: "1TQACVSWJnzwLl29Swv5-_tfNvMR6n2YCa9Uh0IXQ-M" }, + { hash: 'sha256', key: '', data: ['A', 'B'], digest: 'base64url', + expected: "1TQACVSWJnzwLl29Swv5-_tfNvMR6n2YCa9Uh0IXQ-M" }, + { hash: 'sha256', key: '', data: [Buffer.from('XABX').subarray(1,3)], digest: 'base64url', + expected: "1TQACVSWJnzwLl29Swv5-_tfNvMR6n2YCa9Uh0IXQ-M" }, + + { hash: 'sha256', key: 'A'.repeat(64), data: ['AB'], digest: 'hex', + expected: "ee9dce43b12eb3e865614ad9c1a8d4fad4b6eac2b64647bd24cd192888d3f367" }, + + { hash: 'sha256', key: 'A'.repeat(100), data: ['AB'], digest: 'hex', + expected: "5647b6c429701ff512f0f18232b4507065d2376ca8899a816a0a6e721bf8ddcc" }, + + { hash: 'sha1', + hmac_value(hash, key) { + var Hmac = cr.createHmac(hash, key).constructor; + return Hmac(hash, key); + }, + key: '', data: [], digest: 'hex', + expected: "fbdb1d1b18aa6c08324b7d64b71fb76370690e1d" }, + + { hash: 'sha1', + hmac_value(hash, key) { + var h = cr.createHmac(hash, key); + h.digest('hex'); + return h; + }, + key: '', data: [], digest: 'hex', + exception: "TypeError: Digest already called" }, + + { hash: 'sha1', + hmac_value(hash, key) { + var h = cr.createHmac(hash, key); + h.digest('hex'); + h.update('A'); + return h; + }, + key: '', data: [], digest: 'hex', + exception: "TypeError: Digest already called" }, + + { hash: 'sha1', key: '', data: [undefined], digest: 'hex', + exception: "TypeError: data is not a string or Buffer-like object" }, + { hash: 'sha1', key: '', data: [{}], digest: 'hex', + exception: "TypeError: data is not a string or Buffer-like object" }, + + { hash: 'unknown', key: '', data: [], digest: 'hex', + exception: 'TypeError: not supported algorithm: "unknown"' }, + { hash: 'sha1', key: '', data: [], digest: 'unknown', + exception: 'TypeError: unknown digest type: "unknown"' }, + + { hash: 'sha1', key: [], data: [], digest: 'hex', + exception: 'TypeError: key is not a string or Buffer-like object' }, + + { hash: 'sha1', + hmac_value(hash, key) { + var h = cr.createHash('sha1'); + h.update.call(cr.createHmac(hash, key), ''); + }, + key: '', data: [], digest: 'hex', + exception: 'TypeError: "this" is not a hash object' }, +]}; + + +run([ + createHash_tsuite, + createHmac_tsuite +]) +.then($DONE, $DONE); From noreply at nginx.com Wed Mar 19 03:17:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 19 Mar 2025 03:17:02 +0000 (UTC) Subject: [njs] QuickJS: added error checks in modules initialization. Message-ID: <20250319031702.313C748379@pubserv1.nginx> details: https://github.com/nginx/njs/commit/6736121eba717407356c0a7e8ac7046c7744b7e6 branches: master commit: 6736121eba717407356c0a7e8ac7046c7744b7e6 user: Vadim Zhestikov date: Tue, 18 Mar 2025 10:40:24 -0700 description: QuickJS: added error checks in modules initialization. --- external/qjs_fs_module.c | 9 ++++++++- external/qjs_query_string_module.c | 5 ++++- external/qjs_webcrypto_module.c | 9 ++++++++- external/qjs_zlib_module.c | 9 ++++++++- src/qjs_buffer.c | 9 ++++++++- 5 files changed, 36 insertions(+), 5 deletions(-) diff --git a/external/qjs_fs_module.c b/external/qjs_fs_module.c index 9d1f7687..b09d1dcd 100644 --- a/external/qjs_fs_module.c +++ b/external/qjs_fs_module.c @@ -2960,6 +2960,10 @@ qjs_fs_module_init(JSContext *cx, JSModuleDef *m) JSValue proto; proto = JS_NewObject(cx); + if (JS_IsException(proto)) { + return -1; + } + JS_SetPropertyFunctionList(cx, proto, qjs_fs_export, njs_nitems(qjs_fs_export)); @@ -3031,7 +3035,10 @@ qjs_fs_init(JSContext *cx, const char *name) return NULL; } - JS_AddModuleExport(cx, m, "default"); + if (JS_AddModuleExport(cx, m, "default") < 0) { + return NULL; + } + rc = JS_AddModuleExportList(cx, m, qjs_fs_export, njs_nitems(qjs_fs_export)); if (rc != 0) { diff --git a/external/qjs_query_string_module.c b/external/qjs_query_string_module.c index 85d2fcb3..3059ee1b 100644 --- a/external/qjs_query_string_module.c +++ b/external/qjs_query_string_module.c @@ -998,7 +998,10 @@ qjs_querystring_init(JSContext *ctx, const char *name) return NULL; } - JS_AddModuleExport(ctx, m, "default"); + if (JS_AddModuleExport(ctx, m, "default") < 0) { + return NULL; + } + rc = JS_AddModuleExportList(ctx, m, qjs_querystring_export, njs_nitems(qjs_querystring_export)); if (rc != 0) { diff --git a/external/qjs_webcrypto_module.c b/external/qjs_webcrypto_module.c index a1983651..9552ca12 100644 --- a/external/qjs_webcrypto_module.c +++ b/external/qjs_webcrypto_module.c @@ -4775,6 +4775,10 @@ qjs_webcrypto_module_init(JSContext *cx, JSModuleDef *m) JSValue proto; proto = JS_NewObject(cx); + if (JS_IsException(proto)) { + return -1; + } + JS_SetPropertyFunctionList(cx, proto, qjs_webcrypto_export, njs_nitems(qjs_webcrypto_export)); @@ -4833,7 +4837,10 @@ qjs_webcrypto_init(JSContext *cx, const char *name) return NULL; } - JS_AddModuleExport(cx, m, "default"); + if (JS_AddModuleExport(cx, m, "default") < 0) { + return NULL; + } + rc = JS_AddModuleExportList(cx, m, qjs_webcrypto_export, njs_nitems(qjs_webcrypto_export)); if (rc != 0) { diff --git a/external/qjs_zlib_module.c b/external/qjs_zlib_module.c index 2d3b2d92..5abf108a 100644 --- a/external/qjs_zlib_module.c +++ b/external/qjs_zlib_module.c @@ -463,6 +463,10 @@ qjs_zlib_module_init(JSContext *ctx, JSModuleDef *m) JSValue proto; proto = JS_NewObject(ctx); + if (JS_IsException(proto)) { + return -1; + } + JS_SetPropertyFunctionList(ctx, proto, qjs_zlib_export, njs_nitems(qjs_zlib_export)); @@ -487,7 +491,10 @@ qjs_zlib_init(JSContext *ctx, const char *name) return NULL; } - JS_AddModuleExport(ctx, m, "default"); + if (JS_AddModuleExport(ctx, m, "default") < 0) { + return NULL; + } + rc = JS_AddModuleExportList(ctx, m, qjs_zlib_export, njs_nitems(qjs_zlib_export)); if (rc != 0) { diff --git a/src/qjs_buffer.c b/src/qjs_buffer.c index 638d273c..dfe7b04d 100644 --- a/src/qjs_buffer.c +++ b/src/qjs_buffer.c @@ -2550,6 +2550,10 @@ qjs_buffer_module_init(JSContext *ctx, JSModuleDef *m) JSValue proto; proto = JS_NewObject(ctx); + if (JS_IsException(proto)) { + return -1; + } + JS_SetPropertyFunctionList(ctx, proto, qjs_buffer_export, njs_nitems(qjs_buffer_export)); @@ -2576,7 +2580,10 @@ qjs_buffer_init(JSContext *ctx, const char *name) return NULL; } - JS_AddModuleExport(ctx, m, "default"); + if (JS_AddModuleExport(ctx, m, "default") < 0) { + return NULL; + } + rc = JS_AddModuleExportList(ctx, m, qjs_buffer_export, njs_nitems(qjs_buffer_export)); if (rc != 0) { From mike.maccana at gmail.com Wed Mar 19 03:43:14 2025 From: mike.maccana at gmail.com (Mike MacCana) Date: Tue, 18 Mar 2025 23:43:14 -0400 Subject: [njs] Test262: allowing to omit empty default option argument. In-Reply-To: <20250318214602.C9EEC4837A@pubserv1.nginx> References: <20250318214602.C9EEC4837A@pubserv1.nginx> Message-ID: Unsubscribe On Tue, Mar 18, 2025 at 17:46 wrote: > details: > https://github.com/nginx/njs/commit/3ad475cb9a0b24fc7bc49460ede80f4ec104c3fd > branches: master > commit: 3ad475cb9a0b24fc7bc49460ede80f4ec104c3fd > user: Dmitry Volyntsev > date: Tue, 25 Feb 2025 22:44:39 -0800 > description: > Test262: allowing to omit empty default option argument. > > > --- > test/buffer.t.js | 37 > ------------------------------------- > test/fs/methods.t.mjs | 4 ---- > test/harness/runTsuite.js | 2 +- > test/querystring.t.mjs | 8 -------- > test/text_decoder.t.js | 6 ------ > test/text_encoder.t.js | 4 ---- > test/webcrypto/digest.t.mjs | 1 - > test/webcrypto/import.t.mjs | 4 ---- > test/webcrypto/rsa_decoding.t.mjs | 1 - > 9 files changed, 1 insertion(+), 66 deletions(-) > > diff --git a/test/buffer.t.js b/test/buffer.t.js > index 01e25bed..37821104 100644 > --- a/test/buffer.t.js > +++ b/test/buffer.t.js > @@ -86,7 +86,6 @@ let concat_tsuite = { > return 'SUCCESS'; > }, > > - opts: {}, > tests: [ > { buffers: [ Buffer.from('abc'), > Buffer.from(new Uint8Array([0x64, 0x65, > 0x66]).buffer, 1) ], > @@ -112,8 +111,6 @@ let compare_tsuite = { > return 'SUCCESS'; > }, > > - opts: {}, > - > tests: [ > { buf1: Buffer.from('abc'), buf2: Buffer.from('abc'), expected: 0 > }, > { buf1: Buffer.from('abc'), > @@ -150,8 +147,6 @@ let comparePrototype_tsuite = { > return 'SUCCESS'; > }, > > - opts: {}, > - > tests: [ > { buf: Buffer.from('abc'), target: Buffer.from('abc'), expected: > 0 }, > { buf: Buffer.from('abc'), > @@ -200,8 +195,6 @@ let copy_tsuite = { > return 'SUCCESS'; > }, > > - opts: {}, > - > tests: [ > { buf: Buffer.from('abcdef'), target: Buffer.from('123456'), > expected: 6, expected_buf: 'abcdef' }, > @@ -236,7 +229,6 @@ let equals_tsuite = { > return 'SUCCESS'; > }, > > - opts: {}, > tests: [ > > { buf1: Buffer.from('abc'), buf2: Buffer.from('abc'), expected: > true }, > @@ -271,7 +263,6 @@ let fill_tsuite = { > return 'SUCCESS'; > }, > > - opts: {}, > tests: [ > { buf: Buffer.from('abc'), value: 0x61, expected: 'aaa' }, > { buf: Buffer.from('abc'), value: 0x61, expected: 'aaa', offset: > 0, end: 3 }, > @@ -430,8 +421,6 @@ let includes_tsuite = { > return 'SUCCESS'; > }, > > - opts: {}, > - > tests: [ > { buf: Buffer.from('abcdef'), value: 'abc', expected: true }, > { buf: Buffer.from('abcdef'), value: 'def', expected: true }, > @@ -455,8 +444,6 @@ let indexOf_tsuite = { > return 'SUCCESS'; > }, > > - opts: {}, > - > tests: [ > { buf: Buffer.from('abcdef'), value: 'abc', expected: 0 }, > { buf: Buffer.from('abcdef'), value: 'def', expected: 3 }, > @@ -503,8 +490,6 @@ let isBuffer_tsuite = { > return 'SUCCESS'; > }, > > - opts: {}, > - > tests: [ > { value: Buffer.from('α'), expected: true }, > { value: new Uint8Array(10), expected: false }, > @@ -526,8 +511,6 @@ let isEncoding_tsuite = { > return 'SUCCESS'; > }, > > - opts: {}, > - > tests: [ > { value: 'utf-8', expected: true }, > { value: 'utf8', expected: true }, > @@ -576,8 +559,6 @@ let lastIndexOf_tsuite = { > return 'SUCCESS'; > }, > > - opts: {}, > - > tests: [ > { buf: Buffer.from('abcdef'), value: 'abc', expected: 0 }, > { buf: Buffer.from('abcabc'), value: 'abc', expected: 3 }, > @@ -640,8 +621,6 @@ let readXIntXX_tsuite = { > return 'SUCCESS'; > }, > > - opts: {}, > - > tests: [ > { buf: Buffer.from([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]), offset: > 0, > expected: [ > -86,170,-17494,-21829,48042,43707,-573785174,-1430532899,3721182122,2864434397 > ] }, > @@ -699,8 +678,6 @@ let readFloat_tsuite = { > return 'SUCCESS'; > }, > > - opts: {}, > - > tests: [ > {} > ], > @@ -727,8 +704,6 @@ let readGeneric_tsuite = { > return 'SUCCESS'; > }, > > - opts: {}, > - > tests: [ > { buf: Buffer.from([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]), offset: > 0, length: 1, > expected: [ 170, 170, -86, -86 ] }, > @@ -767,8 +742,6 @@ let slice_tsuite = { > return 'SUCCESS'; > }, > > - opts: {}, > - > tests: [ > { buf: Buffer.from('abcdef'), start: 1, expected: 'bcdef' }, > { buf: Buffer.from('abcdef'), start: 1, end: 3, expected: 'bc' }, > @@ -795,8 +768,6 @@ let subarray_tsuite = { > return 'SUCCESS'; > }, > > - opts: {}, > - > tests: [ > { buf: Buffer.from('abcdef'), start: 0, end: 3, expected: 'Zbc' }, > { buf: Buffer.from('abcdef'), start: 1, expected: 'bcdef' }, > @@ -842,8 +813,6 @@ let toJSON_tsuite = { > return 'SUCCESS'; > }, > > - opts: {}, > - > tests: [ > { value: '', expected: { type: 'Buffer', data: [] } }, > { value: 'αβγ', expected: { type: 'Buffer', data: [0xCE, 0xB1, > 0xCE, 0xB2, 0xCE, 0xB3] } }, > @@ -912,8 +881,6 @@ let write_tsuite = { > return 'SUCCESS'; > }, > > - opts: {}, > - > tests: [ > { value: 'abc', expected: 3, expected_buf: 'abcZZZZZZZ' }, > { value: 'abc', offset: 1, expected: 3, expected_buf: > 'ZabcZZZZZZ' }, > @@ -955,8 +922,6 @@ let writeXIntXX_tsuite = { > return 'SUCCESS'; > }, > > - opts: {}, > - > tests: [ > { write: 'writeInt8', value: 0xaa, exception: 'RangeError: Index > out of range' }, > { write: 'writeInt8', value: 0x00, offset: 3, expected: 4, > expected_buf: '5a5a5a005a5a5a5a5a5a' }, > @@ -995,8 +960,6 @@ let writeGeneric_tsuite = { > return 'SUCCESS'; > }, > > - opts: {}, > - > tests: [ > { write: 'writeUIntLE', value: 0xaa, length: 1, > exception: 'RangeError: Index out of range' }, > diff --git a/test/fs/methods.t.mjs b/test/fs/methods.t.mjs > index 928d1682..705c1b5a 100644 > --- a/test/fs/methods.t.mjs > +++ b/test/fs/methods.t.mjs > @@ -1008,7 +1008,6 @@ let readSync_tsuite = { > skip: () => (!has_buffer()), > T: read_test, > prepare_args: p, > - opts: {}, > get tests() { return read_tests() }, > }; > > @@ -1017,7 +1016,6 @@ let readFh_tsuite = { > skip: () => (!has_buffer()), > T: readFh_test, > prepare_args: p, > - opts: {}, > get tests() { return read_tests() }, > }; > > @@ -1213,7 +1211,6 @@ let writeSync_tsuite = { > skip: () => (!has_buffer()), > T: write_test, > prepare_args: p, > - opts: {}, > get tests() { return write_tests() }, > }; > > @@ -1222,7 +1219,6 @@ let writeFh_tsuite = { > skip: () => (!has_buffer()), > T: writeFh_test, > prepare_args: p, > - opts: {}, > get tests() { return write_tests() }, > }; > > diff --git a/test/harness/runTsuite.js b/test/harness/runTsuite.js > index 068a19e1..2a324fbe 100644 > --- a/test/harness/runTsuite.js > +++ b/test/harness/runTsuite.js > @@ -29,7 +29,7 @@ async function run(tlist) { > let prepare_args = ts.prepare_args ? ts.prepare_args > : default_prepare_args; > > - return ts.T(prepare_args(t, ts.opts)); > + return ts.T(prepare_args(t, ts.opts ? ts.opts : {})); > > } catch (e) { > return Promise.reject(e); > diff --git a/test/querystring.t.mjs b/test/querystring.t.mjs > index cb42305e..8ea14050 100644 > --- a/test/querystring.t.mjs > +++ b/test/querystring.t.mjs > @@ -17,8 +17,6 @@ let escape_tsuite = { > return 'SUCCESS'; > }, > > - opts: { }, > - > tests: [ > { value: '', expected: '' }, > { value: 'baz=fuz', expected: 'baz%3Dfuz' }, > @@ -62,8 +60,6 @@ let parse_tsuite = { > return 'SUCCESS'; > }, > > - opts: { }, > - > tests: [ > { value: '', expected: {} }, > { value: 'baz=fuz', expected: { baz:'fuz' } }, > @@ -165,8 +161,6 @@ let stringify_tsuite = { > return 'SUCCESS'; > }, > > - opts: { }, > - > tests: [ > { obj: {}, expected: '' }, > { obj: { baz:'fuz', muz:'tax' }, expected: 'baz=fuz&muz=tax' }, > @@ -216,8 +210,6 @@ let unescape_tsuite = { > return 'SUCCESS'; > }, > > - opts: { }, > - > tests: [ > { value: '', expected: '' }, > { value: 'baz%3Dfuz', expected: 'baz=fuz' }, > diff --git a/test/text_decoder.t.js b/test/text_decoder.t.js > index a6fced2b..afc91fc4 100644 > --- a/test/text_decoder.t.js > +++ b/test/text_decoder.t.js > @@ -40,8 +40,6 @@ let stream_tsuite = { > return 'SUCCESS'; > }, > > - opts: {}, > - > tests: [ > { chunks: [new Uint8Array([0xF0, 0x9F, 0x8C, 0x9F])], > expected: ['🌟'] }, > @@ -95,8 +93,6 @@ let fatal_tsuite = { > return 'SUCCESS'; > }, > > - opts: {}, > - > tests: [ > { chunks: [new Uint8Array([0xF0, 0xA0, 0xAE, 0xB7])], > expected: ['𠮷'] }, > @@ -124,8 +120,6 @@ let ignoreBOM_tsuite = { > return 'SUCCESS'; > }, > > - opts: {}, > - > tests: [ > { value: new Uint8Array([239, 187, 191, 50]), > opts: {ignoreBOM: true}, > diff --git a/test/text_encoder.t.js b/test/text_encoder.t.js > index 10324bca..25173cd1 100644 > --- a/test/text_encoder.t.js > +++ b/test/text_encoder.t.js > @@ -26,8 +26,6 @@ let encode_tsuite = { > return 'SUCCESS'; > }, > > - opts: {}, > - > tests: [ > { value: "", expected: [] }, > { value: "abc", expected: [97, 98, 99] }, > @@ -58,8 +56,6 @@ let encodeinto_tsuite = { > return 'SUCCESS'; > }, > > - opts: {}, > - > tests: [ > { value: "", dest: new Uint8Array(4), expected: [], read: 0 }, > { value: "aα", dest: new Uint8Array(3), expected: [97, 206, 177], > read: 2 }, > diff --git a/test/webcrypto/digest.t.mjs b/test/webcrypto/digest.t.mjs > index 4318fe16..f07f0346 100644 > --- a/test/webcrypto/digest.t.mjs > +++ b/test/webcrypto/digest.t.mjs > @@ -25,7 +25,6 @@ let digest_tsuite = { > skip: () => (!has_buffer() || !has_webcrypto()), > T: test, > prepare_args: p, > - opts: { }, > > tests: [ > { name: "XXX", data: "", > diff --git a/test/webcrypto/import.t.mjs b/test/webcrypto/import.t.mjs > index 1b804006..9ec0fb60 100644 > --- a/test/webcrypto/import.t.mjs > +++ b/test/webcrypto/import.t.mjs > @@ -85,7 +85,6 @@ let aes_tsuite = { > skip: () => (!has_webcrypto()), > T: test, > prepare_args: p, > - opts: {}, > > tests: [ > { key: { fmt: "jwk", > @@ -186,7 +185,6 @@ let ec_tsuite = { > skip: () => (!has_webcrypto()), > T: test, > prepare_args: p, > - opts: {}, > > tests: [ > { key: { fmt: "jwk", > @@ -350,7 +348,6 @@ let hmac_tsuite = { > skip: () => (!has_webcrypto()), > T: test, > prepare_args: p, > - opts: {}, > > tests: [ > { key: { fmt: "raw", > @@ -465,7 +462,6 @@ let rsa_tsuite = { > skip: () => (!has_webcrypto()), > T: test, > prepare_args: p, > - opts: {}, > > tests: [ > { key: { fmt: "jwk", > diff --git a/test/webcrypto/rsa_decoding.t.mjs > b/test/webcrypto/rsa_decoding.t.mjs > index 08874491..179072ff 100644 > --- a/test/webcrypto/rsa_decoding.t.mjs > +++ b/test/webcrypto/rsa_decoding.t.mjs > @@ -29,7 +29,6 @@ let rsa_tsuite = { > name: "RSA-OAEP decoding", > T: test, > prepare_args: (v) => v, > - opts: { }, > > tests: [ > { pem: "rsa.pkcs8", src: "text.base64.rsa-oaep.enc", expected: > "WAKAWAKA" }, > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel > -------------- next part -------------- An HTML attachment was scrubbed... URL: From noreply at nginx.com Wed Mar 19 05:28:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 19 Mar 2025 05:28:02 +0000 (UTC) Subject: [njs] QuickJS: fixed njs_qjs_object_completions(). Message-ID: <20250319052802.699394837B@pubserv1.nginx> details: https://github.com/nginx/njs/commit/f6a2a795e9df4911dde8d0c3f02dd5a83c610ca5 branches: master commit: f6a2a795e9df4911dde8d0c3f02dd5a83c610ca5 user: hongzhidao date: Wed, 19 Mar 2025 11:18:41 +0800 description: QuickJS: fixed njs_qjs_object_completions(). This commit also exposed qjs_free_prop_enum() as public. --- external/njs_shell.c | 5 ++--- external/qjs_query_string_module.c | 13 ------------- src/qjs.c | 13 +++++++++++++ src/qjs.h | 2 ++ 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/external/njs_shell.c b/external/njs_shell.c index 1228b374..aeab226c 100644 --- a/external/njs_shell.c +++ b/external/njs_shell.c @@ -2950,7 +2950,6 @@ njs_qjs_object_completions(njs_engine_t *engine, JSContext *ctx, for (n = 0; n < length; n++) { key.start = (u_char *) JS_AtomToCString(ctx, ptab[n].atom); - JS_FreeAtom(ctx, ptab[n].atom); if (njs_slow_path(key.start == NULL)) { goto fail; } @@ -2993,7 +2992,7 @@ next: JS_FreeCString(ctx, (const char *) key.start); } - js_free_rt(JS_GetRuntime(ctx), ptab); + qjs_free_prop_enum(ctx, ptab, length); prototype = JS_GetPrototype(ctx, object); if (JS_IsException(prototype)) { @@ -3017,7 +3016,7 @@ fail: } if (ptab != NULL) { - js_free_rt(JS_GetRuntime(ctx), ptab); + qjs_free_prop_enum(ctx, ptab, length); } JS_FreeValue(ctx, object); diff --git a/external/qjs_query_string_module.c b/external/qjs_query_string_module.c index 3059ee1b..42322234 100644 --- a/external/qjs_query_string_module.c +++ b/external/qjs_query_string_module.c @@ -727,19 +727,6 @@ qjs_query_string_push_array(JSContext *cx, njs_chb_t *chain, JSValue key, } -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) diff --git a/src/qjs.c b/src/qjs.c index 15a575a2..52401983 100644 --- a/src/qjs.c +++ b/src/qjs.c @@ -1027,6 +1027,19 @@ qjs_string_create_chb(JSContext *cx, njs_chb_t *chain) } +void +qjs_free_prop_enum(JSContext *ctx, JSPropertyEnum *tab, uint32_t len) +{ + uint32_t i; + + for(i = 0; i < len; i++) { + JS_FreeAtom(ctx, tab[i].atom); + } + + js_free(ctx, tab); +} + + JSValue qjs_string_hex(JSContext *cx, const njs_str_t *src) { diff --git a/src/qjs.h b/src/qjs.h index 54f96dfe..7c13f039 100644 --- a/src/qjs.h +++ b/src/qjs.h @@ -127,6 +127,8 @@ JSValue qjs_typed_array_data(JSContext *ctx, JSValueConst value, JS_NewStringLen(ctx, (const char *) (data), len) JSValue qjs_string_create_chb(JSContext *cx, njs_chb_t *chain); +void qjs_free_prop_enum(JSContext *ctx, JSPropertyEnum *tab, uint32_t len); + JSValue qjs_string_hex(JSContext *cx, const njs_str_t *src); JSValue qjs_string_base64(JSContext *cx, const njs_str_t *src); JSValue qjs_string_base64url(JSContext *cx, const njs_str_t *src); From noreply at nginx.com Wed Mar 19 07:08:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 19 Mar 2025 07:08:02 +0000 (UTC) Subject: [njs] QuickJS: fixed ngx_qjs_string() to handle strings containing "\0". Message-ID: <20250319070802.5C8424837C@pubserv1.nginx> details: https://github.com/nginx/njs/commit/1b7adef4d532df55d81e22ec8fe998b76de34935 branches: master commit: 1b7adef4d532df55d81e22ec8fe998b76de34935 user: hongzhidao date: Wed, 19 Mar 2025 13:47:07 +0800 description: QuickJS: fixed ngx_qjs_string() to handle strings containing "\0". --- nginx/ngx_js.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/nginx/ngx_js.c b/nginx/ngx_js.c index 84cdcc2e..c91a5530 100644 --- a/nginx/ngx_js.c +++ b/nginx/ngx_js.c @@ -1482,13 +1482,11 @@ ngx_qjs_string(JSContext *cx, JSValueConst val, ngx_str_t *dst) string: - str = JS_ToCString(cx, val); + str = JS_ToCStringLen(cx, &len, val); if (str == NULL) { return NGX_ERROR; } - len = strlen(str); - start = njs_mp_alloc(e->pool, len); if (start == NULL) { JS_FreeCString(cx, str); From noreply at nginx.com Wed Mar 19 07:47:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 19 Mar 2025 07:47:02 +0000 (UTC) Subject: [njs] QuickJS: calling njs_chb_destroy() in qjs_string_create_chb() internally. Message-ID: <20250319074702.9DE054837D@pubserv1.nginx> details: https://github.com/nginx/njs/commit/1d36e231ed6a317cc9d01f8223091619e89e7a65 branches: master commit: 1d36e231ed6a317cc9d01f8223091619e89e7a65 user: hongzhidao date: Tue, 18 Mar 2025 21:34:59 +0800 description: QuickJS: calling njs_chb_destroy() in qjs_string_create_chb() internally. No functional changes. --- external/qjs_query_string_module.c | 17 +++-------------- nginx/ngx_http_js_module.c | 30 ++++++++++++------------------ src/qjs.c | 2 ++ 3 files changed, 17 insertions(+), 32 deletions(-) diff --git a/external/qjs_query_string_module.c b/external/qjs_query_string_module.c index 42322234..63553a53 100644 --- a/external/qjs_query_string_module.c +++ b/external/qjs_query_string_module.c @@ -171,7 +171,6 @@ 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; @@ -250,11 +249,7 @@ qjs_query_string_decode(JSContext *cx, const u_char *start, size_t size) } - ret = qjs_string_create_chb(cx, &chain); - - njs_chb_destroy(&chain); - - return ret; + return qjs_string_create_chb(cx, &chain); } @@ -281,8 +276,6 @@ qjs_query_string_escape(JSContext *cx, JSValueConst this_val, int argc, ret = qjs_string_create_chb(cx, &chain); - njs_chb_destroy(&chain); - JS_FreeCString(cx, (char *) str.start); return ret; @@ -733,7 +726,7 @@ qjs_query_string_stringify_internal(JSContext *cx, JSValue obj, njs_str_t *sep, { int rc; uint32_t n, length; - JSValue key, val, ret; + JSValue key, val; njs_str_t sep_val, eq_val; njs_chb_t chain; JSPropertyEnum *ptab; @@ -809,11 +802,7 @@ qjs_query_string_stringify_internal(JSContext *cx, JSValue obj, njs_str_t *sep, qjs_free_prop_enum(cx, ptab, length); } - ret = qjs_string_create_chb(cx, &chain); - - njs_chb_destroy(&chain); - - return ret; + return qjs_string_create_chb(cx, &chain); fail: diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c index ae970eb1..369ae50b 100644 --- a/nginx/ngx_http_js_module.c +++ b/nginx/ngx_http_js_module.c @@ -6400,10 +6400,9 @@ ngx_http_qjs_header_generic(JSContext *cx, ngx_http_request_t *r, ngx_list_t *headers, ngx_table_elt_t **ph, ngx_str_t *name, JSPropertyDescriptor *pdesc, unsigned flags) { - int ret; u_char sep; - njs_chb_t chain; JSValue val; + njs_chb_t chain; ngx_uint_t i; ngx_list_part_t *part; ngx_table_elt_t *header, *h; @@ -6493,6 +6492,10 @@ ngx_http_qjs_header_generic(JSContext *cx, ngx_http_request_t *r, return 1; } + if (pdesc == NULL) { + return 1; + } + NJS_CHB_CTX_INIT(&chain, cx); sep = flags & NJS_HEADER_SEMICOLON ? ';' : ','; @@ -6503,24 +6506,15 @@ ngx_http_qjs_header_generic(JSContext *cx, ngx_http_request_t *r, njs_chb_append_literal(&chain, " "); } - ret = 1; - - if (pdesc != NULL) { - pdesc->flags = JS_PROP_ENUMERABLE; - pdesc->getter = JS_UNDEFINED; - pdesc->setter = JS_UNDEFINED; - pdesc->value = qjs_string_create_chb(cx, &chain); - if (JS_IsException(pdesc->value)) { - ret = -1; - goto done; - } + pdesc->flags = JS_PROP_ENUMERABLE; + pdesc->getter = JS_UNDEFINED; + pdesc->setter = JS_UNDEFINED; + pdesc->value = qjs_string_create_chb(cx, &chain); + if (JS_IsException(pdesc->value)) { + return -1; } -done: - - njs_chb_destroy(&chain); - - return ret; + return 1; } diff --git a/src/qjs.c b/src/qjs.c index 52401983..38015bb9 100644 --- a/src/qjs.c +++ b/src/qjs.c @@ -1015,6 +1015,8 @@ qjs_string_create_chb(JSContext *cx, njs_chb_t *chain) njs_str_t str; ret = njs_chb_join(chain, &str); + njs_chb_destroy(chain); + if (ret != NJS_OK) { return JS_ThrowInternalError(cx, "failed to create string"); } From noreply at nginx.com Thu Mar 20 03:43:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Thu, 20 Mar 2025 03:43:02 +0000 (UTC) Subject: [njs] QuickJS: using JS_AddIntrinsicBigInt() if detected. Message-ID: <20250320034302.A1165478F1@pubserv1.nginx> details: https://github.com/nginx/njs/commit/f3454f01588a6eabfc7c883577544019fbb50f76 branches: master commit: f3454f01588a6eabfc7c883577544019fbb50f76 user: Vadim Zhestikov date: Wed, 19 Mar 2025 09:12:45 -0700 description: QuickJS: using JS_AddIntrinsicBigInt() if detected. --- auto/quickjs | 23 +++++++++++++++++++++++ src/qjs.c | 2 ++ 2 files changed, 25 insertions(+) diff --git a/auto/quickjs b/auto/quickjs index 97432142..e4eecd29 100644 --- a/auto/quickjs +++ b/auto/quickjs @@ -137,6 +137,29 @@ if [ $NJS_TRY_QUICKJS = YES ]; then . auto/feature + njs_feature="QuickJS JS_AddIntrinsicBigInt()" + njs_feature_name=NJS_HAVE_QUICKJS_ADD_INTRINSIC_BIG_INT + njs_feature_test="#if defined(__GNUC__) && (__GNUC__ >= 8) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored \"-Wcast-function-type\" + #endif + + #include + + int main() { + JSRuntime *rt; + JSContext *ctx; + + rt = JS_NewRuntime(); + ctx = JS_NewContextRaw(rt); + JS_AddIntrinsicBigInt(ctx); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + return 0; + }" + + . auto/feature + njs_feature="QuickJS version" njs_feature_name=NJS_QUICKJS_VERSION njs_feature_run=value diff --git a/src/qjs.c b/src/qjs.c index 38015bb9..7763c165 100644 --- a/src/qjs.c +++ b/src/qjs.c @@ -165,7 +165,9 @@ qjs_new_context(JSRuntime *rt, qjs_module_t **addons) JS_AddIntrinsicMapSet(ctx); JS_AddIntrinsicTypedArrays(ctx); JS_AddIntrinsicPromise(ctx); +#ifdef NJS_HAVE_QUICKJS_ADD_INTRINSIC_BIG_INT JS_AddIntrinsicBigInt(ctx); +#endif JS_AddIntrinsicEval(ctx); for (module = qjs_modules; *module != NULL; module++) { From noreply at nginx.com Mon Mar 24 22:58:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Mon, 24 Mar 2025 22:58:02 +0000 (UTC) Subject: [njs] QuickJS: added missed JS_NewClass() for the SharedDictError class. Message-ID: <20250324225802.81FBC478F5@pubserv1.nginx> details: https://github.com/nginx/njs/commit/02ccc1168c41014bcc267fe932afd34ce63c7f55 branches: master commit: 02ccc1168c41014bcc267fe932afd34ce63c7f55 user: hongzhidao date: Mon, 24 Mar 2025 15:40:15 +0800 description: QuickJS: added missed JS_NewClass() for the SharedDictError class. --- nginx/ngx_js_shared_dict.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/nginx/ngx_js_shared_dict.c b/nginx/ngx_js_shared_dict.c index 7f8c808e..c0928088 100644 --- a/nginx/ngx_js_shared_dict.c +++ b/nginx/ngx_js_shared_dict.c @@ -465,6 +465,11 @@ static JSClassDef ngx_qjs_shared_class = { }, }; +static JSClassDef ngx_qjs_shared_dict_error_class = { + "SharedDictError", + .finalizer = NULL, +}; + qjs_module_t ngx_qjs_ngx_shared_dict_module = { .name = "shared_dict", .init = ngx_qjs_ngx_shared_dict_init, @@ -3001,6 +3006,12 @@ ngx_qjs_ngx_shared_dict_init(JSContext *cx, const char *name) return NULL; } + if (JS_NewClass(JS_GetRuntime(cx), NGX_QJS_CLASS_ID_SHARED_DICT_ERROR, + &ngx_qjs_shared_dict_error_class) < 0) + { + return NULL; + } + proto = JS_NewObject(cx); if (JS_IsException(proto)) { return NULL; From noreply at nginx.com Tue Mar 25 01:36:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Tue, 25 Mar 2025 01:36:02 +0000 (UTC) Subject: [njs] QuickJS: introduced qjs_promise_result(). Message-ID: <20250325013602.0C32947902@pubserv1.nginx> details: https://github.com/nginx/njs/commit/87086636eda9a967a44f8aa6b695dbfe916574a4 branches: master commit: 87086636eda9a967a44f8aa6b695dbfe916574a4 user: Dmitry Volyntsev date: Tue, 25 Mar 2025 08:39:06 +0800 description: QuickJS: introduced qjs_promise_result(). --- external/qjs_fs_module.c | 35 ++++------------------ external/qjs_webcrypto_module.c | 64 ++++++++--------------------------------- src/qjs.c | 39 +++++++++++++++++++++++++ src/qjs.h | 2 ++ 4 files changed, 58 insertions(+), 82 deletions(-) diff --git a/external/qjs_fs_module.c b/external/qjs_fs_module.c index b09d1dcd..48ae9833 100644 --- a/external/qjs_fs_module.c +++ b/external/qjs_fs_module.c @@ -2689,18 +2689,10 @@ qjs_fs_filehandle_finalizer(JSRuntime *rt, JSValue val) } -static JSValue -qjs_fs_promise_trampoline(JSContext *cx, int argc, JSValueConst *argv) -{ - return JS_Call(cx, argv[0], JS_UNDEFINED, 1, &argv[1]); -} - - static JSValue qjs_fs_result(JSContext *cx, JSValue result, int calltype, JSValue callback) { - JS_BOOL is_error; - JSValue promise, callbacks[2], arguments[2]; + JSValue promise, arguments[2]; switch (calltype) { case QJS_FS_DIRECT: @@ -2712,29 +2704,12 @@ qjs_fs_result(JSContext *cx, JSValue result, int calltype, JSValue callback) return result; case QJS_FS_PROMISE: - promise = JS_NewPromiseCapability(cx, callbacks); - if (JS_IsException(promise)) { - JS_FreeValue(cx, result); - return JS_EXCEPTION; - } - - is_error = !!JS_IsError(cx, result); - - arguments[0] = callbacks[is_error]; - arguments[1] = result; - JS_FreeValue(cx, callbacks[!is_error]); - - if (JS_EnqueueJob(cx, qjs_fs_promise_trampoline, 2, arguments) < 0) { - JS_FreeValue(cx, promise); - JS_FreeValue(cx, callbacks[is_error]); - JS_FreeValue(cx, result); - return JS_EXCEPTION; + if (JS_IsError(cx, result)) { + JS_Throw(cx, result); + return qjs_promise_result(cx, JS_EXCEPTION); } - JS_FreeValue(cx, arguments[0]); - JS_FreeValue(cx, arguments[1]); - - return promise; + return qjs_promise_result(cx, result); case QJS_FS_CALLBACK: if (JS_IsError(cx, result)) { diff --git a/external/qjs_webcrypto_module.c b/external/qjs_webcrypto_module.c index 9552ca12..9c4bb452 100644 --- a/external/qjs_webcrypto_module.c +++ b/external/qjs_webcrypto_module.c @@ -156,7 +156,6 @@ static const char *qjs_algorithm_string(qjs_webcrypto_algorithm_t *algorithm); static const char *qjs_algorithm_hash_name(qjs_webcrypto_hash_t hash); static JSValue qjs_key_usage(JSContext *cx, JSValue value, unsigned *mask); static JSValue qjs_key_ops(JSContext *cx, unsigned mask); -static JSValue qjs_webcrypto_result(JSContext *cx, JSValue result, int rc); static void qjs_webcrypto_error(JSContext *cx, const char *fmt, ...); static JSModuleDef *qjs_webcrypto_init(JSContext *cx, const char *name); @@ -547,11 +546,11 @@ qjs_webcrypto_cipher(JSContext *cx, JSValueConst this_val, } } - return qjs_webcrypto_result(cx, ret, 0); + return qjs_promise_result(cx, ret); fail: - return qjs_webcrypto_result(cx, JS_UNDEFINED, -1); + return qjs_promise_result(cx, JS_EXCEPTION); } @@ -1956,7 +1955,7 @@ free: ret = qjs_new_array_buffer(cx, k, length); } - return qjs_webcrypto_result(cx, ret, 0); + return qjs_promise_result(cx, ret); fail: @@ -1964,7 +1963,7 @@ fail: js_free(cx, k); - return qjs_webcrypto_result(cx, JS_UNDEFINED, -1); + return qjs_promise_result(cx, JS_EXCEPTION); } @@ -2170,11 +2169,11 @@ qjs_webcrypto_export_key(JSContext *cx, JSValueConst this_val, int argc, } - return qjs_webcrypto_result(cx, ret, 0); + return qjs_promise_result(cx, ret); fail: - return qjs_webcrypto_result(cx, JS_UNDEFINED, -1); + return qjs_promise_result(cx, JS_EXCEPTION); } @@ -2437,7 +2436,7 @@ qjs_webcrypto_generate_key(JSContext *cx, JSValueConst this_val, goto fail; } - return qjs_webcrypto_result(cx, obj, 0); + return qjs_promise_result(cx, obj); fail: @@ -2448,7 +2447,7 @@ fail: JS_FreeValue(cx, key); JS_FreeValue(cx, keypub); - return qjs_webcrypto_result(cx, JS_UNDEFINED, -1); + return qjs_promise_result(cx, JS_EXCEPTION); } @@ -3489,7 +3488,7 @@ qjs_webcrypto_import_key(JSContext *cx, JSValueConst this_val, int argc, break; } - return qjs_webcrypto_result(cx, key, 0); + return qjs_promise_result(cx, key); fail: @@ -3499,7 +3498,7 @@ fail: JS_FreeValue(cx, key); - return qjs_webcrypto_result(cx, JS_UNDEFINED, -1); + return qjs_promise_result(cx, JS_EXCEPTION); } @@ -3955,7 +3954,7 @@ qjs_webcrypto_sign(JSContext *cx, JSValueConst this_val, int argc, ret = JS_NewBool(cx, rc != 0); } - return qjs_webcrypto_result(cx, ret, 0); + return qjs_promise_result(cx, ret); fail: @@ -3971,7 +3970,7 @@ fail: js_free(cx, dst); } - return qjs_webcrypto_result(cx, JS_UNDEFINED, -1); + return qjs_promise_result(cx, JS_EXCEPTION); } @@ -4669,45 +4668,6 @@ qjs_cpystrn(u_char *dst, u_char *src, size_t n) } -static JSValue -qjs_webcrypto_promise_trampoline(JSContext *cx, int argc, JSValueConst *argv) -{ - return JS_Call(cx, argv[0], JS_UNDEFINED, 1, &argv[1]); -} - - -static JSValue -qjs_webcrypto_result(JSContext *cx, JSValue result, int rc) -{ - JS_BOOL is_error; - JSValue promise, callbacks[2], arguments[2]; - - promise = JS_NewPromiseCapability(cx, callbacks); - if (JS_IsException(promise)) { - JS_FreeValue(cx, result); - return JS_EXCEPTION; - } - - is_error = !!(rc != 0); - - JS_FreeValue(cx, callbacks[!is_error]); - arguments[0] = callbacks[is_error]; - arguments[1] = is_error ? JS_GetException(cx) : result; - - if (JS_EnqueueJob(cx, qjs_webcrypto_promise_trampoline, 2, arguments) < 0) { - JS_FreeValue(cx, promise); - JS_FreeValue(cx, callbacks[is_error]); - JS_FreeValue(cx, arguments[1]); - return JS_EXCEPTION; - } - - JS_FreeValue(cx, arguments[0]); - JS_FreeValue(cx, arguments[1]); - - return promise; -} - - static void qjs_webcrypto_error(JSContext *cx, const char *fmt, ...) { diff --git a/src/qjs.c b/src/qjs.c index 7763c165..a941ba71 100644 --- a/src/qjs.c +++ b/src/qjs.c @@ -1145,3 +1145,42 @@ qjs_string_base64url(JSContext *cx, const njs_str_t *src) return ret; } + + +static JSValue +qjs_promise_fill_trampoline(JSContext *cx, int argc, JSValueConst *argv) +{ + return JS_Call(cx, argv[0], JS_UNDEFINED, 1, &argv[1]); +} + + +JSValue +qjs_promise_result(JSContext *cx, JSValue result) +{ + JS_BOOL is_error; + JSValue promise, callbacks[2], arguments[2]; + + promise = JS_NewPromiseCapability(cx, callbacks); + if (JS_IsException(promise)) { + JS_FreeValue(cx, result); + return JS_EXCEPTION; + } + + is_error = JS_IsException(result); + + JS_FreeValue(cx, callbacks[!is_error]); + arguments[0] = callbacks[is_error]; + arguments[1] = is_error ? JS_GetException(cx) : result; + + if (JS_EnqueueJob(cx, qjs_promise_fill_trampoline, 2, arguments) < 0) { + JS_FreeValue(cx, promise); + JS_FreeValue(cx, callbacks[is_error]); + JS_FreeValue(cx, result); + return JS_EXCEPTION; + } + + JS_FreeValue(cx, arguments[0]); + JS_FreeValue(cx, arguments[1]); + + return promise; +} diff --git a/src/qjs.h b/src/qjs.h index 7c13f039..c7ef4de0 100644 --- a/src/qjs.h +++ b/src/qjs.h @@ -129,6 +129,8 @@ JSValue qjs_string_create_chb(JSContext *cx, njs_chb_t *chain); void qjs_free_prop_enum(JSContext *ctx, JSPropertyEnum *tab, uint32_t len); +JSValue qjs_promise_result(JSContext *cx, JSValue result); + JSValue qjs_string_hex(JSContext *cx, const njs_str_t *src); JSValue qjs_string_base64(JSContext *cx, const njs_str_t *src); JSValue qjs_string_base64url(JSContext *cx, const njs_str_t *src); From noreply at nginx.com Wed Mar 26 02:00:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 26 Mar 2025 02:00:02 +0000 (UTC) Subject: [njs] QuickJS: fixed compatibility with recent change in upstream. Message-ID: <20250326020002.0A9F548389@pubserv1.nginx> details: https://github.com/nginx/njs/commit/76a5e4586edd0041c7a2e23b08cbd202aa2ae8cf branches: master commit: 76a5e4586edd0041c7a2e23b08cbd202aa2ae8cf user: Dmitry Volyntsev date: Tue, 25 Mar 2025 17:08:09 -0700 description: QuickJS: fixed compatibility with recent change in upstream. JS_VALUE_GET_OBJ(v) was made hidden in 156d981. --- src/qjs_buffer.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qjs_buffer.c b/src/qjs_buffer.c index dfe7b04d..48f609be 100644 --- a/src/qjs_buffer.c +++ b/src/qjs_buffer.c @@ -707,8 +707,8 @@ qjs_buffer_is_buffer(JSContext *ctx, JSValueConst this_val, proto = JS_GetPrototype(ctx, argv[0]); buffer_proto = JS_GetClassProto(ctx, QJS_CORE_CLASS_ID_BUFFER); - ret = JS_NewBool(ctx, JS_VALUE_GET_TAG(argv[0]) == JS_TAG_OBJECT && - JS_VALUE_GET_OBJ(buffer_proto) == JS_VALUE_GET_OBJ(proto)); + ret = JS_NewBool(ctx, JS_IsObject(argv[0]) + && qjs_is_same_value(ctx, proto, buffer_proto)); JS_FreeValue(ctx, buffer_proto); JS_FreeValue(ctx, proto); From pluknet at nginx.com Thu Mar 27 11:20:59 2025 From: pluknet at nginx.com (Sergey Kandaurov) Date: Thu, 27 Mar 2025 15:20:59 +0400 Subject: Use directive code from other module inside my custom ngx_http_mycontrol_module In-Reply-To: References: Message-ID: > On 12 Mar 2025, at 19:04, Zyrot System wrote: > > Hello everyone, first I want to say I already searched mailman.nginx.org for a topic relating to my question but I cannot find it. > > ngx_http_rewrite_module has directives rewrite, return, etc. I want to use directives code from other module inside my module so can do code reuse "DRY", for example create my own directive like: > server { # important to work in this Context > > mycontrol * { > myrewrite ^(/download/.*)/media/(.*)\..*$ $1/mp3/$2.mp3 last; # using the core ngx_http_rewrite_module.c > > } > mycontrol * { > # optional using the original ngx_http_rewrite_module.c > rewrite ^(/download/.*)/media/(.*)\..*$ $1/mp3/$2.mp3 last; > > } > } > > So can avoid replicate the already functionality (I want to extend the ngx_http_rewrite_module, and others modules in my module mycontrol with more features). Any ideas ? > I just want to be sure for what route to go, maybe instead I should used the PCRE library directly in ngx_http_mycontrol_module like does other modules ? > Note that in ngx_http_rewrite_module, PCRE API is accessed indirectly, via ngx_http_script.c / ngx_http_variables.c / ngx_regex.c interfaces reused with other regex users. It may depend on your needs on which layer is to reuse the code. -- Sergey Kandaurov From pluknet at nginx.com Thu Mar 27 11:36:00 2025 From: pluknet at nginx.com (Sergey Kandaurov) Date: Thu, 27 Mar 2025 15:36:00 +0400 Subject: Proposal: Change `ssl_client_certificate` to `ssl_client_ca_certificate` In-Reply-To: <3545d0b5-b53f-4d45-b82c-23bb593c22f2@thomas-ward.net> References: <3545d0b5-b53f-4d45-b82c-23bb593c22f2@thomas-ward.net> Message-ID: <7E39E0EB-0CDD-4913-A45C-15806B4AAB5E@nginx.com> > On 16 Mar 2025, at 01:55, Thomas Ward via nginx-devel wrote: > > In line with a recent nginx mailing list thread I had with a user about how to properly secure a site with SSL/TLS Client Certificates, the user indicated that "ssl_client_certificate" is a confusing misnomer. It implies that the certificate(s) provided are a bundle of certs that are *individual client certificates* not the Certification Authority (CA) certificate and chain that issued the certificiates. > It is clearly documented as a directive to specify CA certificates, please see http://nginx.org/r/ssl_client_certificate for details. > It's always annoyed me slightly that it has been "ssl_client_certificate" and has no mention of it being a CA cert. I'm guessing that's because you could theoretically use a self-signed certificate and verify it against itself, thus not needing a CA certificate, however that's not the primary use case nor is that how it's really explained in the NGINX documentation of the command. > > Has there been any discussion or consideration of renaming ssl_client_certificate to something that is less confusing to people new to the process, to show that this is supposed to be the CA certificate of the authority that is issuing the client certificates? No, thanks. It will make more harm than good. As always, to avoid ambiguity, consider consulting with documentation. The official documentation for nginx can be accessed here: http://nginx.org/en/docs/ -- Sergey Kandaurov From noreply at nginx.com Thu Mar 27 16:05:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Thu, 27 Mar 2025 16:05:02 +0000 (UTC) Subject: [njs] XML: allowing to remove the property $text for XMLNode. Message-ID: <20250327160502.4530F4839B@pubserv1.nginx> details: https://github.com/nginx/njs/commit/f89644e7a4b96c3a6dbd603e7b45ea4c87c5b7a0 branches: master commit: f89644e7a4b96c3a6dbd603e7b45ea4c87c5b7a0 user: Dmitry Volyntsev date: Mon, 3 Mar 2025 17:07:59 -0800 description: XML: allowing to remove the property $text for XMLNode. --- external/njs_xml_module.c | 1 + 1 file changed, 1 insertion(+) diff --git a/external/njs_xml_module.c b/external/njs_xml_module.c index f66c8cdb..6bdbb639 100644 --- a/external/njs_xml_module.c +++ b/external/njs_xml_module.c @@ -370,6 +370,7 @@ static njs_external_t njs_ext_xml_node[] = { .name.string = njs_str("$text"), .enumerable = 1, .writable = 1, + .configurable = 1, .u.property = { .handler = njs_xml_node_ext_text, } From noreply at nginx.com Thu Mar 27 16:05:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Thu, 27 Mar 2025 16:05:02 +0000 (UTC) Subject: [njs] XML: fixed serializeToString(). Message-ID: <20250327160502.408AD4839A@pubserv1.nginx> details: https://github.com/nginx/njs/commit/1f1dd0ae9a97fc4acd14751403be547c8575edb5 branches: master commit: 1f1dd0ae9a97fc4acd14751403be547c8575edb5 user: Dmitry Volyntsev date: Wed, 26 Feb 2025 22:12:31 -0800 description: XML: fixed serializeToString(). Previously, serializeToString() was exclusiveC14n() which returned string instead of Buffer. According to the published documentation it should be c14n(). --- external/njs_xml_module.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/njs_xml_module.c b/external/njs_xml_module.c index d5ab9ddd..f66c8cdb 100644 --- a/external/njs_xml_module.c +++ b/external/njs_xml_module.c @@ -181,7 +181,7 @@ static njs_external_t njs_ext_xml[] = { .enumerable = 1, .u.method = { .native = njs_xml_ext_canonicalization, - .magic8 = 3, + .magic8 = 2, } }, From noreply at nginx.com Thu Mar 27 16:05:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Thu, 27 Mar 2025 16:05:02 +0000 (UTC) Subject: [njs] Test262: improved compatibility with node.js for fs/methods.t.mjs. Message-ID: <20250327160502.3D06B48399@pubserv1.nginx> details: https://github.com/nginx/njs/commit/4ee93af07245da8750feeaf6a1a1b965f40c63c4 branches: master commit: 4ee93af07245da8750feeaf6a1a1b965f40c63c4 user: Dmitry Volyntsev date: Tue, 25 Feb 2025 23:05:31 -0800 description: Test262: improved compatibility with node.js for fs/methods.t.mjs. --- test/fs/methods.t.mjs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test/fs/methods.t.mjs b/test/fs/methods.t.mjs index 705c1b5a..72e42cb9 100644 --- a/test/fs/methods.t.mjs +++ b/test/fs/methods.t.mjs @@ -21,6 +21,10 @@ function p(args, default_opts) { return params; } +function has_quickjs() { + return (typeof njs != 'undefined' && njs.engine == 'QuickJS'); +} + function promisify(f) { return function (...args) { return new Promise((resolve, reject) => { @@ -116,7 +120,7 @@ let readfile_tests = () => [ return true; } }, - { args: ["test/fs/non_utf8", "utf8"], expected: "��", skip() { return njs && njs.engine == 'QuickJS'; } }, + { args: ["test/fs/non_utf8", "utf8"], expected: "��", skip: () => has_quickjs() }, { args: ["test/fs/non_utf8", {encoding: "hex"}], expected: "8080" }, { args: ["test/fs/non_utf8", "base64"], expected: "gIA=" }, { args: ["test/fs/ascii", "utf8"], expected: "x".repeat(600) }, @@ -219,7 +223,7 @@ let writefile_tests = () => [ { args: ["@", Buffer.from("XYZ"), {encoding: "utf8", mode: 0o666}], expected: Buffer.from("XYZ") }, { args: ["@", new DataView(Buffer.alloc(3).fill(66).buffer)], - expected: Buffer.from("BBB"), skip() { return njs && njs.engine == 'QuickJS'; } }, + expected: Buffer.from("BBB"), skip: () => has_quickjs() }, { args: ["@", new Uint8Array(Buffer.from("ABCD"))], expected: Buffer.from("ABCD")}, { args: ["@", "XYZ"], expected: Buffer.from("XYZ")}, @@ -309,7 +313,7 @@ let append_tests = () => [ { args: ["@", Buffer.from("XYZ"), {encoding: "utf8", mode: 0o666}], expected: Buffer.from("XYZXYZ") }, { args: ["@", new DataView(Buffer.alloc(3).fill(66).buffer)], - expected: Buffer.from("BBBBBB"), skip() { return njs && njs.engine == 'QuickJS'; } }, + expected: Buffer.from("BBBBBB"), skip: () => has_quickjs() }, { args: ["@", new Uint8Array(Buffer.from("ABCD"))], expected: Buffer.from("ABCDABCD")}, { args: ["@", "XYZ"], expected: Buffer.from("XYZXYZ")}, From noreply at nginx.com Thu Mar 27 16:05:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Thu, 27 Mar 2025 16:05:02 +0000 (UTC) Subject: [njs] QuickJS: added xml module. Message-ID: <20250327160502.53C404839C@pubserv1.nginx> details: https://github.com/nginx/njs/commit/cec9a1650a5a46bbede4a0b7abd750d25df19f28 branches: master commit: cec9a1650a5a46bbede4a0b7abd750d25df19f28 user: Dmitry Volyntsev date: Tue, 18 Mar 2025 16:22:53 -0700 description: QuickJS: added xml module. --- .github/workflows/check-pr.yml | 2 +- auto/qjs_modules | 8 + external/qjs_xml_module.c | 2161 +++++++++++++++++++++++++++++++++ src/qjs.h | 5 +- src/test/njs_unit_test.c | 284 ----- test/xml/external_entity_ignored.t.js | 6 +- test/xml/saml_verify.t.mjs | 9 +- test/xml/xml.t.mjs | 385 ++++++ 8 files changed, 2568 insertions(+), 292 deletions(-) diff --git a/.github/workflows/check-pr.yml b/.github/workflows/check-pr.yml index 7cff7937..75e590fe 100644 --- a/.github/workflows/check-pr.yml +++ b/.github/workflows/check-pr.yml @@ -32,7 +32,7 @@ jobs: run: | sudo dpkg --add-architecture i386 sudo apt-get update - sudo apt-get install -y gcc-multilib libc6:i386 libpcre2-dev:i386 zlib1g-dev:i386 + sudo apt-get install -y gcc-multilib libc6:i386 libssl-dev:i386 libpcre2-dev:i386 zlib1g-dev:i386 libxml2-dev:i386 - name: Check out nginx run: | diff --git a/auto/qjs_modules b/auto/qjs_modules index 1381d2c5..03501d7d 100644 --- a/auto/qjs_modules +++ b/auto/qjs_modules @@ -33,6 +33,14 @@ if [ $NJS_OPENSSL = YES -a $NJS_HAVE_OPENSSL = YES ]; then . auto/qjs_module fi +if [ $NJS_LIBXML2 = YES -a $NJS_HAVE_LIBXML2 = YES ]; then + njs_module_name=qjs_xml_module + njs_module_incs= + njs_module_srcs=external/qjs_xml_module.c + + . auto/qjs_module +fi + if [ $NJS_ZLIB = YES -a $NJS_HAVE_ZLIB = YES ]; then njs_module_name=qjs_zlib_module njs_module_incs= diff --git a/external/qjs_xml_module.c b/external/qjs_xml_module.c new file mode 100644 index 00000000..087b4a7b --- /dev/null +++ b/external/qjs_xml_module.c @@ -0,0 +1,2161 @@ + +/* + * Copyright (C) Dmitry Volyntsev + * Copyright (C) F5, Inc. + */ + +#include +#include +#include +#include +#include +#include + + +typedef struct { + xmlDoc *doc; + xmlParserCtxt *ctx; + xmlNode *free; + int ref_count; +} qjs_xml_doc_t; + + +typedef struct { + xmlNode *node; + qjs_xml_doc_t *doc; +} qjs_xml_node_t; + + +typedef struct { + xmlAttr *attr; + qjs_xml_doc_t *doc; +} qjs_xml_attr_t; + + +typedef enum { + XML_NSET_TREE = 0, + XML_NSET_TREE_NO_COMMENTS, + XML_NSET_TREE_INVERT, +} qjs_xml_nset_type_t; + + +typedef struct qjs_xml_nset_s qjs_xml_nset_t; + +struct qjs_xml_nset_s { + xmlNodeSet *nodes; + xmlDoc *doc; + qjs_xml_nset_type_t type; + qjs_xml_nset_t *next; + qjs_xml_nset_t *prev; +}; + + +static JSValue qjs_xml_parse(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv); +static JSValue qjs_xml_canonicalization(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic); + +static int qjs_xml_doc_get_own_property(JSContext *cx, + JSPropertyDescriptor *pdesc, JSValueConst obj, JSAtom prop); +static int qjs_xml_doc_get_own_property_names(JSContext *cx, + JSPropertyEnum **ptab, uint32_t *plen, JSValueConst obj); +static void qjs_xml_doc_finalizer(JSRuntime *rt, JSValue val); +static void qjs_xml_doc_free(JSRuntime *rt, qjs_xml_doc_t *current); + +static JSValue qjs_xml_node_make(JSContext *cx, qjs_xml_doc_t *doc, + xmlNode *node); +static int qjs_xml_node_get_own_property(JSContext *cx, + JSPropertyDescriptor *pdesc, JSValueConst obj, JSAtom prop); +static int qjs_xml_node_get_own_property_names(JSContext *cx, + JSPropertyEnum **ptab, uint32_t *plen, JSValueConst obj); +static int qjs_xml_node_set_property(JSContext *cx, JSValueConst obj, + JSAtom atom, JSValueConst value, JSValueConst receiver, int flags); +static int qjs_xml_node_delete_property(JSContext *cx, JSValueConst obj, + JSAtom prop); +static int qjs_xml_node_define_own_property(JSContext *cx, JSValueConst obj, + JSAtom atom, JSValueConst value, JSValueConst getter, JSValueConst setter, + int flags); +static JSValue qjs_xml_node_add_child(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv); +static JSValue qjs_xml_node_remove_children(JSContext *cx, + JSValueConst this_val, int argc, JSValueConst *argv); +static JSValue qjs_xml_node_set_attribute(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv); +static JSValue qjs_xml_node_remove_attribute(JSContext *cx, + JSValueConst this_val, int argc, JSValueConst *argv); +static JSValue qjs_xml_node_remove_all_attributes(JSContext *cx, + JSValueConst this_val, int argc, JSValueConst *argv); +static JSValue qjs_xml_node_set_text(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv); +static JSValue qjs_xml_node_remove_text(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv); +static void qjs_xml_node_finalizer(JSRuntime *rt, JSValue val); +static xmlNode *qjs_xml_node(JSContext *cx, JSValueConst val, xmlDoc **doc); + +static JSValue qjs_xml_attr_make(JSContext *cx, qjs_xml_doc_t *doc, + xmlAttr *attr); +static int qjs_xml_attr_get_own_property(JSContext *cx, + JSPropertyDescriptor *pdesc, JSValueConst obj, JSAtom prop); +static int qjs_xml_attr_get_own_property_names(JSContext *cx, + JSPropertyEnum **ptab, uint32_t *plen, JSValueConst obj); +static void qjs_xml_attr_finalizer(JSRuntime *rt, JSValue val); + +static u_char **qjs_xml_parse_ns_list(JSContext *cx, u_char *src); +static int qjs_xml_c14n_visibility_cb(void *user_data, xmlNode *node, + xmlNode *parent); +static int qjs_xml_buf_write_cb(void *context, const char *buffer, int len); +static qjs_xml_nset_t *qjs_xml_nset_create(JSContext *cx, xmlDoc *doc, + xmlNode *current, qjs_xml_nset_type_t type); +static qjs_xml_nset_t *qjs_xml_nset_add(qjs_xml_nset_t *nset, + qjs_xml_nset_t *add); +static void qjs_xml_nset_free(JSContext *cx, qjs_xml_nset_t *nset); +static int qjs_xml_encode_special_chars(JSContext *cx, njs_str_t *src, + njs_str_t *out); +static void qjs_xml_replace_node(JSContext *cx, qjs_xml_node_t *node, + xmlNode *current); + +static void qjs_xml_error(JSContext *cx, qjs_xml_doc_t *current, + const char *fmt, ...); +static JSModuleDef *qjs_xml_init(JSContext *ctx, const char *name); + + +static const JSCFunctionListEntry qjs_xml_export[] = { + JS_CFUNC_DEF("parse", 2, qjs_xml_parse), + JS_CFUNC_MAGIC_DEF("c14n", 4, qjs_xml_canonicalization, 0), + JS_CFUNC_MAGIC_DEF("exclusiveC14n", 4, qjs_xml_canonicalization, 1), + JS_CFUNC_MAGIC_DEF("serialize", 4, qjs_xml_canonicalization, 0), + JS_CFUNC_MAGIC_DEF("serializeToString", 4, qjs_xml_canonicalization, 2), +}; + + +static const JSCFunctionListEntry qjs_xml_doc_proto[] = { + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "XMLDoc", JS_PROP_CONFIGURABLE), +}; + + +static const JSCFunctionListEntry qjs_xml_node_proto[] = { + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "XMLNode", JS_PROP_CONFIGURABLE), + JS_CFUNC_DEF("addChild", 1, qjs_xml_node_add_child), + JS_CFUNC_DEF("removeChildren", 1, qjs_xml_node_remove_children), + JS_CFUNC_DEF("setAttribute", 2, qjs_xml_node_set_attribute), + JS_CFUNC_DEF("removeAttribute", 1, qjs_xml_node_remove_attribute), + JS_CFUNC_DEF("removeAllAttributes", 0, qjs_xml_node_remove_all_attributes), + JS_CFUNC_DEF("setText", 1, qjs_xml_node_set_text), + JS_CFUNC_DEF("removeText", 0, qjs_xml_node_remove_text), +}; + + +static const JSCFunctionListEntry qjs_xml_attr_proto[] = { + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "XMLAttr", JS_PROP_CONFIGURABLE), +}; + + +static JSClassDef qjs_xml_doc_class = { + "XMLDoc", + .finalizer = qjs_xml_doc_finalizer, + .exotic = & (JSClassExoticMethods) { + .get_own_property = qjs_xml_doc_get_own_property, + .get_own_property_names = qjs_xml_doc_get_own_property_names, + }, +}; + + +static JSClassDef qjs_xml_node_class = { + "XMLNode", + .finalizer = qjs_xml_node_finalizer, + .exotic = & (JSClassExoticMethods) { + .get_own_property = qjs_xml_node_get_own_property, + .get_own_property_names = qjs_xml_node_get_own_property_names, + .set_property = qjs_xml_node_set_property, + .delete_property = qjs_xml_node_delete_property, + .define_own_property = qjs_xml_node_define_own_property, + }, +}; + + +static JSClassDef qjs_xml_attr_class = { + "XMLAttr", + .finalizer = qjs_xml_attr_finalizer, + .exotic = & (JSClassExoticMethods) { + .get_own_property = qjs_xml_attr_get_own_property, + .get_own_property_names = qjs_xml_attr_get_own_property_names, + }, +}; + + +qjs_module_t qjs_xml_module = { + .name = "xml", + .init = qjs_xml_init, +}; + + +static JSValue +qjs_xml_parse(JSContext *cx, JSValueConst this_val, int argc, + JSValueConst *argv) +{ + JSValue ret; + qjs_bytes_t data; + qjs_xml_doc_t *tree; + + if (qjs_to_bytes(cx, &data, argv[0]) < 0) { + return JS_EXCEPTION; + } + + tree = js_mallocz(cx, sizeof(qjs_xml_doc_t)); + if (tree == NULL) { + qjs_bytes_free(cx, &data); + JS_ThrowOutOfMemory(cx); + return JS_EXCEPTION; + } + + tree->ref_count = 1; + + tree->ctx = xmlNewParserCtxt(); + if (tree->ctx == NULL) { + qjs_bytes_free(cx, &data); + JS_ThrowInternalError(cx, "xmlNewParserCtxt() failed"); + qjs_xml_doc_free(JS_GetRuntime(cx), tree); + return JS_EXCEPTION; + } + + tree->doc = xmlCtxtReadMemory(tree->ctx, (char *) data.start, data.length, + NULL, NULL, XML_PARSE_NOWARNING + | XML_PARSE_NOERROR); + qjs_bytes_free(cx, &data); + if (tree->doc == NULL) { + qjs_xml_error(cx, tree, "failed to parse XML"); + qjs_xml_doc_free(JS_GetRuntime(cx), tree); + return JS_EXCEPTION; + } + + ret = JS_NewObjectClass(cx, QJS_CORE_CLASS_ID_XML_DOC); + if (JS_IsException(ret)) { + qjs_xml_doc_free(JS_GetRuntime(cx), tree); + return ret; + } + + JS_SetOpaque(ret, tree); + + return ret; +} + + +static JSValue +qjs_xml_canonicalization(JSContext *cx, JSValueConst this_val, int argc, + JSValueConst *argv, int magic) +{ + int comments; + u_char **prefix_list, *pref; + xmlDoc *doc; + xmlNode *node; + ssize_t size; + JSValue excluding, prefixes, ret; + njs_chb_t chain; + qjs_xml_node_t *nd; + qjs_xml_nset_t *nset, *children; + xmlOutputBuffer *buf; + + node = qjs_xml_node(cx, argv[0], &doc); + if (node == NULL) { + return JS_EXCEPTION; + } + + comments = JS_ToBool(cx, argv[2]); + if (comments < 0) { + return JS_EXCEPTION; + } + + buf = NULL; + nset = NULL; + children = NULL; + + excluding = argv[1]; + if (!JS_IsNullOrUndefined(excluding)) { + nd = JS_GetOpaque(excluding, QJS_CORE_CLASS_ID_XML_NODE); + if (nd == NULL) { + JS_ThrowTypeError(cx, "\"excluding\" argument is not a XMLNode " + "object"); + return JS_EXCEPTION; + } + + nset = qjs_xml_nset_create(cx, doc, node, XML_NSET_TREE_NO_COMMENTS); + if (nset == NULL) { + return JS_ThrowOutOfMemory(cx); + } + + children = qjs_xml_nset_create(cx, nd->doc->doc, nd->node, + XML_NSET_TREE_INVERT); + if (children == NULL) { + qjs_xml_nset_free(cx, nset); + return JS_ThrowOutOfMemory(cx); + } + + nset = qjs_xml_nset_add(nset, children); + + } else { + nset = qjs_xml_nset_create(cx, doc, node, + comments ? XML_NSET_TREE + : XML_NSET_TREE_NO_COMMENTS); + if (nset == NULL) { + return JS_ThrowOutOfMemory(cx); + } + } + + prefix_list = NULL; + prefixes = argv[3]; + if (!JS_IsNullOrUndefined(prefixes)) { + if (!JS_IsString(prefixes)) { + JS_ThrowTypeError(cx, "\"prefixes\" argument is not a string"); + goto fail; + } + + pref = (u_char *) JS_ToCString(cx, prefixes); + if (pref == NULL) { + JS_ThrowOutOfMemory(cx); + goto fail; + } + + prefix_list = qjs_xml_parse_ns_list(cx, pref); + if (prefix_list == NULL) { + goto fail; + } + } + + NJS_CHB_CTX_INIT(&chain, cx); + + buf = xmlOutputBufferCreateIO(qjs_xml_buf_write_cb, NULL, + &chain, NULL); + if (buf == NULL) { + JS_ThrowInternalError(cx, "xmlOutputBufferCreateIO() failed"); + goto fail; + } + + size = xmlC14NExecute(doc, qjs_xml_c14n_visibility_cb, nset, + magic & 0x1 ? XML_C14N_EXCLUSIVE_1_0 : XML_C14N_1_0, + prefix_list, comments, buf); + + if (size < 0) { + njs_chb_destroy(&chain); + (void) xmlOutputBufferClose(buf); + JS_ThrowInternalError(cx, "xmlC14NExecute() failed"); + goto fail; + } + + if (magic & 0x2) { + ret = qjs_string_create_chb(cx, &chain); + + } else { + ret = qjs_buffer_chb_alloc(cx, &chain); + njs_chb_destroy(&chain); + } + + (void) xmlOutputBufferClose(buf); + + qjs_xml_nset_free(cx, nset); + qjs_xml_nset_free(cx, children); + + if (prefix_list != NULL) { + js_free(cx, prefix_list); + } + + return ret; + +fail: + + qjs_xml_nset_free(cx, nset); + qjs_xml_nset_free(cx, children); + + if (prefix_list != NULL) { + js_free(cx, prefix_list); + } + + return JS_EXCEPTION; +} + + +static int +qjs_xml_doc_get_own_property(JSContext *cx, JSPropertyDescriptor *pdesc, + JSValueConst obj, JSAtom prop) +{ + int any; + xmlNode *node; + njs_str_t name; + qjs_xml_doc_t *tree; + + tree = JS_GetOpaque(obj, QJS_CORE_CLASS_ID_XML_DOC); + if (tree == NULL) { + (void) JS_ThrowInternalError(cx, "\"this\" is not an XMLDoc"); + return -1; + } + + name.start = (u_char *) JS_AtomToCString(cx, prop); + if (name.start == NULL) { + return -1; + } + + name.length = njs_strlen(name.start); + + any = (name.length == njs_strlen("$root") + && njs_strncmp(name.start, "$root", name.length) == 0); + + for (node = xmlDocGetRootElement(tree->doc); + node != NULL; + node = node->next) + { + if (node->type != XML_ELEMENT_NODE) { + continue; + } + + if (!any) { + if (name.length != njs_strlen(node->name) + || njs_strncmp(name.start, node->name, name.length) != 0) + { + continue; + } + } + + JS_FreeCString(cx, (char *) name.start); + + if (pdesc != NULL) { + pdesc->flags = JS_PROP_ENUMERABLE; + pdesc->getter = JS_UNDEFINED; + pdesc->setter = JS_UNDEFINED; + pdesc->value = qjs_xml_node_make(cx, tree, node); + if (JS_IsException(pdesc->value)) { + return -1; + } + } + + return 1; + } + + JS_FreeCString(cx, (char *) name.start); + + return 0; +} + + +static int +qjs_xml_push_string(JSContext *cx, JSValue obj, const char *start) +{ + JSAtom key; + + key = JS_NewAtomLen(cx, start, njs_strlen(start)); + if (key == JS_ATOM_NULL) { + return -1; + } + + if (JS_DefinePropertyValue(cx, obj, key, JS_UNDEFINED, + JS_PROP_ENUMERABLE) < 0) + { + JS_FreeAtom(cx, key); + return -1; + } + + JS_FreeAtom(cx, key); + + return 0; +} + + +static int +qjs_xml_doc_get_own_property_names(JSContext *cx, JSPropertyEnum **ptab, + uint32_t *plen, JSValueConst obj) +{ + int rc; + JSValue keys; + xmlNode *node; + qjs_xml_doc_t *tree; + + tree = JS_GetOpaque(obj, QJS_CORE_CLASS_ID_XML_DOC); + if (tree == NULL) { + (void) JS_ThrowInternalError(cx, "\"this\" is not an XMLDoc"); + return -1; + } + + keys = JS_NewObject(cx); + if (JS_IsException(keys)) { + return -1; + } + + for (node = xmlDocGetRootElement(tree->doc); + node != NULL; + node = node->next) + { + if (node->type != XML_ELEMENT_NODE) { + continue; + } + + if (qjs_xml_push_string(cx, keys, (char *) node->name) < 0) { + JS_FreeValue(cx, keys); + return -1; + } + } + + rc = JS_GetOwnPropertyNames(cx, ptab, plen, keys, JS_GPN_STRING_MASK); + + JS_FreeValue(cx, keys); + + return rc; +} + + +static void +qjs_xml_doc_free(JSRuntime *rt, qjs_xml_doc_t *current) +{ + xmlNode *node, *next; + + if (--current->ref_count > 0) { + return; + } + + node = current->free; + + while (node != NULL) { + next = node->next; + xmlFreeNode(node); + node = next; + } + + if (current->doc != NULL) { + xmlFreeDoc(current->doc); + } + + if (current->ctx != NULL) { + xmlFreeParserCtxt(current->ctx); + } + + js_free_rt(rt, current); +} + + +static void +qjs_xml_doc_finalizer(JSRuntime *rt, JSValue val) +{ + qjs_xml_doc_t *current; + + current = JS_GetOpaque(val, QJS_CORE_CLASS_ID_XML_DOC); + + qjs_xml_doc_free(rt, current); +} + + +static JSValue +qjs_xml_node_make(JSContext *cx, qjs_xml_doc_t *doc, xmlNode *node) +{ + JSValue ret; + qjs_xml_node_t *current; + + current = js_malloc(cx, sizeof(qjs_xml_node_t)); + if (current == NULL) { + JS_ThrowOutOfMemory(cx); + return JS_EXCEPTION; + } + + current->node = node; + current->doc = doc; + doc->ref_count++; + + ret = JS_NewObjectClass(cx, QJS_CORE_CLASS_ID_XML_NODE); + if (JS_IsException(ret)) { + js_free(cx, current); + return ret; + } + + JS_SetOpaque(ret, current); + + return ret; +} + + +static JSValue +qjs_xml_node_tag_handler(JSContext *cx, qjs_xml_node_t *current, + njs_str_t *name) +{ + size_t size; + xmlNode *node; + + node = current->node; + + for (node = node->children; node != NULL; node = node->next) { + if (node->type != XML_ELEMENT_NODE) { + continue; + } + + size = njs_strlen(node->name); + + if (name->length > 0 + && (name->length != size + || njs_strncmp(name->start, node->name, size) != 0)) + { + continue; + } + + return qjs_xml_node_make(cx, current->doc, node); + } + + return JS_UNDEFINED; +} + + +static JSValue +qjs_xml_node_tags_handler(JSContext *cx, qjs_xml_node_t *current, + njs_str_t *name) +{ + size_t size; + int32_t i; + xmlNode *node; + JSValue arr, ret; + + arr = JS_NewArray(cx); + if (JS_IsException(arr)) { + return arr; + } + + i = 0; + node = current->node; + + for (node = node->children; node != NULL; node = node->next) { + if (node->type != XML_ELEMENT_NODE) { + continue; + } + + size = njs_strlen(node->name); + + if (name->length > 0 + && (name->length != size + || njs_strncmp(name->start, node->name, size) != 0)) + { + continue; + } + + ret = qjs_xml_node_make(cx, current->doc, node); + if (JS_IsException(ret)) { + JS_FreeValue(cx, arr); + return JS_EXCEPTION; + } + + if (JS_SetPropertyUint32(cx, arr, i++, ret) < 0) { + JS_FreeValue(cx, arr); + JS_FreeValue(cx, ret); + return JS_EXCEPTION; + } + } + + return arr; +} + + +static JSValue +qjs_xml_node_attr_handler(JSContext *cx, qjs_xml_node_t *current, + njs_str_t *name) +{ + size_t size; + xmlAttr *attr; + xmlNode *node; + + node = current->node; + + for (attr = node->properties; attr != NULL; attr = attr->next) { + if (attr->type != XML_ATTRIBUTE_NODE) { + continue; + } + + size = njs_strlen(attr->name); + + if (name->length > 0 + && (name->length != size + || njs_strncmp(name->start, attr->name, size) != 0)) + { + continue; + } + + if (attr->children != NULL + && attr->children->next == NULL + && attr->children->type == XML_TEXT_NODE + && attr->children->content != NULL) + { + return JS_NewString(cx, (char *) attr->children->content); + } + } + + return JS_UNDEFINED; +} + + +static int +qjs_xml_node_attr_modify(JSContext *cx, JSValue current, const u_char *name, + JSValue setval) +{ + xmlAttr *attr; + const u_char *value; + qjs_xml_node_t *node; + + node = JS_GetOpaque(current, QJS_CORE_CLASS_ID_XML_NODE); + if (node == NULL) { + return -1; + } + + if (xmlValidateQName(name, 0) != 0) { + JS_ThrowTypeError(cx, "attribute name \"%s\" is not valid", name); + return -1; + } + + if (JS_IsNullOrUndefined(setval)) { + attr = xmlHasProp(node->node, name); + + if (attr != NULL) { + xmlRemoveProp(attr); + } + + return 1; + } + + value = (const u_char *) JS_ToCString(cx, setval); + if (value == NULL) { + return -1; + } + + attr = xmlSetProp(node->node, name, value); + JS_FreeCString(cx, (char *) value); + if (attr == NULL) { + JS_ThrowInternalError(cx, "xmlSetProp() failed"); + return -1; + } + + return 1; +} + + +static int +qjs_xml_node_tag_modify(JSContext *cx, JSValue obj, njs_str_t *name, + JSValue setval) +{ + size_t size; + xmlNode *node, *next, *copy; + qjs_xml_node_t *current; + + current = JS_GetOpaque(obj, QJS_CORE_CLASS_ID_XML_NODE); + if (current == NULL) { + return -1; + } + + if (!JS_IsNullOrUndefined(setval)) { + JS_ThrowInternalError(cx, "XMLNode.$tag$xxx is not assignable, " + "use addChild() or node.$tags = [node1, node2, ..] " + "syntax"); + return -1; + } + + copy = xmlDocCopyNode(current->node, current->doc->doc, 1); + if (copy == NULL) { + JS_ThrowInternalError(cx, "xmlDocCopyNode() failed"); + return -1; + } + + for (node = copy->children; node != NULL; node = next) { + next = node->next; + + if (node->type != XML_ELEMENT_NODE) { + continue; + } + + size = njs_strlen(node->name); + + if (name->length > 0 + && (name->length != size + || njs_strncmp(name->start, node->name, size) != 0)) + { + continue; + } + + xmlUnlinkNode(node); + + node->next = current->doc->free; + current->doc->free = node; + } + + qjs_xml_replace_node(cx, current, copy); + + return 1; +} + + +static int +qjs_xml_node_tags_modify(JSContext *cx, JSValue obj, njs_str_t *name, + JSValue setval) +{ + int32_t len, i; + xmlNode *node, *rnode, *copy; + JSValue length, v; + qjs_xml_node_t *current; + + current = JS_GetOpaque(obj, QJS_CORE_CLASS_ID_XML_NODE); + if (current == NULL) { + return -1; + } + + if (!JS_IsArray(cx, setval)) { + JS_ThrowTypeError(cx, "setval is not an array"); + return -1; + } + + length = JS_GetPropertyStr(cx, setval, "length"); + if (JS_IsException(length)) { + return -1; + } + + if (JS_ToInt32(cx, &len, length) < 0) { + return -1; + } + + copy = xmlDocCopyNode(current->node, current->doc->doc, + 2 /* copy properties and namespaces */); + if (copy == NULL) { + JS_ThrowInternalError(cx, "xmlDocCopyNode() failed"); + return -1; + } + + for (i = 0; i < len; i++) { + v = JS_GetPropertyUint32(cx, setval, i); + if (JS_IsException(v)) { + goto error; + } + + node = qjs_xml_node(cx, v, NULL); + JS_FreeValue(cx, v); + if (node == NULL) { + goto error; + } + + node = xmlDocCopyNode(node, current->doc->doc, 1); + if (node == NULL) { + JS_ThrowInternalError(cx, "xmlDocCopyNode() failed"); + goto error; + } + + rnode = xmlAddChild(copy, node); + if (rnode == NULL) { + xmlFreeNode(node); + JS_ThrowInternalError(cx, "xmlAddChild() failed"); + goto error; + } + } + + if (xmlReconciliateNs(current->doc->doc, copy) == -1) { + JS_ThrowInternalError(cx, "xmlReconciliateNs() failed"); + goto error; + } + + qjs_xml_replace_node(cx, current, copy); + + return 1; + +error: + + xmlFreeNode(copy); + + return -1; +} + + +static int +qjs_xml_node_text_handler(JSContext *cx, JSValue current, JSValue setval) +{ + xmlNode *copy; + njs_str_t content, enc; + qjs_xml_node_t *node; + + enc.start = NULL; + enc.length = 0; + + node = JS_GetOpaque(current, QJS_CORE_CLASS_ID_XML_NODE); + if (node == NULL) { + return -1; + } + + if (!JS_IsNullOrUndefined(setval)) { + content.start = (u_char *) JS_ToCStringLen(cx, &content.length, setval); + if (content.start == NULL) { + return -1; + } + + if (qjs_xml_encode_special_chars(cx, &content, &enc) < 0) { + JS_FreeCString(cx, (char *) content.start); + return -1; + } + + JS_FreeCString(cx, (char *) content.start); + } + + copy = xmlDocCopyNode(node->node, node->doc->doc, 1); + if (copy == NULL) { + if (enc.start != NULL) { + js_free(cx, enc.start); + } + + JS_ThrowInternalError(cx, "xmlDocCopyNode() failed"); + return -1; + } + + xmlNodeSetContentLen(copy, enc.start, enc.length); + + if (enc.start != NULL) { + js_free(cx, enc.start); + } + + qjs_xml_replace_node(cx, node, copy); + + return 1; +} + + +static int +qjs_xml_node_get_own_property(JSContext *cx, JSPropertyDescriptor *pdesc, + JSValueConst obj, JSAtom prop) +{ + u_char *text; + JSValue value; + xmlNode *node; + njs_str_t name, nm; + qjs_xml_node_t *current; + + /* + * $tag$foo - the first tag child with the name "foo" + * $tags$foo - the all children with the name "foo" as an array + * $attr$foo - the attribute with the name "foo" + * foo - the same as $tag$foo + */ + + current = JS_GetOpaque(obj, QJS_CORE_CLASS_ID_XML_NODE); + if (current == NULL) { + (void) JS_ThrowInternalError(cx, "\"this\" is not an XMLNode"); + return -1; + } + + name.start = (u_char *) JS_AtomToCString(cx, prop); + if (name.start == NULL) { + return -1; + } + + name.length = njs_strlen(name.start); + node = current->node; + + if (name.length > 1 && name.start[0] == '$') { + if (name.length == njs_length("$attrs") + && njs_strncmp(&name.start[1], "attrs", njs_length("attrs")) == 0) + { + JS_FreeCString(cx, (char *) name.start); + + if (node->properties == NULL) { + return 0; + } + + if (pdesc != NULL) { + pdesc->flags = JS_PROP_ENUMERABLE; + pdesc->getter = JS_UNDEFINED; + pdesc->setter = JS_UNDEFINED; + pdesc->value = qjs_xml_attr_make(cx, current->doc, + node->properties); + if (JS_IsException(pdesc->value)) { + return -1; + } + } + + return 1; + } + + if (name.length > njs_length("$attr$") + && njs_strncmp(&name.start[1], "attr$", njs_length("attr$")) == 0) + { + nm.length = name.length - njs_length("$attr$"); + nm.start = name.start + njs_length("$attr$"); + + value = qjs_xml_node_attr_handler(cx, current, &nm); + JS_FreeCString(cx, (char *) name.start); + if (JS_IsException(value)) { + return -1; + } + + if (JS_IsUndefined(value)) { + return 0; + } + + if (pdesc != NULL) { + pdesc->flags = JS_PROP_ENUMERABLE; + pdesc->getter = JS_UNDEFINED; + pdesc->setter = JS_UNDEFINED; + pdesc->value = value; + } + + return 1; + } + + if (name.length == njs_length("$name") + && njs_strncmp(&name.start[1], "name", njs_length("name")) == 0) + { + JS_FreeCString(cx, (char *) name.start); + + if (node->type != XML_ELEMENT_NODE) { + return 0; + } + + if (pdesc != NULL) { + pdesc->flags = JS_PROP_ENUMERABLE; + pdesc->getter = JS_UNDEFINED; + pdesc->setter = JS_UNDEFINED; + pdesc->value = JS_NewString(cx, (char *) node->name); + if (JS_IsException(pdesc->value)) { + return -1; + } + } + + return 1; + } + + if (name.length == njs_length("$ns") + && njs_strncmp(&name.start[1], "ns", njs_length("ns")) == 0) + { + JS_FreeCString(cx, (char *) name.start); + + if (node->ns == NULL || node->ns->href == NULL) { + return 0; + } + + if (pdesc != NULL) { + pdesc->flags = JS_PROP_ENUMERABLE; + pdesc->getter = JS_UNDEFINED; + pdesc->setter = JS_UNDEFINED; + pdesc->value = JS_NewString(cx, (char *) node->ns->href); + if (JS_IsException(pdesc->value)) { + return -1; + } + } + + return 1; + } + + if (name.length == njs_length("$parent") + && njs_strncmp(&name.start[1], "parent", njs_length("parent")) == 0) + { + JS_FreeCString(cx, (char *) name.start); + + if (node->parent == NULL + || node->parent->type != XML_ELEMENT_NODE) + { + return 0; + } + + if (pdesc != NULL) { + pdesc->flags = JS_PROP_ENUMERABLE; + pdesc->getter = JS_UNDEFINED; + pdesc->setter = JS_UNDEFINED; + pdesc->value = qjs_xml_node_make(cx, current->doc, + node->parent); + if (JS_IsException(pdesc->value)) { + return -1; + } + } + + return 1; + } + + if (name.length == njs_length("$tags") + && njs_strncmp(&name.start[1], "tags", njs_length("tags")) == 0) + { + JS_FreeCString(cx, (char *) name.start); + + if (pdesc != NULL) { + nm.start = NULL; + nm.length = 0; + + pdesc->flags = JS_PROP_ENUMERABLE; + pdesc->getter = JS_UNDEFINED; + pdesc->setter = JS_UNDEFINED; + pdesc->value = qjs_xml_node_tags_handler(cx, current, &nm); + if (JS_IsException(pdesc->value)) { + return -1; + } + } + + return 1; + } + + if (name.length > njs_length("$tags$") + && njs_strncmp(&name.start[1], "tags$", njs_length("tags$")) == 0) + { + nm.start = name.start + njs_length("$tags$"); + nm.length = name.length - njs_length("$tags$"); + + value = qjs_xml_node_tags_handler(cx, current, &nm); + JS_FreeCString(cx, (char *) name.start); + if (JS_IsException(value)) { + return -1; + } + + if (pdesc != NULL) { + pdesc->flags = JS_PROP_ENUMERABLE; + pdesc->getter = JS_UNDEFINED; + pdesc->setter = JS_UNDEFINED; + pdesc->value = value; + } + + return 1; + } + + if (name.length > njs_length("$tag$") + && njs_strncmp(&name.start[1], "tag$", njs_length("tag$")) == 0) + { + nm.length = name.length - njs_length("$tag$"); + nm.start = name.start + njs_length("$tag$"); + goto tag; + } + + if (name.length == njs_length("$text") + && njs_strncmp(&name.start[1], "text", njs_length("text")) == 0) + { + JS_FreeCString(cx, (char *) name.start); + + text = xmlNodeGetContent(current->node); + if (text == NULL) { + return 0; + } + + if (pdesc != NULL) { + pdesc->flags = JS_PROP_ENUMERABLE; + pdesc->getter = JS_UNDEFINED; + pdesc->setter = JS_UNDEFINED; + pdesc->value = JS_NewString(cx, (char *) text); + if (JS_IsException(pdesc->value)) { + xmlFree(text); + return -1; + } + } + + xmlFree(text); + + return 1; + } + } + + nm = name; + +tag: + + value = qjs_xml_node_tag_handler(cx, current, &nm); + JS_FreeCString(cx, (char *) name.start); + if (JS_IsException(value)) { + return -1; + } + + if (JS_IsUndefined(value)) { + return 0; + } + + if (pdesc != NULL) { + pdesc->flags = JS_PROP_ENUMERABLE; + pdesc->getter = JS_UNDEFINED; + pdesc->setter = JS_UNDEFINED; + pdesc->value = value; + } + + return 1; +} + + +static int +qjs_xml_node_get_own_property_names(JSContext *cx, JSPropertyEnum **ptab, + uint32_t *plen, JSValueConst obj) +{ + int rc; + JSValue keys; + xmlNode *node, *current; + qjs_xml_node_t *tree; + + tree = JS_GetOpaque(obj, QJS_CORE_CLASS_ID_XML_NODE); + if (tree == NULL) { + (void) JS_ThrowInternalError(cx, "\"this\" is not an XMLNode"); + return -1; + } + + keys = JS_NewObject(cx); + if (JS_IsException(keys)) { + return -1; + } + + current = tree->node; + + if (current->name != NULL && current->type == XML_ELEMENT_NODE) { + if (qjs_xml_push_string(cx, keys, "$name") < 0) { + goto fail; + } + } + + if (current->ns != NULL) { + if (qjs_xml_push_string(cx, keys, "$ns") < 0) { + goto fail; + } + } + + if (current->properties != NULL) { + if (qjs_xml_push_string(cx, keys, "$attrs") < 0) { + goto fail; + } + } + + if (current->children != NULL && current->children->content != NULL) { + if (qjs_xml_push_string(cx, keys, "$text") < 0) { + goto fail; + } + } + + for (node = current->children; node != NULL; node = node->next) { + if (node->type != XML_ELEMENT_NODE) { + continue; + } + + if (qjs_xml_push_string(cx, keys, "$tags") < 0) { + goto fail; + } + + break; + } + + rc = JS_GetOwnPropertyNames(cx, ptab, plen, keys, JS_GPN_STRING_MASK); + + JS_FreeValue(cx, keys); + + return rc; + +fail: + + JS_FreeValue(cx, keys); + + return -1; +} + +static int +qjs_xml_node_set_property(JSContext *cx, JSValueConst obj, JSAtom atom, + JSValueConst value, JSValueConst receiver, int flags) +{ + return qjs_xml_node_define_own_property(cx, obj, atom, value, + JS_UNDEFINED, JS_UNDEFINED, flags); +} + + +static int +qjs_xml_node_delete_property(JSContext *cx, JSValueConst obj, JSAtom prop) +{ + return qjs_xml_node_define_own_property(cx, obj, prop, JS_UNDEFINED, + JS_UNDEFINED, JS_UNDEFINED, + JS_PROP_THROW); +} + + +static int +qjs_xml_node_define_own_property(JSContext *cx, JSValueConst obj, JSAtom atom, + JSValueConst value, JSValueConst getter, JSValueConst setter, int flags) +{ + int rc; + njs_str_t name, nm; + + name.start = (u_char *) JS_AtomToCString(cx, atom); + if (name.start == NULL) { + return -1; + } + + name.length = njs_strlen(name.start); + + if (name.length > 1 && name.start[0] == '$') { + if (name.length > njs_length("$attr$") + && njs_strncmp(&name.start[1], "attr$", njs_length("attr$")) == 0) + { + nm.start = name.start + njs_length("$attr$"); + + rc = qjs_xml_node_attr_modify(cx, obj, nm.start, value); + + JS_FreeCString(cx, (char *) name.start); + + return rc; + } + + if (name.length > njs_length("$tag$") + && njs_strncmp(&name.start[1], "tag$", njs_length("tag$")) == 0) + { + nm.start = name.start + njs_length("$tag$"); + nm.length = name.length - njs_length("$tag$"); + + rc = qjs_xml_node_tag_modify(cx, obj, &nm, value); + + JS_FreeCString(cx, (char *) name.start); + + return rc; + } + + if (name.length >= njs_length("$tags$") + && njs_strncmp(&name.start[1], "tags$", njs_length("tags$")) == 0) + { + nm.start = name.start + njs_length("$tags$"); + nm.length = name.length - njs_length("$tags$"); + + rc = qjs_xml_node_tags_modify(cx, obj, &nm, value); + + JS_FreeCString(cx, (char *) name.start); + + return rc; + } + + if (name.length >= njs_length("$tags") + && njs_strncmp(&name.start[1], "tags", njs_length("tags")) == 0) + { + nm.start = name.start + njs_length("$tags"); + nm.length = name.length - njs_length("$tags"); + + rc = qjs_xml_node_tags_modify(cx, obj, &nm, value); + + JS_FreeCString(cx, (char *) name.start); + + return rc; + } + + if (name.length == njs_length("$text") + && njs_strncmp(&name.start[1], "text", njs_length("text")) == 0) + { + JS_FreeCString(cx, (char *) name.start); + + return qjs_xml_node_text_handler(cx, obj, value); + } + } + + rc = qjs_xml_node_tag_modify(cx, obj, &name, value); + JS_FreeCString(cx, (char *) name.start); + + return rc; +} + + +static JSValue +qjs_xml_node_add_child(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + xmlNode *copy, *node, *rnode; + qjs_xml_node_t *current; + + current = JS_GetOpaque(this_val, QJS_CORE_CLASS_ID_XML_NODE); + if (current == NULL) { + JS_ThrowTypeError(cx, "\"this\" is not a XMLNode object"); + return JS_EXCEPTION; + } + + node = qjs_xml_node(cx, argv[0], NULL); + if (node == NULL) { + return JS_EXCEPTION; + } + + copy = xmlDocCopyNode(current->node, current->doc->doc, 1); + if (copy == NULL) { + JS_ThrowInternalError(cx, "xmlDocCopyNode() failed"); + return JS_EXCEPTION; + } + + node = xmlDocCopyNode(node, current->doc->doc, 1); + if (node == NULL) { + JS_ThrowInternalError(cx, "xmlDocCopyNode() failed"); + goto error; + } + + rnode = xmlAddChild(copy, node); + if (rnode == NULL) { + xmlFreeNode(node); + JS_ThrowInternalError(cx, "xmlAddChild() failed"); + goto error; + } + + if (xmlReconciliateNs(current->doc->doc, copy) == -1) { + JS_ThrowInternalError(cx, "xmlReconciliateNs() failed"); + goto error; + } + + qjs_xml_replace_node(cx, current, copy); + + return JS_UNDEFINED; + +error: + + xmlFreeNode(copy); + + return JS_EXCEPTION; +} + + +static JSValue +qjs_xml_node_remove_children(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int rc; + njs_str_t name; + qjs_xml_node_t *current; + + current = JS_GetOpaque(this_val, QJS_CORE_CLASS_ID_XML_NODE); + if (current == NULL) { + JS_ThrowTypeError(cx, "\"this\" is not a XMLNode object"); + return JS_EXCEPTION; + } + + if (!JS_IsNullOrUndefined(argv[0])) { + if (!JS_IsString(argv[0])) { + JS_ThrowTypeError(cx, "selector is not a string"); + return JS_EXCEPTION; + } + + name.start = (u_char *) JS_ToCString(cx, argv[0]); + if (name.start == NULL) { + return JS_EXCEPTION; + } + + name.length = njs_strlen(name.start); + + } else { + name.start = NULL; + name.length = 0; + } + + rc = qjs_xml_node_tag_modify(cx, this_val, &name, JS_UNDEFINED); + + if (name.start != NULL) { + JS_FreeCString(cx, (char *) name.start); + } + + if (rc < 0) { + return JS_EXCEPTION; + } + + return JS_UNDEFINED; +} + + +static JSValue +qjs_xml_node_set_attribute(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const u_char *name; + + if (!JS_IsString(argv[0])) { + JS_ThrowTypeError(cx, "\"name\" argument is not a string"); + return JS_EXCEPTION; + } + + name = (const u_char *) JS_ToCString(cx, argv[0]); + + if (qjs_xml_node_attr_modify(cx, this_val, name, argv[1]) < 0) { + JS_FreeCString(cx, (char *) name); + return JS_EXCEPTION; + } + + JS_FreeCString(cx, (char *) name); + + return JS_UNDEFINED; +} + + +static JSValue +qjs_xml_node_remove_attribute(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const u_char *name; + + if (!JS_IsString(argv[0])) { + JS_ThrowTypeError(cx, "\"name\" argument is not a string"); + return JS_EXCEPTION; + } + + name = (const u_char *) JS_ToCString(cx, argv[0]); + + if (qjs_xml_node_attr_modify(cx, this_val, name, JS_UNDEFINED) < 0) { + JS_FreeCString(cx, (char *) name); + return JS_EXCEPTION; + } + + JS_FreeCString(cx, (char *) name); + + return JS_UNDEFINED; +} + + +static JSValue +qjs_xml_node_remove_all_attributes(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + xmlNode *node; + qjs_xml_node_t *current; + + current = JS_GetOpaque(this_val, QJS_CORE_CLASS_ID_XML_NODE); + if (current == NULL) { + JS_ThrowTypeError(cx, "\"this\" is not a XMLNode object"); + return JS_EXCEPTION; + } + + node = current->node; + + if (node->properties != NULL) { + xmlFreePropList(node->properties); + node->properties = NULL; + } + + return JS_UNDEFINED; +} + + +static JSValue +qjs_xml_node_set_text(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + if (qjs_xml_node_text_handler(cx, this_val, argv[0]) < 0) { + return JS_EXCEPTION; + } + + return JS_UNDEFINED; +} + + +static JSValue +qjs_xml_node_remove_text(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + if (qjs_xml_node_text_handler(cx, this_val, JS_UNDEFINED) < 0) { + return JS_EXCEPTION; + } + + return JS_UNDEFINED; +} + + +static void +qjs_xml_node_finalizer(JSRuntime *rt, JSValue val) +{ + qjs_xml_node_t *node; + + node = JS_GetOpaque(val, QJS_CORE_CLASS_ID_XML_NODE); + + qjs_xml_doc_free(rt, node->doc); + + js_free_rt(rt, node); +} + + +static xmlNode * +qjs_xml_node(JSContext *cx, JSValueConst val, xmlDoc **doc) +{ + qjs_xml_doc_t *tree; + qjs_xml_node_t *current; + + current = JS_GetOpaque(val, QJS_CORE_CLASS_ID_XML_NODE); + if (current == NULL) { + tree = JS_GetOpaque(val, QJS_CORE_CLASS_ID_XML_DOC); + if (tree == NULL) { + JS_ThrowInternalError(cx, "'this' is not XMLNode or XMLDoc"); + return NULL; + } + + if (doc != NULL) { + *doc = tree->doc; + } + + return xmlDocGetRootElement(tree->doc); + } + + if (doc != NULL) { + *doc = current->doc->doc; + } + + return current->node; +} + + +static JSValue +qjs_xml_attr_make(JSContext *cx, qjs_xml_doc_t *doc, xmlAttr *attr) +{ + JSValue ret; + qjs_xml_attr_t *current; + + current = js_malloc(cx, sizeof(qjs_xml_attr_t)); + if (current == NULL) { + JS_ThrowOutOfMemory(cx); + return JS_EXCEPTION; + } + + current->attr = attr; + current->doc = doc; + doc->ref_count++; + + ret = JS_NewObjectClass(cx, QJS_CORE_CLASS_ID_XML_ATTR); + if (JS_IsException(ret)) { + js_free(cx, current); + return ret; + } + + JS_SetOpaque(ret, current); + + return ret; +} + + +static int +qjs_xml_attr_get_own_property(JSContext *cx, JSPropertyDescriptor *pdesc, + JSValueConst obj, JSAtom prop) +{ + size_t size; + u_char *text; + xmlAttr *attr; + njs_str_t name; + qjs_xml_attr_t *current; + + current = JS_GetOpaque(obj, QJS_CORE_CLASS_ID_XML_ATTR); + if (current == NULL) { + (void) JS_ThrowInternalError(cx, "\"this\" is not an XMLAttr"); + return -1; + } + + name.start = (u_char *) JS_AtomToCString(cx, prop); + if (name.start == NULL) { + return -1; + } + + name.length = njs_strlen(name.start); + + for (attr = current->attr; attr != NULL; attr = attr->next) { + if (attr->type != XML_ATTRIBUTE_NODE) { + continue; + } + + size = njs_strlen(attr->name); + + if (name.length != size + || njs_strncmp(name.start, attr->name, size) != 0) + { + continue; + } + + JS_FreeCString(cx, (char *) name.start); + + text = xmlNodeGetContent(attr->children); + if (text == NULL) { + return 0; + } + + if (pdesc != NULL) { + pdesc->flags = JS_PROP_ENUMERABLE; + pdesc->getter = JS_UNDEFINED; + pdesc->setter = JS_UNDEFINED; + pdesc->value = JS_NewString(cx, (char *) text); + if (JS_IsException(pdesc->value)) { + xmlFree(text); + return -1; + } + } + + xmlFree(text); + + return 1; + } + + JS_FreeCString(cx, (char *) name.start); + + return 0; +} + + +static int +qjs_xml_attr_get_own_property_names(JSContext *cx, JSPropertyEnum **ptab, + uint32_t *plen, JSValueConst obj) +{ + int rc; + JSValue keys; + xmlAttr *attr; + qjs_xml_attr_t *current; + + current = JS_GetOpaque(obj, QJS_CORE_CLASS_ID_XML_ATTR); + if (current == NULL) { + (void) JS_ThrowInternalError(cx, "\"this\" is not an XMLAttr"); + return -1; + } + + keys = JS_NewObject(cx); + if (JS_IsException(keys)) { + return -1; + } + + for (attr = current->attr; attr != NULL; attr = attr->next) { + if (attr->type != XML_ATTRIBUTE_NODE) { + continue; + } + + if (qjs_xml_push_string(cx, keys, (char *) attr->name) < 0) { + goto fail; + } + } + + rc = JS_GetOwnPropertyNames(cx, ptab, plen, keys, JS_GPN_STRING_MASK); + + JS_FreeValue(cx, keys); + + return rc; + +fail: + + JS_FreeValue(cx, keys); + + return -1; +} + + +static void +qjs_xml_attr_finalizer(JSRuntime *rt, JSValue val) +{ + qjs_xml_attr_t *attr; + + attr = JS_GetOpaque(val, QJS_CORE_CLASS_ID_XML_ATTR); + + qjs_xml_doc_free(rt, attr->doc); + + js_free_rt(rt, attr); +} + + +static u_char ** +qjs_xml_parse_ns_list(JSContext *cx, u_char *src) +{ + u_char *p, **buf, **out; + size_t size, idx; + + size = 8; + p = src; + + buf = js_mallocz(cx, size * sizeof(char *)); + if (buf == NULL) { + JS_ThrowOutOfMemory(cx); + return NULL; + } + + out = buf; + + while (*p != '\0') { + idx = out - buf; + + if (idx >= size) { + size *= 2; + + buf = js_realloc(cx, buf, size * sizeof(char *)); + if (buf == NULL) { + JS_ThrowOutOfMemory(cx); + return NULL; + } + + out = &buf[idx]; + } + + *out++ = p; + + while (*p != ' ' && *p != '\0') { + p++; + } + + if (*p == ' ') { + *p++ = '\0'; + } + } + + *out = NULL; + + return buf; +} + + +static int +qjs_xml_node_one_contains(qjs_xml_nset_t *nset, xmlNode *node, xmlNode *parent) +{ + int in; + xmlNs ns; + + if (nset->type == XML_NSET_TREE_NO_COMMENTS + && node->type == XML_COMMENT_NODE) + { + return 0; + } + + in = 1; + + if (nset->nodes != NULL) { + if (node->type != XML_NAMESPACE_DECL) { + in = xmlXPathNodeSetContains(nset->nodes, node); + + } else { + + memcpy(&ns, node, sizeof(ns)); + + /* libxml2 workaround, check xpath.c for details */ + + if ((parent != NULL) && (parent->type == XML_ATTRIBUTE_NODE)) { + ns.next = (xmlNs *) parent->parent; + + } else { + ns.next = (xmlNs *) parent; + } + + in = xmlXPathNodeSetContains(nset->nodes, (xmlNode *) &ns); + } + } + + switch (nset->type) { + case XML_NSET_TREE: + case XML_NSET_TREE_NO_COMMENTS: + if (in != 0) { + return 1; + } + + if ((parent != NULL) && (parent->type == XML_ELEMENT_NODE)) { + return qjs_xml_node_one_contains(nset, parent, parent->parent); + } + + return 0; + + case XML_NSET_TREE_INVERT: + default: + if (in != 0) { + return 0; + } + + if ((parent != NULL) && (parent->type == XML_ELEMENT_NODE)) { + return qjs_xml_node_one_contains(nset, parent, parent->parent); + } + } + + return 1; +} + + +static int +qjs_xml_c14n_visibility_cb(void *user_data, xmlNode *node, xmlNode *parent) +{ + int status; + qjs_xml_nset_t *n, *nset; + + nset = user_data; + + if (nset == NULL) { + return 1; + } + + status = 1; + + n = nset; + + do { + if (status && !qjs_xml_node_one_contains(n, node, parent)) { + status = 0; + } + + n = n->next; + } while (n != nset); + + return status; +} + + +static int +qjs_xml_buf_write_cb(void *context, const char *buffer, int len) +{ + njs_chb_t *chain = context; + + njs_chb_append(chain, buffer, len); + + return chain->error ? -1 : len; +} + + +static qjs_xml_nset_t * +qjs_xml_nset_create(JSContext *cx, xmlDoc *doc, xmlNode *current, + qjs_xml_nset_type_t type) +{ + xmlNodeSet *nodes; + qjs_xml_nset_t *nset; + + nset = js_mallocz(cx, sizeof(qjs_xml_nset_t)); + if (nset == NULL) { + JS_ThrowOutOfMemory(cx); + return NULL; + } + + nodes = xmlXPathNodeSetCreate(current); + if (nodes == NULL) { + js_free(cx, nset); + JS_ThrowOutOfMemory(cx); + return NULL; + } + + nset->doc = doc; + nset->type = type; + nset->nodes = nodes; + nset->next = nset->prev = nset; + + return nset; +} + + +static qjs_xml_nset_t * +qjs_xml_nset_add(qjs_xml_nset_t *nset, qjs_xml_nset_t *add) +{ + if (nset == NULL) { + return add; + } + + add->next = nset; + add->prev = nset->prev; + nset->prev->next = add; + nset->prev = add; + + return nset; +} + + +static void +qjs_xml_nset_free(JSContext *cx, qjs_xml_nset_t *nset) +{ + if (nset == NULL) { + return; + } + + if (nset->nodes != NULL) { + xmlXPathFreeNodeSet(nset->nodes); + } + + js_free(cx, nset); +} + + +static int +qjs_xml_encode_special_chars(JSContext *cx, njs_str_t *src, njs_str_t *out) +{ + size_t len; + u_char *p, *dst, *end; + + len = 0; + end = src->start + src->length; + + for (p = src->start; p < end; p++) { + if (*p == '<' || *p == '>') { + len += njs_length("<"); + } + + if (*p == '&' || *p == '\r') { + len += njs_length("&"); + } + + if (*p == '"') { + len += njs_length("""); + } + + len += 1; + } + + if (len == 0) { + out->start = NULL; + out->length = 0; + + return 0; + } + + out->start = js_malloc(cx, len); + if (out->start == NULL) { + JS_ThrowOutOfMemory(cx); + return -1; + } + + dst = out->start; + + for (p = src->start; p < end; p++) { + if (*p == '<') { + *dst++ = '&'; + *dst++ = 'l'; + *dst++ = 't'; + *dst++ = ';'; + + } else if (*p == '>') { + *dst++ = '&'; + *dst++ = 'g'; + *dst++ = 't'; + *dst++ = ';'; + + } else if (*p == '&') { + *dst++ = '&'; + *dst++ = 'a'; + *dst++ = 'm'; + *dst++ = 'p'; + *dst++ = ';'; + + } else if (*p == '"') { + *dst++ = '&'; + *dst++ = 'q'; + *dst++ = 'u'; + *dst++ = 'o'; + *dst++ = 't'; + *dst++ = ';'; + + } else if (*p == '\r') { + *dst++ = '&'; + *dst++ = '#'; + *dst++ = '1'; + *dst++ = '3'; + *dst++ = ';'; + + } else { + *dst++ = *p; + } + } + + out->length = len; + + return 0; +} + + +static void +qjs_xml_replace_node(JSContext *cx, qjs_xml_node_t *node, xmlNode *current) +{ + xmlNode *old; + + old = node->node; + + if (current != NULL) { + old = xmlReplaceNode(old, current); + + } else { + xmlUnlinkNode(old); + } + + old->next = node->doc->free; + node->doc->free = old; +} + + +static void +qjs_xml_error(JSContext *cx, qjs_xml_doc_t *current, const char *fmt, ...) +{ + u_char *p, *last; + va_list args; + const xmlError *err; + u_char errstr[NJS_MAX_ERROR_STR]; + + last = &errstr[NJS_MAX_ERROR_STR]; + + va_start(args, fmt); + p = njs_vsprintf(errstr, last - 1, fmt, args); + va_end(args); + + err = xmlCtxtGetLastError(current->ctx); + + if (err != NULL) { + p = njs_sprintf(p, last - 1, " (libxml2: \"%*s\" at %d:%d)", + njs_strlen(err->message) - 1, err->message, err->line, + err->int2); + } + + JS_ThrowSyntaxError(cx, "%.*s", (int) (p - errstr), errstr); +} + + +static int +qjs_xml_module_init(JSContext *cx, JSModuleDef *m) +{ + JSValue proto; + + proto = JS_NewObject(cx); + if (JS_IsException(proto)) { + return -1; + } + + JS_SetPropertyFunctionList(cx, proto, qjs_xml_export, + njs_nitems(qjs_xml_export)); + + if (JS_SetModuleExport(cx, m, "default", proto) != 0) { + return -1; + } + + return JS_SetModuleExportList(cx, m, qjs_xml_export, + njs_nitems(qjs_xml_export)); +} + + +static JSModuleDef * +qjs_xml_init(JSContext *cx, const char *name) +{ + int rc; + JSValue proto; + JSModuleDef *m; + + if (!JS_IsRegisteredClass(JS_GetRuntime(cx), + QJS_CORE_CLASS_ID_XML_DOC)) + { + if (JS_NewClass(JS_GetRuntime(cx), QJS_CORE_CLASS_ID_XML_DOC, + &qjs_xml_doc_class) < 0) + { + return NULL; + } + + proto = JS_NewObject(cx); + if (JS_IsException(proto)) { + return NULL; + } + + JS_SetPropertyFunctionList(cx, proto, qjs_xml_doc_proto, + njs_nitems(qjs_xml_doc_proto)); + + JS_SetClassProto(cx, QJS_CORE_CLASS_ID_XML_DOC, proto); + + if (JS_NewClass(JS_GetRuntime(cx), QJS_CORE_CLASS_ID_XML_NODE, + &qjs_xml_node_class) < 0) + { + return NULL; + } + + proto = JS_NewObject(cx); + if (JS_IsException(proto)) { + return NULL; + } + + JS_SetPropertyFunctionList(cx, proto, qjs_xml_node_proto, + njs_nitems(qjs_xml_node_proto)); + + JS_SetClassProto(cx, QJS_CORE_CLASS_ID_XML_NODE, proto); + + if (JS_NewClass(JS_GetRuntime(cx), QJS_CORE_CLASS_ID_XML_ATTR, + &qjs_xml_attr_class) < 0) + { + return NULL; + } + + proto = JS_NewObject(cx); + if (JS_IsException(proto)) { + return NULL; + } + + JS_SetPropertyFunctionList(cx, proto, qjs_xml_attr_proto, + njs_nitems(qjs_xml_attr_proto)); + + JS_SetClassProto(cx, QJS_CORE_CLASS_ID_XML_ATTR, proto); + } + + m = JS_NewCModule(cx, name, qjs_xml_module_init); + if (m == NULL) { + return NULL; + } + + JS_AddModuleExport(cx, m, "default"); + rc = JS_AddModuleExportList(cx, m, qjs_xml_export, + njs_nitems(qjs_xml_export)); + if (rc != 0) { + return NULL; + } + + return m; +} diff --git a/src/qjs.h b/src/qjs.h index c7ef4de0..25d6cba3 100644 --- a/src/qjs.h +++ b/src/qjs.h @@ -44,7 +44,10 @@ #define QJS_CORE_CLASS_ID_WEBCRYPTO_KEY (QJS_CORE_CLASS_ID_OFFSET + 7) #define QJS_CORE_CLASS_CRYPTO_HASH (QJS_CORE_CLASS_ID_OFFSET + 8) #define QJS_CORE_CLASS_CRYPTO_HMAC (QJS_CORE_CLASS_ID_OFFSET + 9) -#define QJS_CORE_CLASS_ID_LAST (QJS_CORE_CLASS_ID_OFFSET + 10) +#define QJS_CORE_CLASS_ID_XML_DOC (QJS_CORE_CLASS_ID_OFFSET + 10) +#define QJS_CORE_CLASS_ID_XML_NODE (QJS_CORE_CLASS_ID_OFFSET + 11) +#define QJS_CORE_CLASS_ID_XML_ATTR (QJS_CORE_CLASS_ID_OFFSET + 12) +#define QJS_CORE_CLASS_ID_LAST (QJS_CORE_CLASS_ID_OFFSET + 13) typedef JSModuleDef *(*qjs_addon_init_pt)(JSContext *ctx, const char *name); diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index f39660f5..980fd7fa 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -20239,279 +20239,6 @@ static njs_unit_test_t njs_fs_module_test[] = }; -#define NJS_XML_DOC "const xml = require('xml');" \ - "let data = `ToveJani`;" \ - "let doc = xml.parse(data);" - - -static njs_unit_test_t njs_xml_test[] = -{ - { njs_str(NJS_XML_DOC - "[doc.note.$name," - " doc.note.to.$text," - " doc.note.$parent," - " doc.note.to.$parent.$name," - " doc.note.$tag$to.$text," - " doc.note.to.$attr$b," - " doc.note.$tags[1].$text," - " doc.note.$tags$from[0].$text]"), - njs_str("note,Tove,,note,Tove,bar,Jani,Jani") }, - - { njs_str("const xml = require('xml');" - "let doc = xml.parse(`FOOBAR`);" - "[doc.root.$tags$foo[0].$text," - " doc.root.$tags$foo[1].$text," - " doc.root.$tags$bar.length," - " doc.root.$tags$.length]"), - njs_str("FOO,BAR,0,2") }, - - { njs_str("const xml = require('xml');" - "let doc = xml.parse(`GARBAGE`)"), - njs_str("Error: failed to parse XML (libxml2: \"Start tag expected, '<' not found\" at 1:1)") }, - - { njs_str("const xml = require('xml');" - "let doc = xml.parse(`TEXT`);" - "doc.r.$text"), - njs_str("TEXT") }, - - { njs_str("const xml = require('xml');" - "let doc = xml.parse(`俄语данные`);" - "doc.r.$text[2]"), - njs_str("д") }, - - { njs_str("const xml = require('xml');" - "let doc = xml.parse(`<俄语 լեզու=\"ռուսերեն\">данные`);" - "[doc['俄语'].$name[1]," - " doc['俄语']['$attr$լեզու'][7]," - " doc['俄语'].$text[5]]"), - njs_str("语,ն,е") }, - - { njs_str("const xml = require('xml');" - "var doc = xml.parse(`" - "foo`);" - "[xml.c14n(doc.pdu.elem1)," - " xml.exclusiveC14n(doc.pdu.elem1)," - " xml.exclusiveC14n(doc.pdu.elem1, null, 1)," - " xml.exclusiveC14n(doc.pdu.elem1, null, false, 'n0 n1')]" - ".map(v => (new TextDecoder().decode(v)))"), - njs_str("foo," - "foo," - "foo," - "foo") }, - - { njs_str(NJS_XML_DOC - "let dec = new TextDecoder();" - "dec.decode(xml.exclusiveC14n(doc.note))"), - njs_str("ToveJani") }, - - { njs_str(NJS_XML_DOC - "let dec = new TextDecoder();" - "dec.decode(xml.serialize(doc.note))"), - njs_str("ToveJani") }, - - { njs_str(NJS_XML_DOC - "xml.serializeToString(doc.note)"), - njs_str("ToveJani") }, - - { njs_str(NJS_XML_DOC - "let dec = new TextDecoder();" - "dec.decode(xml.exclusiveC14n(doc.note, doc.note.to))"), - njs_str("Jani") }, - - { njs_str(NJS_XML_DOC - "njs.dump(doc)"), - njs_str("XMLDoc {note:XMLNode {$name:'note'," - "$tags:[XMLNode {$name:'to'," - "$attrs:XMLAttr {b:'bar',a:'foo'}," - "$text:'Tove'}," - "XMLNode {$name:'from',$text:'Jani'}]}}") }, - - { njs_str(NJS_XML_DOC - "JSON.stringify(doc)"), - njs_str("{\"note\":{\"$name\":\"note\",\"$tags\":" - "[{\"$name\":\"to\",\"$attrs\":{\"b\":\"bar\",\"a\":\"foo\"}," - "\"$text\":\"Tove\"},{\"$name\":\"from\",\"$text\":\"Jani\"}]}}") }, - - { njs_str("var xml = require('xml');" - "var doc = xml.parse(``); xml.exclusiveC14n(doc, 1)"), - njs_str("TypeError: \"excluding\" argument is not a XMLNode object") }, - - { njs_str(NJS_XML_DOC - "doc.$root.$text"), - njs_str("ToveJani") }, - - { njs_str(NJS_XML_DOC - "doc.$root.$text = 'WAKA';" - "[doc.$root.$text, (new TextDecoder).decode(xml.c14n(doc))]"), - njs_str("WAKA,WAKA") }, - - { njs_str(NJS_XML_DOC - "doc.$root.setText('WAKA');" - "[doc.$root.$text, (new TextDecoder).decode(xml.c14n(doc))]"), - njs_str("WAKA,WAKA") }, - - { njs_str(NJS_XML_DOC - "doc.$root.$text = '';" - "[doc.$root.$text, (new TextDecoder).decode(xml.c14n(doc))]"), - njs_str(",<WA&KA>") }, - - { njs_str(NJS_XML_DOC - "doc.$root.setText('');" - "[doc.$root.$text, (new TextDecoder).decode(xml.c14n(doc))]"), - njs_str(",<WA&KA>") }, - - { njs_str(NJS_XML_DOC - "doc.$root.$text = '\"WAKA\"';" - "[doc.$root.$text, (new TextDecoder).decode(xml.c14n(doc))]"), - njs_str("\"WAKA\",\"WAKA\"") }, - - { njs_str(NJS_XML_DOC - "doc.$root.$text = '';" - "[doc.$root.$text, (new TextDecoder).decode(xml.c14n(doc))]"), - njs_str(",") }, - - { njs_str(NJS_XML_DOC - "doc.$root.setText();" - "[doc.$root.$text, (new TextDecoder).decode(xml.c14n(doc))]"), - njs_str(",") }, - - { njs_str(NJS_XML_DOC - "doc.$root.setText(null);" - "[doc.$root.$text, (new TextDecoder).decode(xml.c14n(doc))]"), - njs_str(",") }, - - { njs_str(NJS_XML_DOC - "doc.$root.removeText();" - "[doc.$root.$text, (new TextDecoder).decode(xml.c14n(doc))]"), - njs_str(",") }, - - { njs_str(NJS_XML_DOC - "let to = doc.note.to;" - "doc.$root.$text = '';" - "[to.$name, to.$text, to.$attr$b, to.$parent.$name]"), - njs_str("to,Tove,bar,note") }, - - { njs_str(NJS_XML_DOC - "doc.$root.$text = 'WAKA';" - "doc.$root.$attr$aaa = 'foo';" - "doc.$root.$attr$bbb = 'bar';" - "[doc.$root.$attr$aaa, (new TextDecoder).decode(xml.c14n(doc))]"), - njs_str("foo,WAKA") }, - - { njs_str(NJS_XML_DOC - "doc.$root.$text = 'WAKA';" - "doc.$root.setAttribute('aaa', 'foo');" - "doc.$root.setAttribute('bbb', 'WAKA") }, - - { njs_str(NJS_XML_DOC - "doc.note.to.setAttribute('a', null);" - "(new TextDecoder).decode(xml.c14n(doc.note.to))"), - njs_str("Tove") }, - - { njs_str(NJS_XML_DOC - "doc.$root.setAttribute('<', 'xxx')"), - njs_str("TypeError: attribute name \"<\" is not valid") }, - - { njs_str(NJS_XML_DOC - "doc.$root.$text = 'WAKA';" - "doc.$root['$attr$' + 'x'.repeat(1024)] = 1;"), - njs_str("InternalError: njs_xml_str_to_c_string() very long string, length >= 511") }, - - { njs_str(NJS_XML_DOC - "delete doc.note.to.$attr$a;" - "(new TextDecoder).decode(xml.c14n(doc.note.to))"), - njs_str("Tove") }, - - { njs_str(NJS_XML_DOC - "doc.note.to.removeAttribute('a');" - "(new TextDecoder).decode(xml.c14n(doc.note.to))"), - njs_str("Tove") }, - - { njs_str(NJS_XML_DOC - "delete doc.note.to.removeAttribute('c');" - "(new TextDecoder).decode(xml.c14n(doc.note.to))"), - njs_str("Tove") }, - - { njs_str(NJS_XML_DOC - "delete doc.note.to.removeAllAttributes();" - "(new TextDecoder).decode(xml.c14n(doc.note.to))"), - njs_str("Tove") }, - - { njs_str(NJS_XML_DOC - "delete doc.note.$tag$to;" - "(new TextDecoder).decode(xml.c14n(doc))"), - njs_str("Jani") }, - - { njs_str(NJS_XML_DOC - "delete doc.note.to;" - "(new TextDecoder).decode(xml.c14n(doc))"), - njs_str("Jani") }, - - { njs_str("var xml = require('xml');" - "var doc = xml.parse(``);" - "delete doc.$root.a;" - "(new TextDecoder).decode(xml.c14n(doc))"), - njs_str("") }, - - { njs_str("var xml = require('xml');" - "var doc = xml.parse(``);" - "doc.$root.removeChildren('c');" - "(new TextDecoder).decode(xml.c14n(doc))"), - njs_str("") }, - - { njs_str("var xml = require('xml');" - "var doc = xml.parse(``);" - "doc.$root.removeChildren('a');" - "(new TextDecoder).decode(xml.c14n(doc))"), - njs_str("") }, - - { njs_str("var xml = require('xml');" - "var doc = xml.parse(``);" - "doc.$root.removeChildren();" - "(new TextDecoder).decode(xml.c14n(doc))"), - njs_str("") }, - - { njs_str(NJS_XML_DOC - "doc.note.$tags = [];" - "(new TextDecoder).decode(xml.c14n(doc))"), - njs_str("") }, - - { njs_str(NJS_XML_DOC - "var doc2 = xml.parse(``);" - "doc.note.addChild(doc2);" - "(new TextDecoder).decode(xml.c14n(doc))"), - njs_str("ToveJani") }, - - { njs_str(NJS_XML_DOC - "var doc2 = xml.parse(``);" - "doc.note.addChild(doc2);" - "doc.note.addChild(doc2);" - "(new TextDecoder).decode(xml.c14n(doc))"), - njs_str("ToveJani" - "") }, - - { njs_str(NJS_XML_DOC - "delete doc.note.$tags$;" - "(new TextDecoder).decode(xml.c14n(doc))"), - njs_str("") }, - - { njs_str(NJS_XML_DOC - "var doc2 = xml.parse(``);" - "doc.note.$tags = [doc.note.to, doc2];" - "(new TextDecoder).decode(xml.c14n(doc))"), - njs_str("Tove") }, - - { njs_str(NJS_XML_DOC - "var doc2 = xml.parse(``);" - "doc.note.$tags = [doc2, doc.note.to];" - "(new TextDecoder).decode(xml.c14n(doc))"), - njs_str("Tove") }, -}; - - static njs_unit_test_t njs_module_test[] = { { njs_str("function f(){return 2}; var f; f()"), @@ -23288,17 +23015,6 @@ static njs_test_suite_t njs_suites[] = njs_nitems(njs_disabled_denormals_test), njs_disabled_denormals_tests }, - { -#if (NJS_HAVE_LIBXML2 && !NJS_HAVE_MEMORY_SANITIZER) - njs_str("xml"), -#else - njs_str(""), -#endif - { .externals = 1, .repeat = 1, .unsafe = 1 }, - njs_xml_test, - njs_nitems(njs_xml_test), - njs_unit_test }, - { njs_str("module"), { .repeat = 1, .module = 1, .unsafe = 1 }, njs_module_test, diff --git a/test/xml/external_entity_ignored.t.js b/test/xml/external_entity_ignored.t.js index 57c91e7f..88b192f2 100644 --- a/test/xml/external_entity_ignored.t.js +++ b/test/xml/external_entity_ignored.t.js @@ -1,9 +1,11 @@ /*--- -includes: [compatXml.js, compatNjs.js] +includes: [compatNjs.js] flags: [] paths: [] ---*/ +import xml from 'xml'; + let data = ` @@ -11,7 +13,7 @@ let data = ` &c; `; -if (has_njs() && has_xml()) { +if (has_njs()) { let doc = xml.parse(data); assert.sameValue(doc.$root.$text, ""); } diff --git a/test/xml/saml_verify.t.mjs b/test/xml/saml_verify.t.mjs index d6f06a14..65d4e06b 100644 --- a/test/xml/saml_verify.t.mjs +++ b/test/xml/saml_verify.t.mjs @@ -1,8 +1,10 @@ /*--- -includes: [compatFs.js, compatXml.js, compatWebcrypto.js, compatNjs.js, runTsuite.js] +includes: [compatFs.js, compatWebcrypto.js, compatNjs.js, runTsuite.js] flags: [async] ---*/ +import xml from 'xml'; + async function verify(params) { let file_data = fs.readFileSync(`test/xml/${params.saml}`); let key_data = fs.readFileSync(`test/webcrypto/${params.key.file}`); @@ -11,14 +13,13 @@ async function verify(params) { let sign_key_data = fs.readFileSync(`test/webcrypto/${params.key.sign_file}`); let signed = await signSAML(xml.parse(file_data), sign_key_data); file_data = xml.c14n(signed); - //console.log((new TextDecoder()).decode(file_data)); } let saml = xml.parse(file_data); let r = await verifySAMLSignature(saml, key_data) .catch (e => { - if (e.toString().startsWith("Error: EVP_PKEY_CTX_set_signature_md() failed")) { + if (e.message.startsWith("EVP_PKEY_CTX_set_signature_md() failed")) { /* Red Hat Enterprise Linux: SHA-1 is disabled */ return "SKIPPED"; } @@ -273,7 +274,7 @@ async function signatureSAML(signature, key_data, produce) { let saml_verify_tsuite = { name: "SAML verify", - skip: () => (!has_njs() || !has_webcrypto() || !has_xml()), + skip: () => (!has_njs() || !has_webcrypto()), T: verify, opts: { key: { fmt: "spki", file: "rsa.spki" }, diff --git a/test/xml/xml.t.mjs b/test/xml/xml.t.mjs new file mode 100644 index 00000000..29e2fb41 --- /dev/null +++ b/test/xml/xml.t.mjs @@ -0,0 +1,385 @@ +/*--- +includes: [compatFs.js, compatNjs.js, runTsuite.js] +flags: [async] +---*/ + +import xml from 'xml'; + +let parse_tsuite = { + name: "parse()", + skip: () => (!has_njs()), + T: async (params) => { + let doc = xml.parse(params.doc); + let r = params.get(doc); + + if (r !== params.expected) { + throw Error(`unexpected output "${r}" != "${params.expected}"`); + } + + return 'SUCCESS'; + }, + + opts: { + doc: `ToveJani`, + }, + + tests: [ + { get: (doc) => doc.nonexist, expected: undefined }, + { get: (doc) => doc.note.$name, expected: 'note' }, + { get: (doc) => doc.note.$text, expected: 'ToveJani' }, + { get: (doc) => doc.note.to.$text, expected: 'Tove' }, + { get: (doc) => doc.note.$tag$to.$text, expected: 'Tove' }, + { get: (doc) => doc.note.$attrs, expected: undefined }, + { get: (doc) => doc.note.to.$attrs.a, expected: 'foo' }, + { get: (doc) => doc.note.to.$attrs.b, expected: 'bar' }, + { get: (doc) => doc.note.to.$attr$b, expected: 'bar' }, + { get: (doc) => doc.note.$attr$a, expected: undefined }, + { get: (doc) => Array.isArray(doc.note.$tags), expected: true }, + { get: (doc) => doc.note.$tags[0].$text, expected: 'Tove' }, + { get: (doc) => doc.note.$tags[1].$text, expected: 'Jani' }, + { get: (doc) => doc.note.$tags$from[0].$text, expected: 'Jani' }, + { get: (doc) => doc.note.$parent, expected: undefined }, + { get: (doc) => doc.note.to.$parent.$name, expected: 'note' }, + { doc: ``, + get: (doc) => doc.pdu.$ns, + expected: 'http://a' }, + { doc: `FOOBAR`, + get: (doc) => doc.root.$tags$foo[0].$text, + expected: 'FOO' }, + { doc: `FOOBAR`, + get: (doc) => doc.root.$tags$foo[1].$text, + expected: 'BAR' }, + { doc: `FOOBAR`, + get: (doc) => doc.root.$tags$bar.length, + expected: 0 }, + { doc: `FOOBAR`, + get: (doc) => doc.root.$tags.length, + expected: 2 }, + { doc: `TEXT`, + get: (doc) => doc.r.$text, + expected: 'TEXT' }, + { doc: `TEXT`, + get: (doc) => doc.$root.$text, + expected: 'TEXT' }, + { doc: `俄语данные`, + get: (doc) => doc.r.$text[2], + expected: 'д' }, + { doc: `<俄语 լեզու=\"ռուսերեն\">данные`, + get: (doc) => JSON.stringify([doc['俄语'].$name[1],doc['俄语']['$attr$լեզու'][7],doc['俄语'].$text[5]]), + expected: '["语","ն","е"]' }, + + { get: (doc) => JSON.stringify(Object.getOwnPropertyNames(doc)), + expected: '["note"]' }, + { get: (doc) => JSON.stringify(Object.getOwnPropertyNames(doc.note)), + expected: '["$name","$tags"]' }, + { get: (doc) => JSON.stringify(Object.getOwnPropertyNames(doc.note.to)), + expected: '["$name","$attrs","$text"]' }, + + { get: (doc) => JSON.stringify(doc.note.to.$attrs), + expected: '{"b":"bar","a":"foo"}' }, + { get: (doc) => JSON.stringify(doc.note.$tags), + expected: '[{"$name":"to","$attrs":{"b":"bar","a":"foo"},"$text":"Tove"},{"$name":"from","$text":"Jani"}]' }, + { get: (doc) => JSON.stringify(doc), + expected: '{"note":{"$name":"note","$tags":[{"$name":"to","$attrs":{"b":"bar","a":"foo"},"$text":"Tove"},{"$name":"from","$text":"Jani"}]}}' }, + { get: (doc) => JSON.stringify(doc.note), + expected: '{"$name":"note","$tags":[{"$name":"to","$attrs":{"b":"bar","a":"foo"},"$text":"Tove"},{"$name":"from","$text":"Jani"}]}' }, + + { doc: `GARBAGE`, + exception: 'Error: failed to parse XML (libxml2: "Start tag expected, \'<\' not found" at 1:1)' }, +]}; + +let c14n_tsuite = { + name: "c14n()", + skip: () => (!has_njs()), + T: async (params) => { + let doc = xml.parse(params.doc); + let r = params.call(doc); + + if (params.buffer) { + r = new TextDecoder().decode(r); + } + + if (r !== params.expected) { + throw Error(`unexpected output "${r}" != "${params.expected}"`); + } + + return 'SUCCESS'; + }, + + opts: { + buffer: true, + doc: `foo`, + }, + + tests: [ + { call: (doc) => xml.c14n(doc.pdu.elem1), + expected: `foo` }, + { call: (doc) => xml.serialize(doc.pdu.elem1), + expected: `foo` }, + { call: (doc) => xml.serializeToString(doc.pdu.elem1), + buffer: false, + expected: `foo` }, + { call: (doc) => xml.exclusiveC14n(doc.pdu.elem1), + expected: `foo` }, + { call: (doc) => xml.exclusiveC14n(doc.pdu.elem1, null, true), + expected: `foo` }, + { call: (doc) => xml.exclusiveC14n(doc.pdu.elem1, null, false, 'n1'), + expected: `foo` }, + { call: (doc) => xml.exclusiveC14n(doc.pdu.elem1, null, false, 'a b c d e f g h i j'), + expected: `foo` }, + { doc: `ToveJani`, + call: (doc) => xml.c14n(doc.note), + expected: `ToveJani` }, + { doc: `ToveJani`, + call: (doc) => xml.exclusiveC14n(doc.note), + expected: `ToveJani` }, + { doc: `ToveJani`, + call: (doc) => xml.exclusiveC14n(doc.note, doc.note.to), + expected: `Jani` }, + { doc: ``, + call: (doc) => xml.exclusiveC14n(doc, 1), + exception: 'TypeError: "excluding" argument is not a XMLNode object' }, +]}; + +let modify_tsuite = { + name: "modifying XML", + skip: () => (!has_njs()), + T: async (params) => { + let doc = xml.parse(params.doc); + let r = params.get(doc); + + if (r !== params.expected) { + throw Error(`unexpected output "${r}" != "${params.expected}"`); + } + + return 'SUCCESS'; + }, + + opts: { + doc: `ToveJani`, + }, + + tests: [ + { get: (doc) => { + doc.note.setText('WAKA'); + return doc.note.$text; + }, + expected: 'WAKA' }, + { get: (doc) => { + doc.note.setText('WAKA'); + return xml.serializeToString(doc); + }, + expected: `WAKA` }, + { get: (doc) => { + doc.note.$text = ''; + return xml.serializeToString(doc); + }, + expected: `<WA&KA>` }, + { get: (doc) => { + doc.note.$text = ''; + return xml.serializeToString(doc); + }, + expected: `` }, + { get: (doc) => { + doc.note.setText(''); + return doc.note.$text; + }, + expected: '' }, + { get: (doc) => { + doc.note.setText(''); + return xml.serializeToString(doc); + }, + expected: `<WA&KA>` }, + { get: (doc) => { + doc.note.setText('"WAKA"'); + return xml.serializeToString(doc); + }, + expected: `"WAKA"` }, + { get: (doc) => { + doc.note.setText(''); + return doc.note.$text; + }, + expected: '' }, + { get: (doc) => { + doc.note.setText(''); + return xml.serializeToString(doc); + }, + expected: `` }, + { get: (doc) => { + doc.note.setText(null); + return doc.note.$text; + }, + expected: '' }, + { get: (doc) => { + doc.note.setText(undefined); + return doc.note.$text; + }, + expected: '' }, + { get: (doc) => { + doc.note.removeText(); + return doc.note.$text; + }, + expected: '' }, + { get: (doc) => { + delete doc.note.$text; + return xml.serializeToString(doc); + }, + expected: `` }, + { get: (doc) => { + doc.note.removeText(); + return xml.serializeToString(doc); + }, + expected: `` }, + { get: (doc) => { + let to = doc.note.to; + doc.$root.$text = ''; + return to.$name; + }, + expected: 'to' }, + { get: (doc) => { + let to = doc.note.to; + doc.$root.$text = ''; + return [to.$name, to.$text, to.$attr$b, to.$parent.$name].toString(); + }, + expected: 'to,Tove,bar,note' }, + { get: (doc) => { + doc.note.to.setAttribute('aaa', 'foo'); + doc.note.to.setAttribute('bbb', 'Tove` }, + { get: (doc) => { + doc.note.to.$attr$aaa = 'foo'; + doc.note.to.$attr$bbb = 'Tove` }, + { get: (doc) => { + doc.note.to.$attr$aaa = 'foo'; + return doc.note.to.$attr$aaa; + }, + expected: `foo` }, + { get: (doc) => { + doc.note.to.setAttribute('aaa', 'foo'); + doc.note.to.setAttribute('aaa', 'foo2'); + return xml.serializeToString(doc.note.to); + }, + expected: `Tove` }, + { get: (doc) => { + doc.note.to.removeAttribute('a'); + return xml.serializeToString(doc.note.to); + }, + expected: `Tove` }, + { get: (doc) => { + doc.note.to.removeAllAttributes(); + return xml.serializeToString(doc.note.to); + }, + expected: `Tove` }, + { get: (doc) => { + delete doc.note.to.$attr$a; + return xml.serializeToString(doc.note.to); + }, + expected: `Tove` }, + { get: (doc) => { + doc.note.to.setAttribute('a', null); + return xml.serializeToString(doc.note.to); + }, + expected: `Tove` }, + { get: (doc) => { + doc.note.to.setAttribute('<', 'foo'); + return xml.serializeToString(doc.note.to); + }, + exception: 'TypeError: attribute name "<" is not valid' }, + { get: (doc) => { + let doc2 = xml.parse(``); + doc.note.addChild(doc2); + return xml.serializeToString(doc); + }, + expected: `ToveJani` }, + { get: (doc) => { + let doc2 = xml.parse(``); + doc.note.addChild(doc2); + doc.note.addChild(doc2); + return xml.serializeToString(doc); + }, + expected: `ToveJani` }, + { get: (doc) => { + doc.note.removeChildren('to'); + return xml.serializeToString(doc); + }, + expected: `Jani` }, + { get: (doc) => { + delete doc.note.$tag$to; + return xml.serializeToString(doc); + }, + expected: `Jani` }, + { get: (doc) => { + delete doc.note.to; + return xml.serializeToString(doc); + }, + expected: `Jani` }, + { get: (doc) => { + doc.note.removeChildren('xxx'); + return xml.serializeToString(doc); + }, + expected: `ToveJani` }, + { get: (doc) => { + delete doc.note.$tag$xxx; + return xml.serializeToString(doc); + }, + expected: `ToveJani` }, + { doc: `ABC`, + get: (doc) => { + doc.$root.removeChildren('a'); + return xml.serializeToString(doc); + }, + expected: `B` }, + { doc: `ABC`, + get: (doc) => { + doc.$root.removeChildren(); + return xml.serializeToString(doc); + }, + expected: `` }, + { doc: `ABC`, + get: (doc) => { + doc.$root.$tags = []; + return xml.serializeToString(doc); + }, + expected: `` }, + { doc: `ABC`, + get: (doc) => { + doc.$root.$tags$ = []; + return xml.serializeToString(doc); + }, + expected: `` }, + { doc: `ABC`, + get: (doc) => { + delete doc.$root.$tag$a; + return xml.serializeToString(doc); + }, + expected: `B` }, + { get: (doc) => { + doc.note.$tags = [doc.note.to]; + return xml.serializeToString(doc); + }, + expected: `Tove` }, + { get: (doc) => { + let doc2 = xml.parse(``); + doc.note.$tags = [doc.note.to, doc2]; + return xml.serializeToString(doc); + }, + expected: `Tove` }, + { get: (doc) => { + let doc2 = xml.parse(``); + doc.note.$tags = [doc2, doc.note.to]; + return xml.serializeToString(doc); + }, + expected: `Tove` }, +]}; + +run([ + parse_tsuite, + c14n_tsuite, + modify_tsuite, +]) +.then($DONE, $DONE); From noreply at nginx.com Thu Mar 27 21:43:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Thu, 27 Mar 2025 21:43:02 +0000 (UTC) Subject: [njs] QuickJS: fixed compatibility issues with QuickJS-NG 0.9.0. Message-ID: <20250327214302.B5AF248399@pubserv1.nginx> details: https://github.com/nginx/njs/commit/211f229edd02c87caef872418fe550290edb7326 branches: master commit: 211f229edd02c87caef872418fe550290edb7326 user: Dmitry Volyntsev date: Wed, 26 Mar 2025 22:13:22 -0700 description: QuickJS: fixed compatibility issues with QuickJS-NG 0.9.0. This fixes #872 on Github. --- .github/workflows/check-pr.yml | 2 +- auto/quickjs | 23 +++++++++++++++++++++++ external/qjs_query_string_module.c | 4 ++-- external/qjs_webcrypto_module.c | 2 +- external/qjs_xml_module.c | 2 +- nginx/ngx_http_js_module.c | 16 ++++++++-------- src/qjs.h | 10 +++++++++- src/qjs_buffer.c | 2 +- 8 files changed, 46 insertions(+), 15 deletions(-) diff --git a/.github/workflows/check-pr.yml b/.github/workflows/check-pr.yml index 75e590fe..2be02e7b 100644 --- a/.github/workflows/check-pr.yml +++ b/.github/workflows/check-pr.yml @@ -52,7 +52,7 @@ jobs: run: | git clone https://github.com/quickjs-ng/quickjs quickjs-ng cd quickjs-ng - git checkout v0.8.0 + git checkout v0.9.0 CFLAGS="$CC_OPT -fPIC" LDFLAGS=$LD_OPT cmake -B build cmake --build build --target qjs -j $(nproc) diff --git a/auto/quickjs b/auto/quickjs index e4eecd29..60c0888e 100644 --- a/auto/quickjs +++ b/auto/quickjs @@ -137,6 +137,29 @@ if [ $NJS_TRY_QUICKJS = YES ]; then . auto/feature + njs_feature="QuickJS JS_IsArray()" + njs_feature_name=NJS_HAVE_QUICKJS_IS_ARRAY_SINGLE_ARG + njs_feature_test="#if defined(__GNUC__) && (__GNUC__ >= 8) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored \"-Wcast-function-type\" + #endif + + #include + + int main() { + JSRuntime *rt; + JSContext *ctx; + + rt = JS_NewRuntime(); + ctx = JS_NewContext(rt); + (void) JS_IsArray(JS_UNDEFINED); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + return 0; + }" + + . auto/feature + njs_feature="QuickJS JS_AddIntrinsicBigInt()" njs_feature_name=NJS_HAVE_QUICKJS_ADD_INTRINSIC_BIG_INT njs_feature_test="#if defined(__GNUC__) && (__GNUC__ >= 8) diff --git a/external/qjs_query_string_module.c b/external/qjs_query_string_module.c index 63553a53..bb787229 100644 --- a/external/qjs_query_string_module.c +++ b/external/qjs_query_string_module.c @@ -403,7 +403,7 @@ qjs_query_string_append(JSContext *cx, JSValue object, const u_char *key, goto exception; } - } else if (JS_IsArray(cx, prev)) { + } else if (qjs_is_array(cx, prev)) { length = JS_GetPropertyStr(cx, prev, "length"); if (JS_ToUint32(cx, &len, length) < 0) { @@ -762,7 +762,7 @@ qjs_query_string_stringify_internal(JSContext *cx, JSValue obj, njs_str_t *sep, goto fail; } - if (JS_IsArray(cx, val)) { + if (qjs_is_array(cx, val)) { key = JS_AtomToString(cx, ptab[n].atom); if (JS_IsException(key)) { JS_FreeValue(cx, val); diff --git a/external/qjs_webcrypto_module.c b/external/qjs_webcrypto_module.c index 9c4bb452..a28a8581 100644 --- a/external/qjs_webcrypto_module.c +++ b/external/qjs_webcrypto_module.c @@ -4556,7 +4556,7 @@ qjs_key_usage(JSContext *cx, JSValue value, unsigned *mask) njs_str_t s; qjs_webcrypto_entry_t *e; - if (!JS_IsArray(cx, value)) { + if (!qjs_is_array(cx, value)) { JS_ThrowTypeError(cx, "\"keyUsages\" argument must be an Array"); return JS_EXCEPTION; } diff --git a/external/qjs_xml_module.c b/external/qjs_xml_module.c index 087b4a7b..8f90ee5c 100644 --- a/external/qjs_xml_module.c +++ b/external/qjs_xml_module.c @@ -795,7 +795,7 @@ qjs_xml_node_tags_modify(JSContext *cx, JSValue obj, njs_str_t *name, return -1; } - if (!JS_IsArray(cx, setval)) { + if (!qjs_is_array(cx, setval)) { JS_ThrowTypeError(cx, "setval is not an array"); return -1; } diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c index 369ae50b..0c8215c5 100644 --- a/nginx/ngx_http_js_module.c +++ b/nginx/ngx_http_js_module.c @@ -4910,7 +4910,7 @@ ngx_http_qjs_ext_args(JSContext *cx, JSValueConst this_val) goto exception; } - } else if (JS_IsArray(cx, prev)) { + } else if (qjs_is_array(cx, prev)) { length = JS_GetPropertyStr(cx, prev, "length"); if (JS_ToUint32(cx, &len, length)) { @@ -6729,7 +6729,7 @@ ngx_http_qjs_headers_out_handler(JSContext *cx, ngx_http_request_t *r, return 1; } - if (JS_IsArray(cx, *value)) { + if (qjs_is_array(cx, *value)) { v = JS_GetPropertyStr(cx, *value, "length"); if (JS_IsException(v)) { return -1; @@ -6750,7 +6750,7 @@ ngx_http_qjs_headers_out_handler(JSContext *cx, ngx_http_request_t *r, ph = &header; for (i = 0; i < (uint32_t) length; i++) { - if (JS_IsArray(cx, *value)) { + if (qjs_is_array(cx, *value)) { v = JS_GetPropertyUint32(cx, *value, i); if (JS_IsException(v)) { return -1; @@ -6759,7 +6759,7 @@ ngx_http_qjs_headers_out_handler(JSContext *cx, ngx_http_request_t *r, rc = ngx_qjs_string(cx, v, &s); - if (JS_IsArray(cx, *value)) { + if (qjs_is_array(cx, *value)) { JS_FreeValue(cx, v); } @@ -6832,7 +6832,7 @@ ngx_http_qjs_headers_out_special_handler(JSContext *cx, ngx_http_request_t *r, } if (value != NULL) { - if (JS_IsArray(cx, *value)) { + if (qjs_is_array(cx, *value)) { len = JS_GetPropertyStr(cx, *value, "length"); if (JS_IsException(len)) { return -1; @@ -6860,7 +6860,7 @@ ngx_http_qjs_headers_out_special_handler(JSContext *cx, ngx_http_request_t *r, rc = ngx_qjs_string(cx, setval, &s); - if (value != NULL && JS_IsArray(cx, *value)) { + if (value != NULL && qjs_is_array(cx, *value)) { JS_FreeValue(cx, setval); } @@ -7071,7 +7071,7 @@ ngx_http_qjs_headers_out_content_type(JSContext *cx, ngx_http_request_t *r, return 1; } - if (JS_IsArray(cx, *value)) { + if (qjs_is_array(cx, *value)) { len = JS_GetPropertyStr(cx, *value, "length"); if (JS_IsException(len)) { return -1; @@ -7095,7 +7095,7 @@ ngx_http_qjs_headers_out_content_type(JSContext *cx, ngx_http_request_t *r, rc = ngx_qjs_string(cx, setval, &s); - if (JS_IsArray(cx, *value)) { + if (qjs_is_array(cx, *value)) { JS_FreeValue(cx, setval); } diff --git a/src/qjs.h b/src/qjs.h index 25d6cba3..d3bbc0e8 100644 --- a/src/qjs.h +++ b/src/qjs.h @@ -27,6 +27,10 @@ #include +#ifndef JS_BOOL +#define JS_BOOL bool +#endif + #if defined(__GNUC__) && (__GNUC__ >= 8) #pragma GCC diagnostic pop #endif @@ -144,13 +148,17 @@ static inline JS_BOOL JS_IsNullOrUndefined(JSValueConst v) || JS_VALUE_GET_TAG(v) == JS_TAG_UNDEFINED; } - #ifdef NJS_HAVE_QUICKJS_IS_SAME_VALUE #define qjs_is_same_value(cx, a, b) JS_IsSameValue(cx, a, b) #else #define qjs_is_same_value(cx, a, b) JS_SameValue(cx, a, b) #endif +#ifdef NJS_HAVE_QUICKJS_IS_ARRAY_SINGLE_ARG +#define qjs_is_array(cx, a) JS_IsArray(a) +#else +#define qjs_is_array(cx, a) JS_IsArray(cx, a) +#endif extern qjs_module_t *qjs_modules[]; diff --git a/src/qjs_buffer.c b/src/qjs_buffer.c index 48f609be..a45f57ce 100644 --- a/src/qjs_buffer.c +++ b/src/qjs_buffer.c @@ -440,7 +440,7 @@ qjs_buffer_concat(JSContext *ctx, JSValueConst this_val, int argc, list = argv[0]; - if (!JS_IsArray(ctx, list)) { + if (!qjs_is_array(ctx, list)) { return JS_ThrowTypeError(ctx, "\"list\" argument must be an instance of Array"); } From noreply at nginx.com Sat Mar 29 22:39:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Sat, 29 Mar 2025 22:39:02 +0000 (UTC) Subject: [njs] QuickJS: fixed ngx_qjs_external_resolver(). Message-ID: <20250329223902.484284839C@pubserv1.nginx> details: https://github.com/nginx/njs/commit/fd004b1b3b6effae94d37a41b723aef1ec9b90f1 branches: master commit: fd004b1b3b6effae94d37a41b723aef1ec9b90f1 user: Zhidao HONG date: Sat, 29 Mar 2025 22:02:19 +0800 description: QuickJS: fixed ngx_qjs_external_resolver(). --- nginx/ngx_js.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nginx/ngx_js.h b/nginx/ngx_js.h index 6fac6994..ba4cbdf9 100644 --- a/nginx/ngx_js.h +++ b/nginx/ngx_js.h @@ -357,7 +357,7 @@ ngx_int_t ngx_qjs_string(JSContext *cx, JSValueConst val, ngx_str_t *str); #define ngx_qjs_external_pool(cx, e) \ ((ngx_external_pool_pt) ngx_qjs_meta(cx, 1))(e) #define ngx_qjs_external_resolver(cx, e) \ - ((ngx_external_resolver_pt) ngx_qjs_meta(vm, 2))(e) + ((ngx_external_resolver_pt) ngx_qjs_meta(cx, 2))(e) #define ngx_qjs_external_resolver_timeout(cx, e) \ ((ngx_external_timeout_pt) ngx_qjs_meta(cx, 3))(e) #define ngx_qjs_external_event_finalize(cx) \