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 crate::scan::is_control(b) {
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 pub(crate) const fn from_raw(raw: &'a [u8], has_escapes: bool) -> Self {
317 Self { raw, has_escapes }
318 }
319
320 #[must_use]
322 pub const fn raw(&self) -> &'a [u8] {
323 self.raw
324 }
325
326 #[must_use]
328 pub const fn has_escapes(&self) -> bool {
329 self.has_escapes
330 }
331
332 #[must_use]
334 pub const fn as_plain(&self) -> Option<&'a [u8]> {
335 if self.has_escapes {
336 None
337 } else {
338 Some(self.raw)
339 }
340 }
341
342 #[must_use]
344 pub fn unescaped_len(&self) -> usize {
345 if self.has_escapes {
346 self.bytes().count()
347 } else {
348 self.raw.len()
349 }
350 }
351
352 #[must_use]
354 pub fn bytes(&self) -> Unescape<'a> {
355 Unescape {
356 rest: self.raw.iter(),
357 }
358 }
359
360 pub fn unescape_into<'b>(&self, out: &'b mut [u8]) -> Result<&'b [u8]> {
368 let len = self.unescaped_len();
369 let Some(dst) = out.get_mut(..len) else {
370 return err(ErrorKind::BufferTooSmall, 0);
371 };
372 for (d, b) in dst.iter_mut().zip(self.bytes()) {
373 *d = b;
374 }
375 Ok(dst)
376 }
377
378 #[must_use]
380 pub fn eq_bytes(&self, other: &[u8]) -> bool {
381 match self.as_plain() {
382 Some(p) => p == other,
383 None => self.bytes().eq(other.iter().copied()),
384 }
385 }
386
387 #[must_use]
390 pub fn eq_ignore_ascii_case(&self, other: &[u8]) -> bool {
391 match self.as_plain() {
392 Some(p) => p.eq_ignore_ascii_case(other),
393 None => {
394 let mut theirs = other.iter();
395 self.bytes()
396 .all(|b| theirs.next().is_some_and(|o| o.eq_ignore_ascii_case(&b)))
397 && theirs.next().is_none()
398 }
399 }
400 }
401}
402
403impl fmt::Debug for Quoted<'_> {
404 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
405 f.write_str("Quoted(\"")?;
406 for &b in self.raw {
407 for c in core::ascii::escape_default(b) {
408 f.write_str(core::str::from_utf8(&[c]).unwrap_or("?"))?;
409 }
410 }
411 f.write_str("\")")
412 }
413}
414
415#[derive(Clone, Debug)]
417pub struct Unescape<'a> {
418 rest: core::slice::Iter<'a, u8>,
419}
420
421impl Iterator for Unescape<'_> {
422 type Item = u8;
423
424 #[inline]
425 fn next(&mut self) -> Option<u8> {
426 match self.rest.next() {
427 Some(&b'\\') => self.rest.next().copied(),
428 other => other.copied(),
429 }
430 }
431
432 fn size_hint(&self) -> (usize, Option<usize>) {
433 let n = self.rest.len();
434 (n.div_ceil(2), Some(n))
435 }
436}
437
438#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
457pub enum Word<'a> {
458 Token(&'a [u8]),
460 Quoted(Quoted<'a>),
462}
463
464impl<'a> Word<'a> {
465 #[must_use]
467 pub const fn as_plain(&self) -> Option<&'a [u8]> {
468 match self {
469 Self::Token(t) => Some(t),
470 Self::Quoted(q) => q.as_plain(),
471 }
472 }
473
474 #[must_use]
476 pub fn as_str(&self) -> Option<&'a str> {
477 self.as_plain().and_then(|p| core::str::from_utf8(p).ok())
478 }
479
480 #[must_use]
482 pub fn eq_bytes(&self, other: &[u8]) -> bool {
483 match self {
484 Self::Token(t) => *t == other,
485 Self::Quoted(q) => q.eq_bytes(other),
486 }
487 }
488
489 #[must_use]
491 pub fn eq_ignore_ascii_case(&self, other: &[u8]) -> bool {
492 match self {
493 Self::Token(t) => t.eq_ignore_ascii_case(other),
494 Self::Quoted(q) => q.eq_ignore_ascii_case(other),
495 }
496 }
497
498 pub fn unescape_into<'b>(&self, out: &'b mut [u8]) -> Result<&'b [u8]> {
504 match self {
505 Self::Token(t) => {
506 let Some(dst) = out.get_mut(..t.len()) else {
507 return err(ErrorKind::BufferTooSmall, 0);
508 };
509 dst.copy_from_slice(t);
510 Ok(dst)
511 }
512 Self::Quoted(q) => q.unescape_into(out),
513 }
514 }
515
516 #[must_use]
518 pub fn len(&self) -> usize {
519 match self {
520 Self::Token(t) => t.len(),
521 Self::Quoted(q) => q.unescaped_len(),
522 }
523 }
524
525 pub(crate) fn same_value(&self, other: &Word<'_>, ignore_case: bool) -> bool {
529 match other {
530 Word::Token(t) if ignore_case => self.eq_ignore_ascii_case(t),
531 Word::Token(t) => self.eq_bytes(t),
532 Word::Quoted(q) => {
533 let mut theirs = q.bytes();
534 let eq = |a: u8, b: u8| {
535 if ignore_case {
536 a.eq_ignore_ascii_case(&b)
537 } else {
538 a == b
539 }
540 };
541 let all = match self {
542 Word::Token(t) => t.iter().all(|&a| theirs.next().is_some_and(|b| eq(a, b))),
543 Word::Quoted(mine) => mine
544 .bytes()
545 .all(|a| theirs.next().is_some_and(|b| eq(a, b))),
546 };
547 all && theirs.next().is_none()
548 }
549 }
550 }
551
552 #[must_use]
554 pub fn is_empty(&self) -> bool {
555 match self {
556 Self::Token(t) => t.is_empty(),
557 Self::Quoted(q) => q.raw.is_empty(),
558 }
559 }
560}
561
562#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
575pub struct Comment<'a> {
576 raw: &'a [u8],
577 max_depth: u8,
578}
579
580impl<'a> Comment<'a> {
581 #[must_use]
583 pub const fn raw(&self) -> &'a [u8] {
584 self.raw
585 }
586
587 #[must_use]
589 pub const fn max_depth(&self) -> u8 {
590 self.max_depth
591 }
592}
593
594#[cfg(test)]
595mod tests {
596 use std::{format, vec, vec::Vec};
597
598 use super::*;
599
600 fn q(s: &[u8]) -> Result<(Quoted<'_>, usize)> {
601 parse_quoted(s)
602 }
603
604 #[test]
605 fn test_quoted_edge_cases() {
606 assert_eq!(q(b"\"\"").unwrap().0.raw(), b"");
607 assert!(q(b"\"\\\"\"").unwrap().0.eq_bytes(b"\""));
608 assert!(q(b"\"\\\\\"").unwrap().0.eq_bytes(b"\\"));
609 assert_eq!(q(b"\"\\").unwrap_err().kind(), ErrorKind::UnterminatedQuote);
610 assert_eq!(q(b"\"a").unwrap_err().kind(), ErrorKind::UnterminatedQuote);
611 let e = q(b"\"a\x00\"").unwrap_err();
612 assert_eq!((e.kind(), e.offset()), (ErrorKind::Nul, 2));
613 let e = q(b"\"\\\x7f\"").unwrap_err();
614 assert_eq!((e.kind(), e.offset()), (ErrorKind::InvalidQuotedPair, 2));
615 assert!(q(b"\"\x80\xff\"").is_ok());
616 assert!(q(b"\"\\\t\"").unwrap().0.eq_bytes(b"\t"));
617 assert_eq!(q(b"x").unwrap_err().kind(), ErrorKind::InvalidByte);
618 assert_eq!(q(b"").unwrap_err().kind(), ErrorKind::InvalidByte);
619 assert_eq!(q(b"\"a\rb\"").unwrap_err().kind(), ErrorKind::BareCr);
620 assert_eq!(q(b"\"a\nb\"").unwrap_err().kind(), ErrorKind::LineFeed);
621 }
622
623 #[test]
624 fn test_quoted_unescape_buffer_one_short_writes_nothing() {
625 let (v, _) = q(b"\"a\\\"b\"").unwrap();
626 let mut buf = [0xaau8; 2];
627 assert_eq!(
628 v.unescape_into(&mut buf).unwrap_err().kind(),
629 ErrorKind::BufferTooSmall
630 );
631 assert_eq!(buf, [0xaa, 0xaa]);
632 let mut buf = [0u8; 3];
633 assert_eq!(v.unescape_into(&mut buf).unwrap(), b"a\"b");
634 }
635
636 #[test]
637 fn test_quoted_many_backslashes_linear() {
638 let mut v = vec![b'"'];
639 v.extend(core::iter::repeat_n(b'\\', 64 * 1024));
640 v.push(b'"');
641 let (quoted, used) = q(&v).unwrap();
642 assert_eq!(used, v.len());
643 assert_eq!(quoted.unescaped_len(), 32 * 1024);
644 }
645
646 #[test]
647 fn test_quoted_case_insensitive_compare() {
648 let (v, _) = q(b"\"A\\Bc\"").unwrap();
649 assert!(v.eq_ignore_ascii_case(b"abc"));
650 assert!(!v.eq_ignore_ascii_case(b"ab"));
651 assert!(!v.eq_ignore_ascii_case(b"abcd"));
652 assert!(!v.eq_bytes(b"abc"));
653 assert_eq!(v.bytes().collect::<Vec<u8>>(), b"ABc");
654 }
655
656 #[test]
657 fn test_word_token_and_quoted_compare_equal() {
658 let (a, _) = parse_word(b"abc").unwrap();
659 let (b, _) = parse_word(b"\"a\\bc\"").unwrap();
660 assert!(a.eq_bytes(b"abc") && b.eq_bytes(b"abc"));
661 assert_ne!(a, b);
662 assert_eq!((a.len(), b.len()), (3, 3));
663 assert_eq!(b.as_str(), None);
664 let (e, _) = parse_word(b"\"\"").unwrap();
665 assert!(e.is_empty() && !a.is_empty());
666 let mut buf = [0u8; 3];
667 assert_eq!(a.unescape_into(&mut buf).unwrap(), b"abc");
668 assert_eq!(
669 a.unescape_into(&mut buf[..2]).unwrap_err().kind(),
670 ErrorKind::BufferTooSmall
671 );
672 assert_eq!(parse_word(b";").unwrap_err().kind(), ErrorKind::Empty);
673 }
674
675 #[test]
676 fn test_trim_ows_never_strips_vt_ff_nbsp() {
677 assert_eq!(trim_ows(b"\x0b a"), b"\x0b a");
678 assert_eq!(trim_ows(b"a\x0c"), b"a\x0c");
679 assert_eq!(trim_ows(b"\xc2\xa0a"), b"\xc2\xa0a");
680 assert_eq!(trim_ows(b""), b"");
681 assert_eq!(skip_ows(b""), 0);
682 }
683
684 #[test]
685 fn test_comment_edge_cases() {
686 let l = Limits::DEFAULT;
687 assert_eq!(parse_comment(b"()", &l).unwrap().0.raw(), b"");
688 assert_eq!(parse_comment(b"(a(b)c)", &l).unwrap().0.max_depth(), 2);
689 assert_eq!(parse_comment(b"(\\))", &l).unwrap().1, 4);
690 assert_eq!(
691 parse_comment(b"((a)", &l).unwrap_err().kind(),
692 ErrorKind::UnbalancedComment
693 );
694 assert_eq!(parse_comment(b"(a))", &l).unwrap().1, 3);
695 assert_eq!(
696 parse_comment(b"x", &l).unwrap_err().kind(),
697 ErrorKind::InvalidByte
698 );
699 assert_eq!(
700 parse_comment(b"(\\", &l).unwrap_err().kind(),
701 ErrorKind::UnbalancedComment
702 );
703 assert_eq!(
704 parse_comment(b"(\\\x00)", &l).unwrap_err().kind(),
705 ErrorKind::InvalidQuotedPair
706 );
707 assert_eq!(
708 parse_comment(b"(a\rb)", &l).unwrap_err().kind(),
709 ErrorKind::BareCr
710 );
711 }
712
713 #[test]
714 fn test_comment_depth_limit_plus_one_refused() {
715 let l = Limits::DEFAULT.max_comment_depth(3);
716 assert!(parse_comment(b"(((x)))", &l).is_ok());
717 let e = parse_comment(b"((((x))))", &l).unwrap_err();
718 assert_eq!((e.kind(), e.offset()), (ErrorKind::CommentTooDeep, 3));
719 let deep: Vec<u8> = core::iter::repeat_n(b'(', 10_000)
720 .chain(core::iter::repeat_n(b')', 10_000))
721 .collect();
722 assert_eq!(
723 parse_comment(&deep, &Limits::DEFAULT).unwrap_err().kind(),
724 ErrorKind::CommentTooDeep
725 );
726 }
727
728 #[test]
729 fn test_quoted_debug_escapes() {
730 let (v, _) = q(b"\"a\\\"\x80\"").unwrap();
731 assert_eq!(format!("{v:?}"), "Quoted(\"a\\\\\\\"\\x80\")");
732 }
733}