]> git.ipfire.org Git - thirdparty/squid.git/blame - src/HttpReply.cc
Do not send Content-Length in 1xx or 204 responses (#249)
[thirdparty/squid.git] / src / HttpReply.cc
CommitLineData
cb69b4c7 1/*
5b74111a 2 * Copyright (C) 1996-2018 The Squid Software Foundation and contributors
e25c139f 3 *
bbc27441
AJ
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.
cb69b4c7 7 */
8
bbc27441
AJ
9/* DEBUG: section 58 HTTP Reply (Response) */
10
582c2af2 11#include "squid.h"
1328cfb7 12#include "acl/AclSizeLimit.h"
582c2af2 13#include "acl/FilledChecklist.h"
81ab22b6 14#include "base/EnumIterator.h"
582c2af2 15#include "globals.h"
4f1c93a7 16#include "http/ContentLengthInterpreter.h"
0521f8be 17#include "HttpBody.h"
7ebe76de 18#include "HttpHdrCc.h"
582c2af2 19#include "HttpHdrContRange.h"
25b6a907 20#include "HttpHdrSc.h"
582c2af2 21#include "HttpReply.h"
0667cbfb 22#include "HttpRequest.h"
0eb49b6d 23#include "MemBuf.h"
4d5904f7 24#include "SquidConfig.h"
582c2af2
FC
25#include "SquidTime.h"
26#include "Store.h"
28204b3b 27#include "StrList.h"
cb69b4c7 28
8341f96d
EB
29HttpReply::HttpReply():
30 Http::Message(hoReply),
31 date(0),
32 last_modified(0),
33 expires(0),
34 surrogate_control(nullptr),
35 keep_alive(0),
36 protoPrefix("HTTP/"),
37 bodySizeMax(-2),
38 content_range(nullptr)
cb69b4c7 39{
06a5ae20 40 init();
cb69b4c7 41}
42
06a5ae20 43HttpReply::~HttpReply()
cb69b4c7 44{
06a5ae20 45 if (do_clean)
46 clean();
cb69b4c7 47}
48
49void
06a5ae20 50HttpReply::init()
cb69b4c7 51{
06a5ae20 52 hdrCacheInit();
9b769c67 53 sline.init();
fb654382 54 pstate = Http::Message::psReadyToParseStartLine;
06a5ae20 55 do_clean = true;
cb69b4c7 56}
57
06a5ae20 58void HttpReply::reset()
cb69b4c7 59{
06a5ae20 60
8596962e 61 // reset should not reset the protocol; could have made protoPrefix a
62 // virtual function instead, but it is not clear whether virtual methods
63 // are allowed with MEMPROXY_CLASS() and whether some cbdata void*
64 // conversions are not going to kill virtual tables
30abd221 65 const String pfx = protoPrefix;
06a5ae20 66 clean();
67 init();
68 protoPrefix = pfx;
69}
70
71void
72HttpReply::clean()
73{
26ac0430 74 // we used to assert that the pipe is NULL, but now the message only
5f8252d2 75 // points to a pipe that is owned and initiated by another object.
76 body_pipe = NULL;
77
0521f8be 78 body.clear();
06a5ae20 79 hdrCacheClean();
519e0948 80 header.clean();
9b769c67 81 sline.clean();
0667cbfb 82 bodySizeMax = -2; // hack: make calculatedBodySizeMax() false
cb69b4c7 83}
84
cb69b4c7 85void
f8432432 86HttpReply::packHeadersUsingFastPacker(Packable &p) const
cb69b4c7 87{
f8432432
EB
88 sline.packInto(&p);
89 header.packInto(&p);
90 p.append("\r\n", 2);
528b2c61 91}
92
93void
f8432432 94HttpReply::packHeadersUsingSlowPacker(Packable &p) const
528b2c61 95{
f8432432
EB
96 MemBuf buf;
97 buf.init();
98 packHeadersUsingFastPacker(buf);
99 p.append(buf.content(), buf.contentSize());
100}
101
102void
103HttpReply::packInto(MemBuf &buf) const
104{
105 packHeadersUsingFastPacker(buf);
106 body.packInto(&buf);
cb69b4c7 107}
108
06a5ae20 109/* create memBuf, create mem-based packer, pack, destroy packer, return MemBuf */
032785bf 110MemBuf *
1f28a150 111HttpReply::pack() const
cb69b4c7 112{
032785bf 113 MemBuf *mb = new MemBuf;
2fe7eff9 114 mb->init();
f8432432 115 packInto(*mb);
cb69b4c7 116 return mb;
117}
118
528b2c61 119HttpReply *
11992b6f 120HttpReply::make304() const
cb69b4c7 121{
789217a2 122 static const Http::HdrType ImsEntries[] = {Http::HdrType::DATE, Http::HdrType::CONTENT_TYPE, Http::HdrType::EXPIRES, Http::HdrType::LAST_MODIFIED, /* eof */ Http::HdrType::OTHER};
62e76326 123
06a5ae20 124 HttpReply *rv = new HttpReply;
728da2ee 125 int t;
de336bbe 126 HttpHeaderEntry *e;
cb69b4c7 127
528b2c61 128 /* rv->content_length; */
06a5ae20 129 rv->date = date;
130 rv->last_modified = last_modified;
131 rv->expires = expires;
132 rv->content_type = content_type;
528b2c61 133 /* rv->content_range */
134 /* rv->keep_alive */
2592bc70 135 rv->sline.set(Http::ProtocolVersion(), Http::scNotModified, NULL);
62e76326 136
81ab22b6 137 for (t = 0; ImsEntries[t] != Http::HdrType::OTHER; ++t) {
a9925b40 138 if ((e = header.findEntry(ImsEntries[t])))
eede25e7 139 rv->header.addEntry(e->clone());
81ab22b6 140 }
62e76326 141
0c90d3b1
DD
142 rv->putCc(cache_control);
143
528b2c61 144 /* rv->body */
145 return rv;
146}
147
032785bf 148MemBuf *
1f28a150 149HttpReply::packed304Reply() const
528b2c61 150{
151 /* Not as efficient as skipping the header duplication,
152 * but easier to maintain
153 */
871c031f 154 HttpReply *temp = make304();
06a5ae20 155 MemBuf *rv = temp->pack();
156 delete temp;
528b2c61 157 return rv;
cb69b4c7 158}
159
160void
955394ce 161HttpReply::setHeaders(Http::StatusCode status, const char *reason,
350e2aec 162 const char *ctype, int64_t clen, time_t lmt, time_t expiresTime)
cb69b4c7 163{
164 HttpHeader *hdr;
2592bc70 165 sline.set(Http::ProtocolVersion(), status, reason);
06a5ae20 166 hdr = &header;
789217a2
FC
167 hdr->putStr(Http::HdrType::SERVER, visible_appname_string);
168 hdr->putStr(Http::HdrType::MIME_VERSION, "1.0");
169 hdr->putTime(Http::HdrType::DATE, squid_curtime);
62e76326 170
d8b249ef 171 if (ctype) {
789217a2 172 hdr->putStr(Http::HdrType::CONTENT_TYPE, ctype);
06a5ae20 173 content_type = ctype;
d8b249ef 174 } else
30abd221 175 content_type = String();
62e76326 176
de336bbe 177 if (clen >= 0)
789217a2 178 hdr->putInt64(Http::HdrType::CONTENT_LENGTH, clen);
62e76326 179
350e2aec 180 if (expiresTime >= 0)
789217a2 181 hdr->putTime(Http::HdrType::EXPIRES, expiresTime);
62e76326 182
f53969cc 183 if (lmt > 0) /* this used to be lmt != 0 @?@ */
789217a2 184 hdr->putTime(Http::HdrType::LAST_MODIFIED, lmt);
62e76326 185
06a5ae20 186 date = squid_curtime;
62e76326 187
06a5ae20 188 content_length = clen;
62e76326 189
350e2aec 190 expires = expiresTime;
62e76326 191
06a5ae20 192 last_modified = lmt;
cb69b4c7 193}
194
6d38ef86 195void
955394ce 196HttpReply::redirect(Http::StatusCode status, const char *loc)
6d38ef86 197{
198 HttpHeader *hdr;
2592bc70 199 sline.set(Http::ProtocolVersion(), status, NULL);
06a5ae20 200 hdr = &header;
789217a2
FC
201 hdr->putStr(Http::HdrType::SERVER, APP_FULLNAME);
202 hdr->putTime(Http::HdrType::DATE, squid_curtime);
203 hdr->putInt64(Http::HdrType::CONTENT_LENGTH, 0);
204 hdr->putStr(Http::HdrType::LOCATION, loc);
06a5ae20 205 date = squid_curtime;
206 content_length = 0;
6d38ef86 207}
208
528b2c61 209/* compare the validators of two replies.
210 * 1 = they match
211 * 0 = they do not match
212 */
213int
06a5ae20 214HttpReply::validatorsMatch(HttpReply const * otherRep) const
62e76326 215{
30abd221 216 String one,two;
06a5ae20 217 assert (otherRep);
528b2c61 218 /* Numbers first - easiest to check */
219 /* Content-Length */
220 /* TODO: remove -1 bypass */
62e76326 221
06a5ae20 222 if (content_length != otherRep->content_length
223 && content_length > -1 &&
62e76326 224 otherRep->content_length > -1)
225 return 0;
226
528b2c61 227 /* ETag */
789217a2 228 one = header.getStrOrList(Http::HdrType::ETAG);
62e76326 229
789217a2 230 two = otherRep->header.getStrOrList(Http::HdrType::ETAG);
62e76326 231
a1377698 232 if (one.size()==0 || two.size()==0 || one.caseCmp(two)!=0 ) {
30abd221 233 one.clean();
234 two.clean();
62e76326 235 return 0;
528b2c61 236 }
62e76326 237
06a5ae20 238 if (last_modified != otherRep->last_modified)
62e76326 239 return 0;
240
528b2c61 241 /* MD5 */
789217a2 242 one = header.getStrOrList(Http::HdrType::CONTENT_MD5);
62e76326 243
789217a2 244 two = otherRep->header.getStrOrList(Http::HdrType::CONTENT_MD5);
62e76326 245
a1377698 246 if (one.size()==0 || two.size()==0 || one.caseCmp(two)!=0 ) {
30abd221 247 one.clean();
248 two.clean();
62e76326 249 return 0;
528b2c61 250 }
62e76326 251
528b2c61 252 return 1;
253}
254
1a210de4 255bool
06a5ae20 256HttpReply::updateOnNotModified(HttpReply const * freshRep)
cb69b4c7 257{
07947ad8 258 assert(freshRep);
1d7ab0f4 259
1a210de4
EB
260 /* update raw headers */
261 if (!header.update(&freshRep->header))
262 return false;
263
d8b249ef 264 /* clean cache */
06a5ae20 265 hdrCacheClean();
1d7ab0f4 266
394499bd 267 header.compact();
d8b249ef 268 /* init cache */
07947ad8 269 hdrCacheInit();
1a210de4
EB
270
271 return true;
cb69b4c7 272}
273
d8b249ef 274/* internal routines */
cb69b4c7 275
06a5ae20 276time_t
277HttpReply::hdrExpirationTime()
d20b1cd0 278{
279 /* The s-maxage and max-age directive takes priority over Expires */
62e76326 280
06a5ae20 281 if (cache_control) {
810d879f
EB
282 int maxAge = -1;
283 /*
284 * Conservatively handle the case when we have a max-age
285 * header, but no Date for reference?
286 */
287 if (cache_control->hasSMaxAge(&maxAge) || cache_control->hasMaxAge(&maxAge))
288 return (date >= 0) ? date + maxAge : squid_curtime;
d20b1cd0 289 }
62e76326 290
f66a9ef4 291 if (Config.onoff.vary_ignore_expire &&
789217a2
FC
292 header.has(Http::HdrType::VARY)) {
293 const time_t d = header.getTime(Http::HdrType::DATE);
294 const time_t e = header.getTime(Http::HdrType::EXPIRES);
62e76326 295
296 if (d == e)
297 return -1;
f66a9ef4 298 }
62e76326 299
789217a2
FC
300 if (header.has(Http::HdrType::EXPIRES)) {
301 const time_t e = header.getTime(Http::HdrType::EXPIRES);
62e76326 302 /*
303 * HTTP/1.0 says that robust implementations should consider
304 * bad or malformed Expires header as equivalent to "expires
305 * immediately."
306 */
307 return e < 0 ? squid_curtime : e;
d20b1cd0 308 }
62e76326 309
d20b1cd0 310 return -1;
311}
312
d8b249ef 313/* sync this routine when you update HttpReply struct */
8596962e 314void
07947ad8 315HttpReply::hdrCacheInit()
cb69b4c7 316{
63df1d28 317 Http::Message::hdrCacheInit();
07947ad8 318
4a1acc56 319 http_ver = sline.version;
789217a2
FC
320 content_length = header.getInt64(Http::HdrType::CONTENT_LENGTH);
321 date = header.getTime(Http::HdrType::DATE);
322 last_modified = header.getTime(Http::HdrType::LAST_MODIFIED);
a9925b40 323 surrogate_control = header.getSc();
8341f96d
EB
324 content_range = (sline.status() == Http::scPartialContent) ?
325 header.getContRange() : nullptr;
4a1acc56 326 keep_alive = persistent() ? 1 : 0;
789217a2 327 const char *str = header.getStr(Http::HdrType::CONTENT_TYPE);
62e76326 328
d8b249ef 329 if (str)
07947ad8 330 content_type.limitInit(str, strcspn(str, ";\t "));
d8b249ef 331 else
30abd221 332 content_type = String();
62e76326 333
d20b1cd0 334 /* be sure to set expires after date and cache-control */
06a5ae20 335 expires = hdrExpirationTime();
cb69b4c7 336}
337
8341f96d
EB
338const HttpHdrContRange *
339HttpReply::contentRange() const
340{
341 assert(!content_range || sline.status() == Http::scPartialContent);
342 return content_range;
343}
344
d8b249ef 345/* sync this routine when you update HttpReply struct */
06a5ae20 346void
347HttpReply::hdrCacheClean()
2ac76861 348{
30abd221 349 content_type.clean();
62e76326 350
06a5ae20 351 if (cache_control) {
3d7782c1 352 delete cache_control;
06a5ae20 353 cache_control = NULL;
07947ad8 354 }
62e76326 355
06a5ae20 356 if (surrogate_control) {
45a58345 357 delete surrogate_control;
06a5ae20 358 surrogate_control = NULL;
07947ad8 359 }
43ae1d95 360
06a5ae20 361 if (content_range) {
3c670b50 362 delete content_range;
06a5ae20 363 content_range = NULL;
07947ad8 364 }
63259c34 365}
cb69b4c7 366
35282fbf 367/*
368 * Returns the body size of a HTTP response
369 */
47f6e231 370int64_t
60745f24 371HttpReply::bodySize(const HttpRequestMethod& method) const
35282fbf 372{
06a5ae20 373 if (sline.version.major < 1)
1bda350e 374 return -1;
c2a7cefd 375 else if (method.id() == Http::METHOD_HEAD)
62e76326 376 return 0;
9b769c67 377 else if (sline.status() == Http::scOkay)
f53969cc 378 (void) 0; /* common case, continue */
9b769c67 379 else if (sline.status() == Http::scNoContent)
62e76326 380 return 0;
9b769c67 381 else if (sline.status() == Http::scNotModified)
62e76326 382 return 0;
9b769c67 383 else if (sline.status() < Http::scOkay)
62e76326 384 return 0;
385
06a5ae20 386 return content_length;
35282fbf 387}
8596962e 388
96ee497f
AJ
389/**
390 * Checks the first line of an HTTP Reply is valid.
391 * currently only checks "HTTP/" exists.
392 *
393 * NP: not all error cases are detected yet. Some are left for detection later in parse.
394 */
395bool
84ae6223 396HttpReply::sanityCheckStartLine(const char *buf, const size_t hdr_len, Http::StatusCode *error)
8596962e 397{
96ee497f
AJ
398 // hack warning: using psize instead of size here due to type mismatches with MemBuf.
399
400 // content is long enough to possibly hold a reply
401 // 4 being magic size of a 3-digit number plus space delimiter
84ae6223 402 if (hdr_len < (size_t)(protoPrefix.psize() + 4)) {
0246f6b8 403 if (hdr_len > 0) {
84ae6223 404 debugs(58, 3, "Too small reply header (" << hdr_len << " bytes)");
955394ce 405 *error = Http::scInvalidHeader;
0246f6b8 406 }
96ee497f
AJ
407 return false;
408 }
409
e77d7ef0 410 int pos;
96ee497f 411 // catch missing or mismatched protocol identifier
e77d7ef0 412 // allow special-case for ICY protocol (non-HTTP identifier) in response to faked HTTP request.
84ae6223 413 if (strncmp(buf, "ICY", 3) == 0) {
e77d7ef0
AJ
414 protoPrefix = "ICY";
415 pos = protoPrefix.psize();
dd20bfd3 416 } else {
8596962e 417
84ae6223
AJ
418 if (protoPrefix.cmp(buf, protoPrefix.size()) != 0) {
419 debugs(58, 3, "missing protocol prefix (" << protoPrefix << ") in '" << buf << "'");
955394ce 420 *error = Http::scInvalidHeader;
e77d7ef0
AJ
421 return false;
422 }
96ee497f 423
e77d7ef0
AJ
424 // catch missing or negative status value (negative '-' is not a digit)
425 pos = protoPrefix.psize();
dd20bfd3 426
e77d7ef0 427 // skip arbitrary number of digits and a dot in the verion portion
84ae6223 428 while ((size_t)pos <= hdr_len && (*(buf+pos) == '.' || xisdigit(*(buf+pos)) ) ) ++pos;
96ee497f 429
e77d7ef0
AJ
430 // catch missing version info
431 if (pos == protoPrefix.psize()) {
84ae6223 432 debugs(58, 3, "missing protocol version numbers (ie. " << protoPrefix << "/1.0) in '" << buf << "'");
955394ce 433 *error = Http::scInvalidHeader;
e77d7ef0
AJ
434 return false;
435 }
96ee497f
AJ
436 }
437
438 // skip arbitrary number of spaces...
84ae6223 439 while ((size_t)pos <= hdr_len && (char)*(buf+pos) == ' ') ++pos;
96ee497f 440
84ae6223
AJ
441 if ((size_t)pos < hdr_len && !xisdigit(*(buf+pos))) {
442 debugs(58, 3, "missing or invalid status number in '" << buf << "'");
955394ce 443 *error = Http::scInvalidHeader;
96ee497f
AJ
444 return false;
445 }
446
8596962e 447 return true;
448}
449
9b769c67
AJ
450bool
451HttpReply::parseFirstLine(const char *blk_start, const char *blk_end)
429f7150 452{
9b769c67 453 return sline.parse(protoPrefix, blk_start, blk_end);
429f7150 454}
5c09dcb8 455
4f1c93a7
EB
456void
457HttpReply::configureContentLengthInterpreter(Http::ContentLengthInterpreter &interpreter)
458{
459 interpreter.applyStatusCodeRules(sline.status());
460}
461
462bool
463HttpReply::parseHeader(Http1::Parser &hp)
464{
465 Http::ContentLengthInterpreter clen;
466 return Message::parseHeader(hp, clen);
467}
468
fb525683 469/* handy: resets and returns -1 */
470int
471HttpReply::httpMsgParseError()
472{
63df1d28 473 int result(Http::Message::httpMsgParseError());
fb525683 474 /* indicate an error in the status line */
2592bc70 475 sline.set(Http::ProtocolVersion(), Http::scInvalidHeader);
fb525683 476 return result;
477}
478
5c09dcb8 479/*
480 * Indicate whether or not we would usually expect an entity-body
481 * along with this response
482 */
483bool
60745f24 484HttpReply::expectingBody(const HttpRequestMethod& req_method, int64_t& theSize) const
5c09dcb8 485{
486 bool expectBody = true;
487
c2a7cefd 488 if (req_method == Http::METHOD_HEAD)
5c09dcb8 489 expectBody = false;
9b769c67 490 else if (sline.status() == Http::scNoContent)
5c09dcb8 491 expectBody = false;
9b769c67 492 else if (sline.status() == Http::scNotModified)
5c09dcb8 493 expectBody = false;
9b769c67 494 else if (sline.status() < Http::scOkay)
5c09dcb8 495 expectBody = false;
496 else
497 expectBody = true;
498
499 if (expectBody) {
c3d0ba0c 500 if (header.chunked())
5c09dcb8 501 theSize = -1;
502 else if (content_length >= 0)
503 theSize = content_length;
504 else
505 theSize = -1;
506 }
507
508 return expectBody;
509}
0667cbfb 510
511bool
512HttpReply::receivedBodyTooLarge(HttpRequest& request, int64_t receivedSize)
513{
514 calcMaxBodySize(request);
515 debugs(58, 3, HERE << receivedSize << " >? " << bodySizeMax);
516 return bodySizeMax >= 0 && receivedSize > bodySizeMax;
517}
518
519bool
520HttpReply::expectedBodyTooLarge(HttpRequest& request)
521{
522 calcMaxBodySize(request);
523 debugs(58, 7, HERE << "bodySizeMax=" << bodySizeMax);
524
525 if (bodySizeMax < 0) // no body size limit
526 return false;
527
528 int64_t expectedSize = -1;
529 if (!expectingBody(request.method, expectedSize))
530 return false;
26ac0430 531
0667cbfb 532 debugs(58, 6, HERE << expectedSize << " >? " << bodySizeMax);
533
534 if (expectedSize < 0) // expecting body of an unknown length
535 return false;
536
537 return expectedSize > bodySizeMax;
538}
539
540void
b248c2a3 541HttpReply::calcMaxBodySize(HttpRequest& request) const
0667cbfb 542{
543 // hack: -2 is used as "we have not calculated max body size yet" state
544 if (bodySizeMax != -2) // already tried
545 return;
546 bodySizeMax = -1;
547
4194f58d
AJ
548 // short-circuit ACL testing if there are none configured
549 if (!Config.ReplyBodySize)
550 return;
551
c0941a6a 552 ACLFilledChecklist ch(NULL, &request, NULL);
b248c2a3
AJ
553 // XXX: cont-cast becomes irrelevant when checklist is HttpReply::Pointer
554 ch.reply = const_cast<HttpReply *>(this);
555 HTTPMSGLOCK(ch.reply);
1328cfb7 556 for (AclSizeLimit *l = Config.ReplyBodySize; l; l = l -> next) {
b50e327b 557 /* if there is no ACL list or if the ACLs listed match use this size value */
06bf5384 558 if (!l->aclList || ch.fastCheck(l->aclList).allowed()) {
0667cbfb 559 debugs(58, 4, HERE << "bodySizeMax=" << bodySizeMax);
560 bodySizeMax = l->size; // may be -1
561 break;
562 }
563 }
564}
da33c835 565
fa0e6114 566// XXX: check that this is sufficient for eCAP cloning
da33c835
HN
567HttpReply *
568HttpReply::clone() const
569{
570 HttpReply *rep = new HttpReply();
66363092 571 rep->sline = sline; // used in hdrCacheInit() call below
da33c835
HN
572 rep->header.append(&header);
573 rep->hdrCacheInit();
574 rep->hdr_sz = hdr_sz;
f230832f
HN
575 rep->http_ver = http_ver;
576 rep->pstate = pstate;
fa0e6114
AR
577 rep->body_pipe = body_pipe;
578
66363092 579 // keep_alive is handled in hdrCacheInit()
da33c835
HN
580 return rep;
581}
d67acb4e 582
63df1d28
AJ
583bool
584HttpReply::inheritProperties(const Http::Message *aMsg)
d67acb4e
AJ
585{
586 const HttpReply *aRep = dynamic_cast<const HttpReply*>(aMsg);
26ac0430
AJ
587 if (!aRep)
588 return false;
d67acb4e 589 keep_alive = aRep->keep_alive;
88df846b 590 sources = aRep->sources;
d67acb4e
AJ
591 return true;
592}
c679653d
AR
593
594void HttpReply::removeStaleWarnings()
595{
596 String warning;
789217a2 597 if (header.getList(Http::HdrType::WARNING, &warning)) {
c679653d
AR
598 const String newWarning = removeStaleWarningValues(warning);
599 if (warning.size() && warning.size() == newWarning.size())
600 return; // some warnings are there and none changed
789217a2 601 header.delById(Http::HdrType::WARNING);
c679653d
AR
602 if (newWarning.size()) { // some warnings left
603 HttpHeaderEntry *const e =
d5f18517 604 new HttpHeaderEntry(Http::HdrType::WARNING, SBuf(), newWarning.termedBuf());
c679653d
AR
605 header.addEntry(e);
606 }
607 }
608}
609
610/**
611 * Remove warning-values with warn-date different from Date value from
612 * a single header entry. Returns a string with all valid warning-values.
613 */
614String HttpReply::removeStaleWarningValues(const String &value)
615{
616 String newValue;
617 const char *item = 0;
618 int len = 0;
619 const char *pos = 0;
620 while (strListGetItem(&value, ',', &item, &len, &pos)) {
621 bool keep = true;
622 // Does warning-value have warn-date (which contains quoted date)?
623 // We scan backwards, looking for two quoted strings.
624 // warning-value = warn-code SP warn-agent SP warn-text [SP warn-date]
625 const char *p = item + len - 1;
626
627 while (p >= item && xisspace(*p)) --p; // skip whitespace
628
629 // warning-value MUST end with quote
630 if (p >= item && *p == '"') {
631 const char *const warnDateEnd = p;
632 --p;
633 while (p >= item && *p != '"') --p; // find the next quote
634
635 const char *warnDateBeg = p + 1;
636 --p;
637 while (p >= item && xisspace(*p)) --p; // skip whitespace
638
639 if (p >= item && *p == '"' && warnDateBeg - p > 2) {
640 // found warn-text
641 String warnDate;
642 warnDate.append(warnDateBeg, warnDateEnd - warnDateBeg);
643 const time_t time = parse_rfc1123(warnDate.termedBuf());
644 keep = (time > 0 && time == date); // keep valid and matching date
645 }
646 }
647
648 if (keep) {
649 if (newValue.size())
650 newValue.append(", ");
651 newValue.append(item, len);
652 }
653 }
654
655 return newValue;
656}
f53969cc 657
eace013e
EB
658bool
659HttpReply::olderThan(const HttpReply *them) const
660{
661 if (!them || !them->date || !date)
662 return false;
663 return date < them->date;
664}
665
4f1c93a7
EB
666void
667HttpReply::removeIrrelevantContentLength() {
668 if (Http::ProhibitsContentLength(sline.status()))
669 if (header.delById(Http::HdrType::CONTENT_LENGTH))
670 debugs(58, 3, "Removing unexpected Content-Length header");
671}
672