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