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

Invalid character in chunk extension

Error message

Invalid character in chunk extension

What it means

Raised in read_extension (chunked.rs:105) when a chunk extension contains a control character in the ranges 0x00-0x08, 0x0a-0x1f, or 0x7f. The extension parser only allows CR to terminate and tolerates printable bytes plus tab; bare LF or other controls are rejected (test hrs_chunk_extension_invalid at chunked.rs:385 sends "2;x\nx" to trigger it).

Solutions

  1. Keep chunk extensions free of raw control characters; quote values if they contain specials.
  2. Avoid relying on chunk extensions at all - most clients/servers ignore them.
  3. Treat repeated occurrences as hostile traffic and rate-limit/block the source.

Example fix

// before
"2;x\nx\r\nxx\r\n"
// after (no control chars in extension)
"2;x=ok\r\nxx\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(), // control char in chunk ext
    _ => { /* ... */ }
}

Prevention

When it happens

Trigger: A chunk extension like "2;x\nx\r\n" contains a raw newline/control byte inside the extension value. The match at chunked.rs:105 returns the InvalidInput error.

Common situations: Request-smuggling attempts injecting LF into extensions to confuse parsers; a client that doesn't escape/quote extension values; malformed quoted-string handling.

Understand the failure class

Related errors


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

Appendix: source

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

    }

    fn read_size_lws(rdr: &mut BytesMut) -> Poll<Result<ChunkedState, io::Error>> {
        match byte!(rdr) {
            // LWS can follow the chunk size, but no more digits can come
            b'\t' | b' ' => Poll::Ready(Ok(ChunkedState::SizeLws)),
            b';' => Poll::Ready(Ok(ChunkedState::Extension)),
            b'\r' => Poll::Ready(Ok(ChunkedState::SizeLf)),
            _ => Poll::Ready(Err(io::Error::new(
                io::ErrorKind::InvalidInput,
                "Invalid chunk size linear white space",
            ))),
        }
    }
    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(

View on GitHub (pinned to 4d435abc28)