actix_web/guard/
acceptable.rs

1use super::{Guard, GuardContext};
2use crate::http::header::Accept;
3
4/// A guard that verifies that an `Accept` header is present and it contains a compatible MIME type.
5///
6/// An exception is that matching `*/*` must be explicitly enabled because most browsers send this
7/// as part of their `Accept` header for almost every request.
8///
9/// # Examples
10/// ```
11/// use actix_web::{guard::Acceptable, web, HttpResponse};
12///
13/// web::resource("/images")
14///     .guard(Acceptable::new(mime::IMAGE_STAR))
15///     .default_service(web::to(|| async {
16///         HttpResponse::Ok().body("only called when images responses are acceptable")
17///     }));
18/// ```
19#[derive(Debug, Clone)]
20pub struct Acceptable {
21    mime: mime::Mime,
22
23    /// Whether to match `*/*` mime type.
24    ///
25    /// Defaults to false because it's not very useful otherwise.
26    match_star_star: bool,
27}
28
29impl Acceptable {
30    /// Constructs new `Acceptable` guard with the given `mime` type/pattern.
31    pub fn new(mime: mime::Mime) -> Self {
32        Self {
33            mime,
34            match_star_star: false,
35        }
36    }
37
38    /// Allows `*/*` in the `Accept` header to pass the guard check.
39    pub fn match_star_star(mut self) -> Self {
40        self.match_star_star = true;
41        self
42    }
43}
44
45impl Guard for Acceptable {
46    fn check(&self, ctx: &GuardContext<'_>) -> bool {
47        let accept = match ctx.header::<Accept>() {
48            Some(hdr) => hdr,
49            None => return false,
50        };
51
52        let target_type = self.mime.type_();
53        let target_subtype = self.mime.subtype();
54
55        for mime in accept.0.into_iter().map(|q| q.item) {
56            return match (mime.type_(), mime.subtype()) {
57                (typ, subtype) if typ == target_type && subtype == target_subtype => true,
58                (typ, mime::STAR) if typ == target_type => true,
59                (mime::STAR, mime::STAR) if self.match_star_star => true,
60                _ => continue,
61            };
62        }
63
64        false
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71    use crate::{http::header, test::TestRequest};
72
73    #[test]
74    fn test_acceptable() {
75        let req = TestRequest::default().to_srv_request();
76        assert!(!Acceptable::new(mime::APPLICATION_JSON).check(&req.guard_ctx()));
77
78        let req = TestRequest::default()
79            .insert_header((header::ACCEPT, "application/json"))
80            .to_srv_request();
81        assert!(Acceptable::new(mime::APPLICATION_JSON).check(&req.guard_ctx()));
82
83        let req = TestRequest::default()
84            .insert_header((header::ACCEPT, "text/html, application/json"))
85            .to_srv_request();
86        assert!(Acceptable::new(mime::APPLICATION_JSON).check(&req.guard_ctx()));
87    }
88
89    #[test]
90    fn test_acceptable_star() {
91        let req = TestRequest::default()
92            .insert_header((header::ACCEPT, "text/html, */*;q=0.8"))
93            .to_srv_request();
94
95        assert!(Acceptable::new(mime::APPLICATION_JSON)
96            .match_star_star()
97            .check(&req.guard_ctx()));
98    }
99}