simplefiles

A lightweight C CGI web application for managing files.
git clone https://scm.kuandu.systems/git-raw/simplefiles.git
Log | Files | Refs | README | LICENSE

multipart.c (38295B)


      1 #include <stdio.h>
      2 #include <stdlib.h>
      3 #include <string.h>
      4 #include <fcntl.h>
      5 #include <unistd.h>
      6 
      7 #include "multipart.h"
      8 
      9 #define TEST_SPOOL "/tmp/simplefiles-multipart-test-spool"
     10 #define TEST_OUTPUT "/tmp/simplefiles-multipart-test-output"
     11 #define TEST_BOUNDARY_MAX 70
     12 
     13 static int write_file(int fd, const char *data);
     14 static int create_spool(const char *body, struct multipart_request * req);
     15 
     16 static int
     17 test_parse_request(const char *ctype,
     18     const char *expected_boundary,
     19     int expected)
     20 {
     21 	struct multipart_request req;
     22 	int ret;
     23 
     24 	memset(&req, 0, sizeof(req));
     25 
     26 	if (setenv("CONTENT_TYPE", ctype, 1) == -1 ||
     27 	    setenv("CONTENT_LENGTH", "100", 1) == -1) {
     28 		fprintf(stderr,
     29 		    "FAIL: parse request: could not set environment\n");
     30 		return 1;
     31 	}
     32 	ret = multipart_parse_request(&req);
     33 
     34 	if (ret != expected) {
     35 		fprintf(stderr,
     36 		    "FAIL: parse request: '%s' returned %d, "
     37 		    "expected %d\n",
     38 		    ctype, ret, expected);
     39 		return 1;
     40 	}
     41 	if (expected_boundary != NULL &&
     42 	    strcmp(req.boundary, expected_boundary) != 0) {
     43 		fprintf(stderr,
     44 		    "FAIL: parse request: boundary was '%s', "
     45 		    "expected '%s'\n",
     46 		    req.boundary, expected_boundary);
     47 		return 1;
     48 	}
     49 	return 0;
     50 }
     51 
     52 static int
     53 create_spool(const char *body, struct multipart_request * req)
     54 {
     55 	int fd;
     56 
     57 	fd = open(TEST_SPOOL, O_RDWR | O_CREAT | O_TRUNC, 0600);
     58 	if (fd == -1)
     59 		return -1;
     60 
     61 	if (write_file(fd, body) == -1) {
     62 		close(fd);
     63 		unlink(TEST_SPOOL);
     64 		return -1;
     65 	}
     66 	memset(req, 0, sizeof(*req));
     67 
     68 	if (strlcpy(req->boundary, "test-boundary",
     69 		sizeof(req->boundary)) >= sizeof(req->boundary)) {
     70 		close(fd);
     71 		unlink(TEST_SPOOL);
     72 		return -1;
     73 	}
     74 	req->length = (off_t) strlen(body);
     75 
     76 	return fd;
     77 }
     78 
     79 static int
     80 write_file(int fd, const char *data)
     81 {
     82 	size_t len;
     83 	size_t written;
     84 	ssize_t n;
     85 
     86 	len = strlen(data);
     87 	written = 0;
     88 
     89 	while (written < len) {
     90 		n = write(fd, data + written, len - written);
     91 		if (n <= 0)
     92 			return -1;
     93 
     94 		written += (size_t) n;
     95 	}
     96 
     97 	if (lseek(fd, 0, SEEK_SET) == (off_t) - 1)
     98 		return -1;
     99 
    100 	return 0;
    101 }
    102 
    103 static int
    104 test_extract_part(void)
    105 {
    106 	const char *body =
    107 	"--test-boundary\r\n"
    108 	"Content-Disposition: form-data; name=\"file\"; "
    109 	"filename=\"hello.txt\"\r\n"
    110 	"Content-Type: text/plain\r\n"
    111 	"\r\n"
    112 	"hello world\r\n"
    113 	"--test-boundary--\r\n";
    114 	struct multipart_request req;
    115 	struct multipart_part part;
    116 	char output[64];
    117 	int fd;
    118 	int outfd;
    119 	ssize_t n;
    120 
    121 	fd = open(TEST_SPOOL, O_RDWR | O_CREAT | O_TRUNC, 0600);
    122 	if (fd == -1) {
    123 		fprintf(stderr,
    124 		    "FAIL: extract part: cannot create spool\n");
    125 		return 1;
    126 	}
    127 	if (write_file(fd, body) == -1) {
    128 		fprintf(stderr,
    129 		    "FAIL: extract part: cannot write spool\n");
    130 		close(fd);
    131 		unlink(TEST_SPOOL);
    132 		return 1;
    133 	}
    134 	memset(&req, 0, sizeof(req));
    135 
    136 	if (strlcpy(req.boundary, "test-boundary",
    137 		sizeof(req.boundary)) >= sizeof(req.boundary)) {
    138 		fprintf(stderr,
    139 		    "FAIL: extract part: boundary too long\n");
    140 		close(fd);
    141 		unlink(TEST_SPOOL);
    142 		return 1;
    143 	}
    144 	req.length = (off_t) strlen(body);
    145 
    146 	if (multipart_extract_part(fd, &req, &part) != MULTIPART_OK) {
    147 		fprintf(stderr,
    148 		    "FAIL: extract part: multipart_extract_part() failed\n");
    149 		close(fd);
    150 		unlink(TEST_SPOOL);
    151 		return 1;
    152 	}
    153 	if (strcmp(part.filename, "hello.txt") != 0) {
    154 		fprintf(stderr,
    155 		    "FAIL: extract part: filename was '%s', "
    156 		    "expected 'hello.txt'\n",
    157 		    part.filename);
    158 		close(fd);
    159 		unlink(TEST_SPOOL);
    160 		return 1;
    161 	}
    162 	if (part.length != 11) {
    163 		fprintf(stderr,
    164 		    "FAIL: extract part: length was %lld, expected 11\n",
    165 		    (long long) part.length);
    166 		close(fd);
    167 		unlink(TEST_SPOOL);
    168 		return 1;
    169 	}
    170 	outfd = open(TEST_OUTPUT, O_RDWR | O_CREAT | O_TRUNC, 0600);
    171 	if (outfd == -1) {
    172 		fprintf(stderr,
    173 		    "FAIL: extract part: cannot create output\n");
    174 		close(fd);
    175 		unlink(TEST_SPOOL);
    176 		return 1;
    177 	}
    178 	if (multipart_copy_part(fd, &part, outfd) != MULTIPART_OK) {
    179 		fprintf(stderr,
    180 		    "FAIL: extract part: multipart_copy_part() failed\n");
    181 		close(outfd);
    182 		close(fd);
    183 		unlink(TEST_SPOOL);
    184 		unlink(TEST_OUTPUT);
    185 		return 1;
    186 	}
    187 	if (lseek(outfd, 0, SEEK_SET) == (off_t) - 1) {
    188 		fprintf(stderr,
    189 		    "FAIL: extract part: cannot seek output\n");
    190 		close(outfd);
    191 		close(fd);
    192 		unlink(TEST_SPOOL);
    193 		unlink(TEST_OUTPUT);
    194 		return 1;
    195 	}
    196 	n = read(outfd, output, sizeof(output) - 1);
    197 	if (n < 0) {
    198 		fprintf(stderr,
    199 		    "FAIL: extract part: cannot read output\n");
    200 		close(outfd);
    201 		close(fd);
    202 		unlink(TEST_SPOOL);
    203 		unlink(TEST_OUTPUT);
    204 		return 1;
    205 	}
    206 	output[n] = '\0';
    207 
    208 	if (strcmp(output, "hello world") != 0) {
    209 		fprintf(stderr,
    210 		    "FAIL: extract part: output was '%s', "
    211 		    "expected 'hello world'\n",
    212 		    output);
    213 		close(outfd);
    214 		close(fd);
    215 		unlink(TEST_SPOOL);
    216 		unlink(TEST_OUTPUT);
    217 		return 1;
    218 	}
    219 	close(outfd);
    220 	close(fd);
    221 	unlink(TEST_SPOOL);
    222 	unlink(TEST_OUTPUT);
    223 
    224 	printf("ok: extract and copy multipart part\n");
    225 
    226 	return 0;
    227 }
    228 
    229 static int
    230 test_multipart_copy_empty_part(void)
    231 {
    232 	struct multipart_part part;
    233 	int fd;
    234 	int outfd;
    235 
    236 	fd = open(TEST_SPOOL, O_RDWR | O_CREAT | O_TRUNC, 0600);
    237 	if (fd == -1) {
    238 		fprintf(stderr,
    239 		    "FAIL: empty part: cannot create spool\n");
    240 		return 1;
    241 	}
    242 	memset(&part, 0, sizeof(part));
    243 
    244 	part.offset = 0;
    245 	part.length = 0;
    246 
    247 	outfd = open(TEST_OUTPUT, O_RDWR | O_CREAT | O_TRUNC, 0600);
    248 	if (outfd == -1) {
    249 		fprintf(stderr,
    250 		    "FAIL: empty part: cannot create output\n");
    251 		close(fd);
    252 		unlink(TEST_SPOOL);
    253 		return 1;
    254 	}
    255 	if (multipart_copy_part(fd, &part, outfd) == -1) {
    256 		fprintf(stderr,
    257 		    "FAIL: empty part: multipart_copy_part() failed\n");
    258 		close(outfd);
    259 		close(fd);
    260 		unlink(TEST_SPOOL);
    261 		unlink(TEST_OUTPUT);
    262 		return 1;
    263 	}
    264 	close(outfd);
    265 	close(fd);
    266 	unlink(TEST_SPOOL);
    267 	unlink(TEST_OUTPUT);
    268 
    269 	printf("ok: copy empty multipart part\n");
    270 
    271 	return 0;
    272 }
    273 
    274 static int
    275 test_filename_not_found_in_payload(void)
    276 {
    277 	const char *body =
    278 	"--test-boundary\r\n"
    279 	"Content-Disposition: form-data; name=\"file\"; "
    280 	"filename=\"hello.txt\"\r\n"
    281 	"Content-Type: text/plain\r\n"
    282 	"\r\n"
    283 	"this is file data containing filename=\"fake.txt\"\r\n"
    284 	"--test-boundary--\r\n";
    285 	struct multipart_request req;
    286 	struct multipart_part part;
    287 	int fd;
    288 
    289 	fd = open(TEST_SPOOL, O_RDWR | O_CREAT | O_TRUNC, 0600);
    290 	if (fd == -1) {
    291 		fprintf(stderr,
    292 		    "FAIL: filename payload: cannot create spool\n");
    293 		return 1;
    294 	}
    295 	if (write_file(fd, body) == -1) {
    296 		fprintf(stderr,
    297 		    "FAIL: filename payload: cannot write spool\n");
    298 		close(fd);
    299 		unlink(TEST_SPOOL);
    300 		return 1;
    301 	}
    302 	memset(&req, 0, sizeof(req));
    303 
    304 	if (strlcpy(req.boundary, "test-boundary",
    305 		sizeof(req.boundary)) >= sizeof(req.boundary)) {
    306 		fprintf(stderr,
    307 		    "FAIL: filename payload: boundary too long\n");
    308 		close(fd);
    309 		unlink(TEST_SPOOL);
    310 		return 1;
    311 	}
    312 	req.length = (off_t) strlen(body);
    313 
    314 	if (multipart_extract_part(fd, &req, &part) == -1) {
    315 		fprintf(stderr,
    316 		    "FAIL: filename payload: extraction failed\n");
    317 		close(fd);
    318 		unlink(TEST_SPOOL);
    319 		return 1;
    320 	}
    321 	if (strcmp(part.filename, "hello.txt") != 0) {
    322 		fprintf(stderr,
    323 		    "FAIL: filename payload: filename was '%s', "
    324 		    "expected 'hello.txt'\n",
    325 		    part.filename);
    326 		close(fd);
    327 		unlink(TEST_SPOOL);
    328 		return 1;
    329 	}
    330 	close(fd);
    331 	unlink(TEST_SPOOL);
    332 
    333 	printf("ok: filename is extracted from headers only\n");
    334 
    335 	return 0;
    336 }
    337 
    338 static int
    339 test_reject_multiple_file_parts(void)
    340 {
    341 	const char *body =
    342 	"--test-boundary\r\n"
    343 	"Content-Disposition: form-data; name=\"file\"; "
    344 	"filename=\"one.txt\"\r\n"
    345 	"Content-Type: text/plain\r\n"
    346 	"\r\n"
    347 	"one\r\n"
    348 	"--test-boundary\r\n"
    349 	"Content-Disposition: form-data; name=\"file\"; "
    350 	"filename=\"two.txt\"\r\n"
    351 	"Content-Type: text/plain\r\n"
    352 	"\r\n"
    353 	"two\r\n"
    354 	"--test-boundary--\r\n";
    355 	struct multipart_request req;
    356 	struct multipart_part part;
    357 	int fd;
    358 
    359 	fd = open(TEST_SPOOL, O_RDWR | O_CREAT | O_TRUNC, 0600);
    360 	if (fd == -1) {
    361 		fprintf(stderr,
    362 		    "FAIL: multiple file parts: cannot create spool\n");
    363 		return 1;
    364 	}
    365 	if (write_file(fd, body) == -1) {
    366 		fprintf(stderr,
    367 		    "FAIL: multiple file parts: cannot write spool\n");
    368 		close(fd);
    369 		unlink(TEST_SPOOL);
    370 		return 1;
    371 	}
    372 	memset(&req, 0, sizeof(req));
    373 
    374 	if (strlcpy(req.boundary, "test-boundary",
    375 		sizeof(req.boundary)) >= sizeof(req.boundary)) {
    376 		fprintf(stderr,
    377 		    "FAIL: multiple file parts: boundary too long\n");
    378 		close(fd);
    379 		unlink(TEST_SPOOL);
    380 		return 1;
    381 	}
    382 	req.length = (off_t) strlen(body);
    383 
    384 	if (multipart_extract_part(fd, &req, &part) != MULTIPART_MULTIPLE_FILES) {
    385 		fprintf(stderr,
    386 		    "FAIL: multiple file parts: "
    387 		    "multipart_extract_part() accepted multiple files\n");
    388 		close(fd);
    389 		unlink(TEST_SPOOL);
    390 		return 1;
    391 	}
    392 	close(fd);
    393 	unlink(TEST_SPOOL);
    394 
    395 	printf("ok: reject multiple file parts\n");
    396 
    397 	return 0;
    398 }
    399 
    400 static int
    401 test_missing_opening_boundary(void)
    402 {
    403 	const char *body =
    404 	"garbage\r\n"
    405 	"Content-Disposition: form-data; name=\"file\"; "
    406 	"filename=\"hello.txt\"\r\n"
    407 	"\r\n"
    408 	"hello\r\n"
    409 	"--test-boundary--\r\n";
    410 	struct multipart_request req;
    411 	struct multipart_part part;
    412 	int fd;
    413 
    414 	fd = create_spool(body, &req);
    415 	if (fd == -1) {
    416 		fprintf(stderr,
    417 		    "FAIL: missing opening boundary: setup failed\n");
    418 		return 1;
    419 	}
    420 	if (multipart_extract_part(fd, &req, &part) !=
    421 	    MULTIPART_MALFORMED) {
    422 		fprintf(stderr,
    423 		    "FAIL: missing opening boundary: "
    424 		    "accepted malformed body\n");
    425 		close(fd);
    426 		unlink(TEST_SPOOL);
    427 		return 1;
    428 	}
    429 	close(fd);
    430 	unlink(TEST_SPOOL);
    431 
    432 	printf("ok: reject missing opening boundary\n");
    433 	return 0;
    434 }
    435 
    436 static int
    437 test_wrong_opening_boundary(void)
    438 {
    439 	const char *body =
    440 	"--wrong-boundary\r\n"
    441 	"Content-Disposition: form-data; name=\"file\"; "
    442 	"filename=\"hello.txt\"\r\n"
    443 	"\r\n"
    444 	"hello\r\n"
    445 	"--wrong-boundary--\r\n";
    446 	struct multipart_request req;
    447 	struct multipart_part part;
    448 	int fd;
    449 
    450 	fd = create_spool(body, &req);
    451 	if (fd == -1) {
    452 		fprintf(stderr,
    453 		    "FAIL: wrong opening boundary: setup failed\n");
    454 		return 1;
    455 	}
    456 	if (multipart_extract_part(fd, &req, &part) !=
    457 	    MULTIPART_MALFORMED) {
    458 		fprintf(stderr,
    459 		    "FAIL: wrong opening boundary: "
    460 		    "accepted malformed body\n");
    461 		close(fd);
    462 		unlink(TEST_SPOOL);
    463 		return 1;
    464 	}
    465 	close(fd);
    466 	unlink(TEST_SPOOL);
    467 
    468 	printf("ok: reject wrong opening boundary\n");
    469 	return 0;
    470 }
    471 
    472 static int
    473 test_missing_header_terminator(void)
    474 {
    475 	const char *body =
    476 	"--test-boundary\r\n"
    477 	"Content-Disposition: form-data; name=\"file\"; "
    478 	"filename=\"hello.txt\"\r\n"
    479 	"Content-Type: text/plain\r\n"
    480 	"hello";
    481 	struct multipart_request req;
    482 	struct multipart_part part;
    483 	int fd;
    484 
    485 	fd = create_spool(body, &req);
    486 	if (fd == -1) {
    487 		fprintf(stderr,
    488 		    "FAIL: missing header terminator: setup failed\n");
    489 		return 1;
    490 	}
    491 	if (multipart_extract_part(fd, &req, &part) != MULTIPART_MALFORMED) {
    492 		fprintf(stderr,
    493 		    "FAIL: missing header terminator: "
    494 		    "accepted malformed body\n");
    495 		close(fd);
    496 		unlink(TEST_SPOOL);
    497 		return 1;
    498 	}
    499 	close(fd);
    500 	unlink(TEST_SPOOL);
    501 
    502 	printf("ok: reject missing header terminator\n");
    503 	return 0;
    504 }
    505 
    506 static int
    507 test_missing_closing_boundary(void)
    508 {
    509 	const char *body =
    510 	"--test-boundary\r\n"
    511 	"Content-Disposition: form-data; name=\"file\"; "
    512 	"filename=\"hello.txt\"\r\n"
    513 	"\r\n"
    514 	"hello\r\n";
    515 	struct multipart_request req;
    516 	struct multipart_part part;
    517 	int fd;
    518 
    519 	fd = create_spool(body, &req);
    520 
    521 	if (fd == -1) {
    522 		fprintf(stderr,
    523 		    "FAIL: missing closing boundary: setup failed\n");
    524 		return 1;
    525 	}
    526 	if (multipart_extract_part(fd, &req, &part) != MULTIPART_MALFORMED) {
    527 		fprintf(stderr,
    528 		    "FAIL: missing closing boundary: "
    529 		    "accepted malformed body\n");
    530 		close(fd);
    531 		unlink(TEST_SPOOL);
    532 		return 1;
    533 	}
    534 	close(fd);
    535 	unlink(TEST_SPOOL);
    536 
    537 	printf("ok: reject missing closing boundary\n");
    538 	return 0;
    539 }
    540 
    541 static int
    542 test_truncated_closing_boundary(void)
    543 {
    544 	const char *body =
    545 	"--test-boundary\r\n"
    546 	"Content-Disposition: form-data; name=\"file\"; "
    547 	"filename=\"hello.txt\"\r\n"
    548 	"\r\n"
    549 	"hello\r\n"
    550 	"--test-boundary";
    551 	struct multipart_request req;
    552 	struct multipart_part part;
    553 	int fd;
    554 
    555 	fd = create_spool(body, &req);
    556 	if (fd == -1) {
    557 		fprintf(stderr,
    558 		    "FAIL: truncated closing boundary: setup failed\n");
    559 		return 1;
    560 	}
    561 	if (multipart_extract_part(fd, &req, &part) !=
    562 	    MULTIPART_MALFORMED) {
    563 		fprintf(stderr,
    564 		    "FAIL: truncated closing boundary: "
    565 		    "accepted malformed body\n");
    566 		close(fd);
    567 		unlink(TEST_SPOOL);
    568 		return 1;
    569 	}
    570 	close(fd);
    571 	unlink(TEST_SPOOL);
    572 
    573 	printf("ok: reject truncated closing boundary\n");
    574 	return 0;
    575 }
    576 
    577 static int
    578 test_invalid_boundary_suffix(void)
    579 {
    580 	const char *body =
    581 	"--test-boundary\r\n"
    582 	"Content-Disposition: form-data; name=\"file\"; "
    583 	"filename=\"hello.txt\"\r\n"
    584 	"\r\n"
    585 	"hello\r\n"
    586 	"--test-boundaryX\r\n";
    587 	struct multipart_request req;
    588 	struct multipart_part part;
    589 	int fd;
    590 
    591 	fd = create_spool(body, &req);
    592 	if (fd == -1) {
    593 		fprintf(stderr,
    594 		    "FAIL: invalid boundary suffix: setup failed\n");
    595 		return 1;
    596 	}
    597 	if (multipart_extract_part(fd, &req, &part) !=
    598 	    MULTIPART_MALFORMED) {
    599 		fprintf(stderr,
    600 		    "FAIL: invalid boundary suffix: "
    601 		    "accepted malformed body\n");
    602 		close(fd);
    603 		unlink(TEST_SPOOL);
    604 		return 1;
    605 	}
    606 	close(fd);
    607 	unlink(TEST_SPOOL);
    608 
    609 	printf("ok: reject invalid boundary suffix\n");
    610 	return 0;
    611 }
    612 
    613 static int
    614 test_missing_content_disposition(void)
    615 {
    616 	const char *body =
    617 	"--test-boundary\r\n"
    618 	"Content-Type: text/plain\r\n"
    619 	"\r\n"
    620 	"hello\r\n"
    621 	"--test-boundary--\r\n";
    622 	struct multipart_request req;
    623 	struct multipart_part part;
    624 	int fd;
    625 
    626 	fd = create_spool(body, &req);
    627 	if (fd == -1)
    628 		return 1;
    629 
    630 	if (multipart_extract_part(fd, &req, &part) !=
    631 	    MULTIPART_MISSING_FILENAME) {
    632 		fprintf(stderr,
    633 		    "FAIL: missing Content-Disposition: "
    634 		    "accepted body\n");
    635 		close(fd);
    636 		unlink(TEST_SPOOL);
    637 		return 1;
    638 	}
    639 	close(fd);
    640 	unlink(TEST_SPOOL);
    641 
    642 	printf("ok: reject missing Content-Disposition\n");
    643 	return 0;
    644 }
    645 
    646 static int
    647 test_wrong_content_disposition(void)
    648 {
    649 	const char *body =
    650 	"--test-boundary\r\n"
    651 	"Content-Disposition: attachment; filename=\"hello.txt\"\r\n"
    652 	"\r\n"
    653 	"hello\r\n"
    654 	"--test-boundary--\r\n";
    655 	struct multipart_request req;
    656 	struct multipart_part part;
    657 	int fd;
    658 
    659 	fd = create_spool(body, &req);
    660 	if (fd == -1)
    661 		return 1;
    662 
    663 	if (multipart_extract_part(fd, &req, &part) !=
    664 	    MULTIPART_MALFORMED) {
    665 		fprintf(stderr,
    666 		    "FAIL: wrong Content-Disposition: "
    667 		    "accepted body\n");
    668 		close(fd);
    669 		unlink(TEST_SPOOL);
    670 		return 1;
    671 	}
    672 	close(fd);
    673 	unlink(TEST_SPOOL);
    674 
    675 	printf("ok: reject wrong Content-Disposition\n");
    676 	return 0;
    677 }
    678 
    679 static int
    680 test_unquoted_filename(void)
    681 {
    682 	const char *body =
    683 	"--test-boundary\r\n"
    684 	"Content-Disposition: form-data; name=\"file\"; "
    685 	"filename=hello.txt\r\n"
    686 	"\r\n"
    687 	"hello\r\n"
    688 	"--test-boundary--\r\n";
    689 	struct multipart_request req;
    690 	struct multipart_part part;
    691 	int fd;
    692 
    693 	fd = create_spool(body, &req);
    694 	if (fd == -1)
    695 		return 1;
    696 
    697 	if (multipart_extract_part(fd, &req, &part) !=
    698 	    MULTIPART_MALFORMED) {
    699 		fprintf(stderr,
    700 		    "FAIL: unquoted filename: accepted body\n");
    701 		close(fd);
    702 		unlink(TEST_SPOOL);
    703 		return 1;
    704 	}
    705 	close(fd);
    706 	unlink(TEST_SPOOL);
    707 
    708 	printf("ok: reject unquoted filename\n");
    709 	return 0;
    710 }
    711 
    712 static int
    713 test_empty_filename(void)
    714 {
    715 	const char *body =
    716 	"--test-boundary\r\n"
    717 	"Content-Disposition: form-data; name=\"file\"; "
    718 	"filename=\"\"\r\n"
    719 	"\r\n"
    720 	"hello\r\n"
    721 	"--test-boundary--\r\n";
    722 	struct multipart_request req;
    723 	struct multipart_part part;
    724 	int fd;
    725 
    726 	fd = create_spool(body, &req);
    727 	if (fd == -1)
    728 		return 1;
    729 
    730 	if (multipart_extract_part(fd, &req, &part) !=
    731 	    MULTIPART_MALFORMED) {
    732 		fprintf(stderr,
    733 		    "FAIL: empty filename: accepted body\n");
    734 		close(fd);
    735 		unlink(TEST_SPOOL);
    736 		return 1;
    737 	}
    738 	close(fd);
    739 	unlink(TEST_SPOOL);
    740 
    741 	printf("ok: reject empty filename\n");
    742 	return 0;
    743 }
    744 
    745 static int
    746 test_boundary_like_payload(void)
    747 {
    748 	const char *body =
    749 	"--test-boundary\r\n"
    750 	"Content-Disposition: form-data; name=\"file\"; "
    751 	"filename=\"hello.txt\"\r\n"
    752 	"\r\n"
    753 	"hello\r\n"
    754 	"--test-boundaryX\r\n"
    755 	"still file data\r\n"
    756 	"--test-boundary--\r\n";
    757 	struct multipart_request req;
    758 	struct multipart_part part;
    759 	int fd;
    760 
    761 	fd = create_spool(body, &req);
    762 	if (fd == -1)
    763 		return 1;
    764 
    765 	if (multipart_extract_part(fd, &req, &part) != MULTIPART_OK) {
    766 		fprintf(stderr,
    767 		    "FAIL: boundary-like payload: extraction failed\n");
    768 		close(fd);
    769 		unlink(TEST_SPOOL);
    770 		return 1;
    771 	}
    772 	close(fd);
    773 	unlink(TEST_SPOOL);
    774 
    775 	printf("ok: ignore boundary-like payload\n");
    776 	return 0;
    777 }
    778 
    779 static int
    780 test_content_type_normal(void)
    781 {
    782 	if (test_parse_request(
    783 		"multipart/form-data; boundary=test-boundary",
    784 		"test-boundary",
    785 		MULTIPART_OK) != 0)
    786 		return 1;
    787 
    788 	printf("ok: parse normal Content-Type boundary\n");
    789 	return 0;
    790 }
    791 
    792 static int
    793 test_content_type_case_insensitive(void)
    794 {
    795 	if (test_parse_request(
    796 		"Multipart/Form-Data; boundary=test-boundary",
    797 		"test-boundary",
    798 		MULTIPART_OK) != 0)
    799 		return 1;
    800 
    801 	printf("ok: Content-Type media type is case-insensitive\n");
    802 	return 0;
    803 }
    804 
    805 static int
    806 test_content_type_boundary_case_insensitive(void)
    807 {
    808 	if (test_parse_request(
    809 		"multipart/form-data; BOUNDARY=test-boundary",
    810 		"test-boundary",
    811 		MULTIPART_OK) != 0)
    812 		return 1;
    813 
    814 	printf("ok: boundary parameter name is case-insensitive\n");
    815 	return 0;
    816 }
    817 
    818 static int
    819 test_content_type_quoted_boundary(void)
    820 {
    821 	if (test_parse_request(
    822 		"multipart/form-data; boundary=\"test-boundary\"",
    823 		"test-boundary",
    824 		MULTIPART_OK) != 0)
    825 		return 1;
    826 
    827 	printf("ok: parse quoted boundary\n");
    828 	return 0;
    829 }
    830 
    831 static int
    832 test_content_type_boundary_whitespace(void)
    833 {
    834 	if (test_parse_request(
    835 		"multipart/form-data; boundary = test-boundary",
    836 		"test-boundary",
    837 		MULTIPART_OK) != 0)
    838 		return 1;
    839 
    840 	printf("ok: parse boundary with whitespace\n");
    841 	return 0;
    842 }
    843 
    844 static int
    845 test_content_type_parameter_before_boundary(void)
    846 {
    847 	if (test_parse_request(
    848 		"multipart/form-data; charset=utf-8; "
    849 		"boundary=test-boundary",
    850 		"test-boundary",
    851 		MULTIPART_OK) != 0)
    852 		return 1;
    853 
    854 	printf("ok: parse boundary after another parameter\n");
    855 	return 0;
    856 }
    857 
    858 static int
    859 test_content_type_missing_boundary(void)
    860 {
    861 	if (test_parse_request(
    862 		"multipart/form-data",
    863 		NULL,
    864 		MULTIPART_MISSING_BOUNDARY) != 0)
    865 		return 1;
    866 
    867 	printf("ok: reject missing Content-Type boundary\n");
    868 	return 0;
    869 }
    870 
    871 static int
    872 test_content_type_invalid_boundary_character(void)
    873 {
    874 	if (test_parse_request(
    875 		"multipart/form-data; boundary=\"test boundary\"",
    876 		NULL,
    877 		MULTIPART_INVALID_BOUNDARY) != 0)
    878 		return 1;
    879 
    880 	printf("ok: reject invalid boundary character\n");
    881 	return 0;
    882 }
    883 
    884 static int
    885 test_content_type_boundary_too_long(void)
    886 {
    887 	char ctype[128];
    888 	size_t prefix_len;
    889 	size_t i;
    890 	int ret;
    891 
    892 	strcpy(ctype, "multipart/form-data; boundary=");
    893 	prefix_len = strlen(ctype);
    894 
    895 	for (i = 0; i <= TEST_BOUNDARY_MAX; i++)
    896 		ctype[prefix_len + i] = 'a';
    897 
    898 	ctype[prefix_len + TEST_BOUNDARY_MAX + 1] = '\0';
    899 
    900 	ret = test_parse_request(
    901 	    ctype,
    902 	    NULL,
    903 	    MULTIPART_INVALID_BOUNDARY);
    904 
    905 	if (ret != 0)
    906 		return 1;
    907 
    908 	printf("ok: reject oversized boundary\n");
    909 	return 0;
    910 }
    911 
    912 static int
    913 test_content_type_malformed_boundary_parameter(void)
    914 {
    915 	if (test_parse_request(
    916 		"multipart/form-data; boundary",
    917 		NULL,
    918 		MULTIPART_INVALID_CONTENT_TYPE) != 0)
    919 		return 1;
    920 
    921 	printf("ok: reject malformed boundary parameter\n");
    922 	return 0;
    923 }
    924 
    925 static int
    926 test_content_type_wrong_media_type(void)
    927 {
    928 	if (test_parse_request(
    929 		"application/x-www-form-urlencoded; boundary=test-boundary",
    930 		NULL,
    931 		MULTIPART_INVALID_CONTENT_TYPE) != 0)
    932 		return 1;
    933 
    934 	printf("ok: reject wrong media type\n");
    935 	return 0;
    936 }
    937 
    938 static int
    939 test_content_type_duplicate_boundary(void)
    940 {
    941 	if (test_parse_request(
    942 		"multipart/form-data; boundary=one; boundary=two",
    943 		NULL,
    944 		MULTIPART_INVALID_BOUNDARY) != 0)
    945 		return 1;
    946 
    947 	printf("ok: reject duplicate boundary parameters\n");
    948 	return 0;
    949 }
    950 
    951 static int
    952 test_filename_is_actual_parameter(void)
    953 {
    954 	const char *body =
    955 		"--test-boundary\r\n"
    956 		"Content-Disposition: form-data; "
    957 		"name=\"file\"; "
    958 		"filename=\"filename=not-the-filename.txt\"\r\n"
    959 		"\r\n"
    960 		"hello\r\n"
    961 		"--test-boundary--\r\n";
    962 	struct multipart_request req;
    963 	struct multipart_part part;
    964 	int fd;
    965 
    966 	fd = create_spool(body, &req);
    967 	if (fd == -1)
    968 		return 1;
    969 
    970 	if (multipart_extract_part(fd, &req, &part) != MULTIPART_OK) {
    971 		fprintf(stderr,
    972 		    "FAIL: actual filename parameter: extraction failed\n");
    973 		close(fd);
    974 		unlink(TEST_SPOOL);
    975 		return 1;
    976 	}
    977 
    978 	if (strcmp(part.filename, "filename=not-the-filename.txt") != 0) {
    979 		fprintf(stderr,
    980 		    "FAIL: actual filename parameter: filename was '%s', "
    981 		    "expected 'filename=not-the-filename.txt'\n",
    982 		    part.filename);
    983 		close(fd);
    984 		unlink(TEST_SPOOL);
    985 		return 1;
    986 	}
    987 
    988 	close(fd);
    989 	unlink(TEST_SPOOL);
    990 
    991 	printf("ok: filename is an actual parameter\n");
    992 	return 0;
    993 }
    994 
    995 static int
    996 test_filename_parameter_name_must_match(void)
    997 {
    998 	const char *body =
    999 		"--test-boundary\r\n"
   1000 		"Content-Disposition: form-data; "
   1001 		"name=\"file\"; filenameX=\"wrong.txt\"\r\n"
   1002 		"\r\n"
   1003 		"hello\r\n"
   1004 		"--test-boundary--\r\n";
   1005 	struct multipart_request req;
   1006 	struct multipart_part part;
   1007 	int fd;
   1008 
   1009 	fd = create_spool(body, &req);
   1010 	if (fd == -1)
   1011 		return 1;
   1012 
   1013 	if (multipart_extract_part(fd, &req, &part) !=
   1014 	    MULTIPART_MISSING_FILENAME) {
   1015 		fprintf(stderr,
   1016 		    "FAIL: filename parameter name: "
   1017 		    "accepted non-filename parameter\n");
   1018 		close(fd);
   1019 		unlink(TEST_SPOOL);
   1020 		return 1;
   1021 	}
   1022 
   1023 	close(fd);
   1024 	unlink(TEST_SPOOL);
   1025 
   1026 	printf("ok: reject filename-like parameter name\n");
   1027 	return 0;
   1028 }
   1029 
   1030 static int
   1031 test_duplicate_filename(void)
   1032 {
   1033 	const char *body =
   1034 		"--test-boundary\r\n"
   1035 		"Content-Disposition: form-data; "
   1036 		"name=\"file\"; filename=\"one.txt\"; "
   1037 		"filename=\"two.txt\"\r\n"
   1038 		"\r\n"
   1039 		"hello\r\n"
   1040 		"--test-boundary--\r\n";
   1041 	struct multipart_request req;
   1042 	struct multipart_part part;
   1043 	int fd;
   1044 
   1045 	fd = create_spool(body, &req);
   1046 	if (fd == -1)
   1047 		return 1;
   1048 
   1049 	if (multipart_extract_part(fd, &req, &part) !=
   1050 	    MULTIPART_MALFORMED) {
   1051 		fprintf(stderr,
   1052 		    "FAIL: duplicate filename: accepted body\n");
   1053 		close(fd);
   1054 		unlink(TEST_SPOOL);
   1055 		return 1;
   1056 	}
   1057 
   1058 	close(fd);
   1059 	unlink(TEST_SPOOL);
   1060 
   1061 	printf("ok: reject duplicate filename parameter\n");
   1062 	return 0;
   1063 }
   1064 
   1065 static int
   1066 test_filename_parameter_whitespace(void)
   1067 {
   1068 	const char *body =
   1069 		"--test-boundary\r\n"
   1070 		"Content-Disposition: form-data; "
   1071 		"name=\"file\"; filename = \"hello.txt\"\r\n"
   1072 		"\r\n"
   1073 		"hello\r\n"
   1074 		"--test-boundary--\r\n";
   1075 	struct multipart_request req;
   1076 	struct multipart_part part;
   1077 	int fd;
   1078 
   1079 	fd = create_spool(body, &req);
   1080 	if (fd == -1)
   1081 		return 1;
   1082 
   1083 	if (multipart_extract_part(fd, &req, &part) != MULTIPART_OK) {
   1084 		fprintf(stderr,
   1085 		    "FAIL: filename whitespace: extraction failed\n");
   1086 		close(fd);
   1087 		unlink(TEST_SPOOL);
   1088 		return 1;
   1089 	}
   1090 
   1091 	if (strcmp(part.filename, "hello.txt") != 0) {
   1092 		fprintf(stderr,
   1093 		    "FAIL: filename whitespace: filename was '%s'\n",
   1094 		    part.filename);
   1095 		close(fd);
   1096 		unlink(TEST_SPOOL);
   1097 		return 1;
   1098 	}
   1099 
   1100 	close(fd);
   1101 	unlink(TEST_SPOOL);
   1102 
   1103 	printf("ok: accept whitespace around filename '='\n");
   1104 	return 0;
   1105 }
   1106 
   1107 static int
   1108 test_reject_extra_non_file_part(void)
   1109 {
   1110 	const char *body =
   1111 		"--test-boundary\r\n"
   1112 		"Content-Disposition: form-data; name=\"file\"; "
   1113 		"filename=\"hello.txt\"\r\n"
   1114 		"\r\n"
   1115 		"hello\r\n"
   1116 		"--test-boundary\r\n"
   1117 		"Content-Disposition: form-data; name=\"extra\"\r\n"
   1118 		"\r\n"
   1119 		"unexpected\r\n"
   1120 		"--test-boundary--\r\n";
   1121 	struct multipart_request req;
   1122 	struct multipart_part part;
   1123 	int fd;
   1124 
   1125 	fd = create_spool(body, &req);
   1126 	if (fd == -1)
   1127 		return 1;
   1128 
   1129 	if (multipart_extract_part(fd, &req, &part) !=
   1130 	    MULTIPART_MALFORMED) {
   1131 		fprintf(stderr,
   1132 		    "FAIL: extra non-file part: "
   1133 		    "accepted unexpected part\n");
   1134 		close(fd);
   1135 		unlink(TEST_SPOOL);
   1136 		return 1;
   1137 	}
   1138 
   1139 	close(fd);
   1140 	unlink(TEST_SPOOL);
   1141 
   1142 	printf("ok: reject extra non-file part\n");
   1143 	return 0;
   1144 }
   1145 
   1146 static int
   1147 test_reject_wrong_field_name(void)
   1148 {
   1149 	const char *body =
   1150 		"--test-boundary\r\n"
   1151 		"Content-Disposition: form-data; name=\"upload\"; "
   1152 		"filename=\"hello.txt\"\r\n"
   1153 		"\r\n"
   1154 		"hello\r\n"
   1155 		"--test-boundary--\r\n";
   1156 	struct multipart_request req;
   1157 	struct multipart_part part;
   1158 	int fd;
   1159 
   1160 	fd = create_spool(body, &req);
   1161 	if (fd == -1)
   1162 		return 1;
   1163 
   1164 	if (multipart_extract_part(fd, &req, &part) !=
   1165 	    MULTIPART_MALFORMED) {
   1166 		fprintf(stderr,
   1167 		    "FAIL: wrong field name: accepted unexpected field\n");
   1168 		close(fd);
   1169 		unlink(TEST_SPOOL);
   1170 		return 1;
   1171 	}
   1172 
   1173 	close(fd);
   1174 	unlink(TEST_SPOOL);
   1175 
   1176 	printf("ok: reject wrong field name\n");
   1177 	return 0;
   1178 }
   1179 
   1180 static int
   1181 test_reject_missing_field_name(void)
   1182 {
   1183 	const char *body =
   1184 		"--test-boundary\r\n"
   1185 		"Content-Disposition: form-data; "
   1186 		"filename=\"hello.txt\"\r\n"
   1187 		"\r\n"
   1188 		"hello\r\n"
   1189 		"--test-boundary--\r\n";
   1190 	struct multipart_request req;
   1191 	struct multipart_part part;
   1192 	int fd;
   1193 
   1194 	fd = create_spool(body, &req);
   1195 	if (fd == -1)
   1196 		return 1;
   1197 
   1198 	if (multipart_extract_part(fd, &req, &part) !=
   1199 	    MULTIPART_MALFORMED) {
   1200 		fprintf(stderr,
   1201 		    "FAIL: missing field name: accepted malformed part\n");
   1202 		close(fd);
   1203 		unlink(TEST_SPOOL);
   1204 		return 1;
   1205 	}
   1206 
   1207 	close(fd);
   1208 	unlink(TEST_SPOOL);
   1209 
   1210 	printf("ok: reject missing field name\n");
   1211 	return 0;
   1212 }
   1213 
   1214 static int
   1215 test_reject_non_file_part_before_file(void)
   1216 {
   1217 	const char *body =
   1218 		"--test-boundary\r\n"
   1219 		"Content-Disposition: form-data; name=\"extra\"\r\n"
   1220 		"\r\n"
   1221 		"unexpected\r\n"
   1222 		"--test-boundary\r\n"
   1223 		"Content-Disposition: form-data; name=\"file\"; "
   1224 		"filename=\"hello.txt\"\r\n"
   1225 		"\r\n"
   1226 		"hello\r\n"
   1227 		"--test-boundary--\r\n";
   1228 	struct multipart_request req;
   1229 	struct multipart_part part;
   1230 	int fd;
   1231 
   1232 	fd = create_spool(body, &req);
   1233 	if (fd == -1)
   1234 		return 1;
   1235 
   1236 	if (multipart_extract_part(fd, &req, &part) !=
   1237 	    MULTIPART_MALFORMED) {
   1238 		fprintf(stderr,
   1239 		    "FAIL: non-file part before file: "
   1240 		    "accepted unexpected structure\n");
   1241 		close(fd);
   1242 		unlink(TEST_SPOOL);
   1243 		return 1;
   1244 	}
   1245 
   1246 	close(fd);
   1247 	unlink(TEST_SPOOL);
   1248 
   1249 	printf("ok: reject non-file part before file\n");
   1250 	return 0;
   1251 }
   1252 
   1253 static int
   1254 test_reject_negative_part_offset(void)
   1255 {
   1256 	struct multipart_part part;
   1257 	int fd;
   1258 	int outfd;
   1259 
   1260 	fd = open(TEST_SPOOL, O_RDWR | O_CREAT | O_TRUNC, 0600);
   1261 	if (fd == -1)
   1262 		return 1;
   1263 
   1264 	outfd = open(TEST_OUTPUT, O_RDWR | O_CREAT | O_TRUNC, 0600);
   1265 	if (outfd == -1) {
   1266 		close(fd);
   1267 		unlink(TEST_SPOOL);
   1268 		return 1;
   1269 	}
   1270 
   1271 	memset(&part, 0, sizeof(part));
   1272 
   1273 	part.offset = -1;
   1274 	part.length = 1;
   1275 
   1276 	if (multipart_copy_part(fd, &part, outfd) != MULTIPART_ERROR) {
   1277 		fprintf(stderr,
   1278 		    "FAIL: negative part offset: accepted invalid offset\n");
   1279 		close(outfd);
   1280 		close(fd);
   1281 		unlink(TEST_SPOOL);
   1282 		unlink(TEST_OUTPUT);
   1283 		return 1;
   1284 	}
   1285 
   1286 	close(outfd);
   1287 	close(fd);
   1288 	unlink(TEST_SPOOL);
   1289 	unlink(TEST_OUTPUT);
   1290 
   1291 	printf("ok: reject negative part offset\n");
   1292 	return 0;
   1293 }
   1294 
   1295 static int
   1296 test_reject_negative_part_length(void)
   1297 {
   1298 	struct multipart_part part;
   1299 	int fd;
   1300 	int outfd;
   1301 
   1302 	fd = open(TEST_SPOOL, O_RDWR | O_CREAT | O_TRUNC, 0600);
   1303 	if (fd == -1)
   1304 		return 1;
   1305 
   1306 	outfd = open(TEST_OUTPUT, O_RDWR | O_CREAT | O_TRUNC, 0600);
   1307 	if (outfd == -1) {
   1308 		close(fd);
   1309 		unlink(TEST_SPOOL);
   1310 		return 1;
   1311 	}
   1312 
   1313 	memset(&part, 0, sizeof(part));
   1314 
   1315 	part.offset = 0;
   1316 	part.length = -1;
   1317 
   1318 	if (multipart_copy_part(fd, &part, outfd) != MULTIPART_ERROR) {
   1319 		fprintf(stderr,
   1320 		    "FAIL: negative part length: accepted invalid length\n");
   1321 		close(outfd);
   1322 		close(fd);
   1323 		unlink(TEST_SPOOL);
   1324 		unlink(TEST_OUTPUT);
   1325 		return 1;
   1326 	}
   1327 
   1328 	close(outfd);
   1329 	close(fd);
   1330 	unlink(TEST_SPOOL);
   1331 	unlink(TEST_OUTPUT);
   1332 
   1333 	printf("ok: reject negative part length\n");
   1334 	return 0;
   1335 }
   1336 
   1337 static int
   1338 test_reject_part_beyond_spool(void)
   1339 {
   1340 	const char *data = "hello";
   1341 	struct multipart_part part;
   1342 	int fd;
   1343 	int outfd;
   1344 
   1345 	fd = open(TEST_SPOOL, O_RDWR | O_CREAT | O_TRUNC, 0600);
   1346 	if (fd == -1)
   1347 		return 1;
   1348 
   1349 	if (write_file(fd, data) == -1) {
   1350 		close(fd);
   1351 		unlink(TEST_SPOOL);
   1352 		return 1;
   1353 	}
   1354 
   1355 	outfd = open(TEST_OUTPUT, O_RDWR | O_CREAT | O_TRUNC, 0600);
   1356 	if (outfd == -1) {
   1357 		close(fd);
   1358 		unlink(TEST_SPOOL);
   1359 		return 1;
   1360 	}
   1361 
   1362 	memset(&part, 0, sizeof(part));
   1363 
   1364 	part.offset = 3;
   1365 	part.length = 3;
   1366 
   1367 	if (multipart_copy_part(fd, &part, outfd) != MULTIPART_ERROR) {
   1368 		fprintf(stderr,
   1369 		    "FAIL: part beyond spool: accepted invalid range\n");
   1370 		close(outfd);
   1371 		close(fd);
   1372 		unlink(TEST_SPOOL);
   1373 		unlink(TEST_OUTPUT);
   1374 		return 1;
   1375 	}
   1376 
   1377 	close(outfd);
   1378 	close(fd);
   1379 	unlink(TEST_SPOOL);
   1380 	unlink(TEST_OUTPUT);
   1381 
   1382 	printf("ok: reject part beyond spool\n");
   1383 	return 0;
   1384 }
   1385 
   1386 static int
   1387 test_reject_part_range_overflow(void)
   1388 {
   1389 	struct multipart_part part;
   1390 	int fd;
   1391 	int outfd;
   1392 
   1393 	fd = open(TEST_SPOOL, O_RDWR | O_CREAT | O_TRUNC, 0600);
   1394 	if (fd == -1)
   1395 		return 1;
   1396 
   1397 	outfd = open(TEST_OUTPUT, O_RDWR | O_CREAT | O_TRUNC, 0600);
   1398 	if (outfd == -1) {
   1399 		close(fd);
   1400 		unlink(TEST_SPOOL);
   1401 		return 1;
   1402 	}
   1403 
   1404 	memset(&part, 0, sizeof(part));
   1405 
   1406 	part.offset = (off_t) -1;
   1407 	part.length = 2;
   1408 
   1409 	if (multipart_copy_part(fd, &part, outfd) != MULTIPART_ERROR) {
   1410 		fprintf(stderr,
   1411 		    "FAIL: part range overflow: accepted overflowing range\n");
   1412 		close(outfd);
   1413 		close(fd);
   1414 		unlink(TEST_SPOOL);
   1415 		unlink(TEST_OUTPUT);
   1416 		return 1;
   1417 	}
   1418 
   1419 	close(outfd);
   1420 	close(fd);
   1421 	unlink(TEST_SPOOL);
   1422 	unlink(TEST_OUTPUT);
   1423 
   1424 	printf("ok: reject overflowing part range\n");
   1425 	return 0;
   1426 }
   1427 
   1428 static int
   1429 test_reject_request_length_beyond_spool(void)
   1430 {
   1431 	const char *body =
   1432 		"--test-boundary\r\n"
   1433 		"Content-Disposition: form-data; name=\"file\"; "
   1434 		"filename=\"hello.txt\"\r\n"
   1435 		"\r\n"
   1436 		"hello\r\n"
   1437 		"--test-boundary--\r\n";
   1438 	struct multipart_request req;
   1439 	struct multipart_part part;
   1440 	int fd;
   1441 
   1442 	fd = create_spool(body, &req);
   1443 	if (fd == -1)
   1444 		return 1;
   1445 
   1446 	/*
   1447 	 * Claim that CONTENT_LENGTH extends beyond the actual spool.
   1448 	 */
   1449 	req.length += 100;
   1450 
   1451 	if (multipart_extract_part(fd, &req, &part) !=
   1452 	    MULTIPART_MALFORMED) {
   1453 		fprintf(stderr,
   1454 		    "FAIL: request length beyond spool: "
   1455 		    "accepted invalid request length\n");
   1456 		close(fd);
   1457 		unlink(TEST_SPOOL);
   1458 		return 1;
   1459 	}
   1460 
   1461 	close(fd);
   1462 	unlink(TEST_SPOOL);
   1463 
   1464 	printf("ok: reject request length beyond spool\n");
   1465 	return 0;
   1466 }
   1467 
   1468 static int
   1469 test_content_length_valid(void)
   1470 {
   1471 	struct multipart_request req;
   1472 	int ret;
   1473 
   1474 	memset(&req, 0, sizeof(req));
   1475 
   1476 	if (setenv("CONTENT_TYPE",
   1477 	    "multipart/form-data; boundary=test-boundary", 1) == -1 ||
   1478 	    setenv("CONTENT_LENGTH", "100", 1) == -1) {
   1479 		fprintf(stderr,
   1480 		    "FAIL: valid Content-Length: could not set environment\n");
   1481 		return 1;
   1482 	}
   1483 
   1484 	ret = multipart_parse_request(&req);
   1485 
   1486 	if (ret != MULTIPART_OK) {
   1487 		fprintf(stderr,
   1488 		    "FAIL: valid Content-Length: returned %d\n", ret);
   1489 		return 1;
   1490 	}
   1491 
   1492 	if (req.length != 100) {
   1493 		fprintf(stderr,
   1494 		    "FAIL: valid Content-Length: parsed length %lld, "
   1495 		    "expected 100\n",
   1496 		    (long long) req.length);
   1497 		return 1;
   1498 	}
   1499 
   1500 	printf("ok: accept valid Content-Length\n");
   1501 	return 0;
   1502 }
   1503 
   1504 static int
   1505 test_content_length_empty(void)
   1506 {
   1507 	struct multipart_request req;
   1508 	int ret;
   1509 
   1510 	memset(&req, 0, sizeof(req));
   1511 
   1512 	if (setenv("CONTENT_TYPE",
   1513 	    "multipart/form-data; boundary=test-boundary", 1) == -1 ||
   1514 	    setenv("CONTENT_LENGTH", "", 1) == -1) {
   1515 		fprintf(stderr,
   1516 		    "FAIL: empty Content-Length: could not set environment\n");
   1517 		return 1;
   1518 	}
   1519 
   1520 	ret = multipart_parse_request(&req);
   1521 
   1522 	if (ret != MULTIPART_INVALID_LENGTH) {
   1523 		fprintf(stderr,
   1524 		    "FAIL: empty Content-Length: returned %d, "
   1525 		    "expected %d\n",
   1526 		    ret, MULTIPART_INVALID_LENGTH);
   1527 		return 1;
   1528 	}
   1529 
   1530 	printf("ok: reject empty Content-Length\n");
   1531 	return 0;
   1532 }
   1533 
   1534 static int
   1535 test_content_length_negative(void)
   1536 {
   1537 	struct multipart_request req;
   1538 	int ret;
   1539 
   1540 	memset(&req, 0, sizeof(req));
   1541 
   1542 	if (setenv("CONTENT_TYPE",
   1543 	    "multipart/form-data; boundary=test-boundary", 1) == -1 ||
   1544 	    setenv("CONTENT_LENGTH", "-1", 1) == -1) {
   1545 		fprintf(stderr,
   1546 		    "FAIL: negative Content-Length: could not set environment\n");
   1547 		return 1;
   1548 	}
   1549 
   1550 	ret = multipart_parse_request(&req);
   1551 
   1552 	if (ret != MULTIPART_INVALID_LENGTH) {
   1553 		fprintf(stderr,
   1554 		    "FAIL: negative Content-Length: returned %d, "
   1555 		    "expected %d\n",
   1556 		    ret, MULTIPART_INVALID_LENGTH);
   1557 		return 1;
   1558 	}
   1559 
   1560 	printf("ok: reject negative Content-Length\n");
   1561 	return 0;
   1562 }
   1563 
   1564 static int
   1565 test_content_length_plus_sign(void)
   1566 {
   1567 	struct multipart_request req;
   1568 	int ret;
   1569 
   1570 	memset(&req, 0, sizeof(req));
   1571 
   1572 	if (setenv("CONTENT_TYPE",
   1573 	    "multipart/form-data; boundary=test-boundary", 1) == -1 ||
   1574 	    setenv("CONTENT_LENGTH", "+100", 1) == -1) {
   1575 		fprintf(stderr,
   1576 		    "FAIL: signed Content-Length: could not set environment\n");
   1577 		return 1;
   1578 	}
   1579 
   1580 	ret = multipart_parse_request(&req);
   1581 
   1582 	if (ret != MULTIPART_INVALID_LENGTH) {
   1583 		fprintf(stderr,
   1584 		    "FAIL: signed Content-Length: returned %d, "
   1585 		    "expected %d\n",
   1586 		    ret, MULTIPART_INVALID_LENGTH);
   1587 		return 1;
   1588 	}
   1589 
   1590 	printf("ok: reject plus-signed Content-Length\n");
   1591 	return 0;
   1592 }
   1593 
   1594 static int
   1595 test_content_length_leading_whitespace(void)
   1596 {
   1597 	struct multipart_request req;
   1598 	int ret;
   1599 
   1600 	memset(&req, 0, sizeof(req));
   1601 
   1602 	if (setenv("CONTENT_TYPE",
   1603 	    "multipart/form-data; boundary=test-boundary", 1) == -1 ||
   1604 	    setenv("CONTENT_LENGTH", " 100", 1) == -1) {
   1605 		fprintf(stderr,
   1606 		    "FAIL: whitespace Content-Length: "
   1607 		    "could not set environment\n");
   1608 		return 1;
   1609 	}
   1610 
   1611 	ret = multipart_parse_request(&req);
   1612 
   1613 	if (ret != MULTIPART_INVALID_LENGTH) {
   1614 		fprintf(stderr,
   1615 		    "FAIL: leading whitespace Content-Length: returned %d, "
   1616 		    "expected %d\n",
   1617 		    ret, MULTIPART_INVALID_LENGTH);
   1618 		return 1;
   1619 	}
   1620 
   1621 	printf("ok: reject leading whitespace in Content-Length\n");
   1622 	return 0;
   1623 }
   1624 
   1625 static int
   1626 test_content_length_trailing_whitespace(void)
   1627 {
   1628 	struct multipart_request req;
   1629 	int ret;
   1630 
   1631 	memset(&req, 0, sizeof(req));
   1632 
   1633 	if (setenv("CONTENT_TYPE",
   1634 	    "multipart/form-data; boundary=test-boundary", 1) == -1 ||
   1635 	    setenv("CONTENT_LENGTH", "100 ", 1) == -1) {
   1636 		fprintf(stderr,
   1637 		    "FAIL: whitespace Content-Length: "
   1638 		    "could not set environment\n");
   1639 		return 1;
   1640 	}
   1641 
   1642 	ret = multipart_parse_request(&req);
   1643 
   1644 	if (ret != MULTIPART_INVALID_LENGTH) {
   1645 		fprintf(stderr,
   1646 		    "FAIL: trailing whitespace Content-Length: returned %d, "
   1647 		    "expected %d\n",
   1648 		    ret, MULTIPART_INVALID_LENGTH);
   1649 		return 1;
   1650 	}
   1651 
   1652 	printf("ok: reject trailing whitespace in Content-Length\n");
   1653 	return 0;
   1654 }
   1655 
   1656 static int
   1657 test_content_length_non_digit(void)
   1658 {
   1659 	struct multipart_request req;
   1660 	int ret;
   1661 
   1662 	memset(&req, 0, sizeof(req));
   1663 
   1664 	if (setenv("CONTENT_TYPE",
   1665 	    "multipart/form-data; boundary=test-boundary", 1) == -1 ||
   1666 	    setenv("CONTENT_LENGTH", "100abc", 1) == -1) {
   1667 		fprintf(stderr,
   1668 		    "FAIL: non-digit Content-Length: "
   1669 		    "could not set environment\n");
   1670 		return 1;
   1671 	}
   1672 
   1673 	ret = multipart_parse_request(&req);
   1674 
   1675 	if (ret != MULTIPART_INVALID_LENGTH) {
   1676 		fprintf(stderr,
   1677 		    "FAIL: non-digit Content-Length: returned %d, "
   1678 		    "expected %d\n",
   1679 		    ret, MULTIPART_INVALID_LENGTH);
   1680 		return 1;
   1681 	}
   1682 
   1683 	printf("ok: reject non-digit Content-Length\n");
   1684 	return 0;
   1685 }
   1686 
   1687 static int
   1688 test_content_length_decimal(void)
   1689 {
   1690 	struct multipart_request req;
   1691 	int ret;
   1692 
   1693 	memset(&req, 0, sizeof(req));
   1694 
   1695 	if (setenv("CONTENT_TYPE",
   1696 	    "multipart/form-data; boundary=test-boundary", 1) == -1 ||
   1697 	    setenv("CONTENT_LENGTH", "10.0", 1) == -1) {
   1698 		fprintf(stderr,
   1699 		    "FAIL: decimal Content-Length: "
   1700 		    "could not set environment\n");
   1701 		return 1;
   1702 	}
   1703 
   1704 	ret = multipart_parse_request(&req);
   1705 
   1706 	if (ret != MULTIPART_INVALID_LENGTH) {
   1707 		fprintf(stderr,
   1708 		    "FAIL: decimal Content-Length: returned %d, "
   1709 		    "expected %d\n",
   1710 		    ret, MULTIPART_INVALID_LENGTH);
   1711 		return 1;
   1712 	}
   1713 
   1714 	printf("ok: reject decimal Content-Length\n");
   1715 	return 0;
   1716 }
   1717 
   1718 static int
   1719 test_content_length_overflow(void)
   1720 {
   1721 	struct multipart_request req;
   1722 	int ret;
   1723 
   1724 	memset(&req, 0, sizeof(req));
   1725 
   1726 	if (setenv("CONTENT_TYPE",
   1727 	    "multipart/form-data; boundary=test-boundary", 1) == -1 ||
   1728 	    setenv("CONTENT_LENGTH",
   1729 	    "184467440737095516160", 1) == -1) {
   1730 		fprintf(stderr,
   1731 		    "FAIL: overflowing Content-Length: "
   1732 		    "could not set environment\n");
   1733 		return 1;
   1734 	}
   1735 
   1736 	ret = multipart_parse_request(&req);
   1737 
   1738 	if (ret != MULTIPART_INVALID_LENGTH) {
   1739 		fprintf(stderr,
   1740 		    "FAIL: overflowing Content-Length: returned %d, "
   1741 		    "expected %d\n",
   1742 		    ret, MULTIPART_INVALID_LENGTH);
   1743 		return 1;
   1744 	}
   1745 
   1746 	printf("ok: reject overflowing Content-Length\n");
   1747 	return 0;
   1748 }
   1749 
   1750 int
   1751 main(void)
   1752 {
   1753 	int failures;
   1754 
   1755 	failures = 0;
   1756 
   1757 	failures += test_extract_part();
   1758 	failures += test_filename_not_found_in_payload();
   1759 	failures += test_reject_multiple_file_parts();
   1760 	failures += test_multipart_copy_empty_part();
   1761 	failures += test_missing_opening_boundary();
   1762 	failures += test_wrong_opening_boundary();
   1763 	failures += test_missing_header_terminator();
   1764 	failures += test_missing_closing_boundary();
   1765 	failures += test_truncated_closing_boundary();
   1766 	failures += test_invalid_boundary_suffix();
   1767 	failures += test_missing_content_disposition();
   1768 	failures += test_wrong_content_disposition();
   1769 	failures += test_unquoted_filename();
   1770 	failures += test_empty_filename();
   1771 	failures += test_filename_is_actual_parameter();
   1772 	failures += test_filename_parameter_name_must_match();
   1773 	failures += test_duplicate_filename();
   1774 	failures += test_filename_parameter_whitespace();
   1775 	failures += test_boundary_like_payload();
   1776 	failures += test_content_type_normal();
   1777 	failures += test_content_type_case_insensitive();
   1778 	failures += test_content_type_boundary_case_insensitive();
   1779 	failures += test_content_type_quoted_boundary();
   1780 	failures += test_content_type_boundary_whitespace();
   1781 	failures += test_content_type_parameter_before_boundary();
   1782 	failures += test_content_type_missing_boundary();
   1783 	failures += test_content_type_invalid_boundary_character();
   1784 	failures += test_content_type_boundary_too_long();
   1785 	failures += test_content_type_malformed_boundary_parameter();
   1786 	failures += test_content_type_wrong_media_type();
   1787 	failures += test_content_type_duplicate_boundary();
   1788 	failures += test_reject_extra_non_file_part();
   1789 	failures += test_reject_wrong_field_name();
   1790 	failures += test_reject_missing_field_name();
   1791 	failures += test_reject_non_file_part_before_file();
   1792 	failures += test_reject_negative_part_offset();
   1793 	failures += test_reject_negative_part_length();
   1794 	failures += test_reject_part_beyond_spool();
   1795 	failures += test_reject_part_range_overflow();
   1796 	failures += test_reject_request_length_beyond_spool();
   1797 	failures += test_content_length_valid();
   1798 	failures += test_content_length_empty();
   1799 	failures += test_content_length_negative();
   1800 	failures += test_content_length_plus_sign();
   1801 	failures += test_content_length_leading_whitespace();
   1802 	failures += test_content_length_trailing_whitespace();
   1803 	failures += test_content_length_non_digit();
   1804 	failures += test_content_length_decimal();
   1805 	failures += test_content_length_overflow();
   1806 
   1807 	if (failures != 0) {
   1808 		fprintf(stderr,
   1809 		    "%d test(s) failed\n",
   1810 		    failures);
   1811 		return EXIT_FAILURE;
   1812 	}
   1813 	printf("all tests passed\n");
   1814 
   1815 	return EXIT_SUCCESS;
   1816 }