]> mj.ucw.cz Git - netgrind.git/blob - netgrind/http.c
c6acd6bca6b59ef1776899a14329163b78edfd45
[netgrind.git] / netgrind / http.c
1 /*
2  *      Netgrind -- HTTP Analyser
3  *
4  *      (c) 2003 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 #define 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 <netinet/in.h>
21
22 #define MAXLINE 4096
23
24 struct http_header {
25   node n;
26   byte *name, *value;
27   byte buf[1];
28 };
29
30 struct http_state {
31   enum {
32     HTTP_INIT,                  /* initialized, waiting for request */
33     HTTP_ERROR,                 /* protocol error, ignoring everything else */
34     HTTP_CUT,                   /* unexpected EOF in one direction, ignoring everything else */
35     HTTP_REQUEST,               /* parsing request */
36     HTTP_BODY_CHUNKED,          /* receiving body: chunked encoding */
37     HTTP_BODY_LENGTH,           /* receiving body: length given */
38     HTTP_BODY_INF,              /* receiving body: till EOF */
39     HTTP_RESPONSE,              /* parsing response */
40     HTTP_DONE,                  /* transaction finished, logging it */
41     HTTP_CONNECT,               /* inside CONNECT transaction */
42   } state;
43   byte *error;
44   u64 init_time;
45   uns id;
46   struct mempool *pool;
47   list tx_queue, rx_queue;
48   byte *req_line, *resp_line;
49   list req_headers, resp_headers;
50   byte line[MAXLINE];
51   uns line_len;
52   uns body_len;
53   uns body_trailer;
54   list *body_queue;
55   uns body_end_state;
56 };
57
58 static void http_open(struct flow *f, u64 when)
59 {
60   static int http_counter;
61   struct http_state *s = xmalloc_zero(sizeof(*s));
62   f->appl_data = s;
63   s->init_time = when;
64   s->id = http_counter++;
65   DBG("HTTP: %d NEW %d.%d.%d.%d:%d -> %d.%d.%d.%d:%d\n", s->id,
66       IPQUAD(f->saddr), ntohs(f->sport), IPQUAD(f->daddr), ntohs(f->dport));
67   list_init(&s->tx_queue);
68   list_init(&s->rx_queue);
69 }
70
71 static void http_close(struct flow *f, int cause, u64 when)
72 {
73   struct http_state *s = f->appl_data;
74   DBG("HTTP: %d CLOSE in state %d (cause %d)\n", s->id, s->state, cause);
75   pkt_flush_queue(&s->rx_queue);
76   pkt_flush_queue(&s->tx_queue);
77   if (s->pool)
78     mp_delete(s->pool);
79   xfree(s);
80 }
81
82 static struct http_header *http_get_line(struct http_state *s, list *l)
83 {
84   for(;;)
85     {
86       struct pkt *p = list_head(l);
87       if (!p)
88         return NULL;
89       while (p->data < p->stop)
90         {
91           uns c = *p->data++;
92           if (c == '\r')
93             ;
94           else if (c == '\n')
95             {
96               struct http_header *h = mp_alloc(s->pool, sizeof(*h) + s->line_len);
97               memcpy(h->buf, s->line, s->line_len);
98               h->buf[s->line_len] = 0;
99               h->name = h->value = NULL;
100               s->line_len = 0;
101               return h;
102             }
103           else if (s->line_len >= MAXLINE-1)
104             {
105               DBG("HTTP: Line too long!\n");
106               s->state = HTTP_ERROR;
107               return NULL;
108             }
109           else
110             s->line[s->line_len++] = c;
111         }
112       list_remove(&p->n);
113       pkt_free(p);
114     }
115 }
116
117 static int http_skip_bytes(list *l, uns *pcnt)
118 {
119   for(;;)
120     {
121       struct pkt *p = list_head(l);
122       if (!p)
123         return 0;
124       uns avail = pkt_len(p);
125       uns want = *pcnt;
126       uns go = MIN(avail, want);
127       p->data += go;
128       *pcnt -= go;
129       if (!*pcnt)
130         return 1;
131       if (!pkt_len(p))
132         {
133           list_remove(&p->n);
134           pkt_free(p);
135         }
136     }
137 }
138
139 static int http_have_input(list *l)
140 {
141   for(;;)
142     {
143       struct pkt *p = list_head(l);
144       if (!p)
145         return 0;
146       if (pkt_len(p))
147         return 1;
148       list_remove(&p->n);
149       pkt_free(p);
150     }
151 }
152
153 static void http_init_xact(struct http_state *s)
154 {
155   list_init(&s->req_headers);
156   list_init(&s->resp_headers);
157   if (s->pool)
158     mp_flush(s->pool);
159   else
160     s->pool = mp_new(4096);
161   s->req_line = s->resp_line = NULL;
162   s->line_len = 0;
163 }
164
165 static void http_parse_hdr(list *l, struct http_header *h)
166 {
167   byte *x = h->buf;
168   h->name = x;
169   while (*x && *x != ' ' && *x != '\t')
170     x++;
171   while (*x == ' ' || *x == '\t')
172     *x++ = 0;
173   h->value = x;
174   list_add_tail(l, &h->n);
175 }
176
177 static byte *http_lookup_hdr(list *l, byte *name)
178 {
179   struct http_header *h;
180   WALK_LIST(h, *l)
181     if (!strcasecmp(h->name, name))
182       return h->value;
183   return NULL;
184 }
185
186 static int http_ask_body(struct http_state *s, list *hdr)
187 {
188   byte *x;
189   if (x = http_lookup_hdr(hdr, "Transfer-Encoding:"))
190     {
191       DBG("\tBody encoding: %s\n", x);
192       if (!strcasecmp(x, "chunked"))
193         {
194           s->state = HTTP_BODY_CHUNKED;
195           s->body_len = 0;
196           s->body_trailer = 0;
197         }
198       else
199         s->state = HTTP_ERROR;
200     }
201   else if (x = http_lookup_hdr(hdr, "Content-Length:"))
202     {
203       s->body_len = atol(x);
204       DBG("\tBody length: %d\n", s->body_len);
205       s->state = HTTP_BODY_LENGTH;
206     }
207   else
208     return 0;
209   return 1;
210 }
211
212 static void http_parse_req(struct http_state *s)
213 {
214   if (!strstr(s->req_line, " HTTP/1"))
215     {
216       DBG("\tNot a HTTP/1.x request!\n");
217       s->state = HTTP_ERROR;
218     }
219   else if (http_ask_body(s, &s->req_headers))
220     ;
221   else if (!strncasecmp(s->req_line, "POST ", 4))
222     {
223       DBG("\tPOST with no request body, that smells!\n");
224       s->state = HTTP_BODY_INF;
225     }
226   else
227     {
228       DBG("\tNo request body, awaiting reply\n");
229       s->state = HTTP_RESPONSE;
230     }
231   s->body_queue = &s->tx_queue;
232   s->body_end_state = HTTP_RESPONSE;
233 }
234
235 static void http_parse_resp(struct http_state *s)
236 {
237   if (http_ask_body(s, &s->resp_headers))
238     ;
239   else if (!strncasecmp(s->req_line, "GET ", 4) && strstr(s->resp_line, " 200 "))
240     {
241       DBG("\tGET with no response body, that smells!\n");
242       s->state = HTTP_BODY_INF;
243     }
244   else
245     {
246       DBG("\tNo response body\n");
247       s->state = HTTP_DONE;
248     }
249   s->body_queue = &s->rx_queue;
250   s->body_end_state = HTTP_DONE;
251 }
252
253 static void http_input(struct flow *f, int dir, struct pkt *p)
254 {
255   struct http_state *s = f->appl_data;
256   struct http_header *h;
257   int fin_tx = (f->pipe[0].state == FLOW_FINISHED);
258   int fin_rx = (f->pipe[1].state == FLOW_FINISHED);
259
260   // DBG("dir=%d txf=%d rxf=%d len=%d\n", dir, fin_tx, fin_rx, pkt_len(p));
261   if (s->state == HTTP_ERROR || s->state == HTTP_CUT)
262     {
263       DBG("HTTP: %d DROPPING INPUT\n", s->id);
264       pkt_free(p);
265       return;
266     }
267   if (pkt_len(p))
268     list_add_tail((dir ? &s->tx_queue : &s->rx_queue), &p->n);
269   for(;;)
270     {
271       DBG("HTTP: %d STATE %d\n", s->id, s->state);
272       switch (s->state)
273         {
274         case HTTP_INIT:
275           if (fin_tx || !http_have_input(&s->tx_queue))
276             return;
277           s->state = HTTP_REQUEST;
278           http_init_xact(s);
279           break;
280         case HTTP_REQUEST:
281           if (fin_tx || fin_rx)
282             goto cut;
283           if (!(h = http_get_line(s, &s->tx_queue)))
284             return;
285           DBG("\t>> %s\n", h->buf);
286           if (!s->req_line)
287             {
288               if (!h->buf[0])
289                 goto err;
290               s->req_line = h->buf;
291             }
292           else if (h->buf[0])
293             http_parse_hdr(&s->req_headers, h);
294           else
295             http_parse_req(s);
296           break;
297         case HTTP_BODY_LENGTH:
298           if (fin_rx)
299             goto cut;
300           if (!http_skip_bytes(s->body_queue, &s->body_len))
301             return;
302           DBG("\tEnd of body\n");
303           s->state = s->body_end_state;
304           break;
305         case HTTP_BODY_CHUNKED:
306           if (fin_rx)
307             goto cut;
308           if (s->body_len)
309             {
310               if (!http_skip_bytes(s->body_queue, &s->body_len))
311                 return;
312             }
313           else if (s->body_trailer)
314             {
315               if (!(h = http_get_line(s, s->body_queue)))
316                 return;
317               if (!h->buf[0])
318                 {
319                   DBG("\tEnd of chunk-encoded body\n");
320                   s->state = s->body_end_state;
321                 }
322             }
323           else
324             {
325               if (!(h = http_get_line(s, s->body_queue)))
326                 return;
327               if (sscanf(h->buf, "%x", &s->body_len) != 1)
328                 goto err;
329               if (s->body_len)
330                 s->body_len += 2; /* extra CRLF */
331               else                /* last chunk */
332                 s->body_trailer = 1;
333             }
334           break;
335         case HTTP_BODY_INF:
336           s->body_len = ~0U;
337           http_skip_bytes(s->body_queue, &s->body_len);
338           if (fin_rx)
339             {
340               DBG("\tEnd of FIN-delimited body\n");
341               s->state = s->body_end_state;
342             }
343           else
344             return;
345           break;
346         case HTTP_RESPONSE:
347           if (fin_rx)
348             goto cut;
349           if (!(h = http_get_line(s, &s->rx_queue)))
350             return;
351           DBG("\t<< %s\n", h->buf);
352           if (!s->resp_line)
353             {
354               if (!h->buf[0])
355                 goto err;
356               s->resp_line = h->buf;
357             }
358           else if (h->buf[0])
359             http_parse_hdr(&s->resp_headers, h);
360           else
361             http_parse_resp(s);
362           break;
363         case HTTP_DONE:
364           DBG("\tTransaction finished.\n");
365           if (!strncasecmp(s->req_line, "CONNECT ", 8))
366             {
367               s->state = HTTP_CONNECT;
368               return;
369             }
370           s->state = HTTP_INIT;
371           break;
372         case HTTP_CONNECT:
373           s->body_len = ~0U;
374           http_skip_bytes(&s->rx_queue, &s->body_len);
375           s->body_len = ~0U;
376           http_skip_bytes(&s->tx_queue, &s->body_len);
377           return;
378         case HTTP_ERROR:
379         case HTTP_CUT:
380           return;
381         default:
382           ASSERT(0);
383         }
384     }
385
386  err:
387   DBG("HTTP: %d ERROR: PROTOCOL VIOLATION\n", s->id);
388   s->state = HTTP_ERROR;
389   return;
390
391  cut:
392   DBG("HTTP: %d ERROR: UNEXPECTED EOF\n", s->id);
393   s->state = HTTP_CUT;
394 }
395
396 struct appl_hooks appl_http = {
397   .open = http_open,
398   .input = http_input,
399   .close = http_close
400 };