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