]> git.ipfire.org Git - thirdparty/squid.git/blame - src/HttpHeader.cc
- Removed debugging messages related to MIB initialization.
[thirdparty/squid.git] / src / HttpHeader.cc
CommitLineData
cb69b4c7 1/*
b4832aa9 2 * $Id: HttpHeader.cc,v 1.12 1998/03/03 22:17:50 rousskov Exp $
cb69b4c7 3 *
123abbe1 4 * DEBUG: section 55 HTTP Header
cb69b4c7 5 * AUTHOR: Alex Rousskov
6 *
7 * SQUID Internet Object Cache http://squid.nlanr.net/Squid/
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 */
30
31#include "squid.h"
cb69b4c7 32
33/*
2ac76861 34 * On naming conventions:
35 *
36 * HTTP/1.1 defines message-header as
37 *
38 * message-header = field-name ":" [ field-value ] CRLF
39 * field-name = token
40 * field-value = *( field-content | LWS )
41 *
42 * HTTP/1.1 does not give a name name a group of all message-headers in a message.
43 * Squid 1.1 seems to refer to that group _plus_ start-line as "headers".
44 *
45 * HttpHeader is an object that represents all message-headers in a message.
46 * HttpHeader does not manage start-line.
47 *
48 * HttpHeader is implemented as a collection of header "entries"
49 * An entry is a (field_id, field) pair where
50 * - field_id is one of the http_hdr_type ids,
51 * - field is a compiled(parsed) image of message-header.
52 */
cb69b4c7 53
54
55/*
56 * local types
57 */
58
59/* HTTP/1.1 extension-header */
60struct _HttpHeaderExtField {
2ac76861 61 char *name; /* field-name from HTTP/1.1 (no column after name!) */
62 char *value; /* field-value from HTTP/1.1 */
cb69b4c7 63};
64
65/* possible types for fields */
66typedef enum {
2ac76861 67 ftInvalid = HDR_ENUM_END, /* to catch nasty errors with hdr_id<->fld_type clashes */
cb69b4c7 68 ftInt,
69 ftPChar,
70 ftDate_1123,
71 ftPSCC,
b4832aa9 72 ftPExtField
cb69b4c7 73} field_type;
74
75/*
76 * HttpHeader entry
77 * ( the concrete type of entry.field is Headers[id].type )
78 */
79struct _HttpHeaderEntry {
80 field_store field;
81 http_hdr_type id;
82};
83
12cf1be2 84
85/* counters and size accumulators for stat objects */
86typedef int StatCount;
87typedef size_t StatSize;
88
89/* per field statistics */
90typedef struct {
2ac76861 91 StatCount aliveCount; /* created but not destroyed (count) */
92 StatCount parsCount; /* #parsing attempts */
93 StatCount errCount; /* #pasring errors */
94 StatCount repCount; /* #repetitons */
12cf1be2 95} HttpHeaderFieldStat;
96
97/* per header statistics */
98typedef struct {
99 const char *label;
100 StatHist hdrUCountDistr;
101 StatHist fieldTypeDistr;
102 StatHist ccTypeDistr;
103} HttpHeaderStat;
104
105
cb69b4c7 106/* constant attributes of fields */
107typedef struct {
108 const char *name;
109 http_hdr_type id;
110 field_type type;
111 int name_len;
12cf1be2 112 HttpHeaderFieldStat stat;
cb69b4c7 113} field_attrs_t;
114
115/* use HttpHeaderPos as opaque type, do not interpret */
116typedef ssize_t HttpHeaderPos;
117/* use this and only this to initialize HttpHeaderPos */
118#define HttpHeaderInitPos (-1)
119
120
cb69b4c7 121/*
122 * local constants and vars
123 */
124
125/*
126 * A table with major attributes for every known field.
127 * We calculate name lengths and reorganize this array on start up.
128 * After reorganization, field id can be used as an index to the table.
129 */
2ac76861 130static field_attrs_t Headers[] =
131{
132 {"Accept", HDR_ACCEPT, ftPChar},
133 {"Age", HDR_AGE, ftInt},
134 {"Cache-Control", HDR_CACHE_CONTROL, ftPSCC},
135 {"Connection", HDR_CONNECTION, ftPChar}, /* for now */
136 {"Content-Encoding", HDR_CONTENT_ENCODING, ftPChar},
137 {"Content-Length", HDR_CONTENT_LENGTH, ftInt},
138 {"Content-MD5", HDR_CONTENT_MD5, ftPChar}, /* for now */
139 {"Content-Type", HDR_CONTENT_TYPE, ftPChar},
140 {"Date", HDR_DATE, ftDate_1123},
141 {"Etag", HDR_ETAG, ftPChar}, /* for now */
142 {"Expires", HDR_EXPIRES, ftDate_1123},
143 {"Host", HDR_HOST, ftPChar},
144 {"If-Modified-Since", HDR_IMS, ftDate_1123},
145 {"Last-Modified", HDR_LAST_MODIFIED, ftDate_1123},
146 {"Location", HDR_LOCATION, ftPChar},
147 {"Max-Forwards", HDR_MAX_FORWARDS, ftInt},
148 {"Proxy-Authenticate", HDR_PROXY_AUTHENTICATE, ftPChar},
149 {"Public", HDR_PUBLIC, ftPChar},
150 {"Retry-After", HDR_RETRY_AFTER, ftPChar}, /* for now */
cb69b4c7 151 /* fix this: make count-but-treat as OTHER mask @?@ @?@ */
2ac76861 152 {"Set-Cookie:", HDR_SET_COOKIE, ftPChar},
153 {"Upgrade", HDR_UPGRADE, ftPChar}, /* for now */
154 {"Warning", HDR_WARNING, ftPChar}, /* for now */
155 {"WWW-Authenticate", HDR_WWW_AUTHENTICATE, ftPChar},
156 {"Proxy-Connection", HDR_PROXY_KEEPALIVE, ftInt}, /* true/false */
157 {"Other:", HDR_OTHER, ftPExtField} /* ':' will not allow matches */
cb69b4c7 158};
159
160/* this table is used for parsing server cache control header */
2ac76861 161static field_attrs_t SccAttrs[] =
162{
163 {"public", SCC_PUBLIC},
164 {"private", SCC_PRIVATE},
165 {"no-cache", SCC_NO_CACHE},
166 {"no-store", SCC_NO_STORE},
167 {"no-transform", SCC_NO_TRANSFORM},
168 {"must-revalidate", SCC_MUST_REVALIDATE},
169 {"proxy-revalidate", SCC_PROXY_REVALIDATE},
170 {"max-age", SCC_MAX_AGE}
cb69b4c7 171};
172
173/*
174 * headers with field values defined as #(values) in HTTP/1.1
175 *
176 * We have listed all possible list headers according to
177 * draft-ietf-http-v11-spec-rev-01.txt. Headers that are currently not
178 * recognized, are commented out.
179 */
2ac76861 180static int ListHeadersMask = 0; /* set run-time using ListHeaders */
181static http_hdr_type ListHeaders[] =
182{
183 HDR_ACCEPT,
cb69b4c7 184 /* HDR_ACCEPT_CHARSET, HDR_ACCEPT_ENCODING, HDR_ACCEPT_LANGUAGE, */
185 /* HDR_ACCEPT_RANGES, */
186 /* HDR_ALLOW, */
187 HDR_CACHE_CONTROL, HDR_CONNECTION,
2ac76861 188 HDR_CONTENT_ENCODING,
cb69b4c7 189 /* HDR_CONTENT_LANGUAGE, HDR_IF_MATCH, HDR_IF_NONE_MATCH,
2ac76861 190 * HDR_PRAGMA, HDR_TRANSFER_ENCODING, */
191 HDR_UPGRADE, /* HDR_VARY, */
cb69b4c7 192 /* HDR_VIA, HDR_WARNING, */
2ac76861 193 HDR_WWW_AUTHENTICATE,
cb69b4c7 194 /* HDR_EXPECT, HDR_TE, HDR_TRAILER */
195};
196
2ac76861 197static int ReplyHeadersMask = 0; /* set run-time using ReplyHeaders */
198static http_hdr_type ReplyHeaders[] =
199{
cb69b4c7 200 HDR_ACCEPT, HDR_AGE, HDR_CACHE_CONTROL, HDR_CONTENT_LENGTH,
2ac76861 201 HDR_CONTENT_MD5, HDR_CONTENT_TYPE, HDR_DATE, HDR_ETAG, HDR_EXPIRES,
cb69b4c7 202 HDR_LAST_MODIFIED, HDR_LOCATION, HDR_MAX_FORWARDS, HDR_PUBLIC, HDR_RETRY_AFTER,
203 HDR_SET_COOKIE, HDR_UPGRADE, HDR_WARNING, HDR_PROXY_KEEPALIVE, HDR_OTHER
204};
205
2ac76861 206static int RequestHeadersMask = 0; /* set run-time using RequestHeaders */
207static http_hdr_type RequestHeaders[] =
208{
cb69b4c7 209 HDR_OTHER
210};
211
12cf1be2 212/* when first field is added, this is how much entries we allocate */
cb69b4c7 213#define INIT_FIELDS_PER_HEADER 8
cb69b4c7 214
12cf1be2 215/* recycle bin for short strings (32KB total only) */
2ac76861 216static const size_t shortStrSize = 32; /* max size of a recyclable string */
217static const size_t shortStrPoolCount = (32 * 1024) / 32; /* sync this with shortStrSize */
cb69b4c7 218static MemPool *shortStrings = NULL;
219
12cf1be2 220/* header accounting */
2ac76861 221static HttpHeaderStat HttpHeaderStats[] =
222{
223 {"reply"},
224 {"request"},
225 {"all"}
12cf1be2 226};
2ac76861 227static int HttpHeaderStatCount = sizeof(HttpHeaderStats) / sizeof(*HttpHeaderStats);
12cf1be2 228
229/* global counters */
230static StatCount HeaderParsedCount = 0;
231static StatCount CcPasredCount = 0;
232static StatCount HeaderEntryParsedCount = 0;
233
cb69b4c7 234/* long strings accounting */
12cf1be2 235static StatCount longStrAliveCount = 0;
236static StatCount longStrHighWaterCount = 0;
237static StatSize longStrAliveSize = 0;
238static StatSize longStrHighWaterSize = 0;
cb69b4c7 239
240
12cf1be2 241/*
242 * local routines
243 */
cb69b4c7 244
245#define assert_eid(id) assert((id) >= 0 && (id) < HDR_ENUM_END)
246
2ac76861 247static void httpHeaderInitAttrTable(field_attrs_t * table, int count);
cb69b4c7 248static int httpHeaderCalcMask(const int *enums, int count);
2ac76861 249static void httpHeaderStatInit(HttpHeaderStat * hs, const char *label);
250
251static HttpHeaderEntry *httpHeaderGetEntry(const HttpHeader * hdr, HttpHeaderPos * pos);
252static void httpHeaderDelAt(HttpHeader * hdr, HttpHeaderPos pos);
253static void httpHeaderAddParsedEntry(HttpHeader * hdr, HttpHeaderEntry * e);
254static void httpHeaderAddNewEntry(HttpHeader * hdr, const HttpHeaderEntry * e);
255static void httpHeaderSet(HttpHeader * hdr, http_hdr_type id, const field_store value);
256static void httpHeaderSyncMasks(HttpHeader * hdr, const HttpHeaderEntry * e, int add);
257static int httpHeaderIdByName(const char *name, int name_len, const field_attrs_t * attrs, int end, int mask);
258static void httpHeaderGrow(HttpHeader * hdr);
259
260static void httpHeaderEntryInit(HttpHeaderEntry * e, http_hdr_type id, field_store field);
261static void httpHeaderEntryClean(HttpHeaderEntry * e);
262static int httpHeaderEntryParseInit(HttpHeaderEntry * e, const char *field_start, const char *field_end, int mask);
263static int httpHeaderEntryParseExtFieldInit(HttpHeaderEntry * e, int id, const HttpHeaderExtField * f);
264static int httpHeaderEntryParseByTypeInit(HttpHeaderEntry * e, int id, const HttpHeaderExtField * f);
265static HttpHeaderEntry httpHeaderEntryClone(const HttpHeaderEntry * e);
266static void httpHeaderEntryPackInto(const HttpHeaderEntry * e, Packer * p);
267static void httpHeaderEntryPackByType(const HttpHeaderEntry * e, Packer * p);
268static void httpHeaderEntryJoinWith(HttpHeaderEntry * e, const HttpHeaderEntry * newe);
269static int httpHeaderEntryIsValid(const HttpHeaderEntry * e);
270static const char *httpHeaderEntryName(const HttpHeaderEntry * e);
271
272static void httpHeaderFieldInit(field_store * field);
cb69b4c7 273static field_store httpHeaderFieldDup(field_type type, field_store value);
274static field_store httpHeaderFieldBadValue(field_type type);
275
276static HttpScc *httpSccCreate();
277static HttpScc *httpSccParseCreate(const char *str);
2ac76861 278static void httpSccParseInit(HttpScc * scc, const char *str);
279static void httpSccDestroy(HttpScc * scc);
280static HttpScc *httpSccDup(HttpScc * scc);
281static void httpSccUpdateStats(const HttpScc * scc, StatHist * hist);
12cf1be2 282
2ac76861 283static void httpSccPackValueInto(HttpScc * scc, Packer * p);
284static void httpSccJoinWith(HttpScc * scc, HttpScc * new_scc);
cb69b4c7 285
286static HttpHeaderExtField *httpHeaderExtFieldCreate(const char *name, const char *value);
287static HttpHeaderExtField *httpHeaderExtFieldParseCreate(const char *field_start, const char *field_end);
2ac76861 288static void httpHeaderExtFieldDestroy(HttpHeaderExtField * f);
289static HttpHeaderExtField *httpHeaderExtFieldDup(HttpHeaderExtField * f);
cb69b4c7 290
2ac76861 291static void httpHeaderStatDump(const HttpHeaderStat * hs, StoreEntry * e);
292static void shortStringStatDump(StoreEntry * e);
cb69b4c7 293
294static char *dupShortStr(const char *str);
295static char *dupShortBuf(const char *str, size_t len);
296static char *appShortStr(char *str, const char *app_str);
297static char *allocShortBuf(size_t size);
298static void freeShortString(char *str);
299
300static int strListGetItem(const char *str, char del, const char **item, int *ilen, const char **pos);
301static const char *getStringPrefix(const char *str);
cb69b4c7 302
303/* handy to determine the #elements in a static array */
304#define countof(arr) (sizeof(arr)/sizeof(*arr))
305
7021844c 306/*
307 * some compilers do not want to convert a type into a union which that type
308 * belongs to
309 */
310field_store intField(int n) { field_store f; f.v_int = n; return f; }
311field_store timeField(time_t t) { field_store f; f.v_time = t; return f; }
312field_store ptrField(void *p) { field_store f; f.v_pchar = (char*)p; return f; }
313
cb69b4c7 314/*
315 * Module initialization routines
316 */
317
318void
319httpHeaderInitModule()
320{
12cf1be2 321 int i;
cb69b4c7 322 /* paranoid check if smbd put a big object into field_store */
2ac76861 323 assert(sizeof(field_store) == sizeof(char *));
cb69b4c7 324 /* have to force removal of const here */
2ac76861 325 httpHeaderInitAttrTable((field_attrs_t *) Headers, countof(Headers));
326 httpHeaderInitAttrTable((field_attrs_t *) SccAttrs, countof(SccAttrs));
cb69b4c7 327 /* create masks */
2ac76861 328 ListHeadersMask = httpHeaderCalcMask((const int *) ListHeaders, countof(ListHeaders));
329 ReplyHeadersMask = httpHeaderCalcMask((const int *) ReplyHeaders, countof(ReplyHeaders));
330 RequestHeadersMask = httpHeaderCalcMask((const int *) RequestHeaders, countof(RequestHeaders));
cb69b4c7 331 /* create a pool of short strings @?@ we never destroy it! */
7021844c 332 shortStrings = memPoolCreate("'short http hdr strs'", shortStrSize);
12cf1be2 333 /* init header stats */
334 for (i = 0; i < HttpHeaderStatCount; i++)
2ac76861 335 httpHeaderStatInit(HttpHeaderStats + i, HttpHeaderStats[i].label);
12cf1be2 336 cachemgrRegister("http_headers",
337 "HTTP Header Statistics", httpHeaderStoreReport, 0);
cb69b4c7 338}
339
7021844c 340void
341httpHeaderCleanModule()
342{
343 if (shortStrings) {
344 memPoolDestroy(shortStrings);
345 shortStrings = NULL;
346 }
347}
348
cb69b4c7 349static void
2ac76861 350httpHeaderInitAttrTable(field_attrs_t * table, int count)
cb69b4c7 351{
352 int i;
353 assert(table);
2ac76861 354 assert(count > 1); /* to protect from buggy "countof" implementations */
cb69b4c7 355
356 /* reorder so that .id becomes an index */
357 for (i = 0; i < count;) {
358 const int id = table[i].id;
2ac76861 359 assert(id >= 0 && id < count); /* sanity check */
360 assert(id >= i); /* entries prior to i have been indexed already */
361 if (id != i) { /* out of order */
cb69b4c7 362 const field_attrs_t fa = table[id];
2ac76861 363 assert(fa.id != id); /* avoid endless loops */
364 table[id] = table[i]; /* swap */
cb69b4c7 365 table[i] = fa;
366 } else
2ac76861 367 i++; /* make progress */
cb69b4c7 368 }
369
12cf1be2 370 /* calculate name lengths and init stats */
cb69b4c7 371 for (i = 0; i < count; ++i) {
372 assert(table[i].name);
373 table[i].name_len = strlen(table[i].name);
2ac76861 374 debug(55, 5) ("hdr table entry[%d]: %s (%d)\n", i, table[i].name, table[i].name_len);
cb69b4c7 375 assert(table[i].name_len);
12cf1be2 376 /* init stats */
377 memset(&table[i].stat, 0, sizeof(table[i].stat));
cb69b4c7 378 }
379}
380
12cf1be2 381static void
2ac76861 382httpHeaderStatInit(HttpHeaderStat * hs, const char *label)
12cf1be2 383{
384 assert(hs);
385 assert(label);
386 hs->label = label;
2ac76861 387 statHistEnumInit(&hs->hdrUCountDistr, 32); /* not a real enum */
12cf1be2 388 statHistEnumInit(&hs->fieldTypeDistr, HDR_ENUM_END);
389 statHistEnumInit(&hs->ccTypeDistr, SCC_ENUM_END);
390}
391
cb69b4c7 392/* calculates a bit mask of a given array (move this to lib/uitils) @?@ */
393static int
394httpHeaderCalcMask(const int *enums, int count)
395{
396 int i;
397 int mask = 0;
398 assert(enums);
2ac76861 399 assert(count < sizeof(int) * 8); /* check for overflow */
cb69b4c7 400
401 for (i = 0; i < count; ++i) {
2ac76861 402 assert(enums[i] < sizeof(int) * 8); /* check for overflow again */
403 assert(!EBIT_TEST(mask, enums[i])); /* check for duplicates */
cb69b4c7 404 EBIT_SET(mask, enums[i]);
405 }
406 return mask;
407}
408
409
410/*
411 * HttpHeader Implementation
412 */
413
414
415HttpHeader *
416httpHeaderCreate()
417{
418 HttpHeader *hdr = xmalloc(sizeof(HttpHeader));
419 httpHeaderInit(hdr);
420 return hdr;
421}
422
423
424/* "create" for non-alloc objects; also used by real Create */
425void
2ac76861 426httpHeaderInit(HttpHeader * hdr)
cb69b4c7 427{
428 assert(hdr);
429 memset(hdr, 0, sizeof(*hdr));
cb69b4c7 430}
431
432void
2ac76861 433httpHeaderClean(HttpHeader * hdr)
cb69b4c7 434{
435 HttpHeaderPos pos = HttpHeaderInitPos;
12cf1be2 436 HttpHeaderEntry *e;
cb69b4c7 437
8d2e9b4a 438 debug(55, 7) ("cleaning hdr: %p\n", hdr);
cb69b4c7 439 assert(hdr);
440
12cf1be2 441 statHistCount(&HttpHeaderStats[0].hdrUCountDistr, hdr->ucount);
442 while ((e = httpHeaderGetEntry(hdr, &pos))) {
443 /* fix this (for scc too) for req headers @?@ */
444 statHistCount(&HttpHeaderStats[0].fieldTypeDistr, e->id);
445 if (e->id == HDR_CACHE_CONTROL)
446 httpSccUpdateStats(e->field.v_pscc, &HttpHeaderStats[0].ccTypeDistr);
cb69b4c7 447 httpHeaderDelAt(hdr, pos);
12cf1be2 448 }
cb69b4c7 449 xfree(hdr->entries);
450 hdr->emask = 0;
451 hdr->entries = NULL;
452 hdr->capacity = hdr->ucount = 0;
453}
454
455void
2ac76861 456httpHeaderDestroy(HttpHeader * hdr)
cb69b4c7 457{
458 httpHeaderClean(hdr);
459 xfree(hdr);
460}
461
462/* create a copy of self */
463HttpHeader *
2ac76861 464httpHeaderClone(HttpHeader * hdr)
cb69b4c7 465{
466 HttpHeader *clone = httpHeaderCreate();
467 HttpHeaderEntry *e;
468 HttpHeaderPos pos = HttpHeaderInitPos;
469
2ac76861 470 debug(55, 7) ("cloning hdr: %p -> %p\n", hdr, clone);
cb69b4c7 471
472 while ((e = httpHeaderGetEntry(hdr, &pos))) {
473 HttpHeaderEntry e_clone = httpHeaderEntryClone(e);
474 httpHeaderAddNewEntry(clone, &e_clone);
475 }
476
477 return clone;
478}
479
480/* just handy in parsing: resets and returns false */
481static int
2ac76861 482httpHeaderReset(HttpHeader * hdr)
483{
cb69b4c7 484 httpHeaderClean(hdr);
485 httpHeaderInit(hdr);
486 return 0;
487}
488
489/*
490 * Note: currently, in most cases, we discard a field if we cannot parse it. We
491 * also truncate some field values (e.g. content-type). Thus, we may not
492 * forward exactly what was received. However, Squid keeps a copy of "raw"
493 * headers anyway, so we are safe until that changes. A possible alternative
494 * would be to store any buggy field as HDR_OTHER, but that still leaves a
495 * problem with truncated fields. The later one requires a better parser and
496 * additional storage, I guess.
497 */
498int
2ac76861 499httpHeaderParse(HttpHeader * hdr, const char *header_start, const char *header_end)
cb69b4c7 500{
501 const char *field_start = header_start;
502 HttpHeaderEntry e;
503 int mask = 0;
504
505 assert(hdr);
506 assert(header_start && header_end);
2ac76861 507 debug(55, 7) ("parsing hdr: (%p) '%s'\n...\n", hdr, getStringPrefix(header_start));
cb69b4c7 508 /* select appropriate field mask */
2ac76861 509 mask = ( /* fix this @?@ @?@ */ 1) ? ReplyHeadersMask : RequestHeadersMask;
cb69b4c7 510 /* commonn format headers are "<name>:[ws]<value>" lines delimited by <CRLF> */
511 while (field_start < header_end) {
512 const char *field_end = field_start + strcspn(field_start, "\r\n");
2ac76861 513 /*tmp_debug(here) ("found end of field: %d\n", (int)*field_end); */
514 if (!*field_end)
515 return httpHeaderReset(hdr); /* missing <CRLF> */
cb69b4c7 516 /*
517 * If we fail to parse a field, we ignore that field. We also could
518 * claim that the whole header is invalid. The latter is safer, but less
519 * robust. Note that we should be able to parse any commonn format field
520 */
521 if (!httpHeaderEntryParseInit(&e, field_start, field_end, mask))
522 debug(55, 1) ("warning: ignoring unparseable http header field near '%s'\n",
523 getStringPrefix(field_start));
524 else
525 httpHeaderAddParsedEntry(hdr, &e);
526 /*
527 * Note that we init() e, bit never clean() it which is equivalent to *
528 * creating a fresh entry on each loop iteration; thus, it is safe to *
529 * add e without dup()-ing it.
530 */
531 field_start = field_end;
532 /* skip CRLF */
2ac76861 533 if (*field_start == '\r')
534 field_start++;
535 if (*field_start == '\n')
536 field_start++;
cb69b4c7 537 }
2ac76861 538 return 1; /* even if no fields where found, they could be optional! */
cb69b4c7 539}
540
541/*
542 * packs all the entries into the buffer,
543 * returns number of bytes packed including terminating '\0'
544 */
545void
2ac76861 546httpHeaderPackInto(const HttpHeader * hdr, Packer * p)
cb69b4c7 547{
548 HttpHeaderPos pos = HttpHeaderInitPos;
549 const HttpHeaderEntry *e;
550 assert(hdr && p);
2ac76861 551 debug(55, 7) ("packing hdr: (%p)\n", hdr);
cb69b4c7 552 /* pack all entries one by one */
553 while ((e = httpHeaderGetEntry(hdr, &pos))) {
554 httpHeaderEntryPackInto(e, p);
555 }
556}
557
558/* returns next valid entry */
559static HttpHeaderEntry *
2ac76861 560httpHeaderGetEntry(const HttpHeader * hdr, HttpHeaderPos * pos)
cb69b4c7 561{
562 assert(hdr && pos);
563 assert(*pos >= HttpHeaderInitPos && *pos < hdr->capacity);
b4832aa9 564 debug(55,8) ("searching next e in hdr %p from %d\n", hdr, *pos);
cb69b4c7 565 for ((*pos)++; *pos < hdr->ucount; (*pos)++) {
566 HttpHeaderEntry *e = hdr->entries + *pos;
2ac76861 567 if (httpHeaderEntryIsValid(e)) {
568 debug(55, 8) ("%p returning entry: %s at %d\n",
cb69b4c7 569 hdr, httpHeaderEntryName(e), *pos);
2ac76861 570 return e;
cb69b4c7 571 }
572 }
2ac76861 573 debug(55, 8) ("no more entries in hdr %p\n", hdr);
cb69b4c7 574 return NULL;
575}
576
577/*
578 * returns a pointer to a specified entry and updates pos;
579 * note that we search from the very begining so it does not make much sense to
580 * ask for HDR_OTHER entries since there could be more than one.
581 */
582static HttpHeaderEntry *
2ac76861 583httpHeaderFindEntry(const HttpHeader * hdr, http_hdr_type id, HttpHeaderPos * pos)
cb69b4c7 584{
585 HttpHeaderPos p;
586 HttpHeaderEntry *e;
587 int is_absent;
588 assert(hdr);
589 assert_eid(id);
590 assert(id != HDR_OTHER);
591
2ac76861 592 debug(55, 8) ("finding entry %d in hdr %p\n", id, hdr);
cb69b4c7 593 /* check mask first @?@ @?@ remove double checking and asserts when done */
594 is_absent = (id != HDR_OTHER && !EBIT_TEST(hdr->emask, id));
2ac76861 595 if (!pos)
596 pos = &p;
cb69b4c7 597 *pos = HttpHeaderInitPos;
598 while ((e = httpHeaderGetEntry(hdr, pos))) {
2ac76861 599 if (e->id == id) {
cb69b4c7 600 assert(!is_absent);
2ac76861 601 return e;
cb69b4c7 602 }
603 }
604 assert(!EBIT_TEST(hdr->emask, id));
605 return NULL;
606}
607
608/*
609 * deletes all field(s) with a given name if any, returns #fields deleted;
610 * used to process Connection: header and delete fields in "paranoid" setup
611 */
2ac76861 612int
613httpHeaderDelFields(HttpHeader * hdr, const char *name)
cb69b4c7 614{
615 int count = 0;
616 HttpHeaderPos pos = HttpHeaderInitPos;
617 HttpHeaderEntry *e;
618
2ac76861 619 debug(55, 7) ("deleting '%s' fields in hdr %p\n", name, hdr);
cb69b4c7 620 while ((e = httpHeaderGetEntry(hdr, &pos))) {
2ac76861 621 if (!strcmp(httpHeaderEntryName(e), name)) {
cb69b4c7 622 httpHeaderDelAt(hdr, pos);
623 count++;
624 }
625 }
626 return count;
627}
628
629/*
630 * deletes an entry at pos and leaves a gap; leaving a gap makes it
631 * possible to iterate(search) and delete fields at the same time
632 */
633static void
2ac76861 634httpHeaderDelAt(HttpHeader * hdr, HttpHeaderPos pos)
cb69b4c7 635{
636 HttpHeaderEntry *e;
637 assert(hdr);
638 assert(pos >= 0 && pos < hdr->ucount);
639 e = hdr->entries + pos;
2ac76861 640 debug(55, 7) ("%p deling entry at %d: id: %d (%p:%p)\n",
cb69b4c7 641 hdr, pos, e->id, hdr->entries, e);
642 /* sync masks */
643 httpHeaderSyncMasks(hdr, e, 0);
644 httpHeaderEntryClean(e);
645 if (pos == hdr->ucount)
646 hdr->ucount--;
647}
648
649/*
650 * adds parsed entry (joins entries if neeeded); assumes e.value is dup()-ed and
651 * clean()s it if needed. Thus, "e" should be treated as uninitialized after
652 * this function returns.
653 */
654static void
2ac76861 655httpHeaderAddParsedEntry(HttpHeader * hdr, HttpHeaderEntry * e)
cb69b4c7 656{
657 HttpHeaderEntry *olde;
658 assert(hdr);
8d2e9b4a 659 assert(e);
cb69b4c7 660 assert_eid(e->id);
661
2ac76861 662 debug(55, 7) ("%p adding parsed entry %d\n", hdr, e->id);
cb69b4c7 663
664 /* there is no good reason to add invalid entries */
665 if (!httpHeaderEntryIsValid(e))
666 return;
667
668 olde = (e->id == HDR_OTHER) ? NULL : httpHeaderFindEntry(hdr, e->id, NULL);
669 if (olde) {
670 if (EBIT_TEST(ListHeadersMask, e->id))
671 httpHeaderEntryJoinWith(olde, e);
12cf1be2 672 else {
673 debug(55, 2) ("ignoring duplicate header: %s\n", httpHeaderEntryName(e));
674 Headers[e->id].stat.repCount++;
675 }
cb69b4c7 676 httpHeaderEntryClean(e);
677 } else {
678 /* actual add */
679 httpHeaderAddNewEntry(hdr, e);
2ac76861 680 debug(55, 6) ("%p done adding parsed entry %d (%s)\n", hdr, e->id, httpHeaderEntryName(e));
cb69b4c7 681 }
cb69b4c7 682}
683
684/*
685 * adds a new entry (low level append, does not check if entry is new) note: we
686 * copy e value, thus, e can point to a tmp variable (but e->field is not dupped!)
687 */
688static void
2ac76861 689httpHeaderAddNewEntry(HttpHeader * hdr, const HttpHeaderEntry * e)
cb69b4c7 690{
691 assert(hdr && e);
2ac76861 692 debug(55, 8) ("%p adding entry: %d at %d, (%p:%p)\n",
693 hdr, e->id, hdr->ucount,
cb69b4c7 694 hdr->entries, hdr->entries + hdr->ucount);
12cf1be2 695 if (!hdr->ucount)
696 HeaderParsedCount++;
697 if (hdr->ucount >= hdr->capacity)
698 httpHeaderGrow(hdr);
cb69b4c7 699 hdr->entries[hdr->ucount++] = *e;
700 /* sync masks */
701 httpHeaderSyncMasks(hdr, e, 1);
cb69b4c7 702}
703
2ac76861 704#if 0 /* save for parts */
cb69b4c7 705/*
706 * Splits list field and appends all entries separately;
707 * Warning: This is internal function, never call this directly,
708 * only for httpHeaderAddField use.
709 */
710static void
2ac76861 711httpHeaderAddListField(HttpHeader * hdr, HttpHeaderField * fld)
cb69b4c7 712{
713 const char *v;
714 assert(hdr);
715 assert(fld);
716 /*
717 * Note: assume that somebody already checked that we can split. The danger
718 * is in splitting something that is not a list field but contains ','s in
719 * its value.
720 */
721 /* we got a fld.value that is a list of values separated by ',' */
722 v = strtok(fld->value, ",");
2ac76861 723 httpHeaderAddSingleField(hdr, fld); /* first strtok() did its job! */
cb69b4c7 724 while ((v = strtok(NULL, ","))) {
725 /* ltrim and skip empty fields */
2ac76861 726 while (isspace(*v) || *v == ',')
727 v++;
cb69b4c7 728 if (*v)
729 httpHeaderAddSingleField(hdr, httpHeaderFieldCreate(fld->name, v));
730 }
731}
732#endif
733
734/*
735 * Global (user level) routines
736 */
737
738/* test if a field is present */
2ac76861 739int
740httpHeaderHas(const HttpHeader * hdr, http_hdr_type id)
cb69b4c7 741{
742 assert(hdr);
743 assert_eid(id);
744 assert(id != HDR_OTHER);
2ac76861 745 debug(55, 7) ("%p lookup for %d\n", hdr, id);
cb69b4c7 746 return EBIT_TEST(hdr->emask, id);
747
748#ifdef SLOW_BUT_SAFE
749 return httpHeaderFindEntry(hdr, id, NULL) != NULL;
750#endif
751}
752
753/* delete a field if any */
2ac76861 754void
755httpHeaderDel(HttpHeader * hdr, http_hdr_type id)
cb69b4c7 756{
757 HttpHeaderPos pos = HttpHeaderInitPos;
758 assert(id != HDR_OTHER);
2ac76861 759 debug(55, 8) ("%p del-by-id %d\n", hdr, id);
cb69b4c7 760 if (httpHeaderFindEntry(hdr, id, &pos)) {
761 httpHeaderDelAt(hdr, pos);
762 }
763}
764
765/*
766 * set a field
767 * setting an invaid value is equivalent to deleting a field
768 * (if field is not present, it is added; otherwise, old content is destroyed).
769 */
770static void
2ac76861 771httpHeaderSet(HttpHeader * hdr, http_hdr_type id, const field_store value)
cb69b4c7 772{
773 HttpHeaderPos pos;
774 HttpHeaderEntry e;
775 assert(hdr);
776 assert_eid(id);
2ac76861 777
778 debug(55, 7) ("%p sets entry with id: %d\n", hdr, id);
779 if (httpHeaderFindEntry(hdr, id, &pos)) /* delete old entry */
cb69b4c7 780 httpHeaderDelAt(hdr, pos);
781
782 httpHeaderEntryInit(&e, id, httpHeaderFieldDup(Headers[id].type, value));
783 if (httpHeaderEntryIsValid(&e))
784 httpHeaderAddNewEntry(hdr, &e);
785 else
786 httpHeaderEntryClean(&e);
787}
788
789void
2ac76861 790httpHeaderSetInt(HttpHeader * hdr, http_hdr_type id, int number)
cb69b4c7 791{
792 field_store value;
793 assert_eid(id);
2ac76861 794 assert(Headers[id].type == ftInt); /* must be of an appropriatre type */
cb69b4c7 795 value.v_int = number;
796 httpHeaderSet(hdr, id, value);
797}
798
799void
2ac76861 800httpHeaderSetTime(HttpHeader * hdr, http_hdr_type id, time_t time)
cb69b4c7 801{
802 field_store value;
803 assert_eid(id);
2ac76861 804 assert(Headers[id].type == ftDate_1123); /* must be of an appropriatre type */
cb69b4c7 805 value.v_time = time;
806 httpHeaderSet(hdr, id, value);
807}
2ac76861 808
cb69b4c7 809void
2ac76861 810httpHeaderSetStr(HttpHeader * hdr, http_hdr_type id, const char *str)
cb69b4c7 811{
812 field_store value;
813 assert_eid(id);
2ac76861 814 assert(Headers[id].type == ftPChar); /* must be of a string type */
cb69b4c7 815 value.v_pcchar = str;
816 httpHeaderSet(hdr, id, value);
817}
818
63259c34 819void
2ac76861 820httpHeaderSetAuth(HttpHeader * hdr, const char *authScheme, const char *realm)
63259c34 821{
822 MemBuf mb;
823 assert(hdr && authScheme && realm);
824 memBufDefInit(&mb);
825 memBufPrintf(&mb, "%s realm=\"%s\"", authScheme, realm);
826 httpHeaderSetStr(hdr, HDR_WWW_AUTHENTICATE, mb.buf);
827 memBufClean(&mb);
828}
829
cb69b4c7 830/* add extension header (these fields are not parsed/analyzed/joined, etc.) */
831void
2ac76861 832httpHeaderAddExt(HttpHeader * hdr, const char *name, const char *value)
cb69b4c7 833{
834 HttpHeaderExtField *ext = httpHeaderExtFieldCreate(name, value);
835 HttpHeaderEntry e;
836
2ac76861 837 debug(55, 8) ("%p adds ext entry '%s:%s'\n", hdr, name, value);
7021844c 838 httpHeaderEntryInit(&e, HDR_OTHER, ptrField(ext));
cb69b4c7 839 httpHeaderAddNewEntry(hdr, &e);
840}
841
842/* get a value of a field (not lvalue though) */
843field_store
2ac76861 844httpHeaderGet(const HttpHeader * hdr, http_hdr_type id)
cb69b4c7 845{
846 HttpHeaderEntry *e;
847 assert_eid(id);
2ac76861 848 assert(id != HDR_OTHER); /* there is no single value for HDR_OTHER */
cb69b4c7 849
2ac76861 850 debug(55, 7) ("%p get for id %d\n", hdr, id);
cb69b4c7 851 if ((e = httpHeaderFindEntry(hdr, id, NULL)))
852 return e->field;
853 else
854 return httpHeaderFieldBadValue(Headers[id].type);
855}
856
857const char *
2ac76861 858httpHeaderGetStr(const HttpHeader * hdr, http_hdr_type id)
cb69b4c7 859{
860 assert_eid(id);
2ac76861 861 assert(Headers[id].type == ftPChar); /* must be of an apropriate type */
cb69b4c7 862 return httpHeaderGet(hdr, id).v_pchar;
863}
864
865time_t
2ac76861 866httpHeaderGetTime(const HttpHeader * hdr, http_hdr_type id)
cb69b4c7 867{
868 assert_eid(id);
2ac76861 869 assert(Headers[id].type == ftDate_1123); /* must be of an apropriate type */
cb69b4c7 870 return httpHeaderGet(hdr, id).v_time;
871}
872
873HttpScc *
2ac76861 874httpHeaderGetScc(const HttpHeader * hdr)
cb69b4c7 875{
876 return httpHeaderGet(hdr, HDR_CACHE_CONTROL).v_pscc;
877}
878
879/* updates header masks */
880static void
2ac76861 881httpHeaderSyncMasks(HttpHeader * hdr, const HttpHeaderEntry * e, int add)
cb69b4c7 882{
883 int isSet;
884 assert(hdr && e);
885 assert_eid(e->id);
886
887 /* we cannot mask HDR_OTHER because it may not be unique */
888 if (e->id == HDR_OTHER)
889 return;
890 isSet = EBIT_TEST(hdr->emask, e->id) != 0;
891 add = add != 0;
892 assert(isSet ^ add);
893 add ? EBIT_SET(hdr->emask, e->id) : EBIT_CLR(hdr->emask, e->id);
894}
895
cb69b4c7 896static int
2ac76861 897httpHeaderIdByName(const char *name, int name_len, const field_attrs_t * attrs, int end, int mask)
cb69b4c7 898{
899 int i;
900 for (i = 0; i < end; ++i) {
901 if (mask < 0 || EBIT_TEST(mask, i)) {
902 if (name_len >= 0 && name_len != attrs[i].name_len)
903 continue;
2ac76861 904 if (!strncasecmp(name, attrs[i].name,
905 name_len < 0 ? attrs[i].name_len + 1 : name_len))
cb69b4c7 906 return i;
907 }
908 }
909 return -1;
910}
911
912/* doubles the size of the fields index, starts with INIT_FIELDS_PER_HEADER */
913static void
2ac76861 914httpHeaderGrow(HttpHeader * hdr)
cb69b4c7 915{
916 int new_cap;
917 int new_size;
918 assert(hdr);
2ac76861 919 new_cap = (hdr->capacity) ? 2 * hdr->capacity : INIT_FIELDS_PER_HEADER;
920 new_size = new_cap * sizeof(HttpHeaderEntry);
cb69b4c7 921
2ac76861 922 debug(55, 9) ("%p grow (%p) %d->%d\n", hdr, hdr->entries, hdr->capacity, new_cap);
cb69b4c7 923 hdr->entries = hdr->entries ?
924 xrealloc(hdr->entries, new_size) :
925 xmalloc(new_size);
2ac76861 926 memset(hdr->entries + hdr->capacity, 0, (new_cap - hdr->capacity) * sizeof(HttpHeaderEntry));
cb69b4c7 927 hdr->capacity = new_cap;
2ac76861 928 debug(55, 9) ("%p grew (%p)\n", hdr, hdr->entries);
cb69b4c7 929}
930
931/*
932 * HttpHeaderEntry
933 */
934
935static void
2ac76861 936httpHeaderEntryInit(HttpHeaderEntry * e, http_hdr_type id, field_store field)
cb69b4c7 937{
938 assert(e);
939 assert_eid(id);
940 e->id = id;
941 e->field = field;
12cf1be2 942 Headers[id].stat.aliveCount++;
cb69b4c7 943}
944
945static void
2ac76861 946httpHeaderEntryClean(HttpHeaderEntry * e)
947{
cb69b4c7 948 assert(e);
949 assert_eid(e->id);
950 /* type-based cleanup */
951 switch (Headers[e->id].type) {
2ac76861 952 case ftInvalid:
953 case ftInt:
954 case ftDate_1123:
955 /* no special cleaning is necessary */
956 break;
957 case ftPChar:
958 freeShortString(e->field.v_pchar);
959 break;
960 case ftPSCC:
961 if (e->field.v_pscc)
962 httpSccDestroy(e->field.v_pscc);
963 break;
964 case ftPExtField:
965 if (e->field.v_pefield)
966 httpHeaderExtFieldDestroy(e->field.v_pefield);
967 break;
968 default:
969 assert(0); /* somebody added a new type? */
cb69b4c7 970 }
12cf1be2 971 Headers[e->id].stat.aliveCount--;
cb69b4c7 972 /* we have to do that so entry will be _invlaid_ */
973 e->id = -1;
974 e->field.v_pchar = NULL;
975}
976
977/* parses and inits header entry, returns true on success */
978static int
2ac76861 979httpHeaderEntryParseInit(HttpHeaderEntry * e, const char *field_start, const char *field_end, int mask)
cb69b4c7 980{
981 HttpHeaderExtField *f;
982 int id;
983 int result;
984
12cf1be2 985 HeaderEntryParsedCount++;
8d2e9b4a 986 /* paranoid reset */
987 e->id = -1;
988 e->field.v_pchar = NULL;
cb69b4c7 989 /* first assume it is just an extension field */
990 f = httpHeaderExtFieldParseCreate(field_start, field_end);
2ac76861 991 if (!f) /* total parsing failure */
cb69b4c7 992 return 0;
993 id = httpHeaderIdByName(f->name, -1, Headers, countof(Headers), mask);
994 if (id < 0)
995 id = HDR_OTHER;
12cf1be2 996 Headers[id].stat.parsCount++;
cb69b4c7 997 if (id == HDR_OTHER) {
998 /* hm.. it is an extension field indeed */
7021844c 999 httpHeaderEntryInit(e, id, ptrField(f));
cb69b4c7 1000 return 1;
1001 }
1002 /* ok, we got something interesting, parse it further */
1003 result = httpHeaderEntryParseExtFieldInit(e, id, f);
1004 /* do not need it anymore */
1005 httpHeaderExtFieldDestroy(f);
1006 return result;
1007}
1008
1009static int
2ac76861 1010httpHeaderEntryParseExtFieldInit(HttpHeaderEntry * e, int id, const HttpHeaderExtField * f)
cb69b4c7 1011{
8d2e9b4a 1012 assert(e && f);
1013 assert_eid(id);
12cf1be2 1014 e->id = -1;
cb69b4c7 1015 /*
1016 * check for exceptions first (parsing is not determined by value type)
1017 * then parse using value type if needed
1018 */
1019 switch (id) {
2ac76861 1020 case HDR_PROXY_KEEPALIVE:
1021 /* we treat Proxy-Connection as "keep alive" only if it says so */
7021844c 1022 httpHeaderEntryInit(e, id, intField(!strcasecmp(f->value, "Keep-Alive")));
2ac76861 1023 break;
1024 default:
1025 /* if we got here, it is something that can be parsed based on value type */
1026 if (!httpHeaderEntryParseByTypeInit(e, id, f))
1027 return 0;
cb69b4c7 1028 }
1029 /* parsing was successful, post-processing maybe required */
1030 switch (id) {
2ac76861 1031 case HDR_CONTENT_TYPE:{
cb69b4c7 1032 /* cut off "; parameter" from Content-Type @?@ why? */
1033 const int l = strcspn(e->field.v_pchar, ";\t ");
1034 if (l > 0)
1035 e->field.v_pchar[l] = '\0';
1036 break;
1037 }
2ac76861 1038 case HDR_EXPIRES:
1039 /*
1040 * The HTTP/1.0 specs says that robust implementations should
1041 * consider bad or malformed Expires header as equivalent to
1042 * "expires immediately."
1043 */
1044 if (!httpHeaderEntryIsValid(e))
1045 e->field.v_time = squid_curtime;
1046 /*
1047 * real expiration value also depends on max-age too, but it is not
1048 * of our business (HttpReply should handle it)
1049 */
1050 break;
cb69b4c7 1051 }
1052 return 1;
1053}
1054
1055static int
2ac76861 1056httpHeaderEntryParseByTypeInit(HttpHeaderEntry * e, int id, const HttpHeaderExtField * f)
cb69b4c7 1057{
1058 int type;
1059 field_store field;
1060 assert(e && f);
1061 assert_eid(id);
1062 type = Headers[id].type;
1063
1064 httpHeaderFieldInit(&field);
2ac76861 1065 switch (type) {
1066 case ftInt:
1067 field.v_int = atoi(f->value);
1068 if (!field.v_int && !isdigit(*f->value)) {
1069 debug(55, 1) ("cannot parse an int header field: id: %d, field: '%s: %s'\n",
1070 id, f->name, f->value);
1071 Headers[id].stat.errCount++;
1072 return 0;
1073 }
1074 break;
cb69b4c7 1075
2ac76861 1076 case ftPChar:
1077 field.v_pchar = dupShortStr(f->value);
1078 break;
cb69b4c7 1079
2ac76861 1080 case ftDate_1123:
1081 field.v_time = parse_rfc1123(f->value);
1082 if (field.v_time <= 0)
1083 Headers[id].stat.errCount++;
1084 /*
1085 * if parse_rfc1123 fails we fall through anyway so upper levels
1086 * will notice invalid date
1087 */
1088 break;
1089
1090 case ftPSCC:
1091 field.v_pscc = httpSccParseCreate(f->value);
1092 if (!field.v_pscc) {
1093 debug(55, 0) ("failed to parse scc hdr: id: %d, field: '%s: %s'\n",
1094 id, f->name, f->value);
1095 Headers[id].stat.errCount++;
1096 return 0;
1097 }
1098 break;
cb69b4c7 1099
2ac76861 1100 default:
1101 debug(55, 0) ("something went wrong with hdr field type analysis: id: %d, type: %d, field: '%s: %s'\n",
1102 id, type, f->name, f->value);
1103 assert(0);
cb69b4c7 1104 }
1105 /* success, do actual init */
1106 httpHeaderEntryInit(e, id, field);
1107 return 1;
1108}
1109
1110
1111static HttpHeaderEntry
2ac76861 1112httpHeaderEntryClone(const HttpHeaderEntry * e)
cb69b4c7 1113{
1114 HttpHeaderEntry clone;
1115 assert(e);
1116 assert_eid(e->id);
1117 httpHeaderEntryInit(&clone, e->id,
1118 httpHeaderFieldDup(Headers[e->id].type, e->field));
1119 return clone;
1120}
1121
1122static void
2ac76861 1123httpHeaderEntryPackInto(const HttpHeaderEntry * e, Packer * p)
cb69b4c7 1124{
1125 assert(e && p);
1126
1127 /* swap the field_name: */
1128 packerPrintf(p, "%s: ", httpHeaderEntryName(e));
1129 /*
1130 * swap the value
1131 * check for exceptions (packing is not determined by value type)
1132 * then swap using value type
1133 */
1134 switch (e->id) {
2ac76861 1135 case HDR_PROXY_KEEPALIVE:
1136 packerPrintf(p, "%s", "Keep-Alive");
1137 break;
1138 default:
1139 /* if we got here, it is something that can be swap based on value type */
1140 httpHeaderEntryPackByType(e, p);
cb69b4c7 1141 }
1142 /* add CRLF */
1143 packerPrintf(p, "%s", "\r\n");
1144}
1145
1146static void
2ac76861 1147httpHeaderEntryPackByType(const HttpHeaderEntry * e, Packer * p)
cb69b4c7 1148{
1149 field_type type;
1150 assert(e && p);
1151 assert_eid(e->id);
2ac76861 1152 type = Headers[e->id].type;
1153 switch (type) {
1154 case ftInt:
1155 packerPrintf(p, "%d", e->field.v_int);
1156 break;
1157 case ftPChar:
1158 packerPrintf(p, "%s", e->field.v_pchar);
1159 break;
1160 case ftDate_1123:
1161 packerPrintf(p, "%s", mkrfc1123(e->field.v_time));
1162 break;
1163 case ftPSCC:
1164 httpSccPackValueInto(e->field.v_pscc, p);
1165 break;
1166 case ftPExtField:
1167 packerPrintf(p, "%s", e->field.v_pefield->value);
1168 break;
1169 default:
1170 assert(0 && type); /* pack for invalid/unknown type */
cb69b4c7 1171 }
1172}
1173
1174static void
2ac76861 1175httpHeaderEntryJoinWith(HttpHeaderEntry * e, const HttpHeaderEntry * newe)
cb69b4c7 1176{
1177 field_type type;
1178 assert(e && newe);
1179 assert_eid(e->id);
1180 assert(e->id == newe->id);
1181
2ac76861 1182 debug(55, 6) ("joining entry (%p) with (%p)\n", e, newe);
cb69b4c7 1183 /* type-based join */
1184 type = Headers[e->id].type;
2ac76861 1185 switch (type) {
1186 case ftPChar:
1187 e->field.v_pchar = appShortStr(e->field.v_pchar, newe->field.v_pchar);
1188 break;
1189 case ftPSCC:
1190 httpSccJoinWith(e->field.v_pscc, newe->field.v_pscc);
1191 break;
1192 default:
1193 debug(55, 0) ("join for invalid/unknown type: id: %d, type: %d\n", e->id, type);
1194 assert(0);
cb69b4c7 1195 }
1196}
1197
1198
1199static int
2ac76861 1200httpHeaderEntryIsValid(const HttpHeaderEntry * e)
cb69b4c7 1201{
1202 assert(e);
1203 if (e->id == -1)
1204 return 0;
1205 assert_eid(e->id);
1206 /* type-based analysis */
2ac76861 1207 switch (Headers[e->id].type) {
1208 case ftInvalid:
1209 return 0;
1210 case ftInt:
1211 return e->field.v_int >= 0;
1212 case ftPChar:
1213 return e->field.v_pchar != NULL;
2ac76861 1214 case ftDate_1123:
1215 return e->field.v_time >= 0;
2ac76861 1216 case ftPSCC:
1217 return e->field.v_pscc != NULL;
2ac76861 1218 case ftPExtField:
1219 return e->field.v_pefield != NULL;
2ac76861 1220 default:
1221 assert(0); /* query for invalid/unknown type */
cb69b4c7 1222 }
2ac76861 1223 return 0; /* not reached */
cb69b4c7 1224}
1225
1226static const char *
2ac76861 1227httpHeaderEntryName(const HttpHeaderEntry * e)
cb69b4c7 1228{
1229 assert(e);
1230 assert_eid(e->id);
1231
2ac76861 1232 return (e->id == HDR_OTHER) ?
cb69b4c7 1233 e->field.v_pefield->name : Headers[e->id].name;
1234}
1235
1236/*
1237 * HttpHeaderField
1238 */
1239
1240static void
2ac76861 1241httpHeaderFieldInit(field_store * field)
cb69b4c7 1242{
1243 assert(field);
1244 memset(field, 0, sizeof(field_store));
1245}
1246
1247static field_store
1248httpHeaderFieldDup(field_type type, field_store value)
1249{
1250 /* type based duplication */
2ac76861 1251 switch (type) {
1252 case ftInt:
2ac76861 1253 case ftDate_1123:
7021844c 1254 return value;
1255 case ftPChar:
1256 return ptrField(dupShortStr(value.v_pchar));
2ac76861 1257 case ftPSCC:
7021844c 1258 return ptrField(httpSccDup(value.v_pscc));
2ac76861 1259 case ftPExtField:
7021844c 1260 return ptrField(httpHeaderExtFieldDup(value.v_pefield));
2ac76861 1261 default:
1262 assert(0); /* dup of invalid/unknown type */
cb69b4c7 1263 }
7021844c 1264 return ptrField(NULL); /* not reached */
cb69b4c7 1265}
1266
1267/*
1268 * bad value table; currently bad values are determined by field type, but this
1269 * can be changed in the future to reflect dependence on entry id if any
1270 */
1271static field_store
1272httpHeaderFieldBadValue(field_type type)
1273{
2ac76861 1274 switch (type) {
1275 case ftInt:
7021844c 1276 return intField(-1);
2ac76861 1277 case ftDate_1123:
7021844c 1278 return timeField(-1);
2ac76861 1279 case ftPChar:
1280 case ftPSCC:
1281 case ftPExtField:
7021844c 1282 return ptrField(NULL);
2ac76861 1283 case ftInvalid:
1284 default:
1285 assert(0); /* query for invalid/unknown type */
cb69b4c7 1286 }
7021844c 1287 return ptrField(NULL); /* not reached */
cb69b4c7 1288}
1289
1290/*
1291 * HttpScc (server cache control)
1292 */
1293
1294static HttpScc *
1295httpSccCreate()
1296{
7021844c 1297 HttpScc *scc = memAllocate(MEM_HTTP_SCC);
cb69b4c7 1298 scc->max_age = -1;
1299 return scc;
1300}
1301
2ac76861 1302/* creates an scc object from a 0-terminating string */
cb69b4c7 1303static HttpScc *
1304httpSccParseCreate(const char *str)
1305{
1306 HttpScc *scc = httpSccCreate();
1307 httpSccParseInit(scc, str);
1308 return scc;
1309}
1310
1311/* parses a 0-terminating string and inits scc */
1312static void
2ac76861 1313httpSccParseInit(HttpScc * scc, const char *str)
cb69b4c7 1314{
1315 const char *item;
2ac76861 1316 const char *p; /* '=' parameter */
cb69b4c7 1317 const char *pos = NULL;
1318 int type;
1319 int ilen;
1320 assert(scc && str);
1321
12cf1be2 1322 CcPasredCount++;
cb69b4c7 1323 /* iterate through comma separated list */
2ac76861 1324 while (strListGetItem(str, ',', &item, &ilen, &pos)) {
cb69b4c7 1325 /* strip '=' statements @?@ */
2ac76861 1326 if ((p = strchr(item, '=')) && (p - item < ilen))
cb69b4c7 1327 ilen = p++ - item;
1328 /* find type */
1329 type = httpHeaderIdByName(item, ilen,
1330 SccAttrs, SCC_ENUM_END, -1);
1331 if (type < 0) {
1332 debug(55, 0) ("cc: unknown cache-directive: near '%s' in '%s'\n", item, str);
1333 continue;
1334 }
1335 if (EBIT_TEST(scc->mask, type)) {
1336 debug(55, 0) ("cc: ignoring duplicate cache-directive: near '%s' in '%s'\n", item, str);
12cf1be2 1337 SccAttrs[type].stat.repCount++;
cb69b4c7 1338 continue;
1339 }
1340 /* update mask */
2ac76861 1341 EBIT_SET(scc->mask, type);
cb69b4c7 1342 /* post-processing special cases */
1343 switch (type) {
2ac76861 1344 case SCC_MAX_AGE:
1345 if (p)
1346 scc->max_age = (time_t) atoi(p);
1347 if (scc->max_age < 0) {
1348 debug(55, 0) ("scc: invalid max-age specs near '%s'\n", item);
1349 scc->max_age = -1;
1350 EBIT_CLR(scc->mask, type);
1351 }
1352 break;
1353 default:
1354 /* note that we ignore most of '=' specs @?@ */
1355 break;
cb69b4c7 1356 }
1357 }
1358 return;
1359}
1360
1361static void
2ac76861 1362httpSccDestroy(HttpScc * scc)
cb69b4c7 1363{
1364 assert(scc);
1365 memFree(MEM_HTTP_SCC, scc);
1366}
1367
1368static HttpScc *
2ac76861 1369httpSccDup(HttpScc * scc)
cb69b4c7 1370{
1371 HttpScc *dup;
1372 assert(scc);
1373 dup = httpSccCreate();
1374 dup->mask = scc->mask;
1375 dup->max_age = scc->max_age;
1376 return dup;
1377}
1378
1379static void
2ac76861 1380httpSccPackValueInto(HttpScc * scc, Packer * p)
cb69b4c7 1381{
1382 http_scc_type flag;
1383 int pcount = 0;
1384 assert(scc && p);
1385 if (scc->max_age >= 0) {
2ac76861 1386 packerPrintf(p, "max-age=%d", scc->max_age);
cb69b4c7 1387 pcount++;
1388 }
1389 for (flag = 0; flag < SCC_ENUM_END; flag++) {
1390 if (EBIT_TEST(scc->mask, flag)) {
1391 packerPrintf(p, pcount ? ", %s" : "%s", SccAttrs[flag].name);
1392 pcount++;
1393 }
1394 }
1395}
1396
1397static void
2ac76861 1398httpSccJoinWith(HttpScc * scc, HttpScc * new_scc)
cb69b4c7 1399{
1400 assert(scc && new_scc);
1401 if (scc->max_age < 0)
1402 scc->max_age = new_scc->max_age;
1403 scc->mask |= new_scc->mask;
1404}
1405
12cf1be2 1406static void
2ac76861 1407httpSccUpdateStats(const HttpScc * scc, StatHist * hist)
12cf1be2 1408{
1409 http_scc_type c;
1410 assert(scc);
1411 for (c = 0; c < SCC_ENUM_END; c++)
1412 if (EBIT_TEST(scc->mask, c))
1413 statHistCount(hist, c);
1414}
cb69b4c7 1415
1416/*
1417 * HttpHeaderExtField
1418 */
1419
1420static HttpHeaderExtField *
1421httpHeaderExtFieldCreate(const char *name, const char *value)
1422{
1423 HttpHeaderExtField *f = xcalloc(1, sizeof(HttpHeaderExtField));
1424 f->name = dupShortStr(name);
1425 f->value = dupShortStr(value);
1426 return f;
1427}
1428
1429/* parses ext field; returns fresh ext field on success and NULL on failure */
1430static HttpHeaderExtField *
1431httpHeaderExtFieldParseCreate(const char *field_start, const char *field_end)
1432{
1433 HttpHeaderExtField *f = NULL;
1434 /* note: name_start == field_start */
1435 const char *name_end = strchr(field_start, ':');
1436 const char *value_start;
1437 /* note: value_end == field_end */
1438
2ac76861 1439 if (!name_end || name_end <= field_start || name_end > field_end)
cb69b4c7 1440 return NULL;
1441
2ac76861 1442 value_start = name_end + 1; /* skip ':' */
cb69b4c7 1443 /* skip white space */
2ac76861 1444 while (value_start < field_end && isspace(*value_start))
cb69b4c7 1445 value_start++;
1446
1447 /* cut off "; parameter" from Content-Type @?@ why? */
1448 if (!strncasecmp(field_start, "Content-Type:", 13)) {
1449 const int l = strcspn(value_start, ";\t ");
1450 if (l > 0 && value_start + l < field_end)
1451 field_end = value_start + l;
1452 }
cb69b4c7 1453 f = xcalloc(1, sizeof(HttpHeaderExtField));
2ac76861 1454 f->name = dupShortBuf(field_start, name_end - field_start);
1455 f->value = dupShortBuf(value_start, field_end - value_start);
1456 debug(55, 8) ("got field: '%s: %s' (%p)\n", f->name, f->value, f);
cb69b4c7 1457 return f;
1458}
1459
1460static void
2ac76861 1461httpHeaderExtFieldDestroy(HttpHeaderExtField * f)
cb69b4c7 1462{
1463 assert(f);
1464 freeShortString(f->name);
1465 freeShortString(f->value);
1466 xfree(f);
1467}
1468
1469static HttpHeaderExtField *
2ac76861 1470httpHeaderExtFieldDup(HttpHeaderExtField * f)
cb69b4c7 1471{
1472 assert(f);
1473 return httpHeaderExtFieldCreate(f->name, f->value);
1474}
1475
cb69b4c7 1476/*
12cf1be2 1477 * Reports
cb69b4c7 1478 */
cb69b4c7 1479
1480static void
12cf1be2 1481httpHeaderFieldStatDumper(StoreEntry * sentry, int idx, double val, double size, int count)
cb69b4c7 1482{
12cf1be2 1483 const int id = (int) val;
1484 const int valid_id = id >= 0 && id < HDR_ENUM_END;
1485 const char *name = valid_id ? Headers[id].name : "INVALID";
1486 if (count || valid_id)
7021844c 1487 storeAppendPrintf(sentry, "%2d\t %-20s\t %5d\t %6.2f\n",
12cf1be2 1488 id, name, count, xdiv(count, HeaderParsedCount));
cb69b4c7 1489}
1490
12cf1be2 1491static void
1492httpHeaderCCStatDumper(StoreEntry * sentry, int idx, double val, double size, int count)
cb69b4c7 1493{
12cf1be2 1494 const int id = (int) val;
1495 const int valid_id = id >= 0 && id < SCC_ENUM_END;
1496 const char *name = valid_id ? SccAttrs[id].name : "INVALID";
1497 if (count || valid_id)
7021844c 1498 storeAppendPrintf(sentry, "%2d\t %-20s\t %5d\t %6.2f\n",
12cf1be2 1499 id, name, count, xdiv(count, CcPasredCount));
cb69b4c7 1500}
1501
cb69b4c7 1502
12cf1be2 1503static void
1504httpHeaderFldsPerHdrDumper(StoreEntry * sentry, int idx, double val, double size, int count)
cb69b4c7 1505{
12cf1be2 1506 if (count)
7021844c 1507 storeAppendPrintf(sentry, "%2d\t %5d\t %5d\t %6.2f\n",
2ac76861 1508 idx, ((int) (val + size)), count, xpercent(count, HeaderEntryParsedCount));
cb69b4c7 1509}
1510
1511
1512static void
2ac76861 1513httpHeaderStatDump(const HttpHeaderStat * hs, StoreEntry * e)
12cf1be2 1514{
1515 assert(hs && e);
1516
1517 storeAppendPrintf(e, "\n<h3>Header Stats: %s</h3>\n", hs->label);
7021844c 1518 storeAppendPrintf(e, "<h3>Field type distribution</h3>\n");
12cf1be2 1519 storeAppendPrintf(e, "%2s\t %-20s\t %5s\t %6s\n",
1520 "id", "name", "count", "#/header");
1521 statHistDump(&hs->fieldTypeDistr, e, httpHeaderFieldStatDumper);
7021844c 1522 storeAppendPrintf(e, "<h3>Cache-control directives distribution</h3>\n");
12cf1be2 1523 storeAppendPrintf(e, "%2s\t %-20s\t %5s\t %6s\n",
1524 "id", "name", "count", "#/cc_field");
1525 statHistDump(&hs->ccTypeDistr, e, httpHeaderCCStatDumper);
7021844c 1526 storeAppendPrintf(e, "<h3>Number of fields per header distribution (init size: %d)</h3>\n",
12cf1be2 1527 INIT_FIELDS_PER_HEADER);
1528 storeAppendPrintf(e, "%2s\t %-5s\t %5s\t %6s\n",
1529 "id", "#flds", "count", "%total");
1530 statHistDump(&hs->hdrUCountDistr, e, httpHeaderFldsPerHdrDumper);
cb69b4c7 1531}
1532
1533static void
2ac76861 1534shortStringStatDump(StoreEntry * e)
12cf1be2 1535{
7021844c 1536 storeAppendPrintf(e, "<h3>Short String Stats</h3>\n<p>");
1537 memPoolReport(shortStrings, e);
1538 storeAppendPrintf(e, "\n</p>\n");
12cf1be2 1539 storeAppendPrintf(e, "<br><h3>Long String Stats</h3>\n");
7021844c 1540 storeAppendPrintf(e, "alive: %3d (%5.1f KB) high-water: %3d (%5.1f KB)\n",
2ac76861 1541 longStrAliveCount, (double) longStrAliveSize / 1024.,
1542 longStrHighWaterCount, (double) longStrHighWaterSize / 1024.);
cb69b4c7 1543}
1544
12cf1be2 1545void
2ac76861 1546httpHeaderStoreReport(StoreEntry * e)
cb69b4c7 1547{
12cf1be2 1548 int i;
1549 http_hdr_type ht;
1550 assert(e);
cb69b4c7 1551
12cf1be2 1552 /* fix this (including summing for totals) for req hdrs @?@ */
2ac76861 1553 for (i = 0; i < 1 /*HttpHeaderStatCount */ ; i++) {
1554 httpHeaderStatDump(HttpHeaderStats + i, e);
12cf1be2 1555 storeAppendPrintf(e, "%s\n", "<br>");
1556 }
1557 storeAppendPrintf(e, "%s\n", "<hr size=1 noshade>");
1558 /* field stats */
1559 storeAppendPrintf(e, "<h3>Http Fields Stats (replies and requests)</h3>\n");
1560 storeAppendPrintf(e, "%2s\t %-20s\t %5s\t %6s\t %6s\n",
1561 "id", "name", "#alive", "%err", "%repeat");
1562 for (ht = 0; ht < HDR_ENUM_END; ht++) {
2ac76861 1563 field_attrs_t *f = Headers + ht;
7021844c 1564 storeAppendPrintf(e, "%2d\t %-20s\t %5d\t %6.3f\t %6.3f\n",
12cf1be2 1565 f->id, f->name, f->stat.aliveCount,
2ac76861 1566 xpercent(f->stat.errCount, f->stat.parsCount),
12cf1be2 1567 xpercent(f->stat.repCount, f->stat.parsCount));
1568 }
1569 storeAppendPrintf(e, "%s\n", "<hr size=1 noshade>");
1570 /* short strings */
1571 shortStringStatDump(e);
cb69b4c7 1572}
cb69b4c7 1573
12cf1be2 1574
1575/*
1576 * "short string" routines below are trying to recycle memory for short strings
1577 */
1578
cb69b4c7 1579static char *
1580dupShortStr(const char *str)
1581{
1582 return dupShortBuf(str, strlen(str));
1583}
1584
1585static char *
1586dupShortBuf(const char *str, size_t len)
1587{
1588 char *buf;
1589 assert(str);
cb69b4c7 1590 buf = allocShortBuf(len + 1);
1591 assert(buf);
1592 if (len)
2ac76861 1593 xmemcpy(buf, str, len); /* may not have terminating 0 */
1594 buf[len] = '\0'; /* terminate */
1595 debug(55, 9) ("dupped short buf[%d] (%p): '%s'\n", len + 1, buf, buf);
cb69b4c7 1596 return buf;
1597}
1598
1599static char *
1600appShortStr(char *str, const char *app_str)
1601{
2ac76861 1602 const size_t size = strlen(str) + strlen(app_str) + 1;
cb69b4c7 1603 char *buf = allocShortBuf(size);
1604 snprintf(buf, size, "%s, %s", str, app_str);
1605 freeShortString(str);
1606 return buf;
1607}
1608
1609static char *
1610allocShortBuf(size_t sz)
1611{
1612 char *buf = NULL;
1613 assert(shortStrings);
7021844c 1614 if (sz > shortStrSize) {
cb69b4c7 1615 buf = xmalloc(sz);
12cf1be2 1616 longStrAliveCount++;
1617 longStrAliveSize += sz;
1618 if (longStrHighWaterCount < longStrAliveCount)
1619 longStrHighWaterCount = longStrAliveCount;
1620 if (longStrHighWaterSize < longStrAliveSize)
1621 longStrHighWaterSize = longStrAliveSize;
cb69b4c7 1622 } else
7021844c 1623 buf = memPoolAlloc(shortStrings);
cb69b4c7 1624 return buf;
1625}
1626
1627static void
1628freeShortString(char *str)
1629{
1630 assert(shortStrings);
1631 if (str) {
2ac76861 1632 const size_t sz = strlen(str) + 1;
7021844c 1633 debug(55, 9) ("freeing short str of size %d (max: %d) '%s' (%p)\n", sz, shortStrSize, str, str);
1634 if (sz > shortStrSize) {
1635 debug(55, 9) ("LONG short string[%d>%d]: %s\n", sz, shortStrSize, str);
12cf1be2 1636 assert(longStrAliveCount);
cb69b4c7 1637 xfree(str);
12cf1be2 1638 longStrAliveCount--;
1639 longStrAliveSize -= sz;
cb69b4c7 1640 } else
7021844c 1641 memPoolFree(shortStrings, str);
cb69b4c7 1642 }
1643}
1644
1645/*
1646 * other routines (move these into lib if you need them somewhere else?)
1647 */
1648
1649/*
123abbe1 1650 * iterates through a 0-terminated string of items separated by 'del's.
cb69b4c7 1651 * white space around 'del' is considered to be a part of 'del'
123abbe1 1652 * like strtok, but preserves the source.
cb69b4c7 1653 *
123abbe1 1654 * returns true if next item is found.
1655 * init pos with NULL to start iteration.
cb69b4c7 1656 */
1657static int
1658strListGetItem(const char *str, char del, const char **item, int *ilen, const char **pos)
1659{
1660 size_t len;
1661 assert(str && item && pos);
1662 if (*pos)
2ac76861 1663 if (!**pos) /* end of string */
cb69b4c7 1664 return 0;
1665 else
1666 (*pos)++;
1667 else
1668 *pos = str;
1669
1670 /* skip leading ws (ltrim) */
1671 *pos += xcountws(*pos);
2ac76861 1672 *item = *pos; /* remember item's start */
cb69b4c7 1673 /* find next delimiter */
1674 *pos = strchr(*item, del);
2ac76861 1675 if (!*pos) /* last item */
cb69b4c7 1676 *pos = *item + strlen(*item);
2ac76861 1677 len = *pos - *item; /* *pos points to del or '\0' */
cb69b4c7 1678 /* rtrim */
2ac76861 1679 while (len > 0 && isspace((*item)[len - 1]))
1680 len--;
cb69b4c7 1681 if (ilen)
1682 *ilen = len;
1683 return len > 0;
1684}
1685
1686/* handy to printf prefixes of potentially very long buffers */
1687static const char *
2ac76861 1688getStringPrefix(const char *str)
1689{
cb69b4c7 1690#define SHORT_PREFIX_SIZE 256
1691 LOCAL_ARRAY(char, buf, SHORT_PREFIX_SIZE);
1692 xstrncpy(buf, str, SHORT_PREFIX_SIZE);
1693 return buf;
1694}