1/*
2 * httpread - Manage reading file(s) from HTTP/TCP socket
3 * Author: Ted Merrill
4 * Copyright 2008 Atheros Communications
5 *
6 * This software may be distributed under the terms of the BSD license.
7 * See README for more details.
8 *
9 * The files are buffered via internal callbacks from eloop, then presented to
10 * an application callback routine when completely read into memory. May also
11 * be used if no file is expected but just to get the header, including HTTP
12 * replies (e.g. HTTP/1.1 200 OK etc.).
13 *
14 * This does not attempt to be an optimally efficient implementation, but does
15 * attempt to be of reasonably small size and memory consumption; assuming that
16 * only small files are to be read. A maximum file size is provided by
17 * application and enforced.
18 *
19 * It is assumed that the application does not expect any of the following:
20 * -- transfer encoding other than chunked
21 * -- trailer fields
22 * It is assumed that, even if the other side requested that the connection be
23 * kept open, that we will close it (thus HTTP messages sent by application
24 * should have the connection closed field); this is allowed by HTTP/1.1 and
25 * simplifies things for us.
26 *
27 * Other limitations:
28 * -- HTTP header may not exceed a hard-coded size.
29 *
30 * Notes:
31 * This code would be massively simpler without some of the new features of
32 * HTTP/1.1, especially chunked data.
33 */
34
35#include "includes.h"
36
37#include "common.h"
38#include "eloop.h"
39#include "httpread.h"
40
41
42/* Tunable parameters */
43#define HTTPREAD_READBUF_SIZE 1024      /* read in chunks of this size */
44#define HTTPREAD_HEADER_MAX_SIZE 4096   /* max allowed for headers */
45#define HTTPREAD_BODYBUF_DELTA 4096     /* increase allocation by this */
46
47#if 0
48/* httpread_debug -- set this global variable > 0 e.g. from debugger
49 * to enable debugs (larger numbers for more debugs)
50 * Make this a #define of 0 to eliminate the debugging code.
51 */
52int httpread_debug = 99;
53#else
54#define httpread_debug 0        /* eliminates even the debugging code */
55#endif
56
57
58/* control instance -- actual definition (opaque to application)
59 */
60struct httpread {
61	/* information from creation */
62	int sd;         /* descriptor of TCP socket to read from */
63	void (*cb)(struct httpread *handle, void *cookie,
64		    enum httpread_event e);  /* call on event */
65	void *cookie;   /* pass to callback */
66	int max_bytes;          /* maximum file size else abort it */
67	int timeout_seconds;            /* 0 or total duration timeout period */
68
69	/* dynamically used information follows */
70
71	int got_hdr;            /* nonzero when header is finalized */
72	char hdr[HTTPREAD_HEADER_MAX_SIZE+1];   /* headers stored here */
73	int hdr_nbytes;
74
75	enum httpread_hdr_type hdr_type;
76	int version;            /* 1 if we've seen 1.1 */
77	int reply_code;         /* for type REPLY, e.g. 200 for HTTP/1.1 200 OK */
78	int got_content_length; /* true if we know content length for sure */
79	int content_length;     /* body length,  iff got_content_length */
80	int chunked;            /* nonzero for chunked data */
81	char *uri;
82
83	int got_body;           /* nonzero when body is finalized */
84	char *body;
85	int body_nbytes;
86	int body_alloc_nbytes;  /* amount allocated */
87
88	int got_file;           /* here when we are done */
89
90	/* The following apply if data is chunked: */
91	int in_chunk_data;      /* 0=in/at header, 1=in the data or tail*/
92	int chunk_start;        /* offset in body of chunk hdr or data */
93	int chunk_size;         /* data of chunk (not hdr or ending CRLF)*/
94	int in_trailer;         /* in header fields after data (chunked only)*/
95	enum trailer_state {
96		trailer_line_begin = 0,
97		trailer_empty_cr,       /* empty line + CR */
98		trailer_nonempty,
99		trailer_nonempty_cr,
100	} trailer_state;
101};
102
103
104/* Check words for equality, where words consist of graphical characters
105 * delimited by whitespace
106 * Returns nonzero if "equal" doing case insensitive comparison.
107 */
108static int word_eq(char *s1, char *s2)
109{
110	int c1;
111	int c2;
112	int end1 = 0;
113	int end2 = 0;
114	for (;;) {
115		c1 = *s1++;
116		c2 = *s2++;
117		if (isalpha(c1) && isupper(c1))
118			c1 = tolower(c1);
119		if (isalpha(c2) && isupper(c2))
120			c2 = tolower(c2);
121		end1 = !isgraph(c1);
122		end2 = !isgraph(c2);
123		if (end1 || end2 || c1 != c2)
124			break;
125	}
126	return end1 && end2;  /* reached end of both words? */
127}
128
129
130static void httpread_timeout_handler(void *eloop_data, void *user_ctx);
131
132/* httpread_destroy -- if h is non-NULL, clean up
133 * This must eventually be called by the application following
134 * call of the application's callback and may be called
135 * earlier if desired.
136 */
137void httpread_destroy(struct httpread *h)
138{
139	if (httpread_debug >= 10)
140		wpa_printf(MSG_DEBUG, "ENTER httpread_destroy(%p)", h);
141	if (!h)
142		return;
143
144	eloop_cancel_timeout(httpread_timeout_handler, NULL, h);
145	eloop_unregister_sock(h->sd, EVENT_TYPE_READ);
146	os_free(h->body);
147	os_free(h->uri);
148	os_memset(h, 0, sizeof(*h));  /* aid debugging */
149	h->sd = -1;     /* aid debugging */
150	os_free(h);
151}
152
153
154/* httpread_timeout_handler -- called on excessive total duration
155 */
156static void httpread_timeout_handler(void *eloop_data, void *user_ctx)
157{
158	struct httpread *h = user_ctx;
159	wpa_printf(MSG_DEBUG, "httpread timeout (%p)", h);
160	(*h->cb)(h, h->cookie, HTTPREAD_EVENT_TIMEOUT);
161}
162
163
164/* Analyze options only so far as is needed to correctly obtain the file.
165 * The application can look at the raw header to find other options.
166 */
167static int httpread_hdr_option_analyze(
168	struct httpread *h,
169	char *hbp       /* pointer to current line in header buffer */
170	)
171{
172	if (word_eq(hbp, "CONTENT-LENGTH:")) {
173		while (isgraph(*hbp))
174			hbp++;
175		while (*hbp == ' ' || *hbp == '\t')
176			hbp++;
177		if (!isdigit(*hbp))
178			return -1;
179		h->content_length = atol(hbp);
180		h->got_content_length = 1;
181		return 0;
182	}
183	if (word_eq(hbp, "TRANSFER_ENCODING:") ||
184	    word_eq(hbp, "TRANSFER-ENCODING:")) {
185		while (isgraph(*hbp))
186			hbp++;
187		while (*hbp == ' ' || *hbp == '\t')
188			hbp++;
189		/* There should (?) be no encodings of interest
190		 * other than chunked...
191		 */
192		if (word_eq(hbp, "CHUNKED")) {
193			h->chunked = 1;
194			h->in_chunk_data = 0;
195			/* ignore possible ;<parameters> */
196		}
197		return 0;
198	}
199	/* skip anything we don't know, which is a lot */
200	return 0;
201}
202
203
204static int httpread_hdr_analyze(struct httpread *h)
205{
206	char *hbp = h->hdr;      /* pointer into h->hdr */
207	int standard_first_line = 1;
208
209	/* First line is special */
210	h->hdr_type = HTTPREAD_HDR_TYPE_UNKNOWN;
211	if (!isgraph(*hbp))
212		goto bad;
213	if (os_strncmp(hbp, "HTTP/", 5) == 0) {
214		h->hdr_type = HTTPREAD_HDR_TYPE_REPLY;
215		standard_first_line = 0;
216		hbp += 5;
217		if (hbp[0] == '1' && hbp[1] == '.' &&
218		    isdigit(hbp[2]) && hbp[2] != '0')
219			h->version = 1;
220		while (isgraph(*hbp))
221			hbp++;
222		while (*hbp == ' ' || *hbp == '\t')
223			hbp++;
224		if (!isdigit(*hbp))
225			goto bad;
226		h->reply_code = atol(hbp);
227	} else if (word_eq(hbp, "GET"))
228		h->hdr_type = HTTPREAD_HDR_TYPE_GET;
229	else if (word_eq(hbp, "HEAD"))
230		h->hdr_type = HTTPREAD_HDR_TYPE_HEAD;
231	else if (word_eq(hbp, "POST"))
232		h->hdr_type = HTTPREAD_HDR_TYPE_POST;
233	else if (word_eq(hbp, "PUT"))
234		h->hdr_type = HTTPREAD_HDR_TYPE_PUT;
235	else if (word_eq(hbp, "DELETE"))
236		h->hdr_type = HTTPREAD_HDR_TYPE_DELETE;
237	else if (word_eq(hbp, "TRACE"))
238		h->hdr_type = HTTPREAD_HDR_TYPE_TRACE;
239	else if (word_eq(hbp, "CONNECT"))
240		h->hdr_type = HTTPREAD_HDR_TYPE_CONNECT;
241	else if (word_eq(hbp, "NOTIFY"))
242		h->hdr_type = HTTPREAD_HDR_TYPE_NOTIFY;
243	else if (word_eq(hbp, "M-SEARCH"))
244		h->hdr_type = HTTPREAD_HDR_TYPE_M_SEARCH;
245	else if (word_eq(hbp, "M-POST"))
246		h->hdr_type = HTTPREAD_HDR_TYPE_M_POST;
247	else if (word_eq(hbp, "SUBSCRIBE"))
248		h->hdr_type = HTTPREAD_HDR_TYPE_SUBSCRIBE;
249	else if (word_eq(hbp, "UNSUBSCRIBE"))
250		h->hdr_type = HTTPREAD_HDR_TYPE_UNSUBSCRIBE;
251	else {
252	}
253
254	if (standard_first_line) {
255		char *rawuri;
256		char *uri;
257		/* skip type */
258		while (isgraph(*hbp))
259			hbp++;
260		while (*hbp == ' ' || *hbp == '\t')
261			hbp++;
262		/* parse uri.
263		 * Find length, allocate memory for translated
264		 * copy, then translate by changing %<hex><hex>
265		 * into represented value.
266		 */
267		rawuri = hbp;
268		while (isgraph(*hbp))
269			hbp++;
270		h->uri = os_malloc((hbp - rawuri) + 1);
271		if (h->uri == NULL)
272			goto bad;
273		uri = h->uri;
274		while (rawuri < hbp) {
275			int c = *rawuri;
276			if (c == '%' &&
277			    isxdigit(rawuri[1]) && isxdigit(rawuri[2])) {
278				*uri++ = hex2byte(rawuri + 1);
279				rawuri += 3;
280			} else {
281				*uri++ = c;
282				rawuri++;
283			}
284		}
285		*uri = 0;       /* null terminate */
286		while (isgraph(*hbp))
287			hbp++;
288		while (*hbp == ' ' || *hbp == '\t')
289			hbp++;
290		/* get version */
291		if (0 == strncmp(hbp, "HTTP/", 5)) {
292			hbp += 5;
293			if (hbp[0] == '1' && hbp[1] == '.' &&
294			    isdigit(hbp[2]) && hbp[2] != '0')
295				h->version = 1;
296		}
297	}
298	/* skip rest of line */
299	while (*hbp)
300		if (*hbp++ == '\n')
301			break;
302
303	/* Remainder of lines are options, in any order;
304	 * or empty line to terminate
305	 */
306	for (;;) {
307		/* Empty line to terminate */
308		if (hbp[0] == '\n' ||
309		    (hbp[0] == '\r' && hbp[1] == '\n'))
310			break;
311		if (!isgraph(*hbp))
312			goto bad;
313		if (httpread_hdr_option_analyze(h, hbp))
314			goto bad;
315		/* skip line */
316		while (*hbp)
317			if (*hbp++ == '\n')
318				break;
319	}
320
321	/* chunked overrides content-length always */
322	if (h->chunked)
323		h->got_content_length = 0;
324
325	/* For some types, we should not try to read a body
326	 * This is in addition to the application determining
327	 * that we should not read a body.
328	 */
329	switch (h->hdr_type) {
330	case HTTPREAD_HDR_TYPE_REPLY:
331		/* Some codes can have a body and some not.
332		 * For now, just assume that any other than 200
333		 * do not...
334		 */
335		if (h->reply_code != 200)
336			h->max_bytes = 0;
337		break;
338	case HTTPREAD_HDR_TYPE_GET:
339	case HTTPREAD_HDR_TYPE_HEAD:
340		/* in practice it appears that it is assumed
341		 * that GETs have a body length of 0... ?
342		 */
343		if (h->chunked == 0 && h->got_content_length == 0)
344			h->max_bytes = 0;
345		break;
346	case HTTPREAD_HDR_TYPE_POST:
347	case HTTPREAD_HDR_TYPE_PUT:
348	case HTTPREAD_HDR_TYPE_DELETE:
349	case HTTPREAD_HDR_TYPE_TRACE:
350	case HTTPREAD_HDR_TYPE_CONNECT:
351	case HTTPREAD_HDR_TYPE_NOTIFY:
352	case HTTPREAD_HDR_TYPE_M_SEARCH:
353	case HTTPREAD_HDR_TYPE_M_POST:
354	case HTTPREAD_HDR_TYPE_SUBSCRIBE:
355	case HTTPREAD_HDR_TYPE_UNSUBSCRIBE:
356	default:
357		break;
358	}
359
360	return 0;
361
362bad:
363	/* Error */
364	return -1;
365}
366
367
368/* httpread_read_handler -- called when socket ready to read
369 *
370 * Note: any extra data we read past end of transmitted file is ignored;
371 * if we were to support keeping connections open for multiple files then
372 * this would have to be addressed.
373 */
374static void httpread_read_handler(int sd, void *eloop_ctx, void *sock_ctx)
375{
376	struct httpread *h = sock_ctx;
377	int nread;
378	char *rbp;      /* pointer into read buffer */
379	char *hbp;      /* pointer into header buffer */
380	char *bbp;      /* pointer into body buffer */
381	char readbuf[HTTPREAD_READBUF_SIZE];  /* temp use to read into */
382
383	if (httpread_debug >= 20)
384		wpa_printf(MSG_DEBUG, "ENTER httpread_read_handler(%p)", h);
385
386	/* read some at a time, then search for the interal
387	 * boundaries between header and data and etc.
388	 */
389	nread = read(h->sd, readbuf, sizeof(readbuf));
390	if (nread < 0)
391		goto bad;
392	if (nread == 0) {
393		/* end of transmission... this may be normal
394		 * or may be an error... in some cases we can't
395		 * tell which so we must assume it is normal then.
396		 */
397		if (!h->got_hdr) {
398			/* Must at least have completed header */
399			wpa_printf(MSG_DEBUG, "httpread premature eof(%p)", h);
400			goto bad;
401		}
402		if (h->chunked || h->got_content_length) {
403			/* Premature EOF; e.g. dropped connection */
404			wpa_printf(MSG_DEBUG,
405				   "httpread premature eof(%p) %d/%d",
406				   h, h->body_nbytes,
407				   h->content_length);
408			goto bad;
409		}
410		/* No explicit length, hopefully we have all the data
411		 * although dropped connections can cause false
412		 * end
413		 */
414		if (httpread_debug >= 10)
415			wpa_printf(MSG_DEBUG, "httpread ok eof(%p)", h);
416		h->got_body = 1;
417		goto got_file;
418	}
419	rbp = readbuf;
420
421	/* Header consists of text lines (terminated by both CR and LF)
422	 * and an empty line (CR LF only).
423	 */
424	if (!h->got_hdr) {
425		hbp = h->hdr + h->hdr_nbytes;
426		/* add to headers until:
427		 *      -- we run out of data in read buffer
428		 *      -- or, we run out of header buffer room
429		 *      -- or, we get double CRLF in headers
430		 */
431		for (;;) {
432			if (nread == 0)
433				goto get_more;
434			if (h->hdr_nbytes == HTTPREAD_HEADER_MAX_SIZE) {
435				goto bad;
436			}
437			*hbp++ = *rbp++;
438			nread--;
439			h->hdr_nbytes++;
440			if (h->hdr_nbytes >= 4 &&
441			    hbp[-1] == '\n' &&
442			    hbp[-2] == '\r' &&
443			    hbp[-3] == '\n' &&
444			    hbp[-4] == '\r' ) {
445				h->got_hdr = 1;
446				*hbp = 0;       /* null terminate */
447				break;
448			}
449		}
450		/* here we've just finished reading the header */
451		if (httpread_hdr_analyze(h)) {
452			wpa_printf(MSG_DEBUG, "httpread bad hdr(%p)", h);
453			goto bad;
454		}
455		if (h->max_bytes == 0) {
456			if (httpread_debug >= 10)
457				wpa_printf(MSG_DEBUG,
458					   "httpread no body hdr end(%p)", h);
459			goto got_file;
460		}
461		if (h->got_content_length && h->content_length == 0) {
462			if (httpread_debug >= 10)
463				wpa_printf(MSG_DEBUG,
464					   "httpread zero content length(%p)",
465					   h);
466			goto got_file;
467		}
468	}
469
470	/* Certain types of requests never have data and so
471	 * must be specially recognized.
472	 */
473	if (!os_strncasecmp(h->hdr, "SUBSCRIBE", 9) ||
474	    !os_strncasecmp(h->hdr, "UNSUBSCRIBE", 11) ||
475	    !os_strncasecmp(h->hdr, "HEAD", 4) ||
476	    !os_strncasecmp(h->hdr, "GET", 3)) {
477		if (!h->got_body) {
478			if (httpread_debug >= 10)
479				wpa_printf(MSG_DEBUG,
480					   "httpread NO BODY for sp. type");
481		}
482		h->got_body = 1;
483		goto got_file;
484	}
485
486	/* Data can be just plain binary data, or if "chunked"
487	 * consists of chunks each with a header, ending with
488	 * an ending header.
489	 */
490	if (nread == 0)
491		goto get_more;
492	if (!h->got_body) {
493		/* Here to get (more of) body */
494		/* ensure we have enough room for worst case for body
495		 * plus a null termination character
496		 */
497		if (h->body_alloc_nbytes < (h->body_nbytes + nread + 1)) {
498			char *new_body;
499			int new_alloc_nbytes;
500
501			if (h->body_nbytes >= h->max_bytes)
502				goto bad;
503			new_alloc_nbytes = h->body_alloc_nbytes +
504				HTTPREAD_BODYBUF_DELTA;
505			/* For content-length case, the first time
506			 * through we allocate the whole amount
507			 * we need.
508			 */
509			if (h->got_content_length &&
510			    new_alloc_nbytes < (h->content_length + 1))
511				new_alloc_nbytes = h->content_length + 1;
512			if ((new_body = os_realloc(h->body, new_alloc_nbytes))
513			    == NULL)
514				goto bad;
515
516			h->body = new_body;
517			h->body_alloc_nbytes = new_alloc_nbytes;
518		}
519		/* add bytes */
520		bbp = h->body + h->body_nbytes;
521		for (;;) {
522			int ncopy;
523			/* See if we need to stop */
524			if (h->chunked && h->in_chunk_data == 0) {
525				/* in chunk header */
526				char *cbp = h->body + h->chunk_start;
527				if (bbp-cbp >= 2 && bbp[-2] == '\r' &&
528				    bbp[-1] == '\n') {
529					/* end of chunk hdr line */
530					/* hdr line consists solely
531					 * of a hex numeral and CFLF
532					 */
533					if (!isxdigit(*cbp))
534						goto bad;
535					h->chunk_size = strtoul(cbp, NULL, 16);
536					/* throw away chunk header
537					 * so we have only real data
538					 */
539					h->body_nbytes = h->chunk_start;
540					bbp = cbp;
541					if (h->chunk_size == 0) {
542						/* end of chunking */
543						/* trailer follows */
544						h->in_trailer = 1;
545						if (httpread_debug >= 20)
546							wpa_printf(
547								MSG_DEBUG,
548								"httpread end chunks(%p)", h);
549						break;
550					}
551					h->in_chunk_data = 1;
552					/* leave chunk_start alone */
553				}
554			} else if (h->chunked) {
555				/* in chunk data */
556				if ((h->body_nbytes - h->chunk_start) ==
557				    (h->chunk_size + 2)) {
558					/* end of chunk reached,
559					 * new chunk starts
560					 */
561					/* check chunk ended w/ CRLF
562					 * which we'll throw away
563					 */
564					if (bbp[-1] == '\n' &&
565					    bbp[-2] == '\r') {
566					} else
567						goto bad;
568					h->body_nbytes -= 2;
569					bbp -= 2;
570					h->chunk_start = h->body_nbytes;
571					h->in_chunk_data = 0;
572					h->chunk_size = 0; /* just in case */
573				}
574			} else if (h->got_content_length &&
575				   h->body_nbytes >= h->content_length) {
576				h->got_body = 1;
577				if (httpread_debug >= 10)
578					wpa_printf(
579						MSG_DEBUG,
580						"httpread got content(%p)", h);
581				goto got_file;
582			}
583			if (nread <= 0)
584				break;
585			/* Now transfer. Optimize using memcpy where we can. */
586			if (h->chunked && h->in_chunk_data) {
587				/* copy up to remainder of chunk data
588				 * plus the required CR+LF at end
589				 */
590				ncopy = (h->chunk_start + h->chunk_size + 2) -
591					h->body_nbytes;
592			} else if (h->chunked) {
593				/*in chunk header -- don't optimize */
594				*bbp++ = *rbp++;
595				nread--;
596				h->body_nbytes++;
597				continue;
598			} else if (h->got_content_length) {
599				ncopy = h->content_length - h->body_nbytes;
600			} else {
601				ncopy = nread;
602			}
603			/* Note: should never be 0 */
604			if (ncopy > nread)
605				ncopy = nread;
606			os_memcpy(bbp, rbp, ncopy);
607			bbp += ncopy;
608			h->body_nbytes += ncopy;
609			rbp += ncopy;
610			nread -= ncopy;
611		}       /* body copy loop */
612	}       /* !got_body */
613	if (h->chunked && h->in_trailer) {
614		/* If "chunked" then there is always a trailer,
615		 * consisting of zero or more non-empty lines
616		 * ending with CR LF and then an empty line w/ CR LF.
617		 * We do NOT support trailers except to skip them --
618		 * this is supported (generally) by the http spec.
619		 */
620		for (;;) {
621			int c;
622			if (nread <= 0)
623				break;
624			c = *rbp++;
625			nread--;
626			switch (h->trailer_state) {
627			case trailer_line_begin:
628				if (c == '\r')
629					h->trailer_state = trailer_empty_cr;
630				else
631					h->trailer_state = trailer_nonempty;
632				break;
633			case trailer_empty_cr:
634				/* end empty line */
635				if (c == '\n') {
636					h->trailer_state = trailer_line_begin;
637					h->in_trailer = 0;
638					if (httpread_debug >= 10)
639						wpa_printf(
640							MSG_DEBUG,
641							"httpread got content(%p)", h);
642					h->got_body = 1;
643					goto got_file;
644				}
645				h->trailer_state = trailer_nonempty;
646				break;
647			case trailer_nonempty:
648				if (c == '\r')
649					h->trailer_state = trailer_nonempty_cr;
650				break;
651			case trailer_nonempty_cr:
652				if (c == '\n')
653					h->trailer_state = trailer_line_begin;
654				else
655					h->trailer_state = trailer_nonempty;
656				break;
657			}
658		}
659	}
660	goto get_more;
661
662bad:
663	/* Error */
664	wpa_printf(MSG_DEBUG, "httpread read/parse failure (%p)", h);
665	(*h->cb)(h, h->cookie, HTTPREAD_EVENT_ERROR);
666	return;
667
668get_more:
669	return;
670
671got_file:
672	if (httpread_debug >= 10)
673		wpa_printf(MSG_DEBUG,
674			   "httpread got file %d bytes type %d",
675			   h->body_nbytes, h->hdr_type);
676	/* Null terminate for convenience of some applications */
677	if (h->body)
678		h->body[h->body_nbytes] = 0; /* null terminate */
679	h->got_file = 1;
680	/* Assume that we do NOT support keeping connection alive,
681	 * and just in case somehow we don't get destroyed right away,
682	 * unregister now.
683	 */
684	eloop_unregister_sock(h->sd, EVENT_TYPE_READ);
685	/* The application can destroy us whenever they feel like...
686	 * cancel timeout.
687	 */
688	eloop_cancel_timeout(httpread_timeout_handler, NULL, h);
689	(*h->cb)(h, h->cookie, HTTPREAD_EVENT_FILE_READY);
690}
691
692
693/* httpread_create -- start a new reading session making use of eloop.
694 * The new instance will use the socket descriptor for reading (until
695 * it gets a file and not after) but will not close the socket, even
696 * when the instance is destroyed (the application must do that).
697 * Return NULL on error.
698 *
699 * Provided that httpread_create successfully returns a handle,
700 * the callback fnc is called to handle httpread_event events.
701 * The caller should do destroy on any errors or unknown events.
702 *
703 * Pass max_bytes == 0 to not read body at all (required for e.g.
704 * reply to HEAD request).
705 */
706struct httpread * httpread_create(
707	int sd,	 /* descriptor of TCP socket to read from */
708	void (*cb)(struct httpread *handle, void *cookie,
709		   enum httpread_event e),  /* call on event */
710	void *cookie,    /* pass to callback */
711	int max_bytes,	  /* maximum body size else abort it */
712	int timeout_seconds     /* 0; or total duration timeout period */
713	)
714{
715	struct httpread *h = NULL;
716
717	h = os_zalloc(sizeof(*h));
718	if (h == NULL)
719		goto fail;
720	h->sd = sd;
721	h->cb = cb;
722	h->cookie = cookie;
723	h->max_bytes = max_bytes;
724	h->timeout_seconds = timeout_seconds;
725
726	if (timeout_seconds > 0 &&
727	    eloop_register_timeout(timeout_seconds, 0,
728				   httpread_timeout_handler, NULL, h)) {
729		/* No way to recover (from malloc failure) */
730		goto fail;
731	}
732	if (eloop_register_sock(sd, EVENT_TYPE_READ, httpread_read_handler,
733				NULL, h)) {
734		/* No way to recover (from malloc failure) */
735		goto fail;
736	}
737	return h;
738
739fail:
740
741	/* Error */
742	httpread_destroy(h);
743	return NULL;
744}
745
746
747/* httpread_hdr_type_get -- When file is ready, returns header type. */
748enum httpread_hdr_type httpread_hdr_type_get(struct httpread *h)
749{
750	return h->hdr_type;
751}
752
753
754/* httpread_uri_get -- When file is ready, uri_get returns (translated) URI
755 * or possibly NULL (which would be an error).
756 */
757char * httpread_uri_get(struct httpread *h)
758{
759	return h->uri;
760}
761
762
763/* httpread_reply_code_get -- When reply is ready, returns reply code */
764int httpread_reply_code_get(struct httpread *h)
765{
766	return h->reply_code;
767}
768
769
770/* httpread_length_get -- When file is ready, returns file length. */
771int httpread_length_get(struct httpread *h)
772{
773	return h->body_nbytes;
774}
775
776
777/* httpread_data_get -- When file is ready, returns file content
778 * with null byte appened.
779 * Might return NULL in some error condition.
780 */
781void * httpread_data_get(struct httpread *h)
782{
783	return h->body ? h->body : "";
784}
785
786
787/* httpread_hdr_get -- When file is ready, returns header content
788 * with null byte appended.
789 * Might return NULL in some error condition.
790 */
791char * httpread_hdr_get(struct httpread *h)
792{
793	return h->hdr;
794}
795
796
797/* httpread_hdr_line_get -- When file is ready, returns pointer
798 * to line within header content matching the given tag
799 * (after the tag itself and any spaces/tabs).
800 *
801 * The tag should end with a colon for reliable matching.
802 *
803 * If not found, returns NULL;
804 */
805char * httpread_hdr_line_get(struct httpread *h, const char *tag)
806{
807	int tag_len = os_strlen(tag);
808	char *hdr = h->hdr;
809	hdr = os_strchr(hdr, '\n');
810	if (hdr == NULL)
811		return NULL;
812	hdr++;
813	for (;;) {
814		if (!os_strncasecmp(hdr, tag, tag_len)) {
815			hdr += tag_len;
816			while (*hdr == ' ' || *hdr == '\t')
817				hdr++;
818			return hdr;
819		}
820		hdr = os_strchr(hdr, '\n');
821		if (hdr == NULL)
822			return NULL;
823		hdr++;
824	}
825}
826