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