| 418 | } |
| 419 | |
| 420 | bool HTTPRequest::LoadBody(LineReader& reader) |
| 421 | { |
| 422 | // https://httpwg.org/specs/rfc9112.html#message.body |
| 423 | auto transfer_encoding_header = m_headers.FindFirst("Transfer-Encoding"); |
| 424 | if (transfer_encoding_header && ToLower(transfer_encoding_header.value()) == "chunked") { |
| 425 | // Transfer-Encoding: https://datatracker.ietf.org/doc/html/rfc7230.html#section-3.3.1 |
| 426 | // Chunked Transfer Coding: https://datatracker.ietf.org/doc/html/rfc7230.html#section-4.1 |
| 427 | // see evhttp_handle_chunked_read() in libevent http.c |
| 428 | while (reader.Remaining() > 0) { |
| 429 | auto maybe_chunk_size = reader.ReadLine(); |
| 430 | if (!maybe_chunk_size) return false; |
| 431 | |
| 432 | // Allow (but ignore) Chunk Extensions |
| 433 | // See https://www.rfc-editor.org/rfc/rfc9112.html#name-chunk-extensions |
| 434 | std::string_view chunk_size_noext{maybe_chunk_size.value()}; |
| 435 | const auto semicolon_pos = chunk_size_noext.find(';'); |
| 436 | if (semicolon_pos != chunk_size_noext.npos) { |
| 437 | chunk_size_noext.remove_suffix(chunk_size_noext.size() - semicolon_pos); |
| 438 | } |
| 439 | |
| 440 | const auto chunk_size{ToIntegral<uint64_t>(util::TrimStringView(chunk_size_noext), /*base=*/16)}; |
| 441 | if (!chunk_size) throw std::runtime_error("Cannot parse chunk length value"); |
| 442 | |
| 443 | if ((m_body.size() > MAX_BODY_SIZE) || |
| 444 | (*chunk_size > MAX_BODY_SIZE - m_body.size())) |
| 445 | throw ContentTooLargeError("Chunk will exceed max body size"); |
| 446 | |
| 447 | // Last chunk has size 0 |
| 448 | if (*chunk_size == 0) { |
| 449 | // Allow (but ignore) Chunked Trailer section, by |
| 450 | // reading CRLF-terminated lines until we read an empty line, |
| 451 | // which indicates the end of this request. |
| 452 | // See https://httpwg.org/specs/rfc9112.html#rfc.section.7.1.2 |
| 453 | const size_t trailer_start{reader.Consumed()}; |
| 454 | while (true) { |
| 455 | auto maybe_trailer = reader.ReadLine(); |
| 456 | if (reader.Consumed() - trailer_start > MAX_HEADERS_SIZE) { |
| 457 | throw std::runtime_error("HTTP chunked trailer exceeds size limit"); |
| 458 | } |
| 459 | if (!maybe_trailer) return false; |
| 460 | if (maybe_trailer->empty()) break; |
| 461 | } |
| 462 | // Complete request has been parsed, reader is now pointing |
| 463 | // to beginning of next request or end of the buffer. |
| 464 | return true; |
| 465 | } |
| 466 | |
| 467 | // We are still expecting more data for this chunk |
| 468 | if (reader.Remaining() < *chunk_size) { |
| 469 | return false; |
| 470 | } |
| 471 | |
| 472 | // Pack chunk onto body |
| 473 | m_body += reader.ReadLength(*chunk_size); |
| 474 | |
| 475 | // Even though every chunk size is explicitly declared, |
| 476 | // they are still terminated by a CRLF we don't need, |
| 477 | // just consume it here. |