1use actix_http::{header, uri::Uri, RequestHead, Version};
2
3use super::{Guard, GuardContext};
4
5#[allow(non_snake_case)]
58pub fn Host(host: impl AsRef<str>) -> HostGuard {
59 HostGuard {
60 host: host.as_ref().to_string(),
61 scheme: None,
62 }
63}
64
65fn get_host_uri(req: &RequestHead) -> Option<Uri> {
66 req.headers
67 .get(header::HOST)
68 .and_then(|host_value| host_value.to_str().ok())
69 .filter(|_| req.version < Version::HTTP_2)
70 .or_else(|| req.uri.host())
71 .and_then(|host| host.parse().ok())
72}
73
74#[doc(hidden)]
75pub struct HostGuard {
76 host: String,
77 scheme: Option<String>,
78}
79
80impl HostGuard {
81 pub fn scheme<H: AsRef<str>>(mut self, scheme: H) -> HostGuard {
83 self.scheme = Some(scheme.as_ref().to_string());
84 self
85 }
86}
87
88impl Guard for HostGuard {
89 fn check(&self, ctx: &GuardContext<'_>) -> bool {
90 let req_host_uri = match get_host_uri(ctx.head()) {
92 Some(uri) => uri,
93
94 None => return false,
96 };
97
98 match req_host_uri.host() {
99 Some(uri_host) if self.host == uri_host => {}
101
102 _ => return false,
106 }
107
108 if let Some(ref scheme) = self.scheme {
109 if let Some(ref req_host_uri_scheme) = req_host_uri.scheme_str() {
110 return scheme == req_host_uri_scheme;
111 }
112
113 }
116
117 true
119 }
120
121 #[cfg(feature = "experimental-introspection")]
122 fn name(&self) -> String {
123 if let Some(ref scheme) = self.scheme {
124 format!("Host({}, scheme={})", self.host, scheme)
125 } else {
126 format!("Host({})", self.host)
127 }
128 }
129
130 #[cfg(feature = "experimental-introspection")]
131 fn details(&self) -> Option<Vec<super::GuardDetail>> {
132 let mut details = vec![super::GuardDetail::Headers(vec![(
133 "host".to_string(),
134 self.host.clone(),
135 )])];
136
137 if let Some(ref scheme) = self.scheme {
138 details.push(super::GuardDetail::Generic(format!("scheme={scheme}")));
139 }
140
141 Some(details)
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148 use crate::test::TestRequest;
149
150 #[test]
151 fn host_not_from_header_if_http2() {
152 let req = TestRequest::default()
153 .uri("www.rust-lang.org")
154 .insert_header((
155 header::HOST,
156 header::HeaderValue::from_static("www.example.com"),
157 ))
158 .to_srv_request();
159
160 let host = Host("www.example.com");
161 assert!(host.check(&req.guard_ctx()));
162
163 let host = Host("www.rust-lang.org");
164 assert!(!host.check(&req.guard_ctx()));
165
166 let req = TestRequest::default()
167 .version(actix_http::Version::HTTP_2)
168 .uri("www.rust-lang.org")
169 .insert_header((
170 header::HOST,
171 header::HeaderValue::from_static("www.example.com"),
172 ))
173 .to_srv_request();
174
175 let host = Host("www.example.com");
176 assert!(!host.check(&req.guard_ctx()));
177
178 let host = Host("www.rust-lang.org");
179 assert!(host.check(&req.guard_ctx()));
180 }
181
182 #[test]
183 fn host_from_header() {
184 let req = TestRequest::default()
185 .insert_header((
186 header::HOST,
187 header::HeaderValue::from_static("www.rust-lang.org"),
188 ))
189 .to_srv_request();
190
191 let host = Host("www.rust-lang.org");
192 assert!(host.check(&req.guard_ctx()));
193
194 let host = Host("www.rust-lang.org").scheme("https");
195 assert!(host.check(&req.guard_ctx()));
196
197 let host = Host("blog.rust-lang.org");
198 assert!(!host.check(&req.guard_ctx()));
199
200 let host = Host("blog.rust-lang.org").scheme("https");
201 assert!(!host.check(&req.guard_ctx()));
202
203 let host = Host("crates.io");
204 assert!(!host.check(&req.guard_ctx()));
205
206 let host = Host("localhost");
207 assert!(!host.check(&req.guard_ctx()));
208 }
209
210 #[test]
211 fn host_without_header() {
212 let req = TestRequest::default()
213 .uri("www.rust-lang.org")
214 .to_srv_request();
215
216 let host = Host("www.rust-lang.org");
217 assert!(host.check(&req.guard_ctx()));
218
219 let host = Host("www.rust-lang.org").scheme("https");
220 assert!(host.check(&req.guard_ctx()));
221
222 let host = Host("blog.rust-lang.org");
223 assert!(!host.check(&req.guard_ctx()));
224
225 let host = Host("blog.rust-lang.org").scheme("https");
226 assert!(!host.check(&req.guard_ctx()));
227
228 let host = Host("crates.io");
229 assert!(!host.check(&req.guard_ctx()));
230
231 let host = Host("localhost");
232 assert!(!host.check(&req.guard_ctx()));
233 }
234
235 #[test]
236 fn host_scheme() {
237 let req = TestRequest::default()
238 .insert_header((
239 header::HOST,
240 header::HeaderValue::from_static("https://www.rust-lang.org"),
241 ))
242 .to_srv_request();
243
244 let host = Host("www.rust-lang.org").scheme("https");
245 assert!(host.check(&req.guard_ctx()));
246
247 let host = Host("www.rust-lang.org");
248 assert!(host.check(&req.guard_ctx()));
249
250 let host = Host("www.rust-lang.org").scheme("http");
251 assert!(!host.check(&req.guard_ctx()));
252
253 let host = Host("blog.rust-lang.org");
254 assert!(!host.check(&req.guard_ctx()));
255
256 let host = Host("blog.rust-lang.org").scheme("https");
257 assert!(!host.check(&req.guard_ctx()));
258
259 let host = Host("crates.io").scheme("https");
260 assert!(!host.check(&req.guard_ctx()));
261
262 let host = Host("localhost");
263 assert!(!host.check(&req.guard_ctx()));
264 }
265
266 #[cfg(feature = "experimental-introspection")]
267 #[test]
268 fn host_guard_details_include_host_and_scheme() {
269 let host = Host("example.com").scheme("https");
270 let details = host.details().expect("missing guard details");
271
272 assert!(details.iter().any(|detail| match detail {
273 crate::guard::GuardDetail::Headers(headers) => headers
274 .iter()
275 .any(|(name, value)| name == "host" && value == "example.com"),
276 _ => false,
277 }));
278 assert!(details.iter().any(|detail| match detail {
279 crate::guard::GuardDetail::Generic(value) => value == "scheme=https",
280 _ => false,
281 }));
282 assert_eq!(host.name(), "Host(example.com, scheme=https)");
283 }
284}