1use nom::{
2 branch::alt,
3 bytes::complete::{tag, take, take_while1},
4 combinator::opt,
5 multi::many0,
6 sequence::{pair, preceded},
7 IResult,
8};
9
10use crate::text::ascii;
11use crate::text::whitespace::{cfws, fws, is_obs_no_ws_ctl};
12
13#[derive(Debug, PartialEq, Default, Clone)]
14pub struct QuotedString<'a>(pub Vec<&'a [u8]>);
15
16impl<'a> QuotedString<'a> {
17 pub fn push(&mut self, e: &'a [u8]) {
18 self.0.push(e)
19 }
20
21 pub fn to_string(&self) -> String {
22 let enc = encoding_rs::UTF_8;
23 let size = self.0.iter().fold(0, |acc, v| acc + v.len());
24
25 self.0
26 .iter()
27 .fold(String::with_capacity(size), |mut acc, v| {
28 let (content, _) = enc.decode_without_bom_handling(v);
29 acc.push_str(content.as_ref());
30 acc
31 })
32 }
33}
34
35pub fn quoted_pair(input: &[u8]) -> IResult<&[u8], &[u8]> {
42 preceded(tag(&[ascii::BACKSLASH]), take(1usize))(input)
43}
44
45fn is_restr_qtext(c: u8) -> bool {
54 c == ascii::EXCLAMATION
55 || (ascii::NUM..=ascii::LEFT_BRACKET).contains(&c)
56 || (ascii::RIGHT_BRACKET..=ascii::TILDE).contains(&c)
57}
58
59fn is_qtext(c: u8) -> bool {
60 is_restr_qtext(c) || is_obs_no_ws_ctl(c)
61}
62
63fn qcontent(input: &[u8]) -> IResult<&[u8], &[u8]> {
69 alt((take_while1(is_qtext), quoted_pair))(input)
70}
71
72pub fn quoted_string(input: &[u8]) -> IResult<&[u8], QuotedString> {
80 let (input, _) = opt(cfws)(input)?;
81 let (input, _) = tag("\"")(input)?;
82 let (input, content) = many0(pair(opt(fws), qcontent))(input)?;
83
84 let mut qstring = content
86 .iter()
87 .fold(QuotedString::default(), |mut acc, (maybe_wsp, c)| {
88 if maybe_wsp.is_some() {
89 acc.push(&[ascii::SP]);
90 }
91 acc.push(c);
92 acc
93 });
94
95 let (input, maybe_wsp) = opt(fws)(input)?;
96 if maybe_wsp.is_some() {
97 qstring.push(&[ascii::SP]);
98 }
99
100 let (input, _) = tag("\"")(input)?;
101 let (input, _) = opt(cfws)(input)?;
102 Ok((input, qstring))
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 #[test]
110 fn test_quoted_string_parser() {
111 assert_eq!(
112 quoted_string(b" \"hello\\\"world\" ").unwrap().1,
113 QuotedString(vec![b"hello", &[ascii::DQUOTE], b"world"])
114 );
115
116 assert_eq!(
117 quoted_string(b"\"hello\r\n world\""),
118 Ok((
119 &b""[..],
120 QuotedString(vec![b"hello", &[ascii::SP], b"world"])
121 )),
122 );
123 }
124
125 use crate::text::ascii;
126
127 #[test]
128 fn test_quoted_string_object() {
129 assert_eq!(
130 QuotedString(vec![b"hello", &[ascii::SP], b"world"]).to_string(),
131 "hello world".to_string(),
132 );
133 }
134}