diff options
author | Christian Grothoff <christian@grothoff.org> | 2008-01-31 06:16:41 +0000 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2008-01-31 06:16:41 +0000 |
commit | ccad20a05fcf4ae866551ea792ebdceb2ffd5664 (patch) | |
tree | 069fee4d16e81114a9d16535b22cbad47b92fb09 | |
parent | e7240b0c33a6bbdea113a0b3d24e4c71aa007592 (diff) | |
download | libmicrohttpd-ccad20a05fcf4ae866551ea792ebdceb2ffd5664.tar.gz libmicrohttpd-ccad20a05fcf4ae866551ea792ebdceb2ffd5664.zip |
finished support for nested multiparts
-rw-r--r-- | ChangeLog | 3 | ||||
-rw-r--r-- | src/daemon/postprocessor.c | 951 | ||||
-rw-r--r-- | src/daemon/postprocessor_test.c | 5 |
3 files changed, 662 insertions, 297 deletions
@@ -1,3 +1,6 @@ | |||
1 | Wed Jan 30 23:15:44 MST 2008 | ||
2 | Added support for nested multiparts to post processor. -CG | ||
3 | |||
1 | Mon Jan 21 11:59:46 MST 2008 | 4 | Mon Jan 21 11:59:46 MST 2008 |
2 | Added option to limit number of concurrent connections | 5 | Added option to limit number of concurrent connections |
3 | accepted from the same IP address. -CG | 6 | accepted from the same IP address. -CG |
diff --git a/src/daemon/postprocessor.c b/src/daemon/postprocessor.c index 2b978384..7147c114 100644 --- a/src/daemon/postprocessor.c +++ b/src/daemon/postprocessor.c | |||
@@ -26,28 +26,87 @@ | |||
26 | #include "internal.h" | 26 | #include "internal.h" |
27 | 27 | ||
28 | /** | 28 | /** |
29 | * Size of on-stack buffer that we use for un-escaping of the value. | ||
30 | */ | ||
31 | #define XBUF_SIZE 1024 | ||
32 | |||
33 | /** | ||
29 | * States in the PP parser's state machine. | 34 | * States in the PP parser's state machine. |
30 | */ | 35 | */ |
31 | enum PP_State | 36 | enum PP_State |
32 | { | 37 | { |
33 | PP_Init = 0, | 38 | /* general states */ |
34 | PP_HaveKey = 1, | 39 | PP_Error, |
35 | PP_ExpectNewLine = 2, | 40 | PP_Done, |
36 | PP_ExpectNewLineR = 3, | 41 | PP_Init, |
37 | PP_ExpectNewLineN = 4, | 42 | |
38 | PP_ExpectNewLineNOPT = 5, | 43 | /* url encoding-states*/ |
39 | PP_Headers = 6, | 44 | PP_ProcessValue, |
40 | PP_SkipRN = 7, | 45 | PP_ExpectNewLine, |
41 | PP_SkipN = 8, | 46 | |
42 | PP_ValueToBoundary = 9, | 47 | /* post encoding-states */ |
43 | PP_FinalDash = 10, | 48 | PP_ProcessEntryHeaders, |
44 | PP_FinalRN = 11, | 49 | PP_PerformCheckMultipart, |
45 | PP_FinalN = 12, | 50 | PP_ProcessValueToBoundary, |
46 | PP_Error = 9999, | 51 | PP_PerformCleanup, |
52 | |||
53 | /* nested post-encoding states */ | ||
54 | PP_Nested_Init, | ||
55 | PP_Nested_PerformMarking, | ||
56 | PP_Nested_ProcessEntryHeaders, | ||
57 | PP_Nested_ProcessValueToBoundary, | ||
58 | PP_Nested_PerformCleanup, | ||
59 | |||
60 | }; | ||
61 | |||
62 | enum RN_State | ||
63 | { | ||
64 | /** | ||
65 | * No RN-preprocessing in this state. | ||
66 | */ | ||
67 | RN_Inactive = 0, | ||
68 | |||
69 | /** | ||
70 | * If the next character is '\n', skip it. Otherwise, | ||
71 | * just go inactive. | ||
72 | */ | ||
73 | RN_OptN = 1, | ||
74 | |||
75 | /** | ||
76 | * Expect '\r\n' (and only '\r\n'). As always, we also | ||
77 | * expect only '\r' or only '\n'. | ||
78 | */ | ||
79 | RN_Full = 2, | ||
80 | |||
81 | /** | ||
82 | * Expect either '\r\n' or '--\r\n'. If '--\r\n', transition into dash-state | ||
83 | * for the main state machine | ||
84 | */ | ||
85 | RN_Dash = 3, | ||
86 | |||
87 | /** | ||
88 | * Got a single dash, expect second dash. | ||
89 | */ | ||
90 | RN_Dash2 = 4, | ||
91 | }; | ||
92 | |||
93 | /** | ||
94 | * Bits for the globally known fields that | ||
95 | * should not be deleted when we exit the | ||
96 | * nested state. | ||
97 | */ | ||
98 | enum NE_State | ||
99 | { | ||
100 | NE_none = 0, | ||
101 | NE_content_name = 1, | ||
102 | NE_content_type = 2, | ||
103 | NE_content_filename = 4, | ||
104 | NE_content_transfer_encoding = 8, | ||
47 | }; | 105 | }; |
48 | 106 | ||
49 | /** | 107 | /** |
50 | * Internal state of the post-processor. | 108 | * Internal state of the post-processor. Note that the fields |
109 | * are sorted by type to enable optimal packing by the compiler. | ||
51 | */ | 110 | */ |
52 | struct MHD_PostProcessor | 111 | struct MHD_PostProcessor |
53 | { | 112 | { |
@@ -75,9 +134,19 @@ struct MHD_PostProcessor | |||
75 | const char *encoding; | 134 | const char *encoding; |
76 | 135 | ||
77 | /** | 136 | /** |
137 | * Primary boundary (points into encoding string) | ||
138 | */ | ||
139 | const char * boundary; | ||
140 | |||
141 | /** | ||
142 | * Nested boundary (if we have multipart/mixed encoding). | ||
143 | */ | ||
144 | char * nested_boundary; | ||
145 | |||
146 | /** | ||
78 | * Pointer to the name given in disposition. | 147 | * Pointer to the name given in disposition. |
79 | */ | 148 | */ |
80 | char *content_disposition; | 149 | char *content_name; |
81 | 150 | ||
82 | /** | 151 | /** |
83 | * Pointer to the (current) content type. | 152 | * Pointer to the (current) content type. |
@@ -87,12 +156,12 @@ struct MHD_PostProcessor | |||
87 | /** | 156 | /** |
88 | * Pointer to the (current) filename. | 157 | * Pointer to the (current) filename. |
89 | */ | 158 | */ |
90 | char *filename; | 159 | char *content_filename; |
91 | 160 | ||
92 | /** | 161 | /** |
93 | * Pointer to the (current) encoding. | 162 | * Pointer to the (current) encoding. |
94 | */ | 163 | */ |
95 | char *transfer_encoding; | 164 | char *content_transfer_encoding; |
96 | 165 | ||
97 | /** | 166 | /** |
98 | * Unprocessed value bytes due to escape | 167 | * Unprocessed value bytes due to escape |
@@ -121,10 +190,40 @@ struct MHD_PostProcessor | |||
121 | unsigned int value_offset; | 190 | unsigned int value_offset; |
122 | 191 | ||
123 | /** | 192 | /** |
193 | * strlen(boundary) -- if boundary != NULL. | ||
194 | */ | ||
195 | size_t blen; | ||
196 | |||
197 | /** | ||
198 | * strlen(nested_boundary) -- if nested_boundary != NULL. | ||
199 | */ | ||
200 | size_t nlen; | ||
201 | |||
202 | /** | ||
124 | * State of the parser. | 203 | * State of the parser. |
125 | */ | 204 | */ |
126 | enum PP_State state; | 205 | enum PP_State state; |
127 | 206 | ||
207 | /** | ||
208 | * Side-state-machine: skip '\r\n' (or just '\n'). | ||
209 | * Set to 0 if we are not in skip mode. Set to 2 | ||
210 | * if a '\r\n' is expected, set to 1 if a '\n' should | ||
211 | * be skipped if it is the next character. | ||
212 | */ | ||
213 | enum RN_State skip_rn; | ||
214 | |||
215 | /** | ||
216 | * If we are in skip_rn with "dash" mode and | ||
217 | * do find 2 dashes, what state do we go into? | ||
218 | */ | ||
219 | enum PP_State dash_state; | ||
220 | |||
221 | /** | ||
222 | * Which headers are global? (used to tell which | ||
223 | * headers were only valid for the nested multipart). | ||
224 | */ | ||
225 | enum NE_State have; | ||
226 | |||
128 | }; | 227 | }; |
129 | 228 | ||
130 | 229 | ||
@@ -153,6 +252,8 @@ MHD_create_post_processor (struct MHD_Connection *connection, | |||
153 | { | 252 | { |
154 | struct MHD_PostProcessor *ret; | 253 | struct MHD_PostProcessor *ret; |
155 | const char *encoding; | 254 | const char *encoding; |
255 | const char *boundary; | ||
256 | size_t blen; | ||
156 | 257 | ||
157 | if ((buffer_size < 256) || (connection == NULL) || (ikvi == NULL)) | 258 | if ((buffer_size < 256) || (connection == NULL) || (ikvi == NULL)) |
158 | abort (); | 259 | abort (); |
@@ -161,30 +262,42 @@ MHD_create_post_processor (struct MHD_Connection *connection, | |||
161 | MHD_HTTP_HEADER_CONTENT_TYPE); | 262 | MHD_HTTP_HEADER_CONTENT_TYPE); |
162 | if (encoding == NULL) | 263 | if (encoding == NULL) |
163 | return NULL; | 264 | return NULL; |
164 | if ((0 != strcasecmp (MHD_HTTP_POST_ENCODING_FORM_URLENCODED, | 265 | boundary = NULL; |
165 | encoding)) && | 266 | if (0 != strcasecmp (MHD_HTTP_POST_ENCODING_FORM_URLENCODED, |
166 | (0 != strncasecmp (MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA, encoding, | 267 | encoding)) |
167 | strlen (MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA)))) | 268 | { |
168 | return NULL; | 269 | if (0 != strncasecmp (MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA, encoding, |
270 | strlen (MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA))) | ||
271 | return NULL; | ||
272 | boundary = | ||
273 | &encoding[strlen (MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA)]; | ||
274 | /* Q: should this be "strcasestr"? */ | ||
275 | if (NULL != strstr (boundary, "boundary=")) | ||
276 | boundary = strstr (boundary, "boundary=") + strlen ("boundary="); | ||
277 | else | ||
278 | return NULL; /* failed to determine boundary */ | ||
279 | blen = strlen (boundary); | ||
280 | if ( (blen == 0) || | ||
281 | (blen * 2 + 2 > buffer_size) ) | ||
282 | return NULL; /* (will be) out of memory or invalid boundary */ | ||
283 | } | ||
169 | ret = malloc (sizeof (struct MHD_PostProcessor) + buffer_size + 1); | 284 | ret = malloc (sizeof (struct MHD_PostProcessor) + buffer_size + 1); |
170 | if (ret == NULL) | 285 | if (ret == NULL) |
171 | return NULL; | 286 | return NULL; |
172 | memset (ret, 0, sizeof (struct MHD_PostProcessor)); | 287 | memset (ret, 0, sizeof (struct MHD_PostProcessor) + buffer_size + 1); |
173 | ret->connection = connection; | 288 | ret->connection = connection; |
174 | ret->ikvi = ikvi; | 289 | ret->ikvi = ikvi; |
175 | ret->cls = cls; | 290 | ret->cls = cls; |
176 | ret->encoding = encoding; | 291 | ret->encoding = encoding; |
177 | ret->buffer_size = buffer_size; | 292 | ret->buffer_size = buffer_size; |
178 | ret->state = PP_Init; | 293 | ret->state = PP_Init; |
294 | ret->blen = blen; | ||
295 | ret->boundary = boundary; | ||
296 | ret->skip_rn = RN_Inactive; | ||
179 | return ret; | 297 | return ret; |
180 | } | 298 | } |
181 | 299 | ||
182 | /** | 300 | /** |
183 | * On-stack buffer that we use for un-escaping of the value. | ||
184 | */ | ||
185 | #define XBUF_SIZE 1024 | ||
186 | |||
187 | /** | ||
188 | * Process url-encoded POST data. | 301 | * Process url-encoded POST data. |
189 | */ | 302 | */ |
190 | static int | 303 | static int |
@@ -205,6 +318,12 @@ post_process_urlencoded (struct MHD_PostProcessor *pp, | |||
205 | { | 318 | { |
206 | switch (pp->state) | 319 | switch (pp->state) |
207 | { | 320 | { |
321 | case PP_Error: | ||
322 | return MHD_NO; | ||
323 | case PP_Done: | ||
324 | /* did not expect to receive more data */ | ||
325 | pp->state = PP_Error; | ||
326 | return MHD_NO; | ||
208 | case PP_Init: | 327 | case PP_Init: |
209 | equals = 0; | 328 | equals = 0; |
210 | while ((equals + poff < post_data_len) && | 329 | while ((equals + poff < post_data_len) && |
@@ -223,10 +342,10 @@ post_process_urlencoded (struct MHD_PostProcessor *pp, | |||
223 | pp->buffer_pos = 0; /* reset for next key */ | 342 | pp->buffer_pos = 0; /* reset for next key */ |
224 | MHD_http_unescape (buf); | 343 | MHD_http_unescape (buf); |
225 | poff += equals + 1; | 344 | poff += equals + 1; |
226 | pp->state = PP_HaveKey; | 345 | pp->state = PP_ProcessValue; |
227 | pp->value_offset = 0; | 346 | pp->value_offset = 0; |
228 | break; | 347 | break; |
229 | case PP_HaveKey: | 348 | case PP_ProcessValue: |
230 | /* obtain rest of value from previous iteration */ | 349 | /* obtain rest of value from previous iteration */ |
231 | memcpy (xbuf, pp->xbuf, pp->xbuf_pos); | 350 | memcpy (xbuf, pp->xbuf, pp->xbuf_pos); |
232 | xoff = pp->xbuf_pos; | 351 | xoff = pp->xbuf_pos; |
@@ -301,12 +420,10 @@ post_process_urlencoded (struct MHD_PostProcessor *pp, | |||
301 | { | 420 | { |
302 | poff++; | 421 | poff++; |
303 | /* we are done, report error if we receive any more... */ | 422 | /* we are done, report error if we receive any more... */ |
304 | pp->state = PP_Error; | 423 | pp->state = PP_Done; |
305 | return MHD_YES; | 424 | return MHD_YES; |
306 | } | 425 | } |
307 | return MHD_NO; | 426 | return MHD_NO; |
308 | case PP_Error: | ||
309 | return MHD_NO; | ||
310 | default: | 427 | default: |
311 | abort (); /* should never happen! */ | 428 | abort (); /* should never happen! */ |
312 | } | 429 | } |
@@ -323,6 +440,8 @@ post_process_urlencoded (struct MHD_PostProcessor *pp, | |||
323 | static int | 440 | static int |
324 | try_match_header (const char *prefix, char *line, char **suffix) | 441 | try_match_header (const char *prefix, char *line, char **suffix) |
325 | { | 442 | { |
443 | if (NULL != *suffix) | ||
444 | return MHD_NO; | ||
326 | while(*line != 0) | 445 | while(*line != 0) |
327 | { | 446 | { |
328 | if (0 == strncasecmp (prefix, line, strlen (prefix))) | 447 | if (0 == strncasecmp (prefix, line, strlen (prefix))) |
@@ -335,290 +454,534 @@ try_match_header (const char *prefix, char *line, char **suffix) | |||
335 | return MHD_NO; | 454 | return MHD_NO; |
336 | } | 455 | } |
337 | 456 | ||
457 | static int | ||
458 | find_boundary(struct MHD_PostProcessor * pp, | ||
459 | const char * boundary, | ||
460 | size_t blen, | ||
461 | unsigned int * ioffptr, | ||
462 | enum PP_State next_state, | ||
463 | enum PP_State next_dash_state) | ||
464 | { | ||
465 | char * buf = (char*) &pp[1]; | ||
466 | |||
467 | if (pp->buffer_pos < 2 + blen) | ||
468 | { | ||
469 | if (pp->buffer_pos == pp->buffer_size) | ||
470 | pp->state = PP_Error; /* out of memory */ | ||
471 | return MHD_NO; /* not enough data */ | ||
472 | } | ||
473 | if ( (0 != memcmp ("--", buf, 2)) || | ||
474 | (0 != memcmp (&buf[2], boundary, blen))) | ||
475 | { | ||
476 | pp->state = PP_Error; | ||
477 | return MHD_NO; /* expected boundary */ | ||
478 | } | ||
479 | /* remove boundary from buffer */ | ||
480 | (*ioffptr) += 2 + blen; | ||
481 | /* next: start with headers */ | ||
482 | pp->skip_rn = RN_Dash; | ||
483 | pp->state = next_state; | ||
484 | pp->dash_state = next_dash_state; | ||
485 | return MHD_YES; | ||
486 | } | ||
487 | |||
338 | /** | 488 | /** |
339 | * Decode multipart POST data. | 489 | * In buf, there maybe an expression |
340 | * | 490 | * '$key="$value"'. If that is the case, |
341 | * TODO: If the content-type is multipart/mixed, we do not do anything | 491 | * copy a copy of $value to destination. |
342 | * special. However, we should probably break the individual values | 492 | * |
343 | * apart and give them to the callback individually (will require some | 493 | * If destination is already non-NULL, |
344 | * additional states & state). | 494 | * do nothing. |
495 | */ | ||
496 | static void | ||
497 | try_get_value(const char * buf, | ||
498 | const char * key, | ||
499 | char ** destination) | ||
500 | { | ||
501 | const char * spos; | ||
502 | const char * bpos; | ||
503 | const char * endv; | ||
504 | size_t klen; | ||
505 | size_t vlen; | ||
506 | |||
507 | if (NULL != *destination) | ||
508 | return; | ||
509 | bpos = buf; | ||
510 | klen = strlen(key); | ||
511 | while (NULL != (spos = strstr(bpos, key))) | ||
512 | { | ||
513 | if ( (spos[klen] != '=') || | ||
514 | ( (spos != buf) && | ||
515 | (spos[-1] != ' ') ) ) | ||
516 | { | ||
517 | /* no match */ | ||
518 | bpos = spos + 1; | ||
519 | continue; | ||
520 | } | ||
521 | if (spos[klen + 1] != '"') | ||
522 | return; /* not quoted */ | ||
523 | if (NULL == (endv = strstr(&spos[klen+2], "\""))) | ||
524 | return; /* no end-quote */ | ||
525 | vlen = endv - spos - klen - 1; | ||
526 | *destination = malloc(vlen); | ||
527 | if (NULL == *destination) | ||
528 | return; /* out of memory */ | ||
529 | (*destination)[vlen - 1] = '\0'; | ||
530 | memcpy(*destination, | ||
531 | &spos[klen + 2], | ||
532 | vlen - 1); | ||
533 | return; /* success */ | ||
534 | } | ||
535 | } | ||
536 | |||
537 | /** | ||
538 | * Go over the headers of the part and update | ||
539 | * the fields in "pp" according to what we find. | ||
540 | * If we are at the end of the headers (as indicated | ||
541 | * by an empty line), transition into next_state. | ||
542 | * | ||
543 | * @param ioffptr set to how many bytes have been | ||
544 | * processed | ||
545 | * @return MHD_YES if we can continue processing, | ||
546 | * MHD_NO on error or if we do not have | ||
547 | * enough data yet | ||
548 | */ | ||
549 | static int | ||
550 | process_multipart_headers(struct MHD_PostProcessor * pp, | ||
551 | unsigned int * ioffptr, | ||
552 | enum PP_State next_state) | ||
553 | { | ||
554 | char * buf = (char*) &pp[1]; | ||
555 | unsigned int newline; | ||
556 | |||
557 | newline = 0; | ||
558 | while ((newline < pp->buffer_pos) && | ||
559 | (buf[newline] != '\r') && | ||
560 | (buf[newline] != '\n')) | ||
561 | newline++; | ||
562 | if (newline == pp->buffer_size) | ||
563 | { | ||
564 | pp->state = PP_Error; | ||
565 | return MHD_NO; /* out of memory */ | ||
566 | } | ||
567 | if (newline == pp->buffer_pos) | ||
568 | return MHD_NO; /* will need more data */ | ||
569 | if (newline == 0) | ||
570 | { | ||
571 | /* empty line - end of headers */ | ||
572 | pp->skip_rn = RN_Full; | ||
573 | pp->state = next_state; | ||
574 | return MHD_YES; | ||
575 | } | ||
576 | /* got an actual header */ | ||
577 | if (buf[newline] == '\r') | ||
578 | pp->skip_rn = RN_OptN; | ||
579 | buf[newline] = '\0'; | ||
580 | if (0 == strncasecmp("Content-disposition: ", | ||
581 | buf, | ||
582 | strlen("Content-disposition: "))) | ||
583 | { | ||
584 | try_get_value(&buf[strlen("Content-disposition: ")], | ||
585 | "name", | ||
586 | &pp->content_name); | ||
587 | try_get_value(&buf[strlen("Content-disposition: ")], | ||
588 | "filename", | ||
589 | &pp->content_filename); | ||
590 | } | ||
591 | else | ||
592 | { | ||
593 | try_match_header ("Content-type: ", buf, &pp->content_type); | ||
594 | try_match_header ("Content-Transfer-Encoding: ", | ||
595 | buf, &pp->content_transfer_encoding); | ||
596 | } | ||
597 | (*ioffptr) += newline + 1; | ||
598 | return MHD_YES; | ||
599 | } | ||
600 | |||
601 | /** | ||
602 | * We have the value until we hit the given boundary; | ||
603 | * process accordingly. | ||
345 | * | 604 | * |
346 | * See http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4 | 605 | * @param boundary the boundary to look for |
606 | * @param blen strlen(boundary) | ||
607 | * @param next_state what state to go into after the | ||
608 | * boundary was found | ||
609 | * @param next_dash_state state to go into if the next | ||
610 | * boundary ends with "--" | ||
611 | * @return MHD_YES if we can continue processing, | ||
612 | * MHD_NO on error or if we do not have | ||
613 | * enough data yet | ||
614 | */ | ||
615 | static int | ||
616 | process_value_to_boundary(struct MHD_PostProcessor * pp, | ||
617 | unsigned int * ioffptr, | ||
618 | const char * boundary, | ||
619 | size_t blen, | ||
620 | enum PP_State next_state, | ||
621 | enum PP_State next_dash_state) | ||
622 | { | ||
623 | char * buf = (char*) &pp[1]; | ||
624 | unsigned int newline; | ||
625 | |||
626 | /* all data in buf until the boundary | ||
627 | (\r\n--+boundary) is part of the value */ | ||
628 | newline = 0; | ||
629 | while (1) | ||
630 | { | ||
631 | while ((newline + 4 < pp->buffer_pos) && | ||
632 | (0 != memcmp ("\r\n--", &buf[newline], 4))) | ||
633 | newline++; | ||
634 | if (newline + pp->blen + 4 <= pp->buffer_pos) | ||
635 | { | ||
636 | /* can check boundary */ | ||
637 | if (0 != memcmp (&buf[newline + 4], boundary, pp->blen)) | ||
638 | { | ||
639 | /* no boundary, "\r\n--" is part of content, skip */ | ||
640 | newline += 4; | ||
641 | continue; | ||
642 | } | ||
643 | else | ||
644 | { | ||
645 | /* boundary found, process until newline then | ||
646 | skip boundary and go back to init */ | ||
647 | pp->skip_rn = RN_Dash; | ||
648 | pp->state = next_state; | ||
649 | pp->dash_state = next_dash_state; | ||
650 | (*ioffptr) += pp->blen + 4; /* skip boundary as well */ | ||
651 | break; | ||
652 | } | ||
653 | } | ||
654 | else | ||
655 | { | ||
656 | /* cannot check for boundary, process content that | ||
657 | we have and check again later; except, if we have | ||
658 | no content, abort (out of memory) */ | ||
659 | if ( (newline == 0) && | ||
660 | (pp->buffer_pos == pp->buffer_size) ) | ||
661 | { | ||
662 | pp->state = PP_Error; | ||
663 | return MHD_NO; | ||
664 | } | ||
665 | return MHD_NO; | ||
666 | } | ||
667 | } | ||
668 | /* newline is either at beginning of boundary or | ||
669 | at least at the last character that we are sure | ||
670 | is not part of the boundary */ | ||
671 | if (MHD_NO == pp->ikvi (pp->cls, | ||
672 | MHD_POSTDATA_KIND, | ||
673 | pp->content_name, | ||
674 | pp->content_filename, | ||
675 | pp->content_type, | ||
676 | pp->content_transfer_encoding, | ||
677 | buf, | ||
678 | pp->value_offset, newline)) | ||
679 | { | ||
680 | pp->state = PP_Error; | ||
681 | return MHD_NO; | ||
682 | } | ||
683 | pp->value_offset += newline; | ||
684 | (*ioffptr) += newline; | ||
685 | return MHD_YES; | ||
686 | } | ||
687 | |||
688 | static void | ||
689 | free_unmarked(struct MHD_PostProcessor * pp) | ||
690 | { | ||
691 | if ( (pp->content_name != NULL) && | ||
692 | (0 == (pp->have & NE_content_name)) ) | ||
693 | { | ||
694 | free(pp->content_name); | ||
695 | pp->content_name = NULL; | ||
696 | } | ||
697 | if ( (pp->content_type != NULL) && | ||
698 | (0 == (pp->have & NE_content_type)) ) | ||
699 | { | ||
700 | free(pp->content_type); | ||
701 | pp->content_type = NULL; | ||
702 | } | ||
703 | if ( (pp->content_filename != NULL) && | ||
704 | (0 == (pp->have & NE_content_filename)) ) | ||
705 | { | ||
706 | free(pp->content_filename); | ||
707 | pp->content_filename = NULL; | ||
708 | } | ||
709 | if ( (pp->content_transfer_encoding != NULL) && | ||
710 | (0 == (pp->have & NE_content_transfer_encoding)) ) | ||
711 | { | ||
712 | free(pp->content_transfer_encoding); | ||
713 | pp->content_transfer_encoding = NULL; | ||
714 | } | ||
715 | } | ||
716 | |||
717 | /** | ||
718 | * Decode multipart POST data. | ||
347 | */ | 719 | */ |
348 | static int | 720 | static int |
349 | post_process_multipart (struct MHD_PostProcessor *pp, | 721 | post_process_multipart (struct MHD_PostProcessor *pp, |
350 | const char *post_data, unsigned int post_data_len) | 722 | const char *post_data, unsigned int post_data_len) |
351 | { | 723 | { |
352 | char *buf; | 724 | char *buf; |
353 | const char *boundary; | ||
354 | unsigned int max; | 725 | unsigned int max; |
355 | unsigned int ioff; | 726 | unsigned int ioff; |
356 | unsigned int poff; | 727 | unsigned int poff; |
357 | unsigned int newline; | ||
358 | unsigned int endquote; | ||
359 | size_t blen; | ||
360 | 728 | ||
361 | buf = (char *) &pp[1]; | 729 | buf = (char *) &pp[1]; |
362 | ioff = 0; | 730 | ioff = 0; |
363 | poff = 0; | 731 | poff = 0; |
364 | boundary = | 732 | max = 1; |
365 | &pp->encoding[strlen (MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA)]; | 733 | while ( (poff < post_data_len) || |
366 | /* Q: should this be "strcasestr"? */ | 734 | ( (pp->buffer_pos > 0) && |
367 | if (NULL != strstr (boundary, "boundary=")) | 735 | (max != 0) ) ) |
368 | boundary = strstr (boundary, "boundary=") + strlen ("boundary="); | ||
369 | else | ||
370 | return MHD_NO; /* failed to determine boundary */ | ||
371 | blen = strlen (boundary); | ||
372 | if (blen * 2 + 2 > pp->buffer_size) | ||
373 | return MHD_NO; /* (will be) out of memory */ | ||
374 | while ((poff < post_data_len) || (pp->buffer_pos > ioff)) | ||
375 | { | 736 | { |
376 | /* first, move data to our internal buffer */ | 737 | /* first, move as much input data |
738 | as possible to our internal buffer */ | ||
377 | max = pp->buffer_size - pp->buffer_pos; | 739 | max = pp->buffer_size - pp->buffer_pos; |
378 | if ((max < ioff) && (max < post_data_len - poff)) | ||
379 | { | ||
380 | memmove (buf, &buf[ioff], pp->buffer_pos - ioff); | ||
381 | pp->buffer_pos -= ioff; | ||
382 | ioff = 0; | ||
383 | max = pp->buffer_size - pp->buffer_pos; | ||
384 | } | ||
385 | if (max > post_data_len - poff) | 740 | if (max > post_data_len - poff) |
386 | max = post_data_len - poff; | 741 | max = post_data_len - poff; |
387 | memcpy (&buf[pp->buffer_pos], &post_data[poff], max); | 742 | memcpy (&buf[pp->buffer_pos], &post_data[poff], max); |
388 | poff += max; | 743 | poff += max; |
389 | pp->buffer_pos += max; | 744 | pp->buffer_pos += max; |
745 | if ( (max == 0) && | ||
746 | (poff < post_data_len) ) | ||
747 | { | ||
748 | pp->state = PP_Error; | ||
749 | return MHD_NO; /* out of memory */ | ||
750 | } | ||
751 | |||
752 | /* first state machine for '\r'-'\n' and '--' handling */ | ||
753 | switch (pp->skip_rn) | ||
754 | { | ||
755 | case RN_Inactive: | ||
756 | break; | ||
757 | case RN_OptN: | ||
758 | if (buf[0] == '\n') | ||
759 | { | ||
760 | ioff++; | ||
761 | pp->skip_rn = RN_Inactive; | ||
762 | goto AGAIN; | ||
763 | } | ||
764 | case RN_Dash: | ||
765 | if (buf[0] == '-') | ||
766 | { | ||
767 | ioff++; | ||
768 | pp->skip_rn = RN_Dash2; | ||
769 | goto AGAIN; | ||
770 | } | ||
771 | pp->skip_rn = RN_Full; | ||
772 | /* fall-through! */ | ||
773 | case RN_Full: | ||
774 | if (buf[0] == '\r') | ||
775 | { | ||
776 | if ( (pp->buffer_pos > 1) && | ||
777 | (buf[1] == '\n') ) | ||
778 | { | ||
779 | pp->skip_rn = RN_Inactive; | ||
780 | ioff += 2; | ||
781 | } | ||
782 | else | ||
783 | { | ||
784 | pp->skip_rn = RN_OptN; | ||
785 | ioff++; | ||
786 | } | ||
787 | goto AGAIN; | ||
788 | } | ||
789 | if (buf[0] == '\n') | ||
790 | { | ||
791 | ioff++; | ||
792 | pp->skip_rn = RN_Inactive; | ||
793 | goto AGAIN; | ||
794 | } | ||
795 | pp->skip_rn = RN_Inactive; | ||
796 | pp->state = PP_Error; | ||
797 | return MHD_NO; /* no '\r\n' */ | ||
798 | case RN_Dash2: | ||
799 | if (buf[0] == '-') | ||
800 | { | ||
801 | ioff++; | ||
802 | pp->skip_rn = RN_Full; | ||
803 | pp->state = pp->dash_state; | ||
804 | goto AGAIN; | ||
805 | } | ||
806 | pp->state = PP_Error; | ||
807 | break; | ||
808 | } | ||
390 | 809 | ||
810 | /* main state engine */ | ||
391 | switch (pp->state) | 811 | switch (pp->state) |
392 | { | 812 | { |
393 | case PP_Init: | 813 | case PP_Error: |
394 | /* we're looking for the boundary */ | 814 | return MHD_NO; |
395 | if (pp->buffer_pos < 2 + blen + ioff) | 815 | case PP_Done: |
396 | goto END; | 816 | /* did not expect to receive more data */ |
397 | if ((0 != memcmp ("--", &buf[ioff], 2)) || | 817 | pp->state = PP_Error; |
398 | (0 != memcmp (&buf[ioff + 2], boundary, blen))) | 818 | return MHD_NO; |
399 | return MHD_NO; /* expected boundary */ | 819 | case PP_Init: |
400 | 820 | if (MHD_NO == find_boundary(pp, | |
401 | /* remove boundary from buffer */ | 821 | pp->boundary, |
402 | ioff += 2 + blen; | 822 | pp->blen, |
403 | 823 | &ioff, | |
404 | /* next: start with headers */ | 824 | PP_ProcessEntryHeaders, |
405 | pp->state = PP_ExpectNewLineR; | 825 | PP_Done)) |
826 | { | ||
827 | if (pp->state == PP_Error) | ||
828 | return MHD_NO; | ||
829 | goto END; | ||
830 | } | ||
831 | break; | ||
832 | case PP_ProcessEntryHeaders: | ||
833 | if (MHD_NO == process_multipart_headers(pp, &ioff, PP_PerformCheckMultipart)) | ||
834 | { | ||
835 | if (pp->state == PP_Error) | ||
836 | return MHD_NO; | ||
837 | else | ||
838 | goto END; | ||
839 | } | ||
840 | max = 1; | ||
406 | break; | 841 | break; |
407 | case PP_ExpectNewLineR: | 842 | case PP_PerformCheckMultipart: |
408 | if (buf[ioff] == '-') | 843 | if ( (pp->content_type != NULL) && |
409 | { | 844 | (0 == strncasecmp(pp->content_type, |
410 | /* last boundary ends with "--" */ | 845 | "multipart/mixed", |
411 | ioff++; | 846 | strlen("multipart/mixed")) ) ) |
412 | pp->state = PP_FinalDash; | 847 | { |
413 | break; | 848 | pp->nested_boundary = strstr(pp->content_type, |
414 | } | 849 | "boundary="); |
415 | if (buf[ioff] == '\r') | 850 | if (pp->nested_boundary == NULL) |
416 | { | 851 | { |
417 | ioff++; | 852 | pp->state = PP_Error; |
418 | pp->state = PP_ExpectNewLineNOPT; | 853 | return MHD_NO; |
419 | break; | 854 | } |
420 | } | 855 | pp->nested_boundary = strdup(&pp->nested_boundary[strlen("boundary=")]); |
421 | /* fall through! */ | 856 | if (pp->nested_boundary == NULL) |
422 | case PP_ExpectNewLineN: | 857 | { |
423 | if (buf[ioff] == '\n') | 858 | /* out of memory */ |
424 | { | 859 | pp->state = PP_Error; |
425 | ioff++; | 860 | return MHD_NO; |
426 | pp->state = PP_Headers; | 861 | } |
427 | break; | 862 | /* free old content type, we will need that field |
428 | } | 863 | for the content type of the nested elements */ |
429 | return MHD_NO; | 864 | free(pp->content_type); |
430 | case PP_ExpectNewLineNOPT: | 865 | pp->content_type = NULL; |
431 | if (buf[ioff] == '\n') | 866 | pp->nlen = strlen(pp->nested_boundary); |
432 | { | 867 | pp->state = PP_Nested_Init; |
433 | ioff++; | 868 | max = 1; |
434 | pp->state = PP_Headers; | 869 | break; |
435 | break; | 870 | } |
436 | } | 871 | pp->state = PP_ProcessValueToBoundary; |
437 | /* fall through! */ | 872 | pp->value_offset = 0; |
438 | case PP_Headers: | 873 | max = 1; |
439 | newline = 0; | 874 | break; |
440 | while ((newline + ioff < pp->buffer_pos) && | 875 | case PP_ProcessValueToBoundary: |
441 | (buf[newline + ioff] != '\r') && | 876 | if (MHD_NO == process_value_to_boundary(pp, |
442 | (buf[newline + ioff] != '\n')) | 877 | &ioff, |
443 | newline++; | 878 | pp->boundary, |
444 | if (newline == pp->buffer_size) | 879 | pp->blen, |
445 | return MHD_NO; /* out of memory */ | 880 | PP_PerformCleanup, |
446 | if (newline + ioff == pp->buffer_pos) | 881 | PP_Done)) |
447 | { | 882 | { |
448 | /* try to make more room */ | 883 | if (pp->state == PP_Error) |
449 | memmove (buf, &buf[ioff], pp->buffer_pos - ioff); | 884 | return MHD_NO; |
450 | pp->buffer_pos -= ioff; | 885 | break; |
451 | ioff = 0; | 886 | } |
452 | break; | 887 | break; |
453 | } | 888 | case PP_PerformCleanup: |
454 | if (newline == 0) | 889 | /* clean up state of one multipart form-data element! */ |
455 | { | 890 | pp->have = NE_none; |
456 | pp->state = PP_SkipRN; | 891 | free_unmarked(pp); |
457 | break; | 892 | if (pp->nested_boundary != NULL) |
458 | } | 893 | { |
459 | buf[ioff + newline] = '\0'; | 894 | free (pp->nested_boundary); |
460 | if ((MHD_YES | 895 | pp->nested_boundary = NULL; |
461 | == try_match_header ("Content-Disposition: form-data; name=\"", | 896 | } |
462 | &buf[ioff], | 897 | pp->state = PP_ProcessEntryHeaders; |
463 | &pp->content_disposition)) && | 898 | max = 1; |
464 | (pp->content_disposition != NULL) && | 899 | break; |
465 | (0 < strlen (pp->content_disposition))) | 900 | case PP_Nested_Init: |
466 | { | 901 | if (pp->nested_boundary == NULL) |
467 | /* find end-quote; then check if we also have a filename! */ | 902 | { |
468 | endquote = 0; | 903 | pp->state = PP_Error; |
469 | while ((pp->content_disposition[endquote] != '\"') && | 904 | return MHD_NO; |
470 | (pp->content_disposition[endquote] != '\0')) | 905 | } |
471 | endquote++; | 906 | if (MHD_NO == find_boundary(pp, |
472 | pp->content_disposition[endquote++] = '\0'; /* remove end-quote */ | 907 | pp->nested_boundary, |
473 | if ((MHD_YES | 908 | pp->nlen, |
474 | == try_match_header (" filename=\"", | 909 | &ioff, |
475 | &pp->content_disposition[endquote], | 910 | PP_Nested_PerformMarking, |
476 | &pp->filename)) && | 911 | PP_Init /* or PP_Error? */)) |
477 | (pp->filename != NULL) && (0 < strlen (pp->filename))) | 912 | { |
478 | pp->filename[strlen (pp->filename) - 1] = '\0'; /* remove end-quote */ | 913 | if (pp->state == PP_Error) |
479 | } | 914 | return MHD_NO; |
480 | try_match_header ("Content-Type: ", &buf[ioff], &pp->content_type); | 915 | goto END; |
481 | try_match_header ("Content-Transfer-Encoding: ", | 916 | } |
482 | &buf[ioff], &pp->transfer_encoding); | 917 | break; |
483 | ioff += newline + 1; | 918 | case PP_Nested_PerformMarking: |
484 | pp->state = PP_ExpectNewLineNOPT; | 919 | /* remember what headers were given |
920 | globally */ | ||
921 | pp->have = NE_none; | ||
922 | if (pp->content_name != NULL) | ||
923 | pp->have |= NE_content_name; | ||
924 | if (pp->content_type != NULL) | ||
925 | pp->have |= NE_content_type; | ||
926 | if (pp->content_filename != NULL) | ||
927 | pp->have |= NE_content_filename; | ||
928 | if (pp->content_transfer_encoding != NULL) | ||
929 | pp->have |= NE_content_transfer_encoding; | ||
930 | pp->state = PP_Nested_ProcessEntryHeaders; | ||
931 | max = 1; | ||
932 | break; | ||
933 | case PP_Nested_ProcessEntryHeaders: | ||
934 | pp->value_offset = 0; | ||
935 | if (MHD_NO == process_multipart_headers(pp, &ioff, PP_Nested_ProcessValueToBoundary)) | ||
936 | { | ||
937 | if (pp->state == PP_Error) | ||
938 | return MHD_NO; | ||
939 | else | ||
940 | goto END; | ||
941 | } | ||
942 | max = 1; | ||
485 | break; | 943 | break; |
486 | case PP_SkipRN: | 944 | case PP_Nested_ProcessValueToBoundary: |
487 | if (buf[ioff] == '\r') | 945 | if (MHD_NO == process_value_to_boundary(pp, |
488 | { | 946 | &ioff, |
489 | ioff++; | 947 | pp->nested_boundary, |
490 | pp->state = PP_SkipN; | 948 | pp->nlen, |
491 | break; | 949 | PP_Nested_PerformCleanup, |
492 | } | 950 | PP_Init)) |
493 | /* fall through! */ | 951 | { |
494 | case PP_SkipN: | 952 | if (pp->state == PP_Error) |
495 | if (buf[ioff] == '\n') | 953 | return MHD_NO; |
496 | { | 954 | break; |
497 | ioff++; | 955 | } |
498 | pp->state = PP_ValueToBoundary; | 956 | break; |
499 | pp->value_offset = 0; | 957 | case PP_Nested_PerformCleanup: |
500 | break; | 958 | free_unmarked(pp); |
501 | } | 959 | pp->state = PP_Nested_ProcessEntryHeaders; |
502 | return MHD_NO; /* parse error */ | 960 | max = 1; |
503 | case PP_ValueToBoundary: | 961 | break; |
504 | /* all data in buf until the boundary | ||
505 | (\r\n--+boundary) is part of the value */ | ||
506 | newline = 0; | ||
507 | while (1) | ||
508 | { | ||
509 | while ((newline + ioff + 4 < pp->buffer_pos) && | ||
510 | (0 != memcmp ("\r\n--", &buf[newline + ioff], 4))) | ||
511 | newline++; | ||
512 | if (newline + blen + 4 > pp->buffer_size) | ||
513 | { | ||
514 | /* boundary not in sight -- | ||
515 | process data, then make room for more! */ | ||
516 | if (MHD_NO == pp->ikvi (pp->cls, | ||
517 | MHD_POSTDATA_KIND, | ||
518 | pp->content_disposition, | ||
519 | pp->filename, | ||
520 | pp->content_type, | ||
521 | pp->transfer_encoding, | ||
522 | &buf[ioff], | ||
523 | pp->value_offset, newline)) | ||
524 | { | ||
525 | pp->state = PP_Error; | ||
526 | break; | ||
527 | } | ||
528 | pp->value_offset += newline; | ||
529 | ioff += newline; | ||
530 | memmove (buf, &buf[ioff], pp->buffer_pos - ioff); | ||
531 | pp->buffer_pos -= ioff; | ||
532 | ioff = 0; | ||
533 | break; | ||
534 | } | ||
535 | if (newline + blen + 4 < pp->buffer_pos) | ||
536 | { | ||
537 | /* can check for boundary right now! */ | ||
538 | if (0 == memcmp (&buf[newline + ioff + 4], boundary, blen)) | ||
539 | { | ||
540 | /* found: process data, then look for more */ | ||
541 | if (MHD_NO == pp->ikvi (pp->cls, | ||
542 | MHD_POSTDATA_KIND, | ||
543 | pp->content_disposition, | ||
544 | pp->filename, | ||
545 | pp->content_type, | ||
546 | pp->transfer_encoding, | ||
547 | &buf[ioff], | ||
548 | pp->value_offset, newline)) | ||
549 | { | ||
550 | pp->state = PP_Error; | ||
551 | break; | ||
552 | } | ||
553 | |||
554 | /* clean up! */ | ||
555 | if (pp->content_type != NULL) | ||
556 | { | ||
557 | free (pp->content_type); | ||
558 | pp->content_type = NULL; | ||
559 | } | ||
560 | if (pp->content_disposition != NULL) | ||
561 | { | ||
562 | free (pp->content_disposition); | ||
563 | pp->content_disposition = NULL; | ||
564 | } | ||
565 | if (pp->filename != NULL) | ||
566 | { | ||
567 | free (pp->filename); | ||
568 | pp->filename = NULL; | ||
569 | } | ||
570 | if (pp->transfer_encoding != NULL) | ||
571 | { | ||
572 | free (pp->transfer_encoding); | ||
573 | pp->transfer_encoding = NULL; | ||
574 | } | ||
575 | pp->value_offset = 0; | ||
576 | ioff += newline + 2; /* skip data + new line */ | ||
577 | pp->state = PP_Init; | ||
578 | break; | ||
579 | } | ||
580 | /* not the boundary, look further! */ | ||
581 | newline += 4; | ||
582 | continue; | ||
583 | } | ||
584 | goto END; | ||
585 | } | ||
586 | break; | ||
587 | case PP_FinalDash: | ||
588 | if (buf[ioff] == '-') | ||
589 | { | ||
590 | /* last boundary ends with "--" */ | ||
591 | ioff++; | ||
592 | pp->state = PP_FinalRN; | ||
593 | break; | ||
594 | } | ||
595 | return MHD_NO; /* parse error */ | ||
596 | case PP_FinalRN: | ||
597 | if (buf[ioff] == '\r') | ||
598 | { | ||
599 | ioff++; | ||
600 | pp->state = PP_FinalN; | ||
601 | break; | ||
602 | } | ||
603 | /* fall through! */ | ||
604 | case PP_FinalN: | ||
605 | if (buf[ioff] == '\n') | ||
606 | { | ||
607 | ioff++; | ||
608 | pp->state = PP_Error; | ||
609 | break; | ||
610 | } | ||
611 | return MHD_NO; /* parse error */ | ||
612 | case PP_Error: | ||
613 | return MHD_NO; | ||
614 | default: | 962 | default: |
615 | abort (); /* should never happen! */ | 963 | abort (); /* should never happen! */ |
616 | |||
617 | } | 964 | } |
965 | AGAIN: | ||
966 | if (ioff > 0) | ||
967 | { | ||
968 | memmove (buf, &buf[ioff], pp->buffer_pos - ioff); | ||
969 | pp->buffer_pos -= ioff; | ||
970 | ioff = 0; | ||
971 | max = 1; | ||
972 | } | ||
618 | } | 973 | } |
619 | END: | 974 | END: |
620 | memmove (buf, &buf[ioff], pp->buffer_pos - ioff); | 975 | if (ioff != 0) |
621 | pp->buffer_pos -= ioff; | 976 | { |
977 | memmove (buf, &buf[ioff], pp->buffer_pos - ioff); | ||
978 | pp->buffer_pos -= ioff; | ||
979 | } | ||
980 | if (poff < post_data_len) | ||
981 | { | ||
982 | pp->state = PP_Error; | ||
983 | return MHD_NO; /* serious error */ | ||
984 | } | ||
622 | return MHD_YES; | 985 | return MHD_YES; |
623 | } | 986 | } |
624 | 987 | ||
@@ -661,14 +1024,10 @@ MHD_destroy_post_processor (struct MHD_PostProcessor *pp) | |||
661 | /* These internal strings need cleaning up since | 1024 | /* These internal strings need cleaning up since |
662 | the post-processing may have been interrupted | 1025 | the post-processing may have been interrupted |
663 | at any stage */ | 1026 | at any stage */ |
664 | if (pp->content_type != NULL) | 1027 | pp->have = NE_none; |
665 | free (pp->content_type); | 1028 | free_unmarked(pp); |
666 | if (pp->content_disposition != NULL) | 1029 | if (pp->nested_boundary != NULL) |
667 | free (pp->content_disposition); | 1030 | free(pp->nested_boundary); |
668 | if (pp->filename != NULL) | ||
669 | free (pp->filename); | ||
670 | if (pp->transfer_encoding != NULL) | ||
671 | free (pp->transfer_encoding); | ||
672 | free (pp); | 1031 | free (pp); |
673 | } | 1032 | } |
674 | 1033 | ||
diff --git a/src/daemon/postprocessor_test.c b/src/daemon/postprocessor_test.c index 46f2463a..a931b300 100644 --- a/src/daemon/postprocessor_test.c +++ b/src/daemon/postprocessor_test.c | |||
@@ -85,9 +85,11 @@ value_checker(void * cls, | |||
85 | int * want_off = cls; | 85 | int * want_off = cls; |
86 | int idx = *want_off; | 86 | int idx = *want_off; |
87 | 87 | ||
88 | #if 0 | ||
88 | fprintf(stderr, | 89 | fprintf(stderr, |
89 | "VC: `%s' `%s' `%s' `%s' `%.*s'\n", | 90 | "VC: `%s' `%s' `%s' `%s' `%.*s'\n", |
90 | key, filename, content_type, transfer_encoding, size, data); | 91 | key, filename, content_type, transfer_encoding, size, data); |
92 | #endif | ||
91 | if (size == 0) | 93 | if (size == 0) |
92 | return MHD_YES; | 94 | return MHD_YES; |
93 | if ( (idx < 0) || | 95 | if ( (idx < 0) || |
@@ -213,8 +215,9 @@ int | |||
213 | main (int argc, char *const *argv) | 215 | main (int argc, char *const *argv) |
214 | { | 216 | { |
215 | unsigned int errorCount = 0; | 217 | unsigned int errorCount = 0; |
218 | |||
216 | errorCount += test_urlencoding(); | 219 | errorCount += test_urlencoding(); |
217 | errorCount += test_multipart(); | 220 | errorCount += test_multipart(); |
218 | errorCount += test_nested_multipart(); | 221 | errorCount += test_nested_multipart(); |
219 | if (errorCount != 0) | 222 | if (errorCount != 0) |
220 | fprintf (stderr, "Error (code: %u)\n", errorCount); | 223 | fprintf (stderr, "Error (code: %u)\n", errorCount); |