]> git.ipfire.org Git - thirdparty/squid.git/blame - src/client_side.cc
LINT
[thirdparty/squid.git] / src / client_side.cc
CommitLineData
3c66d057 1
dd11e0b7 2/*
79d39a72 3 * $Id: client_side.cc,v 1.141 1997/11/05 05:29:20 wessels Exp $
dd11e0b7 4 *
5 * DEBUG: section 33 Client-side Routines
6 * AUTHOR: Duane Wessels
7 *
42c04c16 8 * SQUID Internet Object Cache http://squid.nlanr.net/Squid/
dd11e0b7 9 * --------------------------------------------------------
10 *
11 * Squid is the result of efforts by numerous individuals from the
12 * Internet community. Development is led by Duane Wessels of the
13 * National Laboratory for Applied Network Research and funded by
14 * the National Science Foundation.
15 *
16 * This program is free software; you can redistribute it and/or modify
17 * it under the terms of the GNU General Public License as published by
18 * the Free Software Foundation; either version 2 of the License, or
19 * (at your option) any later version.
20 *
21 * This program is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 * GNU General Public License for more details.
25 *
26 * You should have received a copy of the GNU General Public License
27 * along with this program; if not, write to the Free Software
28 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
29 *
30 */
f88bb09c 31
32#include "squid.h"
33
7a2f978b 34static const char *const crlf = "\r\n";
35
36#define REQUEST_BUF_SIZE 4096
37#define FAILURE_MODE_TIME 300
38
39/* Local functions */
40
41static CWCB icpHandleIMSComplete;
42static PF clientReadRequest;
43static PF connStateFree;
44static PF requestTimeout;
45static STCB icpGetHeadersForIMS;
46static char *icpConstruct304reply(struct _http_reply *);
47static int CheckQuickAbort2(const clientHttpRequest *);
48static int icpCheckTransferDone(clientHttpRequest *);
49static void CheckQuickAbort(clientHttpRequest *);
50static void checkFailureRatio(log_type, hier_code);
51static void clientProcessMISS(int, clientHttpRequest *);
52static void clientAppendReplyHeader(char *, const char *, size_t *, size_t);
53size_t clientBuildReplyHeader(clientHttpRequest *, char *, size_t *, char *, size_t);
54static clientHttpRequest *parseHttpRequest(ConnStateData *, method_t *, int *, char **, size_t *);
582b6456 55static RH clientRedirectDone;
382d851a 56static STCB icpHandleIMSReply;
f5b8bbc4 57static int clientGetsOldEntry(StoreEntry * new, StoreEntry * old, request_t * request);
58static int checkAccelOnly(clientHttpRequest *);
7a2f978b 59static ERCB clientErrorComplete;
60static STCB clientSendMoreData;
61static STCB clientCacheHit;
62static void icpParseRequestHeaders(clientHttpRequest *);
63static void icpProcessRequest(int, clientHttpRequest *);
64
65
ea6f43cd 66
38d7734b 67static int
382d851a 68checkAccelOnly(clientHttpRequest * http)
38d7734b 69{
70 /* return TRUE if someone makes a proxy request to us and
71 * we are in httpd-accel only mode */
f1dc9b30 72 if (!Config2.Accel.on)
38d7734b 73 return 0;
17a0a4ee 74 if (Config.onoff.accel_with_proxy)
38d7734b 75 return 0;
382d851a 76 if (http->request->protocol == PROTO_CACHEOBJ)
38d7734b 77 return 0;
382d851a 78 if (http->accel)
38d7734b 79 return 0;
80 return 1;
81}
82
b8d8561b 83void
382d851a 84clientAccessCheck(void *data)
f88bb09c 85{
382d851a 86 clientHttpRequest *http = data;
87 ConnStateData *conn = http->conn;
75e88d56 88 char *browser;
17a0a4ee 89 if (Config.onoff.ident_lookup && conn->ident.state == IDENT_NONE) {
382d851a 90 identStart(-1, conn, clientAccessCheck);
ba1d2afa 91 return;
92 }
382d851a 93 if (checkAccelOnly(http)) {
94 clientAccessCheckDone(0, http);
75e88d56 95 return;
f88bb09c 96 }
f182d1c5 97 browser = mime_get_header(http->request->headers, "User-Agent");
f1dc9b30 98 http->acl_checklist = aclChecklistCreate(Config.accessList.http,
382d851a 99 http->request,
100 conn->peer.sin_addr,
75e88d56 101 browser,
382d851a 102 conn->ident.ident);
103 aclNBCheck(http->acl_checklist, clientAccessCheckDone, http);
f88bb09c 104}
105
b8d8561b 106void
75e88d56 107clientAccessCheckDone(int answer, void *data)
f88bb09c 108{
382d851a 109 clientHttpRequest *http = data;
110 ConnStateData *conn = http->conn;
111 int fd = conn->fd;
e92d33a5 112 char *redirectUrl = NULL;
9b312a19 113 ErrorState *err = NULL;
a3d5953d 114 debug(33, 5) ("clientAccessCheckDone: '%s' answer=%d\n", http->url, answer);
382d851a 115 http->acl_checklist = NULL;
f88bb09c 116 if (answer) {
382d851a 117 urlCanonical(http->request, http->url);
118 if (http->redirect_state != REDIRECT_NONE)
429fdbec 119 fatal_dump("clientAccessCheckDone: wrong redirect_state");
382d851a 120 http->redirect_state = REDIRECT_PENDING;
121 redirectStart(http, clientRedirectDone, http);
f88bb09c 122 } else {
a3d5953d 123 debug(33, 5) ("Access Denied: %s\n", http->url);
85034133 124 redirectUrl = aclGetDenyInfoUrl(&Config.denyInfoList, AclMatchedName);
e92d33a5 125 if (redirectUrl) {
fe40a877 126 err = errorCon(ERR_ACCESS_DENIED, HTTP_MOVED_TEMPORARILY);
127 err->request = requestLink(http->request);
128 err->src_addr = http->conn->peer.sin_addr;
9b312a19 129 err->redirect_url = xstrdup(redirectUrl);
fe40a877 130 errorSend(fd, err);
e92d33a5 131 } else {
d2989683 132 /* NOTE: don't use HTTP_UNAUTHORIZED because then the
437e2060 133 * stupid browser wants us to authenticate */
fe40a877 134 err = errorCon(ERR_ACCESS_DENIED, HTTP_FORBIDDEN);
135 err->request = requestLink(http->request);
136 err->src_addr = http->conn->peer.sin_addr;
137 errorSend(fd, err);
e92d33a5 138 }
f88bb09c 139 }
140}
141
b8d8561b 142static void
143clientRedirectDone(void *data, char *result)
f88bb09c 144{
382d851a 145 clientHttpRequest *http = data;
146 int fd = http->conn->fd;
88738790 147 size_t l;
c0cdaf99 148 request_t *new_request = NULL;
382d851a 149 request_t *old_request = http->request;
a3d5953d 150 debug(33, 5) ("clientRedirectDone: '%s' result=%s\n", http->url,
f88bb09c 151 result ? result : "NULL");
382d851a 152 if (http->redirect_state != REDIRECT_PENDING)
429fdbec 153 fatal_dump("clientRedirectDone: wrong redirect_state");
382d851a 154 http->redirect_state = REDIRECT_DONE;
c0cdaf99 155 if (result)
20cc1450 156 new_request = urlParse(old_request->method, result);
c0cdaf99 157 if (new_request) {
382d851a 158 safe_free(http->url);
88738790 159 /* need to malloc because the URL returned by the redirector might
160 * not be big enough to append the local domain
161 * -- David Lamkin drl@net-tel.co.uk */
162 l = strlen(result) + Config.appendDomainLen + 5;
163 http->url = xcalloc(l, 1);
164 xstrncpy(http->url, result, l);
20cc1450 165 new_request->http_ver = old_request->http_ver;
2357f74a 166 new_request->headers = old_request->headers;
167 new_request->headers_sz = old_request->headers_sz;
20cc1450 168 requestUnlink(old_request);
382d851a 169 http->request = requestLink(new_request);
170 urlCanonical(http->request, http->url);
f88bb09c 171 }
382d851a 172 icpParseRequestHeaders(http);
173 fd_note(fd, http->url);
174 icpProcessRequest(fd, http);
e81957b7 175}
176
52d4522b 177void
178icpProcessExpired(int fd, void *data)
620da955 179{
382d851a 180 clientHttpRequest *http = data;
181 char *url = http->url;
620da955 182 StoreEntry *entry = NULL;
183
a3d5953d 184 debug(33, 3) ("icpProcessExpired: FD %d '%s'\n", fd, http->url);
620da955 185
382d851a 186 BIT_SET(http->request->flags, REQ_REFRESH);
187 http->old_entry = http->entry;
620da955 188 entry = storeCreateEntry(url,
88738790 189 http->log_url,
382d851a 190 http->request->flags,
191 http->request->method);
620da955 192 /* NOTE, don't call storeLockObject(), storeCreateEntry() does it */
fe96bbe6 193 storeClientListAdd(entry, http);
194 storeClientListAdd(http->old_entry, http);
620da955 195
382d851a 196 entry->lastmod = http->old_entry->lastmod;
a3d5953d 197 debug(33, 5) ("icpProcessExpired: setting lmt = %d\n",
620da955 198 entry->lastmod);
199
d1a43e28 200 entry->refcount++; /* EXPIRED CASE */
382d851a 201 http->entry = entry;
202 http->out.offset = 0;
382d851a 203 protoDispatch(fd, http->entry, http->request);
f990cccc 204 /* Register with storage manager to receive updates when data comes in. */
d89d1fb6 205 storeClientCopy(entry,
fe96bbe6 206 http->out.offset,
d89d1fb6 207 http->out.offset,
208 4096,
209 get_free_4k_page(),
210 icpHandleIMSReply,
211 http);
620da955 212}
213
91f4d519 214static int
215clientGetsOldEntry(StoreEntry * new_entry, StoreEntry * old_entry, request_t * request)
216{
217 /* If the reply is anything but "Not Modified" then
218 * we must forward it to the client */
219 if (new_entry->mem_obj->reply->code != 304) {
a3d5953d 220 debug(33, 5) ("clientGetsOldEntry: NO, reply=%d\n", new_entry->mem_obj->reply->code);
91f4d519 221 return 0;
222 }
223 /* If the client did not send IMS in the request, then it
224 * must get the old object, not this "Not Modified" reply */
225 if (!BIT_TEST(request->flags, REQ_IMS)) {
a3d5953d 226 debug(33, 5) ("clientGetsOldEntry: YES, no client IMS\n");
91f4d519 227 return 1;
228 }
229 /* If the client IMS time is prior to the entry LASTMOD time we
230 * need to send the old object */
231 if (modifiedSince(old_entry, request)) {
a3d5953d 232 debug(33, 5) ("clientGetsOldEntry: YES, modified since %d\n", request->ims);
91f4d519 233 return 1;
234 }
a3d5953d 235 debug(33, 5) ("clientGetsOldEntry: NO, new one is fine\n");
91f4d519 236 return 0;
237}
238
239
52d4522b 240static void
02be0294 241icpHandleIMSReply(void *data, char *buf, ssize_t size)
620da955 242{
382d851a 243 clientHttpRequest *http = data;
244 int fd = http->conn->fd;
245 StoreEntry *entry = http->entry;
620da955 246 MemObject *mem = entry->mem_obj;
9fb13bb6 247 const char *url = storeUrl(entry);
e92d33a5 248 int unlink_request = 0;
76cff1d4 249 StoreEntry *oldentry;
9fb13bb6 250 debug(33, 3) ("icpHandleIMSReply: FD %d '%s'\n", fd, url);
76a5501f 251 put_free_4k_page(buf);
252 buf = NULL;
620da955 253 /* unregister this handler */
070d29eb 254 if (size < 0 || entry->store_status == STORE_ABORTED) {
9fb13bb6 255 debug(33, 3) ("icpHandleIMSReply: ABORTED '%s'\n", url);
c54e9052 256 /* We have an existing entry, but failed to validate it */
accc6d47 257 /* Its okay to send the old one anyway */
258 http->log_type = LOG_TCP_REFRESH_FAIL_HIT;
259 storeUnregister(entry, http);
260 storeUnlockObject(entry);
261 entry = http->entry = http->old_entry;
262 entry->refcount++;
a18b8172 263 } else if (mem->reply->code == 0) {
9fb13bb6 264 debug(33, 3) ("icpHandleIMSReply: Incomplete headers for '%s'\n", url);
d89d1fb6 265 storeClientCopy(entry,
fe96bbe6 266 http->out.offset + size,
d89d1fb6 267 http->out.offset,
268 4096,
269 get_free_4k_page(),
270 icpHandleIMSReply,
271 http);
52d4522b 272 return;
382d851a 273 } else if (clientGetsOldEntry(entry, http->old_entry, http->request)) {
620da955 274 /* We initiated the IMS request, the client is not expecting
76cff1d4 275 * 304, so put the good one back. First, make sure the old entry
276 * headers have been loaded from disk. */
382d851a 277 oldentry = http->old_entry;
382d851a 278 http->log_type = LOG_TCP_REFRESH_HIT;
d89d1fb6 279 if (oldentry->mem_obj->request == NULL) {
280 oldentry->mem_obj->request = requestLink(mem->request);
281 unlink_request = 1;
e92d33a5 282 }
d89d1fb6 283 memcpy(oldentry->mem_obj->reply, entry->mem_obj->reply, sizeof(struct _http_reply));
284 storeTimestampsSet(oldentry);
382d851a 285 storeUnregister(entry, http);
6d54aea8 286 storeUnlockObject(entry);
382d851a 287 entry = http->entry = oldentry;
41fad779 288 entry->timestamp = squid_curtime;
657266fe 289 if (unlink_request) {
e92d33a5 290 requestUnlink(entry->mem_obj->request);
657266fe 291 entry->mem_obj->request = NULL;
292 }
620da955 293 } else {
294 /* the client can handle this reply, whatever it is */
382d851a 295 http->log_type = LOG_TCP_REFRESH_MISS;
d1a43e28 296 if (mem->reply->code == 304) {
382d851a 297 http->old_entry->timestamp = squid_curtime;
298 http->old_entry->refcount++;
299 http->log_type = LOG_TCP_REFRESH_HIT;
d1a43e28 300 }
382d851a 301 storeUnregister(http->old_entry, http);
302 storeUnlockObject(http->old_entry);
620da955 303 }
382d851a 304 http->old_entry = NULL; /* done with old_entry */
e8479da9 305 /* use clientCacheHit() here as the callback because we might
6a54c60e 306 * be swapping in from disk, and the file might not really be
307 * there */
d89d1fb6 308 storeClientCopy(entry,
fe96bbe6 309 http->out.offset,
d89d1fb6 310 http->out.offset,
311 4096,
312 get_free_4k_page(),
e8479da9 313 clientCacheHit,
d89d1fb6 314 http);
620da955 315}
91f4d519 316
317int
304d07cb 318modifiedSince(StoreEntry * entry, request_t * request)
91f4d519 319{
320 int object_length;
321 MemObject *mem = entry->mem_obj;
9fb13bb6 322 debug(33, 3) ("modifiedSince: '%s'\n", storeUrl(entry));
91f4d519 323 if (entry->lastmod < 0)
324 return 1;
325 /* Find size of the object */
326 if (mem->reply->content_length)
327 object_length = mem->reply->content_length;
328 else
329 object_length = entry->object_len - mem->reply->hdr_sz;
330 if (entry->lastmod > request->ims) {
a3d5953d 331 debug(33, 3) ("--> YES: entry newer than client\n");
91f4d519 332 return 1;
333 } else if (entry->lastmod < request->ims) {
a3d5953d 334 debug(33, 3) ("--> NO: entry older than client\n");
91f4d519 335 return 0;
336 } else if (request->imslen < 0) {
a3d5953d 337 debug(33, 3) ("--> NO: same LMT, no client length\n");
91f4d519 338 return 0;
339 } else if (request->imslen == object_length) {
a3d5953d 340 debug(33, 3) ("--> NO: same LMT, same length\n");
91f4d519 341 return 0;
342 } else {
a3d5953d 343 debug(33, 3) ("--> YES: same LMT, different length\n");
91f4d519 344 return 1;
345 }
346}
b3b64e58 347
348char *
382d851a 349clientConstructTraceEcho(clientHttpRequest * http)
b3b64e58 350{
351 LOCAL_ARRAY(char, line, 256);
352 LOCAL_ARRAY(char, buf, 8192);
353 size_t len;
354 memset(buf, '\0', 8192);
042461c3 355 snprintf(buf, 8192, "HTTP/1.0 200 OK\r\n");
56878878 356 snprintf(line, 256, "Date: %s\r\n", mkrfc1123(squid_curtime));
b3b64e58 357 strcat(buf, line);
042461c3 358 snprintf(line, 256, "Server: Squid/%s\r\n", SQUID_VERSION);
b3b64e58 359 strcat(buf, line);
042461c3 360 snprintf(line, 256, "Content-Type: message/http\r\n");
b3b64e58 361 strcat(buf, line);
362 strcat(buf, "\r\n");
363 len = strlen(buf);
382d851a 364 httpBuildRequestHeader(http->request,
365 http->request,
b3b64e58 366 NULL, /* entry */
b3b64e58 367 NULL, /* in_len */
368 buf + len,
369 8192 - len,
603a02fd 370 http->conn->fd,
371 0); /* flags */
382d851a 372 http->log_type = LOG_TCP_MISS;
4869e8a1 373 http->http_code = HTTP_OK;
b3b64e58 374 return buf;
375}
a90eae18 376
377void
382d851a 378clientPurgeRequest(clientHttpRequest * http)
a90eae18 379{
382d851a 380 int fd = http->conn->fd;
9b312a19 381 char *msg;
a90eae18 382 StoreEntry *entry;
9b312a19 383 ErrorState *err = NULL;
9fb13bb6 384 const cache_key *k;
9b312a19 385 debug(33, 3) ("Config.onoff.enable_purge = %d\n", Config.onoff.enable_purge);
17a0a4ee 386 if (!Config.onoff.enable_purge) {
fe40a877 387 err = errorCon(ERR_ACCESS_DENIED, HTTP_FORBIDDEN);
9b312a19 388 err->request = requestLink(http->request);
389 err->src_addr = http->conn->peer.sin_addr;
9b312a19 390 errorSend(fd, err);
a90eae18 391 return;
392 }
382d851a 393 http->log_type = LOG_TCP_MISS;
9fb13bb6 394 k = storeKeyPublic(http->url, METHOD_GET);
395 if ((entry = storeGet(k)) == NULL) {
9b312a19 396 http->http_code = HTTP_NOT_FOUND;
a90eae18 397 } else {
398 storeRelease(entry);
9b312a19 399 http->http_code = HTTP_OK;
a90eae18 400 }
9b312a19 401 msg = httpReplyHeader(1.0, http->http_code, NULL, 0, 0, -1);
402 if (strlen(msg) < 8190)
403 strcat(msg, "\r\n");
404 comm_write(fd, xstrdup(msg), strlen(msg), clientWriteComplete, http, xfree);
a90eae18 405}
e33ba616 406
407int
ea3a2a69 408checkNegativeHit(StoreEntry * e)
e33ba616 409{
ea3a2a69 410 if (!BIT_TEST(e->flag, ENTRY_NEGCACHED))
411 return 0;
412 if (e->expires <= squid_curtime)
413 return 0;
414 if (e->store_status != STORE_OK)
415 return 0;
416 return 1;
e33ba616 417}
7a2f978b 418
419static void
420httpRequestFree(void *data)
421{
422 clientHttpRequest *http = data;
423 clientHttpRequest **H;
424 ConnStateData *conn = http->conn;
425 StoreEntry *entry = http->entry;
426 request_t *request = http->request;
427 MemObject *mem = NULL;
428 debug(12, 3) ("httpRequestFree: %s\n", storeUrl(entry));
429 if (!icpCheckTransferDone(http)) {
430 if (entry)
431 storeUnregister(entry, http); /* unregister BEFORE abort */
432 CheckQuickAbort(http);
433 entry = http->entry; /* reset, IMS might have changed it */
434 if (entry && entry->ping_status == PING_WAITING)
435 storeReleaseRequest(entry);
79d39a72 436 protoUnregister(entry, request);
7a2f978b 437 }
438 assert(http->log_type < LOG_TYPE_MAX);
439 if (entry)
440 mem = entry->mem_obj;
441 if (http->out.size || http->log_type) {
442 http->al.icp.opcode = 0;
443 http->al.url = http->url;
444 if (mem) {
445 http->al.http.code = mem->reply->code;
446 http->al.http.content_type = mem->reply->content_type;
447 }
448 http->al.cache.caddr = conn->log_addr;
449 http->al.cache.size = http->out.size;
450 http->al.cache.code = http->log_type;
451 http->al.cache.msec = tvSubMsec(http->start, current_time);
452 http->al.cache.ident = conn->ident.ident;
453 if (request) {
454 http->al.http.method = request->method;
455 http->al.headers.request = request->headers;
456 http->al.hier = request->hier;
457 }
458 accessLogLog(&http->al);
459 HTTPCacheInfo->proto_count(HTTPCacheInfo,
460 request ? request->protocol : PROTO_NONE,
461 http->log_type);
462 clientdbUpdate(conn->peer.sin_addr, http->log_type, PROTO_HTTP);
463 }
464 if (http->redirect_state == REDIRECT_PENDING)
465 redirectUnregister(http->url, http);
466 if (http->acl_checklist)
467 aclChecklistFree(http->acl_checklist);
468 checkFailureRatio(http->log_type, http->al.hier.code);
469 safe_free(http->url);
470 safe_free(http->log_url);
471 safe_free(http->al.headers.reply);
472 if (entry) {
473 http->entry = NULL;
474 storeUnregister(entry, http);
475 storeUnlockObject(entry);
476 }
477 /* old_entry might still be set if we didn't yet get the reply
478 * code in icpHandleIMSReply() */
479 if (http->old_entry) {
480 storeUnregister(http->old_entry, http);
481 storeUnlockObject(http->old_entry);
482 http->old_entry = NULL;
483 }
484 requestUnlink(http->request);
485 assert(http != http->next);
486 assert(http->conn->chr != NULL);
487 H = &http->conn->chr;
488 while (*H) {
489 if (*H == http)
490 break;
491 H = &(*H)->next;
492 }
493 assert(*H != NULL);
494 *H = http->next;
495 http->next = NULL;
496 cbdataFree(http);
497}
498
499/* This is a handler normally called by comm_close() */
500static void
501connStateFree(int fd, void *data)
502{
503 ConnStateData *connState = data;
504 clientHttpRequest *http;
505 debug(12, 3) ("connStateFree: FD %d\n", fd);
506 assert(connState != NULL);
79d39a72 507 while ((http = connState->chr) != NULL) {
7a2f978b 508 assert(http->conn == connState);
509 assert(connState->chr != connState->chr->next);
510 httpRequestFree(http);
511 }
512 if (connState->ident.fd > -1)
513 comm_close(connState->ident.fd);
514 safe_free(connState->in.buf);
515 meta_data.misc -= connState->in.size;
516 pconnHistCount(0, connState->nrequests);
517 cbdataFree(connState);
518}
519
520static void
521icpParseRequestHeaders(clientHttpRequest * http)
522{
523 request_t *request = http->request;
524 char *request_hdr = request->headers;
525 char *t = NULL;
526 request->ims = -2;
527 request->imslen = -1;
528 if ((t = mime_get_header(request_hdr, "If-Modified-Since"))) {
529 BIT_SET(request->flags, REQ_IMS);
530 request->ims = parse_rfc1123(t);
531 while ((t = strchr(t, ';'))) {
532 for (t++; isspace(*t); t++);
533 if (strncasecmp(t, "length=", 7) == 0)
534 request->imslen = atoi(t + 7);
535 }
536 }
537 if ((t = mime_get_header(request_hdr, "Pragma"))) {
538 if (!strcasecmp(t, "no-cache"))
539 BIT_SET(request->flags, REQ_NOCACHE);
540 }
541 if (mime_get_header(request_hdr, "Range")) {
542 BIT_SET(request->flags, REQ_NOCACHE);
543 BIT_SET(request->flags, REQ_RANGE);
544 } else if (mime_get_header(request_hdr, "Request-Range")) {
545 BIT_SET(request->flags, REQ_NOCACHE);
546 BIT_SET(request->flags, REQ_RANGE);
547 }
548 if (mime_get_header(request_hdr, "Authorization"))
549 BIT_SET(request->flags, REQ_AUTH);
550 if (request->login[0] != '\0')
551 BIT_SET(request->flags, REQ_AUTH);
552 if ((t = mime_get_header(request_hdr, "Proxy-Connection"))) {
553 if (!strcasecmp(t, "Keep-Alive"))
554 BIT_SET(request->flags, REQ_PROXY_KEEPALIVE);
555 }
556 if ((t = mime_get_header(request_hdr, "Via")))
557 if (strstr(t, ThisCache)) {
558 if (!http->accel) {
559 debug(12, 1) ("WARNING: Forwarding loop detected for '%s'\n",
560 http->url);
561 debug(12, 1) ("--> %s\n", t);
562 }
563 BIT_SET(request->flags, REQ_LOOPDETECT);
564 }
565#if USE_USERAGENT_LOG
566 if ((t = mime_get_header(request_hdr, "User-Agent")))
567 logUserAgent(fqdnFromAddr(http->conn->peer.sin_addr), t);
568#endif
569 request->max_age = -1;
570 if ((t = mime_get_header(request_hdr, "Cache-control"))) {
571 if (!strncasecmp(t, "Max-age=", 8))
572 request->max_age = atoi(t + 8);
573 }
574 if (request->method == METHOD_TRACE) {
575 if ((t = mime_get_header(request_hdr, "Max-Forwards")))
576 request->max_forwards = atoi(t);
577 }
578}
579
580static int
581icpCachable(clientHttpRequest * http)
582{
583 const char *url = http->url;
584 request_t *req = http->request;
585 method_t method = req->method;
586 const wordlist *p;
587 for (p = Config.cache_stoplist; p; p = p->next) {
588 if (strstr(url, p->key))
589 return 0;
590 }
591 if (Config.cache_stop_relist)
592 if (aclMatchRegex(Config.cache_stop_relist, url))
593 return 0;
594 if (req->protocol == PROTO_HTTP)
595 return httpCachable(method);
596 /* FTP is always cachable */
597 if (req->protocol == PROTO_GOPHER)
598 return gopherCachable(url);
599 if (req->protocol == PROTO_WAIS)
600 return 0;
601 if (method == METHOD_CONNECT)
602 return 0;
603 if (method == METHOD_TRACE)
604 return 0;
605 if (req->protocol == PROTO_CACHEOBJ)
606 return 0;
607 return 1;
608}
609
610/* Return true if we can query our neighbors for this object */
611static int
612icpHierarchical(clientHttpRequest * http)
613{
614 const char *url = http->url;
615 request_t *request = http->request;
616 method_t method = request->method;
617 const wordlist *p = NULL;
618
619 /* IMS needs a private key, so we can use the hierarchy for IMS only
620 * if our neighbors support private keys */
621 if (BIT_TEST(request->flags, REQ_IMS) && !neighbors_do_private_keys)
622 return 0;
623 if (BIT_TEST(request->flags, REQ_AUTH))
624 return 0;
625 if (method == METHOD_TRACE)
626 return 1;
627 if (method != METHOD_GET)
628 return 0;
629 /* scan hierarchy_stoplist */
630 for (p = Config.hierarchy_stoplist; p; p = p->next)
631 if (strstr(url, p->key))
632 return 0;
633 if (BIT_TEST(request->flags, REQ_LOOPDETECT))
634 return 0;
635 if (request->protocol == PROTO_HTTP)
636 return httpCachable(method);
637 if (request->protocol == PROTO_GOPHER)
638 return gopherCachable(url);
639 if (request->protocol == PROTO_WAIS)
640 return 0;
641 if (request->protocol == PROTO_CACHEOBJ)
642 return 0;
643 return 1;
644}
645
646static void
647clientErrorComplete(int fd, void *data, int size)
648{
649 clientHttpRequest *http = data;
650 if (http)
651 http->out.size += size;
652 comm_close(fd);
653}
654
655int
656isTcpHit(log_type code)
657{
658 /* this should be a bitmap for better optimization */
659 if (code == LOG_TCP_HIT)
660 return 1;
661 if (code == LOG_TCP_IMS_HIT)
662 return 1;
663 if (code == LOG_TCP_REFRESH_FAIL_HIT)
664 return 1;
665 if (code == LOG_TCP_REFRESH_HIT)
666 return 1;
667 if (code == LOG_TCP_NEGATIVE_HIT)
668 return 1;
669 if (code == LOG_TCP_MEM_HIT)
670 return 1;
671 return 0;
672}
673
674static void
675clientAppendReplyHeader(char *hdr, const char *line, size_t * sz, size_t max)
676{
677 size_t n = *sz + strlen(line) + 2;
678 if (n >= max)
679 return;
680 strcpy(hdr + (*sz), line);
681 strcat(hdr + (*sz), crlf);
682 *sz = n;
683}
684
685size_t
686clientBuildReplyHeader(clientHttpRequest * http,
687 char *hdr_in,
688 size_t * in_len,
689 char *hdr_out,
690 size_t out_sz)
691{
692 char *xbuf;
693 char *ybuf;
694 char *t = NULL;
695 char *end = NULL;
696 size_t len = 0;
697 size_t hdr_len = 0;
698 size_t l;
699 end = mime_headers_end(hdr_in);
700 if (end == NULL) {
701 debug(12, 3) ("clientBuildReplyHeader: DIDN'T FIND END-OF-HEADERS\n");
702 debug(12, 3) ("\n%s", hdr_in);
703 return 0;
704 }
705 xbuf = get_free_4k_page();
706 ybuf = get_free_4k_page();
707 for (t = hdr_in; t < end; t += strcspn(t, crlf), t += strspn(t, crlf)) {
708 hdr_len = t - hdr_in;
709 l = strcspn(t, crlf) + 1;
710 xstrncpy(xbuf, t, l > 4096 ? 4096 : l);
711 debug(12, 5) ("clientBuildReplyHeader: %s\n", xbuf);
712#if 0
713 if (strncasecmp(xbuf, "Accept-Ranges:", 14) == 0)
714 continue;
715 if (strncasecmp(xbuf, "Etag:", 5) == 0)
716 continue;
717#endif
718 if (strncasecmp(xbuf, "Proxy-Connection:", 17) == 0)
719 continue;
720 if (strncasecmp(xbuf, "Connection:", 11) == 0)
721 continue;
722 if (strncasecmp(xbuf, "Keep-Alive:", 11) == 0)
723 continue;
724 if (strncasecmp(xbuf, "Set-Cookie:", 11) == 0)
725 if (isTcpHit(http->log_type))
726 continue;
727 clientAppendReplyHeader(hdr_out, xbuf, &len, out_sz - 512);
728 }
729 hdr_len = end - hdr_in;
730 /* Append X-Cache: */
731 snprintf(ybuf, 4096, "X-Cache: %s", isTcpHit(http->log_type) ? "HIT" : "MISS");
732 clientAppendReplyHeader(hdr_out, ybuf, &len, out_sz);
733 /* Append Proxy-Connection: */
734 if (BIT_TEST(http->request->flags, REQ_PROXY_KEEPALIVE)) {
735 snprintf(ybuf, 4096, "Proxy-Connection: Keep-Alive");
736 clientAppendReplyHeader(hdr_out, ybuf, &len, out_sz);
737 }
738 clientAppendReplyHeader(hdr_out, null_string, &len, out_sz);
739 if (in_len)
740 *in_len = hdr_len;
741 if ((l = strlen(hdr_out)) != len) {
742 debug_trap("clientBuildReplyHeader: size mismatch");
743 len = l;
744 }
745 debug(12, 3) ("clientBuildReplyHeader: OUTPUT:\n%s\n", hdr_out);
746 put_free_4k_page(xbuf);
747 put_free_4k_page(ybuf);
748 return len;
749}
750
751void
752clientCacheHit(void *data, char *buf, ssize_t size)
753{
754 clientHttpRequest *http = data;
755 if (size >= 0) {
756 clientSendMoreData(data, buf, size);
757 } else if (http->entry == NULL) {
758 debug(12, 3) ("clientCacheHit: request aborted\n");
759 } else {
760 /* swap in failure */
761 http->log_type = LOG_TCP_SWAPFAIL_MISS;
762 clientProcessMISS(http->conn->fd, http);
763 }
764}
765
766void
767clientSendMoreData(void *data, char *buf, ssize_t size)
768{
769 clientHttpRequest *http = data;
770 StoreEntry *entry = http->entry;
771 ConnStateData *conn = http->conn;
772 int fd = conn->fd;
773 char *p = NULL;
774 size_t hdrlen;
775 size_t l = 0;
776 size_t writelen;
777 char *newbuf;
778 FREE *freefunc = put_free_4k_page;
779 int hack = 0;
780 char C = '\0';
781 assert(size <= SM_PAGE_SIZE);
782 debug(12, 5) ("clientSendMoreData: FD %d '%s', out.offset=%d\n",
783 fd, storeUrl(entry), http->out.offset);
784 if (conn->chr != http) {
785 /* there is another object in progress, defer this one */
786 debug(0, 0) ("clientSendMoreData: Deferring delivery of\n");
787 debug(0, 0) ("--> %s\n", storeUrl(entry));
788 debug(0, 0) ("--> because other requests are in front\n");
789 freefunc(buf);
790 return;
791 } else if (entry->store_status == STORE_ABORTED) {
792 freefunc(buf);
793 return;
794 } else if (size < 0) {
795 freefunc(buf);
796 return;
797 } else if (size == 0) {
798 clientWriteComplete(fd, NULL, 0, DISK_OK, http);
799 freefunc(buf);
800 return;
801 }
802 writelen = size;
803 if (http->out.offset == 0 && http->request->protocol != PROTO_CACHEOBJ) {
804 if (Config.onoff.log_mime_hdrs) {
805 if ((p = mime_headers_end(buf))) {
806 safe_free(http->al.headers.reply);
807 http->al.headers.reply = xcalloc(1 + p - buf, 1);
808 xstrncpy(http->al.headers.reply, buf, p - buf);
809 }
810 }
811 /* make sure 'buf' is null terminated somewhere */
812 if (size == SM_PAGE_SIZE) {
813 hack = 1;
814 size--;
815 C = *(buf + size);
816 }
817 *(buf + size) = '\0';
818 newbuf = get_free_8k_page();
819 hdrlen = 0;
820 l = clientBuildReplyHeader(http, buf, &hdrlen, newbuf, 8192);
821 if (hack)
822 *(buf + size++) = C;
823 if (l != 0) {
824 writelen = l + size - hdrlen;
825 assert(writelen <= 8192);
826 /*
827 * l is the length of the new headers in newbuf
828 * hdrlen is the length of the old headers in buf
829 * size - hdrlen is the amount of body in buf
830 */
831 debug(12, 3) ("clientSendMoreData: Appending %d bytes after headers\n",
832 (int) (size - hdrlen));
833 xmemcpy(newbuf + l, buf + hdrlen, size - hdrlen);
834 /* replace buf with newbuf */
835 freefunc(buf);
836 buf = newbuf;
837 freefunc = put_free_8k_page;
838 newbuf = NULL;
839 } else {
840 put_free_8k_page(newbuf);
841 newbuf = NULL;
842 if (size < SM_PAGE_SIZE && entry->store_status == STORE_PENDING) {
843 /* wait for more to arrive */
844 storeClientCopy(entry,
845 http->out.offset + size,
846 http->out.offset,
847 SM_PAGE_SIZE,
848 buf,
849 clientSendMoreData,
850 http);
851 return;
852 }
853 }
854 }
855 http->out.offset += size;
856 if (http->request->method == METHOD_HEAD) {
857 if ((p = mime_headers_end(buf))) {
858 *p = '\0';
859 writelen = p - buf;
860 /* force end */
861 http->out.offset = entry->mem_obj->inmem_hi;
862 }
863 }
864 comm_write(fd, buf, writelen, clientWriteComplete, http, freefunc);
865}
866
867void
79d39a72 868clientWriteComplete(int fd, char *bufnotused, int size, int errflag, void *data)
7a2f978b 869{
870 clientHttpRequest *http = data;
871 ConnStateData *conn;
872 StoreEntry *entry = http->entry;
873 http->out.size += size;
874 debug(12, 5) ("clientWriteComplete: FD %d, sz %d, err %d, off %d, len %d\n",
875 fd, size, errflag, http->out.offset, entry->object_len);
876 if (errflag) {
877 CheckQuickAbort(http);
878 /* Log the number of bytes that we managed to read */
879 HTTPCacheInfo->proto_touchobject(HTTPCacheInfo,
880 urlParseProtocol(storeUrl(entry)),
881 http->out.size);
882 comm_close(fd);
883 } else if (entry->store_status == STORE_ABORTED) {
884 HTTPCacheInfo->proto_touchobject(HTTPCacheInfo,
885 urlParseProtocol(storeUrl(entry)),
886 http->out.size);
887 comm_close(fd);
888 } else if (icpCheckTransferDone(http) || size == 0) {
889 debug(12, 5) ("clientWriteComplete: FD %d transfer is DONE\n", fd);
890 /* We're finished case */
891 HTTPCacheInfo->proto_touchobject(HTTPCacheInfo,
892 http->request->protocol,
893 http->out.size);
894 if (http->entry->mem_obj->reply->content_length <= 0) {
895 comm_close(fd);
896 } else if (BIT_TEST(http->request->flags, REQ_PROXY_KEEPALIVE)) {
897 debug(12, 5) ("clientWriteComplete: FD %d Keeping Alive\n", fd);
898 conn = http->conn;
899 httpRequestFree(http);
79d39a72 900 if ((http = conn->chr) != NULL) {
7a2f978b 901 debug(12, 1) ("clientWriteComplete: FD %d Sending next request\n", fd);
902 storeClientCopy(entry,
903 http->out.offset,
904 http->out.offset,
905 SM_PAGE_SIZE,
906 get_free_4k_page(),
907 clientSendMoreData,
908 http);
909 } else {
910 debug(12, 5) ("clientWriteComplete: FD %d Setting read handler for next request\n", fd);
911 fd_note(fd, "Reading next request");
912 commSetSelect(fd, COMM_SELECT_READ, clientReadRequest, conn, 0);
913 commSetTimeout(fd, 15, requestTimeout, conn);
914 }
915 } else {
916 comm_close(fd);
917 }
918 } else {
919 /* More data will be coming from primary server; register with
920 * storage manager. */
921 storeClientCopy(entry,
922 http->out.offset,
923 http->out.offset,
924 SM_PAGE_SIZE,
925 get_free_4k_page(),
926 clientSendMoreData,
927 http);
928 }
929}
930
931static void
932icpGetHeadersForIMS(void *data, char *buf, ssize_t size)
933{
934 clientHttpRequest *http = data;
935 int fd = http->conn->fd;
936 StoreEntry *entry = http->entry;
937 MemObject *mem = entry->mem_obj;
938 char *reply = NULL;
939 assert(size <= SM_PAGE_SIZE);
940 if (size < 0) {
941 debug(12, 1) ("icpGetHeadersForIMS: storeClientCopy failed for '%s'\n",
942 storeKeyText(entry->key));
943 put_free_4k_page(buf);
944 clientProcessMISS(fd, http);
945 return;
946 }
947 if (mem->reply->code == 0) {
948 if (entry->mem_status == IN_MEMORY) {
949 put_free_4k_page(buf);
950 clientProcessMISS(fd, http);
951 return;
952 }
953 /* All headers are not yet available, wait for more data */
954 storeClientCopy(entry,
955 http->out.offset + size,
956 http->out.offset,
957 SM_PAGE_SIZE,
958 buf,
959 icpGetHeadersForIMS,
960 http);
961 return;
962 }
963 /* All headers are available, check if object is modified or not */
964 /* ---------------------------------------------------------------
965 * Removed check for reply->code != 200 because of a potential
966 * problem with ICP. We will return a HIT for any public, cached
967 * object. This includes other responses like 301, 410, as coded in
968 * http.c. It is Bad(tm) to return UDP_HIT and then, if the reply
969 * code is not 200, hand off to clientProcessMISS(), which may disallow
970 * the request based on 'miss_access' rules. Alternatively, we might
971 * consider requiring returning UDP_HIT only for 200's. This
972 * problably means an entry->flag bit, which would be lost during
973 * restart because the flags aren't preserved across restarts.
974 * --DW 3/11/96.
975 * ---------------------------------------------------------------- */
976#ifdef CHECK_REPLY_CODE_NOTEQUAL_200
977 /* Only objects with statuscode==200 can be "Not modified" */
978 if (mem->reply->code != 200) {
979 debug(12, 4) ("icpGetHeadersForIMS: Reply code %d!=200\n",
980 mem->reply->code);
981 put_free_4k_page(buf);
982 clientProcessMISS(fd, http);
983 return;
984 }
985 +
986#endif
987 http->log_type = LOG_TCP_IMS_HIT;
988 entry->refcount++;
989 if (modifiedSince(entry, http->request)) {
990 storeClientCopy(entry,
991 http->out.offset,
992 http->out.offset,
993 SM_PAGE_SIZE,
994 buf,
995 clientSendMoreData,
996 http);
997 return;
998 }
999 debug(12, 4) ("icpGetHeadersForIMS: Not modified '%s'\n", storeUrl(entry));
1000 reply = icpConstruct304reply(mem->reply);
1001 comm_write(fd,
1002 xstrdup(reply),
1003 strlen(reply),
1004 icpHandleIMSComplete,
1005 http,
1006 xfree);
1007}
1008
1009static void
79d39a72 1010icpHandleIMSComplete(int fd, char *bufnotused, int size, int errflag, void *data)
7a2f978b 1011{
1012 clientHttpRequest *http = data;
1013 StoreEntry *entry = http->entry;
1014 debug(12, 5) ("icpHandleIMSComplete: Not Modified sent '%s'\n", storeUrl(entry));
1015 HTTPCacheInfo->proto_touchobject(HTTPCacheInfo,
1016 http->request->protocol,
1017 size);
1018 /* Set up everything for the logging */
1019 storeUnregister(entry, http);
1020 storeUnlockObject(entry);
1021 http->entry = NULL;
1022 http->out.size += size;
1023 http->al.http.code = 304;
1024 if (errflag != COMM_ERR_CLOSING)
1025 comm_close(fd);
1026}
1027
1028/*
1029 * Below, we check whether the object is a hit or a miss. If it's a hit,
1030 * we check whether the object is still valid or whether it is a MISS_TTL.
1031 */
1032void
1033icpProcessRequest(int fd, clientHttpRequest * http)
1034{
1035 char *url = http->url;
1036 const cache_key *pubkey;
1037 StoreEntry *entry = NULL;
1038 request_t *request = http->request;
1039 char *reply;
1040 debug(12, 4) ("icpProcessRequest: %s '%s'\n",
1041 RequestMethodStr[http->request->method],
1042 url);
1043 if (http->request->method == METHOD_CONNECT) {
1044 http->log_type = LOG_TCP_MISS;
1045 sslStart(fd, url, http->request, &http->out.size);
1046 return;
1047 } else if (request->method == METHOD_PURGE) {
1048 clientPurgeRequest(http);
1049 return;
1050 } else if (request->method == METHOD_TRACE) {
1051 if (request->max_forwards == 0) {
1052 reply = clientConstructTraceEcho(http);
1053 comm_write(fd,
1054 xstrdup(reply),
1055 strlen(reply),
1056 clientWriteComplete,
1057 http,
1058 xfree);
1059 return;
1060 }
1061 /* yes, continue */
1062 } else if (request->method != METHOD_GET) {
1063 http->log_type = LOG_TCP_MISS;
1064 passStart(fd, url, http->request, &http->out.size);
1065 return;
1066 }
1067 if (icpCachable(http))
1068 BIT_SET(request->flags, REQ_CACHABLE);
1069 if (icpHierarchical(http))
1070 BIT_SET(request->flags, REQ_HIERARCHICAL);
1071 debug(12, 5) ("icpProcessRequest: REQ_NOCACHE = %s\n",
1072 BIT_TEST(request->flags, REQ_NOCACHE) ? "SET" : "NOT SET");
1073 debug(12, 5) ("icpProcessRequest: REQ_CACHABLE = %s\n",
1074 BIT_TEST(request->flags, REQ_CACHABLE) ? "SET" : "NOT SET");
1075 debug(12, 5) ("icpProcessRequest: REQ_HIERARCHICAL = %s\n",
1076 BIT_TEST(request->flags, REQ_HIERARCHICAL) ? "SET" : "NOT SET");
1077
1078 /* NOTE on HEAD requests: We currently don't cache HEAD reqeusts
1079 * at all, so look for the corresponding GET object, or just go
1080 * directly. The only way to get a TCP_HIT on a HEAD reqeust is
1081 * if someone already did a GET. Maybe we should turn HEAD
1082 * misses into full GET's? */
1083 if (http->request->method == METHOD_HEAD)
1084 pubkey = storeKeyPublic(http->url, METHOD_GET);
1085 else
1086 pubkey = storeKeyPublic(http->url, http->request->method);
1087
1088 if ((entry = storeGet(pubkey)) == NULL) {
1089 /* this object isn't in the cache */
1090 http->log_type = LOG_TCP_MISS;
1091 } else if (BIT_TEST(entry->flag, ENTRY_SPECIAL)) {
1092 if (entry->mem_status == IN_MEMORY)
1093 http->log_type = LOG_TCP_MEM_HIT;
1094 else
1095 http->log_type = LOG_TCP_HIT;
1096 } else if (!storeEntryValidToSend(entry)) {
1097 http->log_type = LOG_TCP_MISS;
1098 storeRelease(entry);
1099 entry = NULL;
1100 } else if (BIT_TEST(request->flags, REQ_NOCACHE)) {
1101 /* NOCACHE should always eject a negative cached object */
1102 if (BIT_TEST(entry->flag, ENTRY_NEGCACHED))
1103 storeRelease(entry);
1104 /* NOCACHE+IMS should not eject a valid object */
1105 else if (BIT_TEST(request->flags, REQ_IMS))
1106 (void) 0;
1107 /* Request-Range should not eject a valid object */
1108 else if (BIT_TEST(request->flags, REQ_RANGE))
1109 (void) 0;
1110 else
1111 storeRelease(entry);
1112 ipcacheReleaseInvalid(http->request->host);
1113 entry = NULL;
1114 http->log_type = LOG_TCP_CLIENT_REFRESH;
1115 } else if (checkNegativeHit(entry)) {
1116 http->log_type = LOG_TCP_NEGATIVE_HIT;
1117 } else if (refreshCheck(entry, request, 0)) {
1118 /* The object is in the cache, but it needs to be validated. Use
1119 * LOG_TCP_REFRESH_MISS for the time being, maybe change it to
1120 * _HIT later in icpHandleIMSReply() */
1121 if (request->protocol == PROTO_HTTP)
1122 http->log_type = LOG_TCP_REFRESH_MISS;
1123 else
1124 http->log_type = LOG_TCP_MISS; /* XXX zoinks */
1125 } else if (BIT_TEST(request->flags, REQ_IMS)) {
1126 /* User-initiated IMS request for something we think is valid */
1127 http->log_type = LOG_TCP_IMS_MISS;
1128 } else {
1129 if (entry->mem_status == IN_MEMORY)
1130 http->log_type = LOG_TCP_MEM_HIT;
1131 else
1132 http->log_type = LOG_TCP_HIT;
1133 }
1134 debug(12, 4) ("icpProcessRequest: %s for '%s'\n",
1135 log_tags[http->log_type],
1136 http->url);
1137 if (entry) {
1138 storeLockObject(entry);
1139 storeCreateMemObject(entry, http->url, http->log_url);
1140 storeClientListAdd(entry, http);
1141 }
1142 http->entry = entry; /* Save a reference to the object */
1143 http->out.offset = 0;
1144 switch (http->log_type) {
1145 case LOG_TCP_HIT:
1146 case LOG_TCP_NEGATIVE_HIT:
1147 case LOG_TCP_MEM_HIT:
1148 entry->refcount++; /* HIT CASE */
1149 storeClientCopy(entry,
1150 http->out.offset,
1151 http->out.offset,
1152 SM_PAGE_SIZE,
1153 get_free_4k_page(),
1154 clientCacheHit,
1155 http);
1156 break;
1157 case LOG_TCP_IMS_MISS:
1158 storeClientCopy(entry,
1159 http->out.offset,
1160 http->out.offset,
1161 SM_PAGE_SIZE,
1162 get_free_4k_page(),
1163 icpGetHeadersForIMS,
1164 http);
1165 break;
1166 case LOG_TCP_REFRESH_MISS:
1167 icpProcessExpired(fd, http);
1168 break;
1169 default:
1170 clientProcessMISS(fd, http);
1171 break;
1172 }
1173}
1174
1175/*
1176 * Prepare to fetch the object as it's a cache miss of some kind.
1177 */
1178static void
1179clientProcessMISS(int fd, clientHttpRequest * http)
1180{
1181 char *url = http->url;
1182 char *request_hdr = http->request->headers;
1183 StoreEntry *entry = NULL;
1184 aclCheck_t ch;
1185 int answer;
1186 ErrorState *err = NULL;
1187 debug(12, 4) ("clientProcessMISS: '%s %s'\n",
1188 RequestMethodStr[http->request->method], url);
1189 debug(12, 10) ("clientProcessMISS: request_hdr:\n%s\n", request_hdr);
1190
1191 /* Check if this host is allowed to fetch MISSES from us */
1192 memset(&ch, '\0', sizeof(aclCheck_t));
1193 ch.src_addr = http->conn->peer.sin_addr;
1194 ch.request = http->request;
1195 answer = aclCheckFast(Config.accessList.miss, &ch);
1196 if (answer == 0) {
1197 http->al.http.code = HTTP_FORBIDDEN;
1198 err = errorCon(ERR_CANNOT_FORWARD, HTTP_FORBIDDEN);
1199 err->request = requestLink(http->request);
1200 err->src_addr = http->conn->peer.sin_addr;
1201 err->callback = clientErrorComplete;
1202 err->callback_data = http;
1203 errorSend(fd, err);
1204 return;
1205 }
1206 /* Get rid of any references to a StoreEntry (if any) */
1207 if (http->entry) {
1208 storeUnregister(http->entry, http);
1209 storeUnlockObject(http->entry);
1210 http->entry = NULL;
1211 }
1212 entry = storeCreateEntry(url,
1213 http->log_url,
1214 http->request->flags,
1215 http->request->method);
1216 /* NOTE, don't call storeLockObject(), storeCreateEntry() does it */
1217 storeClientListAdd(entry, http);
1218 entry->mem_obj->fd = fd;
1219 entry->refcount++; /* MISS CASE */
1220 http->entry = entry;
1221 http->out.offset = 0;
1222 /* Register with storage manager to receive updates when data comes in. */
1223 storeClientCopy(entry,
1224 http->out.offset,
1225 http->out.offset,
1226 SM_PAGE_SIZE,
1227 get_free_4k_page(),
1228 clientSendMoreData,
1229 http);
1230 /* protoDispatch() needs to go after storeClientCopy() at least
1231 * for OBJCACHE requests */
1232 protoDispatch(fd, http->entry, http->request);
1233 return;
1234}
1235
1236/*
1237 * parseHttpRequest()
1238 *
1239 * Returns
1240 * NULL on error or incomplete request
1241 * a clientHttpRequest structure on success
1242 */
1243static clientHttpRequest *
1244parseHttpRequest(ConnStateData * conn, method_t * method_p, int *status,
1245 char **headers_p, size_t * headers_sz_p)
1246{
1247 char *inbuf = NULL;
1248 char *mstr = NULL;
1249 char *url = NULL;
1250 char *req_hdr = NULL;
1251 LOCAL_ARRAY(char, http_ver_s, 32);
1252 float http_ver;
1253 char *token = NULL;
1254 char *t = NULL;
1255 char *end = NULL;
1256 int free_request = 0;
1257 size_t header_sz; /* size of headers, not including first line */
1258 size_t req_sz; /* size of whole request */
1259 size_t url_sz;
1260 method_t method;
1261 clientHttpRequest *http = NULL;
1262
1263 /* Make sure a complete line has been received */
1264 if (strchr(conn->in.buf, '\n') == NULL) {
1265 debug(12, 5) ("Incomplete request line, waiting for more data\n");
1266 *status = 0;
1267 return NULL;
1268 }
1269 /* Use xmalloc/xmemcpy instead of xstrdup because inbuf might
1270 * contain NULL bytes; especially for POST data */
1271 inbuf = xmalloc(conn->in.offset + 1);
1272 xmemcpy(inbuf, conn->in.buf, conn->in.offset);
1273 *(inbuf + conn->in.offset) = '\0';
1274
1275 /* Look for request method */
1276 if ((mstr = strtok(inbuf, "\t ")) == NULL) {
1277 debug(12, 1) ("parseHttpRequest: Can't get request method\n");
1278 xfree(inbuf);
1279 *status = -1;
1280 return NULL;
1281 }
1282 method = urlParseMethod(mstr);
1283 if (method == METHOD_NONE) {
1284 debug(12, 1) ("parseHttpRequest: Unsupported method '%s'\n", mstr);
1285 xfree(inbuf);
1286 *status = -1;
1287 return NULL;
1288 }
1289 debug(12, 5) ("parseHttpRequest: Method is '%s'\n", mstr);
1290
1291 /* look for URL */
1292 if ((url = strtok(NULL, "\r\n\t ")) == NULL) {
1293 debug(12, 1) ("parseHttpRequest: Missing URL\n");
1294 xfree(inbuf);
1295 *status = -1;
1296 return NULL;
1297 }
1298 debug(12, 5) ("parseHttpRequest: Request is '%s'\n", url);
1299
1300 token = strtok(NULL, null_string);
1301 for (t = token; t && *t && *t != '\n' && *t != '\r'; t++);
1302 if (t == NULL || *t == '\0' || t == token) {
1303 debug(12, 3) ("parseHttpRequest: Missing HTTP identifier\n");
1304 xfree(inbuf);
1305 *status = -1;
1306 return NULL;
1307 }
1308 memset(http_ver_s, '\0', 32);
1309 xstrncpy(http_ver_s, token, 32);
1310 sscanf(http_ver_s, "HTTP/%f", &http_ver);
1311 debug(12, 5) ("parseHttpRequest: HTTP version is '%3.1f'\n", http_ver);
1312
1313 /* Check if headers are received */
1314 if ((end = mime_headers_end(t)) == NULL) {
1315 xfree(inbuf);
1316 *status = 0;
1317 return NULL;
1318 }
1319 while (isspace(*t))
1320 t++;
1321 req_hdr = t;
1322 header_sz = end - req_hdr;
1323 req_sz = end - inbuf;
1324
1325 /* Ok, all headers are received */
1326 http = xcalloc(1, sizeof(clientHttpRequest));
1327 cbdataAdd(http);
1328 http->http_ver = http_ver;
1329 http->conn = conn;
1330 http->start = current_time;
1331 http->req_sz = req_sz;
1332 *headers_sz_p = header_sz;
1333 *headers_p = xmalloc(header_sz + 1);
1334 xmemcpy(*headers_p, req_hdr, header_sz);
1335 *(*headers_p + header_sz) = '\0';
1336
1337 debug(12, 5) ("parseHttpRequest: Request Header is\n%s\n", *headers_p);
1338
1339 /* Assign http->url */
1340 if ((t = strchr(url, '\n'))) /* remove NL */
1341 *t = '\0';
1342 if ((t = strchr(url, '\r'))) /* remove CR */
1343 *t = '\0';
1344 if ((t = strchr(url, '#'))) /* remove HTML anchors */
1345 *t = '\0';
1346
1347 /* see if we running in Config2.Accel.on, if so got to convert it to URL */
1348 if (Config2.Accel.on && *url == '/') {
1349 /* prepend the accel prefix */
1350 if (vhost_mode) {
1351 /* Put the local socket IP address as the hostname */
1352 url_sz = strlen(url) + 32 + Config.appendDomainLen;
1353 http->url = xcalloc(url_sz, 1);
1354 snprintf(http->url, url_sz, "http://%s:%d%s",
1355 inet_ntoa(http->conn->me.sin_addr),
1356 (int) Config.Accel.port,
1357 url);
1358 debug(12, 5) ("VHOST REWRITE: '%s'\n", http->url);
1359 } else if (opt_accel_uses_host && (t = mime_get_header(req_hdr, "Host"))) {
1360 /* If a Host: header was specified, use it to build the URL
1361 * instead of the one in the Config file. */
1362 /*
1363 * XXX Use of the Host: header here opens a potential
1364 * security hole. There are no checks that the Host: value
1365 * corresponds to one of your servers. It might, for example,
1366 * refer to www.playboy.com. The 'dst' and/or 'dst_domain' ACL
1367 * types should be used to prevent httpd-accelerators
1368 * handling requests for non-local servers */
1369 strtok(t, " :/;@");
1370 url_sz = strlen(url) + 32 + Config.appendDomainLen;
1371 http->url = xcalloc(url_sz, 1);
1372 snprintf(http->url, url_sz, "http://%s:%d%s",
1373 t, (int) Config.Accel.port, url);
1374 } else {
1375 url_sz = strlen(Config2.Accel.prefix) + strlen(url) +
1376 Config.appendDomainLen + 1;
1377 http->url = xcalloc(url_sz, 1);
1378 snprintf(http->url, url_sz, "%s%s", Config2.Accel.prefix, url);
1379 }
1380 http->accel = 1;
1381 } else {
1382 /* URL may be rewritten later, so make extra room */
1383 url_sz = strlen(url) + Config.appendDomainLen + 5;
1384 http->url = xcalloc(url_sz, 1);
1385 strcpy(http->url, url);
1386 http->accel = 0;
1387 }
1388 http->log_url = xstrdup(http->url);
1389 debug(12, 5) ("parseHttpRequest: Complete request received\n");
1390 if (free_request)
1391 safe_free(url);
1392 xfree(inbuf);
1393 *method_p = method;
1394 *status = 1;
1395 return http;
1396}
1397
1398static int
79d39a72 1399clientReadDefer(int fdnotused, void *data)
7a2f978b 1400{
1401 ConnStateData *conn = data;
1402 return conn->defer.until > squid_curtime;
1403}
1404
1405static void
1406clientReadRequest(int fd, void *data)
1407{
1408 ConnStateData *conn = data;
1409 int parser_return_code = 0;
1410 int k;
1411 request_t *request = NULL;
1412 char *tmp;
1413 int size;
1414 int len;
1415 method_t method;
1416 clientHttpRequest *http = NULL;
1417 clientHttpRequest **H = NULL;
1418 char *headers;
1419 size_t headers_sz;
1420 ErrorState *err = NULL;
1421 fde *F = &fd_table[fd];
1422
1423 len = conn->in.size - conn->in.offset - 1;
1424 debug(12, 4) ("clientReadRequest: FD %d: reading request...\n", fd);
1425 size = read(fd, conn->in.buf + conn->in.offset, len);
1426 fd_bytes(fd, size, FD_READ);
1427
1428 if (size == 0) {
1429 if (conn->chr == NULL) {
1430 /* no current or pending requests */
1431 comm_close(fd);
1432 return;
1433 }
1434 /* It might be half-closed, we can't tell */
1435 debug(12, 5) ("clientReadRequest: FD %d closed?\n", fd);
1436 BIT_SET(F->flags, FD_SOCKET_EOF);
1437 conn->defer.until = squid_curtime + 1;
1438 conn->defer.n++;
1439 commSetSelect(fd, COMM_SELECT_READ, clientReadRequest, conn, 0);
1440 return;
1441 } else if (size < 0) {
1442 if (errno == EWOULDBLOCK || errno == EAGAIN || errno == EINTR) {
1443 commSetSelect(fd, COMM_SELECT_READ, clientReadRequest, conn, 0);
1444 } else {
1445 debug(50, 2) ("clientReadRequest: FD %d: %s\n", fd, xstrerror());
1446 comm_close(fd);
1447 }
1448 return;
1449 }
1450 conn->in.offset += size;
1451 conn->in.buf[conn->in.offset] = '\0'; /* Terminate the string */
1452
1453 while (conn->in.offset > 0) {
1454 http = parseHttpRequest(conn,
1455 &method,
1456 &parser_return_code,
1457 &headers,
1458 &headers_sz);
1459 if (http) {
1460 assert(http->req_sz > 0);
1461 conn->in.offset -= http->req_sz;
1462 assert(conn->in.offset >= 0);
1463 if (conn->in.offset > 0) {
1464 tmp = xstrdup(conn->in.buf + http->req_sz);
1465 xstrncpy(conn->in.buf, tmp, conn->in.size);
1466 safe_free(tmp);
1467 }
1468 /* link */
1469 for (H = &conn->chr; *H; H = &(*H)->next);
1470 *H = http;
1471 conn->nrequests++;
1472 commSetTimeout(fd, Config.Timeout.lifetime, NULL, NULL);
1473 if ((request = urlParse(method, http->url)) == NULL) {
1474 debug(12, 5) ("Invalid URL: %s\n", http->url);
1475 err = errorCon(ERR_INVALID_URL, HTTP_BAD_REQUEST);
1476 err->src_addr = conn->peer.sin_addr;
1477 err->callback = clientErrorComplete;
1478 err->callback_data = http;
1479 err->url = xstrdup(http->url);
1480 http->al.http.code = err->http_status;
1481 errorSend(fd, err);
1482 safe_free(headers);
1483 break;
1484 }
1485 safe_free(http->log_url);
1486 http->log_url = xstrdup(urlCanonicalClean(request));
1487 request->client_addr = conn->peer.sin_addr;
1488 request->http_ver = http->http_ver;
1489 request->headers = headers;
1490 request->headers_sz = headers_sz;
1491 if (!urlCheckRequest(request)) {
1492 err = errorCon(ERR_UNSUP_REQ, HTTP_NOT_IMPLEMENTED);
1493 err->src_addr = conn->peer.sin_addr;
1494 err->callback = clientErrorComplete;
1495 err->callback_data = http;
1496 err->request = requestLink(request);
1497 http->al.http.code = err->http_status;
1498 errorSend(fd, err);
1499 return;
1500 }
1501 http->request = requestLink(request);
1502 clientAccessCheck(http);
1503 /* break here for NON-GET because most likely there is a
1504 * reqeust body following and we don't want to parse it
1505 * as though it was new request */
1506 if (request->method != METHOD_GET) {
1507 if (conn->in.offset) {
1508 request->body_sz = conn->in.offset;
1509 request->body = xmalloc(request->body_sz);
1510 xmemcpy(request->body, conn->in.buf, request->body_sz);
1511 conn->in.offset = 0;
1512 }
1513 break;
1514 }
1515 commSetSelect(fd, COMM_SELECT_READ, clientReadRequest, conn, 0);
1516 continue; /* while offset > 0 */
1517 } else if (parser_return_code == 0) {
1518 /*
1519 * Partial request received; reschedule until parseHttpRequest()
1520 * is happy with the input
1521 */
1522 k = conn->in.size - 1 - conn->in.offset;
1523 if (k == 0) {
1524 if (conn->in.offset >= Config.maxRequestSize) {
1525 /* The request is too large to handle */
1526 debug(12, 0) ("Request won't fit in buffer.\n");
1527 debug(12, 0) ("Config 'request_size'= %d bytes.\n",
1528 Config.maxRequestSize);
1529 debug(12, 0) ("This request = %d bytes.\n",
1530 conn->in.offset);
1531 err = errorCon(ERR_INVALID_REQ, HTTP_REQUEST_ENTITY_TOO_LARGE);
1532 err->callback = clientErrorComplete;
1533 err->callback_data = NULL;
1534 errorSend(fd, err);
1535 return;
1536 }
1537 /* Grow the request memory area to accomodate for a large request */
1538 conn->in.size += REQUEST_BUF_SIZE;
1539 conn->in.buf = xrealloc(conn->in.buf, conn->in.size);
1540 meta_data.misc += REQUEST_BUF_SIZE;
1541 debug(12, 2) ("Handling a large request, offset=%d inbufsize=%d\n",
1542 conn->in.offset, conn->in.size);
1543 k = conn->in.size - 1 - conn->in.offset;
1544 }
1545 commSetSelect(fd, COMM_SELECT_READ, clientReadRequest, conn, 0);
1546 break;
1547 } else {
1548 /* parser returned -1 */
1549 debug(12, 1) ("clientReadRequest: FD %d Invalid Request\n", fd);
1550 err = errorCon(ERR_INVALID_REQ, HTTP_BAD_REQUEST);
1551 err->callback = clientErrorComplete;
1552 err->callback_data = NULL;
1553 errorSend(fd, err);
1554 return;
1555 }
1556 }
1557}
1558
1559/* general lifetime handler for HTTP requests */
1560static void
1561requestTimeout(int fd, void *data)
1562{
1563 ConnStateData *conn = data;
1564 ErrorState *err;
1565 debug(12, 2) ("requestTimeout: FD %d: lifetime is expired.\n", fd);
1566 if (fd_table[fd].rwstate) {
1567 /* Some data has been sent to the client, just close the FD */
1568 comm_close(fd);
1569 } else if (conn->nrequests) {
1570 /* assume its a persistent connection; just close it */
1571 comm_close(fd);
1572 } else {
1573 /* Generate an error */
1574 err = errorCon(ERR_LIFETIME_EXP, HTTP_REQUEST_TIMEOUT);
1575 err->callback = clientErrorComplete;
1576 err->url = xstrdup("N/A");
1577 errorSend(fd, err);
1578 /* if we don't close() here, we still need a timeout handler! */
1579 commSetTimeout(fd, 30, requestTimeout, conn);
1580 }
1581}
1582
1583int
79d39a72 1584httpAcceptDefer(int fdnotused, void *notused)
7a2f978b 1585{
1586 return !fdstat_are_n_free_fd(RESERVED_FD);
1587}
1588
1589/* Handle a new connection on ascii input socket. */
1590void
1591httpAccept(int sock, void *notused)
1592{
1593 int fd = -1;
1594 ConnStateData *connState = NULL;
1595 struct sockaddr_in peer;
1596 struct sockaddr_in me;
1597 memset(&peer, '\0', sizeof(struct sockaddr_in));
1598 memset(&me, '\0', sizeof(struct sockaddr_in));
1599 commSetSelect(sock, COMM_SELECT_READ, httpAccept, NULL, 0);
1600 if ((fd = comm_accept(sock, &peer, &me)) < 0) {
1601 debug(50, 1) ("httpAccept: FD %d: accept failure: %s\n",
1602 sock, xstrerror());
1603 return;
1604 }
1605 ntcpconn++;
1606 debug(12, 4) ("httpAccept: FD %d: accepted\n", fd);
1607 connState = xcalloc(1, sizeof(ConnStateData));
1608 connState->peer = peer;
1609 connState->log_addr = peer.sin_addr;
1610 connState->log_addr.s_addr &= Config.Addrs.client_netmask.s_addr;
1611 connState->me = me;
1612 connState->fd = fd;
1613 connState->ident.fd = -1;
1614 connState->in.size = REQUEST_BUF_SIZE;
1615 connState->in.buf = xcalloc(connState->in.size, 1);
1616 cbdataAdd(connState);
1617 meta_data.misc += connState->in.size;
1618 comm_add_close_handler(fd, connStateFree, connState);
1619 if (Config.onoff.log_fqdn)
1620 fqdncache_gethostbyaddr(peer.sin_addr, FQDN_LOOKUP_IF_MISS);
1621 commSetTimeout(fd, Config.Timeout.request, requestTimeout, connState);
1622 commSetSelect(fd, COMM_SELECT_READ, clientReadRequest, connState, 0);
1623 commSetDefer(fd, clientReadDefer, connState);
1624}
1625
1626void
1627AppendUdp(icpUdpData * item)
1628{
1629 item->next = NULL;
1630 if (UdpQueueHead == NULL) {
1631 UdpQueueHead = item;
1632 UdpQueueTail = item;
1633 } else if (UdpQueueTail == UdpQueueHead) {
1634 UdpQueueTail = item;
1635 UdpQueueHead->next = item;
1636 } else {
1637 UdpQueueTail->next = item;
1638 UdpQueueTail = item;
1639 }
1640}
1641
1642/* return 1 if the request should be aborted */
1643static int
1644CheckQuickAbort2(const clientHttpRequest * http)
1645{
1646 long curlen;
1647 long minlen;
1648 long expectlen;
1649
1650 if (!BIT_TEST(http->request->flags, REQ_CACHABLE))
1651 return 1;
1652 if (BIT_TEST(http->entry->flag, KEY_PRIVATE))
1653 return 1;
1654 if (http->entry->mem_obj == NULL)
1655 return 1;
1656 expectlen = http->entry->mem_obj->reply->content_length;
1657 curlen = http->entry->mem_obj->inmem_hi;
1658 minlen = Config.quickAbort.min;
1659 if (minlen < 0)
1660 /* disabled */
1661 return 0;
1662 if (curlen > expectlen)
1663 /* bad content length */
1664 return 1;
1665 if ((expectlen - curlen) < minlen)
1666 /* only little more left */
1667 return 0;
1668 if ((expectlen - curlen) > Config.quickAbort.max)
1669 /* too much left to go */
1670 return 1;
1671 if ((curlen / (expectlen / 128U)) > Config.quickAbort.pct)
1672 /* past point of no return */
1673 return 0;
1674 return 1;
1675}
1676
1677
1678static void
1679CheckQuickAbort(clientHttpRequest * http)
1680{
1681 StoreEntry *entry = http->entry;
1682 /* Note, set entry here because http->entry might get changed (for IMS
1683 * requests) during the storeAbort() call */
1684 if (entry == NULL)
1685 return;
1686 if (storePendingNClients(entry) > 1)
1687 return;
1688 if (entry->store_status != STORE_PENDING)
1689 return;
1690 if (CheckQuickAbort2(http) == 0)
1691 return;
1692 debug(12, 3) ("CheckQuickAbort: ABORTING %s\n", storeUrl(entry));
1693 storeAbort(entry, 1);
1694}
1695
1696static int
1697icpCheckTransferDone(clientHttpRequest * http)
1698{
1699 StoreEntry *entry = http->entry;
1700 MemObject *mem = NULL;
1701
1702 if (entry == NULL)
1703 return 0;
1704 if (entry->store_status != STORE_PENDING)
1705 if (http->out.offset >= entry->object_len)
1706 return 1;
1707 if ((mem = entry->mem_obj) == NULL)
1708 return 0;
1709 if (mem->reply->content_length == 0)
1710 return 0;
1711 if (http->out.offset >= mem->reply->content_length + mem->reply->hdr_sz)
1712 return 1;
1713 return 0;
1714}
1715
1716static char *
1717icpConstruct304reply(struct _http_reply *source)
1718{
1719 LOCAL_ARRAY(char, line, 256);
1720 LOCAL_ARRAY(char, reply, 8192);
1721
1722 memset(reply, '\0', 8192);
1723 strcpy(reply, "HTTP/1.0 304 Not Modified\r\n");
1724 if (source->date > -1) {
1725 snprintf(line, 256, "Date: %s\r\n", mkrfc1123(source->date));
1726 strcat(reply, line);
1727 }
1728 if ((int) strlen(source->content_type) > 0) {
1729 snprintf(line, 256, "Content-type: %s\r\n", source->content_type);
1730 strcat(reply, line);
1731 }
1732 if (source->content_length) {
1733 snprintf(line, 256, "Content-length: %d\r\n", source->content_length);
1734 strcat(reply, line);
1735 }
1736 if (source->expires > -1) {
1737 snprintf(line, 256, "Expires: %s\r\n", mkrfc1123(source->expires));
1738 strcat(reply, line);
1739 }
1740 if (source->last_modified > -1) {
1741 snprintf(line, 256, "Last-modified: %s\r\n",
1742 mkrfc1123(source->last_modified));
1743 strcat(reply, line);
1744 }
1745 strcat(reply, "\r\n");
1746 return reply;
1747}
1748
1749/*
1750 * This function is designed to serve a fairly specific purpose.
1751 * Occasionally our vBNS-connected caches can talk to each other, but not
1752 * the rest of the world. Here we try to detect frequent failures which
1753 * make the cache unusable (e.g. DNS lookup and connect() failures). If
1754 * the failure:success ratio goes above 1.0 then we go into "hit only"
1755 * mode where we only return UDP_HIT or UDP_MISS_NOFETCH. Neighbors
1756 * will only fetch HITs from us if they are using the ICP protocol. We
1757 * stay in this mode for 5 minutes.
1758 *
1759 * Duane W., Sept 16, 1996
1760 */
1761
1762static void
1763checkFailureRatio(log_type rcode, hier_code hcode)
1764{
1765 static double fail_ratio = 0.0;
1766 static double magic_factor = 100;
1767 double n_good;
1768 double n_bad;
1769 if (hcode == HIER_NONE)
1770 return;
1771 n_good = magic_factor / (1.0 + fail_ratio);
1772 n_bad = magic_factor - n_good;
1773 switch (rcode) {
1774 case ERR_DNS_FAIL:
1775 case ERR_CONNECT_FAIL:
1776 case ERR_READ_ERROR:
1777 n_bad++;
1778 break;
1779 default:
1780 n_good++;
1781 }
1782 fail_ratio = n_bad / n_good;
1783 if (hit_only_mode_until > squid_curtime)
1784 return;
1785 if (fail_ratio < 1.0)
1786 return;
1787 debug(12, 0) ("Failure Ratio at %4.2f\n", fail_ratio);
1788 debug(12, 0) ("Going into hit-only-mode for %d minutes...\n",
1789 FAILURE_MODE_TIME / 60);
1790 hit_only_mode_until = squid_curtime + FAILURE_MODE_TIME;
1791 fail_ratio = 0.8; /* reset to something less than 1.0 */
1792}