email_encoding/headers/
quoted_string.rs

1//! Quoted String encoder.
2
3use core::fmt::{self, Write};
4
5use super::{rfc2047, utils, writer::EmailWriter};
6
7/// Encode a string that may need to be quoted.
8///
9/// # Examples
10///
11/// ```rust
12/// # use email_encoding::headers::writer::EmailWriter;
13/// # fn main() -> core::fmt::Result {
14/// {
15///     let input = "John";
16///
17///     let mut output = String::new();
18///     {
19///         let mut writer = EmailWriter::new(&mut output, 0, 0, false);
20///         email_encoding::headers::quoted_string::encode(input, &mut writer)?;
21///     }
22///     assert_eq!(output, "John");
23/// }
24///
25/// {
26///     let input = "John Smith";
27///
28///     let mut output = String::new();
29///     {
30///         let mut writer = EmailWriter::new(&mut output, 0, 0, false);
31///         email_encoding::headers::quoted_string::encode(input, &mut writer)?;
32///     }
33///     assert_eq!(output, "\"John Smith\"");
34/// }
35///
36/// {
37///     let input = "Rogue \" User";
38///
39///     let mut output = String::new();
40///     {
41///         let mut writer = EmailWriter::new(&mut output, 0, 0, false);
42///         email_encoding::headers::quoted_string::encode(input, &mut writer)?;
43///     }
44///     assert_eq!(output, "\"Rogue \\\" User\"");
45/// }
46///
47/// {
48///     let input = "Adrián";
49///
50///     let mut output = String::new();
51///     {
52///         let mut writer = EmailWriter::new(&mut output, 0, 0, false);
53///         email_encoding::headers::quoted_string::encode(input, &mut writer)?;
54///     }
55///     assert_eq!(output, "=?utf-8?b?QWRyacOhbg==?=");
56/// }
57/// # Ok(())
58/// # }
59/// ```
60pub fn encode(value: &str, w: &mut EmailWriter<'_>) -> fmt::Result {
61    #[derive(Debug)]
62    enum Strategy {
63        Plain,
64        Quoted,
65        QuotedEscaped,
66        Rfc2047,
67    }
68
69    let mut strategy = Strategy::Plain;
70
71    let mut bytes = value.as_bytes();
72
73    // Plain -> Quoted
74    while !bytes.is_empty() {
75        let byte = bytes[0];
76
77        if !byte.is_ascii_alphanumeric() && !matches!(byte, b'-' | b'_' | b'.') {
78            strategy = Strategy::Quoted;
79            break;
80        }
81
82        bytes = &bytes[1..];
83    }
84
85    // Quoted -> QuotedEscaped
86    while !bytes.is_empty() {
87        let byte = bytes[0];
88
89        if !byte.is_ascii_alphanumeric() && !matches!(byte, b' ' | b'-' | b'_' | b'.') {
90            strategy = Strategy::QuotedEscaped;
91            break;
92        }
93
94        bytes = &bytes[1..];
95    }
96
97    // QuotedEscaped -> Rfc2047
98    while !bytes.is_empty() {
99        let byte = bytes[0];
100
101        if !byte.is_ascii_alphanumeric()
102            && !matches!(byte, b'\\' | b'"' | b' ' | b'-' | b'_' | b'.')
103        {
104            strategy = Strategy::Rfc2047;
105            break;
106        }
107
108        bytes = &bytes[1..];
109    }
110
111    match strategy {
112        Strategy::Plain => {
113            w.write_str(value)?;
114        }
115        Strategy::Quoted => {
116            w.write_char('"')?;
117            w.folding().write_str(value)?;
118            w.write_char('"')?;
119        }
120        Strategy::QuotedEscaped => {
121            w.write_char('"')?;
122            utils::write_escaped(value, &mut w.folding())?;
123            w.write_char('"')?;
124        }
125        Strategy::Rfc2047 => {
126            rfc2047::encode(value, w)?;
127        }
128    }
129
130    Ok(())
131}
132
133#[cfg(test)]
134mod tests {
135    use alloc::string::String;
136
137    use pretty_assertions::assert_eq;
138
139    use super::*;
140
141    #[test]
142    fn plain() {
143        let mut s = String::new();
144        let line_len = s.len();
145
146        {
147            let mut w = EmailWriter::new(&mut s, line_len, 0, false);
148            encode("1234567890abcd", &mut w).unwrap();
149        }
150
151        assert_eq!(s, "1234567890abcd");
152    }
153
154    #[test]
155    fn quoted() {
156        let mut s = String::new();
157        let line_len = s.len();
158
159        {
160            let mut w = EmailWriter::new(&mut s, line_len, 0, false);
161            encode("1234567890 abcd", &mut w).unwrap();
162        }
163
164        assert_eq!(s, "\"1234567890 abcd\"");
165    }
166
167    #[test]
168    fn quoted_long() {
169        let mut s = String::new();
170        let line_len = s.len();
171
172        {
173            let mut w = EmailWriter::new(&mut s, line_len, 0, false);
174            encode("1234567890 abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd", &mut w).unwrap();
175        }
176
177        assert_eq!(s, concat!(
178            "\"1234567890\r\n",
179            " abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd\""
180        ));
181    }
182
183    #[test]
184    fn quoted_escaped() {
185        let mut s = String::new();
186        let line_len = s.len();
187
188        {
189            let mut w = EmailWriter::new(&mut s, line_len, 0, false);
190            encode("12345\\67890 ab\"cd", &mut w).unwrap();
191        }
192
193        assert_eq!(s, "\"12345\\\\67890 ab\\\"cd\"");
194    }
195
196    // TODO: get it working for the quoted escaped strategy
197    // #[test]
198    // fn quoted_escaped_long() {
199    //     let mut s = String::new();
200    //     let line_len = s.len();
201    //
202    //     {
203    //         let mut w = EmailWriter::new(&mut s, line_len, 0, false, false);
204    //         encode("12345\\67890 ab\"cdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd", &mut w).unwrap();
205    //     }
206    //
207    //     assert_eq!(s, concat!(
208    //         "\"12345\\\\67890\r\n",
209    //         " ab\\\"cdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd\""
210    //     ));
211    // }
212
213    #[test]
214    fn rfc2047() {
215        let mut s = String::new();
216        let line_len = s.len();
217
218        {
219            let mut w = EmailWriter::new(&mut s, line_len, 0, false);
220            encode("12345\\67890 perché ab\"cd", &mut w).unwrap();
221        }
222
223        assert_eq!(s, "=?utf-8?b?MTIzNDVcNjc4OTAgcGVyY2jDqSBhYiJjZA==?=");
224    }
225}