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 }