1pub mod body;
2pub mod cache;
3pub mod client;
4pub mod dns;
5pub mod headers;
6pub mod middleware;
7pub mod proxy;
8pub mod server;
9pub mod shed;
10
11use axum::response::{IntoResponse, Redirect};
12use http::{HeaderMap, Method, Request, StatusCode, Uri, Version, header::HOST, uri::PathAndQuery};
13
14#[cfg(feature = "clients-hyper")]
15pub use client::clients_hyper::{HyperClient, HyperClientLeastLoaded};
16pub use client::clients_reqwest::{
17 ReqwestClient, ReqwestClientLeastLoaded, ReqwestClientRoundRobin,
18};
19pub use server::{Server, ServerBuilder};
20use url::Url;
21
22use crate::{http::headers::X_FORWARDED_HOST, network::AsyncReadWrite};
23
24pub fn calc_headers_size(h: &HeaderMap) -> usize {
28 h.iter().map(|(k, v)| k.as_str().len() + v.len() + 2).sum()
29}
30
31pub const fn http_version(v: Version) -> &'static str {
33 match v {
34 Version::HTTP_09 => "0.9",
35 Version::HTTP_10 => "1.0",
36 Version::HTTP_11 => "1.1",
37 Version::HTTP_2 => "2.0",
38 Version::HTTP_3 => "3.0",
39 _ => "-",
40 }
41}
42
43pub const fn http_method(v: &Method) -> &'static str {
45 match *v {
46 Method::OPTIONS => "OPTIONS",
47 Method::GET => "GET",
48 Method::POST => "POST",
49 Method::PUT => "PUT",
50 Method::DELETE => "DELETE",
51 Method::HEAD => "HEAD",
52 Method::TRACE => "TRACE",
53 Method::CONNECT => "CONNECT",
54 Method::PATCH => "PATCH",
55 _ => "",
56 }
57}
58
59pub fn extract_host(host_port: &str) -> Option<&str> {
62 if host_port.is_empty() {
63 return None;
64 }
65
66 if host_port.as_bytes()[0] == b'[' {
68 host_port.find(']').map(|i| &host_port[1..i])
69 } else {
70 host_port.split(':').next()
71 }
72 .filter(|x| !x.is_empty())
73}
74
75pub fn extract_authority<T>(request: &Request<T>) -> Option<&str> {
78 request
80 .headers()
81 .get(X_FORWARDED_HOST)
82 .and_then(|x| x.to_str().ok())
83 .or_else(|| request.uri().authority().map(|x| x.host()))
85 .or_else(|| request.headers().get(HOST).and_then(|x| x.to_str().ok()))
87 .and_then(extract_host)
89}
90
91#[derive(thiserror::Error, Debug)]
93pub enum UrlToUriError {
94 #[error("No Authority")]
95 NoAuthority,
96 #[error("No Host")]
97 NoHost,
98 #[error(transparent)]
99 Http(#[from] http::Error),
100}
101
102pub fn url_to_uri(url: &Url) -> Result<Uri, UrlToUriError> {
104 if !url.has_authority() {
105 return Err(UrlToUriError::NoAuthority);
106 }
107
108 if !url.has_host() {
109 return Err(UrlToUriError::NoHost);
110 }
111
112 let scheme = url.scheme();
113 let authority = url.authority();
114
115 let authority_end = scheme.len() + "://".len() + authority.len();
116 let path_and_query = &url.as_str()[authority_end..];
117
118 Uri::builder()
119 .scheme(scheme)
120 .authority(authority)
121 .path_and_query(path_and_query)
122 .build()
123 .map_err(UrlToUriError::Http)
124}
125
126pub async fn redirect_to_https(
128 request: axum::extract::Request,
129) -> Result<impl IntoResponse, impl IntoResponse> {
130 let host = extract_authority(&request)
131 .ok_or((StatusCode::BAD_REQUEST, "Unable to extract authority"))?;
132 let uri = request.uri().clone();
133
134 let fallback_path = PathAndQuery::from_static("/");
135 let pq = uri.path_and_query().unwrap_or(&fallback_path).as_str();
136
137 Ok::<_, (_, _)>(Redirect::permanent(
138 &Uri::builder()
139 .scheme("https")
140 .authority(host)
141 .path_and_query(pq)
142 .build()
143 .map_err(|_| (StatusCode::BAD_REQUEST, "Incorrect URL"))?
144 .to_string(),
145 ))
146}
147
148#[cfg(test)]
149mod test {
150 use axum::{Router, body::Body};
151 use http::{
152 Uri,
153 header::{HOST, LOCATION},
154 };
155 use tower::ServiceExt;
156
157 use crate::hval;
158
159 use super::*;
160
161 #[test]
162 fn test_extract_host() {
163 assert_eq!(extract_host("foo.bar"), Some("foo.bar"));
164 assert_eq!(extract_host("foo.bar:443"), Some("foo.bar"));
165 assert_eq!(extract_host("foo.bar:"), Some("foo.bar"));
166 assert_eq!(extract_host("foo:443"), Some("foo"));
167
168 assert_eq!(extract_host("127.0.0.1:443"), Some("127.0.0.1"));
169 assert_eq!(extract_host("[::1]:443"), Some("::1"));
170
171 assert_eq!(
172 extract_host("[fe80::b696:91ff:fe84:3ae8]"),
173 Some("fe80::b696:91ff:fe84:3ae8")
174 );
175 assert_eq!(
176 extract_host("[fe80::b696:91ff:fe84:3ae8]:123"),
177 Some("fe80::b696:91ff:fe84:3ae8")
178 );
179
180 assert_eq!(extract_host("[fe80::b696:91ff:fe84:3ae8:123"), None);
182 assert_eq!(extract_host(""), None);
184 assert_eq!(extract_host("[]:443"), None);
185 }
186
187 #[test]
188 fn test_extract_authority() {
189 let mut req = Request::new(());
191 *req.uri_mut() = Uri::builder()
192 .path_and_query("/foo?bar=baz")
193 .build()
194 .unwrap();
195 assert_eq!(extract_authority(&req), None);
196
197 let mut req = Request::new(());
199 *req.uri_mut() = Uri::builder()
200 .scheme("http")
201 .authority("foo.bar:443")
202 .path_and_query("/foo?bar=baz")
203 .build()
204 .unwrap();
205 assert_eq!(extract_authority(&req), Some("foo.bar"));
206
207 let mut req = Request::new(());
208 *req.uri_mut() = Uri::builder()
209 .scheme("http")
210 .authority("[::1]:443")
211 .path_and_query("/foo?bar=baz")
212 .build()
213 .unwrap();
214 assert_eq!(extract_authority(&req), Some("::1"));
215
216 let mut req = Request::new(());
218 *req.uri_mut() = Uri::builder()
219 .path_and_query("/foo?bar=baz")
220 .build()
221 .unwrap();
222 (*req.headers_mut()).insert(HOST, hval!("foo.baz:443"));
223 assert_eq!(extract_authority(&req), Some("foo.baz"));
224
225 let mut req = Request::new(());
227 *req.uri_mut() = Uri::builder()
228 .path_and_query("/foo?bar=baz")
229 .build()
230 .unwrap();
231 (*req.headers_mut()).insert(X_FORWARDED_HOST, hval!("foo.baz:443"));
232 assert_eq!(extract_authority(&req), Some("foo.baz"));
233
234 let mut req = Request::new(());
236 *req.uri_mut() = Uri::builder()
237 .scheme("http")
238 .authority("foo.bar:443")
239 .path_and_query("/foo?bar=baz")
240 .build()
241 .unwrap();
242 (*req.headers_mut()).insert(HOST, hval!("foo.baz:443"));
243 assert_eq!(extract_authority(&req), Some("foo.bar"));
244
245 let mut req = Request::new(());
247 *req.uri_mut() = Uri::builder()
248 .scheme("http")
249 .authority("foo.bar:443")
250 .path_and_query("/foo?bar=baz")
251 .build()
252 .unwrap();
253 (*req.headers_mut()).insert(HOST, hval!("foo.baz:443"));
254 (*req.headers_mut()).insert(X_FORWARDED_HOST, hval!("dead.beef:443"));
255 assert_eq!(extract_authority(&req), Some("dead.beef"));
256 }
257
258 #[test]
259 fn test_url_to_uri() {
260 let url = "https://foo.bar/baz?dead=beef".parse().unwrap();
261
262 assert_eq!(
263 url_to_uri(&url).unwrap(),
264 Uri::from_static("https://foo.bar/baz?dead=beef")
265 );
266
267 let url = "unix:/foo/bar".parse().unwrap();
268 assert!(url_to_uri(&url).is_err());
269 }
270
271 #[tokio::test]
272 async fn test_redirect_to_https() {
273 let mut request = axum::extract::Request::new(Body::empty());
274 *request.uri_mut() = Uri::from_static("http://foo/bar/baz.bin?a=b");
275
276 let router = Router::new().fallback(redirect_to_https);
277
278 let response = router.oneshot(request).await.unwrap();
279 let location = response.headers().get(LOCATION).unwrap().to_str().unwrap();
280 assert_eq!(location, "https://foo/bar/baz.bin?a=b");
281 }
282}