]> git.ipfire.org Git - thirdparty/squid.git/blob - src/HttpReply.cc
Moved HttpRequest lock/link counter to the base HttpMsg class. This
[thirdparty/squid.git] / src / HttpReply.cc
1
2 /*
3 * $Id: HttpReply.cc,v 1.82 2006/01/23 20:04:24 wessels 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 "Store.h"
38 #include "HttpReply.h"
39 #include "HttpHdrContRange.h"
40 #include "ACLChecklist.h"
41 #include "MemBuf.h"
42
43 /* local constants */
44
45 /* these entity-headers must be ignored if a bogus server sends them in 304 */
46 static HttpHeaderMask Denied304HeadersMask;
47 static http_hdr_type Denied304HeadersArr[] =
48 {
49 HDR_ALLOW, HDR_CONTENT_ENCODING, HDR_CONTENT_LANGUAGE, HDR_CONTENT_LENGTH,
50 HDR_CONTENT_LOCATION, HDR_CONTENT_RANGE, HDR_LAST_MODIFIED, HDR_LINK,
51 HDR_OTHER
52 };
53
54 /* module initialization */
55 void
56 httpReplyInitModule(void)
57 {
58 assert(HTTP_STATUS_NONE == 0); // HttpReply::parse() interface assumes that
59 httpHeaderMaskInit(&Denied304HeadersMask, 0);
60 httpHeaderCalcMask(&Denied304HeadersMask, Denied304HeadersArr, countof(Denied304HeadersArr));
61 }
62
63 HttpReply::HttpReply() : HttpMsg(hoReply), date (0), last_modified (0), expires (0), surrogate_control (NULL), content_range (NULL), keep_alive (0), protoPrefix("HTTP/")
64 {
65 init();
66 }
67
68 HttpReply::~HttpReply()
69 {
70 if (do_clean)
71 clean();
72 }
73
74 void
75 HttpReply::init()
76 {
77 httpBodyInit(&body);
78 hdrCacheInit();
79 httpStatusLineInit(&sline);
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 httpBodyClean(&body);
100 hdrCacheClean();
101 httpHeaderClean(&header);
102 httpStatusLineClean(&sline);
103 }
104
105 #if OLD
106 /* absorb: copy the contents of a new reply to the old one, destroy new one */
107 void
108 HttpReply::absorb(HttpReply * new_rep)
109 {
110 assert(new_rep);
111 clean();
112 *this = *new_rep;
113 new_rep->header.entries.clean();
114 /* cannot use Clean() on new reply now! */
115 new_rep->do_clean = false;
116 new_rep->cache_control = NULL; // helps with debugging
117 delete new_rep;
118 }
119
120 #endif
121
122 void
123 HttpReply::packHeadersInto(Packer * p) const
124 {
125 httpStatusLinePackInto(&sline, p);
126 httpHeaderPackInto(&header, p);
127 packerAppend(p, "\r\n", 2);
128 }
129
130 void
131 HttpReply::packInto(Packer * p)
132 {
133 packHeadersInto(p);
134 httpBodyPackInto(&body, p);
135 }
136
137 /* create memBuf, create mem-based packer, pack, destroy packer, return MemBuf */
138 MemBuf *
139 HttpReply::pack()
140 {
141 MemBuf *mb = new MemBuf;
142 Packer p;
143
144 mb->init();
145 packerToMemInit(&p, mb);
146 packInto(&p);
147 packerClean(&p);
148 return mb;
149 }
150
151 MemBuf *
152 httpPackedReply(HttpVersion ver, http_status status, const char *ctype,
153 int clen, time_t lmt, time_t expires)
154 {
155 HttpReply *rep = new HttpReply;
156 rep->setHeaders(ver, status, ctype, NULL, clen, lmt, expires);
157 MemBuf *mb = rep->pack();
158 delete rep;
159 return mb;
160 }
161
162 HttpReply *
163 HttpReply::make304 () const
164 {
165 static const http_hdr_type ImsEntries[] = {HDR_DATE, HDR_CONTENT_TYPE, HDR_EXPIRES, HDR_LAST_MODIFIED, /* eof */ HDR_OTHER};
166
167 HttpReply *rv = new HttpReply;
168 int t;
169 HttpHeaderEntry *e;
170
171 /* rv->content_length; */
172 rv->date = date;
173 rv->last_modified = last_modified;
174 rv->expires = expires;
175 rv->content_type = content_type;
176 /* rv->cache_control */
177 /* rv->content_range */
178 /* rv->keep_alive */
179 HttpVersion ver(1,0);
180 httpStatusLineSet(&rv->sline, ver,
181 HTTP_NOT_MODIFIED, "");
182
183 for (t = 0; ImsEntries[t] != HDR_OTHER; ++t)
184 if ((e = httpHeaderFindEntry(&header, ImsEntries[t])))
185 httpHeaderAddEntry(&rv->header, httpHeaderEntryClone(e));
186
187 /* rv->body */
188 return rv;
189 }
190
191 MemBuf *
192 HttpReply::packed304Reply()
193 {
194 /* Not as efficient as skipping the header duplication,
195 * but easier to maintain
196 */
197 HttpReply *temp = make304 ();
198 MemBuf *rv = temp->pack();
199 delete temp;
200 return rv;
201 }
202
203 void
204 HttpReply::setHeaders(HttpVersion ver, http_status status, const char *reason,
205 const char *ctype, int clen, time_t lmt, time_t expires)
206 {
207 HttpHeader *hdr;
208 httpStatusLineSet(&sline, ver, status, reason);
209 hdr = &header;
210 httpHeaderPutStr(hdr, HDR_SERVER, visible_appname_string);
211 httpHeaderPutStr(hdr, HDR_MIME_VERSION, "1.0");
212 httpHeaderPutTime(hdr, HDR_DATE, squid_curtime);
213
214 if (ctype) {
215 httpHeaderPutStr(hdr, HDR_CONTENT_TYPE, ctype);
216 content_type = ctype;
217 } else
218 content_type = String();
219
220 if (clen >= 0)
221 httpHeaderPutInt(hdr, HDR_CONTENT_LENGTH, clen);
222
223 if (expires >= 0)
224 httpHeaderPutTime(hdr, HDR_EXPIRES, expires);
225
226 if (lmt > 0) /* this used to be lmt != 0 @?@ */
227 httpHeaderPutTime(hdr, HDR_LAST_MODIFIED, lmt);
228
229 date = squid_curtime;
230
231 content_length = clen;
232
233 expires = expires;
234
235 last_modified = lmt;
236 }
237
238 void
239 HttpReply::redirect(http_status status, const char *loc)
240 {
241 HttpHeader *hdr;
242 HttpVersion ver(1,0);
243 httpStatusLineSet(&sline, ver, status, httpStatusString(status));
244 hdr = &header;
245 httpHeaderPutStr(hdr, HDR_SERVER, full_appname_string);
246 httpHeaderPutTime(hdr, HDR_DATE, squid_curtime);
247 httpHeaderPutInt(hdr, HDR_CONTENT_LENGTH, 0);
248 httpHeaderPutStr(hdr, HDR_LOCATION, loc);
249 date = squid_curtime;
250 content_length = 0;
251 }
252
253 /* compare the validators of two replies.
254 * 1 = they match
255 * 0 = they do not match
256 */
257 int
258 HttpReply::validatorsMatch(HttpReply const * otherRep) const
259 {
260 String one,two;
261 assert (otherRep);
262 /* Numbers first - easiest to check */
263 /* Content-Length */
264 /* TODO: remove -1 bypass */
265
266 if (content_length != otherRep->content_length
267 && content_length > -1 &&
268 otherRep->content_length > -1)
269 return 0;
270
271 /* ETag */
272 one = httpHeaderGetStrOrList(&header, HDR_ETAG);
273
274 two = httpHeaderGetStrOrList(&otherRep->header, HDR_ETAG);
275
276 if (!one.buf() || !two.buf() || strcasecmp (one.buf(), two.buf())) {
277 one.clean();
278 two.clean();
279 return 0;
280 }
281
282 if (last_modified != otherRep->last_modified)
283 return 0;
284
285 /* MD5 */
286 one = httpHeaderGetStrOrList(&header, HDR_CONTENT_MD5);
287
288 two = httpHeaderGetStrOrList(&otherRep->header, HDR_CONTENT_MD5);
289
290 if (!one.buf() || !two.buf() || strcasecmp (one.buf(), two.buf())) {
291 one.clean();
292 two.clean();
293 return 0;
294 }
295
296 return 1;
297 }
298
299 void
300 HttpReply::updateOnNotModified(HttpReply const * freshRep)
301 {
302 assert(freshRep);
303 /* Can not update modified headers that don't match! */
304 assert (validatorsMatch(freshRep));
305 /* clean cache */
306 hdrCacheClean();
307 /* update raw headers */
308 httpHeaderUpdate(&header, &freshRep->header,
309 (const HttpHeaderMask *) &Denied304HeadersMask);
310 /* init cache */
311 hdrCacheInit();
312 }
313
314 /* internal routines */
315
316 time_t
317 HttpReply::hdrExpirationTime()
318 {
319 /* The s-maxage and max-age directive takes priority over Expires */
320
321 if (cache_control) {
322 if (date >= 0) {
323 if (cache_control->s_maxage >= 0)
324 return date + cache_control->s_maxage;
325
326 if (cache_control->max_age >= 0)
327 return date + cache_control->max_age;
328 } else {
329 /*
330 * Conservatively handle the case when we have a max-age
331 * header, but no Date for reference?
332 */
333
334 if (cache_control->s_maxage >= 0)
335 return squid_curtime;
336
337 if (cache_control->max_age >= 0)
338 return squid_curtime;
339 }
340 }
341
342 if (Config.onoff.vary_ignore_expire &&
343 httpHeaderHas(&header, HDR_VARY)) {
344 const time_t d = httpHeaderGetTime(&header, HDR_DATE);
345 const time_t e = httpHeaderGetTime(&header, HDR_EXPIRES);
346
347 if (d == e)
348 return -1;
349 }
350
351 if (httpHeaderHas(&header, HDR_EXPIRES)) {
352 const time_t e = httpHeaderGetTime(&header, HDR_EXPIRES);
353 /*
354 * HTTP/1.0 says that robust implementations should consider
355 * bad or malformed Expires header as equivalent to "expires
356 * immediately."
357 */
358 return e < 0 ? squid_curtime : e;
359 }
360
361 return -1;
362 }
363
364 /* sync this routine when you update HttpReply struct */
365 void
366 HttpReply::hdrCacheInit()
367 {
368 HttpMsg::hdrCacheInit();
369
370 content_length = httpHeaderGetInt(&header, HDR_CONTENT_LENGTH);
371 date = httpHeaderGetTime(&header, HDR_DATE);
372 last_modified = httpHeaderGetTime(&header, HDR_LAST_MODIFIED);
373 surrogate_control = httpHeaderGetSc(&header);
374 content_range = httpHeaderGetContRange(&header);
375 keep_alive = httpMsgIsPersistent(sline.version, &header);
376 const char *str = httpHeaderGetStr(&header, HDR_CONTENT_TYPE);
377
378 if (str)
379 content_type.limitInit(str, strcspn(str, ";\t "));
380 else
381 content_type = String();
382
383 /* be sure to set expires after date and cache-control */
384 expires = hdrExpirationTime();
385 }
386
387 /* sync this routine when you update HttpReply struct */
388 void
389 HttpReply::hdrCacheClean()
390 {
391 content_type.clean();
392
393 if (cache_control) {
394 httpHdrCcDestroy(cache_control);
395 cache_control = NULL;
396 }
397
398 if (surrogate_control) {
399 httpHdrScDestroy(surrogate_control);
400 surrogate_control = NULL;
401 }
402
403 if (content_range) {
404 httpHdrContRangeDestroy(content_range);
405 content_range = NULL;
406 }
407 }
408
409 /*
410 * Returns the body size of a HTTP response
411 */
412 int
413 HttpReply::bodySize(method_t method) const
414 {
415 if (sline.version.major < 1)
416 return -1;
417 else if (METHOD_HEAD == method)
418 return 0;
419 else if (sline.status == HTTP_OK)
420 (void) 0; /* common case, continue */
421 else if (sline.status == HTTP_NO_CONTENT)
422 return 0;
423 else if (sline.status == HTTP_NOT_MODIFIED)
424 return 0;
425 else if (sline.status < HTTP_OK)
426 return 0;
427
428 return content_length;
429 }
430
431 bool HttpReply::sanityCheckStartLine(MemBuf *buf, http_status *error)
432 {
433 if (buf->contentSize() >= protoPrefix.size() && protoPrefix.cmp(buf->content(), protoPrefix.size()) != 0) {
434 debugs(58, 3, "HttpReply::sanityCheckStartLine: missing protocol prefix (" << protoPrefix.buf() << ") in '" << buf->content() << "'");
435 *error = HTTP_INVALID_HEADER;
436 return false;
437 }
438
439 return true;
440 }
441
442 void HttpReply::packFirstLineInto(Packer *p, bool unused) const
443 {
444 httpStatusLinePackInto(&sline, p);
445 }
446
447 bool HttpReply::parseFirstLine(const char *blk_start, const char *blk_end)
448 {
449 return httpStatusLineParse(&sline, protoPrefix, blk_start, blk_end);
450 }
451
452 /*
453 * Indicate whether or not we would usually expect an entity-body
454 * along with this response
455 */
456 bool
457 HttpReply::expectingBody(method_t req_method, ssize_t& theSize) const
458 {
459 bool expectBody = true;
460
461 if (req_method == METHOD_HEAD)
462 expectBody = false;
463 else if (sline.status == HTTP_NO_CONTENT)
464 expectBody = false;
465 else if (sline.status == HTTP_NOT_MODIFIED)
466 expectBody = false;
467 else if (sline.status < HTTP_OK)
468 expectBody = false;
469 else
470 expectBody = true;
471
472 if (expectBody) {
473 if (httpHeaderHasListMember(&header, HDR_TRANSFER_ENCODING, "chunked", ','))
474 theSize = -1;
475 else if (content_length >= 0)
476 theSize = content_length;
477 else
478 theSize = -1;
479 }
480
481 return expectBody;
482 }