]> git.ipfire.org Git - thirdparty/squid.git/blame - src/http.cc
adding
[thirdparty/squid.git] / src / http.cc
CommitLineData
30a4f2a8 1/*
bba6fa8f 2 * $Id: http.cc,v 1.149 1997/02/24 20:22:10 wessels Exp $
30a4f2a8 3 *
4 * DEBUG: section 11 Hypertext Transfer Protocol (HTTP)
5 * AUTHOR: Harvest Derived
6 *
42c04c16 7 * SQUID Internet Object Cache http://squid.nlanr.net/Squid/
30a4f2a8 8 * --------------------------------------------------------
9 *
10 * Squid is the result of efforts by numerous individuals from the
11 * Internet community. Development is led by Duane Wessels of the
12 * National Laboratory for Applied Network Research and funded by
13 * the National Science Foundation.
14 *
15 * This program is free software; you can redistribute it and/or modify
16 * it under the terms of the GNU General Public License as published by
17 * the Free Software Foundation; either version 2 of the License, or
18 * (at your option) any later version.
19 *
20 * This program is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * You should have received a copy of the GNU General Public License
26 * along with this program; if not, write to the Free Software
27 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
28 *
29 */
019dd986 30
31/*
30a4f2a8 32 * Copyright (c) 1994, 1995. All rights reserved.
33 *
34 * The Harvest software was developed by the Internet Research Task
35 * Force Research Group on Resource Discovery (IRTF-RD):
36 *
37 * Mic Bowman of Transarc Corporation.
38 * Peter Danzig of the University of Southern California.
39 * Darren R. Hardy of the University of Colorado at Boulder.
40 * Udi Manber of the University of Arizona.
41 * Michael F. Schwartz of the University of Colorado at Boulder.
42 * Duane Wessels of the University of Colorado at Boulder.
43 *
44 * This copyright notice applies to software in the Harvest
45 * ``src/'' directory only. Users should consult the individual
46 * copyright notices in the ``components/'' subdirectories for
47 * copyright information about other software bundled with the
48 * Harvest source code distribution.
49 *
50 * TERMS OF USE
51 *
52 * The Harvest software may be used and re-distributed without
53 * charge, provided that the software origin and research team are
54 * cited in any use of the system. Most commonly this is
55 * accomplished by including a link to the Harvest Home Page
56 * (http://harvest.cs.colorado.edu/) from the query page of any
57 * Broker you deploy, as well as in the query result pages. These
58 * links are generated automatically by the standard Broker
59 * software distribution.
60 *
61 * The Harvest software is provided ``as is'', without express or
62 * implied warranty, and with no support nor obligation to assist
63 * in its use, correction, modification or enhancement. We assume
64 * no liability with respect to the infringement of copyrights,
65 * trade secrets, or any patents, and are not responsible for
66 * consequential damages. Proper use of the Harvest software is
67 * entirely the responsibility of the user.
68 *
69 * DERIVATIVE WORKS
70 *
71 * Users may make derivative works from the Harvest software, subject
72 * to the following constraints:
73 *
74 * - You must include the above copyright notice and these
75 * accompanying paragraphs in all forms of derivative works,
76 * and any documentation and other materials related to such
77 * distribution and use acknowledge that the software was
78 * developed at the above institutions.
79 *
80 * - You must notify IRTF-RD regarding your distribution of
81 * the derivative work.
82 *
83 * - You must clearly notify users that your are distributing
84 * a modified version and not the original Harvest software.
85 *
86 * - Any derivative product is also subject to these copyright
87 * and use restrictions.
88 *
89 * Note that the Harvest software is NOT in the public domain. We
90 * retain copyright, as specified above.
91 *
92 * HISTORY OF FREE SOFTWARE STATUS
93 *
94 * Originally we required sites to license the software in cases
95 * where they were going to build commercial products/services
96 * around Harvest. In June 1995 we changed this policy. We now
97 * allow people to use the core Harvest software (the code found in
98 * the Harvest ``src/'' directory) for free. We made this change
99 * in the interest of encouraging the widest possible deployment of
100 * the technology. The Harvest software is really a reference
101 * implementation of a set of protocols and formats, some of which
102 * we intend to standardize. We encourage commercial
103 * re-implementations of code complying to this set of standards.
019dd986 104 */
44a47c6e 105
4a83b852 106/*
107 * Anonymizing patch by lutz@as-node.jena.thur.de
de3bdb4c 108 * have a look into http-anon.c to get more informations.
4a83b852 109 */
110
44a47c6e 111#include "squid.h"
090089c4 112
234967c9 113#define HTTP_DELETE_GAP (1<<18)
090089c4 114
4db43fab 115static const char *const w_space = " \t\n\r";
6bf8443a 116static const char *const crlf = "\r\n";
4db43fab 117
6fb52f6c 118typedef enum {
119 SCC_PUBLIC,
120 SCC_PRIVATE,
121 SCC_NOCACHE,
122 SCC_NOSTORE,
123 SCC_NOTRANSFORM,
124 SCC_MUSTREVALIDATE,
125 SCC_PROXYREVALIDATE,
126 SCC_MAXAGE,
127 SCC_ENUM_END
128} http_server_cc_t;
129
6bf8443a 130enum {
6fb52f6c 131 CCC_NOCACHE,
132 CCC_NOSTORE,
133 CCC_MAXAGE,
134 CCC_MAXSTALE,
135 CCC_MINFRESH,
136 CCC_ONLYIFCACHED,
137 CCC_ENUM_END
6bf8443a 138};
139
151a0b6d 140typedef enum {
141 HDR_ACCEPT,
142 HDR_AGE,
143 HDR_CONTENT_LENGTH,
144 HDR_CONTENT_MD5,
145 HDR_CONTENT_TYPE,
146 HDR_DATE,
147 HDR_ETAG,
148 HDR_EXPIRES,
6bf8443a 149 HDR_HOST,
151a0b6d 150 HDR_IMS,
151 HDR_LAST_MODIFIED,
152 HDR_MAX_FORWARDS,
153 HDR_PUBLIC,
154 HDR_RETRY_AFTER,
155 HDR_SET_COOKIE,
156 HDR_UPGRADE,
157 HDR_WARNING,
158 HDR_MISC_END
159} http_hdr_misc_t;
6fb52f6c 160
161char *HttpServerCCStr[] =
162{
163 "public",
164 "private",
165 "no-cache",
166 "no-store",
167 "no-transform",
168 "must-revalidate",
169 "proxy-revalidate",
170 "max-age",
171 "NONE"
172};
173
151a0b6d 174static char *HttpHdrMiscStr[] =
175{
176 "Accept",
177 "Age",
178 "Content-Length",
179 "Content-MD5",
180 "Content-Type",
181 "Date",
182 "Etag",
183 "Expires",
184 "Host",
185 "If-Modified-Since",
186 "Last-Modified",
187 "Max-Forwards",
188 "Public",
189 "Retry-After",
190 "Set-Cookie",
191 "Upgrade",
192 "Warning",
193 "NONE"
194};
195
24382924 196static struct {
30a4f2a8 197 int parsed;
151a0b6d 198 int misc[HDR_MISC_END];
6fb52f6c 199 int cc[SCC_ENUM_END];
30a4f2a8 200} ReplyHeaderStats;
090089c4 201
b177367b 202static void httpStateFree _PARAMS((int fd, void *));
203static void httpReadReplyTimeout _PARAMS((int fd, void *));
204static void httpLifetimeExpire _PARAMS((int fd, void *));
67508012 205static void httpMakePublic _PARAMS((StoreEntry *));
206static void httpMakePrivate _PARAMS((StoreEntry *));
207static void httpCacheNegatively _PARAMS((StoreEntry *));
b177367b 208static void httpReadReply _PARAMS((int fd, void *));
67508012 209static void httpSendComplete _PARAMS((int fd, char *, int, int, void *));
b177367b 210static void httpSendRequest _PARAMS((int fd, void *));
0ee4272b 211static void httpConnect _PARAMS((int fd, const ipcache_addrs *, void *));
e5f6c5c2 212static void httpConnectDone _PARAMS((int fd, int status, void *data));
6bf8443a 213static void httpAppendRequestHeader _PARAMS((char *hdr, const char *line, size_t * sz, size_t max));
214
b8d8561b 215
b177367b 216static void
217httpStateFree(int fd, void *data)
f5558c95 218{
b177367b 219 HttpStateData *httpState = data;
0d4d4170 220 if (httpState == NULL)
b177367b 221 return;
30a4f2a8 222 storeUnlockObject(httpState->entry);
0d4d4170 223 if (httpState->reply_hdr) {
224 put_free_8k_page(httpState->reply_hdr);
225 httpState->reply_hdr = NULL;
226 }
30a4f2a8 227 requestUnlink(httpState->request);
20cc1450 228 requestUnlink(httpState->orig_request);
0d4d4170 229 xfree(httpState);
f5558c95 230}
231
b8d8561b 232int
0ee4272b 233httpCachable(const char *url, int method)
090089c4 234{
090089c4 235 /* GET and HEAD are cachable. Others are not. */
6eb42cae 236 if (method != METHOD_GET && method != METHOD_HEAD)
090089c4 237 return 0;
090089c4 238 /* else cachable */
239 return 1;
240}
241
242/* This will be called when timeout on read. */
b8d8561b 243static void
b177367b 244httpReadReplyTimeout(int fd, void *data)
090089c4 245{
b177367b 246 HttpStateData *httpState = data;
090089c4 247 StoreEntry *entry = NULL;
30a4f2a8 248 entry = httpState->entry;
593c9a75 249 debug(11, 4, "httpReadReplyTimeout: FD %d: '%s'\n", fd, entry->url);
b8de7ebe 250 squid_error_entry(entry, ERR_READ_TIMEOUT, NULL);
b177367b 251 commSetSelect(fd, COMM_SELECT_READ, NULL, NULL, 0);
0d4d4170 252 comm_close(fd);
090089c4 253}
254
255/* This will be called when socket lifetime is expired. */
b8d8561b 256static void
b177367b 257httpLifetimeExpire(int fd, void *data)
090089c4 258{
b177367b 259 HttpStateData *httpState = data;
593c9a75 260 StoreEntry *entry = httpState->entry;
261 debug(11, 4, "httpLifeTimeExpire: FD %d: '%s'\n", fd, entry->url);
ce49f524 262 squid_error_entry(entry, ERR_LIFETIME_EXP, NULL);
b177367b 263 commSetSelect(fd, COMM_SELECT_READ | COMM_SELECT_WRITE, NULL, NULL, 0);
0d4d4170 264 comm_close(fd);
090089c4 265}
266
30a4f2a8 267/* This object can be cached for a long time */
b8d8561b 268static void
269httpMakePublic(StoreEntry * entry)
30a4f2a8 270{
1c481e00 271 if (BIT_TEST(entry->flag, ENTRY_CACHABLE))
30a4f2a8 272 storeSetPublicKey(entry);
273}
274
275/* This object should never be cached at all */
b8d8561b 276static void
277httpMakePrivate(StoreEntry * entry)
30a4f2a8 278{
30a4f2a8 279 storeExpireNow(entry);
1c481e00 280 BIT_RESET(entry->flag, ENTRY_CACHABLE);
30a4f2a8 281 storeReleaseRequest(entry); /* delete object when not used */
282}
283
284/* This object may be negatively cached */
b8d8561b 285static void
286httpCacheNegatively(StoreEntry * entry)
30a4f2a8 287{
79b5cc5f 288 storeNegativeCache(entry);
1c481e00 289 if (BIT_TEST(entry->flag, ENTRY_CACHABLE))
30a4f2a8 290 storeSetPublicKey(entry);
30a4f2a8 291}
292
293
294/* Build a reply structure from HTTP reply headers */
b8d8561b 295void
48f44632 296httpParseReplyHeaders(const char *buf, struct _http_reply *reply)
30a4f2a8 297{
33b589ff 298 char *headers = get_free_4k_page();
ca85027a 299 char *line;
33b589ff 300 char *end;
30a4f2a8 301 char *s = NULL;
33b589ff 302 char *t;
ca98227c 303 time_t delta;
304 size_t l;
30a4f2a8 305
f1494beb 306 reply->code = 600;
30a4f2a8 307 ReplyHeaderStats.parsed++;
33b589ff 308 xstrncpy(headers, buf, 4096);
309 end = mime_headers_end(headers);
ca85027a 310 if (end == NULL) {
311 t = headers;
e2ad7f85 312 if (!strncasecmp(t, "HTTP/", 5)) {
313 reply->version = atof(t + 5);
314 if ((t = strchr(t, ' ')))
315 reply->code = atoi(++t);
ca85027a 316 }
e2ad7f85 317 put_free_4k_page(headers);
318 return;
ca85027a 319 }
320 reply->hdr_sz = end - headers;
321 line = get_free_4k_page();
33b589ff 322 for (s = headers; s < end; s += strcspn(s, crlf), s += strspn(s, crlf)) {
323 l = strcspn(s, crlf) + 1;
324 if (l > 4096)
325 l = 4096;
326 xstrncpy(line, s, l);
327 t = line;
328 debug(11, 3, "httpParseReplyHeaders: %s\n", t);
329 if (!strncasecmp(t, "HTTP/", 5)) {
e2ad7f85 330 reply->version = atof(t + 5);
33b589ff 331 if ((t = strchr(t, ' ')))
332 reply->code = atoi(++t);
30a4f2a8 333 } else if (!strncasecmp(t, "Content-type:", 13)) {
33b589ff 334 for (t += 13; isspace(*t); t++);
e91b56e5 335 if ((l = strcspn(t, ";\t ")) > 0)
336 *(t + l) = '\0';
33b589ff 337 xstrncpy(reply->content_type, t, HTTP_REPLY_FIELD_SZ);
151a0b6d 338 ReplyHeaderStats.misc[HDR_CONTENT_TYPE]++;
30a4f2a8 339 } else if (!strncasecmp(t, "Content-length:", 15)) {
33b589ff 340 for (t += 15; isspace(*t); t++);
33b589ff 341 reply->content_length = atoi(t);
151a0b6d 342 ReplyHeaderStats.misc[HDR_CONTENT_LENGTH]++;
30a4f2a8 343 } else if (!strncasecmp(t, "Date:", 5)) {
33b589ff 344 for (t += 5; isspace(*t); t++);
345 reply->date = parse_rfc1123(t);
151a0b6d 346 ReplyHeaderStats.misc[HDR_DATE]++;
30a4f2a8 347 } else if (!strncasecmp(t, "Expires:", 8)) {
33b589ff 348 for (t += 8; isspace(*t); t++);
349 reply->expires = parse_rfc1123(t);
350 /*
351 * The HTTP/1.0 specs says that robust implementations
352 * should consider bad or malformed Expires header as
353 * equivalent to "expires immediately."
354 */
355 if (reply->expires == -1)
356 reply->expires = squid_curtime;
151a0b6d 357 ReplyHeaderStats.misc[HDR_EXPIRES]++;
30a4f2a8 358 } else if (!strncasecmp(t, "Last-Modified:", 14)) {
33b589ff 359 for (t += 14; isspace(*t); t++);
360 reply->last_modified = parse_rfc1123(t);
151a0b6d 361 ReplyHeaderStats.misc[HDR_LAST_MODIFIED]++;
362 } else if (!strncasecmp(t, "Accept:", 7)) {
363 ReplyHeaderStats.misc[HDR_ACCEPT]++;
364 } else if (!strncasecmp(t, "Age:", 4)) {
365 ReplyHeaderStats.misc[HDR_AGE]++;
366 } else if (!strncasecmp(t, "Content-MD5:", 12)) {
367 ReplyHeaderStats.misc[HDR_CONTENT_MD5]++;
368 } else if (!strncasecmp(t, "ETag:", 5)) {
369 ReplyHeaderStats.misc[HDR_ETAG]++;
370 } else if (!strncasecmp(t, "Max-Forwards:", 13)) {
371 ReplyHeaderStats.misc[HDR_MAX_FORWARDS]++;
372 } else if (!strncasecmp(t, "Public:", 7)) {
373 ReplyHeaderStats.misc[HDR_PUBLIC]++;
374 } else if (!strncasecmp(t, "Retry-After:", 12)) {
375 ReplyHeaderStats.misc[HDR_RETRY_AFTER]++;
376 } else if (!strncasecmp(t, "Upgrade:", 8)) {
377 ReplyHeaderStats.misc[HDR_UPGRADE]++;
378 } else if (!strncasecmp(t, "Warning:", 8)) {
379 ReplyHeaderStats.misc[HDR_WARNING]++;
caebbe00 380 } else if (!strncasecmp(t, "Cache-Control:", 14)) {
33b589ff 381 for (t += 14; isspace(*t); t++);
4db43fab 382 if (!strncasecmp(t, "public", 6)) {
383 EBIT_SET(reply->cache_control, SCC_PUBLIC);
384 ReplyHeaderStats.cc[SCC_PUBLIC]++;
385 } else if (!strncasecmp(t, "private", 7)) {
386 EBIT_SET(reply->cache_control, SCC_PRIVATE);
387 ReplyHeaderStats.cc[SCC_PRIVATE]++;
388 } else if (!strncasecmp(t, "no-cache", 8)) {
389 EBIT_SET(reply->cache_control, SCC_NOCACHE);
390 ReplyHeaderStats.cc[SCC_NOCACHE]++;
c1764328 391 } else if (!strncasecmp(t, "no-store", 8)) {
392 EBIT_SET(reply->cache_control, SCC_NOSTORE);
393 ReplyHeaderStats.cc[SCC_NOSTORE]++;
394 } else if (!strncasecmp(t, "no-transform", 12)) {
395 EBIT_SET(reply->cache_control, SCC_NOTRANSFORM);
396 ReplyHeaderStats.cc[SCC_NOTRANSFORM]++;
397 } else if (!strncasecmp(t, "must-revalidate", 15)) {
398 EBIT_SET(reply->cache_control, SCC_MUSTREVALIDATE);
399 ReplyHeaderStats.cc[SCC_MUSTREVALIDATE]++;
400 } else if (!strncasecmp(t, "proxy-revalidate", 16)) {
401 EBIT_SET(reply->cache_control, SCC_PROXYREVALIDATE);
402 ReplyHeaderStats.cc[SCC_PROXYREVALIDATE]++;
4db43fab 403 } else if (!strncasecmp(t, "max-age", 7)) {
404 if ((t = strchr(t, '='))) {
ca98227c 405 delta = (time_t) atoi(++t);
406 reply->expires = squid_curtime + delta;
4db43fab 407 EBIT_SET(reply->cache_control, SCC_MAXAGE);
408 ReplyHeaderStats.cc[SCC_MAXAGE]++;
5df61230 409 }
caebbe00 410 }
df3ac7c0 411 } else if (!strncasecmp(t, "Set-Cookie:", 11)) {
412 EBIT_SET(reply->misc_headers, HDR_SET_COOKIE);
58202be7 413 ReplyHeaderStats.misc[HDR_SET_COOKIE]++;
30a4f2a8 414 }
30a4f2a8 415 }
33b589ff 416 put_free_4k_page(headers);
417 put_free_4k_page(line);
30a4f2a8 418}
419
090089c4 420
b8d8561b 421void
0ee4272b 422httpProcessReplyHeader(HttpStateData * httpState, const char *buf, int size)
f5558c95 423{
424 char *t = NULL;
30a4f2a8 425 StoreEntry *entry = httpState->entry;
d3fb4dea 426 int room;
427 int hdr_len;
33b589ff 428 struct _http_reply *reply = entry->mem_obj->reply;
d3fb4dea 429
ed85b771 430 debug(11, 3, "httpProcessReplyHeader: key '%s'\n", entry->key);
f5558c95 431
30a4f2a8 432 if (httpState->reply_hdr == NULL) {
433 httpState->reply_hdr = get_free_8k_page();
434 memset(httpState->reply_hdr, '\0', 8192);
f5558c95 435 }
30a4f2a8 436 if (httpState->reply_hdr_state == 0) {
437 hdr_len = strlen(httpState->reply_hdr);
ed85b771 438 room = 8191 - hdr_len;
30a4f2a8 439 strncat(httpState->reply_hdr, buf, room < size ? room : size);
d3fb4dea 440 hdr_len += room < size ? room : size;
30a4f2a8 441 if (hdr_len > 4 && strncmp(httpState->reply_hdr, "HTTP/", 5)) {
60bf30cb 442 debug(11, 3, "httpProcessReplyHeader: Non-HTTP-compliant header: '%s'\n", entry->key);
30a4f2a8 443 httpState->reply_hdr_state += 2;
33b589ff 444 reply->code = 555;
ed85b771 445 return;
d3fb4dea 446 }
d1a43e28 447 t = httpState->reply_hdr + hdr_len;
448 /* headers can be incomplete only if object still arriving */
f86a6a46 449 if (!httpState->eof)
d1a43e28 450 if ((t = mime_headers_end(httpState->reply_hdr)) == NULL)
451 return; /* headers not complete */
2285407f 452 *t = '\0';
30a4f2a8 453 httpState->reply_hdr_state++;
f5558c95 454 }
30a4f2a8 455 if (httpState->reply_hdr_state == 1) {
456 httpState->reply_hdr_state++;
019dd986 457 debug(11, 9, "GOT HTTP REPLY HDR:\n---------\n%s\n----------\n",
30a4f2a8 458 httpState->reply_hdr);
459 /* Parse headers into reply structure */
48f44632 460 httpParseReplyHeaders(httpState->reply_hdr, reply);
ca98227c 461 storeTimestampsSet(entry);
30a4f2a8 462 /* Check if object is cacheable or not based on reply code */
cb24292d 463 debug(11, 3, "httpProcessReplyHeader: HTTP CODE: %d\n", reply->code);
2285407f 464 switch (reply->code) {
30a4f2a8 465 /* Responses that are cacheable */
f5558c95 466 case 200: /* OK */
4e38e700 467 case 203: /* Non-Authoritative Information */
468 case 300: /* Multiple Choices */
f5558c95 469 case 301: /* Moved Permanently */
4e38e700 470 case 410: /* Gone */
30a4f2a8 471 /* don't cache objects from neighbors w/o LMT, Date, or Expires */
6fb52f6c 472 if (EBIT_TEST(reply->cache_control, SCC_PRIVATE))
caebbe00 473 httpMakePrivate(entry);
6fb52f6c 474 else if (EBIT_TEST(reply->cache_control, SCC_NOCACHE))
caebbe00 475 httpMakePrivate(entry);
e2ad7f85 476 /*
477 * Dealing with cookies is quite a bit more complicated
478 * than this. Ideally we should strip the cookie
479 * header from the reply but still cache the reply body.
480 * More confusion at draft-ietf-http-state-mgmt-05.txt.
481 */
df3ac7c0 482 else if (EBIT_TEST(reply->misc_headers, HDR_SET_COOKIE))
483 httpMakePrivate(entry);
ca98227c 484 else if (reply->date > -1)
30a4f2a8 485 httpMakePublic(entry);
ca98227c 486 else if (reply->last_modified > -1)
30a4f2a8 487 httpMakePublic(entry);
488 else if (!httpState->neighbor)
489 httpMakePublic(entry);
ca98227c 490 else if (reply->expires > -1)
30a4f2a8 491 httpMakePublic(entry);
af00901c 492 else if (entry->mem_obj->request->protocol != PROTO_HTTP)
493 /* XXX Remove this check after a while. DW 8/21/96
494 * We won't keep some FTP objects from neighbors running
495 * 1.0.8 or earlier because their ftpget's don't
496 * add a Date: field */
497 httpMakePublic(entry);
30a4f2a8 498 else
499 httpMakePrivate(entry);
500 break;
501 /* Responses that only are cacheable if the server says so */
502 case 302: /* Moved temporarily */
ca98227c 503 if (reply->expires > -1)
30a4f2a8 504 httpMakePublic(entry);
505 else
506 httpMakePrivate(entry);
f5558c95 507 break;
30a4f2a8 508 /* Errors can be negatively cached */
509 case 204: /* No Content */
510 case 305: /* Use Proxy (proxy redirect) */
511 case 400: /* Bad Request */
512 case 403: /* Forbidden */
513 case 404: /* Not Found */
514 case 405: /* Method Now Allowed */
515 case 414: /* Request-URI Too Long */
516 case 500: /* Internal Server Error */
517 case 501: /* Not Implemented */
518 case 502: /* Bad Gateway */
519 case 503: /* Service Unavailable */
520 case 504: /* Gateway Timeout */
851eeef7 521 httpCacheNegatively(entry);
30a4f2a8 522 break;
523 /* Some responses can never be cached */
524 case 303: /* See Other */
234967c9 525 case 304: /* Not Modified */
4e38e700 526 case 401: /* Unauthorized */
527 case 407: /* Proxy Authentication Required */
f1494beb 528 case 600: /* Squid header parsing error */
30a4f2a8 529 default: /* Unknown status code */
530 httpMakePrivate(entry);
4e38e700 531 break;
f5558c95 532 }
533 }
534}
535
090089c4 536
537/* This will be called when data is ready to be read from fd. Read until
538 * error or connection closed. */
f5558c95 539/* XXX this function is too long! */
b8d8561b 540static void
b177367b 541httpReadReply(int fd, void *data)
090089c4 542{
b177367b 543 HttpStateData *httpState = data;
95d659f0 544 LOCAL_ARRAY(char, buf, SQUID_TCP_SO_RCVBUF);
090089c4 545 int len;
30a4f2a8 546 int bin;
090089c4 547 int clen;
548 int off;
549 StoreEntry *entry = NULL;
550
30a4f2a8 551 entry = httpState->entry;
234967c9 552 if (entry->flag & DELETE_BEHIND && !storeClientWaiting(entry)) {
553 /* we can terminate connection right now */
554 squid_error_entry(entry, ERR_NO_CLIENTS_BIG_OBJ, NULL);
555 comm_close(fd);
556 return;
557 }
558 /* check if we want to defer reading */
559 clen = entry->mem_obj->e_current_len;
560 off = storeGetLowestReaderOffset(entry);
561 if ((clen - off) > HTTP_DELETE_GAP) {
30a4f2a8 562 if (entry->flag & CLIENT_ABORT_REQUEST) {
563 squid_error_entry(entry, ERR_CLIENT_ABORT, NULL);
564 comm_close(fd);
565 return;
566 }
567 IOStats.Http.reads_deferred++;
234967c9 568 debug(11, 3, "httpReadReply: Read deferred for Object: %s\n",
569 entry->url);
570 debug(11, 3, " Current Gap: %d bytes\n", clen - off);
571 /* reschedule, so it will be automatically reactivated
572 * when Gap is big enough. */
b177367b 573 commSetSelect(fd,
234967c9 574 COMM_SELECT_READ,
b177367b 575 httpReadReply,
576 (void *) httpState, 0);
30a4f2a8 577 /* disable read timeout until we are below the GAP */
b177367b 578 commSetSelect(fd,
234967c9 579 COMM_SELECT_TIMEOUT,
b177367b 580 NULL,
234967c9 581 (void *) NULL,
582 (time_t) 0);
56fa4cad 583 if (!BIT_TEST(entry->flag, READ_DEFERRED)) {
584 comm_set_fd_lifetime(fd, 3600); /* limit during deferring */
585 BIT_SET(entry->flag, READ_DEFERRED);
586 }
234967c9 587 /* dont try reading again for a while */
b6f794d6 588 comm_set_stall(fd, Config.stallDelay);
234967c9 589 return;
56fa4cad 590 } else {
591 BIT_RESET(entry->flag, READ_DEFERRED);
090089c4 592 }
1513873c 593 errno = 0;
30a4f2a8 594 len = read(fd, buf, SQUID_TCP_SO_RCVBUF);
019dd986 595 debug(11, 5, "httpReadReply: FD %d: len %d.\n", fd, len);
30a4f2a8 596 comm_set_fd_lifetime(fd, 86400); /* extend after good read */
597 if (len > 0) {
4a63c85f 598 IOStats.Http.reads++;
30a4f2a8 599 for (clen = len - 1, bin = 0; clen; bin++)
600 clen >>= 1;
601 IOStats.Http.read_hist[bin]++;
602 }
ba718c8f 603 if (len < 0) {
881f7a6c 604 debug(50, 2, "httpReadReply: FD %d: read failure: %s.\n",
090089c4 605 fd, xstrerror());
ba718c8f 606 if (errno == EAGAIN || errno == EWOULDBLOCK) {
1513873c 607 /* reinstall handlers */
6fe6313d 608 /* XXX This may loop forever */
b177367b 609 commSetSelect(fd, COMM_SELECT_READ,
610 httpReadReply, (void *) httpState, 0);
611 commSetSelect(fd, COMM_SELECT_TIMEOUT,
612 httpReadReplyTimeout, (void *) httpState, Config.readTimeout);
090089c4 613 } else {
1c481e00 614 BIT_RESET(entry->flag, ENTRY_CACHABLE);
2daae136 615 storeReleaseRequest(entry);
b8de7ebe 616 squid_error_entry(entry, ERR_READ_ERROR, xstrerror());
0d4d4170 617 comm_close(fd);
090089c4 618 }
ba718c8f 619 } else if (len == 0 && entry->mem_obj->e_current_len == 0) {
f86a6a46 620 httpState->eof = 1;
b8de7ebe 621 squid_error_entry(entry,
ba718c8f 622 ERR_ZERO_SIZE_OBJECT,
623 errno ? xstrerror() : NULL);
0d4d4170 624 comm_close(fd);
090089c4 625 } else if (len == 0) {
626 /* Connection closed; retrieval done. */
f86a6a46 627 httpState->eof = 1;
d1a43e28 628 if (httpState->reply_hdr_state < 2)
629 httpProcessReplyHeader(httpState, buf, len);
630 storeAppend(entry, buf, len); /* invoke handlers! */
631 storeComplete(entry); /* deallocates mem_obj->request */
0d4d4170 632 comm_close(fd);
090089c4 633 } else if (entry->flag & CLIENT_ABORT_REQUEST) {
634 /* append the last bit of info we get */
635 storeAppend(entry, buf, len);
b8de7ebe 636 squid_error_entry(entry, ERR_CLIENT_ABORT, NULL);
0d4d4170 637 comm_close(fd);
090089c4 638 } else {
d1a43e28 639 if (httpState->reply_hdr_state < 2)
30a4f2a8 640 httpProcessReplyHeader(httpState, buf, len);
620da955 641 storeAppend(entry, buf, len);
b177367b 642 commSetSelect(fd,
ba718c8f 643 COMM_SELECT_READ,
b177367b 644 httpReadReply,
645 (void *) httpState, 0);
646 commSetSelect(fd,
ba718c8f 647 COMM_SELECT_TIMEOUT,
b177367b 648 httpReadReplyTimeout,
30a4f2a8 649 (void *) httpState,
b6f794d6 650 Config.readTimeout);
090089c4 651 }
652}
653
654/* This will be called when request write is complete. Schedule read of
655 * reply. */
b8d8561b 656static void
657httpSendComplete(int fd, char *buf, int size, int errflag, void *data)
090089c4 658{
30a4f2a8 659 HttpStateData *httpState = data;
090089c4 660 StoreEntry *entry = NULL;
661
30a4f2a8 662 entry = httpState->entry;
019dd986 663 debug(11, 5, "httpSendComplete: FD %d: size %d: errflag %d.\n",
090089c4 664 fd, size, errflag);
665
090089c4 666 if (errflag) {
b8de7ebe 667 squid_error_entry(entry, ERR_CONNECT_FAIL, xstrerror());
0d4d4170 668 comm_close(fd);
090089c4 669 return;
670 } else {
671 /* Schedule read reply. */
b177367b 672 commSetSelect(fd,
019dd986 673 COMM_SELECT_READ,
b177367b 674 httpReadReply,
675 (void *) httpState, 0);
676 commSetSelect(fd,
019dd986 677 COMM_SELECT_TIMEOUT,
b177367b 678 httpReadReplyTimeout,
30a4f2a8 679 (void *) httpState,
b6f794d6 680 Config.readTimeout);
30a4f2a8 681 comm_set_fd_lifetime(fd, 86400); /* extend lifetime */
090089c4 682 }
683}
684
6bf8443a 685static void
686httpAppendRequestHeader(char *hdr, const char *line, size_t * sz, size_t max)
687{
688 size_t n = *sz + strlen(line) + 2;
689 if (n >= max)
690 return;
fa562c67 691 if (Config.Options.anonymizer == ANONYMIZER_PARANOID) {
bba6fa8f 692 if (!httpAnonAllowed(line))
fa562c67 693 return;
694 } else if (Config.Options.anonymizer == ANONYMIZER_STANDARD) {
bba6fa8f 695 if (httpAnonDenied(line))
fa562c67 696 return;
4a83b852 697 }
4a83b852 698 /* allowed header, explicitly known to be not dangerous */
6bf8443a 699 debug(11, 5, "httpAppendRequestHeader: %s\n", line);
929545fe 700 strcpy(hdr + (*sz), line);
6bf8443a 701 strcat(hdr + (*sz), crlf);
702 *sz = n;
703}
704
705size_t
706httpBuildRequestHeader(request_t * request,
707 request_t * orig_request,
708 StoreEntry * entry,
709 char *hdr_in,
710 size_t * in_len,
711 char *hdr_out,
712 size_t out_sz,
713 int cfd)
714{
715 char *xbuf = get_free_4k_page();
872b720f 716 char *ybuf = get_free_8k_page();
6bf8443a 717 char *viabuf = get_free_4k_page();
718 char *fwdbuf = get_free_4k_page();
719 char *t = NULL;
720 char *s = NULL;
721 char *end = NULL;
722 size_t len = 0;
723 size_t hdr_len = 0;
724 size_t in_sz;
6bf8443a 725 size_t l;
726 int hdr_flags = 0;
151a0b6d 727 int cc_flags = 0;
b3b64e58 728 int n;
6bf8443a 729 const char *url = NULL;
730
731 debug(11, 3, "httpBuildRequestHeader: INPUT:\n%s\n", hdr_in);
732 xstrncpy(fwdbuf, "X-Forwarded-For: ", 4096);
733 xstrncpy(viabuf, "Via: ", 4096);
734 sprintf(ybuf, "%s %s HTTP/1.0",
735 RequestMethodStr[request->method],
736 *request->urlpath ? request->urlpath : "/");
737 httpAppendRequestHeader(hdr_out, ybuf, &len, out_sz);
738 /* Add IMS header */
739 if (entry && entry->lastmod && request->method == METHOD_GET) {
740 sprintf(ybuf, "If-Modified-Since: %s", mkrfc1123(entry->lastmod));
741 httpAppendRequestHeader(hdr_out, ybuf, &len, out_sz);
742 EBIT_SET(hdr_flags, HDR_IMS);
743 }
00c59270 744 end = mime_headers_end(hdr_in);
6bf8443a 745 in_sz = strlen(hdr_in);
746 for (t = hdr_in; t < end; t += strcspn(t, crlf), t += strspn(t, crlf)) {
747 hdr_len = t - hdr_in;
6bf8443a 748 l = strcspn(t, crlf) + 1;
749 if (l > 4096)
750 l = 4096;
751 xstrncpy(xbuf, t, l);
a7011ca4 752 debug(11, 5, "httpBuildRequestHeader: %s\n", xbuf);
6bf8443a 753 if (strncasecmp(xbuf, "Proxy-Connection:", 17) == 0)
754 continue;
755 if (strncasecmp(xbuf, "Connection:", 11) == 0)
756 continue;
66f7337b 757 if (strncasecmp(xbuf, "Host:", 5) == 0) {
6bf8443a 758 EBIT_SET(hdr_flags, HDR_HOST);
66f7337b 759 } else if (strncasecmp(xbuf, "Cache-Control:", 14) == 0) {
6bf8443a 760 for (s = xbuf + 14; *s && isspace(*s); s++);
761 if (strncasecmp(s, "Max-age=", 8) == 0)
151a0b6d 762 EBIT_SET(cc_flags, CCC_MAXAGE);
66f7337b 763 } else if (strncasecmp(xbuf, "Via:", 4) == 0) {
6bf8443a 764 for (s = xbuf + 4; *s && isspace(*s); s++);
765 if (strlen(viabuf) + strlen(s) < 4000)
766 strcat(viabuf, s);
767 strcat(viabuf, ", ");
768 continue;
66f7337b 769 } else if (strncasecmp(xbuf, "X-Forwarded-For:", 16) == 0) {
6bf8443a 770 for (s = xbuf + 16; *s && isspace(*s); s++);
771 if (strlen(fwdbuf) + strlen(s) < 4000)
772 strcat(fwdbuf, s);
773 strcat(fwdbuf, ", ");
774 continue;
66f7337b 775 } else if (strncasecmp(xbuf, "If-Modified-Since:", 18) == 0) {
6bf8443a 776 if (EBIT_TEST(hdr_flags, HDR_IMS))
777 continue;
b3b64e58 778 } else if (strncasecmp(xbuf, "Max-Forwards:", 13) == 0) {
779 if (orig_request->method == METHOD_TRACE) {
780 for (s = xbuf + 13; *s && isspace(*s); s++);
781 n = atoi(s);
782 sprintf(xbuf, "Max-Forwards: %d", n - 1);
783 }
66f7337b 784 }
6bf8443a 785 httpAppendRequestHeader(hdr_out, xbuf, &len, out_sz - 512);
786 }
787 hdr_len = t - hdr_in;
788 /* Append Via: */
43f1c650 789 sprintf(ybuf, "%3.1f %s", orig_request->http_ver, ThisCache);
6bf8443a 790 strcat(viabuf, ybuf);
791 httpAppendRequestHeader(hdr_out, viabuf, &len, out_sz);
792 /* Append to X-Forwarded-For: */
a08307eb 793 strcat(fwdbuf, cfd < 0 ? "unknown" : fd_table[cfd].ipaddr);
6bf8443a 794 httpAppendRequestHeader(hdr_out, fwdbuf, &len, out_sz);
795 if (!EBIT_TEST(hdr_flags, HDR_HOST)) {
796 sprintf(ybuf, "Host: %s", orig_request->host);
797 httpAppendRequestHeader(hdr_out, ybuf, &len, out_sz);
798 }
151a0b6d 799 if (!EBIT_TEST(cc_flags, CCC_MAXAGE)) {
6bf8443a 800 url = entry ? entry->url : urlCanonical(orig_request, NULL);
801 sprintf(ybuf, "Cache-control: Max-age=%d", (int) getMaxAge(url));
802 httpAppendRequestHeader(hdr_out, ybuf, &len, out_sz);
803 }
804 httpAppendRequestHeader(hdr_out, null_string, &len, out_sz);
805 put_free_4k_page(xbuf);
872b720f 806 put_free_8k_page(ybuf);
6bf8443a 807 put_free_4k_page(viabuf);
808 put_free_4k_page(fwdbuf);
809 if (in_len)
810 *in_len = hdr_len;
9d9d144b 811 if ((l = strlen(hdr_out)) != len) {
812 debug_trap("httpBuildRequestHeader: size mismatch");
813 len = l;
814 }
6bf8443a 815 debug(11, 3, "httpBuildRequestHeader: OUTPUT:\n%s\n", hdr_out);
816 return len;
817}
818
090089c4 819/* This will be called when connect completes. Write request. */
b8d8561b 820static void
b177367b 821httpSendRequest(int fd, void *data)
090089c4 822{
b177367b 823 HttpStateData *httpState = data;
090089c4 824 char *buf = NULL;
090089c4 825 int len = 0;
826 int buflen;
30a4f2a8 827 request_t *req = httpState->request;
9864ee44 828 int buftype = 0;
620da955 829 StoreEntry *entry = httpState->entry;
2a26c096 830 int cfd;
090089c4 831
30a4f2a8 832 debug(11, 5, "httpSendRequest: FD %d: httpState %p.\n", fd, httpState);
6bf8443a 833 buflen = strlen(req->urlpath);
30a4f2a8 834 if (httpState->req_hdr)
4768dbe0 835 buflen += httpState->req_hdr_sz + 1;
090089c4 836 buflen += 512; /* lots of extra */
837
78657218 838 if ((req->method == METHOD_POST || req->method == METHOD_PUT)) {
839 debug_trap("httpSendRequest: should not be handling POST/PUT request");
840 return;
090089c4 841 }
30a4f2a8 842 if (buflen < DISK_PAGE_SIZE) {
9864ee44 843 buf = get_free_8k_page();
844 memset(buf, '\0', buflen);
845 buftype = BUF_TYPE_8K;
6bf8443a 846 buflen = DISK_PAGE_SIZE;
30a4f2a8 847 } else {
9864ee44 848 buf = xcalloc(buflen, 1);
849 buftype = BUF_TYPE_MALLOC;
090089c4 850 }
2a26c096 851 if (!opt_forwarded_for)
6bf8443a 852 cfd = -1;
2a26c096 853 else if (entry->mem_obj == NULL)
6bf8443a 854 cfd = -1;
2a26c096 855 else
6bf8443a 856 cfd = storeFirstClientFD(entry->mem_obj);
857 len = httpBuildRequestHeader(req,
858 httpState->orig_request ? httpState->orig_request : req,
859 entry,
860 httpState->req_hdr,
861 NULL,
862 buf,
863 buflen,
864 cfd);
67508012 865 debug(11, 6, "httpSendRequest: FD %d:\n%s\n", fd, buf);
30a4f2a8 866 comm_write(fd,
14e59844 867 buf,
868 len,
869 30,
870 httpSendComplete,
9864ee44 871 httpState,
872 buftype == BUF_TYPE_8K ? put_free_8k_page : xfree);
20cc1450 873 requestUnlink(httpState->orig_request);
874 httpState->orig_request = NULL;
090089c4 875}
876
b8d8561b 877int
20cc1450 878proxyhttpStart(const char *url,
879 request_t * orig_request,
880 StoreEntry * entry,
deb79f06 881 peer * e)
090089c4 882{
f5558c95 883 int sock;
30a4f2a8 884 HttpStateData *httpState = NULL;
7111c86a 885 request_t *request = NULL;
090089c4 886
7111c86a 887 debug(11, 3, "proxyhttpStart: \"%s %s\"\n",
2cc3f720 888 RequestMethodStr[orig_request->method], url);
019dd986 889 debug(11, 10, "proxyhttpStart: HTTP request header:\n%s\n",
22e4fa85 890 entry->mem_obj->mime_hdr);
090089c4 891
30a4f2a8 892 if (e->options & NEIGHBOR_PROXY_ONLY)
090089c4 893 storeStartDeleteBehind(entry);
894
895 /* Create socket. */
16b204c4 896 sock = comm_open(SOCK_STREAM,
897 0,
898 Config.Addrs.tcp_outgoing,
899 0,
900 COMM_NONBLOCKING,
901 url);
090089c4 902 if (sock == COMM_ERROR) {
019dd986 903 debug(11, 4, "proxyhttpStart: Failed because we're out of sockets.\n");
b8de7ebe 904 squid_error_entry(entry, ERR_NO_FDS, xstrerror());
090089c4 905 return COMM_ERROR;
906 }
30a4f2a8 907 httpState = xcalloc(1, sizeof(HttpStateData));
908 storeLockObject(httpState->entry = entry, NULL, NULL);
909 httpState->req_hdr = entry->mem_obj->mime_hdr;
4768dbe0 910 httpState->req_hdr_sz = entry->mem_obj->mime_hdr_sz;
30a4f2a8 911 request = get_free_request_t();
912 httpState->request = requestLink(request);
913 httpState->neighbor = e;
20cc1450 914 httpState->orig_request = requestLink(orig_request);
0d4d4170 915 /* register the handler to free HTTP state data when the FD closes */
30a4f2a8 916 comm_add_close_handler(sock,
b177367b 917 httpStateFree,
30a4f2a8 918 (void *) httpState);
2cc3f720 919 request->method = orig_request->method;
d5aa0e3b 920 xstrncpy(request->host, e->host, SQUIDHOSTNAMELEN);
30a4f2a8 921 request->port = e->http_port;
d5aa0e3b 922 xstrncpy(request->urlpath, url, MAX_URL);
313c1c28 923 BIT_SET(request->flags, REQ_PROXYING);
a13d9042 924 ipcache_nbgethostbyname(request->host,
925 sock,
b15fe823 926 httpConnect,
a13d9042 927 httpState);
928 return COMM_OK;
929}
930
b15fe823 931static void
fe4e214f 932httpConnect(int fd, const ipcache_addrs * ia, void *data)
a13d9042 933{
934 HttpStateData *httpState = data;
935 request_t *request = httpState->request;
936 StoreEntry *entry = httpState->entry;
e5f6c5c2 937 if (ia == NULL) {
a13d9042 938 debug(11, 4, "httpConnect: Unknown host: %s\n", request->host);
b8de7ebe 939 squid_error_entry(entry, ERR_DNS_FAIL, dns_error_message);
a13d9042 940 comm_close(fd);
b15fe823 941 return;
090089c4 942 }
943 /* Open connection. */
e5f6c5c2 944 httpState->connectState.fd = fd;
945 httpState->connectState.host = request->host;
946 httpState->connectState.port = request->port;
947 httpState->connectState.handler = httpConnectDone;
948 httpState->connectState.data = httpState;
949 comm_nbconnect(fd, &httpState->connectState);
950}
951
952static void
953httpConnectDone(int fd, int status, void *data)
954{
955 HttpStateData *httpState = data;
956 request_t *request = httpState->request;
957 StoreEntry *entry = httpState->entry;
deb79f06 958 peer *e = NULL;
e5f6c5c2 959 if (status != COMM_OK) {
5269d0bd 960 if ((e = httpState->neighbor))
e5f6c5c2 961 e->last_fail_time = squid_curtime;
e5f6c5c2 962 squid_error_entry(entry, ERR_CONNECT_FAIL, xstrerror());
963 comm_close(fd);
964 } else {
965 /* Install connection complete handler. */
966 if (opt_no_ipcache)
967 ipcacheInvalidate(request->host);
968 fd_note(fd, entry->url);
b177367b 969 commSetSelect(fd, COMM_SELECT_LIFETIME,
970 httpLifetimeExpire, (void *) httpState, 0);
971 commSetSelect(fd, COMM_SELECT_WRITE,
972 httpSendRequest, (void *) httpState, 0);
9d4b2981 973 if (vizSock > -1)
ef2d27ff 974 vizHackSendPkt(&httpState->connectState.S, 2);
090089c4 975 }
090089c4 976}
977
b8d8561b 978int
20cc1450 979httpStart(char *url,
4768dbe0 980 request_t * request,
981 char *req_hdr,
982 int req_hdr_sz,
983 StoreEntry * entry)
090089c4 984{
985 /* Create state structure. */
a13d9042 986 int sock;
30a4f2a8 987 HttpStateData *httpState = NULL;
090089c4 988
7111c86a 989 debug(11, 3, "httpStart: \"%s %s\"\n",
990 RequestMethodStr[request->method], url);
019dd986 991 debug(11, 10, "httpStart: req_hdr '%s'\n", req_hdr);
090089c4 992
090089c4 993 /* Create socket. */
16b204c4 994 sock = comm_open(SOCK_STREAM,
995 0,
996 Config.Addrs.tcp_outgoing,
997 0,
998 COMM_NONBLOCKING,
999 url);
090089c4 1000 if (sock == COMM_ERROR) {
019dd986 1001 debug(11, 4, "httpStart: Failed because we're out of sockets.\n");
b8de7ebe 1002 squid_error_entry(entry, ERR_NO_FDS, xstrerror());
090089c4 1003 return COMM_ERROR;
1004 }
30a4f2a8 1005 httpState = xcalloc(1, sizeof(HttpStateData));
1006 storeLockObject(httpState->entry = entry, NULL, NULL);
1007 httpState->req_hdr = req_hdr;
4768dbe0 1008 httpState->req_hdr_sz = req_hdr_sz;
30a4f2a8 1009 httpState->request = requestLink(request);
1010 comm_add_close_handler(sock,
b177367b 1011 httpStateFree,
30a4f2a8 1012 (void *) httpState);
a13d9042 1013 ipcache_nbgethostbyname(request->host,
1014 sock,
1015 httpConnect,
1016 httpState);
090089c4 1017 return COMM_OK;
1018}
30a4f2a8 1019
b8d8561b 1020void
1021httpReplyHeaderStats(StoreEntry * entry)
30a4f2a8 1022{
6fb52f6c 1023 http_server_cc_t i;
151a0b6d 1024 http_hdr_misc_t j;
30a4f2a8 1025 storeAppendPrintf(entry, open_bracket);
6fb52f6c 1026 storeAppendPrintf(entry, "{HTTP Reply Headers:}\n");
1027 storeAppendPrintf(entry, "{ Headers parsed: %d}\n",
30a4f2a8 1028 ReplyHeaderStats.parsed);
151a0b6d 1029 for (j = HDR_AGE; j < HDR_MISC_END; j++)
1030 storeAppendPrintf(entry, "{%21.21s: %d}\n",
1031 HttpHdrMiscStr[j],
1032 ReplyHeaderStats.misc[j]);
4db43fab 1033 for (i = SCC_PUBLIC; i < SCC_ENUM_END; i++)
884011a1 1034 storeAppendPrintf(entry, "{Cache-Control %s: %d}\n",
6fb52f6c 1035 HttpServerCCStr[i],
1036 ReplyHeaderStats.cc[i]);
30a4f2a8 1037 storeAppendPrintf(entry, close_bracket);
1038}