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