diff --git a/plugins/authproxy/authproxy.cc b/plugins/authproxy/authproxy.cc index 9edb1af3f3f..3d697241a48 100644 --- a/plugins/authproxy/authproxy.cc +++ b/plugins/authproxy/authproxy.cc @@ -296,6 +296,16 @@ AuthWriteHeadRequest(AuthRequestContext *auth) // Next, we need to rewrite the client request URL to be a HEAD request. TSReleaseAssert(TSHttpHdrMethodSet(rq.buffer, rq.header, TS_HTTP_METHOD_HEAD, -1) == TS_SUCCESS); + // This sub-request is bodyless (HEAD + Content-Length: 0), but the copied + // client request may carry request-body framing (e.g. a chunked POST or + // Expect: 100-continue). Left in place it is self-contradictory: ATS sets up + // a request-body tunnel for a body that never arrives (stalling the probe + // until timeout), and proxy.config.http.reject_head_with_content rejects a + // HEAD that declares content. Strip the framing when forcing Content-Length: 0. + HttpRemoveMimeHeader(rq.buffer, rq.header, TS_MIME_FIELD_TRANSFER_ENCODING); + HttpRemoveMimeHeader(rq.buffer, rq.header, "Trailer"); + HttpRemoveMimeHeader(rq.buffer, rq.header, TS_MIME_FIELD_EXPECT); + HttpSetMimeHeader(rq.buffer, rq.header, TS_MIME_FIELD_CONTENT_LENGTH, 0u); HttpSetMimeHeader(rq.buffer, rq.header, TS_MIME_FIELD_CACHE_CONTROL, "no-cache"); @@ -333,6 +343,13 @@ AuthWriteRangeRequest(AuthRequestContext *auth) TSReleaseAssert(TSHttpHdrMethodSet(rq.buffer, rq.header, TS_HTTP_METHOD_GET, -1) == TS_SUCCESS); } + // The body is dropped (Content-Length: 0), so strip any request-body framing + // (Transfer-Encoding/Trailer/Expect) copied from the client request to keep + // the sub-request well-formed. + HttpRemoveMimeHeader(rq.buffer, rq.header, TS_MIME_FIELD_TRANSFER_ENCODING); + HttpRemoveMimeHeader(rq.buffer, rq.header, "Trailer"); + HttpRemoveMimeHeader(rq.buffer, rq.header, TS_MIME_FIELD_EXPECT); + HttpSetMimeHeader(rq.buffer, rq.header, TS_MIME_FIELD_CONTENT_LENGTH, 0u); HttpSetMimeHeader(rq.buffer, rq.header, TS_MIME_FIELD_RANGE, "bytes=0-0"); HttpSetMimeHeader(rq.buffer, rq.header, TS_MIME_FIELD_CACHE_CONTROL, "no-cache"); @@ -386,6 +403,14 @@ AuthWriteRedirectedRequest(AuthRequestContext *auth) TSHandleMLocRelease(rq.buffer, rq.header, murl); HttpSetMimeHeader(rq.buffer, rq.header, TS_MIME_FIELD_HOST, hostbuf); + + // The body is dropped (Content-Length: 0), so strip any request-body framing + // (Transfer-Encoding/Trailer/Expect) copied from the client request to keep + // the sub-request well-formed. + HttpRemoveMimeHeader(rq.buffer, rq.header, TS_MIME_FIELD_TRANSFER_ENCODING); + HttpRemoveMimeHeader(rq.buffer, rq.header, "Trailer"); + HttpRemoveMimeHeader(rq.buffer, rq.header, TS_MIME_FIELD_EXPECT); + HttpSetMimeHeader(rq.buffer, rq.header, TS_MIME_FIELD_CONTENT_LENGTH, 0u); HttpSetMimeHeader(rq.buffer, rq.header, TS_MIME_FIELD_CACHE_CONTROL, "no-cache"); diff --git a/plugins/authproxy/utils.cc b/plugins/authproxy/utils.cc index 15732089070..ce55d40c26a 100644 --- a/plugins/authproxy/utils.cc +++ b/plugins/authproxy/utils.cc @@ -101,6 +101,18 @@ HttpSetMimeHeader(TSMBuffer mbuf, TSMLoc mhdr, const std::string_view name, cons TSHandleMLocRelease(mbuf, mhdr, mloc); } +void +HttpRemoveMimeHeader(TSMBuffer mbuf, TSMLoc mhdr, const char *name) +{ + TSMLoc mloc; + + // A field may be repeated on multiple lines; destroy every instance. + while ((mloc = TSMimeHdrFieldFind(mbuf, mhdr, name, -1)) != TS_NULL_MLOC) { + TSReleaseAssert(TSMimeHdrFieldDestroy(mbuf, mhdr, mloc) == TS_SUCCESS); + TSHandleMLocRelease(mbuf, mhdr, mloc); + } +} + unsigned HttpGetContentLength(TSMBuffer mbuf, TSMLoc mhdr) { diff --git a/plugins/authproxy/utils.h b/plugins/authproxy/utils.h index a36a0a90bd9..5291baa88c8 100644 --- a/plugins/authproxy/utils.h +++ b/plugins/authproxy/utils.h @@ -108,6 +108,9 @@ void HttpSetMimeHeader(TSMBuffer mbuf, TSMLoc mhdr, const char *name, const char void HttpSetMimeHeader(TSMBuffer mbuf, TSMLoc mhdr, const char *name, unsigned value); void HttpSetMimeHeader(TSMBuffer mbuf, TSMLoc mhdr, const std::string_view name, const std::string_view value); +// Remove every instance of an HTTP header. +void HttpRemoveMimeHeader(TSMBuffer mbuf, TSMLoc mhdr, const char *name); + // Dump the given HTTP header to the debug log. void HttpDebugHeader(TSMBuffer mbuf, TSMLoc mhdr);