]> mj.ucw.cz Git - netgrind.git/blob - netgrind/http.c
Let the caching flags mention conditional requests
[netgrind.git] / netgrind / http.c
1 /*
2  *      Netgrind -- HTTP Analyser
3  *
4  *      (c) 2003--2013 Martin Mares <mj@ucw.cz>
5  *
6  *      This software may be freely distributed and used according to the terms
7  *      of the GNU General Public License.
8  */
9
10 #undef LOCAL_DEBUG
11
12 #include "lib/lib.h"
13 #include "lib/pools.h"
14 #include "netgrind/pkt.h"
15 #include "netgrind/netgrind.h"
16
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <alloca.h>
21 #include <time.h>
22 #include <netinet/in.h>
23
24 #define MAXLINE 4096
25
26 struct http_header {
27   node n;
28   byte *name, *value;
29   byte buf[1];
30 };
31
32 struct http_state {
33   struct flow *flow;
34   enum {
35     HTTP_IDLE,                  /* initialized, waiting for request */
36     HTTP_ERROR,                 /* protocol error, ignoring everything else */
37     HTTP_CUT,                   /* unexpected EOF in one direction, ignoring everything else */
38     HTTP_REQUEST,               /* parsing request */
39     HTTP_BODY_CHUNKED,          /* receiving body: chunked encoding */
40     HTTP_BODY_LENGTH,           /* receiving body: length given */
41     HTTP_BODY_INF,              /* receiving body: till EOF */
42     HTTP_RESPONSE,              /* parsing response */
43     HTTP_DONE,                  /* transaction finished, logging it */
44     HTTP_CONNECT,               /* inside CONNECT transaction */
45   } state;
46   byte *error;
47   u64 req_start_time, resp_start_time;
48   uns conn_id;
49   struct mempool *pool;
50   list tx_queue, rx_queue;
51   uns xact_id;
52   byte *req_line, *resp_line;
53   list req_headers, resp_headers;
54   byte line[MAXLINE];
55   uns line_len;
56   uns body_len;
57   uns body_trailer;
58   list *body_queue;
59   uns body_end_state;
60   uns body_total_size;
61   uns req_counter;
62   FILE *log_file;
63 };
64
65 char *http_log_dir;
66
67 static uns http_conn_counter;
68 static uns http_xact_counter;
69
70 static void http_open(struct flow *f, u64 when)
71 {
72   struct http_state *s = xmalloc_zero(sizeof(*s));
73   s->flow = f;
74   f->appl_data = s;
75   s->conn_id = http_conn_counter++;
76   DBG("HTTP: %d NEW %d.%d.%d.%d:%d -> %d.%d.%d.%d:%d\n", s->conn_id,
77       IPQUAD(f->saddr), ntohs(f->sport), IPQUAD(f->daddr), ntohs(f->dport));
78   list_init(&s->tx_queue);
79   list_init(&s->rx_queue);
80   s->req_start_time = when;
81 }
82
83 static byte *http_lookup_hdr(list *l, byte *name)
84 {
85   struct http_header *h;
86   WALK_LIST(h, *l)
87     if (!strcasecmp(h->name, name))
88       return h->value;
89   return NULL;
90 }
91
92 static uns find_token(byte *hay, byte *needle)
93 {
94   if (!hay)
95     return 0;
96   while (*hay)
97     {
98       if (*hay == ' ' || *hay == '\t' || *hay == ',')
99         hay++;
100       else
101         {
102           byte *h = hay;
103           while (*hay && *hay != ',' && *hay != ' ' && *hay != '\t')
104             hay++;
105           uns old = *hay;
106           *hay = 0;
107           uns found = !strcasecmp(h, needle);
108           *hay = old;
109           if (found)
110             return 1;
111         }
112     }
113   return 0;
114 }
115
116 static byte *find_token_val(byte *hay, byte *needle)
117 {
118   if (!hay)
119     return NULL;
120   while (*hay)
121     {
122       if (*hay == ' ' || *hay == '\t' || *hay == ',')
123         hay++;
124       else
125         {
126           byte *h = hay;
127           while (*hay && *hay != ',' && *hay != ' ' && *hay != '\t' && *hay != '=')
128             hay++;
129           if (*hay == '=')
130             {
131               uns old = *hay;
132               *hay = 0;
133               uns found = !strcasecmp(h, needle);
134               *hay = old;
135               if (found)
136                 return hay + 1;
137             }
138           while (*hay && *hay != ',' && *hay != ' ' && *hay != '\t')
139             hay++;
140         }
141     }
142   return NULL;
143 }
144
145 /* Logging */
146
147 static void http_log_start(struct http_state *s)
148 {
149   if (!http_log_dir)
150     return;
151
152   char name[256], stamp[TIMESTAMP_LEN];
153   struct flow *f = s->flow;
154
155   sprintf(name, "%s/%06u-%d.%d.%d.%d:%d-%d.%d.%d.%d:%d", http_log_dir, s->xact_id,
156     IPQUAD(f->saddr), ntohs(f->sport), IPQUAD(f->daddr), ntohs(f->dport));
157   if (!(s->log_file = fopen(name, "w")))
158     die("Unable to create %s: %m", name);
159
160   format_timestamp(stamp, s->req_start_time);
161   fprintf(s->log_file, "; [%s] From %d.%d.%d.%d:%d to %d.%d.%d.%d:%d (req %u)\n",
162     stamp, IPQUAD(f->saddr), ntohs(f->sport), IPQUAD(f->daddr), ntohs(f->dport),
163     s->req_counter + 1);
164 }
165
166 static void http_log_end(struct http_state *s)
167 {
168   if (!s->log_file)
169     return;
170   fclose(s->log_file);
171   s->log_file = NULL;
172 }
173
174 static void http_log_req_line(struct http_state *s, byte *line)
175 {
176   if (s->log_file)
177     fprintf(s->log_file, "> %s\n", line);
178 }
179
180 static void http_log_resp_line(struct http_state *s, byte *line)
181 {
182   if (s->log_file)
183     fprintf(s->log_file, "< %s\n", line);
184 }
185
186 static void http_log_body(struct http_state *s, byte *data, uns len)
187 {
188   if (s->log_file)
189     fwrite(data, len, 1, s->log_file);
190 }
191
192 /* Date parsing */
193
194 static int
195 validate_time(byte *a, byte *p)
196 {
197   while (*p && *a)
198     {
199       switch (*p)
200         {
201         case '.':
202           break;
203         case '$':
204           if (*a == ' ')
205             break;
206           /* Fall-thru */
207         case '#':
208           if (*a < '0' || *a > '9')
209             return 0;
210           break;
211         case 'a':
212           if (*a < 'a' || *a > 'z')
213             return 0;
214           break;
215         case 'A':
216           if (*a < 'A' || *a > 'Z')
217             return 0;
218           break;
219         case '@':
220           if ((*a < 'a' && *a > 'z') && (*a < 'A' && *a > 'Z'))
221             return 0;
222           break;
223         case '*':
224           return 1;
225         case '_':
226           if (*a != ' ' && *a != '-')
227             return 0;
228           break;
229         default:
230           if (*p != *a)
231             return 0;
232         }
233       p++;
234       a++;
235     }
236   return (*p == *a || *p == '*');
237 }
238
239 static int
240 twodig(byte *p)
241 {
242   if (p[0] == ' ')
243     return p[1] - '0';
244   else
245     return (p[0] - '0')*10 + p[1] - '0';
246 }
247
248 static int
249 fourdig(byte *p)
250 {
251   return twodig(p)*100 + twodig(p+2);
252 }
253
254 static byte short_months[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
255
256 static int
257 find_month(byte *p)
258 {
259   byte *z = short_months;
260   int m = 0;
261
262   while (*z)
263     {
264       m++;
265       if (p[0] == z[0] && p[1] == z[1] && p[2] == z[2])
266         return m;
267       z += 3;
268     }
269   return 0;
270 }
271
272 static s64 http_parse_date(char *date)
273 {
274   char *p = date;
275   char *q;
276   struct tm tm;
277   int m, y;
278
279   if (!date)
280     return -1;
281
282   bzero(&tm, sizeof(tm));
283   if (validate_time(p, "Aaa, ##_Aaa_#### ##:##:##*"))
284     {                                   /* RFC 822/1123 */
285       tm.tm_mday = twodig(p+5);
286       m = find_month(p+8);
287       if (!m)
288         goto unk;
289       tm.tm_mon = m - 1;
290       y = fourdig(p+12);
291       if (y < 1970)
292         goto unk;
293       tm.tm_year = y - 1900;
294       tm.tm_hour = twodig(p+17);
295       tm.tm_min = twodig(p+20);
296       tm.tm_sec = twodig(p+23);
297       goto ook;
298     }
299   if (q = strchr(p, ','))
300     {
301       int flag = 0;
302       if (validate_time(q, ", ##-Aaa-## ##:##:## GMT")) /* RFC 850 */
303         flag = 1;
304       else if (validate_time(q, ", #-Aaa-## ##:##:## GMT"))     /* Incorrectly implemented RFC 850 */
305         flag = 2;
306       if (flag)
307         {
308           tm.tm_mday = twodig(q+2);
309           if (flag == 2)
310             q--;
311           m = find_month(q+5);
312           if (!m)
313             goto unk;
314           tm.tm_mon = m - 1;
315           tm.tm_year = twodig(q+9);
316           if (tm.tm_year < 76)
317             tm.tm_year += 100;
318           tm.tm_hour = twodig(q+12);
319           tm.tm_min = twodig(q+15);
320           tm.tm_sec = twodig(q+18);
321           goto ook;
322         }
323     }
324   if (validate_time(p, "Aaa Aaa $# ##:##:## ####"))
325     {                                   /* ANSI C asctime() */
326       m = find_month(p+4);
327       if (!m)
328         goto unk;
329       tm.tm_mon = m - 1;
330       tm.tm_mday = twodig(p+8);
331       tm.tm_hour = twodig(p+11);
332       tm.tm_min = twodig(p+14);
333       tm.tm_sec = twodig(p+17);
334       y = fourdig(p+20);
335       if (y < 1980)
336         goto unk;
337       tm.tm_year = y - 1900;
338       goto ook;
339     }
340   if (!strcmp(p, "0") || !strcmp(p, "-1"))
341     return 0;                           /* Porcine hacks */
342
343 unk:
344   fprintf(stderr, "Unable to parse date `%s'\n", date);
345   return 0;
346
347 ook:
348   m = timegm(&tm);
349   if (m == (time_t) -1)
350     goto unk;
351   return (u64) m * 1000000;
352 }
353
354 static void http_cache_report(struct http_state *s, char *buf)
355 {
356   byte *rq_pragma = http_lookup_hdr(&s->req_headers, "Pragma:");
357   byte *rp_pragma = http_lookup_hdr(&s->resp_headers, "Pragma:");
358   byte *rq_cc = http_lookup_hdr(&s->req_headers, "Cache-control:");
359   byte *rp_cc = http_lookup_hdr(&s->resp_headers, "Cache-control:");
360   byte *rp_vary = http_lookup_hdr(&s->resp_headers, "Vary:");
361   byte *rp_cache = http_lookup_hdr(&s->resp_headers, "X-Cache:");
362   s64 rp_expires = http_parse_date(http_lookup_hdr(&s->resp_headers, "Expires:"));
363   s64 rp_date = http_parse_date(http_lookup_hdr(&s->resp_headers, "Date:"));
364   s64 rp_time = rp_date > 0 ? rp_date : s->resp_line ? (s64) s->resp_start_time : (s64) s->req_start_time;
365
366   // Cache control in request
367   if (find_token(rq_pragma, "no-cache") || find_token(rq_cc, "no-cache"))
368     buf[0] = 'N';
369   else if (find_token(rq_cc, "max-age=0") || find_token(rq_cc, "must-revalidate"))
370     buf[0] = 'R';
371   else if (http_lookup_hdr(&s->req_headers, "If-Match:") || http_lookup_hdr(&s->req_headers, "If-Modified-Since:"))
372     buf[0] = 'c';
373   else
374     buf[0] = '.';
375
376   // HTTP/1.0 cache control in reply
377   uns expired10 = (rp_expires > 0 && rp_expires <= rp_time);
378   uns nocache10 = find_token(rp_pragma, "no-cache");
379
380   // Expiration of reply
381   byte *rp_maxage_arg = find_token_val(rp_cc, "max-age");
382   s64 rp_maxage;
383   if (rp_maxage_arg)
384     rp_maxage = (s64) atoi(rp_maxage_arg) * 1000000;
385   else if (expired10)
386     rp_maxage = 0;
387   else if (rp_expires >= rp_time)
388     rp_maxage = rp_expires - rp_time;
389   else
390     rp_maxage = -1;
391 #if 0
392   if (s->log_file)
393     fprintf(s->log_file, "; rp_expires=%Ld rp_time=%Ld rp_maxage=%Ld expired10=%d nocache10=%d\n", rp_expires, rp_time, rp_maxage, expired10, nocache10);
394 #endif
395
396   // Cache control in reply
397   if (nocache10 || find_token(rp_cc, "no-cache"))
398     buf[1] = 'N';
399   else if (find_token(rp_cc, "private"))
400     buf[1] = 'P';
401   else if (find_token(rp_cc, "no-store"))
402     buf[1] = 'S';
403   else if (expired10 || !rp_maxage)
404     buf[1] = 'X';
405   else if (rp_vary)
406     buf[1] = 'V';
407   else if (find_token(rp_cc, "must-revalidate"))
408     buf[1] = 'R';
409   else if (rp_maxage > 0 && rp_maxage < (s64) 300 * 1000000)
410     buf[1] = 'E';
411   else if (rp_maxage > 0)
412     buf[1] = 'L';
413   else
414     buf[1] = '.';
415
416   // Do HTTP/1.1 and HTTP/1.0 behaviour match?
417   if (buf[1] != '.' && buf[1] != 'E' && buf[1] != 'L' && !expired10 && !nocache10)
418     buf[1] |= 0x20;             // Lowercase
419
420   // Validators in reply
421   byte *rp_etag = http_lookup_hdr(&s->resp_headers, "ETag:");
422   s64 rp_lastmod = http_parse_date(http_lookup_hdr(&s->resp_headers, "Last-Modified:"));
423   if (rp_etag)
424     {
425       if (rp_etag[0] == 'W' && rp_etag[1] == '/')
426         buf[2] = 'W';
427       else
428         buf[2] = 'E';
429     }
430   else if (rp_lastmod > 0)
431     buf[2] = 'L';
432   else
433     buf[2] = '.';
434
435   // Is there cache status in reply?
436   if (!rp_cache)
437     buf[3] = '.';
438   else if (!strncmp(rp_cache, "HIT", 3))
439     buf[3] = '+';
440   else if (!strncmp(rp_cache, "MISS", 4))
441     buf[3] = '-';
442   else
443     buf[3] = '?';
444
445   buf[4] = 0;
446 }
447
448 static void http_report(struct flow *f, struct http_state *s, u64 when, byte *reason)
449 {
450   byte *method, *url, *x, *y, *stat;
451
452   if (!(method = s->req_line))
453     {
454       http_log_end(s);
455       return;
456     }
457
458   /* Analyse request line */
459   url = method;
460   while (*url && *url != ' ')
461     url++;
462   while (*url == ' ')
463     *url++ = 0;
464   x = url;
465   while (*x != ' ')
466     x++;
467   *x = 0;
468
469   /* Analyse response line */
470   if (stat = s->resp_line)
471     {
472       while (*stat && *stat != ' ')
473         stat++;
474       while (*stat == ' ')
475         stat++;
476       x = stat;
477       while (*x && *x != ' ')
478         x++;
479       *x = 0;
480     }
481   else
482     stat = "";
483   if (!reason)
484     reason = stat[0] ? stat : (byte*)"???";
485
486   /* Reconstruct full URL */
487   if (!strstr(url, "://") && strcasecmp(method, "CONNECT"))
488     {
489       if (!(x = http_lookup_hdr(&s->req_headers, "Host:")))
490         x = "???";
491       y = url;
492       url = alloca(7 + strlen(x) + strlen(y) + 1);
493       sprintf(url, "http://%s%s", x, y);
494     }
495   char *ffor = http_lookup_hdr(&s->req_headers, "X-Forwarded-For:");
496
497   /* Find out cacheability */
498   char cache_flags[16];
499   http_cache_report(s, cache_flags);
500
501   /* Format log message */
502   byte stamp[TIMESTAMP_LEN], src[22], dst[22];
503   sprintf(src, "%d.%d.%d.%d:%d", IPQUAD(f->saddr), ntohs(f->sport));
504   sprintf(dst, "%d.%d.%d.%d:%d", IPQUAD(f->daddr), ntohs(f->dport));
505   format_timestamp(stamp, s->req_start_time);
506   u64 ttotal = when - s->req_start_time;
507   u64 tresp = (s->resp_line ? (s->resp_start_time - s->req_start_time) : 0);
508   byte *ctype = (http_lookup_hdr(&s->resp_headers, "Content-type:") ? : http_lookup_hdr(&s->req_headers, "Content-type:")) ? : (byte*)"-";
509   byte *sep;
510   if (sep = strchr(ctype, ';'))
511     *sep = 0;
512   if (!s->xact_id)
513     printf("# id   timestamp               source                destination           forwarded-for   res cach que   length total time  wait time ctype      method URL\n");
514          /* 000000 2003-06-06 22:53:38.642 81.27.194.19:1175     205.217.153.53:80     123.123.123.123 200 ....   0    14030      0.957      0.444 text/plain GET http://... */
515   printf("%06u %s %-21s %-21s %-15s %-3s %s %3d %8d %6d.%03d %6d.%03d %-12s %s %s\n",
516          s->xact_id, stamp, src, dst, (ffor ? : "-"), reason,
517          cache_flags,
518          s->req_counter,
519          s->body_total_size,
520          (uns)(ttotal/1000000), (uns)(ttotal%1000000)/1000,
521          (uns)(tresp/1000000), (uns)(tresp%1000000)/1000,
522          ctype, method, url);
523
524   http_log_end(s);
525   s->req_counter++;
526 }
527
528 static void http_close(struct flow *f, int cause, u64 when)
529 {
530   struct http_state *s = f->appl_data;
531   DBG("HTTP: %d CLOSE in state %d (cause %d)\n", s->conn_id, s->state, cause);
532   if (cause != CAUSE_CLOSE)
533     {
534       if (s->state != HTTP_IDLE)
535         {
536           byte buf[16];
537           sprintf(buf, "T%s", flow_cause_names_short[cause]);
538           http_report(f, s, when, buf);
539         }
540     }
541   else
542     switch (s->state)
543       {
544       case HTTP_ERROR:
545         http_report(f, s, when, "ERR");
546         break;
547       case HTTP_CUT:
548         http_report(f, s, when, "CUT");
549         break;
550       case HTTP_CONNECT:
551         http_report(f, s, when, "FIN");
552         break;
553       default: ;
554       }
555   http_log_end(s);
556   pkt_flush_queue(&s->rx_queue);
557   pkt_flush_queue(&s->tx_queue);
558   if (s->pool)
559     mp_delete(s->pool);
560   xfree(s);
561 }
562
563 static struct http_header *http_get_line(struct http_state *s, list *l)
564 {
565   for(;;)
566     {
567       struct pkt *p = list_head(l);
568       if (!p)
569         return NULL;
570       while (p->data < p->stop)
571         {
572           uns c = *p->data++;
573           if (c == '\r')
574             ;
575           else if (c == '\n')
576             {
577               struct http_header *h = mp_alloc(s->pool, sizeof(*h) + s->line_len);
578               memcpy(h->buf, s->line, s->line_len);
579               h->buf[s->line_len] = 0;
580               h->name = h->value = NULL;
581               s->line_len = 0;
582               return h;
583             }
584           else if (s->line_len >= MAXLINE-1)
585             {
586               DBG("HTTP: Line too long!\n");
587               s->state = HTTP_ERROR;
588               return NULL;
589             }
590           else
591             s->line[s->line_len++] = c;
592         }
593       list_remove(&p->n);
594       pkt_free(p);
595     }
596 }
597
598 static int http_skip_body_bytes(struct http_state *s)
599 {
600   for(;;)
601     {
602       struct pkt *p = list_head(s->body_queue);
603       if (!p)
604         return 0;
605       uns avail = pkt_len(p);
606       uns want = s->body_len;
607       uns go = MIN(avail, want);
608       http_log_body(s, p->data, go);
609       p->data += go;
610       s->body_len -= go;
611       s->body_total_size += go;
612       if (!s->body_len)
613         return 1;
614       if (!pkt_len(p))
615         {
616           list_remove(&p->n);
617           pkt_free(p);
618         }
619     }
620 }
621
622 static int http_have_input(list *l)
623 {
624   for(;;)
625     {
626       struct pkt *p = list_head(l);
627       if (!p)
628         return 0;
629       if (pkt_len(p))
630         return 1;
631       list_remove(&p->n);
632       pkt_free(p);
633     }
634 }
635
636 static void http_init_xact(struct http_state *s)
637 {
638   list_init(&s->req_headers);
639   list_init(&s->resp_headers);
640   if (s->pool)
641     mp_flush(s->pool);
642   else
643     s->pool = mp_new(4096);
644   s->req_line = s->resp_line = NULL;
645   s->line_len = 0;
646   s->body_total_size = 0;
647   s->xact_id = http_xact_counter++;
648
649   http_log_start(s);
650 }
651
652 static void http_parse_hdr(list *l, struct http_header *h)
653 {
654   byte *x = h->buf;
655   h->name = x;
656   while (*x && *x != ' ' && *x != '\t')
657     x++;
658   while (*x == ' ' || *x == '\t')
659     *x++ = 0;
660   h->value = x;
661   list_add_tail(l, &h->n);
662 }
663
664 static int http_ask_body(struct http_state *s, list *hdr)
665 {
666   byte *x;
667   if (x = http_lookup_hdr(hdr, "Transfer-Encoding:"))
668     {
669       DBG("\tBody encoding: %s\n", x);
670       if (!strcasecmp(x, "chunked"))
671         {
672           s->state = HTTP_BODY_CHUNKED;
673           s->body_len = 0;
674           s->body_trailer = 0;
675         }
676       else
677         s->state = HTTP_ERROR;
678     }
679   else if (x = http_lookup_hdr(hdr, "Content-Length:"))
680     {
681       s->body_len = atol(x);
682       DBG("\tBody length: %d\n", s->body_len);
683       s->state = HTTP_BODY_LENGTH;
684     }
685   else
686     return 0;
687   return 1;
688 }
689
690 static void http_parse_req(struct http_state *s)
691 {
692   if (!strstr(s->req_line, " HTTP/1"))
693     {
694       DBG("\tNot a HTTP/1.x request!\n");
695       s->state = HTTP_ERROR;
696     }
697   else if (http_ask_body(s, &s->req_headers))
698     ;
699   else if (!strncasecmp(s->req_line, "POST ", 4))
700     {
701       DBG("\tPOST with no request body, that smells!\n");
702       s->state = HTTP_BODY_INF;
703     }
704   else
705     {
706       DBG("\tNo request body, awaiting reply\n");
707       s->state = HTTP_RESPONSE;
708     }
709   s->body_queue = &s->tx_queue;
710   s->body_end_state = HTTP_RESPONSE;
711 }
712
713 static void http_parse_resp(struct http_state *s)
714 {
715   if (!strncasecmp(s->req_line, "HEAD ", 5))
716     {
717       DBG("\tHEAD has no body :)\n");
718       s->state = HTTP_DONE;
719     }
720   else if (http_ask_body(s, &s->resp_headers))
721     ;
722   else if (!strncasecmp(s->req_line, "GET ", 4) && strstr(s->resp_line, " 200 "))
723     {
724       DBG("\tGET with no response body, that smells!\n");
725       s->state = HTTP_BODY_INF;
726     }
727   else
728     {
729       DBG("\tNo response body\n");
730       s->state = HTTP_DONE;
731     }
732   s->body_queue = &s->rx_queue;
733   s->body_end_state = HTTP_DONE;
734 }
735
736 static void http_input(struct flow *f, int dir, struct pkt *p)
737 {
738   struct http_state *s = f->appl_data;
739   struct http_header *h;
740   int fin_tx = (f->pipe[0].state == FLOW_FINISHED);
741   int fin_rx = (f->pipe[1].state == FLOW_FINISHED);
742
743   // DBG("dir=%d txf=%d rxf=%d len=%d\n", dir, fin_tx, fin_rx, pkt_len(p));
744   if (s->state == HTTP_ERROR || s->state == HTTP_CUT)
745     {
746       DBG("HTTP: %d DROPPING INPUT\n", s->conn_id);
747       pkt_free(p);
748       return;
749     }
750   if (pkt_len(p))
751     list_add_tail((dir ? &s->tx_queue : &s->rx_queue), &p->n);
752   for(;;)
753     {
754       DBG("HTTP: %d STATE %d\n", s->conn_id, s->state);
755       switch (s->state)
756         {
757         case HTTP_IDLE:
758           if (fin_tx || !http_have_input(&s->tx_queue))
759             return;
760           s->state = HTTP_REQUEST;
761           if (!s->req_start_time)
762             s->req_start_time = p->timestamp;
763           http_init_xact(s);
764           break;
765         case HTTP_REQUEST:
766           if (fin_tx || fin_rx)
767             goto cut;
768           if (!(h = http_get_line(s, &s->tx_queue)))
769             return;
770           DBG("\t>> %s\n", h->buf);
771           http_log_req_line(s, h->buf);
772           if (!s->req_line)
773             {
774               if (!h->buf[0])
775                 goto err;
776               s->req_line = h->buf;
777             }
778           else if (h->buf[0])
779             http_parse_hdr(&s->req_headers, h);
780           else
781             http_parse_req(s);
782           break;
783         case HTTP_BODY_LENGTH:
784           if (fin_rx)
785             goto cut;
786           if (!http_skip_body_bytes(s))
787             return;
788           DBG("\tEnd of body\n");
789           s->state = s->body_end_state;
790           break;
791         case HTTP_BODY_CHUNKED:
792           if (fin_rx)
793             goto cut;
794           if (s->body_len)
795             {
796               if (!http_skip_body_bytes(s))
797                 return;
798             }
799           else if (s->body_trailer)
800             {
801               if (!(h = http_get_line(s, s->body_queue)))
802                 return;
803               if (!h->buf[0])
804                 {
805                   DBG("\tEnd of chunk-encoded body\n");
806                   s->state = s->body_end_state;
807                 }
808             }
809           else
810             {
811               if (!(h = http_get_line(s, s->body_queue)))
812                 return;
813               if (sscanf(h->buf, "%x", &s->body_len) != 1)
814                 goto err;
815               if (s->body_len)
816                 s->body_len += 2; /* extra CRLF */
817               else                /* last chunk */
818                 s->body_trailer = 1;
819             }
820           break;
821         case HTTP_BODY_INF:
822           s->body_len = ~0U;
823           http_skip_body_bytes(s);
824           if (fin_rx)
825             {
826               DBG("\tEnd of FIN-delimited body\n");
827               s->state = s->body_end_state;
828             }
829           else
830             return;
831           break;
832         case HTTP_RESPONSE:
833           if (fin_rx)
834             goto cut;
835           if (!(h = http_get_line(s, &s->rx_queue)))
836             return;
837           DBG("\t<< %s\n", h->buf);
838           http_log_resp_line(s, h->buf);
839           if (!s->resp_line)
840             {
841               if (!h->buf[0])
842                 goto err;
843               s->resp_line = h->buf;
844               s->resp_start_time = p->timestamp;
845             }
846           else if (h->buf[0])
847             http_parse_hdr(&s->resp_headers, h);
848           else
849             http_parse_resp(s);
850           break;
851         case HTTP_DONE:
852           DBG("\tTransaction finished.\n");
853           if (!strncasecmp(s->req_line, "CONNECT ", 8))
854             {
855               s->state = HTTP_CONNECT;
856               return;
857             }
858           http_report(f, s, p->timestamp, NULL);
859           s->state = HTTP_IDLE;
860           s->req_start_time = 0;
861           break;
862         case HTTP_CONNECT:
863           s->body_len = ~0U;
864           s->body_queue = &s->rx_queue;
865           http_skip_body_bytes(s);
866           s->body_len = ~0U;
867           s->body_queue = &s->tx_queue;
868           http_skip_body_bytes(s);
869           return;
870         case HTTP_ERROR:
871         case HTTP_CUT:
872           return;
873         default:
874           ASSERT(0);
875         }
876     }
877
878  err:
879   DBG("HTTP: %d ERROR: PROTOCOL VIOLATION\n", s->conn_id);
880   s->state = HTTP_ERROR;
881   return;
882
883  cut:
884   DBG("HTTP: %d ERROR: UNEXPECTED EOF\n", s->conn_id);
885   s->state = HTTP_CUT;
886 }
887
888 struct appl_hooks appl_http = {
889   .open = http_open,
890   .input = http_input,
891   .close = http_close
892 };