atlas_http/
request.rs

1use super::{HttpBody, HttpClientConfig, HttpHeaders, ProxyType};
2use crate::error::Error;
3use url::Url;
4use std::io::{BufRead, BufReader, Read};
5use std::net::TcpStream;
6//use std::io::BufReader as TokioBufReader;
7use tokio::io::AsyncBufReadExt;
8use tokio::io::AsyncBufRead;
9
10#[derive(Clone, Debug)]
11pub struct HttpRequest {
12    pub method: String,
13    pub url: String,
14    pub headers: HttpHeaders,
15    pub body: HttpBody,
16}
17
18impl HttpRequest {
19    pub fn new(method: &str, url: &str, headers: &Vec<&str>, body: &HttpBody) -> Self {
20        Self {
21            method: method.to_uppercase().to_string(),
22            url: url.to_string(),
23            headers: HttpHeaders::from_vec(&headers.iter().map(|s| s.to_string()).collect()),
24            body: body.clone(),
25        }
26    }
27
28    // Validate URL and scheme
29    pub fn prepare(&self, config: &HttpClientConfig) -> Result<(Url, u16, Vec<u8>), Error> {
30        // Parse url
31        let uri = match Url::parse(&self.url) {
32            Ok(r) => r,
33            Err(_err) => {
34                return Err(Error::InvalidUri(self.url.clone()));
35            }
36        };
37
38        // Check scheme
39        if uri.scheme() != "http" && uri.scheme() != "https" {
40            return Err(Error::ProtoNotSupported(uri.scheme().to_string()));
41        }
42
43        // Get port
44        let mut _port: u16 = 0;
45        if uri.port().is_none() && uri.scheme() == "https" {
46            _port = 443;
47        } else if uri.port().is_none() && uri.scheme() == "http" {
48            _port = 80;
49        } else {
50            _port = uri.port().unwrap();
51        }
52
53        // Generate message
54        let message = self.generate_raw(config, &uri);
55
56        Ok((uri, _port, message))
57    }
58
59    /// Generate raw HTTP message to be sent
60    fn generate_raw(&self, config: &HttpClientConfig, uri: &Url) -> Vec<u8> {
61        // Get target
62        let mut target = uri.path().to_string();
63        if let Some(query) = uri.query() {
64            target = format!("{}?{}", target, query);
65        }
66
67        // Modify target for proxy, if needed
68        if config.proxy_type != ProxyType::None {
69            target = format!(
70                "{}://{}{}",
71                uri.scheme(),
72                uri.host_str().unwrap(),
73                uri.path()
74            );
75        }
76
77        let mut lines = vec![
78            format!("{} {} HTTP/1.1", &self.method, target),
79            format!("Host: {}", uri.host_str().unwrap()),
80        ];
81
82        if let Some(ua) = &config.user_agent {
83            lines.push(format!("User-Agent: {}", ua));
84        }
85
86        // HTTP client headers
87        for (key, value) in config.headers.all().iter() {
88            lines.push(format!("{}: {}", key, value.join("; ")));
89        }
90
91        // Cookie header
92        if let Some(cookie_hdr) = config.cookie.get_http_header(uri) {
93            lines.push(format!("Cookie: {}", cookie_hdr));
94        }
95
96        // POST headers
97        if !self.body.files().is_empty() && !self.headers.has_lower("content-type") {
98            lines.push(format!(
99                "Content-type: multipart/form-data; boundary={}",
100                self.body.boundary()
101            ));
102        } else if self.body.is_form_post() && !self.headers.has_lower("content-type") {
103            lines.push("Content-type: application/x-www-form-urlencoded".to_string());
104        }
105
106        // Format post body, if needed
107        let mut post_body: Vec<u8> = Vec::new();
108        if self.body.is_form_post() {
109            post_body = self.body.format();
110            lines.push(format!("Content-length: {}", post_body.len()));
111        }
112
113        // HTTP request headers
114        for (key, value) in self.headers.all().iter() {
115            lines.push(format!("{}: {}", key, value.join("; ")));
116        }
117        lines.push("\r\n".to_string());
118
119        // Add body
120        let mut message = lines.join("\r\n").as_bytes().to_vec();
121        message.extend(post_body);
122        message.extend_from_slice("\r\n".as_bytes());
123
124        message
125    }
126
127    /// Build from buf reader
128    pub fn build(stream: &mut TcpStream) -> Result<Self, Error> {
129
130        // Get first line
131        let mut reader = BufReader::new(stream);
132        let mut first_line = String::new();
133        match reader.read_line(&mut first_line) {
134            Ok(_) => {}
135            Err(e) => return Err(Error::Custom("Invalid first line".to_string()))
136        };
137
138        // Parse first line
139        let (method, path) = Self::parse_first_line(&first_line)?;
140
141        // Get headers
142        let mut header_lines = Vec::new();
143        loop {
144            let mut line = String::new();
145            match reader.read_line(&mut line) {
146                Ok(_) => {}
147                Err(e) => return Err(Error::Custom("Unable to read from incoming connection.".to_string()))
148            };
149
150            if line.trim().is_empty() {
151                break;
152            }
153            header_lines.push(line.trim().to_string());
154        }
155        let headers = HttpHeaders::from_vec(&header_lines);
156
157        // Read body from buffer
158        let length: usize = headers.get_lower_line("content-length").unwrap_or("0".to_string()).parse::<usize>().unwrap();
159        let mut body_bytes = vec![0; length];
160        let bytes_read = reader.read(&mut body_bytes).unwrap();
161        let body_str: String = String::from_utf8_lossy(&body_bytes).to_string();
162
163        // Get body
164        let body = if headers.has_lower("content-type") && headers.get_lower_line("content-type").unwrap() == "application/x-www-form-urlencoded".to_string() {
165            HttpBody::from_string(&body_str.as_str())
166        } else {
167            HttpBody::from_raw(&body_bytes)
168        };
169
170        // Return
171        Ok( Self {
172            method,
173            url: format!("http://127.0.0.1{}", path),
174            headers,
175            body
176        })
177
178    }
179
180    /// Build request from stream asynchronously
181    pub async fn build_async(stream: &mut tokio::net::TcpStream) -> Result<Self, Error> {
182
183        // Read into buffer
184        //let (reader, mut writer) = tokio::io::split(stream);
185        let mut reader = tokio::io::BufReader::new(stream);
186
187        // Get first line
188        let mut first_line = String::new();
189        match reader.read_line(&mut first_line).await {
190            Ok(_) => {}
191            Err(e) => return Err(Error::Custom("Invalid first line".to_string()))
192        };
193
194        // Parse first line
195        let (method, path) = Self::parse_first_line(&first_line)?;
196
197        // Get headers
198        let mut header_lines = Vec::new();
199        loop {
200            let mut line = String::new();
201            let n = match reader.read_line(&mut line).await {
202                Ok(r) => r,
203                Err(e) => return Err(Error::Custom("Unable to read from incoming connection.".to_string()))
204            };
205
206            if n == 0 || line.trim().is_empty() {
207                break;
208            }
209            header_lines.push(line.trim().to_string());
210        }
211        let headers = HttpHeaders::from_vec(&header_lines);
212
213        // Read body from buffer
214        let length: usize = headers.get_lower_line("content-length").unwrap_or("0".to_string()).parse::<usize>().unwrap();
215        let mut body_bytes = vec![0; length];
216        let mut body_str = String::new();
217
218        if length > 0 {
219            let body_bytes = reader.fill_buf().await.unwrap();
220            body_str = String::from_utf8_lossy(&body_bytes).to_string();
221        }
222
223        // Get body
224        let body = if headers.has_lower("content-type") && headers.get_lower_line("content-type").unwrap() == "application/x-www-form-urlencoded".to_string() {
225            HttpBody::from_string(&body_str.as_str())
226        } else {
227            HttpBody::from_raw(&body_bytes)
228        };
229
230        // Return
231        Ok( Self {
232            method,
233            url: format!("http://127.0.0.1{}", path),
234            headers,
235            body
236        })
237
238    }
239
240    /// Parse first line
241    pub fn parse_first_line(first_line: &str) -> Result<(String, String), Error> {
242
243        // Split into parts
244        let parts = first_line.split(" ").collect::<Vec<&str>>();
245        if parts.len() != 3 {
246            return Err(Error::Custom("Invalid first line.".to_string()));
247        } else if !parts[2].starts_with("HTTP/") {
248            return Err(Error::Custom("Invalid first line.".to_string()));
249        } else if !vec!["GET","POST","PUT","DELETE","HEAD","OPTIONS"].contains(&parts[0].to_uppercase().as_str()) {
250            return Err(Error::Custom("Invalid first line.".to_string()));
251        }
252
253        // Validate path
254        let url = match Url::parse(&format!("http://example.com{}", parts[1])) {
255            Ok(url) => url,
256            Err(_) => return Err(Error::Custom("Invalid first line.".to_string()))
257        };
258
259        // Return
260        Ok((parts[0].to_uppercase().to_string(), parts[1].to_string()))
261    }
262
263
264}
265
266