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