This is an automated email from the ASF dual-hosted git repository.

bneradt pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new ef9e3ce967 Fix request buffering with post_copy_size=0 (#12702)
ef9e3ce967 is described below

commit ef9e3ce967a13c1dcbf43c2d03e865a9eec8b97d
Author: Brian Neradt <[email protected]>
AuthorDate: Tue Dec 2 11:11:11 2025 -0600

    Fix request buffering with post_copy_size=0 (#12702)
    
    When proxy.config.http.request_buffer_enabled is set to 1 with
    proxy.config.http.post_copy_size set to 0, POST requests were failing.
    The fix disables request buffering when post_copy_size is 0, treating it
    as an indication to not buffer POST data.
    
    Fixes: #6900
---
 src/proxy/http/HttpConfig.cc                       |  10 +-
 src/proxy/http/HttpTransact.cc                     |   2 +-
 .../pluginTest/test_hooks/body_buffer.test.py      | 119 +------------------
 .../test_hooks/replay/body_buffer.replay.yaml      | 123 ++++++++++++++++++++
 .../replay/zero_post_copy_size.replay.yaml         | 127 +++++++++++++++++++++
 5 files changed, 264 insertions(+), 117 deletions(-)

diff --git a/src/proxy/http/HttpConfig.cc b/src/proxy/http/HttpConfig.cc
index adead9915d..baac34ddf9 100644
--- a/src/proxy/http/HttpConfig.cc
+++ b/src/proxy/http/HttpConfig.cc
@@ -1345,9 +1345,13 @@ HttpConfig::reconfigure()
   params->redirection_host_no_port          = 
INT_TO_BOOL(m_master.redirection_host_no_port);
   params->oride.number_of_redirections      = 
m_master.oride.number_of_redirections;
   params->post_copy_size                    = m_master.post_copy_size;
-  params->redirect_actions_string           = 
ats_strdup(m_master.redirect_actions_string);
-  params->redirect_actions_map = 
parse_redirect_actions(params->redirect_actions_string, 
params->redirect_actions_self_action);
-  params->http_host_sni_policy = m_master.http_host_sni_policy;
+  if (params->oride.request_buffer_enabled && params->post_copy_size == 0) {
+    Warning("proxy.config.http.request_buffer_enabled is set but 
proxy.config.http.post_copy_size is 0; request buffering "
+            "will be disabled");
+  }
+  params->redirect_actions_string = 
ats_strdup(m_master.redirect_actions_string);
+  params->redirect_actions_map    = 
parse_redirect_actions(params->redirect_actions_string, 
params->redirect_actions_self_action);
+  params->http_host_sni_policy    = m_master.http_host_sni_policy;
   params->scheme_proto_mismatch_policy = m_master.scheme_proto_mismatch_policy;
 
   params->oride.ssl_client_sni_policy     = 
ats_strdup(m_master.oride.ssl_client_sni_policy);
diff --git a/src/proxy/http/HttpTransact.cc b/src/proxy/http/HttpTransact.cc
index 9ab7302eaa..a911d82c0c 100644
--- a/src/proxy/http/HttpTransact.cc
+++ b/src/proxy/http/HttpTransact.cc
@@ -1573,7 +1573,7 @@ HttpTransact::HandleRequest(State *s)
         }
       }
     }
-    if (s->txn_conf->request_buffer_enabled &&
+    if (s->txn_conf->request_buffer_enabled && 
s->http_config_param->post_copy_size > 0 &&
         
s->state_machine->get_ua_txn()->has_request_body(s->hdr_info.request_content_length,
                                                          
s->client_info.transfer_encoding == TransferEncoding_t::CHUNKED)) {
       TRANSACT_RETURN(StateMachineAction_t::WAIT_FOR_FULL_BODY, nullptr);
diff --git a/tests/gold_tests/pluginTest/test_hooks/body_buffer.test.py 
b/tests/gold_tests/pluginTest/test_hooks/body_buffer.test.py
index 07e1934341..16c966dd3d 100644
--- a/tests/gold_tests/pluginTest/test_hooks/body_buffer.test.py
+++ b/tests/gold_tests/pluginTest/test_hooks/body_buffer.test.py
@@ -1,5 +1,5 @@
 '''
-Verify HTTP body buffering.
+Verify HTTP body buffering with request_buffer.so plugin.
 '''
 #  Licensed to the Apache Software Foundation (ASF) under one
 #  or more contributor license agreements.  See the NOTICE file
@@ -17,118 +17,11 @@ Verify HTTP body buffering.
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
-Test.SkipUnless(Condition.PluginExists('request_buffer.so'))
-
-
-def int_to_hex_string(int_value):
-    '''
-    Convert the given int value to a hex string with no '0x' prefix.
-
-    >>> int_to_hex_string(0)
-    '0'
-    >>> int_to_hex_string(1)
-    '1'
-    >>> int_to_hex_string(10)
-    'a'
-    >>> int_to_hex_string(16)
-    '10'
-    >>> int_to_hex_string(17)
-    'f1'
-    '''
-    if not isinstance(int_value, int):
-        raise ValueError("Input should be an int type.")
-    return hex(int_value).split('x')[1]
-
-
-class BodyBufferTest:
+Test.Summary = 'Verify HTTP body buffering with request_buffer.so plugin.'
 
-    def __init__(cls, description):
-        Test.Summary = description
-        cls._origin_max_connections = 3
-        cls.setupOriginServer()
-        cls.setupTS()
-
-    def setupOriginServer(self):
-        self._server = Test.MakeOriginServer("server")
-        self.content_length_request_body = "content-length request"
-        self.content_length_size = len(self.content_length_request_body)
-        request_header = {
-            "headers":
-                "POST /contentlength HTTP/1.1\r\n"
-                "Host: www.example.com\r\n"
-                f"Content-Length: {self.content_length_size}\r\n\r\n",
-            "timestamp": "1469733493.993",
-            "body": self.content_length_request_body
-        }
-        content_length_response_body = "content-length response"
-        content_length_response_size = len(content_length_response_body)
-        response_header = {
-            "headers":
-                "HTTP/1.1 200 OK\r\n"
-                "Server: microserver\r\n"
-                f"Content-Length: {content_length_response_size}\r\n\r\n"
-                "Connection: close\r\n\r\n",
-            "timestamp": "1469733493.993",
-            "body": content_length_response_body
-        }
-        self._server.addResponse("sessionlog.json", request_header, 
response_header)
-
-        self.chunked_request_body = "chunked request"
-        hex_size = int_to_hex_string(len(self.chunked_request_body))
-        self.encoded_chunked_request = 
f"{hex_size}\r\n{self.chunked_request_body}\r\n0\r\n\r\n"
-        self.encoded_chunked_size = len(self.content_length_request_body)
-        request_header2 = {
-            "headers":
-                "POST /chunked HTTP/1.1\r\n"
-                "Transfer-Encoding: chunked\r\n"
-                "Host: www.example.com\r\n"
-                "Connection: keep-alive\r\n\r\n",
-            "timestamp": "1469733493.993",
-            "body": self.encoded_chunked_request
-        }
-        self.chunked_response_body = "chunked response"
-        hex_size = int_to_hex_string(len(self.chunked_response_body))
-        self.encoded_chunked_response = 
f"{hex_size}\r\n{self.chunked_response_body}\r\n0\r\n\r\n"
-        response_header2 = {
-            "headers": "HTTP/1.1 200 OK\r\n"
-                       "Transfer-Encoding: chunked\r\n"
-                       "Server: microserver\r\n"
-                       "Connection: close\r\n\r\n",
-            "timestamp": "1469733493.993",
-            "body": self.encoded_chunked_response
-        }
-        self._server.addResponse("sessionlog.json", request_header2, 
response_header2)
-
-    def setupTS(self):
-        self._ts = Test.MakeATSProcess("ts", select_ports=False)
-        self._ts.Disk.remap_config.AddLine(f'map / 
http://127.0.0.1:{self._server.Variables.Port}')
-        Test.PrepareInstalledPlugin('request_buffer.so', self._ts)
-        self._ts.Disk.records_config.update(
-            {
-                'proxy.config.diags.debug.enabled': 1,
-                'proxy.config.diags.debug.tags': 'request_buffer',
-                'proxy.config.http.server_ports': str(self._ts.Variables.port) 
+ f" {self._ts.Variables.uds_path}",
-            })
-
-        self._ts.Disk.traffic_out.Content = Testers.ContainsExpression(
-            rf"request_buffer_plugin gets the request body with 
length\[{self.content_length_size}\]",
-            "Verify that the plugin parsed the content-length request body 
data.")
-        self._ts.Disk.traffic_out.Content += Testers.ContainsExpression(
-            rf"request_buffer_plugin gets the request body with 
length\[{self.encoded_chunked_size}\]",
-            "Verify that the plugin parsed the chunked request body.")
-
-    def run(self):
-        tr = Test.AddTestRun()
-        # Send both a Content-Length request and a chunked-encoded request.
-        tr.MakeCurlCommand(
-            f'-v http://127.0.0.1:{self._ts.Variables.port}/contentlength -d 
"{self.content_length_request_body}" --next '
-            f'-v http://127.0.0.1:{self._ts.Variables.port}/chunked -H 
"Transfer-Encoding: chunked" -d "{self.chunked_request_body}"',
-            ts=self._ts)
-        tr.Processes.Default.ReturnCode = 0
-        tr.Processes.Default.StartBefore(self._server)
-        tr.Processes.Default.StartBefore(Test.Processes.ts)
-        tr.Processes.Default.Streams.stderr = "200.gold"
+Test.SkipUnless(Condition.PluginExists('request_buffer.so'))
 
+Test.ATSReplayTest(replay_file="replay/body_buffer.replay.yaml")
 
-bodyBufferTest = BodyBufferTest("Test request body buffering.")
-bodyBufferTest.run()
+# Test for issue #6900: post_copy_size=0 with request_buffer_enabled should 
not cause 403.
+Test.ATSReplayTest(replay_file="replay/zero_post_copy_size.replay.yaml")
diff --git 
a/tests/gold_tests/pluginTest/test_hooks/replay/body_buffer.replay.yaml 
b/tests/gold_tests/pluginTest/test_hooks/replay/body_buffer.replay.yaml
new file mode 100644
index 0000000000..a629e2da98
--- /dev/null
+++ b/tests/gold_tests/pluginTest/test_hooks/replay/body_buffer.replay.yaml
@@ -0,0 +1,123 @@
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+#
+# Test request body buffering with request_buffer.so plugin.
+#
+meta:
+  version: "1.0"
+
+autest:
+  description: 'Verify request body buffering with request_buffer.so plugin'
+
+  server:
+    name: 'body-buffer-server'
+
+  client:
+    name: 'body-buffer-client'
+
+  ats:
+    name: 'ts-body-buffer'
+    process_config:
+      enable_cache: false
+
+    plugin_config:
+      - request_buffer.so
+
+    records_config:
+      proxy.config.diags.debug.enabled: 1
+      proxy.config.diags.debug.tags: 'request_buffer'
+
+    remap_config:
+      - from: /
+        to: http://127.0.0.1:{SERVER_HTTP_PORT}/
+
+    log_validation:
+      traffic_out:
+        contains:
+          - expression: "request_buffer_plugin gets the request body with 
length\\[22\\]"
+            description: "Verify that the plugin parsed the content-length 
request body data"
+          - expression: "request_buffer_plugin gets the request body with 
length\\[25\\]"
+            description: "Verify that the plugin parsed the chunked request 
body data"
+
+sessions:
+  - transactions:
+      # Content-Length POST request
+      - client-request:
+          method: "POST"
+          version: "1.1"
+          url: /contentlength
+          headers:
+            fields:
+              - [Host, www.example.com]
+              - [Content-Length, 22]
+              - [uuid, content-length-post]
+          content:
+            data: "content-length request"
+
+        proxy-request:
+          method: "POST"
+          url: /contentlength
+          content:
+            verify: { value: "content-length request", as: equal }
+
+        server-response:
+          status: 200
+          reason: OK
+          headers:
+            fields:
+              - [Content-Length, 23]
+          content:
+            data: "content-length response"
+
+        proxy-response:
+          status: 200
+          content:
+            verify: { value: "content-length response", as: equal }
+
+      # Chunked POST request
+      - client-request:
+          method: "POST"
+          version: "1.1"
+          url: /chunked
+          headers:
+            fields:
+              - [Host, www.example.com]
+              - [Transfer-Encoding, chunked]
+              - [uuid, chunked-post]
+          content:
+            data: "chunked request"
+
+        proxy-request:
+          method: "POST"
+          url: /chunked
+          content:
+            verify: { value: "f\r\nchunked request\r\n0\r\n\r\n", as: equal }
+
+        server-response:
+          status: 200
+          reason: OK
+          headers:
+            fields:
+              - [Transfer-Encoding, chunked]
+          content:
+            data: "chunked response"
+
+        proxy-response:
+          status: 200
+          content:
+            verify: { value: "10\r\nchunked response\r\n0\r\n\r\n", as: equal }
+
diff --git 
a/tests/gold_tests/pluginTest/test_hooks/replay/zero_post_copy_size.replay.yaml 
b/tests/gold_tests/pluginTest/test_hooks/replay/zero_post_copy_size.replay.yaml
new file mode 100644
index 0000000000..c34af71d87
--- /dev/null
+++ 
b/tests/gold_tests/pluginTest/test_hooks/replay/zero_post_copy_size.replay.yaml
@@ -0,0 +1,127 @@
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+#
+# Test for issue #6900: Verify that post_copy_size=0 with 
request_buffer_enabled=1
+# does not cause 403 errors. When post_copy_size is 0, request buffering 
should be
+# disabled and POST requests should proceed normally.
+#
+meta:
+  version: "1.0"
+
+autest:
+  description: 'Verify post_copy_size=0 with request_buffer_enabled does not 
cause 403 (issue #6900)'
+
+  server:
+    name: 'zero-copy-server'
+
+  client:
+    name: 'zero-copy-client'
+
+  ats:
+    name: 'ts-zero-copy'
+    process_config:
+      enable_cache: false
+
+    plugin_config:
+      - request_buffer.so
+
+    records_config:
+      proxy.config.diags.debug.enabled: 1
+      proxy.config.diags.debug.tags: 'http|request_buffer'
+      # The key configuration: post_copy_size=0 with request_buffer_enabled=1
+      # This should NOT cause 403 errors (issue #6900 fix)
+      proxy.config.http.post_copy_size: 0
+      proxy.config.http.request_buffer_enabled: 1
+
+    remap_config:
+      - from: /
+        to: http://127.0.0.1:{SERVER_HTTP_PORT}/
+
+    log_validation:
+      diags_log:
+        contains:
+          - expression: "request_buffer_enabled is set but 
proxy.config.http.post_copy_size is 0"
+            description: "Verify warning is logged at startup for 
misconfiguration"
+
+sessions:
+  - transactions:
+      # POST with a body - should return 200, not 403
+      - client-request:
+          method: "POST"
+          version: "1.1"
+          url: /post_small
+          headers:
+            fields:
+              - [Host, example.com]
+              - [Content-Length, 5]
+              - [uuid, zero-copy-small]
+          content:
+            data: "small"
+
+        proxy-request:
+          method: "POST"
+          url: /post_small
+          content:
+            verify: { value: "small", as: equal }
+
+        server-response:
+          status: 200
+          reason: OK
+          headers:
+            fields:
+              - [Content-Length, 2]
+          content:
+            data: OK
+
+        proxy-response:
+          status: 200
+          content:
+            verify: { value: "OK", as: equal }
+
+      # Same with a large body.
+      - client-request:
+          method: "POST"
+          version: "1.1"
+          url: /post_large
+          headers:
+            fields:
+              - [Host, example.com]
+              - [Content-Length, 10000]
+              - [uuid, zero-copy-large]
+
+        proxy-request:
+          method: "POST"
+          url: /post_large
+          headers:
+            fields:
+              - [Content-Length, {value: 10000, as: equal}]
+          content:
+            size: 10000
+
+        server-response:
+          status: 200
+          reason: OK
+          headers:
+            fields:
+              - [Content-Length, 2]
+          content:
+            data: OK
+
+        proxy-response:
+          status: 200
+          content:
+            verify: { value: "OK", as: equal }

Reply via email to