1#![allow(clippy::large_enum_variant)]
2
3use super::{HttpHeaders, HttpRequest};
4use crate::error::{Error, InvalidFirstLineError, InvalidResponseError};
5use std::io::BufRead;
6
7#[derive(Clone, Debug)]
8pub struct HttpResponse {
9 version: String,
10 status_code: u16,
11 reason: String,
12 headers: HttpHeaders,
13 body: String,
14}
15
16impl HttpResponse {
17 pub fn new(status: &u16, headers: &Vec<String>, body: &String) -> Self {
19 Self::new_full(
20 status,
21 &HttpHeaders::from_vec(headers),
22 body,
23 &"1.1".to_string(),
24 &"".to_string(),
25 )
26 }
27
28 pub fn new_full(
30 status: &u16,
31 headers: &HttpHeaders,
32 body: &String,
33 version: &String,
34 reason: &String,
35 ) -> Self {
36 Self {
37 version: version.clone(),
38 status_code: *status,
39 reason: reason.clone(),
40 headers: headers.clone(),
41 body: body.trim().trim_end_matches('0').to_string(),
42 }
43 }
44
45 pub fn version(&self) -> String {
47 self.version.clone()
48 }
49
50 pub fn status_code(&self) -> u16 {
52 self.status_code
53 }
54
55 pub fn reason(&self) -> String {
57 self.reason.clone()
58 }
59
60 pub fn headers(&self) -> HttpHeaders {
62 self.headers.clone()
63 }
64
65 pub fn body(&self) -> String {
67 self.body.clone()
68 }
69
70 pub fn raw(&self) -> String {
72 let headers_str = self
73 .headers
74 .all()
75 .iter()
76 .map(|(key, value)| format!("{}: {}", key, value.join("; ")))
77 .collect::<Vec<String>>()
78 .join("\r\n");
79
80 let res = format!(
81 "HTTP/{} {} {}\r\n{}\n\n{}\n\n",
82 self.version, self.status_code, self.reason, &headers_str, self.body
83 );
84 res.to_string()
85 }
86
87 pub fn read_header(
89 reader: &mut Box<dyn BufRead>,
90 req: &HttpRequest,
91 dest_file: &str,
92 ) -> Result<Self, Error> {
93 let mut first_line = String::new();
95 match reader.read_line(&mut first_line) {
96 Ok(_) => {}
97 Err(e) => {
98 return Err(Error::NoRead(InvalidResponseError {
99 url: req.url.clone(),
100 response: e.to_string(),
101 }));
102 }
103 };
104
105 let (version, status, reason) = Self::parse_first_line(&first_line, req)?;
107
108 let mut header_lines = Vec::new();
110 loop {
111 let mut line = String::new();
112 match reader.read_line(&mut line) {
113 Ok(_) => {}
114 Err(e) => {
115 return Err(Error::NoRead(InvalidResponseError {
116 url: req.url.clone(),
117 response: e.to_string(),
118 }));
119 }
120 };
121
122 if line.trim().is_empty() {
123 break;
124 }
125 header_lines.push(line.trim().to_string());
126 }
127 let headers = HttpHeaders::from_vec(&header_lines);
128
129 if headers.has_lower("transfer-encoding")
131 && headers.get_lower("transfer-encoding").unwrap().as_str() == "chunked"
132 {
133 let mut _tmp = String::new();
134 reader.read_line(&mut _tmp).unwrap();
135 }
136
137 let mut body = String::new();
139 if dest_file.is_empty() {
140 reader.read_to_string(&mut body);
141 }
142
143 let res = Self::new_full(&status, &headers, &body, &version, &reason);
145 Ok(res)
146 }
147
148 pub fn parse_first_line(
150 first_line: &str,
151 req: &HttpRequest,
152 ) -> Result<(String, u16, String), Error> {
153 let mut is_valid = true;
155 let parts = first_line
156 .trim_start_matches("HTTP/")
157 .split(' ')
158 .collect::<Vec<&str>>();
159 if !["1.0", "1.1", "2", "3"].contains(&parts[0]) {
160 is_valid = false;
161 } else if parts[1].len() != 3 || !parts[1].chars().all(|c| c.is_ascii_digit()) {
162 is_valid = false;
163 }
164
165 if !is_valid {
166 let error = InvalidFirstLineError {
167 request: req.clone(),
168 first_line: first_line.to_string(),
169 };
170 return Err(Error::InvalidFirstLine(error));
171 }
172
173 Ok((
174 parts[0].to_string(),
175 parts[1].parse::<u16>().unwrap(),
176 parts[2].to_string(),
177 ))
178 }
179}