multipart.c (18279B)
1 #include <ctype.h> 2 #include <errno.h> 3 #include <stdio.h> 4 #include <stdlib.h> 5 #include <string.h> 6 #include <strings.h> 7 #include <sys/stat.h> 8 #include <unistd.h> 9 10 #include "config.h" 11 #include "multipart.h" 12 13 #define BOUNDARY_MAX 128 14 #define MULTIPART_BOUNDARY_MAX 70 15 #define MIME_TYPE "multipart/form-data" 16 17 static int 18 parse_content_disposition(char *value, char *filename, size_t size) 19 { 20 char *p; 21 char *start; 22 char *end; 23 size_t len; 24 int seen_filename; 25 int seen_name; 26 27 if (value == NULL || filename == NULL || size == 0) 28 return MULTIPART_ERROR; 29 30 p = value; 31 seen_filename = 0; 32 seen_name = 0; 33 34 while (*p == ' ' || *p == '\t') 35 p++; 36 37 if (strncasecmp(p, "form-data", 9) != 0) 38 return MULTIPART_MALFORMED; 39 40 p += 9; 41 42 if (*p != ';' && *p != '\0') 43 return MULTIPART_MALFORMED; 44 45 while (*p != '\0') { 46 /* 47 * Parameter separator. 48 */ 49 if (*p != ';') 50 return MULTIPART_MALFORMED; 51 52 p++; 53 54 while (*p == ' ' || *p == '\t') 55 p++; 56 57 /* 58 * Parameter name. 59 */ 60 start = p; 61 62 while (*p != '\0' && 63 *p != '=' && 64 *p != ';' && 65 *p != ' ' && 66 *p != '\t') 67 p++; 68 69 if (p == start) 70 return MULTIPART_MALFORMED; 71 72 end = p; 73 74 while (*p == ' ' || *p == '\t') 75 p++; 76 77 if (*p != '=') 78 return MULTIPART_MALFORMED; 79 80 p++; 81 82 while (*p == ' ' || *p == '\t') 83 p++; 84 85 if ((size_t) (end - start) == 4 && 86 strncasecmp(start, "name", 4) == 0) { 87 if (seen_name) 88 return MULTIPART_MALFORMED; 89 90 seen_name = 1; 91 92 if (*p != '"') 93 return MULTIPART_MALFORMED; 94 95 p++; 96 start = p; 97 98 while (*p != '\0' && *p != '"') 99 p++; 100 101 if (*p != '"' || p == start) 102 return MULTIPART_MALFORMED; 103 104 len = (size_t) (p - start); 105 106 if (len != 4 || strncasecmp(start, "file", 4) != 0) 107 return MULTIPART_MALFORMED; 108 109 p++; 110 111 while (*p == ' ' || *p == '\t') 112 p++; 113 114 if (*p != '\0' && *p != ';') 115 return MULTIPART_MALFORMED; 116 117 } else if ((size_t) (end - start) == 8 && 118 strncasecmp(start, "filename", 8) == 0) { 119 if (seen_filename) 120 return MULTIPART_MALFORMED; 121 122 seen_filename = 1; 123 124 if (*p != '"') 125 return MULTIPART_MALFORMED; 126 127 p++; 128 start = p; 129 130 while (*p != '\0' && *p != '"') 131 p++; 132 133 if (*p != '"' || p == start) 134 return MULTIPART_MALFORMED; 135 136 len = (size_t) (p - start); 137 138 if (len >= size) 139 return MULTIPART_MALFORMED; 140 141 memcpy(filename, start, len); 142 filename[len] = '\0'; 143 144 p++; 145 146 /* 147 * After a quoted filename, only whitespace, 148 * another parameter, or end-of-line is allowed. 149 */ 150 while (*p == ' ' || *p == '\t') 151 p++; 152 153 if (*p != '\0' && *p != ';') 154 return MULTIPART_MALFORMED; 155 } else { 156 /* 157 * Ignore unrelated parameters, but require a 158 * syntactically valid value. 159 */ 160 if (*p == '"') { 161 p++; 162 163 while (*p != '\0' && *p != '"') 164 p++; 165 166 if (*p != '"') 167 return MULTIPART_MALFORMED; 168 169 p++; 170 } else { 171 start = p; 172 173 while (*p != '\0' && 174 *p != ';' && 175 *p != ' ' && 176 *p != '\t') 177 p++; 178 179 if (p == start) 180 return MULTIPART_MALFORMED; 181 } 182 183 while (*p == ' ' || *p == '\t') 184 p++; 185 186 if (*p != '\0' && *p != ';') 187 return MULTIPART_MALFORMED; 188 } 189 } 190 191 if (!seen_name) 192 return MULTIPART_MALFORMED; 193 194 if (!seen_filename) 195 return MULTIPART_MISSING_FILENAME; 196 197 return MULTIPART_OK; 198 } 199 200 static int 201 next_part_is_file(int fd, off_t offset, 202 const struct multipart_request *req) 203 { 204 char headers[8192]; 205 char filename[NAME_MAX + 1]; 206 char *line; 207 char *next; 208 char *disposition; 209 size_t used; 210 ssize_t n; 211 int disposition_found; 212 213 if (req == NULL) 214 return MULTIPART_ERROR; 215 216 if (lseek(fd, offset, SEEK_SET) == (off_t)-1) 217 return MULTIPART_ERROR; 218 219 used = 0; 220 221 while (used < sizeof(headers) - 1) { 222 n = read(fd, headers + used, 1); 223 224 if (n == 0) 225 return MULTIPART_MALFORMED; 226 227 if (n < 0) 228 return MULTIPART_ERROR; 229 230 used++; 231 232 if (used >= 4 && 233 headers[used - 4] == '\r' && 234 headers[used - 3] == '\n' && 235 headers[used - 2] == '\r' && 236 headers[used - 1] == '\n') 237 break; 238 } 239 240 if (used >= sizeof(headers) - 1) 241 return MULTIPART_MALFORMED; 242 243 headers[used] = '\0'; 244 245 line = headers; 246 disposition_found = 0; 247 248 while (*line != '\0') { 249 next = strstr(line, "\r\n"); 250 251 if (next == NULL) 252 return MULTIPART_MALFORMED; 253 254 /* 255 * Empty line here would mean the header terminator was 256 * encountered. Since we already read CRLFCRLF, this is 257 * the end of the header section. 258 */ 259 if (next == line) 260 break; 261 262 *next = '\0'; 263 264 if (strncasecmp(line, "Content-Disposition:", 20) == 0) { 265 if (disposition_found) 266 return MULTIPART_MALFORMED; 267 268 disposition_found = 1; 269 270 disposition = line + 20; 271 272 while (*disposition == ' ' || 273 *disposition == '\t') 274 disposition++; 275 276 n = parse_content_disposition( 277 disposition, 278 filename, 279 sizeof(filename)); 280 281 if (n != MULTIPART_OK) 282 return (int)n; 283 } 284 285 line = next + 2; 286 } 287 288 if (!disposition_found) 289 return MULTIPART_MISSING_FILENAME; 290 291 return MULTIPART_OK; 292 } 293 294 static int 295 write_all(int fd, const void *buf, size_t len) 296 { 297 const unsigned char *p = buf; 298 size_t remaining = len; 299 300 while (remaining > 0) { 301 ssize_t n; 302 303 n = write(fd, p, remaining); 304 305 if (n <= 0) 306 return -1; 307 308 p += n; 309 remaining -= (size_t) n; 310 } 311 312 return 0; 313 } 314 315 static int 316 is_boundary_char(unsigned char c) 317 { 318 if (isalnum(c)) 319 return 1; 320 321 switch (c) { 322 case '\'': 323 case '(': 324 case ')': 325 case '+': 326 case '_': 327 case ',': 328 case '-': 329 case '.': 330 case '/': 331 case ':': 332 case '=': 333 case '?': 334 return 1; 335 default: 336 return 0; 337 } 338 } 339 /* 340 * Parse the Content-Type boundary. 341 * 342 * This intentionally preserves the existing parser behavior for now. 343 * Boundary parsing can be hardened in a later step. 344 */ 345 static int 346 parse_boundary(struct multipart_request * req, const char *ctype) 347 { 348 const char *p; 349 const char *start; 350 size_t len; 351 int seen_boundary; 352 353 /* 354 * Content-Type must begin with multipart/form-data. 355 * The media type comparison is case-insensitive. 356 */ 357 if (strncasecmp(ctype, "multipart/form-data", 19) != 0) 358 return MULTIPART_INVALID_CONTENT_TYPE; 359 360 p = ctype + 19; 361 seen_boundary = 0; 362 363 /* 364 * Parse MIME parameters. 365 * 366 * We accept optional whitespace around ';', parameter names, 367 * and '='. The boundary value may be quoted. 368 */ 369 for (;;) { 370 while (*p == ' ' || *p == '\t') 371 p++; 372 373 if (*p == '\0') { 374 if (!seen_boundary) 375 return MULTIPART_MISSING_BOUNDARY; 376 return MULTIPART_OK; 377 } 378 if (*p != ';') 379 return MULTIPART_INVALID_CONTENT_TYPE; 380 381 p++; 382 383 while (*p == ' ' || *p == '\t') 384 p++; 385 386 /* Parameter name. */ 387 start = p; 388 389 while (*p != '\0' && 390 *p != '=' && 391 *p != ';' && 392 *p != ' ' && 393 *p != '\t') 394 p++; 395 396 len = (size_t) (p - start); 397 398 while (*p == ' ' || *p == '\t') 399 p++; 400 401 if (*p != '=') 402 return MULTIPART_INVALID_CONTENT_TYPE; 403 404 p++; 405 406 while (*p == ' ' || *p == '\t') 407 p++; 408 409 /* 410 * We only care about the boundary parameter. 411 * Other parameters are syntactically allowed. 412 */ 413 if (len == 8 && 414 strncasecmp(start, "boundary", 8) == 0) { 415 if (seen_boundary) 416 return MULTIPART_INVALID_BOUNDARY; 417 418 seen_boundary = 1; 419 420 /* 421 * Quoted boundary. 422 */ 423 if (*p == '"') { 424 p++; 425 start = p; 426 427 while (*p != '\0' && *p != '"') { 428 if (!is_boundary_char( 429 (unsigned char) *p)) 430 return MULTIPART_INVALID_BOUNDARY; 431 p++; 432 } 433 434 if (*p != '"') 435 return MULTIPART_INVALID_BOUNDARY; 436 437 len = (size_t) (p - start); 438 439 if (len == 0 || len >= MULTIPART_BOUNDARY_MAX) 440 return MULTIPART_INVALID_BOUNDARY; 441 442 memcpy(req->boundary, start, len); 443 req->boundary[len] = '\0'; 444 445 p++; 446 447 while (*p == ' ' || *p == '\t') 448 p++; 449 450 if (*p != '\0' && *p != ';') 451 return MULTIPART_INVALID_CONTENT_TYPE; 452 } else { 453 /* 454 * Unquoted boundary. 455 */ 456 start = p; 457 458 while (*p != '\0' && *p != ';' && 459 *p != ' ' && *p != '\t') { 460 if (!is_boundary_char( 461 (unsigned char) *p)) 462 return MULTIPART_INVALID_BOUNDARY; 463 p++; 464 } 465 466 len = (size_t) (p - start); 467 468 if (len == 0 || len >= MULTIPART_BOUNDARY_MAX) 469 return MULTIPART_INVALID_BOUNDARY; 470 471 memcpy(req->boundary, start, len); 472 req->boundary[len] = '\0'; 473 474 while (*p == ' ' || *p == '\t') 475 p++; 476 477 if (*p != '\0' && *p != ';') 478 return MULTIPART_INVALID_CONTENT_TYPE; 479 } 480 } else { 481 /* 482 * Skip the value of an unrelated parameter. 483 * Handle quoted values so semicolons inside quotes 484 * aren't mistaken for parameter separators. 485 */ 486 if (*p == '"') { 487 p++; 488 489 while (*p != '\0' && *p != '"') 490 p++; 491 492 if (*p != '"') 493 return MULTIPART_INVALID_CONTENT_TYPE; 494 495 p++; 496 } else { 497 while (*p != '\0' && *p != ';') 498 p++; 499 } 500 } 501 } 502 } 503 504 int 505 multipart_parse_request(struct multipart_request * req) 506 { 507 const char *ctype; 508 const char *clen; 509 char *ep; 510 unsigned long long len; 511 off_t max_upload; 512 int ret; 513 const char *p; 514 515 if (req == NULL) 516 return MULTIPART_ERROR; 517 518 memset(req, 0, sizeof(*req)); 519 520 ctype = getenv("CONTENT_TYPE"); 521 522 if (ctype == NULL) 523 return MULTIPART_MISSING_CONTENT_TYPE; 524 525 ret = parse_boundary(req, ctype); 526 527 if (ret != MULTIPART_OK) 528 return ret; 529 530 clen = getenv("CONTENT_LENGTH"); 531 532 if (clen == NULL) 533 return MULTIPART_MISSING_LENGTH; 534 535 for (p = clen; *p != '\0'; p++) { 536 if (*p < '0' || *p > '9') 537 return MULTIPART_INVALID_LENGTH; 538 } 539 540 errno = 0; 541 542 len = strtoull(clen, &ep, 10); 543 544 if (errno != 0 || 545 ep == clen || 546 *ep != '\0') 547 return MULTIPART_INVALID_LENGTH; 548 549 if (errno == ERANGE || ep == clen || *ep != '\0') 550 return MULTIPART_INVALID_LENGTH; 551 552 max_upload = config_max_upload(); 553 554 if (len == 0 || 555 len > (unsigned long long) max_upload) 556 return MULTIPART_TOO_LARGE; 557 558 req->length = (off_t) len; 559 560 return MULTIPART_OK; 561 } 562 563 int 564 multipart_spool_request(int fd, off_t length) 565 { 566 unsigned char buffer[8192]; 567 off_t remaining = length; 568 569 while (remaining > 0) { 570 size_t want; 571 ssize_t n; 572 573 want = remaining > (off_t) sizeof(buffer) 574 ? sizeof(buffer) 575 : (size_t) remaining; 576 577 n = read(STDIN_FILENO, buffer, want); 578 579 if (n <= 0) 580 return MULTIPART_ERROR; 581 582 /* 583 * Keep the write loop local to the multipart/spooling 584 * implementation for now. 585 */ 586 { 587 const unsigned char *p = buffer; 588 size_t left = (size_t) n; 589 590 while (left > 0) { 591 ssize_t written; 592 593 written = write(fd, p, left); 594 595 if (written <= 0) 596 return MULTIPART_ERROR; 597 598 p += written; 599 left -= (size_t) written; 600 } 601 } 602 603 remaining -= n; 604 } 605 606 if (lseek(fd, 0, SEEK_SET) == (off_t) - 1) 607 return MULTIPART_ERROR; 608 609 return MULTIPART_OK; 610 } 611 612 static int 613 extract_filename(int fd, const struct multipart_request * req, 614 char *filename, size_t size) 615 { 616 char headers[8192]; 617 size_t used = 0; 618 size_t header_end = 0; 619 char *line; 620 char *next; 621 char *disposition; 622 ssize_t n; 623 624 if (req == NULL || filename == NULL || size == 0) 625 return MULTIPART_ERROR; 626 627 if (lseek(fd, 0, SEEK_SET) == (off_t) - 1) 628 return MULTIPART_ERROR; 629 630 /* 631 * Read only the first multipart part's headers. 632 * 633 * The header terminator is part of the multipart structure, so 634 * anything after it belongs to the payload and must not be used 635 * when identifying the filename. 636 */ 637 while (used < sizeof(headers) - 1) { 638 n = read(fd, headers + used, 1); 639 640 if (n == 0) 641 return MULTIPART_MALFORMED; 642 643 if (n < 0) 644 return MULTIPART_ERROR; 645 646 used++; 647 648 if (used >= 4 && 649 headers[used - 4] == '\r' && 650 headers[used - 3] == '\n' && 651 headers[used - 2] == '\r' && 652 headers[used - 1] == '\n') { 653 header_end = used - 4; 654 break; 655 } 656 } 657 658 if (header_end == 0) 659 return MULTIPART_MALFORMED; 660 661 headers[header_end + 2] = '\0'; 662 663 /* 664 * The first line must be the opening multipart boundary. 665 */ 666 { 667 size_t boundary_len = strlen(req->boundary); 668 size_t opening_len; 669 670 opening_len = boundary_len + 4; /* "--" + boundary + "\r\n" */ 671 672 if (header_end < opening_len || 673 headers[0] != '-' || 674 headers[1] != '-' || 675 memcmp(headers + 2, 676 req->boundary, boundary_len) != 0 || 677 headers[2 + boundary_len] != '\r' || 678 headers[3 + boundary_len] != '\n') 679 return MULTIPART_MALFORMED; 680 } 681 682 /* 683 * Examine each header line. Header names are case-insensitive. 684 */ 685 line = strchr(headers, '\n'); 686 if (line == NULL) 687 return MULTIPART_MALFORMED; 688 689 line++; 690 691 while (*line != '\0') { 692 next = strstr(line, "\r\n"); 693 694 if (next == NULL) 695 break; 696 697 *next = '\0'; 698 699 if (strncasecmp(line, "Content-Disposition:", 20) == 0) { 700 disposition = line + 20; 701 while (*disposition == ' ' || *disposition == '\t') 702 disposition++; 703 704 return parse_content_disposition(disposition, filename, size); 705 706 } 707 line = next + 2; 708 } 709 710 return MULTIPART_MISSING_FILENAME; 711 } 712 713 static int 714 find_payload(int fd, 715 const struct multipart_request * req, 716 off_t * payload_offset, 717 off_t * payload_length) 718 { 719 unsigned char buffer[8192]; 720 char delimiter[BOUNDARY_MAX + 3]; 721 size_t delimiter_len; 722 size_t match; 723 off_t payload_start; 724 off_t pos; 725 off_t remaining; 726 727 if (req == NULL || 728 payload_offset == NULL || 729 payload_length == NULL) 730 return MULTIPART_ERROR; 731 732 { 733 struct stat st; 734 735 if (req->length < 0) 736 return MULTIPART_MALFORMED; 737 738 if (fstat(fd, &st) == -1) 739 return MULTIPART_ERROR; 740 741 if (st.st_size < req->length) 742 return MULTIPART_MALFORMED; 743 } 744 745 if (snprintf(delimiter, sizeof(delimiter), 746 "\r\n--%s", req->boundary) >= 747 (int) sizeof(delimiter)) 748 return MULTIPART_ERROR; 749 750 delimiter_len = strlen(delimiter); 751 752 /* 753 * The payload starts after the first CRLFCRLF 754 * terminating the part headers. 755 */ 756 if (lseek(fd, 0, SEEK_SET) == (off_t) - 1) 757 return MULTIPART_ERROR; 758 759 pos = 0; 760 remaining = req->length; 761 762 { 763 const char header_end[] = "\r\n\r\n"; 764 size_t header_match = 0; 765 766 while (remaining > 0) { 767 size_t want; 768 ssize_t n; 769 size_t i; 770 771 want = remaining > (off_t) sizeof(buffer) 772 ? sizeof(buffer) 773 : (size_t) remaining; 774 775 n = read(fd, buffer, want); 776 if (n < 0) 777 return MULTIPART_ERROR; 778 if (n == 0) 779 return MULTIPART_MALFORMED; 780 781 for (i = 0; i < (size_t) n; i++) { 782 if (buffer[i] == 783 (unsigned char) header_end[header_match]) { 784 header_match++; 785 786 if (header_match == 787 sizeof(header_end) - 1) { 788 payload_start = 789 pos + (off_t) i + 1; 790 goto header_found; 791 } 792 } else { 793 header_match = 0; 794 } 795 } 796 797 pos += n; 798 remaining -= n; 799 } 800 } 801 802 /* 803 * No CRLFCRLF means the part headers were never 804 * properly terminated. 805 */ 806 return MULTIPART_MALFORMED; 807 808 header_found: 809 if (payload_start > req->length) 810 return MULTIPART_MALFORMED; 811 812 if (lseek(fd, payload_start, SEEK_SET) == (off_t) - 1) 813 return MULTIPART_ERROR; 814 815 pos = payload_start; 816 remaining = req->length - payload_start; 817 match = 0; 818 819 while (remaining > 0) { 820 size_t want; 821 ssize_t n; 822 size_t i; 823 824 want = remaining > (off_t) sizeof(buffer) 825 ? sizeof(buffer) 826 : (size_t) remaining; 827 828 n = read(fd, buffer, want); 829 if (n < 0) 830 return MULTIPART_ERROR; 831 if (n == 0) 832 return MULTIPART_MALFORMED; 833 834 for (i = 0; i < (size_t) n; i++) { 835 unsigned char c; 836 837 c = buffer[i]; 838 839 if (c == (unsigned char) delimiter[match]) { 840 match++; 841 842 if (match == delimiter_len) { 843 off_t delimiter_end; 844 unsigned char suffix[2]; 845 ssize_t suffix_len; 846 847 delimiter_end = 848 pos + (off_t) i + 1; 849 850 /* 851 * The boundary delimiter must be 852 * followed by exactly one of: 853 * 854 * "--" final boundary 855 * "\r\n" next part 856 */ 857 suffix_len = pread(fd, 858 suffix, sizeof(suffix), 859 delimiter_end); 860 861 if (suffix_len == -1) 862 return MULTIPART_ERROR; 863 864 /* 865 * We found the beginning of a 866 * boundary, but the request ends 867 * before its required suffix. 868 */ 869 if (suffix_len != 2) 870 return MULTIPART_MALFORMED; 871 872 if (suffix[0] == '-' && 873 suffix[1] == '-') { 874 /* 875 * payload ends immediately before 876 * the CRLF that introduced this 877 * delimiter. 878 */ 879 *payload_offset = 880 payload_start; 881 882 *payload_length = 883 delimiter_end - 884 delimiter_len - 885 payload_start; 886 887 if (*payload_length < 0) 888 return MULTIPART_MALFORMED; 889 890 return MULTIPART_OK; 891 } 892 if (suffix[0] == '\r' && 893 suffix[1] == '\n') { 894 int ret; 895 896 ret = next_part_is_file(fd, delimiter_end + 2, req); 897 898 if (ret == MULTIPART_OK) 899 return MULTIPART_MULTIPLE_FILES; 900 901 if (ret == MULTIPART_MISSING_FILENAME) 902 return MULTIPART_MALFORMED; 903 904 return ret; 905 } 906 /* 907 * This only looked like a boundary. 908 * It is ordinary payload data. 909 */ 910 match = 0; 911 continue; 912 } 913 } else { 914 /* 915 * If this byte can begin a new delimiter, 916 * retain it as the first match. 917 */ 918 if (c == (unsigned char) delimiter[0]) 919 match = 1; 920 else 921 match = 0; 922 } 923 } 924 925 pos += n; 926 remaining -= n; 927 } 928 929 /* 930 * Reaching the end of CONTENT_LENGTH without a valid 931 * closing boundary means the multipart body is malformed. 932 */ 933 return MULTIPART_MALFORMED; 934 } 935 936 int 937 multipart_extract_part(int fd, 938 const struct multipart_request * req, 939 struct multipart_part * part) 940 { 941 int ret; 942 943 if (req == NULL || part == NULL) 944 return MULTIPART_ERROR; 945 946 memset(part, 0, sizeof(*part)); 947 948 ret = extract_filename(fd, 949 req, 950 part->filename, 951 sizeof(part->filename)); 952 953 if (ret != MULTIPART_OK) 954 return ret; 955 956 ret = find_payload(fd, 957 req, 958 &part->offset, 959 &part->length); 960 961 if (ret != MULTIPART_OK) 962 return ret; 963 964 return MULTIPART_OK; 965 } 966 967 int 968 multipart_copy_part(int spool_fd, 969 const struct multipart_part * part, 970 int output_fd) 971 { 972 unsigned char buffer[8192]; 973 off_t remaining; 974 975 if (part == NULL) 976 return MULTIPART_ERROR; 977 978 if (part->offset < 0 || part->length < 0) 979 return MULTIPART_ERROR; 980 981 if (lseek(spool_fd, part->offset, SEEK_SET) == (off_t) - 1) 982 return MULTIPART_ERROR; 983 984 remaining = part->length; 985 986 while (remaining > 0) { 987 size_t want; 988 ssize_t n; 989 990 want = remaining > (off_t) sizeof(buffer) 991 ? sizeof(buffer) 992 : (size_t) remaining; 993 994 n = read(spool_fd, buffer, want); 995 996 if (n <= 0) 997 return MULTIPART_ERROR; 998 999 if (write_all(output_fd, buffer, (size_t) n) == -1) 1000 return MULTIPART_ERROR; 1001 1002 remaining -= n; 1003 } 1004 1005 return MULTIPART_OK; 1006 }