]>
Commit | Line | Data |
---|---|---|
11fdf7f2 | 1 | /* Copyright (c) 2016-2017 the Civetweb developers |
7c673cae FG |
2 | * |
3 | * Permission is hereby granted, free of charge, to any person obtaining a copy | |
4 | * of this software and associated documentation files (the "Software"), to deal | |
5 | * in the Software without restriction, including without limitation the rights | |
6 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
7 | * copies of the Software, and to permit persons to whom the Software is | |
8 | * furnished to do so, subject to the following conditions: | |
9 | * | |
10 | * The above copyright notice and this permission notice shall be included in | |
11 | * all copies or substantial portions of the Software. | |
12 | * | |
13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
19 | * THE SOFTWARE. | |
20 | */ | |
21 | ||
22 | ||
23 | static int | |
24 | url_encoded_field_found(const struct mg_connection *conn, | |
25 | const char *key, | |
26 | size_t key_len, | |
27 | const char *filename, | |
28 | size_t filename_len, | |
29 | char *path, | |
30 | size_t path_len, | |
31 | struct mg_form_data_handler *fdh) | |
32 | { | |
33 | char key_dec[1024]; | |
34 | char filename_dec[1024]; | |
35 | int key_dec_len; | |
36 | int filename_dec_len; | |
37 | int ret; | |
38 | ||
39 | key_dec_len = | |
40 | mg_url_decode(key, (int)key_len, key_dec, (int)sizeof(key_dec), 1); | |
41 | ||
42 | if (((size_t)key_dec_len >= (size_t)sizeof(key_dec)) || (key_dec_len < 0)) { | |
43 | return FORM_FIELD_STORAGE_SKIP; | |
44 | } | |
45 | ||
46 | if (filename) { | |
47 | filename_dec_len = mg_url_decode(filename, | |
48 | (int)filename_len, | |
49 | filename_dec, | |
50 | (int)sizeof(filename_dec), | |
51 | 1); | |
52 | ||
53 | if (((size_t)filename_dec_len >= (size_t)sizeof(filename_dec)) | |
54 | || (filename_dec_len < 0)) { | |
55 | /* Log error message and skip this field. */ | |
56 | mg_cry(conn, "%s: Cannot decode filename", __func__); | |
57 | return FORM_FIELD_STORAGE_SKIP; | |
58 | } | |
59 | } else { | |
60 | filename_dec[0] = 0; | |
61 | } | |
62 | ||
63 | ret = | |
64 | fdh->field_found(key_dec, filename_dec, path, path_len, fdh->user_data); | |
65 | ||
66 | if ((ret & 0xF) == FORM_FIELD_STORAGE_GET) { | |
67 | if (fdh->field_get == NULL) { | |
68 | mg_cry(conn, "%s: Function \"Get\" not available", __func__); | |
69 | return FORM_FIELD_STORAGE_SKIP; | |
70 | } | |
71 | } | |
72 | if ((ret & 0xF) == FORM_FIELD_STORAGE_STORE) { | |
73 | if (fdh->field_store == NULL) { | |
74 | mg_cry(conn, "%s: Function \"Store\" not available", __func__); | |
75 | return FORM_FIELD_STORAGE_SKIP; | |
76 | } | |
77 | } | |
78 | ||
79 | return ret; | |
80 | } | |
81 | ||
82 | ||
83 | static int | |
84 | url_encoded_field_get(const struct mg_connection *conn, | |
85 | const char *key, | |
86 | size_t key_len, | |
87 | const char *value, | |
88 | size_t value_len, | |
89 | struct mg_form_data_handler *fdh) | |
90 | { | |
91 | char key_dec[1024]; | |
92 | ||
11fdf7f2 TL |
93 | char *value_dec = (char *)mg_malloc_ctx(value_len + 1, conn->ctx); |
94 | int value_dec_len, ret; | |
7c673cae FG |
95 | |
96 | if (!value_dec) { | |
97 | /* Log error message and stop parsing the form data. */ | |
98 | mg_cry(conn, | |
99 | "%s: Not enough memory (required: %lu)", | |
100 | __func__, | |
101 | (unsigned long)(value_len + 1)); | |
102 | return FORM_FIELD_STORAGE_ABORT; | |
103 | } | |
104 | ||
105 | mg_url_decode(key, (int)key_len, key_dec, (int)sizeof(key_dec), 1); | |
106 | ||
107 | value_dec_len = | |
108 | mg_url_decode(value, (int)value_len, value_dec, (int)value_len + 1, 1); | |
109 | ||
11fdf7f2 TL |
110 | ret = fdh->field_get(key_dec, |
111 | value_dec, | |
112 | (size_t)value_dec_len, | |
113 | fdh->user_data); | |
114 | ||
115 | mg_free(value_dec); | |
116 | ||
117 | return ret; | |
118 | } | |
119 | ||
120 | ||
121 | static int | |
122 | unencoded_field_get(const struct mg_connection *conn, | |
123 | const char *key, | |
124 | size_t key_len, | |
125 | const char *value, | |
126 | size_t value_len, | |
127 | struct mg_form_data_handler *fdh) | |
128 | { | |
129 | char key_dec[1024]; | |
130 | (void)conn; | |
131 | ||
132 | mg_url_decode(key, (int)key_len, key_dec, (int)sizeof(key_dec), 1); | |
133 | ||
134 | return fdh->field_get(key_dec, value, value_len, fdh->user_data); | |
7c673cae FG |
135 | } |
136 | ||
137 | ||
138 | static int | |
139 | field_stored(const struct mg_connection *conn, | |
140 | const char *path, | |
141 | long long file_size, | |
142 | struct mg_form_data_handler *fdh) | |
143 | { | |
144 | /* Equivalent to "upload" callback of "mg_upload". */ | |
145 | ||
146 | (void)conn; /* we do not need mg_cry here, so conn is currently unused */ | |
147 | ||
148 | return fdh->field_store(path, file_size, fdh->user_data); | |
149 | } | |
150 | ||
151 | ||
152 | static const char * | |
153 | search_boundary(const char *buf, | |
154 | size_t buf_len, | |
155 | const char *boundary, | |
156 | size_t boundary_len) | |
157 | { | |
158 | /* We must do a binary search here, not a string search, since the buffer | |
159 | * may contain '\x00' bytes, if binary data is transferred. */ | |
160 | int clen = (int)buf_len - (int)boundary_len - 4; | |
161 | int i; | |
162 | ||
163 | for (i = 0; i <= clen; i++) { | |
164 | if (!memcmp(buf + i, "\r\n--", 4)) { | |
165 | if (!memcmp(buf + i + 4, boundary, boundary_len)) { | |
166 | return buf + i; | |
167 | } | |
168 | } | |
169 | } | |
170 | return NULL; | |
171 | } | |
172 | ||
173 | ||
174 | int | |
175 | mg_handle_form_request(struct mg_connection *conn, | |
176 | struct mg_form_data_handler *fdh) | |
177 | { | |
178 | const char *content_type; | |
179 | char path[512]; | |
11fdf7f2 | 180 | char buf[1024]; /* Must not be smaller than ~900 - see sanity check */ |
7c673cae FG |
181 | int field_storage; |
182 | int buf_fill = 0; | |
183 | int r; | |
184 | int field_count = 0; | |
11fdf7f2 | 185 | struct mg_file fstore = STRUCT_FILE_INITIALIZER; |
7c673cae FG |
186 | int64_t file_size = 0; /* init here, to a avoid a false positive |
187 | "uninitialized variable used" warning */ | |
188 | ||
189 | int has_body_data = | |
190 | (conn->request_info.content_length > 0) || (conn->is_chunked); | |
191 | ||
192 | /* There are three ways to encode data from a HTML form: | |
193 | * 1) method: GET (default) | |
194 | * The form data is in the HTTP query string. | |
195 | * 2) method: POST, enctype: "application/x-www-form-urlencoded" | |
196 | * The form data is in the request body. | |
197 | * The body is url encoded (the default encoding for POST). | |
198 | * 3) method: POST, enctype: "multipart/form-data". | |
199 | * The form data is in the request body of a multipart message. | |
200 | * This is the typical way to handle file upload from a form. | |
201 | */ | |
202 | ||
203 | if (!has_body_data) { | |
204 | const char *data; | |
205 | ||
206 | if (strcmp(conn->request_info.request_method, "GET")) { | |
207 | /* No body data, but not a GET request. | |
208 | * This is not a valid form request. */ | |
209 | return -1; | |
210 | } | |
211 | ||
212 | /* GET request: form data is in the query string. */ | |
213 | /* The entire data has already been loaded, so there is no nead to | |
214 | * call mg_read. We just need to split the query string into key-value | |
215 | * pairs. */ | |
216 | data = conn->request_info.query_string; | |
217 | if (!data) { | |
218 | /* No query string. */ | |
219 | return -1; | |
220 | } | |
221 | ||
222 | /* Split data in a=1&b=xy&c=3&c=4 ... */ | |
223 | while (*data) { | |
224 | const char *val = strchr(data, '='); | |
225 | const char *next; | |
226 | ptrdiff_t keylen, vallen; | |
227 | ||
228 | if (!val) { | |
229 | break; | |
230 | } | |
231 | keylen = val - data; | |
232 | ||
233 | /* In every "field_found" callback we ask what to do with the | |
234 | * data ("field_storage"). This could be: | |
235 | * FORM_FIELD_STORAGE_SKIP (0) ... ignore the value of this field | |
236 | * FORM_FIELD_STORAGE_GET (1) ... read the data and call the get | |
237 | * callback function | |
238 | * FORM_FIELD_STORAGE_STORE (2) ... store the data in a file | |
239 | * FORM_FIELD_STORAGE_READ (3) ... let the user read the data | |
240 | * (for parsing long data on the fly) | |
241 | * (currently not implemented) | |
242 | * FORM_FIELD_STORAGE_ABORT (flag) ... stop parsing | |
243 | */ | |
244 | memset(path, 0, sizeof(path)); | |
245 | field_count++; | |
246 | field_storage = url_encoded_field_found(conn, | |
247 | data, | |
248 | (size_t)keylen, | |
249 | NULL, | |
250 | 0, | |
251 | path, | |
252 | sizeof(path) - 1, | |
253 | fdh); | |
254 | ||
255 | val++; | |
256 | next = strchr(val, '&'); | |
257 | if (next) { | |
258 | vallen = next - val; | |
259 | next++; | |
260 | } else { | |
261 | vallen = (ptrdiff_t)strlen(val); | |
262 | next = val + vallen; | |
263 | } | |
264 | ||
265 | if (field_storage == FORM_FIELD_STORAGE_GET) { | |
266 | /* Call callback */ | |
267 | url_encoded_field_get( | |
268 | conn, data, (size_t)keylen, val, (size_t)vallen, fdh); | |
269 | } | |
270 | if (field_storage == FORM_FIELD_STORAGE_STORE) { | |
271 | /* Store the content to a file */ | |
11fdf7f2 TL |
272 | if (mg_fopen(conn, path, MG_FOPEN_MODE_WRITE, &fstore) == 0) { |
273 | fstore.access.fp = NULL; | |
7c673cae FG |
274 | } |
275 | file_size = 0; | |
11fdf7f2 TL |
276 | if (fstore.access.fp != NULL) { |
277 | size_t n = (size_t) | |
278 | fwrite(val, 1, (size_t)vallen, fstore.access.fp); | |
279 | if ((n != (size_t)vallen) || (ferror(fstore.access.fp))) { | |
7c673cae FG |
280 | mg_cry(conn, |
281 | "%s: Cannot write file %s", | |
282 | __func__, | |
283 | path); | |
11fdf7f2 | 284 | (void)mg_fclose(&fstore.access); |
7c673cae FG |
285 | remove_bad_file(conn, path); |
286 | } | |
287 | file_size += (int64_t)n; | |
288 | ||
11fdf7f2 TL |
289 | if (fstore.access.fp) { |
290 | r = mg_fclose(&fstore.access); | |
7c673cae FG |
291 | if (r == 0) { |
292 | /* stored successfully */ | |
293 | field_stored(conn, path, file_size, fdh); | |
294 | } else { | |
295 | mg_cry(conn, | |
296 | "%s: Error saving file %s", | |
297 | __func__, | |
298 | path); | |
299 | remove_bad_file(conn, path); | |
300 | } | |
11fdf7f2 | 301 | fstore.access.fp = NULL; |
7c673cae FG |
302 | } |
303 | ||
304 | } else { | |
305 | mg_cry(conn, "%s: Cannot create file %s", __func__, path); | |
306 | } | |
307 | } | |
308 | ||
309 | /* if (field_storage == FORM_FIELD_STORAGE_READ) { */ | |
310 | /* The idea of "field_storage=read" is to let the API user read | |
311 | * data chunk by chunk and to some data processing on the fly. | |
312 | * This should avoid the need to store data in the server: | |
313 | * It should neither be stored in memory, like | |
314 | * "field_storage=get" does, nor in a file like | |
315 | * "field_storage=store". | |
316 | * However, for a "GET" request this does not make any much | |
317 | * sense, since the data is already stored in memory, as it is | |
318 | * part of the query string. | |
319 | */ | |
320 | /* } */ | |
321 | ||
322 | if ((field_storage & FORM_FIELD_STORAGE_ABORT) | |
323 | == FORM_FIELD_STORAGE_ABORT) { | |
324 | /* Stop parsing the request */ | |
325 | break; | |
326 | } | |
327 | ||
328 | /* Proceed to next entry */ | |
329 | data = next; | |
330 | } | |
331 | ||
332 | return field_count; | |
333 | } | |
334 | ||
335 | content_type = mg_get_header(conn, "Content-Type"); | |
336 | ||
337 | if (!content_type | |
338 | || !mg_strcasecmp(content_type, "APPLICATION/X-WWW-FORM-URLENCODED") | |
339 | || !mg_strcasecmp(content_type, "APPLICATION/WWW-FORM-URLENCODED")) { | |
340 | /* The form data is in the request body data, encoded in key/value | |
341 | * pairs. */ | |
342 | int all_data_read = 0; | |
343 | ||
344 | /* Read body data and split it in keys and values. | |
345 | * The encoding is like in the "GET" case above: a=1&b&c=3&c=4. | |
346 | * Here we use "POST", and read the data from the request body. | |
347 | * The data read on the fly, so it is not required to buffer the | |
348 | * entire request in memory before processing it. */ | |
349 | for (;;) { | |
350 | const char *val; | |
351 | const char *next; | |
352 | ptrdiff_t keylen, vallen; | |
353 | ptrdiff_t used; | |
354 | int end_of_key_value_pair_found = 0; | |
355 | int get_block; | |
356 | ||
357 | if ((size_t)buf_fill < (sizeof(buf) - 1)) { | |
358 | ||
359 | size_t to_read = sizeof(buf) - 1 - (size_t)buf_fill; | |
360 | r = mg_read(conn, buf + (size_t)buf_fill, to_read); | |
361 | if (r < 0) { | |
362 | /* read error */ | |
363 | return -1; | |
364 | } | |
365 | if (r != (int)to_read) { | |
366 | /* TODO: Create a function to get "all_data_read" from | |
367 | * the conn object. All data is read if the Content-Length | |
368 | * has been reached, or if chunked encoding is used and | |
369 | * the end marker has been read, or if the connection has | |
370 | * been closed. */ | |
371 | all_data_read = 1; | |
372 | } | |
373 | buf_fill += r; | |
374 | buf[buf_fill] = 0; | |
375 | if (buf_fill < 1) { | |
376 | break; | |
377 | } | |
378 | } | |
379 | ||
380 | val = strchr(buf, '='); | |
381 | ||
382 | if (!val) { | |
383 | break; | |
384 | } | |
385 | keylen = val - buf; | |
386 | val++; | |
387 | ||
388 | /* Call callback */ | |
389 | memset(path, 0, sizeof(path)); | |
390 | field_count++; | |
391 | field_storage = url_encoded_field_found(conn, | |
392 | buf, | |
393 | (size_t)keylen, | |
394 | NULL, | |
395 | 0, | |
396 | path, | |
397 | sizeof(path) - 1, | |
398 | fdh); | |
399 | ||
400 | if ((field_storage & FORM_FIELD_STORAGE_ABORT) | |
401 | == FORM_FIELD_STORAGE_ABORT) { | |
402 | /* Stop parsing the request */ | |
403 | break; | |
404 | } | |
405 | ||
406 | if (field_storage == FORM_FIELD_STORAGE_STORE) { | |
11fdf7f2 TL |
407 | if (mg_fopen(conn, path, MG_FOPEN_MODE_WRITE, &fstore) == 0) { |
408 | fstore.access.fp = NULL; | |
7c673cae FG |
409 | } |
410 | file_size = 0; | |
11fdf7f2 | 411 | if (!fstore.access.fp) { |
7c673cae FG |
412 | mg_cry(conn, "%s: Cannot create file %s", __func__, path); |
413 | } | |
414 | } | |
415 | ||
416 | get_block = 0; | |
417 | /* Loop to read values larger than sizeof(buf)-keylen-2 */ | |
418 | do { | |
419 | next = strchr(val, '&'); | |
420 | if (next) { | |
421 | vallen = next - val; | |
422 | next++; | |
423 | end_of_key_value_pair_found = 1; | |
424 | } else { | |
425 | vallen = (ptrdiff_t)strlen(val); | |
426 | next = val + vallen; | |
427 | } | |
428 | ||
429 | if (field_storage == FORM_FIELD_STORAGE_GET) { | |
430 | #if 0 | |
431 | if (!end_of_key_value_pair_found && !all_data_read) { | |
432 | /* This callback will deliver partial contents */ | |
433 | } | |
434 | #else | |
435 | (void)all_data_read; /* avoid warning */ | |
436 | #endif | |
437 | ||
438 | /* Call callback */ | |
439 | url_encoded_field_get(conn, | |
440 | ((get_block > 0) ? NULL : buf), | |
441 | ((get_block > 0) ? 0 | |
442 | : (size_t)keylen), | |
443 | val, | |
444 | (size_t)vallen, | |
445 | fdh); | |
446 | get_block++; | |
447 | } | |
11fdf7f2 TL |
448 | if (fstore.access.fp) { |
449 | size_t n = (size_t) | |
450 | fwrite(val, 1, (size_t)vallen, fstore.access.fp); | |
451 | if ((n != (size_t)vallen) || (ferror(fstore.access.fp))) { | |
7c673cae FG |
452 | mg_cry(conn, |
453 | "%s: Cannot write file %s", | |
454 | __func__, | |
455 | path); | |
11fdf7f2 | 456 | mg_fclose(&fstore.access); |
7c673cae FG |
457 | remove_bad_file(conn, path); |
458 | } | |
459 | file_size += (int64_t)n; | |
460 | } | |
461 | ||
462 | if (!end_of_key_value_pair_found) { | |
463 | used = next - buf; | |
464 | memmove(buf, | |
465 | buf + (size_t)used, | |
466 | sizeof(buf) - (size_t)used); | |
467 | buf_fill -= (int)used; | |
468 | if ((size_t)buf_fill < (sizeof(buf) - 1)) { | |
469 | ||
470 | size_t to_read = sizeof(buf) - 1 - (size_t)buf_fill; | |
471 | r = mg_read(conn, buf + (size_t)buf_fill, to_read); | |
472 | if (r < 0) { | |
473 | /* read error */ | |
474 | return -1; | |
475 | } | |
476 | if (r != (int)to_read) { | |
477 | /* TODO: Create a function to get "all_data_read" | |
478 | * from the conn object. All data is read if the | |
479 | * Content-Length has been reached, or if chunked | |
480 | * encoding is used and the end marker has been | |
481 | * read, or if the connection has been closed. */ | |
482 | all_data_read = 1; | |
483 | } | |
484 | buf_fill += r; | |
485 | buf[buf_fill] = 0; | |
486 | if (buf_fill < 1) { | |
487 | break; | |
488 | } | |
489 | val = buf; | |
490 | } | |
491 | } | |
492 | ||
493 | } while (!end_of_key_value_pair_found); | |
494 | ||
11fdf7f2 TL |
495 | if (fstore.access.fp) { |
496 | r = mg_fclose(&fstore.access); | |
7c673cae FG |
497 | if (r == 0) { |
498 | /* stored successfully */ | |
499 | field_stored(conn, path, file_size, fdh); | |
500 | } else { | |
501 | mg_cry(conn, "%s: Error saving file %s", __func__, path); | |
502 | remove_bad_file(conn, path); | |
503 | } | |
11fdf7f2 | 504 | fstore.access.fp = NULL; |
7c673cae FG |
505 | } |
506 | ||
507 | /* Proceed to next entry */ | |
508 | used = next - buf; | |
509 | memmove(buf, buf + (size_t)used, sizeof(buf) - (size_t)used); | |
510 | buf_fill -= (int)used; | |
511 | } | |
512 | ||
513 | return field_count; | |
514 | } | |
515 | ||
516 | if (!mg_strncasecmp(content_type, "MULTIPART/FORM-DATA;", 20)) { | |
517 | /* The form data is in the request body data, encoded as multipart | |
518 | * content (see https://www.ietf.org/rfc/rfc1867.txt, | |
519 | * https://www.ietf.org/rfc/rfc2388.txt). */ | |
11fdf7f2 | 520 | char *boundary; |
7c673cae FG |
521 | size_t bl; |
522 | ptrdiff_t used; | |
523 | struct mg_request_info part_header; | |
11fdf7f2 TL |
524 | char *hbuf; |
525 | const char *content_disp, *hend, *fbeg, *fend, *nbeg, *nend; | |
7c673cae | 526 | const char *next; |
11fdf7f2 | 527 | unsigned part_no; |
7c673cae FG |
528 | |
529 | memset(&part_header, 0, sizeof(part_header)); | |
530 | ||
531 | /* Skip all spaces between MULTIPART/FORM-DATA; and BOUNDARY= */ | |
532 | bl = 20; | |
533 | while (content_type[bl] == ' ') { | |
534 | bl++; | |
535 | } | |
536 | ||
537 | /* There has to be a BOUNDARY definition in the Content-Type header */ | |
538 | if (mg_strncasecmp(content_type + bl, "BOUNDARY=", 9)) { | |
539 | /* Malformed request */ | |
540 | return -1; | |
541 | } | |
542 | ||
11fdf7f2 TL |
543 | /* Copy boundary string to variable "boundary" */ |
544 | fbeg = content_type + bl + 9; | |
545 | bl = strlen(fbeg); | |
546 | boundary = (char *)mg_malloc(bl + 1); | |
547 | if (!boundary) { | |
548 | /* Out of memory */ | |
549 | mg_cry(conn, | |
550 | "%s: Cannot allocate memory for boundary [%lu]", | |
551 | __func__, | |
552 | (unsigned long)bl); | |
553 | return -1; | |
554 | } | |
555 | memcpy(boundary, fbeg, bl); | |
556 | boundary[bl] = 0; | |
557 | ||
558 | /* RFC 2046 permits the boundary string to be quoted. */ | |
559 | /* If the boundary is quoted, trim the quotes */ | |
560 | if (boundary[0] == '"') { | |
561 | hbuf = strchr(boundary + 1, '"'); | |
562 | if ((!hbuf) || (*hbuf != '"')) { | |
563 | /* Malformed request */ | |
564 | mg_free(boundary); | |
565 | return -1; | |
566 | } | |
567 | *hbuf = 0; | |
568 | memmove(boundary, boundary + 1, bl); | |
569 | bl = strlen(boundary); | |
570 | } | |
571 | ||
572 | /* Do some sanity checks for boundary lengths */ | |
573 | if (bl > 70) { | |
574 | /* From RFC 2046: | |
575 | * Boundary delimiters must not appear within the | |
576 | * encapsulated material, and must be no longer | |
577 | * than 70 characters, not counting the two | |
578 | * leading hyphens. | |
579 | */ | |
7c673cae | 580 | |
11fdf7f2 TL |
581 | /* The initial sanity check |
582 | * (bl + 800 > sizeof(buf)) | |
583 | * is no longer required, since sizeof(buf) == 1024 | |
584 | * | |
585 | * Original comment: | |
586 | */ | |
7c673cae FG |
587 | /* Sanity check: The algorithm can not work if bl >= sizeof(buf), |
588 | * and it will not work effectively, if the buf is only a few byte | |
11fdf7f2 | 589 | * larger than bl, or if buf can not hold the multipart header |
7c673cae FG |
590 | * plus the boundary. |
591 | * Check some reasonable number here, that should be fulfilled by | |
592 | * any reasonable request from every browser. If it is not | |
593 | * fulfilled, it might be a hand-made request, intended to | |
594 | * interfere with the algorithm. */ | |
11fdf7f2 TL |
595 | mg_free(boundary); |
596 | return -1; | |
597 | } | |
598 | if (bl < 4) { | |
599 | /* Sanity check: A boundary string of less than 4 bytes makes | |
600 | * no sense either. */ | |
601 | mg_free(boundary); | |
7c673cae FG |
602 | return -1; |
603 | } | |
604 | ||
11fdf7f2 | 605 | for (part_no = 0;; part_no++) { |
7c673cae FG |
606 | size_t towrite, n; |
607 | int get_block; | |
608 | ||
609 | r = mg_read(conn, | |
610 | buf + (size_t)buf_fill, | |
611 | sizeof(buf) - 1 - (size_t)buf_fill); | |
612 | if (r < 0) { | |
613 | /* read error */ | |
11fdf7f2 | 614 | mg_free(boundary); |
7c673cae FG |
615 | return -1; |
616 | } | |
617 | buf_fill += r; | |
618 | buf[buf_fill] = 0; | |
619 | if (buf_fill < 1) { | |
620 | /* No data */ | |
11fdf7f2 | 621 | mg_free(boundary); |
7c673cae FG |
622 | return -1; |
623 | } | |
624 | ||
11fdf7f2 TL |
625 | if (part_no == 0) { |
626 | int d = 0; | |
627 | while ((buf[d] != '-') && (d < buf_fill)) { | |
628 | d++; | |
629 | } | |
630 | if ((d > 0) && (buf[d] == '-')) { | |
631 | memmove(buf, buf + d, (unsigned)buf_fill - (unsigned)d); | |
632 | buf_fill -= d; | |
633 | buf[buf_fill] = 0; | |
634 | } | |
635 | } | |
636 | ||
7c673cae FG |
637 | if (buf[0] != '-' || buf[1] != '-') { |
638 | /* Malformed request */ | |
11fdf7f2 | 639 | mg_free(boundary); |
7c673cae FG |
640 | return -1; |
641 | } | |
642 | if (strncmp(buf + 2, boundary, bl)) { | |
643 | /* Malformed request */ | |
11fdf7f2 | 644 | mg_free(boundary); |
7c673cae FG |
645 | return -1; |
646 | } | |
647 | if (buf[bl + 2] != '\r' || buf[bl + 3] != '\n') { | |
648 | /* Every part must end with \r\n, if there is another part. | |
649 | * The end of the request has an extra -- */ | |
650 | if (((size_t)buf_fill != (size_t)(bl + 6)) | |
651 | || (strncmp(buf + bl + 2, "--\r\n", 4))) { | |
652 | /* Malformed request */ | |
11fdf7f2 | 653 | mg_free(boundary); |
7c673cae FG |
654 | return -1; |
655 | } | |
656 | /* End of the request */ | |
657 | break; | |
658 | } | |
659 | ||
660 | /* Next, we need to get the part header: Read until \r\n\r\n */ | |
661 | hbuf = buf + bl + 4; | |
662 | hend = strstr(hbuf, "\r\n\r\n"); | |
663 | if (!hend) { | |
664 | /* Malformed request */ | |
11fdf7f2 | 665 | mg_free(boundary); |
7c673cae FG |
666 | return -1; |
667 | } | |
668 | ||
11fdf7f2 TL |
669 | part_header.num_headers = |
670 | parse_http_headers(&hbuf, part_header.http_headers); | |
7c673cae FG |
671 | if ((hend + 2) != hbuf) { |
672 | /* Malformed request */ | |
11fdf7f2 | 673 | mg_free(boundary); |
7c673cae FG |
674 | return -1; |
675 | } | |
676 | ||
677 | /* Skip \r\n\r\n */ | |
678 | hend += 4; | |
679 | ||
680 | /* According to the RFC, every part has to have a header field like: | |
681 | * Content-Disposition: form-data; name="..." */ | |
11fdf7f2 TL |
682 | content_disp = get_header(part_header.http_headers, |
683 | part_header.num_headers, | |
684 | "Content-Disposition"); | |
7c673cae FG |
685 | if (!content_disp) { |
686 | /* Malformed request */ | |
11fdf7f2 | 687 | mg_free(boundary); |
7c673cae FG |
688 | return -1; |
689 | } | |
690 | ||
691 | /* Get the mandatory name="..." part of the Content-Disposition | |
692 | * header. */ | |
693 | nbeg = strstr(content_disp, "name=\""); | |
11fdf7f2 TL |
694 | while ((nbeg != NULL) && (strcspn(nbeg - 1, ":,; \t") != 0)) { |
695 | /* It could be somethingname= instead of name= */ | |
696 | nbeg = strstr(nbeg + 1, "name=\""); | |
7c673cae | 697 | } |
11fdf7f2 TL |
698 | |
699 | /* This line is not required, but otherwise some compilers | |
700 | * generate spurious warnings. */ | |
701 | nend = nbeg; | |
702 | /* And others complain, the result is unused. */ | |
703 | (void)nend; | |
704 | ||
705 | /* If name=" is found, search for the closing " */ | |
706 | if (nbeg) { | |
707 | nbeg += 6; | |
708 | nend = strchr(nbeg, '\"'); | |
709 | if (!nend) { | |
710 | /* Malformed request */ | |
711 | mg_free(boundary); | |
712 | return -1; | |
713 | } | |
714 | } else { | |
715 | /* name= without quotes is also allowed */ | |
716 | nbeg = strstr(content_disp, "name="); | |
717 | while ((nbeg != NULL) && (strcspn(nbeg - 1, ":,; \t") != 0)) { | |
718 | /* It could be somethingname= instead of name= */ | |
719 | nbeg = strstr(nbeg + 1, "name="); | |
720 | } | |
721 | if (!nbeg) { | |
722 | /* Malformed request */ | |
723 | mg_free(boundary); | |
724 | return -1; | |
725 | } | |
726 | nbeg += 5; | |
727 | ||
728 | /* RFC 2616 Sec. 2.2 defines a list of allowed | |
729 | * separators, but many of them make no sense | |
730 | * here, e.g. various brackets or slashes. | |
731 | * If they are used, probably someone is | |
732 | * trying to attack with curious hand made | |
733 | * requests. Only ; , space and tab seem to be | |
734 | * reasonable here. Ignore everything else. */ | |
735 | nend = nbeg + strcspn(nbeg, ",; \t"); | |
7c673cae FG |
736 | } |
737 | ||
738 | /* Get the optional filename="..." part of the Content-Disposition | |
739 | * header. */ | |
740 | fbeg = strstr(content_disp, "filename=\""); | |
11fdf7f2 TL |
741 | while ((fbeg != NULL) && (strcspn(fbeg - 1, ":,; \t") != 0)) { |
742 | /* It could be somethingfilename= instead of filename= */ | |
743 | fbeg = strstr(fbeg + 1, "filename=\""); | |
744 | } | |
745 | ||
746 | /* This line is not required, but otherwise some compilers | |
747 | * generate spurious warnings. */ | |
748 | fend = fbeg; | |
749 | ||
750 | /* If filename=" is found, search for the closing " */ | |
7c673cae FG |
751 | if (fbeg) { |
752 | fbeg += 10; | |
753 | fend = strchr(fbeg, '\"'); | |
11fdf7f2 | 754 | |
7c673cae FG |
755 | if (!fend) { |
756 | /* Malformed request (the filename field is optional, but if | |
757 | * it exists, it needs to be terminated correctly). */ | |
11fdf7f2 | 758 | mg_free(boundary); |
7c673cae FG |
759 | return -1; |
760 | } | |
761 | ||
762 | /* TODO: check Content-Type */ | |
763 | /* Content-Type: application/octet-stream */ | |
11fdf7f2 TL |
764 | } |
765 | if (!fbeg) { | |
766 | /* Try the same without quotes */ | |
767 | fbeg = strstr(content_disp, "filename="); | |
768 | while ((fbeg != NULL) && (strcspn(fbeg - 1, ":,; \t") != 0)) { | |
769 | /* It could be somethingfilename= instead of filename= */ | |
770 | fbeg = strstr(fbeg + 1, "filename="); | |
771 | } | |
772 | if (fbeg) { | |
773 | fbeg += 9; | |
774 | fend = fbeg + strcspn(fbeg, ",; \t"); | |
775 | } | |
776 | } | |
777 | if (!fbeg) { | |
778 | fend = NULL; | |
779 | } | |
7c673cae | 780 | |
11fdf7f2 TL |
781 | /* In theory, it could be possible that someone crafts |
782 | * a request like name=filename=xyz. Check if name and | |
783 | * filename do not overlap. */ | |
784 | if (!(((ptrdiff_t)fbeg > (ptrdiff_t)nend) | |
785 | || ((ptrdiff_t)nbeg > (ptrdiff_t)fend))) { | |
786 | mg_free(boundary); | |
787 | return -1; | |
7c673cae FG |
788 | } |
789 | ||
11fdf7f2 | 790 | /* Call callback for new field */ |
7c673cae FG |
791 | memset(path, 0, sizeof(path)); |
792 | field_count++; | |
793 | field_storage = url_encoded_field_found(conn, | |
794 | nbeg, | |
795 | (size_t)(nend - nbeg), | |
796 | fbeg, | |
797 | (size_t)(fend - fbeg), | |
798 | path, | |
799 | sizeof(path) - 1, | |
800 | fdh); | |
801 | ||
802 | /* If the boundary is already in the buffer, get the address, | |
803 | * otherwise next will be NULL. */ | |
804 | next = search_boundary(hbuf, | |
805 | (size_t)((buf - hbuf) + buf_fill), | |
806 | boundary, | |
807 | bl); | |
808 | ||
809 | if (field_storage == FORM_FIELD_STORAGE_STORE) { | |
810 | /* Store the content to a file */ | |
11fdf7f2 TL |
811 | if (mg_fopen(conn, path, MG_FOPEN_MODE_WRITE, &fstore) == 0) { |
812 | fstore.access.fp = NULL; | |
7c673cae FG |
813 | } |
814 | file_size = 0; | |
815 | ||
11fdf7f2 | 816 | if (!fstore.access.fp) { |
7c673cae FG |
817 | mg_cry(conn, "%s: Cannot create file %s", __func__, path); |
818 | } | |
819 | } | |
820 | ||
821 | get_block = 0; | |
822 | while (!next) { | |
823 | /* Set "towrite" to the number of bytes available | |
824 | * in the buffer */ | |
825 | towrite = (size_t)(buf - hend + buf_fill); | |
826 | /* Subtract the boundary length, to deal with | |
827 | * cases the boundary is only partially stored | |
828 | * in the buffer. */ | |
829 | towrite -= bl + 4; | |
830 | ||
831 | if (field_storage == FORM_FIELD_STORAGE_GET) { | |
11fdf7f2 TL |
832 | unencoded_field_get(conn, |
833 | ((get_block > 0) ? NULL : nbeg), | |
834 | ((get_block > 0) | |
835 | ? 0 | |
836 | : (size_t)(nend - nbeg)), | |
837 | hend, | |
838 | towrite, | |
839 | fdh); | |
7c673cae FG |
840 | get_block++; |
841 | } | |
842 | ||
843 | if (field_storage == FORM_FIELD_STORAGE_STORE) { | |
11fdf7f2 | 844 | if (fstore.access.fp) { |
7c673cae FG |
845 | |
846 | /* Store the content of the buffer. */ | |
11fdf7f2 TL |
847 | n = (size_t)fwrite(hend, 1, towrite, fstore.access.fp); |
848 | if ((n != towrite) || (ferror(fstore.access.fp))) { | |
7c673cae FG |
849 | mg_cry(conn, |
850 | "%s: Cannot write file %s", | |
851 | __func__, | |
852 | path); | |
11fdf7f2 | 853 | mg_fclose(&fstore.access); |
7c673cae FG |
854 | remove_bad_file(conn, path); |
855 | } | |
856 | file_size += (int64_t)n; | |
857 | } | |
858 | } | |
859 | ||
860 | memmove(buf, hend + towrite, bl + 4); | |
861 | buf_fill = (int)(bl + 4); | |
862 | hend = buf; | |
863 | ||
864 | /* Read new data */ | |
865 | r = mg_read(conn, | |
866 | buf + (size_t)buf_fill, | |
867 | sizeof(buf) - 1 - (size_t)buf_fill); | |
868 | if (r < 0) { | |
869 | /* read error */ | |
11fdf7f2 | 870 | mg_free(boundary); |
7c673cae FG |
871 | return -1; |
872 | } | |
873 | buf_fill += r; | |
874 | buf[buf_fill] = 0; | |
875 | if (buf_fill < 1) { | |
876 | /* No data */ | |
11fdf7f2 | 877 | mg_free(boundary); |
7c673cae FG |
878 | return -1; |
879 | } | |
880 | ||
881 | /* Find boundary */ | |
882 | next = search_boundary(buf, (size_t)buf_fill, boundary, bl); | |
883 | } | |
884 | ||
885 | towrite = (size_t)(next - hend); | |
886 | ||
887 | if (field_storage == FORM_FIELD_STORAGE_GET) { | |
888 | /* Call callback */ | |
11fdf7f2 TL |
889 | unencoded_field_get(conn, |
890 | ((get_block > 0) ? NULL : nbeg), | |
891 | ((get_block > 0) ? 0 | |
892 | : (size_t)(nend - nbeg)), | |
893 | hend, | |
894 | towrite, | |
895 | fdh); | |
7c673cae FG |
896 | } |
897 | ||
898 | if (field_storage == FORM_FIELD_STORAGE_STORE) { | |
899 | ||
11fdf7f2 TL |
900 | if (fstore.access.fp) { |
901 | n = (size_t)fwrite(hend, 1, towrite, fstore.access.fp); | |
902 | if ((n != towrite) || (ferror(fstore.access.fp))) { | |
7c673cae FG |
903 | mg_cry(conn, |
904 | "%s: Cannot write file %s", | |
905 | __func__, | |
906 | path); | |
11fdf7f2 | 907 | mg_fclose(&fstore.access); |
7c673cae | 908 | remove_bad_file(conn, path); |
7c673cae | 909 | } else { |
11fdf7f2 TL |
910 | file_size += (int64_t)n; |
911 | r = mg_fclose(&fstore.access); | |
912 | if (r == 0) { | |
913 | /* stored successfully */ | |
914 | field_stored(conn, path, file_size, fdh); | |
915 | } else { | |
916 | mg_cry(conn, | |
917 | "%s: Error saving file %s", | |
918 | __func__, | |
919 | path); | |
920 | remove_bad_file(conn, path); | |
921 | } | |
7c673cae | 922 | } |
11fdf7f2 | 923 | fstore.access.fp = NULL; |
7c673cae FG |
924 | } |
925 | } | |
926 | ||
927 | if ((field_storage & FORM_FIELD_STORAGE_ABORT) | |
928 | == FORM_FIELD_STORAGE_ABORT) { | |
929 | /* Stop parsing the request */ | |
930 | break; | |
931 | } | |
932 | ||
933 | /* Remove from the buffer */ | |
934 | used = next - buf + 2; | |
935 | memmove(buf, buf + (size_t)used, sizeof(buf) - (size_t)used); | |
936 | buf_fill -= (int)used; | |
937 | } | |
938 | ||
939 | /* All parts handled */ | |
11fdf7f2 | 940 | mg_free(boundary); |
7c673cae FG |
941 | return field_count; |
942 | } | |
943 | ||
944 | /* Unknown Content-Type */ | |
945 | return -1; | |
946 | } | |
11fdf7f2 TL |
947 | |
948 | ||
949 | /* End of handle_form.inl */ |