| /* |
| * httpread - Manage reading file(s) from HTTP/TCP socket |
| * Author: Ted Merrill |
| * Copyright 2008 Atheros Communications |
| * |
| * This software may be distributed under the terms of the BSD license. |
| * See README 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 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? */ |
| } |
| |
| |
| 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; |
| |
| eloop_cancel_timeout(httpread_timeout_handler, NULL, h); |
| eloop_unregister_sock(h->sd, EVENT_TYPE_READ); |
| 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->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++ = hex2byte(rawuri + 1); |
| 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. |
| */ |
| 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. |
| */ |
| eloop_unregister_sock(h->sd, EVENT_TYPE_READ); |
| /* The application can destroy us whenever they feel like... |
| * cancel timeout. |
| */ |
| eloop_cancel_timeout(httpread_timeout_handler, NULL, h); |
| (*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 && |
| eloop_register_timeout(timeout_seconds, 0, |
| httpread_timeout_handler, NULL, h)) { |
| /* No way to recover (from malloc failure) */ |
| goto fail; |
| } |
| if (eloop_register_sock(sd, EVENT_TYPE_READ, httpread_read_handler, |
| NULL, h)) { |
| /* No way to recover (from malloc failure) */ |
| goto fail; |
| } |
| 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++; |
| } |
| } |