{"id":"73a225c0a2a35452","repo":"hyperium/hyper","slug":"invalid-chunk-end-lf","errorCode":null,"errorMessage":"Invalid chunk end LF","messagePattern":"Invalid chunk end LF","errorType":"exception","errorClass":"std::io::Error","httpStatus":null,"severity":"error","filePath":"src/proto/h1/decode.rs","lineNumber":630,"sourceCode":"                Poll::Ready(Ok(ChunkedState::Trailer))\n            }\n        }\n    }\n    fn read_end_lf<R: MemRead>(\n        cx: &mut Context<'_>,\n        rdr: &mut R,\n        trailers_buf: &mut Option<BytesMut>,\n        h1_max_header_size: usize,\n    ) -> Poll<Result<ChunkedState, io::Error>> {\n        let byte = byte!(rdr, cx);\n        match byte {\n            b'\\n' => {\n                if let Some(trailers_buf) = trailers_buf {\n                    put_u8!(trailers_buf, byte, h1_max_header_size);\n                }\n                Poll::Ready(Ok(ChunkedState::End))\n            }\n            _ => Poll::Ready(Err(io::Error::new(\n                io::ErrorKind::InvalidInput,\n                \"Invalid chunk end LF\",\n            ))),\n        }\n    }\n}\n\n// TODO: disallow Transfer-Encoding, Content-Length, Trailer, etc in trailers ??\nfn decode_trailers(buf: &mut BytesMut, count: usize) -> Result<HeaderMap, io::Error> {\n    let mut trailers = HeaderMap::new();\n    let mut headers = vec![httparse::EMPTY_HEADER; count];\n    let res = httparse::parse_headers(buf, &mut headers);\n    match res {\n        Ok(httparse::Status::Complete((_, headers))) => {\n            for header in headers {\n                use std::convert::TryFrom;\n                let name = match HeaderName::try_from(header.name) {\n                    Ok(name) => name,","sourceCodeStart":612,"sourceCodeEnd":648,"githubUrl":"https://github.com/hyperium/hyper/blob/084473f728f9d07b3be5845475aa2f62ed9ff579/src/proto/h1/decode.rs#L612-L648","documentation":"Thrown in read_end_lf (src/proto/h1/decode.rs:630) for the final terminator of the chunked stream: after the closing '\\r' (the blank line that ends the trailer block / body), the next byte must be '\\n'. Any other byte yields io::ErrorKind::InvalidInput — the chunked body's terminating CRLF is broken.","triggerScenarios":"A chunked body that ends with \"0\\r\\n\\r\" but never sends the final LF, or sends a different byte there — e.g. \"0\\r\\n\\rX\". The EndLf state is reached from EndCr after the final CR.","commonSituations":"A sender that closes the socket immediately after the final CR without the LF; a transport bug that drops the very last byte; a proxy that truncates the response tail.","solutions":["Confirm the chunked stream ends with the full \"0\\r\\n\\r\\n\" (zero chunk + blank line).","Make sure the sender flushes and only then closes the connection.","Check for proxies/load-balancers known to truncate the final byte of a streamed response."],"exampleFix":"// before: closing after the CR\nwrite!(w, \"0\\r\\n\\r\").await?; // -> error 31\n\n// after: full terminating sequence\nwrite!(w, \"0\\r\\n\\r\\n\").await?;","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"Some(Err(e)) => {\n    let kind = e.source()\n        .and_then(|s| s.downcast_ref::<std::io::Error>())\n        .map(|io| io.kind());\n    if matches!(kind, Some(std::io::ErrorKind::InvalidInput)) {\n        tracing::warn!(error=%e, \"chunked terminator CR not followed by LF\");\n        break;\n    }\n    return Err(e.into());\n}","preventionTips":["Always close a chunked stream with the full \"0\\r\\n\\r\\n\" (zero chunk + blank line).","Flush and only then close the connection; don't half-close mid-terminator.","Watch for middleboxes that truncate the final byte of a streamed response."],"tags":["http","http1","chunked","framing","hyper","rust"],"analyzedSha":"084473f728f9d07b3be5845475aa2f62ed9ff579","analyzedAt":"2026-08-06T01:20:18.522Z","schemaVersion":2}