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