Below is the file 'src/client_side.c' from this revision. You can also download the file.
/* * $Id: client_side.c,v 1.561.2.96 2006/03/10 22:58:35 hno Exp $ * * DEBUG: section 33 Client-side Routines * AUTHOR: Duane Wessels * * SQUID Web Proxy Cache http://www.squid-cache.org/ * ---------------------------------------------------------- * * Squid is the result of efforts by numerous individuals from * the Internet community; see the CONTRIBUTORS file for full * details. Many organizations have provided support for Squid's * development; see the SPONSORS file for full details. Squid is * Copyrighted (C) 2001 by the Regents of the University of * California; see the COPYRIGHT file for full details. Squid * incorporates software developed and/or copyrighted by other * sources; see the CREDITS file for full details. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. * */ #include "squid.h" #if IPF_TRANSPARENT #if HAVE_SYS_IOCTL_H #include <sys/ioctl.h> #endif #include <netinet/tcp.h> #include <net/if.h> /* SG - 14 Aug 2005 * Workaround needed to allow the build of both ipfilter and ARP acl * support on Solaris x86. * * Some defines, like * #define free + * are used in squid.h to block misuse of standard malloc routines * where the Squid versions should be used. This pollutes the C/C++ * token namespace crashing any structures or classes having members * of the same names. */ #ifdef _SQUID_SOLARIS_ #undef free #endif #ifdef HAVE_IPL_H #include <ipl.h> #elif HAVE_NETINET_IPL_H #include <netinet/ipl.h> #endif #if HAVE_IP_FIL_COMPAT_H #include <ip_fil_compat.h> #elif HAVE_NETINET_IP_FIL_COMPAT_H #include <netinet/ip_fil_compat.h> #elif HAVE_IP_COMPAT_H #include <ip_compat.h> #elif HAVE_NETINET_IP_COMPAT_H #include <netinet/ip_compat.h> #endif #if HAVE_IP_FIL_H #include <ip_fil.h> #elif HAVE_NETINET_IP_FIL_H #include <netinet/ip_fil.h> #endif #if HAVE_IP_NAT_H #include <ip_nat.h> #elif HAVE_NETINET_IP_NAT_H #include <netinet/ip_nat.h> #endif #endif #if PF_TRANSPARENT #include <sys/types.h> #include <sys/socket.h> #include <sys/ioctl.h> #include <sys/fcntl.h> #include <net/if.h> #include <netinet/in.h> #include <net/pfvar.h> #endif #if LINUX_NETFILTER #include <linux/netfilter_ipv4.h> #endif #if LINGERING_CLOSE #define comm_close comm_lingering_close #endif static const char *const crlf = "\r\n"; #define FAILURE_MODE_TIME 300 /* Local functions */ static CWCB clientWriteComplete; static CWCB clientWriteBodyComplete; static PF clientReadRequest; static PF connStateFree; static PF requestTimeout; static PF clientLifetimeTimeout; static int clientCheckTransferDone(clientHttpRequest *); static int clientGotNotEnough(clientHttpRequest *); static void checkFailureRatio(err_type, hier_code); static void clientProcessMiss(clientHttpRequest *); static void clientBuildReplyHeader(clientHttpRequest * http, HttpReply * rep); static clientHttpRequest *parseHttpRequestAbort(ConnStateData * conn, const char *uri); static clientHttpRequest *parseHttpRequest(ConnStateData *, method_t *, int *, char **, size_t *); static void clientRedirectStart(clientHttpRequest * http); static RH clientRedirectDone; static void clientCheckNoCache(clientHttpRequest *); static void clientCheckNoCacheDone(int answer, void *data); static STCB clientHandleIMSReply; static int clientGetsOldEntry(StoreEntry * new, StoreEntry * old, request_t * request); static int checkAccelOnly(clientHttpRequest *); #if USE_IDENT static IDCB clientIdentDone; #endif static int clientOnlyIfCached(clientHttpRequest * http); static STCB clientSendMoreData; static STCB clientCacheHit; static void clientSetKeepaliveFlag(clientHttpRequest *); static void clientPackRangeHdr(const HttpReply * rep, const HttpHdrRangeSpec * spec, String boundary, MemBuf * mb); static void clientPackTermBound(String boundary, MemBuf * mb); static void clientInterpretRequestHeaders(clientHttpRequest *); static void clientProcessRequest(clientHttpRequest *); static void clientProcessExpired(void *data); static void clientProcessOnlyIfCachedMiss(clientHttpRequest * http); static int clientCachable(clientHttpRequest * http); static int clientHierarchical(clientHttpRequest * http); static int clientCheckContentLength(request_t * r); static DEFER httpAcceptDefer; static log_type clientProcessRequest2(clientHttpRequest * http); static int clientReplyBodyTooLarge(clientHttpRequest *, squid_off_t clen); static int clientRequestBodyTooLarge(squid_off_t clen); static void clientProcessBody(ConnStateData * conn); static void clientEatRequestBody(clientHttpRequest *); static BODY_HANDLER clientReadBody; static void clientAbortBody(request_t * req); static int checkAccelOnly(clientHttpRequest * http) { /* return TRUE if someone makes a proxy request to us and * we are in httpd-accel only mode */ if (!Config2.Accel.on) return 0; if (Config.onoff.accel_with_proxy) return 0; if (http->request->protocol == PROTO_CACHEOBJ) return 0; if (http->flags.accel) return 0; if (http->request->method == METHOD_PURGE) return 0; return 1; } #if USE_IDENT static void clientIdentDone(const char *ident, void *data) { ConnStateData *conn = data; xstrncpy(conn->rfc931, ident ? ident : dash_str, USER_IDENT_SZ); } #endif static aclCheck_t * clientAclChecklistCreate(const acl_access * acl, const clientHttpRequest * http) { aclCheck_t *ch; ConnStateData *conn = http->conn; ch = aclChecklistCreate(acl, http->request, conn->rfc931); /* * hack for ident ACL. It needs to get full addresses, and a * place to store the ident result on persistent connections... */ /* connection oriented auth also needs these two lines for it's operation. */ ch->conn = conn; cbdataLock(ch->conn); return ch; } void clientAccessCheck(void *data) { clientHttpRequest *http = data; if (checkAccelOnly(http)) { /* deny proxy requests in accel_only mode */ debug(33, 1) ("clientAccessCheck: proxy request denied in accel_only mode\n"); clientAccessCheckDone(ACCESS_DENIED, http); return; } http->acl_checklist = clientAclChecklistCreate(Config.accessList.http, http); aclNBCheck(http->acl_checklist, clientAccessCheckDone, http); } /* * returns true if client specified that the object must come from the cache * without contacting origin server */ static int clientOnlyIfCached(clientHttpRequest * http) { const request_t *r = http->request; assert(r); return r->cache_control && EBIT_TEST(r->cache_control->mask, CC_ONLY_IF_CACHED); } StoreEntry * clientCreateStoreEntry(clientHttpRequest * h, method_t m, request_flags flags) { StoreEntry *e; /* * For erroneous requests, we might not have a h->request, * so make a fake one. */ if (h->request == NULL) h->request = requestLink(requestCreate(m, PROTO_NONE, null_string)); e = storeCreateEntry(h->uri, h->log_uri, flags, m); h->sc = storeClientListAdd(e, h); #if DELAY_POOLS if (h->log_type != LOG_TCP_DENIED) delaySetStoreClient(h->sc, delayClient(h)); #endif storeClientCopy(h->sc, e, 0, 0, CLIENT_SOCK_SZ, memAllocate(MEM_CLIENT_SOCK_BUF), clientSendMoreData, h); return e; } void clientAccessCheckDone(int answer, void *data) { clientHttpRequest *http = data; err_type page_id; http_status status; ErrorState *err = NULL; char *proxy_auth_msg = NULL; debug(33, 2) ("The request %s %s is %s, because it matched '%s'\n", RequestMethodStr[http->request->method], http->uri, answer == ACCESS_ALLOWED ? "ALLOWED" : "DENIED", AclMatchedName ? AclMatchedName : "NO ACL's"); proxy_auth_msg = authenticateAuthUserRequestMessage(http->conn->auth_user_request ? http->conn->auth_user_request : http->request->auth_user_request); http->acl_checklist = NULL; if (answer == ACCESS_ALLOWED) { safe_free(http->uri); http->uri = xstrdup(urlCanonical(http->request)); assert(http->redirect_state == REDIRECT_NONE); http->redirect_state = REDIRECT_PENDING; clientRedirectStart(http); } else { int require_auth = (answer == ACCESS_REQ_PROXY_AUTH || aclIsProxyAuth(AclMatchedName)); debug(33, 5) ("Access Denied: %s\n", http->uri); debug(33, 5) ("AclMatchedName = %s\n", AclMatchedName ? AclMatchedName : "<null>"); debug(33, 5) ("Proxy Auth Message = %s\n", proxy_auth_msg ? proxy_auth_msg : "<null>"); /* * NOTE: get page_id here, based on AclMatchedName because * if USE_DELAY_POOLS is enabled, then AclMatchedName gets * clobbered in the clientCreateStoreEntry() call * just below. Pedro Ribeiro <pribeiro@isel.pt> */ page_id = aclGetDenyInfoPage(&Config.denyInfoList, AclMatchedName); http->log_type = LOG_TCP_DENIED; http->entry = clientCreateStoreEntry(http, http->request->method, null_request_flags); if (require_auth) { if (!http->flags.accel) { /* Proxy authorisation needed */ status = HTTP_PROXY_AUTHENTICATION_REQUIRED; } else { /* WWW authorisation needed */ status = HTTP_UNAUTHORIZED; } if (page_id == ERR_NONE) page_id = ERR_CACHE_ACCESS_DENIED; } else { status = HTTP_FORBIDDEN; if (page_id == ERR_NONE) page_id = ERR_ACCESS_DENIED; } err = errorCon(page_id, status); err->request = requestLink(http->request); err->src_addr = http->conn->peer.sin_addr; if (http->conn->auth_user_request) err->auth_user_request = http->conn->auth_user_request; else if (http->request->auth_user_request) err->auth_user_request = http->request->auth_user_request; /* lock for the error state */ if (err->auth_user_request) authenticateAuthUserRequestLock(err->auth_user_request); err->callback_data = NULL; errorAppendEntry(http->entry, err); } } static void clientRedirectAccessCheckDone(int answer, void *data) { clientHttpRequest *http = data; http->acl_checklist = NULL; if (answer == ACCESS_ALLOWED) redirectStart(http, clientRedirectDone, http); else clientRedirectDone(http, NULL); } static void clientRedirectStart(clientHttpRequest * http) { debug(33, 5) ("clientRedirectStart: '%s'\n", http->uri); if (Config.Program.redirect == NULL) { clientRedirectDone(http, NULL); return; } if (Config.accessList.redirector) { http->acl_checklist = clientAclChecklistCreate(Config.accessList.redirector, http); aclNBCheck(http->acl_checklist, clientRedirectAccessCheckDone, http); } else { redirectStart(http, clientRedirectDone, http); } } static void clientRedirectDone(void *data, char *result) { clientHttpRequest *http = data; request_t *new_request = NULL; request_t *old_request = http->request; debug(33, 5) ("clientRedirectDone: '%s' result=%s\n", http->uri, result ? result : "NULL"); assert(http->redirect_state == REDIRECT_PENDING); http->redirect_state = REDIRECT_DONE; if (result) { http_status status = (http_status) atoi(result); if (status == HTTP_MOVED_PERMANENTLY || status == HTTP_MOVED_TEMPORARILY || status == HTTP_SEE_OTHER || status == HTTP_TEMPORARY_REDIRECT) { char *t = result; if ((t = strchr(result, ':')) != NULL) { http->redirect.status = status; http->redirect.location = xstrdup(t + 1); } else { debug(33, 1) ("clientRedirectDone: bad input: %s\n", result); } } else if (strcmp(result, http->uri)) new_request = urlParse(old_request->method, result); } if (new_request) { safe_free(http->uri); http->uri = xstrdup(urlCanonical(new_request)); new_request->http_ver = old_request->http_ver; httpHeaderAppend(&new_request->header, &old_request->header); new_request->client_addr = old_request->client_addr; new_request->client_port = old_request->client_port; new_request->my_addr = old_request->my_addr; new_request->my_port = old_request->my_port; new_request->flags = old_request->flags; new_request->flags.redirected = 1; if (old_request->auth_user_request) { new_request->auth_user_request = old_request->auth_user_request; authenticateAuthUserRequestLock(new_request->auth_user_request); } if (old_request->body_reader) { new_request->body_reader = old_request->body_reader; new_request->body_reader_data = old_request->body_reader_data; old_request->body_reader = NULL; old_request->body_reader_data = NULL; } new_request->content_length = old_request->content_length; requestUnlink(old_request); http->request = requestLink(new_request); } clientInterpretRequestHeaders(http); #if HEADERS_LOG headersLog(0, 1, request->method, request); #endif fd_note(http->conn->fd, http->uri); clientCheckNoCache(http); } static void clientCheckNoCache(clientHttpRequest * http) { if (Config.accessList.noCache && http->request->flags.cachable) { http->acl_checklist = clientAclChecklistCreate(Config.accessList.noCache, http); aclNBCheck(http->acl_checklist, clientCheckNoCacheDone, http); } else { clientCheckNoCacheDone(http->request->flags.cachable, http); } } void clientCheckNoCacheDone(int answer, void *data) { clientHttpRequest *http = data; http->request->flags.cachable = answer; http->acl_checklist = NULL; clientProcessRequest(http); } static void clientProcessExpired(void *data) { clientHttpRequest *http = data; char *url = http->uri; StoreEntry *entry = NULL; debug(33, 3) ("clientProcessExpired: '%s'\n", http->uri); assert(http->entry->lastmod >= 0); /* * check if we are allowed to contact other servers * @?@: Instead of a 504 (Gateway Timeout) reply, we may want to return * a stale entry *if* it matches client requirements */ if (clientOnlyIfCached(http)) { clientProcessOnlyIfCachedMiss(http); return; } http->request->flags.refresh = 1; http->old_entry = http->entry; http->old_sc = http->sc; /* * Assert that 'http' is already a client of old_entry. If * it is not, then the beginning of the object data might get * freed from memory before we need to access it. */ assert(http->sc->callback_data == http); entry = storeCreateEntry(url, http->log_uri, http->request->flags, http->request->method); /* NOTE, don't call storeLockObject(), storeCreateEntry() does it */ http->sc = storeClientListAdd(entry, http); #if DELAY_POOLS /* delay_id is already set on original store client */ delaySetStoreClient(http->sc, delayClient(http)); #endif http->request->lastmod = http->old_entry->lastmod; debug(33, 5) ("clientProcessExpired: lastmod %ld\n", (long int) entry->lastmod); http->entry = entry; http->out.offset = 0; fwdStart(http->conn->fd, http->entry, http->request); /* Register with storage manager to receive updates when data comes in. */ if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) debug(33, 0) ("clientProcessExpired: found ENTRY_ABORTED object\n"); storeClientCopy(http->sc, entry, http->out.offset, http->out.offset, CLIENT_SOCK_SZ, memAllocate(MEM_CLIENT_SOCK_BUF), clientHandleIMSReply, http); } static int clientGetsOldEntry(StoreEntry * new_entry, StoreEntry * old_entry, request_t * request) { const http_status status = new_entry->mem_obj->reply->sline.status; if (0 == status) { debug(33, 5) ("clientGetsOldEntry: YES, broken HTTP reply\n"); return 1; } /* If the reply is a failure then send the old object as a last * resort */ if (status >= 500 && status < 600) { debug(33, 3) ("clientGetsOldEntry: YES, failure reply=%d\n", status); return 1; } /* If the reply is anything but "Not Modified" then * we must forward it to the client */ if (HTTP_NOT_MODIFIED != status) { debug(33, 5) ("clientGetsOldEntry: NO, reply=%d\n", status); return 0; } /* If the client did not send IMS in the request, then it * must get the old object, not this "Not Modified" reply */ if (!request->flags.ims) { debug(33, 5) ("clientGetsOldEntry: YES, no client IMS\n"); return 1; } /* If the client IMS time is prior to the entry LASTMOD time we * need to send the old object */ if (modifiedSince(old_entry, request)) { debug(33, 5) ("clientGetsOldEntry: YES, modified since %ld\n", (long int) request->ims); return 1; } debug(33, 5) ("clientGetsOldEntry: NO, new one is fine\n"); return 0; } static void clientHandleIMSReply(void *data, char *buf, ssize_t size) { clientHttpRequest *http = data; StoreEntry *entry = http->entry; MemObject *mem; const char *url = storeUrl(entry); int unlink_request = 0; StoreEntry *oldentry; int recopy = 1; http_status status; debug(33, 3) ("clientHandleIMSReply: %s, %ld bytes\n", url, (long int) size); if (entry == NULL) { memFree(buf, MEM_CLIENT_SOCK_BUF); return; } if (size < 0 && !EBIT_TEST(entry->flags, ENTRY_ABORTED)) { memFree(buf, MEM_CLIENT_SOCK_BUF); return; } mem = entry->mem_obj; status = mem->reply->sline.status; if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) { debug(33, 3) ("clientHandleIMSReply: ABORTED '%s'\n", url); /* We have an existing entry, but failed to validate it */ /* Its okay to send the old one anyway */ http->log_type = LOG_TCP_REFRESH_FAIL_HIT; storeUnregister(http->sc, entry, http); storeUnlockObject(entry); entry = http->entry = http->old_entry; http->sc = http->old_sc; } else if (STORE_PENDING == entry->store_status && 0 == status) { debug(33, 3) ("clientHandleIMSReply: Incomplete headers for '%s'\n", url); if (size >= CLIENT_SOCK_SZ) { /* will not get any bigger than that */ debug(33, 3) ("clientHandleIMSReply: Reply is too large '%s', using old entry\n", url); /* use old entry, this repeats the code abovez */ http->log_type = LOG_TCP_REFRESH_FAIL_HIT; storeUnregister(http->sc, entry, http); storeUnlockObject(entry); entry = http->entry = http->old_entry; http->sc = http->old_sc; /* continue */ } else { storeClientCopy(http->sc, entry, http->out.offset + size, http->out.offset, CLIENT_SOCK_SZ, buf, clientHandleIMSReply, http); return; } } else if (clientGetsOldEntry(entry, http->old_entry, http->request)) { /* We initiated the IMS request, the client is not expecting * 304, so put the good one back. First, make sure the old entry * headers have been loaded from disk. */ oldentry = http->old_entry; http->log_type = LOG_TCP_REFRESH_HIT; if (oldentry->mem_obj->request == NULL) { oldentry->mem_obj->request = requestLink(mem->request); unlink_request = 1; } /* Don't memcpy() the whole reply structure here. For example, * www.thegist.com (Netscape/1.13) returns a content-length for * 304's which seems to be the length of the 304 HEADERS!!! and * not the body they refer to. */ httpReplyUpdateOnNotModified(oldentry->mem_obj->reply, mem->reply); storeTimestampsSet(oldentry); storeUnregister(http->sc, entry, http); http->sc = http->old_sc; storeUnlockObject(entry); entry = http->entry = oldentry; entry->timestamp = squid_curtime; if (unlink_request) { requestUnlink(entry->mem_obj->request); entry->mem_obj->request = NULL; } } else { /* the client can handle this reply, whatever it is */ http->flags.hit = 0; http->log_type = LOG_TCP_REFRESH_MISS; if (HTTP_NOT_MODIFIED == mem->reply->sline.status) { httpReplyUpdateOnNotModified(http->old_entry->mem_obj->reply, mem->reply); storeTimestampsSet(http->old_entry); http->log_type = LOG_TCP_REFRESH_HIT; } storeUnregister(http->old_sc, http->old_entry, http); storeUnlockObject(http->old_entry); recopy = 0; } http->old_entry = NULL; /* done with old_entry */ http->old_sc = NULL; assert(!EBIT_TEST(entry->flags, ENTRY_ABORTED)); if (recopy) { storeClientCopy(http->sc, entry, http->out.offset, http->out.offset, CLIENT_SOCK_SZ, buf, clientSendMoreData, http); } else { clientSendMoreData(data, buf, size); } } int modifiedSince(StoreEntry * entry, request_t * request) { squid_off_t object_length; MemObject *mem = entry->mem_obj; time_t mod_time = entry->lastmod; debug(33, 3) ("modifiedSince: '%s'\n", storeUrl(entry)); if (mod_time < 0) mod_time = entry->timestamp; debug(33, 3) ("modifiedSince: mod_time = %ld\n", (long int) mod_time); if (mod_time < 0) return 1; /* Find size of the object */ object_length = mem->reply->content_length; if (object_length < 0) object_length = contentLen(entry); if (mod_time > request->ims) { debug(33, 3) ("--> YES: entry newer than client\n"); return 1; } else if (mod_time < request->ims) { debug(33, 3) ("--> NO: entry older than client\n"); return 0; } else if (request->imslen < 0) { debug(33, 3) ("--> NO: same LMT, no client length\n"); return 0; } else if (request->imslen == object_length) { debug(33, 3) ("--> NO: same LMT, same length\n"); return 0; } else { debug(33, 3) ("--> YES: same LMT, different length\n"); return 1; } } void clientPurgeRequest(clientHttpRequest * http) { StoreEntry *entry; ErrorState *err = NULL; HttpReply *r; http_status status = HTTP_NOT_FOUND; http_version_t version; debug(33, 3) ("Config2.onoff.enable_purge = %d\n", Config2.onoff.enable_purge); if (!Config2.onoff.enable_purge) { http->log_type = LOG_TCP_DENIED; err = errorCon(ERR_ACCESS_DENIED, HTTP_FORBIDDEN); err->request = requestLink(http->request); err->src_addr = http->conn->peer.sin_addr; http->entry = clientCreateStoreEntry(http, http->request->method, null_request_flags); errorAppendEntry(http->entry, err); return; } /* Release both IP cache */ ipcacheInvalidate(http->request->host); if (!http->flags.purging) { /* Try to find a base entry */ http->flags.purging = 1; entry = storeGetPublicByRequestMethod(http->request, METHOD_GET); if (!entry) entry = storeGetPublicByRequestMethod(http->request, METHOD_HEAD); if (entry) { if (EBIT_TEST(entry->flags, ENTRY_SPECIAL)) { http->log_type = LOG_TCP_DENIED; err = errorCon(ERR_ACCESS_DENIED, HTTP_FORBIDDEN); err->request = requestLink(http->request); err->src_addr = http->conn->peer.sin_addr; http->entry = clientCreateStoreEntry(http, http->request->method, null_request_flags); errorAppendEntry(http->entry, err); return; } /* Swap in the metadata */ http->entry = entry; storeLockObject(http->entry); storeCreateMemObject(http->entry, http->uri, http->log_uri); http->entry->mem_obj->method = http->request->method; http->sc = storeClientListAdd(http->entry, http); http->log_type = LOG_TCP_HIT; storeClientCopy(http->sc, http->entry, http->out.offset, http->out.offset, CLIENT_SOCK_SZ, memAllocate(MEM_CLIENT_SOCK_BUF), clientCacheHit, http); return; } } http->log_type = LOG_TCP_MISS; /* Release the cached URI */ entry = storeGetPublicByRequestMethod(http->request, METHOD_GET); if (entry) { debug(33, 4) ("clientPurgeRequest: GET '%s'\n", storeUrl(entry)); storeRelease(entry); status = HTTP_OK; } entry = storeGetPublicByRequestMethod(http->request, METHOD_HEAD); if (entry) { debug(33, 4) ("clientPurgeRequest: HEAD '%s'\n", storeUrl(entry)); storeRelease(entry); status = HTTP_OK; } /* And for Vary, release the base URI if none of the headers was included in the request */ if (http->request->vary_headers && !strstr(http->request->vary_headers, "=")) { entry = storeGetPublic(urlCanonical(http->request), METHOD_GET); if (entry) { debug(33, 4) ("clientPurgeRequest: Vary GET '%s'\n", storeUrl(entry)); storeRelease(entry); status = HTTP_OK; } entry = storeGetPublic(urlCanonical(http->request), METHOD_HEAD); if (entry) { debug(33, 4) ("clientPurgeRequest: Vary HEAD '%s'\n", storeUrl(entry)); storeRelease(entry); status = HTTP_OK; } } /* * Make a new entry to hold the reply to be written * to the client. */ http->entry = clientCreateStoreEntry(http, http->request->method, null_request_flags); httpReplyReset(r = http->entry->mem_obj->reply); httpBuildVersion(&version, 1, 0); httpReplySetHeaders(r, version, status, NULL, NULL, 0, 0, -1); httpReplySwapOut(r, http->entry); storeComplete(http->entry); } int checkNegativeHit(StoreEntry * e) { if (!EBIT_TEST(e->flags, ENTRY_NEGCACHED)) return 0; if (e->expires <= squid_curtime) return 0; if (e->store_status != STORE_OK) return 0; return 1; } static void clientUpdateCounters(clientHttpRequest * http) { int svc_time = tvSubMsec(http->start, current_time); ping_data *i; HierarchyLogEntry *H; statCounter.client_http.requests++; if (isTcpHit(http->log_type)) statCounter.client_http.hits++; if (http->log_type == LOG_TCP_HIT) statCounter.client_http.disk_hits++; else if (http->log_type == LOG_TCP_MEM_HIT) statCounter.client_http.mem_hits++; if (http->request->err_type != ERR_NONE) statCounter.client_http.errors++; statHistCount(&statCounter.client_http.all_svc_time, svc_time); /* * The idea here is not to be complete, but to get service times * for only well-defined types. For example, we don't include * LOG_TCP_REFRESH_FAIL_HIT because its not really a cache hit * (we *tried* to validate it, but failed). */ switch (http->log_type) { case LOG_TCP_REFRESH_HIT: statHistCount(&statCounter.client_http.nh_svc_time, svc_time); break; case LOG_TCP_IMS_HIT: statHistCount(&statCounter.client_http.nm_svc_time, svc_time); break; case LOG_TCP_HIT: case LOG_TCP_MEM_HIT: case LOG_TCP_OFFLINE_HIT: statHistCount(&statCounter.client_http.hit_svc_time, svc_time); break; case LOG_TCP_MISS: case LOG_TCP_CLIENT_REFRESH_MISS: statHistCount(&statCounter.client_http.miss_svc_time, svc_time); break; default: /* make compiler warnings go away */ break; } H = &http->request->hier; switch (H->code) { #if USE_CACHE_DIGESTS case CD_PARENT_HIT: case CD_SIBLING_HIT: statCounter.cd.times_used++; break; #endif case SIBLING_HIT: case PARENT_HIT: case FIRST_PARENT_MISS: case CLOSEST_PARENT_MISS: statCounter.icp.times_used++; i = &H->ping; if (0 != i->stop.tv_sec && 0 != i->start.tv_sec) statHistCount(&statCounter.icp.query_svc_time, tvSubUsec(i->start, i->stop)); if (i->timeout) statCounter.icp.query_timeouts++; break; case CLOSEST_PARENT: case CLOSEST_DIRECT: statCounter.netdb.times_used++; break; default: break; } } static void httpRequestFree(void *data) { clientHttpRequest *http = data; clientHttpRequest **H; ConnStateData *conn = http->conn; StoreEntry *e; request_t *request = http->request; MemObject *mem = NULL; debug(33, 3) ("httpRequestFree: %s\n", storeUrl(http->entry)); if (!clientCheckTransferDone(http)) { requestAbortBody(request); /* abort request body transter */ /* HN: This looks a bit odd.. why should client_side care about * the ICP selection status? */ if (http->entry && http->entry->ping_status == PING_WAITING) storeReleaseRequest(http->entry); } assert(http->log_type < LOG_TYPE_MAX); if (http->entry) mem = http->entry->mem_obj; if (http->out.size || http->log_type) { http->al.icp.opcode = ICP_INVALID; http->al.url = http->log_uri; debug(33, 9) ("httpRequestFree: al.url='%s'\n", http->al.url); if (http->reply && http->log_type != LOG_TCP_DENIED) { http->al.http.code = http->reply->sline.status; http->al.http.content_type = strBuf(http->reply->content_type); } else if (mem) { http->al.http.code = mem->reply->sline.status; http->al.http.content_type = strBuf(mem->reply->content_type); } http->al.cache.caddr = conn->log_addr; http->al.cache.size = http->out.size; http->al.cache.code = http->log_type; http->al.cache.msec = tvSubMsec(http->start, current_time); if (request) { Packer p; MemBuf mb; memBufDefInit(&mb); packerToMemInit(&p, &mb); httpHeaderPackInto(&request->header, &p); http->al.http.method = request->method; http->al.http.version = request->http_ver; http->al.headers.request = xstrdup(mb.buf); http->al.hier = request->hier; if (request->auth_user_request) { if (authenticateUserRequestUsername(request->auth_user_request)) http->al.cache.authuser = xstrdup(authenticateUserRequestUsername(request->auth_user_request)); authenticateAuthUserRequestUnlock(request->auth_user_request); request->auth_user_request = NULL; } if (conn->rfc931[0]) http->al.cache.rfc931 = conn->rfc931; packerClean(&p); memBufClean(&mb); } accessLogLog(&http->al); clientUpdateCounters(http); clientdbUpdate(conn->peer.sin_addr, http->log_type, PROTO_HTTP, http->out.size); } if (http->acl_checklist) aclChecklistFree(http->acl_checklist); if (request) checkFailureRatio(request->err_type, http->al.hier.code); safe_free(http->uri); safe_free(http->log_uri); safe_free(http->al.headers.request); safe_free(http->al.headers.reply); safe_free(http->al.cache.authuser); safe_free(http->redirect.location); stringClean(&http->range_iter.boundary); if ((e = http->entry)) { http->entry = NULL; storeUnregister(http->sc, e, http); http->sc = NULL; storeUnlockObject(e); } /* old_entry might still be set if we didn't yet get the reply * code in clientHandleIMSReply() */ if ((e = http->old_entry)) { http->old_entry = NULL; storeUnregister(http->old_sc, e, http); http->old_sc = NULL; storeUnlockObject(e); } requestUnlink(http->request); if (http->reply) httpReplyDestroy(http->reply); assert(http != http->next); assert(http->conn->chr != NULL); /* Unlink us from the clients request list */ H = &http->conn->chr; while (*H) { if (*H == http) break; H = &(*H)->next; } assert(*H != NULL); *H = http->next; http->next = NULL; dlinkDelete(&http->active, &ClientActiveRequests); cbdataFree(http); } /* This is a handler normally called by comm_close() */ static void connStateFree(int fd, void *data) { ConnStateData *connState = data; clientHttpRequest *http; debug(33, 3) ("connStateFree: FD %d\n", fd); assert(connState != NULL); clientdbEstablished(connState->peer.sin_addr, -1); /* decrement */ while ((http = connState->chr) != NULL) { assert(http->conn == connState); assert(connState->chr != connState->chr->next); httpRequestFree(http); } if (connState->auth_user_request) authenticateAuthUserRequestUnlock(connState->auth_user_request); connState->auth_user_request = NULL; authenticateOnCloseConnection(connState); if (connState->in.size == CLIENT_REQ_BUF_SZ) { memFree(connState->in.buf, MEM_CLIENT_REQ_BUF); connState->in.buf = NULL; } else safe_free(connState->in.buf); /* XXX account connState->in.buf */ pconnHistCount(0, connState->nrequests); cbdataFree(connState); #ifdef _SQUID_LINUX_ /* prevent those nasty RST packets */ { char buf[SQUID_TCP_SO_RCVBUF]; while (FD_READ_METHOD(fd, buf, SQUID_TCP_SO_RCVBUF) > 0); } #endif } static void clientInterpretRequestHeaders(clientHttpRequest * http) { request_t *request = http->request; const HttpHeader *req_hdr = &request->header; int no_cache = 0; const char *str; request->imslen = -1; request->ims = httpHeaderGetTime(req_hdr, HDR_IF_MODIFIED_SINCE); if (request->ims > 0) request->flags.ims = 1; if (httpHeaderHas(req_hdr, HDR_PRAGMA)) { String s = httpHeaderGetList(req_hdr, HDR_PRAGMA); if (strListIsMember(&s, "no-cache", ',')) no_cache++; stringClean(&s); } request->cache_control = httpHeaderGetCc(req_hdr); if (request->cache_control) if (EBIT_TEST(request->cache_control->mask, CC_NO_CACHE)) no_cache++; /* Work around for supporting the Reload button in IE browsers * when Squid is used as an accelerator or transparent proxy, * by turning accelerated IMS request to no-cache requests. * Now knows about IE 5.5 fix (is actually only fixed in SP1, * but we can't tell whether we are talking to SP1 or not so * all 5.5 versions are treated 'normally'). */ if (Config.onoff.ie_refresh) { if (http->flags.accel && request->flags.ims) { if ((str = httpHeaderGetStr(req_hdr, HDR_USER_AGENT))) { if (strstr(str, "MSIE 5.01") != NULL) no_cache++; else if (strstr(str, "MSIE 5.0") != NULL) no_cache++; else if (strstr(str, "MSIE 4.") != NULL) no_cache++; else if (strstr(str, "MSIE 3.") != NULL) no_cache++; } } } if (no_cache) { #if HTTP_VIOLATIONS if (Config.onoff.reload_into_ims) request->flags.nocache_hack = 1; else if (refresh_nocache_hack) request->flags.nocache_hack = 1; else #endif request->flags.nocache = 1; } /* ignore range header in non-GETs */ if (request->method == METHOD_GET) { request->range = httpHeaderGetRange(req_hdr); if (request->range) request->flags.range = 1; } if(http->conn->pinned) { request->flags.auth = 1; request->flags.pinned = 1; request->flags.must_keepalive = 1; } if (httpHeaderHas(req_hdr, HDR_AUTHORIZATION)) request->flags.auth = 1; if (request->login[0] != '\0') request->flags.auth = 1; if (httpHeaderHas(req_hdr, HDR_VIA)) { String s = httpHeaderGetList(req_hdr, HDR_VIA); /* * ThisCache cannot be a member of Via header, "1.0 ThisCache" can. * Note ThisCache2 has a space prepended to the hostname so we don't * accidentally match super-domains. */ if (strListIsSubstr(&s, ThisCache2, ',')) { debugObj(33, 1, "WARNING: Forwarding loop detected for:\n", request, (ObjPackMethod) & httpRequestPackDebug); request->flags.loopdetect = 1; } #if FORW_VIA_DB fvdbCountVia(strBuf(s)); #endif stringClean(&s); } #if USE_USERAGENT_LOG if ((str = httpHeaderGetStr(req_hdr, HDR_USER_AGENT))) logUserAgent(fqdnFromAddr(http->conn->log_addr), str); #endif #if USE_REFERER_LOG if ((str = httpHeaderGetStr(req_hdr, HDR_REFERER))) logReferer(fqdnFromAddr(http->conn->log_addr), str, http->log_uri); #endif #if FORW_VIA_DB if (httpHeaderHas(req_hdr, HDR_X_FORWARDED_FOR)) { String s = httpHeaderGetList(req_hdr, HDR_X_FORWARDED_FOR); fvdbCountForw(strBuf(s)); stringClean(&s); } #endif if (request->method