]>
git.ipfire.org Git - thirdparty/squid.git/blob - src/HttpReply.cc
2 * Copyright (C) 1996-2017 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"
14 #include "base/EnumIterator.h"
17 #include "HttpHdrCc.h"
18 #include "HttpHdrContRange.h"
19 #include "HttpHdrSc.h"
20 #include "HttpReply.h"
21 #include "HttpRequest.h"
23 #include "SquidConfig.h"
24 #include "SquidTime.h"
28 HttpReply::HttpReply() : Http::Message(hoReply
), date (0), last_modified (0),
29 expires (0), surrogate_control (NULL
), content_range (NULL
), keep_alive (0),
30 protoPrefix("HTTP/"), bodySizeMax(-2)
35 HttpReply::~HttpReply()
46 pstate
= Http::Message::psReadyToParseStartLine
;
50 void HttpReply::reset()
53 // reset should not reset the protocol; could have made protoPrefix a
54 // virtual function instead, but it is not clear whether virtual methods
55 // are allowed with MEMPROXY_CLASS() and whether some cbdata void*
56 // conversions are not going to kill virtual tables
57 const String pfx
= protoPrefix
;
66 // we used to assert that the pipe is NULL, but now the message only
67 // points to a pipe that is owned and initiated by another object.
74 bodySizeMax
= -2; // hack: make calculatedBodySizeMax() false
78 HttpReply::packHeadersInto(Packable
* p
) const
86 HttpReply::packInto(Packable
* p
) const
92 /* create memBuf, create mem-based packer, pack, destroy packer, return MemBuf */
94 HttpReply::pack() const
96 MemBuf
*mb
= new MemBuf
;
103 HttpReply::make304() const
105 static const Http::HdrType ImsEntries
[] = {Http::HdrType::DATE
, Http::HdrType::CONTENT_TYPE
, Http::HdrType::EXPIRES
, Http::HdrType::LAST_MODIFIED
, /* eof */ Http::HdrType::OTHER
};
107 HttpReply
*rv
= new HttpReply
;
111 /* rv->content_length; */
113 rv
->last_modified
= last_modified
;
114 rv
->expires
= expires
;
115 rv
->content_type
= content_type
;
116 /* rv->content_range */
118 rv
->sline
.set(Http::ProtocolVersion(), Http::scNotModified
, NULL
);
120 for (t
= 0; ImsEntries
[t
] != Http::HdrType::OTHER
; ++t
) {
121 if ((e
= header
.findEntry(ImsEntries
[t
])))
122 rv
->header
.addEntry(e
->clone());
125 rv
->putCc(cache_control
);
132 HttpReply::packed304Reply() const
134 /* Not as efficient as skipping the header duplication,
135 * but easier to maintain
137 HttpReply
*temp
= make304();
138 MemBuf
*rv
= temp
->pack();
144 HttpReply::setHeaders(Http::StatusCode status
, const char *reason
,
145 const char *ctype
, int64_t clen
, time_t lmt
, time_t expiresTime
)
148 sline
.set(Http::ProtocolVersion(), status
, reason
);
150 hdr
->putStr(Http::HdrType::SERVER
, visible_appname_string
);
151 hdr
->putStr(Http::HdrType::MIME_VERSION
, "1.0");
152 hdr
->putTime(Http::HdrType::DATE
, squid_curtime
);
155 hdr
->putStr(Http::HdrType::CONTENT_TYPE
, ctype
);
156 content_type
= ctype
;
158 content_type
= String();
161 hdr
->putInt64(Http::HdrType::CONTENT_LENGTH
, clen
);
163 if (expiresTime
>= 0)
164 hdr
->putTime(Http::HdrType::EXPIRES
, expiresTime
);
166 if (lmt
> 0) /* this used to be lmt != 0 @?@ */
167 hdr
->putTime(Http::HdrType::LAST_MODIFIED
, lmt
);
169 date
= squid_curtime
;
171 content_length
= clen
;
173 expires
= expiresTime
;
179 HttpReply::redirect(Http::StatusCode status
, const char *loc
)
182 sline
.set(Http::ProtocolVersion(), status
, NULL
);
184 hdr
->putStr(Http::HdrType::SERVER
, APP_FULLNAME
);
185 hdr
->putTime(Http::HdrType::DATE
, squid_curtime
);
186 hdr
->putInt64(Http::HdrType::CONTENT_LENGTH
, 0);
187 hdr
->putStr(Http::HdrType::LOCATION
, loc
);
188 date
= squid_curtime
;
192 /* compare the validators of two replies.
194 * 0 = they do not match
197 HttpReply::validatorsMatch(HttpReply
const * otherRep
) const
201 /* Numbers first - easiest to check */
203 /* TODO: remove -1 bypass */
205 if (content_length
!= otherRep
->content_length
206 && content_length
> -1 &&
207 otherRep
->content_length
> -1)
211 one
= header
.getStrOrList(Http::HdrType::ETAG
);
213 two
= otherRep
->header
.getStrOrList(Http::HdrType::ETAG
);
215 if (one
.size()==0 || two
.size()==0 || one
.caseCmp(two
)!=0 ) {
221 if (last_modified
!= otherRep
->last_modified
)
225 one
= header
.getStrOrList(Http::HdrType::CONTENT_MD5
);
227 two
= otherRep
->header
.getStrOrList(Http::HdrType::CONTENT_MD5
);
229 if (one
.size()==0 || two
.size()==0 || one
.caseCmp(two
)!=0 ) {
239 HttpReply::updateOnNotModified(HttpReply
const * freshRep
)
243 /* update raw headers */
244 if (!header
.update(&freshRep
->header
))
257 /* internal routines */
260 HttpReply::hdrExpirationTime()
262 /* The s-maxage and max-age directive takes priority over Expires */
266 if (cache_control
->hasSMaxAge())
267 return date
+ cache_control
->sMaxAge();
269 if (cache_control
->hasMaxAge())
270 return date
+ cache_control
->maxAge();
273 * Conservatively handle the case when we have a max-age
274 * header, but no Date for reference?
277 if (cache_control
->hasSMaxAge())
278 return squid_curtime
;
280 if (cache_control
->hasMaxAge())
281 return squid_curtime
;
285 if (Config
.onoff
.vary_ignore_expire
&&
286 header
.has(Http::HdrType::VARY
)) {
287 const time_t d
= header
.getTime(Http::HdrType::DATE
);
288 const time_t e
= header
.getTime(Http::HdrType::EXPIRES
);
294 if (header
.has(Http::HdrType::EXPIRES
)) {
295 const time_t e
= header
.getTime(Http::HdrType::EXPIRES
);
297 * HTTP/1.0 says that robust implementations should consider
298 * bad or malformed Expires header as equivalent to "expires
301 return e
< 0 ? squid_curtime
: e
;
307 /* sync this routine when you update HttpReply struct */
309 HttpReply::hdrCacheInit()
311 Http::Message::hdrCacheInit();
313 http_ver
= sline
.version
;
314 content_length
= header
.getInt64(Http::HdrType::CONTENT_LENGTH
);
315 date
= header
.getTime(Http::HdrType::DATE
);
316 last_modified
= header
.getTime(Http::HdrType::LAST_MODIFIED
);
317 surrogate_control
= header
.getSc();
318 content_range
= header
.getContRange();
319 keep_alive
= persistent() ? 1 : 0;
320 const char *str
= header
.getStr(Http::HdrType::CONTENT_TYPE
);
323 content_type
.limitInit(str
, strcspn(str
, ";\t "));
325 content_type
= String();
327 /* be sure to set expires after date and cache-control */
328 expires
= hdrExpirationTime();
331 /* sync this routine when you update HttpReply struct */
333 HttpReply::hdrCacheClean()
335 content_type
.clean();
338 delete cache_control
;
339 cache_control
= NULL
;
342 if (surrogate_control
) {
343 delete surrogate_control
;
344 surrogate_control
= NULL
;
348 delete content_range
;
349 content_range
= NULL
;
354 * Returns the body size of a HTTP response
357 HttpReply::bodySize(const HttpRequestMethod
& method
) const
359 if (sline
.version
.major
< 1)
361 else if (method
.id() == Http::METHOD_HEAD
)
363 else if (sline
.status() == Http::scOkay
)
364 (void) 0; /* common case, continue */
365 else if (sline
.status() == Http::scNoContent
)
367 else if (sline
.status() == Http::scNotModified
)
369 else if (sline
.status() < Http::scOkay
)
372 return content_length
;
376 * Checks the first line of an HTTP Reply is valid.
377 * currently only checks "HTTP/" exists.
379 * NP: not all error cases are detected yet. Some are left for detection later in parse.
382 HttpReply::sanityCheckStartLine(const char *buf
, const size_t hdr_len
, Http::StatusCode
*error
)
384 // hack warning: using psize instead of size here due to type mismatches with MemBuf.
386 // content is long enough to possibly hold a reply
387 // 4 being magic size of a 3-digit number plus space delimiter
388 if (hdr_len
< (size_t)(protoPrefix
.psize() + 4)) {
390 debugs(58, 3, "Too small reply header (" << hdr_len
<< " bytes)");
391 *error
= Http::scInvalidHeader
;
397 // catch missing or mismatched protocol identifier
398 // allow special-case for ICY protocol (non-HTTP identifier) in response to faked HTTP request.
399 if (strncmp(buf
, "ICY", 3) == 0) {
401 pos
= protoPrefix
.psize();
404 if (protoPrefix
.cmp(buf
, protoPrefix
.size()) != 0) {
405 debugs(58, 3, "missing protocol prefix (" << protoPrefix
<< ") in '" << buf
<< "'");
406 *error
= Http::scInvalidHeader
;
410 // catch missing or negative status value (negative '-' is not a digit)
411 pos
= protoPrefix
.psize();
413 // skip arbitrary number of digits and a dot in the verion portion
414 while ((size_t)pos
<= hdr_len
&& (*(buf
+pos
) == '.' || xisdigit(*(buf
+pos
)) ) ) ++pos
;
416 // catch missing version info
417 if (pos
== protoPrefix
.psize()) {
418 debugs(58, 3, "missing protocol version numbers (ie. " << protoPrefix
<< "/1.0) in '" << buf
<< "'");
419 *error
= Http::scInvalidHeader
;
424 // skip arbitrary number of spaces...
425 while ((size_t)pos
<= hdr_len
&& (char)*(buf
+pos
) == ' ') ++pos
;
427 if ((size_t)pos
< hdr_len
&& !xisdigit(*(buf
+pos
))) {
428 debugs(58, 3, "missing or invalid status number in '" << buf
<< "'");
429 *error
= Http::scInvalidHeader
;
437 HttpReply::parseFirstLine(const char *blk_start
, const char *blk_end
)
439 return sline
.parse(protoPrefix
, blk_start
, blk_end
);
442 /* handy: resets and returns -1 */
444 HttpReply::httpMsgParseError()
446 int result(Http::Message::httpMsgParseError());
447 /* indicate an error in the status line */
448 sline
.set(Http::ProtocolVersion(), Http::scInvalidHeader
);
453 * Indicate whether or not we would usually expect an entity-body
454 * along with this response
457 HttpReply::expectingBody(const HttpRequestMethod
& req_method
, int64_t& theSize
) const
459 bool expectBody
= true;
461 if (req_method
== Http::METHOD_HEAD
)
463 else if (sline
.status() == Http::scNoContent
)
465 else if (sline
.status() == Http::scNotModified
)
467 else if (sline
.status() < Http::scOkay
)
473 if (header
.chunked())
475 else if (content_length
>= 0)
476 theSize
= content_length
;
485 HttpReply::receivedBodyTooLarge(HttpRequest
& request
, int64_t receivedSize
)
487 calcMaxBodySize(request
);
488 debugs(58, 3, HERE
<< receivedSize
<< " >? " << bodySizeMax
);
489 return bodySizeMax
>= 0 && receivedSize
> bodySizeMax
;
493 HttpReply::expectedBodyTooLarge(HttpRequest
& request
)
495 calcMaxBodySize(request
);
496 debugs(58, 7, HERE
<< "bodySizeMax=" << bodySizeMax
);
498 if (bodySizeMax
< 0) // no body size limit
501 int64_t expectedSize
= -1;
502 if (!expectingBody(request
.method
, expectedSize
))
505 debugs(58, 6, HERE
<< expectedSize
<< " >? " << bodySizeMax
);
507 if (expectedSize
< 0) // expecting body of an unknown length
510 return expectedSize
> bodySizeMax
;
514 HttpReply::calcMaxBodySize(HttpRequest
& request
) const
516 // hack: -2 is used as "we have not calculated max body size yet" state
517 if (bodySizeMax
!= -2) // already tried
521 // short-circuit ACL testing if there are none configured
522 if (!Config
.ReplyBodySize
)
525 ACLFilledChecklist
ch(NULL
, &request
, NULL
);
526 // XXX: cont-cast becomes irrelevant when checklist is HttpReply::Pointer
527 ch
.reply
= const_cast<HttpReply
*>(this);
528 HTTPMSGLOCK(ch
.reply
);
529 for (AclSizeLimit
*l
= Config
.ReplyBodySize
; l
; l
= l
-> next
) {
530 /* if there is no ACL list or if the ACLs listed match use this size value */
531 if (!l
->aclList
|| ch
.fastCheck(l
->aclList
) == ACCESS_ALLOWED
) {
532 debugs(58, 4, HERE
<< "bodySizeMax=" << bodySizeMax
);
533 bodySizeMax
= l
->size
; // may be -1
539 // XXX: check that this is sufficient for eCAP cloning
541 HttpReply::clone() const
543 HttpReply
*rep
= new HttpReply();
544 rep
->sline
= sline
; // used in hdrCacheInit() call below
545 rep
->header
.append(&header
);
547 rep
->hdr_sz
= hdr_sz
;
548 rep
->http_ver
= http_ver
;
549 rep
->pstate
= pstate
;
550 rep
->body_pipe
= body_pipe
;
552 // keep_alive is handled in hdrCacheInit()
557 HttpReply::inheritProperties(const Http::Message
*aMsg
)
559 const HttpReply
*aRep
= dynamic_cast<const HttpReply
*>(aMsg
);
562 keep_alive
= aRep
->keep_alive
;
563 sources
= aRep
->sources
;
567 void HttpReply::removeStaleWarnings()
570 if (header
.getList(Http::HdrType::WARNING
, &warning
)) {
571 const String newWarning
= removeStaleWarningValues(warning
);
572 if (warning
.size() && warning
.size() == newWarning
.size())
573 return; // some warnings are there and none changed
574 header
.delById(Http::HdrType::WARNING
);
575 if (newWarning
.size()) { // some warnings left
576 HttpHeaderEntry
*const e
=
577 new HttpHeaderEntry(Http::HdrType::WARNING
, NULL
, newWarning
.termedBuf());
584 * Remove warning-values with warn-date different from Date value from
585 * a single header entry. Returns a string with all valid warning-values.
587 String
HttpReply::removeStaleWarningValues(const String
&value
)
590 const char *item
= 0;
593 while (strListGetItem(&value
, ',', &item
, &len
, &pos
)) {
595 // Does warning-value have warn-date (which contains quoted date)?
596 // We scan backwards, looking for two quoted strings.
597 // warning-value = warn-code SP warn-agent SP warn-text [SP warn-date]
598 const char *p
= item
+ len
- 1;
600 while (p
>= item
&& xisspace(*p
)) --p
; // skip whitespace
602 // warning-value MUST end with quote
603 if (p
>= item
&& *p
== '"') {
604 const char *const warnDateEnd
= p
;
606 while (p
>= item
&& *p
!= '"') --p
; // find the next quote
608 const char *warnDateBeg
= p
+ 1;
610 while (p
>= item
&& xisspace(*p
)) --p
; // skip whitespace
612 if (p
>= item
&& *p
== '"' && warnDateBeg
- p
> 2) {
615 warnDate
.append(warnDateBeg
, warnDateEnd
- warnDateBeg
);
616 const time_t time
= parse_rfc1123(warnDate
.termedBuf());
617 keep
= (time
> 0 && time
== date
); // keep valid and matching date
623 newValue
.append(", ");
624 newValue
.append(item
, len
);
632 HttpReply::olderThan(const HttpReply
*them
) const
634 if (!them
|| !them
->date
|| !date
)
636 return date
< them
->date
;