Home | History | Annotate | Line # | Download | only in isc
      1 /*	$NetBSD: picohttpparser.c,v 1.8 2026/08/29 14:55:18 christos Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase,
      5  *                         Shigeo Mitsunari
      6  *
      7  * SPDX-License-Identifier: MIT
      8  *
      9  * The software is licensed under either the MIT License (below) or the Perl
     10  * license.
     11  *
     12  * Permission is hereby granted, free of charge, to any person obtaining a copy
     13  * of this software and associated documentation files (the "Software"), to
     14  * deal in the Software without restriction, including without limitation the
     15  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
     16  * sell copies of the Software, and to permit persons to whom the Software is
     17  * furnished to do so, subject to the following conditions:
     18  *
     19  * The above copyright notice and this permission notice shall be included in
     20  * all copies or substantial portions of the Software.
     21  *
     22  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     23  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     24  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
     25  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     26  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
     27  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
     28  * IN THE SOFTWARE.
     29  */
     30 
     31 #include <assert.h>
     32 #include <stddef.h>
     33 #include <string.h>
     34 #ifdef __SSE4_2__
     35 #ifdef _MSC_VER
     36 #include <nmmintrin.h>
     37 #else
     38 #include <x86intrin.h>
     39 #endif
     40 #endif
     41 #include "picohttpparser.h"
     42 
     43 #if __GNUC__ >= 3
     44 #define likely(x)   __builtin_expect(!!(x), 1)
     45 #define unlikely(x) __builtin_expect(!!(x), 0)
     46 #else
     47 #define likely(x)   (x)
     48 #define unlikely(x) (x)
     49 #endif
     50 
     51 #ifdef _MSC_VER
     52 #define ALIGNED(n) _declspec(align(n))
     53 #else
     54 #define ALIGNED(n) __attribute__((aligned(n)))
     55 #endif
     56 
     57 #define IS_PRINTABLE_ASCII(c) ((unsigned char)(c) - 040u < 0137u)
     58 
     59 #define CHECK_EOF()           \
     60 	if (buf == buf_end) { \
     61 		*ret = -2;    \
     62 		return NULL;  \
     63 	}
     64 
     65 #define EXPECT_CHAR_NO_CHECK(ch) \
     66 	if (*buf++ != ch) {      \
     67 		*ret = -1;       \
     68 		return NULL;     \
     69 	}
     70 
     71 #define EXPECT_CHAR(ch) \
     72 	CHECK_EOF();    \
     73 	EXPECT_CHAR_NO_CHECK(ch);
     74 
     75 #define ADVANCE_TOKEN(tok, toklen)                                        \
     76 	do {                                                              \
     77 		const char *tok_start = buf;                              \
     78 		static const char ALIGNED(16)                             \
     79 			ranges2[16] = "\000\040\177\177";                 \
     80 		int found2;                                               \
     81 		buf = findchar_fast(buf, buf_end, ranges2, 4, &found2);   \
     82 		if (!found2) {                                            \
     83 			CHECK_EOF();                                      \
     84 		}                                                         \
     85 		while (1) {                                               \
     86 			if (*buf == ' ') {                                \
     87 				break;                                    \
     88 			} else if (unlikely(!IS_PRINTABLE_ASCII(*buf))) { \
     89 				if ((unsigned char)*buf < '\040' ||       \
     90 				    *buf == '\177')                       \
     91 				{                                         \
     92 					*ret = -1;                        \
     93 					return NULL;                      \
     94 				}                                         \
     95 			}                                                 \
     96 			++buf;                                            \
     97 			CHECK_EOF();                                      \
     98 		}                                                         \
     99 		tok = tok_start;                                          \
    100 		toklen = buf - tok_start;                                 \
    101 	} while (0)
    102 
    103 static const char *token_char_map =
    104 	"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
    105 	"\0\1\0\1\1\1\1\1\0\0\1\1\0\1\1\0\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0"
    106 	"\0\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\1\1"
    107 	"\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\1\0\1\0"
    108 	"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
    109 	"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
    110 	"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
    111 	"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
    112 
    113 static const char *
    114 findchar_fast(const char *buf, const char *buf_end, const char *ranges,
    115 	      size_t ranges_size, int *found) {
    116 	*found = 0;
    117 #if __SSE4_2__
    118 	if (likely(buf_end - buf >= 16)) {
    119 		__m128i ranges16 = _mm_loadu_si128((const __m128i *)ranges);
    120 
    121 		size_t left = (buf_end - buf) & ~15;
    122 		do {
    123 			__m128i b16 = _mm_loadu_si128((const __m128i *)buf);
    124 			int r = _mm_cmpestri(ranges16, ranges_size, b16, 16,
    125 					     _SIDD_LEAST_SIGNIFICANT |
    126 						     _SIDD_CMP_RANGES |
    127 						     _SIDD_UBYTE_OPS);
    128 			if (unlikely(r != 16)) {
    129 				buf += r;
    130 				*found = 1;
    131 				break;
    132 			}
    133 			buf += 16;
    134 			left -= 16;
    135 		} while (likely(left != 0));
    136 	}
    137 #else
    138 	/* suppress unused parameter warning */
    139 	(void)buf_end;
    140 	(void)ranges;
    141 	(void)ranges_size;
    142 #endif
    143 	return buf;
    144 }
    145 
    146 static const char *
    147 get_token_to_eol(const char *buf, const char *buf_end, const char **token,
    148 		 size_t *token_len, int *ret) {
    149 	const char *token_start = buf;
    150 
    151 #ifdef __SSE4_2__
    152 	static const char ALIGNED(16)
    153 		ranges1[16] = "\0\010"	 /* allow HT */
    154 			      "\012\037" /* allow SP and up to but not including
    155 					    DEL */
    156 			      "\177\177"; /* allow chars w. MSB set */
    157 	int found;
    158 	buf = findchar_fast(buf, buf_end, ranges1, 6, &found);
    159 	if (found) {
    160 		goto FOUND_CTL;
    161 	}
    162 #else
    163 	/* find non-printable char within the next 8 bytes, this is the hottest
    164 	 * code; manually inlined */
    165 	while (likely(buf_end - buf >= 8)) {
    166 #define DOIT()                                             \
    167 	do {                                               \
    168 		if (unlikely(!IS_PRINTABLE_ASCII(*buf))) { \
    169 			goto NonPrintable;                 \
    170 		}                                          \
    171 		++buf;                                     \
    172 	} while (0)
    173 		DOIT();
    174 		DOIT();
    175 		DOIT();
    176 		DOIT();
    177 		DOIT();
    178 		DOIT();
    179 		DOIT();
    180 		DOIT();
    181 #undef DOIT
    182 		continue;
    183 	NonPrintable:
    184 		if ((likely((unsigned char)*buf < '\040') &&
    185 		     likely(*buf != '\011')) ||
    186 		    unlikely(*buf == '\177'))
    187 		{
    188 			goto FOUND_CTL;
    189 		}
    190 		++buf;
    191 	}
    192 #endif
    193 	for (;; ++buf) {
    194 		CHECK_EOF();
    195 		if (unlikely(!IS_PRINTABLE_ASCII(*buf))) {
    196 			if ((likely((unsigned char)*buf < '\040') &&
    197 			     likely(*buf != '\011')) ||
    198 			    unlikely(*buf == '\177'))
    199 			{
    200 				goto FOUND_CTL;
    201 			}
    202 		}
    203 	}
    204 FOUND_CTL:
    205 	if (likely(*buf == '\015')) {
    206 		++buf;
    207 		EXPECT_CHAR('\012');
    208 		*token_len = buf - 2 - token_start;
    209 	} else if (*buf == '\012') {
    210 		*token_len = buf - token_start;
    211 		++buf;
    212 	} else {
    213 		*ret = -1;
    214 		return NULL;
    215 	}
    216 	*token = token_start;
    217 
    218 	return buf;
    219 }
    220 
    221 static const char *
    222 is_complete(const char *buf, const char *buf_end, size_t last_len, int *ret) {
    223 	int ret_cnt = 0;
    224 	buf = last_len < 3 ? buf : buf + last_len - 3;
    225 
    226 	while (1) {
    227 		CHECK_EOF();
    228 		if (*buf == '\015') {
    229 			++buf;
    230 			CHECK_EOF();
    231 			EXPECT_CHAR('\012');
    232 			++ret_cnt;
    233 		} else if (*buf == '\012') {
    234 			++buf;
    235 			++ret_cnt;
    236 		} else {
    237 			++buf;
    238 			ret_cnt = 0;
    239 		}
    240 		if (ret_cnt == 2) {
    241 			return buf;
    242 		}
    243 	}
    244 
    245 	*ret = -2;
    246 	return NULL;
    247 }
    248 
    249 #define PARSE_INT(valp_, mul_)          \
    250 	if (*buf < '0' || '9' < *buf) { \
    251 		buf++;                  \
    252 		*ret = -1;              \
    253 		return NULL;            \
    254 	}                               \
    255 	*(valp_) = (mul_) * (*buf++ - '0');
    256 
    257 #define PARSE_INT_3(valp_)            \
    258 	do {                          \
    259 		int res_ = 0;         \
    260 		PARSE_INT(&res_, 100) \
    261 		*valp_ = res_;        \
    262 		PARSE_INT(&res_, 10)  \
    263 		*valp_ += res_;       \
    264 		PARSE_INT(&res_, 1)   \
    265 		*valp_ += res_;       \
    266 	} while (0)
    267 
    268 /* returned pointer is always within [buf, buf_end), or null */
    269 static const char *
    270 parse_token(const char *buf, const char *buf_end, const char **token,
    271 	    size_t *token_len, char next_char, int *ret) {
    272 	/* We use pcmpestri to detect non-token characters. This instruction can
    273 	 * take no more than eight character ranges (8*2*8=128 bits that is the
    274 	 * size of a SSE register). Due to this restriction, characters `|` and
    275 	 * `~` are handled in the slow loop. */
    276 	static const char ALIGNED(16) ranges[] = "\x00 " /* control chars and up
    277 							    to SP */
    278 						 "\"\""	 /* 0x22 */
    279 						 "()"	 /* 0x28,0x29 */
    280 						 ",,"	 /* 0x2c */
    281 						 "//"	 /* 0x2f */
    282 						 ":@"	 /* 0x3a-0x40 */
    283 						 "[]"	 /* 0x5b-0x5d */
    284 						 "{\xff"; /* 0x7b-0xff */
    285 	const char *buf_start = buf;
    286 	int found;
    287 	buf = findchar_fast(buf, buf_end, ranges, sizeof(ranges) - 1, &found);
    288 	if (!found) {
    289 		CHECK_EOF();
    290 	}
    291 	while (1) {
    292 		if (*buf == next_char) {
    293 			break;
    294 		} else if (!token_char_map[(unsigned char)*buf]) {
    295 			*ret = -1;
    296 			return NULL;
    297 		}
    298 		++buf;
    299 		CHECK_EOF();
    300 	}
    301 	*token = buf_start;
    302 	*token_len = buf - buf_start;
    303 	return buf;
    304 }
    305 
    306 /* returned pointer is always within [buf, buf_end), or null */
    307 static const char *
    308 parse_http_version(const char *buf, const char *buf_end, int *minor_version,
    309 		   int *ret) {
    310 	/* we want at least [HTTP/1.<two chars>] to try to parse */
    311 	if (buf_end - buf < 9) {
    312 		*ret = -2;
    313 		return NULL;
    314 	}
    315 	EXPECT_CHAR_NO_CHECK('H');
    316 	EXPECT_CHAR_NO_CHECK('T');
    317 	EXPECT_CHAR_NO_CHECK('T');
    318 	EXPECT_CHAR_NO_CHECK('P');
    319 	EXPECT_CHAR_NO_CHECK('/');
    320 	EXPECT_CHAR_NO_CHECK('1');
    321 	EXPECT_CHAR_NO_CHECK('.');
    322 	PARSE_INT(minor_version, 1);
    323 	return buf;
    324 }
    325 
    326 static const char *
    327 parse_headers(const char *buf, const char *buf_end, struct phr_header *headers,
    328 	      size_t *num_headers, size_t max_headers, int *ret) {
    329 	for (;; ++*num_headers) {
    330 		CHECK_EOF();
    331 		if (*buf == '\015') {
    332 			++buf;
    333 			EXPECT_CHAR('\012');
    334 			break;
    335 		} else if (*buf == '\012') {
    336 			++buf;
    337 			break;
    338 		}
    339 		if (*num_headers == max_headers) {
    340 			*ret = -1;
    341 			return NULL;
    342 		}
    343 		if (!(*num_headers != 0 && (*buf == ' ' || *buf == '\t'))) {
    344 			/* parsing name, but do not discard SP before colon, see
    345 			 * http://www.mozilla.org/security/announce/2006/mfsa2006-33.html
    346 			 */
    347 			if ((buf = parse_token(buf, buf_end,
    348 					       &headers[*num_headers].name,
    349 					       &headers[*num_headers].name_len,
    350 					       ':', ret)) == NULL)
    351 			{
    352 				return NULL;
    353 			}
    354 			if (headers[*num_headers].name_len == 0) {
    355 				*ret = -1;
    356 				return NULL;
    357 			}
    358 			++buf;
    359 			for (;; ++buf) {
    360 				CHECK_EOF();
    361 				if (!(*buf == ' ' || *buf == '\t')) {
    362 					break;
    363 				}
    364 			}
    365 		} else {
    366 			headers[*num_headers].name = NULL;
    367 			headers[*num_headers].name_len = 0;
    368 		}
    369 		const char *value;
    370 		size_t value_len;
    371 		if ((buf = get_token_to_eol(buf, buf_end, &value, &value_len,
    372 					    ret)) == NULL)
    373 		{
    374 			return NULL;
    375 		}
    376 		/* remove trailing SPs and HTABs */
    377 		const char *value_end = value + value_len;
    378 		for (; value_end != value; --value_end) {
    379 			const char c = *(value_end - 1);
    380 			if (!(c == ' ' || c == '\t')) {
    381 				break;
    382 			}
    383 		}
    384 		headers[*num_headers].value = value;
    385 		headers[*num_headers].value_len = value_end - value;
    386 	}
    387 	return buf;
    388 }
    389 
    390 static const char *
    391 parse_request(const char *buf, const char *buf_end, const char **method,
    392 	      size_t *method_len, const char **path, size_t *path_len,
    393 	      int *minor_version, struct phr_header *headers,
    394 	      size_t *num_headers, size_t max_headers, int *ret) {
    395 	/* skip first empty line (some clients add CRLF after POST content) */
    396 	CHECK_EOF();
    397 	if (*buf == '\015') {
    398 		++buf;
    399 		EXPECT_CHAR('\012');
    400 	} else if (*buf == '\012') {
    401 		++buf;
    402 	}
    403 
    404 	/* parse request line */
    405 	if ((buf = parse_token(buf, buf_end, method, method_len, ' ', ret)) ==
    406 	    NULL)
    407 	{
    408 		return NULL;
    409 	}
    410 	do {
    411 		++buf;
    412 		CHECK_EOF();
    413 	} while (*buf == ' ');
    414 	ADVANCE_TOKEN(*path, *path_len);
    415 	do {
    416 		++buf;
    417 		CHECK_EOF();
    418 	} while (*buf == ' ');
    419 	if (*method_len == 0 || *path_len == 0) {
    420 		*ret = -1;
    421 		return NULL;
    422 	}
    423 	if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) ==
    424 	    NULL)
    425 	{
    426 		return NULL;
    427 	}
    428 	if (*buf == '\015') {
    429 		++buf;
    430 		EXPECT_CHAR('\012');
    431 	} else if (*buf == '\012') {
    432 		++buf;
    433 	} else {
    434 		*ret = -1;
    435 		return NULL;
    436 	}
    437 
    438 	return parse_headers(buf, buf_end, headers, num_headers, max_headers,
    439 			     ret);
    440 }
    441 
    442 int
    443 phr_parse_request(const char *buf_start, size_t len, const char **method,
    444 		  size_t *method_len, const char **path, size_t *path_len,
    445 		  int *minor_version, struct phr_header *headers,
    446 		  size_t *num_headers, size_t last_len) {
    447 	const char *buf = buf_start, *buf_end = buf_start + len;
    448 	size_t max_headers = *num_headers;
    449 	int r = -1;
    450 
    451 	*method = NULL;
    452 	*method_len = 0;
    453 	*path = NULL;
    454 	*path_len = 0;
    455 	*minor_version = -1;
    456 	*num_headers = 0;
    457 
    458 	/* if last_len != 0, check if the request is complete (a fast
    459 	   countermeasure againt slowloris */
    460 	if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) {
    461 		return r;
    462 	}
    463 
    464 	if ((buf = parse_request(buf, buf_end, method, method_len, path,
    465 				 path_len, minor_version, headers, num_headers,
    466 				 max_headers, &r)) == NULL)
    467 	{
    468 		return r;
    469 	}
    470 
    471 	return (int)(buf - buf_start);
    472 }
    473 
    474 static const char *
    475 parse_response(const char *buf, const char *buf_end, int *minor_version,
    476 	       int *status, const char **msg, size_t *msg_len,
    477 	       struct phr_header *headers, size_t *num_headers,
    478 	       size_t max_headers, int *ret) {
    479 	/* parse "HTTP/1.x" */
    480 	if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) ==
    481 	    NULL)
    482 	{
    483 		return NULL;
    484 	}
    485 	/* skip space */
    486 	if (*buf != ' ') {
    487 		*ret = -1;
    488 		return NULL;
    489 	}
    490 	do {
    491 		++buf;
    492 		CHECK_EOF();
    493 	} while (*buf == ' ');
    494 	/* parse status code, we want at least [:digit:][:digit:][:digit:]<other
    495 	 * char> to try to parse */
    496 	if (buf_end - buf < 4) {
    497 		*ret = -2;
    498 		return NULL;
    499 	}
    500 	PARSE_INT_3(status);
    501 
    502 	/* get message including preceding space */
    503 	if ((buf = get_token_to_eol(buf, buf_end, msg, msg_len, ret)) == NULL) {
    504 		return NULL;
    505 	}
    506 	if (*msg_len == 0) {
    507 		/* ok */
    508 	} else if (**msg == ' ') {
    509 		/* Remove preceding space. Successful return from
    510 		 * `get_token_to_eol` guarantees that we would hit something
    511 		 * other than SP before running past the end of the given
    512 		 * buffer. */
    513 		do {
    514 			++*msg;
    515 			--*msg_len;
    516 		} while (**msg == ' ');
    517 	} else {
    518 		/* garbage found after status code */
    519 		*ret = -1;
    520 		return NULL;
    521 	}
    522 
    523 	return parse_headers(buf, buf_end, headers, num_headers, max_headers,
    524 			     ret);
    525 }
    526 
    527 int
    528 phr_parse_response(const char *buf_start, size_t len, int *minor_version,
    529 		   int *status, const char **msg, size_t *msg_len,
    530 		   struct phr_header *headers, size_t *num_headers,
    531 		   size_t last_len) {
    532 	const char *buf = buf_start, *buf_end = buf + len;
    533 	size_t max_headers = *num_headers;
    534 	int r;
    535 
    536 	*minor_version = -1;
    537 	*status = 0;
    538 	*msg = NULL;
    539 	*msg_len = 0;
    540 	*num_headers = 0;
    541 
    542 	/* if last_len != 0, check if the response is complete (a fast
    543 	   countermeasure against slowloris */
    544 	if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) {
    545 		return r;
    546 	}
    547 
    548 	if ((buf = parse_response(buf, buf_end, minor_version, status, msg,
    549 				  msg_len, headers, num_headers, max_headers,
    550 				  &r)) == NULL)
    551 	{
    552 		return r;
    553 	}
    554 
    555 	return (int)(buf - buf_start);
    556 }
    557 
    558 int
    559 phr_parse_headers(const char *buf_start, size_t len, struct phr_header *headers,
    560 		  size_t *num_headers, size_t last_len) {
    561 	const char *buf = buf_start, *buf_end = buf + len;
    562 	size_t max_headers = *num_headers;
    563 	int r;
    564 
    565 	*num_headers = 0;
    566 
    567 	/* if last_len != 0, check if the response is complete (a fast
    568 	   countermeasure against slowloris */
    569 	if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) {
    570 		return r;
    571 	}
    572 
    573 	if ((buf = parse_headers(buf, buf_end, headers, num_headers,
    574 				 max_headers, &r)) == NULL)
    575 	{
    576 		return r;
    577 	}
    578 
    579 	return (int)(buf - buf_start);
    580 }
    581 
    582 enum {
    583 	CHUNKED_IN_CHUNK_SIZE,
    584 	CHUNKED_IN_CHUNK_EXT,
    585 	CHUNKED_IN_CHUNK_HEADER_EXPECT_LF,
    586 	CHUNKED_IN_CHUNK_DATA,
    587 	CHUNKED_IN_CHUNK_DATA_EXPECT_CR,
    588 	CHUNKED_IN_CHUNK_DATA_EXPECT_LF,
    589 	CHUNKED_IN_TRAILERS_LINE_HEAD,
    590 	CHUNKED_IN_TRAILERS_LINE_MIDDLE
    591 };
    592 
    593 static int
    594 decode_hex(int ch) {
    595 	if ('0' <= ch && ch <= '9') {
    596 		return ch - '0';
    597 	} else if ('A' <= ch && ch <= 'F') {
    598 		return ch - 'A' + 0xa;
    599 	} else if ('a' <= ch && ch <= 'f') {
    600 		return ch - 'a' + 0xa;
    601 	} else {
    602 		return -1;
    603 	}
    604 }
    605 
    606 ssize_t
    607 phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf,
    608 		   size_t *_bufsz) {
    609 	size_t dst = 0, src = 0, bufsz = *_bufsz;
    610 	ssize_t ret = -2; /* incomplete */
    611 
    612 	decoder->_total_read += bufsz;
    613 
    614 	while (1) {
    615 		switch (decoder->_state) {
    616 		case CHUNKED_IN_CHUNK_SIZE:
    617 			for (;; ++src) {
    618 				int v;
    619 				if (src == bufsz) {
    620 					goto Exit;
    621 				}
    622 				if ((v = decode_hex(buf[src])) == -1) {
    623 					if (decoder->_hex_count == 0) {
    624 						ret = -1;
    625 						goto Exit;
    626 					}
    627 					/* the only characters that may appear
    628 					 * after the chunk size are BWS,
    629 					 * semicolon, or CRLF */
    630 					switch (buf[src]) {
    631 					case ' ':
    632 					case '\011':
    633 					case ';':
    634 					case '\012':
    635 					case '\015':
    636 						break;
    637 					default:
    638 						ret = -1;
    639 						goto Exit;
    640 					}
    641 					break;
    642 				}
    643 				if (decoder->_hex_count == sizeof(size_t) * 2) {
    644 					ret = -1;
    645 					goto Exit;
    646 				}
    647 				decoder->bytes_left_in_chunk =
    648 					decoder->bytes_left_in_chunk * 16 + v;
    649 				++decoder->_hex_count;
    650 			}
    651 			decoder->_hex_count = 0;
    652 			decoder->_state = CHUNKED_IN_CHUNK_EXT;
    653 		/* fall through */
    654 		case CHUNKED_IN_CHUNK_EXT:
    655 			/* RFC 7230 A.2 "Line folding in chunk extensions is
    656 			 * disallowed" */
    657 			for (;; ++src) {
    658 				if (src == bufsz) {
    659 					goto Exit;
    660 				}
    661 				if (buf[src] == '\015') {
    662 					break;
    663 				} else if (buf[src] == '\012') {
    664 					ret = -1;
    665 					goto Exit;
    666 				}
    667 			}
    668 			++src;
    669 			decoder->_state = CHUNKED_IN_CHUNK_HEADER_EXPECT_LF;
    670 		/* fall through */
    671 		case CHUNKED_IN_CHUNK_HEADER_EXPECT_LF:
    672 			if (src == bufsz) {
    673 				goto Exit;
    674 			}
    675 			if (buf[src] != '\012') {
    676 				ret = -1;
    677 				goto Exit;
    678 			}
    679 			++src;
    680 			if (decoder->bytes_left_in_chunk == 0) {
    681 				if (decoder->consume_trailer) {
    682 					decoder->_state =
    683 						CHUNKED_IN_TRAILERS_LINE_HEAD;
    684 					break;
    685 				} else {
    686 					goto Complete;
    687 				}
    688 			}
    689 			decoder->_state = CHUNKED_IN_CHUNK_DATA;
    690 		/* fall through */
    691 		case CHUNKED_IN_CHUNK_DATA: {
    692 			size_t avail = bufsz - src;
    693 			if (avail < decoder->bytes_left_in_chunk) {
    694 				if (dst != src) {
    695 					memmove(buf + dst, buf + src, avail);
    696 				}
    697 				src += avail;
    698 				dst += avail;
    699 				decoder->bytes_left_in_chunk -= avail;
    700 				goto Exit;
    701 			}
    702 			if (dst != src) {
    703 				memmove(buf + dst, buf + src,
    704 					decoder->bytes_left_in_chunk);
    705 			}
    706 			src += decoder->bytes_left_in_chunk;
    707 			dst += decoder->bytes_left_in_chunk;
    708 			decoder->bytes_left_in_chunk = 0;
    709 			decoder->_state = CHUNKED_IN_CHUNK_DATA_EXPECT_CR;
    710 		}
    711 		/* fall through */
    712 		case CHUNKED_IN_CHUNK_DATA_EXPECT_CR:
    713 			if (src == bufsz) {
    714 				goto Exit;
    715 			}
    716 			if (buf[src] != '\015') {
    717 				ret = -1;
    718 				goto Exit;
    719 			}
    720 			++src;
    721 			decoder->_state = CHUNKED_IN_CHUNK_DATA_EXPECT_LF;
    722 		/* fall through */
    723 		case CHUNKED_IN_CHUNK_DATA_EXPECT_LF:
    724 			if (src == bufsz) {
    725 				goto Exit;
    726 			}
    727 			if (buf[src] != '\012') {
    728 				ret = -1;
    729 				goto Exit;
    730 			}
    731 			++src;
    732 			decoder->_state = CHUNKED_IN_CHUNK_SIZE;
    733 			break;
    734 		case CHUNKED_IN_TRAILERS_LINE_HEAD:
    735 			for (;; ++src) {
    736 				if (src == bufsz) {
    737 					goto Exit;
    738 				}
    739 				if (buf[src] != '\015') {
    740 					break;
    741 				}
    742 			}
    743 			if (buf[src++] == '\012') {
    744 				goto Complete;
    745 			}
    746 			decoder->_state = CHUNKED_IN_TRAILERS_LINE_MIDDLE;
    747 		/* fall through */
    748 		case CHUNKED_IN_TRAILERS_LINE_MIDDLE:
    749 			for (;; ++src) {
    750 				if (src == bufsz) {
    751 					goto Exit;
    752 				}
    753 				if (buf[src] == '\012') {
    754 					break;
    755 				}
    756 			}
    757 			++src;
    758 			decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD;
    759 			break;
    760 		default:
    761 			assert(!"decoder is corrupt");
    762 		}
    763 	}
    764 
    765 Complete:
    766 	ret = bufsz - src;
    767 Exit:
    768 	if (dst != src) {
    769 		memmove(buf + dst, buf + src, bufsz - src);
    770 	}
    771 	*_bufsz = dst;
    772 	/* if incomplete but the overhead of the chunked encoding is >=100KB and
    773 	 * >80%, signal an error */
    774 	if (ret == -2) {
    775 		decoder->_total_overhead += bufsz - dst;
    776 		if (decoder->_total_overhead >= 100 * 1024 &&
    777 		    decoder->_total_read - decoder->_total_overhead <
    778 			    decoder->_total_read / 4)
    779 		{
    780 			ret = -1;
    781 		}
    782 	}
    783 	return ret;
    784 }
    785 
    786 int
    787 phr_decode_chunked_is_in_data(struct phr_chunked_decoder *decoder) {
    788 	return decoder->_state == CHUNKED_IN_CHUNK_DATA;
    789 }
    790 
    791 #undef CHECK_EOF
    792 #undef EXPECT_CHAR
    793 #undef ADVANCE_TOKEN
    794