]> git.ipfire.org Git - thirdparty/squid.git/blame - src/client_side_request.cc
Add HTTP status code 307 as HTTP_TEMPORARY_REDIRECT.
[thirdparty/squid.git] / src / client_side_request.cc
CommitLineData
edce4d98 1
2/*
69660be0 3 * $Id: client_side_request.cc,v 1.4 2002/09/23 03:59:15 wessels Exp $
4 *
5 * DEBUG: section 85 Client-side Request Routines AUTHOR: Robert Collins
6 * (Originally Duane Wessels in client_side.c)
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"
45
46#if LINGERING_CLOSE
47#define comm_close comm_lingering_close
48#endif
49
50static const char *const crlf = "\r\n";
51
52typedef struct _clientRequestContext {
53 aclCheck_t *acl_checklist; /* need ptr back so we can unreg if needed */
54 int redirect_state;
55 clientHttpRequest *http;
56} clientRequestContext;
57
58CBDATA_TYPE(clientRequestContext);
59
60/* Local functions */
61/* clientRequestContext */
62clientRequestContext *clientRequestContextNew(clientHttpRequest *);
63FREE clientRequestContextFree;
64/* other */
65static int checkAccelOnly(clientHttpRequest *);
66static void clientAccessCheckDone(int, void *);
69660be0 67 /* static */ aclCheck_t *
68 clientAclChecklistCreate(const acl_access * acl,
edce4d98 69 const clientHttpRequest * http);
70static int clientCachable(clientHttpRequest * http);
71static int clientHierarchical(clientHttpRequest * http);
72static void clientInterpretRequestHeaders(clientHttpRequest * http);
73static RH clientRedirectDone;
74static void clientCheckNoCache(clientRequestContext * context);
75static void clientCheckNoCacheDone(int answer, void *data);
76void clientProcessRequest(clientHttpRequest *);
77extern CSR clientGetMoreData;
78extern CSS clientReplyStatus;
79extern CSD clientReplyDetach;
80
81void
82clientRequestContextFree(void *data)
83{
84 clientRequestContext *context = data;
85 cbdataReferenceDone(context->http);
86 if (context->acl_checklist)
87 aclChecklistFree(context->acl_checklist);
88}
89
90clientRequestContext *
91clientRequestContextNew(clientHttpRequest * http)
92{
93 clientRequestContext *rv;
94 assert(http != NULL);
95 CBDATA_INIT_TYPE_FREECB(clientRequestContext, clientRequestContextFree);
96 rv = cbdataAlloc(clientRequestContext);
97 rv->http = cbdataReference(http);
98 return rv;
99}
100
101/* Create a request and kick it off */
69660be0 102/*
103 * TODO: Pass in the buffers to be used in the inital Read request, as they are
104 * determined by the user
edce4d98 105 */
106int /* returns nonzero on failure */
107clientBeginRequest(method_t method, char const *url, CSCB * streamcallback,
108 CSD * streamdetach, void *streamdata, HttpHeader const *header,
109 char *tailbuf, size_t taillen)
110{
111 size_t url_sz;
112 http_version_t http_ver =
113 {1, 0};
114 clientHttpRequest *http = cbdataAlloc(clientHttpRequest);
115 request_t *request;
116 http->http_ver = http_ver;
117 http->conn = NULL;
118 http->start = current_time;
119 /* this is only used to adjust the connection offset in client_side.c */
120 http->req_sz = 0;
121 /* client stream setup */
122 clientStreamInit(&http->client_stream, clientGetMoreData, clientReplyDetach,
123 clientReplyStatus, clientReplyNewContext(http), streamcallback,
124 streamdetach, streamdata, tailbuf, taillen);
125 /* make it visible in the 'current acctive requests list' */
126 dlinkAdd(http, &http->active, &ClientActiveRequests);
127 /* Set flags */
69660be0 128 http->flags.accel = 1; /* internal requests only makes sense in an
edce4d98 129 * accelerator today. TODO: accept flags ? */
130 /* allow size for url rewriting */
131 url_sz = strlen(url) + Config.appendDomainLen + 5;
132 http->uri = xcalloc(url_sz, 1);
133 strcpy(http->uri, url);
134
135 if ((request = urlParse(method, http->uri)) == NULL) {
136 debug(85, 5) ("Invalid URL: %s\n", http->uri);
137 return -1;
138 }
69660be0 139 /*
140 * now update the headers in request with our supplied headers. urLParse
141 * should return a blank header set, but we use Update to be sure of
142 * correctness.
edce4d98 143 */
144 if (header)
145 httpHeaderUpdate(&request->header, header, NULL);
146 http->log_uri = xstrdup(urlCanonicalClean(request));
147 /* http struct now ready */
148
69660be0 149 /*
150 * build new header list *? TODO
edce4d98 151 */
152 request->flags.accelerated = http->flags.accel;
69660be0 153 request->flags.internalclient = 1; /* this is an internally created
154 * request, not subject to acceleration
155 * target overrides */
156 /*
157 * FIXME? Do we want to detect and handle internal requests of internal
158 * objects ?
159 */
edce4d98 160
161 /* Internally created requests cannot have bodies today */
162 request->content_length = 0;
163 request->client_addr = no_addr;
164 request->my_addr = no_addr; /* undefined for internal requests */
165 request->my_port = 0;
166 request->http_ver = http_ver;
167 http->request = requestLink(request);
168
169 /* optional - skip the access check ? */
170 clientAccessCheck(http);
171 return 0;
172}
173
174static int
175checkAccelOnly(clientHttpRequest * http)
176{
69660be0 177 /*
178 * return TRUE if someone makes a proxy request to us and we are in
179 * httpd-accel only mode
180 */
edce4d98 181 if (!Config2.Accel.on)
182 return 0;
183 if (Config.onoff.accel_with_proxy)
184 return 0;
185 if (http->request->protocol == PROTO_CACHEOBJ)
186 return 0;
187 if (http->flags.accel)
188 return 0;
189 if (http->request->method == METHOD_PURGE)
190 return 0;
191 return 1;
192}
193
194aclCheck_t *
195clientAclChecklistCreate(const acl_access * acl, const clientHttpRequest * http)
196{
197 aclCheck_t *ch;
198 ConnStateData *conn = http->conn;
199 ch = aclChecklistCreate(acl, http->request, conn ? conn->rfc931 : dash_str);
200
201 /*
69660be0 202 * hack for ident ACL. It needs to get full addresses, and a place to store
203 * the ident result on persistent connections...
edce4d98 204 */
205 /* connection oriented auth also needs these two lines for it's operation. */
69660be0 206 /*
207 * Internal requests do not have a connection reference, because: A) their
208 * byte count may be transformed before being applied to an outbound
209 * connection B) they are internal - any limiting on them should be done on
210 * the server end.
edce4d98 211 */
212 if (conn)
213 ch->conn = cbdataReference(conn); /* unreferenced in acl.c */
214
215 return ch;
216}
217
218/* This is the entry point for external users of the client_side routines */
219void
220clientAccessCheck(void *data)
221{
222 clientHttpRequest *http = data;
223 clientRequestContext *context = clientRequestContextNew(http);
224 if (checkAccelOnly(http)) {
225 /* deny proxy requests in accel_only mode */
226 debug(85,
227 1) ("clientAccessCheck: proxy request denied in accel_only mode\n");
228 clientAccessCheckDone(ACCESS_DENIED, context);
229 return;
230 }
231 context->acl_checklist =
232 clientAclChecklistCreate(Config.accessList.http, http);
233 aclNBCheck(context->acl_checklist, clientAccessCheckDone, context);
234}
235
236void
237clientAccessCheckDone(int answer, void *data)
238{
239 clientRequestContext *context = data;
240 clientHttpRequest *http = context->http;
241 err_type page_id;
242 http_status status;
243 char *proxy_auth_msg = NULL;
244 debug(85, 2) ("The request %s %s is %s, because it matched '%s'\n",
245 RequestMethodStr[http->request->method], http->uri,
246 answer == ACCESS_ALLOWED ? "ALLOWED" : "DENIED",
247 AclMatchedName ? AclMatchedName : "NO ACL's");
248 proxy_auth_msg = authenticateAuthUserRequestMessage((http->conn
249 && http->conn->auth_user_request) ? http->conn->
250 auth_user_request : http->request->auth_user_request);
251 context->acl_checklist = NULL;
252 if (answer == ACCESS_ALLOWED) {
253 safe_free(http->uri);
254 http->uri = xstrdup(urlCanonical(http->request));
255 assert(context->redirect_state == REDIRECT_NONE);
256 context->redirect_state = REDIRECT_PENDING;
257 redirectStart(http, clientRedirectDone, context);
258 } else {
259 /* Send an error */
260 clientStreamNode *node = http->client_stream.tail->prev->data;
261 cbdataFree(context);
262 debug(85, 5) ("Access Denied: %s\n", http->uri);
263 debug(85, 5) ("AclMatchedName = %s\n",
264 AclMatchedName ? AclMatchedName : "<null>");
265 debug(85, 5) ("Proxy Auth Message = %s\n",
266 proxy_auth_msg ? proxy_auth_msg : "<null>");
267 /*
69660be0 268 * NOTE: get page_id here, based on AclMatchedName because if
269 * USE_DELAY_POOLS is enabled, then AclMatchedName gets clobbered in
270 * the clientCreateStoreEntry() call just below. Pedro Ribeiro
271 * <pribeiro@isel.pt>
edce4d98 272 */
273 page_id = aclGetDenyInfoPage(&Config.denyInfoList, AclMatchedName);
29b8d8d6 274 http->logType = LOG_TCP_DENIED;
edce4d98 275 if (answer == ACCESS_REQ_PROXY_AUTH || aclIsProxyAuth(AclMatchedName)) {
276 if (!http->flags.accel) {
277 /* Proxy authorisation needed */
278 status = HTTP_PROXY_AUTHENTICATION_REQUIRED;
279 } else {
280 /* WWW authorisation needed */
281 status = HTTP_UNAUTHORIZED;
282 }
283 if (page_id == ERR_NONE)
284 page_id = ERR_CACHE_ACCESS_DENIED;
285 } else {
286 status = HTTP_FORBIDDEN;
287 if (page_id == ERR_NONE)
288 page_id = ERR_ACCESS_DENIED;
289 }
290 clientSetReplyToError(node->data, page_id, status,
291 http->request->method, NULL,
292 http->conn ? &http->conn->peer.sin_addr : &no_addr, http->request,
293 NULL, http->conn
294 && http->conn->auth_user_request ? http->conn->
295 auth_user_request : http->request->auth_user_request);
296 node = http->client_stream.tail->data;
297 clientStreamRead(node, http, node->readoff, node->readlen,
298 node->readbuf);
299 }
300}
301
302static int
303clientCachable(clientHttpRequest * http)
304{
305 request_t *req = http->request;
306 method_t method = req->method;
307 if (req->protocol == PROTO_HTTP)
308 return httpCachable(method);
309 /* FTP is always cachable */
310 if (req->protocol == PROTO_WAIS)
311 return 0;
69660be0 312 /*
313 * The below looks questionable: what non HTTP protocols use connect,
314 * trace, put and post? RC
edce4d98 315 */
316 if (method == METHOD_CONNECT)
317 return 0;
318 if (method == METHOD_TRACE)
319 return 0;
320 if (method == METHOD_PUT)
321 return 0;
322 if (method == METHOD_POST)
69660be0 323 return 0; /* XXX POST may be cached sometimes.. ignored
324 * for now */
edce4d98 325 if (req->protocol == PROTO_GOPHER)
326 return gopherCachable(req);
327 if (req->protocol == PROTO_CACHEOBJ)
328 return 0;
329 return 1;
330}
331
332static int
333clientHierarchical(clientHttpRequest * http)
334{
335 const char *url = http->uri;
336 request_t *request = http->request;
337 method_t method = request->method;
338 const wordlist *p = NULL;
339
69660be0 340 /*
341 * IMS needs a private key, so we can use the hierarchy for IMS only if our
342 * neighbors support private keys
343 */
edce4d98 344 if (request->flags.ims && !neighbors_do_private_keys)
345 return 0;
69660be0 346 /*
347 * This is incorrect: authenticating requests can be sent via a hierarchy
348 * (they can even be cached if the correct headers are set on the reply
edce4d98 349 */
350 if (request->flags.auth)
351 return 0;
352 if (method == METHOD_TRACE)
353 return 1;
354 if (method != METHOD_GET)
355 return 0;
356 /* scan hierarchy_stoplist */
357 for (p = Config.hierarchy_stoplist; p; p = p->next)
358 if (strstr(url, p->key))
359 return 0;
360 if (request->flags.loopdetect)
361 return 0;
362 if (request->protocol == PROTO_HTTP)
363 return httpCachable(method);
364 if (request->protocol == PROTO_GOPHER)
365 return gopherCachable(request);
366 if (request->protocol == PROTO_WAIS)
367 return 0;
368 if (request->protocol == PROTO_CACHEOBJ)
369 return 0;
370 return 1;
371}
372
373
374static void
375clientInterpretRequestHeaders(clientHttpRequest * http)
376{
377 request_t *request = http->request;
378 const HttpHeader *req_hdr = &request->header;
379 int no_cache = 0;
380#if !defined(ESI) || defined(USE_USERAGENT_LOG) || defined(USE_REFERER_LOG)
381 const char *str;
382#endif
383 request->imslen = -1;
384 request->ims = httpHeaderGetTime(req_hdr, HDR_IF_MODIFIED_SINCE);
385 if (request->ims > 0)
386 request->flags.ims = 1;
387#if ESI
69660be0 388 /*
389 * We ignore Cache-Control as per the Edge Architecture Section 3. See
390 * www.esi.org for more information.
edce4d98 391 */
392#else
393 if (httpHeaderHas(req_hdr, HDR_PRAGMA)) {
394 String s = httpHeaderGetList(req_hdr, HDR_PRAGMA);
395 if (strListIsMember(&s, "no-cache", ','))
396 no_cache++;
397 stringClean(&s);
398 }
399 request->cache_control = httpHeaderGetCc(req_hdr);
400 if (request->cache_control)
401 if (EBIT_TEST(request->cache_control->mask, CC_NO_CACHE))
402 no_cache++;
69660be0 403 /*
404 * Work around for supporting the Reload button in IE browsers when Squid
405 * is used as an accelerator or transparent proxy, by turning accelerated
406 * IMS request to no-cache requests. Now knows about IE 5.5 fix (is
407 * actually only fixed in SP1, but we can't tell whether we are talking to
408 * SP1 or not so all 5.5 versions are treated 'normally').
edce4d98 409 */
410 if (Config.onoff.ie_refresh) {
411 if (http->flags.accel && request->flags.ims) {
412 if ((str = httpHeaderGetStr(req_hdr, HDR_USER_AGENT))) {
413 if (strstr(str, "MSIE 5.01") != NULL)
414 no_cache++;
415 else if (strstr(str, "MSIE 5.0") != NULL)
416 no_cache++;
417 else if (strstr(str, "MSIE 4.") != NULL)
418 no_cache++;
419 else if (strstr(str, "MSIE 3.") != NULL)
420 no_cache++;
421 }
422 }
423 }
424#endif
425 if (no_cache) {
426#if HTTP_VIOLATIONS
427 if (Config.onoff.reload_into_ims)
428 request->flags.nocache_hack = 1;
429 else if (refresh_nocache_hack)
430 request->flags.nocache_hack = 1;
431 else
432#endif
433 request->flags.nocache = 1;
434 }
435 /* ignore range header in non-GETs */
436 if (request->method == METHOD_GET) {
437 /*
69660be0 438 * Since we're not doing ranges atm, just set the flag if the header
439 * exists, and then free the range header info -- adrian
edce4d98 440 */
441 request->range = httpHeaderGetRange(req_hdr);
442 if (request->range) {
443 request->flags.range = 1;
444 httpHdrRangeDestroy(request->range);
445 request->range = NULL;
446 }
447 }
448 if (httpHeaderHas(req_hdr, HDR_AUTHORIZATION))
449 request->flags.auth = 1;
450 if (request->login[0] != '\0')
451 request->flags.auth = 1;
452 if (httpHeaderHas(req_hdr, HDR_VIA)) {
453 String s = httpHeaderGetList(req_hdr, HDR_VIA);
454 /*
455 * ThisCache cannot be a member of Via header, "1.0 ThisCache" can.
456 * Note ThisCache2 has a space prepended to the hostname so we don't
457 * accidentally match super-domains.
458 */
459 if (strListIsSubstr(&s, ThisCache2, ',')) {
460 debugObj(33, 1, "WARNING: Forwarding loop detected for:\n",
461 request, (ObjPackMethod) & httpRequestPack);
462 request->flags.loopdetect = 1;
463 }
464#if FORW_VIA_DB
465 fvdbCountVia(strBuf(s));
466#endif
467 stringClean(&s);
468 }
469#if USE_USERAGENT_LOG
470 if ((str = httpHeaderGetStr(req_hdr, HDR_USER_AGENT)))
ff1e79fd 471 logUserAgent(fqdnFromAddr(http->conn ? http->conn->log_addr : no_addr), str);
edce4d98 472#endif
473#if USE_REFERER_LOG
474 if ((str = httpHeaderGetStr(req_hdr, HDR_REFERER)))
ff1e79fd 475 logReferer(fqdnFromAddr(http->conn ? http->conn->log_addr : no_addr), str, http->log_uri);
edce4d98 476#endif
477#if FORW_VIA_DB
478 if (httpHeaderHas(req_hdr, HDR_X_FORWARDED_FOR)) {
479 String s = httpHeaderGetList(req_hdr, HDR_X_FORWARDED_FOR);
480 fvdbCountForw(strBuf(s));
481 stringClean(&s);
482 }
483#endif
484 if (request->method == METHOD_TRACE) {
485 request->max_forwards = httpHeaderGetInt(req_hdr, HDR_MAX_FORWARDS);
486 }
487 if (clientCachable(http))
488 request->flags.cachable = 1;
489 if (clientHierarchical(http))
490 request->flags.hierarchical = 1;
491 debug(85, 5) ("clientInterpretRequestHeaders: REQ_NOCACHE = %s\n",
492 request->flags.nocache ? "SET" : "NOT SET");
493 debug(85, 5) ("clientInterpretRequestHeaders: REQ_CACHABLE = %s\n",
494 request->flags.cachable ? "SET" : "NOT SET");
495 debug(85, 5) ("clientInterpretRequestHeaders: REQ_HIERARCHICAL = %s\n",
496 request->flags.hierarchical ? "SET" : "NOT SET");
497}
498
499void
500clientRedirectDone(void *data, char *result)
501{
502 clientRequestContext *context = data;
503 clientHttpRequest *http = context->http;
504 request_t *new_request = NULL;
505 request_t *old_request = http->request;
506 debug(85, 5) ("clientRedirectDone: '%s' result=%s\n", http->uri,
507 result ? result : "NULL");
508 assert(context->redirect_state == REDIRECT_PENDING);
509 context->redirect_state = REDIRECT_DONE;
510 if (result) {
511 http_status status = (http_status) atoi(result);
512 if (status == HTTP_MOVED_PERMANENTLY
69660be0 513 || status == HTTP_MOVED_TEMPORARILY
514 || status == HTTP_SEE_OTHER
515 || status == HTTP_TEMPORARY_REDIRECT) {
edce4d98 516 char *t = result;
517 if ((t = strchr(result, ':')) != NULL) {
518 http->redirect.status = status;
519 http->redirect.location = xstrdup(t + 1);
520 } else {
521 debug(85, 1) ("clientRedirectDone: bad input: %s\n", result);
522 }
523 }
524 if (strcmp(result, http->uri))
525 new_request = urlParse(old_request->method, result);
526 }
527 if (new_request) {
528 safe_free(http->uri);
529 http->uri = xstrdup(urlCanonical(new_request));
530 new_request->http_ver = old_request->http_ver;
531 httpHeaderAppend(&new_request->header, &old_request->header);
532 new_request->client_addr = old_request->client_addr;
533 new_request->my_addr = old_request->my_addr;
534 new_request->my_port = old_request->my_port;
535 new_request->flags.redirected = 1;
536 if (old_request->auth_user_request) {
537 new_request->auth_user_request = old_request->auth_user_request;
538 authenticateAuthUserRequestLock(new_request->auth_user_request);
539 }
540 if (old_request->body_connection) {
541 new_request->body_connection = old_request->body_connection;
542 old_request->body_connection = NULL;
543 }
544 new_request->content_length = old_request->content_length;
545 new_request->flags.proxy_keepalive = old_request->flags.proxy_keepalive;
546 requestUnlink(old_request);
547 http->request = requestLink(new_request);
548 }
549 clientInterpretRequestHeaders(http);
550#if HEADERS_LOG
551 headersLog(0, 1, request->method, request);
552#endif
553 /* FIXME PIPELINE: This is innacurate during pipelining */
554 if (http->conn)
555 fd_note(http->conn->fd, http->uri);
556 clientCheckNoCache(context);
557}
558
559void
560clientCheckNoCache(clientRequestContext * context)
561{
562 clientHttpRequest *http = context->http;
563 if (Config.accessList.noCache && http->request->flags.cachable) {
564 context->acl_checklist =
565 clientAclChecklistCreate(Config.accessList.noCache, http);
566 aclNBCheck(context->acl_checklist, clientCheckNoCacheDone, context);
567 } else {
568 clientCheckNoCacheDone(http->request->flags.cachable, context);
569 }
570}
571
572void
573clientCheckNoCacheDone(int answer, void *data)
574{
575 clientRequestContext *context = data;
576 clientHttpRequest *http = context->http;
577 http->request->flags.cachable = answer;
578 context->acl_checklist = NULL;
579 cbdataFree(context);
580 clientProcessRequest(http);
581}
582
69660be0 583/*
584 * Identify requests that do not go through the store and client side stream
585 * and forward them to the appropriate location. All other requests, request
586 * them.
edce4d98 587 */
588void
589clientProcessRequest(clientHttpRequest * http)
590{
591 request_t *r = http->request;
592 clientStreamNode *node;
593 debug(85, 4) ("clientProcessRequest: %s '%s'\n",
594 RequestMethodStr[r->method], http->uri);
595 if (r->method == METHOD_CONNECT) {
29b8d8d6 596 http->logType = LOG_TCP_MISS;
edce4d98 597 sslStart(http, &http->out.size, &http->al.http.code);
598 return;
599 } else {
29b8d8d6 600 http->logType = LOG_TAG_NONE;
edce4d98 601 }
602 debug(85, 4) ("clientProcessRequest: %s for '%s'\n",
29b8d8d6 603 log_tags[http->logType], http->uri);
edce4d98 604 /* no one should have touched this */
605 assert(http->out.offset == 0);
606 /* Use the Stream Luke */
607 node = http->client_stream.tail->data;
608 clientStreamRead(node, http, node->readoff, node->readlen, node->readbuf);
609}