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