2 * Copyright (C) 1996-2015 The Squid Software Foundation and contributors
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
9 /* DEBUG: section 58 HTTP Reply (Response) */
12 #include "acl/AclSizeLimit.h"
13 #include "acl/FilledChecklist.h"
16 #include "HttpHdrCc.h"
17 #include "HttpHdrContRange.h"
18 #include "HttpHdrSc.h"
19 #include "HttpReply.h"
20 #include "HttpRequest.h"
22 #include "SquidConfig.h"
23 #include "SquidTime.h"
29 /* If we receive a 304 from the origin during a cache revalidation, we must
30 * update the headers of the existing entry. Specifically, we need to update all
31 * end-to-end headers and not any hop-by-hop headers (rfc2616 13.5.3).
33 * This is not the whole story though: since it is possible for a faulty/malicious
34 * origin server to set headers it should not in a 304, we must explicitly ignore
35 * these too. Specifically all entity-headers except those permitted in a 304
36 * (rfc2616 10.3.5) must be ignored.
38 * The list of headers we don't update is made up of:
39 * all hop-by-hop headers
40 * all entity-headers except Expires and Content-Location
42 static HttpHeaderMask Denied304HeadersMask
;
43 static http_hdr_type Denied304HeadersArr
[] = {
45 HDR_CONNECTION
, HDR_KEEP_ALIVE
, HDR_PROXY_AUTHENTICATE
, HDR_PROXY_AUTHORIZATION
,
46 HDR_TE
, HDR_TRAILER
, HDR_TRANSFER_ENCODING
, HDR_UPGRADE
,
48 HDR_ALLOW
, HDR_CONTENT_ENCODING
, HDR_CONTENT_LANGUAGE
, HDR_CONTENT_LENGTH
,
49 HDR_CONTENT_MD5
, HDR_CONTENT_RANGE
, HDR_CONTENT_TYPE
, HDR_LAST_MODIFIED
52 /* module initialization */
54 httpReplyInitModule(void)
56 assert(Http::scNone
== 0); // HttpReply::parse() interface assumes that
57 httpHeaderMaskInit(&Denied304HeadersMask
, 0);
58 httpHeaderCalcMask(&Denied304HeadersMask
, Denied304HeadersArr
, countof(Denied304HeadersArr
));
61 HttpReply::HttpReply() : HttpMsg(hoReply
), date (0), last_modified (0),
62 expires (0), surrogate_control (NULL
), content_range (NULL
), keep_alive (0),
63 protoPrefix("HTTP/"), bodySizeMax(-2)
68 HttpReply::~HttpReply()
79 pstate
= psReadyToParseStartLine
;
83 void HttpReply::reset()
86 // reset should not reset the protocol; could have made protoPrefix a
87 // virtual function instead, but it is not clear whether virtual methods
88 // are allowed with MEMPROXY_CLASS() and whether some cbdata void*
89 // conversions are not going to kill virtual tables
90 const String pfx
= protoPrefix
;
99 // we used to assert that the pipe is NULL, but now the message only
100 // points to a pipe that is owned and initiated by another object.
107 bodySizeMax
= -2; // hack: make calculatedBodySizeMax() false
111 HttpReply::packHeadersInto(Packable
* p
) const
115 p
->append("\r\n", 2);
119 HttpReply::packInto(Packable
* p
)
125 /* create memBuf, create mem-based packer, pack, destroy packer, return MemBuf */
129 MemBuf
*mb
= new MemBuf
;
136 HttpReply::make304() const
138 static const http_hdr_type ImsEntries
[] = {HDR_DATE
, HDR_CONTENT_TYPE
, HDR_EXPIRES
, HDR_LAST_MODIFIED
, /* eof */ HDR_OTHER
};
140 HttpReply
*rv
= new HttpReply
;
144 /* rv->content_length; */
146 rv
->last_modified
= last_modified
;
147 rv
->expires
= expires
;
148 rv
->content_type
= content_type
;
149 /* rv->cache_control */
150 /* rv->content_range */
152 rv
->sline
.set(Http::ProtocolVersion(), Http::scNotModified
, NULL
);
154 for (t
= 0; ImsEntries
[t
] != HDR_OTHER
; ++t
)
155 if ((e
= header
.findEntry(ImsEntries
[t
])))
156 rv
->header
.addEntry(e
->clone());
163 HttpReply::packed304Reply()
165 /* Not as efficient as skipping the header duplication,
166 * but easier to maintain
168 HttpReply
*temp
= make304();
169 MemBuf
*rv
= temp
->pack();
175 HttpReply::setHeaders(Http::StatusCode status
, const char *reason
,
176 const char *ctype
, int64_t clen
, time_t lmt
, time_t expiresTime
)
179 sline
.set(Http::ProtocolVersion(), status
, reason
);
181 hdr
->putStr(HDR_SERVER
, visible_appname_string
);
182 hdr
->putStr(HDR_MIME_VERSION
, "1.0");
183 hdr
->putTime(HDR_DATE
, squid_curtime
);
186 hdr
->putStr(HDR_CONTENT_TYPE
, ctype
);
187 content_type
= ctype
;
189 content_type
= String();
192 hdr
->putInt64(HDR_CONTENT_LENGTH
, clen
);
194 if (expiresTime
>= 0)
195 hdr
->putTime(HDR_EXPIRES
, expiresTime
);
197 if (lmt
> 0) /* this used to be lmt != 0 @?@ */
198 hdr
->putTime(HDR_LAST_MODIFIED
, lmt
);
200 date
= squid_curtime
;
202 content_length
= clen
;
204 expires
= expiresTime
;
210 HttpReply::redirect(Http::StatusCode status
, const char *loc
)
213 sline
.set(Http::ProtocolVersion(), status
, NULL
);
215 hdr
->putStr(HDR_SERVER
, APP_FULLNAME
);
216 hdr
->putTime(HDR_DATE
, squid_curtime
);
217 hdr
->putInt64(HDR_CONTENT_LENGTH
, 0);
218 hdr
->putStr(HDR_LOCATION
, loc
);
219 date
= squid_curtime
;
223 /* compare the validators of two replies.
225 * 0 = they do not match
228 HttpReply::validatorsMatch(HttpReply
const * otherRep
) const
232 /* Numbers first - easiest to check */
234 /* TODO: remove -1 bypass */
236 if (content_length
!= otherRep
->content_length
237 && content_length
> -1 &&
238 otherRep
->content_length
> -1)
242 one
= header
.getStrOrList(HDR_ETAG
);
244 two
= otherRep
->header
.getStrOrList(HDR_ETAG
);
246 if (one
.size()==0 || two
.size()==0 || one
.caseCmp(two
)!=0 ) {
252 if (last_modified
!= otherRep
->last_modified
)
256 one
= header
.getStrOrList(HDR_CONTENT_MD5
);
258 two
= otherRep
->header
.getStrOrList(HDR_CONTENT_MD5
);
260 if (one
.size()==0 || two
.size()==0 || one
.caseCmp(two
)!=0 ) {
270 HttpReply::updateOnNotModified(HttpReply
const * freshRep
)
276 /* update raw headers */
277 header
.update(&freshRep
->header
,
278 (const HttpHeaderMask
*) &Denied304HeadersMask
);
285 /* internal routines */
288 HttpReply::hdrExpirationTime()
290 /* The s-maxage and max-age directive takes priority over Expires */
294 if (cache_control
->hasSMaxAge())
295 return date
+ cache_control
->sMaxAge();
297 if (cache_control
->hasMaxAge())
298 return date
+ cache_control
->maxAge();
301 * Conservatively handle the case when we have a max-age
302 * header, but no Date for reference?
305 if (cache_control
->hasSMaxAge())
306 return squid_curtime
;
308 if (cache_control
->hasMaxAge())
309 return squid_curtime
;
313 if (Config
.onoff
.vary_ignore_expire
&&
314 header
.has(HDR_VARY
)) {
315 const time_t d
= header
.getTime(HDR_DATE
);
316 const time_t e
= header
.getTime(HDR_EXPIRES
);
322 if (header
.has(HDR_EXPIRES
)) {
323 const time_t e
= header
.getTime(HDR_EXPIRES
);
325 * HTTP/1.0 says that robust implementations should consider
326 * bad or malformed Expires header as equivalent to "expires
329 return e
< 0 ? squid_curtime
: e
;
335 /* sync this routine when you update HttpReply struct */
337 HttpReply::hdrCacheInit()
339 HttpMsg::hdrCacheInit();
341 http_ver
= sline
.version
;
342 content_length
= header
.getInt64(HDR_CONTENT_LENGTH
);
343 date
= header
.getTime(HDR_DATE
);
344 last_modified
= header
.getTime(HDR_LAST_MODIFIED
);
345 surrogate_control
= header
.getSc();
346 content_range
= header
.getContRange();
347 keep_alive
= persistent() ? 1 : 0;
348 const char *str
= header
.getStr(HDR_CONTENT_TYPE
);
351 content_type
.limitInit(str
, strcspn(str
, ";\t "));
353 content_type
= String();
355 /* be sure to set expires after date and cache-control */
356 expires
= hdrExpirationTime();
359 /* sync this routine when you update HttpReply struct */
361 HttpReply::hdrCacheClean()
363 content_type
.clean();
366 delete cache_control
;
367 cache_control
= NULL
;
370 if (surrogate_control
) {
371 delete surrogate_control
;
372 surrogate_control
= NULL
;
376 httpHdrContRangeDestroy(content_range
);
377 content_range
= NULL
;
382 * Returns the body size of a HTTP response
385 HttpReply::bodySize(const HttpRequestMethod
& method
) const
387 if (sline
.version
.major
< 1)
389 else if (method
.id() == Http::METHOD_HEAD
)
391 else if (sline
.status() == Http::scOkay
)
392 (void) 0; /* common case, continue */
393 else if (sline
.status() == Http::scNoContent
)
395 else if (sline
.status() == Http::scNotModified
)
397 else if (sline
.status() < Http::scOkay
)
400 return content_length
;
404 * Checks the first line of an HTTP Reply is valid.
405 * currently only checks "HTTP/" exists.
407 * NP: not all error cases are detected yet. Some are left for detection later in parse.
410 HttpReply::sanityCheckStartLine(MemBuf
*buf
, const size_t hdr_len
, Http::StatusCode
*error
)
412 // hack warning: using psize instead of size here due to type mismatches with MemBuf.
414 // content is long enough to possibly hold a reply
415 // 4 being magic size of a 3-digit number plus space delimiter
416 if ( buf
->contentSize() < (protoPrefix
.psize() + 4) ) {
418 debugs(58, 3, HERE
<< "Too small reply header (" << hdr_len
<< " bytes)");
419 *error
= Http::scInvalidHeader
;
425 // catch missing or mismatched protocol identifier
426 // allow special-case for ICY protocol (non-HTTP identifier) in response to faked HTTP request.
427 if (strncmp(buf
->content(), "ICY", 3) == 0) {
429 pos
= protoPrefix
.psize();
432 if (protoPrefix
.cmp(buf
->content(), protoPrefix
.size()) != 0) {
433 debugs(58, 3, "HttpReply::sanityCheckStartLine: missing protocol prefix (" << protoPrefix
<< ") in '" << buf
->content() << "'");
434 *error
= Http::scInvalidHeader
;
438 // catch missing or negative status value (negative '-' is not a digit)
439 pos
= protoPrefix
.psize();
441 // skip arbitrary number of digits and a dot in the verion portion
442 while ( pos
<= buf
->contentSize() && (*(buf
->content()+pos
) == '.' || xisdigit(*(buf
->content()+pos
)) ) ) ++pos
;
444 // catch missing version info
445 if (pos
== protoPrefix
.psize()) {
446 debugs(58, 3, "HttpReply::sanityCheckStartLine: missing protocol version numbers (ie. " << protoPrefix
<< "/1.0) in '" << buf
->content() << "'");
447 *error
= Http::scInvalidHeader
;
452 // skip arbitrary number of spaces...
453 while (pos
<= buf
->contentSize() && (char)*(buf
->content()+pos
) == ' ') ++pos
;
455 if (pos
< buf
->contentSize() && !xisdigit(*(buf
->content()+pos
))) {
456 debugs(58, 3, "HttpReply::sanityCheckStartLine: missing or invalid status number in '" << buf
->content() << "'");
457 *error
= Http::scInvalidHeader
;
465 HttpReply::parseFirstLine(const char *blk_start
, const char *blk_end
)
467 return sline
.parse(protoPrefix
, blk_start
, blk_end
);
470 /* handy: resets and returns -1 */
472 HttpReply::httpMsgParseError()
474 int result(HttpMsg::httpMsgParseError());
475 /* indicate an error in the status line */
476 sline
.set(Http::ProtocolVersion(), Http::scInvalidHeader
);
481 * Indicate whether or not we would usually expect an entity-body
482 * along with this response
485 HttpReply::expectingBody(const HttpRequestMethod
& req_method
, int64_t& theSize
) const
487 bool expectBody
= true;
489 if (req_method
== Http::METHOD_HEAD
)
491 else if (sline
.status() == Http::scNoContent
)
493 else if (sline
.status() == Http::scNotModified
)
495 else if (sline
.status() < Http::scOkay
)
501 if (header
.chunked())
503 else if (content_length
>= 0)
504 theSize
= content_length
;
513 HttpReply::receivedBodyTooLarge(HttpRequest
& request
, int64_t receivedSize
)
515 calcMaxBodySize(request
);
516 debugs(58, 3, HERE
<< receivedSize
<< " >? " << bodySizeMax
);
517 return bodySizeMax
>= 0 && receivedSize
> bodySizeMax
;
521 HttpReply::expectedBodyTooLarge(HttpRequest
& request
)
523 calcMaxBodySize(request
);
524 debugs(58, 7, HERE
<< "bodySizeMax=" << bodySizeMax
);
526 if (bodySizeMax
< 0) // no body size limit
529 int64_t expectedSize
= -1;
530 if (!expectingBody(request
.method
, expectedSize
))
533 debugs(58, 6, HERE
<< expectedSize
<< " >? " << bodySizeMax
);
535 if (expectedSize
< 0) // expecting body of an unknown length
538 return expectedSize
> bodySizeMax
;
542 HttpReply::calcMaxBodySize(HttpRequest
& request
) const
544 // hack: -2 is used as "we have not calculated max body size yet" state
545 if (bodySizeMax
!= -2) // already tried
549 // short-circuit ACL testing if there are none configured
550 if (!Config
.ReplyBodySize
)
553 ACLFilledChecklist
ch(NULL
, &request
, NULL
);
554 // XXX: cont-cast becomes irrelevant when checklist is HttpReply::Pointer
555 ch
.reply
= const_cast<HttpReply
*>(this);
556 HTTPMSGLOCK(ch
.reply
);
557 for (AclSizeLimit
*l
= Config
.ReplyBodySize
; l
; l
= l
-> next
) {
558 /* if there is no ACL list or if the ACLs listed match use this size value */
559 if (!l
->aclList
|| ch
.fastCheck(l
->aclList
) == ACCESS_ALLOWED
) {
560 debugs(58, 4, HERE
<< "bodySizeMax=" << bodySizeMax
);
561 bodySizeMax
= l
->size
; // may be -1
567 // XXX: check that this is sufficient for eCAP cloning
569 HttpReply::clone() const
571 HttpReply
*rep
= new HttpReply();
572 rep
->sline
= sline
; // used in hdrCacheInit() call below
573 rep
->header
.append(&header
);
575 rep
->hdr_sz
= hdr_sz
;
576 rep
->http_ver
= http_ver
;
577 rep
->pstate
= pstate
;
578 rep
->body_pipe
= body_pipe
;
580 // keep_alive is handled in hdrCacheInit()
584 bool HttpReply::inheritProperties(const HttpMsg
*aMsg
)
586 const HttpReply
*aRep
= dynamic_cast<const HttpReply
*>(aMsg
);
589 keep_alive
= aRep
->keep_alive
;
593 void HttpReply::removeStaleWarnings()
596 if (header
.getList(HDR_WARNING
, &warning
)) {
597 const String newWarning
= removeStaleWarningValues(warning
);
598 if (warning
.size() && warning
.size() == newWarning
.size())
599 return; // some warnings are there and none changed
600 header
.delById(HDR_WARNING
);
601 if (newWarning
.size()) { // some warnings left
602 HttpHeaderEntry
*const e
=
603 new HttpHeaderEntry(HDR_WARNING
, NULL
, newWarning
.termedBuf());
610 * Remove warning-values with warn-date different from Date value from
611 * a single header entry. Returns a string with all valid warning-values.
613 String
HttpReply::removeStaleWarningValues(const String
&value
)
616 const char *item
= 0;
619 while (strListGetItem(&value
, ',', &item
, &len
, &pos
)) {
621 // Does warning-value have warn-date (which contains quoted date)?
622 // We scan backwards, looking for two quoted strings.
623 // warning-value = warn-code SP warn-agent SP warn-text [SP warn-date]
624 const char *p
= item
+ len
- 1;
626 while (p
>= item
&& xisspace(*p
)) --p
; // skip whitespace
628 // warning-value MUST end with quote
629 if (p
>= item
&& *p
== '"') {
630 const char *const warnDateEnd
= p
;
632 while (p
>= item
&& *p
!= '"') --p
; // find the next quote
634 const char *warnDateBeg
= p
+ 1;
636 while (p
>= item
&& xisspace(*p
)) --p
; // skip whitespace
638 if (p
>= item
&& *p
== '"' && warnDateBeg
- p
> 2) {
641 warnDate
.append(warnDateBeg
, warnDateEnd
- warnDateBeg
);
642 const time_t time
= parse_rfc1123(warnDate
.termedBuf());
643 keep
= (time
> 0 && time
== date
); // keep valid and matching date
649 newValue
.append(", ");
650 newValue
.append(item
, len
);