]>
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 | * | |
e22d4d95 JM |
6 | * This software may be distributed under the terms of the BSD license. |
7 | * See README for more details. | |
f620268f JM |
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 | ||
f620268f JM |
47 | |
48 | /* control instance -- actual definition (opaque to application) | |
49 | */ | |
50 | struct httpread { | |
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 */ | |
58 | ||
59 | /* dynamically used information follows */ | |
f620268f JM |
60 | |
61 | int got_hdr; /* nonzero when header is finalized */ | |
62 | char hdr[HTTPREAD_HEADER_MAX_SIZE+1]; /* headers stored here */ | |
63 | int hdr_nbytes; | |
64 | ||
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 */ | |
71 | char *uri; | |
72 | ||
73 | int got_body; /* nonzero when body is finalized */ | |
74 | char *body; | |
75 | int body_nbytes; | |
76 | int body_alloc_nbytes; /* amount allocated */ | |
77 | ||
78 | int got_file; /* here when we are done */ | |
79 | ||
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)*/ | |
85 | enum trailer_state { | |
86 | trailer_line_begin = 0, | |
87 | trailer_empty_cr, /* empty line + CR */ | |
88 | trailer_nonempty, | |
89 | trailer_nonempty_cr, | |
90 | } trailer_state; | |
91 | }; | |
92 | ||
93 | ||
94 | /* Check words for equality, where words consist of graphical characters | |
95 | * delimited by whitespace | |
96 | * Returns nonzero if "equal" doing case insensitive comparison. | |
97 | */ | |
98 | static int word_eq(char *s1, char *s2) | |
99 | { | |
100 | int c1; | |
101 | int c2; | |
102 | int end1 = 0; | |
103 | int end2 = 0; | |
104 | for (;;) { | |
105 | c1 = *s1++; | |
106 | c2 = *s2++; | |
107 | if (isalpha(c1) && isupper(c1)) | |
108 | c1 = tolower(c1); | |
109 | if (isalpha(c2) && isupper(c2)) | |
110 | c2 = tolower(c2); | |
111 | end1 = !isgraph(c1); | |
112 | end2 = !isgraph(c2); | |
113 | if (end1 || end2 || c1 != c2) | |
114 | break; | |
115 | } | |
116 | return end1 && end2; /* reached end of both words? */ | |
117 | } | |
118 | ||
119 | ||
f620268f JM |
120 | static void httpread_timeout_handler(void *eloop_data, void *user_ctx); |
121 | ||
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. | |
126 | */ | |
127 | void httpread_destroy(struct httpread *h) | |
128 | { | |
1bd0d578 | 129 | wpa_printf(MSG_DEBUG, "httpread_destroy(%p)", h); |
f620268f JM |
130 | if (!h) |
131 | return; | |
132 | ||
c89d9dba JM |
133 | eloop_cancel_timeout(httpread_timeout_handler, NULL, h); |
134 | eloop_unregister_sock(h->sd, EVENT_TYPE_READ); | |
f620268f JM |
135 | os_free(h->body); |
136 | os_free(h->uri); | |
137 | os_memset(h, 0, sizeof(*h)); /* aid debugging */ | |
138 | h->sd = -1; /* aid debugging */ | |
139 | os_free(h); | |
140 | } | |
141 | ||
142 | ||
143 | /* httpread_timeout_handler -- called on excessive total duration | |
144 | */ | |
145 | static void httpread_timeout_handler(void *eloop_data, void *user_ctx) | |
146 | { | |
147 | struct httpread *h = user_ctx; | |
148 | wpa_printf(MSG_DEBUG, "httpread timeout (%p)", h); | |
f620268f JM |
149 | (*h->cb)(h, h->cookie, HTTPREAD_EVENT_TIMEOUT); |
150 | } | |
151 | ||
152 | ||
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. | |
155 | */ | |
156 | static int httpread_hdr_option_analyze( | |
157 | struct httpread *h, | |
158 | char *hbp /* pointer to current line in header buffer */ | |
159 | ) | |
160 | { | |
161 | if (word_eq(hbp, "CONTENT-LENGTH:")) { | |
162 | while (isgraph(*hbp)) | |
163 | hbp++; | |
164 | while (*hbp == ' ' || *hbp == '\t') | |
165 | hbp++; | |
166 | if (!isdigit(*hbp)) | |
167 | return -1; | |
168 | h->content_length = atol(hbp); | |
7da4f4b4 JM |
169 | if (h->content_length < 0 || h->content_length > h->max_bytes) { |
170 | wpa_printf(MSG_DEBUG, | |
171 | "httpread: Unacceptable Content-Length %d", | |
172 | h->content_length); | |
173 | return -1; | |
174 | } | |
f620268f JM |
175 | h->got_content_length = 1; |
176 | return 0; | |
177 | } | |
814aaa84 JM |
178 | if (word_eq(hbp, "TRANSFER_ENCODING:") || |
179 | word_eq(hbp, "TRANSFER-ENCODING:")) { | |
f620268f JM |
180 | while (isgraph(*hbp)) |
181 | hbp++; | |
182 | while (*hbp == ' ' || *hbp == '\t') | |
183 | hbp++; | |
184 | /* There should (?) be no encodings of interest | |
185 | * other than chunked... | |
186 | */ | |
814aaa84 | 187 | if (word_eq(hbp, "CHUNKED")) { |
f620268f JM |
188 | h->chunked = 1; |
189 | h->in_chunk_data = 0; | |
190 | /* ignore possible ;<parameters> */ | |
191 | } | |
192 | return 0; | |
193 | } | |
194 | /* skip anything we don't know, which is a lot */ | |
195 | return 0; | |
196 | } | |
197 | ||
198 | ||
199 | static int httpread_hdr_analyze(struct httpread *h) | |
200 | { | |
201 | char *hbp = h->hdr; /* pointer into h->hdr */ | |
202 | int standard_first_line = 1; | |
203 | ||
204 | /* First line is special */ | |
205 | h->hdr_type = HTTPREAD_HDR_TYPE_UNKNOWN; | |
206 | if (!isgraph(*hbp)) | |
207 | goto bad; | |
208 | if (os_strncmp(hbp, "HTTP/", 5) == 0) { | |
209 | h->hdr_type = HTTPREAD_HDR_TYPE_REPLY; | |
210 | standard_first_line = 0; | |
211 | hbp += 5; | |
212 | if (hbp[0] == '1' && hbp[1] == '.' && | |
213 | isdigit(hbp[2]) && hbp[2] != '0') | |
214 | h->version = 1; | |
215 | while (isgraph(*hbp)) | |
216 | hbp++; | |
217 | while (*hbp == ' ' || *hbp == '\t') | |
218 | hbp++; | |
219 | if (!isdigit(*hbp)) | |
220 | goto bad; | |
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; | |
246 | else { | |
247 | } | |
248 | ||
249 | if (standard_first_line) { | |
250 | char *rawuri; | |
251 | char *uri; | |
252 | /* skip type */ | |
253 | while (isgraph(*hbp)) | |
254 | hbp++; | |
255 | while (*hbp == ' ' || *hbp == '\t') | |
256 | hbp++; | |
257 | /* parse uri. | |
258 | * Find length, allocate memory for translated | |
259 | * copy, then translate by changing %<hex><hex> | |
260 | * into represented value. | |
261 | */ | |
262 | rawuri = hbp; | |
263 | while (isgraph(*hbp)) | |
264 | hbp++; | |
265 | h->uri = os_malloc((hbp - rawuri) + 1); | |
266 | if (h->uri == NULL) | |
267 | goto bad; | |
268 | uri = h->uri; | |
269 | while (rawuri < hbp) { | |
270 | int c = *rawuri; | |
271 | if (c == '%' && | |
272 | isxdigit(rawuri[1]) && isxdigit(rawuri[2])) { | |
c86bf160 | 273 | *uri++ = hex2byte(rawuri + 1); |
f620268f JM |
274 | rawuri += 3; |
275 | } else { | |
276 | *uri++ = c; | |
277 | rawuri++; | |
278 | } | |
279 | } | |
280 | *uri = 0; /* null terminate */ | |
f620268f JM |
281 | while (*hbp == ' ' || *hbp == '\t') |
282 | hbp++; | |
283 | /* get version */ | |
284 | if (0 == strncmp(hbp, "HTTP/", 5)) { | |
285 | hbp += 5; | |
286 | if (hbp[0] == '1' && hbp[1] == '.' && | |
287 | isdigit(hbp[2]) && hbp[2] != '0') | |
288 | h->version = 1; | |
289 | } | |
290 | } | |
291 | /* skip rest of line */ | |
292 | while (*hbp) | |
293 | if (*hbp++ == '\n') | |
294 | break; | |
295 | ||
296 | /* Remainder of lines are options, in any order; | |
297 | * or empty line to terminate | |
298 | */ | |
299 | for (;;) { | |
300 | /* Empty line to terminate */ | |
301 | if (hbp[0] == '\n' || | |
302 | (hbp[0] == '\r' && hbp[1] == '\n')) | |
303 | break; | |
304 | if (!isgraph(*hbp)) | |
305 | goto bad; | |
306 | if (httpread_hdr_option_analyze(h, hbp)) | |
307 | goto bad; | |
308 | /* skip line */ | |
309 | while (*hbp) | |
310 | if (*hbp++ == '\n') | |
311 | break; | |
312 | } | |
313 | ||
314 | /* chunked overrides content-length always */ | |
315 | if (h->chunked) | |
316 | h->got_content_length = 0; | |
317 | ||
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. | |
321 | */ | |
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 | |
326 | * do not... | |
327 | */ | |
328 | if (h->reply_code != 200) | |
329 | h->max_bytes = 0; | |
330 | break; | |
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... ? | |
335 | */ | |
336 | if (h->chunked == 0 && h->got_content_length == 0) | |
337 | h->max_bytes = 0; | |
338 | break; | |
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: | |
349 | default: | |
350 | break; | |
351 | } | |
352 | ||
353 | return 0; | |
354 | ||
355 | bad: | |
356 | /* Error */ | |
357 | return -1; | |
358 | } | |
359 | ||
360 | ||
361 | /* httpread_read_handler -- called when socket ready to read | |
362 | * | |
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. | |
366 | */ | |
367 | static void httpread_read_handler(int sd, void *eloop_ctx, void *sock_ctx) | |
368 | { | |
369 | struct httpread *h = sock_ctx; | |
370 | int nread; | |
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 */ | |
375 | ||
f620268f JM |
376 | /* read some at a time, then search for the interal |
377 | * boundaries between header and data and etc. | |
378 | */ | |
1bd0d578 | 379 | wpa_printf(MSG_DEBUG, "httpread: Trying to read more data(%p)", h); |
f620268f | 380 | nread = read(h->sd, readbuf, sizeof(readbuf)); |
8640cf7f JM |
381 | if (nread < 0) { |
382 | wpa_printf(MSG_DEBUG, "httpread failed: %s", strerror(errno)); | |
f620268f | 383 | goto bad; |
8640cf7f JM |
384 | } |
385 | wpa_hexdump_ascii(MSG_MSGDUMP, "httpread - read", readbuf, nread); | |
f620268f JM |
386 | if (nread == 0) { |
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. | |
390 | */ | |
391 | if (!h->got_hdr) { | |
392 | /* Must at least have completed header */ | |
393 | wpa_printf(MSG_DEBUG, "httpread premature eof(%p)", h); | |
394 | goto bad; | |
395 | } | |
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", | |
400 | h, h->body_nbytes, | |
401 | h->content_length); | |
402 | goto bad; | |
403 | } | |
404 | /* No explicit length, hopefully we have all the data | |
405 | * although dropped connections can cause false | |
406 | * end | |
407 | */ | |
1bd0d578 | 408 | wpa_printf(MSG_DEBUG, "httpread ok eof(%p)", h); |
c02f35fb JM |
409 | h->got_body = 1; |
410 | goto got_file; | |
f620268f JM |
411 | } |
412 | rbp = readbuf; | |
413 | ||
414 | /* Header consists of text lines (terminated by both CR and LF) | |
415 | * and an empty line (CR LF only). | |
416 | */ | |
417 | if (!h->got_hdr) { | |
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 | |
423 | */ | |
424 | for (;;) { | |
425 | if (nread == 0) | |
426 | goto get_more; | |
427 | if (h->hdr_nbytes == HTTPREAD_HEADER_MAX_SIZE) { | |
8640cf7f JM |
428 | wpa_printf(MSG_DEBUG, |
429 | "httpread: Too long header"); | |
f620268f JM |
430 | goto bad; |
431 | } | |
432 | *hbp++ = *rbp++; | |
433 | nread--; | |
434 | h->hdr_nbytes++; | |
435 | if (h->hdr_nbytes >= 4 && | |
436 | hbp[-1] == '\n' && | |
437 | hbp[-2] == '\r' && | |
438 | hbp[-3] == '\n' && | |
439 | hbp[-4] == '\r' ) { | |
440 | h->got_hdr = 1; | |
441 | *hbp = 0; /* null terminate */ | |
442 | break; | |
443 | } | |
444 | } | |
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); | |
448 | goto bad; | |
449 | } | |
450 | if (h->max_bytes == 0) { | |
1bd0d578 JM |
451 | wpa_printf(MSG_DEBUG, "httpread no body hdr end(%p)", |
452 | h); | |
f620268f JM |
453 | goto got_file; |
454 | } | |
455 | if (h->got_content_length && h->content_length == 0) { | |
1bd0d578 JM |
456 | wpa_printf(MSG_DEBUG, |
457 | "httpread zero content length(%p)", h); | |
f620268f JM |
458 | goto got_file; |
459 | } | |
460 | } | |
461 | ||
462 | /* Certain types of requests never have data and so | |
463 | * must be specially recognized. | |
464 | */ | |
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)) { | |
469 | if (!h->got_body) { | |
1bd0d578 | 470 | wpa_printf(MSG_DEBUG, "httpread NO BODY for sp. type"); |
f620268f JM |
471 | } |
472 | h->got_body = 1; | |
473 | goto got_file; | |
474 | } | |
475 | ||
476 | /* Data can be just plain binary data, or if "chunked" | |
477 | * consists of chunks each with a header, ending with | |
478 | * an ending header. | |
479 | */ | |
814aaa84 JM |
480 | if (nread == 0) |
481 | goto get_more; | |
f620268f JM |
482 | if (!h->got_body) { |
483 | /* Here to get (more of) body */ | |
484 | /* ensure we have enough room for worst case for body | |
485 | * plus a null termination character | |
486 | */ | |
487 | if (h->body_alloc_nbytes < (h->body_nbytes + nread + 1)) { | |
488 | char *new_body; | |
489 | int new_alloc_nbytes; | |
490 | ||
8640cf7f JM |
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); | |
f620268f | 495 | goto bad; |
8640cf7f | 496 | } |
f620268f JM |
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 | |
501 | * we need. | |
502 | */ | |
503 | if (h->got_content_length && | |
504 | new_alloc_nbytes < (h->content_length + 1)) | |
505 | new_alloc_nbytes = h->content_length + 1; | |
7da4f4b4 | 506 | if (new_alloc_nbytes < h->body_alloc_nbytes || |
2ce741fe JM |
507 | new_alloc_nbytes > h->max_bytes + |
508 | HTTPREAD_BODYBUF_DELTA) { | |
7da4f4b4 | 509 | wpa_printf(MSG_DEBUG, |
2ce741fe JM |
510 | "httpread: Unacceptable body length %d (body_alloc_nbytes=%u max_bytes=%u)", |
511 | new_alloc_nbytes, | |
512 | h->body_alloc_nbytes, | |
513 | h->max_bytes); | |
7da4f4b4 JM |
514 | goto bad; |
515 | } | |
f620268f | 516 | if ((new_body = os_realloc(h->body, new_alloc_nbytes)) |
8640cf7f JM |
517 | == NULL) { |
518 | wpa_printf(MSG_DEBUG, | |
519 | "httpread: Failed to reallocate buffer (len=%d)", | |
520 | new_alloc_nbytes); | |
f620268f | 521 | goto bad; |
8640cf7f | 522 | } |
f620268f JM |
523 | |
524 | h->body = new_body; | |
525 | h->body_alloc_nbytes = new_alloc_nbytes; | |
526 | } | |
527 | /* add bytes */ | |
528 | bbp = h->body + h->body_nbytes; | |
529 | for (;;) { | |
530 | int ncopy; | |
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' && | |
536 | bbp[-1] == '\n') { | |
537 | /* end of chunk hdr line */ | |
538 | /* hdr line consists solely | |
539 | * of a hex numeral and CFLF | |
540 | */ | |
8640cf7f JM |
541 | if (!isxdigit(*cbp)) { |
542 | wpa_printf(MSG_DEBUG, | |
543 | "httpread: Unexpected chunk header value (not a hex digit)"); | |
f620268f | 544 | goto bad; |
8640cf7f | 545 | } |
f620268f | 546 | h->chunk_size = strtoul(cbp, NULL, 16); |
5acd23f4 JM |
547 | if (h->chunk_size < 0 || |
548 | h->chunk_size > h->max_bytes) { | |
549 | wpa_printf(MSG_DEBUG, | |
550 | "httpread: Invalid chunk size %d", | |
551 | h->chunk_size); | |
552 | goto bad; | |
553 | } | |
f620268f JM |
554 | /* throw away chunk header |
555 | * so we have only real data | |
556 | */ | |
557 | h->body_nbytes = h->chunk_start; | |
558 | bbp = cbp; | |
559 | if (h->chunk_size == 0) { | |
560 | /* end of chunking */ | |
561 | /* trailer follows */ | |
562 | h->in_trailer = 1; | |
1bd0d578 JM |
563 | wpa_printf(MSG_DEBUG, |
564 | "httpread end chunks(%p)", | |
565 | h); | |
f620268f JM |
566 | break; |
567 | } | |
568 | h->in_chunk_data = 1; | |
569 | /* leave chunk_start alone */ | |
570 | } | |
571 | } else if (h->chunked) { | |
572 | /* in chunk data */ | |
573 | if ((h->body_nbytes - h->chunk_start) == | |
574 | (h->chunk_size + 2)) { | |
575 | /* end of chunk reached, | |
576 | * new chunk starts | |
577 | */ | |
578 | /* check chunk ended w/ CRLF | |
579 | * which we'll throw away | |
580 | */ | |
581 | if (bbp[-1] == '\n' && | |
582 | bbp[-2] == '\r') { | |
8640cf7f JM |
583 | } else { |
584 | wpa_printf(MSG_DEBUG, | |
585 | "httpread: Invalid chunk end"); | |
f620268f | 586 | goto bad; |
8640cf7f | 587 | } |
f620268f JM |
588 | h->body_nbytes -= 2; |
589 | bbp -= 2; | |
590 | h->chunk_start = h->body_nbytes; | |
591 | h->in_chunk_data = 0; | |
592 | h->chunk_size = 0; /* just in case */ | |
593 | } | |
594 | } else if (h->got_content_length && | |
595 | h->body_nbytes >= h->content_length) { | |
596 | h->got_body = 1; | |
1bd0d578 JM |
597 | wpa_printf(MSG_DEBUG, |
598 | "httpread got content(%p)", h); | |
f620268f JM |
599 | goto got_file; |
600 | } | |
601 | if (nread <= 0) | |
602 | break; | |
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 | |
607 | */ | |
608 | ncopy = (h->chunk_start + h->chunk_size + 2) - | |
609 | h->body_nbytes; | |
610 | } else if (h->chunked) { | |
611 | /*in chunk header -- don't optimize */ | |
612 | *bbp++ = *rbp++; | |
613 | nread--; | |
614 | h->body_nbytes++; | |
615 | continue; | |
616 | } else if (h->got_content_length) { | |
617 | ncopy = h->content_length - h->body_nbytes; | |
618 | } else { | |
619 | ncopy = nread; | |
620 | } | |
621 | /* Note: should never be 0 */ | |
af185d0b JM |
622 | if (ncopy < 0) { |
623 | wpa_printf(MSG_DEBUG, | |
624 | "httpread: Invalid ncopy=%d", ncopy); | |
625 | goto bad; | |
626 | } | |
f620268f JM |
627 | if (ncopy > nread) |
628 | ncopy = nread; | |
629 | os_memcpy(bbp, rbp, ncopy); | |
630 | bbp += ncopy; | |
631 | h->body_nbytes += ncopy; | |
632 | rbp += ncopy; | |
633 | nread -= ncopy; | |
634 | } /* body copy loop */ | |
635 | } /* !got_body */ | |
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. | |
642 | */ | |
f620268f JM |
643 | for (;;) { |
644 | int c; | |
645 | if (nread <= 0) | |
646 | break; | |
647 | c = *rbp++; | |
648 | nread--; | |
649 | switch (h->trailer_state) { | |
650 | case trailer_line_begin: | |
651 | if (c == '\r') | |
652 | h->trailer_state = trailer_empty_cr; | |
653 | else | |
654 | h->trailer_state = trailer_nonempty; | |
655 | break; | |
656 | case trailer_empty_cr: | |
657 | /* end empty line */ | |
658 | if (c == '\n') { | |
659 | h->trailer_state = trailer_line_begin; | |
660 | h->in_trailer = 0; | |
1bd0d578 JM |
661 | wpa_printf(MSG_DEBUG, |
662 | "httpread got content(%p)", | |
663 | h); | |
f620268f JM |
664 | h->got_body = 1; |
665 | goto got_file; | |
666 | } | |
667 | h->trailer_state = trailer_nonempty; | |
668 | break; | |
669 | case trailer_nonempty: | |
670 | if (c == '\r') | |
671 | h->trailer_state = trailer_nonempty_cr; | |
672 | break; | |
673 | case trailer_nonempty_cr: | |
674 | if (c == '\n') | |
675 | h->trailer_state = trailer_line_begin; | |
676 | else | |
677 | h->trailer_state = trailer_nonempty; | |
678 | break; | |
679 | } | |
680 | } | |
681 | } | |
682 | goto get_more; | |
683 | ||
684 | bad: | |
685 | /* Error */ | |
686 | wpa_printf(MSG_DEBUG, "httpread read/parse failure (%p)", h); | |
687 | (*h->cb)(h, h->cookie, HTTPREAD_EVENT_ERROR); | |
688 | return; | |
689 | ||
690 | get_more: | |
8640cf7f | 691 | wpa_printf(MSG_DEBUG, "httpread: get more (%p)", h); |
f620268f JM |
692 | return; |
693 | ||
694 | got_file: | |
1bd0d578 JM |
695 | wpa_printf(MSG_DEBUG, "httpread got file %d bytes type %d", |
696 | h->body_nbytes, h->hdr_type); | |
8640cf7f JM |
697 | wpa_hexdump_ascii(MSG_MSGDUMP, "httpread: body", |
698 | h->body, h->body_nbytes); | |
f620268f JM |
699 | /* Null terminate for convenience of some applications */ |
700 | if (h->body) | |
701 | h->body[h->body_nbytes] = 0; /* null terminate */ | |
702 | h->got_file = 1; | |
703 | /* Assume that we do NOT support keeping connection alive, | |
704 | * and just in case somehow we don't get destroyed right away, | |
705 | * unregister now. | |
706 | */ | |
c89d9dba | 707 | eloop_unregister_sock(h->sd, EVENT_TYPE_READ); |
f620268f JM |
708 | /* The application can destroy us whenever they feel like... |
709 | * cancel timeout. | |
710 | */ | |
c89d9dba | 711 | eloop_cancel_timeout(httpread_timeout_handler, NULL, h); |
f620268f JM |
712 | (*h->cb)(h, h->cookie, HTTPREAD_EVENT_FILE_READY); |
713 | } | |
714 | ||
715 | ||
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. | |
721 | * | |
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. | |
725 | * | |
726 | * Pass max_bytes == 0 to not read body at all (required for e.g. | |
727 | * reply to HEAD request). | |
728 | */ | |
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 */ | |
736 | ) | |
737 | { | |
738 | struct httpread *h = NULL; | |
739 | ||
740 | h = os_zalloc(sizeof(*h)); | |
741 | if (h == NULL) | |
742 | goto fail; | |
743 | h->sd = sd; | |
744 | h->cb = cb; | |
745 | h->cookie = cookie; | |
746 | h->max_bytes = max_bytes; | |
747 | h->timeout_seconds = timeout_seconds; | |
748 | ||
c89d9dba JM |
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) */ | |
753 | goto fail; | |
f620268f JM |
754 | } |
755 | if (eloop_register_sock(sd, EVENT_TYPE_READ, httpread_read_handler, | |
756 | NULL, h)) { | |
757 | /* No way to recover (from malloc failure) */ | |
758 | goto fail; | |
759 | } | |
f620268f JM |
760 | return h; |
761 | ||
762 | fail: | |
763 | ||
764 | /* Error */ | |
765 | httpread_destroy(h); | |
766 | return NULL; | |
767 | } | |
768 | ||
769 | ||
770 | /* httpread_hdr_type_get -- When file is ready, returns header type. */ | |
771 | enum httpread_hdr_type httpread_hdr_type_get(struct httpread *h) | |
772 | { | |
773 | return h->hdr_type; | |
774 | } | |
775 | ||
776 | ||
777 | /* httpread_uri_get -- When file is ready, uri_get returns (translated) URI | |
778 | * or possibly NULL (which would be an error). | |
779 | */ | |
780 | char * httpread_uri_get(struct httpread *h) | |
781 | { | |
782 | return h->uri; | |
783 | } | |
784 | ||
785 | ||
786 | /* httpread_reply_code_get -- When reply is ready, returns reply code */ | |
787 | int httpread_reply_code_get(struct httpread *h) | |
788 | { | |
789 | return h->reply_code; | |
790 | } | |
791 | ||
792 | ||
793 | /* httpread_length_get -- When file is ready, returns file length. */ | |
794 | int httpread_length_get(struct httpread *h) | |
795 | { | |
796 | return h->body_nbytes; | |
797 | } | |
798 | ||
799 | ||
800 | /* httpread_data_get -- When file is ready, returns file content | |
801 | * with null byte appened. | |
802 | * Might return NULL in some error condition. | |
803 | */ | |
804 | void * httpread_data_get(struct httpread *h) | |
805 | { | |
806 | return h->body ? h->body : ""; | |
807 | } | |
808 | ||
809 | ||
810 | /* httpread_hdr_get -- When file is ready, returns header content | |
811 | * with null byte appended. | |
812 | * Might return NULL in some error condition. | |
813 | */ | |
814 | char * httpread_hdr_get(struct httpread *h) | |
815 | { | |
816 | return h->hdr; | |
817 | } | |
818 | ||
819 | ||
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). | |
823 | * | |
824 | * The tag should end with a colon for reliable matching. | |
825 | * | |
826 | * If not found, returns NULL; | |
827 | */ | |
828 | char * httpread_hdr_line_get(struct httpread *h, const char *tag) | |
829 | { | |
830 | int tag_len = os_strlen(tag); | |
831 | char *hdr = h->hdr; | |
832 | hdr = os_strchr(hdr, '\n'); | |
833 | if (hdr == NULL) | |
834 | return NULL; | |
835 | hdr++; | |
836 | for (;;) { | |
837 | if (!os_strncasecmp(hdr, tag, tag_len)) { | |
838 | hdr += tag_len; | |
839 | while (*hdr == ' ' || *hdr == '\t') | |
840 | hdr++; | |
841 | return hdr; | |
842 | } | |
843 | hdr = os_strchr(hdr, '\n'); | |
844 | if (hdr == NULL) | |
845 | return NULL; | |
846 | hdr++; | |
847 | } | |
848 | } |