--- /dev/null
+/*
+ * Netgrind -- HTTP Analyser
+ *
+ * (c) 2003 Martin Mares <mj@ucw.cz>
+ *
+ * This software may be freely distributed and used according to the terms
+ * of the GNU General Public License.
+ */
+
+#define LOCAL_DEBUG
+
+#include "lib/lib.h"
+#include "lib/pools.h"
+#include "netgrind/pkt.h"
+#include "netgrind/netgrind.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <netinet/in.h>
+
+#define MAXLINE 4096
+
+struct http_header {
+ node n;
+ byte *name, *value;
+ byte buf[1];
+};
+
+struct http_state {
+ enum {
+ HTTP_INIT, /* initialized, waiting for request */
+ HTTP_ERROR, /* protocol error, ignoring everything else */
+ HTTP_CUT, /* unexpected EOF in one direction, ignoring everything else */
+ HTTP_REQUEST, /* parsing request */
+ HTTP_BODY_CHUNKED, /* receiving body: chunked encoding */
+ HTTP_BODY_LENGTH, /* receiving body: length given */
+ HTTP_BODY_INF, /* receiving body: till EOF */
+ HTTP_RESPONSE, /* parsing response */
+ HTTP_DONE, /* transaction finished, logging it */
+ HTTP_CONNECT, /* inside CONNECT transaction */
+ } state;
+ byte *error;
+ u64 init_time;
+ uns id;
+ struct mempool *pool;
+ list tx_queue, rx_queue;
+ byte *req_line, *resp_line;
+ list req_headers, resp_headers;
+ byte line[MAXLINE];
+ uns line_len;
+ uns body_len;
+ uns body_trailer;
+ list *body_queue;
+ uns body_end_state;
+};
+
+static void http_open(struct flow *f, u64 when)
+{
+ static int http_counter;
+ struct http_state *s = xmalloc_zero(sizeof(*s));
+ f->appl_data = s;
+ s->init_time = when;
+ s->id = http_counter++;
+ DBG("HTTP: %d NEW %d.%d.%d.%d:%d -> %d.%d.%d.%d:%d\n", s->id,
+ IPQUAD(f->saddr), ntohs(f->sport), IPQUAD(f->daddr), ntohs(f->dport));
+ list_init(&s->tx_queue);
+ list_init(&s->rx_queue);
+}
+
+static void http_close(struct flow *f, int cause, u64 when)
+{
+ struct http_state *s = f->appl_data;
+ DBG("HTTP: %d CLOSE in state %d (cause %d)\n", s->id, s->state, cause);
+ pkt_flush_queue(&s->rx_queue);
+ pkt_flush_queue(&s->tx_queue);
+ if (s->pool)
+ mp_delete(s->pool);
+ xfree(s);
+}
+
+static struct http_header *http_get_line(struct http_state *s, list *l)
+{
+ for(;;)
+ {
+ struct pkt *p = list_head(l);
+ if (!p)
+ return NULL;
+ while (p->data < p->stop)
+ {
+ uns c = *p->data++;
+ if (c == '\r')
+ ;
+ else if (c == '\n')
+ {
+ struct http_header *h = mp_alloc(s->pool, sizeof(*h) + s->line_len);
+ memcpy(h->buf, s->line, s->line_len);
+ h->buf[s->line_len] = 0;
+ h->name = h->value = NULL;
+ s->line_len = 0;
+ return h;
+ }
+ else if (s->line_len >= MAXLINE-1)
+ {
+ DBG("HTTP: Line too long!\n");
+ s->state = HTTP_ERROR;
+ return NULL;
+ }
+ else
+ s->line[s->line_len++] = c;
+ }
+ list_remove(&p->n);
+ pkt_free(p);
+ }
+}
+
+static int http_skip_bytes(list *l, uns *pcnt)
+{
+ for(;;)
+ {
+ struct pkt *p = list_head(l);
+ if (!p)
+ return 0;
+ uns avail = pkt_len(p);
+ uns want = *pcnt;
+ uns go = MIN(avail, want);
+ p->data += go;
+ *pcnt -= go;
+ if (!*pcnt)
+ return 1;
+ if (!pkt_len(p))
+ {
+ list_remove(&p->n);
+ pkt_free(p);
+ }
+ }
+}
+
+static int http_have_input(list *l)
+{
+ for(;;)
+ {
+ struct pkt *p = list_head(l);
+ if (!p)
+ return 0;
+ if (pkt_len(p))
+ return 1;
+ list_remove(&p->n);
+ pkt_free(p);
+ }
+}
+
+static void http_init_xact(struct http_state *s)
+{
+ list_init(&s->req_headers);
+ list_init(&s->resp_headers);
+ if (s->pool)
+ mp_flush(s->pool);
+ else
+ s->pool = mp_new(4096);
+ s->req_line = s->resp_line = NULL;
+ s->line_len = 0;
+}
+
+static void http_parse_hdr(list *l, struct http_header *h)
+{
+ byte *x = h->buf;
+ h->name = x;
+ while (*x && *x != ' ' && *x != '\t')
+ x++;
+ while (*x == ' ' || *x == '\t')
+ *x++ = 0;
+ h->value = x;
+ list_add_tail(l, &h->n);
+}
+
+static byte *http_lookup_hdr(list *l, byte *name)
+{
+ struct http_header *h;
+ WALK_LIST(h, *l)
+ if (!strcasecmp(h->name, name))
+ return h->value;
+ return NULL;
+}
+
+static int http_ask_body(struct http_state *s, list *hdr)
+{
+ byte *x;
+ if (x = http_lookup_hdr(hdr, "Transfer-Encoding:"))
+ {
+ DBG("\tBody encoding: %s\n", x);
+ if (!strcasecmp(x, "chunked"))
+ {
+ s->state = HTTP_BODY_CHUNKED;
+ s->body_len = 0;
+ s->body_trailer = 0;
+ }
+ else
+ s->state = HTTP_ERROR;
+ }
+ else if (x = http_lookup_hdr(hdr, "Content-Length:"))
+ {
+ s->body_len = atol(x);
+ DBG("\tBody length: %d\n", s->body_len);
+ s->state = HTTP_BODY_LENGTH;
+ }
+ else
+ return 0;
+ return 1;
+}
+
+static void http_parse_req(struct http_state *s)
+{
+ if (!strstr(s->req_line, " HTTP/1"))
+ {
+ DBG("\tNot a HTTP/1.x request!\n");
+ s->state = HTTP_ERROR;
+ }
+ else if (http_ask_body(s, &s->req_headers))
+ ;
+ else if (!strncasecmp(s->req_line, "POST ", 4))
+ {
+ DBG("\tPOST with no request body, that smells!\n");
+ s->state = HTTP_BODY_INF;
+ }
+ else
+ {
+ DBG("\tNo request body, awaiting reply\n");
+ s->state = HTTP_RESPONSE;
+ }
+ s->body_queue = &s->tx_queue;
+ s->body_end_state = HTTP_RESPONSE;
+}
+
+static void http_parse_resp(struct http_state *s)
+{
+ if (http_ask_body(s, &s->resp_headers))
+ ;
+ else if (!strncasecmp(s->req_line, "GET ", 4) && strstr(s->resp_line, " 200 "))
+ {
+ DBG("\tGET with no response body, that smells!\n");
+ s->state = HTTP_BODY_INF;
+ }
+ else
+ {
+ DBG("\tNo response body\n");
+ s->state = HTTP_DONE;
+ }
+ s->body_queue = &s->rx_queue;
+ s->body_end_state = HTTP_DONE;
+}
+
+static void http_input(struct flow *f, int dir, struct pkt *p)
+{
+ struct http_state *s = f->appl_data;
+ struct http_header *h;
+ int fin_tx = (f->pipe[0].state == FLOW_FINISHED);
+ int fin_rx = (f->pipe[1].state == FLOW_FINISHED);
+
+ // DBG("dir=%d txf=%d rxf=%d len=%d\n", dir, fin_tx, fin_rx, pkt_len(p));
+ if (s->state == HTTP_ERROR || s->state == HTTP_CUT)
+ {
+ DBG("HTTP: %d DROPPING INPUT\n", s->id);
+ pkt_free(p);
+ return;
+ }
+ if (pkt_len(p))
+ list_add_tail((dir ? &s->tx_queue : &s->rx_queue), &p->n);
+ for(;;)
+ {
+ DBG("HTTP: %d STATE %d\n", s->id, s->state);
+ switch (s->state)
+ {
+ case HTTP_INIT:
+ if (fin_tx || !http_have_input(&s->tx_queue))
+ return;
+ s->state = HTTP_REQUEST;
+ http_init_xact(s);
+ break;
+ case HTTP_REQUEST:
+ if (fin_tx || fin_rx)
+ goto cut;
+ if (!(h = http_get_line(s, &s->tx_queue)))
+ return;
+ DBG("\t>> %s\n", h->buf);
+ if (!s->req_line)
+ {
+ if (!h->buf[0])
+ goto err;
+ s->req_line = h->buf;
+ }
+ else if (h->buf[0])
+ http_parse_hdr(&s->req_headers, h);
+ else
+ http_parse_req(s);
+ break;
+ case HTTP_BODY_LENGTH:
+ if (fin_rx)
+ goto cut;
+ if (!http_skip_bytes(s->body_queue, &s->body_len))
+ return;
+ DBG("\tEnd of body\n");
+ s->state = s->body_end_state;
+ break;
+ case HTTP_BODY_CHUNKED:
+ if (fin_rx)
+ goto cut;
+ if (s->body_len)
+ {
+ if (!http_skip_bytes(s->body_queue, &s->body_len))
+ return;
+ }
+ else if (s->body_trailer)
+ {
+ if (!(h = http_get_line(s, s->body_queue)))
+ return;
+ if (!h->buf[0])
+ {
+ DBG("\tEnd of chunk-encoded body\n");
+ s->state = s->body_end_state;
+ }
+ }
+ else
+ {
+ if (!(h = http_get_line(s, s->body_queue)))
+ return;
+ if (sscanf(h->buf, "%x", &s->body_len) != 1)
+ goto err;
+ if (s->body_len)
+ s->body_len += 2; /* extra CRLF */
+ else /* last chunk */
+ s->body_trailer = 1;
+ }
+ break;
+ case HTTP_BODY_INF:
+ s->body_len = ~0U;
+ http_skip_bytes(s->body_queue, &s->body_len);
+ if (fin_rx)
+ {
+ DBG("\tEnd of FIN-delimited body\n");
+ s->state = s->body_end_state;
+ }
+ else
+ return;
+ break;
+ case HTTP_RESPONSE:
+ if (fin_rx)
+ goto cut;
+ if (!(h = http_get_line(s, &s->rx_queue)))
+ return;
+ DBG("\t<< %s\n", h->buf);
+ if (!s->resp_line)
+ {
+ if (!h->buf[0])
+ goto err;
+ s->resp_line = h->buf;
+ }
+ else if (h->buf[0])
+ http_parse_hdr(&s->resp_headers, h);
+ else
+ http_parse_resp(s);
+ break;
+ case HTTP_DONE:
+ DBG("\tTransaction finished.\n");
+ if (!strncasecmp(s->req_line, "CONNECT ", 8))
+ {
+ s->state = HTTP_CONNECT;
+ return;
+ }
+ s->state = HTTP_INIT;
+ break;
+ case HTTP_CONNECT:
+ s->body_len = ~0U;
+ http_skip_bytes(&s->rx_queue, &s->body_len);
+ s->body_len = ~0U;
+ http_skip_bytes(&s->tx_queue, &s->body_len);
+ return;
+ case HTTP_ERROR:
+ case HTTP_CUT:
+ return;
+ default:
+ ASSERT(0);
+ }
+ }
+
+ err:
+ DBG("HTTP: %d ERROR: PROTOCOL VIOLATION\n", s->id);
+ s->state = HTTP_ERROR;
+ return;
+
+ cut:
+ DBG("HTTP: %d ERROR: UNEXPECTED EOF\n", s->id);
+ s->state = HTTP_CUT;
+}
+
+struct appl_hooks appl_http = {
+ .open = http_open,
+ .input = http_input,
+ .close = http_close
+};