/js/lib/Socket.IO-node/support/expresso/deps/jscoverage/http-message.c
C | 900 lines | 782 code | 76 blank | 42 comment | 114 complexity | 160bbf8ff486ff79316ddb137d32082a MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, MPL-2.0-no-copyleft-exception, BSD-3-Clause
1/* 2 http-message.c - HTTP message object 3 Copyright (C) 2008 siliconforks.com 4 5 This program is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published by 7 the Free Software Foundation; either version 2 of the License, or 8 (at your option) any later version. 9 10 This program is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public License along 16 with this program; if not, write to the Free Software Foundation, Inc., 17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18*/ 19 20#include <config.h> 21 22#include "http-server.h" 23 24#include <assert.h> 25#include <string.h> 26 27#include "stream.h" 28#include "util.h" 29 30enum ChunkedBodyState { 31 CHUNKED_BODY_CHUNK_SIZE, CHUNKED_BODY_CHUNK_DATA, CHUNKED_BODY_TRAILER, CHUNKED_BODY_DONE 32}; 33 34struct HTTPMessage { 35 char * start_line; 36 HTTPHeader * headers; 37 38 /* used for sending and receiving */ 39 struct HTTPConnection * connection; 40 41 /* used only for receiving */ 42 bool has_content_length; 43 bool is_chunked; 44 size_t bytes_remaining; 45 enum ChunkedBodyState chunked_body_state; 46 Stream * chunk_buffer; 47 48 /* used only for sending */ 49 bool is_started; 50}; 51 52static bool is_lws(uint8_t c) { 53 return c == '\r' || c == '\n' || c == ' ' || c == '\t'; 54} 55 56static bool is_separator(uint8_t c) { 57 /* RFC 2616 2.2 */ 58 return strchr("()<>@,;:\\\"/[]?={} \t", c) != NULL; 59} 60 61static bool is_token_char(uint8_t c) { 62 /* RFC 2616 2.2 */ 63 return 32 <= c && c <= 126 && ! is_separator(c); 64} 65 66static bool is_text(uint8_t c) { 67 return ! (c <= 31 || c == 127); 68} 69 70static void skip_lws(const uint8_t ** p) { 71 while (**p != '\0' && is_lws(**p)) { 72 (*p)++; 73 } 74} 75 76static uint8_t * parse_token(const uint8_t ** p) { 77 const uint8_t * start = *p; 78 while (**p != '\0' && is_token_char(**p)) { 79 (*p)++; 80 } 81 82 if (*p == start) { 83 return NULL; 84 } 85 86 return (uint8_t *) xstrndup((char *) start, *p - start); 87} 88 89static uint8_t * parse_quoted_string(const uint8_t ** p) { 90 const uint8_t * start = *p; 91 92 if (**p != '"') { 93 return NULL; 94 } 95 (*p)++; 96 97 while (**p != '\0' && **p != '"') { 98 if (**p == '\\') { 99 (*p)++; 100 if (**p < 1 || **p > 127) { 101 return NULL; 102 } 103 (*p)++; 104 } 105 else if (is_text(**p)) { 106 (*p)++; 107 } 108 else { 109 return NULL; 110 } 111 } 112 113 if (**p != '"') { 114 return NULL; 115 } 116 (*p)++; 117 118 return (uint8_t *) xstrndup((char *) start, *p - start); 119} 120 121HTTPMessage * HTTPMessage_new(HTTPConnection * connection) { 122 HTTPMessage * message = xmalloc(sizeof(HTTPMessage)); 123 message->start_line = NULL; 124 message->headers = NULL; 125 message->connection = connection; 126 127 message->has_content_length = false; 128 message->is_chunked = false; 129 message->bytes_remaining = 0; 130 message->chunked_body_state = CHUNKED_BODY_CHUNK_SIZE; 131 message->chunk_buffer = NULL; 132 133 message->is_started = false; 134 return message; 135} 136 137void HTTPMessage_delete(HTTPMessage * message) { 138 free(message->start_line); 139 140 HTTPHeader * h = message->headers; 141 while (h != NULL) { 142 HTTPHeader * doomed = h; 143 h = h->next; 144 free(doomed->name); 145 free(doomed->value); 146 free(doomed); 147 } 148 149 if (message->chunk_buffer != NULL) { 150 Stream_delete(message->chunk_buffer); 151 } 152 153 free(message); 154} 155 156HTTPConnection * HTTPMessage_get_connection(const HTTPMessage * message) { 157 return message->connection; 158} 159 160void HTTPMessage_add_header(HTTPMessage * message, const char * name, const char * value) { 161 HTTPHeader * last = NULL; 162 for (HTTPHeader * h = message->headers; h != NULL; h = h->next) { 163 if (strcmp(h->name, name) == 0) { 164 char * new_value; 165 xasprintf(&new_value, "%s, %s", h->value, value); 166 free(h->value); 167 h->value = new_value; 168 return; 169 } 170 last = h; 171 } 172 173 HTTPHeader * header = xmalloc(sizeof(HTTPHeader)); 174 header->name = xstrdup(name); 175 header->value = xstrdup(value); 176 header->next = NULL; 177 if (last == NULL) { 178 message->headers = header; 179 } 180 else { 181 last->next = header; 182 } 183} 184 185void HTTPMessage_set_header(HTTPMessage * message, const char * name, const char * value) { 186 for (HTTPHeader * h = message->headers; h != NULL; h = h->next) { 187 if (strcmp(name, h->name) == 0) { 188 free(h->value); 189 h->value = xstrdup(value); 190 return; 191 } 192 } 193 HTTPMessage_add_header(message, name, value); 194} 195 196char * HTTPMessage_get_charset(const HTTPMessage * message) { 197 const char * content_type = HTTPMessage_find_header(message, HTTP_CONTENT_TYPE); 198 if (content_type == NULL) { 199 return NULL; 200 } 201 202 const uint8_t * p = (const uint8_t *) content_type; 203 204 /* e.g., text/html */ 205 uint8_t * token; 206 skip_lws(&p); 207 if (! is_token_char(*p)) { 208 return NULL; 209 } 210 token = parse_token(&p); 211 free(token); 212 skip_lws(&p); 213 if (*p != '/') { 214 return NULL; 215 } 216 p++; 217 skip_lws(&p); 218 if (! is_token_char(*p)) { 219 return NULL; 220 } 221 token = parse_token(&p); 222 free(token); 223 224 skip_lws(&p); 225 226 while (*p != '\0') { 227 bool is_charset = false; 228 if (*p != ';') { 229 return NULL; 230 } 231 p++; 232 233 skip_lws(&p); 234 235 if (! is_token_char(*p)) { 236 return NULL; 237 } 238 uint8_t * attribute = parse_token(&p); 239 if (strcasecmp((char *) attribute, "charset") == 0) { 240 is_charset = true; 241 } 242 free(attribute); 243 skip_lws(&p); 244 if (*p != '=') { 245 return NULL; 246 } 247 p++; 248 249 skip_lws(&p); 250 251 if (*p == '"') { 252 uint8_t * value = parse_quoted_string(&p); 253 if (value == NULL) { 254 return NULL; 255 } 256 if (is_charset) { 257 return (char *) value; 258 } 259 free(value); 260 } 261 else if (is_token_char(*p)) { 262 uint8_t * value = parse_token(&p); 263 if (is_charset) { 264 return (char *) value; 265 } 266 free(value); 267 } 268 else { 269 return NULL; 270 } 271 272 skip_lws(&p); 273 } 274 275 return NULL; 276} 277 278void HTTPMessage_set_content_length(HTTPMessage * message, size_t value) { 279 char * s; 280 xasprintf(&s, "%u", value); 281 HTTPMessage_set_header(message, HTTP_CONTENT_LENGTH, s); 282 free(s); 283} 284 285const char * HTTPMessage_find_header(const HTTPMessage * message, const char * name) { 286 for (HTTPHeader * h = message->headers; h != NULL; h = h->next) { 287 if (strcasecmp(h->name, name) == 0) { 288 return h->value; 289 } 290 } 291 return NULL; 292} 293 294const HTTPHeader * HTTPMessage_get_headers(const HTTPMessage * message) { 295 return message->headers; 296} 297 298const char * HTTPMessage_get_start_line(const HTTPMessage * message) { 299 return message->start_line; 300} 301 302void HTTPMessage_set_start_line(HTTPMessage * message, const char * start_line) { 303 free(message->start_line); 304 message->start_line = xstrdup(start_line); 305} 306 307static int read_line(Stream * stream, HTTPConnection * connection) __attribute__((warn_unused_result)); 308 309static int read_line(Stream * stream, HTTPConnection * connection) { 310 for (;;) { 311 int octet; 312 int result = HTTPConnection_read_octet(connection, &octet); 313 if (result != 0) { 314 return result; 315 } 316 317 /* check for end of input */ 318 if (octet == -1) { 319 return 0; 320 } 321 322 char c = (char) octet; 323 Stream_write_char(stream, c); 324 if (c == '\n') { 325 return 0; 326 } 327 } 328} 329 330static int read_header(Stream * stream, HTTPConnection * connection) __attribute__((warn_unused_result)); 331 332static int read_header(Stream * stream, HTTPConnection * connection) { 333 int c; 334 335 do { 336 int result = read_line(stream, connection); 337 if (result != 0) { 338 return result; 339 } 340 341 /* check for blank line ending the headers */ 342 if (stream->length == 0 || 343 (stream->length == 1 && stream->data[0] == '\n') || 344 (stream->length == 2 && stream->data[0] == '\r' && stream->data[1] == '\n')) { 345 break; 346 } 347 348 result = HTTPConnection_peek_octet(connection, &c); 349 if (result != 0) { 350 return result; 351 } 352 } 353 while (c == ' ' || c == '\t'); 354 355 return 0; 356} 357 358static bool stream_contains_nul(const Stream * stream) { 359 for (size_t i = 0; i < stream->length; i++) { 360 if (stream->data[i] == '\0') { 361 return true; 362 } 363 } 364 return false; 365} 366 367int HTTPMessage_read_start_line_and_headers(HTTPMessage * message) { 368 Stream * stream = Stream_new(0); 369 370 /* read the start line */ 371 int result = read_line(stream, message->connection); 372 if (result != 0) { 373 Stream_delete(stream); 374 return -1; 375 } 376 377 /* check for NUL byte */ 378 if (stream_contains_nul(stream)) { 379 Stream_delete(stream); 380 return -1; 381 } 382 383 message->start_line = xstrndup((char *) stream->data, stream->length); 384 385 /* read the headers - RFC 2616 4.2 */ 386 message->headers = NULL; 387 for (;;) { 388 Stream_reset(stream); 389 result = read_header(stream, message->connection); 390 if (result != 0) { 391 Stream_delete(stream); 392 return -1; 393 } 394 395 /* check for CRLF (or similar) to terminate headers */ 396 if (stream->length == 0 || 397 (stream->length == 1 && stream->data[0] == '\n') || 398 (stream->length == 2 && stream->data[0] == '\r' && stream->data[1] == '\n')) { 399 break; 400 } 401 402 /* check for NUL byte */ 403 if (stream_contains_nul(stream)) { 404 Stream_delete(stream); 405 return -1; 406 } 407 408 /* NUL-terminate the header */ 409 Stream_write_char(stream, '\0'); 410 411 const uint8_t * p = stream->data; 412 413 char * name = (char *) parse_token(&p); 414 if (name == NULL) { 415 Stream_delete(stream); 416 return -1; 417 } 418 419 skip_lws(&p); 420 421 /* expect colon */ 422 if (*p != ':') { 423 free(name); 424 Stream_delete(stream); 425 return -1; 426 } 427 428 /* skip over colon */ 429 p++; 430 431 skip_lws(&p); 432 433 if (*p == '\0') { 434 free(name); 435 Stream_delete(stream); 436 return -1; 437 } 438 439 /* skip backward over LWS, starting from the last char in the buffer */ 440 uint8_t * end = stream->data + stream->length - 2; 441 while (end > p && is_lws(*end)) { 442 end--; 443 } 444 445 char * value = xstrndup((char *) p, end - p + 1); 446 447 HTTPMessage_add_header(message, name, value); 448 free(name); 449 free(value); 450 } 451 452 Stream_delete(stream); 453 454 /* 455 RFC 2616 4.3: 456 - a request has a body iff the request headers include Content-Length or Transfer-Encoding 457 - a response has a body iff the request is not HEAD and the response is not 1xx, 204, 304 458 */ 459 460 const char * content_length = HTTPMessage_find_header(message, HTTP_CONTENT_LENGTH); 461 if (content_length != NULL) { 462 size_t value = 0; 463 for (const char * p = content_length; *p != '\0'; p++) { 464 /* check for overflow */ 465 if (SIZE_MAX / 10 < value) { 466 return -1; 467 } 468 value *= 10; 469 470 uint8_t digit = *p; 471 472 /* check that it contains only decimal digits */ 473 if (digit < '0' || digit > '9') { 474 return -1; 475 } 476 477 size_t digit_value = digit - '0'; 478 479 /* check for overflow */ 480 if (SIZE_MAX - digit_value < value) { 481 return -1; 482 } 483 value += digit_value; 484 } 485 486 message->bytes_remaining = value; 487 message->has_content_length = true; 488 } 489 490 const char * transfer_encoding = HTTPMessage_find_header(message, HTTP_TRANSFER_ENCODING); 491 if (transfer_encoding != NULL) { 492 uint8_t * token = NULL; 493 494 const uint8_t * p = (const uint8_t *) transfer_encoding; 495 result = 0; 496 for (;;) { 497 skip_lws(&p); 498 499 if (! is_token_char(*p)) { 500 result = -1; 501 break; 502 } 503 504 free(token); 505 token = parse_token(&p); 506 507 skip_lws(&p); 508 509 while (*p == ';') { 510 p++; 511 512 skip_lws(&p); 513 514 if (! is_token_char(*p)) { 515 result = -1; 516 break; 517 } 518 uint8_t * attribute = parse_token(&p); 519 free(attribute); 520 521 skip_lws(&p); 522 523 if (*p != '=') { 524 result = -1; 525 break; 526 } 527 p++; 528 529 skip_lws(&p); 530 531 if (*p == '"') { 532 uint8_t * value = parse_quoted_string(&p); 533 if (value == NULL) { 534 result = -1; 535 break; 536 } 537 free(value); 538 } 539 else if (is_token_char(*p)) { 540 uint8_t * value = parse_token(&p); 541 free(value); 542 } 543 else { 544 result = -1; 545 break; 546 } 547 548 skip_lws(&p); 549 } 550 551 if (result == -1) { 552 break; 553 } 554 555 if (*p != ',') { 556 break; 557 } 558 559 p++; 560 } 561 562 if (result == 0 && *p == '\0' && token != NULL && strcasecmp((char *) token, "chunked") == 0) { 563 message->is_chunked = true; 564 message->chunk_buffer = Stream_new(0); 565 } 566 567 free(token); 568 } 569 570 return result; 571} 572 573int HTTPMessage_write_start_line_and_headers(HTTPMessage * message) { 574 int result = 0; 575 576 if (message->is_started) { 577 return result; 578 } 579 580 message->is_started = true; 581 582 /* send the start line */ 583 assert(message->start_line != NULL); 584 size_t length = strlen(message->start_line); 585 assert(length >= 2 && message->start_line[length - 2] == '\r' && message->start_line[length - 1] == '\n'); 586 result = HTTPConnection_write(message->connection, message->start_line, length); 587 if (result != 0) { 588 return result; 589 } 590 591 /* send the headers */ 592 HTTPMessage_set_header(message, HTTP_CONNECTION, "close"); 593 for (HTTPHeader * h = message->headers; h != NULL; h = h->next) { 594 result = HTTPConnection_write(message->connection, h->name, strlen(h->name)); 595 if (result != 0) { 596 return result; 597 } 598 result = HTTPConnection_write(message->connection, ": ", 2); 599 if (result != 0) { 600 return result; 601 } 602 result = HTTPConnection_write(message->connection, h->value, strlen(h->value)); 603 if (result != 0) { 604 return result; 605 } 606 result = HTTPConnection_write(message->connection, "\r\n", 2); 607 if (result != 0) { 608 return result; 609 } 610 } 611 612 result = HTTPConnection_write(message->connection, "\r\n", 2); 613 return result; 614} 615 616bool HTTPMessage_has_sent_headers(const HTTPMessage * message) { 617 return message->is_started; 618} 619 620int HTTPMessage_write(HTTPMessage * message, const void * p, size_t size) { 621 int result = 0; 622 result = HTTPMessage_write_start_line_and_headers(message); 623 if (result != 0) { 624 return result; 625 } 626 result = HTTPConnection_write(message->connection, p, size); 627 return result; 628} 629 630int HTTPMessage_flush(HTTPMessage * message) { 631 int result = 0; 632 result = HTTPMessage_write_start_line_and_headers(message); 633 if (result != 0) { 634 return result; 635 } 636 result = HTTPConnection_flush(message->connection); 637 return result; 638} 639 640static int read_chunk_size_line(HTTPMessage * message) __attribute__((warn_unused_result)); 641 642static int read_chunk_size_line(HTTPMessage * message) { 643 Stream_reset(message->chunk_buffer); 644 int result = read_line(message->chunk_buffer, message->connection); 645 if (result != 0) { 646 return result; 647 } 648 if (message->chunk_buffer->length < 2) { 649 return -1; 650 } 651 return 0; 652} 653 654static int read_chunk_size(Stream * stream, size_t * chunk_size) __attribute__((warn_unused_result)); 655 656static int read_chunk_size(Stream * stream, size_t * chunk_size) { 657 size_t value = 0; 658 for (size_t i = 0; i < stream->length; i++) { 659 uint8_t digit = stream->data[i]; 660 661 /* check that it contains only hexadecimal digits */ 662 size_t digit_value; 663 if ('0' <= digit && digit <= '9') { 664 digit_value = digit - '0'; 665 } 666 else if ('a' <= digit && digit <= 'f') { 667 digit_value = digit - 'a' + 10; 668 } 669 else if ('A' <= digit && digit <= 'F') { 670 digit_value = digit - 'A' + 10; 671 } 672 else if (is_lws(digit) || digit == ';') { 673 break; 674 } 675 else { 676 return -1; 677 } 678 679 /* check for overflow */ 680 if (SIZE_MAX / 16 < value) { 681 return -1; 682 } 683 value *= 16; 684 685 /* check for overflow */ 686 if (SIZE_MAX - digit_value < value) { 687 return -1; 688 } 689 value += digit_value; 690 } 691 692 *chunk_size = value; 693 return 0; 694} 695 696static int read_chunked_message_body(HTTPMessage * message, void * p, size_t capacity, size_t * bytes_read) { 697 int result = 0; 698 *bytes_read = 0; 699 700 if (message->chunked_body_state == CHUNKED_BODY_DONE) { 701 return 0; 702 } 703 704 uint8_t * s = p; 705 int c; 706 for (*bytes_read = 0; *bytes_read < capacity; (*bytes_read)++) { 707 switch (message->chunked_body_state) { 708 case CHUNKED_BODY_CHUNK_SIZE: 709 if (message->chunk_buffer->length == 0) { 710 /* read a `chunk-size' line */ 711 result = read_chunk_size_line(message); 712 if (result != 0) { 713 return result; 714 } 715 716 message->bytes_remaining = message->chunk_buffer->length; 717 } 718 719 if (message->bytes_remaining == 0) { 720 return -1; 721 } 722 723 /* serve from the chunk buffer */ 724 s[*bytes_read] = message->chunk_buffer->data[message->chunk_buffer->length - message->bytes_remaining]; 725 message->bytes_remaining--; 726 727 if (message->bytes_remaining == 0) { 728 size_t chunk_size; 729 result = read_chunk_size(message->chunk_buffer, &chunk_size); 730 if (result != 0) { 731 return result; 732 } 733 Stream_reset(message->chunk_buffer); 734 if (chunk_size == 0) { 735 message->chunked_body_state = CHUNKED_BODY_TRAILER; 736 } 737 else if (SIZE_MAX - 2 < chunk_size) { 738 /* overflow */ 739 return -1; 740 } 741 else { 742 message->chunked_body_state = CHUNKED_BODY_CHUNK_DATA; 743 message->bytes_remaining = chunk_size + 2; 744 } 745 } 746 747 break; 748 case CHUNKED_BODY_CHUNK_DATA: 749 /* serve from the chunk */ 750 result = HTTPConnection_read_octet(message->connection, &c); 751 if (result != 0) { 752 return result; 753 } 754 if (c == -1) { 755 result = -1; 756 message->chunked_body_state = CHUNKED_BODY_DONE; 757 return result; 758 } 759 s[*bytes_read] = (uint8_t) c; 760 message->bytes_remaining--; 761 762 if (message->bytes_remaining == 0) { 763 message->chunked_body_state = CHUNKED_BODY_CHUNK_SIZE; 764 } 765 766 break; 767 case CHUNKED_BODY_TRAILER: 768 if (message->chunk_buffer->length == 0) { 769 /* read a header */ 770 result = read_header(message->chunk_buffer, message->connection); 771 if (result != 0) { 772 return result; 773 } 774 message->bytes_remaining = message->chunk_buffer->length; 775 } 776 777 if (message->bytes_remaining == 0) { 778 message->chunked_body_state = CHUNKED_BODY_DONE; 779 return result; 780 } 781 782 /* serve from the chunk buffer */ 783 s[*bytes_read] = message->chunk_buffer->data[message->chunk_buffer->length - message->bytes_remaining]; 784 message->bytes_remaining--; 785 786 if (message->bytes_remaining == 0) { 787 size_t length = message->chunk_buffer->length; 788 uint8_t * chunk_buffer = message->chunk_buffer->data; 789 if (length == 0 || 790 (length == 1 && chunk_buffer[0] == '\n') || 791 (length == 2 && chunk_buffer[0] == '\r' && chunk_buffer[1] == '\n')) { 792 message->chunked_body_state = CHUNKED_BODY_DONE; 793 return result; 794 } 795 Stream_reset(message->chunk_buffer); 796 } 797 798 break; 799 default: 800 break; 801 } 802 } 803 804 return result; 805} 806 807static int read_chunked_entity_body(HTTPMessage * message, void * p, size_t capacity, size_t * bytes_read) { 808 int result = 0; 809 *bytes_read = 0; 810 811 if (message->chunked_body_state == CHUNKED_BODY_DONE) { 812 return result; 813 } 814 815 uint8_t * s = p; 816 for (*bytes_read = 0; *bytes_read < capacity; (*bytes_read)++) { 817 if (message->bytes_remaining == 0) { 818 result = read_chunk_size_line(message); 819 if (result != 0) { 820 break; 821 } 822 size_t chunk_size; 823 result = read_chunk_size(message->chunk_buffer, &chunk_size); 824 if (result != 0) { 825 break; 826 } 827 message->bytes_remaining = chunk_size; 828 if (chunk_size == 0) { 829 message->chunked_body_state = CHUNKED_BODY_DONE; 830 break; 831 } 832 } 833 834 int c; 835 result = HTTPConnection_read_octet(message->connection, &c); 836 if (result != 0) { 837 break; 838 } 839 if (c == -1) { 840 result = -1; 841 message->chunked_body_state = CHUNKED_BODY_DONE; 842 break; 843 } 844 s[*bytes_read] = (uint8_t) c; 845 message->bytes_remaining--; 846 } 847 848 return result; 849} 850 851int HTTPMessage_read_message_body(HTTPMessage * message, void * p, size_t capacity, size_t * bytes_read) { 852 if (message->is_chunked) { 853 return read_chunked_message_body(message, p, capacity, bytes_read); 854 } 855 856 int result = 0; 857 uint8_t * s = p; 858 for (*bytes_read = 0; *bytes_read < capacity; (*bytes_read)++) { 859 if (message->has_content_length && message->bytes_remaining == 0) { 860 break; 861 } 862 863 int c; 864 result = HTTPConnection_read_octet(message->connection, &c); 865 if (result != 0) { 866 break; 867 } 868 if (c == -1) { 869 break; 870 } 871 s[*bytes_read] = (uint8_t) c; 872 message->bytes_remaining--; 873 } 874 return result; 875} 876 877int HTTPMessage_read_entity_body(HTTPMessage * message, void * p, size_t capacity, size_t * bytes_read) { 878 if (message->is_chunked) { 879 return read_chunked_entity_body(message, p, capacity, bytes_read); 880 } 881 882 return HTTPMessage_read_message_body(message, p, capacity, bytes_read); 883} 884 885int HTTPMessage_read_entire_entity_body(HTTPMessage * message, Stream * input_stream) { 886 int result = 0; 887 uint8_t * buffer[8192]; 888 for (;;) { 889 size_t bytes_read; 890 result = HTTPMessage_read_entity_body(message, buffer, 8192, &bytes_read); 891 if (result != 0) { 892 break; 893 } 894 if (bytes_read == 0) { 895 break; 896 } 897 Stream_write(input_stream, buffer, bytes_read); 898 } 899 return result; 900}