From 968022ebc5076fce2944e6555fb97040ac045544 Mon Sep 17 00:00:00 2001 From: Joe Orton Date: Fri, 9 Oct 2026 19:37:05 +0000 Subject: [PATCH] * modules/filters/mod_sed.c (sed_request_filter): Fix handling of the Content-Length request header; unset it after reading from the next filter. * test/pyhttpd/mod_aptest/mod_aptest.c (aptest_getline_echo): Buffer the body, and return the Content-Length request header left after reading it in an AP-Test-Content-Length response header. * test/modules/filters/test_003_sed.py (test_filters_003_34): New test. Co-Authored-By: Claude Opus 5.5 GitHub: PR #791 git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1938964 13f79535-47bb-0310-9956-ffa450edef68 --- modules/filters/mod_sed.c | 5 +++++ test/modules/filters/test_003_sed.py | 21 +++++++++++++++++++ test/pyhttpd/mod_aptest/mod_aptest.c | 30 ++++++++++++++++++++-------- 3 files changed, 48 insertions(+), 8 deletions(-) diff --git a/modules/filters/mod_sed.c b/modules/filters/mod_sed.c index 9a7b884ca5..d244bc201a 100644 --- a/modules/filters/mod_sed.c +++ b/modules/filters/mod_sed.c @@ -516,6 +516,11 @@ static apr_status_t sed_request_filter(ap_filter_t *f, if (status != APR_SUCCESS) { return status; } + /* The body passed on is not the length the request gave. Only + * drop the header after this read: HTTP_IN takes the length from + * it on the first. + */ + apr_table_unset(f->r->headers_in, "Content-Length"); for (b = APR_BRIGADE_FIRST(bbinp); b != APR_BRIGADE_SENTINEL(bbinp); b = APR_BUCKET_NEXT(b)) { const char *buf = NULL; diff --git a/test/modules/filters/test_003_sed.py b/test/modules/filters/test_003_sed.py index babf550df3..a02804ff52 100644 --- a/test/modules/filters/test_003_sed.py +++ b/test/modules/filters/test_003_sed.py @@ -386,6 +386,27 @@ class TestSed: assert r.response["status"] == 200 assert r.response["body"] == b"one MON two\nMON MON\n" + # The request Content-Length is the length of the body before it was + # filtered, so once mod_sed has started reading the body it is removed: + # a handler must not find the original length. + def test_filters_003_34(self, env): + self.configure(env, "s/monday/MONDAYMONDAY/g", input_sed=True, + extra=""" + + SetHandler aptest-getline-echo + SetInputFilter SED + + """) + data = "one monday two\nmonday monday\n" + r = env.curl_post_data(env.mkurl("http", "test1", "/getline"), + data=data) + assert r.response, "no response" + assert r.response["status"] == 200 + assert r.response["body"] == \ + data.replace("monday", "MONDAYMONDAY").encode() + assert "ap-test-content-length" not in r.response["header"], \ + r.response["header"] + # --- interval expressions --------------------------------------------- # \{m,n\} repeats a character or a bracket expression. diff --git a/test/pyhttpd/mod_aptest/mod_aptest.c b/test/pyhttpd/mod_aptest/mod_aptest.c index 6c12fb6a40..2294dfe5d6 100644 --- a/test/pyhttpd/mod_aptest/mod_aptest.c +++ b/test/pyhttpd/mod_aptest/mod_aptest.c @@ -76,11 +76,16 @@ static int aptest_allow_methods(request_rec *r) /* * Handler "aptest-getline-echo": echo the request body back, reading it - * with AP_MODE_GETLINE rather than AP_MODE_READBYTES. + * with AP_MODE_GETLINE rather than AP_MODE_READBYTES. The Content-Length + * request header left once the body has been read, if any, is returned in + * an "AP-Test-Content-Length" response header. */ static int aptest_getline_echo(request_rec *r) { - apr_bucket_brigade *bb; + apr_bucket_brigade *bb, *body; + const char *clen; + char *data; + apr_size_t len; apr_status_t rv; int seen_eos = 0; @@ -88,12 +93,9 @@ static int aptest_getline_echo(request_rec *r) return DECLINED; } - ap_set_content_type(r, "text/plain"); bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); + body = apr_brigade_create(r->pool, r->connection->bucket_alloc); while (!seen_eos) { - char *line; - apr_size_t len; - rv = ap_get_brigade(r->input_filters, bb, AP_MODE_GETLINE, APR_BLOCK_READ, HUGE_STRING_LEN); if (rv != APR_SUCCESS) { @@ -105,14 +107,26 @@ static int aptest_getline_echo(request_rec *r) && APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb))) { seen_eos = 1; } - rv = apr_brigade_pflatten(bb, &line, &len, r->pool); + rv = apr_brigade_pflatten(bb, &data, &len, r->pool); + if (rv == APR_SUCCESS) { + rv = apr_brigade_write(body, NULL, NULL, data, len); + } if (rv != APR_SUCCESS) { return HTTP_INTERNAL_SERVER_ERROR; } - ap_rwrite(line, len, r); apr_brigade_cleanup(bb); } + clen = apr_table_get(r->headers_in, "Content-Length"); + if (clen) { + apr_table_setn(r->headers_out, "AP-Test-Content-Length", clen); + } + ap_set_content_type(r, "text/plain"); + if (apr_brigade_pflatten(body, &data, &len, r->pool) != APR_SUCCESS) { + return HTTP_INTERNAL_SERVER_ERROR; + } + ap_rwrite(data, len, r); + return OK; }