2 * Copyright (C) 1996-2025 The Squid Software Foundation and contributors
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.
9 /* DEBUG: section 52 URN Parsing */
12 #include "AccessLogEntry.h"
13 #include "acl/FilledChecklist.h"
14 #include "base/TextException.h"
16 #include "errorpage.h"
19 #include "HttpReply.h"
20 #include "HttpRequest.h"
21 #include "icmp/net_db.h"
23 #include "mime_header.h"
24 #include "RequestFlags.h"
26 #include "StoreClient.h"
30 class UrnState
: public StoreClient
32 CBDATA_CLASS(UrnState
);
35 explicit UrnState(const AccessLogEntry::Pointer
&anAle
): ale(anAle
) {}
37 void start (HttpRequest
*, StoreEntry
*);
38 void setUriResFromRequest(HttpRequest
*);
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
49 /// for receiving a URN resolver reply body from Store and interpreting it
50 Store::ParsingBuffer parsingBuffer
;
54 LogTags
*loggingTags() const override
{ return ale
? &ale
->cache
.code
: nullptr; }
55 void fillChecklist(ACLFilledChecklist
&) const override
;
57 char *urlres
= nullptr;
70 static STCB urnHandleReply
;
71 static url_entry
*urnParseReply(const SBuf
&, const HttpRequestMethod
&);
72 static const char *const crlf
= "\r\n";
74 CBDATA_CLASS_INIT(UrnState
);
81 storeUnregister(sc
, urlres_e
, this);
82 urlres_e
->unlock("~UrnState+res");
86 entry
->unlock("~UrnState+prime");
93 urnFindMinRtt(url_entry
* urls
, const HttpRequestMethod
&, int *rtt_ret
)
96 url_entry
*u
= nullptr;
97 url_entry
*min_u
= nullptr;
100 debugs(52, 3, "urnFindMinRtt");
101 assert(urls
!= nullptr);
103 for (i
= 0; nullptr != urls
[i
].url
; ++i
)
106 debugs(53, 3, "urnFindMinRtt: Counted " << i
<< " URLs");
109 debugs(52, 3, "urnFindMinRtt: Only one URL - return it!");
113 for (i
= 0; i
< urlcnt
; ++i
) {
115 debugs(52, 3, "urnFindMinRtt: " << u
->host
<< " rtt=" << u
->rtt
);
120 if (u
->rtt
> min_rtt
&& min_rtt
!= 0)
131 debugs(52, DBG_IMPORTANT
, "urnFindMinRtt: Returning '" <<
132 (min_u
? min_u
->url
: "NONE") << "' RTT " <<
139 UrnState::setUriResFromRequest(HttpRequest
*r
)
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
));
147 urlres_r
= HttpRequest::FromUrlXXX(local_urlres
, r
->masterXaction
);
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
);
157 urlres
= xstrdup(local_urlres
);
158 urlres_r
->header
.putStr(Http::HdrType::ACCEPT
, "text/plain");
162 UrnState::start(HttpRequest
* r
, StoreEntry
* e
)
164 debugs(52, 3, "urnStart: '" << e
->url() << "'" );
168 entry
->lock("UrnState::start");
169 setUriResFromRequest(r
);
171 if (urlres_r
== nullptr) {
176 auto urlEntry
= storeGetPublic(urlres
, Http::METHOD_GET
);
178 if (!urlEntry
|| (urlEntry
->hittingRequiresCollapsing() && !startCollapsingOn(*urlEntry
, false))) {
179 urlres_e
= storeCreateEntry(urlres
, urlres
, RequestFlags(), Http::METHOD_GET
);
180 sc
= storeClientListAdd(urlres_e
, this);
181 FwdState::Start(Comm::ConnectionPointer(), urlres_e
, urlres_r
.getRaw(), ale
);
183 urlEntry
->abandon(__func__
);
188 urlres_e
->lock(__func__
);
189 sc
= storeClientListAdd(urlres_e
, this);
192 storeClientCopy(sc
, urlres_e
,
193 parsingBuffer
.makeInitialSpace(),
199 UrnState::fillChecklist(ACLFilledChecklist
&checklist
) const
201 checklist
.setRequest(request
.getRaw());
206 urnStart(HttpRequest
*r
, StoreEntry
*e
, const AccessLogEntryPointer
&ale
)
208 const auto anUrn
= new UrnState(ale
);
213 url_entry_sort(const void *A
, const void *B
)
215 const url_entry
*u1
= (const url_entry
*)A
;
216 const url_entry
*u2
= (const url_entry
*)B
;
218 if (u2
->rtt
== u1
->rtt
)
220 else if (0 == u1
->rtt
)
222 else if (0 == u2
->rtt
)
225 return u1
->rtt
- u2
->rtt
;
228 /* TODO: use the clientStream support for this */
230 urnHandleReply(void *data
, StoreIOBuffer result
)
232 UrnState
*urnState
= static_cast<UrnState
*>(data
);
233 StoreEntry
*e
= urnState
->entry
;
234 StoreEntry
*urlres_e
= urnState
->urlres_e
;
242 debugs(52, 3, result
<< " with " << *e
);
244 if (EBIT_TEST(urlres_e
->flags
, ENTRY_ABORTED
) || result
.flags
.error
) {
249 if (!e
->isAccepting()) {
250 debugs(52, 3, "terminating due to bad " << *e
);
255 urnState
->parsingBuffer
.appended(result
.data
, result
.length
);
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
);
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.
269 storeClientCopy(urnState
->sc
, urlres_e
,
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
);
287 // XXX: Missing reply freshness checks (e.g., calling refreshCheckHTTP()).
289 urls
= urnParseReply(urnState
->parsingBuffer
.toSBuf(), urnState
->request
->method
);
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
);
300 for (i
= 0; urls
[i
].url
; ++i
)
303 debugs(53, 3, "urnFindMinRtt: Counted " << i
<< " URLs");
305 min_u
= urnFindMinRtt(urls
, urnState
->request
->method
, nullptr);
306 char *min_url
= nullptr;
308 min_url
= xstrdup(min_u
->url
);
311 qsort(urls
, urlcnt
, sizeof(*urls
), url_entry_sort
);
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());
320 for (i
= 0; i
< urlcnt
; ++i
) {
322 debugs(52, 3, "URL {" << u
->url
<< "}");
324 "<TR><TD><A HREF=\"%s\">%s</A></TD>", u
->url
, u
->url
);
328 "<TD align=\"right\">%4d <it>ms</it></TD>", u
->rtt
);
330 mb
->appendf("<TD align=\"right\">Unknown</TD>");
332 mb
->appendf("<TD>%s</TD></TR>\n", u
->flags
.cached
? " [cached]" : " ");
337 "<HR noshade size=\"1px\">\n"
339 "Generated by %s@%s\n"
341 visible_appname_string
, getMyHostname());
342 const auto rep
= new HttpReply
;
343 rep
->setHeaders(Http::scFound
, nullptr, "text/html", mb
->length(), 0, squid_curtime
);
346 rep
->header
.putStr(Http::HdrType::LOCATION
, min_url
);
351 e
->replaceHttpReply(rep
);
354 for (i
= 0; i
< urlcnt
; ++i
) {
355 safe_free(urls
[i
].url
);
356 safe_free(urls
[i
].host
);
365 urnParseReply(const SBuf
&inBuf
, const HttpRequestMethod
&m
)
372 debugs(52, 3, "urnParseReply");
373 list
= (url_entry
*)xcalloc(n
+ 1, sizeof(*list
));
375 // XXX: Switch to tokenizer-based parsing.
376 const auto allocated
= SBufToCstring(inBuf
);
378 auto buf
= allocated
;
379 while (xisspace(*buf
))
382 for (token
= strtok(buf
, crlf
); token
; token
= strtok(nullptr, crlf
)) {
383 debugs(52, 3, "urnParseReply: got '" << token
<< "'");
388 list
= (url_entry
*)xcalloc(n
+ 1, sizeof(*list
));
389 memcpy(list
, old
, i
* sizeof(*list
));
394 if (!uri
.parse(m
, SBuf(token
)) || !*uri
.host())
398 list
[i
].rtt
= netdbHostRtt(uri
.host());
400 if (0 == list
[i
].rtt
) {
401 debugs(52, 3, "Pinging " << uri
.host());
402 netdbPingSite(uri
.host());
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
412 list
[i
].flags
.cached
= storeGetPublic(list
[i
].url
, m
) ? 1 : 0;
416 debugs(52, 3, "urnParseReply: Found " << i
<< " URLs");