1use std::convert::TryFrom;
2use std::net::{SocketAddr, ToSocketAddrs};
3
4use super::headers::Headers;
5use super::{Method, Scheme};
6use serde::Serialize;
7use url::Url;
8
9use crate::error::Error;
10use crate::proxy::should_proxy;
11
12#[derive(Serialize)]
13pub struct Request {
14 #[serde(skip)]
15 pub url: Url,
16 #[serde(skip)]
17 pub proxy: bool,
18 #[serde(skip)]
19 pub servers: Vec<SocketAddr>,
20 #[serde(rename = "url")]
21 pub full_path: String,
22 pub scheme: Scheme,
23 pub protocol: String,
24 pub method: Method,
25 path: String,
26 pub headers: Headers,
27 #[serde(skip_serializing_if = "Option::is_none")]
28 body: Option<String>,
29 #[serde(skip_serializing_if = "Option::is_none")]
30 query: Option<String>,
31}
32
33impl Request {
34 pub fn new(url: Url, method: Method, headers: Headers) -> Result<Request, Error> {
35 let scheme = Scheme::try_from(url.scheme())?;
36 let url_servers = find_socket_addresses(&url, &scheme)?;
37 let (servers, proxy) = match should_proxy(&url, &url_servers, &scheme)? {
38 Some(servers) => (servers, true),
39 None => (url_servers, false),
40 };
41
42 Ok(Request {
43 proxy,
44 servers,
45 full_path: url.to_string(),
46 scheme,
47 protocol: String::from("HTTP/1.1"),
48 method,
49 path: String::from(url.path()),
50 headers: standard_headers(headers, &url.host().unwrap().to_string()),
51 body: None,
52 query: url.query().map_or_else(|| None, |s| Some(String::from(s))),
53 url,
54 })
55 }
56
57 pub fn with_body(
58 url: Url,
59 method: Method,
60 headers: Headers,
61 body: &str,
62 ) -> Result<Request, Error> {
63 let mut request = Request::new(url, method, headers)?;
64 request.body = Some(body.to_string());
65 request
66 .headers
67 .add("Content-Length", &body.as_bytes().len().to_string());
68 Ok(request)
69 }
70
71 fn build_request(&self, path: &str) -> String {
72 let mut message = self.make_status_line(path);
73 self.add_headers(&mut message);
74 self.add_body(&mut message);
75 message.push_str("\r\n\r\n");
76 message
77 }
78
79 pub fn build(&self) -> String {
80 let path = match (self.proxy, &self.scheme) {
81 (true, Scheme::Http) => &self.full_path,
82 _ => &self.path,
83 };
84 self.build_request(path)
85 }
86
87 fn make_status_line(&self, path: &str) -> String {
88 let path = match &self.query {
89 Some(query) => format!("{path}?{query}"),
90 None => path.to_string(),
91 };
92 format!(
93 "{method} {path} {protocol}\r\n",
94 method = self.method.to_string().to_uppercase(),
95 path = path,
96 protocol = self.protocol,
97 )
98 }
99
100 fn add_body(&self, message: &mut String) {
101 if let Some(body) = &self.body {
102 message.push_str("\r\n");
103 message.push_str(body);
104 }
105 }
106
107 fn add_headers(&self, message: &mut String) {
108 for (key, value_vec) in self.headers.iter() {
109 for val in value_vec {
110 message.push_str(&format!(
111 "{key}: {value}\r\n",
112 key = key,
113 value = val.trim(),
114 ));
115 }
116 }
117 }
118}
119
120fn find_socket_addresses(url: &Url, scheme: &Scheme) -> Result<Vec<SocketAddr>, Error> {
121 let mut server_details = String::new();
122 match url.domain() {
123 Some(domain) => server_details.push_str(domain),
124 None => server_details.push_str(&url.host().unwrap().to_string()),
125 };
126 server_details.push(':');
127 match url.port() {
128 Some(port) => server_details.push_str(&port.to_string()),
129 None => match scheme {
130 Scheme::Https => server_details.push_str("443"),
131 Scheme::Http => server_details.push_str("80"),
132 },
133 }
134 Ok(server_details.to_socket_addrs()?.collect())
135}
136
137fn standard_headers(input_headers: Headers, host: &str) -> Headers {
138 let mut hs = Headers::new();
139 hs.add(
140 "User-Agent",
141 &format!("{}/{}", clap::crate_name!(), clap::crate_version!()),
142 );
143 hs.add("Host", host);
144 hs.add("Connection", "close");
145 hs.append(input_headers);
146 hs
147}