]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/client_side.cc
Major rewrite of proxy authentication to support other schemes than
[thirdparty/squid.git] / src / client_side.cc
index a7362de59e887398bde78bed927baf2b1f691fb9..c9db0e5eb1c9a55b7b8239ed7b6903d252381d23 100644 (file)
@@ -1,6 +1,6 @@
 
 /*
- * $Id: client_side.cc,v 1.446 1999/04/26 21:06:13 wessels Exp $
+ * $Id: client_side.cc,v 1.522 2001/01/07 23:36:37 hno Exp $
  *
  * DEBUG: section 33    Client-side Routines
  * AUTHOR: Duane Wessels
  *  Internet community.  Development is led by Duane Wessels of the
  *  National Laboratory for Applied Network Research and funded by the
  *  National Science Foundation.  Squid is Copyrighted (C) 1998 by
- *  Duane Wessels and the University of California San Diego.  Please
- *  see the COPYRIGHT file for full details.  Squid incorporates
- *  software developed and/or copyrighted by other sources.  Please see
- *  the CREDITS file for full details.
+ *  the Regents of the University of California.  Please see the
+ *  COPYRIGHT file for full details.  Squid incorporates software
+ *  developed and/or copyrighted by other sources.  Please 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
 #endif
 #include <netinet/tcp.h>
 #include <net/if.h>
+#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 LINUX_NETFILTER
+#include <linux/netfilter_ipv4.h>
+#endif
 
 
 #if LINGERING_CLOSE
 
 static const char *const crlf = "\r\n";
 
-#define REQUEST_BUF_SIZE 4096
 #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);
@@ -71,6 +91,8 @@ 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 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 *);
@@ -90,8 +112,11 @@ static void clientProcessOnlyIfCachedMiss(clientHttpRequest * http);
 static int clientCachable(clientHttpRequest * http);
 static int clientHierarchical(clientHttpRequest * http);
 static int clientCheckContentLength(request_t * r);
-static int httpAcceptDefer(void);
+static DEFER httpAcceptDefer;
 static log_type clientProcessRequest2(clientHttpRequest * http);
+static int clientReplyBodyTooLarge(int clen);
+static int clientRequestBodyTooLarge(int clen);
+static void clientProcessBody(ConnStateData * conn);
 
 static int
 checkAccelOnly(clientHttpRequest * http)
@@ -110,48 +135,52 @@ checkAccelOnly(clientHttpRequest * http)
 }
 
 #if USE_IDENT
-void
+static void
 clientIdentDone(const char *ident, void *data)
 {
     ConnStateData *conn = data;
-    if (ident)
-       xstrncpy(conn->ident, ident, sizeof(conn->ident));
-    else
-       xstrncpy(conn->ident, "-", sizeof(conn->ident));
+    xstrncpy(conn->rfc931, ident ? ident : dash_str, USER_IDENT_SZ);
 }
+
 #endif
 
-void
-clientAccessCheck(void *data)
+static aclCheck_t *
+clientAclChecklistCreate(const acl_access * acl, const clientHttpRequest * http)
 {
-    clientHttpRequest *http = data;
+    aclCheck_t *ch;
     ConnStateData *conn = http->conn;
-    const char *browser;
-    if (checkAccelOnly(http)) {
-       clientAccessCheckDone(0, http);
-       return;
-    }
-    browser = httpHeaderGetStr(&http->request->header, HDR_USER_AGENT);
-    http->acl_checklist = aclChecklistCreate(Config.accessList.http,
+    ch = aclChecklistCreate(acl,
        http->request,
-       conn->peer.sin_addr,
-       conn->me.sin_addr,
-       browser,
-       conn->ident);
-#if USE_IDENT
+       conn->rfc931);
+
     /*
      * hack for ident ACL. It needs to get full addresses, and a
      * place to store the ident result on persistent connections...
      */
-    http->acl_checklist->conn = conn;
-    cbdataLock(http->acl_checklist->conn);
-#endif
+    /* 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
- * witout contacting origin server
+ * without contacting origin server
  */
 static int
 clientOnlyIfCached(clientHttpRequest * http)
@@ -171,13 +200,13 @@ clientCreateStoreEntry(clientHttpRequest * h, method_t m, request_flags flags)
      * so make a fake one.
      */
     if (h->request == NULL)
-       h->request = requestLink(requestCreate(m, PROTO_NONE, NULL));
+       h->request = requestLink(requestCreate(m, PROTO_NONE, null_string));
     e = storeCreateEntry(h->uri, h->log_uri, flags, m);
-    storeClientListAdd(e, h);
+    h->sc = storeClientListAdd(e, h);
 #if DELAY_POOLS
-    delaySetStoreClient(e, h, delayClient(h->request));
+    delaySetStoreClient(h->sc, delayClient(h->request));
 #endif
-    storeClientCopy(e, 0, 0, CLIENT_SOCK_SZ,
+    storeClientCopy(h->sc, e, 0, 0, CLIENT_SOCK_SZ,
        memAllocate(MEM_CLIENT_SOCK_BUF), clientSendMoreData, h);
     return e;
 }
@@ -189,7 +218,12 @@ clientAccessCheckDone(int answer, void *data)
     int page_id = -1;
     http_status status;
     ErrorState *err = NULL;
-    debug(33, 5) ("clientAccessCheckDone: '%s' answer=%d\n", http->uri, answer);
+    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);
@@ -201,6 +235,8 @@ clientAccessCheckDone(int answer, void *data)
        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
@@ -229,6 +265,14 @@ clientAccessCheckDone(int answer, void *data)
        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);
     }
 }
@@ -264,17 +308,43 @@ clientRedirectDone(void *data, char *result)
        httpHeaderAppend(&new_request->header, &old_request->header);
        new_request->client_addr = old_request->client_addr;
        new_request->my_addr = old_request->my_addr;
+       new_request->my_port = old_request->my_port;
        new_request->flags.redirected = 1;
-       if (old_request->body) {
-           new_request->body = xmalloc(old_request->body_sz);
-           xmemcpy(new_request->body, old_request->body, old_request->body_sz);
-           new_request->body_sz = old_request->body_sz;
+       new_request->auth_user_request = old_request->auth_user_request;
+       if (old_request->body_connection) {
+           new_request->body_connection = old_request->body_connection;
+           old_request->body_connection = NULL;
        }
+       new_request->content_length = old_request->content_length;
+       new_request->flags.proxy_keepalive = old_request->flags.proxy_keepalive;
        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);
 }
 
@@ -297,33 +367,32 @@ clientProcessExpired(void *data)
     }
     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(storeClientListSearch(http->old_entry->mem_obj, http));
+    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 */
-    storeClientListAdd(entry, http);
+    http->sc = storeClientListAdd(entry, http);
 #if DELAY_POOLS
     /* delay_id is already set on original store client */
-    delaySetStoreClient(entry, http, delayClient(http->request));
+    delaySetStoreClient(http->sc, delayClient(http->request));
 #endif
-    entry->lastmod = http->old_entry->lastmod;
+    http->request->lastmod = http->old_entry->lastmod;
     debug(33, 5) ("clientProcessExpired: lastmod %d\n", (int) entry->lastmod);
-    entry->refcount++;         /* EXPIRED CASE */
     http->entry = entry;
     http->out.offset = 0;
-    fwdStart(http->conn->fd, http->entry, http->request,
-       http->conn->peer.sin_addr, http->conn->me.sin_addr);
+    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(entry,
+    storeClientCopy(http->sc, entry,
        http->out.offset,
        http->out.offset,
        CLIENT_SOCK_SZ,
@@ -343,7 +412,7 @@ clientGetsOldEntry(StoreEntry * new_entry, StoreEntry * old_entry, request_t * r
     /* If the reply is a failure then send the old object as a last
      * resort */
     if (status >= 500 && status < 600) {
-       debug(33, 2) ("clientGetsOldEntry: YES, failure reply=%d\n", status);
+       debug(33, 3) ("clientGetsOldEntry: YES, failure reply=%d\n", status);
        return 1;
     }
     /* If the reply is anything but "Not Modified" then
@@ -397,10 +466,10 @@ clientHandleIMSReply(void *data, char *buf, ssize_t size)
        /* 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(entry, http);
+       storeUnregister(http->sc, entry, http);
        storeUnlockObject(entry);
        entry = http->entry = http->old_entry;
-       entry->refcount++;
+       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) {
@@ -408,13 +477,13 @@ clientHandleIMSReply(void *data, char *buf, ssize_t size)
            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(entry, http);
+           storeUnregister(http->sc, entry, http);
            storeUnlockObject(entry);
            entry = http->entry = http->old_entry;
-           entry->refcount++;
+           http->sc = http->old_sc;
            /* continue */
        } else {
-           storeClientCopy(entry,
+           storeClientCopy(http->sc, entry,
                http->out.offset + size,
                http->out.offset,
                CLIENT_SOCK_SZ,
@@ -439,7 +508,8 @@ clientHandleIMSReply(void *data, char *buf, ssize_t size)
         * not the body they refer to.  */
        httpReplyUpdateOnNotModified(oldentry->mem_obj->reply, mem->reply);
        storeTimestampsSet(oldentry);
-       storeUnregister(entry, http);
+       storeUnregister(http->sc, entry, http);
+       http->sc = http->old_sc;
        storeUnlockObject(entry);
        entry = http->entry = oldentry;
        entry->timestamp = squid_curtime;
@@ -454,17 +524,17 @@ clientHandleIMSReply(void *data, char *buf, ssize_t size)
            httpReplyUpdateOnNotModified(http->old_entry->mem_obj->reply,
                mem->reply);
            storeTimestampsSet(http->old_entry);
-           http->old_entry->refcount++;
            http->log_type = LOG_TCP_REFRESH_HIT;
        }
-       storeUnregister(http->old_entry, http);
+       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(entry,
+       storeClientCopy(http->sc, entry,
            http->out.offset,
            http->out.offset,
            CLIENT_SOCK_SZ,
@@ -516,8 +586,10 @@ clientPurgeRequest(clientHttpRequest * http)
     StoreEntry *entry;
     ErrorState *err = NULL;
     HttpReply *r;
-    debug(33, 3) ("Config.onoff.enable_purge = %d\n", Config.onoff.enable_purge);
-    if (!Config.onoff.enable_purge) {
+    http_status status;
+    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);
@@ -527,11 +599,13 @@ clientPurgeRequest(clientHttpRequest * http)
        return;
     }
     http->log_type = LOG_TCP_MISS;
+    /* Release both IP and object cache entries */
+    ipcacheInvalidate(http->request->host);
     if ((entry = storeGetPublic(http->uri, METHOD_GET)) == NULL) {
-       http->http_code = HTTP_NOT_FOUND;
+       status = HTTP_NOT_FOUND;
     } else {
        storeRelease(entry);
-       http->http_code = HTTP_OK;
+       status = HTTP_OK;
     }
     debug(33, 4) ("clientPurgeRequest: Not modified '%s'\n",
        storeUrl(entry));
@@ -541,7 +615,8 @@ clientPurgeRequest(clientHttpRequest * http)
      */
     http->entry = clientCreateStoreEntry(http, http->request->method, null_request_flags);
     httpReplyReset(r = http->entry->mem_obj->reply);
-    httpReplySetHeaders(r, 1.0, http->http_code, NULL, NULL, 0, 0, -1);
+    httpBuildVersion(&version, 1, 0);
+    httpReplySetHeaders(r, version, status, NULL, NULL, 0, 0, -1);
     httpReplySwapOut(r, http->entry);
     storeComplete(http->entry);
 }
@@ -564,12 +639,16 @@ clientUpdateCounters(clientHttpRequest * http)
     int svc_time = tvSubMsec(http->start, current_time);
     ping_data *i;
     HierarchyLogEntry *H;
-    Counter.client_http.requests++;
+    statCounter.client_http.requests++;
     if (isTcpHit(http->log_type))
-       Counter.client_http.hits++;
+       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)
-       Counter.client_http.errors++;
-    statHistCount(&Counter.client_http.all_svc_time, svc_time);
+       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
@@ -578,19 +657,19 @@ clientUpdateCounters(clientHttpRequest * http)
      */
     switch (http->log_type) {
     case LOG_TCP_REFRESH_HIT:
-       statHistCount(&Counter.client_http.nh_svc_time, svc_time);
+       statHistCount(&statCounter.client_http.nh_svc_time, svc_time);
        break;
     case LOG_TCP_IMS_HIT:
-       statHistCount(&Counter.client_http.nm_svc_time, svc_time);
+       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(&Counter.client_http.hit_svc_time, svc_time);
+       statHistCount(&statCounter.client_http.hit_svc_time, svc_time);
        break;
     case LOG_TCP_MISS:
     case LOG_TCP_CLIENT_REFRESH_MISS:
-       statHistCount(&Counter.client_http.miss_svc_time, svc_time);
+       statHistCount(&statCounter.client_http.miss_svc_time, svc_time);
        break;
     default:
        /* make compiler warnings go away */
@@ -599,19 +678,19 @@ clientUpdateCounters(clientHttpRequest * http)
     H = &http->request->hier;
     switch (H->alg) {
     case PEER_SA_DIGEST:
-       Counter.cd.times_used++;
+       statCounter.cd.times_used++;
        break;
     case PEER_SA_ICP:
-       Counter.icp.times_used++;
+       statCounter.icp.times_used++;
        i = &H->ping;
        if (0 != i->stop.tv_sec && 0 != i->start.tv_sec)
-           statHistCount(&Counter.icp.query_svc_time,
+           statHistCount(&statCounter.icp.query_svc_time,
                tvSubUsec(i->start, i->stop));
        if (i->timeout)
-           Counter.icp.query_timeouts++;
+           statCounter.icp.query_timeouts++;
        break;
     case PEER_SA_NETDB:
-       Counter.netdb.times_used++;
+       statCounter.netdb.times_used++;
        break;
     default:
        break;
@@ -629,11 +708,20 @@ httpRequestFree(void *data)
     MemObject *mem = NULL;
     debug(33, 3) ("httpRequestFree: %s\n", storeUrl(http->entry));
     if (!clientCheckTransferDone(http)) {
+       if (request && request->body_connection)
+           clientAbortBody(request);   /* abort body transter */
+#if MYSTERIOUS_CODE
+       /*
+        * DW: this seems odd here, is it really needed?  It causes
+        * incomplete transfers to get logged with "000" status
+        * code because http->entry becomes NULL.
+        */
        if ((e = http->entry)) {
            http->entry = NULL;
-           storeUnregister(e, http);
+           storeUnregister(http->sc, e, http);
            storeUnlockObject(e);
        }
+#endif
        if (http->entry && http->entry->ping_status == PING_WAITING)
            storeReleaseRequest(http->entry);
     }
@@ -652,10 +740,6 @@ httpRequestFree(void *data)
        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->user_ident[0])
-           http->al.cache.ident = request->user_ident;
-       else
-           http->al.cache.ident = conn->ident;
        if (request) {
            Packer p;
            MemBuf mb;
@@ -666,6 +750,13 @@ httpRequestFree(void *data)
            http->al.http.version = request->http_ver;
            http->al.headers.request = xstrdup(mb.buf);
            http->al.hier = request->hier;
+           if (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);
        }
@@ -685,19 +776,22 @@ httpRequestFree(void *data)
     stringClean(&http->range_iter.boundary);
     if ((e = http->entry)) {
        http->entry = NULL;
-       storeUnregister(e, http);
+       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(e, http);
+       storeUnregister(http->old_sc, e, http);
+       http->old_sc = NULL;
        storeUnlockObject(e);
     }
     requestUnlink(http->request);
     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)
@@ -719,12 +813,17 @@ connStateFree(int fd, void *data)
     clientHttpRequest *http;
     debug(33, 3) ("connStateFree: FD %d\n", fd);
     assert(connState != NULL);
+    authenticateOnCloseConnection(connState);
+    clientdbEstablished(connState->peer.sin_addr, -1); /* decrement */
     while ((http = connState->chr) != NULL) {
        assert(http->conn == connState);
        assert(connState->chr != connState->chr->next);
        httpRequestFree(http);
     }
-    safe_free(connState->in.buf);
+    if (connState->in.size == CLIENT_REQ_BUF_SZ)
+       memFree(connState->in.buf, MEM_CLIENT_REQ_BUF);
+    else
+       safe_free(connState->in.buf);
     /* XXX account connState->in.buf */
     pconnHistCount(0, connState->nrequests);
     cbdataFree(connState);
@@ -743,9 +842,7 @@ clientInterpretRequestHeaders(clientHttpRequest * http)
     request_t *request = http->request;
     const HttpHeader *req_hdr = &request->header;
     int no_cache = 0;
-#if USE_USERAGENT_LOG
     const char *str;
-#endif
     request->imslen = -1;
     request->ims = httpHeaderGetTime(req_hdr, HDR_IF_MODIFIED_SINCE);
     if (request->ims > 0)
@@ -756,9 +853,31 @@ clientInterpretRequestHeaders(clientHttpRequest * http)
            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)
@@ -800,6 +919,11 @@ clientInterpretRequestHeaders(clientHttpRequest * http)
     if ((str = httpHeaderGetStr(req_hdr, HDR_USER_AGENT)))
        logUserAgent(fqdnFromAddr(http->conn->peer.sin_addr), str);
 #endif
+#if USE_REFERER_LOG
+    if ((str = httpHeaderGetStr(req_hdr, HDR_REFERER)))
+       logReferer(fqdnFromAddr(http->conn->peer.sin_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);
@@ -807,7 +931,6 @@ clientInterpretRequestHeaders(clientHttpRequest * http)
        stringClean(&s);
     }
 #endif
-    request->cache_control = httpHeaderGetCc(req_hdr);
     if (request->method == METHOD_TRACE) {
        request->max_forwards = httpHeaderGetInt(req_hdr, HDR_MAX_FORWARDS);
     }
@@ -834,23 +957,33 @@ clientSetKeepaliveFlag(clientHttpRequest * http)
 {
     request_t *request = http->request;
     const HttpHeader *req_hdr = &request->header;
-    debug(33, 3) ("clientSetKeepaliveFlag: http_ver = %3.1f\n",
-       request->http_ver);
+    debug(33, 3) ("clientSetKeepaliveFlag: http_ver = %d.%d\n",
+       request->http_ver.major, request->http_ver.minor);
     debug(33, 3) ("clientSetKeepaliveFlag: method = %s\n",
        RequestMethodStr[request->method]);
-    if (httpMsgIsPersistent(request->http_ver, req_hdr))
+    if (!Config.onoff.client_pconns)
+       request->flags.proxy_keepalive = 0;
+    else if (httpMsgIsPersistent(request->http_ver, req_hdr))
        request->flags.proxy_keepalive = 1;
 }
 
 static int
 clientCheckContentLength(request_t * r)
 {
-    /* We only require a content-length for "upload" methods */
-    if (!pumpMethod(r->method))
+    switch (r->method) {
+    case METHOD_PUT:
+    case METHOD_POST:
+       /* PUT/POST requires a request entity */
+       return (r->content_length >= 0);
+    case METHOD_GET:
+    case METHOD_HEAD:
+       /* We do not want to see a request entity on GET/HEAD requests */
+       return (r->content_length <= 0);
+    default:
+       /* For other types of requests we don't care */
        return 1;
-    if (httpHeaderGetInt(&r->header, HDR_CONTENT_LENGTH) < 0)
-       return 0;
-    return 1;
+    }
+    /* NOT REACHED */
 }
 
 static int
@@ -859,37 +992,21 @@ clientCachable(clientHttpRequest * http)
     const char *url = http->uri;
     request_t *req = http->request;
     method_t method = req->method;
-    aclCheck_t ch;
-    memset(&ch, '\0', sizeof(ch));
-    /*
-     * Hopefully, nobody really wants 'no_cache' by client's IP
-     * address, but if they do, this should work if they use IP
-     * addresses in their ACLs, or if the client's address is in
-     * the FQDN cache.
-     *
-     * This may not work yet for 'dst' and 'dst_domain' ACLs.
-     */
-    ch.src_addr = http->conn->peer.sin_addr;
-    ch.my_addr = http->conn->me.sin_addr;
-    ch.request = http->request;
-    /*
-     * aclCheckFast returns 1 for ALLOW and 0 for DENY.  The default
-     * is ALLOW, so we require 'no_cache DENY foo' in squid.conf
-     * to indicate uncachable objects.
-     */
-    if (!aclCheckFast(Config.accessList.noCache, &ch))
-       return 0;
     if (req->protocol == PROTO_HTTP)
        return httpCachable(method);
     /* FTP is always cachable */
-    if (req->protocol == PROTO_GOPHER)
-       return gopherCachable(url);
     if (req->protocol == PROTO_WAIS)
        return 0;
     if (method == METHOD_CONNECT)
        return 0;
     if (method == METHOD_TRACE)
        return 0;
+    if (method == METHOD_PUT)
+       return 0;
+    if (method == METHOD_POST)
+       return 0;               /* XXX POST may be cached sometimes.. ignored for now */
+    if (req->protocol == PROTO_GOPHER)
+       return gopherCachable(url);
     if (req->protocol == PROTO_CACHEOBJ)
        return 0;
     return 1;
@@ -1029,7 +1146,8 @@ clientBuildRangeHeader(clientHttpRequest * http, HttpReply * rep)
 {
     HttpHeader *hdr = rep ? &rep->header : 0;
     const char *range_err = NULL;
-    assert(http->request->range);
+    request_t *request = http->request;
+    assert(request->range);
     /* check if we still want to do ranges */
     if (!rep)
        range_err = "no [parse-able] reply";
@@ -1047,16 +1165,18 @@ clientBuildRangeHeader(clientHttpRequest * http, HttpReply * rep)
        range_err = "canonization failed";
     else if (httpHdrRangeIsComplex(http->request->range))
        range_err = "too complex range header";
+    else if (!request->flags.cachable) /* from we_do_ranges in http.c */
+       range_err = "non-cachable request";
     /* get rid of our range specs on error */
     if (range_err) {
-       debug(33, 2) ("clientBuildRangeHeader: will not do ranges: %s.\n", range_err);
+       debug(33, 3) ("clientBuildRangeHeader: will not do ranges: %s.\n", range_err);
        httpHdrRangeDestroy(http->request->range);
        http->request->range = NULL;
     } else {
        const int spec_count = http->request->range->specs.count;
        int actual_clen = -1;
 
-       debug(33, 2) ("clientBuildRangeHeader: range spec count: %d virgin clen: %d\n",
+       debug(33, 3) ("clientBuildRangeHeader: range spec count: %d virgin clen: %d\n",
            spec_count, rep->content_length);
        assert(spec_count > 0);
        /* ETags should not be returned with Partial Content replies? */
@@ -1089,7 +1209,7 @@ clientBuildRangeHeader(clientHttpRequest * http, HttpReply * rep)
        assert(actual_clen >= 0);
        httpHeaderDelById(hdr, HDR_CONTENT_LENGTH);
        httpHeaderPutInt(hdr, HDR_CONTENT_LENGTH, actual_clen);
-       debug(33, 2) ("clientBuildRangeHeader: actual content length: %d\n", actual_clen);
+       debug(33, 3) ("clientBuildRangeHeader: actual content length: %d\n", actual_clen);
     }
 }
 
@@ -1140,21 +1260,34 @@ clientBuildReplyHeader(clientHttpRequest * http, HttpReply * rep)
     if (request->range)
        clientBuildRangeHeader(http, rep);
     /*
-     * Add Age header, not that our header must replace Age headers
-     * from other caches if any
+     * Add a estimated Age header on cache hits.
      */
-    if (http->entry->timestamp > 0) {
+    if (is_hit) {
+       /*
+        * Remove any existing Age header sent by upstream caches
+        * (note that the existing header is passed along unmodified
+        * on cache misses)
+        */
        httpHeaderDelById(hdr, HDR_AGE);
        /*
-        * we do not follow HTTP/1.1 precisely here becuase we rely
-        * on Date header when computing entry->timestamp; we should
-        * be using _request_ time if Date header is not available
-        * or if it is out of sync
+        * This adds the calculated object age. Note that the details of the
+        * age calculation is performed by adjusting the timestamp in
+        * storeTimestampsSet(), not here.
+        *
+        * BROWSER WORKAROUND: IE sometimes hangs when receiving a 0 Age
+        * header, so don't use it unless there is a age to report. Please
+        * note that Age is only used to make a conservative estimation of
+        * the objects age, so a Age: 0 header does not add any useful
+        * information to the reply in any case.
         */
-       httpHeaderPutInt(hdr, HDR_AGE,
-           http->entry->timestamp <= squid_curtime ?
-           squid_curtime - http->entry->timestamp : 0);
-    }
+       if (http->entry->timestamp > -1)
+           if (http->entry->timestamp < squid_curtime)
+               httpHeaderPutInt(hdr, HDR_AGE,
+                   squid_curtime - http->entry->timestamp);
+    }
+    /* Handle authentication headers */
+    if (request->auth_user_request)
+       authenticateFixHeader(rep, request->auth_user_request, request, http->flags.accel);
     /* Append X-Cache */
     httpHeaderPutStrf(hdr, HDR_X_CACHE, "%s from %s",
        is_hit ? "HIT" : "MISS", getMyHostname());
@@ -1162,10 +1295,10 @@ clientBuildReplyHeader(clientHttpRequest * http, HttpReply * rep)
     /* Append X-Cache-Lookup: -- temporary hack, to be removed @?@ @?@ */
     httpHeaderPutStrf(hdr, HDR_X_CACHE_LOOKUP, "%s from %s:%d",
        http->lookup_type ? http->lookup_type : "NONE",
-       getMyHostname(), Config.Port.http->i);
+       getMyHostname(), ntohs(Config.Sockaddr.http->s.sin_port));
 #endif
-    if (httpReplyBodySize(request->method, http->entry->mem_obj->reply) < 0) {
-       debug(0, 0) ("persistent connection lossage\n");
+    if (httpReplyBodySize(request->method, rep) < 0) {
+       debug(33, 3) ("clientBuildReplyHeader: can't keep-alive, unknown body size\n");
        request->flags.proxy_keepalive = 0;
     }
     /* Signal keep-alive if needed */
@@ -1182,27 +1315,32 @@ clientBuildReplyHeader(clientHttpRequest * http, HttpReply * rep)
     httpHeaderPutStr(hdr, HDR_X_REQUEST_URI,
        http->entry->mem_obj->url ? http->entry->mem_obj->url : http->uri);
 #endif
+    httpHdrMangleList(hdr, request);
 }
 
 static HttpReply *
 clientBuildReply(clientHttpRequest * http, const char *buf, size_t size)
 {
     HttpReply *rep = httpReplyCreate();
-    if (httpReplyParse(rep, buf)) {
+    size_t k = headersEnd(buf, size);
+    if (k && httpReplyParse(rep, buf, k)) {
        /* enforce 1.0 reply version */
-       rep->sline.version = 1.0;
+       httpBuildVersion(&rep->sline.version, 1, 0);
        /* do header conversions */
        clientBuildReplyHeader(http, rep);
        /* if we do ranges, change status to "Partial Content" */
        if (http->request->range)
-           httpStatusLineSet(&rep->sline, rep->sline.version, HTTP_PARTIAL_CONTENT, NULL);
+           httpStatusLineSet(&rep->sline, rep->sline.version,
+               HTTP_PARTIAL_CONTENT, NULL);
     } else {
        /* parsing failure, get rid of the invalid reply */
        httpReplyDestroy(rep);
        rep = NULL;
        /* if we were going to do ranges, backoff */
-       if (http->request->range)
-           clientBuildRangeHeader(http, rep);  /* will fail and destroy request->range */
+       if (http->request->range) {
+           /* this will fail and destroy request->range */
+           clientBuildRangeHeader(http, rep);
+       }
     }
     return rep;
 }
@@ -1233,7 +1371,8 @@ clientCacheHit(void *data, char *buf, ssize_t size)
        http->log_type = LOG_TCP_SWAPFAIL_MISS;
        if ((e = http->entry)) {
            http->entry = NULL;
-           storeUnregister(e, http);
+           storeUnregister(http->sc, e, http);
+           http->sc = NULL;
            storeUnlockObject(e);
        }
        clientProcessMiss(http);
@@ -1255,7 +1394,7 @@ clientCacheHit(void *data, char *buf, ssize_t size)
            clientProcessMiss(http);
        } else {
            debug(33, 3) ("clientCacheHit: waiting for HTTP reply headers\n");
-           storeClientCopy(e,
+           storeClientCopy(http->sc, e,
                http->out.offset + size,
                http->out.offset,
                CLIENT_SOCK_SZ,
@@ -1332,19 +1471,27 @@ clientCacheHit(void *data, char *buf, ssize_t size)
            debug(33, 4) ("clientCacheHit: Reply code %d != 200\n",
                mem->reply->sline.status);
            memFree(buf, MEM_CLIENT_SOCK_BUF);
+           http->log_type = LOG_TCP_MISS;
            clientProcessMiss(http);
        } else if (modifiedSince(e, http->request)) {
            http->log_type = LOG_TCP_IMS_HIT;
            clientSendMoreData(data, buf, size);
        } else {
+           time_t timestamp = e->timestamp;
            MemBuf mb = httpPacked304Reply(e->mem_obj->reply);
            http->log_type = LOG_TCP_IMS_HIT;
            memFree(buf, MEM_CLIENT_SOCK_BUF);
-           storeUnregister(e, http);
+           storeUnregister(http->sc, e, http);
+           http->sc = NULL;
            storeUnlockObject(e);
            e = clientCreateStoreEntry(http, http->request->method, null_request_flags);
+           /*
+            * Copy timestamp from the original entry so the 304
+            * reply has a meaningful Age: header.
+            */
+           e->timestamp = timestamp;
            http->entry = e;
-           httpReplyParse(e->mem_obj->reply, mb.buf);
+           httpReplyParse(e->mem_obj->reply, mb.buf, mb.size);
            storeAppend(e, mb.buf, mb.size);
            memBufClean(&mb);
            storeComplete(e);
@@ -1397,20 +1544,30 @@ clientPackRangeHdr(const HttpReply * rep, const HttpHdrRangeSpec * spec, String
     memBufPrintf(mb, crlf);
 }
 
-/* extracts a "range" from *buf and appends them to mb, updating all offsets and such */
+/*
+ * extracts a "range" from *buf and appends them to mb, updating
+ * all offsets and such.
+ */
 static void
-clientPackRange(clientHttpRequest * http, HttpHdrRangeIter * i, const char **buf, ssize_t * size, MemBuf * mb)
+clientPackRange(clientHttpRequest * http,
+    HttpHdrRangeIter * i,
+    const char **buf,
+    ssize_t * size,
+    MemBuf * mb)
 {
-    const size_t copy_sz = i->debt_size <= *size ? i->debt_size : *size;
+    const ssize_t copy_sz = i->debt_size <= *size ? i->debt_size : *size;
     off_t body_off = http->out.offset - i->prefix_size;
     assert(*size > 0);
     assert(i->spec);
-
-    /* intersection of "have" and "need" ranges must not be empty */
+    /*
+     * intersection of "have" and "need" ranges must not be empty
+     */
     assert(body_off < i->spec->offset + i->spec->length);
     assert(body_off + *size > i->spec->offset);
-
-    /* put boundary and headers at the beginning of a range in a multi-range */
+    /*
+     * put boundary and headers at the beginning of a range in a
+     * multi-range
+     */
     if (http->request->range->specs.count > 1 && i->debt_size == i->spec->length) {
        assert(http->entry->mem_obj);
        clientPackRangeHdr(
@@ -1420,18 +1577,22 @@ clientPackRange(clientHttpRequest * http, HttpHdrRangeIter * i, const char **buf
            mb
            );
     }
-    /* append content */
+    /*
+     * append content
+     */
     debug(33, 3) ("clientPackRange: appending %d bytes\n", copy_sz);
     memBufAppend(mb, *buf, copy_sz);
-
-    /* update offsets */
+    /*
+     * update offsets
+     */
     *size -= copy_sz;
     i->debt_size -= copy_sz;
     body_off += copy_sz;
     *buf += copy_sz;
     http->out.offset = body_off + i->prefix_size;      /* sync */
-
-    /* paranoid check */
+    /*
+     * paranoid check
+     */
     assert(*size >= 0 && i->debt_size >= 0);
 }
 
@@ -1467,9 +1628,9 @@ clientPackMoreRanges(clientHttpRequest * http, const char *buf, ssize_t size, Me
        off_t start;            /* offset of still missing data */
        assert(i->spec);
        start = i->spec->offset + i->spec->length - i->debt_size;
-       debug(33, 2) ("clientPackMoreRanges: in:  offset: %d size: %d\n",
+       debug(33, 3) ("clientPackMoreRanges: in:  offset: %d size: %d\n",
            (int) body_off, size);
-       debug(33, 2) ("clientPackMoreRanges: out: start: %d spec[%d]: [%d, %d), len: %d debt: %d\n",
+       debug(33, 3) ("clientPackMoreRanges: out: start: %d spec[%d]: [%d, %d), len: %d debt: %d\n",
            (int) start, (int) i->pos, i->spec->offset, (int) (i->spec->offset + i->spec->length), i->spec->length, i->debt_size);
        assert(body_off <= start);      /* we did not miss it */
        /* skip up to start */
@@ -1492,10 +1653,10 @@ clientPackMoreRanges(clientHttpRequest * http, const char *buf, ssize_t size, Me
        }
     }
     assert(!i->debt_size == !i->spec); /* paranoid sync condition */
-    debug(33, 2) ("clientPackMoreRanges: buf exhausted: in:  offset: %d size: %d need_more: %d\n",
+    debug(33, 3) ("clientPackMoreRanges: buf exhausted: in:  offset: %d size: %d need_more: %d\n",
        (int) body_off, size, i->debt_size);
     if (i->debt_size) {
-       debug(33, 2) ("clientPackMoreRanges: need more: spec[%d]: [%d, %d), len: %d\n",
+       debug(33, 3) ("clientPackMoreRanges: need more: spec[%d]: [%d, %d), len: %d\n",
            (int) i->pos, i->spec->offset, (int) (i->spec->offset + i->spec->length), i->spec->length);
        /* skip the data we do not need if possible */
        if (i->debt_size == i->spec->length)    /* at the start of the cur. spec */
@@ -1510,6 +1671,30 @@ clientPackMoreRanges(clientHttpRequest * http, const char *buf, ssize_t size, Me
     return i->debt_size > 0;
 }
 
+static int
+clientReplyBodyTooLarge(int clen)
+{
+    if (0 == Config.maxReplyBodySize)
+       return 0;               /* disabled */
+    if (clen < 0)
+       return 0;               /* unknown */
+    if (clen > Config.maxReplyBodySize)
+       return 1;               /* too large */
+    return 0;
+}
+
+static int
+clientRequestBodyTooLarge(int clen)
+{
+    if (0 == Config.maxRequestBodySize)
+       return 0;               /* disabled */
+    if (clen < 0)
+       return 0;               /* unknown, bug? */
+    if (clen > Config.maxRequestBodySize)
+       return 1;               /* too large */
+    return 0;
+}
+
 /*
  * accepts chunk of a http message in buf, parses prefix, filters headers and
  * such, writes processed message to the client's socket
@@ -1564,7 +1749,18 @@ clientSendMoreData(void *data, char *buf, ssize_t size)
            }
        }
        rep = clientBuildReply(http, buf, size);
-       if (rep) {
+       if (rep && clientReplyBodyTooLarge(rep->content_length)) {
+           ErrorState *err = errorCon(ERR_TOO_BIG, HTTP_FORBIDDEN);
+           err->request = requestLink(http->request);
+           storeUnregister(http->sc, http->entry, http);
+           http->sc = NULL;
+           storeUnlockObject(http->entry);
+           http->entry = clientCreateStoreEntry(http, http->request->method,
+               null_request_flags);
+           errorAppendEntry(http->entry, err);
+           httpReplyDestroy(rep);
+           return;
+       } else if (rep) {
            body_size = size - rep->hdr_sz;
            assert(body_size >= 0);
            body_buf = buf + rep->hdr_sz;
@@ -1573,7 +1769,7 @@ clientSendMoreData(void *data, char *buf, ssize_t size)
                body_size, rep->hdr_sz);
        } else if (size < CLIENT_SOCK_SZ && entry->store_status == STORE_PENDING) {
            /* wait for more to arrive */
-           storeClientCopy(entry,
+           storeClientCopy(http->sc, entry,
                http->out.offset + size,
                http->out.offset,
                CLIENT_SOCK_SZ,
@@ -1584,6 +1780,13 @@ clientSendMoreData(void *data, char *buf, ssize_t size)
        }
        /* reset range iterator */
        http->range_iter.pos = HttpHdrRangeInitPos;
+    } else if (!http->request->range) {
+       /* Avoid copying to MemBuf for non-range requests */
+       /* Note, if we're here, then 'rep' is known to be NULL */
+       http->out.offset += body_size;
+       comm_write(fd, buf, size, clientWriteBodyComplete, http, NULL);
+       /* NULL because clientWriteBodyComplete frees it */
+       return;
     }
     if (http->request->method == METHOD_HEAD) {
        if (rep) {
@@ -1609,6 +1812,9 @@ clientSendMoreData(void *data, char *buf, ssize_t size)
        mb = httpReplyPack(rep);
        http->out.offset += rep->hdr_sz;
        check_size += rep->hdr_sz;
+#if HEADERS_LOG
+       headersLog(0, 0, http->request->method, rep);
+#endif
        httpReplyDestroy(rep);
        rep = NULL;
     } else {
@@ -1635,6 +1841,23 @@ clientSendMoreData(void *data, char *buf, ssize_t size)
     memFree(buf, MEM_CLIENT_SOCK_BUF);
 }
 
+/*
+ * clientWriteBodyComplete is called for MEM_CLIENT_SOCK_BUF's
+ * written directly to the client socket, versus copying to a MemBuf
+ * and going through comm_write_mbuf.  Most non-range responses after
+ * the headers probably go through here.
+ */
+static void
+clientWriteBodyComplete(int fd, char *buf, size_t size, int errflag, void *data)
+{
+    /*
+     * NOTE: clientWriteComplete doesn't currently use its "buf"
+     * (second) argument, so we pass in NULL.
+     */
+    clientWriteComplete(fd, NULL, size, errflag, data);
+    memFree(buf, MEM_CLIENT_SOCK_BUF);
+}
+
 static void
 clientKeepaliveNextRequest(clientHttpRequest * http)
 {
@@ -1646,12 +1869,22 @@ clientKeepaliveNextRequest(clientHttpRequest * http)
     if ((http = conn->chr) == NULL) {
        debug(33, 5) ("clientKeepaliveNextRequest: FD %d reading next req\n",
            conn->fd);
-       fd_note(conn->fd, "Reading next request");
+       fd_note(conn->fd, "Waiting for next request");
        /*
         * Set the timeout BEFORE calling clientReadRequest().
         */
-       commSetTimeout(conn->fd, 15, requestTimeout, conn);
+       commSetTimeout(conn->fd, Config.Timeout.pconn, requestTimeout, conn);
+       /*
+        * CYGWIN has a problem and is blocking on read() requests when there
+        * is no data present.
+        * This hack may hit performance a little, but it's better than 
+        * blocking!.
+        */
+#ifdef _SQUID_CYGWIN_
+       commSetSelect(conn->fd, COMM_SELECT_READ, clientReadRequest, conn, 0);
+#else
        clientReadRequest(conn->fd, conn);      /* Read next request */
+#endif
        /*
         * Note, the FD may be closed at this point.
         */
@@ -1664,10 +1897,10 @@ clientKeepaliveNextRequest(clientHttpRequest * http)
        debug(33, 1) ("clientKeepaliveNextRequest: FD %d Sending next\n",
            conn->fd);
        assert(entry);
-       if (0 == storeClientCopyPending(entry, http)) {
+       if (0 == storeClientCopyPending(http->sc, entry, http)) {
            if (EBIT_TEST(entry->flags, ENTRY_ABORTED))
                debug(33, 0) ("clientKeepaliveNextRequest: ENTRY_ABORTED\n");
-           storeClientCopy(entry,
+           storeClientCopy(http->sc, entry,
                http->out.offset,
                http->out.offset,
                CLIENT_SOCK_SZ,
@@ -1688,9 +1921,9 @@ clientWriteComplete(int fd, char *bufnotused, size_t size, int errflag, void *da
     debug(33, 5) ("clientWriteComplete: FD %d, sz %d, err %d, off %d, len %d\n",
        fd, size, errflag, (int) http->out.offset, entry ? objectLen(entry) : 0);
     if (size > 0) {
-       kb_incr(&Counter.client_http.kbytes_out, size);
+       kb_incr(&statCounter.client_http.kbytes_out, size);
        if (isTcpHit(http->log_type))
-           kb_incr(&Counter.client_http.hit_kbytes_out, size);
+           kb_incr(&statCounter.client_http.hit_kbytes_out, size);
     }
     if (errflag) {
        /*
@@ -1704,7 +1937,7 @@ clientWriteComplete(int fd, char *bufnotused, size_t size, int errflag, void *da
     } else if ((done = clientCheckTransferDone(http)) != 0 || size == 0) {
        debug(33, 5) ("clientWriteComplete: FD %d transfer is DONE\n", fd);
        /* We're finished case */
-       if (http->entry->mem_obj->reply->content_length < 0) {
+       if (httpReplyBodySize(http->request->method, entry->mem_obj->reply) < 0) {
            debug(33, 5) ("clientWriteComplete: closing, content_length < 0\n");
            comm_close(fd);
        } else if (!done) {
@@ -1719,12 +1952,14 @@ clientWriteComplete(int fd, char *bufnotused, size_t size, int errflag, void *da
        } else {
            comm_close(fd);
        }
+    } else if (clientReplyBodyTooLarge((int) http->out.offset)) {
+       comm_close(fd);
     } else {
        /* More data will be coming from primary server; register with 
         * storage manager. */
        if (EBIT_TEST(entry->flags, ENTRY_ABORTED))
            debug(33, 0) ("clientWriteComplete 2: ENTRY_ABORTED\n");
-       storeClientCopy(entry,
+       storeClientCopy(http->sc, entry,
            http->out.offset,
            http->out.offset,
            CLIENT_SOCK_SZ,
@@ -1753,13 +1988,50 @@ clientProcessOnlyIfCachedMiss(clientHttpRequest * http)
     err->request = requestLink(r);
     err->src_addr = http->conn->peer.sin_addr;
     if (http->entry) {
-       storeUnregister(http->entry, http);
+       storeUnregister(http->sc, http->entry, http);
+       http->sc = NULL;
        storeUnlockObject(http->entry);
     }
     http->entry = clientCreateStoreEntry(http, r->method, null_request_flags);
     errorAppendEntry(http->entry, err);
 }
 
+/* 
+ * Return true if we should force a cache miss on this range request.
+ * entry must be non-NULL.
+ */
+static int
+clientCheckRangeForceMiss(StoreEntry * entry, HttpHdrRange * range)
+{
+    /*
+     * If the range_offset_limit is NOT in effect, there
+     * is no reason to force a miss.
+     */
+    if (0 == httpHdrRangeOffsetLimit(range))
+       return 0;
+    /*
+     * Here, we know it's possibly a hit.  If we already have the
+     * whole object cached, we won't force a miss.
+     */
+    if (STORE_OK == entry->store_status)
+       return 0;               /* we have the whole object */
+    /*
+     * Now we have a hit on a PENDING object.  We need to see
+     * if the part we want is already cached.  If so, we don't
+     * force a miss.
+     */
+    assert(NULL != entry->mem_obj);
+    if (httpHdrRangeFirstOffset(range) <= entry->mem_obj->inmem_hi)
+       return 0;
+    /*
+     * Even though we have a PENDING copy of the object, we
+     * don't want to wait to reach the first range offset,
+     * so we force a miss for a new range request to the
+     * origin.
+     */
+    return 1;
+}
+
 static log_type
 clientProcessRequest2(clientHttpRequest * http)
 {
@@ -1770,6 +2042,13 @@ clientProcessRequest2(clientHttpRequest * http)
        /* We can generate a HEAD reply from a cached GET object */
        e = http->entry = storeGetPublic(http->uri, METHOD_GET);
     }
+    /* Release negatively cached IP-cache entries on reload */
+    if (r->flags.nocache)
+       ipcacheInvalidate(r->host);
+#if HTTP_VIOLATIONS
+    else if (r->flags.nocache_hack)
+       ipcacheInvalidate(r->host);
+#endif
 #if USE_CACHE_DIGESTS
     http->lookup_type = e ? "HIT" : "MISS";
 #endif
@@ -1783,6 +2062,11 @@ clientProcessRequest2(clientHttpRequest * http)
        http->entry = e;
        return LOG_TCP_HIT;
     }
+    if (http->redirect.status) {
+       /* force this to be a miss */
+       http->entry = NULL;
+       return LOG_TCP_MISS;
+    }
     if (!storeEntryValidToSend(e)) {
        debug(33, 3) ("clientProcessRequest2: !storeEntryValidToSend MISS\n");
        http->entry = NULL;
@@ -1795,12 +2079,6 @@ clientProcessRequest2(clientHttpRequest * http)
        return LOG_TCP_HIT;
     }
 #if HTTP_VIOLATIONS
-    if (r->flags.nocache_hack) {
-       /* if nocache_hack is set, nocache should always be clear, right? */
-       assert(!r->flags.nocache);
-       ipcacheReleaseInvalid(r->host);
-       /* continue! */
-    }
     if (e->store_status == STORE_PENDING) {
        if (r->flags.nocache || r->flags.nocache_hack) {
            debug(33, 3) ("Clearing no-cache for STORE_PENDING request\n\t%s\n",
@@ -1813,10 +2091,12 @@ clientProcessRequest2(clientHttpRequest * http)
     if (r->flags.nocache) {
        debug(33, 3) ("clientProcessRequest2: no-cache REFRESH MISS\n");
        http->entry = NULL;
-       ipcacheReleaseInvalid(r->host);
+       ipcacheInvalidate(r->host);
        return LOG_TCP_CLIENT_REFRESH_MISS;
     }
-    if (r->range && httpHdrRangeWillBeComplex(r->range)) {
+    if (NULL == r->range) {
+       (void) 0;
+    } else if (httpHdrRangeWillBeComplex(r->range)) {
        /*
         * Some clients break if we return "200 OK" for a Range
         * request.  We would have to return "200 OK" for a _complex_
@@ -1826,6 +2106,10 @@ clientProcessRequest2(clientHttpRequest * http)
        debug(33, 3) ("clientProcessRequest2: complex range MISS\n");
        http->entry = NULL;
        return LOG_TCP_MISS;
+    } else if (clientCheckRangeForceMiss(e, r->range)) {
+       debug(33, 3) ("clientProcessRequest2: forcing miss due to range_offset_limit\n");
+       http->entry = NULL;
+       return LOG_TCP_MISS;
     }
     debug(33, 3) ("clientProcessRequest2: default HIT\n");
     http->entry = e;
@@ -1839,12 +2123,13 @@ clientProcessRequest(clientHttpRequest * http)
     request_t *r = http->request;
     int fd = http->conn->fd;
     HttpReply *rep;
+    http_version_t version;
     debug(33, 4) ("clientProcessRequest: %s '%s'\n",
        RequestMethodStr[r->method],
        url);
     if (r->method == METHOD_CONNECT) {
        http->log_type = LOG_TCP_MISS;
-       sslStart(fd, url, r, &http->out.size);
+       sslStart(fd, url, r, &http->out.size, &http->al.http.code);
        return;
     } else if (r->method == METHOD_PURGE) {
        clientPurgeRequest(http);
@@ -1855,7 +2140,8 @@ clientProcessRequest(clientHttpRequest * http)
            storeReleaseRequest(http->entry);
            storeBuffer(http->entry);
            rep = httpReplyCreate();
-           httpReplySetHeaders(rep, 1.0, HTTP_OK, NULL, "text/plain",
+           httpBuildVersion(&version, 1, 0);
+           httpReplySetHeaders(rep, version, HTTP_OK, NULL, "text/plain",
                httpRequestPrefixLen(r), 0, squid_curtime);
            httpReplySwapOut(rep, http->entry);
            httpReplyDestroy(rep);
@@ -1865,10 +2151,6 @@ clientProcessRequest(clientHttpRequest * http)
        }
        /* yes, continue */
        http->log_type = LOG_TCP_MISS;
-    } else if (pumpMethod(r->method)) {
-       http->log_type = LOG_TCP_MISS;
-       /* XXX oof, POST can be cached! */
-       pumpInit(fd, r, http->uri);
     } else {
        http->log_type = clientProcessRequest2(http);
     }
@@ -1879,12 +2161,12 @@ clientProcessRequest(clientHttpRequest * http)
     if (NULL != http->entry) {
        storeLockObject(http->entry);
        storeCreateMemObject(http->entry, http->uri, http->log_uri);
-       storeClientListAdd(http->entry, http);
+       http->entry->mem_obj->method = r->method;
+       http->sc = storeClientListAdd(http->entry, http);
 #if DELAY_POOLS
-       delaySetStoreClient(http->entry, http, delayClient(r));
+       delaySetStoreClient(http->sc, delayClient(r));
 #endif
-       http->entry->refcount++;
-       storeClientCopy(http->entry,
+       storeClientCopy(http->sc, http->entry,
            http->out.offset,
            http->out.offset,
            CLIENT_SOCK_SZ,
@@ -1892,8 +2174,7 @@ clientProcessRequest(clientHttpRequest * http)
            clientCacheHit,
            http);
     } else {
-       /* MISS CASE */
-       http->log_type = LOG_TCP_MISS;
+       /* MISS CASE, http->log_type is already set! */
        clientProcessMiss(http);
     }
 }
@@ -1914,9 +2195,13 @@ clientProcessMiss(clientHttpRequest * http)
      * or IMS request.
      */
     if (http->entry) {
-       if (EBIT_TEST(http->entry->flags, ENTRY_SPECIAL))
+       if (EBIT_TEST(http->entry->flags, ENTRY_SPECIAL)) {
            debug(33, 0) ("clientProcessMiss: miss on a special object (%s).\n", url);
-       storeUnregister(http->entry, http);
+           debug(33, 0) ("\tlog_type = %s\n", log_tags[http->log_type]);
+           storeEntryDump(http->entry, 1);
+       }
+       storeUnregister(http->sc, http->entry, http);
+       http->sc = NULL;
        storeUnlockObject(http->entry);
        http->entry = NULL;
     }
@@ -1938,9 +2223,11 @@ clientProcessMiss(clientHttpRequest * http)
     }
     assert(http->out.offset == 0);
     http->entry = clientCreateStoreEntry(http, r->method, r->flags);
-    http->entry->refcount++;
     if (http->redirect.status) {
        HttpReply *rep = httpReplyCreate();
+#if LOG_TCP_REDIRECTS
+       http->log_type = LOG_TCP_REDIRECT;
+#endif
        storeReleaseRequest(http->entry);
        httpRedirectReply(rep, http->redirect.status, http->redirect.location);
        httpReplySwapOut(rep, http->entry);
@@ -1950,15 +2237,14 @@ clientProcessMiss(clientHttpRequest * http)
     }
     if (http->flags.internal)
        r->protocol = PROTO_INTERNAL;
-    fwdStart(http->conn->fd, http->entry, r,
-       http->conn->peer.sin_addr, http->conn->me.sin_addr);
+    fwdStart(http->conn->fd, http->entry, r);
 }
 
 static clientHttpRequest *
 parseHttpRequestAbort(ConnStateData * conn, const char *uri)
 {
-    clientHttpRequest *http = xcalloc(1, sizeof(clientHttpRequest));
-    cbdataAdd(http, cbdataXfree, 0);
+    clientHttpRequest *http;
+    http = CBDATA_ALLOC(clientHttpRequest, NULL);
     http->conn = conn;
     http->start = current_time;
     http->req_sz = conn->in.offset;
@@ -1984,11 +2270,10 @@ parseHttpRequest(ConnStateData * conn, method_t * method_p, int *status,
     char *mstr = NULL;
     char *url = NULL;
     char *req_hdr = NULL;
-    float http_ver;
+    http_version_t http_ver;
     char *token = NULL;
     char *t = NULL;
     char *end;
-    int free_request = 0;
     size_t header_sz;          /* size of headers, not including first line */
     size_t prefix_sz;          /* size of whole request (req-line + headers) */
     size_t url_sz;
@@ -1998,6 +2283,11 @@ parseHttpRequest(ConnStateData * conn, method_t * method_p, int *status,
 #if IPF_TRANSPARENT
     struct natlookup natLookup;
     static int natfd = -1;
+    static int siocgnatl_cmd = SIOCGNATL & 0xff;
+    int x;
+#endif
+#if LINUX_NETFILTER
+    size_t sock_sz = sizeof(conn->me);
 #endif
 
     if ((req_sz = headersEnd(conn->in.buf, conn->in.offset)) == 0) {
@@ -2059,12 +2349,16 @@ parseHttpRequest(ConnStateData * conn, method_t * method_p, int *status,
     if (token == NULL) {
        debug(33, 3) ("parseHttpRequest: Missing HTTP identifier\n");
 #if RELAXED_HTTP_PARSER
-       http_ver = (float) 0.9; /* wild guess */
+       httpBuildVersion(&http_ver, 0, 9);      /* wild guess */
 #else
        return parseHttpRequestAbort(conn, "error:missing-http-ident");
 #endif
     } else {
-       http_ver = (float) atof(token + 5);
+       if (sscanf(token + 5, "%d.%d", &http_ver.major, &http_ver.minor) != 2) {
+           debug(33, 3) ("parseHttpRequest: Invalid HTTP identifier.\n");
+           return parseHttpRequestAbort(conn, "error: invalid HTTP-ident");
+       }
+       debug(33, 6) ("parseHttpRequest: Client HTTP version %d.%d.\n", http_ver.major, http_ver.minor);
     }
 
     /*
@@ -2089,8 +2383,7 @@ parseHttpRequest(ConnStateData * conn, method_t * method_p, int *status,
     assert(prefix_sz <= conn->in.offset);
 
     /* Ok, all headers are received */
-    http = xcalloc(1, sizeof(clientHttpRequest));
-    cbdataAdd(http, cbdataXfree, 0);
+    http = CBDATA_ALLOC(clientHttpRequest, NULL);
     http->http_ver = http_ver;
     http->conn = conn;
     http->start = current_time;
@@ -2116,6 +2409,8 @@ parseHttpRequest(ConnStateData * conn, method_t * method_p, int *status,
     else if (Config2.Accel.on && *url == '/') {
        /* prepend the accel prefix */
        if (opt_accel_uses_host && (t = mime_get_header(req_hdr, "Host"))) {
+           int vport = (int) Config.Accel.port;
+           char *q;
            /* If a Host: header was specified, use it to build the URL 
             * instead of the one in the Config file. */
            /*
@@ -2125,16 +2420,25 @@ parseHttpRequest(ConnStateData * conn, method_t * method_p, int *status,
             * refer to www.playboy.com.  The 'dst' and/or 'dst_domain' ACL 
             * types should be used to prevent httpd-accelerators 
             * handling requests for non-local servers */
-           strtok(t, " :/;@");
+           strtok(t, " /;@");
+           if ((q = strchr(t, ':'))) {
+               *q++ = '\0';
+               vport = atoi(q);
+           }
            url_sz = strlen(url) + 32 + Config.appendDomainLen +
                strlen(t);
            http->uri = xcalloc(url_sz, 1);
            snprintf(http->uri, url_sz, "http://%s:%d%s",
-               t, (int) Config.Accel.port, url);
+               t, vport, url);
        } else if (vhost_mode) {
+           int vport;
            /* Put the local socket IP address as the hostname */
            url_sz = strlen(url) + 32 + Config.appendDomainLen;
            http->uri = xcalloc(url_sz, 1);
+           if (vport_mode)
+               vport = (int) ntohs(http->conn->me.sin_port);
+           else
+               vport = (int) Config.Accel.port;
 #if IPF_TRANSPARENT
            natLookup.nl_inport = http->conn->me.sin_port;
            natLookup.nl_outport = http->conn->peer.sin_port;
@@ -2148,7 +2452,20 @@ parseHttpRequest(ConnStateData * conn, method_t * method_p, int *status,
                    xstrerror());
                return parseHttpRequestAbort(conn, "error:nat-open-failed");
            }
-           if (ioctl(natfd, SIOCGNATL, &natLookup) < 0) {
+           /*
+            * IP-Filter changed the type for SIOCGNATL between
+            * 3.3 and 3.4.  It also changed the cmd value for
+            * SIOCGNATL, so at least we can detect it.  We could
+            * put something in configure and use ifdefs here, but
+            * this seems simpler.
+            */
+           if (63 == siocgnatl_cmd) {
+               struct natlookup *nlp = &natLookup;
+               x = ioctl(natfd, SIOCGNATL, &nlp);
+           } else {
+               x = ioctl(natfd, SIOCGNATL, &natLookup);
+           }
+           if (x < 0) {
                if (errno != ESRCH) {
                    debug(50, 1) ("parseHttpRequest: NAT lookup failed: ioctl(SIOCGNATL)\n");
                    close(natfd);
@@ -2157,18 +2474,20 @@ parseHttpRequest(ConnStateData * conn, method_t * method_p, int *status,
                } else
                    snprintf(http->uri, url_sz, "http://%s:%d%s",
                        inet_ntoa(http->conn->me.sin_addr),
-                       (int) Config.Accel.port,
-                       url);
+                       vport, url);
            } else
                snprintf(http->uri, url_sz, "http://%s:%d%s",
                    inet_ntoa(natLookup.nl_realip),
-                   (int) Config.Accel.port,
-                   url);
+                   vport, url);
 #else
+#if LINUX_NETFILTER
+           /* If the call fails the address structure will be unchanged */
+           getsockopt(conn->fd, SOL_IP, SO_ORIGINAL_DST, &conn->me, &sock_sz);
+           debug(33, 5) ("parseHttpRequest: addr = %s", inet_ntoa(conn->me.sin_addr));
+#endif
            snprintf(http->uri, url_sz, "http://%s:%d%s",
                inet_ntoa(http->conn->me.sin_addr),
-               (int) Config.Accel.port,
-               url);
+               vport, url);
 #endif
            debug(33, 5) ("VHOST REWRITE: '%s'\n", http->uri);
        } else {
@@ -2185,13 +2504,11 @@ parseHttpRequest(ConnStateData * conn, method_t * method_p, int *status,
        strcpy(http->uri, url);
        http->flags.accel = 0;
     }
-    if (!stringHasWhitespace(http->uri))
+    if (!stringHasCntl(http->uri))
        http->log_uri = xstrndup(http->uri, MAX_URL);
     else
-       http->log_uri = xstrndup(rfc1738_escape(http->uri), MAX_URL);
+       http->log_uri = xstrndup(rfc1738_escape_unescaped(http->uri), MAX_URL);
     debug(33, 5) ("parseHttpRequest: Complete request received\n");
-    if (free_request)
-       safe_free(url);
     xfree(inbuf);
     *status = 1;
     return http;
@@ -2201,7 +2518,10 @@ static int
 clientReadDefer(int fdnotused, void *data)
 {
     ConnStateData *conn = data;
-    return conn->defer.until > squid_curtime;
+    if (conn->body.size_left)
+       return conn->in.offset >= conn->in.size;
+    else
+       return conn->defer.until > squid_curtime;
 }
 
 static void
@@ -2212,6 +2532,7 @@ clientReadRequest(int fd, void *data)
     int k;
     request_t *request = NULL;
     int size;
+    void *p;
     method_t method;
     clientHttpRequest *http = NULL;
     clientHttpRequest **H = NULL;
@@ -2220,11 +2541,11 @@ clientReadRequest(int fd, void *data)
     fde *F = &fd_table[fd];
     int len = conn->in.size - conn->in.offset - 1;
     debug(33, 4) ("clientReadRequest: FD %d: reading request...\n", fd);
-    Counter.syscalls.sock.reads++;
+    statCounter.syscalls.sock.reads++;
     size = read(fd, conn->in.buf + conn->in.offset, len);
     if (size > 0) {
        fd_bytes(fd, size, FD_READ);
-       kb_incr(&Counter.client_http.kbytes_in, size);
+       kb_incr(&statCounter.client_http.kbytes_in, size);
     }
     /*
      * Don't reset the timeout value here.  The timeout value will be
@@ -2233,14 +2554,18 @@ clientReadRequest(int fd, void *data)
      * whole, not individual read() calls.  Plus, it breaks our
      * lame half-close detection
      */
-    commSetSelect(fd, COMM_SELECT_READ, clientReadRequest, conn, 0);
-    if (size == 0) {
-       if (conn->chr == NULL) {
+    if (size > 0) {
+       conn->in.offset += size;
+       conn->in.buf[conn->in.offset] = '\0';   /* Terminate the string */
+    } else if (size == 0 && len > 0) {
+       if (conn->chr == NULL && conn->in.offset == 0) {
            /* no current or pending requests */
+           debug(33, 4) ("clientReadRequest: FD %d closed\n", fd);
            comm_close(fd);
            return;
        } else if (!Config.onoff.half_closed_clients) {
            /* admin doesn't want to support half-closed client sockets */
+           debug(33, 3) ("clientReadRequest: FD %d aborted (half_closed_clients disabled)\n", fd);
            comm_close(fd);
            return;
        }
@@ -2250,7 +2575,11 @@ clientReadRequest(int fd, void *data)
        conn->defer.until = squid_curtime + 1;
        conn->defer.n++;
        fd_note(fd, "half-closed");
-       return;
+       /* There is one more close check at the end, to detect aborted
+        * (partial) requests. At this point we can't tell if the request
+        * is partial.
+        */
+       /* Continue to process previously read data */
     } else if (size < 0) {
        if (!ignoreErrno(errno)) {
            debug(50, 2) ("clientReadRequest: FD %d: %s\n", fd, xstrerror());
@@ -2261,13 +2590,16 @@ clientReadRequest(int fd, void *data)
            return;
        }
        /* Continue to process previously read data */
-       size = 0;
     }
-    conn->in.offset += size;
-    /* Skip leading (and trailing) whitespace */
-    while (conn->in.offset > 0) {
+    commSetSelect(fd, COMM_SELECT_READ, clientReadRequest, conn, 0);
+    /* Process request body if any */
+    if (conn->in.offset > 0 && conn->body.callback != NULL)
+       clientProcessBody(conn);
+    /* Process next request */
+    while (conn->in.offset > 0 && conn->body.size_left == 0) {
        int nrequests;
        size_t req_line_sz;
+       /* Skip leading (and trailing) whitespace */
        while (conn->in.offset > 0 && xisspace(conn->in.buf[0])) {
            xmemmove(conn->in.buf, conn->in.buf + 1, conn->in.offset - 1);
            conn->in.offset--;
@@ -2278,11 +2610,14 @@ clientReadRequest(int fd, void *data)
        /* Limit the number of concurrent requests to 2 */
        for (H = &conn->chr, nrequests = 0; *H; H = &(*H)->next, nrequests++);
        if (nrequests >= 2) {
-           debug(33, 2) ("clientReadRequest: FD %d max concurrent requests reached\n", fd);
+           debug(33, 3) ("clientReadRequest: FD %d max concurrent requests reached\n", fd);
            debug(33, 5) ("clientReadRequest: FD %d defering new request until one is done\n", fd);
            conn->defer.until = squid_curtime + 100;    /* Reset when a request is complete */
            break;
        }
+       conn->in.buf[conn->in.offset] = '\0';   /* Terminate the string */
+       if (nrequests == 0)
+           fd_note(conn->fd, "Reading next request");
        /* Process request */
        http = parseHttpRequest(conn,
            &method,
@@ -2306,7 +2641,14 @@ clientReadRequest(int fd, void *data)
            for (H = &conn->chr; *H; H = &(*H)->next);
            *H = http;
            conn->nrequests++;
-           commSetTimeout(fd, Config.Timeout.lifetime, NULL, NULL);
+           /*
+            * I wanted to lock 'http' here since its callback data for 
+            * clientLifetimeTimeout(), but there's no logical place to
+            * cbdataUnlock if the timeout never happens.  Maybe its safe
+            * enough to assume that if the FD is open, and the timeout
+            * triggers, that 'http' is valid.
+            */
+           commSetTimeout(fd, Config.Timeout.lifetime, clientLifetimeTimeout, http);
            if (parser_return_code < 0) {
                debug(33, 1) ("clientReadRequest: FD %d Invalid Request\n", fd);
                err = errorCon(ERR_INVALID_REQ, HTTP_BAD_REQUEST);
@@ -2337,33 +2679,40 @@ clientReadRequest(int fd, void *data)
            request->flags.accelerated = http->flags.accel;
            if (!http->flags.internal) {
                if (internalCheck(strBuf(request->urlpath))) {
-                   if (0 == strcasecmp(request->host, internalHostname()) &&
-                       request->port == Config.Port.http->i) {
+                   if (internalHostnameIs(request->host) &&
+                       request->port == ntohs(Config.Sockaddr.http->s.sin_port)) {
                        http->flags.internal = 1;
                    } else if (internalStaticCheck(strBuf(request->urlpath))) {
                        xstrncpy(request->host, internalHostname(), SQUIDHOSTNAMELEN);
-                       request->port = Config.Port.http->i;
+                       request->port = ntohs(Config.Sockaddr.http->s.sin_port);
                        http->flags.internal = 1;
                    }
                }
            }
+           /*
+            * cache the Content-length value in request_t.
+            */
+           request->content_length = httpHeaderGetInt(&request->header,
+               HDR_CONTENT_LENGTH);
            request->flags.internal = http->flags.internal;
            safe_free(prefix);
            safe_free(http->log_uri);
            http->log_uri = xstrdup(urlCanonicalClean(request));
            request->client_addr = conn->peer.sin_addr;
            request->my_addr = conn->me.sin_addr;
+           request->my_port = ntohs(conn->me.sin_port);
            request->http_ver = http->http_ver;
            if (!urlCheckRequest(request)) {
                err = errorCon(ERR_UNSUP_REQ, HTTP_NOT_IMPLEMENTED);
                err->src_addr = conn->peer.sin_addr;
                err->request = requestLink(request);
+               request->flags.proxy_keepalive = 0;
                http->al.http.code = err->http_status;
                http->entry = clientCreateStoreEntry(http, request->method, null_request_flags);
                errorAppendEntry(http->entry, err);
                break;
            }
-           if (0 == clientCheckContentLength(request)) {
+           if (!clientCheckContentLength(request)) {
                err = errorCon(ERR_INVALID_REQ, HTTP_LENGTH_REQUIRED);
                err->src_addr = conn->peer.sin_addr;
                err->request = requestLink(request);
@@ -2373,39 +2722,23 @@ clientReadRequest(int fd, void *data)
                break;
            }
            http->request = requestLink(request);
-           /*
-            * We need to set the keepalive flag before doing some
-            * hacks for POST/PUT requests below.  Maybe we could
-            * set keepalive flag even earlier.
-            */
            clientSetKeepaliveFlag(http);
-           /*
-            * break here for NON-GET because most likely there is a
-            * reqeust body following and we don't want to parse it
-            * as though it was new request
-            */
-           if (request->method != METHOD_GET) {
-               int cont_len = httpHeaderGetInt(&request->header, HDR_CONTENT_LENGTH);
-               int copy_len = XMIN(conn->in.offset, cont_len);
-               if (copy_len > 0) {
-                   assert(conn->in.offset >= copy_len);
-                   request->body_sz = copy_len;
-                   request->body = xmalloc(request->body_sz);
-                   xmemcpy(request->body, conn->in.buf, request->body_sz);
-                   conn->in.offset -= copy_len;
-                   if (conn->in.offset)
-                       xmemmove(conn->in.buf, conn->in.buf + copy_len, conn->in.offset);
+           /* Do we expect a request-body? */
+           if (request->content_length > 0) {
+               conn->body.size_left = request->content_length;
+               request->body_connection = conn;
+               /* Is it too large? */
+               if (clientRequestBodyTooLarge(request->content_length)) {
+                   err = errorCon(ERR_TOO_BIG, HTTP_REQUEST_ENTITY_TOO_LARGE);
+                   err->request = requestLink(request);
+                   http->entry = clientCreateStoreEntry(http,
+                       METHOD_NONE, null_request_flags);
+                   errorAppendEntry(http->entry, err);
+                   break;
                }
-               /*
-                * if we didn't get the full body now, then more will
-                * be arriving on the client socket.  Lets cancel
-                * the read handler until this request gets forwarded.
-                */
-               if (request->body_sz < cont_len)
-                   commSetSelect(fd, COMM_SELECT_READ, NULL, NULL, 0);
            }
            clientAccessCheck(http);
-           continue;           /* while offset > 0 */
+           continue;           /* while offset > 0 && body.size_left == 0 */
        } else if (parser_return_code == 0) {
            /*
             *    Partial request received; reschedule until parseHttpRequest()
@@ -2413,14 +2746,13 @@ clientReadRequest(int fd, void *data)
             */
            k = conn->in.size - 1 - conn->in.offset;
            if (k == 0) {
-               if (conn->in.offset >= Config.maxRequestSize) {
+               if (conn->in.offset >= Config.maxRequestHeaderSize) {
                    /* The request is too large to handle */
-                   debug(33, 0) ("Request won't fit in buffer.\n");
-                   debug(33, 0) ("Config 'request_size'= %d bytes.\n",
-                       Config.maxRequestSize);
-                   debug(33, 0) ("This request = %d bytes.\n",
+                   debug(33, 1) ("Request header is too large (%d bytes)\n",
                        (int) conn->in.offset);
-                   err = errorCon(ERR_INVALID_REQ, HTTP_REQUEST_ENTITY_TOO_LARGE);
+                   debug(33, 1) ("Config 'request_header_max_size'= %d bytes.\n",
+                       Config.maxRequestHeaderSize);
+                   err = errorCon(ERR_TOO_BIG, HTTP_REQUEST_ENTITY_TOO_LARGE);
                    http = parseHttpRequestAbort(conn, "error:request-too-large");
                    /* add to the client request queue */
                    for (H = &conn->chr; *H; H = &(*H)->next);
@@ -2430,25 +2762,157 @@ clientReadRequest(int fd, void *data)
                    return;
                }
                /* Grow the request memory area to accomodate for a large request */
-               conn->in.size += REQUEST_BUF_SIZE;
-               conn->in.buf = xrealloc(conn->in.buf, conn->in.size);
+               conn->in.size += CLIENT_REQ_BUF_SZ;
+               if (conn->in.size == 2 * CLIENT_REQ_BUF_SZ) {
+                   p = conn->in.buf;   /* get rid of fixed size Pooled buffer */
+                   conn->in.buf = xcalloc(2, CLIENT_REQ_BUF_SZ);
+                   xmemcpy(conn->in.buf, p, CLIENT_REQ_BUF_SZ);
+                   memFree(p, MEM_CLIENT_REQ_BUF);
+               } else
+                   conn->in.buf = xrealloc(conn->in.buf, conn->in.size);
                /* XXX account conn->in.buf */
-               debug(33, 2) ("Handling a large request, offset=%d inbufsize=%d\n",
+               debug(33, 3) ("Handling a large request, offset=%d inbufsize=%d\n",
                    (int) conn->in.offset, conn->in.size);
                k = conn->in.size - 1 - conn->in.offset;
            }
            break;
        }
+    }                          /* while offset > 0 && conn->body.size_left == 0 */
+    /* Check if a half-closed connection was aborted in the middle */
+    if (F->flags.socket_eof) {
+       if (conn->in.offset != conn->body.size_left) {  /* != 0 when no request body */
+           /* Partial request received. Abort client connection! */
+           debug(33, 3) ("clientReadRequest: FD %d aborted\n", fd);
+           comm_close(fd);
+           return;
+       }
+    }
+}
+
+/* file_read like function, for reading body content */
+void
+clientReadBody(request_t * request, char *buf, size_t size, CBCB * callback, void *cbdata)
+{
+    ConnStateData *conn = request->body_connection;
+    if (!conn) {
+       debug(33, 5) ("clientReadBody: no body to read, request=%p\n", request);
+       callback(buf, 0, cbdata);       /* Signal end of body */
+       return;
+    }
+    debug(33, 2) ("clientReadBody: start fd=%d body_size=%d in.offset=%d cb=%p req=%p\n", conn->fd, conn->body.size_left, conn->in.offset, callback, request);
+    conn->body.callback = callback;
+    conn->body.cbdata = cbdata;
+    conn->body.buf = buf;
+    conn->body.bufsize = size;
+    conn->body.request = requestLink(request);
+    if (conn->in.offset) {
+       /* Data available */
+       clientProcessBody(conn);
+    } else {
+       debug(33, 2) ("clientReadBody: fd %d wait for clientReadRequest\n", conn->fd);
+    }
+}
+
+/* Called by clientReadRequest to process body content */
+static void
+clientProcessBody(ConnStateData * conn)
+{
+    int size;
+    char *buf = conn->body.buf;
+    void *cbdata = conn->body.cbdata;
+    CBCB *callback = conn->body.callback;
+    request_t *request = conn->body.request;
+    /* Note: request is null while eating "aborted" transfers */
+    debug(33, 2) ("clientProcessBody: start fd=%d body_size=%d in.offset=%d cb=%p req=%p\n", conn->fd, conn->body.size_left, conn->in.offset, callback, request);
+    /* Some sanity checks... */
+    assert(conn->body.size_left > 0);
+    assert(conn->in.offset > 0);
+    assert(callback != NULL);
+    assert(buf != NULL);
+    /* How much do we have to process? */
+    size = conn->in.offset;
+    if (size > conn->body.size_left)   /* only process the body part */
+       size = conn->body.size_left;
+    if (size > conn->body.bufsize)     /* don't copy more than requested */
+       size = conn->body.bufsize;
+    xmemcpy(buf, conn->in.buf, size);
+    conn->body.size_left -= size;
+    /* Move any remaining data */
+    conn->in.offset -= size;
+    if (conn->in.offset > 0)
+       xmemmove(conn->in.buf, conn->in.buf + size, conn->in.offset);
+    /* Remove request link if this is the last part of the body, as
+     * clientReadRequest automatically continues to process next request */
+    if (conn->body.size_left <= 0 && request != NULL)
+       request->body_connection = NULL;
+    /* Remove clientReadBody arguments (the call is completed) */
+    conn->body.request = NULL;
+    conn->body.callback = NULL;
+    conn->body.buf = NULL;
+    conn->body.bufsize = 0;
+    /* Remember that we have touched the body, not restartable */
+    if (request != NULL)
+       request->flags.body_sent = 1;
+    /* Invoke callback function */
+    callback(buf, size, cbdata);
+    if (request != NULL)
+       requestUnlink(request); /* Linked in clientReadBody */
+    debug(33, 2) ("clientProcessBody: end fd=%d size=%d body_size=%d in.offset=%d cb=%p req=%p\n", conn->fd, size, conn->body.size_left, conn->in.offset, callback, request);
+    return;
+}
+
+/* A dummy handler that throws away a request-body */
+static char bodyAbortBuf[SQUID_TCP_SO_RCVBUF];
+void
+clientReadBodyAbortHandler(char *buf, size_t size, void *data)
+{
+    ConnStateData *conn = (ConnStateData *) data;
+    debug(33, 2) ("clientReadBodyAbortHandler: fd=%d body_size=%d in.offset=%d\n", conn->fd, conn->body.size_left, conn->in.offset);
+    if (size != 0 && conn->body.size_left != 0) {
+       debug(33, 3) ("clientReadBodyAbortHandler: fd=%d shedule next read\n", conn->fd);
+       conn->body.callback = clientReadBodyAbortHandler;
+       conn->body.buf = bodyAbortBuf;
+       conn->body.bufsize = sizeof(bodyAbortBuf);
+       conn->body.cbdata = data;
     }
 }
 
+/* Abort a body request */
+int
+clientAbortBody(request_t * request)
+{
+    ConnStateData *conn = request->body_connection;
+    char *buf;
+    CBCB *callback;
+    void *cbdata;
+    request->body_connection = NULL;
+    if (!conn || conn->body.size_left <= 0)
+       return 0;               /* No body to abort */
+    if (conn->body.callback != NULL) {
+       buf = conn->body.buf;
+       callback = conn->body.callback;
+       cbdata = conn->body.cbdata;
+       assert(request == conn->body.request);
+       conn->body.buf = NULL;
+       conn->body.callback = NULL;
+       conn->body.cbdata = NULL;
+       conn->body.request = NULL;
+       callback(buf, -1, cbdata);      /* Signal abort to clientReadBody caller */
+       requestUnlink(request);
+    }
+    clientReadBodyAbortHandler(NULL, -1, conn);                /* Install abort handler */
+    /* clientProcessBody() */
+    return 1;                  /* Aborted */
+}
+
 /* general lifetime handler for HTTP requests */
 static void
 requestTimeout(int fd, void *data)
 {
+#if THIS_CONFUSES_PERSISTENT_CONNECTION_AWARE_BROWSERS_AND_USERS
     ConnStateData *conn = data;
     ErrorState *err;
-    debug(33, 2) ("requestTimeout: FD %d: lifetime is expired.\n", fd);
+    debug(33, 3) ("requestTimeout: FD %d: lifetime is expired.\n", fd);
     if (fd_table[fd].rwstate) {
        /*
         * Some data has been sent to the client, just close the FD
@@ -2477,11 +2941,38 @@ requestTimeout(int fd, void *data)
         * if we don't close() here, we still need a timeout handler!
         */
        commSetTimeout(fd, 30, requestTimeout, conn);
+       /*
+        * Aha, but we don't want a read handler!
+        */
+       commSetSelect(fd, COMM_SELECT_READ, NULL, NULL, 0);
     }
+#else
+    /*
+     * Just close the connection to not confuse browsers
+     * using persistent connections. Some browsers opens
+     * an connection and then does not use it until much
+     * later (presumeably because the request triggering
+     * the open has already been completed on another
+     * connection)
+     */
+    debug(33, 3) ("requestTimeout: FD %d: lifetime is expired.\n", fd);
+    comm_close(fd);
+#endif
+}
+
+static void
+clientLifetimeTimeout(int fd, void *data)
+{
+    clientHttpRequest *http = data;
+    ConnStateData *conn = http->conn;
+    debug(33, 1) ("WARNING: Closing client %s connection due to lifetime timeout\n",
+       inet_ntoa(conn->peer.sin_addr));
+    debug(33, 1) ("\t%s\n", http->uri);
+    comm_close(fd);
 }
 
 static int
-httpAcceptDefer(void)
+httpAcceptDefer(int fdunused, void *dataunused)
 {
     static time_t last_warn = 0;
     if (fdNFree() >= RESERVED_FD)
@@ -2507,7 +2998,7 @@ httpAccept(int sock, void *data)
     static aclCheck_t identChecklist;
 #endif
     commSetSelect(sock, COMM_SELECT_READ, httpAccept, NULL, 0);
-    while (max-- && !httpAcceptDefer()) {
+    while (max-- && !httpAcceptDefer(sock, NULL)) {
        memset(&peer, '\0', sizeof(struct sockaddr_in));
        memset(&me, '\0', sizeof(struct sockaddr_in));
        if ((fd = comm_accept(sock, &peer, &me)) < 0) {
@@ -2517,15 +3008,14 @@ httpAccept(int sock, void *data)
            break;
        }
        debug(33, 4) ("httpAccept: FD %d: accepted\n", fd);
-       connState = xcalloc(1, sizeof(ConnStateData));
+       connState = CBDATA_ALLOC(ConnStateData, NULL);
        connState->peer = peer;
        connState->log_addr = peer.sin_addr;
        connState->log_addr.s_addr &= Config.Addrs.client_netmask.s_addr;
        connState->me = me;
        connState->fd = fd;
-       connState->in.size = REQUEST_BUF_SIZE;
-       connState->in.buf = xcalloc(connState->in.size, 1);
-       cbdataAdd(connState, cbdataXfree, 0);
+       connState->in.size = CLIENT_REQ_BUF_SZ;
+       connState->in.buf = memAllocate(MEM_CLIENT_REQ_BUF);
        /* XXX account connState->in.buf */
        comm_add_close_handler(fd, connStateFree, connState);
        if (Config.onoff.log_fqdn)
@@ -2534,11 +3024,14 @@ httpAccept(int sock, void *data)
 #if USE_IDENT
        identChecklist.src_addr = peer.sin_addr;
        identChecklist.my_addr = me.sin_addr;
+       identChecklist.my_port = ntohs(me.sin_port);
        if (aclCheckFast(Config.accessList.identLookup, &identChecklist))
            identStart(&me, &peer, clientIdentDone, connState);
 #endif
        commSetSelect(fd, COMM_SELECT_READ, clientReadRequest, connState, 0);
        commSetDefer(fd, clientReadDefer, connState);
+       clientdbEstablished(peer.sin_addr, 1);
+       assert(N);
        (*N)++;
     }
 }
@@ -2615,7 +3108,7 @@ clientCheckTransferDone(clientHttpRequest * http)
 static int
 clientGotNotEnough(clientHttpRequest * http)
 {
-    int cl = http->entry->mem_obj->reply->content_length;
+    int cl = httpReplyBodySize(http->request->method, http->entry->mem_obj->reply);
     int hs = http->entry->mem_obj->reply->hdr_sz;
     assert(cl >= 0);
     if (http->out.offset < cl + hs)
@@ -2670,14 +3163,19 @@ checkFailureRatio(err_type etype, hier_code hcode)
 void
 clientHttpConnectionsOpen(void)
 {
-    ushortlist *u;
+    sockaddr_in_list *s;
     int fd;
-    for (u = Config.Port.http; u; u = u->next) {
+    for (s = Config.Sockaddr.http; s; s = s->next) {
+       if (MAXHTTPPORTS == NHttpSockets) {
+           debug(1, 1) ("WARNING: You have too many 'http_port' lines.\n");
+           debug(1, 1) ("         The limit is %d\n", MAXHTTPPORTS);
+           continue;
+       }
        enter_suid();
        fd = comm_open(SOCK_STREAM,
            0,
-           Config.Addrs.tcp_incoming,
-           u->i,
+           s->s.sin_addr,
+           ntohs(s->s.sin_port),
            COMM_NONBLOCKING,
            "HTTP Socket");
        leave_suid();
@@ -2685,9 +3183,15 @@ clientHttpConnectionsOpen(void)
            continue;
        comm_listen(fd);
        commSetSelect(fd, COMM_SELECT_READ, httpAccept, NULL, 0);
-       /*commSetDefer(fd, httpAcceptDefer, NULL); */
-       debug(1, 1) ("Accepting HTTP connections on port %d, FD %d.\n",
-           (int) u->i, fd);
+       /*
+        * We need to set a defer handler here so that we don't
+        * peg the CPU with select() when we hit the FD limit.
+        */
+       commSetDefer(fd, httpAcceptDefer, NULL);
+       debug(1, 1) ("Accepting HTTP connections at %s, port %d, FD %d.\n",
+           inet_ntoa(s->s.sin_addr),
+           (int) ntohs(s->s.sin_port),
+           fd);
        HttpSockets[NHttpSockets++] = fd;
     }
     if (NHttpSockets < 1)