changeset 2579:8b6a2ce70d39

Fetch: fixed handling of Content-Length header when body is provided. body value length takes precedence over Content-Length from header list. https://fetch.spec.whatwg.org/#http-network-or-cache-fetch Let contentLength be httpRequest’s body’s length, if httpRequest’s body is non-null; otherwise null. Let contentLengthHeaderValue be null. If httpRequest’s body is null and httpRequest’s method is `POST` or `PUT`, then set contentLengthHeaderValue to `0`. If contentLength is non-null, then set contentLengthHeaderValue to contentLength, serialized and isomorphic encoded. If contentLengthHeaderValue is non-null, then append (`Content-Length`, contentLengthHeaderValue) to httpRequest’s header list. This fixes #930 issue in Github.
author Dmitry Volyntsev <xeioex@nginx.com>
date Mon, 16 Jun 2025 19:36:35 -0700
parents cf91a31a0947
children ecf2499ed755
files nginx/ngx_js_fetch.c nginx/ngx_qjs_fetch.c nginx/t/js_fetch.t
diffstat 3 files changed, 61 insertions(+), 4 deletions(-) [+]
line wrap: on
line diff
--- a/nginx/ngx_js_fetch.c	Mon Jun 16 18:16:46 2025 -0700
+++ b/nginx/ngx_js_fetch.c	Mon Jun 16 19:36:35 2025 -0700
@@ -514,6 +514,7 @@
     ngx_url_t            u;
     ngx_uint_t           i;
     njs_bool_t           has_host;
+    ngx_str_t            method;
     ngx_pool_t          *pool;
     njs_value_t         *init, *value;
     ngx_js_http_t       *http;
@@ -674,6 +675,13 @@
             continue;
         }
 
+        if (h[i].key.len == 14
+            && ngx_strncasecmp(h[i].key.data, (u_char *) "Content-Length", 14)
+            == 0)
+        {
+            continue;
+        }
+
         njs_chb_append(&http->chain, h[i].key.data, h[i].key.len);
         njs_chb_append_literal(&http->chain, ": ");
         njs_chb_append(&http->chain, h[i].value.data, h[i].value.len);
@@ -693,7 +701,18 @@
         njs_chb_append(&http->chain, request.body.data, request.body.len);
 
     } else {
-        njs_chb_append_literal(&http->chain, CRLF);
+        method = request.method;
+
+        if ((method.len == 4
+            && (ngx_strncasecmp(method.data, (u_char *) "POST", 4) == 0))
+            || (method.len == 3
+                && ngx_strncasecmp(method.data, (u_char *) "PUT", 3) == 0))
+        {
+            njs_chb_append_literal(&http->chain, "Content-Length: 0" CRLF CRLF);
+
+        } else {
+            njs_chb_append_literal(&http->chain, CRLF);
+        }
     }
 
     if (u.addrs == NULL) {
--- a/nginx/ngx_qjs_fetch.c	Mon Jun 16 18:16:46 2025 -0700
+++ b/nginx/ngx_qjs_fetch.c	Mon Jun 16 19:36:35 2025 -0700
@@ -241,6 +241,7 @@
     JSValue              init, value, promise;
     ngx_int_t            rc;
     ngx_url_t            u;
+    ngx_str_t            method;
     ngx_uint_t           i;
     ngx_pool_t          *pool;
     ngx_js_ctx_t        *ctx;
@@ -410,6 +411,13 @@
             continue;
         }
 
+        if (h[i].key.len == 14
+            && ngx_strncasecmp(h[i].key.data, (u_char *) "Content-Length", 14)
+            == 0)
+        {
+            continue;
+        }
+
         njs_chb_append(&http->chain, h[i].key.data, h[i].key.len);
         njs_chb_append_literal(&http->chain, ": ");
         njs_chb_append(&http->chain, h[i].value.data, h[i].value.len);
@@ -429,7 +437,18 @@
         njs_chb_append(&http->chain, request.body.data, request.body.len);
 
     } else {
-        njs_chb_append_literal(&http->chain, CRLF);
+        method = request.method;
+
+        if ((method.len == 4
+            && (ngx_strncasecmp(method.data, (u_char *) "POST", 4) == 0))
+            || (method.len == 3
+                && ngx_strncasecmp(method.data, (u_char *) "PUT", 3) == 0))
+        {
+            njs_chb_append_literal(&http->chain, "Content-Length: 0" CRLF CRLF);
+
+        } else {
+            njs_chb_append_literal(&http->chain, CRLF);
+        }
     }
 
     if (u.addrs == NULL) {
--- a/nginx/t/js_fetch.t	Mon Jun 16 18:16:46 2025 -0700
+++ b/nginx/t/js_fetch.t	Mon Jun 16 19:36:35 2025 -0700
@@ -64,6 +64,10 @@
             js_content test.body;
         }
 
+        location /body_content_length {
+            js_content test.body_content_length;
+        }
+
         location /body_special {
             js_content test.body_special;
         }
@@ -156,6 +160,13 @@
         .catch(e => r.return(501, e.message))
     }
 
+    async function body_content_length(r) {
+        let resp = await ngx.fetch(`http://127.0.0.1:$p0/loc`,
+                                   {headers: {'Content-Length': '100'},
+                                    body: "CONTENT-BODY"});
+        r.return(resp.status);
+    }
+
     function property(r) {
         var opts = {headers:{}};
 
@@ -400,12 +411,12 @@
 
      export default {njs: test_njs, body, broken, broken_response, body_special,
                      chain, chunked_ok, chunked_fail, header, header_iter,
-                     host_header, multi, loc, property};
+                     host_header, multi, loc, property, body_content_length };
 EOF
 
 $t->try_run('no njs.fetch');
 
-$t->plan(37);
+$t->plan(38);
 
 $t->run_daemon(\&http_daemon, port(8082));
 $t->waitforsocket('127.0.0.1:' . port(8082));
@@ -508,6 +519,14 @@
 	qr/200 OK.*<empty>$/s, 'fetch head method large content-length');
 }
 
+TODO: {
+local $TODO = 'not yet' unless has_version('0.9.1');
+
+like(http_get('/body_content_length'), qr/200 OK/s,
+	'fetch body content-length');
+
+}
+
 ###############################################################################
 
 sub has_version {