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