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;
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 pub fn prepare(&self, config: &HttpClientConfig) -> Result<(Url, u16, Vec<u8>), Error> {
30 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 if uri.scheme() != "http" && uri.scheme() != "https" {
40 return Err(Error::ProtoNotSupported(uri.scheme().to_string()));
41 }
42
43 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 let message = self.generate_raw(config, &uri);
55
56 Ok((uri, _port, message))
57 }
58
59 fn generate_raw(&self, config: &HttpClientConfig, uri: &Url) -> Vec<u8> {
61 let mut target = uri.path().to_string();
63 if let Some(query) = uri.query() {
64 target = format!("{}?{}", target, query);
65 }
66
67 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 for (key, value) in config.headers.all().iter() {
88 lines.push(format!("{}: {}", key, value.join("; ")));
89 }
90
91 if let Some(cookie_hdr) = config.cookie.get_http_header(uri) {
93 lines.push(format!("Cookie: {}", cookie_hdr));
94 }
95
96 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 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 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 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 pub fn build(stream: &mut TcpStream) -> Result<Self, Error> {
129
130 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 let (method, path) = Self::parse_first_line(&first_line)?;
140
141 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 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 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 Ok( Self {
172 method,
173 url: format!("http://127.0.0.1{}", path),
174 headers,
175 body
176 })
177
178 }
179
180 pub async fn build_async(stream: &mut tokio::net::TcpStream) -> Result<Self, Error> {
182
183 let mut reader = tokio::io::BufReader::new(stream);
186
187 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 let (method, path) = Self::parse_first_line(&first_line)?;
196
197 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 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 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 Ok( Self {
232 method,
233 url: format!("http://127.0.0.1{}", path),
234 headers,
235 body
236 })
237
238 }
239
240 pub fn parse_first_line(first_line: &str) -> Result<(String, String), Error> {
242
243 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 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 Ok((parts[0].to_uppercase().to_string(), parts[1].to_string()))
261 }
262
263
264}
265
266