]> git.ipfire.org Git - thirdparty/squid.git/blame - src/urn.cc
Source Format Enforcement (#1234)
[thirdparty/squid.git] / src / urn.cc
CommitLineData
85491f8d 1/*
b8ae064d 2 * Copyright (C) 1996-2023 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
f53969cc 30#define URN_REQBUF_SZ 4096
add2192d 31
62e76326 32class UrnState : public StoreClient
33{
5c2f68b7 34 CBDATA_CLASS(UrnState);
62e76326 35
e6ccf245 36public:
d2a6dcba
EB
37 explicit UrnState(const AccessLogEntry::Pointer &anAle): ale(anAle) {}
38
190154cf 39 void start (HttpRequest *, StoreEntry *);
190154cf 40 void setUriResFromRequest(HttpRequest *);
e6ccf245 41
337b9aa4 42 ~UrnState() override;
62e76326 43
d2a6dcba
EB
44 StoreEntry *entry = nullptr;
45 store_client *sc = nullptr;
46 StoreEntry *urlres_e = nullptr;
8a70cdbb
AJ
47 HttpRequest::Pointer request;
48 HttpRequest::Pointer urlres_r;
7e6eabbc 49 AccessLogEntry::Pointer ale; ///< details of the requesting transaction
62e76326 50
d2a6dcba
EB
51 char reqbuf[URN_REQBUF_SZ] = { '\0' };
52 int reqofs = 0;
62e76326 53
e6ccf245 54private:
819be284 55 /* StoreClient API */
337b9aa4
AR
56 LogTags *loggingTags() const override { return ale ? &ale->cache.code : nullptr; }
57 void fillChecklist(ACLFilledChecklist &) const override;
819be284 58
d2a6dcba 59 char *urlres = nullptr;
e6ccf245 60};
85491f8d 61
26ac0430 62typedef struct {
9ce5e3e6 63 char *url;
64 char *host;
65 int rtt;
62e76326 66
26ac0430 67 struct {
62e76326 68 int cached;
2fadd50d
HN
69 } flags;
70} url_entry;
9ce5e3e6 71
72static STCB urnHandleReply;
60745f24 73static url_entry *urnParseReply(const char *inbuf, const HttpRequestMethod&);
9ce5e3e6 74static const char *const crlf = "\r\n";
9ce5e3e6 75
bda078fe 76CBDATA_CLASS_INIT(UrnState);
62e76326 77
bda078fe 78UrnState::~UrnState()
e6ccf245 79{
bd10977c
EB
80 SWALLOW_EXCEPTIONS({
81 if (urlres_e) {
82 if (sc)
83 storeUnregister(sc, urlres_e, this);
84 urlres_e->unlock("~UrnState+res");
85 }
86
87 if (entry)
88 entry->unlock("~UrnState+prime");
89
90 safe_free(urlres);
91 });
e6ccf245 92}
93
48ebcb22 94static url_entry *
ced8def3 95urnFindMinRtt(url_entry * urls, const HttpRequestMethod &, int *rtt_ret)
85491f8d 96{
23d92c64 97 int min_rtt = 0;
aee3523a
AR
98 url_entry *u = nullptr;
99 url_entry *min_u = nullptr;
9ce5e3e6 100 int i;
1caf595b 101 int urlcnt = 0;
bf8fe701 102 debugs(52, 3, "urnFindMinRtt");
aee3523a 103 assert(urls != nullptr);
62e76326 104
aee3523a 105 for (i = 0; nullptr != urls[i].url; ++i)
14942edd 106 ++urlcnt;
62e76326 107
bf8fe701 108 debugs(53, 3, "urnFindMinRtt: Counted " << i << " URLs");
62e76326 109
9ce5e3e6 110 if (1 == urlcnt) {
bf8fe701 111 debugs(52, 3, "urnFindMinRtt: Only one URL - return it!");
62e76326 112 return urls;
1caf595b 113 }
62e76326 114
14942edd 115 for (i = 0; i < urlcnt; ++i) {
62e76326 116 u = &urls[i];
bf8fe701 117 debugs(52, 3, "urnFindMinRtt: " << u->host << " rtt=" << u->rtt);
62e76326 118
119 if (u->rtt == 0)
120 continue;
121
122 if (u->rtt > min_rtt && min_rtt != 0)
123 continue;
124
125 min_rtt = u->rtt;
126
127 min_u = u;
23d92c64 128 }
62e76326 129
23d92c64 130 if (rtt_ret)
62e76326 131 *rtt_ret = min_rtt;
132
e0236918 133 debugs(52, DBG_IMPORTANT, "urnFindMinRtt: Returning '" <<
26ac0430
AJ
134 (min_u ? min_u->url : "NONE") << "' RTT " <<
135 min_rtt );
62e76326 136
9ce5e3e6 137 return min_u;
85491f8d 138}
139
e6ccf245 140void
51b5dcf5 141UrnState::setUriResFromRequest(HttpRequest *r)
e6ccf245 142{
6c880a16
AJ
143 const auto &query = r->url.absolute();
144 const auto host = r->url.host();
c8ab5ec6 145 // TODO: use class AnyP::Uri instead of generating a string and re-parsing
e6ccf245 146 LOCAL_ARRAY(char, local_urlres, 4096);
6c880a16 147 snprintf(local_urlres, 4096, "http://%s/uri-res/N2L?" SQUIDSBUFPH, host, SQUIDSBUFPRINT(query));
86c63190 148 safe_free(urlres);
6c880a16 149 urlres_r = HttpRequest::FromUrlXXX(local_urlres, r->masterXaction);
62e76326 150
8babada0
AJ
151 if (!urlres_r) {
152 debugs(52, 3, "Bad uri-res URL " << local_urlres);
7e6eabbc 153 const auto err = new ErrorState(ERR_URN_RESOLVE, Http::scNotFound, r, ale);
8babada0 154 err->url = xstrdup(local_urlres);
62e76326 155 errorAppendEntry(entry, err);
156 return;
0adbab7c 157 }
62e76326 158
8babada0 159 urlres = xstrdup(local_urlres);
789217a2 160 urlres_r->header.putStr(Http::HdrType::ACCEPT, "text/plain");
e6ccf245 161}
162
163void
190154cf 164UrnState::start(HttpRequest * r, StoreEntry * e)
e6ccf245 165{
bf8fe701 166 debugs(52, 3, "urnStart: '" << e->url() << "'" );
e6ccf245 167 entry = e;
b248c2a3 168 request = r;
34266cde 169
1bfe9ade 170 entry->lock("UrnState::start");
e6ccf245 171 setUriResFromRequest(r);
62e76326 172
aee3523a 173 if (urlres_r == nullptr)
62e76326 174 return;
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
EB
182 if (urlEntry) {
183 urlEntry->abandon(__FUNCTION__);
184 urlEntry = nullptr;
185 }
cf26e54c 186 } else {
7976fed3
EB
187 urlres_e = urlEntry;
188 urlres_e->lock("UrnState::start");
62e76326 189 sc = storeClientListAdd(urlres_e, this);
85491f8d 190 }
62e76326 191
e6ccf245 192 reqofs = 0;
528b2c61 193 StoreIOBuffer tempBuffer;
e6ccf245 194 tempBuffer.offset = reqofs;
c8be6d7b 195 tempBuffer.length = URN_REQBUF_SZ;
e6ccf245 196 tempBuffer.data = reqbuf;
197 storeClientCopy(sc, urlres_e,
62e76326 198 tempBuffer,
199 urnHandleReply,
200 this);
e6ccf245 201}
202
7976fed3
EB
203void
204UrnState::fillChecklist(ACLFilledChecklist &checklist) const
205{
206 checklist.setRequest(request.getRaw());
207 checklist.al = ale;
208}
209
e6ccf245 210void
d2a6dcba 211urnStart(HttpRequest *r, StoreEntry *e, const AccessLogEntryPointer &ale)
e6ccf245 212{
d2a6dcba 213 const auto anUrn = new UrnState(ale);
e6ccf245 214 anUrn->start (r, e);
85491f8d 215}
216
9ce5e3e6 217static int
218url_entry_sort(const void *A, const void *B)
219{
e6ccf245 220 const url_entry *u1 = (const url_entry *)A;
221 const url_entry *u2 = (const url_entry *)B;
62e76326 222
9ce5e3e6 223 if (u2->rtt == u1->rtt)
62e76326 224 return 0;
9ce5e3e6 225 else if (0 == u1->rtt)
62e76326 226 return 1;
9ce5e3e6 227 else if (0 == u2->rtt)
62e76326 228 return -1;
9ce5e3e6 229 else
62e76326 230 return u1->rtt - u2->rtt;
9ce5e3e6 231}
232
528b2c61 233/* TODO: use the clientStream support for this */
85491f8d 234static void
c8be6d7b 235urnHandleReply(void *data, StoreIOBuffer result)
85491f8d 236{
e6ccf245 237 UrnState *urnState = static_cast<UrnState *>(data);
cf26e54c 238 StoreEntry *e = urnState->entry;
239 StoreEntry *urlres_e = urnState->urlres_e;
aee3523a 240 char *s = nullptr;
2334c194 241 size_t k;
cb69b4c7 242 HttpReply *rep;
9ce5e3e6 243 url_entry *urls;
00141c96 244 url_entry *u;
9ce5e3e6 245 url_entry *min_u;
cf26e54c 246 ErrorState *err;
9ce5e3e6 247 int i;
248 int urlcnt = 0;
add2192d 249 char *buf = urnState->reqbuf;
c8be6d7b 250 StoreIOBuffer tempBuffer;
cf26e54c 251
58c0b17d 252 debugs(52, 3, "urnHandleReply: Called with size=" << result.length << ".");
62e76326 253
682d5190
EB
254 if (EBIT_TEST(urlres_e->flags, ENTRY_ABORTED) || result.flags.error) {
255 delete urnState;
bb9edbb2 256 return;
85491f8d 257 }
62e76326 258
d2b604ec
AR
259 if (!e->isAccepting()) {
260 debugs(52, 3, "terminating due to bad " << *e);
261 delete urnState;
262 return;
263 }
264
add2192d 265 /* Update reqofs to point to where in the buffer we'd be */
c8be6d7b 266 urnState->reqofs += result.length;
add2192d 267
268 /* Handle reqofs being bigger than normal */
269 if (urnState->reqofs >= URN_REQBUF_SZ) {
682d5190 270 delete urnState;
bb9edbb2 271 return;
add2192d 272 }
62e76326 273
add2192d 274 /* If we haven't received the entire object (urn), copy more */
682d5190
EB
275 if (urlres_e->store_status == STORE_PENDING) {
276 Must(result.length > 0); // zero length ought to imply STORE_OK
62e76326 277 tempBuffer.offset = urnState->reqofs;
682d5190 278 tempBuffer.length = URN_REQBUF_SZ - urnState->reqofs;
62e76326 279 tempBuffer.data = urnState->reqbuf + urnState->reqofs;
280 storeClientCopy(urnState->sc, urlres_e,
281 tempBuffer,
282 urnHandleReply,
283 urnState);
284 return;
23d92c64 285 }
62e76326 286
23d92c64 287 /* we know its STORE_OK */
add2192d 288 k = headersEnd(buf, urnState->reqofs);
62e76326 289
2334c194 290 if (0 == k) {
e0236918 291 debugs(52, DBG_IMPORTANT, "urnHandleReply: didn't find end-of-headers for " << e->url() );
682d5190 292 delete urnState;
bb9edbb2 293 return;
85491f8d 294 }
62e76326 295
2334c194 296 s = buf + k;
66d51f4f 297 // TODO: Check whether we should parse urlres_e reply, as before 528b2c61.
06a5ae20 298 rep = new HttpReply;
59eed7dc 299 rep->parseCharBuf(buf, k);
9b769c67 300 debugs(52, 3, "reply exists, code=" << rep->sline.status() << ".");
62e76326 301
9b769c67 302 if (rep->sline.status() != Http::scOkay) {
bf8fe701 303 debugs(52, 3, "urnHandleReply: failed.");
7e6eabbc 304 err = new ErrorState(ERR_URN_RESOLVE, Http::scNotFound, urnState->request.getRaw(), urnState->ale);
3900307b 305 err->url = xstrdup(e->url());
62e76326 306 errorAppendEntry(e, err);
06a5ae20 307 delete rep;
682d5190 308 delete urnState;
bb9edbb2 309 return;
85491f8d 310 }
62e76326 311
06a5ae20 312 delete rep;
62e76326 313
b6a2f15e 314 while (xisspace(*s))
14942edd 315 ++s;
62e76326 316
9ce5e3e6 317 urls = urnParseReply(s, urnState->request->method);
62e76326 318
892ee3d0 319 if (!urls) { /* unknown URN error */
8a70cdbb 320 debugs(52, 3, "urnTranslateDone: unknown URN " << e->url());
7e6eabbc 321 err = new ErrorState(ERR_URN_RESOLVE, Http::scNotFound, urnState->request.getRaw(), urnState->ale);
3900307b 322 err->url = xstrdup(e->url());
62e76326 323 errorAppendEntry(e, err);
682d5190 324 delete urnState;
bb9edbb2 325 return;
164f7660 326 }
62e76326 327
892ee3d0
AJ
328 for (i = 0; urls[i].url; ++i)
329 ++urlcnt;
330
331 debugs(53, 3, "urnFindMinRtt: Counted " << i << " URLs");
332
aee3523a 333 min_u = urnFindMinRtt(urls, urnState->request->method, nullptr);
9ce5e3e6 334 qsort(urls, urlcnt, sizeof(*urls), url_entry_sort);
3900307b 335 e->buffer();
7e6eabbc
CT
336 SBuf body;
337 SBuf *mb = &body; // diff reduction hack; TODO: Remove
4391cd15 338 mb->appendf( "<TITLE>Select URL for %s</TITLE>\n"
f680026f
SM
339 "<STYLE type=\"text/css\"><!--BODY{background-color:#ffffff;font-family:verdana,sans-serif}--></STYLE>\n"
340 "<H2>Select URL for %s</H2>\n"
341 "<TABLE BORDER=\"0\" WIDTH=\"100%%\">\n", e->url(), e->url());
62e76326 342
14942edd 343 for (i = 0; i < urlcnt; ++i) {
62e76326 344 u = &urls[i];
bf8fe701 345 debugs(52, 3, "URL {" << u->url << "}");
4391cd15 346 mb->appendf(
2fe7eff9 347 "<TR><TD><A HREF=\"%s\">%s</A></TD>", u->url, u->url);
62e76326 348
349 if (urls[i].rtt > 0)
4391cd15 350 mb->appendf(
2fe7eff9 351 "<TD align=\"right\">%4d <it>ms</it></TD>", u->rtt);
62e76326 352 else
4391cd15 353 mb->appendf("<TD align=\"right\">Unknown</TD>");
62e76326 354
4391cd15 355 mb->appendf("<TD>%s</TD></TR>\n", u->flags.cached ? " [cached]" : " ");
cf26e54c 356 }
62e76326 357
4391cd15 358 mb->appendf(
2fe7eff9 359 "</TABLE>"
360 "<HR noshade size=\"1px\">\n"
361 "<ADDRESS>\n"
362 "Generated by %s@%s\n"
363 "</ADDRESS>\n",
c81de627 364 visible_appname_string, getMyHostname());
06a5ae20 365 rep = new HttpReply;
aee3523a 366 rep->setHeaders(Http::scFound, nullptr, "text/html", mb->length(), 0, squid_curtime);
62e76326 367
6c880a16 368 if (min_u) {
789217a2 369 rep->header.putStr(Http::HdrType::LOCATION, min_u->url);
cb69b4c7 370 }
62e76326 371
7e6eabbc 372 rep->body.set(body);
db237875 373 e->replaceHttpReply(rep);
528b2c61 374 e->complete();
62e76326 375
14942edd 376 for (i = 0; i < urlcnt; ++i) {
62e76326 377 safe_free(urls[i].url);
378 safe_free(urls[i].host);
9ce5e3e6 379 }
62e76326 380
9ce5e3e6 381 safe_free(urls);
62e76326 382
682d5190 383 delete urnState;
85491f8d 384}
385
9ce5e3e6 386static url_entry *
60745f24 387urnParseReply(const char *inbuf, const HttpRequestMethod& m)
85491f8d 388{
23d92c64 389 char *buf = xstrdup(inbuf);
390 char *token;
9ce5e3e6 391 url_entry *list;
392 url_entry *old;
393 int n = 32;
394 int i = 0;
bf8fe701 395 debugs(52, 3, "urnParseReply");
e6ccf245 396 list = (url_entry *)xcalloc(n + 1, sizeof(*list));
62e76326 397
aee3523a 398 for (token = strtok(buf, crlf); token; token = strtok(nullptr, crlf)) {
bf8fe701 399 debugs(52, 3, "urnParseReply: got '" << token << "'");
62e76326 400
401 if (i == n) {
402 old = list;
403 n <<= 2;
404 list = (url_entry *)xcalloc(n + 1, sizeof(*list));
41d00cd3 405 memcpy(list, old, i * sizeof(*list));
62e76326 406 safe_free(old);
407 }
408
1ac1d4d3
AJ
409 AnyP::Uri uri;
410 if (!uri.parse(m, SBuf(token)) || !*uri.host())
62e76326 411 continue;
412
9b5c4a9a 413#if USE_ICMP
1ac1d4d3 414 list[i].rtt = netdbHostRtt(uri.host());
62e76326 415
9b5c4a9a 416 if (0 == list[i].rtt) {
1ac1d4d3
AJ
417 debugs(52, 3, "Pinging " << uri.host());
418 netdbPingSite(uri.host());
62e76326 419 }
9b5c4a9a
AJ
420#else
421 list[i].rtt = 0;
422#endif
62e76326 423
1ac1d4d3
AJ
424 list[i].url = xstrdup(uri.absolute().c_str());
425 list[i].host = xstrdup(uri.host());
5bd484b5
AR
426 // TODO: Use storeHas() or lock/unlock entry to avoid creating unlocked
427 // ones.
8babada0 428 list[i].flags.cached = storeGetPublic(list[i].url, m) ? 1 : 0;
14942edd 429 ++i;
85491f8d 430 }
62e76326 431
bf8fe701 432 debugs(52, 3, "urnParseReply: Found " << i << " URLs");
47a085ff 433 xfree(buf);
9ce5e3e6 434 return list;
85491f8d 435}
f53969cc 436