{"id":"aaecc72085fd8a31","repo":"hyperium/hyper","slug":"invalid-trailer-end-lf","errorCode":null,"errorMessage":"Invalid trailer end LF","messagePattern":"Invalid trailer end LF","errorType":"exception","errorClass":"std::io::Error","httpStatus":null,"severity":"error","filePath":"src/proto/h1/decode.rs","lineNumber":578,"sourceCode":"        match byte {\n            b'\\n' => {\n                if *trailers_cnt >= h1_max_headers {\n                    return Poll::Ready(Err(io::Error::new(\n                        io::ErrorKind::InvalidData,\n                        \"chunk trailers count overflow\",\n                    )));\n                }\n                *trailers_cnt += 1;\n\n                put_u8!(\n                    trailers_buf.as_mut().expect(\"trailers_buf is None\"),\n                    byte,\n                    h1_max_header_size\n                );\n\n                Poll::Ready(Ok(ChunkedState::EndCr))\n            }\n            _ => Poll::Ready(Err(io::Error::new(\n                io::ErrorKind::InvalidInput,\n                \"Invalid trailer end LF\",\n            ))),\n        }\n    }\n\n    fn read_end_cr<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'\\r' => {\n                if let Some(trailers_buf) = trailers_buf {\n                    put_u8!(trailers_buf, byte, h1_max_header_size);\n                }","sourceCodeStart":560,"sourceCodeEnd":596,"githubUrl":"https://github.com/hyperium/hyper/blob/084473f728f9d07b3be5845475aa2f62ed9ff579/src/proto/h1/decode.rs#L560-L596","documentation":"Thrown in read_trailer_lf (src/proto/h1/decode.rs:578) for a trailer line that began with '\\r' (transitioning to TrailerLf) but whose next byte is not '\\n'. A trailer header must be terminated by CRLF; a CR not followed by LF yields io::ErrorKind::InvalidInput.","triggerScenarios":"A trailer block like \"0\\r\\nbad\\r\\r\\n\" where a trailer line's CR is followed by another CR or other byte instead of LF, or any trailer whose terminator lost its LF.","commonSituations":"A proxy that mangles newlines in trailers; a custom encoder that writes \"\\r\" only at end of each trailer; corrupted bytes near the end of the body.","solutions":["Ensure every trailer line ends with a true CRLF (\\r\\n).","Validate/normalize newline handling for trailer-encoding middleware.","Write the trailer block from a single buffer so CR and LF can't be split."],"exampleFix":"// before: trailer written with lone CR\nwrite!(w, \"0\\r\\nx-trace: abc\\r\").await?; // -> error 30\nwrite!(w, \"\\r\\n\").await?;\n\n// after: each trailer terminated by CRLF, then blank line\nwrite!(w, \"0\\r\\nx-trace: abc\\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, \"trailer CR not followed by LF\");\n        break;\n    }\n    return Err(e.into());\n}","preventionTips":["Terminate every trailer line with a full CRLF and end the block with a blank CRLF line.","Build the trailer block in one buffer and write it once, so CR/LF aren't split.","Check newline handling in any component that rewrites trailers."],"tags":["http","http1","chunked","trailers","framing","hyper","rust"],"analyzedSha":"084473f728f9d07b3be5845475aa2f62ed9ff579","analyzedAt":"2026-08-06T01:20:18.522Z","schemaVersion":2}