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 (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 }