3 * DEBUG: section 58 HTTP Reply (Response)
4 * AUTHOR: Alex Rousskov
6 * SQUID Web Proxy Cache http://www.squid-cache.org/
7 * ----------------------------------------------------------
9 * Squid is the result of efforts by numerous individuals from
10 * the Internet community; see the CONTRIBUTORS file for full
11 * details. Many organizations have provided support for Squid's
12 * development; see the SPONSORS file for full details. Squid is
13 * Copyrighted (C) 2001 by the Regents of the University of
14 * California; see the COPYRIGHT file for full details. Squid
15 * incorporates software developed and/or copyrighted by other
16 * sources; see the CREDITS file for full details.
18 * This program is free software; you can redistribute it and/or modify
19 * it under the terms of the GNU General Public License as published by
20 * the Free Software Foundation; either version 2 of the License, or
21 * (at your option) any later version.
23 * This program is distributed in the hope that it will be useful,
24 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 * GNU General Public License for more details.
28 * You should have received a copy of the GNU General Public License
29 * along with this program; if not, write to the Free Software
30 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
35 #include "acl/AclSizeLimit.h"
36 #include "acl/FilledChecklist.h"
39 #include "HttpHdrCc.h"
40 #include "HttpHdrContRange.h"
41 #include "HttpHdrSc.h"
42 #include "HttpReply.h"
43 #include "HttpRequest.h"
45 #include "SquidConfig.h"
46 #include "SquidTime.h"
52 /* If we receive a 304 from the origin during a cache revalidation, we must
53 * update the headers of the existing entry. Specifically, we need to update all
54 * end-to-end headers and not any hop-by-hop headers (rfc2616 13.5.3).
56 * This is not the whole story though: since it is possible for a faulty/malicious
57 * origin server to set headers it should not in a 304, we must explicitly ignore
58 * these too. Specifically all entity-headers except those permitted in a 304
59 * (rfc2616 10.3.5) must be ignored.
61 * The list of headers we don't update is made up of:
62 * all hop-by-hop headers
63 * all entity-headers except Expires and Content-Location
65 static HttpHeaderMask Denied304HeadersMask
;
66 static http_hdr_type Denied304HeadersArr
[] = {
68 HDR_CONNECTION
, HDR_KEEP_ALIVE
, HDR_PROXY_AUTHENTICATE
, HDR_PROXY_AUTHORIZATION
,
69 HDR_TE
, HDR_TRAILER
, HDR_TRANSFER_ENCODING
, HDR_UPGRADE
,
71 HDR_ALLOW
, HDR_CONTENT_ENCODING
, HDR_CONTENT_LANGUAGE
, HDR_CONTENT_LENGTH
,
72 HDR_CONTENT_MD5
, HDR_CONTENT_RANGE
, HDR_CONTENT_TYPE
, HDR_LAST_MODIFIED
75 /* module initialization */
77 httpReplyInitModule(void)
79 assert(HTTP_STATUS_NONE
== 0); // HttpReply::parse() interface assumes that
80 httpHeaderMaskInit(&Denied304HeadersMask
, 0);
81 httpHeaderCalcMask(&Denied304HeadersMask
, Denied304HeadersArr
, countof(Denied304HeadersArr
));
84 HttpReply::HttpReply() : HttpMsg(hoReply
), date (0), last_modified (0),
85 expires (0), surrogate_control (NULL
), content_range (NULL
), keep_alive (0),
86 protoPrefix("HTTP/"), bodySizeMax(-2)
91 HttpReply::~HttpReply()
101 httpStatusLineInit(&sline
);
102 pstate
= psReadyToParseStartLine
;
106 void HttpReply::reset()
109 // reset should not reset the protocol; could have made protoPrefix a
110 // virtual function instead, but it is not clear whether virtual methods
111 // are allowed with MEMPROXY_CLASS() and whether some cbdata void*
112 // conversions are not going to kill virtual tables
113 const String pfx
= protoPrefix
;
122 // we used to assert that the pipe is NULL, but now the message only
123 // points to a pipe that is owned and initiated by another object.
129 httpStatusLineClean(&sline
);
130 bodySizeMax
= -2; // hack: make calculatedBodySizeMax() false
134 HttpReply::packHeadersInto(Packer
* p
) const
136 httpStatusLinePackInto(&sline
, p
);
138 packerAppend(p
, "\r\n", 2);
142 HttpReply::packInto(Packer
* p
)
148 /* create memBuf, create mem-based packer, pack, destroy packer, return MemBuf */
152 MemBuf
*mb
= new MemBuf
;
156 packerToMemInit(&p
, mb
);
164 httpPackedReply(http_status status
, const char *ctype
, int64_t clen
, time_t lmt
, time_t expires
)
166 HttpReply
*rep
= new HttpReply
;
167 rep
->setHeaders(status
, ctype
, NULL
, clen
, lmt
, expires
);
168 MemBuf
*mb
= rep
->pack();
175 HttpReply::make304() const
177 static const http_hdr_type ImsEntries
[] = {HDR_DATE
, HDR_CONTENT_TYPE
, HDR_EXPIRES
, HDR_LAST_MODIFIED
, /* eof */ HDR_OTHER
};
179 HttpReply
*rv
= new HttpReply
;
183 /* rv->content_length; */
185 rv
->last_modified
= last_modified
;
186 rv
->expires
= expires
;
187 rv
->content_type
= content_type
;
188 /* rv->cache_control */
189 /* rv->content_range */
191 HttpVersion
ver(1,1);
192 httpStatusLineSet(&rv
->sline
, ver
, HTTP_NOT_MODIFIED
, NULL
);
194 for (t
= 0; ImsEntries
[t
] != HDR_OTHER
; ++t
)
195 if ((e
= header
.findEntry(ImsEntries
[t
])))
196 rv
->header
.addEntry(e
->clone());
203 HttpReply::packed304Reply()
205 /* Not as efficient as skipping the header duplication,
206 * but easier to maintain
208 HttpReply
*temp
= make304();
209 MemBuf
*rv
= temp
->pack();
215 HttpReply::setHeaders(http_status status
, const char *reason
,
216 const char *ctype
, int64_t clen
, time_t lmt
, time_t expiresTime
)
219 HttpVersion
ver(1,1);
220 httpStatusLineSet(&sline
, ver
, status
, reason
);
222 hdr
->putStr(HDR_SERVER
, visible_appname_string
);
223 hdr
->putStr(HDR_MIME_VERSION
, "1.0");
224 hdr
->putTime(HDR_DATE
, squid_curtime
);
227 hdr
->putStr(HDR_CONTENT_TYPE
, ctype
);
228 content_type
= ctype
;
230 content_type
= String();
233 hdr
->putInt64(HDR_CONTENT_LENGTH
, clen
);
235 if (expiresTime
>= 0)
236 hdr
->putTime(HDR_EXPIRES
, expiresTime
);
238 if (lmt
> 0) /* this used to be lmt != 0 @?@ */
239 hdr
->putTime(HDR_LAST_MODIFIED
, lmt
);
241 date
= squid_curtime
;
243 content_length
= clen
;
245 expires
= expiresTime
;
251 HttpReply::redirect(http_status status
, const char *loc
)
254 HttpVersion
ver(1,1);
255 httpStatusLineSet(&sline
, ver
, status
, httpStatusString(status
));
257 hdr
->putStr(HDR_SERVER
, APP_FULLNAME
);
258 hdr
->putTime(HDR_DATE
, squid_curtime
);
259 hdr
->putInt64(HDR_CONTENT_LENGTH
, 0);
260 hdr
->putStr(HDR_LOCATION
, loc
);
261 date
= squid_curtime
;
265 /* compare the validators of two replies.
267 * 0 = they do not match
270 HttpReply::validatorsMatch(HttpReply
const * otherRep
) const
274 /* Numbers first - easiest to check */
276 /* TODO: remove -1 bypass */
278 if (content_length
!= otherRep
->content_length
279 && content_length
> -1 &&
280 otherRep
->content_length
> -1)
284 one
= header
.getStrOrList(HDR_ETAG
);
286 two
= otherRep
->header
.getStrOrList(HDR_ETAG
);
288 if (one
.undefined() || two
.undefined() || one
.caseCmp(two
)!=0 ) {
294 if (last_modified
!= otherRep
->last_modified
)
298 one
= header
.getStrOrList(HDR_CONTENT_MD5
);
300 two
= otherRep
->header
.getStrOrList(HDR_CONTENT_MD5
);
302 if (one
.undefined() || two
.undefined() || one
.caseCmp(two
) != 0 ) {
312 HttpReply::updateOnNotModified(HttpReply
const * freshRep
)
318 /* update raw headers */
319 header
.update(&freshRep
->header
,
320 (const HttpHeaderMask
*) &Denied304HeadersMask
);
327 /* internal routines */
330 HttpReply::hdrExpirationTime()
332 /* The s-maxage and max-age directive takes priority over Expires */
336 if (cache_control
->hasSMaxAge())
337 return date
+ cache_control
->sMaxAge();
339 if (cache_control
->hasMaxAge())
340 return date
+ cache_control
->maxAge();
343 * Conservatively handle the case when we have a max-age
344 * header, but no Date for reference?
347 if (cache_control
->hasSMaxAge())
348 return squid_curtime
;
350 if (cache_control
->hasMaxAge())
351 return squid_curtime
;
355 if (Config
.onoff
.vary_ignore_expire
&&
356 header
.has(HDR_VARY
)) {
357 const time_t d
= header
.getTime(HDR_DATE
);
358 const time_t e
= header
.getTime(HDR_EXPIRES
);
364 if (header
.has(HDR_EXPIRES
)) {
365 const time_t e
= header
.getTime(HDR_EXPIRES
);
367 * HTTP/1.0 says that robust implementations should consider
368 * bad or malformed Expires header as equivalent to "expires
371 return e
< 0 ? squid_curtime
: e
;
377 /* sync this routine when you update HttpReply struct */
379 HttpReply::hdrCacheInit()
381 HttpMsg::hdrCacheInit();
383 http_ver
= sline
.version
;
384 content_length
= header
.getInt64(HDR_CONTENT_LENGTH
);
385 date
= header
.getTime(HDR_DATE
);
386 last_modified
= header
.getTime(HDR_LAST_MODIFIED
);
387 surrogate_control
= header
.getSc();
388 content_range
= header
.getContRange();
389 keep_alive
= persistent() ? 1 : 0;
390 const char *str
= header
.getStr(HDR_CONTENT_TYPE
);
393 content_type
.limitInit(str
, strcspn(str
, ";\t "));
395 content_type
= String();
397 /* be sure to set expires after date and cache-control */
398 expires
= hdrExpirationTime();
401 /* sync this routine when you update HttpReply struct */
403 HttpReply::hdrCacheClean()
405 content_type
.clean();
408 delete cache_control
;
409 cache_control
= NULL
;
412 if (surrogate_control
) {
413 delete surrogate_control
;
414 surrogate_control
= NULL
;
418 httpHdrContRangeDestroy(content_range
);
419 content_range
= NULL
;
424 * Returns the body size of a HTTP response
427 HttpReply::bodySize(const HttpRequestMethod
& method
) const
429 if (sline
.version
.major
< 1)
431 else if (method
.id() == METHOD_HEAD
)
433 else if (sline
.status
== HTTP_OK
)
434 (void) 0; /* common case, continue */
435 else if (sline
.status
== HTTP_NO_CONTENT
)
437 else if (sline
.status
== HTTP_NOT_MODIFIED
)
439 else if (sline
.status
< HTTP_OK
)
442 return content_length
;
446 * Checks the first line of an HTTP Reply is valid.
447 * currently only checks "HTTP/" exists.
449 * NP: not all error cases are detected yet. Some are left for detection later in parse.
452 HttpReply::sanityCheckStartLine(MemBuf
*buf
, const size_t hdr_len
, http_status
*error
)
454 // hack warning: using psize instead of size here due to type mismatches with MemBuf.
456 // content is long enough to possibly hold a reply
457 // 4 being magic size of a 3-digit number plus space delimiter
458 if ( buf
->contentSize() < (protoPrefix
.psize() + 4) ) {
460 debugs(58, 3, HERE
<< "Too small reply header (" << hdr_len
<< " bytes)");
461 *error
= HTTP_INVALID_HEADER
;
467 // catch missing or mismatched protocol identifier
468 // allow special-case for ICY protocol (non-HTTP identifier) in response to faked HTTP request.
469 if (strncmp(buf
->content(), "ICY", 3) == 0) {
471 pos
= protoPrefix
.psize();
474 if (protoPrefix
.cmp(buf
->content(), protoPrefix
.size()) != 0) {
475 debugs(58, 3, "HttpReply::sanityCheckStartLine: missing protocol prefix (" << protoPrefix
<< ") in '" << buf
->content() << "'");
476 *error
= HTTP_INVALID_HEADER
;
480 // catch missing or negative status value (negative '-' is not a digit)
481 pos
= protoPrefix
.psize();
483 // skip arbitrary number of digits and a dot in the verion portion
484 while ( pos
<= buf
->contentSize() && (*(buf
->content()+pos
) == '.' || xisdigit(*(buf
->content()+pos
)) ) ) ++pos
;
486 // catch missing version info
487 if (pos
== protoPrefix
.psize()) {
488 debugs(58, 3, "HttpReply::sanityCheckStartLine: missing protocol version numbers (ie. " << protoPrefix
<< "/1.0) in '" << buf
->content() << "'");
489 *error
= HTTP_INVALID_HEADER
;
494 // skip arbitrary number of spaces...
495 while (pos
<= buf
->contentSize() && (char)*(buf
->content()+pos
) == ' ') ++pos
;
497 if (pos
< buf
->contentSize() && !xisdigit(*(buf
->content()+pos
))) {
498 debugs(58, 3, "HttpReply::sanityCheckStartLine: missing or invalid status number in '" << buf
->content() << "'");
499 *error
= HTTP_INVALID_HEADER
;
506 void HttpReply::packFirstLineInto(Packer
*p
, bool unused
) const
508 httpStatusLinePackInto(&sline
, p
);
511 bool HttpReply::parseFirstLine(const char *blk_start
, const char *blk_end
)
513 return httpStatusLineParse(&sline
, protoPrefix
, blk_start
, blk_end
);
516 /* handy: resets and returns -1 */
518 HttpReply::httpMsgParseError()
520 int result(HttpMsg::httpMsgParseError());
521 /* indicate an error in the status line */
522 sline
.status
= HTTP_INVALID_HEADER
;
527 * Indicate whether or not we would usually expect an entity-body
528 * along with this response
531 HttpReply::expectingBody(const HttpRequestMethod
& req_method
, int64_t& theSize
) const
533 bool expectBody
= true;
535 if (req_method
== METHOD_HEAD
)
537 else if (sline
.status
== HTTP_NO_CONTENT
)
539 else if (sline
.status
== HTTP_NOT_MODIFIED
)
541 else if (sline
.status
< HTTP_OK
)
547 if (header
.chunked())
549 else if (content_length
>= 0)
550 theSize
= content_length
;
559 HttpReply::receivedBodyTooLarge(HttpRequest
& request
, int64_t receivedSize
)
561 calcMaxBodySize(request
);
562 debugs(58, 3, HERE
<< receivedSize
<< " >? " << bodySizeMax
);
563 return bodySizeMax
>= 0 && receivedSize
> bodySizeMax
;
567 HttpReply::expectedBodyTooLarge(HttpRequest
& request
)
569 calcMaxBodySize(request
);
570 debugs(58, 7, HERE
<< "bodySizeMax=" << bodySizeMax
);
572 if (bodySizeMax
< 0) // no body size limit
575 int64_t expectedSize
= -1;
576 if (!expectingBody(request
.method
, expectedSize
))
579 debugs(58, 6, HERE
<< expectedSize
<< " >? " << bodySizeMax
);
581 if (expectedSize
< 0) // expecting body of an unknown length
584 return expectedSize
> bodySizeMax
;
588 HttpReply::calcMaxBodySize(HttpRequest
& request
)
590 // hack: -2 is used as "we have not calculated max body size yet" state
591 if (bodySizeMax
!= -2) // already tried
595 // short-circuit ACL testing if there are none configured
596 if (!Config
.ReplyBodySize
)
599 ACLFilledChecklist
ch(NULL
, &request
, NULL
);
600 ch
.reply
= HTTPMSGLOCK(this); // XXX: this lock makes method non-const
601 for (AclSizeLimit
*l
= Config
.ReplyBodySize
; l
; l
= l
-> next
) {
602 /* if there is no ACL list or if the ACLs listed match use this size value */
603 if (!l
->aclList
|| ch
.fastCheck(l
->aclList
) == ACCESS_ALLOWED
) {
604 debugs(58, 4, HERE
<< "bodySizeMax=" << bodySizeMax
);
605 bodySizeMax
= l
->size
; // may be -1
611 // XXX: check that this is sufficient for eCAP cloning
613 HttpReply::clone() const
615 HttpReply
*rep
= new HttpReply();
616 rep
->sline
= sline
; // used in hdrCacheInit() call below
617 rep
->header
.append(&header
);
619 rep
->hdr_sz
= hdr_sz
;
620 rep
->http_ver
= http_ver
;
621 rep
->pstate
= pstate
;
622 rep
->body_pipe
= body_pipe
;
624 rep
->protocol
= protocol
;
625 // keep_alive is handled in hdrCacheInit()
629 bool HttpReply::inheritProperties(const HttpMsg
*aMsg
)
631 const HttpReply
*aRep
= dynamic_cast<const HttpReply
*>(aMsg
);
634 keep_alive
= aRep
->keep_alive
;
638 void HttpReply::removeStaleWarnings()
641 if (header
.getList(HDR_WARNING
, &warning
)) {
642 const String newWarning
= removeStaleWarningValues(warning
);
643 if (warning
.size() && warning
.size() == newWarning
.size())
644 return; // some warnings are there and none changed
645 header
.delById(HDR_WARNING
);
646 if (newWarning
.size()) { // some warnings left
647 HttpHeaderEntry
*const e
=
648 new HttpHeaderEntry(HDR_WARNING
, NULL
, newWarning
.termedBuf());
655 * Remove warning-values with warn-date different from Date value from
656 * a single header entry. Returns a string with all valid warning-values.
658 String
HttpReply::removeStaleWarningValues(const String
&value
)
661 const char *item
= 0;
664 while (strListGetItem(&value
, ',', &item
, &len
, &pos
)) {
666 // Does warning-value have warn-date (which contains quoted date)?
667 // We scan backwards, looking for two quoted strings.
668 // warning-value = warn-code SP warn-agent SP warn-text [SP warn-date]
669 const char *p
= item
+ len
- 1;
671 while (p
>= item
&& xisspace(*p
)) --p
; // skip whitespace
673 // warning-value MUST end with quote
674 if (p
>= item
&& *p
== '"') {
675 const char *const warnDateEnd
= p
;
677 while (p
>= item
&& *p
!= '"') --p
; // find the next quote
679 const char *warnDateBeg
= p
+ 1;
681 while (p
>= item
&& xisspace(*p
)) --p
; // skip whitespace
683 if (p
>= item
&& *p
== '"' && warnDateBeg
- p
> 2) {
686 warnDate
.append(warnDateBeg
, warnDateEnd
- warnDateBeg
);
687 const time_t time
= parse_rfc1123(warnDate
.termedBuf());
688 keep
= (time
> 0 && time
== date
); // keep valid and matching date
694 newValue
.append(", ");
695 newValue
.append(item
, len
);