Parse Content-Length or Transfer-Encoding from HTTP headers. Per RFC 7230 Section 3.3.3, rejects requests containing both `Content-Length` and `Transfer-Encoding` headers to prevent request smuggling via CL/TE ambiguity.
(headers: &str)
| 1689 | /// `Content-Length` and `Transfer-Encoding` headers to prevent request |
| 1690 | /// smuggling via CL/TE ambiguity. |
| 1691 | pub(crate) fn parse_body_length(headers: &str) -> Result<BodyLength> { |
| 1692 | let mut has_te_chunked = false; |
| 1693 | let mut cl_value: Option<u64> = None; |
| 1694 | |
| 1695 | for line in headers.lines().skip(1) { |
| 1696 | let lower = line.to_ascii_lowercase(); |
| 1697 | if lower.starts_with("transfer-encoding:") { |
| 1698 | let val = lower.split_once(':').map_or("", |(_, v)| v.trim()); |
| 1699 | if val.split(',').any(|enc| enc.trim() == "chunked") { |
| 1700 | has_te_chunked = true; |
| 1701 | } |
| 1702 | } |
| 1703 | if lower.starts_with("content-length:") { |
| 1704 | let val = lower.split_once(':').map_or("", |(_, v)| v.trim()); |
| 1705 | let len: u64 = val |
| 1706 | .parse() |
| 1707 | .map_err(|_| miette!("Request contains invalid Content-Length value"))?; |
| 1708 | if let Some(prev) = cl_value |
| 1709 | && prev != len |
| 1710 | { |
| 1711 | return Err(miette!( |
| 1712 | "Request contains multiple Content-Length headers with differing values ({prev} vs {len})" |
| 1713 | )); |
| 1714 | } |
| 1715 | cl_value = Some(len); |
| 1716 | } |
| 1717 | } |
| 1718 | |
| 1719 | if has_te_chunked && cl_value.is_some() { |
| 1720 | return Err(miette!( |
| 1721 | "Request contains both Transfer-Encoding and Content-Length headers" |
| 1722 | )); |
| 1723 | } |
| 1724 | |
| 1725 | if has_te_chunked { |
| 1726 | return Ok(BodyLength::Chunked); |
| 1727 | } |
| 1728 | if let Some(len) = cl_value { |
| 1729 | return Ok(BodyLength::ContentLength(len)); |
| 1730 | } |
| 1731 | Ok(BodyLength::None) |
| 1732 | } |
| 1733 | |
| 1734 | /// Relay exactly `len` bytes from reader to writer. |
| 1735 | async fn relay_fixed<R, W>( |