siderite_core/middleware/
hosts.rs1use super::TrustedProxies;
4use super::adapt::{BoxService, Next, from_fn, impl_layer};
5use crate::body::Body;
6use crate::error::ApiError;
7use crate::extract::Request;
8use crate::response::{IntoResponse, Response};
9use http::uri::Authority;
10use http::{HeaderValue, StatusCode, header};
11use std::sync::Arc;
12use tower::Layer;
13
14fn bad_request(message: &str) -> Response {
15 ApiError::bad_request(message).into_response()
16}
17
18fn request_authority(req: &Request) -> Option<Authority> {
20 let mut hosts = req.headers().get_all(header::HOST).iter();
21 let raw = match hosts.next() {
22 Some(value) => value.to_str().ok()?,
23 None => req.uri().authority()?.as_str(),
24 };
25 if hosts.next().is_some() {
26 return None;
27 }
28 let authority = raw.parse::<Authority>().ok()?;
29 valid_authority(&authority).then_some(authority)
30}
31
32fn valid_authority(authority: &Authority) -> bool {
33 let raw = authority.as_str();
34 let host = authority.host();
35 if raw.contains('@') || host.is_empty() {
36 return false;
37 }
38 raw.strip_prefix(host).is_some_and(|suffix| {
39 suffix.is_empty()
40 || suffix
41 .strip_prefix(':')
42 .is_some_and(|port| port.parse::<u16>().is_ok())
43 })
44}
45
46fn request_host(req: &Request) -> Option<String> {
47 let authority = request_authority(req)?;
48 Some(
49 authority
50 .host()
51 .trim_start_matches('[')
52 .trim_end_matches(']')
53 .to_ascii_lowercase(),
54 )
55}
56
57#[derive(Debug, Clone)]
63pub struct TrustedHosts {
64 patterns: Arc<[String]>,
65}
66
67impl TrustedHosts {
68 pub fn new<I, S>(patterns: I) -> Self
70 where
71 I: IntoIterator<Item = S>,
72 S: AsRef<str>,
73 {
74 let patterns: Vec<String> = patterns
75 .into_iter()
76 .map(|p| p.as_ref().to_ascii_lowercase())
77 .collect();
78 Self {
79 patterns: patterns.into(),
80 }
81 }
82
83 fn allows(&self, host: &str) -> bool {
84 self.patterns.iter().any(|p| match p.strip_prefix("*.") {
85 Some(suffix) => host
86 .strip_suffix(suffix)
87 .is_some_and(|head| head.ends_with('.') && head.len() > 1),
88 None => p == "*" || p == host,
89 })
90 }
91
92 fn wrap(&self, inner: BoxService) -> BoxService {
93 let this = self.clone();
94 from_fn(move |req: Request, next: Next| {
95 let allowed = request_host(&req).is_some_and(|h| this.allows(&h));
96 async move {
97 if allowed {
98 next.run(req).await
99 } else {
100 bad_request("Invalid or missing Host header.")
101 }
102 }
103 })
104 .layer(inner)
105 }
106}
107
108#[derive(Debug, Clone, Default)]
116pub struct HttpsRedirect {
117 proxies: TrustedProxies,
118 canonical: Option<Authority>,
119 allowed: Option<TrustedHosts>,
120}
121
122impl HttpsRedirect {
123 pub fn new() -> Self {
125 Self::default()
126 }
127
128 #[must_use]
136 pub fn trusted_proxies(mut self, proxies: TrustedProxies) -> Self {
137 self.proxies = proxies;
138 self
139 }
140
141 #[must_use]
149 pub fn trust_forwarded_proto(self, trust: bool) -> Self {
150 self.trusted_proxies(if trust {
151 TrustedProxies::dangerously_trust_all()
152 } else {
153 TrustedProxies::default()
154 })
155 }
156
157 #[must_use]
165 pub fn canonical_authority(mut self, authority: Authority) -> Self {
166 self.canonical = Some(authority);
167 self
168 }
169
170 #[must_use]
178 pub fn allowed_hosts(mut self, hosts: TrustedHosts) -> Self {
179 self.allowed = Some(hosts);
180 self
181 }
182
183 fn wrap(&self, inner: BoxService) -> BoxService {
184 let this = self.clone();
185 from_fn(move |req: Request, next: Next| {
186 let secure = this
187 .proxies
188 .resolve(&req)
189 .map(|context| context.scheme == "https");
190 let incoming = request_authority(&req);
191 let host = request_host(&req);
192 let allowed = match this.allowed.as_ref() {
193 Some(p) => host.as_ref().is_some_and(|h| p.allows(h)),
194 None => true,
195 };
196 let canonical = this.canonical.clone();
197 async move {
198 if !allowed {
199 return bad_request("Invalid or disallowed Host header.");
200 }
201 let secure = match secure {
202 Ok(secure) => secure,
203 Err(_) => {
204 return bad_request("Invalid forwarded scheme.");
205 }
206 };
207 if secure {
208 return next.run(req).await;
209 }
210 let Some(incoming) = incoming else {
211 return bad_request("Invalid or missing Host header.");
212 };
213 let host = match canonical.as_ref() {
214 Some(value) if valid_authority(value) => value.as_str(),
215 Some(_) => {
216 return bad_request("Invalid canonical authority.");
217 }
218 None => incoming.host(),
219 };
220 let path = req.uri().path_and_query();
221 let target = path.map_or("/", |pq| pq.as_str());
222 let uri = format!("https://{host}{target}");
223 match HeaderValue::from_str(&uri) {
224 Ok(location) => {
225 let mut response = Response::new(Body::empty());
226 let status = StatusCode::PERMANENT_REDIRECT;
227 *response.status_mut() = status;
228 let headers = response.headers_mut();
229 headers.insert(header::LOCATION, location);
230 response
231 }
232 Err(_) => bad_request("Invalid redirect target."),
233 }
234 }
235 })
236 .layer(inner)
237 }
238}
239
240impl_layer!(TrustedHosts, HttpsRedirect);