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