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(¤t))?);
426/// assert!(!if_match(b"W/\"x\"", Some(¤t))?);
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}