Skip to main content

parse_net/
etag.rs

1//! Entity tags and the `If-Match` / `If-None-Match` conditions (RFC 9110
2//! sections 8.8.3 and 13.1).
3//!
4//! # Examples
5//!
6//! ```
7//! use parse_net::{self as pn, EntityTag};
8//!
9//! let v2 = EntityTag::strong(b"v2")?;
10//! assert!(pn::if_none_match(b"\"v1\", W/\"v2\"", &v2)?);         // weak comparison: 304
11//! assert!(!pn::if_match(b"W/\"v2\"", Some(&v2))?);               // strong comparison: 412
12//! assert!(pn::if_match(b"*", Some(&v2))? && !pn::if_match(b"*", None)?);
13//! # Ok::<(), pn::ParseError>(())
14//! ```
15
16use 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/// An entity tag: a weak flag and the opaque bytes between the quotes.
26///
27/// # Examples
28///
29/// ```
30/// use parse_net::EntityTag;
31///
32/// let t = EntityTag::parse(b"W/\"abc\"")?;
33/// assert!(t.is_weak());
34/// assert_eq!(t.opaque(), b"abc");
35/// assert!(t.weak_eq(&EntityTag::strong(b"abc")?));
36/// assert!(!t.strong_eq(&EntityTag::strong(b"abc")?));
37/// # Ok::<(), parse_net::ParseError>(())
38/// ```
39#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
40pub struct EntityTag<'a> {
41    pub(crate) weak: bool,
42    pub(crate) opaque: &'a [u8],
43}
44
45/// The offset of the first byte of `opaque` that is not an `etagc`.
46const fn first_bad(opaque: &[u8]) -> Option<usize> {
47    let mut i = 0;
48    while i < opaque.len() {
49        // `i < opaque.len()` was just checked.
50        #[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    /// Parses `[ "W/" ] DQUOTE *etagc DQUOTE` (OWS around it stripped).
62    /// `W/` is case-sensitive.
63    ///
64    /// # Errors
65    ///
66    /// [`ErrorKind::InvalidEntityTag`] (a lowercase `w/`, no quotes, a byte
67    /// outside `etagc`, `W/ "a"`); [`ErrorKind::TrailingData`].
68    ///
69    /// # Examples
70    ///
71    /// ```
72    /// use parse_net::EntityTag;
73    ///
74    /// let t = EntityTag::parse(b" \"xyzzy\" ")?;
75    /// assert!(!t.is_weak() && t.opaque() == b"xyzzy");
76    /// assert!(EntityTag::parse(b"w/\"xyzzy\"").is_err());
77    /// # Ok::<(), parse_net::ParseError>(())
78    /// ```
79    pub fn parse(value: &'a [u8]) -> Result<Self> {
80        let lead = crate::text::skip_ows(value);
81        let (tag, used) = parse_prefix(trim_ows(value)).map_err(|e| e.shifted(lead))?;
82        if used != trim_ows(value).len() {
83            return err(ErrorKind::TrailingData, lead + used);
84        }
85        Ok(tag)
86    }
87
88    /// A strong tag with the given opaque bytes.
89    ///
90    /// # Errors
91    ///
92    /// [`ErrorKind::InvalidEntityTag`] at the first byte that is not an
93    /// `etagc`.
94    ///
95    /// # Examples
96    ///
97    /// ```
98    /// use parse_net::EntityTag;
99    ///
100    /// assert!(!EntityTag::strong(b"v1")?.is_weak());
101    /// assert!(EntityTag::strong(b"a\"b").is_err());
102    /// # Ok::<(), parse_net::ParseError>(())
103    /// ```
104    pub const fn strong(opaque: &'a [u8]) -> Result<Self> {
105        match first_bad(opaque) {
106            Some(at) => Err(ParseError::new(ErrorKind::InvalidEntityTag, at)),
107            None => Ok(Self {
108                weak: false,
109                opaque,
110            }),
111        }
112    }
113
114    /// A weak tag with the given opaque bytes.
115    ///
116    /// # Errors
117    ///
118    /// As [`strong`](Self::strong).
119    ///
120    /// # Examples
121    ///
122    /// ```
123    /// let t = parse_net::EntityTag::weak(b"v1")?;
124    /// assert!(t.is_weak() && t.opaque() == b"v1");
125    /// # Ok::<(), parse_net::ParseError>(())
126    /// ```
127    pub const fn weak(opaque: &'a [u8]) -> Result<Self> {
128        match first_bad(opaque) {
129            Some(at) => Err(ParseError::new(ErrorKind::InvalidEntityTag, at)),
130            None => Ok(Self { weak: true, opaque }),
131        }
132    }
133
134    /// Whether the tag is weak (`W/`).
135    ///
136    /// # Examples
137    ///
138    /// ```
139    /// use parse_net::EntityTag;
140    ///
141    /// assert!(EntityTag::parse(b"W/\"1\"")?.is_weak());
142    /// assert!(!EntityTag::parse(b"\"1\"")?.is_weak());
143    /// # Ok::<(), parse_net::ParseError>(())
144    /// ```
145    #[must_use]
146    pub const fn is_weak(&self) -> bool {
147        self.weak
148    }
149
150    /// The opaque bytes, without the quotes.
151    ///
152    /// # Examples
153    ///
154    /// ```
155    /// use parse_net::EntityTag;
156    ///
157    /// assert_eq!(EntityTag::parse(b"W/\"abc\"")?.opaque(), b"abc");
158    /// assert_eq!(EntityTag::parse(b"\"\"")?.opaque(), b"");
159    /// # Ok::<(), parse_net::ParseError>(())
160    /// ```
161    #[must_use]
162    pub const fn opaque(&self) -> &'a [u8] {
163        self.opaque
164    }
165
166    /// Strong comparison (RFC 9110 section 8.8.3.2): both strong and equal.
167    ///
168    /// # Examples
169    ///
170    /// ```
171    /// use parse_net::EntityTag;
172    ///
173    /// let a = EntityTag::strong(b"1")?;
174    /// assert!(a.strong_eq(&EntityTag::parse(b"\"1\"")?));
175    /// assert!(!a.strong_eq(&EntityTag::weak(b"1")?));
176    /// # Ok::<(), parse_net::ParseError>(())
177    /// ```
178    #[must_use]
179    pub fn strong_eq(&self, other: &EntityTag<'_>) -> bool {
180        !self.weak && !other.weak && self.opaque == other.opaque
181    }
182
183    /// Weak comparison: equal opaque bytes, either may be weak.
184    ///
185    /// # Examples
186    ///
187    /// ```
188    /// use parse_net::EntityTag;
189    ///
190    /// let (w, s) = (EntityTag::weak(b"1")?, EntityTag::strong(b"1")?);
191    /// assert!(w.weak_eq(&s) && !w.weak_eq(&EntityTag::strong(b"2")?));
192    /// # Ok::<(), parse_net::ParseError>(())
193    /// ```
194    #[must_use]
195    pub fn weak_eq(&self, other: &EntityTag<'_>) -> bool {
196        self.opaque == other.opaque
197    }
198
199    /// Writes `W/"..."` or `"..."`.
200    ///
201    /// # Errors
202    ///
203    /// [`WriteError::Full`]; nothing is written when the sink reports its
204    /// room.
205    ///
206    /// # Examples
207    ///
208    /// ```
209    /// use parse_net::{EntityTag, SliceSink};
210    ///
211    /// let mut buf = [0u8; 16];
212    /// let mut out = SliceSink::new(&mut buf);
213    /// EntityTag::weak(b"v1")?.write_to(&mut out).expect("room");
214    /// assert_eq!(out.written(), b"W/\"v1\"");
215    /// # Ok::<(), parse_net::ParseError>(())
216    /// ```
217    pub fn write_to(&self, out: &mut impl Sink) -> core::result::Result<(), WriteError> {
218        let prefix: &[u8] = if self.weak { b"W/\"" } else { b"\"" };
219        reserve(out, prefix.len() + self.opaque.len() + 1)?;
220        out.put(prefix)?;
221        out.put(self.opaque)?;
222        out.put(b"\"")?;
223        Ok(())
224    }
225}
226
227/// Parses one entity tag at the start of `s`; returns it and the bytes used.
228fn parse_prefix(s: &[u8]) -> Result<(EntityTag<'_>, usize)> {
229    let (weak, start) = match s {
230        [b'W', b'/', ..] => (true, 2),
231        _ => (false, 0),
232    };
233    if s.get(start) != Some(&b'"') {
234        return err(ErrorKind::InvalidEntityTag, start);
235    }
236    let body = s.get(start + 1..).unwrap_or(&[]);
237    let close = body.iter().position(|&b| !class::is_etagc(b));
238    match close {
239        Some(i) if body.get(i) == Some(&b'"') => {
240            let opaque = body.get(..i).unwrap_or(&[]);
241            Ok((EntityTag { weak, opaque }, start + i + 2))
242        }
243        Some(i) => {
244            let b = body.get(i).copied().unwrap_or(0);
245            let kind = if crate::scan::is_control(b) {
246                crate::scan::control_kind(b)
247            } else {
248                ErrorKind::InvalidEntityTag
249            };
250            err(kind, start + 1 + i)
251        }
252        None => err(ErrorKind::InvalidEntityTag, s.len()),
253    }
254}
255
256/// An `If-Match` or `If-None-Match` value: `*` or a list of entity tags.
257///
258/// # Examples
259///
260/// ```
261/// use parse_net::{etag::parse_condition, Condition, Limits};
262///
263/// assert_eq!(parse_condition(b"*", &Limits::DEFAULT)?, Condition::Any);
264/// assert!(matches!(parse_condition(b"\"a\"", &Limits::DEFAULT)?, Condition::Tags(t) if t.len() == 1));
265/// # Ok::<(), parse_net::ParseError>(())
266/// ```
267#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
268pub enum Condition<'a> {
269    /// `*`: any current representation.
270    Any,
271    /// The listed tags.
272    Tags(TagList<'a>),
273}
274
275/// The tags of a validated condition.
276///
277/// # Examples
278///
279/// ```
280/// use parse_net::{etag::parse_condition, Condition, Limits};
281///
282/// let Condition::Tags(tags) = parse_condition(b"\"a\", W/\"b\"", &Limits::DEFAULT)? else { unreachable!() };
283/// assert_eq!(tags.iter().filter(|t| t.is_weak()).count(), 1);
284/// # Ok::<(), parse_net::ParseError>(())
285/// ```
286#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
287pub struct TagList<'a> {
288    list: list::List<'a>,
289}
290
291impl<'a> TagList<'a> {
292    /// The tags, in order.
293    ///
294    /// # Examples
295    ///
296    /// ```
297    /// use parse_net::{etag::parse_condition, Condition, Limits};
298    ///
299    /// let Condition::Tags(tags) = parse_condition(b"\"a\", W/\"b\"", &Limits::DEFAULT)? else { unreachable!() };
300    /// let opaque: Vec<_> = tags.iter().map(|t| t.opaque()).collect();
301    /// assert_eq!(opaque, [&b"a"[..], &b"b"[..]]);
302    /// # Ok::<(), parse_net::ParseError>(())
303    /// ```
304    pub fn iter(&self) -> impl Iterator<Item = EntityTag<'a>> + use<'a> {
305        self.list
306            .iter()
307            .filter_map(|e| parse_prefix(e).ok().map(|(t, _)| t))
308    }
309
310    /// The number of tags.
311    ///
312    /// # Examples
313    ///
314    /// ```
315    /// use parse_net::{etag::parse_condition, Condition, Limits};
316    ///
317    /// let Condition::Tags(tags) = parse_condition(b"\"a\", , \"b\"", &Limits::DEFAULT)? else { unreachable!() };
318    /// assert_eq!(tags.len(), 2);
319    /// # Ok::<(), parse_net::ParseError>(())
320    /// ```
321    #[must_use]
322    pub const fn len(&self) -> usize {
323        self.list.len()
324    }
325
326    /// Whether there are no tags.
327    ///
328    /// # Examples
329    ///
330    /// ```
331    /// use parse_net::{etag::parse_condition, Condition, Limits};
332    ///
333    /// let Condition::Tags(tags) = parse_condition(b"\"a\"", &Limits::DEFAULT)? else { unreachable!() };
334    /// assert!(!tags.is_empty());
335    /// # Ok::<(), parse_net::ParseError>(())
336    /// ```
337    #[must_use]
338    pub const fn is_empty(&self) -> bool {
339        self.list.is_empty()
340    }
341}
342
343/// Parses `"*" / #entity-tag`.
344///
345/// # Errors
346///
347/// [`ErrorKind::Malformed`] for `*` mixed with tags; [`ErrorKind::InvalidEntityTag`];
348/// the list errors.
349///
350/// # Examples
351///
352/// ```
353/// use parse_net::{etag::{parse_condition, Condition}, Limits};
354///
355/// let Condition::Tags(tags) = parse_condition(b"\"a\", , W/\"b\"", &Limits::DEFAULT)? else { unreachable!() };
356/// assert_eq!(tags.len(), 2);
357/// assert!(matches!(parse_condition(b"*", &Limits::DEFAULT)?, Condition::Any));
358/// assert!(parse_condition(b"*, \"a\"", &Limits::DEFAULT).is_err());
359/// # Ok::<(), parse_net::ParseError>(())
360/// ```
361pub fn parse_condition<'a>(value: &'a [u8], limits: &Limits) -> Result<Condition<'a>> {
362    let l = list::parse(value, limits)?;
363    let mut star = false;
364    for e in l.iter() {
365        let at = (e.as_ptr() as usize).saturating_sub(value.as_ptr() as usize);
366        if e == b"*" {
367            star = true;
368            continue;
369        }
370        let (_, used) = parse_prefix(e).map_err(|x| x.shifted(at))?;
371        if used != e.len() {
372            return err(ErrorKind::InvalidEntityTag, at + used);
373        }
374    }
375    if star {
376        if l.len() != 1 {
377            return err(ErrorKind::Malformed, 0);
378        }
379        return Ok(Condition::Any);
380    }
381    Ok(Condition::Tags(TagList { list: l }))
382}
383
384/// Evaluates `If-None-Match` against the current representation's tag by
385/// weak comparison (RFC 9110 section 13.1.2).
386///
387/// `true` means the condition is false for the request: answer 304 (GET,
388/// HEAD) or 412 (other methods). `*` matches any current representation.
389///
390/// # Errors
391///
392/// As [`parse_condition`].
393///
394/// # Examples
395///
396/// ```
397/// use parse_net::{if_none_match, EntityTag};
398///
399/// assert!(if_none_match(b"W/\"x\"", &EntityTag::strong(b"x")?)?);
400/// assert!(!if_none_match(b"\"y\"", &EntityTag::strong(b"x")?)?);
401/// # Ok::<(), parse_net::ParseError>(())
402/// ```
403pub fn if_none_match(value: &[u8], current: &EntityTag<'_>) -> Result<bool> {
404    Ok(match parse_condition(value, &Limits::DEFAULT)? {
405        Condition::Any => true,
406        Condition::Tags(tags) => tags.iter().any(|t| t.weak_eq(current)),
407    })
408}
409
410/// Evaluates `If-Match` by strong comparison (RFC 9110 section 13.1.1).
411///
412/// `true` means the precondition holds; `false` means answer 412. `*` holds
413/// when a current representation exists (`current` is `Some`).
414///
415/// # Errors
416///
417/// As [`parse_condition`].
418///
419/// # Examples
420///
421/// ```
422/// use parse_net::{if_match, EntityTag};
423///
424/// let current = EntityTag::strong(b"x")?;
425/// assert!(if_match(b"\"x\"", Some(&current))?);
426/// assert!(!if_match(b"W/\"x\"", Some(&current))?);
427/// assert!(!if_match(b"\"x\"", None)?);
428/// # Ok::<(), parse_net::ParseError>(())
429/// ```
430pub fn if_match(value: &[u8], current: Option<&EntityTag<'_>>) -> Result<bool> {
431    Ok(match parse_condition(value, &Limits::DEFAULT)? {
432        Condition::Any => current.is_some(),
433        Condition::Tags(tags) => current.is_some_and(|c| tags.iter().any(|t| t.strong_eq(c))),
434    })
435}
436
437#[cfg(test)]
438mod tests {
439    use super::*;
440
441    #[test]
442    fn test_entity_tag_edge_cases() {
443        assert_eq!(EntityTag::parse(b"\"\"").unwrap().opaque(), b"");
444        assert!(EntityTag::parse(b"W/\"\"").unwrap().is_weak());
445        let k = |v: &[u8]| EntityTag::parse(v).unwrap_err().kind();
446        assert_eq!(k(b"w/\"a\""), ErrorKind::InvalidEntityTag);
447        assert_eq!(k(b"\"a\"b"), ErrorKind::TrailingData);
448        assert_eq!(k(b"\"a"), ErrorKind::InvalidEntityTag);
449        assert_eq!(k(b"W/ \"a\""), ErrorKind::InvalidEntityTag);
450        assert_eq!(k(b"\"a\x7f\""), ErrorKind::Control);
451        assert_eq!(k(b"\"a b\""), ErrorKind::InvalidEntityTag);
452        assert_eq!(k(b"abc"), ErrorKind::InvalidEntityTag);
453        assert_eq!(k(b""), ErrorKind::InvalidEntityTag);
454        assert!(EntityTag::parse(b"\"\x80\xff!\"").is_ok());
455        assert!(EntityTag::strong(b"a b").is_err());
456        assert!(EntityTag::weak(b"\"").is_err());
457    }
458
459    #[test]
460    fn test_comparison_table_rfc_9110() {
461        // RFC 9110 section 8.8.3.2.
462        let w1 = EntityTag::weak(b"1").unwrap();
463        let w2 = EntityTag::weak(b"2").unwrap();
464        let s1 = EntityTag::strong(b"1").unwrap();
465        assert!(!w1.strong_eq(&w1) && w1.weak_eq(&w1));
466        assert!(!w1.strong_eq(&w2) && !w1.weak_eq(&w2));
467        assert!(!w1.strong_eq(&s1) && w1.weak_eq(&s1));
468        assert!(s1.strong_eq(&s1) && s1.weak_eq(&s1));
469    }
470
471    #[test]
472    fn test_conditions() {
473        let l = Limits::DEFAULT;
474        assert_eq!(
475            parse_condition(b"*, \"a\"", &l).unwrap_err().kind(),
476            ErrorKind::Malformed
477        );
478        assert_eq!(
479            parse_condition(b"\"a\" \"b\"", &l).unwrap_err().kind(),
480            ErrorKind::InvalidEntityTag
481        );
482        let e = parse_condition(b"\"a\", w/\"b\"", &l).unwrap_err();
483        assert_eq!((e.kind(), e.offset()), (ErrorKind::InvalidEntityTag, 5));
484        let s = EntityTag::strong(b"b").unwrap();
485        assert!(if_none_match(b"\"a\", , \"b\"", &s).unwrap());
486        assert!(if_none_match(b"*", &s).unwrap());
487        assert!(!if_match(b"*", None).unwrap());
488        assert!(if_match(b"\"a\", \"b\"", Some(&s)).unwrap());
489        assert!(if_none_match(b"\"a", &s).is_err());
490    }
491
492    #[test]
493    fn test_write_round_trip() {
494        let mut buf = [0u8; 8];
495        let mut out = crate::SliceSink::new(&mut buf);
496        let t = EntityTag::strong(b"xyz").unwrap();
497        t.write_to(&mut out).unwrap();
498        assert_eq!(EntityTag::parse(out.written()).unwrap(), t);
499        let mut small = [0u8; 3];
500        let mut out = crate::SliceSink::new(&mut small);
501        assert_eq!(t.write_to(&mut out), Err(WriteError::Full));
502    }
503}