1use crate::{
17 class,
18 error::{ErrorKind, ParseError, Result, WriteError, err},
19 limits::Limits,
20 list,
21 text::trim_ows,
22 write::{Sink, reserve},
23};
24
25#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
40pub struct EntityTag<'a> {
41 weak: bool,
42 opaque: &'a [u8],
43}
44
45const fn first_bad(opaque: &[u8]) -> Option<usize> {
47 let mut i = 0;
48 while i < opaque.len() {
49 #[allow(clippy::indexing_slicing)]
51 let b = opaque[i];
52 if !class::is_etagc(b) {
53 return Some(i);
54 }
55 i += 1;
56 }
57 None
58}
59
60impl<'a> EntityTag<'a> {
61 pub fn parse(value: &'a [u8]) -> Result<Self> {
69 let lead = crate::text::skip_ows(value);
70 let (tag, used) = parse_prefix(trim_ows(value)).map_err(|e| e.shifted(lead))?;
71 if used != trim_ows(value).len() {
72 return err(ErrorKind::TrailingData, lead + used);
73 }
74 Ok(tag)
75 }
76
77 pub const fn strong(opaque: &'a [u8]) -> Result<Self> {
84 match first_bad(opaque) {
85 Some(at) => Err(ParseError::new(ErrorKind::InvalidEntityTag, at)),
86 None => Ok(Self {
87 weak: false,
88 opaque,
89 }),
90 }
91 }
92
93 pub const fn weak(opaque: &'a [u8]) -> Result<Self> {
99 match first_bad(opaque) {
100 Some(at) => Err(ParseError::new(ErrorKind::InvalidEntityTag, at)),
101 None => Ok(Self { weak: true, opaque }),
102 }
103 }
104
105 #[must_use]
107 pub const fn is_weak(&self) -> bool {
108 self.weak
109 }
110
111 #[must_use]
113 pub const fn opaque(&self) -> &'a [u8] {
114 self.opaque
115 }
116
117 #[must_use]
119 pub fn strong_eq(&self, other: &EntityTag<'_>) -> bool {
120 !self.weak && !other.weak && self.opaque == other.opaque
121 }
122
123 #[must_use]
125 pub fn weak_eq(&self, other: &EntityTag<'_>) -> bool {
126 self.opaque == other.opaque
127 }
128
129 pub fn write_to(&self, out: &mut impl Sink) -> core::result::Result<(), WriteError> {
148 let prefix: &[u8] = if self.weak { b"W/\"" } else { b"\"" };
149 reserve(out, prefix.len() + self.opaque.len() + 1)?;
150 out.put(prefix)?;
151 out.put(self.opaque)?;
152 out.put(b"\"")?;
153 Ok(())
154 }
155}
156
157fn parse_prefix(s: &[u8]) -> Result<(EntityTag<'_>, usize)> {
159 let (weak, start) = match s {
160 [b'W', b'/', ..] => (true, 2),
161 _ => (false, 0),
162 };
163 if s.get(start) != Some(&b'"') {
164 return err(ErrorKind::InvalidEntityTag, start);
165 }
166 let body = s.get(start + 1..).unwrap_or(&[]);
167 let close = body.iter().position(|&b| !class::is_etagc(b));
168 match close {
169 Some(i) if body.get(i) == Some(&b'"') => {
170 let opaque = body.get(..i).unwrap_or(&[]);
171 Ok((EntityTag { weak, opaque }, start + i + 2))
172 }
173 Some(i) => {
174 let b = body.get(i).copied().unwrap_or(0);
175 let kind = if crate::scan::is_control(b) {
176 crate::scan::control_kind(b)
177 } else {
178 ErrorKind::InvalidEntityTag
179 };
180 err(kind, start + 1 + i)
181 }
182 None => err(ErrorKind::InvalidEntityTag, s.len()),
183 }
184}
185
186#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
188pub enum Condition<'a> {
189 Any,
191 Tags(TagList<'a>),
193}
194
195#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
197pub struct TagList<'a> {
198 list: list::List<'a>,
199}
200
201impl<'a> TagList<'a> {
202 pub fn iter(&self) -> impl Iterator<Item = EntityTag<'a>> + use<'a> {
204 self.list
205 .iter()
206 .filter_map(|e| parse_prefix(e).ok().map(|(t, _)| t))
207 }
208
209 #[must_use]
211 pub const fn len(&self) -> usize {
212 self.list.len()
213 }
214
215 #[must_use]
217 pub const fn is_empty(&self) -> bool {
218 self.list.is_empty()
219 }
220}
221
222pub fn parse_condition<'a>(value: &'a [u8], limits: &Limits) -> Result<Condition<'a>> {
241 let l = list::parse(value, limits)?;
242 let mut star = false;
243 for e in l.iter() {
244 let at = (e.as_ptr() as usize).saturating_sub(value.as_ptr() as usize);
245 if e == b"*" {
246 star = true;
247 continue;
248 }
249 let (_, used) = parse_prefix(e).map_err(|x| x.shifted(at))?;
250 if used != e.len() {
251 return err(ErrorKind::InvalidEntityTag, at + used);
252 }
253 }
254 if star {
255 if l.len() != 1 {
256 return err(ErrorKind::Malformed, 0);
257 }
258 return Ok(Condition::Any);
259 }
260 Ok(Condition::Tags(TagList { list: l }))
261}
262
263pub fn if_none_match(value: &[u8], current: &EntityTag<'_>) -> Result<bool> {
283 Ok(match parse_condition(value, &Limits::DEFAULT)? {
284 Condition::Any => true,
285 Condition::Tags(tags) => tags.iter().any(|t| t.weak_eq(current)),
286 })
287}
288
289pub fn if_match(value: &[u8], current: Option<&EntityTag<'_>>) -> Result<bool> {
310 Ok(match parse_condition(value, &Limits::DEFAULT)? {
311 Condition::Any => current.is_some(),
312 Condition::Tags(tags) => current.is_some_and(|c| tags.iter().any(|t| t.strong_eq(c))),
313 })
314}
315
316#[cfg(test)]
317mod tests {
318 use super::*;
319
320 #[test]
321 fn test_entity_tag_edge_cases() {
322 assert_eq!(EntityTag::parse(b"\"\"").unwrap().opaque(), b"");
323 assert!(EntityTag::parse(b"W/\"\"").unwrap().is_weak());
324 let k = |v: &[u8]| EntityTag::parse(v).unwrap_err().kind();
325 assert_eq!(k(b"w/\"a\""), ErrorKind::InvalidEntityTag);
326 assert_eq!(k(b"\"a\"b"), ErrorKind::TrailingData);
327 assert_eq!(k(b"\"a"), ErrorKind::InvalidEntityTag);
328 assert_eq!(k(b"W/ \"a\""), ErrorKind::InvalidEntityTag);
329 assert_eq!(k(b"\"a\x7f\""), ErrorKind::Control);
330 assert_eq!(k(b"\"a b\""), ErrorKind::InvalidEntityTag);
331 assert_eq!(k(b"abc"), ErrorKind::InvalidEntityTag);
332 assert_eq!(k(b""), ErrorKind::InvalidEntityTag);
333 assert!(EntityTag::parse(b"\"\x80\xff!\"").is_ok());
334 assert!(EntityTag::strong(b"a b").is_err());
335 assert!(EntityTag::weak(b"\"").is_err());
336 }
337
338 #[test]
339 fn test_comparison_table_rfc_9110() {
340 let w1 = EntityTag::weak(b"1").unwrap();
342 let w2 = EntityTag::weak(b"2").unwrap();
343 let s1 = EntityTag::strong(b"1").unwrap();
344 assert!(!w1.strong_eq(&w1) && w1.weak_eq(&w1));
345 assert!(!w1.strong_eq(&w2) && !w1.weak_eq(&w2));
346 assert!(!w1.strong_eq(&s1) && w1.weak_eq(&s1));
347 assert!(s1.strong_eq(&s1) && s1.weak_eq(&s1));
348 }
349
350 #[test]
351 fn test_conditions() {
352 let l = Limits::DEFAULT;
353 assert_eq!(
354 parse_condition(b"*, \"a\"", &l).unwrap_err().kind(),
355 ErrorKind::Malformed
356 );
357 assert_eq!(
358 parse_condition(b"\"a\" \"b\"", &l).unwrap_err().kind(),
359 ErrorKind::InvalidEntityTag
360 );
361 let e = parse_condition(b"\"a\", w/\"b\"", &l).unwrap_err();
362 assert_eq!((e.kind(), e.offset()), (ErrorKind::InvalidEntityTag, 5));
363 let s = EntityTag::strong(b"b").unwrap();
364 assert!(if_none_match(b"\"a\", , \"b\"", &s).unwrap());
365 assert!(if_none_match(b"*", &s).unwrap());
366 assert!(!if_match(b"*", None).unwrap());
367 assert!(if_match(b"\"a\", \"b\"", Some(&s)).unwrap());
368 assert!(if_none_match(b"\"a", &s).is_err());
369 }
370
371 #[test]
372 fn test_write_round_trip() {
373 let mut buf = [0u8; 8];
374 let mut out = crate::SliceSink::new(&mut buf);
375 let t = EntityTag::strong(b"xyz").unwrap();
376 t.write_to(&mut out).unwrap();
377 assert_eq!(EntityTag::parse(out.written()).unwrap(), t);
378 let mut small = [0u8; 3];
379 let mut out = crate::SliceSink::new(&mut small);
380 assert_eq!(t.write_to(&mut out), Err(WriteError::Full));
381 }
382}