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 pub fn prepare(&self, config: &HttpClientConfig) -> Result<(Url, u16, Vec<u8>), Error> {
29 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 if uri.scheme() != "http" && uri.scheme() != "https" {
39 return Err(Error::ProtoNotSupported(uri.scheme().to_string()));
40 }
41
42 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 let message = self.generate_raw(config, &uri);
54
55 Ok((uri, _port, message))
56 }
57
58 fn generate_raw(&self, config: &HttpClientConfig, uri: &Url) -> Vec<u8> {
60 let mut target = uri.path().to_string();
62 if let Some(query) = uri.query() {
63 target = format!("{}?{}", target, query);
64 }
65
66 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 for (key, value) in config.headers.all().iter() {
87 lines.push(format!("{}: {}", key, value.join("; ")));
88 }
89
90 if let Some(cookie_hdr) = config.cookie.get_http_header(uri) {
92 lines.push(format!("Cookie: {}", cookie_hdr));
93 }
94
95 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 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 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 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 pub fn build(stream: &mut TcpStream) -> Result<Self, Error> {
128
129 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 let (method, path) = Self::parse_first_line(&first_line)?;
139
140 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 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 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 Ok( Self {
171 method,
172 url: format!("http://127.0.0.1{}", path),
173 headers,
174 body
175 })
176
177 }
178
179 pub async fn build_async(stream: &mut tokio::net::TcpStream) -> Result<Self, Error> {
182 let mut reader = tokio::io::BufReader::new(stream);
183
184 let mut first_line = String::new();
186 reader.read_line(&mut first_line).await?;
187 let first_line = first_line.trim();
188
189 let (method, path) = Self::parse_first_line(&first_line)?;
191
192 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 if line.is_empty() {
201 break;
202 }
203 header_lines.push(line.trim().to_string());
204 }
205
206 let headers = HttpHeaders::from_vec(&header_lines);
208
209 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 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 pub async fn build_async_tls(stream: &mut tokio_rustls::server::TlsStream<tokio::net::TcpStream>) -> Result<Self, Error> {
238
239 let mut reader = tokio::io::BufReader::new(stream);
242
243 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 let (method, path) = Self::parse_first_line(&first_line)?;
252
253 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 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 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 Ok( Self {
288 method,
289 url: format!("http://127.0.0.1{}", path),
290 headers,
291 body
292 })
293
294 }
295
296 pub fn parse_first_line(first_line: &str) -> Result<(String, String), Error> {
298
299 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 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 Ok((parts[0].to_uppercase().to_string(), parts[1].to_string()))
317 }
318
319}
320
321