go_http/parse/
response.rs1use std::io::Read;
5
6use super::{read_headers, read_line, ParseError};
7use crate::header::Header;
8use crate::parse::transfer::{resolve_body, Body, MessageKind, RequestMethod};
9
10pub struct ParsedResponse {
12 pub proto: String,
13 pub proto_major: u8,
14 pub proto_minor: u8,
15 pub status: u16,
16 pub status_text: String,
17 pub header: Header,
18 pub body: Body,
19 pub content_length: i64,
20 pub transfer_encoding: Vec<String>,
21}
22
23impl std::fmt::Debug for ParsedResponse {
24 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25 f.debug_struct("ParsedResponse")
26 .field("status", &self.status)
27 .field("status_text", &self.status_text)
28 .finish_non_exhaustive()
29 }
30}
31
32pub fn read_response(
39 r: impl Read + Send + 'static,
40 req_method: Option<&str>,
41 max_header_bytes: usize,
42) -> Result<ParsedResponse, ParseError> {
43 let mut r: Box<dyn Read + Send> = Box::new(r);
44
45 let line = read_line(&mut r)?;
47
48 let sp1 = line.find(' ').ok_or(ParseError::BadStatusLine)?;
50 let proto = &line[..sp1];
51 let rest = line[sp1 + 1..].trim_start();
52
53 let (proto_major, proto_minor) = parse_proto(proto)?;
54
55 let (status_str, status_text) = match rest.find(' ') {
56 Some(i) => (&rest[..i], rest[i + 1..].to_owned()),
57 None => (rest, String::new()),
58 };
59
60 let status: u16 = status_str
61 .parse()
62 .map_err(|_| ParseError::BadStatusLine)?;
63
64 if !(100..=999).contains(&status) {
65 return Err(ParseError::BadStatusLine);
66 }
67
68 let header = read_headers(&mut r, max_header_bytes)?;
70
71 let transfer_encoding: Vec<String> = header
73 .values("Transfer-Encoding")
74 .iter()
75 .flat_map(|v| v.split(',').map(|s| s.trim().to_ascii_lowercase()))
76 .collect();
77
78 let content_length = match header.get("Content-Length") {
80 None => -1,
81 Some(s) => s.trim().parse::<i64>().map_err(|_| ParseError::InvalidContentLength)?,
82 };
83
84 let method = match req_method.map(|m| m.to_ascii_uppercase()).as_deref() {
86 Some("HEAD") => Some(RequestMethod::Head),
87 Some("CONNECT") => Some(RequestMethod::Connect),
88 _ => Some(RequestMethod::Other),
89 };
90
91 let body = resolve_body(
92 r,
93 &header,
94 MessageKind::Response { status, method },
95 )?;
96
97 Ok(ParsedResponse {
98 proto: proto.to_owned(),
99 proto_major,
100 proto_minor,
101 status,
102 status_text,
103 header,
104 body,
105 content_length,
106 transfer_encoding,
107 })
108}
109
110fn parse_proto(proto: &str) -> Result<(u8, u8), ParseError> {
115 if !proto.starts_with("HTTP/") {
116 return Err(ParseError::BadStatusLine);
117 }
118 let ver = &proto[5..];
119 let dot = ver.find('.').ok_or(ParseError::BadStatusLine)?;
120 let major: u8 = ver[..dot].parse().map_err(|_| ParseError::BadStatusLine)?;
121 let minor: u8 = ver[dot + 1..].parse().map_err(|_| ParseError::BadStatusLine)?;
122 Ok((major, minor))
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128 use std::io::{Cursor, Read};
129
130 fn parse(raw: &'static [u8], method: Option<&str>) -> ParsedResponse {
131 read_response(Cursor::new(raw), method, 65536).unwrap()
132 }
133
134 #[test]
135 fn simple_200() {
136 let resp = parse(
137 b"HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nHello",
138 Some("GET"),
139 );
140 assert_eq!(resp.status, 200);
141 assert_eq!(resp.status_text, "OK");
142 assert_eq!(resp.proto_major, 1);
143 assert_eq!(resp.proto_minor, 1);
144 assert_eq!(resp.content_length, 5);
145 }
146
147 #[test]
148 fn head_response_no_body() {
149 let raw = b"HTTP/1.1 200 OK\r\nContent-Length: 100\r\n\r\n";
150 let mut resp = read_response(Cursor::new(raw.as_ref()), Some("HEAD"), 65536).unwrap();
151 let mut out = Vec::new();
152 resp.body.read_to_end(&mut out).unwrap();
153 assert!(out.is_empty());
154 }
155
156 #[test]
157 fn no_content_204() {
158 let raw = b"HTTP/1.1 204 No Content\r\n\r\n";
159 let mut resp = parse(raw, Some("DELETE"));
160 let mut out = Vec::new();
161 resp.body.read_to_end(&mut out).unwrap();
162 assert!(out.is_empty());
163 }
164
165 #[test]
166 fn chunked_response() {
167 let raw = b"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nHello\r\n0\r\n\r\n";
168 let mut resp = parse(raw, Some("GET"));
169 let mut out = Vec::new();
170 resp.body.read_to_end(&mut out).unwrap();
171 assert_eq!(out, b"Hello");
172 }
173
174 #[test]
175 fn bad_status_code() {
176 let result = read_response(
177 Cursor::new(b"HTTP/1.1 999 Bad\r\n\r\n"),
178 None,
179 65536,
180 );
181 assert!(result.is_ok(), "status 999 should be valid");
184 let result2 = read_response(
185 Cursor::new(b"HTTP/1.1 abc Bad\r\n\r\n"),
186 None,
187 65536,
188 );
189 assert_eq!(result2.unwrap_err(), ParseError::BadStatusLine);
190 }
191}