1use core::fmt;
18
19use crate::{
20 class,
21 error::{ErrorKind, Result, err},
22 limits::Limits,
23 scan::control_kind,
24};
25
26#[inline]
37#[must_use]
38pub fn is_token(s: &[u8]) -> bool {
39 !s.is_empty() && class::all(s, class::Class::TCHAR)
40}
41
42#[inline]
52#[must_use]
53pub const fn is_ows(b: u8) -> bool {
54 b == b' ' || b == b'\t'
55}
56
57#[inline]
73#[must_use]
74pub fn trim_ows(mut s: &[u8]) -> &[u8] {
75 while let [first, rest @ ..] = s {
76 if is_ows(*first) {
77 s = rest;
78 } else {
79 break;
80 }
81 }
82 while let [rest @ .., last] = s {
83 if is_ows(*last) {
84 s = rest;
85 } else {
86 break;
87 }
88 }
89 s
90}
91
92#[inline]
100#[must_use]
101pub fn skip_ows(s: &[u8]) -> usize {
102 s.iter().take_while(|&&b| is_ows(b)).count()
103}
104
105pub fn parse_token(s: &[u8]) -> Result<(&[u8], usize)> {
123 let n = s.iter().take_while(|&&b| class::is_tchar(b)).count();
124 if n == 0 {
125 return err(ErrorKind::Empty, 0);
126 }
127 Ok((s.get(..n).unwrap_or(&[]), n))
128}
129
130#[inline]
132const fn refused(b: u8) -> ErrorKind {
133 if b < 0x20 || b == 0x7f {
134 control_kind(b)
135 } else {
136 ErrorKind::InvalidByte
137 }
138}
139
140pub fn parse_quoted(s: &[u8]) -> Result<(Quoted<'_>, usize)> {
163 let Some((&b'"', body)) = s.split_first() else {
164 return err(ErrorKind::InvalidByte, 0);
165 };
166 let mut has_escapes = false;
167 let mut i = 0usize;
168 let mut bytes = body.iter();
169 while let Some(&b) = bytes.next() {
170 match b {
171 b'"' => {
172 let raw = body.get(..i).unwrap_or(&[]);
173 return Ok((Quoted { raw, has_escapes }, i + 2));
174 }
175 b'\\' => {
176 has_escapes = true;
177 match bytes.next() {
178 None => return err(ErrorKind::UnterminatedQuote, s.len()),
179 Some(&e) if class::is_quoted_pair_char(e) => i += 2,
180 Some(_) => return err(ErrorKind::InvalidQuotedPair, i + 2),
181 }
182 }
183 _ if class::is_qdtext(b) => i += 1,
184 _ => return err(refused(b), i + 1),
185 }
186 }
187 err(ErrorKind::UnterminatedQuote, s.len())
188}
189
190pub fn parse_word(s: &[u8]) -> Result<(Word<'_>, usize)> {
208 if s.first() == Some(&b'"') {
209 let (q, n) = parse_quoted(s)?;
210 Ok((Word::Quoted(q), n))
211 } else {
212 let (t, n) = parse_token(s)?;
213 Ok((Word::Token(t), n))
214 }
215}
216
217pub fn parse_comment<'a>(s: &'a [u8], limits: &Limits) -> Result<(Comment<'a>, usize)> {
240 if s.first() != Some(&b'(') {
241 return err(ErrorKind::InvalidByte, 0);
242 }
243 let end = comment_end(s, 0, limits)?;
244 let (end, max_depth) = end;
245 let raw = s.get(1..end - 1).unwrap_or(&[]);
246 Ok((Comment { raw, max_depth }, end))
247}
248
249pub(crate) fn comment_end(s: &[u8], start: usize, limits: &Limits) -> Result<(usize, u8)> {
252 let limit = u16::from(limits.comment_depth);
253 let mut depth: u16 = 0;
254 let mut max: u16 = 0;
255 let mut i = start;
256 let mut bytes = s.get(start..).unwrap_or(&[]).iter();
257 while let Some(&b) = bytes.next() {
258 match b {
259 b'(' => {
260 depth += 1;
261 if depth > limit {
262 return err(ErrorKind::CommentTooDeep, i);
263 }
264 max = max.max(depth);
265 i += 1;
266 }
267 b')' => {
268 depth -= 1;
269 i += 1;
270 if depth == 0 {
271 return Ok((i, u8::try_from(max).unwrap_or(u8::MAX)));
273 }
274 }
275 b'\\' => match bytes.next() {
276 None => return err(ErrorKind::UnbalancedComment, s.len()),
277 Some(&e) if class::is_quoted_pair_char(e) => i += 2,
278 Some(_) => return err(ErrorKind::InvalidQuotedPair, i + 1),
279 },
280 _ if class::is_ctext(b) => i += 1,
281 _ => return err(refused(b), i),
282 }
283 }
284 err(ErrorKind::UnbalancedComment, s.len())
285}
286
287#[derive(Clone, Copy, PartialEq, Eq, Hash)]
308pub struct Quoted<'a> {
309 raw: &'a [u8],
310 has_escapes: bool,
311}
312
313impl<'a> Quoted<'a> {
314 #[must_use]
316 pub const fn raw(&self) -> &'a [u8] {
317 self.raw
318 }
319
320 #[must_use]
322 pub const fn has_escapes(&self) -> bool {
323 self.has_escapes
324 }
325
326 #[must_use]
328 pub const fn as_plain(&self) -> Option<&'a [u8]> {
329 if self.has_escapes {
330 None
331 } else {
332 Some(self.raw)
333 }
334 }
335
336 #[must_use]
338 pub fn unescaped_len(&self) -> usize {
339 if self.has_escapes {
340 self.bytes().count()
341 } else {
342 self.raw.len()
343 }
344 }
345
346 #[must_use]
348 pub fn bytes(&self) -> Unescape<'a> {
349 Unescape {
350 rest: self.raw.iter(),
351 }
352 }
353
354 pub fn unescape_into<'b>(&self, out: &'b mut [u8]) -> Result<&'b [u8]> {
362 let len = self.unescaped_len();
363 let Some(dst) = out.get_mut(..len) else {
364 return err(ErrorKind::BufferTooSmall, 0);
365 };
366 for (d, b) in dst.iter_mut().zip(self.bytes()) {
367 *d = b;
368 }
369 Ok(dst)
370 }
371
372 #[must_use]
374 pub fn eq_bytes(&self, other: &[u8]) -> bool {
375 match self.as_plain() {
376 Some(p) => p == other,
377 None => self.bytes().eq(other.iter().copied()),
378 }
379 }
380
381 #[must_use]
384 pub fn eq_ignore_ascii_case(&self, other: &[u8]) -> bool {
385 match self.as_plain() {
386 Some(p) => p.eq_ignore_ascii_case(other),
387 None => {
388 let mut theirs = other.iter();
389 self.bytes()
390 .all(|b| theirs.next().is_some_and(|o| o.eq_ignore_ascii_case(&b)))
391 && theirs.next().is_none()
392 }
393 }
394 }
395}
396
397impl fmt::Debug for Quoted<'_> {
398 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
399 f.write_str("Quoted(\"")?;
400 for &b in self.raw {
401 for c in core::ascii::escape_default(b) {
402 f.write_str(core::str::from_utf8(&[c]).unwrap_or("?"))?;
403 }
404 }
405 f.write_str("\")")
406 }
407}
408
409#[derive(Clone, Debug)]
411pub struct Unescape<'a> {
412 rest: core::slice::Iter<'a, u8>,
413}
414
415impl Iterator for Unescape<'_> {
416 type Item = u8;
417
418 #[inline]
419 fn next(&mut self) -> Option<u8> {
420 match self.rest.next() {
421 Some(&b'\\') => self.rest.next().copied(),
422 other => other.copied(),
423 }
424 }
425
426 fn size_hint(&self) -> (usize, Option<usize>) {
427 let n = self.rest.len();
428 (n.div_ceil(2), Some(n))
429 }
430}
431
432#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
451pub enum Word<'a> {
452 Token(&'a [u8]),
454 Quoted(Quoted<'a>),
456}
457
458impl<'a> Word<'a> {
459 #[must_use]
461 pub const fn as_plain(&self) -> Option<&'a [u8]> {
462 match self {
463 Self::Token(t) => Some(t),
464 Self::Quoted(q) => q.as_plain(),
465 }
466 }
467
468 #[must_use]
470 pub fn as_str(&self) -> Option<&'a str> {
471 self.as_plain().and_then(|p| core::str::from_utf8(p).ok())
472 }
473
474 #[must_use]
476 pub fn eq_bytes(&self, other: &[u8]) -> bool {
477 match self {
478 Self::Token(t) => *t == other,
479 Self::Quoted(q) => q.eq_bytes(other),
480 }
481 }
482
483 #[must_use]
485 pub fn eq_ignore_ascii_case(&self, other: &[u8]) -> bool {
486 match self {
487 Self::Token(t) => t.eq_ignore_ascii_case(other),
488 Self::Quoted(q) => q.eq_ignore_ascii_case(other),
489 }
490 }
491
492 pub fn unescape_into<'b>(&self, out: &'b mut [u8]) -> Result<&'b [u8]> {
498 match self {
499 Self::Token(t) => {
500 let Some(dst) = out.get_mut(..t.len()) else {
501 return err(ErrorKind::BufferTooSmall, 0);
502 };
503 dst.copy_from_slice(t);
504 Ok(dst)
505 }
506 Self::Quoted(q) => q.unescape_into(out),
507 }
508 }
509
510 #[must_use]
512 pub fn len(&self) -> usize {
513 match self {
514 Self::Token(t) => t.len(),
515 Self::Quoted(q) => q.unescaped_len(),
516 }
517 }
518
519 #[must_use]
521 pub fn is_empty(&self) -> bool {
522 match self {
523 Self::Token(t) => t.is_empty(),
524 Self::Quoted(q) => q.raw.is_empty(),
525 }
526 }
527}
528
529#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
542pub struct Comment<'a> {
543 raw: &'a [u8],
544 max_depth: u8,
545}
546
547impl<'a> Comment<'a> {
548 #[must_use]
550 pub const fn raw(&self) -> &'a [u8] {
551 self.raw
552 }
553
554 #[must_use]
556 pub const fn max_depth(&self) -> u8 {
557 self.max_depth
558 }
559}
560
561#[cfg(test)]
562mod tests {
563 use std::{format, vec, vec::Vec};
564
565 use super::*;
566
567 fn q(s: &[u8]) -> Result<(Quoted<'_>, usize)> {
568 parse_quoted(s)
569 }
570
571 #[test]
572 fn test_quoted_edge_cases() {
573 assert_eq!(q(b"\"\"").unwrap().0.raw(), b"");
574 assert!(q(b"\"\\\"\"").unwrap().0.eq_bytes(b"\""));
575 assert!(q(b"\"\\\\\"").unwrap().0.eq_bytes(b"\\"));
576 assert_eq!(q(b"\"\\").unwrap_err().kind(), ErrorKind::UnterminatedQuote);
577 assert_eq!(q(b"\"a").unwrap_err().kind(), ErrorKind::UnterminatedQuote);
578 let e = q(b"\"a\x00\"").unwrap_err();
579 assert_eq!((e.kind(), e.offset()), (ErrorKind::Nul, 2));
580 let e = q(b"\"\\\x7f\"").unwrap_err();
581 assert_eq!((e.kind(), e.offset()), (ErrorKind::InvalidQuotedPair, 2));
582 assert!(q(b"\"\x80\xff\"").is_ok());
583 assert!(q(b"\"\\\t\"").unwrap().0.eq_bytes(b"\t"));
584 assert_eq!(q(b"x").unwrap_err().kind(), ErrorKind::InvalidByte);
585 assert_eq!(q(b"").unwrap_err().kind(), ErrorKind::InvalidByte);
586 assert_eq!(q(b"\"a\rb\"").unwrap_err().kind(), ErrorKind::BareCr);
587 assert_eq!(q(b"\"a\nb\"").unwrap_err().kind(), ErrorKind::LineFeed);
588 }
589
590 #[test]
591 fn test_quoted_unescape_buffer_one_short_writes_nothing() {
592 let (v, _) = q(b"\"a\\\"b\"").unwrap();
593 let mut buf = [0xaau8; 2];
594 assert_eq!(
595 v.unescape_into(&mut buf).unwrap_err().kind(),
596 ErrorKind::BufferTooSmall
597 );
598 assert_eq!(buf, [0xaa, 0xaa]);
599 let mut buf = [0u8; 3];
600 assert_eq!(v.unescape_into(&mut buf).unwrap(), b"a\"b");
601 }
602
603 #[test]
604 fn test_quoted_many_backslashes_linear() {
605 let mut v = vec![b'"'];
606 v.extend(core::iter::repeat_n(b'\\', 64 * 1024));
607 v.push(b'"');
608 let (quoted, used) = q(&v).unwrap();
609 assert_eq!(used, v.len());
610 assert_eq!(quoted.unescaped_len(), 32 * 1024);
611 }
612
613 #[test]
614 fn test_quoted_case_insensitive_compare() {
615 let (v, _) = q(b"\"A\\Bc\"").unwrap();
616 assert!(v.eq_ignore_ascii_case(b"abc"));
617 assert!(!v.eq_ignore_ascii_case(b"ab"));
618 assert!(!v.eq_ignore_ascii_case(b"abcd"));
619 assert!(!v.eq_bytes(b"abc"));
620 assert_eq!(v.bytes().collect::<Vec<u8>>(), b"ABc");
621 }
622
623 #[test]
624 fn test_word_token_and_quoted_compare_equal() {
625 let (a, _) = parse_word(b"abc").unwrap();
626 let (b, _) = parse_word(b"\"a\\bc\"").unwrap();
627 assert!(a.eq_bytes(b"abc") && b.eq_bytes(b"abc"));
628 assert_ne!(a, b);
629 assert_eq!((a.len(), b.len()), (3, 3));
630 assert_eq!(b.as_str(), None);
631 let (e, _) = parse_word(b"\"\"").unwrap();
632 assert!(e.is_empty() && !a.is_empty());
633 let mut buf = [0u8; 3];
634 assert_eq!(a.unescape_into(&mut buf).unwrap(), b"abc");
635 assert_eq!(
636 a.unescape_into(&mut buf[..2]).unwrap_err().kind(),
637 ErrorKind::BufferTooSmall
638 );
639 assert_eq!(parse_word(b";").unwrap_err().kind(), ErrorKind::Empty);
640 }
641
642 #[test]
643 fn test_trim_ows_never_strips_vt_ff_nbsp() {
644 assert_eq!(trim_ows(b"\x0b a"), b"\x0b a");
645 assert_eq!(trim_ows(b"a\x0c"), b"a\x0c");
646 assert_eq!(trim_ows(b"\xc2\xa0a"), b"\xc2\xa0a");
647 assert_eq!(trim_ows(b""), b"");
648 assert_eq!(skip_ows(b""), 0);
649 }
650
651 #[test]
652 fn test_comment_edge_cases() {
653 let l = Limits::DEFAULT;
654 assert_eq!(parse_comment(b"()", &l).unwrap().0.raw(), b"");
655 assert_eq!(parse_comment(b"(a(b)c)", &l).unwrap().0.max_depth(), 2);
656 assert_eq!(parse_comment(b"(\\))", &l).unwrap().1, 4);
657 assert_eq!(
658 parse_comment(b"((a)", &l).unwrap_err().kind(),
659 ErrorKind::UnbalancedComment
660 );
661 assert_eq!(parse_comment(b"(a))", &l).unwrap().1, 3);
662 assert_eq!(
663 parse_comment(b"x", &l).unwrap_err().kind(),
664 ErrorKind::InvalidByte
665 );
666 assert_eq!(
667 parse_comment(b"(\\", &l).unwrap_err().kind(),
668 ErrorKind::UnbalancedComment
669 );
670 assert_eq!(
671 parse_comment(b"(\\\x00)", &l).unwrap_err().kind(),
672 ErrorKind::InvalidQuotedPair
673 );
674 assert_eq!(
675 parse_comment(b"(a\rb)", &l).unwrap_err().kind(),
676 ErrorKind::BareCr
677 );
678 }
679
680 #[test]
681 fn test_comment_depth_limit_plus_one_refused() {
682 let l = Limits::DEFAULT.max_comment_depth(3);
683 assert!(parse_comment(b"(((x)))", &l).is_ok());
684 let e = parse_comment(b"((((x))))", &l).unwrap_err();
685 assert_eq!((e.kind(), e.offset()), (ErrorKind::CommentTooDeep, 3));
686 let deep: Vec<u8> = core::iter::repeat_n(b'(', 10_000)
687 .chain(core::iter::repeat_n(b')', 10_000))
688 .collect();
689 assert_eq!(
690 parse_comment(&deep, &Limits::DEFAULT).unwrap_err().kind(),
691 ErrorKind::CommentTooDeep
692 );
693 }
694
695 #[test]
696 fn test_quoted_debug_escapes() {
697 let (v, _) = q(b"\"a\\\"\x80\"").unwrap();
698 assert_eq!(format!("{v:?}"), "Quoted(\"a\\\\\\\"\\x80\")");
699 }
700}