{"id":"74121a45846e47da","repo":"hyperium/hyper","slug":"invalid-chunk-body-cr","errorCode":null,"errorMessage":"Invalid chunk body CR","messagePattern":"Invalid chunk body CR","errorType":"exception","errorClass":"std::io::Error","httpStatus":null,"severity":"error","filePath":"src/proto/h1/decode.rs","lineNumber":511,"sourceCode":"                IncompleteBody,\n            )));\n        }\n        *buf = Some(slice);\n        *rem -= count as u64;\n\n        if *rem > 0 {\n            Poll::Ready(Ok(ChunkedState::Body))\n        } else {\n            Poll::Ready(Ok(ChunkedState::BodyCr))\n        }\n    }\n    fn read_body_cr<R: MemRead>(\n        cx: &mut Context<'_>,\n        rdr: &mut R,\n    ) -> Poll<Result<ChunkedState, io::Error>> {\n        match byte!(rdr, cx) {\n            b'\\r' => Poll::Ready(Ok(ChunkedState::BodyLf)),\n            _ => Poll::Ready(Err(io::Error::new(\n                io::ErrorKind::InvalidInput,\n                \"Invalid chunk body CR\",\n            ))),\n        }\n    }\n    fn read_body_lf<R: MemRead>(\n        cx: &mut Context<'_>,\n        rdr: &mut R,\n    ) -> Poll<Result<ChunkedState, io::Error>> {\n        match byte!(rdr, cx) {\n            b'\\n' => Poll::Ready(Ok(ChunkedState::Start)),\n            _ => Poll::Ready(Err(io::Error::new(\n                io::ErrorKind::InvalidInput,\n                \"Invalid chunk body LF\",\n            ))),\n        }\n    }\n","sourceCodeStart":493,"sourceCodeEnd":529,"githubUrl":"https://github.com/hyperium/hyper/blob/084473f728f9d07b3be5845475aa2f62ed9ff579/src/proto/h1/decode.rs#L493-L529","documentation":"Thrown in read_body_cr (src/proto/h1/decode.rs:511) after reading exactly chunk_len bytes of chunk data: the next byte must be '\\r' (start of the trailing CRLF). Any other byte yields io::ErrorKind::InvalidInput, meaning the chunk's data ran long or short relative to its announced size.","triggerScenarios":"A chunk that announced \"10\\r\\n\" (16 bytes) but whose 17th byte is not '\\r' — e.g. \"10\\r\\n1234567890abcdefX\\r\\n\" — because the sender wrote 17 bytes, or miscounted and the CRLF landed at the wrong offset.","commonSituations":"Off-by-one in a custom chunker; concurrent writes to the body stream that interleave data of different lengths than announced; compression applied after chunking so byte counts drift.","solutions":["Verify the announced hex size equals the exact number of data bytes before the CRLF.","If you compress, chunk after compression (or recompute the length) so counts stay consistent.","Serialize/stream body writes from a single task so sizes and data can't drift apart."],"exampleFix":"// before: announcing a fixed size then streaming variable data\nwrite!(w, \"10\\r\\n\").await?;\nw.write_all(&maybe_seventeen_bytes).await?; // -> error 27\nwrite!(w, \"\\r\\n\").await?;\n\n// after: announce the real length\nwrite!(w, \"{:x}\\r\\n\", data.len()).await?;\nw.write_all(&data).await?;\nwrite!(w, \"\\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, \"chunk body not followed by CRLF (size/data mismatch)\");\n        break;\n    }\n    return Err(e.into());\n}","preventionTips":["Compute the chunk length from the exact bytes you write: write!(\"{:x}\\r\\n\", data.len()).","Apply compression before chunking (or recompute size) so counts don't drift.","Serialize body writes from one task to avoid interleaving size/data from different producers."],"tags":["http","http1","chunked","framing","hyper","rust"],"analyzedSha":"084473f728f9d07b3be5845475aa2f62ed9ff579","analyzedAt":"2026-08-06T01:20:18.522Z","schemaVersion":2}