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 
30 class UrnState : public StoreClient
31 {
33 
34 public:
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 
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 
71 static url_entry *urnParseReply(const SBuf &, const HttpRequestMethod &);
72 static const char *const crlf = "\r\n";
73 
75 
77 {
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 
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
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);
159 }
160 
161 void
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))) {
180  sc = storeClientListAdd(urlres_e, this);
182  if (urlEntry) {
183  urlEntry->abandon(__func__);
184  urlEntry = nullptr;
185  }
186  } else {
187  urlres_e = urlEntry;
188  urlres_e->lock(__func__);
189  sc = storeClientListAdd(urlres_e, this);
190  }
191 
195  this);
196 }
197 
198 void
200 {
201  checklist.setRequest(request.getRaw());
202  checklist.al = ale;
203 }
204 
205 void
207 {
208  const auto anUrn = new UrnState(ale);
209  anUrn->start (r, e);
210 }
211 
212 static int
213 url_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 */
229 static void
230 urnHandleReply(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 
364 static url_entry *
365 urnParseReply(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 
void * xcalloc(size_t n, size_t sz)
Definition: xalloc.cc:71
AnyP::Uri url
the request URI
Definition: HttpRequest.h:115
Definition: Uri.h:31
void start(HttpRequest *, StoreEntry *)
Definition: urn.cc:162
#define LOCAL_ARRAY(type, name, size)
Definition: squid.h:62
HttpHeader header
Definition: Message.h:74
void errorAppendEntry(StoreEntry *entry, ErrorState *err)
Definition: errorpage.cc:738
const char * url() const
Definition: store.cc:1566
MemObject & mem()
Definition: Store.h:47
@ ENTRY_ABORTED
Definition: enums.h:110
void lock(const char *context)
Definition: store.cc:445
#define CBDATA_CLASS(type)
Definition: cbdata.h:289
bool parse(const HttpRequestMethod &, const SBuf &url)
Definition: Uri.cc:314
static url_entry * urnFindMinRtt(url_entry *urls, const HttpRequestMethod &, int *rtt_ret)
Definition: urn.cc:93
char * urlres
Definition: urn.cc:57
Definition: SBuf.h:93
void SBufToCstring(char *d, const SBuf &s)
Definition: SBuf.h:756
#define xstrdup
StoreIOBuffer & positionAt(const int64_t newOffset)
convenience method for changing the offset of a being-configured buffer
Definition: StoreIOBuffer.h:47
int cached
Definition: urn.cc:66
C * getRaw() const
Definition: RefCount.h:89
uint16_t flags
Definition: Store.h:231
AccessLogEntry::Pointer al
info for the future access.log, and external ACL
bool isAccepting() const
Definition: store.cc:1988
int netdbHostRtt(const char *host)
Definition: net_db.cc:944
void replaceHttpReply(const HttpReplyPointer &, const bool andStartWriting=true)
Definition: store.cc:1705
AccessLogEntry::Pointer ale
details of the requesting transaction
Definition: urn.cc:47
~UrnState() override
Definition: urn.cc:76
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
HttpRequest::Pointer request
Definition: urn.cc:45
HttpRequest::Pointer urlres_r
Definition: urn.cc:46
a storeGetPublic*() caller
Definition: StoreClient.h:40
#define SQUIDSBUFPRINT(s)
Definition: SBuf.h:32
static int url_entry_sort(const void *A, const void *B)
Definition: urn.cc:213
int rtt
Definition: urn.cc:63
const HttpReply & baseReply() const
Definition: MemObject.h:60
Definition: urn.cc:30
store_client * sc
Definition: urn.cc:43
void appended(const char *, size_t)
remember the new bytes received into the previously provided space()
static url_entry * urnParseReply(const SBuf &, const HttpRequestMethod &)
Definition: urn.cc:365
unsigned error
Definition: StoreIOBuffer.h:55
#define EBIT_TEST(flag, bit)
Definition: defines.h:67
struct url_entry::@127 flags
int unlock(const char *context)
Definition: store.cc:469
#define safe_free(x)
Definition: xalloc.h:73
const char * visible_appname_string
MasterXaction::Pointer masterXaction
the master transaction this request belongs to. Never nil.
Definition: HttpRequest.h:236
#define assert(EX)
Definition: assert.h:17
class AccessLogEntry::CacheDetails cache
UrnState(const AccessLogEntry::Pointer &anAle)
Definition: urn.cc:35
char * url
Definition: urn.cc:61
void buffer() override
Definition: store.cc:1601
const char * c_str()
Definition: SBuf.cc:516
#define CBDATA_CLASS_INIT(type)
Definition: cbdata.h:325
size_type length() const
Returns the number of bytes stored in SBuf.
Definition: SBuf.h:419
void setUriResFromRequest(HttpRequest *)
Definition: urn.cc:139
time_t squid_curtime
Definition: stub_libtime.cc:20
StoreEntry * storeCreateEntry(const char *url, const char *logUrl, const RequestFlags &flags, const HttpRequestMethod &method)
Definition: store.cc:759
#define xfree
StoreEntry * entry
Definition: urn.cc:42
bool startCollapsingOn(const StoreEntry &, const bool doingRevalidation) const
Definition: store_client.cc:66
static const char *const crlf
Definition: urn.cc:72
void complete()
Definition: store.cc:1031
void urnStart(HttpRequest *r, StoreEntry *e, const AccessLogEntryPointer &ale)
Definition: urn.cc:206
@ scFound
Definition: StatusCode.h:39
SBuf & absolute() const
Definition: Uri.cc:743
HttpRequestMethod method
Definition: HttpRequest.h:114
StoreIOBuffer makeInitialSpace()
Definition: ParsingBuffer.h:84
@ scNotFound
Definition: StatusCode.h:49
Store::ParsingBuffer parsingBuffer
for receiving a URN resolver reply body from Store and interpreting it
Definition: urn.cc:50
char * url
Definition: errorpage.h:178
void putStr(Http::HdrType id, const char *str)
Definition: HttpHeader.cc:1128
void setRequest(HttpRequest *)
configure client request-related fields for the first time
const char * getMyHostname(void)
Definition: tools.cc:468
static HttpRequest * FromUrlXXX(const char *url, const MasterXaction::Pointer &, const HttpRequestMethod &method=Http::METHOD_GET)
Definition: HttpRequest.cc:525
Definition: urn.cc:60
#define DBG_IMPORTANT
Definition: Stream.h:38
StoreEntry * storeGetPublic(const char *uri, const HttpRequestMethod &method)
Definition: store.cc:504
void fillChecklist(ACLFilledChecklist &) const override
configure the given checklist (to reflect the current transaction state)
Definition: urn.cc:199
char * host
Definition: urn.cc:62
SBuf toSBuf() const
export content() into SBuf, avoiding content copying when possible
LogTags * loggingTags() const override
Definition: urn.cc:54
bool atEof() const
Definition: StoreClient.h:106
void setHeaders(Http::StatusCode status, const char *reason, const char *ctype, int64_t clen, time_t lmt, time_t expires)
Definition: HttpReply.cc:170
#define xisspace(x)
Definition: xis.h:15
#define SWALLOW_EXCEPTIONS(code)
Definition: TextException.h:79
@ scOkay
Definition: StatusCode.h:27
void(void *, StoreIOBuffer) STCB
Definition: StoreClient.h:32
SBuf & appendf(const char *fmt,...) PRINTF_FORMAT_ARG2
Definition: SBuf.cc:229
@ ERR_URN_RESOLVE
Definition: forward.h:36
@ METHOD_GET
Definition: MethodType.h:25
StoreEntry * urlres_e
Definition: urn.cc:44
int storeUnregister(store_client *sc, StoreEntry *e, void *data)
void storeClientCopy(store_client *sc, StoreEntry *e, StoreIOBuffer copyInto, STCB *callback, void *data)
size_t contentSize() const
the total number of append()ed bytes that were not consume()d
void host(const char *src)
Definition: Uri.cc:142
StoreIOBuffer space()
struct StoreIOBuffer::@123 flags
void netdbPingSite(const char *hostname)
Definition: net_db.cc:811
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Stream.h:192
static STCB urnHandleReply
Definition: urn.cc:70
#define SQUIDSBUFPH
Definition: SBuf.h:31
store_client * storeClientListAdd(StoreEntry *e, void *data)

 

Introduction

Documentation

Support

Miscellaneous