]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/client_side_reply.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / client_side_reply.cc
index 2d79a115a166eaca664be906e08356469a71411e..d087b58b43556a70e4722eb848af93f7cd47f374 100644 (file)
  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
  *
  */
-#include "config.h"
-
-/* for ClientActiveRequests global */
-#include "dlink.h"
-
-/* old includes without reasons given. */
 #include "squid.h"
 #include "acl/FilledChecklist.h"
 #include "acl/Gadgets.h"
-#include "auth/UserRequest.h"
-#include "client_side.h"
+#include "anyp/PortCfg.h"
 #include "client_side_reply.h"
+#include "client_side.h"
 #include "clientStream.h"
-#if DELAY_POOLS
-#include "DelayPools.h"
-#endif
+#include "dlink.h"
 #include "errorpage.h"
-#if USE_SQUID_ESI
-#include "esi/Esi.h"
-#endif
 #include "fde.h"
+#include "format/Token.h"
 #include "forward.h"
+#include "globals.h"
+#include "globals.h"
 #include "HttpReply.h"
 #include "HttpRequest.h"
 #include "ip/QosConfig.h"
+#include "ipcache.h"
 #include "MemObject.h"
+#include "protos.h"
 #include "SquidTime.h"
-#include "StoreClient.h"
 #include "Store.h"
+#include "StoreClient.h"
+#if USE_AUTH
+#include "auth/UserRequest.h"
+#endif
+#if USE_DELAY_POOLS
+#include "DelayPools.h"
+#endif
+#if USE_SQUID_ESI
+#include "esi/Esi.h"
+#endif
 
 CBDATA_CLASS_INIT(clientReplyContext);
 
@@ -96,28 +99,51 @@ void
 clientReplyContext::setReplyToError(
     err_type err, http_status status, const HttpRequestMethod& method, char const *uri,
     Ip::Address &addr, HttpRequest * failedrequest, const char *unparsedrequest,
-    AuthUserRequest::Pointer auth_user_request)
+#if USE_AUTH
+    Auth::UserRequest::Pointer auth_user_request
+#else
+    void*
+#endif
+)
 {
     ErrorState *errstate = clientBuildError(err, status, uri, addr, failedrequest);
 
     if (unparsedrequest)
         errstate->request_hdrs = xstrdup(unparsedrequest);
 
-    if (status == HTTP_NOT_IMPLEMENTED && http->request)
+#if USE_AUTH
+    errstate->auth_user_request = auth_user_request;
+#endif
+    setReplyToError(method, errstate);
+}
+
+void clientReplyContext::setReplyToError(const HttpRequestMethod& method, ErrorState *errstate)
+{
+    if (errstate->httpStatus == HTTP_NOT_IMPLEMENTED && http->request)
         /* prevent confusion over whether we default to persistent or not */
         http->request->flags.proxy_keepalive = 0;
 
-    http->al.http.code = errstate->httpStatus;
+    http->al->http.code = errstate->httpStatus;
 
     createStoreEntry(method, request_flags());
-
-    errstate->auth_user_request = auth_user_request;
-
     assert(errstate->callback_data == NULL);
     errorAppendEntry(http->storeEntry(), errstate);
     /* Now the caller reads to get this */
 }
 
+void clientReplyContext::setReplyToStoreEntry(StoreEntry *entry)
+{
+    entry->lock(); // removeClientStoreReference() unlocks
+    sc = storeClientListAdd(entry, this);
+#if USE_DELAY_POOLS
+    sc->setDelayId(DelayId::DelayClient(http));
+#endif
+    reqofs = 0;
+    reqsize = 0;
+    flags.storelogiccomplete = 1;
+    http->storeEntry(entry);
+}
+
 void
 clientReplyContext::removeStoreReference(store_client ** scp,
         StoreEntry ** ep)
@@ -251,7 +277,7 @@ clientReplyContext::processExpired()
                              http->log_uri, http->request->flags, http->request->method);
     /* NOTE, don't call StoreEntry->lock(), storeCreateEntry() does it */
     sc = storeClientListAdd(entry, this);
-#if DELAY_POOLS
+#if USE_DELAY_POOLS
     /* delay_id is already set on original store client */
     sc->setDelayId(DelayId::DelayClient(http));
 #endif
@@ -260,19 +286,19 @@ clientReplyContext::processExpired()
     debugs(88, 5, "clientReplyContext::processExpired : lastmod " << entry->lastmod );
     http->storeEntry(entry);
     assert(http->out.offset == 0);
-
-    http->request->clientConnection = http->getConn();
+    assert(http->request->clientConnectionManager == http->getConn());
 
     /*
      * A refcounted pointer so that FwdState stays around as long as
      * this clientReplyContext does
      */
-    Comm::ConnectionPointer conn = http->getConn() != NULL ? http->getConn()->clientConn : NULL;
-    FwdState::fwdStart(conn, http->storeEntry(), http->request);
+    Comm::ConnectionPointer conn = http->getConn() != NULL ? http->getConn()->clientConnection : NULL;
+    FwdState::Start(conn, http->storeEntry(), http->request, http->al);
 
     /* Register with storage manager to receive updates when data comes in. */
+
     if (EBIT_TEST(entry->flags, ENTRY_ABORTED))
-        debugs(88, 0, "clientReplyContext::processExpired: Found ENTRY_ABORTED object");
+        debugs(88, DBG_CRITICAL, "clientReplyContext::processExpired: Found ENTRY_ABORTED object");
 
     {
         /* start counting the length from 0 */
@@ -281,7 +307,6 @@ clientReplyContext::processExpired()
     }
 }
 
-
 void
 clientReplyContext::sendClientUpstreamResponse()
 {
@@ -357,6 +382,7 @@ clientReplyContext::handleIMSReply(StoreIOBuffer result)
     // origin replied 304
     if (status == HTTP_NOT_MODIFIED) {
         http->logType = LOG_TCP_REFRESH_UNMODIFIED;
+        http->request->flags.stale_if_hit = 0; // old_entry is no longer stale
 
         // update headers on existing entry
         old_rep->updateOnNotModified(http->storeEntry()->getReply());
@@ -464,7 +490,7 @@ clientReplyContext::cacheHit(StoreIOBuffer result)
     assert(http->logType == LOG_TCP_HIT);
 
     if (strcmp(e->mem_obj->url, urlCanonical(r)) != 0) {
-        debugs(33, 1, "clientProcessHit: URL mismatch, '" << e->mem_obj->url << "' != '" << urlCanonical(r) << "'");
+        debugs(33, DBG_IMPORTANT, "clientProcessHit: URL mismatch, '" << e->mem_obj->url << "' != '" << urlCanonical(r) << "'");
         processMiss();
         return;
     }
@@ -495,7 +521,7 @@ clientReplyContext::cacheHit(StoreIOBuffer result)
 
     case VARY_CANCEL:
         /* varyEvaluateMatch found a object loop. Process as miss */
-        debugs(88, 1, "clientProcessHit: Vary object loop!");
+        debugs(88, DBG_IMPORTANT, "clientProcessHit: Vary object loop!");
         processMiss();
         return;
     }
@@ -542,7 +568,7 @@ clientReplyContext::cacheHit(StoreIOBuffer result)
              */
             http->logType = LOG_TCP_CLIENT_REFRESH_MISS;
             processMiss();
-        } else if (r->protocol == PROTO_HTTP) {
+        } else if (r->protocol == AnyP::PROTO_HTTP) {
             /*
              * Object needs to be revalidated
              * XXX This could apply to FTP as well, if Last-Modified is known.
@@ -556,46 +582,14 @@ clientReplyContext::cacheHit(StoreIOBuffer result)
             http->logType = LOG_TCP_MISS;
             processMiss();
         }
-    } else if (r->flags.ims) {
-        /*
-         * Handle If-Modified-Since requests from the client
-         */
-
-        if (e->getReply()->sline.status != HTTP_OK) {
-            debugs(88, 4, "clientCacheHit: Reply code " <<
-                   e->getReply()->sline.status << " != 200");
-            http->logType = LOG_TCP_MISS;
-            processMiss();
-        } else if (e->modifiedSince(http->request)) {
-            http->logType = LOG_TCP_IMS_HIT;
-            sendMoreData(result);
-        } else {
-            time_t const timestamp = e->timestamp;
-            HttpReply *temprep = e->getReply()->make304();
-            http->logType = LOG_TCP_IMS_HIT;
-            removeClientStoreReference(&sc, http);
-            createStoreEntry(http->request->method,
-                             request_flags());
-            e = http->storeEntry();
-            /*
-             * Copy timestamp from the original entry so the 304
-             * reply has a meaningful Age: header.
-             */
-            e->timestamp = timestamp;
-            e->replaceHttpReply(temprep);
-            e->complete();
-            /* TODO: why put this in the store and then serialise it and then parse it again.
-             * Simply mark the request complete in our context and
-             * write the reply struct to the client side
-             */
-            triggerInitialStoreRead();
-        }
-    } else {
+    } else if (r->conditional())
+        processConditional(result);
+    else {
         /*
          * plain ol' cache hit
          */
 
-#if DELAY_POOLS
+#if USE_DELAY_POOLS
         if (e->store_status != STORE_OK)
             http->logType = LOG_TCP_MISS;
         else
@@ -626,8 +620,8 @@ clientReplyContext::processMiss()
      */
     if (http->storeEntry()) {
         if (EBIT_TEST(http->storeEntry()->flags, ENTRY_SPECIAL)) {
-            debugs(88, 0, "clientProcessMiss: miss on a special object (" << url << ").");
-            debugs(88, 0, "\tlog_type = " << log_tags[http->logType]);
+            debugs(88, DBG_CRITICAL, "clientProcessMiss: miss on a special object (" << url << ").");
+            debugs(88, DBG_CRITICAL, "\tlog_type = " << Format::log_tags[http->logType]);
             http->storeEntry()->dump(1);
         }
 
@@ -654,8 +648,8 @@ clientReplyContext::processMiss()
     /// Deny loops for accelerator and interceptor. TODO: deny in all modes?
     if (r->flags.loopdetect &&
             (http->flags.accel || http->flags.intercepted)) {
-        http->al.http.code = HTTP_FORBIDDEN;
-        err = clientBuildError(ERR_ACCESS_DENIED, HTTP_FORBIDDEN, NULL, http->getConn()->peer, http->request);
+        http->al->http.code = HTTP_FORBIDDEN;
+        err = clientBuildError(ERR_ACCESS_DENIED, HTTP_FORBIDDEN, NULL, http->getConn()->clientConnection->remote, http->request);
         createStoreEntry(r->method, request_flags());
         errorAppendEntry(http->storeEntry(), err);
         triggerInitialStoreRead();
@@ -667,11 +661,7 @@ clientReplyContext::processMiss()
 
         if (http->redirect.status) {
             HttpReply *rep = new HttpReply;
-#if LOG_TCP_REDIRECTS
-
             http->logType = LOG_TCP_REDIRECT;
-#endif
-
             http->storeEntry()->releaseRequest();
             rep->redirect(http->redirect.status, http->redirect.location);
             http->storeEntry()->replaceHttpReply(rep);
@@ -681,13 +671,13 @@ clientReplyContext::processMiss()
 
         /** Check for internal requests. Update Protocol info if so. */
         if (http->flags.internal)
-            r->protocol = PROTO_INTERNAL;
+            r->protocol = AnyP::PROTO_INTERNAL;
 
-        r->clientConnection = http->getConn();
+        assert(r->clientConnectionManager == http->getConn());
 
         /** Start forwarding to get the new object from network */
-        Comm::ConnectionPointer conn = http->getConn() != NULL ? http->getConn()->clientConn : NULL;
-        FwdState::fwdStart(conn, http->storeEntry(), r);
+        Comm::ConnectionPointer conn = http->getConn() != NULL ? http->getConn()->clientConnection : NULL;
+        FwdState::Start(conn, http->storeEntry(), r, http->al);
     }
 }
 
@@ -700,15 +690,81 @@ clientReplyContext::processMiss()
 void
 clientReplyContext::processOnlyIfCachedMiss()
 {
-    ErrorState *err = NULL;
     debugs(88, 4, "clientProcessOnlyIfCachedMiss: '" <<
            RequestMethodStr(http->request->method) << " " << http->uri << "'");
-    http->al.http.code = HTTP_GATEWAY_TIMEOUT;
-    err = clientBuildError(ERR_ONLY_IF_CACHED_MISS, HTTP_GATEWAY_TIMEOUT, NULL, http->getConn()->peer, http->request);
+    http->al->http.code = HTTP_GATEWAY_TIMEOUT;
+    ErrorState *err = clientBuildError(ERR_ONLY_IF_CACHED_MISS, HTTP_GATEWAY_TIMEOUT, NULL,
+                                       http->getConn()->clientConnection->remote, http->request);
     removeClientStoreReference(&sc, http);
     startError(err);
 }
 
+/// process conditional request from client
+void
+clientReplyContext::processConditional(StoreIOBuffer &result)
+{
+    StoreEntry *const e = http->storeEntry();
+
+    if (e->getReply()->sline.status != HTTP_OK) {
+        debugs(88, 4, "clientReplyContext::processConditional: Reply code " <<
+               e->getReply()->sline.status << " != 200");
+        http->logType = LOG_TCP_MISS;
+        processMiss();
+        return;
+    }
+
+    HttpRequest &r = *http->request;
+
+    if (r.header.has(HDR_IF_MATCH) && !e->hasIfMatchEtag(r)) {
+        // RFC 2616: reply with 412 Precondition Failed if If-Match did not match
+        sendPreconditionFailedError();
+        return;
+    }
+
+    bool matchedIfNoneMatch = false;
+    if (r.header.has(HDR_IF_NONE_MATCH)) {
+        if (!e->hasIfNoneMatchEtag(r)) {
+            // RFC 2616: ignore IMS if If-None-Match did not match
+            r.flags.ims = 0;
+            r.ims = -1;
+            r.imslen = 0;
+            r.header.delById(HDR_IF_MODIFIED_SINCE);
+            http->logType = LOG_TCP_MISS;
+            sendMoreData(result);
+            return;
+        }
+
+        if (!r.flags.ims) {
+            // RFC 2616: if If-None-Match matched and there is no IMS,
+            // reply with 304 Not Modified or 412 Precondition Failed
+            sendNotModifiedOrPreconditionFailedError();
+            return;
+        }
+
+        // otherwise check IMS below to decide if we reply with 304 or 412
+        matchedIfNoneMatch = true;
+    }
+
+    if (r.flags.ims) {
+        // handle If-Modified-Since requests from the client
+        if (e->modifiedSince(&r)) {
+            http->logType = LOG_TCP_IMS_HIT;
+            sendMoreData(result);
+            return;
+        }
+
+        if (matchedIfNoneMatch) {
+            // If-None-Match matched, reply with 304 Not Modified or
+            // 412 Precondition Failed
+            sendNotModifiedOrPreconditionFailedError();
+            return;
+        }
+
+        // otherwise reply with 304 Not Modified
+        sendNotModified();
+    }
+}
+
 void
 clientReplyContext::purgeRequestFindObjectToPurge()
 {
@@ -804,7 +860,8 @@ clientReplyContext::purgeFoundObject(StoreEntry *entry)
 
     if (EBIT_TEST(entry->flags, ENTRY_SPECIAL)) {
         http->logType = LOG_TCP_DENIED;
-        ErrorState *err = clientBuildError(ERR_ACCESS_DENIED, HTTP_FORBIDDEN, NULL, http->getConn()->peer, http->request);
+        ErrorState *err = clientBuildError(ERR_ACCESS_DENIED, HTTP_FORBIDDEN, NULL,
+                                           http->getConn()->clientConnection->remote, http->request);
         startError(err);
         return;
     }
@@ -842,7 +899,7 @@ clientReplyContext::purgeRequest()
 
     if (!Config2.onoff.enable_purge) {
         http->logType = LOG_TCP_DENIED;
-        ErrorState *err = clientBuildError(ERR_ACCESS_DENIED, HTTP_FORBIDDEN, NULL, http->getConn()->peer, http->request);
+        ErrorState *err = clientBuildError(ERR_ACCESS_DENIED, HTTP_FORBIDDEN, NULL, http->getConn()->clientConnection->remote, http->request);
         startError(err);
         return;
     }
@@ -946,7 +1003,6 @@ clientReplyContext::traceReply(clientStreamNode * node)
 {
     clientStreamNode *nextNode = (clientStreamNode *)node->node.next->data;
     StoreIOBuffer localTempBuffer;
-    assert(http->request->max_forwards == 0);
     createStoreEntry(http->request->method, request_flags());
     localTempBuffer.offset = nextNode->readBuffer.offset + headers_sz;
     localTempBuffer.length = nextNode->readBuffer.length;
@@ -1000,6 +1056,8 @@ clientReplyContext::checkTransferDone()
 int
 clientReplyContext::storeOKTransferDone() const
 {
+    assert(http->storeEntry()->objectLen() >= 0);
+    assert(http->storeEntry()->objectLen() >= headers_sz);
     if (http->out.offset >= http->storeEntry()->objectLen() - headers_sz) {
         debugs(88,3,HERE << "storeOKTransferDone " <<
                " out.offset=" << http->out.offset <<
@@ -1027,14 +1085,6 @@ clientReplyContext::storeNotOKTransferDone() const
         /* haven't found end of headers yet */
         return 0;
 
-    int sending = SENDING_BODY;
-
-    if (curReply->sline.status == HTTP_NO_CONTENT ||
-            curReply->sline.status == HTTP_NOT_MODIFIED ||
-            curReply->sline.status < HTTP_OK ||
-            http->request->method == METHOD_HEAD)
-        sending = SENDING_HDRSONLY;
-
     /*
      * Figure out how much data we are supposed to send.
      * If we are sending a body and we don't have a content-length,
@@ -1055,7 +1105,6 @@ clientReplyContext::storeNotOKTransferDone() const
     }
 }
 
-
 /* A write has completed, what is the next status based on the
  * canonical request data?
  * 1 something is wrong
@@ -1067,18 +1116,18 @@ clientHttpRequestStatus(int fd, ClientHttpRequest const *http)
 {
 #if SIZEOF_INT64_T == 4
     if (http->out.size > 0x7FFF0000) {
-        debugs(88, 1, "WARNING: closing FD " << fd << " to prevent out.size counter overflow");
-        debugs(88, 1, "\tclient " << http->getConn()->peer);
-        debugs(88, 1, "\treceived " << http->out.size << " bytes");
-        debugs(88, 1, "\tURI " << http->log_uri);
+        debugs(88, DBG_IMPORTANT, "WARNING: closing FD " << fd << " to prevent out.size counter overflow");
+        debugs(88, DBG_IMPORTANT, "\tclient " << http->getConn()->peer);
+        debugs(88, DBG_IMPORTANT, "\treceived " << http->out.size << " bytes");
+        debugs(88, DBG_IMPORTANT, "\tURI " << http->log_uri);
         return 1;
     }
 
     if (http->out.offset > 0x7FFF0000) {
-        debugs(88, 1, "WARNING: closing FD " << fd < " to prevent out.offset counter overflow");
-        debugs(88, 1, "\tclient " << http->getConn()->peer);
-        debugs(88, 1, "\treceived " << http->out.size << " bytes, offset " << http->out.offset);
-        debugs(88, 1, "\tURI " << http->log_uri);
+        debugs(88, DBG_IMPORTANT, "WARNING: closing FD " << fd < " to prevent out.offset counter overflow");
+        debugs(88, DBG_IMPORTANT, "\tclient " << http->getConn()->peer);
+        debugs(88, DBG_IMPORTANT, "\treceived " << http->out.size << " bytes, offset " << http->out.offset);
+        debugs(88, DBG_IMPORTANT, "\tURI " << http->log_uri);
         return 1;
     }
 
@@ -1220,10 +1269,11 @@ clientReplyContext::buildReplyHeader()
 
     if (is_hit)
         hdr->delById(HDR_SET_COOKIE);
+    // TODO: RFC 2965 : Must honour Cache-Control: no-cache="set-cookie2" and remove header.
 
-    // if there is not configured a peer proxy with login=PASS option enabled
+    // if there is not configured a peer proxy with login=PASS or login=PASSTHRU option enabled
     // remove the Proxy-Authenticate header
-    if ( !(request->peer_login && strcmp(request->peer_login,"PASS") ==0))
+    if ( !request->peer_login || (strcmp(request->peer_login,"PASS") != 0 && strcmp(request->peer_login,"PASSTHRU") != 0))
         reply->header.delById(HDR_PROXY_AUTHENTICATE);
 
     reply->header.removeHopByHopEntries();
@@ -1258,6 +1308,25 @@ clientReplyContext::buildReplyHeader()
         if (EBIT_TEST(http->storeEntry()->flags, ENTRY_SPECIAL)) {
             hdr->delById(HDR_DATE);
             hdr->insertTime(HDR_DATE, squid_curtime);
+        } else if (http->getConn() && http->getConn()->port->actAsOrigin) {
+            // Swap the Date: header to current time if we are simulating an origin
+            HttpHeaderEntry *h = hdr->findEntry(HDR_DATE);
+            if (h)
+                hdr->putExt("X-Origin-Date", h->value.termedBuf());
+            hdr->delById(HDR_DATE);
+            hdr->insertTime(HDR_DATE, squid_curtime);
+            h = hdr->findEntry(HDR_EXPIRES);
+            if (h && http->storeEntry()->expires >= 0) {
+                hdr->putExt("X-Origin-Expires", h->value.termedBuf());
+                hdr->delById(HDR_EXPIRES);
+                hdr->insertTime(HDR_EXPIRES, squid_curtime + http->storeEntry()->expires - http->storeEntry()->timestamp);
+            }
+            if (http->storeEntry()->timestamp <= squid_curtime) {
+                // put X-Cache-Age: instead of Age:
+                char age[64];
+                snprintf(age, sizeof(age), "%ld", (long int) squid_curtime - http->storeEntry()->timestamp);
+                hdr->putExt("X-Cache-Age", age);
+            }
         } else if (http->storeEntry()->timestamp <= squid_curtime) {
             hdr->putInt(HDR_AGE,
                         squid_curtime - http->storeEntry()->timestamp);
@@ -1293,8 +1362,9 @@ clientReplyContext::buildReplyHeader()
         else if (http->storeEntry()->timestamp > 0)
             hdr->insertTime(HDR_DATE, http->storeEntry()->timestamp);
         else {
-            debugs(88,1,"WARNING: An error inside Squid has caused an HTTP reply without Date:. Please report this");
-            /* TODO: dump something useful about the problem */
+            debugs(88,DBG_IMPORTANT,"WARNING: An error inside Squid has caused an HTTP reply without Date:. Please report this:");
+            /* dump something useful about the problem */
+            http->storeEntry()->dump(DBG_IMPORTANT);
         }
     }
 
@@ -1306,7 +1376,6 @@ clientReplyContext::buildReplyHeader()
     }
 
     /* Filter unproxyable authentication types */
-
     if (http->logType != LOG_TCP_DENIED &&
             hdr->has(HDR_WWW_AUTHENTICATE)) {
         HttpHeaderPos pos = HttpHeaderInitPos;
@@ -1349,6 +1418,7 @@ clientReplyContext::buildReplyHeader()
             hdr->refreshMask();
     }
 
+#if USE_AUTH
     /* Handle authentication headers */
     if (http->logType == LOG_TCP_DENIED &&
             ( reply->sline.status == HTTP_PROXY_AUTHENTICATION_REQUIRED ||
@@ -1363,6 +1433,7 @@ clientReplyContext::buildReplyHeader()
         authenticateFixHeader(reply, request->auth_user_request, request, 0, 1);
     } else if (request->auth_user_request != NULL)
         authenticateFixHeader(reply, request->auth_user_request, request, http->flags.accel, 0);
+#endif
 
     /* Append X-Cache */
     httpHeaderPutStrf(hdr, HDR_X_CACHE, "%s from %s",
@@ -1377,6 +1448,7 @@ clientReplyContext::buildReplyHeader()
 #endif
 
     const bool maySendChunkedReply = !request->multipartRangeRequest() &&
+                                     reply->sline.protocol == AnyP::PROTO_HTTP && // response is HTTP
                                      (request->http_ver >= HttpVersion(1, 1));
 
     /* Check whether we should send keep-alive */
@@ -1398,6 +1470,10 @@ clientReplyContext::buildReplyHeader()
     } else if (fdUsageHigh()&& !request->flags.must_keepalive) {
         debugs(88, 3, "clientBuildReplyHeader: Not many unused FDs, can't keep-alive");
         request->flags.proxy_keepalive = 0;
+    } else if (request->flags.sslBumped && !reply->persistent()) {
+        // We do not really have to close, but we pretend we are a tunnel.
+        debugs(88, 3, "clientBuildReplyHeader: bumped reply forces close");
+        request->flags.proxy_keepalive = 0;
     }
 
     // Decide if we send chunked reply
@@ -1448,7 +1524,6 @@ clientReplyContext::buildReplyHeader()
     httpHdrMangleList(hdr, request, ROR_REPLY);
 }
 
-
 void
 clientReplyContext::cloneReply()
 {
@@ -1458,7 +1533,7 @@ clientReplyContext::cloneReply()
 
     reply = HTTPMSGLOCK(rep);
 
-    if (reply->sline.protocol == PROTO_HTTP) {
+    if (reply->sline.protocol == AnyP::PROTO_HTTP) {
         /* RFC 2616 requires us to advertise our 1.1 version (but only on real HTTP traffic) */
         reply->sline.version = HttpVersion(1,1);
     }
@@ -1505,15 +1580,11 @@ clientReplyContext::identifyFoundObject(StoreEntry *newEntry)
       */
     if (r->flags.nocache) {
 
-#if USE_DNSSERVERS
-
+#if USE_DNSHELPER
         ipcacheInvalidate(r->GetHost());
-
 #else
-
         ipcacheInvalidateNegative(r->GetHost());
-
-#endif /* USE_DNSSERVERS */
+#endif /* USE_DNSHELPER */
 
     }
 
@@ -1521,15 +1592,11 @@ clientReplyContext::identifyFoundObject(StoreEntry *newEntry)
 
     else if (r->flags.nocache_hack) {
 
-#if USE_DNSSERVERS
-
+#if USE_DNSHELPER
         ipcacheInvalidate(r->GetHost());
-
 #else
-
         ipcacheInvalidateNegative(r->GetHost());
-
-#endif /* USE_DNSSERVERS */
+#endif /* USE_DNSHELPER */
 
     }
 
@@ -1558,9 +1625,9 @@ clientReplyContext::identifyFoundObject(StoreEntry *newEntry)
 
     if (http->redirect.status) {
         /** \li If redirection status is True force this to be a MISS */
-        debugs(85, 3, "clientProcessRequest2: redirectStatus forced StoreEntry to NULL -  MISS");
+        debugs(85, 3, HERE << "REDIRECT status forced StoreEntry to NULL (no body on 3XX responses)");
         http->storeEntry(NULL);
-        http->logType = LOG_TCP_MISS;
+        http->logType = LOG_TCP_REDIRECT;
         doGetMoreData();
         return;
     }
@@ -1615,7 +1682,6 @@ clientGetMoreData(clientStreamNode * aNode, ClientHttpRequest * http)
     assert (context);
     assert(context->http == http);
 
-
     clientStreamNode *next = ( clientStreamNode *)aNode->node.next->data;
 
     if (!context->ourNode)
@@ -1641,7 +1707,7 @@ clientGetMoreData(clientStreamNode * aNode, ClientHttpRequest * http)
     // OPTIONS with Max-Forwards:0 handled in clientProcessRequest()
 
     if (context->http->request->method == METHOD_TRACE) {
-        if (context->http->request->max_forwards == 0) {
+        if (context->http->request->header.getInt64(HDR_MAX_FORWARDS) == 0) {
             context->traceReply(aNode);
             return;
         }
@@ -1677,8 +1743,7 @@ clientReplyContext::doGetMoreData()
         }
 
         sc = storeClientListAdd(http->storeEntry(), this);
-#if DELAY_POOLS
-
+#if USE_DELAY_POOLS
         sc->setDelayId(DelayId::DelayClient(http));
 #endif
 
@@ -1689,11 +1754,11 @@ clientReplyContext::doGetMoreData()
         assert(http->out.offset == 0);
 
         if (Ip::Qos::TheConfig.isHitTosActive()) {
-            Ip::Qos::doTosLocalHit(http->getConn()->clientConn);
+            Ip::Qos::doTosLocalHit(http->getConn()->clientConnection);
         }
 
         if (Ip::Qos::TheConfig.isHitNfmarkActive()) {
-            Ip::Qos::doNfmarkLocalHit(http->getConn()->clientConn);
+            Ip::Qos::doNfmarkLocalHit(http->getConn()->clientConnection);
         }
 
         localTempBuffer.offset = reqofs;
@@ -1794,7 +1859,7 @@ clientReplyContext::sendBodyTooLargeError()
     tmp_noaddr.SetNoAddr(); // TODO: make a global const
     http->logType = LOG_TCP_DENIED_REPLY;
     ErrorState *err = clientBuildError(ERR_TOO_BIG, HTTP_FORBIDDEN, NULL,
-                                       http->getConn() != NULL ? http->getConn()->peer : tmp_noaddr,
+                                       http->getConn() != NULL ? http->getConn()->clientConnection->remote : tmp_noaddr,
                                        http->request);
     removeClientStoreReference(&(sc), http);
     HTTPMSGUNLOCK(reply);
@@ -1802,6 +1867,56 @@ clientReplyContext::sendBodyTooLargeError()
 
 }
 
+/// send 412 (Precondition Failed) to client
+void
+clientReplyContext::sendPreconditionFailedError()
+{
+    http->logType = LOG_TCP_HIT;
+    ErrorState *const err =
+        clientBuildError(ERR_PRECONDITION_FAILED, HTTP_PRECONDITION_FAILED,
+                         NULL, http->getConn()->clientConnection->remote, http->request);
+    removeClientStoreReference(&sc, http);
+    HTTPMSGUNLOCK(reply);
+    startError(err);
+}
+
+/// send 304 (Not Modified) to client
+void
+clientReplyContext::sendNotModified()
+{
+    StoreEntry *e = http->storeEntry();
+    const time_t timestamp = e->timestamp;
+    HttpReply *const temprep = e->getReply()->make304();
+    http->logType = LOG_TCP_IMS_HIT;
+    removeClientStoreReference(&sc, http);
+    createStoreEntry(http->request->method, request_flags());
+    e = http->storeEntry();
+    // Copy timestamp from the original entry so the 304
+    // reply has a meaningful Age: header.
+    e->timestampsSet();
+    e->timestamp = timestamp;
+    e->replaceHttpReply(temprep);
+    e->complete();
+    /*
+     * TODO: why put this in the store and then serialise it and
+     * then parse it again. Simply mark the request complete in
+     * our context and write the reply struct to the client side.
+     */
+    triggerInitialStoreRead();
+}
+
+/// send 304 (Not Modified) or 412 (Precondition Failed) to client
+/// depending on request method
+void
+clientReplyContext::sendNotModifiedOrPreconditionFailedError()
+{
+    if (http->request->method == METHOD_GET ||
+            http->request->method == METHOD_HEAD)
+        sendNotModified();
+    else
+        sendPreconditionFailedError();
+}
+
 void
 clientReplyContext::processReplyAccess ()
 {
@@ -1813,7 +1928,7 @@ clientReplyContext::processReplyAccess ()
             http->logType == LOG_TCP_DENIED_REPLY ||
             alwaysAllowResponse(reply->sline.status)) {
         headers_sz = reply->hdr_sz;
-        processReplyAccessResult(1);
+        processReplyAccessResult(ACCESS_ALLOWED);
         return;
     }
 
@@ -1827,7 +1942,7 @@ clientReplyContext::processReplyAccess ()
 
     /** check for absent access controls (permit by default) */
     if (!Config.accessList.reply) {
-        processReplyAccessResult(1);
+        processReplyAccessResult(ACCESS_ALLOWED);
         return;
     }
 
@@ -1839,22 +1954,20 @@ clientReplyContext::processReplyAccess ()
 }
 
 void
-clientReplyContext::ProcessReplyAccessResult (int rv, void *voidMe)
+clientReplyContext::ProcessReplyAccessResult(allow_t rv, void *voidMe)
 {
     clientReplyContext *me = static_cast<clientReplyContext *>(voidMe);
     me->processReplyAccessResult(rv);
 }
 
 void
-clientReplyContext::processReplyAccessResult(bool accessAllowed)
+clientReplyContext::processReplyAccessResult(const allow_t &accessAllowed)
 {
     debugs(88, 2, "The reply for " << RequestMethodStr(http->request->method)
-           << " " << http->uri << " is "
-           << ( accessAllowed ? "ALLOWED" : "DENIED")
-           << ", because it matched '"
+           << " " << http->uri << " is " << accessAllowed << ", because it matched '"
            << (AclMatchedName ? AclMatchedName : "NO ACL's") << "'" );
 
-    if (!accessAllowed) {
+    if (accessAllowed != ACCESS_ALLOWED) {
         ErrorState *err;
         err_type page_id;
         page_id = aclGetDenyInfoPage(&Config.denyInfoList, AclMatchedName, 1);
@@ -1867,7 +1980,7 @@ clientReplyContext::processReplyAccessResult(bool accessAllowed)
         Ip::Address tmp_noaddr;
         tmp_noaddr.SetNoAddr();
         err = clientBuildError(page_id, HTTP_FORBIDDEN, NULL,
-                               http->getConn() != NULL ? http->getConn()->peer : tmp_noaddr,
+                               http->getConn() != NULL ? http->getConn()->clientConnection->remote : tmp_noaddr,
                                http->request);
 
         removeClientStoreReference(&sc, http);
@@ -1876,7 +1989,6 @@ clientReplyContext::processReplyAccessResult(bool accessAllowed)
 
         startError(err);
 
-
         return;
     }
 
@@ -1957,32 +2069,27 @@ clientReplyContext::sendMoreData (StoreIOBuffer result)
 
     ConnStateData * conn = http->getConn();
 
-    // AYJ: this seems a bit weird to ignore CLOSED but drop on closing.
-    if (conn != NULL && Comm::IsConnOpen(conn->clientConn) && fd_table[conn->clientConn->fd].closing()) {
+    if (conn == NULL || !conn->isOpen()) {
         // too late, our conn is closing
         // TODO: should we also quit?
-        debugs(33,3, HERE << "not sending more data to a closing " << conn->clientConn);
+        debugs(33,3, HERE << "not sending more data to a closing " << conn->clientConnection);
         return;
     }
 
     char *buf = next()->readBuffer.data;
 
-    char *body_buf = buf;
-
     if (buf != result.data) {
         /* we've got to copy some data */
         assert(result.length <= next()->readBuffer.length);
-        xmemcpy(buf, result.data, result.length);
-        body_buf = buf;
+        memcpy(buf, result.data, result.length);
     }
 
-    if (reqofs==0 && !logTypeIsATcpHit(http->logType)) {
-        assert(conn != NULL && Comm::IsConnOpen(conn->clientConn)); // the beginning of this method implies FD may be closed.
+    if (reqofs==0 && !logTypeIsATcpHit(http->logType) && Comm::IsConnOpen(conn->clientConnection)) {
         if (Ip::Qos::TheConfig.isHitTosActive()) {
-            Ip::Qos::doTosLocalMiss(conn->clientConn, http->request->hier.code);
+            Ip::Qos::doTosLocalMiss(conn->clientConnection, http->request->hier.code);
         }
         if (Ip::Qos::TheConfig.isHitNfmarkActive()) {
-            Ip::Qos::doNfmarkLocalMiss(conn->clientConn, http->request->hier.code);
+            Ip::Qos::doNfmarkLocalMiss(conn->clientConnection, http->request->hier.code);
         }
     }
 
@@ -2005,7 +2112,7 @@ clientReplyContext::sendMoreData (StoreIOBuffer result)
            reqofs << " bytes (" << result.length <<
            " new bytes)");
     debugs(88, 5, "clientReplyContext::sendMoreData:"
-           << conn->clientConn <<
+           << conn->clientConnection <<
            " '" << entry->url() << "'" <<
            " out.offset=" << http->out.offset);
 
@@ -2030,9 +2137,9 @@ clientReplyContext::sendMoreData (StoreIOBuffer result)
         size_t k;
 
         if ((k = headersEnd(buf, reqofs))) {
-            safe_free(http->al.headers.reply);
-            http->al.headers.reply = (char *)xcalloc(k + 1, 1);
-            xstrncpy(http->al.headers.reply, buf, k);
+            safe_free(http->al->headers.reply);
+            http->al->headers.reply = (char *)xcalloc(k + 1, 1);
+            xstrncpy(http->al->headers.reply, buf, k);
         }
     }
 
@@ -2053,16 +2160,14 @@ clientReplyContext::createStoreEntry(const HttpRequestMethod& m, request_flags r
      */
 
     if (http->request == NULL)
-        http->request = HTTPMSGLOCK(new HttpRequest(m, PROTO_NONE, null_string));
+        http->request = HTTPMSGLOCK(new HttpRequest(m, AnyP::PROTO_NONE, null_string));
 
     StoreEntry *e = storeCreateEntry(http->uri, http->log_uri, reqFlags, m);
 
     sc = storeClientListAdd(e, this);
 
-#if DELAY_POOLS
-
+#if USE_DELAY_POOLS
     sc->setDelayId(DelayId::DelayClient(http));
-
 #endif
 
     reqofs = 0;
@@ -2091,7 +2196,7 @@ ErrorState *
 clientBuildError(err_type page_id, http_status status, char const *url,
                  Ip::Address &src_addr, HttpRequest * request)
 {
-    ErrorState *err = errorCon(page_id, status, request);
+    ErrorState *err = new ErrorState(page_id, status, request);
     err->src_addr = src_addr;
 
     if (url)