From 01fde66111c1d47130551038ce2b3fa58d20d3a2 Mon Sep 17 00:00:00 2001 From: Dmitry Volyntsev Date: Wed, 25 Nov 2020 10:47:47 +0000 Subject: [PATCH] Modules: introduced Buffer alternatives for object properties. Buffer variant returns the property bytes as is, whereas the string version may convert bytes invalid in UTF-8 encoding into replacement character. HTTP new request object properties: r.reqBody (r.requestBody), r.resBody (r.responseBody), r.vars (r.variables). Stream new stream object properties: s.vars (s.variables). new events: The events' callbacks are identical to the string counterparts, except the data argument: upstream (upload), downstream (download). --- nginx/ngx_http_js_module.c | 71 +++++++++++++++++++-- nginx/ngx_js.h | 10 +++ nginx/ngx_stream_js_module.c | 117 ++++++++++++++++++++++++++--------- src/njs.h | 2 + src/njs_extern.c | 1 + src/njs_value.c | 8 +++ src/njs_value.h | 1 + 7 files changed, 175 insertions(+), 35 deletions(-) diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c index bf849e8c..437f6bef 100644 --- a/nginx/ngx_http_js_module.c +++ b/nginx/ngx_http_js_module.c @@ -43,6 +43,7 @@ typedef struct { ngx_int_t status; njs_opaque_value_t request; njs_opaque_value_t request_body; + njs_opaque_value_t response_body; ngx_str_t redirect_uri; ngx_array_t promise_callbacks; } ngx_http_js_ctx_t; @@ -319,9 +320,19 @@ static njs_external_t ngx_http_js_ext_request[] = { { .flags = NJS_EXTERN_PROPERTY, .name.string = njs_str("requestBody"), + .u.property = { + .handler = ngx_http_js_ext_get_request_body, + .magic32 = NGX_JS_STRING, + } + }, + + { + .flags = NJS_EXTERN_PROPERTY, + .name.string = njs_str("reqBody"), .enumerable = 1, .u.property = { .handler = ngx_http_js_ext_get_request_body, + .magic32 = NGX_JS_BUFFER, } }, @@ -336,9 +347,19 @@ static njs_external_t ngx_http_js_ext_request[] = { { .flags = NJS_EXTERN_PROPERTY, .name.string = njs_str("responseBody"), + .u.property = { + .handler = ngx_http_js_ext_get_response_body, + .magic32 = NGX_JS_STRING, + } + }, + + { + .flags = NJS_EXTERN_PROPERTY, + .name.string = njs_str("resBody"), .enumerable = 1, .u.property = { .handler = ngx_http_js_ext_get_response_body, + .magic32 = NGX_JS_BUFFER, } }, @@ -379,6 +400,17 @@ static njs_external_t ngx_http_js_ext_request[] = { .u.object = { .writable = 1, .prop_handler = ngx_http_js_ext_variables, + .magic32 = NGX_JS_STRING, + } + }, + + { + .flags = NJS_EXTERN_OBJECT, + .name.string = njs_str("vars"), + .u.object = { + .writable = 1, + .prop_handler = ngx_http_js_ext_variables, + .magic32 = NGX_JS_BUFFER, } }, @@ -1891,8 +1923,12 @@ ngx_http_js_ext_get_request_body(njs_vm_t *vm, njs_object_prop_t *prop, request_body = (njs_value_t *) &ctx->request_body; if (!njs_value_is_null(request_body)) { - njs_value_assign(retval, request_body); - return NJS_OK; + if ((njs_vm_prop_magic32(prop) == NGX_JS_BUFFER) + == (uint32_t) njs_value_is_buffer(request_body)) + { + njs_value_assign(retval, request_body); + return NJS_OK; + } } if (r->request_body == NULL || r->request_body->bufs == NULL) { @@ -1939,8 +1975,7 @@ ngx_http_js_ext_get_request_body(njs_vm_t *vm, njs_object_prop_t *prop, done: - ret = njs_vm_value_string_set(vm, request_body, body, len); - + ret = ngx_js_prop(vm, njs_vm_prop_magic32(prop), request_body, body, len); if (ret != NJS_OK) { return NJS_ERROR; } @@ -2220,7 +2255,8 @@ ngx_http_js_ext_variables(njs_vm_t *vm, njs_object_prop_t *prop, return NJS_DECLINED; } - return njs_vm_value_string_set(vm, retval, vv->data, vv->len); + return ngx_js_prop(vm, njs_vm_prop_magic32(prop), retval, vv->data, + vv->len); } cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); @@ -2743,7 +2779,10 @@ ngx_http_js_ext_get_response_body(njs_vm_t *vm, njs_object_prop_t *prop, { size_t len; u_char *p; + njs_int_t ret; ngx_buf_t *b; + njs_value_t *response_body; + ngx_http_js_ctx_t *ctx; ngx_http_request_t *r; r = njs_vm_external(vm, value); @@ -2752,6 +2791,18 @@ ngx_http_js_ext_get_response_body(njs_vm_t *vm, njs_object_prop_t *prop, return NJS_DECLINED; } + ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); + response_body = (njs_value_t *) &ctx->response_body; + + if (!njs_value_is_null(response_body)) { + if ((njs_vm_prop_magic32(prop) == NGX_JS_BUFFER) + == (uint32_t) njs_value_is_buffer(response_body)) + { + njs_value_assign(retval, response_body); + return NJS_OK; + } + } + b = r->out ? r->out->buf : NULL; if (b == NULL) { @@ -2761,8 +2812,9 @@ ngx_http_js_ext_get_response_body(njs_vm_t *vm, njs_object_prop_t *prop, len = b->last - b->pos; - p = njs_vm_value_string_alloc(vm, retval, len); + p = ngx_pnalloc(r->pool, len); if (p == NULL) { + njs_vm_memory_error(vm); return NJS_ERROR; } @@ -2770,6 +2822,13 @@ ngx_http_js_ext_get_response_body(njs_vm_t *vm, njs_object_prop_t *prop, ngx_memcpy(p, b->pos, len); } + ret = ngx_js_prop(vm, njs_vm_prop_magic32(prop), response_body, p, len); + if (ret != NJS_OK) { + return NJS_ERROR; + } + + njs_value_assign(retval, response_body); + return NJS_OK; } diff --git a/nginx/ngx_js.h b/nginx/ngx_js.h index 2bd90e25..0e290d52 100644 --- a/nginx/ngx_js.h +++ b/nginx/ngx_js.h @@ -15,10 +15,20 @@ #include +#define NGX_JS_UNSET 0 +#define NGX_JS_STRING 1 +#define NGX_JS_BUFFER 2 + + #define ngx_external_connection(vm, ext) \ (*((ngx_connection_t **) ((u_char *) ext + njs_vm_meta(vm, 0)))) +#define ngx_js_prop(vm, type, value, start, len) \ + ((type == NGX_JS_STRING) ? njs_vm_value_string_set(vm, value, start, len) \ + : njs_vm_value_buffer_set(vm, value, start, len)) + + ngx_int_t ngx_js_call(njs_vm_t *vm, ngx_str_t *s, njs_opaque_value_t *value, ngx_log_t *log); diff --git a/nginx/ngx_stream_js_module.c b/nginx/ngx_stream_js_module.c index c4cac7a6..2900ac57 100644 --- a/nginx/ngx_stream_js_module.c +++ b/nginx/ngx_stream_js_module.c @@ -38,6 +38,12 @@ typedef struct { } ngx_stream_js_srv_conf_t; +typedef struct { + njs_vm_event_t ev; + ngx_uint_t data_type; +} ngx_stream_js_ev_t; + + typedef struct { njs_vm_t *vm; ngx_log_t *log; @@ -51,7 +57,7 @@ typedef struct { #define NGX_JS_EVENT_UPLOAD 0 #define NGX_JS_EVENT_DOWNLOAD 1 #define NGX_JS_EVENT_MAX 2 - njs_vm_event_t events[2]; + ngx_stream_js_ev_t events[2]; unsigned from_upstream:1; unsigned filter:1; unsigned in_progress:1; @@ -79,7 +85,7 @@ static void ngx_stream_js_drop_events(ngx_stream_js_ctx_t *ctx); static void ngx_stream_js_cleanup_ctx(void *data); static void ngx_stream_js_cleanup_vm(void *data); static njs_int_t ngx_stream_js_buffer_arg(ngx_stream_session_t *s, - njs_value_t *buffer); + njs_value_t *buffer, ngx_uint_t data_type); static njs_int_t ngx_stream_js_flags_arg(ngx_stream_session_t *s, njs_value_t *flags); static njs_vm_event_t *ngx_stream_js_event(ngx_stream_session_t *s, @@ -233,6 +239,17 @@ static njs_external_t ngx_stream_js_ext_session[] = { .u.object = { .writable = 1, .prop_handler = ngx_stream_js_ext_variables, + .magic32 = NGX_JS_STRING, + } + }, + + { + .flags = NJS_EXTERN_OBJECT, + .name.string = njs_str("vars"), + .u.object = { + .writable = 1, + .prop_handler = ngx_stream_js_ext_variables, + .magic32 = NGX_JS_BUFFER, } }, @@ -412,6 +429,7 @@ ngx_stream_js_phase_handler(ngx_stream_session_t *s, ngx_str_t *name) njs_int_t ret; ngx_int_t rc; ngx_connection_t *c; + ngx_stream_js_ev_t *event; ngx_stream_js_ctx_t *ctx; if (name->len == 0) { @@ -444,8 +462,11 @@ ngx_stream_js_phase_handler(ngx_stream_session_t *s, ngx_str_t *name) } } - if (ctx->events[NGX_JS_EVENT_UPLOAD] != NULL) { - ret = ngx_stream_js_buffer_arg(s, njs_value_arg(&ctx->args[1])); + event = &ctx->events[NGX_JS_EVENT_UPLOAD]; + + if (event->ev != NULL) { + ret = ngx_stream_js_buffer_arg(s, njs_value_arg(&ctx->args[1]), + event->data_type); if (ret != NJS_OK) { goto exception; } @@ -455,8 +476,7 @@ ngx_stream_js_phase_handler(ngx_stream_session_t *s, ngx_str_t *name) goto exception; } - njs_vm_post_event(ctx->vm, ctx->events[NGX_JS_EVENT_UPLOAD], - njs_value_arg(&ctx->args[1]), 2); + njs_vm_post_event(ctx->vm, event->ev, njs_value_arg(&ctx->args[1]), 2); rc = njs_vm_run(ctx->vm); if (rc == NJS_ERROR) { @@ -466,7 +486,7 @@ ngx_stream_js_phase_handler(ngx_stream_session_t *s, ngx_str_t *name) if (njs_vm_pending(ctx->vm)) { ctx->in_progress = 1; - rc = ctx->events[NGX_JS_EVENT_UPLOAD] ? NGX_AGAIN : NGX_DONE; + rc = ctx->events[NGX_JS_EVENT_UPLOAD].ev ? NGX_AGAIN : NGX_DONE; } else { ctx->in_progress = 0; @@ -490,8 +510,8 @@ exception: #define ngx_stream_event(from_upstream) \ - (from_upstream ? ctx->events[NGX_JS_EVENT_DOWNLOAD] \ - : ctx->events[NGX_JS_EVENT_UPLOAD]) + (from_upstream ? &ctx->events[NGX_JS_EVENT_DOWNLOAD] \ + : &ctx->events[NGX_JS_EVENT_UPLOAD]) static ngx_int_t @@ -503,6 +523,7 @@ ngx_stream_js_body_filter(ngx_stream_session_t *s, ngx_chain_t *in, ngx_int_t rc; ngx_chain_t *out, *cl; ngx_connection_t *c; + ngx_stream_js_ev_t *event; ngx_stream_js_ctx_t *ctx; ngx_stream_js_srv_conf_t *jscf; @@ -543,8 +564,11 @@ ngx_stream_js_body_filter(ngx_stream_session_t *s, ngx_chain_t *in, while (in) { ctx->buf = in->buf; - if (ngx_stream_event(from_upstream) != NULL) { - ret = ngx_stream_js_buffer_arg(s, njs_value_arg(&ctx->args[1])); + event = ngx_stream_event(from_upstream); + + if (event->ev != NULL) { + ret = ngx_stream_js_buffer_arg(s, njs_value_arg(&ctx->args[1]), + event->data_type); if (ret != NJS_OK) { goto exception; } @@ -554,7 +578,7 @@ ngx_stream_js_body_filter(ngx_stream_session_t *s, ngx_chain_t *in, goto exception; } - njs_vm_post_event(ctx->vm, ngx_stream_event(from_upstream), + njs_vm_post_event(ctx->vm, event->ev, njs_value_arg(&ctx->args[1]), 2); rc = njs_vm_run(ctx->vm); @@ -729,9 +753,9 @@ ngx_stream_js_drop_events(ngx_stream_js_ctx_t *ctx) ngx_uint_t i; for (i = 0; i < NGX_JS_EVENT_MAX; i++) { - if (ctx->events[i] != NULL) { - njs_vm_del_event(ctx->vm, ctx->events[i]); - ctx->events[i] = NULL; + if (ctx->events[i].ev != NULL) { + njs_vm_del_event(ctx->vm, ctx->events[i].ev); + ctx->events[i].ev = NULL; } } } @@ -762,7 +786,8 @@ ngx_stream_js_cleanup_vm(void *data) static njs_int_t -ngx_stream_js_buffer_arg(ngx_stream_session_t *s, njs_value_t *buffer) +ngx_stream_js_buffer_arg(ngx_stream_session_t *s, njs_value_t *buffer, + ngx_uint_t data_type) { size_t len; u_char *p; @@ -777,8 +802,9 @@ ngx_stream_js_buffer_arg(ngx_stream_session_t *s, njs_value_t *buffer) len = b ? b->last - b->pos : 0; - p = njs_vm_value_string_alloc(ctx->vm, buffer, len); + p = ngx_pnalloc(c->pool, len); if (p == NULL) { + njs_vm_memory_error(ctx->vm); return NJS_ERROR; } @@ -786,11 +812,10 @@ ngx_stream_js_buffer_arg(ngx_stream_session_t *s, njs_value_t *buffer) ngx_memcpy(p, b->pos, len); } - return NJS_OK; + return ngx_js_prop(ctx->vm, data_type, buffer, p, len); } - static njs_int_t ngx_stream_js_flags_arg(ngx_stream_session_t *s, njs_value_t *flags) { @@ -821,12 +846,37 @@ ngx_stream_js_flags_arg(ngx_stream_session_t *s, njs_value_t *flags) static njs_vm_event_t * ngx_stream_js_event(ngx_stream_session_t *s, njs_str_t *event) { - ngx_uint_t i, n; + ngx_uint_t i, n, type; ngx_stream_js_ctx_t *ctx; - static const njs_str_t events[] = { - njs_str("upload"), - njs_str("download") + static const struct { + ngx_str_t name; + ngx_uint_t data_type; + ngx_uint_t id; + } events[] = { + { + ngx_string("upload"), + NGX_JS_STRING, + NGX_JS_EVENT_UPLOAD, + }, + + { + ngx_string("download"), + NGX_JS_STRING, + NGX_JS_EVENT_DOWNLOAD, + }, + + { + ngx_string("upstream"), + NGX_JS_BUFFER, + NGX_JS_EVENT_UPLOAD, + }, + + { + ngx_string("downstream"), + NGX_JS_BUFFER, + NGX_JS_EVENT_DOWNLOAD, + }, }; ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module); @@ -835,8 +885,9 @@ ngx_stream_js_event(ngx_stream_session_t *s, njs_str_t *event) n = sizeof(events) / sizeof(events[0]); while (i < n) { - if (event->length == events[i].length - && ngx_memcmp(event->start, events[i].start, event->length) == 0) + if (event->length == events[i].name.len + && ngx_memcmp(event->start, events[i].name.data, event->length) + == 0) { break; } @@ -849,11 +900,18 @@ ngx_stream_js_event(ngx_stream_session_t *s, njs_str_t *event) return NULL; } - if (i == 0) { - return &ctx->events[NGX_JS_EVENT_UPLOAD]; + ctx->events[events[i].id].data_type = events[i].data_type; + + for (n = 0; n < NGX_JS_EVENT_MAX; n++) { + type = ctx->events[n].data_type; + if (type != NGX_JS_UNSET && type != events[i].data_type) { + njs_vm_error(ctx->vm, "mixing string and buffer events" + " is not allowed"); + return NULL; + } } - return &ctx->events[NGX_JS_EVENT_DOWNLOAD]; + return &ctx->events[events[i].id].ev; } @@ -1131,7 +1189,8 @@ ngx_stream_js_ext_variables(njs_vm_t *vm, njs_object_prop_t *prop, return NJS_DECLINED; } - return njs_vm_value_string_set(vm, retval, vv->data, vv->len); + return ngx_js_prop(vm, njs_vm_prop_magic32(prop), retval, vv->data, + vv->len); } cmcf = ngx_stream_get_module_main_conf(s, ngx_stream_core_module); diff --git a/src/njs.h b/src/njs.h index cf63f219..1bfc915f 100644 --- a/src/njs.h +++ b/src/njs.h @@ -144,6 +144,7 @@ struct njs_external_s { unsigned configurable; unsigned enumerable; njs_prop_handler_t prop_handler; + uint32_t magic32; njs_exotic_keys_t keys; } object; } u; @@ -389,6 +390,7 @@ NJS_EXPORT njs_int_t njs_value_is_string(const njs_value_t *value); NJS_EXPORT njs_int_t njs_value_is_object(const njs_value_t *value); NJS_EXPORT njs_int_t njs_value_is_array(const njs_value_t *value); NJS_EXPORT njs_int_t njs_value_is_function(const njs_value_t *value); +NJS_EXPORT njs_int_t njs_value_is_buffer(const njs_value_t *value); NJS_EXPORT njs_int_t njs_vm_object_alloc(njs_vm_t *vm, njs_value_t *retval, ...); diff --git a/src/njs_extern.c b/src/njs_extern.c index 8d0ddf59..a2dfd01f 100644 --- a/src/njs_extern.c +++ b/src/njs_extern.c @@ -132,6 +132,7 @@ njs_external_add(njs_vm_t *vm, njs_arr_t *protos, next->configurable = external->u.object.configurable; next->enumerable = external->u.object.enumerable; next->prop_handler = external->u.object.prop_handler; + next->magic32 = external->u.object.magic32; next->keys = external->u.object.keys; break; diff --git a/src/njs_value.c b/src/njs_value.c index 365013fa..d72116a0 100644 --- a/src/njs_value.c +++ b/src/njs_value.c @@ -492,6 +492,13 @@ njs_value_is_function(const njs_value_t *value) } +njs_int_t +njs_value_is_buffer(const njs_value_t *value) +{ + return njs_is_typed_array(value); +} + + /* * ES5.1, 8.12.1: [[GetOwnProperty]], [[GetProperty]]. * The njs_property_query() returns values @@ -932,6 +939,7 @@ njs_external_property_query(njs_vm_t *vm, njs_property_query_t *pq, * njs_set_null(&prop->setter); */ + prop->value.data.magic32 = slots->magic32; prop->name = pq->key; pq->lhq.value = prop; diff --git a/src/njs_value.h b/src/njs_value.h index bd9c6725..06e4fd72 100644 --- a/src/njs_value.h +++ b/src/njs_value.h @@ -189,6 +189,7 @@ union njs_value_s { typedef struct { /* Get, also Set if writable, also Delete if configurable. */ njs_prop_handler_t prop_handler; + uint32_t magic32; unsigned writable:1; unsigned configurable:1; unsigned enumerable:1; -- 2.47.3