2 * httpread - Manage reading file(s) from HTTP/TCP socket
4 * Copyright 2008 Atheros Communications
6 * This software may be distributed under the terms of the BSD license.
7 * See README for more details.
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.).
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.
19 * It is assumed that the application does not expect any of the following:
20 * -- transfer encoding other than chunked
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.
28 * -- HTTP header may not exceed a hard-coded size.
31 * This code would be massively simpler without some of the new features of
32 * HTTP/1.1, especially chunked data.
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 */
48 /* control instance -- actual definition (opaque to application)
51 /* information from creation */
52 int sd
; /* descriptor of TCP socket to read from */
53 void (*cb
)(struct httpread
*handle
, void *cookie
,
54 enum httpread_event e
); /* call on event */
55 void *cookie
; /* pass to callback */
56 int max_bytes
; /* maximum file size else abort it */
57 int timeout_seconds
; /* 0 or total duration timeout period */
59 /* dynamically used information follows */
61 int got_hdr
; /* nonzero when header is finalized */
62 char hdr
[HTTPREAD_HEADER_MAX_SIZE
+1]; /* headers stored here */
65 enum httpread_hdr_type hdr_type
;
66 int version
; /* 1 if we've seen 1.1 */
67 int reply_code
; /* for type REPLY, e.g. 200 for HTTP/1.1 200 OK */
68 int got_content_length
; /* true if we know content length for sure */
69 int content_length
; /* body length, iff got_content_length */
70 int chunked
; /* nonzero for chunked data */
73 int got_body
; /* nonzero when body is finalized */
76 int body_alloc_nbytes
; /* amount allocated */
78 int got_file
; /* here when we are done */
80 /* The following apply if data is chunked: */
81 int in_chunk_data
; /* 0=in/at header, 1=in the data or tail*/
82 int chunk_start
; /* offset in body of chunk hdr or data */
83 int chunk_size
; /* data of chunk (not hdr or ending CRLF)*/
84 int in_trailer
; /* in header fields after data (chunked only)*/
86 trailer_line_begin
= 0,
87 trailer_empty_cr
, /* empty line + CR */
94 /* Check words for equality, where words consist of graphical characters
95 * delimited by whitespace
96 * Returns nonzero if "equal" doing case insensitive comparison.
98 static int word_eq(char *s1
, char *s2
)
107 if (isalpha(c1
) && isupper(c1
))
109 if (isalpha(c2
) && isupper(c2
))
113 if (end1
|| end2
|| c1
!= c2
)
116 return end1
&& end2
; /* reached end of both words? */
120 static void httpread_timeout_handler(void *eloop_data
, void *user_ctx
);
122 /* httpread_destroy -- if h is non-NULL, clean up
123 * This must eventually be called by the application following
124 * call of the application's callback and may be called
125 * earlier if desired.
127 void httpread_destroy(struct httpread
*h
)
129 wpa_printf(MSG_DEBUG
, "httpread_destroy(%p)", h
);
133 eloop_cancel_timeout(httpread_timeout_handler
, NULL
, h
);
134 eloop_unregister_sock(h
->sd
, EVENT_TYPE_READ
);
137 os_memset(h
, 0, sizeof(*h
)); /* aid debugging */
138 h
->sd
= -1; /* aid debugging */
143 /* httpread_timeout_handler -- called on excessive total duration
145 static void httpread_timeout_handler(void *eloop_data
, void *user_ctx
)
147 struct httpread
*h
= user_ctx
;
148 wpa_printf(MSG_DEBUG
, "httpread timeout (%p)", h
);
149 (*h
->cb
)(h
, h
->cookie
, HTTPREAD_EVENT_TIMEOUT
);
153 /* Analyze options only so far as is needed to correctly obtain the file.
154 * The application can look at the raw header to find other options.
156 static int httpread_hdr_option_analyze(
158 char *hbp
/* pointer to current line in header buffer */
161 if (word_eq(hbp
, "CONTENT-LENGTH:")) {
162 while (isgraph(*hbp
))
164 while (*hbp
== ' ' || *hbp
== '\t')
168 h
->content_length
= atol(hbp
);
169 if (h
->content_length
< 0 || h
->content_length
> h
->max_bytes
) {
170 wpa_printf(MSG_DEBUG
,
171 "httpread: Unacceptable Content-Length %d",
175 h
->got_content_length
= 1;
178 if (word_eq(hbp
, "TRANSFER_ENCODING:") ||
179 word_eq(hbp
, "TRANSFER-ENCODING:")) {
180 while (isgraph(*hbp
))
182 while (*hbp
== ' ' || *hbp
== '\t')
184 /* There should (?) be no encodings of interest
185 * other than chunked...
187 if (word_eq(hbp
, "CHUNKED")) {
189 h
->in_chunk_data
= 0;
190 /* ignore possible ;<parameters> */
194 /* skip anything we don't know, which is a lot */
199 static int httpread_hdr_analyze(struct httpread
*h
)
201 char *hbp
= h
->hdr
; /* pointer into h->hdr */
202 int standard_first_line
= 1;
204 /* First line is special */
205 h
->hdr_type
= HTTPREAD_HDR_TYPE_UNKNOWN
;
208 if (os_strncmp(hbp
, "HTTP/", 5) == 0) {
209 h
->hdr_type
= HTTPREAD_HDR_TYPE_REPLY
;
210 standard_first_line
= 0;
212 if (hbp
[0] == '1' && hbp
[1] == '.' &&
213 isdigit(hbp
[2]) && hbp
[2] != '0')
215 while (isgraph(*hbp
))
217 while (*hbp
== ' ' || *hbp
== '\t')
221 h
->reply_code
= atol(hbp
);
222 } else if (word_eq(hbp
, "GET"))
223 h
->hdr_type
= HTTPREAD_HDR_TYPE_GET
;
224 else if (word_eq(hbp
, "HEAD"))
225 h
->hdr_type
= HTTPREAD_HDR_TYPE_HEAD
;
226 else if (word_eq(hbp
, "POST"))
227 h
->hdr_type
= HTTPREAD_HDR_TYPE_POST
;
228 else if (word_eq(hbp
, "PUT"))
229 h
->hdr_type
= HTTPREAD_HDR_TYPE_PUT
;
230 else if (word_eq(hbp
, "DELETE"))
231 h
->hdr_type
= HTTPREAD_HDR_TYPE_DELETE
;
232 else if (word_eq(hbp
, "TRACE"))
233 h
->hdr_type
= HTTPREAD_HDR_TYPE_TRACE
;
234 else if (word_eq(hbp
, "CONNECT"))
235 h
->hdr_type
= HTTPREAD_HDR_TYPE_CONNECT
;
236 else if (word_eq(hbp
, "NOTIFY"))
237 h
->hdr_type
= HTTPREAD_HDR_TYPE_NOTIFY
;
238 else if (word_eq(hbp
, "M-SEARCH"))
239 h
->hdr_type
= HTTPREAD_HDR_TYPE_M_SEARCH
;
240 else if (word_eq(hbp
, "M-POST"))
241 h
->hdr_type
= HTTPREAD_HDR_TYPE_M_POST
;
242 else if (word_eq(hbp
, "SUBSCRIBE"))
243 h
->hdr_type
= HTTPREAD_HDR_TYPE_SUBSCRIBE
;
244 else if (word_eq(hbp
, "UNSUBSCRIBE"))
245 h
->hdr_type
= HTTPREAD_HDR_TYPE_UNSUBSCRIBE
;
249 if (standard_first_line
) {
253 while (isgraph(*hbp
))
255 while (*hbp
== ' ' || *hbp
== '\t')
258 * Find length, allocate memory for translated
259 * copy, then translate by changing %<hex><hex>
260 * into represented value.
263 while (isgraph(*hbp
))
265 h
->uri
= os_malloc((hbp
- rawuri
) + 1);
269 while (rawuri
< hbp
) {
272 isxdigit(rawuri
[1]) && isxdigit(rawuri
[2])) {
273 *uri
++ = hex2byte(rawuri
+ 1);
280 *uri
= 0; /* null terminate */
281 while (*hbp
== ' ' || *hbp
== '\t')
284 if (0 == strncmp(hbp
, "HTTP/", 5)) {
286 if (hbp
[0] == '1' && hbp
[1] == '.' &&
287 isdigit(hbp
[2]) && hbp
[2] != '0')
291 /* skip rest of line */
296 /* Remainder of lines are options, in any order;
297 * or empty line to terminate
300 /* Empty line to terminate */
301 if (hbp
[0] == '\n' ||
302 (hbp
[0] == '\r' && hbp
[1] == '\n'))
306 if (httpread_hdr_option_analyze(h
, hbp
))
314 /* chunked overrides content-length always */
316 h
->got_content_length
= 0;
318 /* For some types, we should not try to read a body
319 * This is in addition to the application determining
320 * that we should not read a body.
322 switch (h
->hdr_type
) {
323 case HTTPREAD_HDR_TYPE_REPLY
:
324 /* Some codes can have a body and some not.
325 * For now, just assume that any other than 200
328 if (h
->reply_code
!= 200)
331 case HTTPREAD_HDR_TYPE_GET
:
332 case HTTPREAD_HDR_TYPE_HEAD
:
333 /* in practice it appears that it is assumed
334 * that GETs have a body length of 0... ?
336 if (h
->chunked
== 0 && h
->got_content_length
== 0)
339 case HTTPREAD_HDR_TYPE_POST
:
340 case HTTPREAD_HDR_TYPE_PUT
:
341 case HTTPREAD_HDR_TYPE_DELETE
:
342 case HTTPREAD_HDR_TYPE_TRACE
:
343 case HTTPREAD_HDR_TYPE_CONNECT
:
344 case HTTPREAD_HDR_TYPE_NOTIFY
:
345 case HTTPREAD_HDR_TYPE_M_SEARCH
:
346 case HTTPREAD_HDR_TYPE_M_POST
:
347 case HTTPREAD_HDR_TYPE_SUBSCRIBE
:
348 case HTTPREAD_HDR_TYPE_UNSUBSCRIBE
:
361 /* httpread_read_handler -- called when socket ready to read
363 * Note: any extra data we read past end of transmitted file is ignored;
364 * if we were to support keeping connections open for multiple files then
365 * this would have to be addressed.
367 static void httpread_read_handler(int sd
, void *eloop_ctx
, void *sock_ctx
)
369 struct httpread
*h
= sock_ctx
;
371 char *rbp
; /* pointer into read buffer */
372 char *hbp
; /* pointer into header buffer */
373 char *bbp
; /* pointer into body buffer */
374 char readbuf
[HTTPREAD_READBUF_SIZE
]; /* temp use to read into */
376 /* read some at a time, then search for the interal
377 * boundaries between header and data and etc.
379 wpa_printf(MSG_DEBUG
, "httpread: Trying to read more data(%p)", h
);
380 nread
= read(h
->sd
, readbuf
, sizeof(readbuf
));
382 wpa_printf(MSG_DEBUG
, "httpread failed: %s", strerror(errno
));
385 wpa_hexdump_ascii(MSG_MSGDUMP
, "httpread - read", readbuf
, nread
);
387 /* end of transmission... this may be normal
388 * or may be an error... in some cases we can't
389 * tell which so we must assume it is normal then.
392 /* Must at least have completed header */
393 wpa_printf(MSG_DEBUG
, "httpread premature eof(%p)", h
);
396 if (h
->chunked
|| h
->got_content_length
) {
397 /* Premature EOF; e.g. dropped connection */
398 wpa_printf(MSG_DEBUG
,
399 "httpread premature eof(%p) %d/%d",
404 /* No explicit length, hopefully we have all the data
405 * although dropped connections can cause false
408 wpa_printf(MSG_DEBUG
, "httpread ok eof(%p)", h
);
414 /* Header consists of text lines (terminated by both CR and LF)
415 * and an empty line (CR LF only).
418 hbp
= h
->hdr
+ h
->hdr_nbytes
;
419 /* add to headers until:
420 * -- we run out of data in read buffer
421 * -- or, we run out of header buffer room
422 * -- or, we get double CRLF in headers
427 if (h
->hdr_nbytes
== HTTPREAD_HEADER_MAX_SIZE
) {
428 wpa_printf(MSG_DEBUG
,
429 "httpread: Too long header");
435 if (h
->hdr_nbytes
>= 4 &&
441 *hbp
= 0; /* null terminate */
445 /* here we've just finished reading the header */
446 if (httpread_hdr_analyze(h
)) {
447 wpa_printf(MSG_DEBUG
, "httpread bad hdr(%p)", h
);
450 if (h
->max_bytes
== 0) {
451 wpa_printf(MSG_DEBUG
, "httpread no body hdr end(%p)",
455 if (h
->got_content_length
&& h
->content_length
== 0) {
456 wpa_printf(MSG_DEBUG
,
457 "httpread zero content length(%p)", h
);
462 /* Certain types of requests never have data and so
463 * must be specially recognized.
465 if (!os_strncasecmp(h
->hdr
, "SUBSCRIBE", 9) ||
466 !os_strncasecmp(h
->hdr
, "UNSUBSCRIBE", 11) ||
467 !os_strncasecmp(h
->hdr
, "HEAD", 4) ||
468 !os_strncasecmp(h
->hdr
, "GET", 3)) {
470 wpa_printf(MSG_DEBUG
, "httpread NO BODY for sp. type");
476 /* Data can be just plain binary data, or if "chunked"
477 * consists of chunks each with a header, ending with
483 /* Here to get (more of) body */
484 /* ensure we have enough room for worst case for body
485 * plus a null termination character
487 if (h
->body_alloc_nbytes
< (h
->body_nbytes
+ nread
+ 1)) {
489 int new_alloc_nbytes
;
491 if (h
->body_nbytes
>= h
->max_bytes
) {
492 wpa_printf(MSG_DEBUG
,
493 "httpread: body_nbytes=%d >= max_bytes=%d",
494 h
->body_nbytes
, h
->max_bytes
);
497 new_alloc_nbytes
= h
->body_alloc_nbytes
+
498 HTTPREAD_BODYBUF_DELTA
;
499 /* For content-length case, the first time
500 * through we allocate the whole amount
503 if (h
->got_content_length
&&
504 new_alloc_nbytes
< (h
->content_length
+ 1))
505 new_alloc_nbytes
= h
->content_length
+ 1;
506 if (new_alloc_nbytes
< h
->body_alloc_nbytes
||
507 new_alloc_nbytes
> h
->max_bytes
+
508 HTTPREAD_BODYBUF_DELTA
) {
509 wpa_printf(MSG_DEBUG
,
510 "httpread: Unacceptable body length %d (body_alloc_nbytes=%u max_bytes=%u)",
512 h
->body_alloc_nbytes
,
516 if ((new_body
= os_realloc(h
->body
, new_alloc_nbytes
))
518 wpa_printf(MSG_DEBUG
,
519 "httpread: Failed to reallocate buffer (len=%d)",
525 h
->body_alloc_nbytes
= new_alloc_nbytes
;
528 bbp
= h
->body
+ h
->body_nbytes
;
531 /* See if we need to stop */
532 if (h
->chunked
&& h
->in_chunk_data
== 0) {
533 /* in chunk header */
534 char *cbp
= h
->body
+ h
->chunk_start
;
535 if (bbp
-cbp
>= 2 && bbp
[-2] == '\r' &&
537 /* end of chunk hdr line */
538 /* hdr line consists solely
539 * of a hex numeral and CFLF
541 if (!isxdigit(*cbp
)) {
542 wpa_printf(MSG_DEBUG
,
543 "httpread: Unexpected chunk header value (not a hex digit)");
546 h
->chunk_size
= strtoul(cbp
, NULL
, 16);
547 if (h
->chunk_size
< 0 ||
548 h
->chunk_size
> h
->max_bytes
) {
549 wpa_printf(MSG_DEBUG
,
550 "httpread: Invalid chunk size %d",
554 /* throw away chunk header
555 * so we have only real data
557 h
->body_nbytes
= h
->chunk_start
;
559 if (h
->chunk_size
== 0) {
560 /* end of chunking */
561 /* trailer follows */
563 wpa_printf(MSG_DEBUG
,
564 "httpread end chunks(%p)",
568 h
->in_chunk_data
= 1;
569 /* leave chunk_start alone */
571 } else if (h
->chunked
) {
573 if ((h
->body_nbytes
- h
->chunk_start
) ==
574 (h
->chunk_size
+ 2)) {
575 /* end of chunk reached,
578 /* check chunk ended w/ CRLF
579 * which we'll throw away
581 if (bbp
[-1] == '\n' &&
584 wpa_printf(MSG_DEBUG
,
585 "httpread: Invalid chunk end");
590 h
->chunk_start
= h
->body_nbytes
;
591 h
->in_chunk_data
= 0;
592 h
->chunk_size
= 0; /* just in case */
594 } else if (h
->got_content_length
&&
595 h
->body_nbytes
>= h
->content_length
) {
597 wpa_printf(MSG_DEBUG
,
598 "httpread got content(%p)", h
);
603 /* Now transfer. Optimize using memcpy where we can. */
604 if (h
->chunked
&& h
->in_chunk_data
) {
605 /* copy up to remainder of chunk data
606 * plus the required CR+LF at end
608 ncopy
= (h
->chunk_start
+ h
->chunk_size
+ 2) -
610 } else if (h
->chunked
) {
611 /*in chunk header -- don't optimize */
616 } else if (h
->got_content_length
) {
617 ncopy
= h
->content_length
- h
->body_nbytes
;
621 /* Note: should never be 0 */
623 wpa_printf(MSG_DEBUG
,
624 "httpread: Invalid ncopy=%d", ncopy
);
629 os_memcpy(bbp
, rbp
, ncopy
);
631 h
->body_nbytes
+= ncopy
;
634 } /* body copy loop */
636 if (h
->chunked
&& h
->in_trailer
) {
637 /* If "chunked" then there is always a trailer,
638 * consisting of zero or more non-empty lines
639 * ending with CR LF and then an empty line w/ CR LF.
640 * We do NOT support trailers except to skip them --
641 * this is supported (generally) by the http spec.
649 switch (h
->trailer_state
) {
650 case trailer_line_begin
:
652 h
->trailer_state
= trailer_empty_cr
;
654 h
->trailer_state
= trailer_nonempty
;
656 case trailer_empty_cr
:
659 h
->trailer_state
= trailer_line_begin
;
661 wpa_printf(MSG_DEBUG
,
662 "httpread got content(%p)",
667 h
->trailer_state
= trailer_nonempty
;
669 case trailer_nonempty
:
671 h
->trailer_state
= trailer_nonempty_cr
;
673 case trailer_nonempty_cr
:
675 h
->trailer_state
= trailer_line_begin
;
677 h
->trailer_state
= trailer_nonempty
;
686 wpa_printf(MSG_DEBUG
, "httpread read/parse failure (%p)", h
);
687 (*h
->cb
)(h
, h
->cookie
, HTTPREAD_EVENT_ERROR
);
691 wpa_printf(MSG_DEBUG
, "httpread: get more (%p)", h
);
695 wpa_printf(MSG_DEBUG
, "httpread got file %d bytes type %d",
696 h
->body_nbytes
, h
->hdr_type
);
697 wpa_hexdump_ascii(MSG_MSGDUMP
, "httpread: body",
698 h
->body
, h
->body_nbytes
);
699 /* Null terminate for convenience of some applications */
701 h
->body
[h
->body_nbytes
] = 0; /* null terminate */
703 /* Assume that we do NOT support keeping connection alive,
704 * and just in case somehow we don't get destroyed right away,
707 eloop_unregister_sock(h
->sd
, EVENT_TYPE_READ
);
708 /* The application can destroy us whenever they feel like...
711 eloop_cancel_timeout(httpread_timeout_handler
, NULL
, h
);
712 (*h
->cb
)(h
, h
->cookie
, HTTPREAD_EVENT_FILE_READY
);
716 /* httpread_create -- start a new reading session making use of eloop.
717 * The new instance will use the socket descriptor for reading (until
718 * it gets a file and not after) but will not close the socket, even
719 * when the instance is destroyed (the application must do that).
720 * Return NULL on error.
722 * Provided that httpread_create successfully returns a handle,
723 * the callback fnc is called to handle httpread_event events.
724 * The caller should do destroy on any errors or unknown events.
726 * Pass max_bytes == 0 to not read body at all (required for e.g.
727 * reply to HEAD request).
729 struct httpread
* httpread_create(
730 int sd
, /* descriptor of TCP socket to read from */
731 void (*cb
)(struct httpread
*handle
, void *cookie
,
732 enum httpread_event e
), /* call on event */
733 void *cookie
, /* pass to callback */
734 int max_bytes
, /* maximum body size else abort it */
735 int timeout_seconds
/* 0; or total duration timeout period */
738 struct httpread
*h
= NULL
;
740 h
= os_zalloc(sizeof(*h
));
746 h
->max_bytes
= max_bytes
;
747 h
->timeout_seconds
= timeout_seconds
;
749 if (timeout_seconds
> 0 &&
750 eloop_register_timeout(timeout_seconds
, 0,
751 httpread_timeout_handler
, NULL
, h
)) {
752 /* No way to recover (from malloc failure) */
755 if (eloop_register_sock(sd
, EVENT_TYPE_READ
, httpread_read_handler
,
757 /* No way to recover (from malloc failure) */
770 /* httpread_hdr_type_get -- When file is ready, returns header type. */
771 enum httpread_hdr_type
httpread_hdr_type_get(struct httpread
*h
)
777 /* httpread_uri_get -- When file is ready, uri_get returns (translated) URI
778 * or possibly NULL (which would be an error).
780 char * httpread_uri_get(struct httpread
*h
)
786 /* httpread_reply_code_get -- When reply is ready, returns reply code */
787 int httpread_reply_code_get(struct httpread
*h
)
789 return h
->reply_code
;
793 /* httpread_length_get -- When file is ready, returns file length. */
794 int httpread_length_get(struct httpread
*h
)
796 return h
->body_nbytes
;
800 /* httpread_data_get -- When file is ready, returns file content
801 * with null byte appened.
802 * Might return NULL in some error condition.
804 void * httpread_data_get(struct httpread
*h
)
806 return h
->body
? h
->body
: "";
810 /* httpread_hdr_get -- When file is ready, returns header content
811 * with null byte appended.
812 * Might return NULL in some error condition.
814 char * httpread_hdr_get(struct httpread
*h
)
820 /* httpread_hdr_line_get -- When file is ready, returns pointer
821 * to line within header content matching the given tag
822 * (after the tag itself and any spaces/tabs).
824 * The tag should end with a colon for reliable matching.
826 * If not found, returns NULL;
828 char * httpread_hdr_line_get(struct httpread
*h
, const char *tag
)
830 int tag_len
= os_strlen(tag
);
832 hdr
= os_strchr(hdr
, '\n');
837 if (!os_strncasecmp(hdr
, tag
, tag_len
)) {
839 while (*hdr
== ' ' || *hdr
== '\t')
843 hdr
= os_strchr(hdr
, '\n');