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