3 * $Id: HttpReply.cc,v 1.100 2008/02/08 18:27:59 rousskov Exp $
5 * DEBUG: section 58 HTTP Reply (Response)
6 * AUTHOR: Alex Rousskov
8 * SQUID Web Proxy Cache http://www.squid-cache.org/
9 * ----------------------------------------------------------
11 * Squid is the result of efforts by numerous individuals from
12 * the Internet community; see the CONTRIBUTORS file for full
13 * details. Many organizations have provided support for Squid's
14 * development; see the SPONSORS file for full details. Squid is
15 * Copyrighted (C) 2001 by the Regents of the University of
16 * California; see the COPYRIGHT file for full details. Squid
17 * incorporates software developed and/or copyrighted by other
18 * sources; see the CREDITS file for full details.
20 * This program is free software; you can redistribute it and/or modify
21 * it under the terms of the GNU General Public License as published by
22 * the Free Software Foundation; either version 2 of the License, or
23 * (at your option) any later version.
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
30 * You should have received a copy of the GNU General Public License
31 * along with this program; if not, write to the Free Software
32 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
37 #include "SquidTime.h"
39 #include "HttpReply.h"
40 #include "HttpHdrContRange.h"
41 #include "HttpHdrSc.h"
42 #include "ACLChecklist.h"
43 #include "HttpRequest.h"
48 /* If we receive a 304 from the origin during a cache revalidation, we must
49 * update the headers of the existing entry. Specifically, we need to update all
50 * end-to-end headers and not any hop-by-hop headers (rfc2616 13.5.3).
52 * This is not the whole story though: since it is possible for a faulty/malicious
53 * origin server to set headers it should not in a 304, we must explicitly ignore
54 * these too. Specifically all entity-headers except those permitted in a 304
55 * (rfc2616 10.3.5) must be ignored.
57 * The list of headers we don't update is made up of:
58 * all hop-by-hop headers
59 * all entity-headers except Expires and Content-Location
61 static HttpHeaderMask Denied304HeadersMask
;
62 static http_hdr_type Denied304HeadersArr
[] =
65 HDR_CONNECTION
, HDR_KEEP_ALIVE
, HDR_PROXY_AUTHENTICATE
, HDR_PROXY_AUTHORIZATION
,
66 HDR_TE
, HDR_TRAILERS
, HDR_TRANSFER_ENCODING
, HDR_UPGRADE
,
68 HDR_ALLOW
, HDR_CONTENT_ENCODING
, HDR_CONTENT_LANGUAGE
, HDR_CONTENT_LENGTH
,
69 HDR_CONTENT_MD5
, HDR_CONTENT_RANGE
, HDR_CONTENT_TYPE
, HDR_LAST_MODIFIED
72 /* module initialization */
74 httpReplyInitModule(void)
76 assert(HTTP_STATUS_NONE
== 0); // HttpReply::parse() interface assumes that
77 httpHeaderMaskInit(&Denied304HeadersMask
, 0);
78 httpHeaderCalcMask(&Denied304HeadersMask
, Denied304HeadersArr
, countof(Denied304HeadersArr
));
81 HttpReply::HttpReply() : HttpMsg(hoReply
), date (0), last_modified (0),
82 expires (0), surrogate_control (NULL
), content_range (NULL
), keep_alive (0),
83 protoPrefix("HTTP/"), bodySizeMax(-2)
88 HttpReply::~HttpReply()
99 httpStatusLineInit(&sline
);
100 pstate
= psReadyToParseStartLine
;
104 void HttpReply::reset()
107 // reset should not reset the protocol; could have made protoPrefix a
108 // virtual function instead, but it is not clear whether virtual methods
109 // are allowed with MEMPROXY_CLASS() and whether some cbdata void*
110 // conversions are not going to kill virtual tables
111 const String pfx
= protoPrefix
;
120 // we used to assert that the pipe is NULL, but now the message only
121 // points to a pipe that is owned and initiated by another object.
124 httpBodyClean(&body
);
127 httpStatusLineClean(&sline
);
128 bodySizeMax
= -2; // hack: make calculatedBodySizeMax() false
132 HttpReply::packHeadersInto(Packer
* p
) const
134 httpStatusLinePackInto(&sline
, p
);
136 packerAppend(p
, "\r\n", 2);
140 HttpReply::packInto(Packer
* p
)
143 httpBodyPackInto(&body
, p
);
146 /* create memBuf, create mem-based packer, pack, destroy packer, return MemBuf */
150 MemBuf
*mb
= new MemBuf
;
154 packerToMemInit(&p
, mb
);
161 httpPackedReply(HttpVersion ver
, http_status status
, const char *ctype
,
162 int64_t clen
, time_t lmt
, time_t expires
)
164 HttpReply
*rep
= new HttpReply
;
165 rep
->setHeaders(ver
, status
, ctype
, NULL
, clen
, lmt
, expires
);
166 MemBuf
*mb
= rep
->pack();
172 HttpReply::make304 () const
174 static const http_hdr_type ImsEntries
[] = {HDR_DATE
, HDR_CONTENT_TYPE
, HDR_EXPIRES
, HDR_LAST_MODIFIED
, /* eof */ HDR_OTHER
};
176 HttpReply
*rv
= new HttpReply
;
180 /* rv->content_length; */
182 rv
->last_modified
= last_modified
;
183 rv
->expires
= expires
;
184 rv
->content_type
= content_type
;
185 /* rv->cache_control */
186 /* rv->content_range */
188 HttpVersion
ver(1,0);
189 httpStatusLineSet(&rv
->sline
, ver
,
190 HTTP_NOT_MODIFIED
, "");
192 for (t
= 0; ImsEntries
[t
] != HDR_OTHER
; ++t
)
193 if ((e
= header
.findEntry(ImsEntries
[t
])))
194 rv
->header
.addEntry(e
->clone());
201 HttpReply::packed304Reply()
203 /* Not as efficient as skipping the header duplication,
204 * but easier to maintain
206 HttpReply
*temp
= make304 ();
207 MemBuf
*rv
= temp
->pack();
213 HttpReply::setHeaders(HttpVersion ver
, http_status status
, const char *reason
,
214 const char *ctype
, int64_t clen
, time_t lmt
, time_t expires
)
217 httpStatusLineSet(&sline
, ver
, status
, reason
);
219 hdr
->putStr(HDR_SERVER
, visible_appname_string
);
220 hdr
->putStr(HDR_MIME_VERSION
, "1.0");
221 hdr
->putTime(HDR_DATE
, squid_curtime
);
224 hdr
->putStr(HDR_CONTENT_TYPE
, ctype
);
225 content_type
= ctype
;
227 content_type
= String();
230 hdr
->putInt64(HDR_CONTENT_LENGTH
, clen
);
233 hdr
->putTime(HDR_EXPIRES
, expires
);
235 if (lmt
> 0) /* this used to be lmt != 0 @?@ */
236 hdr
->putTime(HDR_LAST_MODIFIED
, lmt
);
238 date
= squid_curtime
;
240 content_length
= clen
;
248 HttpReply::redirect(http_status status
, const char *loc
)
251 HttpVersion
ver(1,0);
252 httpStatusLineSet(&sline
, ver
, status
, httpStatusString(status
));
254 hdr
->putStr(HDR_SERVER
, full_appname_string
);
255 hdr
->putTime(HDR_DATE
, squid_curtime
);
256 hdr
->putInt64(HDR_CONTENT_LENGTH
, 0);
257 hdr
->putStr(HDR_LOCATION
, loc
);
258 date
= squid_curtime
;
262 /* compare the validators of two replies.
264 * 0 = they do not match
267 HttpReply::validatorsMatch(HttpReply
const * otherRep
) const
271 /* Numbers first - easiest to check */
273 /* TODO: remove -1 bypass */
275 if (content_length
!= otherRep
->content_length
276 && content_length
> -1 &&
277 otherRep
->content_length
> -1)
281 one
= header
.getStrOrList(HDR_ETAG
);
283 two
= otherRep
->header
.getStrOrList(HDR_ETAG
);
285 if (!one
.buf() || !two
.buf() || strcasecmp (one
.buf(), two
.buf())) {
291 if (last_modified
!= otherRep
->last_modified
)
295 one
= header
.getStrOrList(HDR_CONTENT_MD5
);
297 two
= otherRep
->header
.getStrOrList(HDR_CONTENT_MD5
);
299 if (!one
.buf() || !two
.buf() || strcasecmp (one
.buf(), two
.buf())) {
309 HttpReply::updateOnNotModified(HttpReply
const * freshRep
)
315 /* update raw headers */
316 header
.update(&freshRep
->header
,
317 (const HttpHeaderMask
*) &Denied304HeadersMask
);
324 /* internal routines */
327 HttpReply::hdrExpirationTime()
329 /* The s-maxage and max-age directive takes priority over Expires */
333 if (cache_control
->s_maxage
>= 0)
334 return date
+ cache_control
->s_maxage
;
336 if (cache_control
->max_age
>= 0)
337 return date
+ cache_control
->max_age
;
340 * Conservatively handle the case when we have a max-age
341 * header, but no Date for reference?
344 if (cache_control
->s_maxage
>= 0)
345 return squid_curtime
;
347 if (cache_control
->max_age
>= 0)
348 return squid_curtime
;
352 if (Config
.onoff
.vary_ignore_expire
&&
353 header
.has(HDR_VARY
)) {
354 const time_t d
= header
.getTime(HDR_DATE
);
355 const time_t e
= header
.getTime(HDR_EXPIRES
);
361 if (header
.has(HDR_EXPIRES
)) {
362 const time_t e
= header
.getTime(HDR_EXPIRES
);
364 * HTTP/1.0 says that robust implementations should consider
365 * bad or malformed Expires header as equivalent to "expires
368 return e
< 0 ? squid_curtime
: e
;
374 /* sync this routine when you update HttpReply struct */
376 HttpReply::hdrCacheInit()
378 HttpMsg::hdrCacheInit();
380 content_length
= header
.getInt64(HDR_CONTENT_LENGTH
);
381 date
= header
.getTime(HDR_DATE
);
382 last_modified
= header
.getTime(HDR_LAST_MODIFIED
);
383 surrogate_control
= header
.getSc();
384 content_range
= header
.getContRange();
385 keep_alive
= httpMsgIsPersistent(sline
.version
, &header
);
386 const char *str
= header
.getStr(HDR_CONTENT_TYPE
);
389 content_type
.limitInit(str
, strcspn(str
, ";\t "));
391 content_type
= String();
393 /* be sure to set expires after date and cache-control */
394 expires
= hdrExpirationTime();
397 /* sync this routine when you update HttpReply struct */
399 HttpReply::hdrCacheClean()
401 content_type
.clean();
404 httpHdrCcDestroy(cache_control
);
405 cache_control
= NULL
;
408 if (surrogate_control
) {
409 httpHdrScDestroy(surrogate_control
);
410 surrogate_control
= NULL
;
414 httpHdrContRangeDestroy(content_range
);
415 content_range
= NULL
;
420 * Returns the body size of a HTTP response
423 HttpReply::bodySize(const HttpRequestMethod
& method
) const
425 if (sline
.version
.major
< 1)
427 else if (method
.id() == METHOD_HEAD
)
429 else if (sline
.status
== HTTP_OK
)
430 (void) 0; /* common case, continue */
431 else if (sline
.status
== HTTP_NO_CONTENT
)
433 else if (sline
.status
== HTTP_NOT_MODIFIED
)
435 else if (sline
.status
< HTTP_OK
)
438 return content_length
;
441 bool HttpReply::sanityCheckStartLine(MemBuf
*buf
, http_status
*error
)
443 if (buf
->contentSize() >= protoPrefix
.size() && protoPrefix
.cmp(buf
->content(), protoPrefix
.size()) != 0) {
444 debugs(58, 3, "HttpReply::sanityCheckStartLine: missing protocol prefix (" << protoPrefix
.buf() << ") in '" << buf
->content() << "'");
445 *error
= HTTP_INVALID_HEADER
;
452 void HttpReply::packFirstLineInto(Packer
*p
, bool unused
) const
454 httpStatusLinePackInto(&sline
, p
);
457 bool HttpReply::parseFirstLine(const char *blk_start
, const char *blk_end
)
459 return httpStatusLineParse(&sline
, protoPrefix
, blk_start
, blk_end
);
462 /* handy: resets and returns -1 */
464 HttpReply::httpMsgParseError()
466 int result(HttpMsg::httpMsgParseError());
467 /* indicate an error in the status line */
468 sline
.status
= HTTP_INVALID_HEADER
;
473 * Indicate whether or not we would usually expect an entity-body
474 * along with this response
477 HttpReply::expectingBody(const HttpRequestMethod
& req_method
, int64_t& theSize
) const
479 bool expectBody
= true;
481 if (req_method
== METHOD_HEAD
)
483 else if (sline
.status
== HTTP_NO_CONTENT
)
485 else if (sline
.status
== HTTP_NOT_MODIFIED
)
487 else if (sline
.status
< HTTP_OK
)
493 if (header
.hasListMember(HDR_TRANSFER_ENCODING
, "chunked", ','))
495 else if (content_length
>= 0)
496 theSize
= content_length
;
505 HttpReply::receivedBodyTooLarge(HttpRequest
& request
, int64_t receivedSize
)
507 calcMaxBodySize(request
);
508 debugs(58, 3, HERE
<< receivedSize
<< " >? " << bodySizeMax
);
509 return bodySizeMax
>= 0 && receivedSize
> bodySizeMax
;
513 HttpReply::expectedBodyTooLarge(HttpRequest
& request
)
515 calcMaxBodySize(request
);
516 debugs(58, 7, HERE
<< "bodySizeMax=" << bodySizeMax
);
518 if (bodySizeMax
< 0) // no body size limit
521 int64_t expectedSize
= -1;
522 if (!expectingBody(request
.method
, expectedSize
))
525 debugs(58, 6, HERE
<< expectedSize
<< " >? " << bodySizeMax
);
527 if (expectedSize
< 0) // expecting body of an unknown length
530 return expectedSize
> bodySizeMax
;
534 HttpReply::calcMaxBodySize(HttpRequest
& request
)
536 // hack: -2 is used as "we have not calculated max body size yet" state
537 if (bodySizeMax
!= -2) // already tried
542 ch
.src_addr
= request
.client_addr
;
543 ch
.my_addr
= request
.my_addr
;
544 ch
.reply
= HTTPMSGLOCK(this); // XXX: this lock makes method non-const
545 ch
.request
= HTTPMSGLOCK(&request
);
546 for (acl_size_t
*l
= Config
.ReplyBodySize
; l
; l
= l
-> next
) {
547 if (ch
.matchAclListFast(l
->aclList
)) {
548 debugs(58, 4, HERE
<< "bodySizeMax=" << bodySizeMax
);
549 bodySizeMax
= l
->size
; // may be -1
556 HttpReply::clone() const
558 HttpReply
*rep
= new HttpReply();
559 rep
->header
.append(&header
);
561 rep
->hdr_sz
= hdr_sz
;
562 rep
->http_ver
= http_ver
;
563 rep
->pstate
= pstate
;
564 rep
->protocol
= protocol
;