mailparsing/
normalize.rs

1pub fn has_lone_cr_or_lf(data: &[u8]) -> bool {
2    for i in memchr::memchr2_iter(b'\r', b'\n', data) {
3        match data[i] {
4            b'\r' => {
5                if data.get(i + 1).copied() != Some(b'\n') {
6                    return true;
7                }
8            }
9            b'\n' => {
10                if i == 0 || data[i - 1] != b'\r' {
11                    return true;
12                }
13            }
14            _ => unreachable!(),
15        }
16    }
17    false
18}
19
20pub fn normalize_crlf(data: &[u8]) -> Vec<u8> {
21    let mut normalized = Vec::with_capacity(data.len());
22    let mut last_idx = 0;
23
24    for i in memchr::memchr2_iter(b'\r', b'\n', data) {
25        match data[i] {
26            b'\r' => {
27                normalized.extend_from_slice(&data[last_idx..=i]);
28                if data.get(i + 1).copied() != Some(b'\n') {
29                    normalized.push(b'\n');
30                }
31            }
32            b'\n' => {
33                normalized.extend_from_slice(&data[last_idx..i]);
34                let needs_cr = i == 0 || data[i - 1] != b'\r';
35                if needs_cr {
36                    normalized.push(b'\r');
37                }
38                normalized.push(b'\n');
39            }
40            _ => unreachable!(),
41        }
42        last_idx = i + 1;
43    }
44
45    normalized.extend_from_slice(&data[last_idx..]);
46    normalized
47}
48
49#[cfg(test)]
50mod test {
51    use super::*;
52
53    #[test]
54    fn loner() {
55        assert!(!has_lone_cr_or_lf(b""));
56        assert!(!has_lone_cr_or_lf(b"hello"));
57        assert!(!has_lone_cr_or_lf(b"hello\r\nthere"));
58        assert!(!has_lone_cr_or_lf(b"hello\r\nthere\r\n"));
59        assert!(!has_lone_cr_or_lf(b"\r\nhello\r\nthere\r\n"));
60        assert!(has_lone_cr_or_lf(b"hello\n"));
61        assert!(has_lone_cr_or_lf(b"hello\r"));
62        assert!(has_lone_cr_or_lf(b"\nhello\r\nthere\r\n"));
63        assert!(has_lone_cr_or_lf(b"\rhello\r\nthere\r\n"));
64        assert!(has_lone_cr_or_lf(b"hello\nthere\r\n"));
65        assert!(has_lone_cr_or_lf(b"hello\r\nthere\n"));
66        assert!(has_lone_cr_or_lf(b"hello\r\r\r\nthere\n"));
67    }
68
69    #[test]
70    fn fix_loner() {
71        fn fix(s: &[u8], expect: &[u8]) {
72            assert_eq!(normalize_crlf(s), expect);
73        }
74
75        fix(b"\nhello\r\nthere\r\n", b"\r\nhello\r\nthere\r\n");
76        fix(b"hello\r", b"hello\r\n");
77        fix(b"hello\nthere\r\n", b"hello\r\nthere\r\n");
78        fix(b"hello\r\nthere\n", b"hello\r\nthere\r\n");
79        fix(b"hello\r\r\r\nthere\n", b"hello\r\n\r\n\r\nthere\r\n");
80    }
81
82    #[test]
83    fn test_normalize_crlf() {
84        assert_eq!(
85            normalize_crlf(b"foo\r\nbar\nwoot\rdouble-r\r\rend"),
86            b"foo\r\nbar\r\nwoot\r\ndouble-r\r\n\r\nend"
87        );
88    }
89}