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