]> git.ipfire.org Git - thirdparty/squid.git/blame - src/urn.cc
Identify collapsed transactions (#213)
[thirdparty/squid.git] / src / urn.cc
CommitLineData
85491f8d 1/*
5b74111a 2 * Copyright (C) 1996-2018 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"
bda078fe 14#include "cbdata.h"
aa839030 15#include "errorpage.h"
eb13c21e 16#include "FwdState.h"
582c2af2 17#include "globals.h"
e87137f1
FC
18#include "HttpReply.h"
19#include "HttpRequest.h"
9b5c4a9a 20#include "icmp/net_db.h"
e87137f1 21#include "MemBuf.h"
b6149797 22#include "mime_header.h"
f206b652 23#include "RequestFlags.h"
e87137f1
FC
24#include "SquidTime.h"
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
3b13a8fd 39 void created (StoreEntry *newEntry);
190154cf 40 void start (HttpRequest *, StoreEntry *);
51b5dcf5 41 char *getHost(const SBuf &urlpath);
190154cf 42 void setUriResFromRequest(HttpRequest *);
e6ccf245 43
44 virtual ~UrnState();
62e76326 45
d2a6dcba
EB
46 StoreEntry *entry = nullptr;
47 store_client *sc = nullptr;
48 StoreEntry *urlres_e = nullptr;
8a70cdbb
AJ
49 HttpRequest::Pointer request;
50 HttpRequest::Pointer urlres_r;
62e76326 51
26ac0430 52 struct {
d2a6dcba 53 bool force_menu = false;
3d0ac046 54 } flags;
d2a6dcba
EB
55 char reqbuf[URN_REQBUF_SZ] = { '\0' };
56 int reqofs = 0;
62e76326 57
e6ccf245 58private:
819be284 59 /* StoreClient API */
d2a6dcba 60 virtual LogTags *loggingTags() { return ale ? &ale->cache.code : nullptr; }
819be284
EB
61 virtual void fillChecklist(ACLFilledChecklist &) const;
62
d2a6dcba
EB
63 char *urlres = nullptr;
64 AccessLogEntry::Pointer ale; ///< master transaction summary
e6ccf245 65};
85491f8d 66
26ac0430 67typedef struct {
9ce5e3e6 68 char *url;
69 char *host;
70 int rtt;
62e76326 71
26ac0430 72 struct {
62e76326 73 int cached;
2fadd50d
HN
74 } flags;
75} url_entry;
9ce5e3e6 76
77static STCB urnHandleReply;
60745f24 78static url_entry *urnParseReply(const char *inbuf, const HttpRequestMethod&);
9ce5e3e6 79static const char *const crlf = "\r\n";
9ce5e3e6 80
bda078fe 81CBDATA_CLASS_INIT(UrnState);
62e76326 82
bda078fe 83UrnState::~UrnState()
e6ccf245 84{
85 safe_free(urlres);
86}
87
48ebcb22 88static url_entry *
ced8def3 89urnFindMinRtt(url_entry * urls, const HttpRequestMethod &, int *rtt_ret)
85491f8d 90{
23d92c64 91 int min_rtt = 0;
9ce5e3e6 92 url_entry *u = NULL;
93 url_entry *min_u = NULL;
94 int i;
1caf595b 95 int urlcnt = 0;
bf8fe701 96 debugs(52, 3, "urnFindMinRtt");
23d92c64 97 assert(urls != NULL);
62e76326 98
14942edd
FC
99 for (i = 0; NULL != urls[i].url; ++i)
100 ++urlcnt;
62e76326 101
bf8fe701 102 debugs(53, 3, "urnFindMinRtt: Counted " << i << " URLs");
62e76326 103
9ce5e3e6 104 if (1 == urlcnt) {
bf8fe701 105 debugs(52, 3, "urnFindMinRtt: Only one URL - return it!");
62e76326 106 return urls;
1caf595b 107 }
62e76326 108
14942edd 109 for (i = 0; i < urlcnt; ++i) {
62e76326 110 u = &urls[i];
bf8fe701 111 debugs(52, 3, "urnFindMinRtt: " << u->host << " rtt=" << u->rtt);
62e76326 112
113 if (u->rtt == 0)
114 continue;
115
116 if (u->rtt > min_rtt && min_rtt != 0)
117 continue;
118
119 min_rtt = u->rtt;
120
121 min_u = u;
23d92c64 122 }
62e76326 123
23d92c64 124 if (rtt_ret)
62e76326 125 *rtt_ret = min_rtt;
126
e0236918 127 debugs(52, DBG_IMPORTANT, "urnFindMinRtt: Returning '" <<
26ac0430
AJ
128 (min_u ? min_u->url : "NONE") << "' RTT " <<
129 min_rtt );
62e76326 130
9ce5e3e6 131 return min_u;
85491f8d 132}
133
e6ccf245 134char *
51b5dcf5 135UrnState::getHost(const SBuf &urlpath)
85491f8d 136{
b4f2886c
FC
137 /** FIXME: this appears to be parsing the URL. *very* badly. */
138 /* a proper encapsulated URI/URL type needs to clear this up. */
3f0e38d6
AJ
139 size_t p;
140 if ((p = urlpath.find(':')) != SBuf::npos)
141 return SBufToCstring(urlpath.substr(0, p-1));
142
143 return SBufToCstring(urlpath);
e6ccf245 144}
145
e6ccf245 146void
51b5dcf5 147UrnState::setUriResFromRequest(HttpRequest *r)
e6ccf245 148{
51b5dcf5
AJ
149 static const SBuf menu(".menu");
150 if (r->url.path().startsWith(menu)) {
151 r->url.path(r->url.path().substr(5)); // strip prefix "menu."
152 flags.force_menu = true;
153 }
e6ccf245 154
51b5dcf5 155 SBuf uri = r->url.path();
c8ab5ec6 156 // TODO: use class AnyP::Uri instead of generating a string and re-parsing
e6ccf245 157 LOCAL_ARRAY(char, local_urlres, 4096);
51b5dcf5
AJ
158 char *host = getHost(uri);
159 snprintf(local_urlres, 4096, "http://%s/uri-res/N2L?urn:" SQUIDSBUFPH, host, SQUIDSBUFPRINT(uri));
86c63190
AJ
160 safe_free(host);
161 safe_free(urlres);
8babada0 162 urlres_r = HttpRequest::FromUrl(local_urlres, r->masterXaction);
62e76326 163
8babada0
AJ
164 if (!urlres_r) {
165 debugs(52, 3, "Bad uri-res URL " << local_urlres);
955394ce 166 ErrorState *err = new ErrorState(ERR_URN_RESOLVE, Http::scNotFound, r);
8babada0 167 err->url = xstrdup(local_urlres);
62e76326 168 errorAppendEntry(entry, err);
169 return;
0adbab7c 170 }
62e76326 171
8babada0 172 urlres = xstrdup(local_urlres);
789217a2 173 urlres_r->header.putStr(Http::HdrType::ACCEPT, "text/plain");
e6ccf245 174}
175
176void
190154cf 177UrnState::start(HttpRequest * r, StoreEntry * e)
e6ccf245 178{
bf8fe701 179 debugs(52, 3, "urnStart: '" << e->url() << "'" );
e6ccf245 180 entry = e;
b248c2a3 181 request = r;
34266cde 182
1bfe9ade 183 entry->lock("UrnState::start");
e6ccf245 184 setUriResFromRequest(r);
62e76326 185
e6ccf245 186 if (urlres_r == NULL)
62e76326 187 return;
188
c2a7cefd 189 StoreEntry::getPublic (this, urlres, Http::METHOD_GET);
e6ccf245 190}
191
192void
819be284 193UrnState::fillChecklist(ACLFilledChecklist &checklist) const
e6ccf245 194{
819be284 195 checklist.setRequest(request.getRaw());
d2a6dcba 196 checklist.al = ale;
819be284 197}
62e76326 198
819be284
EB
199void
200UrnState::created(StoreEntry *e)
201{
d2a6dcba 202 if (e->isNull() || (e->hittingRequiresCollapsing() && !startCollapsingOn(*e, false))) {
c2a7cefd 203 urlres_e = storeCreateEntry(urlres, urlres, RequestFlags(), Http::METHOD_GET);
62e76326 204 sc = storeClientListAdd(urlres_e, this);
d2a6dcba 205 FwdState::Start(Comm::ConnectionPointer(), urlres_e, urlres_r.getRaw(), ale);
819be284
EB
206 // TODO: StoreClients must either store/lock or abandon found entries.
207 //if (!e->isNull())
208 // e->abandon();
cf26e54c 209 } else {
819be284 210 urlres_e = e;
1bfe9ade 211 urlres_e->lock("UrnState::created");
62e76326 212 sc = storeClientListAdd(urlres_e, this);
85491f8d 213 }
62e76326 214
e6ccf245 215 reqofs = 0;
528b2c61 216 StoreIOBuffer tempBuffer;
e6ccf245 217 tempBuffer.offset = reqofs;
c8be6d7b 218 tempBuffer.length = URN_REQBUF_SZ;
e6ccf245 219 tempBuffer.data = reqbuf;
220 storeClientCopy(sc, urlres_e,
62e76326 221 tempBuffer,
222 urnHandleReply,
223 this);
e6ccf245 224}
225
226void
d2a6dcba 227urnStart(HttpRequest *r, StoreEntry *e, const AccessLogEntryPointer &ale)
e6ccf245 228{
d2a6dcba 229 const auto anUrn = new UrnState(ale);
e6ccf245 230 anUrn->start (r, e);
85491f8d 231}
232
9ce5e3e6 233static int
234url_entry_sort(const void *A, const void *B)
235{
e6ccf245 236 const url_entry *u1 = (const url_entry *)A;
237 const url_entry *u2 = (const url_entry *)B;
62e76326 238
9ce5e3e6 239 if (u2->rtt == u1->rtt)
62e76326 240 return 0;
9ce5e3e6 241 else if (0 == u1->rtt)
62e76326 242 return 1;
9ce5e3e6 243 else if (0 == u2->rtt)
62e76326 244 return -1;
9ce5e3e6 245 else
62e76326 246 return u1->rtt - u2->rtt;
9ce5e3e6 247}
248
bb9edbb2
AJ
249static void
250urnHandleReplyError(UrnState *urnState, StoreEntry *urlres_e)
251{
1bfe9ade
AR
252 urlres_e->unlock("urnHandleReplyError+res");
253 urnState->entry->unlock("urnHandleReplyError+prime");
bb9edbb2
AJ
254 delete urnState;
255}
256
528b2c61 257/* TODO: use the clientStream support for this */
85491f8d 258static void
c8be6d7b 259urnHandleReply(void *data, StoreIOBuffer result)
85491f8d 260{
e6ccf245 261 UrnState *urnState = static_cast<UrnState *>(data);
cf26e54c 262 StoreEntry *e = urnState->entry;
263 StoreEntry *urlres_e = urnState->urlres_e;
23d92c64 264 char *s = NULL;
2334c194 265 size_t k;
cb69b4c7 266 HttpReply *rep;
9ce5e3e6 267 url_entry *urls;
00141c96 268 url_entry *u;
9ce5e3e6 269 url_entry *min_u;
032785bf 270 MemBuf *mb = NULL;
cf26e54c 271 ErrorState *err;
9ce5e3e6 272 int i;
273 int urlcnt = 0;
add2192d 274 char *buf = urnState->reqbuf;
c8be6d7b 275 StoreIOBuffer tempBuffer;
cf26e54c 276
58c0b17d 277 debugs(52, 3, "urnHandleReply: Called with size=" << result.length << ".");
62e76326 278
58c0b17d 279 if (EBIT_TEST(urlres_e->flags, ENTRY_ABORTED) || result.length == 0 || result.flags.error) {
bb9edbb2
AJ
280 urnHandleReplyError(urnState, urlres_e);
281 return;
85491f8d 282 }
62e76326 283
add2192d 284 /* Update reqofs to point to where in the buffer we'd be */
c8be6d7b 285 urnState->reqofs += result.length;
add2192d 286
287 /* Handle reqofs being bigger than normal */
288 if (urnState->reqofs >= URN_REQBUF_SZ) {
bb9edbb2
AJ
289 urnHandleReplyError(urnState, urlres_e);
290 return;
add2192d 291 }
62e76326 292
add2192d 293 /* If we haven't received the entire object (urn), copy more */
294 if (urlres_e->store_status == STORE_PENDING &&
62e76326 295 urnState->reqofs < URN_REQBUF_SZ) {
296 tempBuffer.offset = urnState->reqofs;
297 tempBuffer.length = URN_REQBUF_SZ;
298 tempBuffer.data = urnState->reqbuf + urnState->reqofs;
299 storeClientCopy(urnState->sc, urlres_e,
300 tempBuffer,
301 urnHandleReply,
302 urnState);
303 return;
23d92c64 304 }
62e76326 305
23d92c64 306 /* we know its STORE_OK */
add2192d 307 k = headersEnd(buf, urnState->reqofs);
62e76326 308
2334c194 309 if (0 == k) {
e0236918 310 debugs(52, DBG_IMPORTANT, "urnHandleReply: didn't find end-of-headers for " << e->url() );
bb9edbb2
AJ
311 urnHandleReplyError(urnState, urlres_e);
312 return;
85491f8d 313 }
62e76326 314
2334c194 315 s = buf + k;
528b2c61 316 assert(urlres_e->getReply());
06a5ae20 317 rep = new HttpReply;
59eed7dc 318 rep->parseCharBuf(buf, k);
9b769c67 319 debugs(52, 3, "reply exists, code=" << rep->sline.status() << ".");
62e76326 320
9b769c67 321 if (rep->sline.status() != Http::scOkay) {
bf8fe701 322 debugs(52, 3, "urnHandleReply: failed.");
8a70cdbb 323 err = new ErrorState(ERR_URN_RESOLVE, Http::scNotFound, urnState->request.getRaw());
3900307b 324 err->url = xstrdup(e->url());
62e76326 325 errorAppendEntry(e, err);
06a5ae20 326 delete rep;
bb9edbb2
AJ
327 urnHandleReplyError(urnState, urlres_e);
328 return;
85491f8d 329 }
62e76326 330
06a5ae20 331 delete rep;
62e76326 332
b6a2f15e 333 while (xisspace(*s))
14942edd 334 ++s;
62e76326 335
9ce5e3e6 336 urls = urnParseReply(s, urnState->request->method);
62e76326 337
892ee3d0 338 if (!urls) { /* unknown URN error */
8a70cdbb
AJ
339 debugs(52, 3, "urnTranslateDone: unknown URN " << e->url());
340 err = new ErrorState(ERR_URN_RESOLVE, Http::scNotFound, urnState->request.getRaw());
3900307b 341 err->url = xstrdup(e->url());
62e76326 342 errorAppendEntry(e, err);
bb9edbb2
AJ
343 urnHandleReplyError(urnState, urlres_e);
344 return;
164f7660 345 }
62e76326 346
892ee3d0
AJ
347 for (i = 0; urls[i].url; ++i)
348 ++urlcnt;
349
350 debugs(53, 3, "urnFindMinRtt: Counted " << i << " URLs");
351
9ce5e3e6 352 min_u = urnFindMinRtt(urls, urnState->request->method, NULL);
353 qsort(urls, urlcnt, sizeof(*urls), url_entry_sort);
3900307b 354 e->buffer();
032785bf 355 mb = new MemBuf;
2fe7eff9 356 mb->init();
4391cd15 357 mb->appendf( "<TITLE>Select URL for %s</TITLE>\n"
f680026f
SM
358 "<STYLE type=\"text/css\"><!--BODY{background-color:#ffffff;font-family:verdana,sans-serif}--></STYLE>\n"
359 "<H2>Select URL for %s</H2>\n"
360 "<TABLE BORDER=\"0\" WIDTH=\"100%%\">\n", e->url(), e->url());
62e76326 361
14942edd 362 for (i = 0; i < urlcnt; ++i) {
62e76326 363 u = &urls[i];
bf8fe701 364 debugs(52, 3, "URL {" << u->url << "}");
4391cd15 365 mb->appendf(
2fe7eff9 366 "<TR><TD><A HREF=\"%s\">%s</A></TD>", u->url, u->url);
62e76326 367
368 if (urls[i].rtt > 0)
4391cd15 369 mb->appendf(
2fe7eff9 370 "<TD align=\"right\">%4d <it>ms</it></TD>", u->rtt);
62e76326 371 else
4391cd15 372 mb->appendf("<TD align=\"right\">Unknown</TD>");
62e76326 373
4391cd15 374 mb->appendf("<TD>%s</TD></TR>\n", u->flags.cached ? " [cached]" : " ");
cf26e54c 375 }
62e76326 376
4391cd15 377 mb->appendf(
2fe7eff9 378 "</TABLE>"
379 "<HR noshade size=\"1px\">\n"
380 "<ADDRESS>\n"
381 "Generated by %s@%s\n"
382 "</ADDRESS>\n",
7dbca7a4 383 APP_FULLNAME, getMyHostname());
06a5ae20 384 rep = new HttpReply;
f11c8e2f 385 rep->setHeaders(Http::scFound, NULL, "text/html", mb->contentSize(), 0, squid_curtime);
62e76326 386
b515fc11 387 if (urnState->flags.force_menu) {
bf8fe701 388 debugs(51, 3, "urnHandleReply: forcing menu");
9ce5e3e6 389 } else if (min_u) {
789217a2 390 rep->header.putStr(Http::HdrType::LOCATION, min_u->url);
cb69b4c7 391 }
62e76326 392
0521f8be 393 rep->body.setMb(mb);
032785bf 394 /* don't clean or delete mb; rep->body owns it now */
db237875 395 e->replaceHttpReply(rep);
528b2c61 396 e->complete();
62e76326 397
14942edd 398 for (i = 0; i < urlcnt; ++i) {
62e76326 399 safe_free(urls[i].url);
400 safe_free(urls[i].host);
9ce5e3e6 401 }
62e76326 402
9ce5e3e6 403 safe_free(urls);
96a5be3d 404 /* mb was absorbed in httpBodySet call, so we must not clean it */
06d2839d 405 storeUnregister(urnState->sc, urlres_e, urnState);
62e76326 406
bb9edbb2 407 urnHandleReplyError(urnState, urlres_e);
85491f8d 408}
409
9ce5e3e6 410static url_entry *
60745f24 411urnParseReply(const char *inbuf, const HttpRequestMethod& m)
85491f8d 412{
23d92c64 413 char *buf = xstrdup(inbuf);
414 char *token;
9ce5e3e6 415 char *host;
9ce5e3e6 416 url_entry *list;
417 url_entry *old;
418 int n = 32;
419 int i = 0;
bf8fe701 420 debugs(52, 3, "urnParseReply");
e6ccf245 421 list = (url_entry *)xcalloc(n + 1, sizeof(*list));
62e76326 422
23d92c64 423 for (token = strtok(buf, crlf); token; token = strtok(NULL, crlf)) {
bf8fe701 424 debugs(52, 3, "urnParseReply: got '" << token << "'");
62e76326 425
426 if (i == n) {
427 old = list;
428 n <<= 2;
429 list = (url_entry *)xcalloc(n + 1, sizeof(*list));
41d00cd3 430 memcpy(list, old, i * sizeof(*list));
62e76326 431 safe_free(old);
432 }
433
8babada0 434 host = urlHostname(token);
62e76326 435
436 if (NULL == host)
437 continue;
438
9b5c4a9a
AJ
439#if USE_ICMP
440 list[i].rtt = netdbHostRtt(host);
62e76326 441
9b5c4a9a 442 if (0 == list[i].rtt) {
bf8fe701 443 debugs(52, 3, "urnParseReply: Pinging " << host);
62e76326 444 netdbPingSite(host);
445 }
9b5c4a9a
AJ
446#else
447 list[i].rtt = 0;
448#endif
62e76326 449
8babada0 450 list[i].url = xstrdup(token);
62e76326 451 list[i].host = xstrdup(host);
5bd484b5
AR
452 // TODO: Use storeHas() or lock/unlock entry to avoid creating unlocked
453 // ones.
8babada0 454 list[i].flags.cached = storeGetPublic(list[i].url, m) ? 1 : 0;
14942edd 455 ++i;
85491f8d 456 }
62e76326 457
bf8fe701 458 debugs(52, 3, "urnParseReply: Found " << i << " URLs");
9ce5e3e6 459 return list;
85491f8d 460}
f53969cc 461