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