1214734Srpaulo/* 2189251Ssam * httpread - Manage reading file(s) from HTTP/TCP socket 3189251Ssam * Author: Ted Merrill 4189251Ssam * Copyright 2008 Atheros Communications 5189251Ssam * 6252726Srpaulo * This software may be distributed under the terms of the BSD license. 7252726Srpaulo * See README for more details. 8189251Ssam * 9189251Ssam * The files are buffered via internal callbacks from eloop, then presented to 10189251Ssam * an application callback routine when completely read into memory. May also 11189251Ssam * be used if no file is expected but just to get the header, including HTTP 12189251Ssam * replies (e.g. HTTP/1.1 200 OK etc.). 13189251Ssam * 14189251Ssam * This does not attempt to be an optimally efficient implementation, but does 15189251Ssam * attempt to be of reasonably small size and memory consumption; assuming that 16189251Ssam * only small files are to be read. A maximum file size is provided by 17189251Ssam * application and enforced. 18189251Ssam * 19189251Ssam * It is assumed that the application does not expect any of the following: 20189251Ssam * -- transfer encoding other than chunked 21189251Ssam * -- trailer fields 22189251Ssam * It is assumed that, even if the other side requested that the connection be 23189251Ssam * kept open, that we will close it (thus HTTP messages sent by application 24189251Ssam * should have the connection closed field); this is allowed by HTTP/1.1 and 25189251Ssam * simplifies things for us. 26189251Ssam * 27189251Ssam * Other limitations: 28189251Ssam * -- HTTP header may not exceed a hard-coded size. 29189251Ssam * 30189251Ssam * Notes: 31189251Ssam * This code would be massively simpler without some of the new features of 32189251Ssam * HTTP/1.1, especially chunked data. 33189251Ssam */ 34189251Ssam 35189251Ssam#include "includes.h" 36189251Ssam 37189251Ssam#include "common.h" 38189251Ssam#include "eloop.h" 39189251Ssam#include "httpread.h" 40189251Ssam 41189251Ssam 42189251Ssam/* Tunable parameters */ 43189251Ssam#define HTTPREAD_READBUF_SIZE 1024 /* read in chunks of this size */ 44189251Ssam#define HTTPREAD_HEADER_MAX_SIZE 4096 /* max allowed for headers */ 45189251Ssam#define HTTPREAD_BODYBUF_DELTA 4096 /* increase allocation by this */ 46189251Ssam 47189251Ssam#if 0 48189251Ssam/* httpread_debug -- set this global variable > 0 e.g. from debugger 49189251Ssam * to enable debugs (larger numbers for more debugs) 50189251Ssam * Make this a #define of 0 to eliminate the debugging code. 51189251Ssam */ 52189251Ssamint httpread_debug = 99; 53189251Ssam#else 54189251Ssam#define httpread_debug 0 /* eliminates even the debugging code */ 55189251Ssam#endif 56189251Ssam 57189251Ssam 58189251Ssam/* control instance -- actual definition (opaque to application) 59189251Ssam */ 60189251Ssamstruct httpread { 61189251Ssam /* information from creation */ 62189251Ssam int sd; /* descriptor of TCP socket to read from */ 63189251Ssam void (*cb)(struct httpread *handle, void *cookie, 64189251Ssam enum httpread_event e); /* call on event */ 65189251Ssam void *cookie; /* pass to callback */ 66189251Ssam int max_bytes; /* maximum file size else abort it */ 67189251Ssam int timeout_seconds; /* 0 or total duration timeout period */ 68189251Ssam 69189251Ssam /* dynamically used information follows */ 70189251Ssam int sd_registered; /* nonzero if we need to unregister socket */ 71189251Ssam int to_registered; /* nonzero if we need to unregister timeout */ 72189251Ssam 73189251Ssam int got_hdr; /* nonzero when header is finalized */ 74189251Ssam char hdr[HTTPREAD_HEADER_MAX_SIZE+1]; /* headers stored here */ 75189251Ssam int hdr_nbytes; 76189251Ssam 77189251Ssam enum httpread_hdr_type hdr_type; 78189251Ssam int version; /* 1 if we've seen 1.1 */ 79189251Ssam int reply_code; /* for type REPLY, e.g. 200 for HTTP/1.1 200 OK */ 80189251Ssam int got_content_length; /* true if we know content length for sure */ 81189251Ssam int content_length; /* body length, iff got_content_length */ 82189251Ssam int chunked; /* nonzero for chunked data */ 83189251Ssam char *uri; 84189251Ssam 85189251Ssam int got_body; /* nonzero when body is finalized */ 86189251Ssam char *body; 87189251Ssam int body_nbytes; 88189251Ssam int body_alloc_nbytes; /* amount allocated */ 89189251Ssam 90189251Ssam int got_file; /* here when we are done */ 91189251Ssam 92189251Ssam /* The following apply if data is chunked: */ 93189251Ssam int in_chunk_data; /* 0=in/at header, 1=in the data or tail*/ 94189251Ssam int chunk_start; /* offset in body of chunk hdr or data */ 95189251Ssam int chunk_size; /* data of chunk (not hdr or ending CRLF)*/ 96189251Ssam int in_trailer; /* in header fields after data (chunked only)*/ 97189251Ssam enum trailer_state { 98189251Ssam trailer_line_begin = 0, 99189251Ssam trailer_empty_cr, /* empty line + CR */ 100189251Ssam trailer_nonempty, 101189251Ssam trailer_nonempty_cr, 102189251Ssam } trailer_state; 103189251Ssam}; 104189251Ssam 105189251Ssam 106189251Ssam/* Check words for equality, where words consist of graphical characters 107189251Ssam * delimited by whitespace 108189251Ssam * Returns nonzero if "equal" doing case insensitive comparison. 109189251Ssam */ 110189251Ssamstatic int word_eq(char *s1, char *s2) 111189251Ssam{ 112189251Ssam int c1; 113189251Ssam int c2; 114189251Ssam int end1 = 0; 115189251Ssam int end2 = 0; 116189251Ssam for (;;) { 117189251Ssam c1 = *s1++; 118189251Ssam c2 = *s2++; 119189251Ssam if (isalpha(c1) && isupper(c1)) 120189251Ssam c1 = tolower(c1); 121189251Ssam if (isalpha(c2) && isupper(c2)) 122189251Ssam c2 = tolower(c2); 123189251Ssam end1 = !isgraph(c1); 124189251Ssam end2 = !isgraph(c2); 125189251Ssam if (end1 || end2 || c1 != c2) 126189251Ssam break; 127189251Ssam } 128189251Ssam return end1 && end2; /* reached end of both words? */ 129189251Ssam} 130189251Ssam 131189251Ssam 132189251Ssam/* convert hex to binary 133189251Ssam * Requires that c have been previously tested true with isxdigit(). 134189251Ssam */ 135189251Ssamstatic int hex_value(int c) 136189251Ssam{ 137189251Ssam if (isdigit(c)) 138189251Ssam return c - '0'; 139189251Ssam if (islower(c)) 140189251Ssam return 10 + c - 'a'; 141189251Ssam return 10 + c - 'A'; 142189251Ssam} 143189251Ssam 144189251Ssam 145189251Ssamstatic void httpread_timeout_handler(void *eloop_data, void *user_ctx); 146189251Ssam 147189251Ssam/* httpread_destroy -- if h is non-NULL, clean up 148189251Ssam * This must eventually be called by the application following 149189251Ssam * call of the application's callback and may be called 150189251Ssam * earlier if desired. 151189251Ssam */ 152189251Ssamvoid httpread_destroy(struct httpread *h) 153189251Ssam{ 154189251Ssam if (httpread_debug >= 10) 155189251Ssam wpa_printf(MSG_DEBUG, "ENTER httpread_destroy(%p)", h); 156189251Ssam if (!h) 157189251Ssam return; 158189251Ssam 159189251Ssam if (h->to_registered) 160189251Ssam eloop_cancel_timeout(httpread_timeout_handler, NULL, h); 161189251Ssam h->to_registered = 0; 162189251Ssam if (h->sd_registered) 163189251Ssam eloop_unregister_sock(h->sd, EVENT_TYPE_READ); 164189251Ssam h->sd_registered = 0; 165189251Ssam os_free(h->body); 166189251Ssam os_free(h->uri); 167189251Ssam os_memset(h, 0, sizeof(*h)); /* aid debugging */ 168189251Ssam h->sd = -1; /* aid debugging */ 169189251Ssam os_free(h); 170189251Ssam} 171189251Ssam 172189251Ssam 173189251Ssam/* httpread_timeout_handler -- called on excessive total duration 174189251Ssam */ 175189251Ssamstatic void httpread_timeout_handler(void *eloop_data, void *user_ctx) 176189251Ssam{ 177189251Ssam struct httpread *h = user_ctx; 178189251Ssam wpa_printf(MSG_DEBUG, "httpread timeout (%p)", h); 179189251Ssam h->to_registered = 0; /* is self-cancelling */ 180189251Ssam (*h->cb)(h, h->cookie, HTTPREAD_EVENT_TIMEOUT); 181189251Ssam} 182189251Ssam 183189251Ssam 184189251Ssam/* Analyze options only so far as is needed to correctly obtain the file. 185189251Ssam * The application can look at the raw header to find other options. 186189251Ssam */ 187189251Ssamstatic int httpread_hdr_option_analyze( 188189251Ssam struct httpread *h, 189189251Ssam char *hbp /* pointer to current line in header buffer */ 190189251Ssam ) 191189251Ssam{ 192189251Ssam if (word_eq(hbp, "CONTENT-LENGTH:")) { 193189251Ssam while (isgraph(*hbp)) 194189251Ssam hbp++; 195189251Ssam while (*hbp == ' ' || *hbp == '\t') 196189251Ssam hbp++; 197189251Ssam if (!isdigit(*hbp)) 198189251Ssam return -1; 199189251Ssam h->content_length = atol(hbp); 200189251Ssam h->got_content_length = 1; 201189251Ssam return 0; 202189251Ssam } 203209158Srpaulo if (word_eq(hbp, "TRANSFER_ENCODING:") || 204209158Srpaulo word_eq(hbp, "TRANSFER-ENCODING:")) { 205189251Ssam while (isgraph(*hbp)) 206189251Ssam hbp++; 207189251Ssam while (*hbp == ' ' || *hbp == '\t') 208189251Ssam hbp++; 209189251Ssam /* There should (?) be no encodings of interest 210189251Ssam * other than chunked... 211189251Ssam */ 212209158Srpaulo if (word_eq(hbp, "CHUNKED")) { 213189251Ssam h->chunked = 1; 214189251Ssam h->in_chunk_data = 0; 215189251Ssam /* ignore possible ;<parameters> */ 216189251Ssam } 217189251Ssam return 0; 218189251Ssam } 219189251Ssam /* skip anything we don't know, which is a lot */ 220189251Ssam return 0; 221189251Ssam} 222189251Ssam 223189251Ssam 224189251Ssamstatic int httpread_hdr_analyze(struct httpread *h) 225189251Ssam{ 226189251Ssam char *hbp = h->hdr; /* pointer into h->hdr */ 227189251Ssam int standard_first_line = 1; 228189251Ssam 229189251Ssam /* First line is special */ 230189251Ssam h->hdr_type = HTTPREAD_HDR_TYPE_UNKNOWN; 231189251Ssam if (!isgraph(*hbp)) 232189251Ssam goto bad; 233189251Ssam if (os_strncmp(hbp, "HTTP/", 5) == 0) { 234189251Ssam h->hdr_type = HTTPREAD_HDR_TYPE_REPLY; 235189251Ssam standard_first_line = 0; 236189251Ssam hbp += 5; 237189251Ssam if (hbp[0] == '1' && hbp[1] == '.' && 238189251Ssam isdigit(hbp[2]) && hbp[2] != '0') 239189251Ssam h->version = 1; 240189251Ssam while (isgraph(*hbp)) 241189251Ssam hbp++; 242189251Ssam while (*hbp == ' ' || *hbp == '\t') 243189251Ssam hbp++; 244189251Ssam if (!isdigit(*hbp)) 245189251Ssam goto bad; 246189251Ssam h->reply_code = atol(hbp); 247189251Ssam } else if (word_eq(hbp, "GET")) 248189251Ssam h->hdr_type = HTTPREAD_HDR_TYPE_GET; 249189251Ssam else if (word_eq(hbp, "HEAD")) 250189251Ssam h->hdr_type = HTTPREAD_HDR_TYPE_HEAD; 251189251Ssam else if (word_eq(hbp, "POST")) 252189251Ssam h->hdr_type = HTTPREAD_HDR_TYPE_POST; 253189251Ssam else if (word_eq(hbp, "PUT")) 254189251Ssam h->hdr_type = HTTPREAD_HDR_TYPE_PUT; 255189251Ssam else if (word_eq(hbp, "DELETE")) 256189251Ssam h->hdr_type = HTTPREAD_HDR_TYPE_DELETE; 257189251Ssam else if (word_eq(hbp, "TRACE")) 258189251Ssam h->hdr_type = HTTPREAD_HDR_TYPE_TRACE; 259189251Ssam else if (word_eq(hbp, "CONNECT")) 260189251Ssam h->hdr_type = HTTPREAD_HDR_TYPE_CONNECT; 261189251Ssam else if (word_eq(hbp, "NOTIFY")) 262189251Ssam h->hdr_type = HTTPREAD_HDR_TYPE_NOTIFY; 263189251Ssam else if (word_eq(hbp, "M-SEARCH")) 264189251Ssam h->hdr_type = HTTPREAD_HDR_TYPE_M_SEARCH; 265189251Ssam else if (word_eq(hbp, "M-POST")) 266189251Ssam h->hdr_type = HTTPREAD_HDR_TYPE_M_POST; 267189251Ssam else if (word_eq(hbp, "SUBSCRIBE")) 268189251Ssam h->hdr_type = HTTPREAD_HDR_TYPE_SUBSCRIBE; 269189251Ssam else if (word_eq(hbp, "UNSUBSCRIBE")) 270189251Ssam h->hdr_type = HTTPREAD_HDR_TYPE_UNSUBSCRIBE; 271189251Ssam else { 272189251Ssam } 273189251Ssam 274189251Ssam if (standard_first_line) { 275189251Ssam char *rawuri; 276189251Ssam char *uri; 277189251Ssam /* skip type */ 278189251Ssam while (isgraph(*hbp)) 279189251Ssam hbp++; 280189251Ssam while (*hbp == ' ' || *hbp == '\t') 281189251Ssam hbp++; 282189251Ssam /* parse uri. 283189251Ssam * Find length, allocate memory for translated 284189251Ssam * copy, then translate by changing %<hex><hex> 285189251Ssam * into represented value. 286189251Ssam */ 287189251Ssam rawuri = hbp; 288189251Ssam while (isgraph(*hbp)) 289189251Ssam hbp++; 290189251Ssam h->uri = os_malloc((hbp - rawuri) + 1); 291189251Ssam if (h->uri == NULL) 292189251Ssam goto bad; 293189251Ssam uri = h->uri; 294189251Ssam while (rawuri < hbp) { 295189251Ssam int c = *rawuri; 296189251Ssam if (c == '%' && 297189251Ssam isxdigit(rawuri[1]) && isxdigit(rawuri[2])) { 298189251Ssam *uri++ = (hex_value(rawuri[1]) << 4) | 299189251Ssam hex_value(rawuri[2]); 300189251Ssam rawuri += 3; 301189251Ssam } else { 302189251Ssam *uri++ = c; 303189251Ssam rawuri++; 304189251Ssam } 305189251Ssam } 306189251Ssam *uri = 0; /* null terminate */ 307189251Ssam while (isgraph(*hbp)) 308189251Ssam hbp++; 309189251Ssam while (*hbp == ' ' || *hbp == '\t') 310189251Ssam hbp++; 311189251Ssam /* get version */ 312189251Ssam if (0 == strncmp(hbp, "HTTP/", 5)) { 313189251Ssam hbp += 5; 314189251Ssam if (hbp[0] == '1' && hbp[1] == '.' && 315189251Ssam isdigit(hbp[2]) && hbp[2] != '0') 316189251Ssam h->version = 1; 317189251Ssam } 318189251Ssam } 319189251Ssam /* skip rest of line */ 320189251Ssam while (*hbp) 321189251Ssam if (*hbp++ == '\n') 322189251Ssam break; 323189251Ssam 324189251Ssam /* Remainder of lines are options, in any order; 325189251Ssam * or empty line to terminate 326189251Ssam */ 327189251Ssam for (;;) { 328189251Ssam /* Empty line to terminate */ 329189251Ssam if (hbp[0] == '\n' || 330189251Ssam (hbp[0] == '\r' && hbp[1] == '\n')) 331189251Ssam break; 332189251Ssam if (!isgraph(*hbp)) 333189251Ssam goto bad; 334189251Ssam if (httpread_hdr_option_analyze(h, hbp)) 335189251Ssam goto bad; 336189251Ssam /* skip line */ 337189251Ssam while (*hbp) 338189251Ssam if (*hbp++ == '\n') 339189251Ssam break; 340189251Ssam } 341189251Ssam 342189251Ssam /* chunked overrides content-length always */ 343189251Ssam if (h->chunked) 344189251Ssam h->got_content_length = 0; 345189251Ssam 346189251Ssam /* For some types, we should not try to read a body 347189251Ssam * This is in addition to the application determining 348189251Ssam * that we should not read a body. 349189251Ssam */ 350189251Ssam switch (h->hdr_type) { 351189251Ssam case HTTPREAD_HDR_TYPE_REPLY: 352189251Ssam /* Some codes can have a body and some not. 353189251Ssam * For now, just assume that any other than 200 354189251Ssam * do not... 355189251Ssam */ 356189251Ssam if (h->reply_code != 200) 357189251Ssam h->max_bytes = 0; 358189251Ssam break; 359189251Ssam case HTTPREAD_HDR_TYPE_GET: 360189251Ssam case HTTPREAD_HDR_TYPE_HEAD: 361189251Ssam /* in practice it appears that it is assumed 362189251Ssam * that GETs have a body length of 0... ? 363189251Ssam */ 364189251Ssam if (h->chunked == 0 && h->got_content_length == 0) 365189251Ssam h->max_bytes = 0; 366189251Ssam break; 367189251Ssam case HTTPREAD_HDR_TYPE_POST: 368189251Ssam case HTTPREAD_HDR_TYPE_PUT: 369189251Ssam case HTTPREAD_HDR_TYPE_DELETE: 370189251Ssam case HTTPREAD_HDR_TYPE_TRACE: 371189251Ssam case HTTPREAD_HDR_TYPE_CONNECT: 372189251Ssam case HTTPREAD_HDR_TYPE_NOTIFY: 373189251Ssam case HTTPREAD_HDR_TYPE_M_SEARCH: 374189251Ssam case HTTPREAD_HDR_TYPE_M_POST: 375189251Ssam case HTTPREAD_HDR_TYPE_SUBSCRIBE: 376189251Ssam case HTTPREAD_HDR_TYPE_UNSUBSCRIBE: 377189251Ssam default: 378189251Ssam break; 379189251Ssam } 380189251Ssam 381189251Ssam return 0; 382189251Ssam 383189251Ssambad: 384189251Ssam /* Error */ 385189251Ssam return -1; 386189251Ssam} 387189251Ssam 388189251Ssam 389189251Ssam/* httpread_read_handler -- called when socket ready to read 390189251Ssam * 391189251Ssam * Note: any extra data we read past end of transmitted file is ignored; 392189251Ssam * if we were to support keeping connections open for multiple files then 393189251Ssam * this would have to be addressed. 394189251Ssam */ 395189251Ssamstatic void httpread_read_handler(int sd, void *eloop_ctx, void *sock_ctx) 396189251Ssam{ 397189251Ssam struct httpread *h = sock_ctx; 398189251Ssam int nread; 399189251Ssam char *rbp; /* pointer into read buffer */ 400189251Ssam char *hbp; /* pointer into header buffer */ 401189251Ssam char *bbp; /* pointer into body buffer */ 402189251Ssam char readbuf[HTTPREAD_READBUF_SIZE]; /* temp use to read into */ 403189251Ssam 404189251Ssam if (httpread_debug >= 20) 405189251Ssam wpa_printf(MSG_DEBUG, "ENTER httpread_read_handler(%p)", h); 406189251Ssam 407189251Ssam /* read some at a time, then search for the interal 408189251Ssam * boundaries between header and data and etc. 409189251Ssam */ 410189251Ssam nread = read(h->sd, readbuf, sizeof(readbuf)); 411189251Ssam if (nread < 0) 412189251Ssam goto bad; 413189251Ssam if (nread == 0) { 414189251Ssam /* end of transmission... this may be normal 415189251Ssam * or may be an error... in some cases we can't 416189251Ssam * tell which so we must assume it is normal then. 417189251Ssam */ 418189251Ssam if (!h->got_hdr) { 419189251Ssam /* Must at least have completed header */ 420189251Ssam wpa_printf(MSG_DEBUG, "httpread premature eof(%p)", h); 421189251Ssam goto bad; 422189251Ssam } 423189251Ssam if (h->chunked || h->got_content_length) { 424189251Ssam /* Premature EOF; e.g. dropped connection */ 425189251Ssam wpa_printf(MSG_DEBUG, 426189251Ssam "httpread premature eof(%p) %d/%d", 427189251Ssam h, h->body_nbytes, 428189251Ssam h->content_length); 429189251Ssam goto bad; 430189251Ssam } 431189251Ssam /* No explicit length, hopefully we have all the data 432189251Ssam * although dropped connections can cause false 433189251Ssam * end 434189251Ssam */ 435189251Ssam if (httpread_debug >= 10) 436189251Ssam wpa_printf(MSG_DEBUG, "httpread ok eof(%p)", h); 437189251Ssam h->got_body = 1; 438189251Ssam goto got_file; 439189251Ssam } 440189251Ssam rbp = readbuf; 441189251Ssam 442189251Ssam /* Header consists of text lines (terminated by both CR and LF) 443189251Ssam * and an empty line (CR LF only). 444189251Ssam */ 445189251Ssam if (!h->got_hdr) { 446189251Ssam hbp = h->hdr + h->hdr_nbytes; 447189251Ssam /* add to headers until: 448189251Ssam * -- we run out of data in read buffer 449189251Ssam * -- or, we run out of header buffer room 450189251Ssam * -- or, we get double CRLF in headers 451189251Ssam */ 452189251Ssam for (;;) { 453189251Ssam if (nread == 0) 454189251Ssam goto get_more; 455189251Ssam if (h->hdr_nbytes == HTTPREAD_HEADER_MAX_SIZE) { 456189251Ssam goto bad; 457189251Ssam } 458189251Ssam *hbp++ = *rbp++; 459189251Ssam nread--; 460189251Ssam h->hdr_nbytes++; 461189251Ssam if (h->hdr_nbytes >= 4 && 462189251Ssam hbp[-1] == '\n' && 463189251Ssam hbp[-2] == '\r' && 464189251Ssam hbp[-3] == '\n' && 465189251Ssam hbp[-4] == '\r' ) { 466189251Ssam h->got_hdr = 1; 467189251Ssam *hbp = 0; /* null terminate */ 468189251Ssam break; 469189251Ssam } 470189251Ssam } 471189251Ssam /* here we've just finished reading the header */ 472189251Ssam if (httpread_hdr_analyze(h)) { 473189251Ssam wpa_printf(MSG_DEBUG, "httpread bad hdr(%p)", h); 474189251Ssam goto bad; 475189251Ssam } 476189251Ssam if (h->max_bytes == 0) { 477189251Ssam if (httpread_debug >= 10) 478189251Ssam wpa_printf(MSG_DEBUG, 479189251Ssam "httpread no body hdr end(%p)", h); 480189251Ssam goto got_file; 481189251Ssam } 482189251Ssam if (h->got_content_length && h->content_length == 0) { 483189251Ssam if (httpread_debug >= 10) 484189251Ssam wpa_printf(MSG_DEBUG, 485189251Ssam "httpread zero content length(%p)", 486189251Ssam h); 487189251Ssam goto got_file; 488189251Ssam } 489189251Ssam } 490189251Ssam 491189251Ssam /* Certain types of requests never have data and so 492189251Ssam * must be specially recognized. 493189251Ssam */ 494189251Ssam if (!os_strncasecmp(h->hdr, "SUBSCRIBE", 9) || 495189251Ssam !os_strncasecmp(h->hdr, "UNSUBSCRIBE", 11) || 496189251Ssam !os_strncasecmp(h->hdr, "HEAD", 4) || 497189251Ssam !os_strncasecmp(h->hdr, "GET", 3)) { 498189251Ssam if (!h->got_body) { 499189251Ssam if (httpread_debug >= 10) 500189251Ssam wpa_printf(MSG_DEBUG, 501189251Ssam "httpread NO BODY for sp. type"); 502189251Ssam } 503189251Ssam h->got_body = 1; 504189251Ssam goto got_file; 505189251Ssam } 506189251Ssam 507189251Ssam /* Data can be just plain binary data, or if "chunked" 508189251Ssam * consists of chunks each with a header, ending with 509189251Ssam * an ending header. 510189251Ssam */ 511209158Srpaulo if (nread == 0) 512209158Srpaulo goto get_more; 513189251Ssam if (!h->got_body) { 514189251Ssam /* Here to get (more of) body */ 515189251Ssam /* ensure we have enough room for worst case for body 516189251Ssam * plus a null termination character 517189251Ssam */ 518189251Ssam if (h->body_alloc_nbytes < (h->body_nbytes + nread + 1)) { 519189251Ssam char *new_body; 520189251Ssam int new_alloc_nbytes; 521189251Ssam 522189251Ssam if (h->body_nbytes >= h->max_bytes) 523189251Ssam goto bad; 524189251Ssam new_alloc_nbytes = h->body_alloc_nbytes + 525189251Ssam HTTPREAD_BODYBUF_DELTA; 526189251Ssam /* For content-length case, the first time 527189251Ssam * through we allocate the whole amount 528189251Ssam * we need. 529189251Ssam */ 530189251Ssam if (h->got_content_length && 531189251Ssam new_alloc_nbytes < (h->content_length + 1)) 532189251Ssam new_alloc_nbytes = h->content_length + 1; 533189251Ssam if ((new_body = os_realloc(h->body, new_alloc_nbytes)) 534189251Ssam == NULL) 535189251Ssam goto bad; 536189251Ssam 537189251Ssam h->body = new_body; 538189251Ssam h->body_alloc_nbytes = new_alloc_nbytes; 539189251Ssam } 540189251Ssam /* add bytes */ 541189251Ssam bbp = h->body + h->body_nbytes; 542189251Ssam for (;;) { 543189251Ssam int ncopy; 544189251Ssam /* See if we need to stop */ 545189251Ssam if (h->chunked && h->in_chunk_data == 0) { 546189251Ssam /* in chunk header */ 547189251Ssam char *cbp = h->body + h->chunk_start; 548189251Ssam if (bbp-cbp >= 2 && bbp[-2] == '\r' && 549189251Ssam bbp[-1] == '\n') { 550189251Ssam /* end of chunk hdr line */ 551189251Ssam /* hdr line consists solely 552189251Ssam * of a hex numeral and CFLF 553189251Ssam */ 554189251Ssam if (!isxdigit(*cbp)) 555189251Ssam goto bad; 556189251Ssam h->chunk_size = strtoul(cbp, NULL, 16); 557189251Ssam /* throw away chunk header 558189251Ssam * so we have only real data 559189251Ssam */ 560189251Ssam h->body_nbytes = h->chunk_start; 561189251Ssam bbp = cbp; 562189251Ssam if (h->chunk_size == 0) { 563189251Ssam /* end of chunking */ 564189251Ssam /* trailer follows */ 565189251Ssam h->in_trailer = 1; 566189251Ssam if (httpread_debug >= 20) 567189251Ssam wpa_printf( 568189251Ssam MSG_DEBUG, 569189251Ssam "httpread end chunks(%p)", h); 570189251Ssam break; 571189251Ssam } 572189251Ssam h->in_chunk_data = 1; 573189251Ssam /* leave chunk_start alone */ 574189251Ssam } 575189251Ssam } else if (h->chunked) { 576189251Ssam /* in chunk data */ 577189251Ssam if ((h->body_nbytes - h->chunk_start) == 578189251Ssam (h->chunk_size + 2)) { 579189251Ssam /* end of chunk reached, 580189251Ssam * new chunk starts 581189251Ssam */ 582189251Ssam /* check chunk ended w/ CRLF 583189251Ssam * which we'll throw away 584189251Ssam */ 585189251Ssam if (bbp[-1] == '\n' && 586189251Ssam bbp[-2] == '\r') { 587189251Ssam } else 588189251Ssam goto bad; 589189251Ssam h->body_nbytes -= 2; 590189251Ssam bbp -= 2; 591189251Ssam h->chunk_start = h->body_nbytes; 592189251Ssam h->in_chunk_data = 0; 593189251Ssam h->chunk_size = 0; /* just in case */ 594189251Ssam } 595189251Ssam } else if (h->got_content_length && 596189251Ssam h->body_nbytes >= h->content_length) { 597189251Ssam h->got_body = 1; 598189251Ssam if (httpread_debug >= 10) 599189251Ssam wpa_printf( 600189251Ssam MSG_DEBUG, 601189251Ssam "httpread got content(%p)", h); 602189251Ssam goto got_file; 603189251Ssam } 604189251Ssam if (nread <= 0) 605189251Ssam break; 606189251Ssam /* Now transfer. Optimize using memcpy where we can. */ 607189251Ssam if (h->chunked && h->in_chunk_data) { 608189251Ssam /* copy up to remainder of chunk data 609189251Ssam * plus the required CR+LF at end 610189251Ssam */ 611189251Ssam ncopy = (h->chunk_start + h->chunk_size + 2) - 612189251Ssam h->body_nbytes; 613189251Ssam } else if (h->chunked) { 614189251Ssam /*in chunk header -- don't optimize */ 615189251Ssam *bbp++ = *rbp++; 616189251Ssam nread--; 617189251Ssam h->body_nbytes++; 618189251Ssam continue; 619189251Ssam } else if (h->got_content_length) { 620189251Ssam ncopy = h->content_length - h->body_nbytes; 621189251Ssam } else { 622189251Ssam ncopy = nread; 623189251Ssam } 624189251Ssam /* Note: should never be 0 */ 625189251Ssam if (ncopy > nread) 626189251Ssam ncopy = nread; 627189251Ssam os_memcpy(bbp, rbp, ncopy); 628189251Ssam bbp += ncopy; 629189251Ssam h->body_nbytes += ncopy; 630189251Ssam rbp += ncopy; 631189251Ssam nread -= ncopy; 632189251Ssam } /* body copy loop */ 633189251Ssam } /* !got_body */ 634189251Ssam if (h->chunked && h->in_trailer) { 635189251Ssam /* If "chunked" then there is always a trailer, 636189251Ssam * consisting of zero or more non-empty lines 637189251Ssam * ending with CR LF and then an empty line w/ CR LF. 638189251Ssam * We do NOT support trailers except to skip them -- 639189251Ssam * this is supported (generally) by the http spec. 640189251Ssam */ 641189251Ssam bbp = h->body + h->body_nbytes; 642189251Ssam for (;;) { 643189251Ssam int c; 644189251Ssam if (nread <= 0) 645189251Ssam break; 646189251Ssam c = *rbp++; 647189251Ssam nread--; 648189251Ssam switch (h->trailer_state) { 649189251Ssam case trailer_line_begin: 650189251Ssam if (c == '\r') 651189251Ssam h->trailer_state = trailer_empty_cr; 652189251Ssam else 653189251Ssam h->trailer_state = trailer_nonempty; 654189251Ssam break; 655189251Ssam case trailer_empty_cr: 656189251Ssam /* end empty line */ 657189251Ssam if (c == '\n') { 658189251Ssam h->trailer_state = trailer_line_begin; 659189251Ssam h->in_trailer = 0; 660189251Ssam if (httpread_debug >= 10) 661189251Ssam wpa_printf( 662189251Ssam MSG_DEBUG, 663189251Ssam "httpread got content(%p)", h); 664189251Ssam h->got_body = 1; 665189251Ssam goto got_file; 666189251Ssam } 667189251Ssam h->trailer_state = trailer_nonempty; 668189251Ssam break; 669189251Ssam case trailer_nonempty: 670189251Ssam if (c == '\r') 671189251Ssam h->trailer_state = trailer_nonempty_cr; 672189251Ssam break; 673189251Ssam case trailer_nonempty_cr: 674189251Ssam if (c == '\n') 675189251Ssam h->trailer_state = trailer_line_begin; 676189251Ssam else 677189251Ssam h->trailer_state = trailer_nonempty; 678189251Ssam break; 679189251Ssam } 680189251Ssam } 681189251Ssam } 682189251Ssam goto get_more; 683189251Ssam 684189251Ssambad: 685189251Ssam /* Error */ 686189251Ssam wpa_printf(MSG_DEBUG, "httpread read/parse failure (%p)", h); 687189251Ssam (*h->cb)(h, h->cookie, HTTPREAD_EVENT_ERROR); 688189251Ssam return; 689189251Ssam 690189251Ssamget_more: 691189251Ssam return; 692189251Ssam 693189251Ssamgot_file: 694189251Ssam if (httpread_debug >= 10) 695189251Ssam wpa_printf(MSG_DEBUG, 696189251Ssam "httpread got file %d bytes type %d", 697189251Ssam h->body_nbytes, h->hdr_type); 698189251Ssam /* Null terminate for convenience of some applications */ 699189251Ssam if (h->body) 700189251Ssam h->body[h->body_nbytes] = 0; /* null terminate */ 701189251Ssam h->got_file = 1; 702189251Ssam /* Assume that we do NOT support keeping connection alive, 703189251Ssam * and just in case somehow we don't get destroyed right away, 704189251Ssam * unregister now. 705189251Ssam */ 706189251Ssam if (h->sd_registered) 707189251Ssam eloop_unregister_sock(h->sd, EVENT_TYPE_READ); 708189251Ssam h->sd_registered = 0; 709189251Ssam /* The application can destroy us whenever they feel like... 710189251Ssam * cancel timeout. 711189251Ssam */ 712189251Ssam if (h->to_registered) 713189251Ssam eloop_cancel_timeout(httpread_timeout_handler, NULL, h); 714189251Ssam h->to_registered = 0; 715189251Ssam (*h->cb)(h, h->cookie, HTTPREAD_EVENT_FILE_READY); 716189251Ssam} 717189251Ssam 718189251Ssam 719189251Ssam/* httpread_create -- start a new reading session making use of eloop. 720189251Ssam * The new instance will use the socket descriptor for reading (until 721189251Ssam * it gets a file and not after) but will not close the socket, even 722189251Ssam * when the instance is destroyed (the application must do that). 723189251Ssam * Return NULL on error. 724189251Ssam * 725189251Ssam * Provided that httpread_create successfully returns a handle, 726189251Ssam * the callback fnc is called to handle httpread_event events. 727189251Ssam * The caller should do destroy on any errors or unknown events. 728189251Ssam * 729189251Ssam * Pass max_bytes == 0 to not read body at all (required for e.g. 730189251Ssam * reply to HEAD request). 731189251Ssam */ 732189251Ssamstruct httpread * httpread_create( 733189251Ssam int sd, /* descriptor of TCP socket to read from */ 734189251Ssam void (*cb)(struct httpread *handle, void *cookie, 735189251Ssam enum httpread_event e), /* call on event */ 736189251Ssam void *cookie, /* pass to callback */ 737189251Ssam int max_bytes, /* maximum body size else abort it */ 738189251Ssam int timeout_seconds /* 0; or total duration timeout period */ 739189251Ssam ) 740189251Ssam{ 741189251Ssam struct httpread *h = NULL; 742189251Ssam 743189251Ssam h = os_zalloc(sizeof(*h)); 744189251Ssam if (h == NULL) 745189251Ssam goto fail; 746189251Ssam h->sd = sd; 747189251Ssam h->cb = cb; 748189251Ssam h->cookie = cookie; 749189251Ssam h->max_bytes = max_bytes; 750189251Ssam h->timeout_seconds = timeout_seconds; 751189251Ssam 752189251Ssam if (timeout_seconds > 0) { 753189251Ssam if (eloop_register_timeout(timeout_seconds, 0, 754189251Ssam httpread_timeout_handler, 755189251Ssam NULL, h)) { 756189251Ssam /* No way to recover (from malloc failure) */ 757189251Ssam goto fail; 758189251Ssam } 759189251Ssam h->to_registered = 1; 760189251Ssam } 761189251Ssam if (eloop_register_sock(sd, EVENT_TYPE_READ, httpread_read_handler, 762189251Ssam NULL, h)) { 763189251Ssam /* No way to recover (from malloc failure) */ 764189251Ssam goto fail; 765189251Ssam } 766189251Ssam h->sd_registered = 1; 767189251Ssam return h; 768189251Ssam 769189251Ssamfail: 770189251Ssam 771189251Ssam /* Error */ 772189251Ssam httpread_destroy(h); 773189251Ssam return NULL; 774189251Ssam} 775189251Ssam 776189251Ssam 777189251Ssam/* httpread_hdr_type_get -- When file is ready, returns header type. */ 778189251Ssamenum httpread_hdr_type httpread_hdr_type_get(struct httpread *h) 779189251Ssam{ 780189251Ssam return h->hdr_type; 781189251Ssam} 782189251Ssam 783189251Ssam 784189251Ssam/* httpread_uri_get -- When file is ready, uri_get returns (translated) URI 785189251Ssam * or possibly NULL (which would be an error). 786189251Ssam */ 787189251Ssamchar * httpread_uri_get(struct httpread *h) 788189251Ssam{ 789189251Ssam return h->uri; 790189251Ssam} 791189251Ssam 792189251Ssam 793189251Ssam/* httpread_reply_code_get -- When reply is ready, returns reply code */ 794189251Ssamint httpread_reply_code_get(struct httpread *h) 795189251Ssam{ 796189251Ssam return h->reply_code; 797189251Ssam} 798189251Ssam 799189251Ssam 800189251Ssam/* httpread_length_get -- When file is ready, returns file length. */ 801189251Ssamint httpread_length_get(struct httpread *h) 802189251Ssam{ 803189251Ssam return h->body_nbytes; 804189251Ssam} 805189251Ssam 806189251Ssam 807189251Ssam/* httpread_data_get -- When file is ready, returns file content 808189251Ssam * with null byte appened. 809189251Ssam * Might return NULL in some error condition. 810189251Ssam */ 811189251Ssamvoid * httpread_data_get(struct httpread *h) 812189251Ssam{ 813189251Ssam return h->body ? h->body : ""; 814189251Ssam} 815189251Ssam 816189251Ssam 817189251Ssam/* httpread_hdr_get -- When file is ready, returns header content 818189251Ssam * with null byte appended. 819189251Ssam * Might return NULL in some error condition. 820189251Ssam */ 821189251Ssamchar * httpread_hdr_get(struct httpread *h) 822189251Ssam{ 823189251Ssam return h->hdr; 824189251Ssam} 825189251Ssam 826189251Ssam 827189251Ssam/* httpread_hdr_line_get -- When file is ready, returns pointer 828189251Ssam * to line within header content matching the given tag 829189251Ssam * (after the tag itself and any spaces/tabs). 830189251Ssam * 831189251Ssam * The tag should end with a colon for reliable matching. 832189251Ssam * 833189251Ssam * If not found, returns NULL; 834189251Ssam */ 835189251Ssamchar * httpread_hdr_line_get(struct httpread *h, const char *tag) 836189251Ssam{ 837189251Ssam int tag_len = os_strlen(tag); 838189251Ssam char *hdr = h->hdr; 839189251Ssam hdr = os_strchr(hdr, '\n'); 840189251Ssam if (hdr == NULL) 841189251Ssam return NULL; 842189251Ssam hdr++; 843189251Ssam for (;;) { 844189251Ssam if (!os_strncasecmp(hdr, tag, tag_len)) { 845189251Ssam hdr += tag_len; 846189251Ssam while (*hdr == ' ' || *hdr == '\t') 847189251Ssam hdr++; 848189251Ssam return hdr; 849189251Ssam } 850189251Ssam hdr = os_strchr(hdr, '\n'); 851189251Ssam if (hdr == NULL) 852189251Ssam return NULL; 853189251Ssam hdr++; 854189251Ssam } 855189251Ssam} 856