Squid Web Cache master
Loading...
Searching...
No Matches
urn.cc
Go to the documentation of this file.
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
30class UrnState : public StoreClient
31{
33
34public:
35 explicit UrnState(const AccessLogEntry::Pointer &anAle): ale(anAle) {}
36
37 void start (HttpRequest *, StoreEntry *);
39
40 ~UrnState() override;
41
42 StoreEntry *entry = nullptr;
43 store_client *sc = nullptr;
44 StoreEntry *urlres_e = nullptr;
48
51
52private:
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
60typedef struct {
61 char *url;
62 char *host;
63 int rtt;
64
65 struct {
66 int cached;
67 } flags;
68} url_entry;
69
71static url_entry *urnParseReply(const SBuf &, const HttpRequestMethod &);
72static const char *const crlf = "\r\n";
73
75
77{
79 if (urlres_e) {
80 if (sc)
82 urlres_e->unlock("~UrnState+res");
83 }
84
85 if (entry)
86 entry->unlock("~UrnState+prime");
87
89 });
90}
91
92static url_entry *
93urnFindMinRtt(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
138void
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));
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);
154 return;
155 }
156
157 urlres = xstrdup(local_urlres);
159}
160
161void
163{
164 debugs(52, 3, "urnStart: '" << e->url() << "'" );
165 entry = e;
166 request = r;
167
168 entry->lock("UrnState::start");
170
171 if (urlres_r == nullptr) {
172 delete this;
173 return;
174 }
175
176 auto urlEntry = storeGetPublic(urlres, Http::METHOD_GET);
177
178 if (!urlEntry || (urlEntry->hittingRequiresCollapsing() && !startCollapsingOn(*urlEntry, false))) {
182 if (urlEntry) {
183 urlEntry->abandon(__func__);
184 urlEntry = nullptr;
185 }
186 } else {
187 urlres_e = urlEntry;
188 urlres_e->lock(__func__);
190 }
191
195 this);
196}
197
198void
200{
201 checklist.setRequest(request.getRaw());
202 checklist.al = ale;
203}
204
205void
207{
208 const auto anUrn = new UrnState(ale);
209 anUrn->start (r, e);
210}
211
212static int
213url_entry_sort(const void *A, const void *B)
214{
215 const url_entry *u1 = (const url_entry *)A;
216 const url_entry *u2 = (const url_entry *)B;
217
218 if (u2->rtt == u1->rtt)
219 return 0;
220 else if (0 == u1->rtt)
221 return 1;
222 else if (0 == u2->rtt)
223 return -1;
224 else
225 return u1->rtt - u2->rtt;
226}
227
228/* TODO: use the clientStream support for this */
229static void
230urnHandleReply(void *data, StoreIOBuffer result)
231{
232 UrnState *urnState = static_cast<UrnState *>(data);
233 StoreEntry *e = urnState->entry;
234 StoreEntry *urlres_e = urnState->urlres_e;
235 url_entry *urls;
236 url_entry *u;
237 url_entry *min_u;
238 ErrorState *err;
239 int i;
240 int urlcnt = 0;
241
242 debugs(52, 3, result << " with " << *e);
243
244 if (EBIT_TEST(urlres_e->flags, ENTRY_ABORTED) || result.flags.error) {
245 delete urnState;
246 return;
247 }
248
249 if (!e->isAccepting()) {
250 debugs(52, 3, "terminating due to bad " << *e);
251 delete urnState;
252 return;
253 }
254
255 urnState->parsingBuffer.appended(result.data, result.length);
256
257 /* If we haven't received the entire object (urn), copy more */
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
269 storeClientCopy(urnState->sc, urlres_e,
270 remainingSpace,
272 urnState);
273 return;
274 }
275
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) {
279 debugs(52, 3, "urnHandleReply: failed.");
280 err = new ErrorState(ERR_URN_RESOLVE, Http::scNotFound, urnState->request.getRaw(), urnState->ale);
281 err->url = xstrdup(e->url());
282 errorAppendEntry(e, err);
283 delete urnState;
284 return;
285 }
286
287 // XXX: Missing reply freshness checks (e.g., calling refreshCheckHTTP()).
288
289 urls = urnParseReply(urnState->parsingBuffer.toSBuf(), urnState->request->method);
290
291 if (!urls) { /* unknown URN error */
292 debugs(52, 3, "urnTranslateDone: unknown URN " << e->url());
293 err = new ErrorState(ERR_URN_RESOLVE, Http::scNotFound, urnState->request.getRaw(), urnState->ale);
294 err->url = xstrdup(e->url());
295 errorAppendEntry(e, err);
296 delete urnState;
297 return;
298 }
299
300 for (i = 0; urls[i].url; ++i)
301 ++urlcnt;
302
303 debugs(53, 3, "urnFindMinRtt: Counted " << i << " URLs");
304
305 min_u = urnFindMinRtt(urls, urnState->request->method, nullptr);
306 char *min_url = nullptr;
307 if (min_u) {
308 min_url = xstrdup(min_u->url);
309 }
310
311 qsort(urls, urlcnt, sizeof(*urls), url_entry_sort);
312 e->buffer();
313 SBuf body;
314 SBuf *mb = &body; // diff reduction hack; TODO: Remove
315 mb->appendf( "<TITLE>Select URL for %s</TITLE>\n"
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());
319
320 for (i = 0; i < urlcnt; ++i) {
321 u = &urls[i];
322 debugs(52, 3, "URL {" << u->url << "}");
323 mb->appendf(
324 "<TR><TD><A HREF=\"%s\">%s</A></TD>", u->url, u->url);
325
326 if (urls[i].rtt > 0)
327 mb->appendf(
328 "<TD align=\"right\">%4d <it>ms</it></TD>", u->rtt);
329 else
330 mb->appendf("<TD align=\"right\">Unknown</TD>");
331
332 mb->appendf("<TD>%s</TD></TR>\n", u->flags.cached ? " [cached]" : " ");
333 }
334
335 mb->appendf(
336 "</TABLE>"
337 "<HR noshade size=\"1px\">\n"
338 "<ADDRESS>\n"
339 "Generated by %s@%s\n"
340 "</ADDRESS>\n",
342 const auto rep = new HttpReply;
343 rep->setHeaders(Http::scFound, nullptr, "text/html", mb->length(), 0, squid_curtime);
344
345 if (min_url) {
346 rep->header.putStr(Http::HdrType::LOCATION, min_url);
347 safe_free(min_url);
348 }
349
350 rep->body.set(body);
351 e->replaceHttpReply(rep);
352 e->complete();
353
354 for (i = 0; i < urlcnt; ++i) {
355 safe_free(urls[i].url);
356 safe_free(urls[i].host);
357 }
358
359 safe_free(urls);
360
361 delete urnState;
362}
363
364static url_entry *
365urnParseReply(const SBuf &inBuf, const HttpRequestMethod &m)
366{
367 char *token;
368 url_entry *list;
369 url_entry *old;
370 int n = 32;
371 int i = 0;
372 debugs(52, 3, "urnParseReply");
373 list = (url_entry *)xcalloc(n + 1, sizeof(*list));
374
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
382 for (token = strtok(buf, crlf); token; token = strtok(nullptr, crlf)) {
383 debugs(52, 3, "urnParseReply: got '" << token << "'");
384
385 if (i == n) {
386 old = list;
387 n <<= 2;
388 list = (url_entry *)xcalloc(n + 1, sizeof(*list));
389 memcpy(list, old, i * sizeof(*list));
390 safe_free(old);
391 }
392
393 AnyP::Uri uri;
394 if (!uri.parse(m, SBuf(token)) || !*uri.host())
395 continue;
396
397#if USE_ICMP
398 list[i].rtt = netdbHostRtt(uri.host());
399
400 if (0 == list[i].rtt) {
401 debugs(52, 3, "Pinging " << uri.host());
402 netdbPingSite(uri.host());
403 }
404#else
405 list[i].rtt = 0;
406#endif
407
408 list[i].url = xstrdup(uri.absolute().c_str());
409 list[i].host = xstrdup(uri.host());
410 // TODO: Use storeHas() or lock/unlock entry to avoid creating unlocked
411 // ones.
412 list[i].flags.cached = storeGetPublic(list[i].url, m) ? 1 : 0;
413 ++i;
414 }
415
416 debugs(52, 3, "urnParseReply: Found " << i << " URLs");
417 xfree(allocated);
418 return list;
419}
420
time_t squid_curtime
#define SQUIDSBUFPH
Definition SBuf.h:31
void SBufToCstring(char *d, const SBuf &s)
Definition SBuf.h:756
#define SQUIDSBUFPRINT(s)
Definition SBuf.h:32
void(void *, StoreIOBuffer) STCB
Definition StoreClient.h:32
#define SWALLOW_EXCEPTIONS(code)
#define assert(EX)
Definition assert.h:17
#define CBDATA_CLASS_INIT(type)
Definition cbdata.h:325
#define CBDATA_CLASS(type)
Definition cbdata.h:289
void setRequest(HttpRequest *)
configure client request-related fields for the first time
AccessLogEntry::Pointer al
info for the future access.log, and external ACL
class AccessLogEntry::CacheDetails cache
SBuf & absolute() const
Definition Uri.cc:743
void host(const char *src)
Definition Uri.cc:154
bool parse(const HttpRequestMethod &, const SBuf &url)
Definition Uri.cc:326
char * url
Definition errorpage.h:178
static void Start(const Comm::ConnectionPointer &client, StoreEntry *, HttpRequest *, const AccessLogEntryPointer &alp)
Initiates request forwarding to a peer or origin server.
Definition FwdState.cc:338
void putStr(Http::HdrType id, const char *str)
void setHeaders(Http::StatusCode status, const char *reason, const char *ctype, int64_t clen, time_t lmt, time_t expires)
Definition HttpReply.cc:170
MasterXaction::Pointer masterXaction
the master transaction this request belongs to. Never nil.
HttpRequestMethod method
static HttpRequest * FromUrlXXX(const char *url, const MasterXaction::Pointer &, const HttpRequestMethod &method=Http::METHOD_GET)
AnyP::Uri url
the request URI
HttpHeader header
Definition Message.h:74
const HttpReply & baseReply() const
Definition MemObject.h:60
C * getRaw() const
Definition RefCount.h:89
Definition SBuf.h:94
const char * c_str()
Definition SBuf.cc:516
size_type length() const
Returns the number of bytes stored in SBuf.
Definition SBuf.h:419
SBuf & appendf(const char *fmt,...) PRINTF_FORMAT_ARG2
Definition SBuf.cc:229
a storeGetPublic*() caller
Definition StoreClient.h:41
bool startCollapsingOn(const StoreEntry &, const bool doingRevalidation) const
bool isAccepting() const
Definition store.cc:1988
uint16_t flags
Definition Store.h:231
MemObject & mem()
Definition Store.h:47
int unlock(const char *context)
Definition store.cc:469
const char * url() const
Definition store.cc:1566
void complete()
Definition store.cc:1031
void lock(const char *context)
Definition store.cc:445
void replaceHttpReply(const HttpReplyPointer &, const bool andStartWriting=true)
Definition store.cc:1705
void buffer() override
Definition store.cc:1601
StoreIOBuffer & positionAt(const int64_t newOffset)
convenience method for changing the offset of a being-configured buffer
struct StoreIOBuffer::@123 flags
size_t contentSize() const
the total number of append()ed bytes that were not consume()d
StoreIOBuffer space()
SBuf toSBuf() const
export content() into SBuf, avoiding content copying when possible
StoreIOBuffer makeInitialSpace()
void appended(const char *, size_t)
remember the new bytes received into the previously provided space()
AccessLogEntry::Pointer ale
details of the requesting transaction
Definition urn.cc:47
void setUriResFromRequest(HttpRequest *)
Definition urn.cc:139
HttpRequest::Pointer urlres_r
Definition urn.cc:46
StoreEntry * urlres_e
Definition urn.cc:44
void start(HttpRequest *, StoreEntry *)
Definition urn.cc:162
Store::ParsingBuffer parsingBuffer
for receiving a URN resolver reply body from Store and interpreting it
Definition urn.cc:50
char * urlres
Definition urn.cc:57
store_client * sc
Definition urn.cc:43
UrnState(const AccessLogEntry::Pointer &anAle)
Definition urn.cc:35
~UrnState() override
Definition urn.cc:76
StoreEntry * entry
Definition urn.cc:42
LogTags * loggingTags() const override
Definition urn.cc:54
void fillChecklist(ACLFilledChecklist &) const override
configure the given checklist (to reflect the current transaction state)
Definition urn.cc:199
HttpRequest::Pointer request
Definition urn.cc:45
bool atEof() const
#define DBG_IMPORTANT
Definition Stream.h:38
#define debugs(SECTION, LEVEL, CONTENT)
Definition Stream.h:192
#define EBIT_TEST(flag, bit)
Definition defines.h:67
@ ENTRY_ABORTED
Definition enums.h:110
@ ERR_URN_RESOLVE
Definition forward.h:36
char const * visible_appname_string
void errorAppendEntry(StoreEntry *entry, ErrorState *err)
Definition errorpage.cc:738
@ scFound
Definition StatusCode.h:39
@ scNotFound
Definition StatusCode.h:49
@ scOkay
Definition StatusCode.h:27
@ METHOD_GET
Definition MethodType.h:25
#define xfree
#define xstrdup
void netdbPingSite(const char *hostname)
Definition net_db.cc:811
int netdbHostRtt(const char *host)
Definition net_db.cc:944
#define LOCAL_ARRAY(type, name, size)
Definition squid.h:62
StoreEntry * storeCreateEntry(const char *url, const char *logUrl, const RequestFlags &flags, const HttpRequestMethod &method)
Definition store.cc:759
StoreEntry * storeGetPublic(const char *uri, const HttpRequestMethod &method)
Definition store.cc:504
int storeUnregister(store_client *sc, StoreEntry *e, void *data)
void storeClientCopy(store_client *sc, StoreEntry *e, StoreIOBuffer copyInto, STCB *callback, void *data)
store_client * storeClientListAdd(StoreEntry *e, void *data)
Definition urn.cc:60
int cached
Definition urn.cc:66
struct url_entry::@127 flags
int rtt
Definition urn.cc:63
char * host
Definition urn.cc:62
char * url
Definition urn.cc:61
const char * getMyHostname(void)
Definition tools.cc:468
static const char *const crlf
Definition urn.cc:72
void urnStart(HttpRequest *r, StoreEntry *e, const AccessLogEntryPointer &ale)
Definition urn.cc:206
static url_entry * urnParseReply(const SBuf &, const HttpRequestMethod &)
Definition urn.cc:365
static int url_entry_sort(const void *A, const void *B)
Definition urn.cc:213
static url_entry * urnFindMinRtt(url_entry *urls, const HttpRequestMethod &, int *rtt_ret)
Definition urn.cc:93
static STCB urnHandleReply
Definition urn.cc:70
void * xcalloc(size_t n, size_t sz)
Definition xalloc.cc:71
#define safe_free(x)
Definition xalloc.h:73
#define xisspace(x)
Definition xis.h:15