2009-11-28 20:34:14 +01:00
|
|
|
/*
|
2009-01-29 17:47:02 +01:00
|
|
|
* 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;
|
|
|
|
}
|
2009-04-15 10:18:09 +02:00
|
|
|
if (word_eq(hbp, "TRANSFER_ENCODING:") ||
|
|
|
|
word_eq(hbp, "TRANSFER-ENCODING:")) {
|
2009-01-29 17:47:02 +01:00
|
|
|
while (isgraph(*hbp))
|
|
|
|
hbp++;
|
|
|
|
while (*hbp == ' ' || *hbp == '\t')
|
|
|
|
hbp++;
|
|
|
|
/* There should (?) be no encodings of interest
|
|
|
|
* other than chunked...
|
|
|
|
*/
|
2009-04-15 10:18:09 +02:00
|
|
|
if (word_eq(hbp, "CHUNKED")) {
|
2009-01-29 17:47:02 +01:00
|
|
|
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.
|
|
|
|
*/
|
2009-04-15 10:18:09 +02:00
|
|
|
if (nread == 0)
|
|
|
|
goto get_more;
|
2009-01-29 17:47:02 +01:00
|
|
|
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++;
|
|
|
|
}
|
|
|
|
}
|