Squid Web Cache master
Loading...
Searching...
No Matches
HeaderMangling.cc
Go to the documentation of this file.
1/*
2 * Copyright (C) 1996-2025 The Squid Software Foundation and contributors
3 *
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
7 */
8
9/* DEBUG: section 66 HTTP Header Tools */
10
11#include "squid.h"
12#include "acl/FilledChecklist.h"
13#include "acl/Gadgets.h"
14#include "base/EnumIterator.h"
15#include "fde.h"
16#include "globals.h"
18#include "HttpHeader.h"
19#include "HttpRequest.h"
20#include "MemBuf.h"
21#include "sbuf/Stream.h"
22#include "sbuf/StringConvert.h"
23#include "SquidConfig.h"
24#include "Store.h"
25
26static void httpHdrAdd(HttpHeader *heads, HttpRequest *request, const AccessLogEntryPointer &al, HeaderWithAclList &headersAdd);
27
36static int
38{
39 int retval;
40
41 assert(e);
42
43 const headerMangler *hm = hms->find(*e);
44
45 /* mangler or checklist went away. default allow */
46 if (!hm || !hm->access_list) {
47 debugs(66, 7, "couldn't find mangler or access list. Allowing");
48 return 1;
49 }
50
51 ACLFilledChecklist checklist(hm->access_list, request);
52 checklist.updateAle(al);
53
54 // XXX: The two "It was denied" clauses below mishandle cases with no
55 // matching rules, violating the "If no rules within the set have matching
56 // ACLs, the header field is left as is" promise in squid.conf.
57 // TODO: Use Acl::Answer::implicit. See HttpStateData::forwardUpgrade().
58 if (checklist.fastCheck().allowed()) {
59 /* aclCheckFast returns true for allow. */
60 debugs(66, 7, "checklist for mangler is positive. Mangle");
61 retval = 1;
62 } else if (nullptr == hm->replacement) {
63 /* It was denied, and we don't have any replacement */
64 debugs(66, 7, "checklist denied, we have no replacement. Pass");
65 // XXX: We said "Pass", but the caller will delete on zero retval.
66 retval = 0;
67 } else {
68 /* It was denied, but we have a replacement. Replace the
69 * header on the fly, and return that the new header
70 * is allowed.
71 */
72 debugs(66, 7, "checklist denied but we have replacement. Replace");
73 e->value = hm->replacement;
74 retval = 1;
75 }
76
77 return retval;
78}
79
81void
83{
86
87 /* check with anonymizer tables */
88 HeaderManglers *hms = nullptr;
89 HeaderWithAclList *headersAdd = nullptr;
90
91 switch (req_or_rep) {
92 case ROR_REQUEST:
94 headersAdd = Config.request_header_add;
95 break;
96 case ROR_REPLY:
98 headersAdd = Config.reply_header_add;
99 break;
100 }
101
102 if (hms) {
103 int headers_deleted = 0;
104 while ((e = l->getEntry(&p))) {
105 if (httpHdrMangle(e, request, hms, al) == 0)
106 l->delAt(p, headers_deleted);
107 }
108
109 if (headers_deleted)
110 l->refreshMask();
111 }
112
113 if (headersAdd && !headersAdd->empty()) {
114 httpHdrAdd(l, request, al, *headersAdd);
115 }
116}
117
118static
124
125static
126void header_mangler_dump_access(StoreEntry * entry, const char *option,
127 const headerMangler &m, const char *name)
128{
129 if (m.access_list != nullptr) {
130 storeAppendPrintf(entry, "%s ", option);
131 dump_acl_access(entry, name, m.access_list);
132 }
133}
134
135static
136void header_mangler_dump_replacement(StoreEntry * entry, const char *option,
137 const headerMangler &m, const char *name)
138{
139 if (m.replacement)
140 storeAppendPrintf(entry, "%s %s %s\n", option, name, m.replacement);
141}
142
144{
145 memset(known, 0, sizeof(known));
146 memset(&all, 0, sizeof(all));
147}
148
150{
151 for (auto i : WholeEnum<Http::HdrType>())
153
154 for (auto i : custom)
155 header_mangler_clean(i.second);
156
158}
159
160void
161HeaderManglers::dumpAccess(StoreEntry * entry, const char *name) const
162{
163 for (auto id : WholeEnum<Http::HdrType>())
164 header_mangler_dump_access(entry, name, known[id], Http::HeaderLookupTable.lookup(id).name);
165
166 for (auto i : custom)
167 header_mangler_dump_access(entry, name, i.second, i.first.c_str());
168
169 header_mangler_dump_access(entry, name, all, "All");
170}
171
172void
173HeaderManglers::dumpReplacement(StoreEntry * entry, const char *name) const
174{
175 for (auto id : WholeEnum<Http::HdrType>()) {
176 header_mangler_dump_replacement(entry, name, known[id], Http::HeaderLookupTable.lookup(id).name);
177 }
178
179 for (auto i: custom) {
180 header_mangler_dump_replacement(entry, name, i.second, i.first.c_str());
181 }
182
183 header_mangler_dump_replacement(entry, name, all, "All");
184}
185
187HeaderManglers::track(const char *name)
188{
189 if (strcmp(name, "All") == 0)
190 return &all;
191
193
194 if (id != Http::HdrType::BAD_HDR)
195 return &known[id];
196
197 if (strcmp(name, "Other") == 0)
199
200 return &custom[name];
201}
202
203void
204HeaderManglers::setReplacement(const char *name, const char *value)
205{
206 // for backword compatibility, we allow replacements to be configured
207 // for headers w/o access rules, but such replacements are ignored
208 headerMangler *m = track(name);
209
210 safe_free(m->replacement); // overwrite old value if any
211 m->replacement = xstrdup(value);
212}
213
214const headerMangler *
216{
217 // a known header with a configured ACL list
220 return &known[e.id];
221
222 // a custom header
223 if (e.id == Http::HdrType::OTHER) {
224 // does it have an ACL list configured?
225 // Optimize: use a name type that we do not need to convert to here
226 SBuf tmp(e.name); // XXX: performance regression. c_str() reallocates
227 const ManglersByName::const_iterator i = custom.find(tmp.c_str());
228 if (i != custom.end())
229 return &i->second;
230 }
231
232 // Next-to-last resort: "Other" rules match any custom header
235
236 // Last resort: "All" rules match any header
237 if (all.access_list)
238 return &all;
239
240 return nullptr;
241}
242
243void
245{
246 ACLFilledChecklist checklist(nullptr, request);
247 checklist.updateAle(al);
248
249 for (HeaderWithAclList::const_iterator hwa = headersAdd.begin(); hwa != headersAdd.end(); ++hwa) {
250 if (!hwa->aclList || checklist.fastCheck(hwa->aclList).allowed()) {
251 const char *fieldValue = nullptr;
252 MemBuf mb;
253 if (hwa->quoted) {
254 if (al != nullptr) {
255 mb.init();
256 hwa->valueFormat->assemble(mb, al, 0);
257 fieldValue = mb.content();
258 }
259 } else {
260 fieldValue = hwa->fieldValue.c_str();
261 }
262
263 if (!fieldValue || fieldValue[0] == '\0')
264 fieldValue = "-";
265
266 HttpHeaderEntry *e = new HttpHeaderEntry(hwa->fieldId, SBuf(hwa->fieldName), fieldValue);
267 heads->addEntry(e);
268 }
269 }
270}
271
static int httpHdrMangle(HttpHeaderEntry *e, HttpRequest *request, HeaderManglers *hms, const AccessLogEntryPointer &al)
static void header_mangler_dump_replacement(StoreEntry *entry, const char *option, const headerMangler &m, const char *name)
static void header_mangler_clean(headerMangler &m)
static void header_mangler_dump_access(StoreEntry *entry, const char *option, const headerMangler &m, const char *name)
void httpHdrMangleList(HttpHeader *l, HttpRequest *request, const AccessLogEntryPointer &al, req_or_rep_t req_or_rep)
static void httpHdrAdd(HttpHeader *heads, HttpRequest *request, const AccessLogEntryPointer &al, HeaderWithAclList &headersAdd)
req_or_rep_t
@ ROR_REPLY
@ ROR_REQUEST
std::list< HeaderWithAcl > HeaderWithAclList
ssize_t HttpHeaderPos
Definition HttpHeader.h:45
#define HttpHeaderInitPos
Definition HttpHeader.h:48
class SquidConfig Config
#define assert(EX)
Definition assert.h:17
Acl::Answer const & fastCheck()
Definition Checklist.cc:298
void updateAle(const AccessLogEntry::Pointer &)
bool allowed() const
Definition Acl.h:82
A collection of headerMangler objects for a given message kind.
void dumpAccess(StoreEntry *entry, const char *optionName) const
report the *_header_access part of the configuration
const headerMangler * find(const HttpHeaderEntry &e) const
returns a header mangler for field e or nil if none was specified
void setReplacement(const char *name, const char *replacementValue)
updates mangler for the named header with a replacement value
ManglersByName custom
one mangler for each custom header
headerMangler all
configured if some mangling ACL applies to all header names
headerMangler * track(const char *name)
returns a mangler for the named header (known or custom)
void dumpReplacement(StoreEntry *entry, const char *optionName) const
report the *_header_replace part of the configuration
headerMangler known[static_cast< int >(Http::HdrType::enumEnd_)]
one mangler for each known header
Http::HdrType id
Definition HttpHeader.h:66
void delAt(HttpHeaderPos pos, int &headers_deleted)
void refreshMask()
HttpHeaderEntry * getEntry(HttpHeaderPos *pos) const
void addEntry(HttpHeaderEntry *e)
const HeaderTableRecord & lookup(const char *buf, const std::size_t len) const
look record type up by name (C-string and length)
void init(mb_size_t szInit, mb_size_t szMax)
Definition MemBuf.cc:93
char * content()
start of the added data
Definition MemBuf.h:41
Definition SBuf.h:94
const char * c_str()
Definition SBuf.cc:516
HeaderWithAclList * request_header_add
request_header_add access list
HeaderManglers * request_header_access
request_header_access and request_header_replace
HeaderManglers * reply_header_access
reply_header_access and reply_header_replace
HeaderWithAclList * reply_header_add
reply_header_add access list
acl_access * access_list
#define debugs(SECTION, LEVEL, CONTENT)
Definition Stream.h:192
void aclDestroyAccessList(acl_access **list)
Definition Gadgets.cc:223
void dump_acl_access(StoreEntry *entry, const char *name, acl_access *head)
Definition cache_cf.cc:1499
bool any_HdrType_enum_value(const Http::HdrType id)
match any known header type, including OTHER and BAD
const HeaderLookupTable_t HeaderLookupTable
#define xstrdup
void storeAppendPrintf(StoreEntry *e, const char *fmt,...)
Definition store.cc:855
#define safe_free(x)
Definition xalloc.h:73