]>
Commit | Line | Data |
---|---|---|
2ac76861 | 1 | |
cb69b4c7 | 2 | /* |
de336bbe | 3 | * $Id: HttpReply.cc,v 1.12 1998/03/11 22:18:46 rousskov Exp $ |
cb69b4c7 | 4 | * |
123abbe1 | 5 | * DEBUG: section 58 HTTP Reply (Response) |
cb69b4c7 | 6 | * AUTHOR: Alex Rousskov |
7 | * | |
8 | * SQUID Internet Object Cache http://squid.nlanr.net/Squid/ | |
9 | * -------------------------------------------------------- | |
10 | * | |
11 | * Squid is the result of efforts by numerous individuals from the | |
12 | * Internet community. Development is led by Duane Wessels of the | |
13 | * National Laboratory for Applied Network Research and funded by | |
14 | * the National Science Foundation. | |
15 | * | |
16 | * This program is free software; you can redistribute it and/or modify | |
17 | * it under the terms of the GNU General Public License as published by | |
18 | * the Free Software Foundation; either version 2 of the License, or | |
19 | * (at your option) any later version. | |
20 | * | |
21 | * This program is distributed in the hope that it will be useful, | |
22 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
23 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
24 | * GNU General Public License for more details. | |
25 | * | |
26 | * You should have received a copy of the GNU General Public License | |
27 | * along with this program; if not, write to the Free Software | |
28 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
29 | * | |
30 | */ | |
31 | ||
32 | /* tmp hack, delete it @?@ */ | |
33 | #define Const | |
34 | ||
35 | #include "squid.h" | |
36 | ||
37 | ||
38 | /* local constants */ | |
39 | ||
40 | /* local routines */ | |
2ac76861 | 41 | static void httpReplyDoDestroy(HttpReply * rep); |
42 | static int httpReplyParseStep(HttpReply * rep, const char *parse_start, int atEnd); | |
43 | static int httpReplyParseError(HttpReply * rep); | |
cb69b4c7 | 44 | static int httpReplyIsolateStart(const char **parse_start, const char **blk_start, const char **blk_end); |
45 | static int httpReplyIsolateHeaders(const char **parse_start, const char **blk_start, const char **blk_end); | |
46 | ||
47 | ||
48 | HttpReply * | |
49 | httpReplyCreate() | |
50 | { | |
7021844c | 51 | HttpReply *rep = memAllocate(MEM_HTTPREPLY); |
399e85ea | 52 | debug(58, 7) ("creating rep: %p\n", rep); |
cb69b4c7 | 53 | httpReplyInit(rep); |
54 | return rep; | |
55 | } | |
56 | ||
57 | void | |
2ac76861 | 58 | httpReplyInit(HttpReply * rep) |
cb69b4c7 | 59 | { |
60 | assert(rep); | |
61 | rep->hdr_sz = 0; | |
62 | rep->pstate = psReadyToParseStartLine; | |
63 | httpBodyInit(&rep->body); | |
64 | httpHeaderInit(&rep->hdr); | |
65 | httpStatusLineInit(&rep->sline); | |
66 | } | |
67 | ||
68 | void | |
2ac76861 | 69 | httpReplyClean(HttpReply * rep) |
cb69b4c7 | 70 | { |
71 | assert(rep); | |
72 | httpBodyClean(&rep->body); | |
73 | httpHeaderClean(&rep->hdr); | |
74 | httpStatusLineClean(&rep->sline); | |
75 | } | |
76 | ||
77 | void | |
2ac76861 | 78 | httpReplyDestroy(HttpReply * rep) |
cb69b4c7 | 79 | { |
80 | assert(rep); | |
399e85ea | 81 | debug(58, 7) ("destroying rep: %p\n", rep); |
cb69b4c7 | 82 | httpReplyClean(rep); |
63259c34 | 83 | httpReplyDoDestroy(rep); |
cb69b4c7 | 84 | } |
85 | ||
86 | void | |
2ac76861 | 87 | httpReplyReset(HttpReply * rep) |
cb69b4c7 | 88 | { |
89 | httpReplyClean(rep); | |
90 | httpReplyInit(rep); | |
91 | } | |
92 | ||
63259c34 | 93 | /* absorb: copy the contents of a new reply to the old one, destroy new one */ |
94 | void | |
2ac76861 | 95 | httpReplyAbsorb(HttpReply * rep, HttpReply * new_rep) |
63259c34 | 96 | { |
97 | assert(rep && new_rep); | |
98 | httpReplyClean(rep); | |
99 | *rep = *new_rep; | |
100 | /* cannot use Clean() on new reply now! */ | |
101 | httpReplyDoDestroy(new_rep); | |
102 | } | |
103 | ||
cb69b4c7 | 104 | /* parses a buffer that may not be 0-terminated */ |
105 | int | |
2ac76861 | 106 | httpReplyParse(HttpReply * rep, const char *buf) |
cb69b4c7 | 107 | { |
108 | /* | |
109 | * this extra buffer/copy will be eliminated when headers become meta-data | |
110 | * in store. Currently we have to xstrncpy the buffer becuase store.c may | |
111 | * feed a non 0-terminated buffer to us @?@. | |
112 | */ | |
7021844c | 113 | char *headers = memAllocate(MEM_4K_BUF); |
cb69b4c7 | 114 | int success; |
115 | /* reset current state, because we are not used in incremental fashion */ | |
116 | httpReplyReset(rep); | |
117 | /* put a 0-terminator */ | |
118 | xstrncpy(headers, buf, 4096); | |
119 | success = httpReplyParseStep(rep, headers, 0); | |
120 | memFree(MEM_4K_BUF, headers); | |
121 | return success == 1; | |
122 | } | |
123 | ||
124 | void | |
2ac76861 | 125 | httpReplyPackInto(const HttpReply * rep, Packer * p) |
cb69b4c7 | 126 | { |
127 | assert(rep); | |
128 | httpStatusLinePackInto(&rep->sline, p); | |
129 | httpHeaderPackInto(&rep->hdr, p); | |
130 | packerAppend(p, "\r\n", 2); | |
131 | httpBodyPackInto(&rep->body, p); | |
132 | } | |
133 | ||
134 | /* create memBuf, create mem-based packer, pack, destroy packer, return MemBuf */ | |
135 | MemBuf | |
2ac76861 | 136 | httpReplyPack(const HttpReply * rep) |
cb69b4c7 | 137 | { |
138 | MemBuf mb; | |
139 | Packer p; | |
140 | assert(rep); | |
141 | ||
142 | memBufDefInit(&mb); | |
143 | packerToMemInit(&p, &mb); | |
144 | httpReplyPackInto(rep, &p); | |
145 | packerClean(&p); | |
146 | return mb; | |
147 | } | |
148 | ||
149 | /* swap: create swap-based packer, pack, destroy packer */ | |
150 | void | |
2ac76861 | 151 | httpReplySwapOut(const HttpReply * rep, StoreEntry * e) |
cb69b4c7 | 152 | { |
153 | Packer p; | |
154 | assert(rep && e); | |
155 | ||
156 | packerToStoreInit(&p, e); | |
157 | httpReplyPackInto(rep, &p); | |
158 | packerClean(&p); | |
159 | } | |
160 | ||
161 | MemBuf | |
162 | httpPackedReply(double ver, http_status status, const char *ctype, | |
163 | int clen, time_t lmt, time_t expires) | |
164 | { | |
165 | HttpReply *rep = httpReplyCreate(); | |
166 | MemBuf mb; | |
167 | httpReplySetHeaders(rep, ver, status, ctype, NULL, clen, lmt, expires); | |
168 | mb = httpReplyPack(rep); | |
169 | httpReplyDestroy(rep); | |
170 | return mb; | |
171 | } | |
172 | ||
173 | MemBuf | |
2ac76861 | 174 | httpPacked304Reply(const HttpReply * rep) |
cb69b4c7 | 175 | { |
de336bbe | 176 | static const http_hdr_type ImsEntries[] = { HDR_DATE, HDR_CONTENT_LENGTH, HDR_CONTENT_TYPE, HDR_EXPIRES, HDR_LAST_MODIFIED, /* eof */ HDR_OTHER }; |
177 | http_hdr_type t; | |
cb69b4c7 | 178 | MemBuf mb; |
de336bbe | 179 | Packer p; |
180 | HttpHeaderEntry *e; | |
cb69b4c7 | 181 | assert(rep); |
182 | ||
cb69b4c7 | 183 | memBufDefInit(&mb); |
de336bbe | 184 | packerToMemInit(&p, &mb); |
cb69b4c7 | 185 | memBufPrintf(&mb, "%s", "HTTP/1.0 304 Not Modified\r\n"); |
de336bbe | 186 | for (t = 0; ImsEntries[t] != HDR_OTHER; ++t) |
187 | if ((e = httpHeaderFindEntry(&rep->hdr, ImsEntries[t], NULL))) | |
188 | httpHeaderEntryPackInto(e, &p); | |
cb69b4c7 | 189 | memBufAppend(&mb, "\r\n", 2); |
de336bbe | 190 | packerClean(&p); |
cb69b4c7 | 191 | return mb; |
192 | } | |
193 | ||
194 | void | |
2ac76861 | 195 | httpReplySetHeaders(HttpReply * reply, double ver, http_status status, const char *reason, |
cb69b4c7 | 196 | const char *ctype, int clen, time_t lmt, time_t expires) |
197 | { | |
198 | HttpHeader *hdr; | |
199 | assert(reply); | |
200 | httpStatusLineSet(&reply->sline, ver, status, reason); | |
201 | hdr = &reply->hdr; | |
202 | httpHeaderAddExt(hdr, "Server", full_appname_string); | |
2ac76861 | 203 | httpHeaderAddExt(hdr, "MIME-Version", "1.0"); /* do we need this? @?@ */ |
cb69b4c7 | 204 | httpHeaderSetTime(hdr, HDR_DATE, squid_curtime); |
205 | if (ctype) | |
206 | httpHeaderSetStr(hdr, HDR_CONTENT_TYPE, ctype); | |
de336bbe | 207 | if (clen >= 0) |
cb69b4c7 | 208 | httpHeaderSetInt(hdr, HDR_CONTENT_LENGTH, clen); |
209 | if (expires >= 0) | |
210 | httpHeaderSetTime(hdr, HDR_EXPIRES, expires); | |
2ac76861 | 211 | if (lmt > 0) /* this used to be lmt != 0 @?@ */ |
212 | httpHeaderSetTime(hdr, HDR_LAST_MODIFIED, lmt); | |
cb69b4c7 | 213 | } |
214 | ||
adba4a64 | 215 | /* |
216 | * header manipulation | |
217 | * | |
218 | * never go to header directly if you can use these: | |
219 | * | |
220 | * our interpretation of headers often changes and you may get into trouble | |
221 | * if you, for example, assume that HDR_EXPIRES contains expire info | |
222 | * | |
223 | * if you think about it, in most cases, you are not looking for the information | |
de336bbe | 224 | * in the header, but rather for current state of the reply, which may or may |
225 | * not depend on headers. | |
adba4a64 | 226 | * |
227 | * For example, the _real_ question is | |
228 | * "when does this object expire?" | |
229 | * not | |
230 | * "what is the value of the 'Expires:' header?" | |
231 | */ | |
232 | ||
cb69b4c7 | 233 | void |
2ac76861 | 234 | httpReplyUpdateOnNotModified(HttpReply * rep, HttpReply * freshRep) |
cb69b4c7 | 235 | { |
de336bbe | 236 | #if 0 /* this is what we want: */ |
cb69b4c7 | 237 | rep->cache_control = freshRep->cache_control; |
238 | rep->misc_headers = freshRep->misc_headers; | |
239 | if (freshRep->date > -1) | |
240 | rep->date = freshRep->date; | |
241 | if (freshRep->last_modified > -1) | |
242 | rep->last_modified = freshRep->last_modified; | |
243 | if (freshRep->expires > -1) | |
244 | rep->expires = freshRep->expires; | |
245 | #endif | |
246 | time_t date; | |
247 | time_t expires; | |
248 | time_t lmt; | |
249 | assert(rep && freshRep); | |
250 | /* save precious info */ | |
251 | date = httpHeaderGetTime(&rep->hdr, HDR_DATE); | |
2ac76861 | 252 | expires = httpReplyExpires(rep); |
cb69b4c7 | 253 | lmt = httpHeaderGetTime(&rep->hdr, HDR_LAST_MODIFIED); |
254 | /* clean old headers */ | |
255 | httpHeaderClean(&rep->hdr); | |
de336bbe | 256 | httpHeaderInit(&rep->hdr); |
257 | /* copy */ | |
258 | httpHeaderCopy(&rep->hdr, &freshRep->hdr); | |
cb69b4c7 | 259 | /* restore missing info if needed */ |
260 | if (!httpHeaderHas(&rep->hdr, HDR_DATE)) | |
261 | httpHeaderSetTime(&rep->hdr, HDR_DATE, date); | |
262 | if (!httpHeaderHas(&rep->hdr, HDR_EXPIRES)) | |
263 | httpHeaderSetTime(&rep->hdr, HDR_EXPIRES, expires); | |
264 | if (!httpHeaderHas(&rep->hdr, HDR_LAST_MODIFIED)) | |
265 | httpHeaderSetTime(&rep->hdr, HDR_LAST_MODIFIED, lmt); | |
266 | } | |
267 | ||
268 | int | |
2ac76861 | 269 | httpReplyContentLen(const HttpReply * rep) |
270 | { | |
cb69b4c7 | 271 | assert(rep); |
7c525cc2 | 272 | return httpHeaderGetInt(&rep->hdr, HDR_CONTENT_LENGTH); |
cb69b4c7 | 273 | } |
274 | ||
275 | /* should we return "" or NULL if no content-type? Return NULL for now @?@ */ | |
276 | const char * | |
2ac76861 | 277 | httpReplyContentType(const HttpReply * rep) |
278 | { | |
cb69b4c7 | 279 | assert(rep); |
280 | return httpHeaderGetStr(&rep->hdr, HDR_CONTENT_TYPE); | |
281 | } | |
282 | ||
cb69b4c7 | 283 | time_t |
2ac76861 | 284 | httpReplyExpires(const HttpReply * rep) |
cb69b4c7 | 285 | { |
7faf2bdb | 286 | HttpHdrCc *cc; |
cb69b4c7 | 287 | time_t exp = -1; |
288 | assert(rep); | |
289 | /* The max-age directive takes priority over Expires, check it first */ | |
7faf2bdb | 290 | cc = httpHeaderGetCc(&rep->hdr); |
291 | if (cc) | |
292 | exp = cc->max_age; | |
cb69b4c7 | 293 | if (exp < 0) |
294 | exp = httpHeaderGetTime(&rep->hdr, HDR_EXPIRES); | |
295 | return exp; | |
296 | } | |
297 | ||
298 | int | |
7faf2bdb | 299 | httpReplyHasCc(const HttpReply * rep, http_hdr_cc_type type) |
cb69b4c7 | 300 | { |
7faf2bdb | 301 | HttpHdrCc *cc; |
cb69b4c7 | 302 | assert(rep); |
7faf2bdb | 303 | assert(type >= 0 && type < CC_ENUM_END); |
cb69b4c7 | 304 | |
7faf2bdb | 305 | cc = httpHeaderGetCc(&rep->hdr); |
306 | return cc && /* scc header is present */ | |
307 | EBIT_TEST(cc->mask, type); | |
cb69b4c7 | 308 | } |
309 | ||
310 | ||
311 | /* internal routines */ | |
312 | ||
63259c34 | 313 | /* internal function used by Destroy and Absorb */ |
314 | static void | |
2ac76861 | 315 | httpReplyDoDestroy(HttpReply * rep) |
316 | { | |
63259c34 | 317 | memFree(MEM_HTTPREPLY, rep); |
318 | } | |
cb69b4c7 | 319 | |
320 | /* | |
321 | * parses a 0-terminating buffer into HttpReply. | |
322 | * Returns: | |
323 | * +1 -- success | |
324 | * 0 -- need more data (partial parse) | |
325 | * -1 -- parse error | |
326 | */ | |
327 | static int | |
2ac76861 | 328 | httpReplyParseStep(HttpReply * rep, const char *buf, int atEnd) |
cb69b4c7 | 329 | { |
330 | const char *parse_start = buf; | |
331 | const char *blk_start, *blk_end; | |
332 | const char **parse_end_ptr = &blk_end; | |
333 | assert(rep); | |
334 | assert(parse_start); | |
335 | assert(rep->pstate < psParsed); | |
336 | ||
337 | *parse_end_ptr = parse_start; | |
338 | if (rep->pstate == psReadyToParseStartLine) { | |
339 | if (!httpReplyIsolateStart(&parse_start, &blk_start, &blk_end)) | |
340 | return 0; | |
341 | if (!httpStatusLineParse(&rep->sline, blk_start, blk_end)) | |
342 | return httpReplyParseError(rep); | |
343 | ||
344 | *parse_end_ptr = parse_start; | |
345 | rep->hdr_sz = *parse_end_ptr - buf; | |
2ac76861 | 346 | rep->pstate++; |
cb69b4c7 | 347 | } |
cb69b4c7 | 348 | if (rep->pstate == psReadyToParseHeaders) { |
349 | if (!httpReplyIsolateHeaders(&parse_start, &blk_start, &blk_end)) | |
350 | if (atEnd) | |
351 | blk_start = parse_start, blk_end = blk_start + strlen(blk_start); | |
352 | else | |
353 | return 0; | |
354 | if (!httpHeaderParse(&rep->hdr, blk_start, blk_end)) | |
355 | return httpReplyParseError(rep); | |
356 | ||
357 | *parse_end_ptr = parse_start; | |
358 | rep->hdr_sz = *parse_end_ptr - buf; | |
359 | rep->pstate++; | |
360 | } | |
cb69b4c7 | 361 | return 1; |
362 | } | |
363 | ||
364 | ||
365 | /* handy: resets and returns -1 */ | |
366 | static int | |
2ac76861 | 367 | httpReplyParseError(HttpReply * rep) |
cb69b4c7 | 368 | { |
369 | assert(rep); | |
370 | /* reset */ | |
371 | httpReplyReset(rep); | |
372 | /* indicate an error */ | |
373 | rep->sline.status = HTTP_INVALID_HEADER; | |
374 | return -1; | |
375 | } | |
376 | ||
377 | /* find first CRLF */ | |
378 | static int | |
379 | httpReplyIsolateStart(const char **parse_start, const char **blk_start, const char **blk_end) | |
380 | { | |
381 | int slen = strcspn(*parse_start, "\r\n"); | |
2ac76861 | 382 | if (!(*parse_start)[slen]) /* no CRLF found */ |
cb69b4c7 | 383 | return 0; |
384 | ||
385 | *blk_start = *parse_start; | |
386 | *blk_end = *blk_start + slen; | |
2ac76861 | 387 | if (**blk_end == '\r') /* CR */ |
cb69b4c7 | 388 | (*blk_end)++; |
2ac76861 | 389 | if (**blk_end == '\n') /* LF */ |
cb69b4c7 | 390 | (*blk_end)++; |
391 | ||
392 | *parse_start = *blk_end; | |
393 | return 1; | |
394 | } | |
395 | ||
396 | /* find end of headers */ | |
397 | static int | |
398 | httpReplyIsolateHeaders(const char **parse_start, const char **blk_start, const char **blk_end) | |
399 | { | |
400 | /* adopted with mods from mime_headers_end() */ | |
401 | const char *p1 = strstr(*parse_start, "\n\r\n"); | |
402 | const char *p2 = strstr(*parse_start, "\n\n"); | |
403 | const char *end = NULL; | |
404 | ||
405 | if (p1 && p2) | |
2ac76861 | 406 | end = p1 < p2 ? p1 : p2; |
cb69b4c7 | 407 | else |
2ac76861 | 408 | end = p1 ? p1 : p2; |
cb69b4c7 | 409 | |
410 | if (end) { | |
2ac76861 | 411 | *blk_start = *parse_start; |
cb69b4c7 | 412 | *blk_end = end + 1; |
413 | *parse_start = end + (end == p1 ? 3 : 2); | |
414 | } | |
415 | return end != NULL; | |
416 | } |