tower_embed_core/headers/
if_none_match.rs1use crate::headers::ETag;
2
3pub struct IfNoneMatch(Inner);
5
6enum Inner {
7 Any,
8 Tags(http::HeaderValue),
9}
10
11impl IfNoneMatch {
12 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 pub fn any() -> IfNoneMatch {
34 IfNoneMatch(Inner::Any)
35 }
36
37 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 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] });
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}