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