/rust/registry/src/index.crates.io-1949cf8c6b5b557f/headers-0.4.1/src/util/entity.rs
Line | Count | Source |
1 | | use std::fmt; |
2 | | |
3 | | use http::HeaderValue; |
4 | | |
5 | | use super::{FlatCsv, IterExt}; |
6 | | use crate::Error; |
7 | | |
8 | | /// An entity tag, defined in [RFC7232](https://tools.ietf.org/html/rfc7232#section-2.3) |
9 | | /// |
10 | | /// An entity tag consists of a string enclosed by two literal double quotes. |
11 | | /// Preceding the first double quote is an optional weakness indicator, |
12 | | /// which always looks like `W/`. Examples for valid tags are `"xyzzy"` and `W/"xyzzy"`. |
13 | | /// |
14 | | /// # ABNF |
15 | | /// |
16 | | /// ```text |
17 | | /// entity-tag = [ weak ] opaque-tag |
18 | | /// weak = %x57.2F ; "W/", case-sensitive |
19 | | /// opaque-tag = DQUOTE *etagc DQUOTE |
20 | | /// etagc = %x21 / %x23-7E / obs-text |
21 | | /// ; VCHAR except double quotes, plus obs-text |
22 | | /// ``` |
23 | | /// |
24 | | /// # Comparison |
25 | | /// To check if two entity tags are equivalent in an application always use the `strong_eq` or |
26 | | /// `weak_eq` methods based on the context of the Tag. Only use `==` to check if two tags are |
27 | | /// identical. |
28 | | /// |
29 | | /// The example below shows the results for a set of entity-tag pairs and |
30 | | /// both the weak and strong comparison function results: |
31 | | /// |
32 | | /// | ETag 1 | ETag 2 | Strong Comparison | Weak Comparison | |
33 | | /// |---------|---------|-------------------|-----------------| |
34 | | /// | `W/"1"` | `W/"1"` | no match | match | |
35 | | /// | `W/"1"` | `W/"2"` | no match | no match | |
36 | | /// | `W/"1"` | `"1"` | no match | match | |
37 | | /// | `"1"` | `"1"` | match | match | |
38 | | #[derive(Clone, Eq, PartialEq)] |
39 | | pub(crate) struct EntityTag<T = HeaderValue>(T); |
40 | | |
41 | | #[derive(Clone, Debug, PartialEq)] |
42 | | pub(crate) enum EntityTagRange { |
43 | | Any, |
44 | | Tags(FlatCsv), |
45 | | } |
46 | | |
47 | | // ===== impl EntityTag ===== |
48 | | |
49 | | impl<T: AsRef<[u8]>> EntityTag<T> { |
50 | | /// Get the tag. |
51 | 0 | pub(crate) fn tag(&self) -> &[u8] { |
52 | 0 | let bytes = self.0.as_ref(); |
53 | 0 | let end = bytes.len() - 1; |
54 | 0 | if bytes[0] == b'W' { |
55 | | // W/"<tag>" |
56 | 0 | &bytes[3..end] |
57 | | } else { |
58 | | // "<tag>" |
59 | 0 | &bytes[1..end] |
60 | | } |
61 | 0 | } Unexecuted instantiation: <headers::util::entity::EntityTag<&str>>::tag Unexecuted instantiation: <headers::util::entity::EntityTag>::tag |
62 | | |
63 | | /// Return if this is a "weak" tag. |
64 | 0 | pub(crate) fn is_weak(&self) -> bool { |
65 | 0 | self.0.as_ref()[0] == b'W' |
66 | 0 | } Unexecuted instantiation: <headers::util::entity::EntityTag<&str>>::is_weak Unexecuted instantiation: <headers::util::entity::EntityTag>::is_weak |
67 | | |
68 | | /// For strong comparison two entity-tags are equivalent if both are not weak and their |
69 | | /// opaque-tags match character-by-character. |
70 | 0 | pub(crate) fn strong_eq<R>(&self, other: &EntityTag<R>) -> bool |
71 | 0 | where |
72 | 0 | R: AsRef<[u8]>, |
73 | | { |
74 | 0 | !self.is_weak() && !other.is_weak() && self.tag() == other.tag() |
75 | 0 | } Unexecuted instantiation: <headers::util::entity::EntityTag<&str>>::strong_eq::<http::header::value::HeaderValue> Unexecuted instantiation: <headers::util::entity::EntityTag>::strong_eq::<http::header::value::HeaderValue> |
76 | | |
77 | | /// For weak comparison two entity-tags are equivalent if their |
78 | | /// opaque-tags match character-by-character, regardless of either or |
79 | | /// both being tagged as "weak". |
80 | 0 | pub(crate) fn weak_eq<R>(&self, other: &EntityTag<R>) -> bool |
81 | 0 | where |
82 | 0 | R: AsRef<[u8]>, |
83 | | { |
84 | 0 | self.tag() == other.tag() |
85 | 0 | } |
86 | | |
87 | | /// The inverse of `EntityTag.strong_eq()`. |
88 | | #[cfg(test)] |
89 | | pub(crate) fn strong_ne(&self, other: &EntityTag) -> bool { |
90 | | !self.strong_eq(other) |
91 | | } |
92 | | |
93 | | /// The inverse of `EntityTag.weak_eq()`. |
94 | | #[cfg(test)] |
95 | | pub(crate) fn weak_ne(&self, other: &EntityTag) -> bool { |
96 | | !self.weak_eq(other) |
97 | | } |
98 | | |
99 | 0 | pub(crate) fn parse(src: T) -> Option<Self> { |
100 | 0 | let slice = src.as_ref(); |
101 | 0 | let length = slice.len(); |
102 | | |
103 | | // Early exits if it doesn't terminate in a DQUOTE. |
104 | 0 | if length < 2 || slice[length - 1] != b'"' { |
105 | 0 | return None; |
106 | 0 | } |
107 | | |
108 | 0 | let start = match slice[0] { |
109 | | // "<tag>" |
110 | 0 | b'"' => 1, |
111 | | // W/"<tag>" |
112 | | b'W' => { |
113 | 0 | if length >= 4 && slice[1] == b'/' && slice[2] == b'"' { |
114 | 0 | 3 |
115 | | } else { |
116 | 0 | return None; |
117 | | } |
118 | | } |
119 | 0 | _ => return None, |
120 | | }; |
121 | | |
122 | 0 | if check_slice_validity(&slice[start..length - 1]) { |
123 | 0 | Some(EntityTag(src)) |
124 | | } else { |
125 | 0 | None |
126 | | } |
127 | 0 | } Unexecuted instantiation: <headers::util::entity::EntityTag<&[u8]>>::parse Unexecuted instantiation: <headers::util::entity::EntityTag<&str>>::parse |
128 | | } |
129 | | |
130 | | impl EntityTag { |
131 | | /* |
132 | | /// Constructs a new EntityTag. |
133 | | /// # Panics |
134 | | /// If the tag contains invalid characters. |
135 | | pub fn new(weak: bool, tag: String) -> EntityTag { |
136 | | assert!(check_slice_validity(&tag), "Invalid tag: {:?}", tag); |
137 | | EntityTag { weak: weak, tag: tag } |
138 | | } |
139 | | |
140 | | /// Constructs a new weak EntityTag. |
141 | | /// # Panics |
142 | | /// If the tag contains invalid characters. |
143 | | pub fn weak(tag: String) -> EntityTag { |
144 | | EntityTag::new(true, tag) |
145 | | } |
146 | | |
147 | | /// Constructs a new strong EntityTag. |
148 | | /// # Panics |
149 | | /// If the tag contains invalid characters. |
150 | | pub fn strong(tag: String) -> EntityTag { |
151 | | EntityTag::new(false, tag) |
152 | | } |
153 | | */ |
154 | | |
155 | | #[cfg(test)] |
156 | | pub fn from_static(bytes: &'static str) -> EntityTag { |
157 | | let val = HeaderValue::from_static(bytes); |
158 | | match EntityTag::from_val(&val) { |
159 | | Some(tag) => tag, |
160 | | None => { |
161 | | panic!("invalid static string for EntityTag: {:?}", bytes); |
162 | | } |
163 | | } |
164 | | } |
165 | | |
166 | 0 | pub(crate) fn from_owned(val: HeaderValue) -> Option<EntityTag> { |
167 | 0 | EntityTag::parse(val.as_bytes())?; |
168 | 0 | Some(EntityTag(val)) |
169 | 0 | } |
170 | | |
171 | 0 | pub(crate) fn from_val(val: &HeaderValue) -> Option<EntityTag> { |
172 | 0 | EntityTag::parse(val.as_bytes()).map(|_entity| EntityTag(val.clone())) |
173 | 0 | } |
174 | | } |
175 | | |
176 | | impl<T: fmt::Debug> fmt::Debug for EntityTag<T> { |
177 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
178 | 0 | self.0.fmt(f) |
179 | 0 | } |
180 | | } |
181 | | |
182 | | impl super::TryFromValues for EntityTag { |
183 | 0 | fn try_from_values<'i, I>(values: &mut I) -> Result<Self, Error> |
184 | 0 | where |
185 | 0 | I: Iterator<Item = &'i HeaderValue>, |
186 | | { |
187 | 0 | values |
188 | 0 | .just_one() |
189 | 0 | .and_then(EntityTag::from_val) |
190 | 0 | .ok_or_else(Error::invalid) |
191 | 0 | } |
192 | | } |
193 | | |
194 | | impl From<EntityTag> for HeaderValue { |
195 | 0 | fn from(tag: EntityTag) -> HeaderValue { |
196 | 0 | tag.0 |
197 | 0 | } |
198 | | } |
199 | | |
200 | | impl<'a> From<&'a EntityTag> for HeaderValue { |
201 | 0 | fn from(tag: &'a EntityTag) -> HeaderValue { |
202 | 0 | tag.0.clone() |
203 | 0 | } |
204 | | } |
205 | | |
206 | | /// check that each char in the slice is either: |
207 | | /// 1. `%x21`, or |
208 | | /// 2. in the range `%x23` to `%x7E`, or |
209 | | /// 3. above `%x80` |
210 | 0 | fn check_slice_validity(slice: &[u8]) -> bool { |
211 | 0 | slice.iter().all(|&c| { |
212 | | // HeaderValue already validates that this doesnt contain control |
213 | | // characters, so we only need to look for DQUOTE (`"`). |
214 | | // |
215 | | // The debug_assert is just in case we use check_slice_validity in |
216 | | // some new context that didnt come from a HeaderValue. |
217 | 0 | debug_assert!( |
218 | 0 | (b'\x21'..=b'\x7e').contains(&c) | (c >= b'\x80'), |
219 | | "EntityTag expects HeaderValue to have check for control characters" |
220 | | ); |
221 | 0 | c != b'"' |
222 | 0 | }) |
223 | 0 | } |
224 | | |
225 | | // ===== impl EntityTagRange ===== |
226 | | |
227 | | impl EntityTagRange { |
228 | 0 | pub(crate) fn matches_strong(&self, entity: &EntityTag) -> bool { |
229 | 0 | self.matches_if(entity, |a, b| a.strong_eq(b)) |
230 | 0 | } |
231 | | |
232 | 0 | pub(crate) fn matches_weak(&self, entity: &EntityTag) -> bool { |
233 | 0 | self.matches_if(entity, |a, b| a.weak_eq(b)) |
234 | 0 | } |
235 | | |
236 | 0 | fn matches_if<F>(&self, entity: &EntityTag, func: F) -> bool |
237 | 0 | where |
238 | 0 | F: Fn(&EntityTag<&str>, &EntityTag) -> bool, |
239 | | { |
240 | 0 | match *self { |
241 | 0 | EntityTagRange::Any => true, |
242 | 0 | EntityTagRange::Tags(ref tags) => tags |
243 | 0 | .iter() |
244 | 0 | .flat_map(EntityTag::<&str>::parse) |
245 | 0 | .any(|tag| func(&tag, entity)), Unexecuted instantiation: <headers::util::entity::EntityTagRange>::matches_if::<<headers::util::entity::EntityTagRange>::matches_weak::{closure#0}>::{closure#0}Unexecuted instantiation: <headers::util::entity::EntityTagRange>::matches_if::<<headers::util::entity::EntityTagRange>::matches_strong::{closure#0}>::{closure#0} |
246 | | } |
247 | 0 | } Unexecuted instantiation: <headers::util::entity::EntityTagRange>::matches_if::<<headers::util::entity::EntityTagRange>::matches_weak::{closure#0}>Unexecuted instantiation: <headers::util::entity::EntityTagRange>::matches_if::<<headers::util::entity::EntityTagRange>::matches_strong::{closure#0}> |
248 | | } |
249 | | |
250 | | impl super::TryFromValues for EntityTagRange { |
251 | 0 | fn try_from_values<'i, I>(values: &mut I) -> Result<Self, Error> |
252 | 0 | where |
253 | 0 | I: Iterator<Item = &'i HeaderValue>, |
254 | | { |
255 | 0 | let flat = FlatCsv::try_from_values(values)?; |
256 | 0 | if flat.value == "*" { |
257 | 0 | Ok(EntityTagRange::Any) |
258 | | } else { |
259 | 0 | Ok(EntityTagRange::Tags(flat)) |
260 | | } |
261 | 0 | } |
262 | | } |
263 | | |
264 | | impl<'a> From<&'a EntityTagRange> for HeaderValue { |
265 | 0 | fn from(tag: &'a EntityTagRange) -> HeaderValue { |
266 | 0 | match *tag { |
267 | 0 | EntityTagRange::Any => HeaderValue::from_static("*"), |
268 | 0 | EntityTagRange::Tags(ref tags) => tags.into(), |
269 | | } |
270 | 0 | } |
271 | | } |
272 | | |
273 | | #[cfg(test)] |
274 | | mod tests { |
275 | | use super::*; |
276 | | |
277 | | fn parse(slice: &[u8]) -> Option<EntityTag> { |
278 | | let val = HeaderValue::from_bytes(slice).ok()?; |
279 | | EntityTag::from_val(&val) |
280 | | } |
281 | | |
282 | | #[test] |
283 | | fn test_etag_parse_success() { |
284 | | // Expected success |
285 | | let tag = parse(b"\"foobar\"").unwrap(); |
286 | | assert!(!tag.is_weak()); |
287 | | assert_eq!(tag.tag(), b"foobar"); |
288 | | |
289 | | let weak = parse(b"W/\"weaktag\"").unwrap(); |
290 | | assert!(weak.is_weak()); |
291 | | assert_eq!(weak.tag(), b"weaktag"); |
292 | | } |
293 | | |
294 | | #[test] |
295 | | fn test_etag_parse_failures() { |
296 | | // Expected failures |
297 | | macro_rules! fails { |
298 | | ($slice:expr) => { |
299 | | assert_eq!(parse($slice), None); |
300 | | }; |
301 | | } |
302 | | |
303 | | fails!(b"no-dquote"); |
304 | | fails!(b"w/\"the-first-w-is-case sensitive\""); |
305 | | fails!(b"W/\""); |
306 | | fails!(b""); |
307 | | fails!(b"\"unmatched-dquotes1"); |
308 | | fails!(b"unmatched-dquotes2\""); |
309 | | fails!(b"\"inner\"quotes\""); |
310 | | } |
311 | | |
312 | | /* |
313 | | #[test] |
314 | | fn test_etag_fmt() { |
315 | | assert_eq!(format!("{}", EntityTag::strong("foobar".to_owned())), "\"foobar\""); |
316 | | assert_eq!(format!("{}", EntityTag::strong("".to_owned())), "\"\""); |
317 | | assert_eq!(format!("{}", EntityTag::weak("weak-etag".to_owned())), "W/\"weak-etag\""); |
318 | | assert_eq!(format!("{}", EntityTag::weak("\u{0065}".to_owned())), "W/\"\x65\""); |
319 | | assert_eq!(format!("{}", EntityTag::weak("".to_owned())), "W/\"\""); |
320 | | } |
321 | | */ |
322 | | |
323 | | #[test] |
324 | | fn test_cmp() { |
325 | | // | ETag 1 | ETag 2 | Strong Comparison | Weak Comparison | |
326 | | // |---------|---------|-------------------|-----------------| |
327 | | // | `W/"1"` | `W/"1"` | no match | match | |
328 | | // | `W/"1"` | `W/"2"` | no match | no match | |
329 | | // | `W/"1"` | `"1"` | no match | match | |
330 | | // | `"1"` | `"1"` | match | match | |
331 | | let mut etag1 = EntityTag::from_static("W/\"1\""); |
332 | | let mut etag2 = etag1.clone(); |
333 | | assert!(!etag1.strong_eq(&etag2)); |
334 | | assert!(etag1.weak_eq(&etag2)); |
335 | | assert!(etag1.strong_ne(&etag2)); |
336 | | assert!(!etag1.weak_ne(&etag2)); |
337 | | |
338 | | etag2 = EntityTag::from_static("W/\"2\""); |
339 | | assert!(!etag1.strong_eq(&etag2)); |
340 | | assert!(!etag1.weak_eq(&etag2)); |
341 | | assert!(etag1.strong_ne(&etag2)); |
342 | | assert!(etag1.weak_ne(&etag2)); |
343 | | |
344 | | etag2 = EntityTag::from_static("\"1\""); |
345 | | assert!(!etag1.strong_eq(&etag2)); |
346 | | assert!(etag1.weak_eq(&etag2)); |
347 | | assert!(etag1.strong_ne(&etag2)); |
348 | | assert!(!etag1.weak_ne(&etag2)); |
349 | | |
350 | | etag1 = EntityTag::from_static("\"1\""); |
351 | | assert!(etag1.strong_eq(&etag2)); |
352 | | assert!(etag1.weak_eq(&etag2)); |
353 | | assert!(!etag1.strong_ne(&etag2)); |
354 | | assert!(!etag1.weak_ne(&etag2)); |
355 | | } |
356 | | } |