parse_net/link.rs
1//! `Link` (RFC 8288 section 3): `#link-value`, where
2//! `link-value = "<" URI-Reference ">" *( OWS ";" OWS link-param )` and
3//! `link-param = token BWS [ "=" BWS ( token / quoted-string ) ]`.
4//!
5//! The target is validated as RFC 3986 characters with well-formed `%XX`
6//! triples and returned unresolved (url-lib resolves). Commas inside `<...>`
7//! do not split. A `title*` is decoded at parse time, as `filename*` is, so a
8//! view never holds an undecodable title.
9//!
10//! # Examples
11//!
12//! ```
13//! use parse_net::{parse_link, Limits};
14//!
15//! let links = parse_link(b"<https://x.test/p?page=2,3>; rel=\"next last\", </a>; rel=prev; crossorigin", &Limits::DEFAULT)?;
16//! let all: Vec<_> = links.iter().collect();
17//! assert_eq!(all[0].target(), b"https://x.test/p?page=2,3");
18//! assert!(all[0].has_rel("last") && all[1].has_rel("prev"));
19//! # Ok::<(), parse_net::ParseError>(())
20//! ```
21
22use crate::{
23 class,
24 disposition::Filename,
25 error::{ErrorKind, ParseError, Result, WriteError, err},
26 ext_value::ExtValue,
27 limits::Limits,
28 scan::control_kind,
29 text::{Word, is_token, parse_word, skip_ows, trim_ows},
30 write::{CountSink, Sink, reserve, write_word},
31};
32
33/// Limits for walking text that was already validated.
34const RELAXED: Limits = Limits::DEFAULT
35 .max_params(u16::MAX)
36 .max_list_members(u16::MAX)
37 .max_empty_elements(u16::MAX);
38
39/// Whether `b` may appear in a URI-reference outside a `%XX` triple
40/// (RFC 3986 sections 2.2 and 2.3).
41const fn is_uri_char(b: u8) -> bool {
42 class::is_unreserved(b)
43 || class::is_sub_delim(b)
44 || matches!(b, b':' | b'/' | b'?' | b'#' | b'[' | b']' | b'@')
45}
46
47fn refused(v: &[u8], at: usize, kind: ErrorKind) -> ParseError {
48 match v.get(at) {
49 Some(&b) if crate::scan::is_control(b) => ParseError::new(control_kind(b), at),
50 _ => ParseError::new(kind, at),
51 }
52}
53
54/// Validates the target after the `<` at `v[at - 1]`; returns the index of
55/// the closing `>`.
56fn target_end(v: &[u8], at: usize) -> Result<usize> {
57 let mut i = at;
58 loop {
59 match v.get(i) {
60 None => return err(ErrorKind::UnterminatedQuote, at - 1),
61 Some(b'>') => return Ok(i),
62 Some(b'%') => match (v.get(i + 1), v.get(i + 2)) {
63 (Some(&h), Some(&l)) if class::is_hexdig(h) && class::is_hexdig(l) => i += 3,
64 _ => return err(ErrorKind::InvalidPercentEncoding, i),
65 },
66 Some(&b) if is_uri_char(b) => i += 1,
67 Some(_) => return Err(refused(v, i, ErrorKind::InvalidByte)),
68 }
69 }
70}
71
72/// One `link-value` starting at `v[i]`; returns the index after its last
73/// parameter (before any OWS) and its parameter count.
74fn link_value(v: &[u8], i: usize, limits: &Limits) -> Result<(usize, usize)> {
75 if v.get(i) != Some(&b'<') {
76 return Err(refused(v, i, ErrorKind::InvalidByte));
77 }
78 let mut i = target_end(v, i + 1)? + 1;
79 let (mut count, mut title_star) = (0usize, false);
80 loop {
81 let mut j = i + skip_ows(v.get(i..).unwrap_or(&[]));
82 if v.get(j) != Some(&b';') {
83 return Ok((i, count));
84 }
85 j += 1;
86 j += skip_ows(v.get(j..).unwrap_or(&[]));
87 let n = v
88 .get(j..)
89 .unwrap_or(&[])
90 .iter()
91 .take_while(|&&b| class::is_tchar(b))
92 .count();
93 if n == 0 {
94 return Err(refused(v, j, ErrorKind::NotToken));
95 }
96 let name = v.get(j..j + n).unwrap_or(&[]);
97 let k = j + n;
98 let mut eq = k + skip_ows(v.get(k..).unwrap_or(&[]));
99 if v.get(eq) == Some(&b'=') {
100 eq += 1;
101 eq += skip_ows(v.get(eq..).unwrap_or(&[]));
102 let (w, used) = parse_word(v.get(eq..).unwrap_or(&[])).map_err(|e| match e.kind() {
103 ErrorKind::Empty => refused(v, eq, ErrorKind::MissingValue),
104 _ => e.shifted(eq),
105 })?;
106 // Only the first counts (RFC 8288 section 3.4.1), and either form
107 // of a link-param is read (section 3).
108 if name.eq_ignore_ascii_case(b"title*") && !title_star {
109 title_star = true;
110 let (t, at) = match w {
111 Word::Token(t) => (t, eq),
112 Word::Quoted(q) => match q.as_plain() {
113 Some(t) => (t, eq + 1),
114 None => return err(ErrorKind::InvalidCharset, eq),
115 },
116 };
117 let ext = ExtValue::parse(t).map_err(|e| e.shifted(at))?;
118 ext.check_text()
119 .map_err(|e| ParseError::new(e.kind(), at))?;
120 }
121 i = eq + used;
122 } else {
123 i = k;
124 }
125 count += 1;
126 if count > usize::from(limits.params) {
127 return err(ErrorKind::TooManyMembers, j);
128 }
129 }
130}
131
132/// Parses a `Link` field value.
133///
134/// # Errors
135///
136/// [`ErrorKind::InvalidByte`] (a target byte outside RFC 3986, a space in the
137/// target, a member that does not start with `<`);
138/// [`ErrorKind::UnterminatedQuote`] (no closing `>` or `"`);
139/// [`ErrorKind::InvalidPercentEncoding`]; [`ErrorKind::NotToken`] and
140/// [`ErrorKind::MissingValue`] for a parameter; the [`ExtValue::parse`]
141/// errors for `title*`; [`ErrorKind::TooManyMembers`] beyond
142/// `max_list_members` links or `max_params` parameters;
143/// [`ErrorKind::TooManyEmptyElements`]; [`ErrorKind::TrailingData`].
144///
145/// # Examples
146///
147/// ```
148/// use parse_net::{parse_link, ErrorKind, Limits};
149///
150/// let l = parse_link(b"<a>; rel=next; rel=prev", &Limits::DEFAULT)?;
151/// assert!(l.iter().next().is_some_and(|v| v.has_rel("next") && !v.has_rel("prev")));
152/// assert_eq!(parse_link(b"<a b>", &Limits::DEFAULT).unwrap_err().kind(), ErrorKind::InvalidByte);
153/// # Ok::<(), parse_net::ParseError>(())
154/// ```
155pub fn parse_link<'a>(value: &'a [u8], limits: &Limits) -> Result<Links<'a>> {
156 let lead = skip_ows(value);
157 let v = trim_ows(value);
158 let (mut i, mut count, mut empty) = (0usize, 0usize, 0usize);
159 loop {
160 i += skip_ows(v.get(i..).unwrap_or(&[]));
161 match v.get(i) {
162 None => break,
163 Some(b',') => {
164 empty += 1;
165 if empty > usize::from(limits.empty_elements) {
166 return err(ErrorKind::TooManyEmptyElements, lead + i);
167 }
168 i += 1;
169 continue;
170 }
171 Some(_) => {}
172 }
173 let at = i;
174 let (end, _) = link_value(v, i, limits).map_err(|e| e.shifted(lead))?;
175 count += 1;
176 if count > usize::from(limits.list_members) {
177 return err(ErrorKind::TooManyMembers, lead + at);
178 }
179 i = end + skip_ows(v.get(end..).unwrap_or(&[]));
180 match v.get(i) {
181 None => break,
182 Some(b',') => i += 1,
183 Some(_) => return Err(refused(v, i, ErrorKind::TrailingData).shifted(lead)),
184 }
185 }
186 Ok(Links { text: v, count })
187}
188
189/// A validated `Link` value.
190///
191/// # Examples
192///
193/// ```
194/// use parse_net::{parse_link, Limits};
195///
196/// let links = parse_link(b"</a>; rel=next, </b>; rel=prev", &Limits::DEFAULT)?;
197/// assert_eq!(links.len(), 2);
198/// assert!(links.iter().any(|v| v.has_rel("prev")));
199/// # Ok::<(), parse_net::ParseError>(())
200/// ```
201#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
202pub struct Links<'a> {
203 text: &'a [u8],
204 count: usize,
205}
206
207impl<'a> Links<'a> {
208 /// The link values, in order.
209 ///
210 /// # Examples
211 ///
212 /// ```
213 /// use parse_net::{parse_link, Limits};
214 ///
215 /// let links = parse_link(b"</a>; rel=next, </b>; rel=prev", &Limits::DEFAULT)?;
216 /// let targets: Vec<&[u8]> = links.iter().map(|v| v.target()).collect();
217 /// assert_eq!(targets, [&b"/a"[..], b"/b"]);
218 /// # Ok::<(), parse_net::ParseError>(())
219 /// ```
220 #[must_use]
221 pub fn iter(&self) -> LinkIter<'a> {
222 LinkIter { v: self.text, i: 0 }
223 }
224
225 /// The number of link values.
226 ///
227 /// # Examples
228 ///
229 /// ```
230 /// use parse_net::{parse_link, Limits};
231 ///
232 /// assert_eq!(parse_link(b"</a>, </b>, </c>", &Limits::DEFAULT)?.len(), 3);
233 /// # Ok::<(), parse_net::ParseError>(())
234 /// ```
235 #[must_use]
236 pub const fn len(&self) -> usize {
237 self.count
238 }
239
240 /// Whether the field holds no link value.
241 ///
242 /// # Examples
243 ///
244 /// ```
245 /// use parse_net::{parse_link, Limits};
246 ///
247 /// assert!(parse_link(b"", &Limits::DEFAULT)?.is_empty());
248 /// assert!(!parse_link(b"</a>", &Limits::DEFAULT)?.is_empty());
249 /// # Ok::<(), parse_net::ParseError>(())
250 /// ```
251 #[must_use]
252 pub const fn is_empty(&self) -> bool {
253 self.count == 0
254 }
255}
256
257/// The link values of a validated [`Links`].
258///
259/// # Examples
260///
261/// ```
262/// use parse_net::{link::LinkIter, parse_link, Limits};
263///
264/// let links = parse_link(b"</a>, </b>", &Limits::DEFAULT)?;
265/// let mut it: LinkIter<'_> = links.iter();
266/// assert_eq!(it.next().map(|v| v.target()), Some(&b"/a"[..]));
267/// assert_eq!(it.count(), 1);
268/// # Ok::<(), parse_net::ParseError>(())
269/// ```
270#[derive(Clone, Debug)]
271pub struct LinkIter<'a> {
272 v: &'a [u8],
273 i: usize,
274}
275
276impl<'a> Iterator for LinkIter<'a> {
277 type Item = LinkValue<'a>;
278
279 fn next(&mut self) -> Option<LinkValue<'a>> {
280 let v = self.v;
281 while matches!(v.get(self.i), Some(b' ' | b'\t' | b',')) {
282 self.i += 1;
283 }
284 let start = self.i;
285 if start >= v.len() {
286 return None;
287 }
288 let (end, count) = link_value(v, start, &RELAXED).ok()?;
289 let close = target_end(v, start + 1).ok()?;
290 self.i = end;
291 Some(LinkValue {
292 target: v.get(start + 1..close).unwrap_or(&[]),
293 params: LinkParams {
294 text: v.get(close + 1..end).unwrap_or(&[]),
295 count,
296 },
297 })
298 }
299}
300
301/// One link value: a target and its parameters.
302///
303/// # Examples
304///
305/// ```
306/// use parse_net::{parse_link, Limits};
307///
308/// let v = parse_link(b"<https://x.test/>; rel=canonical", &Limits::DEFAULT)?.iter().next().expect("one link");
309/// assert_eq!(v.target(), b"https://x.test/");
310/// assert!(v.has_rel("canonical"));
311/// # Ok::<(), parse_net::ParseError>(())
312/// ```
313#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
314pub struct LinkValue<'a> {
315 target: &'a [u8],
316 params: LinkParams<'a>,
317}
318
319impl<'a> LinkValue<'a> {
320 /// The URI-reference between `<` and `>`, as sent (not resolved).
321 ///
322 /// # Examples
323 ///
324 /// ```
325 /// use parse_net::{parse_link, Limits};
326 ///
327 /// let v = parse_link(b"</docs/%7Euser>", &Limits::DEFAULT)?.iter().next().expect("one link");
328 /// assert_eq!(v.target(), b"/docs/%7Euser");
329 /// # Ok::<(), parse_net::ParseError>(())
330 /// ```
331 #[must_use]
332 pub const fn target(&self) -> &'a [u8] {
333 self.target
334 }
335
336 /// The parameters, in order.
337 ///
338 /// # Examples
339 ///
340 /// ```
341 /// use parse_net::{parse_link, Limits};
342 ///
343 /// let v = parse_link(b"</a>; rel=next; crossorigin", &Limits::DEFAULT)?.iter().next().expect("one link");
344 /// let p = v.params();
345 /// assert_eq!(p.len(), 2);
346 /// assert!(p.iter().any(|(n, w)| n == b"crossorigin" && w.is_none()));
347 /// # Ok::<(), parse_net::ParseError>(())
348 /// ```
349 #[must_use]
350 pub const fn params(&self) -> LinkParams<'a> {
351 self.params
352 }
353
354 /// The value of the first parameter named `name` (case-insensitive):
355 /// `None` when absent, `Some(None)` when it has no value.
356 fn first(&self, name: &str) -> Option<Option<Word<'a>>> {
357 self.params
358 .iter()
359 .find(|(n, _)| n.eq_ignore_ascii_case(name.as_bytes()))
360 .map(|(_, w)| w)
361 }
362
363 /// The first `rel` value; later `rel` parameters are ignored (RFC 8288
364 /// section 3.3).
365 ///
366 /// # Examples
367 ///
368 /// ```
369 /// use parse_net::{parse_link, Limits};
370 ///
371 /// let v = parse_link(b"</a>; rel=\"next last\"; rel=prev", &Limits::DEFAULT)?.iter().next().expect("one link");
372 /// assert!(v.rel().is_some_and(|w| w.eq_bytes(b"next last")));
373 /// # Ok::<(), parse_net::ParseError>(())
374 /// ```
375 #[must_use]
376 pub fn rel(&self) -> Option<Word<'a>> {
377 self.first("rel").flatten()
378 }
379
380 /// The relation types of the first `rel`, split on spaces. A relation
381 /// type is a token or a URI, so it never needs a quoted pair; one that
382 /// has one is yielded as sent.
383 ///
384 /// # Examples
385 ///
386 /// ```
387 /// use parse_net::{parse_link, Limits};
388 ///
389 /// let v = parse_link(b"</a>; rel=\"next last\"", &Limits::DEFAULT)?.iter().next().expect("one link");
390 /// let rels: Vec<&[u8]> = v.rels().collect();
391 /// assert_eq!(rels, [&b"next"[..], b"last"]);
392 /// # Ok::<(), parse_net::ParseError>(())
393 /// ```
394 #[must_use]
395 pub fn rels(&self) -> RelIter<'a> {
396 let text = match self.rel() {
397 Some(Word::Token(t)) => t,
398 Some(Word::Quoted(q)) => q.raw(),
399 None => &[],
400 };
401 RelIter { rest: text }
402 }
403
404 /// Whether the first `rel` lists `rel` (ASCII case-insensitive).
405 ///
406 /// # Examples
407 ///
408 /// ```
409 /// use parse_net::{parse_link, Limits};
410 ///
411 /// let l = parse_link(b"</style.css>; rel=\"Preload stylesheet\"", &Limits::DEFAULT)?;
412 /// let v = l.iter().next().expect("one link");
413 /// assert!(v.has_rel("preload") && v.has_rel("STYLESHEET") && !v.has_rel("style"));
414 /// # Ok::<(), parse_net::ParseError>(())
415 /// ```
416 #[must_use]
417 pub fn has_rel(&self, rel: &str) -> bool {
418 self.rels().any(|r| r.eq_ignore_ascii_case(rel.as_bytes()))
419 }
420
421 /// The first `anchor`.
422 ///
423 /// # Examples
424 ///
425 /// ```
426 /// use parse_net::{parse_link, Limits};
427 ///
428 /// let v = parse_link(b"</a>; rel=next; anchor=\"#top\"", &Limits::DEFAULT)?.iter().next().expect("one link");
429 /// assert!(v.anchor().is_some_and(|w| w.eq_bytes(b"#top")));
430 /// # Ok::<(), parse_net::ParseError>(())
431 /// ```
432 #[must_use]
433 pub fn anchor(&self) -> Option<Word<'a>> {
434 self.first("anchor").flatten()
435 }
436
437 /// Every `hreflang`, in order (the one parameter RFC 8288 lets repeat).
438 ///
439 /// # Examples
440 ///
441 /// ```
442 /// use parse_net::{parse_link, Limits};
443 ///
444 /// let v = parse_link(b"</a>; rel=alternate; hreflang=en; hreflang=de", &Limits::DEFAULT)?.iter().next().expect("one link");
445 /// let langs: Vec<_> = v.hreflang().map(|w| w.as_str()).collect();
446 /// assert_eq!(langs, [Some("en"), Some("de")]);
447 /// # Ok::<(), parse_net::ParseError>(())
448 /// ```
449 #[must_use]
450 pub fn hreflang(&self) -> HreflangIter<'a> {
451 HreflangIter {
452 params: self.params.iter(),
453 }
454 }
455
456 /// The title: the first `title*` (RFC 8187, decoded like `filename*`)
457 /// when present, else the first `title`.
458 ///
459 /// # Examples
460 ///
461 /// ```
462 /// use parse_net::{parse_link, Limits};
463 ///
464 /// let l = parse_link(b"<a>; title=\"EUR\"; title*=UTF-8''%E2%82%AC", &Limits::DEFAULT)?;
465 /// let mut buf = [0u8; 8];
466 /// let t = l.iter().next().and_then(|v| v.title()).expect("a title");
467 /// assert_eq!(t.decode_into(&mut buf)?, "\u{20ac}");
468 /// # Ok::<(), parse_net::ParseError>(())
469 /// ```
470 #[must_use]
471 pub fn title(&self) -> Option<Filename<'a>> {
472 let ext = self.first("title*").flatten().and_then(|w| match w {
473 Word::Token(t) => ExtValue::parse(t).ok(),
474 Word::Quoted(q) => ExtValue::parse(q.as_plain()?).ok(),
475 });
476 ext.map(Filename::Ext)
477 .or_else(|| self.first("title").flatten().map(Filename::Plain))
478 }
479
480 /// The first `media`.
481 ///
482 /// # Examples
483 ///
484 /// ```
485 /// use parse_net::{parse_link, Limits};
486 ///
487 /// let v = parse_link(b"</s.css>; rel=stylesheet; media=\"screen\"", &Limits::DEFAULT)?.iter().next().expect("one link");
488 /// assert!(v.media().is_some_and(|w| w.eq_bytes(b"screen")));
489 /// # Ok::<(), parse_net::ParseError>(())
490 /// ```
491 #[must_use]
492 pub fn media(&self) -> Option<Word<'a>> {
493 self.first("media").flatten()
494 }
495
496 /// The first `type`.
497 ///
498 /// # Examples
499 ///
500 /// ```
501 /// use parse_net::{parse_link, Limits};
502 ///
503 /// let v = parse_link(b"</a.json>; rel=alternate; type=\"application/json\"", &Limits::DEFAULT)?.iter().next().expect("one link");
504 /// assert!(v.type_().is_some_and(|w| w.eq_bytes(b"application/json")));
505 /// # Ok::<(), parse_net::ParseError>(())
506 /// ```
507 #[must_use]
508 pub fn type_(&self) -> Option<Word<'a>> {
509 self.first("type").flatten()
510 }
511}
512
513/// The parameters of a link value.
514///
515/// # Examples
516///
517/// ```
518/// use parse_net::{parse_link, Limits};
519///
520/// let v = parse_link(b"</a>; rel=next; title=\"Next\"", &Limits::DEFAULT)?.iter().next().expect("one link");
521/// let p = v.params();
522/// assert_eq!(p.len(), 2);
523/// assert!(p.iter().any(|(n, w)| n == b"title" && w.is_some_and(|w| w.eq_bytes(b"Next"))));
524/// # Ok::<(), parse_net::ParseError>(())
525/// ```
526#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
527pub struct LinkParams<'a> {
528 text: &'a [u8],
529 count: usize,
530}
531
532impl<'a> LinkParams<'a> {
533 /// The parameters, in order: the name as sent and the value, if any.
534 ///
535 /// # Examples
536 ///
537 /// ```
538 /// use parse_net::{parse_link, Limits};
539 ///
540 /// let v = parse_link(b"</a>; rel=next; title=\"Next\"", &Limits::DEFAULT)?.iter().next().expect("one link");
541 /// let names: Vec<&[u8]> = v.params().iter().map(|(n, _)| n).collect();
542 /// assert_eq!(names, [&b"rel"[..], b"title"]);
543 /// # Ok::<(), parse_net::ParseError>(())
544 /// ```
545 #[must_use]
546 pub fn iter(&self) -> LinkParamIter<'a> {
547 LinkParamIter { s: self.text, i: 0 }
548 }
549
550 /// The number of parameters.
551 ///
552 /// # Examples
553 ///
554 /// ```
555 /// use parse_net::{parse_link, Limits};
556 ///
557 /// let v = parse_link(b"</a>; rel=next; title=x; crossorigin", &Limits::DEFAULT)?.iter().next().expect("one link");
558 /// assert_eq!(v.params().len(), 3);
559 /// # Ok::<(), parse_net::ParseError>(())
560 /// ```
561 #[must_use]
562 pub const fn len(&self) -> usize {
563 self.count
564 }
565
566 /// Whether there are no parameters.
567 ///
568 /// # Examples
569 ///
570 /// ```
571 /// use parse_net::{parse_link, Limits};
572 ///
573 /// let v = parse_link(b"</a>", &Limits::DEFAULT)?.iter().next().expect("one link");
574 /// assert!(v.params().is_empty());
575 /// # Ok::<(), parse_net::ParseError>(())
576 /// ```
577 #[must_use]
578 pub const fn is_empty(&self) -> bool {
579 self.count == 0
580 }
581}
582
583/// The parameters of a validated [`LinkParams`].
584///
585/// # Examples
586///
587/// ```
588/// use parse_net::{link::LinkParamIter, parse_link, Limits};
589///
590/// let v = parse_link(b"</a>; rel=next", &Limits::DEFAULT)?.iter().next().expect("one link");
591/// let mut it: LinkParamIter<'_> = v.params().iter();
592/// assert_eq!(it.next().map(|(n, _)| n), Some(&b"rel"[..]));
593/// assert!(it.next().is_none());
594/// # Ok::<(), parse_net::ParseError>(())
595/// ```
596#[derive(Clone, Debug)]
597pub struct LinkParamIter<'a> {
598 s: &'a [u8],
599 i: usize,
600}
601
602impl<'a> Iterator for LinkParamIter<'a> {
603 type Item = (&'a [u8], Option<Word<'a>>);
604
605 fn next(&mut self) -> Option<Self::Item> {
606 let s = self.s;
607 let mut j = self.i + skip_ows(s.get(self.i..)?);
608 if s.get(j) != Some(&b';') {
609 return None;
610 }
611 j += 1;
612 j += skip_ows(s.get(j..)?);
613 let n = s
614 .get(j..)?
615 .iter()
616 .take_while(|&&b| class::is_tchar(b))
617 .count();
618 let name = s.get(j..j + n)?;
619 let k = j + n;
620 let eq = k + skip_ows(s.get(k..)?);
621 if s.get(eq) == Some(&b'=') {
622 let at = eq + 1 + skip_ows(s.get(eq + 1..)?);
623 let (w, used) = parse_word(s.get(at..)?).ok()?;
624 self.i = at + used;
625 Some((name, Some(w)))
626 } else {
627 self.i = k;
628 Some((name, None))
629 }
630 }
631}
632
633/// The relation types of a `rel` value.
634///
635/// # Examples
636///
637/// ```
638/// use parse_net::{parse_link, Limits};
639///
640/// let v = parse_link(b"</s.css>; rel=\"preload stylesheet\"", &Limits::DEFAULT)?.iter().next().expect("one link");
641/// let mut rels = v.rels();
642/// assert_eq!(rels.next(), Some(&b"preload"[..]));
643/// assert_eq!(rels.next(), Some(&b"stylesheet"[..]));
644/// assert_eq!(rels.next(), None);
645/// # Ok::<(), parse_net::ParseError>(())
646/// ```
647#[derive(Clone, Debug)]
648pub struct RelIter<'a> {
649 rest: &'a [u8],
650}
651
652impl<'a> Iterator for RelIter<'a> {
653 type Item = &'a [u8];
654
655 fn next(&mut self) -> Option<&'a [u8]> {
656 let start = self.rest.iter().position(|&b| b != b' ')?;
657 let rest = self.rest.get(start..)?;
658 let n = rest.iter().position(|&b| b == b' ').unwrap_or(rest.len());
659 let (r, tail) = rest.split_at(n);
660 self.rest = tail;
661 Some(r)
662 }
663}
664
665/// The `hreflang` values of a link value.
666///
667/// # Examples
668///
669/// ```
670/// use parse_net::{parse_link, Limits};
671///
672/// let v = parse_link(b"</a>; hreflang=en; rel=alternate; hreflang=\"fr\"", &Limits::DEFAULT)?.iter().next().expect("one link");
673/// assert_eq!(v.hreflang().count(), 2);
674/// assert!(v.hreflang().last().is_some_and(|w| w.eq_bytes(b"fr")));
675/// # Ok::<(), parse_net::ParseError>(())
676/// ```
677#[derive(Clone, Debug)]
678pub struct HreflangIter<'a> {
679 params: LinkParamIter<'a>,
680}
681
682impl<'a> Iterator for HreflangIter<'a> {
683 type Item = Word<'a>;
684
685 fn next(&mut self) -> Option<Word<'a>> {
686 self.params.by_ref().find_map(|(n, w)| {
687 if n.eq_ignore_ascii_case(b"hreflang") {
688 w
689 } else {
690 None
691 }
692 })
693 }
694}
695
696/// Writes one link value: `<target>; name=value` for each parameter, values
697/// through [`write_word`]. An empty value writes
698/// the name alone (`; crossorigin`), which parses back as a parameter with no
699/// value.
700///
701/// # Errors
702///
703/// [`WriteError::Forbidden`] for a target byte that is not an RFC 3986
704/// character (`<`, `>`, SP, a control, a non-ASCII byte) or a `%` not
705/// followed by two hex digits, and for a control byte in a value;
706/// [`WriteError::NotToken`] for a name; [`WriteError::Full`] (nothing written
707/// when the sink reports its room).
708///
709/// # Examples
710///
711/// ```
712/// use parse_net::{link, SliceSink};
713///
714/// let mut buf = [0u8; 64];
715/// let mut out = SliceSink::new(&mut buf);
716/// link::write_link(b"/page/2", &[("rel", "next"), ("title", "Page 2"), ("crossorigin", "")], &mut out)?;
717/// assert_eq!(out.written(), b"</page/2>; rel=next; title=\"Page 2\"; crossorigin");
718/// # Ok::<(), parse_net::WriteError>(())
719/// ```
720pub fn write_link(
721 target: &[u8],
722 params: &[(&str, &str)],
723 out: &mut impl Sink,
724) -> core::result::Result<(), WriteError> {
725 let mut i = 0usize;
726 while let Some(&b) = target.get(i) {
727 if b == b'%' {
728 match (target.get(i + 1), target.get(i + 2)) {
729 (Some(&h), Some(&l)) if class::is_hexdig(h) && class::is_hexdig(l) => i += 3,
730 _ => return Err(WriteError::forbidden(i)),
731 }
732 } else if is_uri_char(b) {
733 i += 1;
734 } else {
735 return Err(WriteError::forbidden(i));
736 }
737 }
738 if params.iter().any(|(name, _)| !is_token(name.as_bytes())) {
739 return Err(WriteError::NotToken);
740 }
741 // A `title*` its own parser would refuse is never written. As there, only
742 // the first with a value counts, and any later one is ignored.
743 let title = params
744 .iter()
745 .find(|(name, value)| name.eq_ignore_ascii_case("title*") && !value.is_empty());
746 if let Some((_, value)) = title {
747 let ext = ExtValue::parse(value.as_bytes());
748 if ext.and_then(|e| e.check_text()).is_err() {
749 return Err(WriteError::forbidden(0));
750 }
751 }
752 let mut count = CountSink::new();
753 put_link(target, params, &mut count)?;
754 reserve(out, count.count())?;
755 put_link(target, params, out)
756}
757
758fn put_link(
759 target: &[u8],
760 params: &[(&str, &str)],
761 out: &mut impl Sink,
762) -> core::result::Result<(), WriteError> {
763 out.put(b"<")?;
764 out.put(target)?;
765 out.put(b">")?;
766 for (name, value) in params {
767 out.put(b"; ")?;
768 out.put(name.as_bytes())?;
769 if !value.is_empty() {
770 out.put(b"=")?;
771 write_word(value.as_bytes(), out)?;
772 }
773 }
774 Ok(())
775}
776
777#[cfg(test)]
778mod tests {
779 use super::*;
780 use std::{vec, vec::Vec};
781
782 fn one(v: &[u8]) -> LinkValue<'_> {
783 let l = parse_link(v, &Limits::DEFAULT).unwrap();
784 assert_eq!(l.len(), 1);
785 l.iter().next().unwrap()
786 }
787
788 fn k(v: &[u8]) -> ErrorKind {
789 parse_link(v, &Limits::DEFAULT).unwrap_err().kind()
790 }
791
792 #[test]
793 fn test_link_edge_cases() {
794 assert_eq!(one(b"<a>; rel=next").target(), b"a");
795 assert_eq!(one(b"<a,b>; rel=next").target(), b"a,b");
796 let v = one(b"<a>; rel=\"next prev\"");
797 assert_eq!(v.rels().collect::<Vec<_>>(), vec![&b"next"[..], b"prev"]);
798 assert!(
799 one(b"<a>;rel=next;rel=prev")
800 .rel()
801 .unwrap()
802 .eq_bytes(b"next")
803 );
804 assert_eq!(k(b"<a"), ErrorKind::UnterminatedQuote);
805 assert_eq!(k(b"<a b>"), ErrorKind::InvalidByte);
806 assert_eq!(k(b"<a%2>"), ErrorKind::InvalidPercentEncoding);
807 assert_eq!(k(b"a"), ErrorKind::InvalidByte);
808 assert_eq!(k(b"<a>; rel=\"x"), ErrorKind::UnterminatedQuote);
809 assert_eq!(k(b"<a>; =x"), ErrorKind::NotToken);
810 assert_eq!(k(b"<a>; rel="), ErrorKind::MissingValue);
811 assert_eq!(k(b"<a> x"), ErrorKind::TrailingData);
812 assert_eq!(k(b"<a>\r\n"), ErrorKind::BareCr);
813 let v = one(b"<a>; crossorigin");
814 assert_eq!(v.params().iter().next(), Some((&b"crossorigin"[..], None)));
815 assert!(v.rel().is_none() && v.rels().next().is_none());
816 assert_eq!(
817 one(b"<a>; rel = next").rel().unwrap().as_plain(),
818 Some(&b"next"[..])
819 );
820 assert!(parse_link(b"", &Limits::DEFAULT).unwrap().is_empty());
821 assert_eq!(
822 parse_link(b" , <a>,, <b> ,", &Limits::DEFAULT)
823 .unwrap()
824 .iter()
825 .count(),
826 2
827 );
828 assert_eq!(
829 one(b"<http://[::1]:8080/a?b#c>").target(),
830 b"http://[::1]:8080/a?b#c"
831 );
832 }
833
834 #[test]
835 fn test_link_params() {
836 let v = one(
837 b"<a>; anchor=\"#x\"; hreflang=en; hreflang=\"de\"; media=screen; type=\"text/html\"",
838 );
839 assert!(v.anchor().unwrap().eq_bytes(b"#x"));
840 let langs: Vec<_> = v.hreflang().map(|w| w.as_plain().unwrap()).collect();
841 assert_eq!(langs, vec![&b"en"[..], b"de"]);
842 assert!(v.media().unwrap().eq_bytes(b"screen"));
843 assert!(v.type_().unwrap().eq_bytes(b"text/html"));
844 assert_eq!(v.params().len(), 5);
845 assert_eq!(v.params().iter().count(), 5);
846 }
847
848 #[test]
849 fn test_link_title_ext_preferred_and_checked() {
850 let mut buf = [0u8; 16];
851 let v = one(b"<a>; title*=UTF-8''%E2%82%AC; title=x");
852 assert_eq!(
853 v.title().unwrap().decode_into(&mut buf).unwrap(),
854 "\u{20ac}"
855 );
856 assert!(matches!(
857 one(b"<a>; title=x").title(),
858 Some(Filename::Plain(_))
859 ));
860 assert!(matches!(
861 one(b"<a>; title*=\"UTF-8''x\"").title(),
862 Some(crate::Filename::Ext(_))
863 ));
864 assert_eq!(k(b"<a>; title*=\"UTF-8\\''x\""), ErrorKind::InvalidCharset);
865 assert_eq!(k(b"<a>; title*=UTF-8''%0a"), ErrorKind::ControlInDecoded);
866 assert_eq!(k(b"<a>; title*=x"), ErrorKind::InvalidCharset);
867 }
868
869 #[test]
870 fn test_link_limits_plus_one_refused() {
871 let l = Limits::DEFAULT.max_list_members(2).max_params(2);
872 assert!(parse_link(b"<a>, <b>", &l).is_ok());
873 assert_eq!(
874 parse_link(b"<a>, <b>, <c>", &l).unwrap_err().kind(),
875 ErrorKind::TooManyMembers
876 );
877 assert!(parse_link(b"<a>;x;y", &l).is_ok());
878 assert_eq!(
879 parse_link(b"<a>;x;y;z", &l).unwrap_err().kind(),
880 ErrorKind::TooManyMembers
881 );
882 let e = Limits::DEFAULT.max_empty_elements(1);
883 assert!(parse_link(b"<a>,,<b>", &e).is_ok());
884 assert_eq!(
885 parse_link(b"<a>,,,<b>", &e).unwrap_err().kind(),
886 ErrorKind::TooManyEmptyElements
887 );
888 }
889
890 #[test]
891 fn test_write_link_refusals_and_round_trip() {
892 let mut buf = [0u8; 64];
893 let mut out = crate::SliceSink::new(&mut buf);
894 assert_eq!(
895 write_link(b"a b", &[], &mut out),
896 Err(WriteError::Forbidden { at: 1 })
897 );
898 assert_eq!(
899 write_link(b"<a", &[], &mut out),
900 Err(WriteError::Forbidden { at: 0 })
901 );
902 assert_eq!(
903 write_link(b"a%2", &[], &mut out),
904 Err(WriteError::Forbidden { at: 1 })
905 );
906 assert_eq!(
907 write_link(b"a", &[("x y", "1")], &mut out),
908 Err(WriteError::NotToken)
909 );
910 assert_eq!(
911 write_link(b"a", &[("x", "\r")], &mut out),
912 Err(WriteError::Forbidden { at: 0 })
913 );
914 assert!(out.is_empty());
915 let mut small = [0u8; 4];
916 let mut s = crate::SliceSink::new(&mut small);
917 assert_eq!(write_link(b"abcd", &[], &mut s), Err(WriteError::Full));
918 assert!(s.is_empty());
919 write_link(
920 b"/x?a=1,2",
921 &[("rel", "next prev"), ("crossorigin", "")],
922 &mut out,
923 )
924 .unwrap();
925 let v = one(out.written());
926 assert_eq!(v.target(), b"/x?a=1,2");
927 assert!(v.has_rel("prev"));
928 assert_eq!(v.params().iter().nth(1), Some((&b"crossorigin"[..], None)));
929 }
930
931 #[test]
932 fn test_link_long_input_linear() {
933 let mut v = Vec::new();
934 for _ in 0..1000 {
935 v.extend_from_slice(b"<a,b>; rel=x, ");
936 }
937 let l = Limits::DEFAULT.max_list_members(u16::MAX);
938 assert_eq!(parse_link(&v, &l).unwrap().iter().count(), 1000);
939 }
940}