xref: /netbsd-src/external/mpl/bind/dist/lib/isc/picohttpparser.c (revision bcda20f65a8566e103791ec395f7f499ef322704)
1 /*	$NetBSD: picohttpparser.c,v 1.4 2025/01/26 16:25:38 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 #else
162 	/* find non-printable char within the next 8 bytes, this is the hottest
163 	 * code; manually inlined */
164 	while (likely(buf_end - buf >= 8)) {
165 #define DOIT()                                           \
166 	do {                                             \
167 		if (unlikely(!IS_PRINTABLE_ASCII(*buf))) \
168 			goto NonPrintable;               \
169 		++buf;                                   \
170 	} while (0)
171 		DOIT();
172 		DOIT();
173 		DOIT();
174 		DOIT();
175 		DOIT();
176 		DOIT();
177 		DOIT();
178 		DOIT();
179 #undef DOIT
180 		continue;
181 	NonPrintable:
182 		if ((likely((unsigned char)*buf < '\040') &&
183 		     likely(*buf != '\011')) ||
184 		    unlikely(*buf == '\177'))
185 		{
186 			goto FOUND_CTL;
187 		}
188 		++buf;
189 	}
190 #endif
191 	for (;; ++buf) {
192 		CHECK_EOF();
193 		if (unlikely(!IS_PRINTABLE_ASCII(*buf))) {
194 			if ((likely((unsigned char)*buf < '\040') &&
195 			     likely(*buf != '\011')) ||
196 			    unlikely(*buf == '\177'))
197 			{
198 				goto FOUND_CTL;
199 			}
200 		}
201 	}
202 FOUND_CTL:
203 	if (likely(*buf == '\015')) {
204 		++buf;
205 		EXPECT_CHAR('\012');
206 		*token_len = buf - 2 - token_start;
207 	} else if (*buf == '\012') {
208 		*token_len = buf - token_start;
209 		++buf;
210 	} else {
211 		*ret = -1;
212 		return NULL;
213 	}
214 	*token = token_start;
215 
216 	return buf;
217 }
218 
219 static const char *
220 is_complete(const char *buf, const char *buf_end, size_t last_len, int *ret) {
221 	int ret_cnt = 0;
222 	buf = last_len < 3 ? buf : buf + last_len - 3;
223 
224 	while (1) {
225 		CHECK_EOF();
226 		if (*buf == '\015') {
227 			++buf;
228 			CHECK_EOF();
229 			EXPECT_CHAR('\012');
230 			++ret_cnt;
231 		} else if (*buf == '\012') {
232 			++buf;
233 			++ret_cnt;
234 		} else {
235 			++buf;
236 			ret_cnt = 0;
237 		}
238 		if (ret_cnt == 2) {
239 			return buf;
240 		}
241 	}
242 
243 	*ret = -2;
244 	return NULL;
245 }
246 
247 #define PARSE_INT(valp_, mul_)          \
248 	if (*buf < '0' || '9' < *buf) { \
249 		buf++;                  \
250 		*ret = -1;              \
251 		return (NULL);          \
252 	}                               \
253 	*(valp_) = (mul_) * (*buf++ - '0');
254 
255 #define PARSE_INT_3(valp_)            \
256 	do {                          \
257 		int res_ = 0;         \
258 		PARSE_INT(&res_, 100) \
259 		*valp_ = res_;        \
260 		PARSE_INT(&res_, 10)  \
261 		*valp_ += res_;       \
262 		PARSE_INT(&res_, 1)   \
263 		*valp_ += res_;       \
264 	} while (0)
265 
266 /* returned pointer is always within [buf, buf_end), or null */
267 static const char *
268 parse_token(const char *buf, const char *buf_end, const char **token,
269 	    size_t *token_len, char next_char, int *ret) {
270 	/* We use pcmpestri to detect non-token characters. This instruction can
271 	 * take no more than eight character ranges (8*2*8=128 bits that is the
272 	 * size of a SSE register). Due to this restriction, characters `|` and
273 	 * `~` are handled in the slow loop. */
274 	static const char ALIGNED(16) ranges[] = "\x00 " /* control chars and up
275 							    to SP */
276 						 "\"\""	 /* 0x22 */
277 						 "()"	 /* 0x28,0x29 */
278 						 ",,"	 /* 0x2c */
279 						 "//"	 /* 0x2f */
280 						 ":@"	 /* 0x3a-0x40 */
281 						 "[]"	 /* 0x5b-0x5d */
282 						 "{\xff"; /* 0x7b-0xff */
283 	const char *buf_start = buf;
284 	int found;
285 	buf = findchar_fast(buf, buf_end, ranges, sizeof(ranges) - 1, &found);
286 	if (!found) {
287 		CHECK_EOF();
288 	}
289 	while (1) {
290 		if (*buf == next_char) {
291 			break;
292 		} else if (!token_char_map[(unsigned char)*buf]) {
293 			*ret = -1;
294 			return NULL;
295 		}
296 		++buf;
297 		CHECK_EOF();
298 	}
299 	*token = buf_start;
300 	*token_len = buf - buf_start;
301 	return buf;
302 }
303 
304 /* returned pointer is always within [buf, buf_end), or null */
305 static const char *
306 parse_http_version(const char *buf, const char *buf_end, int *minor_version,
307 		   int *ret) {
308 	/* we want at least [HTTP/1.<two chars>] to try to parse */
309 	if (buf_end - buf < 9) {
310 		*ret = -2;
311 		return NULL;
312 	}
313 	EXPECT_CHAR_NO_CHECK('H');
314 	EXPECT_CHAR_NO_CHECK('T');
315 	EXPECT_CHAR_NO_CHECK('T');
316 	EXPECT_CHAR_NO_CHECK('P');
317 	EXPECT_CHAR_NO_CHECK('/');
318 	EXPECT_CHAR_NO_CHECK('1');
319 	EXPECT_CHAR_NO_CHECK('.');
320 	PARSE_INT(minor_version, 1);
321 	return buf;
322 }
323 
324 static const char *
325 parse_headers(const char *buf, const char *buf_end, struct phr_header *headers,
326 	      size_t *num_headers, size_t max_headers, int *ret) {
327 	for (;; ++*num_headers) {
328 		CHECK_EOF();
329 		if (*buf == '\015') {
330 			++buf;
331 			EXPECT_CHAR('\012');
332 			break;
333 		} else if (*buf == '\012') {
334 			++buf;
335 			break;
336 		}
337 		if (*num_headers == max_headers) {
338 			*ret = -1;
339 			return NULL;
340 		}
341 		if (!(*num_headers != 0 && (*buf == ' ' || *buf == '\t'))) {
342 			/* parsing name, but do not discard SP before colon, see
343 			 * http://www.mozilla.org/security/announce/2006/mfsa2006-33.html
344 			 */
345 			if ((buf = parse_token(buf, buf_end,
346 					       &headers[*num_headers].name,
347 					       &headers[*num_headers].name_len,
348 					       ':', ret)) == NULL)
349 			{
350 				return NULL;
351 			}
352 			if (headers[*num_headers].name_len == 0) {
353 				*ret = -1;
354 				return NULL;
355 			}
356 			++buf;
357 			for (;; ++buf) {
358 				CHECK_EOF();
359 				if (!(*buf == ' ' || *buf == '\t')) {
360 					break;
361 				}
362 			}
363 		} else {
364 			headers[*num_headers].name = NULL;
365 			headers[*num_headers].name_len = 0;
366 		}
367 		const char *value;
368 		size_t value_len;
369 		if ((buf = get_token_to_eol(buf, buf_end, &value, &value_len,
370 					    ret)) == NULL)
371 		{
372 			return NULL;
373 		}
374 		/* remove trailing SPs and HTABs */
375 		const char *value_end = value + value_len;
376 		for (; value_end != value; --value_end) {
377 			const char c = *(value_end - 1);
378 			if (!(c == ' ' || c == '\t')) {
379 				break;
380 			}
381 		}
382 		headers[*num_headers].value = value;
383 		headers[*num_headers].value_len = value_end - value;
384 	}
385 	return buf;
386 }
387 
388 static const char *
389 parse_request(const char *buf, const char *buf_end, const char **method,
390 	      size_t *method_len, const char **path, size_t *path_len,
391 	      int *minor_version, struct phr_header *headers,
392 	      size_t *num_headers, size_t max_headers, int *ret) {
393 	/* skip first empty line (some clients add CRLF after POST content) */
394 	CHECK_EOF();
395 	if (*buf == '\015') {
396 		++buf;
397 		EXPECT_CHAR('\012');
398 	} else if (*buf == '\012') {
399 		++buf;
400 	}
401 
402 	/* parse request line */
403 	if ((buf = parse_token(buf, buf_end, method, method_len, ' ', ret)) ==
404 	    NULL)
405 	{
406 		return NULL;
407 	}
408 	do {
409 		++buf;
410 		CHECK_EOF();
411 	} while (*buf == ' ');
412 	ADVANCE_TOKEN(*path, *path_len);
413 	do {
414 		++buf;
415 		CHECK_EOF();
416 	} while (*buf == ' ');
417 	if (*method_len == 0 || *path_len == 0) {
418 		*ret = -1;
419 		return NULL;
420 	}
421 	if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) ==
422 	    NULL)
423 	{
424 		return NULL;
425 	}
426 	if (*buf == '\015') {
427 		++buf;
428 		EXPECT_CHAR('\012');
429 	} else if (*buf == '\012') {
430 		++buf;
431 	} else {
432 		*ret = -1;
433 		return NULL;
434 	}
435 
436 	return parse_headers(buf, buf_end, headers, num_headers, max_headers,
437 			     ret);
438 }
439 
440 int
441 phr_parse_request(const char *buf_start, size_t len, const char **method,
442 		  size_t *method_len, const char **path, size_t *path_len,
443 		  int *minor_version, struct phr_header *headers,
444 		  size_t *num_headers, size_t last_len) {
445 	const char *buf = buf_start, *buf_end = buf_start + len;
446 	size_t max_headers = *num_headers;
447 	int r = -1;
448 
449 	*method = NULL;
450 	*method_len = 0;
451 	*path = NULL;
452 	*path_len = 0;
453 	*minor_version = -1;
454 	*num_headers = 0;
455 
456 	/* if last_len != 0, check if the request is complete (a fast
457 	   countermeasure againt slowloris */
458 	if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) {
459 		return r;
460 	}
461 
462 	if ((buf = parse_request(buf, buf_end, method, method_len, path,
463 				 path_len, minor_version, headers, num_headers,
464 				 max_headers, &r)) == NULL)
465 	{
466 		return r;
467 	}
468 
469 	return (int)(buf - buf_start);
470 }
471 
472 static const char *
473 parse_response(const char *buf, const char *buf_end, int *minor_version,
474 	       int *status, const char **msg, size_t *msg_len,
475 	       struct phr_header *headers, size_t *num_headers,
476 	       size_t max_headers, int *ret) {
477 	/* parse "HTTP/1.x" */
478 	if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) ==
479 	    NULL)
480 	{
481 		return NULL;
482 	}
483 	/* skip space */
484 	if (*buf != ' ') {
485 		*ret = -1;
486 		return NULL;
487 	}
488 	do {
489 		++buf;
490 		CHECK_EOF();
491 	} while (*buf == ' ');
492 	/* parse status code, we want at least [:digit:][:digit:][:digit:]<other
493 	 * char> to try to parse */
494 	if (buf_end - buf < 4) {
495 		*ret = -2;
496 		return NULL;
497 	}
498 	PARSE_INT_3(status);
499 
500 	/* get message including preceding space */
501 	if ((buf = get_token_to_eol(buf, buf_end, msg, msg_len, ret)) == NULL) {
502 		return NULL;
503 	}
504 	if (*msg_len == 0) {
505 		/* ok */
506 	} else if (**msg == ' ') {
507 		/* Remove preceding space. Successful return from
508 		 * `get_token_to_eol` guarantees that we would hit something
509 		 * other than SP before running past the end of the given
510 		 * buffer. */
511 		do {
512 			++*msg;
513 			--*msg_len;
514 		} while (**msg == ' ');
515 	} else {
516 		/* garbage found after status code */
517 		*ret = -1;
518 		return NULL;
519 	}
520 
521 	return parse_headers(buf, buf_end, headers, num_headers, max_headers,
522 			     ret);
523 }
524 
525 int
526 phr_parse_response(const char *buf_start, size_t len, int *minor_version,
527 		   int *status, const char **msg, size_t *msg_len,
528 		   struct phr_header *headers, size_t *num_headers,
529 		   size_t last_len) {
530 	const char *buf = buf_start, *buf_end = buf + len;
531 	size_t max_headers = *num_headers;
532 	int r;
533 
534 	*minor_version = -1;
535 	*status = 0;
536 	*msg = NULL;
537 	*msg_len = 0;
538 	*num_headers = 0;
539 
540 	/* if last_len != 0, check if the response is complete (a fast
541 	   countermeasure against slowloris */
542 	if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) {
543 		return r;
544 	}
545 
546 	if ((buf = parse_response(buf, buf_end, minor_version, status, msg,
547 				  msg_len, headers, num_headers, max_headers,
548 				  &r)) == NULL)
549 	{
550 		return r;
551 	}
552 
553 	return (int)(buf - buf_start);
554 }
555 
556 int
557 phr_parse_headers(const char *buf_start, size_t len, struct phr_header *headers,
558 		  size_t *num_headers, size_t last_len) {
559 	const char *buf = buf_start, *buf_end = buf + len;
560 	size_t max_headers = *num_headers;
561 	int r;
562 
563 	*num_headers = 0;
564 
565 	/* if last_len != 0, check if the response is complete (a fast
566 	   countermeasure against slowloris */
567 	if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) {
568 		return r;
569 	}
570 
571 	if ((buf = parse_headers(buf, buf_end, headers, num_headers,
572 				 max_headers, &r)) == NULL)
573 	{
574 		return r;
575 	}
576 
577 	return (int)(buf - buf_start);
578 }
579 
580 enum {
581 	CHUNKED_IN_CHUNK_SIZE,
582 	CHUNKED_IN_CHUNK_EXT,
583 	CHUNKED_IN_CHUNK_DATA,
584 	CHUNKED_IN_CHUNK_CRLF,
585 	CHUNKED_IN_TRAILERS_LINE_HEAD,
586 	CHUNKED_IN_TRAILERS_LINE_MIDDLE
587 };
588 
589 static int
590 decode_hex(int ch) {
591 	if ('0' <= ch && ch <= '9') {
592 		return ch - '0';
593 	} else if ('A' <= ch && ch <= 'F') {
594 		return ch - 'A' + 0xa;
595 	} else if ('a' <= ch && ch <= 'f') {
596 		return ch - 'a' + 0xa;
597 	} else {
598 		return -1;
599 	}
600 }
601 
602 ssize_t
603 phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf,
604 		   size_t *_bufsz) {
605 	size_t dst = 0, src = 0, bufsz = *_bufsz;
606 	ssize_t ret = -2; /* incomplete */
607 
608 	while (1) {
609 		switch (decoder->_state) {
610 		case CHUNKED_IN_CHUNK_SIZE:
611 			for (;; ++src) {
612 				int v;
613 				if (src == bufsz) {
614 					goto Exit;
615 				}
616 				if ((v = decode_hex(buf[src])) == -1) {
617 					if (decoder->_hex_count == 0) {
618 						ret = -1;
619 						goto Exit;
620 					}
621 					break;
622 				}
623 				if (decoder->_hex_count == sizeof(size_t) * 2) {
624 					ret = -1;
625 					goto Exit;
626 				}
627 				decoder->bytes_left_in_chunk =
628 					decoder->bytes_left_in_chunk * 16 + v;
629 				++decoder->_hex_count;
630 			}
631 			decoder->_hex_count = 0;
632 			decoder->_state = CHUNKED_IN_CHUNK_EXT;
633 		/* fallthru */
634 		case CHUNKED_IN_CHUNK_EXT:
635 			/* RFC 7230 A.2 "Line folding in chunk extensions is
636 			 * disallowed" */
637 			for (;; ++src) {
638 				if (src == bufsz) {
639 					goto Exit;
640 				}
641 				if (buf[src] == '\012') {
642 					break;
643 				}
644 			}
645 			++src;
646 			if (decoder->bytes_left_in_chunk == 0) {
647 				if (decoder->consume_trailer) {
648 					decoder->_state =
649 						CHUNKED_IN_TRAILERS_LINE_HEAD;
650 					break;
651 				} else {
652 					goto Complete;
653 				}
654 			}
655 			decoder->_state = CHUNKED_IN_CHUNK_DATA;
656 		/* fallthru */
657 		case CHUNKED_IN_CHUNK_DATA: {
658 			size_t avail = bufsz - src;
659 			if (avail < decoder->bytes_left_in_chunk) {
660 				if (dst != src) {
661 					memmove(buf + dst, buf + src, avail);
662 				}
663 				src += avail;
664 				dst += avail;
665 				decoder->bytes_left_in_chunk -= avail;
666 				goto Exit;
667 			}
668 			if (dst != src) {
669 				memmove(buf + dst, buf + src,
670 					decoder->bytes_left_in_chunk);
671 			}
672 			src += decoder->bytes_left_in_chunk;
673 			dst += decoder->bytes_left_in_chunk;
674 			decoder->bytes_left_in_chunk = 0;
675 			decoder->_state = CHUNKED_IN_CHUNK_CRLF;
676 		}
677 		/* fallthru */
678 		case CHUNKED_IN_CHUNK_CRLF:
679 			for (;; ++src) {
680 				if (src == bufsz) {
681 					goto Exit;
682 				}
683 				if (buf[src] != '\015') {
684 					break;
685 				}
686 			}
687 			if (buf[src] != '\012') {
688 				ret = -1;
689 				goto Exit;
690 			}
691 			++src;
692 			decoder->_state = CHUNKED_IN_CHUNK_SIZE;
693 			break;
694 		case CHUNKED_IN_TRAILERS_LINE_HEAD:
695 			for (;; ++src) {
696 				if (src == bufsz) {
697 					goto Exit;
698 				}
699 				if (buf[src] != '\015') {
700 					break;
701 				}
702 			}
703 			if (buf[src++] == '\012') {
704 				goto Complete;
705 			}
706 			decoder->_state = CHUNKED_IN_TRAILERS_LINE_MIDDLE;
707 		/* fallthru */
708 		case CHUNKED_IN_TRAILERS_LINE_MIDDLE:
709 			for (;; ++src) {
710 				if (src == bufsz) {
711 					goto Exit;
712 				}
713 				if (buf[src] == '\012') {
714 					break;
715 				}
716 			}
717 			++src;
718 			decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD;
719 			break;
720 		default:
721 			assert(!"decoder is corrupt");
722 		}
723 	}
724 
725 Complete:
726 	ret = bufsz - src;
727 Exit:
728 	if (dst != src) {
729 		memmove(buf + dst, buf + src, bufsz - src);
730 	}
731 	*_bufsz = dst;
732 	return ret;
733 }
734 
735 int
736 phr_decode_chunked_is_in_data(struct phr_chunked_decoder *decoder) {
737 	return decoder->_state == CHUNKED_IN_CHUNK_DATA;
738 }
739 
740 #undef CHECK_EOF
741 #undef EXPECT_CHAR
742 #undef ADVANCE_TOKEN
743