]> git.kaiwu.me - njs.git/commitdiff
QuickJS: copy body bytes into ArrayBuffer
authorDmitry Volyntsev <xeioex@nginx.com>
Fri, 30 Oct 2026 01:44:24 +0000 (18:44 -0700)
committerDmitry Volyntsev <xeioexception@gmail.com>
Mon, 2 Nov 2026 14:42:35 +0000 (06:42 -0800)
Previously, r.readRequestArrayBuffer() and the arrayBuffer() methods
of fetch Request and Response objects returned an ArrayBuffer over
memory owned by a pool: the request pool or the engine pool.  The
buffer was created without a free function, which caused the
following problems:

  - with js_context_reuse, a buffer retained in the module scope
    outlived the pool and pointed to freed memory in the next
    request;
  - ArrayBuffer.prototype.transfer() called the NULL free function;
  - writes through the buffer modified the request body seen by
    subsequent r.requestText and r.readRequestText() calls, for a
    single buffer body directly in the nginx request buffers.

Now, the bytes are copied into memory owned by the ArrayBuffer.  For
Response.arrayBuffer() the body chain is joined directly into the
runtime memory, so the number of copies is unchanged.

The issue was introduced in b7f76b71 (0.9.1) and 7684d702 (0.9.9).

Based on a patch by Vadim Zhestikov.

nginx/ngx_http_js_module.c
nginx/ngx_qjs_fetch.c

index 86399ec001d1cb0470c4a3faeeffc60bd292910c..898e9db4541d5e7012e3ca7d31c3182e073f6844 100644 (file)
@@ -6690,8 +6690,8 @@ ngx_http_qjs_body_to_value(JSContext *cx, ngx_http_js_ctx_t *ctx,
 
     switch (type) {
     case NGX_JS_BODY_ARRAY_BUFFER:
-        return qjs_new_external_array_buffer(cx, ctx->body_read_data,
-                                             ctx->body_read_len, 0);
+        return JS_NewArrayBufferCopy(cx, ctx->body_read_data,
+                                     ctx->body_read_len);
 
     case NGX_JS_BODY_JSON:
         if (ctx->body_read_nul) {
index be3f299bfd200a596ba77476bdab4a7433caad20..7149b976b8dba41f302addf17d79602f05cd2639 100644 (file)
@@ -2004,12 +2004,8 @@ ngx_qjs_ext_fetch_request_body(JSContext *cx, JSValueConst this_val,
 
     switch (magic) {
     case NGX_JS_BODY_ARRAY_BUFFER:
-        /*
-         * request->body is allocated from e->pool
-         * and will be freed when context is freed.
-         */
-        result = qjs_new_external_array_buffer(cx, request->body.data,
-                                               request->body.len, 0);
+        result = JS_NewArrayBufferCopy(cx, request->body.data,
+                                       request->body.len);
         if (JS_IsException(result)) {
             return JS_ThrowOutOfMemory(cx);
         }
@@ -2298,6 +2294,8 @@ static JSValue
 ngx_qjs_ext_fetch_response_body(JSContext *cx, JSValueConst this_val,
     int argc, JSValueConst *argv, int magic)
 {
+    u_char             *p;
+    int64_t             size;
     JSValue             result;
     njs_int_t           ret;
     njs_str_t           string;
@@ -2325,21 +2323,22 @@ ngx_qjs_ext_fetch_response_body(JSContext *cx, JSValueConst this_val,
         break;
 
     case NGX_JS_BODY_ARRAY_BUFFER:
-        ret = njs_chb_join(&response->chain, &string);
-        if (ret != NJS_OK) {
+        size = njs_chb_size(&response->chain);
+        if (size < 0) {
             return JS_ThrowOutOfMemory(cx);
         }
 
+        p = js_malloc(cx, njs_max(size, 1));
+        if (p == NULL) {
+            return JS_EXCEPTION;
+        }
+
+        njs_chb_join_to(&response->chain, p);
         njs_chb_destroy(&response->chain);
 
-        /*
-         * string.start is allocated from e->pool
-         * and will be freed when context is freed.
-         */
-        result = qjs_new_external_array_buffer(cx, string.start,
-                                               string.length, 0);
+        result = qjs_new_array_buffer(cx, p, size);
         if (JS_IsException(result)) {
-            return JS_ThrowOutOfMemory(cx);
+            js_free(cx, p);
         }
 
         break;