headers/util/
entity.rs
1use std::fmt;
2
3use super::{FlatCsv, IterExt};
4use HeaderValue;
5
6#[derive(Clone, Eq, PartialEq)]
37pub(crate) struct EntityTag<T = HeaderValue>(T);
38
39#[derive(Clone, Debug, PartialEq)]
40pub(crate) enum EntityTagRange {
41 Any,
42 Tags(FlatCsv),
43}
44
45impl<T: AsRef<[u8]>> EntityTag<T> {
48 pub(crate) fn tag(&self) -> &[u8] {
50 let bytes = self.0.as_ref();
51 let end = bytes.len() - 1;
52 if bytes[0] == b'W' {
53 &bytes[3..end]
55 } else {
56 &bytes[1..end]
58 }
59 }
60
61 pub(crate) fn is_weak(&self) -> bool {
63 self.0.as_ref()[0] == b'W'
64 }
65
66 pub(crate) fn strong_eq<R>(&self, other: &EntityTag<R>) -> bool
69 where
70 R: AsRef<[u8]>,
71 {
72 !self.is_weak() && !other.is_weak() && self.tag() == other.tag()
73 }
74
75 pub(crate) fn weak_eq<R>(&self, other: &EntityTag<R>) -> bool
79 where
80 R: AsRef<[u8]>,
81 {
82 self.tag() == other.tag()
83 }
84
85 #[cfg(test)]
87 pub(crate) fn strong_ne(&self, other: &EntityTag) -> bool {
88 !self.strong_eq(other)
89 }
90
91 #[cfg(test)]
93 pub(crate) fn weak_ne(&self, other: &EntityTag) -> bool {
94 !self.weak_eq(other)
95 }
96
97 pub(crate) fn parse(src: T) -> Option<Self> {
98 let slice = src.as_ref();
99 let length = slice.len();
100
101 if length < 2 || slice[length - 1] != b'"' {
103 return None;
104 }
105
106 let start = match slice[0] {
107 b'"' => 1,
109 b'W' => {
111 if length >= 4 && slice[1] == b'/' && slice[2] == b'"' {
112 3
113 } else {
114 return None;
115 }
116 }
117 _ => return None,
118 };
119
120 if check_slice_validity(&slice[start..length - 1]) {
121 Some(EntityTag(src))
122 } else {
123 None
124 }
125 }
126}
127
128impl EntityTag {
129 #[cfg(test)]
154 pub fn from_static(bytes: &'static str) -> EntityTag {
155 let val = HeaderValue::from_static(bytes);
156 match EntityTag::from_val(&val) {
157 Some(tag) => tag,
158 None => {
159 panic!("invalid static string for EntityTag: {:?}", bytes);
160 }
161 }
162 }
163
164 pub(crate) fn from_owned(val: HeaderValue) -> Option<EntityTag> {
165 EntityTag::parse(val.as_bytes())?;
166 Some(EntityTag(val))
167 }
168
169 pub(crate) fn from_val(val: &HeaderValue) -> Option<EntityTag> {
170 EntityTag::parse(val.as_bytes()).map(|_entity| EntityTag(val.clone()))
171 }
172}
173
174impl<T: fmt::Debug> fmt::Debug for EntityTag<T> {
175 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176 self.0.fmt(f)
177 }
178}
179
180impl super::TryFromValues for EntityTag {
181 fn try_from_values<'i, I>(values: &mut I) -> Result<Self, ::Error>
182 where
183 I: Iterator<Item = &'i HeaderValue>,
184 {
185 values
186 .just_one()
187 .and_then(EntityTag::from_val)
188 .ok_or_else(::Error::invalid)
189 }
190}
191
192impl From<EntityTag> for HeaderValue {
193 fn from(tag: EntityTag) -> HeaderValue {
194 tag.0
195 }
196}
197
198impl<'a> From<&'a EntityTag> for HeaderValue {
199 fn from(tag: &'a EntityTag) -> HeaderValue {
200 tag.0.clone()
201 }
202}
203
204fn check_slice_validity(slice: &[u8]) -> bool {
209 slice.iter().all(|&c| {
210 debug_assert!(
216 (c >= b'\x21' && c <= b'\x7e') | (c >= b'\x80'),
217 "EntityTag expects HeaderValue to have check for control characters"
218 );
219 c != b'"'
220 })
221}
222
223impl EntityTagRange {
226 pub(crate) fn matches_strong(&self, entity: &EntityTag) -> bool {
227 self.matches_if(entity, |a, b| a.strong_eq(b))
228 }
229
230 pub(crate) fn matches_weak(&self, entity: &EntityTag) -> bool {
231 self.matches_if(entity, |a, b| a.weak_eq(b))
232 }
233
234 fn matches_if<F>(&self, entity: &EntityTag, func: F) -> bool
235 where
236 F: Fn(&EntityTag<&str>, &EntityTag) -> bool,
237 {
238 match *self {
239 EntityTagRange::Any => true,
240 EntityTagRange::Tags(ref tags) => tags
241 .iter()
242 .flat_map(EntityTag::<&str>::parse)
243 .any(|tag| func(&tag, entity)),
244 }
245 }
246}
247
248impl super::TryFromValues for EntityTagRange {
249 fn try_from_values<'i, I>(values: &mut I) -> Result<Self, ::Error>
250 where
251 I: Iterator<Item = &'i HeaderValue>,
252 {
253 let flat = FlatCsv::try_from_values(values)?;
254 if flat.value == "*" {
255 Ok(EntityTagRange::Any)
256 } else {
257 Ok(EntityTagRange::Tags(flat))
258 }
259 }
260}
261
262impl<'a> From<&'a EntityTagRange> for HeaderValue {
263 fn from(tag: &'a EntityTagRange) -> HeaderValue {
264 match *tag {
265 EntityTagRange::Any => HeaderValue::from_static("*"),
266 EntityTagRange::Tags(ref tags) => tags.into(),
267 }
268 }
269}
270
271#[cfg(test)]
272mod tests {
273 use super::*;
274
275 fn parse(slice: &[u8]) -> Option<EntityTag> {
276 let val = HeaderValue::from_bytes(slice).ok()?;
277 EntityTag::from_val(&val)
278 }
279
280 #[test]
281 fn test_etag_parse_success() {
282 let tag = parse(b"\"foobar\"").unwrap();
284 assert!(!tag.is_weak());
285 assert_eq!(tag.tag(), b"foobar");
286
287 let weak = parse(b"W/\"weaktag\"").unwrap();
288 assert!(weak.is_weak());
289 assert_eq!(weak.tag(), b"weaktag");
290 }
291
292 #[test]
293 fn test_etag_parse_failures() {
294 macro_rules! fails {
296 ($slice:expr) => {
297 assert_eq!(parse($slice), None);
298 };
299 }
300
301 fails!(b"no-dquote");
302 fails!(b"w/\"the-first-w-is-case sensitive\"");
303 fails!(b"W/\"");
304 fails!(b"");
305 fails!(b"\"unmatched-dquotes1");
306 fails!(b"unmatched-dquotes2\"");
307 fails!(b"\"inner\"quotes\"");
308 }
309
310 #[test]
322 fn test_cmp() {
323 let mut etag1 = EntityTag::from_static("W/\"1\"");
330 let mut etag2 = etag1.clone();
331 assert!(!etag1.strong_eq(&etag2));
332 assert!(etag1.weak_eq(&etag2));
333 assert!(etag1.strong_ne(&etag2));
334 assert!(!etag1.weak_ne(&etag2));
335
336 etag2 = EntityTag::from_static("W/\"2\"");
337 assert!(!etag1.strong_eq(&etag2));
338 assert!(!etag1.weak_eq(&etag2));
339 assert!(etag1.strong_ne(&etag2));
340 assert!(etag1.weak_ne(&etag2));
341
342 etag2 = EntityTag::from_static("\"1\"");
343 assert!(!etag1.strong_eq(&etag2));
344 assert!(etag1.weak_eq(&etag2));
345 assert!(etag1.strong_ne(&etag2));
346 assert!(!etag1.weak_ne(&etag2));
347
348 etag1 = EntityTag::from_static("\"1\"");
349 assert!(etag1.strong_eq(&etag2));
350 assert!(etag1.weak_eq(&etag2));
351 assert!(!etag1.strong_ne(&etag2));
352 assert!(!etag1.weak_ne(&etag2));
353 }
354}