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