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