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