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}