1use std::fmt;
2
3use http::HeaderValue;
4
5use super::{FlatCsv, IterExt};
6use crate::Error;
7
8#[derive(Clone, Eq, PartialEq)]
39pub(crate) struct EntityTag<T = HeaderValue>(T);
40
41#[derive(Clone, Debug, PartialEq)]
42pub(crate) enum EntityTagRange {
43    Any,
44    Tags(FlatCsv),
45}
46
47impl<T: AsRef<[u8]>> EntityTag<T> {
50    pub(crate) fn tag(&self) -> &[u8] {
52        let bytes = self.0.as_ref();
53        let end = bytes.len() - 1;
54        if bytes[0] == b'W' {
55            &bytes[3..end]
57        } else {
58            &bytes[1..end]
60        }
61    }
62
63    pub(crate) fn is_weak(&self) -> bool {
65        self.0.as_ref()[0] == b'W'
66    }
67
68    pub(crate) fn strong_eq<R>(&self, other: &EntityTag<R>) -> bool
71    where
72        R: AsRef<[u8]>,
73    {
74        !self.is_weak() && !other.is_weak() && self.tag() == other.tag()
75    }
76
77    pub(crate) fn weak_eq<R>(&self, other: &EntityTag<R>) -> bool
81    where
82        R: AsRef<[u8]>,
83    {
84        self.tag() == other.tag()
85    }
86
87    #[cfg(test)]
89    pub(crate) fn strong_ne(&self, other: &EntityTag) -> bool {
90        !self.strong_eq(other)
91    }
92
93    #[cfg(test)]
95    pub(crate) fn weak_ne(&self, other: &EntityTag) -> bool {
96        !self.weak_eq(other)
97    }
98
99    pub(crate) fn parse(src: T) -> Option<Self> {
100        let slice = src.as_ref();
101        let length = slice.len();
102
103        if length < 2 || slice[length - 1] != b'"' {
105            return None;
106        }
107
108        let start = match slice[0] {
109            b'"' => 1,
111            b'W' => {
113                if length >= 4 && slice[1] == b'/' && slice[2] == b'"' {
114                    3
115                } else {
116                    return None;
117                }
118            }
119            _ => return None,
120        };
121
122        if check_slice_validity(&slice[start..length - 1]) {
123            Some(EntityTag(src))
124        } else {
125            None
126        }
127    }
128}
129
130impl EntityTag {
131    #[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    pub(crate) fn from_owned(val: HeaderValue) -> Option<EntityTag> {
167        EntityTag::parse(val.as_bytes())?;
168        Some(EntityTag(val))
169    }
170
171    pub(crate) fn from_val(val: &HeaderValue) -> Option<EntityTag> {
172        EntityTag::parse(val.as_bytes()).map(|_entity| EntityTag(val.clone()))
173    }
174}
175
176impl<T: fmt::Debug> fmt::Debug for EntityTag<T> {
177    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178        self.0.fmt(f)
179    }
180}
181
182impl super::TryFromValues for EntityTag {
183    fn try_from_values<'i, I>(values: &mut I) -> Result<Self, Error>
184    where
185        I: Iterator<Item = &'i HeaderValue>,
186    {
187        values
188            .just_one()
189            .and_then(EntityTag::from_val)
190            .ok_or_else(Error::invalid)
191    }
192}
193
194impl From<EntityTag> for HeaderValue {
195    fn from(tag: EntityTag) -> HeaderValue {
196        tag.0
197    }
198}
199
200impl<'a> From<&'a EntityTag> for HeaderValue {
201    fn from(tag: &'a EntityTag) -> HeaderValue {
202        tag.0.clone()
203    }
204}
205
206fn check_slice_validity(slice: &[u8]) -> bool {
211    slice.iter().all(|&c| {
212        debug_assert!(
218            (b'\x21'..=b'\x7e').contains(&c) | (c >= b'\x80'),
219            "EntityTag expects HeaderValue to have check for control characters"
220        );
221        c != b'"'
222    })
223}
224
225impl EntityTagRange {
228    pub(crate) fn matches_strong(&self, entity: &EntityTag) -> bool {
229        self.matches_if(entity, |a, b| a.strong_eq(b))
230    }
231
232    pub(crate) fn matches_weak(&self, entity: &EntityTag) -> bool {
233        self.matches_if(entity, |a, b| a.weak_eq(b))
234    }
235
236    fn matches_if<F>(&self, entity: &EntityTag, func: F) -> bool
237    where
238        F: Fn(&EntityTag<&str>, &EntityTag) -> bool,
239    {
240        match *self {
241            EntityTagRange::Any => true,
242            EntityTagRange::Tags(ref tags) => tags
243                .iter()
244                .flat_map(EntityTag::<&str>::parse)
245                .any(|tag| func(&tag, entity)),
246        }
247    }
248}
249
250impl super::TryFromValues for EntityTagRange {
251    fn try_from_values<'i, I>(values: &mut I) -> Result<Self, Error>
252    where
253        I: Iterator<Item = &'i HeaderValue>,
254    {
255        let flat = FlatCsv::try_from_values(values)?;
256        if flat.value == "*" {
257            Ok(EntityTagRange::Any)
258        } else {
259            Ok(EntityTagRange::Tags(flat))
260        }
261    }
262}
263
264impl<'a> From<&'a EntityTagRange> for HeaderValue {
265    fn from(tag: &'a EntityTagRange) -> HeaderValue {
266        match *tag {
267            EntityTagRange::Any => HeaderValue::from_static("*"),
268            EntityTagRange::Tags(ref tags) => tags.into(),
269        }
270    }
271}
272
273#[cfg(test)]
274mod 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        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        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    #[test]
324    fn test_cmp() {
325        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}