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