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