hyperium/hyper · error · std::io::Error

Invalid chunk body LF

Error message

Invalid chunk body LF

What it means

Thrown in read_body_lf (src/proto/h1/decode.rs:523) after the CR following a chunk's data: the next byte must be '\n' to complete the CRLF. Any other byte yields io::ErrorKind::InvalidInput — the chunk trailer CRLF is broken (CR present, LF missing).

Source

Thrown at src/proto/h1/decode.rs:523

    fn read_body_cr<R: MemRead>(
        cx: &mut Context<'_>,
        rdr: &mut R,
    ) -> Poll<Result<ChunkedState, io::Error>> {
        match byte!(rdr, cx) {
            b'\r' => Poll::Ready(Ok(ChunkedState::BodyLf)),
            _ => Poll::Ready(Err(io::Error::new(
                io::ErrorKind::InvalidInput,
                "Invalid chunk body CR",
            ))),
        }
    }
    fn read_body_lf<R: MemRead>(
        cx: &mut Context<'_>,
        rdr: &mut R,
    ) -> Poll<Result<ChunkedState, io::Error>> {
        match byte!(rdr, cx) {
            b'\n' => Poll::Ready(Ok(ChunkedState::Start)),
            _ => Poll::Ready(Err(io::Error::new(
                io::ErrorKind::InvalidInput,
                "Invalid chunk body LF",
            ))),
        }
    }

    fn read_trailer<R: MemRead>(
        cx: &mut Context<'_>,
        rdr: &mut R,
        trailers_buf: &mut Option<BytesMut>,
        h1_max_header_size: usize,
    ) -> Poll<Result<ChunkedState, io::Error>> {
        trace!("read_trailer");
        let byte = byte!(rdr, cx);

        put_u8!(
            trailers_buf.as_mut().expect("trailers_buf is None"),
            byte,

View on GitHub (pinned to 084473f728)

Solutions

  1. Ensure every chunk is followed by a full CRLF (\r\n), not a lone CR.
  2. Disable any body newline normalization/transcoding in upstream proxies.
  3. Write framing with a single literal "\r\n" buffer to avoid splitting CR and LF writes.

Example fix

// before: CR and LF written through separate paths that drop LF
write!(w, "{:x}\r\n", len).await?;
w.write_all(&data).await?;
w.write_all(b"\r").await?; // LF lost downstream -> error 28

// after: emit the full terminator atomically
w.write_all(b"\r\n").await?;
Defensive patterns

Strategy: try-catch

Try / catch

Some(Err(e)) => {
    let kind = e.source()
        .and_then(|s| s.downcast_ref::<std::io::Error>())
        .map(|io| io.kind());
    if matches!(kind, Some(std::io::ErrorKind::InvalidInput)) {
        tracing::warn!(error=%e, "chunk data terminator CR not followed by LF");
        break;
    }
    return Err(e.into());
}

Prevention

When it happens

Trigger: A chunk like "10\r\n1234567890abcdef\rX" where the byte after the data-terminating CR is 'X' instead of LF; a transport or middleware that drops the LF after the data.

Common situations: A newline-normalizing proxy that converts CRLF→CR in the body; a buggy sender that writes only "\r" after each chunk; byte-level corruption on the link.

Related errors


AI-assisted analysis of hyperium/hyper@084473f728 (2026-08-06). Data as JSON: /data/errors/6162ddc841560d21.json. Report an issue: GitHub.