]> git.ipfire.org Git - thirdparty/squid.git/blame - src/urn.cc
Fix parsing of malformed quoted squid.conf strings (#2239)
[thirdparty/squid.git] / src / urn.cc
CommitLineData
85491f8d 1/*
1f7b830e 2 * Copyright (C) 1996-2025 The Squid Software Foundation and contributors
e25c139f 3 *
bbc27441
AJ
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.
85491f8d 7 */
8
bbc27441
AJ
9/* DEBUG: section 52 URN Parsing */
10
582c2af2 11#include "squid.h"
d2a6dcba 12#include "AccessLogEntry.h"
819be284 13#include "acl/FilledChecklist.h"
bd10977c 14#include "base/TextException.h"
bda078fe 15#include "cbdata.h"
aa839030 16#include "errorpage.h"
eb13c21e 17#include "FwdState.h"
582c2af2 18#include "globals.h"
e87137f1
FC
19#include "HttpReply.h"
20#include "HttpRequest.h"
9b5c4a9a 21#include "icmp/net_db.h"
e87137f1 22#include "MemBuf.h"
b6149797 23#include "mime_header.h"
f206b652 24#include "RequestFlags.h"
e87137f1
FC
25#include "Store.h"
26#include "StoreClient.h"
8d03bdb4 27#include "tools.h"
5eb529cb 28#include "urn.h"
85491f8d 29
62e76326 30class UrnState : public StoreClient
31{
5c2f68b7 32 CBDATA_CLASS(UrnState);
62e76326 33
e6ccf245 34public:
d2a6dcba
EB
35 explicit UrnState(const AccessLogEntry::Pointer &anAle): ale(anAle) {}
36
190154cf 37 void start (HttpRequest *, StoreEntry *);
190154cf 38 void setUriResFromRequest(HttpRequest *);
e6ccf245 39
337b9aa4 40 ~UrnState() override;
62e76326 41
d2a6dcba
EB
42 StoreEntry *entry = nullptr;
43 store_client *sc = nullptr;
44 StoreEntry *urlres_e = nullptr;
8a70cdbb
AJ
45 HttpRequest::Pointer request;
46 HttpRequest::Pointer urlres_r;
7e6eabbc 47 AccessLogEntry::Pointer ale; ///< details of the requesting transaction
62e76326 48
122a6e3c
AR
49 /// for receiving a URN resolver reply body from Store and interpreting it
50 Store::ParsingBuffer parsingBuffer;
62e76326 51
e6ccf245 52private:
819be284 53 /* StoreClient API */
337b9aa4
AR
54 LogTags *loggingTags() const override { return ale ? &ale->cache.code : nullptr; }
55 void fillChecklist(ACLFilledChecklist &) const override;
819be284 56
d2a6dcba 57 char *urlres = nullptr;
e6ccf245 58};
85491f8d 59
26ac0430 60typedef struct {
9ce5e3e6 61 char *url;
62 char *host;
63 int rtt;
62e76326 64
26ac0430 65 struct {
62e76326 66 int cached;
2fadd50d
HN
67 } flags;
68} url_entry;
9ce5e3e6 69
70static STCB urnHandleReply;
122a6e3c 71static url_entry *urnParseReply(const SBuf &, const HttpRequestMethod &);
9ce5e3e6 72static const char *const crlf = "\r\n";
9ce5e3e6 73
bda078fe 74CBDATA_CLASS_INIT(UrnState);
62e76326 75
bda078fe 76UrnState::~UrnState()
e6ccf245 77{
bd10977c
EB
78 SWALLOW_EXCEPTIONS({
79 if (urlres_e) {
80 if (sc)
81 storeUnregister(sc, urlres_e, this);
82 urlres_e->unlock("~UrnState+res");
83 }
84
85 if (entry)
86 entry->unlock("~UrnState+prime");
87
88 safe_free(urlres);
89 });
e6ccf245 90}
91
48ebcb22 92static url_entry *
ced8def3 93urnFindMinRtt(url_entry * urls, const HttpRequestMethod &, int *rtt_ret)
85491f8d 94{
23d92c64 95 int min_rtt = 0;
aee3523a
AR
96 url_entry *u = nullptr;
97 url_entry *min_u = nullptr;
9ce5e3e6 98 int i;
1caf595b 99 int urlcnt = 0;
bf8fe701 100 debugs(52, 3, "urnFindMinRtt");
aee3523a 101 assert(urls != nullptr);
62e76326 102
aee3523a 103 for (i = 0; nullptr != urls[i].url; ++i)
14942edd 104 ++urlcnt;
62e76326 105
bf8fe701 106 debugs(53, 3, "urnFindMinRtt: Counted " << i << " URLs");
62e76326 107
9ce5e3e6 108 if (1 == urlcnt) {
bf8fe701 109 debugs(52, 3, "urnFindMinRtt: Only one URL - return it!");
62e76326 110 return urls;
1caf595b 111 }
62e76326 112
14942edd 113 for (i = 0; i < urlcnt; ++i) {
62e76326 114 u = &urls[i];
bf8fe701 115 debugs(52, 3, "urnFindMinRtt: " << u->host << " rtt=" << u->rtt);
62e76326 116
117 if (u->rtt == 0)
118 continue;
119
120 if (u->rtt > min_rtt && min_rtt != 0)
121 continue;
122
123 min_rtt = u->rtt;
124
125 min_u = u;
23d92c64 126 }
62e76326 127
23d92c64 128 if (rtt_ret)
62e76326 129 *rtt_ret = min_rtt;
130
e0236918 131 debugs(52, DBG_IMPORTANT, "urnFindMinRtt: Returning '" <<
26ac0430
AJ
132 (min_u ? min_u->url : "NONE") << "' RTT " <<
133 min_rtt );
62e76326 134
9ce5e3e6 135 return min_u;
85491f8d 136}
137
e6ccf245 138void
51b5dcf5 139UrnState::setUriResFromRequest(HttpRequest *r)
e6ccf245 140{
6c880a16
AJ
141 const auto &query = r->url.absolute();
142 const auto host = r->url.host();
c8ab5ec6 143 // TODO: use class AnyP::Uri instead of generating a string and re-parsing
e6ccf245 144 LOCAL_ARRAY(char, local_urlres, 4096);
6c880a16 145 snprintf(local_urlres, 4096, "http://%s/uri-res/N2L?" SQUIDSBUFPH, host, SQUIDSBUFPRINT(query));
86c63190 146 safe_free(urlres);
6c880a16 147 urlres_r = HttpRequest::FromUrlXXX(local_urlres, r->masterXaction);
62e76326 148
8babada0
AJ
149 if (!urlres_r) {
150 debugs(52, 3, "Bad uri-res URL " << local_urlres);
7e6eabbc 151 const auto err = new ErrorState(ERR_URN_RESOLVE, Http::scNotFound, r, ale);
8babada0 152 err->url = xstrdup(local_urlres);
62e76326 153 errorAppendEntry(entry, err);
154 return;
0adbab7c 155 }
62e76326 156
8babada0 157 urlres = xstrdup(local_urlres);
789217a2 158 urlres_r->header.putStr(Http::HdrType::ACCEPT, "text/plain");
e6ccf245 159}
160
161void
190154cf 162UrnState::start(HttpRequest * r, StoreEntry * e)
e6ccf245 163{
bf8fe701 164 debugs(52, 3, "urnStart: '" << e->url() << "'" );
e6ccf245 165 entry = e;
b248c2a3 166 request = r;
34266cde 167
1bfe9ade 168 entry->lock("UrnState::start");
e6ccf245 169 setUriResFromRequest(r);
62e76326 170
80b0bdd6
JR
171 if (urlres_r == nullptr) {
172 delete this;
62e76326 173 return;
80b0bdd6 174 }
62e76326 175
7976fed3 176 auto urlEntry = storeGetPublic(urlres, Http::METHOD_GET);
e6ccf245 177
7976fed3 178 if (!urlEntry || (urlEntry->hittingRequiresCollapsing() && !startCollapsingOn(*urlEntry, false))) {
c2a7cefd 179 urlres_e = storeCreateEntry(urlres, urlres, RequestFlags(), Http::METHOD_GET);
62e76326 180 sc = storeClientListAdd(urlres_e, this);
d2a6dcba 181 FwdState::Start(Comm::ConnectionPointer(), urlres_e, urlres_r.getRaw(), ale);
7976fed3 182 if (urlEntry) {
d868b138 183 urlEntry->abandon(__func__);
7976fed3
EB
184 urlEntry = nullptr;
185 }
cf26e54c 186 } else {
7976fed3 187 urlres_e = urlEntry;
d868b138 188 urlres_e->lock(__func__);
62e76326 189 sc = storeClientListAdd(urlres_e, this);
85491f8d 190 }
62e76326 191
e6ccf245 192 storeClientCopy(sc, urlres_e,
122a6e3c 193 parsingBuffer.makeInitialSpace(),
62e76326 194 urnHandleReply,
195 this);
e6ccf245 196}
197
7976fed3
EB
198void
199UrnState::fillChecklist(ACLFilledChecklist &checklist) const
200{
201 checklist.setRequest(request.getRaw());
202 checklist.al = ale;
203}
204
e6ccf245 205void
d2a6dcba 206urnStart(HttpRequest *r, StoreEntry *e, const AccessLogEntryPointer &ale)
e6ccf245 207{
d2a6dcba 208 const auto anUrn = new UrnState(ale);
e6ccf245 209 anUrn->start (r, e);
85491f8d 210}
211
9ce5e3e6 212static int
213url_entry_sort(const void *A, const void *B)
214{
e6ccf245 215 const url_entry *u1 = (const url_entry *)A;
216 const url_entry *u2 = (const url_entry *)B;
62e76326 217
9ce5e3e6 218 if (u2->rtt == u1->rtt)
62e76326 219 return 0;
9ce5e3e6 220 else if (0 == u1->rtt)
62e76326 221 return 1;
9ce5e3e6 222 else if (0 == u2->rtt)
62e76326 223 return -1;
9ce5e3e6 224 else
62e76326 225 return u1->rtt - u2->rtt;
9ce5e3e6 226}
227
528b2c61 228/* TODO: use the clientStream support for this */
85491f8d 229static void
c8be6d7b 230urnHandleReply(void *data, StoreIOBuffer result)
85491f8d 231{
e6ccf245 232 UrnState *urnState = static_cast<UrnState *>(data);
cf26e54c 233 StoreEntry *e = urnState->entry;
234 StoreEntry *urlres_e = urnState->urlres_e;
9ce5e3e6 235 url_entry *urls;
00141c96 236 url_entry *u;
9ce5e3e6 237 url_entry *min_u;
cf26e54c 238 ErrorState *err;
9ce5e3e6 239 int i;
240 int urlcnt = 0;
cf26e54c 241
122a6e3c 242 debugs(52, 3, result << " with " << *e);
62e76326 243
682d5190
EB
244 if (EBIT_TEST(urlres_e->flags, ENTRY_ABORTED) || result.flags.error) {
245 delete urnState;
bb9edbb2 246 return;
85491f8d 247 }
62e76326 248
d2b604ec
AR
249 if (!e->isAccepting()) {
250 debugs(52, 3, "terminating due to bad " << *e);
251 delete urnState;
252 return;
253 }
254
122a6e3c 255 urnState->parsingBuffer.appended(result.data, result.length);
62e76326 256
add2192d 257 /* If we haven't received the entire object (urn), copy more */
122a6e3c
AR
258 if (!urnState->sc->atEof()) {
259 const auto bufferedBytes = urnState->parsingBuffer.contentSize();
260 const auto remainingSpace = urnState->parsingBuffer.space().positionAt(bufferedBytes);
261
262 if (!remainingSpace.length) {
263 debugs(52, 3, "ran out of buffer space after " << bufferedBytes << " bytes");
264 // TODO: Here and in other error cases, send ERR_URN_RESOLVE to client.
265 delete urnState;
266 return;
267 }
268
62e76326 269 storeClientCopy(urnState->sc, urlres_e,
122a6e3c 270 remainingSpace,
62e76326 271 urnHandleReply,
272 urnState);
273 return;
23d92c64 274 }
62e76326 275
122a6e3c
AR
276 const auto &peerReply = urlres_e->mem().baseReply();
277 debugs(52, 3, "got reply, code=" << peerReply.sline.status());
278 if (peerReply.sline.status() != Http::scOkay) {
bf8fe701 279 debugs(52, 3, "urnHandleReply: failed.");
7e6eabbc 280 err = new ErrorState(ERR_URN_RESOLVE, Http::scNotFound, urnState->request.getRaw(), urnState->ale);
3900307b 281 err->url = xstrdup(e->url());
62e76326 282 errorAppendEntry(e, err);
682d5190 283 delete urnState;
bb9edbb2 284 return;
85491f8d 285 }
62e76326 286
ee989932
AR
287 // XXX: Missing reply freshness checks (e.g., calling refreshCheckHTTP()).
288
122a6e3c 289 urls = urnParseReply(urnState->parsingBuffer.toSBuf(), urnState->request->method);
62e76326 290
892ee3d0 291 if (!urls) { /* unknown URN error */
8a70cdbb 292 debugs(52, 3, "urnTranslateDone: unknown URN " << e->url());
7e6eabbc 293 err = new ErrorState(ERR_URN_RESOLVE, Http::scNotFound, urnState->request.getRaw(), urnState->ale);
3900307b 294 err->url = xstrdup(e->url());
62e76326 295 errorAppendEntry(e, err);
682d5190 296 delete urnState;
bb9edbb2 297 return;
164f7660 298 }
62e76326 299
892ee3d0
AJ
300 for (i = 0; urls[i].url; ++i)
301 ++urlcnt;
302
303 debugs(53, 3, "urnFindMinRtt: Counted " << i << " URLs");
304
aee3523a 305 min_u = urnFindMinRtt(urls, urnState->request->method, nullptr);
80b0bdd6
JR
306 char *min_url = nullptr;
307 if (min_u) {
308 min_url = xstrdup(min_u->url);
309 }
310
9ce5e3e6 311 qsort(urls, urlcnt, sizeof(*urls), url_entry_sort);
3900307b 312 e->buffer();
7e6eabbc
CT
313 SBuf body;
314 SBuf *mb = &body; // diff reduction hack; TODO: Remove
4391cd15 315 mb->appendf( "<TITLE>Select URL for %s</TITLE>\n"
f680026f
SM
316 "<STYLE type=\"text/css\"><!--BODY{background-color:#ffffff;font-family:verdana,sans-serif}--></STYLE>\n"
317 "<H2>Select URL for %s</H2>\n"
318 "<TABLE BORDER=\"0\" WIDTH=\"100%%\">\n", e->url(), e->url());
62e76326 319
14942edd 320 for (i = 0; i < urlcnt; ++i) {
62e76326 321 u = &urls[i];
bf8fe701 322 debugs(52, 3, "URL {" << u->url << "}");
4391cd15 323 mb->appendf(
2fe7eff9 324 "<TR><TD><A HREF=\"%s\">%s</A></TD>", u->url, u->url);
62e76326 325
326 if (urls[i].rtt > 0)
4391cd15 327 mb->appendf(
2fe7eff9 328 "<TD align=\"right\">%4d <it>ms</it></TD>", u->rtt);
62e76326 329 else
4391cd15 330 mb->appendf("<TD align=\"right\">Unknown</TD>");
62e76326 331
4391cd15 332 mb->appendf("<TD>%s</TD></TR>\n", u->flags.cached ? " [cached]" : " ");
cf26e54c 333 }
62e76326 334
4391cd15 335 mb->appendf(
2fe7eff9 336 "</TABLE>"
337 "<HR noshade size=\"1px\">\n"
338 "<ADDRESS>\n"
339 "Generated by %s@%s\n"
340 "</ADDRESS>\n",
c81de627 341 visible_appname_string, getMyHostname());
122a6e3c 342 const auto rep = new HttpReply;
aee3523a 343 rep->setHeaders(Http::scFound, nullptr, "text/html", mb->length(), 0, squid_curtime);
62e76326 344
80b0bdd6
JR
345 if (min_url) {
346 rep->header.putStr(Http::HdrType::LOCATION, min_url);
347 safe_free(min_url);
cb69b4c7 348 }
62e76326 349
7e6eabbc 350 rep->body.set(body);
db237875 351 e->replaceHttpReply(rep);
528b2c61 352 e->complete();
62e76326 353
14942edd 354 for (i = 0; i < urlcnt; ++i) {
62e76326 355 safe_free(urls[i].url);
356 safe_free(urls[i].host);
9ce5e3e6 357 }
62e76326 358
9ce5e3e6 359 safe_free(urls);
62e76326 360
682d5190 361 delete urnState;
85491f8d 362}
363
9ce5e3e6 364static url_entry *
122a6e3c 365urnParseReply(const SBuf &inBuf, const HttpRequestMethod &m)
85491f8d 366{
23d92c64 367 char *token;
9ce5e3e6 368 url_entry *list;
369 url_entry *old;
370 int n = 32;
371 int i = 0;
bf8fe701 372 debugs(52, 3, "urnParseReply");
e6ccf245 373 list = (url_entry *)xcalloc(n + 1, sizeof(*list));
62e76326 374
122a6e3c
AR
375 // XXX: Switch to tokenizer-based parsing.
376 const auto allocated = SBufToCstring(inBuf);
377
378 auto buf = allocated;
379 while (xisspace(*buf))
380 ++buf;
381
aee3523a 382 for (token = strtok(buf, crlf); token; token = strtok(nullptr, crlf)) {
bf8fe701 383 debugs(52, 3, "urnParseReply: got '" << token << "'");
62e76326 384
385 if (i == n) {
386 old = list;
387 n <<= 2;
388 list = (url_entry *)xcalloc(n + 1, sizeof(*list));
41d00cd3 389 memcpy(list, old, i * sizeof(*list));
62e76326 390 safe_free(old);
391 }
392
1ac1d4d3
AJ
393 AnyP::Uri uri;
394 if (!uri.parse(m, SBuf(token)) || !*uri.host())
62e76326 395 continue;
396
9b5c4a9a 397#if USE_ICMP
1ac1d4d3 398 list[i].rtt = netdbHostRtt(uri.host());
62e76326 399
9b5c4a9a 400 if (0 == list[i].rtt) {
1ac1d4d3
AJ
401 debugs(52, 3, "Pinging " << uri.host());
402 netdbPingSite(uri.host());
62e76326 403 }
9b5c4a9a
AJ
404#else
405 list[i].rtt = 0;
406#endif
62e76326 407
1ac1d4d3
AJ
408 list[i].url = xstrdup(uri.absolute().c_str());
409 list[i].host = xstrdup(uri.host());
5bd484b5
AR
410 // TODO: Use storeHas() or lock/unlock entry to avoid creating unlocked
411 // ones.
8babada0 412 list[i].flags.cached = storeGetPublic(list[i].url, m) ? 1 : 0;
14942edd 413 ++i;
85491f8d 414 }
62e76326 415
bf8fe701 416 debugs(52, 3, "urnParseReply: Found " << i << " URLs");
122a6e3c 417 xfree(allocated);
9ce5e3e6 418 return list;
85491f8d 419}
f53969cc 420