]> git.ipfire.org Git - thirdparty/squid.git/blob - src/HttpReply.cc
Add string API layer for better string handling.
[thirdparty/squid.git] / src / HttpReply.cc
1
2 /*
3 * $Id: HttpReply.cc,v 1.93 2007/05/18 06:41:22 amosjeffries Exp $
4 *
5 * DEBUG: section 58 HTTP Reply (Response)
6 * AUTHOR: Alex Rousskov
7 *
8 * SQUID Web Proxy Cache http://www.squid-cache.org/
9 * ----------------------------------------------------------
10 *
11 * Squid is the result of efforts by numerous individuals from
12 * the Internet community; see the CONTRIBUTORS file for full
13 * details. Many organizations have provided support for Squid's
14 * development; see the SPONSORS file for full details. Squid is
15 * Copyrighted (C) 2001 by the Regents of the University of
16 * California; see the COPYRIGHT file for full details. Squid
17 * incorporates software developed and/or copyrighted by other
18 * sources; see the CREDITS file for full details.
19 *
20 * This program is free software; you can redistribute it and/or modify
21 * it under the terms of the GNU General Public License as published by
22 * the Free Software Foundation; either version 2 of the License, or
23 * (at your option) any later version.
24 *
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
29 *
30 * You should have received a copy of the GNU General Public License
31 * along with this program; if not, write to the Free Software
32 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
33 *
34 */
35
36 #include "squid.h"
37 #include "SquidTime.h"
38 #include "Store.h"
39 #include "HttpReply.h"
40 #include "HttpHdrContRange.h"
41 #include "HttpHdrSc.h"
42 #include "ACLChecklist.h"
43 #include "MemBuf.h"
44
45 /* local constants */
46
47 /* If we receive a 304 from the origin during a cache revalidation, we must
48 * update the headers of the existing entry. Specifically, we need to update all
49 * end-to-end headers and not any hop-by-hop headers (rfc2616 13.5.3).
50 *
51 * This is not the whole story though: since it is possible for a faulty/malicious
52 * origin server to set headers it should not in a 304, we must explicitly ignore
53 * these too. Specifically all entity-headers except those permitted in a 304
54 * (rfc2616 10.3.5) must be ignored.
55 *
56 * The list of headers we don't update is made up of:
57 * all hop-by-hop headers
58 * all entity-headers except Expires and Content-Location
59 */
60 static HttpHeaderMask Denied304HeadersMask;
61 static http_hdr_type Denied304HeadersArr[] =
62 {
63 // hop-by-hop headers
64 HDR_CONNECTION, HDR_KEEP_ALIVE, HDR_PROXY_AUTHENTICATE, HDR_PROXY_AUTHORIZATION,
65 HDR_TE, HDR_TRAILERS, HDR_TRANSFER_ENCODING, HDR_UPGRADE,
66 // entity headers
67 HDR_ALLOW, HDR_CONTENT_ENCODING, HDR_CONTENT_LANGUAGE, HDR_CONTENT_LENGTH,
68 HDR_CONTENT_MD5, HDR_CONTENT_RANGE, HDR_CONTENT_TYPE, HDR_LAST_MODIFIED
69 };
70
71 /* module initialization */
72 void
73 httpReplyInitModule(void)
74 {
75 assert(HTTP_STATUS_NONE == 0); // HttpReply::parse() interface assumes that
76 httpHeaderMaskInit(&Denied304HeadersMask, 0);
77 httpHeaderCalcMask(&Denied304HeadersMask, Denied304HeadersArr, countof(Denied304HeadersArr));
78 }
79
80 HttpReply::HttpReply() : HttpMsg(hoReply), date (0), last_modified (0), expires (0), surrogate_control (NULL), content_range (NULL), keep_alive (0), protoPrefix("HTTP/")
81 {
82 init();
83 }
84
85 HttpReply::~HttpReply()
86 {
87 if (do_clean)
88 clean();
89 }
90
91 void
92 HttpReply::init()
93 {
94 httpBodyInit(&body);
95 hdrCacheInit();
96 httpStatusLineInit(&sline);
97 pstate = psReadyToParseStartLine;
98 do_clean = true;
99 }
100
101 void HttpReply::reset()
102 {
103
104 // reset should not reset the protocol; could have made protoPrefix a
105 // virtual function instead, but it is not clear whether virtual methods
106 // are allowed with MEMPROXY_CLASS() and whether some cbdata void*
107 // conversions are not going to kill virtual tables
108 const string pfx = protoPrefix;
109 clean();
110 init();
111 protoPrefix = pfx;
112 }
113
114 void
115 HttpReply::clean()
116 {
117 // we used to assert that the pipe is NULL, but now the message only
118 // points to a pipe that is owned and initiated by another object.
119 body_pipe = NULL;
120
121 httpBodyClean(&body);
122 hdrCacheClean();
123 header.clean();
124 httpStatusLineClean(&sline);
125 }
126
127 void
128 HttpReply::packHeadersInto(Packer * p) const
129 {
130 httpStatusLinePackInto(&sline, p);
131 header.packInto(p);
132 packerAppend(p, "\r\n", 2);
133 }
134
135 void
136 HttpReply::packInto(Packer * p)
137 {
138 packHeadersInto(p);
139 httpBodyPackInto(&body, p);
140 }
141
142 /* create memBuf, create mem-based packer, pack, destroy packer, return MemBuf */
143 MemBuf *
144 HttpReply::pack()
145 {
146 MemBuf *mb = new MemBuf;
147 Packer p;
148
149 mb->init();
150 packerToMemInit(&p, mb);
151 packInto(&p);
152 packerClean(&p);
153 return mb;
154 }
155
156 MemBuf *
157 httpPackedReply(HttpVersion ver, http_status status, const char *ctype,
158 int clen, time_t lmt, time_t expires)
159 {
160 HttpReply *rep = new HttpReply;
161 rep->setHeaders(ver, status, ctype, NULL, clen, lmt, expires);
162 MemBuf *mb = rep->pack();
163 delete rep;
164 return mb;
165 }
166
167 HttpReply *
168 HttpReply::make304 () const
169 {
170 static const http_hdr_type ImsEntries[] = {HDR_DATE, HDR_CONTENT_TYPE, HDR_EXPIRES, HDR_LAST_MODIFIED, /* eof */ HDR_OTHER};
171
172 HttpReply *rv = new HttpReply;
173 int t;
174 HttpHeaderEntry *e;
175
176 /* rv->content_length; */
177 rv->date = date;
178 rv->last_modified = last_modified;
179 rv->expires = expires;
180 rv->content_type = content_type;
181 /* rv->cache_control */
182 /* rv->content_range */
183 /* rv->keep_alive */
184 HttpVersion ver(1,0);
185 httpStatusLineSet(&rv->sline, ver,
186 HTTP_NOT_MODIFIED, "");
187
188 for (t = 0; ImsEntries[t] != HDR_OTHER; ++t)
189 if ((e = header.findEntry(ImsEntries[t])))
190 rv->header.addEntry(e->clone());
191
192 /* rv->body */
193 return rv;
194 }
195
196 MemBuf *
197 HttpReply::packed304Reply()
198 {
199 /* Not as efficient as skipping the header duplication,
200 * but easier to maintain
201 */
202 HttpReply *temp = make304 ();
203 MemBuf *rv = temp->pack();
204 delete temp;
205 return rv;
206 }
207
208 void
209 HttpReply::setHeaders(HttpVersion ver, http_status status, const char *reason,
210 const char *ctype, int clen, time_t lmt, time_t expires)
211 {
212 HttpHeader *hdr;
213 httpStatusLineSet(&sline, ver, status, reason);
214 hdr = &header;
215 hdr->putStr(HDR_SERVER, visible_appname_string);
216 hdr->putStr(HDR_MIME_VERSION, "1.0");
217 hdr->putTime(HDR_DATE, squid_curtime);
218
219 if (ctype) {
220 hdr->putStr(HDR_CONTENT_TYPE, ctype);
221 content_type = ctype;
222 } else
223 content_type.clear();
224
225 if (clen >= 0)
226 hdr->putInt(HDR_CONTENT_LENGTH, clen);
227
228 if (expires >= 0)
229 hdr->putTime(HDR_EXPIRES, expires);
230
231 if (lmt > 0) /* this used to be lmt != 0 @?@ */
232 hdr->putTime(HDR_LAST_MODIFIED, lmt);
233
234 date = squid_curtime;
235
236 content_length = clen;
237
238 expires = expires;
239
240 last_modified = lmt;
241 }
242
243 void
244 HttpReply::redirect(http_status status, const char *loc)
245 {
246 HttpHeader *hdr;
247 HttpVersion ver(1,0);
248 httpStatusLineSet(&sline, ver, status, httpStatusString(status));
249 hdr = &header;
250 hdr->putStr(HDR_SERVER, full_appname_string);
251 hdr->putTime(HDR_DATE, squid_curtime);
252 hdr->putInt(HDR_CONTENT_LENGTH, 0);
253 hdr->putStr(HDR_LOCATION, loc);
254 date = squid_curtime;
255 content_length = 0;
256 }
257
258 /* compare the validators of two replies.
259 * 1 = they match
260 * 0 = they do not match
261 */
262 int
263 HttpReply::validatorsMatch(HttpReply const * otherRep) const
264 {
265 string one,two;
266 assert (otherRep);
267 /* Numbers first - easiest to check */
268 /* Content-Length */
269 /* TODO: remove -1 bypass */
270
271 if (content_length != otherRep->content_length
272 && content_length > -1 &&
273 otherRep->content_length > -1)
274 return 0;
275
276 /* ETag */
277 one = header.getStrOrList(HDR_ETAG);
278
279 two = otherRep->header.getStrOrList(HDR_ETAG);
280
281 if (one.empty() || two.empty() || strcasecmp (one, two)) {
282 return 0;
283 }
284
285 if (last_modified != otherRep->last_modified)
286 return 0;
287
288 /* MD5 */
289 one = header.getStrOrList(HDR_CONTENT_MD5);
290
291 two = otherRep->header.getStrOrList(HDR_CONTENT_MD5);
292
293 if (one.empty() || two.empty() || strcasecmp (one, two)) {
294 one.clear();
295 two.clear();
296 return 0;
297 }
298
299 return 1;
300 }
301
302 void
303 HttpReply::updateOnNotModified(HttpReply const * freshRep)
304 {
305 assert(freshRep);
306
307 /* clean cache */
308 hdrCacheClean();
309 /* update raw headers */
310 header.update(&freshRep->header,
311 (const HttpHeaderMask *) &Denied304HeadersMask);
312
313 /* init cache */
314 hdrCacheInit();
315 }
316
317 /* internal routines */
318
319 time_t
320 HttpReply::hdrExpirationTime()
321 {
322 /* The s-maxage and max-age directive takes priority over Expires */
323
324 if (cache_control) {
325 if (date >= 0) {
326 if (cache_control->s_maxage >= 0)
327 return date + cache_control->s_maxage;
328
329 if (cache_control->max_age >= 0)
330 return date + cache_control->max_age;
331 } else {
332 /*
333 * Conservatively handle the case when we have a max-age
334 * header, but no Date for reference?
335 */
336
337 if (cache_control->s_maxage >= 0)
338 return squid_curtime;
339
340 if (cache_control->max_age >= 0)
341 return squid_curtime;
342 }
343 }
344
345 if (Config.onoff.vary_ignore_expire &&
346 header.has(HDR_VARY)) {
347 const time_t d = header.getTime(HDR_DATE);
348 const time_t e = header.getTime(HDR_EXPIRES);
349
350 if (d == e)
351 return -1;
352 }
353
354 if (header.has(HDR_EXPIRES)) {
355 const time_t e = header.getTime(HDR_EXPIRES);
356 /*
357 * HTTP/1.0 says that robust implementations should consider
358 * bad or malformed Expires header as equivalent to "expires
359 * immediately."
360 */
361 return e < 0 ? squid_curtime : e;
362 }
363
364 return -1;
365 }
366
367 /* sync this routine when you update HttpReply struct */
368 void
369 HttpReply::hdrCacheInit()
370 {
371 HttpMsg::hdrCacheInit();
372
373 content_length = header.getInt(HDR_CONTENT_LENGTH);
374 date = header.getTime(HDR_DATE);
375 last_modified = header.getTime(HDR_LAST_MODIFIED);
376 surrogate_control = header.getSc();
377 content_range = header.getContRange();
378 keep_alive = httpMsgIsPersistent(sline.version, &header);
379 const char *str = header.getStr(HDR_CONTENT_TYPE);
380
381 if (str)
382 content_type.limitInit(str, strcspn(str, ";\t "));
383 else
384 content_type = "";
385
386 /* be sure to set expires after date and cache-control */
387 expires = hdrExpirationTime();
388 }
389
390 /* sync this routine when you update HttpReply struct */
391 void
392 HttpReply::hdrCacheClean()
393 {
394 content_type.clear();
395
396 if (cache_control) {
397 httpHdrCcDestroy(cache_control);
398 cache_control = NULL;
399 }
400
401 if (surrogate_control) {
402 httpHdrScDestroy(surrogate_control);
403 surrogate_control = NULL;
404 }
405
406 if (content_range) {
407 httpHdrContRangeDestroy(content_range);
408 content_range = NULL;
409 }
410 }
411
412 /*
413 * Returns the body size of a HTTP response
414 */
415 int
416 HttpReply::bodySize(method_t method) const
417 {
418 if (sline.version.major < 1)
419 return -1;
420 else if (METHOD_HEAD == method)
421 return 0;
422 else if (sline.status == HTTP_OK)
423 (void) 0; /* common case, continue */
424 else if (sline.status == HTTP_NO_CONTENT)
425 return 0;
426 else if (sline.status == HTTP_NOT_MODIFIED)
427 return 0;
428 else if (sline.status < HTTP_OK)
429 return 0;
430
431 return content_length;
432 }
433
434 bool HttpReply::sanityCheckStartLine(MemBuf *buf, http_status *error)
435 {
436 if (buf->contentSize() >= protoPrefix.size() && strncmp(protoPrefix, buf->content(), protoPrefix.size()) != 0) {
437 debugs(58, 3, "HttpReply::sanityCheckStartLine: missing protocol prefix (" << protoPrefix << ") in '" << buf->content() << "'");
438 *error = HTTP_INVALID_HEADER;
439 return false;
440 }
441
442 return true;
443 }
444
445 void HttpReply::packFirstLineInto(Packer *p, bool unused) const
446 {
447 httpStatusLinePackInto(&sline, p);
448 }
449
450 bool HttpReply::parseFirstLine(const char *blk_start, const char *blk_end)
451 {
452 return httpStatusLineParse(&sline, protoPrefix, blk_start, blk_end);
453 }
454
455 /* handy: resets and returns -1 */
456 int
457 HttpReply::httpMsgParseError()
458 {
459 int result(HttpMsg::httpMsgParseError());
460 /* indicate an error in the status line */
461 sline.status = HTTP_INVALID_HEADER;
462 return result;
463 }
464
465 /*
466 * Indicate whether or not we would usually expect an entity-body
467 * along with this response
468 */
469 bool
470 HttpReply::expectingBody(method_t req_method, ssize_t& theSize) const
471 {
472 bool expectBody = true;
473
474 if (req_method == METHOD_HEAD)
475 expectBody = false;
476 else if (sline.status == HTTP_NO_CONTENT)
477 expectBody = false;
478 else if (sline.status == HTTP_NOT_MODIFIED)
479 expectBody = false;
480 else if (sline.status < HTTP_OK)
481 expectBody = false;
482 else
483 expectBody = true;
484
485 if (expectBody) {
486 if (header.hasListMember(HDR_TRANSFER_ENCODING, "chunked", ','))
487 theSize = -1;
488 else if (content_length >= 0)
489 theSize = content_length;
490 else
491 theSize = -1;
492 }
493
494 return expectBody;
495 }