/*
- * $Id: HttpReply.cc,v 1.37 1999/04/26 21:06:12 wessels Exp $
+ * $Id: HttpReply.cc,v 1.100 2008/02/08 18:27:59 rousskov Exp $
*
* DEBUG: section 58 HTTP Reply (Response)
* AUTHOR: Alex Rousskov
*
- * SQUID Internet Object Cache http://squid.nlanr.net/Squid/
+ * SQUID Web Proxy Cache http://www.squid-cache.org/
* ----------------------------------------------------------
*
- * Squid is the result of efforts by numerous individuals from the
- * 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.
+ * Squid is the result of efforts by numerous individuals from
+ * the Internet community; see the CONTRIBUTORS file for full
+ * details. Many organizations have provided support for Squid's
+ * development; see the SPONSORS file for full details. Squid is
+ * Copyrighted (C) 2001 by the Regents of the University of
+ * California; see the COPYRIGHT file for full details. Squid
+ * incorporates software developed and/or copyrighted by other
+ * sources; see the CREDITS file for full details.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
*/
#include "squid.h"
-
+#include "SquidTime.h"
+#include "Store.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[] =
-{
- HDR_ALLOW, HDR_CONTENT_ENCODING, HDR_CONTENT_LANGUAGE, HDR_CONTENT_LENGTH,
- HDR_CONTENT_LOCATION, HDR_CONTENT_RANGE, HDR_LAST_MODIFIED, HDR_LINK,
- HDR_OTHER
-};
-
-/* 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);
-
+ {
+ // 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_MD5, HDR_CONTENT_RANGE, HDR_CONTENT_TYPE, HDR_LAST_MODIFIED
+ };
/* module initialization */
void
-httpReplyInitModule()
+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()
+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 = memAllocate(MEM_HTTP_REPLY);
- 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->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;
- /* 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;
}
-/* parses a 4K buffer that may not be 0-terminated; returns true on success */
-int
-httpReplyParse(HttpReply * rep, const char *buf)
+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 store.c may
- * feed a non 0-terminated buffer to us.
- */
- char *headers = memAllocate(MEM_4K_BUF);
- int success;
- /* reset current state, because we are not used in incremental fashion */
- httpReplyReset(rep);
- /* put a 0-terminator */
- xstrncpy(headers, buf, 4096);
- 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
-httpReplyPackInto(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);
- httpBodyPackInto(&rep->body, p);
}
-/* create memBuf, create mem-based packer, pack, destroy packer, return MemBuf */
-MemBuf
-httpReplyPack(const HttpReply * rep)
+void
+HttpReply::packInto(Packer * p)
{
- MemBuf mb;
- Packer p;
- assert(rep);
-
- memBufDefInit(&mb);
- packerToMemInit(&p, &mb);
- httpReplyPackInto(rep, &p);
- packerClean(&p);
- return mb;
+ packHeadersInto(p);
+ httpBodyPackInto(&body, p);
}
-/* swap: create swap-based packer, pack, destroy packer */
-void
-httpReplySwapOut(const HttpReply * rep, StoreEntry * e)
+/* create memBuf, create mem-based packer, pack, destroy packer, return MemBuf */
+MemBuf *
+HttpReply::pack()
{
+ MemBuf *mb = new MemBuf;
Packer p;
- assert(rep && e);
- packerToStoreInit(&p, e);
- httpReplyPackInto(rep, &p);
+ mb->init();
+ packerToMemInit(&p, mb);
+ packInto(&p);
packerClean(&p);
+ return mb;
}
-MemBuf
-httpPackedReply(double ver, http_status status, const char *ctype,
- int clen, time_t lmt, time_t expires)
+MemBuf *
+httpPackedReply(HttpVersion ver, http_status status, const char *ctype,
+ int64_t 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;
}
-MemBuf
-httpPacked304Reply(const HttpReply * rep)
+HttpReply *
+HttpReply::make304 () const
{
- static const http_hdr_type ImsEntries[] =
- {HDR_DATE, HDR_CONTENT_TYPE, HDR_EXPIRES, HDR_LAST_MODIFIED, /* eof */ HDR_OTHER};
+ static const http_hdr_type ImsEntries[] = {HDR_DATE, HDR_CONTENT_TYPE, HDR_EXPIRES, HDR_LAST_MODIFIED, /* eof */ HDR_OTHER};
+
+ HttpReply *rv = new HttpReply;
int t;
- MemBuf mb;
- Packer p;
HttpHeaderEntry *e;
- assert(rep);
- memBufDefInit(&mb);
- packerToMemInit(&p, &mb);
- memBufPrintf(&mb, "%s", "HTTP/1.0 304 Not Modified\r\n");
+ /* rv->content_length; */
+ 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 */
+ 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])))
- httpHeaderEntryPackInto(e, &p);
- memBufAppend(&mb, "\r\n", 2);
- packerClean(&p);
- return mb;
+ if ((e = header.findEntry(ImsEntries[t])))
+ rv->header.addEntry(e->clone());
+
+ /* rv->body */
+ return rv;
+}
+
+MemBuf *
+HttpReply::packed304Reply()
+{
+ /* Not as efficient as skipping the header duplication,
+ * but easier to maintain
+ */
+ HttpReply *temp = make304 ();
+ MemBuf *rv = temp->pack();
+ delete temp;
+ return rv;
}
void
-httpReplySetHeaders(HttpReply * reply, double 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);
- stringInit(&reply->content_type, ctype);
+ hdr->putStr(HDR_CONTENT_TYPE, ctype);
+ content_type = ctype;
} else
- reply->content_type = StringNull;
+ 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);
- reply->date = squid_curtime;
- reply->content_length = clen;
- reply->expires = expires;
- reply->last_modified = lmt;
+ hdr->putTime(HDR_LAST_MODIFIED, lmt);
+
+ date = squid_curtime;
+
+ content_length = clen;
+
+ expires = expires;
+
+ last_modified = lmt;
}
void
-httpRedirectReply(HttpReply * reply, http_status status, const char *loc)
+HttpReply::redirect(http_status status, const char *loc)
{
HttpHeader *hdr;
- assert(reply);
- httpStatusLineSet(&reply->sline, 1.0, 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.
+ * 1 = they match
+ * 0 = they do not match
+ */
+int
+HttpReply::validatorsMatch(HttpReply const * otherRep) const
+{
+ String one,two;
+ assert (otherRep);
+ /* Numbers first - easiest to check */
+ /* Content-Length */
+ /* TODO: remove -1 bypass */
+
+ if (content_length != otherRep->content_length
+ && content_length > -1 &&
+ otherRep->content_length > -1)
+ return 0;
+
+ /* ETag */
+ one = header.getStrOrList(HDR_ETAG);
+
+ two = otherRep->header.getStrOrList(HDR_ETAG);
+
+ if (!one.buf() || !two.buf() || strcasecmp (one.buf(), two.buf())) {
+ one.clean();
+ two.clean();
+ return 0;
+ }
+
+ if (last_modified != otherRep->last_modified)
+ return 0;
+
+ /* MD5 */
+ one = header.getStrOrList(HDR_CONTENT_MD5);
+
+ two = otherRep->header.getStrOrList(HDR_CONTENT_MD5);
+
+ if (!one.buf() || !two.buf() || strcasecmp (one.buf(), two.buf())) {
+ one.clean();
+ two.clean();
+ return 0;
+ }
+
+ return 1;
}
void
-httpReplyUpdateOnNotModified(HttpReply * rep, HttpReply * freshRep)
+HttpReply::updateOnNotModified(HttpReply const * freshRep)
{
- assert(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)
+time_t
+HttpReply::hdrExpirationTime()
{
- memFree(rep, MEM_HTTP_REPLY);
+ /* The s-maxage and max-age directive takes priority over Expires */
+
+ if (cache_control) {
+ if (date >= 0) {
+ if (cache_control->s_maxage >= 0)
+ return date + cache_control->s_maxage;
+
+ 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 (cache_control->s_maxage >= 0)
+ return squid_curtime;
+
+ if (cache_control->max_age >= 0)
+ return squid_curtime;
+ }
+ }
+
+ if (Config.onoff.vary_ignore_expire &&
+ 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 (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
+ * immediately."
+ */
+ return e < 0 ? squid_curtime : e;
+ }
+
+ return -1;
}
/* 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);
- rep->expires = httpHeaderGetTime(hdr, HDR_EXPIRES);
- 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)
- stringLimitInit(&rep->content_type, str, strcspn(str, ";\t "));
- else
- rep->content_type = StringNull;
- rep->cache_control = httpHeaderGetCc(hdr);
- rep->content_range = httpHeaderGetContRange(hdr);
- rep->keep_alive = httpMsgIsPersistent(rep->sline.version, &rep->header);
- /* final adjustments */
- /* The max-age directive takes priority over Expires, check it first */
- if (rep->cache_control && rep->cache_control->max_age >= 0)
- rep->expires = squid_curtime + rep->cache_control->max_age;
+ content_type.limitInit(str, strcspn(str, ";\t "));
else
- /*
- * The HTTP/1.0 specs says that robust implementations should consider bad
- * or malformed Expires header as equivalent to "expires immediately."
- */
- if (rep->expires < 0 && httpHeaderHas(hdr, HDR_EXPIRES))
- rep->expires = squid_curtime;
+ content_type = String();
+
+ /* be sure to set expires after date and cache-control */
+ expires = hdrExpirationTime();
}
/* sync this routine when you update HttpReply struct */
-static void
-httpReplyHdrCacheClean(HttpReply * rep)
+void
+HttpReply::hdrCacheClean()
{
- stringClean(&rep->content_type);
- if (rep->cache_control)
- httpHdrCcDestroy(rep->cache_control);
- if (rep->content_range)
- httpHdrContRangeDestroy(rep->content_range);
+ content_type.clean();
+
+ if (cache_control) {
+ httpHdrCcDestroy(cache_control);
+ cache_control = NULL;
+ }
+
+ if (surrogate_control) {
+ httpHdrScDestroy(surrogate_control);
+ surrogate_control = NULL;
+ }
+
+ 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;
- rep->hdr_sz = *parse_end_ptr - buf;
- rep->pstate++;
+ 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;
+
+ return content_length;
+}
+
+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);
+}
+
+/* handy: resets and returns -1 */
+int
+HttpReply::httpMsgParseError()
+{
+ int result(HttpMsg::httpMsgParseError());
+ /* indicate an error in the status line */
+ sline.status = HTTP_INVALID_HEADER;
+ return result;
}
/*
- * Returns the body size of a HTTP response
+ * Indicate whether or not we would usually expect an entity-body
+ * along with this response
*/
-int
-httpReplyBodySize(method_t method, HttpReply * reply)
+bool
+HttpReply::expectingBody(const HttpRequestMethod& req_method, int64_t& theSize) const
+{
+ 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;
+ }
+
+ return expectBody;
+}
+
+bool
+HttpReply::receivedBodyTooLarge(HttpRequest& request, int64_t receivedSize)
+{
+ calcMaxBodySize(request);
+ debugs(58, 3, HERE << receivedSize << " >? " << bodySizeMax);
+ return bodySizeMax >= 0 && receivedSize > bodySizeMax;
+}
+
+bool
+HttpReply::expectedBodyTooLarge(HttpRequest& request)
{
- 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;
+ 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 (expectedSize < 0) // expecting body of an unknown length
+ return false;
+
+ return expectedSize > bodySizeMax;
+}
+
+void
+HttpReply::calcMaxBodySize(HttpRequest& request)
+{
+ // 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;
}