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}