actix/actix-web · error · io::Error

Invalid chunk size LF

Error message

Invalid chunk size LF

What it means

Raised in read_size_lf (chunked.rs:116) when the byte expected to be the LF terminating the chunk-size line is not '\n'. After read_size/read_size_lws/read_extension consume the CR, this state requires a single '\n'; anything else is a malformed CRLF terminator.

Solutions

  1. Always terminate chunk-size lines with a full CRLF (\r\n) as required by RFC 7230.
  2. Use a standards-compliant HTTP client library to serialize chunked requests.
  3. Check intermediary proxies for CRLF normalization bugs.

Example fix

// before
"4\rdata\r\n"
// after
"4\r\ndata\r\n"
Defensive patterns

Strategy: try-catch

Try / catch

match payload.next().await {
    Some(Err(PayloadError::Io(e))) if e.kind() == io::ErrorKind::InvalidInput =>
        return HttpResponse::BadRequest().finish(), // chunk size LF missing
    _ => { /* ... */ }
}

Prevention

When it happens

Trigger: A chunk-size line terminates with CR followed by a non-LF byte, e.g. "4\rX\r\n" or a lone CR without LF. The state machine enters SizeLf expecting '\n' and gets something else.

Common situations: Client using bare LF (\n) line endings inconsistently so the parser sees CR then a digit; corrupt/truncated body; proxy mangling CRLF.

Related errors


AI-assisted analysis of actix/actix-web@4d435abc28 (2026-08-09). Data as JSON: /api/errors/b4f995461c9eedce. Report an issue: GitHub.

Appendix: source

Thrown at actix-http/src/h1/chunked.rs:116

            ))),
        }
    }
    fn read_extension(rdr: &mut BytesMut) -> Poll<Result<ChunkedState, io::Error>> {
        match byte!(rdr) {
            b'\r' => Poll::Ready(Ok(ChunkedState::SizeLf)),
            // strictly 0x20 (space) should be disallowed but we don't parse quoted strings here
            0x00..=0x08 | 0x0a..=0x1f | 0x7f => Poll::Ready(Err(io::Error::new(
                io::ErrorKind::InvalidInput,
                "Invalid character in chunk extension",
            ))),
            _ => Poll::Ready(Ok(ChunkedState::Extension)), // no supported extensions
        }
    }
    fn read_size_lf(rdr: &mut BytesMut, size: u64) -> Poll<Result<ChunkedState, io::Error>> {
        match byte!(rdr) {
            b'\n' if size > 0 => Poll::Ready(Ok(ChunkedState::Body)),
            b'\n' if size == 0 => Poll::Ready(Ok(ChunkedState::EndCr)),
            _ => Poll::Ready(Err(io::Error::new(
                io::ErrorKind::InvalidInput,
                "Invalid chunk size LF",
            ))),
        }
    }

    fn read_body(
        rdr: &mut BytesMut,
        rem: &mut u64,
        buf: &mut Option<Bytes>,
    ) -> Poll<Result<ChunkedState, io::Error>> {
        trace!("Chunked read, remaining={:?}", rem);

        let len = rdr.len() as u64;
        if len == 0 {
            Poll::Ready(Ok(ChunkedState::Body))
        } else {
            let slice;

View on GitHub (pinned to 4d435abc28)