]> git.ipfire.org Git - thirdparty/squid.git/blob - src/Notes.cc
Source Format Enforcement (#763)
[thirdparty/squid.git] / src / Notes.cc
1 /*
2 * Copyright (C) 1996-2021 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 #include "squid.h"
10 #include "AccessLogEntry.h"
11 #include "acl/FilledChecklist.h"
12 #include "acl/Gadgets.h"
13 #include "client_side.h"
14 #include "ConfigParser.h"
15 #include "globals.h"
16 #include "http/Stream.h"
17 #include "HttpReply.h"
18 #include "HttpRequest.h"
19 #include "parser/Tokenizer.h"
20 #include "sbuf/Stream.h"
21 #include "sbuf/StringConvert.h"
22 #include "SquidConfig.h"
23 #include "Store.h"
24 #include "StrList.h"
25
26 #include <algorithm>
27 #include <string>
28
29 Note::Value::~Value()
30 {
31 aclDestroyAclList(&aclList);
32 delete valueFormat;
33 }
34
35 Note::Value::Value(const char *aVal, const bool quoted, const char *descr, const Method m)
36 : aclList(nullptr), valueFormat(nullptr), theValue(aVal), theMethod(m)
37 {
38 if (quoted) {
39 valueFormat = new Format::Format(descr ? descr : "Notes");
40 if (!valueFormat->parse(theValue.c_str())) {
41 delete valueFormat;
42 SBuf exceptionMsg;
43 exceptionMsg.Printf("failed to parse annotation value %s", theValue.c_str());
44 throw TexcHere(exceptionMsg.c_str());
45 }
46 }
47 }
48
49 const SBuf &
50 Note::Value::format(const AccessLogEntryPointer &al)
51 {
52 if (al && valueFormat) {
53 static MemBuf mb;
54 mb.reset();
55 valueFormat->assemble(mb, al, 0);
56 theFormattedValue.assign(mb.content());
57 return theFormattedValue;
58 }
59 return theValue;
60 }
61
62 Note::Value::Pointer
63 Note::addValue(const char *value, const bool quoted, const char *descr, const Value::Method m)
64 {
65 values.push_back(new Value(value, quoted, descr, m));
66 return values.back();
67 }
68
69 bool
70 Note::match(HttpRequest *request, HttpReply *reply, const AccessLogEntry::Pointer &al, SBuf &matched)
71 {
72 ACLFilledChecklist ch(nullptr, request, nullptr);
73 ch.al = al;
74 ch.reply = reply;
75 ch.syncAle(request, nullptr);
76 if (reply)
77 HTTPMSGLOCK(ch.reply);
78
79 for (const auto &v: values) {
80 assert(v->aclList);
81 const auto ret = ch.fastCheck(v->aclList);
82 debugs(93, 5, "Check for header name: " << theKey << ": " << v->value() <<
83 ", HttpRequest: " << request << " HttpReply: " << reply << " matched: " << ret);
84 if (ret.allowed()) {
85 matched = v->format(al);
86 return true;
87 }
88 }
89 matched.clear();
90 return false;
91 }
92
93 void
94 Note::updateNotePairs(NotePairs::Pointer pairs, const CharacterSet *delimiters, const AccessLogEntryPointer &al)
95 {
96 for (const auto &v: values) {
97 const SBuf &formatted = v->format(al);
98 if (!pairs->empty() && v->method() == Value::mhReplace)
99 pairs->remove(theKey);
100 if (delimiters)
101 pairs->addStrList(key(), formatted, *delimiters);
102 else
103 pairs->add(key(), formatted);
104 }
105 }
106
107 void
108 Note::dump(StoreEntry *entry, const char *k)
109 {
110 for (const auto &v: values) {
111 storeAppendPrintf(entry, "%s %.*s %s",
112 k, key().length(), key().rawContent(), ConfigParser::QuoteString(SBufToString(v->value())));
113 dump_acl_list(entry, v->aclList);
114 storeAppendPrintf(entry, "\n");
115 }
116 }
117
118 SBuf
119 Note::toString(const char *sep) const
120 {
121 SBuf result;
122 for (const auto &val: values)
123 result.appendf("%.*s: %.*s%s", key().length(), key().rawContent(),
124 val->value().length(), val->value().rawContent(), sep);
125 return result;
126 }
127
128 const Notes::Keys &
129 Notes::ReservedKeys()
130 {
131 // these keys are used for internal Squid-helper communication
132 static const char *names[] = {
133 "group",
134 "ha1",
135 "log",
136 "message",
137 "password",
138 "rewrite-url",
139 "status",
140 "tag",
141 "ttl",
142 "url",
143 "user"
144 };
145
146 static Keys keys(std::begin(names), std::end(names));
147 return keys;
148 }
149
150 Notes::Notes(const char *aDescr, const Keys *extraReservedKeys, bool allowFormatted):
151 descr(aDescr),
152 formattedValues(allowFormatted)
153 {
154 if (extraReservedKeys)
155 reservedKeys = *extraReservedKeys;
156 }
157
158 Note::Pointer
159 Notes::add(const SBuf &noteKey)
160 {
161 if (Note::Pointer p = find(noteKey))
162 return p;
163 notes.push_back(new Note(noteKey));
164 return notes.back();
165 }
166
167 Note::Pointer
168 Notes::find(const SBuf &noteKey)
169 {
170 for (const auto &n: notes)
171 if (n->key() == noteKey)
172 return n;
173 return nullptr;
174 }
175
176 void
177 Notes::banReservedKey(const SBuf &key, const Keys &banned) const
178 {
179 if (std::find(banned.begin(), banned.end(), key) != banned.end())
180 throw TextException(ToSBuf("cannot use a reserved ", descr, " name: ", key), Here());
181 }
182
183 void
184 Notes::validateKey(const SBuf &key) const
185 {
186 banReservedKey(key, ReservedKeys());
187 banReservedKey(key, reservedKeys);
188
189 // TODO: fix code duplication: the same set of specials is produced
190 // by isKeyNameChar().
191 static const CharacterSet allowedSpecials = CharacterSet::ALPHA +
192 CharacterSet::DIGIT + CharacterSet("specials", "-_");
193 const auto specialIndex = key.findFirstNotOf(allowedSpecials);
194 if (specialIndex != SBuf::npos) {
195 debugs(28, DBG_CRITICAL, "Warning: used special character '" <<
196 key[specialIndex] << "' within annotation name. " <<
197 "Future Squid versions will not support this.");
198 }
199 }
200
201 Note::Pointer
202 Notes::parse(ConfigParser &parser)
203 {
204 const char *tok = ConfigParser::NextToken();
205 if (!tok)
206 fatalf("FATAL: Missing note key");
207 SBuf key(tok);
208 validateKey(key);
209 ConfigParser::EnableMacros();
210 const char *val = ConfigParser::NextQuotedToken();
211 if (!val)
212 fatalf("FATAL: Missing note value");
213 ConfigParser::DisableMacros();
214 Note::Pointer note = add(key);
215 Note::Value::Pointer noteValue = note->addValue(val, formattedValues && ConfigParser::LastTokenWasQuoted(), descr);
216 key.append('=');
217 key.append(val);
218 aclParseAclList(parser, &noteValue->aclList, key.c_str());
219 return note;
220 }
221
222 void
223 Notes::parseKvPair() {
224 char *k, *v;
225 int parsedPairs = 0;
226 while (ConfigParser::NextKvPair(k, v)) {
227 int keyLen = strlen(k);
228 const Note::Value::Method method = (k[keyLen - 1] == '+') ? Note::Value::mhAppend : Note::Value::mhReplace;
229 if (method == Note::Value::mhAppend)
230 keyLen--;
231 else {
232 assert(method == Note::Value::mhReplace);
233 if (Note::Pointer oldNote = find(SBuf(k, keyLen)))
234 debugs(28, DBG_CRITICAL, "Warning: annotation configuration with key " << k <<
235 " already exists and will be overwritten");
236 }
237 SBuf key(k, keyLen);
238 validateKey(key);
239 Note::Pointer note = add(key);
240 (void)note->addValue(v, formattedValues && ConfigParser::LastTokenWasQuoted(), descr, method);
241 parsedPairs++;
242 }
243 if (!parsedPairs)
244 fatalf("FATAL: Missing annotation kv pair");
245 }
246
247 void
248 Notes::updateNotePairs(NotePairs::Pointer pairs, const CharacterSet *delimiters, const AccessLogEntry::Pointer &al)
249 {
250 for (const auto &n: notes)
251 n->updateNotePairs(pairs, delimiters, al);
252 }
253
254 void
255 Notes::dump(StoreEntry *entry, const char *key)
256 {
257 for (const auto &n: notes)
258 n->dump(entry, key);
259 }
260
261 const char *
262 Notes::toString(const char *sep) const
263 {
264 static SBuf result;
265 result.clear();
266 for (const auto &note: notes)
267 result.append(note->toString(sep));
268 return result.isEmpty() ? nullptr : result.c_str();
269 }
270
271 bool
272 NotePairs::find(SBuf &resultNote, const char *noteKey, const char *sep) const
273 {
274 resultNote.clear();
275 for (const auto &e: entries) {
276 if (!e->name().cmp(noteKey)) {
277 if (!resultNote.isEmpty())
278 resultNote.append(sep);
279 resultNote.append(e->value());
280 }
281 }
282 return resultNote.length();
283 }
284
285 const char *
286 NotePairs::toString(const char *sep) const
287 {
288 static SBuf result;
289 result.clear();
290 for (const auto &e: entries)
291 result.appendf("%.*s: %.*s%s", e->name().length(), e->name().rawContent(),
292 e->value().length(), e->value().rawContent(), sep);
293 return result.isEmpty() ? nullptr : result.c_str();
294 }
295
296 const char *
297 NotePairs::findFirst(const char *noteKey) const
298 {
299 for (const auto &e: entries)
300 if (!e->name().cmp(noteKey))
301 return const_cast<SBuf &>(e->value()).c_str();
302 return nullptr;
303 }
304
305 void
306 NotePairs::add(const char *key, const char *note)
307 {
308 entries.push_back(new NotePairs::Entry(key, note));
309 }
310
311 void
312 NotePairs::add(const SBuf &key, const SBuf &note)
313 {
314 entries.push_back(new NotePairs::Entry(key, note));
315 }
316
317 void
318 NotePairs::remove(const char *key)
319 {
320 Entries::iterator i = entries.begin();
321 while (i != entries.end())
322 i = (*i)->name().cmp(key) ? i+1 : entries.erase(i);
323 }
324
325 void
326 NotePairs::remove(const SBuf &key)
327 {
328 Entries::iterator i = entries.begin();
329 while (i != entries.end())
330 i = (*i)->name() == key ? entries.erase(i) : i+1;
331 }
332
333 static void
334 AppendTokens(NotePairs::Entries &entries, const SBuf &key, const SBuf &val, const CharacterSet &delimiters)
335 {
336 Parser::Tokenizer tok(val);
337 SBuf v;
338 while (tok.token(v, delimiters))
339 entries.push_back(new NotePairs::Entry(key, v));
340 v = tok.remaining();
341 if (!v.isEmpty())
342 entries.push_back(new NotePairs::Entry(key, v));
343 }
344
345 const NotePairs::Entries &
346 NotePairs::expandListEntries(const CharacterSet *delimiters) const
347 {
348 if (delimiters) {
349 static NotePairs::Entries expandedEntries;
350 expandedEntries.clear();
351 for (const auto &entry: entries)
352 AppendTokens(expandedEntries, entry->name(), entry->value(), *delimiters);
353 return expandedEntries;
354 }
355 return entries;
356 }
357
358 void
359 NotePairs::addStrList(const SBuf &key, const SBuf &values, const CharacterSet &delimiters)
360 {
361 AppendTokens(entries, key, values, delimiters);
362 }
363
364 bool
365 NotePairs::hasPair(const SBuf &key, const SBuf &value) const
366 {
367 for (const auto &e: entries)
368 if (e->name() == key && e->value() == value)
369 return true;
370 return false;
371 }
372
373 void
374 NotePairs::append(const NotePairs *src)
375 {
376 for (const auto &e: src->entries)
377 entries.push_back(new NotePairs::Entry(e->name(), e->value()));
378 }
379
380 void
381 NotePairs::appendNewOnly(const NotePairs *src)
382 {
383 for (const auto &e: src->entries) {
384 if (!hasPair(e->name(), e->value()))
385 entries.push_back(new NotePairs::Entry(e->name(), e->value()));
386 }
387 }
388
389 void
390 NotePairs::replaceOrAddOrAppend(const NotePairs *src, const NotePairs::Names &appendables)
391 {
392 for (const auto &e: src->entries) {
393 if (std::find(appendables.begin(), appendables.end(), e->name()) == appendables.end())
394 remove(e->name());
395 }
396 append(src);
397 }
398
399 void
400 NotePairs::replaceOrAdd(const NotePairs *src)
401 {
402 for (const auto &e: src->entries)
403 remove(e->name());
404 append(src);
405 }
406