aboutsummaryrefslogtreecommitdiffstats
path: root/src/wps/httpread.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/wps/httpread.c')
-rw-r--r--src/wps/httpread.c858
1 files changed, 858 insertions, 0 deletions
diff --git a/src/wps/httpread.c b/src/wps/httpread.c
new file mode 100644
index 0000000..313b468
--- /dev/null
+++ b/src/wps/httpread.c
@@ -0,0 +1,858 @@
+/**
+ * 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:")) {
+ while (isgraph(*hbp))
+ hbp++;
+ while (*hbp == ' ' || *hbp == '\t')
+ hbp++;
+ /* There should (?) be no encodings of interest
+ * other than chunked...
+ */
+ if (os_strncmp(hbp, "CHUNKED", 7)) {
+ 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 (!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++;
+ }
+}