├── .gitignore ├── Makefile ├── LICENSE ├── multipartparser.h ├── README.md ├── multipartparser.c └── tests.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | tests 2 | *.so 3 | *.o 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS += -W -Wall -Wextra -Og 2 | SONAME = libmultipartparser.so 3 | 4 | all: $(SONAME) 5 | 6 | $(SONAME): multipartparser.o 7 | $(CC) $(LDFLAGS) -shared -o $(SONAME) $< 8 | 9 | tests: tests.o multipartparser.o 10 | $(CXX) -o $@ $^ 11 | 12 | multipartparser.o: multipartparser.c multipartparser.h 13 | $(CC) $(CFLAGS) -fPIC -c -o $@ $< 14 | 15 | clean: 16 | rm -f *.o 17 | 18 | fclean: clean 19 | rm -f $(SONAME) tests 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 François Colas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /multipartparser.h: -------------------------------------------------------------------------------- 1 | #ifndef MULTIPARTPARSER_H 2 | #define MULTIPARTPARSER_H 3 | #ifdef __cplusplus 4 | extern "C" { 5 | #endif 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | typedef struct multipartparser multipartparser; 12 | typedef struct multipartparser_callbacks multipartparser_callbacks; 13 | 14 | typedef int (*multipart_cb) (multipartparser*); 15 | typedef int (*multipart_data_cb) (multipartparser*, const char* data, size_t size); 16 | 17 | struct multipartparser { 18 | /** PRIVATE **/ 19 | char boundary[70]; 20 | int boundary_length; 21 | int index; 22 | uint16_t state; 23 | 24 | /** PUBLIC **/ 25 | void* data; 26 | }; 27 | 28 | struct multipartparser_callbacks { 29 | multipart_cb on_body_begin; 30 | multipart_cb on_part_begin; 31 | multipart_data_cb on_header_field; 32 | multipart_data_cb on_header_value; 33 | multipart_cb on_headers_complete; 34 | multipart_data_cb on_data; 35 | multipart_cb on_part_end; 36 | multipart_cb on_body_end; 37 | }; 38 | 39 | void multipartparser_init(multipartparser* parser, const char* boundary); 40 | 41 | void multipartparser_callbacks_init(multipartparser_callbacks* callbacks); 42 | 43 | size_t multipartparser_execute(multipartparser* parser, 44 | multipartparser_callbacks* callbacks, 45 | const char* data, 46 | size_t size); 47 | 48 | #ifdef __cplusplus 49 | } 50 | #endif 51 | #endif // MULTIPARTPARSER_H 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Multipart Parser 2 | ================ 3 | 4 | This is a parser written in C for multipart bodies. The code was mostly 5 | inspired by [joyent/http-parser](https://github.com/joyent/http-parser). The 6 | parser does not make any syscalls nor allocations, it does not buffer data 7 | except the boundary that you pass to `mulpartparser_init()` function. 8 | 9 | Usage 10 | ----- 11 | 12 | One `multipartparser` struct must be initialized every time you need to parse 13 | a body. The `multipartparser_callbacks` struct can be initialized only once 14 | and can be used by many parsers at the same time. The code to initialize a 15 | multipart parser should be similar to this: 16 | 17 | ```C 18 | multipartparser_callbacks callbacks; 19 | multipartparser parser; 20 | 21 | multipartparser_callbacks_init(&callbacks); // It only sets all callbacks to NULL. 22 | callbacks.on_body_begin = &on_body_begin; 23 | callbacks.on_part_begin = &on_part_begin; 24 | callbacks.on_header_field = &on_header_field; 25 | callbacks.on_header_value = &on_header_value; 26 | callbacks.on_headers_complete = &on_headers_complete; 27 | callbacks.on_data = &on_data; 28 | callbacks.on_part_end = &on_part_end; 29 | callbacks.on_body_end = &on_body_end; 30 | 31 | multipartparser_init(&parser, boundary); 32 | parser->data = my_data; 33 | ``` 34 | 35 | When there is data to parse call the multipart parser like this: 36 | 37 | ```C 38 | size_t nparsed; 39 | 40 | nparsed = multipartparser_execute(&parser, &callbacks, data, size); 41 | if (nparsed != size) { 42 | /* parse error */ 43 | } 44 | ``` 45 | 46 | Callbacks 47 | --------- 48 | 49 | See [joyent/http-parser's README](https://github.com/joyent/http-parser/blob/master/README.md#callbacks) 50 | to understand how to use callbacks especially for `on_header_field` and `on_header_value`. 51 | -------------------------------------------------------------------------------- /multipartparser.c: -------------------------------------------------------------------------------- 1 | #include "multipartparser.h" 2 | 3 | #include 4 | 5 | #define CR '\r' 6 | #define LF '\n' 7 | #define SP ' ' 8 | #define HT '\t' 9 | #define HYPHEN '-' 10 | 11 | #define CALLBACK_NOTIFY(NAME) \ 12 | if (callbacks->on_##NAME != NULL) { \ 13 | if (callbacks->on_##NAME(parser) != 0) \ 14 | goto error; \ 15 | } 16 | 17 | #define CALLBACK_DATA(NAME, P, S) \ 18 | if (callbacks->on_##NAME != NULL) { \ 19 | if (callbacks->on_##NAME(parser, P, S) != 0) \ 20 | goto error; \ 21 | } 22 | 23 | enum state { 24 | s_preamble, 25 | s_preamble_hy_hy, 26 | s_first_boundary, 27 | s_header_field_start, 28 | s_header_field, 29 | s_header_value_start, 30 | s_header_value, 31 | s_header_value_cr, 32 | s_headers_done, 33 | s_data, 34 | s_data_cr, 35 | s_data_cr_lf, 36 | s_data_cr_lf_hy, 37 | s_data_boundary_start, 38 | s_data_boundary, 39 | s_data_boundary_done, 40 | s_data_boundary_done_cr_lf, 41 | s_data_boundary_done_hy_hy, 42 | s_epilogue, 43 | }; 44 | 45 | /* Header field name as defined by rfc 2616. Also lowercases them. 46 | * field-name = token 47 | * token = 1* 48 | * CTL = 49 | * tspecials = "(" | ")" | "<" | ">" | "@" 50 | * | "," | ";" | ":" | "\" | DQUOTE 51 | * | "/" | "[" | "]" | "?" | "=" 52 | * | "{" | "}" | SP | HT 53 | * DQUOTE = 54 | * SP = 55 | * HT = 56 | */ 57 | static const char header_field_chars[256] = { 58 | /* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ 59 | 0, 0, 0, 0, 0, 0, 0, 0, 60 | /* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ 61 | 0, 0, 0, 0, 0, 0, 0, 0, 62 | /* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ 63 | 0, 0, 0, 0, 0, 0, 0, 0, 64 | /* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ 65 | 0, 0, 0, 0, 0, 0, 0, 0, 66 | /* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ 67 | 0, '!', 0, '#', '$', '%', '&', '\'', 68 | /* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ 69 | 0, 0, '*', '+', 0, '-', '.', 0, 70 | /* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ 71 | '0', '1', '2', '3', '4', '5', '6', '7', 72 | /* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ 73 | '8', '9', 0, 0, 0, 0, 0, 0, 74 | /* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ 75 | 0, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 76 | /* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ 77 | 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 78 | /* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ 79 | 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 80 | /* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ 81 | 'X', 'Y', 'Z', 0, 0, 0, '^', '_', 82 | /* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ 83 | '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 84 | /* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ 85 | 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 86 | /* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ 87 | 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 88 | /* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ 89 | 'x', 'y', 'z', 0, '|', 0, '~', 0 90 | }; 91 | 92 | void multipartparser_init(multipartparser* parser, const char* boundary) 93 | { 94 | memset(parser, 0, sizeof(*parser)); 95 | 96 | strncpy(parser->boundary, boundary, sizeof(parser->boundary)); 97 | parser->boundary_length = strlen(parser->boundary); 98 | 99 | parser->state = s_preamble; 100 | } 101 | 102 | void multipartparser_callbacks_init(multipartparser_callbacks* callbacks) 103 | { 104 | memset(callbacks, 0, sizeof(*callbacks)); 105 | } 106 | 107 | size_t multipartparser_execute(multipartparser* parser, 108 | multipartparser_callbacks* callbacks, 109 | const char* data, 110 | size_t size) 111 | { 112 | const char* mark; 113 | const char* p; 114 | unsigned char c; 115 | 116 | for (p = data; p < data + size; ++p) { 117 | c = *p; 118 | 119 | reexecute: 120 | switch (parser->state) { 121 | 122 | case s_preamble: 123 | if (c == HYPHEN) 124 | parser->state = s_preamble_hy_hy; 125 | // else ignore everything before first boundary 126 | break; 127 | 128 | case s_preamble_hy_hy: 129 | if (c == HYPHEN) 130 | parser->state = s_first_boundary; 131 | else 132 | parser->state = s_preamble; 133 | break; 134 | 135 | case s_first_boundary: 136 | if (parser->index == parser->boundary_length) { 137 | if (c != CR) 138 | goto error; 139 | parser->index++; 140 | break; 141 | } 142 | if (parser->index == parser->boundary_length + 1) { 143 | if (c != LF) 144 | goto error; 145 | CALLBACK_NOTIFY(body_begin); 146 | CALLBACK_NOTIFY(part_begin); 147 | parser->index = 0; 148 | parser->state = s_header_field_start; 149 | break; 150 | } 151 | if (c == parser->boundary[parser->index]) { 152 | parser->index++; 153 | break; 154 | } 155 | goto error; 156 | 157 | case s_header_field_start: 158 | if (c == CR) { 159 | parser->state = s_headers_done; 160 | break; 161 | } 162 | parser->state = s_header_field; 163 | // fallthrough; 164 | 165 | case s_header_field: 166 | mark = p; 167 | while (p != data + size) { 168 | c = *p; 169 | if (header_field_chars[c] == 0) 170 | break; 171 | ++p; 172 | } 173 | if (p > mark) { 174 | CALLBACK_DATA(header_field, mark, p - mark); 175 | } 176 | if (p == data + size) { 177 | break; 178 | } 179 | if (c == ':') { 180 | parser->state = s_header_value_start; 181 | break; 182 | } 183 | goto error; 184 | 185 | case s_header_value_start: 186 | if (c == SP || c == HT) { 187 | break; 188 | } 189 | parser->state = s_header_value; 190 | // fallthrough; 191 | 192 | case s_header_value: 193 | mark = p; 194 | while (p != data + size) { 195 | c = *p; 196 | if (c == CR) { 197 | parser->state = s_header_value_cr; 198 | break; 199 | } 200 | ++p; 201 | } 202 | if (p > mark) { 203 | CALLBACK_DATA(header_value, mark, p - mark); 204 | } 205 | break; 206 | 207 | case s_header_value_cr: 208 | if (c == LF) { 209 | parser->state = s_header_field_start; 210 | break; 211 | } 212 | goto error; 213 | 214 | case s_headers_done: 215 | if (c == LF) { 216 | CALLBACK_NOTIFY(headers_complete); 217 | parser->state = s_data; 218 | break; 219 | } 220 | goto error; 221 | 222 | case s_data: 223 | mark = p; 224 | while (p != data + size) { 225 | c = *p; 226 | if (c == CR) { 227 | parser->state = s_data_cr; 228 | break; 229 | } 230 | ++p; 231 | } 232 | if (p > mark) { 233 | CALLBACK_DATA(data, mark, p - mark); 234 | } 235 | break; 236 | 237 | case s_data_cr: 238 | if (c == LF) { 239 | parser->state = s_data_cr_lf; 240 | break; 241 | } 242 | CALLBACK_DATA(data, "\r", 1); 243 | parser->state = s_data; 244 | goto reexecute; 245 | 246 | case s_data_cr_lf: 247 | if (c == HYPHEN) { 248 | parser->state = s_data_cr_lf_hy; 249 | break; 250 | } 251 | CALLBACK_DATA(data, "\r\n", 2); 252 | parser->state = s_data; 253 | goto reexecute; 254 | 255 | case s_data_cr_lf_hy: 256 | if (c == HYPHEN) { 257 | parser->state = s_data_boundary_start; 258 | break; 259 | } 260 | CALLBACK_DATA(data, "\r\n-", 3); 261 | parser->state = s_data; 262 | goto reexecute; 263 | 264 | case s_data_boundary_start: 265 | parser->index = 0; 266 | parser->state = s_data_boundary; 267 | // fallthrough; 268 | 269 | case s_data_boundary: 270 | if (parser->index == parser->boundary_length) { 271 | parser->index = 0; 272 | parser->state = s_data_boundary_done; 273 | goto reexecute; 274 | } 275 | if (c == parser->boundary[parser->index]) { 276 | parser->index++; 277 | break; 278 | } 279 | CALLBACK_DATA(data, parser->boundary, parser->index); 280 | parser->state = s_data; 281 | goto reexecute; 282 | 283 | case s_data_boundary_done: 284 | if (c == CR) { 285 | parser->state = s_data_boundary_done_cr_lf; 286 | break; 287 | } 288 | if (c == HYPHEN) { 289 | parser->state = s_data_boundary_done_hy_hy; 290 | break; 291 | } 292 | goto error; 293 | 294 | case s_data_boundary_done_cr_lf: 295 | if (c == LF) { 296 | CALLBACK_NOTIFY(part_end); 297 | CALLBACK_NOTIFY(part_begin); 298 | parser->state = s_header_field_start; 299 | break; 300 | } 301 | goto error; 302 | 303 | case s_data_boundary_done_hy_hy: 304 | if (c == HYPHEN) { 305 | CALLBACK_NOTIFY(part_end); 306 | CALLBACK_NOTIFY(body_end); 307 | parser->state = s_epilogue; 308 | break; 309 | } 310 | goto error; 311 | 312 | case s_epilogue: 313 | // Must be ignored according to rfc 1341. 314 | break; 315 | } 316 | } 317 | return size; 318 | 319 | error: 320 | return p - data; 321 | } 322 | -------------------------------------------------------------------------------- /tests.cpp: -------------------------------------------------------------------------------- 1 | #include "multipartparser.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | typedef struct part { 12 | std::map headers; 13 | std::string body; 14 | } part; 15 | 16 | static multipartparser_callbacks g_callbacks; 17 | 18 | static bool g_body_begin_called; 19 | static std::string g_header_name; 20 | static std::string g_header_value; 21 | static std::list g_parts; 22 | static bool g_body_end_called; 23 | 24 | static void init_globals() 25 | { 26 | g_body_begin_called = false; 27 | g_header_name.clear(); 28 | g_header_value.clear(); 29 | g_parts.clear(); 30 | g_body_end_called = false; 31 | } 32 | 33 | static int on_body_begin(multipartparser* /*parser*/) 34 | { 35 | g_body_begin_called = true; 36 | return 0; 37 | } 38 | 39 | static int on_part_begin(multipartparser* /*parser*/) 40 | { 41 | g_parts.push_back(part()); 42 | return 0; 43 | } 44 | 45 | static void on_header_done() 46 | { 47 | g_parts.back().headers[g_header_name] = g_header_value; 48 | g_header_name.clear(); 49 | g_header_value.clear(); 50 | } 51 | 52 | static int on_header_field(multipartparser* /*parser*/, const char* data, size_t size) 53 | { 54 | if (g_header_value.size() > 0) 55 | on_header_done(); 56 | g_header_name.append(data, size); 57 | return 0; 58 | } 59 | 60 | static int on_header_value(multipartparser* /*parser*/, const char* data, size_t size) 61 | { 62 | g_header_value.append(data, size); 63 | return 0; 64 | } 65 | 66 | static int on_headers_complete(multipartparser* /*parser*/) 67 | { 68 | if (g_header_value.size() > 0) 69 | on_header_done(); 70 | return 0; 71 | } 72 | 73 | static int on_data(multipartparser* /*parser*/, const char* data, size_t size) 74 | { 75 | g_parts.back().body.append(data, size); 76 | return 0; 77 | } 78 | 79 | static int on_part_end(multipartparser* /*parser*/) 80 | { 81 | return 0; 82 | } 83 | 84 | static int on_body_end(multipartparser* /*parser*/) 85 | { 86 | g_body_end_called = true; 87 | return 0; 88 | } 89 | 90 | void test_simple() 91 | { 92 | multipartparser parser; 93 | 94 | #define BOUNDARY "simple boundary" 95 | #define PART0_BODY \ 96 | "This is implicitly typed plain ASCII text.\r\n" \ 97 | "It does NOT end with a linebreak." 98 | #define PART1_BODY \ 99 | "This is explicitly typed plain ASCII text.\r\n" \ 100 | "It DOES end with a linebreak.\r\n" 101 | #define BODY \ 102 | "--" BOUNDARY "\r\n" \ 103 | "\r\n" \ 104 | PART0_BODY \ 105 | "\r\n--" BOUNDARY "\r\n" \ 106 | "Content-type: text/plain; charset=us-ascii\r\n" \ 107 | "\r\n" \ 108 | PART1_BODY \ 109 | "\r\n--" BOUNDARY "--\r\n" \ 110 | 111 | init_globals(); 112 | multipartparser_init(&parser, BOUNDARY); 113 | 114 | assert(multipartparser_execute(&parser, &g_callbacks, BODY, strlen(BODY)) == strlen(BODY)); 115 | assert(g_body_begin_called); 116 | assert(g_parts.size() == 2); 117 | 118 | part p0 = g_parts.front(); g_parts.pop_front(); 119 | assert(p0.headers.empty()); 120 | assert(p0.body.compare(PART0_BODY) == 0); 121 | 122 | part p1 = g_parts.front(); g_parts.pop_front(); 123 | assert(p1.headers.size() == 1); 124 | assert(p1.body == PART1_BODY); 125 | 126 | assert(g_body_end_called); 127 | 128 | #undef BOUNDARY 129 | #undef BODY 130 | } 131 | 132 | void test_simple_with_preamble() 133 | { 134 | multipartparser parser; 135 | 136 | #define BOUNDARY "simple boundary" 137 | #define BODY \ 138 | "This is the preamble. It is to be ignored, though it\r\n" \ 139 | "is a handy place for mail composers to include an\r\n" \ 140 | "explanatory note to non-MIME compliant readers.\r\n" \ 141 | "--" BOUNDARY "\r\n" \ 142 | "\r\n" \ 143 | "This is implicitly typed plain ASCII text.\r\n" \ 144 | "It does NOT end with a linebreak." \ 145 | "\r\n--" BOUNDARY "\r\n" \ 146 | "Content-type: text/plain; charset=us-ascii\r\n" \ 147 | "\r\n" \ 148 | "This is explicitly typed plain ASCII text.\r\n" \ 149 | "It DOES end with a linebreak.\r\n" \ 150 | "\r\n--" BOUNDARY "--\r\n" \ 151 | 152 | init_globals(); 153 | multipartparser_init(&parser, BOUNDARY); 154 | 155 | assert(multipartparser_execute(&parser, &g_callbacks, BODY, strlen(BODY)) == strlen(BODY)); 156 | assert(g_body_begin_called); 157 | assert(g_parts.size() == 2); 158 | assert(g_body_end_called); 159 | 160 | #undef BOUNDARY 161 | #undef BODY 162 | } 163 | 164 | void test_simple_with_epilogue() 165 | { 166 | multipartparser parser; 167 | 168 | #define BOUNDARY "simple boundary" 169 | #define BODY \ 170 | "--" BOUNDARY "\r\n" \ 171 | "\r\n" \ 172 | "This is implicitly typed plain ASCII text.\r\n" \ 173 | "It does NOT end with a linebreak." \ 174 | "\r\n--" BOUNDARY "\r\n" \ 175 | "Content-type: text/plain; charset=us-ascii\r\n" \ 176 | "\r\n" \ 177 | "This is explicitly typed plain ASCII text.\r\n" \ 178 | "It DOES end with a linebreak.\r\n" \ 179 | "\r\n--" BOUNDARY "--\r\n" \ 180 | "This is the epilogue. It is to be ignored.\r\n" \ 181 | 182 | init_globals(); 183 | multipartparser_init(&parser, BOUNDARY); 184 | 185 | assert(multipartparser_execute(&parser, &g_callbacks, BODY, strlen(BODY)) == strlen(BODY)); 186 | assert(g_body_begin_called); 187 | assert(g_parts.size() == 2); 188 | assert(g_body_end_called); 189 | 190 | #undef BOUNDARY 191 | #undef BODY 192 | } 193 | 194 | void test_headers() 195 | { 196 | multipartparser parser; 197 | 198 | #define BOUNDARY "boundary" 199 | #define BODY \ 200 | "--" BOUNDARY "\r\n" \ 201 | "Content-Disposition: form-data; name=\"foo\"; filename=\"bar\"\r\n" \ 202 | "Content-Type: application/octet-stream\r\n" \ 203 | "\r\n" \ 204 | "That's the file content!\r\n" \ 205 | "\r\n--" BOUNDARY "--\r\n" \ 206 | 207 | init_globals(); 208 | multipartparser_init(&parser, BOUNDARY); 209 | 210 | assert(multipartparser_execute(&parser, &g_callbacks, BODY, strlen(BODY)) == strlen(BODY)); 211 | assert(g_body_begin_called); 212 | assert(g_parts.size() == 1); 213 | assert(g_parts.front().headers.size() == 2); 214 | assert(g_parts.front().headers.find("Content-Disposition")->second == "form-data; name=\"foo\"; filename=\"bar\""); 215 | assert(g_parts.front().headers.find("Content-Type")->second == "application/octet-stream"); 216 | assert(g_body_end_called); 217 | 218 | #undef BOUNDARY 219 | #undef BODY 220 | } 221 | 222 | void test_with_empty_headers() 223 | { 224 | multipartparser parser; 225 | 226 | #define BOUNDARY "boundary" 227 | #define BODY \ 228 | "--" BOUNDARY "\r\n" \ 229 | "\r\n" \ 230 | "This is implicitly typed plain ASCII text.\r\n" \ 231 | "It does NOT end with a linebreak." \ 232 | "\r\n--" BOUNDARY "--\r\n" 233 | 234 | init_globals(); 235 | multipartparser_init(&parser, BOUNDARY); 236 | 237 | assert(multipartparser_execute(&parser, &g_callbacks, BODY, strlen(BODY)) == strlen(BODY)); 238 | assert(g_body_begin_called); 239 | assert(g_parts.size() == 1); 240 | assert(g_parts.front().headers.empty()); 241 | assert(g_body_end_called); 242 | 243 | #undef BOUNDARY 244 | #undef BODY 245 | } 246 | 247 | void test_with_header_field_parsed_in_two_times() 248 | { 249 | multipartparser parser; 250 | 251 | #define BOUNDARY "boundary" 252 | #define BODY0 \ 253 | "--" BOUNDARY "\r\n" 254 | #define BODY1 \ 255 | "Content-Disposition: form-data; name=\"foo\"\r\n" \ 256 | "\r\n" \ 257 | "bar" \ 258 | "\r\n--" BOUNDARY "--\r\n" \ 259 | 260 | init_globals(); 261 | multipartparser_init(&parser, BOUNDARY); 262 | 263 | assert(multipartparser_execute(&parser, &g_callbacks, BODY0, strlen(BODY0)) == strlen(BODY0)); 264 | assert(multipartparser_execute(&parser, &g_callbacks, BODY1, strlen(BODY1)) == strlen(BODY1)); 265 | assert(g_body_begin_called); 266 | assert(g_parts.size() == 1); 267 | assert(g_parts.front().headers.size() == 1); 268 | assert(g_parts.front().headers.find("Content-Disposition")->second == "form-data; name=\"foo\""); 269 | assert(g_body_end_called); 270 | 271 | #undef BOUNDARY 272 | #undef BODY0 273 | #undef BODY1 274 | } 275 | 276 | void test_with_header_field_parsed_in_two_times_1() 277 | { 278 | multipartparser parser; 279 | 280 | #define BOUNDARY "boundary" 281 | #define BODY0 \ 282 | "--" BOUNDARY "\r\n" \ 283 | "Content-Di" 284 | #define BODY1 \ 285 | "sposition: form-data; name=\"foo\"\r\n" \ 286 | "\r\n" \ 287 | "bar" \ 288 | "\r\n--" BOUNDARY "--\r\n" \ 289 | 290 | init_globals(); 291 | multipartparser_init(&parser, BOUNDARY); 292 | 293 | assert(multipartparser_execute(&parser, &g_callbacks, BODY0, strlen(BODY0)) == strlen(BODY0)); 294 | assert(multipartparser_execute(&parser, &g_callbacks, BODY1, strlen(BODY1)) == strlen(BODY1)); 295 | assert(g_body_begin_called); 296 | assert(g_parts.size() == 1); 297 | assert(g_parts.front().headers.size() == 1); 298 | assert(g_parts.front().headers.find("Content-Disposition")->second == "form-data; name=\"foo\""); 299 | assert(g_body_end_called); 300 | 301 | #undef BOUNDARY 302 | #undef BODY0 303 | #undef BODY1 304 | } 305 | 306 | void test_with_header_field_parsed_in_two_times_2() 307 | { 308 | multipartparser parser; 309 | 310 | #define BOUNDARY "boundary" 311 | #define BODY0 \ 312 | "--" BOUNDARY "\r\n" \ 313 | "Content-Disposition" 314 | #define BODY1 \ 315 | ": form-data; name=\"foo\"\r\n" \ 316 | "\r\n" \ 317 | "bar" \ 318 | "\r\n--" BOUNDARY "--\r\n" \ 319 | 320 | init_globals(); 321 | multipartparser_init(&parser, BOUNDARY); 322 | 323 | assert(multipartparser_execute(&parser, &g_callbacks, BODY0, strlen(BODY0)) == strlen(BODY0)); 324 | assert(multipartparser_execute(&parser, &g_callbacks, BODY1, strlen(BODY1)) == strlen(BODY1)); 325 | assert(g_body_begin_called); 326 | assert(g_parts.size() == 1); 327 | assert(g_parts.front().headers.size() == 1); 328 | assert(g_parts.front().headers.find("Content-Disposition")->second == "form-data; name=\"foo\""); 329 | assert(g_body_end_called); 330 | 331 | #undef BOUNDARY 332 | #undef BODY0 333 | #undef BODY1 334 | } 335 | 336 | void test_with_header_value_parsed_in_two_times() 337 | { 338 | multipartparser parser; 339 | 340 | #define BOUNDARY "boundary" 341 | #define BODY0 \ 342 | "--" BOUNDARY "\r\n" \ 343 | "Content-Disposition: " 344 | #define BODY1 \ 345 | "form-data; name=\"foo\"\r\n" \ 346 | "\r\n" \ 347 | "bar" \ 348 | "\r\n--" BOUNDARY "--\r\n" \ 349 | 350 | init_globals(); 351 | multipartparser_init(&parser, BOUNDARY); 352 | 353 | assert(multipartparser_execute(&parser, &g_callbacks, BODY0, strlen(BODY0)) == strlen(BODY0)); 354 | assert(multipartparser_execute(&parser, &g_callbacks, BODY1, strlen(BODY1)) == strlen(BODY1)); 355 | assert(g_body_begin_called); 356 | assert(g_parts.size() == 1); 357 | assert(g_parts.front().headers.size() == 1); 358 | assert(g_parts.front().headers.find("Content-Disposition")->second == "form-data; name=\"foo\""); 359 | assert(g_body_end_called); 360 | 361 | #undef BOUNDARY 362 | #undef BODY0 363 | #undef BODY1 364 | } 365 | 366 | void test_with_header_value_parsed_in_two_times_1() 367 | { 368 | multipartparser parser; 369 | 370 | #define BOUNDARY "boundary" 371 | #define BODY0 \ 372 | "--" BOUNDARY "\r\n" \ 373 | "Content-Disposition: form-data" 374 | #define BODY1 \ 375 | "; name=\"foo\"\r\n" \ 376 | "\r\n" \ 377 | "bar" \ 378 | "\r\n--" BOUNDARY "--\r\n" \ 379 | 380 | init_globals(); 381 | multipartparser_init(&parser, BOUNDARY); 382 | 383 | assert(multipartparser_execute(&parser, &g_callbacks, BODY0, strlen(BODY0)) == strlen(BODY0)); 384 | assert(multipartparser_execute(&parser, &g_callbacks, BODY1, strlen(BODY1)) == strlen(BODY1)); 385 | assert(g_body_begin_called); 386 | assert(g_parts.size() == 1); 387 | assert(g_parts.front().headers.size() == 1); 388 | assert(g_parts.front().headers.find("Content-Disposition")->second == "form-data; name=\"foo\""); 389 | assert(g_body_end_called); 390 | 391 | #undef BOUNDARY 392 | #undef BODY0 393 | #undef BODY1 394 | } 395 | 396 | void test_with_header_value_parsed_in_two_times_2() 397 | { 398 | multipartparser parser; 399 | 400 | #define BOUNDARY "boundary" 401 | #define BODY0 \ 402 | "--" BOUNDARY "\r\n" \ 403 | "Content-Disposition: " 404 | #define BODY1 \ 405 | "form-data; name=\"foo\"\r\n" \ 406 | "\r\n" \ 407 | "bar" \ 408 | "\r\n--" BOUNDARY "--\r\n" \ 409 | 410 | init_globals(); 411 | multipartparser_init(&parser, BOUNDARY); 412 | 413 | assert(multipartparser_execute(&parser, &g_callbacks, BODY0, strlen(BODY0)) == strlen(BODY0)); 414 | assert(multipartparser_execute(&parser, &g_callbacks, BODY1, strlen(BODY1)) == strlen(BODY1)); 415 | assert(g_body_begin_called); 416 | assert(g_parts.size() == 1); 417 | assert(g_parts.front().headers.size() == 1); 418 | assert(g_parts.front().headers.find("Content-Disposition")->second == "form-data; name=\"foo\""); 419 | assert(g_body_end_called); 420 | 421 | #undef BOUNDARY 422 | #undef BODY0 423 | #undef BODY1 424 | } 425 | 426 | int main() 427 | { 428 | multipartparser_callbacks_init(&g_callbacks); 429 | g_callbacks.on_body_begin = &on_body_begin; 430 | g_callbacks.on_part_begin = &on_part_begin; 431 | g_callbacks.on_header_field = &on_header_field; 432 | g_callbacks.on_header_value = &on_header_value; 433 | g_callbacks.on_headers_complete = &on_headers_complete; 434 | g_callbacks.on_data = &on_data; 435 | g_callbacks.on_part_end = &on_part_end; 436 | g_callbacks.on_body_end = &on_body_end; 437 | 438 | test_simple(); 439 | test_simple_with_preamble(); 440 | test_simple_with_epilogue(); 441 | 442 | test_headers(); 443 | test_with_empty_headers(); 444 | test_with_header_field_parsed_in_two_times(); 445 | test_with_header_field_parsed_in_two_times_1(); 446 | test_with_header_field_parsed_in_two_times_2(); 447 | test_with_header_value_parsed_in_two_times(); 448 | test_with_header_value_parsed_in_two_times_1(); 449 | test_with_header_value_parsed_in_two_times_2(); 450 | 451 | return 0; 452 | } 453 | --------------------------------------------------------------------------------