go_http/parse/
transfer.rs1use std::io::{self, Read, Take};
9
10use super::{chunk::ChunkedReader, ParseError};
11use crate::header::Header;
12
13pub enum Body {
19 Limited(Take<Box<dyn Read + Send>>),
21 Chunked(ChunkedReader<Box<dyn Read + Send>>),
23 Unbounded(Box<dyn Read + Send>),
25 Empty,
27}
28
29impl Read for Body {
30 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
31 match self {
32 Self::Limited(r) => r.read(buf),
33 Self::Chunked(r) => r.read(buf),
34 Self::Unbounded(r) => r.read(buf),
35 Self::Empty => Ok(0),
36 }
37 }
38}
39
40impl Body {
41 pub fn read_to_vec(&mut self) -> io::Result<Vec<u8>> {
49 let mut buf = Vec::new();
50 self.read_to_end(&mut buf)?;
51 Ok(buf)
52 }
53}
54
55#[derive(Clone, Copy, PartialEq, Eq)]
62pub enum MessageKind {
63 Request,
64 Response { status: u16, method: Option<RequestMethod> },
65}
66
67#[derive(Clone, Copy, PartialEq, Eq)]
69pub enum RequestMethod {
70 Head,
71 Connect,
72 Other,
73}
74
75pub fn resolve_body(
84 r: Box<dyn Read + Send>,
85 headers: &Header,
86 kind: MessageKind,
87) -> Result<Body, ParseError> {
88 if let MessageKind::Response { status, method } = kind {
90 let no_body = status == 204
91 || status == 304
92 || (100..200).contains(&status)
93 || method == Some(RequestMethod::Head)
94 || method == Some(RequestMethod::Connect) && (200..300).contains(&status);
95 if no_body {
96 return Ok(Body::Empty);
97 }
98 }
99
100 let te = headers.get("Transfer-Encoding").unwrap_or("").to_ascii_lowercase();
102 if te.contains("chunked") {
103 return Ok(Body::Chunked(ChunkedReader::new(r)));
104 }
105
106 if let Some(cl_str) = headers.get("Content-Length") {
108 let n: u64 = cl_str
109 .trim()
110 .parse()
111 .map_err(|_| ParseError::InvalidContentLength)?;
112 return Ok(Body::Limited(r.take(n)));
113 }
114
115 match kind {
118 MessageKind::Request => Ok(Body::Empty),
119 MessageKind::Response { .. } => Ok(Body::Unbounded(r)),
120 }
121}
122
123pub fn write_framing_headers(
129 headers: &mut Header,
130 content_length: Option<u64>,
131) {
132 if let Some(n) = content_length {
133 headers.set("Content-Length", n.to_string());
134 headers.del("Transfer-Encoding");
135 } else {
136 headers.set("Transfer-Encoding", "chunked");
137 headers.del("Content-Length");
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144 use std::io::{Cursor, Read};
145
146 fn boxed(b: &'static [u8]) -> Box<dyn Read + Send> {
147 Box::new(Cursor::new(b))
148 }
149
150 #[test]
151 fn content_length_body() {
152 let mut h = Header::new();
153 h.set("Content-Length", "5");
154 let mut body = resolve_body(boxed(b"Hello World"), &h, MessageKind::Request).unwrap();
155 let mut out = Vec::new();
156 body.read_to_end(&mut out).unwrap();
157 assert_eq!(out, b"Hello");
158 }
159
160 #[test]
161 fn chunked_body() {
162 let raw: &'static [u8] = b"5\r\nHello\r\n0\r\n\r\n";
163 let mut h = Header::new();
164 h.set("Transfer-Encoding", "chunked");
165 let mut body = resolve_body(boxed(raw), &h, MessageKind::Request).unwrap();
166 let mut out = Vec::new();
167 body.read_to_end(&mut out).unwrap();
168 assert_eq!(out, b"Hello");
169 }
170
171 #[test]
172 fn no_content_request() {
173 let h = Header::new();
174 let mut body = resolve_body(boxed(b"leftover"), &h, MessageKind::Request).unwrap();
175 let mut out = Vec::new();
176 body.read_to_end(&mut out).unwrap();
177 assert!(out.is_empty());
178 }
179
180 #[test]
181 fn no_body_204() {
182 let h = Header::new();
183 let mut body = resolve_body(
184 boxed(b"ignored"),
185 &h,
186 MessageKind::Response { status: 204, method: None },
187 )
188 .unwrap();
189 let mut out = Vec::new();
190 body.read_to_end(&mut out).unwrap();
191 assert!(out.is_empty());
192 }
193
194 #[test]
195 fn write_framing_content_length() {
196 let mut h = Header::new();
197 write_framing_headers(&mut h, Some(42));
198 assert_eq!(h.get("Content-Length"), Some("42"));
199 assert_eq!(h.get("Transfer-Encoding"), None);
200 }
201
202 #[test]
203 fn write_framing_chunked() {
204 let mut h = Header::new();
205 write_framing_headers(&mut h, None);
206 assert_eq!(h.get("Transfer-Encoding"), Some("chunked"));
207 assert_eq!(h.get("Content-Length"), None);
208 }
209}