]>
Commit | Line | Data |
---|---|---|
edce4d98 | 1 | |
2 | /* | |
3f38a55e | 3 | * $Id: client_side_request.cc,v 1.17 2003/02/21 19:53:01 hno Exp $ |
69660be0 | 4 | * |
ae45c4de | 5 | * DEBUG: section 85 Client-side Request Routines |
6 | * AUTHOR: Robert Collins (Originally Duane Wessels in client_side.c) | |
69660be0 | 7 | * |
edce4d98 | 8 | * SQUID Web Proxy Cache http://www.squid-cache.org/ |
9 | * ---------------------------------------------------------- | |
69660be0 | 10 | * |
11 | * Squid is the result of efforts by numerous individuals from the Internet | |
12 | * community; see the CONTRIBUTORS file for full details. Many organizations | |
13 | * have provided support for Squid's development; see the SPONSORS file for | |
14 | * full details. Squid is Copyrighted (C) 2001 by the Regents of the | |
15 | * University of California; see the COPYRIGHT file for full details. Squid | |
16 | * incorporates software developed and/or copyrighted by other sources; see the | |
17 | * CREDITS file for full details. | |
18 | * | |
19 | * This program is free software; you can redistribute it and/or modify it under | |
20 | * the terms of the GNU General Public License as published by the Free | |
21 | * Software Foundation; either version 2 of the License, or (at your option) | |
22 | * any later version. | |
23 | * | |
24 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY | |
25 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
26 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more | |
27 | * details. | |
28 | * | |
29 | * You should have received a copy of the GNU General Public License along with | |
30 | * this program; if not, write to the Free Software Foundation, Inc., 59 Temple | |
31 | * Place, Suite 330, Boston, MA 02111, USA. | |
32 | * | |
edce4d98 | 33 | */ |
34 | ||
35 | ||
69660be0 | 36 | /* |
37 | * General logic of request processing: | |
38 | * | |
39 | * We run a series of tests to determine if access will be permitted, and to do | |
40 | * any redirection. Then we call into the result clientStream to retrieve data. | |
41 | * From that point on it's up to reply management. | |
edce4d98 | 42 | */ |
43 | ||
44 | #include "squid.h" | |
c8be6d7b | 45 | #include "clientStream.h" |
46 | #include "client_side_request.h" | |
e6ccf245 | 47 | #include "authenticate.h" |
528b2c61 | 48 | #include "HttpRequest.h" |
8000a965 | 49 | #include "ACLChecklist.h" |
50 | #include "ACL.h" | |
edce4d98 | 51 | |
52 | #if LINGERING_CLOSE | |
53 | #define comm_close comm_lingering_close | |
54 | #endif | |
55 | ||
56 | static const char *const crlf = "\r\n"; | |
57 | ||
8e2745f4 | 58 | class ClientRequestContext { |
59 | public: | |
60 | void *operator new(size_t); | |
61 | void operator delete(void *); | |
62 | void deleteSelf() const; | |
63 | ||
64 | ClientRequestContext(); | |
65 | ClientRequestContext(ClientHttpRequest *); | |
66 | ~ClientRequestContext(); | |
67 | ||
68 | void checkNoCache(); | |
69 | ||
4fb35c3c | 70 | ACLChecklist *acl_checklist; /* need ptr back so we can unreg if needed */ |
edce4d98 | 71 | int redirect_state; |
72 | clientHttpRequest *http; | |
8e2745f4 | 73 | private: |
74 | CBDATA_CLASS(ClientRequestContext); | |
75 | static void CheckNoCacheDone(int answer, void *data); | |
76 | void checkNoCacheDone(int answer); | |
77 | }; | |
edce4d98 | 78 | |
8e2745f4 | 79 | CBDATA_CLASS_INIT(ClientRequestContext); |
80 | ||
81 | void * | |
82 | ClientRequestContext::operator new (size_t size) | |
83 | { | |
84 | assert (size == sizeof(ClientRequestContext)); | |
85 | CBDATA_INIT_TYPE(ClientRequestContext); | |
86 | ClientRequestContext *result = cbdataAlloc(ClientRequestContext); | |
87 | /* Mark result as being owned - we want the refcounter to do the delete | |
88 | * call */ | |
89 | cbdataReference(result); | |
90 | return result; | |
91 | } | |
92 | ||
93 | void | |
94 | ClientRequestContext::operator delete (void *address) | |
95 | { | |
96 | ClientRequestContext *t = static_cast<ClientRequestContext *>(address); | |
97 | cbdataFree(address); | |
98 | /* And allow the memory to be freed */ | |
99 | cbdataReferenceDone (t); | |
100 | } | |
101 | ||
102 | void | |
103 | ClientRequestContext::deleteSelf() const | |
104 | { | |
105 | delete this; | |
106 | } | |
edce4d98 | 107 | |
108 | /* Local functions */ | |
edce4d98 | 109 | /* other */ |
edce4d98 | 110 | static void clientAccessCheckDone(int, void *); |
edce4d98 | 111 | static int clientCachable(clientHttpRequest * http); |
112 | static int clientHierarchical(clientHttpRequest * http); | |
113 | static void clientInterpretRequestHeaders(clientHttpRequest * http); | |
114 | static RH clientRedirectDone; | |
e6ccf245 | 115 | extern "C" CSR clientGetMoreData; |
116 | extern "C" CSS clientReplyStatus; | |
117 | extern "C" CSD clientReplyDetach; | |
528b2c61 | 118 | static void checkFailureRatio(err_type, hier_code); |
edce4d98 | 119 | |
8e2745f4 | 120 | ClientRequestContext::~ClientRequestContext() |
121 | { | |
122 | if (http) | |
123 | cbdataReferenceDone(http); | |
124 | if (acl_checklist) | |
8000a965 | 125 | delete acl_checklist; |
8e2745f4 | 126 | } |
127 | ||
128 | ClientRequestContext::ClientRequestContext() : acl_checklist (NULL), redirect_state (REDIRECT_NONE), http(NULL) | |
edce4d98 | 129 | { |
edce4d98 | 130 | } |
131 | ||
8e2745f4 | 132 | ClientRequestContext::ClientRequestContext(ClientHttpRequest *newHttp) : acl_checklist (NULL), redirect_state (REDIRECT_NONE), http(cbdataReference(newHttp)) |
edce4d98 | 133 | { |
8e2745f4 | 134 | assert (newHttp != NULL); |
edce4d98 | 135 | } |
136 | ||
528b2c61 | 137 | CBDATA_CLASS_INIT(ClientHttpRequest); |
8e2745f4 | 138 | |
528b2c61 | 139 | void * |
140 | ClientHttpRequest::operator new (size_t size) | |
141 | { | |
142 | assert (size == sizeof (ClientHttpRequest)); | |
143 | CBDATA_INIT_TYPE(ClientHttpRequest); | |
144 | ClientHttpRequest *result = cbdataAlloc(ClientHttpRequest); | |
145 | /* Mark result as being owned - we want the refcounter to do the delete | |
146 | * call */ | |
147 | cbdataReference(result); | |
148 | return result; | |
149 | } | |
150 | ||
151 | void | |
152 | ClientHttpRequest::operator delete (void *address) | |
153 | { | |
154 | ClientHttpRequest *temp = static_cast<ClientHttpRequest *>(address); | |
155 | cbdataFree(address); | |
156 | /* And allow the memory to be freed */ | |
157 | cbdataReferenceDone (temp); | |
158 | } | |
159 | ||
160 | void | |
161 | ClientHttpRequest::deleteSelf() const | |
162 | { | |
163 | delete this; | |
164 | } | |
165 | ||
166 | ClientHttpRequest::ClientHttpRequest() | |
167 | { | |
168 | /* reset range iterator */ | |
169 | start = current_time; | |
170 | } | |
171 | ||
172 | /* | |
173 | * This function is designed to serve a fairly specific purpose. | |
174 | * Occasionally our vBNS-connected caches can talk to each other, but not | |
175 | * the rest of the world. Here we try to detect frequent failures which | |
176 | * make the cache unusable (e.g. DNS lookup and connect() failures). If | |
177 | * the failure:success ratio goes above 1.0 then we go into "hit only" | |
178 | * mode where we only return UDP_HIT or UDP_MISS_NOFETCH. Neighbors | |
179 | * will only fetch HITs from us if they are using the ICP protocol. We | |
180 | * stay in this mode for 5 minutes. | |
181 | * | |
182 | * Duane W., Sept 16, 1996 | |
183 | */ | |
184 | ||
185 | #define FAILURE_MODE_TIME 300 | |
186 | ||
187 | static void | |
188 | checkFailureRatio(err_type etype, hier_code hcode) | |
189 | { | |
190 | static double magic_factor = 100.0; | |
191 | double n_good; | |
192 | double n_bad; | |
193 | if (hcode == HIER_NONE) | |
194 | return; | |
195 | n_good = magic_factor / (1.0 + request_failure_ratio); | |
196 | n_bad = magic_factor - n_good; | |
197 | switch (etype) { | |
198 | case ERR_DNS_FAIL: | |
199 | case ERR_CONNECT_FAIL: | |
200 | case ERR_READ_ERROR: | |
201 | n_bad++; | |
202 | break; | |
203 | default: | |
204 | n_good++; | |
205 | } | |
206 | request_failure_ratio = n_bad / n_good; | |
207 | if (hit_only_mode_until > squid_curtime) | |
208 | return; | |
209 | if (request_failure_ratio < 1.0) | |
210 | return; | |
211 | debug(33, 0) ("Failure Ratio at %4.2f\n", request_failure_ratio); | |
212 | debug(33, 0) ("Going into hit-only-mode for %d minutes...\n", | |
213 | FAILURE_MODE_TIME / 60); | |
214 | hit_only_mode_until = squid_curtime + FAILURE_MODE_TIME; | |
215 | request_failure_ratio = 0.8; /* reset to something less than 1.0 */ | |
216 | } | |
217 | ||
218 | ClientHttpRequest::~ClientHttpRequest() | |
219 | { | |
220 | debug(33, 3) ("httpRequestFree: %s\n", uri); | |
221 | /* if body_connection !NULL, then ProcessBody has not | |
222 | * found the end of the body yet | |
223 | */ | |
224 | if (request && request->body_connection) | |
225 | clientAbortBody(request); /* abort body transter */ | |
226 | /* the ICP check here was erroneous | |
227 | * - storeReleaseRequest was always called if entry was valid | |
228 | */ | |
229 | assert(logType < LOG_TYPE_MAX); | |
230 | logRequest(); | |
231 | if (request) | |
232 | checkFailureRatio(request->errType, al.hier.code); | |
233 | freeResources(); | |
234 | /* moving to the next connection is handled by the context free */ | |
235 | dlinkDelete(&active, &ClientActiveRequests); | |
236 | } | |
237 | ||
edce4d98 | 238 | /* Create a request and kick it off */ |
69660be0 | 239 | /* |
240 | * TODO: Pass in the buffers to be used in the inital Read request, as they are | |
241 | * determined by the user | |
edce4d98 | 242 | */ |
243 | int /* returns nonzero on failure */ | |
244 | clientBeginRequest(method_t method, char const *url, CSCB * streamcallback, | |
245 | CSD * streamdetach, void *streamdata, HttpHeader const *header, | |
246 | char *tailbuf, size_t taillen) | |
247 | { | |
248 | size_t url_sz; | |
249 | http_version_t http_ver = | |
250 | {1, 0}; | |
528b2c61 | 251 | clientHttpRequest *http = new ClientHttpRequest; |
edce4d98 | 252 | request_t *request; |
528b2c61 | 253 | StoreIOBuffer tempBuffer; |
edce4d98 | 254 | http->http_ver = http_ver; |
255 | http->conn = NULL; | |
256 | http->start = current_time; | |
257 | /* this is only used to adjust the connection offset in client_side.c */ | |
258 | http->req_sz = 0; | |
c8be6d7b | 259 | tempBuffer.length = taillen; |
260 | tempBuffer.data = tailbuf; | |
edce4d98 | 261 | /* client stream setup */ |
262 | clientStreamInit(&http->client_stream, clientGetMoreData, clientReplyDetach, | |
263 | clientReplyStatus, clientReplyNewContext(http), streamcallback, | |
c8be6d7b | 264 | streamdetach, streamdata, tempBuffer); |
edce4d98 | 265 | /* make it visible in the 'current acctive requests list' */ |
266 | dlinkAdd(http, &http->active, &ClientActiveRequests); | |
267 | /* Set flags */ | |
69660be0 | 268 | http->flags.accel = 1; /* internal requests only makes sense in an |
edce4d98 | 269 | * accelerator today. TODO: accept flags ? */ |
270 | /* allow size for url rewriting */ | |
271 | url_sz = strlen(url) + Config.appendDomainLen + 5; | |
e6ccf245 | 272 | http->uri = (char *)xcalloc(url_sz, 1); |
edce4d98 | 273 | strcpy(http->uri, url); |
274 | ||
275 | if ((request = urlParse(method, http->uri)) == NULL) { | |
276 | debug(85, 5) ("Invalid URL: %s\n", http->uri); | |
277 | return -1; | |
278 | } | |
69660be0 | 279 | /* |
280 | * now update the headers in request with our supplied headers. urLParse | |
281 | * should return a blank header set, but we use Update to be sure of | |
282 | * correctness. | |
edce4d98 | 283 | */ |
284 | if (header) | |
285 | httpHeaderUpdate(&request->header, header, NULL); | |
286 | http->log_uri = xstrdup(urlCanonicalClean(request)); | |
287 | /* http struct now ready */ | |
288 | ||
69660be0 | 289 | /* |
290 | * build new header list *? TODO | |
edce4d98 | 291 | */ |
292 | request->flags.accelerated = http->flags.accel; | |
69660be0 | 293 | request->flags.internalclient = 1; /* this is an internally created |
294 | * request, not subject to acceleration | |
295 | * target overrides */ | |
296 | /* | |
297 | * FIXME? Do we want to detect and handle internal requests of internal | |
298 | * objects ? | |
299 | */ | |
edce4d98 | 300 | |
301 | /* Internally created requests cannot have bodies today */ | |
302 | request->content_length = 0; | |
303 | request->client_addr = no_addr; | |
304 | request->my_addr = no_addr; /* undefined for internal requests */ | |
305 | request->my_port = 0; | |
306 | request->http_ver = http_ver; | |
307 | http->request = requestLink(request); | |
308 | ||
309 | /* optional - skip the access check ? */ | |
310 | clientAccessCheck(http); | |
311 | return 0; | |
312 | } | |
313 | ||
edce4d98 | 314 | /* This is the entry point for external users of the client_side routines */ |
315 | void | |
8e2745f4 | 316 | clientAccessCheck(ClientHttpRequest *http) |
edce4d98 | 317 | { |
8e2745f4 | 318 | ClientRequestContext *context = new ClientRequestContext(http); |
edce4d98 | 319 | context->acl_checklist = |
320 | clientAclChecklistCreate(Config.accessList.http, http); | |
225b7b10 | 321 | context->acl_checklist->nonBlockingCheck(clientAccessCheckDone, context); |
edce4d98 | 322 | } |
323 | ||
324 | void | |
325 | clientAccessCheckDone(int answer, void *data) | |
326 | { | |
8e2745f4 | 327 | ClientRequestContext *context = (ClientRequestContext *)data; |
fbade053 | 328 | |
329 | clientHttpRequest *http_ = context->http; | |
330 | ||
331 | if (!cbdataReferenceValid (http_)) { | |
332 | context->deleteSelf(); | |
333 | return; | |
334 | } | |
335 | ||
edce4d98 | 336 | clientHttpRequest *http = context->http; |
337 | err_type page_id; | |
338 | http_status status; | |
e6ccf245 | 339 | char const *proxy_auth_msg = NULL; |
edce4d98 | 340 | debug(85, 2) ("The request %s %s is %s, because it matched '%s'\n", |
341 | RequestMethodStr[http->request->method], http->uri, | |
342 | answer == ACCESS_ALLOWED ? "ALLOWED" : "DENIED", | |
343 | AclMatchedName ? AclMatchedName : "NO ACL's"); | |
344 | proxy_auth_msg = authenticateAuthUserRequestMessage((http->conn | |
345 | && http->conn->auth_user_request) ? http->conn-> | |
346 | auth_user_request : http->request->auth_user_request); | |
347 | context->acl_checklist = NULL; | |
348 | if (answer == ACCESS_ALLOWED) { | |
349 | safe_free(http->uri); | |
350 | http->uri = xstrdup(urlCanonical(http->request)); | |
351 | assert(context->redirect_state == REDIRECT_NONE); | |
352 | context->redirect_state = REDIRECT_PENDING; | |
353 | redirectStart(http, clientRedirectDone, context); | |
354 | } else { | |
355 | /* Send an error */ | |
e6ccf245 | 356 | clientStreamNode *node = (clientStreamNode *)http->client_stream.tail->prev->data; |
8e2745f4 | 357 | context->deleteSelf(); |
edce4d98 | 358 | debug(85, 5) ("Access Denied: %s\n", http->uri); |
359 | debug(85, 5) ("AclMatchedName = %s\n", | |
360 | AclMatchedName ? AclMatchedName : "<null>"); | |
361 | debug(85, 5) ("Proxy Auth Message = %s\n", | |
362 | proxy_auth_msg ? proxy_auth_msg : "<null>"); | |
363 | /* | |
69660be0 | 364 | * NOTE: get page_id here, based on AclMatchedName because if |
365 | * USE_DELAY_POOLS is enabled, then AclMatchedName gets clobbered in | |
366 | * the clientCreateStoreEntry() call just below. Pedro Ribeiro | |
367 | * <pribeiro@isel.pt> | |
edce4d98 | 368 | */ |
369 | page_id = aclGetDenyInfoPage(&Config.denyInfoList, AclMatchedName); | |
29b8d8d6 | 370 | http->logType = LOG_TCP_DENIED; |
edce4d98 | 371 | if (answer == ACCESS_REQ_PROXY_AUTH || aclIsProxyAuth(AclMatchedName)) { |
372 | if (!http->flags.accel) { | |
373 | /* Proxy authorisation needed */ | |
374 | status = HTTP_PROXY_AUTHENTICATION_REQUIRED; | |
375 | } else { | |
376 | /* WWW authorisation needed */ | |
377 | status = HTTP_UNAUTHORIZED; | |
378 | } | |
379 | if (page_id == ERR_NONE) | |
380 | page_id = ERR_CACHE_ACCESS_DENIED; | |
381 | } else { | |
382 | status = HTTP_FORBIDDEN; | |
383 | if (page_id == ERR_NONE) | |
384 | page_id = ERR_ACCESS_DENIED; | |
385 | } | |
386 | clientSetReplyToError(node->data, page_id, status, | |
387 | http->request->method, NULL, | |
388 | http->conn ? &http->conn->peer.sin_addr : &no_addr, http->request, | |
389 | NULL, http->conn | |
390 | && http->conn->auth_user_request ? http->conn-> | |
391 | auth_user_request : http->request->auth_user_request); | |
e6ccf245 | 392 | node = (clientStreamNode *)http->client_stream.tail->data; |
c8be6d7b | 393 | clientStreamRead(node, http, node->readBuffer); |
edce4d98 | 394 | } |
395 | } | |
396 | ||
397 | static int | |
398 | clientCachable(clientHttpRequest * http) | |
399 | { | |
400 | request_t *req = http->request; | |
401 | method_t method = req->method; | |
402 | if (req->protocol == PROTO_HTTP) | |
403 | return httpCachable(method); | |
404 | /* FTP is always cachable */ | |
405 | if (req->protocol == PROTO_WAIS) | |
406 | return 0; | |
69660be0 | 407 | /* |
408 | * The below looks questionable: what non HTTP protocols use connect, | |
409 | * trace, put and post? RC | |
edce4d98 | 410 | */ |
411 | if (method == METHOD_CONNECT) | |
412 | return 0; | |
413 | if (method == METHOD_TRACE) | |
414 | return 0; | |
415 | if (method == METHOD_PUT) | |
416 | return 0; | |
417 | if (method == METHOD_POST) | |
69660be0 | 418 | return 0; /* XXX POST may be cached sometimes.. ignored |
419 | * for now */ | |
edce4d98 | 420 | if (req->protocol == PROTO_GOPHER) |
421 | return gopherCachable(req); | |
422 | if (req->protocol == PROTO_CACHEOBJ) | |
423 | return 0; | |
424 | return 1; | |
425 | } | |
426 | ||
427 | static int | |
428 | clientHierarchical(clientHttpRequest * http) | |
429 | { | |
430 | const char *url = http->uri; | |
431 | request_t *request = http->request; | |
432 | method_t method = request->method; | |
433 | const wordlist *p = NULL; | |
434 | ||
69660be0 | 435 | /* |
436 | * IMS needs a private key, so we can use the hierarchy for IMS only if our | |
437 | * neighbors support private keys | |
438 | */ | |
edce4d98 | 439 | if (request->flags.ims && !neighbors_do_private_keys) |
440 | return 0; | |
69660be0 | 441 | /* |
442 | * This is incorrect: authenticating requests can be sent via a hierarchy | |
443 | * (they can even be cached if the correct headers are set on the reply | |
edce4d98 | 444 | */ |
445 | if (request->flags.auth) | |
446 | return 0; | |
447 | if (method == METHOD_TRACE) | |
448 | return 1; | |
449 | if (method != METHOD_GET) | |
450 | return 0; | |
451 | /* scan hierarchy_stoplist */ | |
452 | for (p = Config.hierarchy_stoplist; p; p = p->next) | |
453 | if (strstr(url, p->key)) | |
454 | return 0; | |
455 | if (request->flags.loopdetect) | |
456 | return 0; | |
457 | if (request->protocol == PROTO_HTTP) | |
458 | return httpCachable(method); | |
459 | if (request->protocol == PROTO_GOPHER) | |
460 | return gopherCachable(request); | |
461 | if (request->protocol == PROTO_WAIS) | |
462 | return 0; | |
463 | if (request->protocol == PROTO_CACHEOBJ) | |
464 | return 0; | |
465 | return 1; | |
466 | } | |
467 | ||
468 | ||
469 | static void | |
470 | clientInterpretRequestHeaders(clientHttpRequest * http) | |
471 | { | |
472 | request_t *request = http->request; | |
473 | const HttpHeader *req_hdr = &request->header; | |
474 | int no_cache = 0; | |
475 | #if !defined(ESI) || defined(USE_USERAGENT_LOG) || defined(USE_REFERER_LOG) | |
476 | const char *str; | |
477 | #endif | |
478 | request->imslen = -1; | |
479 | request->ims = httpHeaderGetTime(req_hdr, HDR_IF_MODIFIED_SINCE); | |
480 | if (request->ims > 0) | |
481 | request->flags.ims = 1; | |
482 | #if ESI | |
69660be0 | 483 | /* |
484 | * We ignore Cache-Control as per the Edge Architecture Section 3. See | |
485 | * www.esi.org for more information. | |
edce4d98 | 486 | */ |
487 | #else | |
488 | if (httpHeaderHas(req_hdr, HDR_PRAGMA)) { | |
489 | String s = httpHeaderGetList(req_hdr, HDR_PRAGMA); | |
490 | if (strListIsMember(&s, "no-cache", ',')) | |
491 | no_cache++; | |
528b2c61 | 492 | s.clean(); |
edce4d98 | 493 | } |
494 | request->cache_control = httpHeaderGetCc(req_hdr); | |
495 | if (request->cache_control) | |
496 | if (EBIT_TEST(request->cache_control->mask, CC_NO_CACHE)) | |
497 | no_cache++; | |
69660be0 | 498 | /* |
499 | * Work around for supporting the Reload button in IE browsers when Squid | |
500 | * is used as an accelerator or transparent proxy, by turning accelerated | |
501 | * IMS request to no-cache requests. Now knows about IE 5.5 fix (is | |
502 | * actually only fixed in SP1, but we can't tell whether we are talking to | |
503 | * SP1 or not so all 5.5 versions are treated 'normally'). | |
edce4d98 | 504 | */ |
505 | if (Config.onoff.ie_refresh) { | |
506 | if (http->flags.accel && request->flags.ims) { | |
507 | if ((str = httpHeaderGetStr(req_hdr, HDR_USER_AGENT))) { | |
508 | if (strstr(str, "MSIE 5.01") != NULL) | |
509 | no_cache++; | |
510 | else if (strstr(str, "MSIE 5.0") != NULL) | |
511 | no_cache++; | |
512 | else if (strstr(str, "MSIE 4.") != NULL) | |
513 | no_cache++; | |
514 | else if (strstr(str, "MSIE 3.") != NULL) | |
515 | no_cache++; | |
516 | } | |
517 | } | |
518 | } | |
519 | #endif | |
520 | if (no_cache) { | |
521 | #if HTTP_VIOLATIONS | |
522 | if (Config.onoff.reload_into_ims) | |
523 | request->flags.nocache_hack = 1; | |
524 | else if (refresh_nocache_hack) | |
525 | request->flags.nocache_hack = 1; | |
526 | else | |
527 | #endif | |
528 | request->flags.nocache = 1; | |
529 | } | |
530 | /* ignore range header in non-GETs */ | |
531 | if (request->method == METHOD_GET) { | |
edce4d98 | 532 | request->range = httpHeaderGetRange(req_hdr); |
533 | if (request->range) { | |
534 | request->flags.range = 1; | |
528b2c61 | 535 | clientStreamNode *node = (clientStreamNode *)http->client_stream.tail->data; |
536 | /* XXX: This is suboptimal. We should give the stream the range set, | |
537 | * and thereby let the top of the stream set the offset when the | |
538 | * size becomes known. As it is, we will end up requesting from 0 | |
539 | * for evey -X range specification. | |
540 | * RBC - this may be somewhat wrong. We should probably set the range | |
541 | * iter up at this point. | |
542 | */ | |
543 | node->readBuffer.offset = request->range->lowestOffset(0); | |
544 | http->range_iter.pos = request->range->begin(); | |
545 | http->range_iter.valid = true; | |
edce4d98 | 546 | } |
547 | } | |
548 | if (httpHeaderHas(req_hdr, HDR_AUTHORIZATION)) | |
549 | request->flags.auth = 1; | |
550 | if (request->login[0] != '\0') | |
551 | request->flags.auth = 1; | |
552 | if (httpHeaderHas(req_hdr, HDR_VIA)) { | |
553 | String s = httpHeaderGetList(req_hdr, HDR_VIA); | |
554 | /* | |
555 | * ThisCache cannot be a member of Via header, "1.0 ThisCache" can. | |
556 | * Note ThisCache2 has a space prepended to the hostname so we don't | |
557 | * accidentally match super-domains. | |
558 | */ | |
559 | if (strListIsSubstr(&s, ThisCache2, ',')) { | |
560 | debugObj(33, 1, "WARNING: Forwarding loop detected for:\n", | |
561 | request, (ObjPackMethod) & httpRequestPack); | |
562 | request->flags.loopdetect = 1; | |
563 | } | |
564 | #if FORW_VIA_DB | |
528b2c61 | 565 | fvdbCountVia(s.buf()); |
edce4d98 | 566 | #endif |
528b2c61 | 567 | s.clean(); |
edce4d98 | 568 | } |
569 | #if USE_USERAGENT_LOG | |
570 | if ((str = httpHeaderGetStr(req_hdr, HDR_USER_AGENT))) | |
ff1e79fd | 571 | logUserAgent(fqdnFromAddr(http->conn ? http->conn->log_addr : no_addr), str); |
edce4d98 | 572 | #endif |
573 | #if USE_REFERER_LOG | |
574 | if ((str = httpHeaderGetStr(req_hdr, HDR_REFERER))) | |
ff1e79fd | 575 | logReferer(fqdnFromAddr(http->conn ? http->conn->log_addr : no_addr), str, http->log_uri); |
edce4d98 | 576 | #endif |
577 | #if FORW_VIA_DB | |
578 | if (httpHeaderHas(req_hdr, HDR_X_FORWARDED_FOR)) { | |
579 | String s = httpHeaderGetList(req_hdr, HDR_X_FORWARDED_FOR); | |
528b2c61 | 580 | fvdbCountForw(s.buf()); |
581 | s.clean(); | |
edce4d98 | 582 | } |
583 | #endif | |
584 | if (request->method == METHOD_TRACE) { | |
585 | request->max_forwards = httpHeaderGetInt(req_hdr, HDR_MAX_FORWARDS); | |
586 | } | |
587 | if (clientCachable(http)) | |
588 | request->flags.cachable = 1; | |
589 | if (clientHierarchical(http)) | |
590 | request->flags.hierarchical = 1; | |
591 | debug(85, 5) ("clientInterpretRequestHeaders: REQ_NOCACHE = %s\n", | |
592 | request->flags.nocache ? "SET" : "NOT SET"); | |
593 | debug(85, 5) ("clientInterpretRequestHeaders: REQ_CACHABLE = %s\n", | |
594 | request->flags.cachable ? "SET" : "NOT SET"); | |
595 | debug(85, 5) ("clientInterpretRequestHeaders: REQ_HIERARCHICAL = %s\n", | |
596 | request->flags.hierarchical ? "SET" : "NOT SET"); | |
597 | } | |
598 | ||
599 | void | |
600 | clientRedirectDone(void *data, char *result) | |
601 | { | |
8e2745f4 | 602 | ClientRequestContext *context = (ClientRequestContext *)data; |
db02222f | 603 | clientHttpRequest *http_ = context->http; |
604 | ||
605 | if (!cbdataReferenceValid (http_)) { | |
606 | context->deleteSelf(); | |
607 | return; | |
608 | } | |
609 | ||
edce4d98 | 610 | clientHttpRequest *http = context->http; |
611 | request_t *new_request = NULL; | |
612 | request_t *old_request = http->request; | |
613 | debug(85, 5) ("clientRedirectDone: '%s' result=%s\n", http->uri, | |
614 | result ? result : "NULL"); | |
615 | assert(context->redirect_state == REDIRECT_PENDING); | |
616 | context->redirect_state = REDIRECT_DONE; | |
617 | if (result) { | |
618 | http_status status = (http_status) atoi(result); | |
619 | if (status == HTTP_MOVED_PERMANENTLY | |
69660be0 | 620 | || status == HTTP_MOVED_TEMPORARILY |
621 | || status == HTTP_SEE_OTHER | |
622 | || status == HTTP_TEMPORARY_REDIRECT) { | |
edce4d98 | 623 | char *t = result; |
624 | if ((t = strchr(result, ':')) != NULL) { | |
625 | http->redirect.status = status; | |
626 | http->redirect.location = xstrdup(t + 1); | |
627 | } else { | |
628 | debug(85, 1) ("clientRedirectDone: bad input: %s\n", result); | |
629 | } | |
630 | } | |
631 | if (strcmp(result, http->uri)) | |
632 | new_request = urlParse(old_request->method, result); | |
633 | } | |
634 | if (new_request) { | |
635 | safe_free(http->uri); | |
636 | http->uri = xstrdup(urlCanonical(new_request)); | |
637 | new_request->http_ver = old_request->http_ver; | |
638 | httpHeaderAppend(&new_request->header, &old_request->header); | |
639 | new_request->client_addr = old_request->client_addr; | |
640 | new_request->my_addr = old_request->my_addr; | |
641 | new_request->my_port = old_request->my_port; | |
3f38a55e | 642 | new_request->flags = old_request->flags; |
edce4d98 | 643 | if (old_request->auth_user_request) { |
644 | new_request->auth_user_request = old_request->auth_user_request; | |
645 | authenticateAuthUserRequestLock(new_request->auth_user_request); | |
646 | } | |
647 | if (old_request->body_connection) { | |
648 | new_request->body_connection = old_request->body_connection; | |
649 | old_request->body_connection = NULL; | |
650 | } | |
651 | new_request->content_length = old_request->content_length; | |
652 | new_request->flags.proxy_keepalive = old_request->flags.proxy_keepalive; | |
653 | requestUnlink(old_request); | |
654 | http->request = requestLink(new_request); | |
655 | } | |
656 | clientInterpretRequestHeaders(http); | |
657 | #if HEADERS_LOG | |
658 | headersLog(0, 1, request->method, request); | |
659 | #endif | |
660 | /* FIXME PIPELINE: This is innacurate during pipelining */ | |
661 | if (http->conn) | |
662 | fd_note(http->conn->fd, http->uri); | |
c8be6d7b | 663 | assert(http->uri); |
8e2745f4 | 664 | context->checkNoCache(); |
edce4d98 | 665 | } |
666 | ||
667 | void | |
8e2745f4 | 668 | ClientRequestContext::checkNoCache() |
edce4d98 | 669 | { |
edce4d98 | 670 | if (Config.accessList.noCache && http->request->flags.cachable) { |
8e2745f4 | 671 | acl_checklist = |
edce4d98 | 672 | clientAclChecklistCreate(Config.accessList.noCache, http); |
225b7b10 | 673 | acl_checklist->nonBlockingCheck(CheckNoCacheDone, cbdataReference(this)); |
edce4d98 | 674 | } else { |
8e2745f4 | 675 | CheckNoCacheDone(http->request->flags.cachable, cbdataReference(this)); |
edce4d98 | 676 | } |
677 | } | |
678 | ||
679 | void | |
8e2745f4 | 680 | ClientRequestContext::CheckNoCacheDone(int answer, void *data) |
edce4d98 | 681 | { |
4fb35c3c | 682 | void *temp; |
683 | bool valid = cbdataReferenceValidDone(data, &temp); | |
8e2745f4 | 684 | /* acl NB calls cannot invalidate cbdata in the normal course of things */ |
4fb35c3c | 685 | assert (valid); |
8e2745f4 | 686 | ClientRequestContext *context = (ClientRequestContext *)temp; |
687 | context->checkNoCacheDone(answer); | |
688 | } | |
4fb35c3c | 689 | |
8e2745f4 | 690 | void |
691 | ClientRequestContext::checkNoCacheDone(int answer) | |
692 | { | |
693 | acl_checklist = NULL; | |
694 | clientHttpRequest *http_ = http; | |
8e2745f4 | 695 | |
3b1b4c07 | 696 | if (!cbdataReferenceValid (http_)) { |
697 | deleteSelf(); | |
4fb35c3c | 698 | return; |
3b1b4c07 | 699 | } |
4fb35c3c | 700 | |
3b1b4c07 | 701 | deleteSelf(); |
8e2745f4 | 702 | http_->request->flags.cachable = answer; |
703 | http_->processRequest(); | |
edce4d98 | 704 | } |
705 | ||
69660be0 | 706 | /* |
707 | * Identify requests that do not go through the store and client side stream | |
708 | * and forward them to the appropriate location. All other requests, request | |
709 | * them. | |
edce4d98 | 710 | */ |
711 | void | |
8e2745f4 | 712 | ClientHttpRequest::processRequest() |
edce4d98 | 713 | { |
edce4d98 | 714 | debug(85, 4) ("clientProcessRequest: %s '%s'\n", |
8e2745f4 | 715 | RequestMethodStr[request->method], uri); |
716 | if (request->method == METHOD_CONNECT) { | |
717 | logType = LOG_TCP_MISS; | |
718 | sslStart(this, &out.size, &al.http.code); | |
edce4d98 | 719 | return; |
edce4d98 | 720 | } |
8e2745f4 | 721 | httpStart(); |
722 | } | |
723 | ||
724 | void | |
725 | ClientHttpRequest::httpStart() | |
726 | { | |
727 | logType = LOG_TAG_NONE; | |
728 | debug(85, 4) ("ClientHttpRequest::httpStart: %s for '%s'\n", | |
729 | log_tags[logType], uri); | |
edce4d98 | 730 | /* no one should have touched this */ |
8e2745f4 | 731 | assert(out.offset == 0); |
edce4d98 | 732 | /* Use the Stream Luke */ |
8e2745f4 | 733 | clientStreamNode *node = (clientStreamNode *)client_stream.tail->data; |
734 | clientStreamRead(node, this, node->readBuffer); | |
edce4d98 | 735 | } |