1use std::{
53 cell::{Ref, RefMut},
54 rc::Rc,
55};
56
57use actix_http::{header, Extensions, Method as HttpMethod, RequestHead};
58
59use crate::{http::header::Header, service::ServiceRequest, HttpMessage as _};
60
61mod acceptable;
62mod host;
63
64pub use self::{
65 acceptable::Acceptable,
66 host::{Host, HostGuard},
67};
68
69#[cfg(feature = "experimental-introspection")]
71#[non_exhaustive]
72#[derive(Debug, Clone)]
73pub enum GuardDetail {
74 HttpMethods(Vec<String>),
76 Headers(Vec<(String, String)>),
78 Generic(String),
80}
81
82#[derive(Debug)]
84pub struct GuardContext<'a> {
85 pub(crate) req: &'a ServiceRequest,
86}
87
88impl<'a> GuardContext<'a> {
89 #[inline]
91 pub fn head(&self) -> &RequestHead {
92 self.req.head()
93 }
94
95 #[inline]
97 pub fn req_data(&self) -> Ref<'a, Extensions> {
98 self.req.extensions()
99 }
100
101 #[inline]
103 pub fn req_data_mut(&self) -> RefMut<'a, Extensions> {
104 self.req.extensions_mut()
105 }
106
107 #[inline]
123 pub fn header<H: Header>(&self) -> Option<H> {
124 H::parse(self.req).ok()
125 }
126
127 #[inline]
129 pub fn app_data<T: 'static>(&self) -> Option<&T> {
130 self.req.app_data()
131 }
132}
133
134pub trait Guard {
138 fn check(&self, ctx: &GuardContext<'_>) -> bool;
140
141 #[cfg(feature = "experimental-introspection")]
143 fn name(&self) -> String {
144 std::any::type_name::<Self>().to_string()
145 }
146
147 #[cfg(feature = "experimental-introspection")]
151 fn details(&self) -> Option<Vec<GuardDetail>> {
152 None
153 }
154}
155
156impl Guard for Rc<dyn Guard> {
157 fn check(&self, ctx: &GuardContext<'_>) -> bool {
158 (**self).check(ctx)
159 }
160
161 #[cfg(feature = "experimental-introspection")]
162 fn name(&self) -> String {
163 (**self).name()
164 }
165
166 #[cfg(feature = "experimental-introspection")]
167 fn details(&self) -> Option<Vec<GuardDetail>> {
168 (**self).details()
169 }
170}
171
172pub fn fn_guard<F>(f: F) -> impl Guard
185where
186 F: Fn(&GuardContext<'_>) -> bool,
187{
188 FnGuard(f)
189}
190
191struct FnGuard<F: Fn(&GuardContext<'_>) -> bool>(F);
192
193impl<F> Guard for FnGuard<F>
194where
195 F: Fn(&GuardContext<'_>) -> bool,
196{
197 fn check(&self, ctx: &GuardContext<'_>) -> bool {
198 (self.0)(ctx)
199 }
200}
201
202impl<F> Guard for F
203where
204 F: Fn(&GuardContext<'_>) -> bool,
205{
206 fn check(&self, ctx: &GuardContext<'_>) -> bool {
207 (self)(ctx)
208 }
209}
210
211#[allow(non_snake_case)]
225pub fn Any<F: Guard + 'static>(guard: F) -> AnyGuard {
226 AnyGuard {
227 guards: vec![Box::new(guard)],
228 }
229}
230
231pub struct AnyGuard {
237 guards: Vec<Box<dyn Guard>>,
238}
239
240impl AnyGuard {
241 pub fn or<F: Guard + 'static>(mut self, guard: F) -> Self {
243 self.guards.push(Box::new(guard));
244 self
245 }
246}
247
248impl Guard for AnyGuard {
249 #[inline]
250 fn check(&self, ctx: &GuardContext<'_>) -> bool {
251 for guard in &self.guards {
252 if guard.check(ctx) {
253 return true;
254 }
255 }
256
257 false
258 }
259
260 #[cfg(feature = "experimental-introspection")]
261 fn name(&self) -> String {
262 format!(
263 "AnyGuard({})",
264 self.guards
265 .iter()
266 .map(|g| g.name())
267 .collect::<Vec<_>>()
268 .join(", ")
269 )
270 }
271
272 #[cfg(feature = "experimental-introspection")]
273 fn details(&self) -> Option<Vec<GuardDetail>> {
274 Some(
275 self.guards
276 .iter()
277 .flat_map(|g| g.details().unwrap_or_default())
278 .collect(),
279 )
280 }
281}
282
283#[allow(non_snake_case)]
299pub fn All<F: Guard + 'static>(guard: F) -> AllGuard {
300 AllGuard {
301 guards: vec![Box::new(guard)],
302 }
303}
304
305pub struct AllGuard {
311 guards: Vec<Box<dyn Guard>>,
312}
313
314impl AllGuard {
315 pub fn and<F: Guard + 'static>(mut self, guard: F) -> Self {
317 self.guards.push(Box::new(guard));
318 self
319 }
320}
321
322impl Guard for AllGuard {
323 #[inline]
324 fn check(&self, ctx: &GuardContext<'_>) -> bool {
325 for guard in &self.guards {
326 if !guard.check(ctx) {
327 return false;
328 }
329 }
330
331 true
332 }
333
334 #[cfg(feature = "experimental-introspection")]
335 fn name(&self) -> String {
336 format!(
337 "AllGuard({})",
338 self.guards
339 .iter()
340 .map(|g| g.name())
341 .collect::<Vec<_>>()
342 .join(", ")
343 )
344 }
345
346 #[cfg(feature = "experimental-introspection")]
347 fn details(&self) -> Option<Vec<GuardDetail>> {
348 Some(
349 self.guards
350 .iter()
351 .flat_map(|g| g.details().unwrap_or_default())
352 .collect(),
353 )
354 }
355}
356
357pub struct Not<G>(pub G);
369
370impl<G: Guard> Guard for Not<G> {
371 #[inline]
372 fn check(&self, ctx: &GuardContext<'_>) -> bool {
373 !self.0.check(ctx)
374 }
375
376 #[cfg(feature = "experimental-introspection")]
377 fn name(&self) -> String {
378 format!("Not({})", self.0.name())
379 }
380
381 #[cfg(feature = "experimental-introspection")]
382 fn details(&self) -> Option<Vec<GuardDetail>> {
383 Some(vec![GuardDetail::Generic(self.name())])
384 }
385}
386
387#[allow(non_snake_case)]
389pub fn Method(method: HttpMethod) -> impl Guard {
390 MethodGuard(method)
391}
392
393#[derive(Debug, Clone)]
394pub(crate) struct RegisteredMethods(pub(crate) Vec<HttpMethod>);
395
396#[derive(Debug)]
398pub(crate) struct MethodGuard(HttpMethod);
399
400impl Guard for MethodGuard {
401 fn check(&self, ctx: &GuardContext<'_>) -> bool {
402 let registered = ctx.req_data_mut().remove::<RegisteredMethods>();
403
404 if let Some(mut methods) = registered {
405 methods.0.push(self.0.clone());
406 ctx.req_data_mut().insert(methods);
407 } else {
408 ctx.req_data_mut()
409 .insert(RegisteredMethods(vec![self.0.clone()]));
410 }
411
412 ctx.head().method == self.0
413 }
414
415 #[cfg(feature = "experimental-introspection")]
416 fn name(&self) -> String {
417 self.0.to_string()
418 }
419
420 #[cfg(feature = "experimental-introspection")]
421 fn details(&self) -> Option<Vec<GuardDetail>> {
422 Some(vec![GuardDetail::HttpMethods(vec![self.0.to_string()])])
423 }
424}
425
426macro_rules! method_guard {
427 ($method_fn:ident, $method_const:ident) => {
428 #[doc = concat!("Creates a guard that matches the `", stringify!($method_const), "` request method.")]
429 #[doc = concat!("The route in this example will only respond to `", stringify!($method_const), "` requests.")]
432 #[doc = concat!(" .guard(guard::", stringify!($method_fn), "())")]
437 #[allow(non_snake_case)]
440 pub fn $method_fn() -> impl Guard {
441 MethodGuard(HttpMethod::$method_const)
442 }
443 };
444}
445
446method_guard!(Get, GET);
447method_guard!(Post, POST);
448method_guard!(Put, PUT);
449method_guard!(Delete, DELETE);
450method_guard!(Head, HEAD);
451method_guard!(Options, OPTIONS);
452method_guard!(Connect, CONNECT);
453method_guard!(Patch, PATCH);
454method_guard!(Trace, TRACE);
455
456#[allow(non_snake_case)]
470pub fn Query() -> impl Guard {
471 MethodGuard(HttpMethod::from_bytes(b"QUERY").unwrap())
472}
473
474#[allow(non_snake_case)]
487pub fn Header(name: &'static str, value: &'static str) -> impl Guard {
488 HeaderGuard(
489 header::HeaderName::try_from(name).unwrap(),
490 header::HeaderValue::from_static(value),
491 )
492}
493
494struct HeaderGuard(header::HeaderName, header::HeaderValue);
495
496impl Guard for HeaderGuard {
497 fn check(&self, ctx: &GuardContext<'_>) -> bool {
498 if let Some(val) = ctx.head().headers.get(&self.0) {
499 return val == self.1;
500 }
501
502 false
503 }
504
505 #[cfg(feature = "experimental-introspection")]
506 fn name(&self) -> String {
507 format!("Header({}, {})", self.0, self.1.to_str().unwrap_or(""))
508 }
509
510 #[cfg(feature = "experimental-introspection")]
511 fn details(&self) -> Option<Vec<GuardDetail>> {
512 Some(vec![GuardDetail::Headers(vec![(
513 self.0.to_string(),
514 self.1.to_str().unwrap_or("").to_string(),
515 )])])
516 }
517}
518
519#[cfg(test)]
520mod tests {
521 use actix_http::Method;
522
523 use super::*;
524 use crate::test::TestRequest;
525
526 #[test]
527 fn header_match() {
528 let req = TestRequest::default()
529 .insert_header((header::TRANSFER_ENCODING, "chunked"))
530 .to_srv_request();
531
532 let hdr = Header("transfer-encoding", "chunked");
533 assert!(hdr.check(&req.guard_ctx()));
534
535 let hdr = Header("transfer-encoding", "other");
536 assert!(!hdr.check(&req.guard_ctx()));
537
538 let hdr = Header("content-type", "chunked");
539 assert!(!hdr.check(&req.guard_ctx()));
540
541 let hdr = Header("content-type", "other");
542 assert!(!hdr.check(&req.guard_ctx()));
543 }
544
545 #[test]
546 fn method_guards() {
547 let get_req = TestRequest::get().to_srv_request();
548 let post_req = TestRequest::post().to_srv_request();
549
550 assert!(Get().check(&get_req.guard_ctx()));
551 assert!(!Get().check(&post_req.guard_ctx()));
552
553 assert!(Post().check(&post_req.guard_ctx()));
554 assert!(!Post().check(&get_req.guard_ctx()));
555
556 let req = TestRequest::put().to_srv_request();
557 assert!(Put().check(&req.guard_ctx()));
558 assert!(!Put().check(&get_req.guard_ctx()));
559
560 let req = TestRequest::patch().to_srv_request();
561 assert!(Patch().check(&req.guard_ctx()));
562 assert!(!Patch().check(&get_req.guard_ctx()));
563
564 let r = TestRequest::delete().to_srv_request();
565 assert!(Delete().check(&r.guard_ctx()));
566 assert!(!Delete().check(&get_req.guard_ctx()));
567
568 let req = TestRequest::default().method(Method::HEAD).to_srv_request();
569 assert!(Head().check(&req.guard_ctx()));
570 assert!(!Head().check(&get_req.guard_ctx()));
571
572 let req = TestRequest::default()
573 .method(Method::OPTIONS)
574 .to_srv_request();
575 assert!(Options().check(&req.guard_ctx()));
576 assert!(!Options().check(&get_req.guard_ctx()));
577
578 let req = TestRequest::default()
579 .method(Method::CONNECT)
580 .to_srv_request();
581 assert!(Connect().check(&req.guard_ctx()));
582 assert!(!Connect().check(&get_req.guard_ctx()));
583
584 let req = TestRequest::default()
585 .method(Method::TRACE)
586 .to_srv_request();
587 assert!(Trace().check(&req.guard_ctx()));
588 assert!(!Trace().check(&get_req.guard_ctx()));
589
590 let query_method = Method::from_bytes(b"QUERY").unwrap();
591 let req = TestRequest::default().method(query_method).to_srv_request();
592 assert!(Query().check(&req.guard_ctx()));
593 assert!(!Query().check(&get_req.guard_ctx()));
594
595 let other_req = TestRequest::default()
596 .method(Method::from_bytes(b"NOT_A_QUERY").unwrap())
597 .to_srv_request();
598 assert!(!Query().check(&other_req.guard_ctx()));
599 }
600
601 #[test]
602 fn aggregate_any() {
603 let req = TestRequest::default()
604 .method(Method::TRACE)
605 .to_srv_request();
606
607 assert!(Any(Trace()).check(&req.guard_ctx()));
608 assert!(Any(Trace()).or(Get()).check(&req.guard_ctx()));
609 assert!(!Any(Get()).or(Get()).check(&req.guard_ctx()));
610 }
611
612 #[test]
613 fn aggregate_all() {
614 let req = TestRequest::default()
615 .method(Method::TRACE)
616 .to_srv_request();
617
618 assert!(All(Trace()).check(&req.guard_ctx()));
619 assert!(All(Trace()).and(Trace()).check(&req.guard_ctx()));
620 assert!(!All(Trace()).and(Get()).check(&req.guard_ctx()));
621 }
622
623 #[test]
624 fn nested_not() {
625 let req = TestRequest::default().to_srv_request();
626
627 let get = Get();
628 assert!(get.check(&req.guard_ctx()));
629
630 let not_get = Not(get);
631 assert!(!not_get.check(&req.guard_ctx()));
632
633 let not_not_get = Not(not_get);
634 assert!(not_not_get.check(&req.guard_ctx()));
635 }
636
637 #[test]
638 fn function_guard() {
639 let domain = "rust-lang.org".to_owned();
640 let guard = fn_guard(|ctx| ctx.head().uri.host().unwrap().ends_with(&domain));
641
642 let req = TestRequest::default()
643 .uri("blog.rust-lang.org")
644 .to_srv_request();
645 assert!(guard.check(&req.guard_ctx()));
646
647 let req = TestRequest::default().uri("crates.io").to_srv_request();
648 assert!(!guard.check(&req.guard_ctx()));
649 }
650
651 #[test]
652 fn mega_nesting() {
653 let guard = fn_guard(|ctx| All(Not(Any(Not(Trace())))).check(ctx));
654
655 let req = TestRequest::default().to_srv_request();
656 assert!(!guard.check(&req.guard_ctx()));
657
658 let req = TestRequest::default()
659 .method(Method::TRACE)
660 .to_srv_request();
661 assert!(guard.check(&req.guard_ctx()));
662 }
663
664 #[test]
665 fn app_data() {
666 const TEST_VALUE: u32 = 42;
667 let guard = fn_guard(|ctx| ctx.app_data::<u32>() == Some(&TEST_VALUE));
668
669 let req = TestRequest::default().app_data(TEST_VALUE).to_srv_request();
670 assert!(guard.check(&req.guard_ctx()));
671
672 let req = TestRequest::default()
673 .app_data(TEST_VALUE * 2)
674 .to_srv_request();
675 assert!(!guard.check(&req.guard_ctx()));
676 }
677}