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