atlas_http/
response.rs

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    /// Instantiate response with minimal properties
18    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    /// Instantiate new response with all properties
29    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    /// Get protocol version
46    pub fn version(&self) -> String {
47        self.version.clone()
48    }
49
50    /// Get HTTP status code
51    pub fn status_code(&self) -> u16 {
52        self.status_code
53    }
54
55    /// Get status reason message
56    pub fn reason(&self) -> String {
57        self.reason.clone()
58    }
59
60    /// Get http headers
61    pub fn headers(&self) -> HttpHeaders {
62        self.headers.clone()
63    }
64
65    /// Get body of response
66    pub fn body(&self) -> String {
67        self.body.clone()
68    }
69
70    /// Get the raw response including headers and body
71    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    /// Read first line and header of response
88    pub fn read_header(
89        reader: &mut Box<dyn BufRead>,
90        req: &HttpRequest,
91        dest_file: &str,
92    ) -> Result<Self, Error> {
93        // Get first line
94        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        // Parse first line
106        let (version, status, reason) = Self::parse_first_line(&first_line, req)?;
107
108        // Get headers
109        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        // Chunked transfer encoding
130        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        // Get body
138        let mut body = String::new();
139        if dest_file.is_empty() {
140            reader.read_to_string(&mut body);
141        }
142
143        // Get response
144        let res = Self::new_full(&status, &headers, &body, &version, &reason);
145        Ok(res)
146    }
147
148    /// Parse first line
149    pub fn parse_first_line(
150        first_line: &str,
151        req: &HttpRequest,
152    ) -> Result<(String, u16, String), Error> {
153        // Parse first line
154        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}