Skip to main content

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}