Skip to main content

tower_embed_core/headers/
if_none_match.rs

1use crate::headers::ETag;
2
3/// `If-None-Match` header.
4pub struct IfNoneMatch(Inner);
5
6enum Inner {
7    Any,
8    Tags(http::HeaderValue),
9}
10
11impl IfNoneMatch {
12    /// Validates and creates an [`IfNoneMatch`]` from a HeaderValue.
13    fn from_header_value(value: &http::HeaderValue) -> Option<Self> {
14        let bytes = value.as_bytes();
15        if bytes == b"*" {
16            return Some(Self::any());
17        }
18
19        let etags = bytes.split(|c| *c == b',').map(|etag| etag.trim_ascii());
20        let is_valid = etags.clone().all(|etag| {
21            let is_quoted = etag.starts_with(b"\"") && etag.ends_with(b"\"");
22            let is_ascii = etag.iter().all(|c| c.is_ascii());
23            is_quoted && is_ascii
24        });
25        if !is_valid {
26            return None;
27        }
28
29        Some(Self(Inner::Tags(value.clone())))
30    }
31
32    /// Creates an `If-None-Match` header that matches any ETag.
33    pub fn any() -> IfNoneMatch {
34        IfNoneMatch(Inner::Any)
35    }
36
37    /// Check if the condition passes.
38    pub fn condition_passes(&self, etag: &ETag) -> bool {
39        match self.etags() {
40            None => false,
41            Some(mut etags) => etags.all(|x| !etag.weak_eq(x)),
42        }
43    }
44
45    /// Iterate over the ETags in the `If-None-Match` header.
46    fn etags(&self) -> Option<impl Iterator<Item = &'_ [u8]> + '_> {
47        match &self.0 {
48            Inner::Any => None,
49            Inner::Tags(value) => {
50                let bytes = value.as_bytes();
51                let etags = bytes.split(|c| *c == b',').map(|etag| {
52                    let etag = etag.trim_ascii();
53                    let len = etag.len();
54                    &etag[1..len - 1] // remove surrounding quotes
55                });
56                Some(etags)
57            }
58        }
59    }
60}
61
62impl super::Header for IfNoneMatch {
63    fn header_name() -> http::HeaderName {
64        http::header::IF_NONE_MATCH
65    }
66
67    fn decode(value: &http::HeaderValue) -> Option<Self> {
68        Self::from_header_value(value)
69    }
70
71    fn encode(self) -> http::HeaderValue {
72        match self.0 {
73            Inner::Any => http::HeaderValue::from_static("*"),
74            Inner::Tags(value) => value,
75        }
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn valid_if_none_match() {
85        let header_value = http::HeaderValue::from_static("*");
86        assert!(IfNoneMatch::from_header_value(&header_value).is_some());
87
88        let header_value = http::HeaderValue::from_static(r#""etag""#);
89        assert!(IfNoneMatch::from_header_value(&header_value).is_some());
90
91        let header_value = http::HeaderValue::from_static(r#""etag1","etag2""#);
92        assert!(IfNoneMatch::from_header_value(&header_value).is_some());
93
94        let header_value = http::HeaderValue::from_static(r#""etag1", "etag2""#);
95        assert!(IfNoneMatch::from_header_value(&header_value).is_some());
96    }
97
98    #[test]
99    fn condition_fails() {
100        let etag = ETag::new("etag").unwrap();
101        let weak_etag = ETag::weak("etag").unwrap();
102
103        let if_none_match = {
104            let header_value = http::HeaderValue::from_static(r#""etag""#);
105            IfNoneMatch::from_header_value(&header_value).unwrap()
106        };
107        assert!(!if_none_match.condition_passes(&etag));
108        assert!(!if_none_match.condition_passes(&weak_etag));
109
110        let if_none_match = {
111            let header_value = http::HeaderValue::from_static(r#""unmatched","etag""#);
112            IfNoneMatch::from_header_value(&header_value).unwrap()
113        };
114        assert!(!if_none_match.condition_passes(&etag));
115        assert!(!if_none_match.condition_passes(&weak_etag));
116
117        let if_none_match = IfNoneMatch::any();
118        assert!(!if_none_match.condition_passes(&etag));
119    }
120
121    #[test]
122    fn condition_passes() {
123        let etag = ETag::new("etag").unwrap();
124        let weak_etag = ETag::weak("etag").unwrap();
125
126        let if_none_match = {
127            let header_value = http::HeaderValue::from_static(r#""unmatched""#);
128            IfNoneMatch::from_header_value(&header_value).unwrap()
129        };
130
131        assert!(if_none_match.condition_passes(&etag));
132        assert!(if_none_match.condition_passes(&weak_etag));
133    }
134}