email_encoding/headers/
quoted_string.rs

1//! Quoted String encoder.
2
3use std::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() -> std::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 pretty_assertions::assert_eq;
136
137    use super::*;
138
139    #[test]
140    fn plain() {
141        let mut s = String::new();
142        let line_len = s.len();
143
144        {
145            let mut w = EmailWriter::new(&mut s, line_len, 0, false);
146            encode("1234567890abcd", &mut w).unwrap();
147        }
148
149        assert_eq!(s, "1234567890abcd");
150    }
151
152    #[test]
153    fn quoted() {
154        let mut s = String::new();
155        let line_len = s.len();
156
157        {
158            let mut w = EmailWriter::new(&mut s, line_len, 0, false);
159            encode("1234567890 abcd", &mut w).unwrap();
160        }
161
162        assert_eq!(s, "\"1234567890 abcd\"");
163    }
164
165    #[test]
166    fn quoted_long() {
167        let mut s = String::new();
168        let line_len = s.len();
169
170        {
171            let mut w = EmailWriter::new(&mut s, line_len, 0, false);
172            encode("1234567890 abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd", &mut w).unwrap();
173        }
174
175        assert_eq!(s, concat!(
176            "\"1234567890\r\n",
177            " abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd\""
178        ));
179    }
180
181    #[test]
182    fn quoted_escaped() {
183        let mut s = String::new();
184        let line_len = s.len();
185
186        {
187            let mut w = EmailWriter::new(&mut s, line_len, 0, false);
188            encode("12345\\67890 ab\"cd", &mut w).unwrap();
189        }
190
191        assert_eq!(s, "\"12345\\\\67890 ab\\\"cd\"");
192    }
193
194    // TODO: get it working for the quoted escaped strategy
195    // #[test]
196    // fn quoted_escaped_long() {
197    //     let mut s = String::new();
198    //     let line_len = s.len();
199    //
200    //     {
201    //         let mut w = EmailWriter::new(&mut s, line_len, 0, false, false);
202    //         encode("12345\\67890 ab\"cdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd", &mut w).unwrap();
203    //     }
204    //
205    //     assert_eq!(s, concat!(
206    //         "\"12345\\\\67890\r\n",
207    //         " ab\\\"cdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd\""
208    //     ));
209    // }
210
211    #[test]
212    fn rfc2047() {
213        let mut s = String::new();
214        let line_len = s.len();
215
216        {
217            let mut w = EmailWriter::new(&mut s, line_len, 0, false);
218            encode("12345\\67890 perché ab\"cd", &mut w).unwrap();
219        }
220
221        assert_eq!(s, "=?utf-8?b?MTIzNDVcNjc4OTAgcGVyY2jDqSBhYiJjZA==?=");
222    }
223}