From e5ccbc76212062bc925365e90954e091a75205b3 Mon Sep 17 00:00:00 2001 From: Amos Jeffries Date: Fri, 3 Oct 2025 15:06:46 +1300 Subject: [PATCH 1/4] HTTP: Support templated TRACE responses HTTP specification for the TRACE method states that Squid should reflect the HTTP Request message (sans content and sensitive details). This change allows admin to supply a custom template file that will be used instead to generate TRACE responses. --- src/client_side_reply.cc | 33 ++++++++++++++++++++++++++++----- src/error/forward.h | 5 +++++ 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/client_side_reply.cc b/src/client_side_reply.cc index f031e8e584f..346d03e48cb 100644 --- a/src/client_side_reply.cc +++ b/src/client_side_reply.cc @@ -1005,11 +1005,34 @@ clientReplyContext::traceReply() triggerInitialStoreRead(); http->storeEntry()->releaseRequest(); http->storeEntry()->buffer(); - const HttpReplyPointer rep(new HttpReply); - rep->setHeaders(Http::scOkay, nullptr, "text/plain", http->request->prefixLen(), 0, squid_curtime); - http->storeEntry()->replaceHttpReply(rep); - http->request->swapOut(http->storeEntry()); - http->storeEntry()->complete(); + + auto *entry = http->storeEntry(); + + ErrorState err(HTTP_TRACE_REPLY, Http::scOkay, http->request, http->al); + err.url = xstrdup(entry->url()); + auto *rep = err.BuildHttpReply(); + // when there is no admin provided HTTP_TRACE_REPLY template + if (strncmp(rep->body.content(),"Internal Error:", 15) == 0) { + /** + * RFC 9110 section 9.3.8: + * The final recipient of the request SHOULD reflect the message received, + * back to the client as the content of a 200 (OK) response. + * + * The final recipient of the request SHOULD exclude any request fields + * that are likely to contain sensitive data when that recipient generates + * the response content. + */ + MemBuf content; + content.init(); + http->request->pack(&content, true /* hide authorization data */); + rep->body.set(SBuf(content.buf, content.size)); + rep->header.delById(Http::HdrType::CONTENT_LENGTH); + rep->header.putInt64(Http::HdrType::CONTENT_LENGTH, content.size); + rep->content_length = content.size; + } + + entry->replaceHttpReply(rep); + entry->completeSuccessfully("TRACE response is atomic"); } #define SENDING_BODY 0 diff --git a/src/error/forward.h b/src/error/forward.h index d341fbc33c2..08f7680d61e 100644 --- a/src/error/forward.h +++ b/src/error/forward.h @@ -82,6 +82,11 @@ typedef enum { ERR_REQUEST_PARSE_TIMEOUT, // Aborts the connection instead of error page ERR_RELAY_REMOTE, // Sends server reply instead of error page + // Reply message for TRACE requests is optional. + // HTTP specification default response (equivalent to '%R') + // will be generated if no template is found. + HTTP_TRACE_REPLY, + /* Cache Manager GUI can install a manager index/home page */ MGR_INDEX, From e18795e8711a16b472eefdf9a5ab91cd17e325e5 Mon Sep 17 00:00:00 2001 From: Amos Jeffries Date: Sun, 5 Oct 2025 20:27:47 +1300 Subject: [PATCH 2/4] Update after review --- src/client_side_reply.cc | 28 ++-------------------------- src/error/forward.h | 4 +--- src/errorpage.cc | 29 +++++++++++++++++++++++++++-- src/errorpage.h | 3 +++ 4 files changed, 33 insertions(+), 31 deletions(-) diff --git a/src/client_side_reply.cc b/src/client_side_reply.cc index 346d03e48cb..dcaa0dec5c7 100644 --- a/src/client_side_reply.cc +++ b/src/client_side_reply.cc @@ -1006,33 +1006,9 @@ clientReplyContext::traceReply() http->storeEntry()->releaseRequest(); http->storeEntry()->buffer(); - auto *entry = http->storeEntry(); - ErrorState err(HTTP_TRACE_REPLY, Http::scOkay, http->request, http->al); - err.url = xstrdup(entry->url()); - auto *rep = err.BuildHttpReply(); - // when there is no admin provided HTTP_TRACE_REPLY template - if (strncmp(rep->body.content(),"Internal Error:", 15) == 0) { - /** - * RFC 9110 section 9.3.8: - * The final recipient of the request SHOULD reflect the message received, - * back to the client as the content of a 200 (OK) response. - * - * The final recipient of the request SHOULD exclude any request fields - * that are likely to contain sensitive data when that recipient generates - * the response content. - */ - MemBuf content; - content.init(); - http->request->pack(&content, true /* hide authorization data */); - rep->body.set(SBuf(content.buf, content.size)); - rep->header.delById(Http::HdrType::CONTENT_LENGTH); - rep->header.putInt64(Http::HdrType::CONTENT_LENGTH, content.size); - rep->content_length = content.size; - } - - entry->replaceHttpReply(rep); - entry->completeSuccessfully("TRACE response is atomic"); + http->storeEntry()->replaceHttpReply(err.BuildHttpReply()); + http->storeEntry()->completeSuccessfully("traceReply() stored the entire response"); } #define SENDING_BODY 0 diff --git a/src/error/forward.h b/src/error/forward.h index 08f7680d61e..4eaf96e1393 100644 --- a/src/error/forward.h +++ b/src/error/forward.h @@ -82,9 +82,7 @@ typedef enum { ERR_REQUEST_PARSE_TIMEOUT, // Aborts the connection instead of error page ERR_RELAY_REMOTE, // Sends server reply instead of error page - // Reply message for TRACE requests is optional. - // HTTP specification default response (equivalent to '%R') - // will be generated if no template is found. + /* TRACE Response is optional, but SHOULD be equivalent to '%R\n' if sent */ HTTP_TRACE_REPLY, /* Cache Manager GUI can install a manager index/home page */ diff --git a/src/errorpage.cc b/src/errorpage.cc index 4634c83ec2f..d3684b872c1 100644 --- a/src/errorpage.cc +++ b/src/errorpage.cc @@ -381,12 +381,17 @@ TemplateFile::loadDefault() } #endif - /* test default location if failed (templates == English translation base templates) */ + /** test default templates if failed (templates == English translation base templates) */ if (!loaded()) { tryLoadTemplate("templates"); } - /* giving up if failed */ + /** test for any 'soft-coded' template available as default-if-none. */ + if (!loaded()) { + tryInternalDefault(); + } + + /** giving up if failed. Will result in "Internal Error" production. */ if (!loaded()) { debugs(1, (templateCode < TCP_RESET ? DBG_CRITICAL : 3), "WARNING: failed to find or read error text file " << templateName); template_.clear(); @@ -420,6 +425,26 @@ TemplateFile::tryLoadTemplate(const char *lang) return false; } +void +TemplateFile::tryInternalDefault() +{ + if (loaded()) + return; // admin has provided a template. + + // Default templates if no file is found. + static const std::list> SoftCodedErrors = { + { HTTP_TRACE_REPLY, SBuf("%R\n") } + }; + + for (const auto &m: SoftCodedErrors) { + if (m.first == templateCode) { + template_ = m.second; + wasLoaded = parse(); + return; + } + } +} + bool TemplateFile::loadFromFile(const char *path) { diff --git a/src/errorpage.h b/src/errorpage.h index abca4a17d7b..7f80519e01b 100644 --- a/src/errorpage.h +++ b/src/errorpage.h @@ -330,6 +330,9 @@ class TemplateFile */ bool tryLoadTemplate(const char *lang); + /// Try to load a "soft-coded" built-in template, if none is configured. + void tryInternalDefault(); + SBuf template_; ///< raw template contents bool wasLoaded; ///< True if the template data read from disk without any problem String errLanguage; ///< The error language of the template. From 7bc145d980bda5e578745d3a1caac5c7d1e68aa0 Mon Sep 17 00:00:00 2001 From: Amos Jeffries Date: Sun, 5 Oct 2025 20:40:18 +1300 Subject: [PATCH 3/4] update documentation --- src/errorpage.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/errorpage.h b/src/errorpage.h index 7f80519e01b..ce462d2b9da 100644 --- a/src/errorpage.h +++ b/src/errorpage.h @@ -330,7 +330,7 @@ class TemplateFile */ bool tryLoadTemplate(const char *lang); - /// Try to load a "soft-coded" built-in template, if none is configured. + /// Try to load a built-in template, if none is configured. void tryInternalDefault(); SBuf template_; ///< raw template contents From f799c8c103bbea78b00ec9a8df0e75926d819e4a Mon Sep 17 00:00:00 2001 From: Amos Jeffries Date: Sun, 5 Oct 2025 22:40:31 +1300 Subject: [PATCH 4/4] Send expiry and caching controls on TRACE reply --- src/client_side_reply.cc | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/client_side_reply.cc b/src/client_side_reply.cc index dcaa0dec5c7..dc942cc3dea 100644 --- a/src/client_side_reply.cc +++ b/src/client_side_reply.cc @@ -23,6 +23,7 @@ #include "globals.h" #include "HeaderMangling.h" #include "http/Stream.h" +#include "HttpHdrCc.h" #include "HttpHeaderTools.h" #include "HttpReply.h" #include "HttpRequest.h" @@ -1007,7 +1008,17 @@ clientReplyContext::traceReply() http->storeEntry()->buffer(); ErrorState err(HTTP_TRACE_REPLY, Http::scOkay, http->request, http->al); - http->storeEntry()->replaceHttpReply(err.BuildHttpReply()); + auto rep = err.BuildHttpReply(); + // RFC 9110 specifies that TRACE response is not cacheable + // Reinforce that with explicit caching controls + HttpHdrCc cc; + cc.noStore(true); + cc.mustRevalidate(true); + cc.maxAge(0); + rep->putCc(cc); + // Reinforce that with explicit Expires for old HTTP/1.0 agents + rep->header.putTime(Http::HdrType::EXPIRES, squid_curtime); + http->storeEntry()->replaceHttpReply(rep); http->storeEntry()->completeSuccessfully("traceReply() stored the entire response"); }