remove @EAP_LDFLAGS@, no longer exists
[mech_eap.orig] / libeap / src / wps / httpread.c
diff --git a/libeap/src/wps/httpread.c b/libeap/src/wps/httpread.c
new file mode 100644 (file)
index 0000000..40422e4
--- /dev/null
@@ -0,0 +1,861 @@
+/*
+ * httpread - Manage reading file(s) from HTTP/TCP socket
+ * Author: Ted Merrill
+ * Copyright 2008 Atheros Communications
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ *
+ * The files are buffered via internal callbacks from eloop, then presented to
+ * an application callback routine when completely read into memory. May also
+ * be used if no file is expected but just to get the header, including HTTP
+ * replies (e.g. HTTP/1.1 200 OK etc.).
+ *
+ * This does not attempt to be an optimally efficient implementation, but does
+ * attempt to be of reasonably small size and memory consumption; assuming that
+ * only small files are to be read. A maximum file size is provided by
+ * application and enforced.
+ *
+ * It is assumed that the application does not expect any of the following:
+ * -- transfer encoding other than chunked
+ * -- trailer fields
+ * It is assumed that, even if the other side requested that the connection be
+ * kept open, that we will close it (thus HTTP messages sent by application
+ * should have the connection closed field); this is allowed by HTTP/1.1 and
+ * simplifies things for us.
+ *
+ * Other limitations:
+ * -- HTTP header may not exceed a hard-coded size.
+ *
+ * Notes:
+ * This code would be massively simpler without some of the new features of
+ * HTTP/1.1, especially chunked data.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "eloop.h"
+#include "httpread.h"
+
+
+/* Tunable parameters */
+#define HTTPREAD_READBUF_SIZE 1024      /* read in chunks of this size */
+#define HTTPREAD_HEADER_MAX_SIZE 4096   /* max allowed for headers */
+#define HTTPREAD_BODYBUF_DELTA 4096     /* increase allocation by this */
+
+#if 0
+/* httpread_debug -- set this global variable > 0 e.g. from debugger
+ * to enable debugs (larger numbers for more debugs)
+ * Make this a #define of 0 to eliminate the debugging code.
+ */
+int httpread_debug = 99;
+#else
+#define httpread_debug 0        /* eliminates even the debugging code */
+#endif
+
+
+/* control instance -- actual definition (opaque to application)
+ */
+struct httpread {
+       /* information from creation */
+       int sd;         /* descriptor of TCP socket to read from */
+       void (*cb)(struct httpread *handle, void *cookie,
+                   enum httpread_event e);  /* call on event */
+       void *cookie;   /* pass to callback */
+       int max_bytes;          /* maximum file size else abort it */
+       int timeout_seconds;            /* 0 or total duration timeout period */
+
+       /* dynamically used information follows */
+       int sd_registered;      /* nonzero if we need to unregister socket */
+       int to_registered;      /* nonzero if we need to unregister timeout */
+
+       int got_hdr;            /* nonzero when header is finalized */
+       char hdr[HTTPREAD_HEADER_MAX_SIZE+1];   /* headers stored here */
+       int hdr_nbytes;
+
+       enum httpread_hdr_type hdr_type;
+       int version;            /* 1 if we've seen 1.1 */
+       int reply_code;         /* for type REPLY, e.g. 200 for HTTP/1.1 200 OK */
+       int got_content_length; /* true if we know content length for sure */
+       int content_length;     /* body length,  iff got_content_length */
+       int chunked;            /* nonzero for chunked data */
+       char *uri;
+
+       int got_body;           /* nonzero when body is finalized */
+       char *body;
+       int body_nbytes;
+       int body_alloc_nbytes;  /* amount allocated */
+
+       int got_file;           /* here when we are done */
+
+       /* The following apply if data is chunked: */
+       int in_chunk_data;      /* 0=in/at header, 1=in the data or tail*/
+       int chunk_start;        /* offset in body of chunk hdr or data */
+       int chunk_size;         /* data of chunk (not hdr or ending CRLF)*/
+       int in_trailer;         /* in header fields after data (chunked only)*/
+       enum trailer_state {
+               trailer_line_begin = 0,
+               trailer_empty_cr,       /* empty line + CR */
+               trailer_nonempty,
+               trailer_nonempty_cr,
+       } trailer_state;
+};
+
+
+/* Check words for equality, where words consist of graphical characters
+ * delimited by whitespace
+ * Returns nonzero if "equal" doing case insensitive comparison.
+ */
+static int word_eq(char *s1, char *s2)
+{
+       int c1;
+       int c2;
+       int end1 = 0;
+       int end2 = 0;
+       for (;;) {
+               c1 = *s1++;
+               c2 = *s2++;
+               if (isalpha(c1) && isupper(c1))
+                       c1 = tolower(c1);
+               if (isalpha(c2) && isupper(c2))
+                       c2 = tolower(c2);
+               end1 = !isgraph(c1);
+               end2 = !isgraph(c2);
+               if (end1 || end2 || c1 != c2)
+                       break;
+       }
+       return end1 && end2;  /* reached end of both words? */
+}
+
+
+/* convert hex to binary
+ * Requires that c have been previously tested true with isxdigit().
+ */
+static int hex_value(int c)
+{
+       if (isdigit(c))
+               return c - '0';
+       if (islower(c))
+               return 10 + c - 'a';
+       return 10 + c - 'A';
+}
+
+
+static void httpread_timeout_handler(void *eloop_data, void *user_ctx);
+
+/* httpread_destroy -- if h is non-NULL, clean up
+ * This must eventually be called by the application following
+ * call of the application's callback and may be called
+ * earlier if desired.
+ */
+void httpread_destroy(struct httpread *h)
+{
+       if (httpread_debug >= 10)
+               wpa_printf(MSG_DEBUG, "ENTER httpread_destroy(%p)", h);
+       if (!h)
+               return;
+
+       if (h->to_registered)
+               eloop_cancel_timeout(httpread_timeout_handler, NULL, h);
+       h->to_registered = 0;
+       if (h->sd_registered)
+               eloop_unregister_sock(h->sd, EVENT_TYPE_READ);
+       h->sd_registered = 0;
+       os_free(h->body);
+       os_free(h->uri);
+       os_memset(h, 0, sizeof(*h));  /* aid debugging */
+       h->sd = -1;     /* aid debugging */
+       os_free(h);
+}
+
+
+/* httpread_timeout_handler -- called on excessive total duration
+ */
+static void httpread_timeout_handler(void *eloop_data, void *user_ctx)
+{
+       struct httpread *h = user_ctx;
+       wpa_printf(MSG_DEBUG, "httpread timeout (%p)", h);
+       h->to_registered = 0;   /* is self-cancelling */
+       (*h->cb)(h, h->cookie, HTTPREAD_EVENT_TIMEOUT);
+}
+
+
+/* Analyze options only so far as is needed to correctly obtain the file.
+ * The application can look at the raw header to find other options.
+ */
+static int httpread_hdr_option_analyze(
+       struct httpread *h,
+       char *hbp       /* pointer to current line in header buffer */
+       )
+{
+       if (word_eq(hbp, "CONTENT-LENGTH:")) {
+               while (isgraph(*hbp))
+                       hbp++;
+               while (*hbp == ' ' || *hbp == '\t')
+                       hbp++;
+               if (!isdigit(*hbp))
+                       return -1;
+               h->content_length = atol(hbp);
+               h->got_content_length = 1;
+               return 0;
+       }
+       if (word_eq(hbp, "TRANSFER_ENCODING:") ||
+           word_eq(hbp, "TRANSFER-ENCODING:")) {
+               while (isgraph(*hbp))
+                       hbp++;
+               while (*hbp == ' ' || *hbp == '\t')
+                       hbp++;
+               /* There should (?) be no encodings of interest
+                * other than chunked...
+                */
+               if (word_eq(hbp, "CHUNKED")) {
+                       h->chunked = 1;
+                       h->in_chunk_data = 0;
+                       /* ignore possible ;<parameters> */
+               }
+               return 0;
+       }
+       /* skip anything we don't know, which is a lot */
+       return 0;
+}
+
+
+static int httpread_hdr_analyze(struct httpread *h)
+{
+       char *hbp = h->hdr;      /* pointer into h->hdr */
+       int standard_first_line = 1;
+
+       /* First line is special */
+       h->hdr_type = HTTPREAD_HDR_TYPE_UNKNOWN;
+       if (!isgraph(*hbp))
+               goto bad;
+       if (os_strncmp(hbp, "HTTP/", 5) == 0) {
+               h->hdr_type = HTTPREAD_HDR_TYPE_REPLY;
+               standard_first_line = 0;
+               hbp += 5;
+               if (hbp[0] == '1' && hbp[1] == '.' &&
+                   isdigit(hbp[2]) && hbp[2] != '0')
+                       h->version = 1;
+               while (isgraph(*hbp))
+                       hbp++;
+               while (*hbp == ' ' || *hbp == '\t')
+                       hbp++;
+               if (!isdigit(*hbp))
+                       goto bad;
+               h->reply_code = atol(hbp);
+       } else if (word_eq(hbp, "GET"))
+               h->hdr_type = HTTPREAD_HDR_TYPE_GET;
+       else if (word_eq(hbp, "HEAD"))
+               h->hdr_type = HTTPREAD_HDR_TYPE_HEAD;
+       else if (word_eq(hbp, "POST"))
+               h->hdr_type = HTTPREAD_HDR_TYPE_POST;
+       else if (word_eq(hbp, "PUT"))
+               h->hdr_type = HTTPREAD_HDR_TYPE_PUT;
+       else if (word_eq(hbp, "DELETE"))
+               h->hdr_type = HTTPREAD_HDR_TYPE_DELETE;
+       else if (word_eq(hbp, "TRACE"))
+               h->hdr_type = HTTPREAD_HDR_TYPE_TRACE;
+       else if (word_eq(hbp, "CONNECT"))
+               h->hdr_type = HTTPREAD_HDR_TYPE_CONNECT;
+       else if (word_eq(hbp, "NOTIFY"))
+               h->hdr_type = HTTPREAD_HDR_TYPE_NOTIFY;
+       else if (word_eq(hbp, "M-SEARCH"))
+               h->hdr_type = HTTPREAD_HDR_TYPE_M_SEARCH;
+       else if (word_eq(hbp, "M-POST"))
+               h->hdr_type = HTTPREAD_HDR_TYPE_M_POST;
+       else if (word_eq(hbp, "SUBSCRIBE"))
+               h->hdr_type = HTTPREAD_HDR_TYPE_SUBSCRIBE;
+       else if (word_eq(hbp, "UNSUBSCRIBE"))
+               h->hdr_type = HTTPREAD_HDR_TYPE_UNSUBSCRIBE;
+       else {
+       }
+
+       if (standard_first_line) {
+               char *rawuri;
+               char *uri;
+               /* skip type */
+               while (isgraph(*hbp))
+                       hbp++;
+               while (*hbp == ' ' || *hbp == '\t')
+                       hbp++;
+               /* parse uri.
+                * Find length, allocate memory for translated
+                * copy, then translate by changing %<hex><hex>
+                * into represented value.
+                */
+               rawuri = hbp;
+               while (isgraph(*hbp))
+                       hbp++;
+               h->uri = os_malloc((hbp - rawuri) + 1);
+               if (h->uri == NULL)
+                       goto bad;
+               uri = h->uri;
+               while (rawuri < hbp) {
+                       int c = *rawuri;
+                       if (c == '%' &&
+                           isxdigit(rawuri[1]) && isxdigit(rawuri[2])) {
+                               *uri++ = (hex_value(rawuri[1]) << 4) |
+                                       hex_value(rawuri[2]);
+                               rawuri += 3;
+                       } else {
+                               *uri++ = c;
+                               rawuri++;
+                       }
+               }
+               *uri = 0;       /* null terminate */
+               while (isgraph(*hbp))
+                       hbp++;
+               while (*hbp == ' ' || *hbp == '\t')
+                       hbp++;
+               /* get version */
+               if (0 == strncmp(hbp, "HTTP/", 5)) {
+                       hbp += 5;
+                       if (hbp[0] == '1' && hbp[1] == '.' &&
+                           isdigit(hbp[2]) && hbp[2] != '0')
+                               h->version = 1;
+               }
+       }
+       /* skip rest of line */
+       while (*hbp)
+               if (*hbp++ == '\n')
+                       break;
+
+       /* Remainder of lines are options, in any order;
+        * or empty line to terminate
+        */
+       for (;;) {
+               /* Empty line to terminate */
+               if (hbp[0] == '\n' ||
+                   (hbp[0] == '\r' && hbp[1] == '\n'))
+                       break;
+               if (!isgraph(*hbp))
+                       goto bad;
+               if (httpread_hdr_option_analyze(h, hbp))
+                       goto bad;
+               /* skip line */
+               while (*hbp)
+                       if (*hbp++ == '\n')
+                               break;
+       }
+
+       /* chunked overrides content-length always */
+       if (h->chunked)
+               h->got_content_length = 0;
+
+       /* For some types, we should not try to read a body
+        * This is in addition to the application determining
+        * that we should not read a body.
+        */
+       switch (h->hdr_type) {
+       case HTTPREAD_HDR_TYPE_REPLY:
+               /* Some codes can have a body and some not.
+                * For now, just assume that any other than 200
+                * do not...
+                */
+               if (h->reply_code != 200)
+                       h->max_bytes = 0;
+               break;
+       case HTTPREAD_HDR_TYPE_GET:
+       case HTTPREAD_HDR_TYPE_HEAD:
+               /* in practice it appears that it is assumed
+                * that GETs have a body length of 0... ?
+                */
+               if (h->chunked == 0 && h->got_content_length == 0)
+                       h->max_bytes = 0;
+               break;
+       case HTTPREAD_HDR_TYPE_POST:
+       case HTTPREAD_HDR_TYPE_PUT:
+       case HTTPREAD_HDR_TYPE_DELETE:
+       case HTTPREAD_HDR_TYPE_TRACE:
+       case HTTPREAD_HDR_TYPE_CONNECT:
+       case HTTPREAD_HDR_TYPE_NOTIFY:
+       case HTTPREAD_HDR_TYPE_M_SEARCH:
+       case HTTPREAD_HDR_TYPE_M_POST:
+       case HTTPREAD_HDR_TYPE_SUBSCRIBE:
+       case HTTPREAD_HDR_TYPE_UNSUBSCRIBE:
+       default:
+               break;
+       }
+
+       return 0;
+
+bad:
+       /* Error */
+       return -1;
+}
+
+
+/* httpread_read_handler -- called when socket ready to read
+ *
+ * Note: any extra data we read past end of transmitted file is ignored;
+ * if we were to support keeping connections open for multiple files then
+ * this would have to be addressed.
+ */
+static void httpread_read_handler(int sd, void *eloop_ctx, void *sock_ctx)
+{
+       struct httpread *h = sock_ctx;
+       int nread;
+       char *rbp;      /* pointer into read buffer */
+       char *hbp;      /* pointer into header buffer */
+       char *bbp;      /* pointer into body buffer */
+       char readbuf[HTTPREAD_READBUF_SIZE];  /* temp use to read into */
+
+       if (httpread_debug >= 20)
+               wpa_printf(MSG_DEBUG, "ENTER httpread_read_handler(%p)", h);
+
+       /* read some at a time, then search for the interal
+        * boundaries between header and data and etc.
+        */
+       nread = read(h->sd, readbuf, sizeof(readbuf));
+       if (nread < 0)
+               goto bad;
+       if (nread == 0) {
+               /* end of transmission... this may be normal
+                * or may be an error... in some cases we can't
+                * tell which so we must assume it is normal then.
+                */
+               if (!h->got_hdr) {
+                       /* Must at least have completed header */
+                       wpa_printf(MSG_DEBUG, "httpread premature eof(%p)", h);
+                       goto bad;
+               }
+               if (h->chunked || h->got_content_length) {
+                       /* Premature EOF; e.g. dropped connection */
+                       wpa_printf(MSG_DEBUG,
+                                  "httpread premature eof(%p) %d/%d",
+                                  h, h->body_nbytes,
+                                  h->content_length);
+                       goto bad;
+               }
+               /* No explicit length, hopefully we have all the data
+                * although dropped connections can cause false
+                * end
+                */
+               if (httpread_debug >= 10)
+                       wpa_printf(MSG_DEBUG, "httpread ok eof(%p)", h);
+                       h->got_body = 1;
+                       goto got_file;
+       }
+       rbp = readbuf;
+
+       /* Header consists of text lines (terminated by both CR and LF)
+        * and an empty line (CR LF only).
+        */
+       if (!h->got_hdr) {
+               hbp = h->hdr + h->hdr_nbytes;
+               /* add to headers until:
+                *      -- we run out of data in read buffer
+                *      -- or, we run out of header buffer room
+                *      -- or, we get double CRLF in headers
+                */
+               for (;;) {
+                       if (nread == 0)
+                               goto get_more;
+                       if (h->hdr_nbytes == HTTPREAD_HEADER_MAX_SIZE) {
+                               goto bad;
+                       }
+                       *hbp++ = *rbp++;
+                       nread--;
+                       h->hdr_nbytes++;
+                       if (h->hdr_nbytes >= 4 &&
+                           hbp[-1] == '\n' &&
+                           hbp[-2] == '\r' &&
+                           hbp[-3] == '\n' &&
+                           hbp[-4] == '\r' ) {
+                               h->got_hdr = 1;
+                               *hbp = 0;       /* null terminate */
+                               break;
+                       }
+               }
+               /* here we've just finished reading the header */
+               if (httpread_hdr_analyze(h)) {
+                       wpa_printf(MSG_DEBUG, "httpread bad hdr(%p)", h);
+                       goto bad;
+               }
+               if (h->max_bytes == 0) {
+                       if (httpread_debug >= 10)
+                               wpa_printf(MSG_DEBUG,
+                                          "httpread no body hdr end(%p)", h);
+                       goto got_file;
+               }
+               if (h->got_content_length && h->content_length == 0) {
+                       if (httpread_debug >= 10)
+                               wpa_printf(MSG_DEBUG,
+                                          "httpread zero content length(%p)",
+                                          h);
+                       goto got_file;
+               }
+       }
+
+       /* Certain types of requests never have data and so
+        * must be specially recognized.
+        */
+       if (!os_strncasecmp(h->hdr, "SUBSCRIBE", 9) ||
+           !os_strncasecmp(h->hdr, "UNSUBSCRIBE", 11) ||
+           !os_strncasecmp(h->hdr, "HEAD", 4) ||
+           !os_strncasecmp(h->hdr, "GET", 3)) {
+               if (!h->got_body) {
+                       if (httpread_debug >= 10)
+                               wpa_printf(MSG_DEBUG,
+                                          "httpread NO BODY for sp. type");
+               }
+               h->got_body = 1;
+               goto got_file;
+       }
+
+       /* Data can be just plain binary data, or if "chunked"
+        * consists of chunks each with a header, ending with
+        * an ending header.
+        */
+       if (nread == 0)
+               goto get_more;
+       if (!h->got_body) {
+               /* Here to get (more of) body */
+               /* ensure we have enough room for worst case for body
+                * plus a null termination character
+                */
+               if (h->body_alloc_nbytes < (h->body_nbytes + nread + 1)) {
+                       char *new_body;
+                       int new_alloc_nbytes;
+
+                       if (h->body_nbytes >= h->max_bytes)
+                               goto bad;
+                       new_alloc_nbytes = h->body_alloc_nbytes +
+                               HTTPREAD_BODYBUF_DELTA;
+                       /* For content-length case, the first time
+                        * through we allocate the whole amount
+                        * we need.
+                        */
+                       if (h->got_content_length &&
+                           new_alloc_nbytes < (h->content_length + 1))
+                               new_alloc_nbytes = h->content_length + 1;
+                       if ((new_body = os_realloc(h->body, new_alloc_nbytes))
+                           == NULL)
+                               goto bad;
+
+                       h->body = new_body;
+                       h->body_alloc_nbytes = new_alloc_nbytes;
+               }
+               /* add bytes */
+               bbp = h->body + h->body_nbytes;
+               for (;;) {
+                       int ncopy;
+                       /* See if we need to stop */
+                       if (h->chunked && h->in_chunk_data == 0) {
+                               /* in chunk header */
+                               char *cbp = h->body + h->chunk_start;
+                               if (bbp-cbp >= 2 && bbp[-2] == '\r' &&
+                                   bbp[-1] == '\n') {
+                                       /* end of chunk hdr line */
+                                       /* hdr line consists solely
+                                        * of a hex numeral and CFLF
+                                        */
+                                       if (!isxdigit(*cbp))
+                                               goto bad;
+                                       h->chunk_size = strtoul(cbp, NULL, 16);
+                                       /* throw away chunk header
+                                        * so we have only real data
+                                        */
+                                       h->body_nbytes = h->chunk_start;
+                                       bbp = cbp;
+                                       if (h->chunk_size == 0) {
+                                               /* end of chunking */
+                                               /* trailer follows */
+                                               h->in_trailer = 1;
+                                               if (httpread_debug >= 20)
+                                                       wpa_printf(
+                                                               MSG_DEBUG,
+                                                               "httpread end chunks(%p)", h);
+                                               break;
+                                       }
+                                       h->in_chunk_data = 1;
+                                       /* leave chunk_start alone */
+                               }
+                       } else if (h->chunked) {
+                               /* in chunk data */
+                               if ((h->body_nbytes - h->chunk_start) ==
+                                   (h->chunk_size + 2)) {
+                                       /* end of chunk reached,
+                                        * new chunk starts
+                                        */
+                                       /* check chunk ended w/ CRLF
+                                        * which we'll throw away
+                                        */
+                                       if (bbp[-1] == '\n' &&
+                                           bbp[-2] == '\r') {
+                                       } else
+                                               goto bad;
+                                       h->body_nbytes -= 2;
+                                       bbp -= 2;
+                                       h->chunk_start = h->body_nbytes;
+                                       h->in_chunk_data = 0;
+                                       h->chunk_size = 0; /* just in case */
+                               }
+                       } else if (h->got_content_length &&
+                                  h->body_nbytes >= h->content_length) {
+                               h->got_body = 1;
+                               if (httpread_debug >= 10)
+                                       wpa_printf(
+                                               MSG_DEBUG,
+                                               "httpread got content(%p)", h);
+                               goto got_file;
+                       }
+                       if (nread <= 0)
+                               break;
+                       /* Now transfer. Optimize using memcpy where we can. */
+                       if (h->chunked && h->in_chunk_data) {
+                               /* copy up to remainder of chunk data
+                                * plus the required CR+LF at end
+                                */
+                               ncopy = (h->chunk_start + h->chunk_size + 2) -
+                                       h->body_nbytes;
+                       } else if (h->chunked) {
+                               /*in chunk header -- don't optimize */
+                               *bbp++ = *rbp++;
+                               nread--;
+                               h->body_nbytes++;
+                               continue;
+                       } else if (h->got_content_length) {
+                               ncopy = h->content_length - h->body_nbytes;
+                       } else {
+                               ncopy = nread;
+                       }
+                       /* Note: should never be 0 */
+                       if (ncopy > nread)
+                               ncopy = nread;
+                       os_memcpy(bbp, rbp, ncopy);
+                       bbp += ncopy;
+                       h->body_nbytes += ncopy;
+                       rbp += ncopy;
+                       nread -= ncopy;
+               }       /* body copy loop */
+       }       /* !got_body */
+       if (h->chunked && h->in_trailer) {
+               /* If "chunked" then there is always a trailer,
+                * consisting of zero or more non-empty lines
+                * ending with CR LF and then an empty line w/ CR LF.
+                * We do NOT support trailers except to skip them --
+                * this is supported (generally) by the http spec.
+                */
+               bbp = h->body + h->body_nbytes;
+               for (;;) {
+                       int c;
+                       if (nread <= 0)
+                               break;
+                       c = *rbp++;
+                       nread--;
+                       switch (h->trailer_state) {
+                       case trailer_line_begin:
+                               if (c == '\r')
+                                       h->trailer_state = trailer_empty_cr;
+                               else
+                                       h->trailer_state = trailer_nonempty;
+                               break;
+                       case trailer_empty_cr:
+                               /* end empty line */
+                               if (c == '\n') {
+                                       h->trailer_state = trailer_line_begin;
+                                       h->in_trailer = 0;
+                                       if (httpread_debug >= 10)
+                                               wpa_printf(
+                                                       MSG_DEBUG,
+                                                       "httpread got content(%p)", h);
+                                       h->got_body = 1;
+                                       goto got_file;
+                               }
+                               h->trailer_state = trailer_nonempty;
+                               break;
+                       case trailer_nonempty:
+                               if (c == '\r')
+                                       h->trailer_state = trailer_nonempty_cr;
+                               break;
+                       case trailer_nonempty_cr:
+                               if (c == '\n')
+                                       h->trailer_state = trailer_line_begin;
+                               else
+                                       h->trailer_state = trailer_nonempty;
+                               break;
+                       }
+               }
+       }
+       goto get_more;
+
+bad:
+       /* Error */
+       wpa_printf(MSG_DEBUG, "httpread read/parse failure (%p)", h);
+       (*h->cb)(h, h->cookie, HTTPREAD_EVENT_ERROR);
+       return;
+
+get_more:
+       return;
+
+got_file:
+       if (httpread_debug >= 10)
+               wpa_printf(MSG_DEBUG,
+                          "httpread got file %d bytes type %d",
+                          h->body_nbytes, h->hdr_type);
+       /* Null terminate for convenience of some applications */
+       if (h->body)
+               h->body[h->body_nbytes] = 0; /* null terminate */
+       h->got_file = 1;
+       /* Assume that we do NOT support keeping connection alive,
+        * and just in case somehow we don't get destroyed right away,
+        * unregister now.
+        */
+       if (h->sd_registered)
+               eloop_unregister_sock(h->sd, EVENT_TYPE_READ);
+       h->sd_registered = 0;
+       /* The application can destroy us whenever they feel like...
+        * cancel timeout.
+        */
+       if (h->to_registered)
+               eloop_cancel_timeout(httpread_timeout_handler, NULL, h);
+       h->to_registered = 0;
+       (*h->cb)(h, h->cookie, HTTPREAD_EVENT_FILE_READY);
+}
+
+
+/* httpread_create -- start a new reading session making use of eloop.
+ * The new instance will use the socket descriptor for reading (until
+ * it gets a file and not after) but will not close the socket, even
+ * when the instance is destroyed (the application must do that).
+ * Return NULL on error.
+ *
+ * Provided that httpread_create successfully returns a handle,
+ * the callback fnc is called to handle httpread_event events.
+ * The caller should do destroy on any errors or unknown events.
+ *
+ * Pass max_bytes == 0 to not read body at all (required for e.g.
+ * reply to HEAD request).
+ */
+struct httpread * httpread_create(
+       int sd,  /* descriptor of TCP socket to read from */
+       void (*cb)(struct httpread *handle, void *cookie,
+                  enum httpread_event e),  /* call on event */
+       void *cookie,    /* pass to callback */
+       int max_bytes,    /* maximum body size else abort it */
+       int timeout_seconds     /* 0; or total duration timeout period */
+       )
+{
+       struct httpread *h = NULL;
+
+       h = os_zalloc(sizeof(*h));
+       if (h == NULL)
+               goto fail;
+       h->sd = sd;
+       h->cb = cb;
+       h->cookie = cookie;
+       h->max_bytes = max_bytes;
+       h->timeout_seconds = timeout_seconds;
+
+       if (timeout_seconds > 0) {
+               if (eloop_register_timeout(timeout_seconds, 0,
+                                          httpread_timeout_handler,
+                                          NULL, h)) {
+                       /* No way to recover (from malloc failure) */
+                       goto fail;
+               }
+               h->to_registered = 1;
+       }
+       if (eloop_register_sock(sd, EVENT_TYPE_READ, httpread_read_handler,
+                               NULL, h)) {
+               /* No way to recover (from malloc failure) */
+               goto fail;
+       }
+       h->sd_registered = 1;
+       return h;
+
+fail:
+
+       /* Error */
+       httpread_destroy(h);
+       return NULL;
+}
+
+
+/* httpread_hdr_type_get -- When file is ready, returns header type. */
+enum httpread_hdr_type httpread_hdr_type_get(struct httpread *h)
+{
+       return h->hdr_type;
+}
+
+
+/* httpread_uri_get -- When file is ready, uri_get returns (translated) URI
+ * or possibly NULL (which would be an error).
+ */
+char * httpread_uri_get(struct httpread *h)
+{
+       return h->uri;
+}
+
+
+/* httpread_reply_code_get -- When reply is ready, returns reply code */
+int httpread_reply_code_get(struct httpread *h)
+{
+       return h->reply_code;
+}
+
+
+/* httpread_length_get -- When file is ready, returns file length. */
+int httpread_length_get(struct httpread *h)
+{
+       return h->body_nbytes;
+}
+
+
+/* httpread_data_get -- When file is ready, returns file content
+ * with null byte appened.
+ * Might return NULL in some error condition.
+ */
+void * httpread_data_get(struct httpread *h)
+{
+       return h->body ? h->body : "";
+}
+
+
+/* httpread_hdr_get -- When file is ready, returns header content
+ * with null byte appended.
+ * Might return NULL in some error condition.
+ */
+char * httpread_hdr_get(struct httpread *h)
+{
+       return h->hdr;
+}
+
+
+/* httpread_hdr_line_get -- When file is ready, returns pointer
+ * to line within header content matching the given tag
+ * (after the tag itself and any spaces/tabs).
+ *
+ * The tag should end with a colon for reliable matching.
+ *
+ * If not found, returns NULL;
+ */
+char * httpread_hdr_line_get(struct httpread *h, const char *tag)
+{
+       int tag_len = os_strlen(tag);
+       char *hdr = h->hdr;
+       hdr = os_strchr(hdr, '\n');
+       if (hdr == NULL)
+               return NULL;
+       hdr++;
+       for (;;) {
+               if (!os_strncasecmp(hdr, tag, tag_len)) {
+                       hdr += tag_len;
+                       while (*hdr == ' ' || *hdr == '\t')
+                               hdr++;
+                       return hdr;
+               }
+               hdr = os_strchr(hdr, '\n');
+               if (hdr == NULL)
+                       return NULL;
+               hdr++;
+       }
+}