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