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