]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/HttpReply.cc
Merged from trunk.
[thirdparty/squid.git] / src / HttpReply.cc
index 2e1629c5c91f5f42566e9b404914d40f6428196d..bfbd0b06fe709602d2cb7e6b7efdd5fdd851e3a5 100644 (file)
@@ -1,6 +1,6 @@
 
 /*
- * $Id: HttpReply.cc,v 1.56 2003/03/06 11:51:55 robertc Exp $
+ * $Id: HttpReply.cc,v 1.100 2008/02/08 18:27:59 rousskov Exp $
  *
  * DEBUG: section 58    HTTP Reply (Response)
  * AUTHOR: Alex Rousskov
  *
  */
 
-#include "HttpReply.h"
 #include "squid.h"
+#include "SquidTime.h"
 #include "Store.h"
-#include "HttpHeader.h"
+#include "HttpReply.h"
 #include "HttpHdrContRange.h"
+#include "HttpHdrSc.h"
 #include "ACLChecklist.h"
+#include "HttpRequest.h"
+#include "MemBuf.h"
 
 /* local constants */
 
-/* these entity-headers must be ignored if a bogus server sends them in 304 */
+/* If we receive a 304 from the origin during a cache revalidation, we must
+ * update the headers of the existing entry. Specifically, we need to update all
+ * end-to-end headers and not any hop-by-hop headers (rfc2616 13.5.3).
+ *
+ * This is not the whole story though: since it is possible for a faulty/malicious
+ * origin server to set headers it should not in a 304, we must explicitly ignore
+ * these too. Specifically all entity-headers except those permitted in a 304
+ * (rfc2616 10.3.5) must be ignored.
+ * 
+ * The list of headers we don't update is made up of:
+ *     all hop-by-hop headers
+ *     all entity-headers except Expires and Content-Location
+ */
 static HttpHeaderMask Denied304HeadersMask;
 static http_hdr_type Denied304HeadersArr[] =
     {
+        // hop-by-hop headers
+        HDR_CONNECTION, HDR_KEEP_ALIVE, HDR_PROXY_AUTHENTICATE, HDR_PROXY_AUTHORIZATION,
+        HDR_TE, HDR_TRAILERS, HDR_TRANSFER_ENCODING, HDR_UPGRADE,
+        // entity headers
         HDR_ALLOW, HDR_CONTENT_ENCODING, HDR_CONTENT_LANGUAGE, HDR_CONTENT_LENGTH,
-        HDR_CONTENT_LOCATION, HDR_CONTENT_RANGE, HDR_LAST_MODIFIED, HDR_LINK,
-        HDR_OTHER
+        HDR_CONTENT_MD5, HDR_CONTENT_RANGE, HDR_CONTENT_TYPE, HDR_LAST_MODIFIED
     };
 
-HttpMsgParseState &operator++ (HttpMsgParseState &aState)
-{
-    aState = (HttpMsgParseState)(++(int)aState);
-    return aState;
-}
-
-
-/* local routines */
-static void httpReplyInit(HttpReply * rep);
-static void httpReplyClean(HttpReply * rep);
-static void httpReplyDoDestroy(HttpReply * rep);
-static void httpReplyHdrCacheInit(HttpReply * rep);
-static void httpReplyHdrCacheClean(HttpReply * rep);
-static int httpReplyParseStep(HttpReply * rep, const char *parse_start, int atEnd);
-static int httpReplyParseError(HttpReply * rep);
-static int httpReplyIsolateStart(const char **parse_start, const char **blk_start, const char **blk_end);
-static time_t httpReplyHdrExpirationTime(const HttpReply * rep);
-
-
 /* module initialization */
 void
 httpReplyInitModule(void)
 {
+    assert(HTTP_STATUS_NONE == 0); // HttpReply::parse() interface assumes that
     httpHeaderMaskInit(&Denied304HeadersMask, 0);
-    httpHeaderCalcMask(&Denied304HeadersMask, (const int *) Denied304HeadersArr, countof(Denied304HeadersArr));
+    httpHeaderCalcMask(&Denied304HeadersMask, Denied304HeadersArr, countof(Denied304HeadersArr));
 }
 
-
-HttpReply *
-httpReplyCreate(void)
+HttpReply::HttpReply() : HttpMsg(hoReply), date (0), last_modified (0), 
+       expires (0), surrogate_control (NULL), content_range (NULL), keep_alive (0), 
+       protoPrefix("HTTP/"), bodySizeMax(-2)
 {
-    HttpReply *rep = new HttpReply;
-    debug(58, 7) ("creating rep: %p\n", rep);
-    httpReplyInit(rep);
-    return rep;
+    init();
 }
 
-static void
-httpReplyInit(HttpReply * rep)
+HttpReply::~HttpReply()
 {
-    assert(rep);
-    rep->hdr_sz = 0;
-    rep->maxBodySize = 0;
-    rep->pstate = psReadyToParseStartLine;
-    httpBodyInit(&rep->body);
-    httpHeaderInit(&rep->header, hoReply);
-    httpReplyHdrCacheInit(rep);
-    httpStatusLineInit(&rep->sline);
-}
-
-static void
-httpReplyClean(HttpReply * rep)
-{
-    assert(rep);
-    httpBodyClean(&rep->body);
-    httpReplyHdrCacheClean(rep);
-    httpHeaderClean(&rep->header);
-    httpStatusLineClean(&rep->sline);
+    if (do_clean)
+        clean();
 }
 
 void
-httpReplyDestroy(HttpReply * rep)
+HttpReply::init()
 {
-    assert(rep);
-    debug(58, 7) ("destroying rep: %p\n", rep);
-    httpReplyClean(rep);
-    httpReplyDoDestroy(rep);
+    httpBodyInit(&body);
+    hdrCacheInit();
+    httpStatusLineInit(&sline);
+    pstate = psReadyToParseStartLine;
+    do_clean = true;
 }
 
-void
-httpReplyReset(HttpReply * rep)
+void HttpReply::reset()
 {
-    httpReplyClean(rep);
-    httpReplyInit(rep);
-}
 
-/* absorb: copy the contents of a new reply to the old one, destroy new one */
-void
-httpReplyAbsorb(HttpReply * rep, HttpReply * new_rep)
-{
-    assert(rep && new_rep);
-    httpReplyClean(rep);
-    *rep = *new_rep;
-    new_rep->header.entries.clean();
-    /* cannot use Clean() on new reply now! */
-    httpReplyDoDestroy(new_rep);
+    // reset should not reset the protocol; could have made protoPrefix a
+    // virtual function instead, but it is not clear whether virtual methods
+    // are allowed with MEMPROXY_CLASS() and whether some cbdata void*
+    // conversions are not going to kill virtual tables
+    const String pfx = protoPrefix;
+    clean();
+    init();
+    protoPrefix = pfx;
 }
 
-/*
- * httpReplyParse takes character buffer of HTTP headers (buf),
- * which may not be NULL-terminated, and fills in an HttpReply
- * structure (rep).  The parameter 'end' specifies the offset to
- * the end of the reply headers.  The caller may know where the
- * end is, but is unable to NULL-terminate the buffer.  This function
- * returns true on success.
- */
-int
-httpReplyParse(HttpReply * rep, const char *buf, ssize_t end)
+void
+HttpReply::clean()
 {
-    /*
-     * this extra buffer/copy will be eliminated when headers become
-     * meta-data in store. Currently we have to xstrncpy the buffer
-     * becuase somebody may feed a non NULL-terminated buffer to
-     * us.
-     */
-    char *headers = (char *)memAllocate(MEM_4K_BUF);
-    int success;
-    size_t s = XMIN(end + 1, 4096);
-    /* reset current state, because we are not used in incremental fashion */
-    httpReplyReset(rep);
-    /* put a string terminator.  s is how many bytes to touch in
-     * 'buf' including the terminating NULL. */
-    xstrncpy(headers, buf, s);
-    success = httpReplyParseStep(rep, headers, 0);
-    memFree(headers, MEM_4K_BUF);
-    return success == 1;
+    // we used to assert that the pipe is NULL, but now the message only 
+    // points to a pipe that is owned and initiated by another object.
+    body_pipe = NULL;
+
+    httpBodyClean(&body);
+    hdrCacheClean();
+    header.clean();
+    httpStatusLineClean(&sline);
+    bodySizeMax = -2; // hack: make calculatedBodySizeMax() false
 }
 
 void
-httpReplyPackHeadersInto(const HttpReply * rep, Packer * p)
+HttpReply::packHeadersInto(Packer * p) const
 {
-    assert(rep);
-    httpStatusLinePackInto(&rep->sline, p);
-    httpHeaderPackInto(&rep->header, p);
+    httpStatusLinePackInto(&sline, p);
+    header.packInto(p);
     packerAppend(p, "\r\n", 2);
 }
 
 void
-httpReplyPackInto(const HttpReply * rep, Packer * p)
+HttpReply::packInto(Packer * p)
 {
-    httpReplyPackHeadersInto(rep, p);
-    httpBodyPackInto(&rep->body, p);
+    packHeadersInto(p);
+    httpBodyPackInto(&body, p);
 }
 
-/* create memBuf, create mem-based packer,  pack, destroy packer, return MemBuf */
-MemBuf
-httpReplyPack(const HttpReply * rep)
+/* create memBuf, create mem-based packer, pack, destroy packer, return MemBuf */
+MemBuf *
+HttpReply::pack()
 {
-    MemBuf mb;
+    MemBuf *mb = new MemBuf;
     Packer p;
-    assert(rep);
 
-    memBufDefInit(&mb);
-    packerToMemInit(&p, &mb);
-    httpReplyPackInto(rep, &p);
+    mb->init();
+    packerToMemInit(&p, mb);
+    packInto(&p);
     packerClean(&p);
     return mb;
 }
 
-/* swap: create swap-based packer, pack, destroy packer
- * This eats the reply.
- */
-void
-httpReplySwapOut(HttpReply * rep, StoreEntry * e)
+MemBuf *
+httpPackedReply(HttpVersion ver, http_status status, const char *ctype,
+                int64_t clen, time_t lmt, time_t expires)
 {
-    assert(rep && e);
-
-    storeEntryReplaceObject(e, rep);
-}
-
-MemBuf
-httpPackedReply(http_version_t ver, http_status status, const char *ctype,
-                int clen, time_t lmt, time_t expires)
-{
-    HttpReply *rep = httpReplyCreate();
-    MemBuf mb;
-    httpReplySetHeaders(rep, ver, status, ctype, NULL, clen, lmt, expires);
-    mb = httpReplyPack(rep);
-    httpReplyDestroy(rep);
+    HttpReply *rep = new HttpReply;
+    rep->setHeaders(ver, status, ctype, NULL, clen, lmt, expires);
+    MemBuf *mb = rep->pack();
+    delete rep;
     return mb;
 }
 
 HttpReply *
-httpReplyMake304 (const HttpReply * rep)
+HttpReply::make304 () const
 {
     static const http_hdr_type ImsEntries[] = {HDR_DATE, HDR_CONTENT_TYPE, HDR_EXPIRES, HDR_LAST_MODIFIED, /* eof */ HDR_OTHER};
 
-    HttpReply *rv;
+    HttpReply *rv = new HttpReply;
     int t;
     HttpHeaderEntry *e;
-    http_version_t ver;
-    assert(rep);
 
-    rv = httpReplyCreate ();
     /* rv->content_length; */
-    rv->date = rep->date;
-    rv->last_modified = rep->last_modified;
-    rv->expires = rep->expires;
-    rv->content_type = rep->content_type;
+    rv->date = date;
+    rv->last_modified = last_modified;
+    rv->expires = expires;
+    rv->content_type = content_type;
     /* rv->cache_control */
     /* rv->content_range */
     /* rv->keep_alive */
-    httpBuildVersion(&ver, 1, 0);
+    HttpVersion ver(1,0);
     httpStatusLineSet(&rv->sline, ver,
                       HTTP_NOT_MODIFIED, "");
 
     for (t = 0; ImsEntries[t] != HDR_OTHER; ++t)
-        if ((e = httpHeaderFindEntry(&rep->header, ImsEntries[t])))
-            httpHeaderAddEntry(&rv->header, httpHeaderEntryClone(e));
+        if ((e = header.findEntry(ImsEntries[t])))
+            rv->header.addEntry(e->clone());
 
     /* rv->body */
     return rv;
 }
 
-MemBuf
-httpPacked304Reply(const HttpReply * rep)
+MemBuf *
+HttpReply::packed304Reply()
 {
     /* Not as efficient as skipping the header duplication,
      * but easier to maintain
      */
-    HttpReply *temp;
-    MemBuf rv;
-    assert (rep);
-    temp = httpReplyMake304 (rep);
-    rv = httpReplyPack(temp);
-    httpReplyDestroy (temp);
+    HttpReply *temp = make304 ();
+    MemBuf *rv = temp->pack();
+    delete temp;
     return rv;
 }
 
 void
-httpReplySetHeaders(HttpReply * reply, http_version_t ver, http_status status, const char *reason,
-                    const char *ctype, int clen, time_t lmt, time_t expires)
+HttpReply::setHeaders(HttpVersion ver, http_status status, const char *reason,
+                      const char *ctype, int64_t clen, time_t lmt, time_t expires)
 {
     HttpHeader *hdr;
-    assert(reply);
-    httpStatusLineSet(&reply->sline, ver, status, reason);
-    hdr = &reply->header;
-    httpHeaderPutStr(hdr, HDR_SERVER, full_appname_string);
-    httpHeaderPutStr(hdr, HDR_MIME_VERSION, "1.0");
-    httpHeaderPutTime(hdr, HDR_DATE, squid_curtime);
+    httpStatusLineSet(&sline, ver, status, reason);
+    hdr = &header;
+    hdr->putStr(HDR_SERVER, visible_appname_string);
+    hdr->putStr(HDR_MIME_VERSION, "1.0");
+    hdr->putTime(HDR_DATE, squid_curtime);
 
     if (ctype) {
-        httpHeaderPutStr(hdr, HDR_CONTENT_TYPE, ctype);
-        reply->content_type = ctype;
+        hdr->putStr(HDR_CONTENT_TYPE, ctype);
+        content_type = ctype;
     } else
-        reply->content_type = String();
+        content_type = String();
 
     if (clen >= 0)
-        httpHeaderPutInt(hdr, HDR_CONTENT_LENGTH, clen);
+        hdr->putInt64(HDR_CONTENT_LENGTH, clen);
 
     if (expires >= 0)
-        httpHeaderPutTime(hdr, HDR_EXPIRES, expires);
+        hdr->putTime(HDR_EXPIRES, expires);
 
     if (lmt > 0)               /* this used to be lmt != 0 @?@ */
-        httpHeaderPutTime(hdr, HDR_LAST_MODIFIED, lmt);
+        hdr->putTime(HDR_LAST_MODIFIED, lmt);
 
-    reply->date = squid_curtime;
+    date = squid_curtime;
 
-    reply->content_length = clen;
+    content_length = clen;
 
-    reply->expires = expires;
+    expires = expires;
 
-    reply->last_modified = lmt;
+    last_modified = lmt;
 }
 
 void
-httpRedirectReply(HttpReply * reply, http_status status, const char *loc)
+HttpReply::redirect(http_status status, const char *loc)
 {
     HttpHeader *hdr;
-    http_version_t ver;
-    assert(reply);
-    httpBuildVersion(&ver, 1, 0);
-    httpStatusLineSet(&reply->sline, ver, status, httpStatusString(status));
-    hdr = &reply->header;
-    httpHeaderPutStr(hdr, HDR_SERVER, full_appname_string);
-    httpHeaderPutTime(hdr, HDR_DATE, squid_curtime);
-    httpHeaderPutInt(hdr, HDR_CONTENT_LENGTH, 0);
-    httpHeaderPutStr(hdr, HDR_LOCATION, loc);
-    reply->date = squid_curtime;
-    reply->content_length = 0;
+    HttpVersion ver(1,0);
+    httpStatusLineSet(&sline, ver, status, httpStatusString(status));
+    hdr = &header;
+    hdr->putStr(HDR_SERVER, APP_FULLNAME);
+    hdr->putTime(HDR_DATE, squid_curtime);
+    hdr->putInt64(HDR_CONTENT_LENGTH, 0);
+    hdr->putStr(HDR_LOCATION, loc);
+    date = squid_curtime;
+    content_length = 0;
 }
 
 /* compare the validators of two replies.
@@ -328,23 +264,23 @@ httpRedirectReply(HttpReply * reply, http_status status, const char *loc)
  * 0 = they do not match
  */
 int
-httpReplyValidatorsMatch(HttpReply const * rep, HttpReply const * otherRep)
+HttpReply::validatorsMatch(HttpReply const * otherRep) const
 {
     String one,two;
-    assert (rep && otherRep);
+    assert (otherRep);
     /* Numbers first - easiest to check */
     /* Content-Length */
     /* TODO: remove -1 bypass */
 
-    if (rep->content_length != otherRep->content_length
-            && rep->content_length > -1 &&
+    if (content_length != otherRep->content_length
+            && content_length > -1 &&
             otherRep->content_length > -1)
         return 0;
 
     /* ETag */
-    one = httpHeaderGetStrOrList(&rep->header, HDR_ETAG);
+    one = header.getStrOrList(HDR_ETAG);
 
-    two = httpHeaderGetStrOrList(&otherRep->header, HDR_ETAG);
+    two = otherRep->header.getStrOrList(HDR_ETAG);
 
     if (!one.buf() || !two.buf() || strcasecmp (one.buf(), two.buf())) {
         one.clean();
@@ -352,15 +288,15 @@ httpReplyValidatorsMatch(HttpReply const * rep, HttpReply const * otherRep)
         return 0;
     }
 
-    if (rep->last_modified != otherRep->last_modified)
+    if (last_modified != otherRep->last_modified)
         return 0;
 
     /* MD5 */
-    one = httpHeaderGetStrOrList(&rep->header, HDR_CONTENT_MD5);
+    one = header.getStrOrList(HDR_CONTENT_MD5);
 
-    two = httpHeaderGetStrOrList(&otherRep->header, HDR_CONTENT_MD5);
+    two = otherRep->header.getStrOrList(HDR_CONTENT_MD5);
 
-    if (strcasecmp (one.buf(), two.buf())) {
+    if (!one.buf() || !two.buf() || strcasecmp (one.buf(), two.buf())) {
         one.clean();
         two.clean();
         return 0;
@@ -369,69 +305,61 @@ httpReplyValidatorsMatch(HttpReply const * rep, HttpReply const * otherRep)
     return 1;
 }
 
-
 void
-httpReplyUpdateOnNotModified(HttpReply * rep, HttpReply const * freshRep)
+HttpReply::updateOnNotModified(HttpReply const * freshRep)
 {
-    assert(rep && freshRep);
-    /* Can not update modified headers that don't match! */
-    assert (httpReplyValidatorsMatch(rep, freshRep));
+    assert(freshRep);
+
     /* clean cache */
-    httpReplyHdrCacheClean(rep);
+    hdrCacheClean();
     /* update raw headers */
-    httpHeaderUpdate(&rep->header, &freshRep->header,
-                     (const HttpHeaderMask *) &Denied304HeadersMask);
+    header.update(&freshRep->header,
+                  (const HttpHeaderMask *) &Denied304HeadersMask);
+
+    header.compact();
     /* init cache */
-    httpReplyHdrCacheInit(rep);
+    hdrCacheInit();
 }
 
-
 /* internal routines */
 
-/* internal function used by Destroy and Absorb */
-static void
-httpReplyDoDestroy(HttpReply * rep)
-{
-    delete rep;
-}
-
-static time_t
-httpReplyHdrExpirationTime(const HttpReply * rep)
+time_t
+HttpReply::hdrExpirationTime()
 {
     /* The s-maxage and max-age directive takes priority over Expires */
 
-    if (rep->cache_control) {
-        if (rep->date >= 0) {
-            if (rep->cache_control->s_maxage >= 0)
-                return rep->date + rep->cache_control->s_maxage;
+    if (cache_control) {
+        if (date >= 0) {
+            if (cache_control->s_maxage >= 0)
+                return date + cache_control->s_maxage;
 
-            if (rep->cache_control->max_age >= 0)
-                return rep->date + rep->cache_control->max_age;
+            if (cache_control->max_age >= 0)
+                return date + cache_control->max_age;
         } else {
             /*
              * Conservatively handle the case when we have a max-age
              * header, but no Date for reference?
              */
 
-            if (rep->cache_control->s_maxage >= 0)
+            if (cache_control->s_maxage >= 0)
                 return squid_curtime;
 
-            if (rep->cache_control->max_age >= 0)
+            if (cache_control->max_age >= 0)
                 return squid_curtime;
         }
     }
 
     if (Config.onoff.vary_ignore_expire &&
-            httpHeaderHas(&rep->header, HDR_VARY)) {
-        const time_t d = httpHeaderGetTime(&rep->header, HDR_DATE);
-        const time_t e = httpHeaderGetTime(&rep->header, HDR_EXPIRES);
+            header.has(HDR_VARY)) {
+        const time_t d = header.getTime(HDR_DATE);
+        const time_t e = header.getTime(HDR_EXPIRES);
 
         if (d == e)
             return -1;
     }
 
-    if (httpHeaderHas(&rep->header, HDR_EXPIRES)) {
-        const time_t e = httpHeaderGetTime(&rep->header, HDR_EXPIRES);
+    if (header.has(HDR_EXPIRES)) {
+        const time_t e = header.getTime(HDR_EXPIRES);
         /*
          * HTTP/1.0 says that robust implementations should consider
          * bad or malformed Expires header as equivalent to "expires
@@ -444,199 +372,199 @@ httpReplyHdrExpirationTime(const HttpReply * rep)
 }
 
 /* sync this routine when you update HttpReply struct */
-static void
-httpReplyHdrCacheInit(HttpReply * rep)
+void
+HttpReply::hdrCacheInit()
 {
-    const HttpHeader *hdr = &rep->header;
-    const char *str;
-    rep->content_length = httpHeaderGetInt(hdr, HDR_CONTENT_LENGTH);
-    rep->date = httpHeaderGetTime(hdr, HDR_DATE);
-    rep->last_modified = httpHeaderGetTime(hdr, HDR_LAST_MODIFIED);
-    str = httpHeaderGetStr(hdr, HDR_CONTENT_TYPE);
+    HttpMsg::hdrCacheInit();
+
+    content_length = header.getInt64(HDR_CONTENT_LENGTH);
+    date = header.getTime(HDR_DATE);
+    last_modified = header.getTime(HDR_LAST_MODIFIED);
+    surrogate_control = header.getSc();
+    content_range = header.getContRange();
+    keep_alive = httpMsgIsPersistent(sline.version, &header);
+    const char *str = header.getStr(HDR_CONTENT_TYPE);
 
     if (str)
-        rep->content_type.limitInit(str, strcspn(str, ";\t "));
+        content_type.limitInit(str, strcspn(str, ";\t "));
     else
-        rep->content_type = String();
-
-    rep->cache_control = httpHeaderGetCc(hdr);
-
-    rep->content_range = httpHeaderGetContRange(hdr);
-
-    rep->keep_alive = httpMsgIsPersistent(rep->sline.version, &rep->header);
+        content_type = String();
 
     /* be sure to set expires after date and cache-control */
-    rep->expires = httpReplyHdrExpirationTime(rep);
+    expires = hdrExpirationTime();
 }
 
 /* sync this routine when you update HttpReply struct */
-static void
-httpReplyHdrCacheClean(HttpReply * rep)
+void
+HttpReply::hdrCacheClean()
 {
-    rep->content_type.clean();
+    content_type.clean();
+
+    if (cache_control) {
+        httpHdrCcDestroy(cache_control);
+        cache_control = NULL;
+    }
 
-    if (rep->cache_control)
-        httpHdrCcDestroy(rep->cache_control);
+    if (surrogate_control) {
+        httpHdrScDestroy(surrogate_control);
+        surrogate_control = NULL;
+    }
 
-    if (rep->content_range)
-        httpHdrContRangeDestroy(rep->content_range);
+    if (content_range) {
+        httpHdrContRangeDestroy(content_range);
+        content_range = NULL;
+    }
 }
 
 /*
- * parses a 0-terminating buffer into HttpReply. 
- * Returns:
- *      1 -- success 
- *       0 -- need more data (partial parse)
- *      -1 -- parse error
+ * Returns the body size of a HTTP response
  */
-static int
-httpReplyParseStep(HttpReply * rep, const char *buf, int atEnd)
+int64_t
+HttpReply::bodySize(const HttpRequestMethod& method) const
 {
-    const char *parse_start = buf;
-    const char *blk_start, *blk_end;
-    const char **parse_end_ptr = &blk_end;
-    assert(rep);
-    assert(parse_start);
-    assert(rep->pstate < psParsed);
-
-    *parse_end_ptr = parse_start;
-
-    if (rep->pstate == psReadyToParseStartLine) {
-        if (!httpReplyIsolateStart(&parse_start, &blk_start, &blk_end))
-            return 0;
-
-        if (!httpStatusLineParse(&rep->sline, blk_start, blk_end))
-            return httpReplyParseError(rep);
-
-        *parse_end_ptr = parse_start;
-
-        rep->hdr_sz = *parse_end_ptr - buf;
-
-        ++rep->pstate;
-    }
-
-    if (rep->pstate == psReadyToParseHeaders) {
-        if (!httpMsgIsolateHeaders(&parse_start, &blk_start, &blk_end)) {
-            if (atEnd)
-                blk_start = parse_start, blk_end = blk_start + strlen(blk_start);
-            else
-                return 0;
-        }
-
-        if (!httpHeaderParse(&rep->header, blk_start, blk_end))
-            return httpReplyParseError(rep);
-
-        httpReplyHdrCacheInit(rep);
-
-        *parse_end_ptr = parse_start;
+    if (sline.version.major < 1)
+        return -1;
+    else if (method.id() == METHOD_HEAD)
+        return 0;
+    else if (sline.status == HTTP_OK)
+        (void) 0;              /* common case, continue */
+    else if (sline.status == HTTP_NO_CONTENT)
+        return 0;
+    else if (sline.status == HTTP_NOT_MODIFIED)
+        return 0;
+    else if (sline.status < HTTP_OK)
+        return 0;
 
-        rep->hdr_sz = *parse_end_ptr - buf;
+    return content_length;
+}
 
-        ++rep->pstate;
+bool HttpReply::sanityCheckStartLine(MemBuf *buf, http_status *error)
+{
+    if (buf->contentSize() >= protoPrefix.size() && protoPrefix.cmp(buf->content(), protoPrefix.size()) != 0) {
+        debugs(58, 3, "HttpReply::sanityCheckStartLine: missing protocol prefix (" << protoPrefix.buf() << ") in '" << buf->content() << "'");
+        *error = HTTP_INVALID_HEADER;
+        return false;
     }
 
-    return 1;
+    return true;
 }
 
-/* handy: resets and returns -1 */
-static int
-httpReplyParseError(HttpReply * rep)
+void HttpReply::packFirstLineInto(Packer *p, bool unused) const
 {
-    assert(rep);
-    /* reset */
-    httpReplyReset(rep);
-    /* indicate an error */
-    rep->sline.status = HTTP_INVALID_HEADER;
-    return -1;
+    httpStatusLinePackInto(&sline, p);
 }
 
-/* find first CRLF */
-static int
-httpReplyIsolateStart(const char **parse_start, const char **blk_start, const char **blk_end)
+bool HttpReply::parseFirstLine(const char *blk_start, const char *blk_end)
 {
-    int slen = strcspn(*parse_start, "\r\n");
-
-    if (!(*parse_start)[slen]) /* no CRLF found */
-        return 0;
-
-    *blk_start = *parse_start;
-
-    *blk_end = *blk_start + slen;
-
-    while (**blk_end == '\r')  /* CR */
-        (*blk_end)++;
-
-    if (**blk_end == '\n')     /* LF */
-        (*blk_end)++;
-
-    *parse_start = *blk_end;
-
-    return 1;
+    return httpStatusLineParse(&sline, protoPrefix, blk_start, blk_end);
 }
 
-/*
- * Returns the body size of a HTTP response
- */
+/* handy: resets and returns -1 */
 int
-httpReplyBodySize(method_t method, HttpReply const * reply)
+HttpReply::httpMsgParseError()
 {
-    if (METHOD_HEAD == method)
-        return 0;
-    else if (reply->sline.status == HTTP_OK)
-        (void) 0;              /* common case, continue */
-    else if (reply->sline.status == HTTP_NO_CONTENT)
-        return 0;
-    else if (reply->sline.status == HTTP_NOT_MODIFIED)
-        return 0;
-    else if (reply->sline.status < HTTP_OK)
-        return 0;
-
-    return reply->content_length;
+    int result(HttpMsg::httpMsgParseError());
+    /* indicate an error in the status line */
+    sline.status = HTTP_INVALID_HEADER;
+    return result;
 }
 
 /*
- * Calculates the maximum size allowed for an HTTP response
+ * Indicate whether or not we would usually expect an entity-body
+ * along with this response
  */
-void
-httpReplyBodyBuildSize(request_t * request, HttpReply * reply, dlink_list * bodylist)
+bool
+HttpReply::expectingBody(const HttpRequestMethod& req_method, int64_t& theSize) const
 {
-    body_size *bs;
-    ACLChecklist *checklist;
-    bs = (body_size *) bodylist->head;
-
-    while (bs) {
-        checklist = aclChecklistCreate(bs->access_list, request, NULL);
-        checklist->reply = reply;
+    bool expectBody = true;
+
+    if (req_method == METHOD_HEAD)
+        expectBody = false;
+    else if (sline.status == HTTP_NO_CONTENT)
+        expectBody = false;
+    else if (sline.status == HTTP_NOT_MODIFIED)
+        expectBody = false;
+    else if (sline.status < HTTP_OK)
+        expectBody = false;
+    else
+        expectBody = true;
+
+    if (expectBody) {
+        if (header.hasListMember(HDR_TRANSFER_ENCODING, "chunked", ','))
+            theSize = -1;
+        else if (content_length >= 0)
+            theSize = content_length;
+        else
+            theSize = -1;
+    }
 
-        if (1 != aclCheckFast(bs->access_list, checklist)) {
-            /* deny - skip this entry */
-            bs = (body_size *) bs->node.next;
-        } else {
-            /* Allow - use this entry */
-            reply->maxBodySize = bs->maxsize;
-            bs = NULL;
-            debug(58, 3) ("httpReplyBodyBuildSize: Setting maxBodySize to %ld\n", (long int) reply->maxBodySize);
-        }
+    return expectBody;
+}
 
-        delete checklist;
-    }
+bool
+HttpReply::receivedBodyTooLarge(HttpRequest& request, int64_t receivedSize)
+{
+    calcMaxBodySize(request);
+    debugs(58, 3, HERE << receivedSize << " >? " << bodySizeMax);
+    return bodySizeMax >= 0 && receivedSize > bodySizeMax;
 }
 
-MemPool *HttpReply::Pool(NULL);
-void *
-HttpReply::operator new (size_t byteCount)
+bool
+HttpReply::expectedBodyTooLarge(HttpRequest& request)
 {
-    /* derived classes with different sizes must implement their own new */
-    assert (byteCount == sizeof (HttpReply));
+    calcMaxBodySize(request);
+    debugs(58, 7, HERE << "bodySizeMax=" << bodySizeMax);
+
+    if (bodySizeMax < 0) // no body size limit
+        return false;
+
+    int64_t expectedSize = -1;
+    if (!expectingBody(request.method, expectedSize))
+        return false;
+    
+    debugs(58, 6, HERE << expectedSize << " >? " << bodySizeMax);
 
-    if (!Pool)
-        Pool = memPoolCreate("HttpReply", sizeof (HttpReply));
+    if (expectedSize < 0) // expecting body of an unknown length
+        return false;
 
-    return memPoolAlloc(Pool);
+    return expectedSize > bodySizeMax;
 }
 
 void
-HttpReply::operator delete (void *address)
+HttpReply::calcMaxBodySize(HttpRequest& request)
 {
-    memPoolFree (Pool, address);
+    // hack: -2 is used as "we have not calculated max body size yet" state
+    if (bodySizeMax != -2) // already tried
+        return;
+    bodySizeMax = -1;
+
+    ACLChecklist ch;
+    ch.src_addr = request.client_addr;
+    ch.my_addr = request.my_addr;
+    ch.reply = HTTPMSGLOCK(this); // XXX: this lock makes method non-const
+    ch.request = HTTPMSGLOCK(&request);
+    for (acl_size_t *l = Config.ReplyBodySize; l; l = l -> next) {
+        if (ch.matchAclListFast(l->aclList)) {
+            debugs(58, 4, HERE << "bodySizeMax=" << bodySizeMax);
+            bodySizeMax = l->size; // may be -1
+            break;
+        }
+    }
+}
+
+// XXX: check that this is sufficient for eCAP cloning
+HttpReply *
+HttpReply::clone() const
+{
+    HttpReply *rep = new HttpReply();
+    rep->header.append(&header);
+    rep->hdrCacheInit();
+    rep->hdr_sz = hdr_sz;
+    rep->http_ver = http_ver;
+    rep->pstate = pstate;
+    rep->body_pipe = body_pipe;
+
+    rep->protocol = protocol;
+    rep->sline = sline;
+    return rep;
 }