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}