]>
Commit | Line | Data |
---|---|---|
da2b3a17 | 1 | |
30a4f2a8 | 2 | /* |
7021844c | 3 | * $Id: http.cc,v 1.243 1998/03/03 00:31:07 rousskov Exp $ |
30a4f2a8 | 4 | * |
5 | * DEBUG: section 11 Hypertext Transfer Protocol (HTTP) | |
6 | * AUTHOR: Harvest Derived | |
7 | * | |
42c04c16 | 8 | * SQUID Internet Object Cache http://squid.nlanr.net/Squid/ |
30a4f2a8 | 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 | */ | |
019dd986 | 31 | |
32 | /* | |
30a4f2a8 | 33 | * Copyright (c) 1994, 1995. All rights reserved. |
34 | * | |
35 | * The Harvest software was developed by the Internet Research Task | |
36 | * Force Research Group on Resource Discovery (IRTF-RD): | |
37 | * | |
38 | * Mic Bowman of Transarc Corporation. | |
39 | * Peter Danzig of the University of Southern California. | |
40 | * Darren R. Hardy of the University of Colorado at Boulder. | |
41 | * Udi Manber of the University of Arizona. | |
42 | * Michael F. Schwartz of the University of Colorado at Boulder. | |
43 | * Duane Wessels of the University of Colorado at Boulder. | |
44 | * | |
45 | * This copyright notice applies to software in the Harvest | |
46 | * ``src/'' directory only. Users should consult the individual | |
47 | * copyright notices in the ``components/'' subdirectories for | |
48 | * copyright information about other software bundled with the | |
49 | * Harvest source code distribution. | |
50 | * | |
51 | * TERMS OF USE | |
52 | * | |
53 | * The Harvest software may be used and re-distributed without | |
54 | * charge, provided that the software origin and research team are | |
55 | * cited in any use of the system. Most commonly this is | |
56 | * accomplished by including a link to the Harvest Home Page | |
57 | * (http://harvest.cs.colorado.edu/) from the query page of any | |
58 | * Broker you deploy, as well as in the query result pages. These | |
59 | * links are generated automatically by the standard Broker | |
60 | * software distribution. | |
61 | * | |
62 | * The Harvest software is provided ``as is'', without express or | |
63 | * implied warranty, and with no support nor obligation to assist | |
64 | * in its use, correction, modification or enhancement. We assume | |
65 | * no liability with respect to the infringement of copyrights, | |
66 | * trade secrets, or any patents, and are not responsible for | |
67 | * consequential damages. Proper use of the Harvest software is | |
68 | * entirely the responsibility of the user. | |
69 | * | |
70 | * DERIVATIVE WORKS | |
71 | * | |
72 | * Users may make derivative works from the Harvest software, subject | |
73 | * to the following constraints: | |
74 | * | |
75 | * - You must include the above copyright notice and these | |
76 | * accompanying paragraphs in all forms of derivative works, | |
77 | * and any documentation and other materials related to such | |
78 | * distribution and use acknowledge that the software was | |
79 | * developed at the above institutions. | |
80 | * | |
81 | * - You must notify IRTF-RD regarding your distribution of | |
82 | * the derivative work. | |
83 | * | |
84 | * - You must clearly notify users that your are distributing | |
85 | * a modified version and not the original Harvest software. | |
86 | * | |
87 | * - Any derivative product is also subject to these copyright | |
88 | * and use restrictions. | |
89 | * | |
90 | * Note that the Harvest software is NOT in the public domain. We | |
91 | * retain copyright, as specified above. | |
92 | * | |
93 | * HISTORY OF FREE SOFTWARE STATUS | |
94 | * | |
95 | * Originally we required sites to license the software in cases | |
96 | * where they were going to build commercial products/services | |
97 | * around Harvest. In June 1995 we changed this policy. We now | |
98 | * allow people to use the core Harvest software (the code found in | |
99 | * the Harvest ``src/'' directory) for free. We made this change | |
100 | * in the interest of encouraging the widest possible deployment of | |
101 | * the technology. The Harvest software is really a reference | |
102 | * implementation of a set of protocols and formats, some of which | |
103 | * we intend to standardize. We encourage commercial | |
104 | * re-implementations of code complying to this set of standards. | |
019dd986 | 105 | */ |
44a47c6e | 106 | |
4a83b852 | 107 | /* |
108 | * Anonymizing patch by lutz@as-node.jena.thur.de | |
de3bdb4c | 109 | * have a look into http-anon.c to get more informations. |
4a83b852 | 110 | */ |
111 | ||
44a47c6e | 112 | #include "squid.h" |
090089c4 | 113 | |
6bf8443a | 114 | static const char *const crlf = "\r\n"; |
4db43fab | 115 | |
ee1679df | 116 | #if 0 /* moved to HttpHeader */ |
6fb52f6c | 117 | typedef enum { |
118 | SCC_PUBLIC, | |
119 | SCC_PRIVATE, | |
120 | SCC_NOCACHE, | |
121 | SCC_NOSTORE, | |
122 | SCC_NOTRANSFORM, | |
123 | SCC_MUSTREVALIDATE, | |
124 | SCC_PROXYREVALIDATE, | |
125 | SCC_MAXAGE, | |
126 | SCC_ENUM_END | |
127 | } http_server_cc_t; | |
ee1679df | 128 | |
cb69b4c7 | 129 | #endif |
6fb52f6c | 130 | |
6bf8443a | 131 | enum { |
6fb52f6c | 132 | CCC_NOCACHE, |
133 | CCC_NOSTORE, | |
134 | CCC_MAXAGE, | |
135 | CCC_MAXSTALE, | |
136 | CCC_MINFRESH, | |
137 | CCC_ONLYIFCACHED, | |
138 | CCC_ENUM_END | |
6bf8443a | 139 | }; |
140 | ||
ee1679df | 141 | #if 0 /* moved to HttpHeader.h */ |
151a0b6d | 142 | typedef enum { |
143 | HDR_ACCEPT, | |
144 | HDR_AGE, | |
145 | HDR_CONTENT_LENGTH, | |
146 | HDR_CONTENT_MD5, | |
147 | HDR_CONTENT_TYPE, | |
148 | HDR_DATE, | |
149 | HDR_ETAG, | |
150 | HDR_EXPIRES, | |
6bf8443a | 151 | HDR_HOST, |
151a0b6d | 152 | HDR_IMS, |
153 | HDR_LAST_MODIFIED, | |
154 | HDR_MAX_FORWARDS, | |
155 | HDR_PUBLIC, | |
156 | HDR_RETRY_AFTER, | |
157 | HDR_SET_COOKIE, | |
158 | HDR_UPGRADE, | |
159 | HDR_WARNING, | |
1294c0fc | 160 | HDR_PROXY_KEEPALIVE, |
151a0b6d | 161 | HDR_MISC_END |
162 | } http_hdr_misc_t; | |
6fb52f6c | 163 | |
e924600d | 164 | static char *HttpServerCCStr[] = |
6fb52f6c | 165 | { |
166 | "public", | |
167 | "private", | |
168 | "no-cache", | |
169 | "no-store", | |
170 | "no-transform", | |
171 | "must-revalidate", | |
172 | "proxy-revalidate", | |
173 | "max-age", | |
174 | "NONE" | |
175 | }; | |
176 | ||
151a0b6d | 177 | static char *HttpHdrMiscStr[] = |
178 | { | |
179 | "Accept", | |
180 | "Age", | |
181 | "Content-Length", | |
182 | "Content-MD5", | |
183 | "Content-Type", | |
184 | "Date", | |
185 | "Etag", | |
186 | "Expires", | |
187 | "Host", | |
188 | "If-Modified-Since", | |
189 | "Last-Modified", | |
190 | "Max-Forwards", | |
191 | "Public", | |
192 | "Retry-After", | |
193 | "Set-Cookie", | |
194 | "Upgrade", | |
195 | "Warning", | |
196 | "NONE" | |
197 | }; | |
198 | ||
24382924 | 199 | static struct { |
30a4f2a8 | 200 | int parsed; |
151a0b6d | 201 | int misc[HDR_MISC_END]; |
6fb52f6c | 202 | int cc[SCC_ENUM_END]; |
30a4f2a8 | 203 | } ReplyHeaderStats; |
ee1679df | 204 | |
cb69b4c7 | 205 | #endif /* if 0 */ |
090089c4 | 206 | |
9e4ad609 | 207 | static CNCB httpConnectDone; |
208 | static CWCB httpSendComplete; | |
9e4ad609 | 209 | static PF httpReadReply; |
210 | static PF httpSendRequest; | |
211 | static PF httpStateFree; | |
212 | static PF httpTimeout; | |
f5b8bbc4 | 213 | static void httpAppendRequestHeader(char *hdr, const char *line, size_t * sz, size_t max, int); |
214 | static void httpCacheNegatively(StoreEntry *); | |
215 | static void httpMakePrivate(StoreEntry *); | |
216 | static void httpMakePublic(StoreEntry *); | |
ee1679df | 217 | #if 0 /* moved to HttpResponse */ |
f5b8bbc4 | 218 | static char *httpStatusString(int status); |
cb69b4c7 | 219 | #endif |
bfcaf585 | 220 | static STABH httpAbort; |
f5b8bbc4 | 221 | static HttpStateData *httpBuildState(int, StoreEntry *, request_t *, peer *); |
222 | static int httpSocketOpen(StoreEntry *, request_t *); | |
223 | static void httpRestart(HttpStateData *); | |
f8309b15 | 224 | static int httpCachableReply(HttpStateData *); |
b8d8561b | 225 | |
b177367b | 226 | static void |
79d39a72 | 227 | httpStateFree(int fdnotused, void *data) |
f5558c95 | 228 | { |
b177367b | 229 | HttpStateData *httpState = data; |
0d4d4170 | 230 | if (httpState == NULL) |
b177367b | 231 | return; |
bfcaf585 | 232 | storeUnregisterAbort(httpState->entry); |
ddb6142d | 233 | assert(httpState->entry->store_status != STORE_PENDING); |
f88211e8 | 234 | storeUnlockObject(httpState->entry); |
0d4d4170 | 235 | if (httpState->reply_hdr) { |
3f6c0fb2 | 236 | memFree(MEM_8K_BUF, httpState->reply_hdr); |
0d4d4170 | 237 | httpState->reply_hdr = NULL; |
238 | } | |
30a4f2a8 | 239 | requestUnlink(httpState->request); |
20cc1450 | 240 | requestUnlink(httpState->orig_request); |
7dd44885 | 241 | httpState->request = NULL; |
242 | httpState->orig_request = NULL; | |
243 | cbdataFree(httpState); | |
f5558c95 | 244 | } |
245 | ||
b8d8561b | 246 | int |
75e88d56 | 247 | httpCachable(method_t method) |
090089c4 | 248 | { |
090089c4 | 249 | /* GET and HEAD are cachable. Others are not. */ |
6eb42cae | 250 | if (method != METHOD_GET && method != METHOD_HEAD) |
090089c4 | 251 | return 0; |
090089c4 | 252 | /* else cachable */ |
253 | return 1; | |
254 | } | |
255 | ||
b8d8561b | 256 | static void |
5c5783a2 | 257 | httpTimeout(int fd, void *data) |
090089c4 | 258 | { |
b177367b | 259 | HttpStateData *httpState = data; |
593c9a75 | 260 | StoreEntry *entry = httpState->entry; |
9b312a19 | 261 | ErrorState *err; |
9fb13bb6 | 262 | debug(11, 4) ("httpTimeout: FD %d: '%s'\n", fd, storeUrl(entry)); |
8796b9e9 | 263 | assert(entry->store_status == STORE_PENDING); |
73a3014d | 264 | if (entry->mem_obj->inmem_hi == 0) { |
fe40a877 | 265 | err = errorCon(ERR_READ_TIMEOUT, HTTP_GATEWAY_TIMEOUT); |
79a15e0a | 266 | err->request = requestLink(httpState->orig_request); |
9b312a19 | 267 | errorAppendEntry(entry, err); |
b50179a6 | 268 | } else { |
b34ed725 | 269 | storeAbort(entry, 0); |
9b312a19 | 270 | } |
0d4d4170 | 271 | comm_close(fd); |
090089c4 | 272 | } |
273 | ||
30a4f2a8 | 274 | /* This object can be cached for a long time */ |
b8d8561b | 275 | static void |
276 | httpMakePublic(StoreEntry * entry) | |
30a4f2a8 | 277 | { |
79a15e0a | 278 | if (EBIT_TEST(entry->flag, ENTRY_CACHABLE)) |
30a4f2a8 | 279 | storeSetPublicKey(entry); |
280 | } | |
281 | ||
282 | /* This object should never be cached at all */ | |
b8d8561b | 283 | static void |
284 | httpMakePrivate(StoreEntry * entry) | |
30a4f2a8 | 285 | { |
30a4f2a8 | 286 | storeExpireNow(entry); |
79a15e0a | 287 | EBIT_CLR(entry->flag, ENTRY_CACHABLE); |
30a4f2a8 | 288 | storeReleaseRequest(entry); /* delete object when not used */ |
289 | } | |
290 | ||
291 | /* This object may be negatively cached */ | |
b8d8561b | 292 | static void |
293 | httpCacheNegatively(StoreEntry * entry) | |
30a4f2a8 | 294 | { |
79b5cc5f | 295 | storeNegativeCache(entry); |
79a15e0a | 296 | if (EBIT_TEST(entry->flag, ENTRY_CACHABLE)) |
30a4f2a8 | 297 | storeSetPublicKey(entry); |
30a4f2a8 | 298 | } |
299 | ||
300 | ||
cb69b4c7 | 301 | #if 0 |
30a4f2a8 | 302 | /* Build a reply structure from HTTP reply headers */ |
b8d8561b | 303 | void |
48f44632 | 304 | httpParseReplyHeaders(const char *buf, struct _http_reply *reply) |
30a4f2a8 | 305 | { |
7021844c | 306 | char *headers = memAllocate(MEM_4K_BUF); |
ca85027a | 307 | char *line; |
33b589ff | 308 | char *end; |
30a4f2a8 | 309 | char *s = NULL; |
33b589ff | 310 | char *t; |
ca98227c | 311 | time_t delta; |
312 | size_t l; | |
30a4f2a8 | 313 | |
5b29969a | 314 | assert(reply != NULL); |
f1494beb | 315 | reply->code = 600; |
30a4f2a8 | 316 | ReplyHeaderStats.parsed++; |
33b589ff | 317 | xstrncpy(headers, buf, 4096); |
318 | end = mime_headers_end(headers); | |
ca85027a | 319 | if (end == NULL) { |
320 | t = headers; | |
e2ad7f85 | 321 | if (!strncasecmp(t, "HTTP/", 5)) { |
322 | reply->version = atof(t + 5); | |
323 | if ((t = strchr(t, ' '))) | |
324 | reply->code = atoi(++t); | |
ca85027a | 325 | } |
3f6c0fb2 | 326 | memFree(MEM_4K_BUF, headers); |
e2ad7f85 | 327 | return; |
ca85027a | 328 | } |
329 | reply->hdr_sz = end - headers; | |
7021844c | 330 | line = memAllocate(MEM_4K_BUF); |
33b589ff | 331 | for (s = headers; s < end; s += strcspn(s, crlf), s += strspn(s, crlf)) { |
332 | l = strcspn(s, crlf) + 1; | |
333 | if (l > 4096) | |
334 | l = 4096; | |
335 | xstrncpy(line, s, l); | |
336 | t = line; | |
a3d5953d | 337 | debug(11, 3) ("httpParseReplyHeaders: %s\n", t); |
33b589ff | 338 | if (!strncasecmp(t, "HTTP/", 5)) { |
e2ad7f85 | 339 | reply->version = atof(t + 5); |
33b589ff | 340 | if ((t = strchr(t, ' '))) |
341 | reply->code = atoi(++t); | |
30a4f2a8 | 342 | } else if (!strncasecmp(t, "Content-type:", 13)) { |
33b589ff | 343 | for (t += 13; isspace(*t); t++); |
e91b56e5 | 344 | if ((l = strcspn(t, ";\t ")) > 0) |
345 | *(t + l) = '\0'; | |
33b589ff | 346 | xstrncpy(reply->content_type, t, HTTP_REPLY_FIELD_SZ); |
151a0b6d | 347 | ReplyHeaderStats.misc[HDR_CONTENT_TYPE]++; |
30a4f2a8 | 348 | } else if (!strncasecmp(t, "Content-length:", 15)) { |
33b589ff | 349 | for (t += 15; isspace(*t); t++); |
33b589ff | 350 | reply->content_length = atoi(t); |
151a0b6d | 351 | ReplyHeaderStats.misc[HDR_CONTENT_LENGTH]++; |
30a4f2a8 | 352 | } else if (!strncasecmp(t, "Date:", 5)) { |
33b589ff | 353 | for (t += 5; isspace(*t); t++); |
354 | reply->date = parse_rfc1123(t); | |
151a0b6d | 355 | ReplyHeaderStats.misc[HDR_DATE]++; |
30a4f2a8 | 356 | } else if (!strncasecmp(t, "Expires:", 8)) { |
33b589ff | 357 | for (t += 8; isspace(*t); t++); |
358 | reply->expires = parse_rfc1123(t); | |
359 | /* | |
360 | * The HTTP/1.0 specs says that robust implementations | |
361 | * should consider bad or malformed Expires header as | |
362 | * equivalent to "expires immediately." | |
363 | */ | |
364 | if (reply->expires == -1) | |
365 | reply->expires = squid_curtime; | |
151a0b6d | 366 | ReplyHeaderStats.misc[HDR_EXPIRES]++; |
30a4f2a8 | 367 | } else if (!strncasecmp(t, "Last-Modified:", 14)) { |
33b589ff | 368 | for (t += 14; isspace(*t); t++); |
369 | reply->last_modified = parse_rfc1123(t); | |
151a0b6d | 370 | ReplyHeaderStats.misc[HDR_LAST_MODIFIED]++; |
371 | } else if (!strncasecmp(t, "Accept:", 7)) { | |
372 | ReplyHeaderStats.misc[HDR_ACCEPT]++; | |
373 | } else if (!strncasecmp(t, "Age:", 4)) { | |
374 | ReplyHeaderStats.misc[HDR_AGE]++; | |
375 | } else if (!strncasecmp(t, "Content-MD5:", 12)) { | |
376 | ReplyHeaderStats.misc[HDR_CONTENT_MD5]++; | |
377 | } else if (!strncasecmp(t, "ETag:", 5)) { | |
378 | ReplyHeaderStats.misc[HDR_ETAG]++; | |
379 | } else if (!strncasecmp(t, "Max-Forwards:", 13)) { | |
380 | ReplyHeaderStats.misc[HDR_MAX_FORWARDS]++; | |
381 | } else if (!strncasecmp(t, "Public:", 7)) { | |
382 | ReplyHeaderStats.misc[HDR_PUBLIC]++; | |
383 | } else if (!strncasecmp(t, "Retry-After:", 12)) { | |
384 | ReplyHeaderStats.misc[HDR_RETRY_AFTER]++; | |
385 | } else if (!strncasecmp(t, "Upgrade:", 8)) { | |
386 | ReplyHeaderStats.misc[HDR_UPGRADE]++; | |
387 | } else if (!strncasecmp(t, "Warning:", 8)) { | |
388 | ReplyHeaderStats.misc[HDR_WARNING]++; | |
caebbe00 | 389 | } else if (!strncasecmp(t, "Cache-Control:", 14)) { |
33b589ff | 390 | for (t += 14; isspace(*t); t++); |
4db43fab | 391 | if (!strncasecmp(t, "public", 6)) { |
392 | EBIT_SET(reply->cache_control, SCC_PUBLIC); | |
393 | ReplyHeaderStats.cc[SCC_PUBLIC]++; | |
394 | } else if (!strncasecmp(t, "private", 7)) { | |
395 | EBIT_SET(reply->cache_control, SCC_PRIVATE); | |
396 | ReplyHeaderStats.cc[SCC_PRIVATE]++; | |
397 | } else if (!strncasecmp(t, "no-cache", 8)) { | |
398 | EBIT_SET(reply->cache_control, SCC_NOCACHE); | |
399 | ReplyHeaderStats.cc[SCC_NOCACHE]++; | |
c1764328 | 400 | } else if (!strncasecmp(t, "no-store", 8)) { |
401 | EBIT_SET(reply->cache_control, SCC_NOSTORE); | |
402 | ReplyHeaderStats.cc[SCC_NOSTORE]++; | |
403 | } else if (!strncasecmp(t, "no-transform", 12)) { | |
404 | EBIT_SET(reply->cache_control, SCC_NOTRANSFORM); | |
405 | ReplyHeaderStats.cc[SCC_NOTRANSFORM]++; | |
406 | } else if (!strncasecmp(t, "must-revalidate", 15)) { | |
407 | EBIT_SET(reply->cache_control, SCC_MUSTREVALIDATE); | |
408 | ReplyHeaderStats.cc[SCC_MUSTREVALIDATE]++; | |
409 | } else if (!strncasecmp(t, "proxy-revalidate", 16)) { | |
410 | EBIT_SET(reply->cache_control, SCC_PROXYREVALIDATE); | |
411 | ReplyHeaderStats.cc[SCC_PROXYREVALIDATE]++; | |
4db43fab | 412 | } else if (!strncasecmp(t, "max-age", 7)) { |
413 | if ((t = strchr(t, '='))) { | |
ca98227c | 414 | delta = (time_t) atoi(++t); |
415 | reply->expires = squid_curtime + delta; | |
4db43fab | 416 | EBIT_SET(reply->cache_control, SCC_MAXAGE); |
417 | ReplyHeaderStats.cc[SCC_MAXAGE]++; | |
5df61230 | 418 | } |
caebbe00 | 419 | } |
df3ac7c0 | 420 | } else if (!strncasecmp(t, "Set-Cookie:", 11)) { |
421 | EBIT_SET(reply->misc_headers, HDR_SET_COOKIE); | |
58202be7 | 422 | ReplyHeaderStats.misc[HDR_SET_COOKIE]++; |
1294c0fc | 423 | } else if (!strncasecmp(t, "Proxy-Connection:", 17)) { |
537eedcb | 424 | for (t += 17; isspace(*t); t++); |
1294c0fc | 425 | if (!strcasecmp(t, "Keep-Alive")) |
426 | EBIT_SET(reply->misc_headers, HDR_PROXY_KEEPALIVE); | |
30a4f2a8 | 427 | } |
30a4f2a8 | 428 | } |
3f6c0fb2 | 429 | memFree(MEM_4K_BUF, headers); |
430 | memFree(MEM_4K_BUF, line); | |
30a4f2a8 | 431 | } |
cb69b4c7 | 432 | #endif /* 0 */ |
30a4f2a8 | 433 | |
f8309b15 | 434 | static int |
435 | httpCachableReply(HttpStateData * httpState) | |
c54e9052 | 436 | { |
cb69b4c7 | 437 | HttpHeader *hdr = &httpState->entry->mem_obj->reply->hdr; |
438 | const HttpScc *scc = httpHeaderGetScc(hdr); | |
439 | const int scc_mask = (scc) ? scc->mask : 0; | |
440 | if (EBIT_TEST(scc_mask, SCC_PRIVATE)) | |
f8309b15 | 441 | return 0; |
cb69b4c7 | 442 | if (EBIT_TEST(scc_mask, SCC_NO_CACHE)) |
f8309b15 | 443 | return 0; |
79a15e0a | 444 | if (EBIT_TEST(httpState->request->flags, REQ_AUTH)) |
cb69b4c7 | 445 | if (!EBIT_TEST(scc_mask, SCC_PROXY_REVALIDATE)) |
fee0cebb | 446 | return 0; |
f8309b15 | 447 | /* |
448 | * Dealing with cookies is quite a bit more complicated | |
449 | * than this. Ideally we should strip the cookie | |
450 | * header from the reply but still cache the reply body. | |
451 | * More confusion at draft-ietf-http-state-mgmt-05.txt. | |
452 | */ | |
cb69b4c7 | 453 | /* With new headers the above stripping should be easy to do? @?@ */ |
454 | if (httpHeaderHas(hdr, HDR_SET_COOKIE)) | |
f8309b15 | 455 | return 0; |
cb69b4c7 | 456 | switch (httpState->entry->mem_obj->reply->sline.status) { |
c54e9052 | 457 | /* Responses that are cacheable */ |
458 | case 200: /* OK */ | |
459 | case 203: /* Non-Authoritative Information */ | |
460 | case 300: /* Multiple Choices */ | |
461 | case 301: /* Moved Permanently */ | |
462 | case 410: /* Gone */ | |
1294c0fc | 463 | /* don't cache objects from peers w/o LMT, Date, or Expires */ |
cb69b4c7 | 464 | /* check that is it enough to check headers @?@ */ |
465 | if (httpHeaderHas(hdr, HDR_DATE)) | |
c54e9052 | 466 | return 1; |
cb69b4c7 | 467 | else if (httpHeaderHas(hdr, HDR_LAST_MODIFIED)) |
c54e9052 | 468 | return 1; |
1294c0fc | 469 | else if (!httpState->peer) |
c54e9052 | 470 | return 1; |
cb69b4c7 | 471 | else if (httpHeaderHas(hdr, HDR_EXPIRES)) |
c54e9052 | 472 | return 1; |
c54e9052 | 473 | else |
474 | return 0; | |
79d39a72 | 475 | /* NOTREACHED */ |
c54e9052 | 476 | break; |
477 | /* Responses that only are cacheable if the server says so */ | |
478 | case 302: /* Moved temporarily */ | |
cb69b4c7 | 479 | if (httpHeaderHas(hdr, HDR_EXPIRES)) |
c54e9052 | 480 | return 1; |
481 | else | |
482 | return 0; | |
79d39a72 | 483 | /* NOTREACHED */ |
c54e9052 | 484 | break; |
cb69b4c7 | 485 | /* @?@ should we replace these magic numbers with http_status enums? */ |
c54e9052 | 486 | /* Errors can be negatively cached */ |
487 | case 204: /* No Content */ | |
488 | case 305: /* Use Proxy (proxy redirect) */ | |
489 | case 400: /* Bad Request */ | |
490 | case 403: /* Forbidden */ | |
491 | case 404: /* Not Found */ | |
492 | case 405: /* Method Now Allowed */ | |
493 | case 414: /* Request-URI Too Long */ | |
494 | case 500: /* Internal Server Error */ | |
495 | case 501: /* Not Implemented */ | |
496 | case 502: /* Bad Gateway */ | |
497 | case 503: /* Service Unavailable */ | |
498 | case 504: /* Gateway Timeout */ | |
499 | return -1; | |
79d39a72 | 500 | /* NOTREACHED */ |
c54e9052 | 501 | break; |
502 | /* Some responses can never be cached */ | |
88738790 | 503 | case 206: /* Partial Content -- Not yet supported */ |
c54e9052 | 504 | case 303: /* See Other */ |
505 | case 304: /* Not Modified */ | |
506 | case 401: /* Unauthorized */ | |
507 | case 407: /* Proxy Authentication Required */ | |
508 | case 600: /* Squid header parsing error */ | |
509 | default: /* Unknown status code */ | |
510 | return 0; | |
79d39a72 | 511 | /* NOTREACHED */ |
c54e9052 | 512 | break; |
513 | } | |
79d39a72 | 514 | /* NOTREACHED */ |
c54e9052 | 515 | } |
090089c4 | 516 | |
cb69b4c7 | 517 | /* rewrite this later using new interfaces @?@ */ |
b8d8561b | 518 | void |
0ee4272b | 519 | httpProcessReplyHeader(HttpStateData * httpState, const char *buf, int size) |
f5558c95 | 520 | { |
521 | char *t = NULL; | |
30a4f2a8 | 522 | StoreEntry *entry = httpState->entry; |
d3fb4dea | 523 | int room; |
524 | int hdr_len; | |
cb69b4c7 | 525 | HttpReply *reply = entry->mem_obj->reply; |
b6cfb65c | 526 | debug(11, 3) ("httpProcessReplyHeader: key '%s'\n", |
527 | storeKeyText(entry->key)); | |
e924600d | 528 | if (httpState->reply_hdr == NULL) |
7021844c | 529 | httpState->reply_hdr = memAllocate(MEM_8K_BUF); |
30a4f2a8 | 530 | if (httpState->reply_hdr_state == 0) { |
531 | hdr_len = strlen(httpState->reply_hdr); | |
ed85b771 | 532 | room = 8191 - hdr_len; |
30a4f2a8 | 533 | strncat(httpState->reply_hdr, buf, room < size ? room : size); |
d3fb4dea | 534 | hdr_len += room < size ? room : size; |
30a4f2a8 | 535 | if (hdr_len > 4 && strncmp(httpState->reply_hdr, "HTTP/", 5)) { |
84fa351c | 536 | debug(11, 3) ("httpProcessReplyHeader: Non-HTTP-compliant header: '%s'\n", httpState->reply_hdr); |
30a4f2a8 | 537 | httpState->reply_hdr_state += 2; |
cb69b4c7 | 538 | reply->sline.status = 555; |
ed85b771 | 539 | return; |
d3fb4dea | 540 | } |
d1a43e28 | 541 | t = httpState->reply_hdr + hdr_len; |
542 | /* headers can be incomplete only if object still arriving */ | |
f86a6a46 | 543 | if (!httpState->eof) |
d1a43e28 | 544 | if ((t = mime_headers_end(httpState->reply_hdr)) == NULL) |
545 | return; /* headers not complete */ | |
2285407f | 546 | *t = '\0'; |
30a4f2a8 | 547 | httpState->reply_hdr_state++; |
f5558c95 | 548 | } |
30a4f2a8 | 549 | if (httpState->reply_hdr_state == 1) { |
123abbe1 | 550 | const Ctx ctx = ctx_enter(entry->mem_obj->url); |
30a4f2a8 | 551 | httpState->reply_hdr_state++; |
a3d5953d | 552 | debug(11, 9) ("GOT HTTP REPLY HDR:\n---------\n%s\n----------\n", |
30a4f2a8 | 553 | httpState->reply_hdr); |
554 | /* Parse headers into reply structure */ | |
cb69b4c7 | 555 | /* Old code never parsed headers if mime_headers_end failed, was it intentional ? @?@ @?@ */ |
556 | /* what happens if we fail to parse here? @?@ @?@ */ | |
ee1679df | 557 | httpReplyParse(reply, httpState->reply_hdr); /* httpState->eof); */ |
ca98227c | 558 | storeTimestampsSet(entry); |
30a4f2a8 | 559 | /* Check if object is cacheable or not based on reply code */ |
cb69b4c7 | 560 | debug(11, 3) ("httpProcessReplyHeader: HTTP CODE: %d\n", reply->sline.status); |
f8309b15 | 561 | switch (httpCachableReply(httpState)) { |
c54e9052 | 562 | case 1: |
563 | httpMakePublic(entry); | |
30a4f2a8 | 564 | break; |
c54e9052 | 565 | case 0: |
566 | httpMakePrivate(entry); | |
f5558c95 | 567 | break; |
c54e9052 | 568 | case -1: |
851eeef7 | 569 | httpCacheNegatively(entry); |
30a4f2a8 | 570 | break; |
c54e9052 | 571 | default: |
572 | assert(0); | |
4e38e700 | 573 | break; |
f5558c95 | 574 | } |
cb69b4c7 | 575 | if (httpReplyHasScc(reply, SCC_PROXY_REVALIDATE)) |
79a15e0a | 576 | EBIT_SET(entry->flag, ENTRY_REVALIDATE); |
9a47da71 | 577 | if (EBIT_TEST(httpState->flags, HTTP_KEEPALIVE)) |
578 | if (httpState->peer) | |
579 | httpState->peer->stats.n_keepalives_sent++; | |
cb69b4c7 | 580 | if (httpHeaderHas(&reply->hdr, HDR_PROXY_KEEPALIVE)) |
1294c0fc | 581 | if (httpState->peer) |
582 | httpState->peer->stats.n_keepalives_recv++; | |
123abbe1 | 583 | ctx_exit(ctx); |
f5558c95 | 584 | } |
585 | } | |
586 | ||
603a02fd | 587 | static int |
588 | httpPconnTransferDone(HttpStateData * httpState) | |
589 | { | |
590 | /* return 1 if we got the last of the data on a persistent connection */ | |
591 | MemObject *mem = httpState->entry->mem_obj; | |
cb69b4c7 | 592 | HttpReply *reply = mem->reply; |
51fdcbd5 | 593 | debug(11, 3) ("httpPconnTransferDone: FD %d\n", httpState->fd); |
978e455f | 594 | /* |
595 | * If we didn't send a Keepalive request header, then this | |
596 | * can not be a persistent connection. | |
597 | */ | |
79a15e0a | 598 | if (!EBIT_TEST(httpState->flags, HTTP_KEEPALIVE)) |
603a02fd | 599 | return 0; |
51fdcbd5 | 600 | debug(11, 5) ("httpPconnTransferDone: content_length=%d\n", |
cb69b4c7 | 601 | httpReplyContentLen(reply)); |
603a02fd | 602 | /* |
978e455f | 603 | * Deal with gross HTTP stuff |
604 | * - If we haven't seen the end of the reply headers, we can't | |
605 | * be persistent. | |
606 | * - For "200 OK" check the content-length in the next block. | |
978e455f | 607 | * - For "204 No Content" (even with content-length) we're done. |
608 | * - For "304 Not Modified" (even with content-length) we're done. | |
a3c60429 | 609 | * - 1XX replies never have a body; we're done. |
978e455f | 610 | * - For HEAD requests with content-length we're done. |
a3c60429 | 611 | * - For all other replies, check content length in next block. |
603a02fd | 612 | */ |
978e455f | 613 | if (httpState->reply_hdr_state < 2) |
614 | return 0; | |
cb69b4c7 | 615 | else if (reply->sline.status == HTTP_OK) |
a3c60429 | 616 | (void) 0; /* common case, continue */ |
cb69b4c7 | 617 | else if (reply->sline.status == HTTP_NO_CONTENT) |
978e455f | 618 | return 1; |
cb69b4c7 | 619 | else if (reply->sline.status == HTTP_NOT_MODIFIED) |
978e455f | 620 | return 1; |
cb69b4c7 | 621 | else if (reply->sline.status < HTTP_OK) |
a3c60429 | 622 | return 1; |
978e455f | 623 | else if (httpState->request->method == METHOD_HEAD) |
624 | return 1; | |
603a02fd | 625 | /* |
a3c60429 | 626 | * If there is no content-length, then we can't be |
978e455f | 627 | * persistent. If there is a content length, then we must |
628 | * wait until we've seen the end of the body. | |
603a02fd | 629 | */ |
cb69b4c7 | 630 | if (httpReplyContentLen(reply) < 0) |
603a02fd | 631 | return 0; |
cb69b4c7 | 632 | else if (mem->inmem_hi < httpReplyContentLen(reply) + reply->hdr_sz) |
603a02fd | 633 | return 0; |
978e455f | 634 | else |
b34ed725 | 635 | return 1; |
603a02fd | 636 | } |
090089c4 | 637 | |
638 | /* This will be called when data is ready to be read from fd. Read until | |
639 | * error or connection closed. */ | |
f5558c95 | 640 | /* XXX this function is too long! */ |
b8d8561b | 641 | static void |
b177367b | 642 | httpReadReply(int fd, void *data) |
090089c4 | 643 | { |
b177367b | 644 | HttpStateData *httpState = data; |
95d659f0 | 645 | LOCAL_ARRAY(char, buf, SQUID_TCP_SO_RCVBUF); |
bfcaf585 | 646 | StoreEntry *entry = httpState->entry; |
603a02fd | 647 | const request_t *request = httpState->request; |
090089c4 | 648 | int len; |
30a4f2a8 | 649 | int bin; |
090089c4 | 650 | int clen; |
9b312a19 | 651 | ErrorState *err; |
d89d1fb6 | 652 | if (protoAbortFetch(entry)) { |
9b312a19 | 653 | storeAbort(entry, 0); |
a3d5953d | 654 | comm_close(fd); |
655 | return; | |
234967c9 | 656 | } |
657 | /* check if we want to defer reading */ | |
8350fe9b | 658 | clen = entry->mem_obj->inmem_hi; |
1513873c | 659 | errno = 0; |
30a4f2a8 | 660 | len = read(fd, buf, SQUID_TCP_SO_RCVBUF); |
a3d5953d | 661 | debug(11, 5) ("httpReadReply: FD %d: len %d.\n", fd, len); |
30a4f2a8 | 662 | if (len > 0) { |
ee1679df | 663 | fd_bytes(fd, len, FD_READ); |
664 | kb_incr(&Counter.server.kbytes_in, len); | |
4f92c80c | 665 | commSetTimeout(fd, Config.Timeout.read, NULL, NULL); |
4a63c85f | 666 | IOStats.Http.reads++; |
30a4f2a8 | 667 | for (clen = len - 1, bin = 0; clen; bin++) |
668 | clen >>= 1; | |
669 | IOStats.Http.read_hist[bin]++; | |
670 | } | |
ba718c8f | 671 | if (len < 0) { |
b224ea98 | 672 | if (ignoreErrno(errno)) { |
9b312a19 | 673 | commSetSelect(fd, COMM_SELECT_READ, httpReadReply, httpState, 0); |
090089c4 | 674 | } else { |
73a3014d | 675 | if (clen == 0) { |
fe40a877 | 676 | err = errorCon(ERR_READ_ERROR, HTTP_INTERNAL_SERVER_ERROR); |
c45ed9ad | 677 | err->xerrno = errno; |
79a15e0a | 678 | err->request = requestLink(httpState->orig_request); |
9b312a19 | 679 | errorAppendEntry(entry, err); |
b50179a6 | 680 | } else { |
b34ed725 | 681 | storeAbort(entry, 0); |
9b312a19 | 682 | } |
0d4d4170 | 683 | comm_close(fd); |
090089c4 | 684 | } |
a3d5953d | 685 | debug(50, 2) ("httpReadReply: FD %d: read failure: %s.\n", |
0a0bf5db | 686 | fd, xstrerror()); |
8350fe9b | 687 | } else if (len == 0 && entry->mem_obj->inmem_hi == 0) { |
b716a8ad | 688 | if (fd_table[fd].uses > 1) { |
689 | httpRestart(httpState); | |
690 | } else { | |
691 | httpState->eof = 1; | |
fe40a877 | 692 | err = errorCon(ERR_ZERO_SIZE_OBJECT, HTTP_SERVICE_UNAVAILABLE); |
c45ed9ad | 693 | err->xerrno = errno; |
79a15e0a | 694 | err->request = requestLink(httpState->orig_request); |
b716a8ad | 695 | errorAppendEntry(entry, err); |
b716a8ad | 696 | comm_close(fd); |
697 | } | |
090089c4 | 698 | } else if (len == 0) { |
699 | /* Connection closed; retrieval done. */ | |
f86a6a46 | 700 | httpState->eof = 1; |
d1a43e28 | 701 | if (httpState->reply_hdr_state < 2) |
b34ed725 | 702 | /* |
703 | * Yes Henrik, there is a point to doing this. When we | |
704 | * called httpProcessReplyHeader() before, we didn't find | |
705 | * the end of headers, but now we are definately at EOF, so | |
706 | * we want to process the reply headers. | |
707 | */ | |
d1a43e28 | 708 | httpProcessReplyHeader(httpState, buf, len); |
d1a43e28 | 709 | storeComplete(entry); /* deallocates mem_obj->request */ |
0d4d4170 | 710 | comm_close(fd); |
090089c4 | 711 | } else { |
d1a43e28 | 712 | if (httpState->reply_hdr_state < 2) |
30a4f2a8 | 713 | httpProcessReplyHeader(httpState, buf, len); |
620da955 | 714 | storeAppend(entry, buf, len); |
603a02fd | 715 | if (httpPconnTransferDone(httpState)) { |
5b29969a | 716 | /* yes we have to clear all these! */ |
8796b9e9 | 717 | commSetDefer(fd, NULL, NULL); |
5b29969a | 718 | commSetTimeout(fd, -1, NULL, NULL); |
719 | commSetSelect(fd, COMM_SELECT_READ, NULL, NULL, 0); | |
603a02fd | 720 | comm_remove_close_handler(fd, httpStateFree, httpState); |
721 | storeComplete(entry); /* deallocates mem_obj->request */ | |
8796b9e9 | 722 | pconnPush(fd, request->host, request->port); |
603a02fd | 723 | httpState->fd = -1; |
724 | httpStateFree(-1, httpState); | |
725 | } else { | |
726 | commSetSelect(fd, COMM_SELECT_READ, httpReadReply, httpState, 0); | |
727 | } | |
090089c4 | 728 | } |
729 | } | |
730 | ||
731 | /* This will be called when request write is complete. Schedule read of | |
732 | * reply. */ | |
b8d8561b | 733 | static void |
79a15e0a | 734 | httpSendComplete(int fd, char *bufnotused, size_t size, int errflag, void *data) |
090089c4 | 735 | { |
30a4f2a8 | 736 | HttpStateData *httpState = data; |
9b312a19 | 737 | StoreEntry *entry = httpState->entry; |
738 | ErrorState *err; | |
a3d5953d | 739 | debug(11, 5) ("httpSendComplete: FD %d: size %d: errflag %d.\n", |
090089c4 | 740 | fd, size, errflag); |
ee1679df | 741 | if (size > 0) { |
742 | fd_bytes(fd, size, FD_WRITE); | |
743 | kb_incr(&Counter.server.kbytes_out, size); | |
744 | } | |
ea3a2a69 | 745 | if (errflag == COMM_ERR_CLOSING) |
746 | return; | |
090089c4 | 747 | if (errflag) { |
fe40a877 | 748 | err = errorCon(ERR_WRITE_ERROR, HTTP_INTERNAL_SERVER_ERROR); |
c45ed9ad | 749 | err->xerrno = errno; |
79a15e0a | 750 | err->request = requestLink(httpState->orig_request); |
9b312a19 | 751 | errorAppendEntry(entry, err); |
0d4d4170 | 752 | comm_close(fd); |
090089c4 | 753 | return; |
754 | } else { | |
755 | /* Schedule read reply. */ | |
b177367b | 756 | commSetSelect(fd, |
019dd986 | 757 | COMM_SELECT_READ, |
b177367b | 758 | httpReadReply, |
cd1fb0eb | 759 | httpState, 0); |
70a9dab4 | 760 | commSetDefer(fd, protoCheckDeferRead, entry); |
090089c4 | 761 | } |
762 | } | |
763 | ||
6bf8443a | 764 | static void |
88738790 | 765 | httpAppendRequestHeader(char *hdr, const char *line, size_t * sz, size_t max, int check) |
6bf8443a | 766 | { |
767 | size_t n = *sz + strlen(line) + 2; | |
768 | if (n >= max) | |
769 | return; | |
88738790 | 770 | if (check) { |
17a0a4ee | 771 | if (Config.onoff.anonymizer == ANONYMIZER_PARANOID) { |
88738790 | 772 | if (!httpAnonAllowed(line)) |
773 | return; | |
17a0a4ee | 774 | } else if (Config.onoff.anonymizer == ANONYMIZER_STANDARD) { |
88738790 | 775 | if (httpAnonDenied(line)) |
776 | return; | |
777 | } | |
4a83b852 | 778 | } |
4a83b852 | 779 | /* allowed header, explicitly known to be not dangerous */ |
a3d5953d | 780 | debug(11, 5) ("httpAppendRequestHeader: %s\n", line); |
929545fe | 781 | strcpy(hdr + (*sz), line); |
6bf8443a | 782 | strcat(hdr + (*sz), crlf); |
783 | *sz = n; | |
784 | } | |
785 | ||
1294c0fc | 786 | #define YBUF_SZ (MAX_URL+32) |
6bf8443a | 787 | size_t |
788 | httpBuildRequestHeader(request_t * request, | |
789 | request_t * orig_request, | |
790 | StoreEntry * entry, | |
6bf8443a | 791 | size_t * in_len, |
792 | char *hdr_out, | |
793 | size_t out_sz, | |
603a02fd | 794 | int cfd, |
795 | int flags) | |
6bf8443a | 796 | { |
1294c0fc | 797 | LOCAL_ARRAY(char, ybuf, YBUF_SZ); |
ab013258 | 798 | LOCAL_ARRAY(char, no_forward, 1024); |
7021844c | 799 | char *xbuf = memAllocate(MEM_4K_BUF); |
800 | char *viabuf = memAllocate(MEM_4K_BUF); | |
801 | char *fwdbuf = memAllocate(MEM_4K_BUF); | |
6bf8443a | 802 | char *t = NULL; |
803 | char *s = NULL; | |
804 | char *end = NULL; | |
805 | size_t len = 0; | |
806 | size_t hdr_len = 0; | |
6bf8443a | 807 | size_t l; |
808 | int hdr_flags = 0; | |
151a0b6d | 809 | int cc_flags = 0; |
b3b64e58 | 810 | int n; |
6bf8443a | 811 | const char *url = NULL; |
2357f74a | 812 | char *hdr_in = orig_request->headers; |
6bf8443a | 813 | |
2357f74a | 814 | assert(hdr_in != NULL); |
a3d5953d | 815 | debug(11, 3) ("httpBuildRequestHeader: INPUT:\n%s\n", hdr_in); |
6bf8443a | 816 | xstrncpy(fwdbuf, "X-Forwarded-For: ", 4096); |
817 | xstrncpy(viabuf, "Via: ", 4096); | |
1294c0fc | 818 | snprintf(ybuf, YBUF_SZ, "%s %s HTTP/1.0", |
6bf8443a | 819 | RequestMethodStr[request->method], |
820 | *request->urlpath ? request->urlpath : "/"); | |
88738790 | 821 | httpAppendRequestHeader(hdr_out, ybuf, &len, out_sz, 1); |
6bf8443a | 822 | /* Add IMS header */ |
823 | if (entry && entry->lastmod && request->method == METHOD_GET) { | |
1294c0fc | 824 | snprintf(ybuf, YBUF_SZ, "If-Modified-Since: %s", mkrfc1123(entry->lastmod)); |
88738790 | 825 | httpAppendRequestHeader(hdr_out, ybuf, &len, out_sz, 1); |
6bf8443a | 826 | EBIT_SET(hdr_flags, HDR_IMS); |
827 | } | |
00c59270 | 828 | end = mime_headers_end(hdr_in); |
6bf8443a | 829 | for (t = hdr_in; t < end; t += strcspn(t, crlf), t += strspn(t, crlf)) { |
830 | hdr_len = t - hdr_in; | |
6bf8443a | 831 | l = strcspn(t, crlf) + 1; |
832 | if (l > 4096) | |
833 | l = 4096; | |
834 | xstrncpy(xbuf, t, l); | |
a3d5953d | 835 | debug(11, 5) ("httpBuildRequestHeader: %s\n", xbuf); |
6bf8443a | 836 | if (strncasecmp(xbuf, "Proxy-Connection:", 17) == 0) |
837 | continue; | |
88738790 | 838 | if (strncasecmp(xbuf, "Proxy-authorization:", 20) == 0) |
afe95a7e | 839 | /* If we're not going to do proxy auth, then it must be passed on */ |
79a15e0a | 840 | if (EBIT_TEST(request->flags, REQ_USED_PROXY_AUTH)) |
88738790 | 841 | continue; |
ab013258 | 842 | if (strncasecmp(xbuf, "Connection:", 11) == 0) { |
067bea91 | 843 | handleConnectionHeader(0, no_forward, &xbuf[11]); |
6bf8443a | 844 | continue; |
067bea91 | 845 | } |
66f7337b | 846 | if (strncasecmp(xbuf, "Host:", 5) == 0) { |
6bf8443a | 847 | EBIT_SET(hdr_flags, HDR_HOST); |
66f7337b | 848 | } else if (strncasecmp(xbuf, "Cache-Control:", 14) == 0) { |
6bf8443a | 849 | for (s = xbuf + 14; *s && isspace(*s); s++); |
850 | if (strncasecmp(s, "Max-age=", 8) == 0) | |
151a0b6d | 851 | EBIT_SET(cc_flags, CCC_MAXAGE); |
66f7337b | 852 | } else if (strncasecmp(xbuf, "Via:", 4) == 0) { |
6bf8443a | 853 | for (s = xbuf + 4; *s && isspace(*s); s++); |
69e81830 | 854 | if ((int) strlen(viabuf) + (int) strlen(s) < 4000) |
6bf8443a | 855 | strcat(viabuf, s); |
856 | strcat(viabuf, ", "); | |
857 | continue; | |
66f7337b | 858 | } else if (strncasecmp(xbuf, "X-Forwarded-For:", 16) == 0) { |
6bf8443a | 859 | for (s = xbuf + 16; *s && isspace(*s); s++); |
69e81830 | 860 | if ((int) strlen(fwdbuf) + (int) strlen(s) < 4000) |
6bf8443a | 861 | strcat(fwdbuf, s); |
862 | strcat(fwdbuf, ", "); | |
863 | continue; | |
66f7337b | 864 | } else if (strncasecmp(xbuf, "If-Modified-Since:", 18) == 0) { |
6bf8443a | 865 | if (EBIT_TEST(hdr_flags, HDR_IMS)) |
866 | continue; | |
b3b64e58 | 867 | } else if (strncasecmp(xbuf, "Max-Forwards:", 13) == 0) { |
868 | if (orig_request->method == METHOD_TRACE) { | |
869 | for (s = xbuf + 13; *s && isspace(*s); s++); | |
870 | n = atoi(s); | |
56878878 | 871 | snprintf(xbuf, 4096, "Max-Forwards: %d", n - 1); |
b3b64e58 | 872 | } |
66f7337b | 873 | } |
067bea91 | 874 | if (!handleConnectionHeader(1, no_forward, xbuf)) |
ab013258 | 875 | httpAppendRequestHeader(hdr_out, xbuf, &len, out_sz - 512, 1); |
88738790 | 876 | } |
877 | hdr_len = t - hdr_in; | |
878 | if (Config.fake_ua && strstr(hdr_out, "User-Agent") == NULL) { | |
1294c0fc | 879 | snprintf(ybuf, YBUF_SZ, "User-Agent: %s", Config.fake_ua); |
88738790 | 880 | httpAppendRequestHeader(hdr_out, ybuf, &len, out_sz, 0); |
6bf8443a | 881 | } |
56878878 | 882 | /* Append Via: */ |
883 | /* snprintf would fail here too */ | |
1294c0fc | 884 | snprintf(ybuf, YBUF_SZ, "%3.1f %s", orig_request->http_ver, ThisCache); |
6bf8443a | 885 | strcat(viabuf, ybuf); |
88738790 | 886 | httpAppendRequestHeader(hdr_out, viabuf, &len, out_sz, 1); |
6bf8443a | 887 | /* Append to X-Forwarded-For: */ |
a08307eb | 888 | strcat(fwdbuf, cfd < 0 ? "unknown" : fd_table[cfd].ipaddr); |
88738790 | 889 | httpAppendRequestHeader(hdr_out, fwdbuf, &len, out_sz, 1); |
6bf8443a | 890 | if (!EBIT_TEST(hdr_flags, HDR_HOST)) { |
1294c0fc | 891 | snprintf(ybuf, YBUF_SZ, "Host: %s", orig_request->host); |
88738790 | 892 | httpAppendRequestHeader(hdr_out, ybuf, &len, out_sz, 1); |
6bf8443a | 893 | } |
151a0b6d | 894 | if (!EBIT_TEST(cc_flags, CCC_MAXAGE)) { |
9fb13bb6 | 895 | url = entry ? storeUrl(entry) : urlCanonical(orig_request, NULL); |
1294c0fc | 896 | snprintf(ybuf, YBUF_SZ, "Cache-control: Max-age=%d", (int) getMaxAge(url)); |
88738790 | 897 | httpAppendRequestHeader(hdr_out, ybuf, &len, out_sz, 1); |
79d39a72 | 898 | if (request->urlpath[0]) |
365e5b34 | 899 | assert(strstr(url, request->urlpath)); |
6bf8443a | 900 | } |
603a02fd | 901 | /* maybe append Connection: Keep-Alive */ |
79a15e0a | 902 | if (EBIT_TEST(flags, HTTP_KEEPALIVE)) { |
903 | if (EBIT_TEST(flags, HTTP_PROXYING)) { | |
1294c0fc | 904 | snprintf(ybuf, YBUF_SZ, "Proxy-Connection: Keep-Alive"); |
603a02fd | 905 | } else { |
1294c0fc | 906 | snprintf(ybuf, YBUF_SZ, "Connection: Keep-Alive"); |
603a02fd | 907 | } |
908 | httpAppendRequestHeader(hdr_out, ybuf, &len, out_sz, 1); | |
909 | } | |
88738790 | 910 | httpAppendRequestHeader(hdr_out, null_string, &len, out_sz, 1); |
3f6c0fb2 | 911 | memFree(MEM_4K_BUF, xbuf); |
912 | memFree(MEM_4K_BUF, viabuf); | |
913 | memFree(MEM_4K_BUF, fwdbuf); | |
6bf8443a | 914 | if (in_len) |
915 | *in_len = hdr_len; | |
9d9d144b | 916 | if ((l = strlen(hdr_out)) != len) { |
917 | debug_trap("httpBuildRequestHeader: size mismatch"); | |
918 | len = l; | |
919 | } | |
a3d5953d | 920 | debug(11, 3) ("httpBuildRequestHeader: OUTPUT:\n%s\n", hdr_out); |
6bf8443a | 921 | return len; |
922 | } | |
923 | ||
090089c4 | 924 | /* This will be called when connect completes. Write request. */ |
b8d8561b | 925 | static void |
b177367b | 926 | httpSendRequest(int fd, void *data) |
090089c4 | 927 | { |
b177367b | 928 | HttpStateData *httpState = data; |
090089c4 | 929 | char *buf = NULL; |
090089c4 | 930 | int len = 0; |
931 | int buflen; | |
30a4f2a8 | 932 | request_t *req = httpState->request; |
9864ee44 | 933 | int buftype = 0; |
620da955 | 934 | StoreEntry *entry = httpState->entry; |
2a26c096 | 935 | int cfd; |
1294c0fc | 936 | peer *p = httpState->peer; |
090089c4 | 937 | |
a3d5953d | 938 | debug(11, 5) ("httpSendRequest: FD %d: httpState %p.\n", fd, httpState); |
6bf8443a | 939 | buflen = strlen(req->urlpath); |
62dec5a7 | 940 | if (req->headers) |
941 | buflen += req->headers_sz + 1; | |
090089c4 | 942 | buflen += 512; /* lots of extra */ |
943 | ||
78657218 | 944 | if ((req->method == METHOD_POST || req->method == METHOD_PUT)) { |
945 | debug_trap("httpSendRequest: should not be handling POST/PUT request"); | |
946 | return; | |
090089c4 | 947 | } |
30a4f2a8 | 948 | if (buflen < DISK_PAGE_SIZE) { |
7021844c | 949 | buf = memAllocate(MEM_8K_BUF); |
9864ee44 | 950 | buftype = BUF_TYPE_8K; |
6bf8443a | 951 | buflen = DISK_PAGE_SIZE; |
30a4f2a8 | 952 | } else { |
9864ee44 | 953 | buf = xcalloc(buflen, 1); |
954 | buftype = BUF_TYPE_MALLOC; | |
090089c4 | 955 | } |
2a26c096 | 956 | if (!opt_forwarded_for) |
6bf8443a | 957 | cfd = -1; |
2a26c096 | 958 | else if (entry->mem_obj == NULL) |
6bf8443a | 959 | cfd = -1; |
2a26c096 | 960 | else |
382d851a | 961 | cfd = entry->mem_obj->fd; |
1294c0fc | 962 | if (p != NULL) |
79a15e0a | 963 | EBIT_SET(httpState->flags, HTTP_PROXYING); |
1294c0fc | 964 | if (req->method == METHOD_GET) { |
9a47da71 | 965 | if (p == NULL) |
966 | EBIT_SET(httpState->flags, HTTP_KEEPALIVE); | |
967 | else if (p->stats.n_keepalives_sent < 10) | |
968 | EBIT_SET(httpState->flags, HTTP_KEEPALIVE); | |
969 | else if ((double) p->stats.n_keepalives_recv / (double) p->stats.n_keepalives_sent > 0.50) | |
79a15e0a | 970 | EBIT_SET(httpState->flags, HTTP_KEEPALIVE); |
1294c0fc | 971 | } |
6bf8443a | 972 | len = httpBuildRequestHeader(req, |
79a15e0a | 973 | httpState->orig_request, |
6bf8443a | 974 | entry, |
6bf8443a | 975 | NULL, |
976 | buf, | |
977 | buflen, | |
603a02fd | 978 | cfd, |
979 | httpState->flags); | |
a3d5953d | 980 | debug(11, 6) ("httpSendRequest: FD %d:\n%s\n", fd, buf); |
30a4f2a8 | 981 | comm_write(fd, |
14e59844 | 982 | buf, |
983 | len, | |
14e59844 | 984 | httpSendComplete, |
9864ee44 | 985 | httpState, |
3f6c0fb2 | 986 | buftype == BUF_TYPE_8K ? memFree8K : xfree); |
52ecbd09 | 987 | #ifdef BREAKS_PCONN_RESTART |
20cc1450 | 988 | requestUnlink(httpState->orig_request); |
989 | httpState->orig_request = NULL; | |
52ecbd09 | 990 | #endif |
090089c4 | 991 | } |
992 | ||
603a02fd | 993 | static int |
b716a8ad | 994 | httpSocketOpen(StoreEntry * entry, request_t * request) |
090089c4 | 995 | { |
9e4ad609 | 996 | int fd; |
9b312a19 | 997 | ErrorState *err; |
9e4ad609 | 998 | fd = comm_open(SOCK_STREAM, |
16b204c4 | 999 | 0, |
1000 | Config.Addrs.tcp_outgoing, | |
1001 | 0, | |
1002 | COMM_NONBLOCKING, | |
9fb13bb6 | 1003 | storeUrl(entry)); |
603a02fd | 1004 | if (fd < 0) { |
79a15e0a | 1005 | debug(50, 4) ("httpSocketOpen: %s\n", xstrerror()); |
fe40a877 | 1006 | err = errorCon(ERR_SOCKET_FAILURE, HTTP_INTERNAL_SERVER_ERROR); |
c45ed9ad | 1007 | err->xerrno = errno; |
79a15e0a | 1008 | err->request = requestLink(request); |
9b312a19 | 1009 | errorAppendEntry(entry, err); |
090089c4 | 1010 | } |
603a02fd | 1011 | return fd; |
1012 | } | |
1013 | ||
1014 | static HttpStateData * | |
1015 | httpBuildState(int fd, StoreEntry * entry, request_t * orig_request, peer * e) | |
1016 | { | |
1017 | HttpStateData *httpState = xcalloc(1, sizeof(HttpStateData)); | |
1018 | request_t *request; | |
770f051d | 1019 | storeLockObject(entry); |
3f6c0fb2 | 1020 | cbdataAdd(httpState, MEM_NONE); |
0a0bf5db | 1021 | httpState->entry = entry; |
9e4ad609 | 1022 | httpState->fd = fd; |
603a02fd | 1023 | if (e) { |
7021844c | 1024 | request = memAllocate(MEM_REQUEST_T); |
603a02fd | 1025 | request->method = orig_request->method; |
1026 | xstrncpy(request->host, e->host, SQUIDHOSTNAMELEN); | |
1027 | request->port = e->http_port; | |
9fb13bb6 | 1028 | xstrncpy(request->urlpath, storeUrl(entry), MAX_URL); |
603a02fd | 1029 | httpState->request = requestLink(request); |
1294c0fc | 1030 | httpState->peer = e; |
603a02fd | 1031 | httpState->orig_request = requestLink(orig_request); |
79a15e0a | 1032 | EBIT_SET(request->flags, REQ_PROXYING); |
603a02fd | 1033 | } else { |
1034 | httpState->request = requestLink(orig_request); | |
79a15e0a | 1035 | httpState->orig_request = requestLink(orig_request); |
603a02fd | 1036 | } |
0d4d4170 | 1037 | /* register the handler to free HTTP state data when the FD closes */ |
603a02fd | 1038 | comm_add_close_handler(httpState->fd, httpStateFree, httpState); |
e102ebda | 1039 | storeRegisterAbort(entry, httpAbort, httpState); |
603a02fd | 1040 | return httpState; |
1041 | } | |
1042 | ||
1043 | void | |
1044 | httpStart(request_t * request, StoreEntry * entry, peer * e) | |
1045 | { | |
1046 | HttpStateData *httpState; | |
1047 | int fd; | |
1048 | debug(11, 3) ("httpStart: \"%s %s\"\n", | |
9fb13bb6 | 1049 | RequestMethodStr[request->method], storeUrl(entry)); |
603a02fd | 1050 | if (e) { |
1051 | if (e->options & NEIGHBOR_PROXY_ONLY) | |
603a02fd | 1052 | storeReleaseRequest(entry); |
603a02fd | 1053 | if ((fd = pconnPop(e->host, e->http_port)) >= 0) { |
51fdcbd5 | 1054 | debug(11, 3) ("httpStart: reusing pconn FD %d\n", fd); |
603a02fd | 1055 | httpState = httpBuildState(fd, entry, request, e); |
b716a8ad | 1056 | commSetTimeout(httpState->fd, |
1057 | Config.Timeout.connect, | |
1058 | httpTimeout, | |
1059 | httpState); | |
603a02fd | 1060 | httpConnectDone(fd, COMM_OK, httpState); |
1061 | return; | |
1062 | } | |
1063 | } else { | |
1064 | if ((fd = pconnPop(request->host, request->port)) >= 0) { | |
51fdcbd5 | 1065 | debug(11, 3) ("httpStart: reusing pconn FD %d\n", fd); |
603a02fd | 1066 | httpState = httpBuildState(fd, entry, request, e); |
b716a8ad | 1067 | commSetTimeout(httpState->fd, |
1068 | Config.Timeout.connect, | |
1069 | httpTimeout, | |
1070 | httpState); | |
603a02fd | 1071 | httpConnectDone(fd, COMM_OK, httpState); |
1072 | return; | |
1073 | } | |
1074 | } | |
79a15e0a | 1075 | if ((fd = httpSocketOpen(entry, request)) < 0) |
603a02fd | 1076 | return; |
1077 | httpState = httpBuildState(fd, entry, request, e); | |
603a02fd | 1078 | commSetTimeout(httpState->fd, |
1079 | Config.Timeout.connect, | |
1080 | httpTimeout, | |
cd1fb0eb | 1081 | httpState); |
edeb28fd | 1082 | commConnectStart(httpState->fd, |
603a02fd | 1083 | httpState->request->host, |
1084 | httpState->request->port, | |
e924600d | 1085 | httpConnectDone, |
1086 | httpState); | |
e5f6c5c2 | 1087 | } |
1088 | ||
b716a8ad | 1089 | static void |
1090 | httpRestart(HttpStateData * httpState) | |
1091 | { | |
1092 | /* restart a botched request from a persistent connection */ | |
9fb13bb6 | 1093 | debug(11, 2) ("Retrying HTTP request for %s\n", storeUrl(httpState->entry)); |
b716a8ad | 1094 | if (httpState->fd >= 0) { |
1095 | comm_remove_close_handler(httpState->fd, httpStateFree, httpState); | |
1096 | comm_close(httpState->fd); | |
1097 | httpState->fd = -1; | |
1098 | } | |
79a15e0a | 1099 | httpState->fd = httpSocketOpen(httpState->entry, httpState->orig_request); |
b716a8ad | 1100 | if (httpState->fd < 0) |
1101 | return; | |
1102 | comm_add_close_handler(httpState->fd, httpStateFree, httpState); | |
1103 | commSetTimeout(httpState->fd, | |
1104 | Config.Timeout.connect, | |
1105 | httpTimeout, | |
1106 | httpState); | |
1107 | commConnectStart(httpState->fd, | |
1108 | httpState->request->host, | |
1109 | httpState->request->port, | |
1110 | httpConnectDone, | |
1111 | httpState); | |
1112 | } | |
1113 | ||
e5f6c5c2 | 1114 | static void |
1115 | httpConnectDone(int fd, int status, void *data) | |
1116 | { | |
1117 | HttpStateData *httpState = data; | |
1118 | request_t *request = httpState->request; | |
1119 | StoreEntry *entry = httpState->entry; | |
9b312a19 | 1120 | ErrorState *err; |
edeb28fd | 1121 | if (status == COMM_ERR_DNS) { |
a3d5953d | 1122 | debug(11, 4) ("httpConnectDone: Unknown host: %s\n", request->host); |
fe40a877 | 1123 | err = errorCon(ERR_DNS_FAIL, HTTP_SERVICE_UNAVAILABLE); |
9b312a19 | 1124 | err->dnsserver_msg = xstrdup(dns_error_message); |
79a15e0a | 1125 | err->request = requestLink(httpState->orig_request); |
9b312a19 | 1126 | errorAppendEntry(entry, err); |
edeb28fd | 1127 | comm_close(fd); |
1128 | } else if (status != COMM_OK) { | |
fe40a877 | 1129 | err = errorCon(ERR_CONNECT_FAIL, HTTP_SERVICE_UNAVAILABLE); |
c45ed9ad | 1130 | err->xerrno = errno; |
9b312a19 | 1131 | err->host = xstrdup(request->host); |
1132 | err->port = request->port; | |
79a15e0a | 1133 | err->request = requestLink(httpState->orig_request); |
9b312a19 | 1134 | errorAppendEntry(entry, err); |
1294c0fc | 1135 | if (httpState->peer) |
1136 | peerCheckConnectStart(httpState->peer); | |
e5f6c5c2 | 1137 | comm_close(fd); |
1138 | } else { | |
9fb13bb6 | 1139 | fd_note(fd, storeUrl(entry)); |
b716a8ad | 1140 | fd_table[fd].uses++; |
bfcaf585 | 1141 | commSetSelect(fd, COMM_SELECT_WRITE, httpSendRequest, httpState, 0); |
090089c4 | 1142 | } |
090089c4 | 1143 | } |
1144 | ||
ee1679df | 1145 | #if 0 /* moved to httpHeader */ |
b8d8561b | 1146 | void |
1147 | httpReplyHeaderStats(StoreEntry * entry) | |
30a4f2a8 | 1148 | { |
6fb52f6c | 1149 | http_server_cc_t i; |
151a0b6d | 1150 | http_hdr_misc_t j; |
15576b6a | 1151 | storeAppendPrintf(entry, "HTTP Reply Headers:\n"); |
1152 | storeAppendPrintf(entry, " Headers parsed: %d\n", | |
30a4f2a8 | 1153 | ReplyHeaderStats.parsed); |
151a0b6d | 1154 | for (j = HDR_AGE; j < HDR_MISC_END; j++) |
15576b6a | 1155 | storeAppendPrintf(entry, "%21.21s: %d\n", |
151a0b6d | 1156 | HttpHdrMiscStr[j], |
1157 | ReplyHeaderStats.misc[j]); | |
4db43fab | 1158 | for (i = SCC_PUBLIC; i < SCC_ENUM_END; i++) |
15576b6a | 1159 | storeAppendPrintf(entry, "Cache-Control %s: %d\n", |
6fb52f6c | 1160 | HttpServerCCStr[i], |
1161 | ReplyHeaderStats.cc[i]); | |
30a4f2a8 | 1162 | } |
cb69b4c7 | 1163 | #endif |
bfcaf585 | 1164 | |
22f3fd98 | 1165 | void |
1166 | httpInit(void) | |
1167 | { | |
22f3fd98 | 1168 | } |
1169 | ||
bfcaf585 | 1170 | static void |
1171 | httpAbort(void *data) | |
1172 | { | |
1173 | HttpStateData *httpState = data; | |
9fb13bb6 | 1174 | debug(11, 2) ("httpAbort: %s\n", storeUrl(httpState->entry)); |
bfcaf585 | 1175 | comm_close(httpState->fd); |
1176 | } | |
9b312a19 | 1177 | |
ee1679df | 1178 | #if 0 /* moved to httpResponse.c */ |
9b312a19 | 1179 | static char * |
1180 | httpStatusString(int status) | |
1181 | { | |
1182 | char *p = NULL; | |
1183 | switch (status) { | |
1184 | case 100: | |
1185 | p = "Continue"; | |
1186 | break; | |
1187 | case 101: | |
1188 | p = "Switching Protocols"; | |
1189 | break; | |
1190 | case 200: | |
1191 | p = "OK"; | |
1192 | break; | |
1193 | case 201: | |
1194 | p = "Created"; | |
1195 | break; | |
1196 | case 202: | |
1197 | p = "Accepted"; | |
1198 | break; | |
1199 | case 203: | |
1200 | p = "Non-Authoritative Information"; | |
1201 | break; | |
1202 | case 204: | |
1203 | p = "No Content"; | |
1204 | break; | |
1205 | case 205: | |
1206 | p = "Reset Content"; | |
1207 | break; | |
1208 | case 206: | |
1209 | p = "Partial Content"; | |
1210 | break; | |
1211 | case 300: | |
1212 | p = "Multiple Choices"; | |
1213 | break; | |
1214 | case 301: | |
1215 | p = "Moved Permanently"; | |
1216 | break; | |
1217 | case 302: | |
1218 | p = "Moved Temporarily"; | |
1219 | break; | |
1220 | case 303: | |
1221 | p = "See Other"; | |
1222 | break; | |
1223 | case 304: | |
1224 | p = "Not Modified"; | |
1225 | break; | |
1226 | case 305: | |
1227 | p = "Use Proxy"; | |
1228 | break; | |
1229 | case 400: | |
1230 | p = "Bad Request"; | |
1231 | break; | |
1232 | case 401: | |
1233 | p = "Unauthorized"; | |
1234 | break; | |
1235 | case 402: | |
1236 | p = "Payment Required"; | |
1237 | break; | |
1238 | case 403: | |
1239 | p = "Forbidden"; | |
1240 | break; | |
1241 | case 404: | |
1242 | p = "Not Found"; | |
1243 | break; | |
1244 | case 405: | |
1245 | p = "Method Not Allowed"; | |
1246 | break; | |
1247 | case 406: | |
1248 | p = "Not Acceptable"; | |
1249 | break; | |
1250 | case 407: | |
1251 | p = "Proxy Authentication Required"; | |
1252 | break; | |
1253 | case 408: | |
1254 | p = "Request Time-out"; | |
1255 | break; | |
1256 | case 409: | |
1257 | p = "Conflict"; | |
1258 | break; | |
1259 | case 410: | |
1260 | p = "Gone"; | |
1261 | break; | |
1262 | case 411: | |
1263 | p = "Length Required"; | |
1264 | break; | |
1265 | case 412: | |
1266 | p = "Precondition Failed"; | |
1267 | break; | |
1268 | case 413: | |
1269 | p = "Request Entity Too Large"; | |
1270 | break; | |
1271 | case 414: | |
1272 | p = "Request-URI Too Large"; | |
1273 | break; | |
1274 | case 415: | |
1275 | p = "Unsupported Media Type"; | |
1276 | break; | |
1277 | case 500: | |
1278 | p = "Internal Server Error"; | |
1279 | break; | |
1280 | case 501: | |
1281 | p = "Not Implemented"; | |
1282 | break; | |
1283 | case 502: | |
1284 | p = "Bad Gateway"; | |
1285 | break; | |
1286 | case 503: | |
1287 | p = "Service Unavailable"; | |
1288 | break; | |
1289 | case 504: | |
1290 | p = "Gateway Time-out"; | |
1291 | break; | |
1292 | case 505: | |
1293 | p = "HTTP Version not supported"; | |
1294 | break; | |
1295 | default: | |
1296 | p = "Unknown"; | |
1297 | debug(11, 0) ("Unknown HTTP status code: %d\n", status); | |
1298 | break; | |
1299 | } | |
1300 | return p; | |
1301 | } | |
cb69b4c7 | 1302 | #endif |
9b312a19 | 1303 | |
ee1679df | 1304 | #if 0 /* moved to HttpResponse.c */ |
9b312a19 | 1305 | char * |
1306 | httpReplyHeader(double ver, | |
1307 | http_status status, | |
1308 | char *ctype, | |
1309 | int clen, | |
1310 | time_t lmt, | |
1311 | time_t expires) | |
1312 | { | |
1313 | LOCAL_ARRAY(char, buf, HTTP_REPLY_BUF_SZ); | |
9b312a19 | 1314 | int l = 0; |
1315 | int s = HTTP_REPLY_BUF_SZ; | |
fc5d6f7f | 1316 | l += snprintf(buf + l, s - l, "HTTP/%3.1f %d %s\r\n", |
1317 | ver, | |
9b312a19 | 1318 | (int) status, |
1319 | httpStatusString(status)); | |
1320 | l += snprintf(buf + l, s - l, "Server: Squid/%s\r\n", SQUID_VERSION); | |
1321 | l += snprintf(buf + l, s - l, "Date: %s\r\n", mkrfc1123(squid_curtime)); | |
1322 | if (expires >= 0) | |
1323 | l += snprintf(buf + l, s - l, "Expires: %s\r\n", mkrfc1123(expires)); | |
1324 | if (lmt) | |
1325 | l += snprintf(buf + l, s - l, "Last-Modified: %s\r\n", mkrfc1123(lmt)); | |
1326 | if (clen > 0) | |
1327 | l += snprintf(buf + l, s - l, "Content-Length: %d\r\n", clen); | |
1328 | if (ctype) | |
1329 | l += snprintf(buf + l, s - l, "Content-Type: %s\r\n", ctype); | |
1330 | return buf; | |
1331 | } | |
cb69b4c7 | 1332 | #endif |