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