differential_engine/
paths.rs1use memchr::memmem;
10
11pub fn parse_diff_git_path(rest: &[u8]) -> Option<Vec<u8>> {
15 if rest.first() == Some(&b'"') {
17 let (a, after) = unquote_c(rest)?;
18 let after = after.strip_prefix(b" ")?;
19 let b = if after.first() == Some(&b'"') {
20 unquote_c(after)?.0
21 } else {
22 after.to_vec()
23 };
24 return strip_ab(&a, &b);
25 }
26 if let Some(pos) = memmem::find(rest, b" \"b/") {
28 let a = &rest[..pos];
29 let (b, _) = unquote_c(&rest[pos + 1..])?;
30 return strip_ab(a, &b);
31 }
32 let mut idx = 0;
34 while let Some(off) = memmem::find(&rest[idx..], b" b/") {
35 let pos = idx + off;
36 if let Some(p) = strip_ab(&rest[..pos], &rest[pos + 1..]) {
37 return Some(p);
38 }
39 idx = pos + 1;
40 }
41 None
42}
43
44fn strip_ab(a: &[u8], b: &[u8]) -> Option<Vec<u8>> {
45 let a = a.strip_prefix(b"a/")?;
46 let b = b.strip_prefix(b"b/")?;
47 (a == b).then(|| a.to_vec())
48}
49
50pub fn unquote_c(s: &[u8]) -> Option<(Vec<u8>, &[u8])> {
53 if s.first() != Some(&b'"') {
54 return None;
55 }
56 let mut out = Vec::new();
57 let mut i = 1;
58 while i < s.len() {
59 match s[i] {
60 b'"' => return Some((out, &s[i + 1..])),
61 b'\\' => {
62 i += 1;
63 let c = *s.get(i)?;
64 match c {
65 b'n' => out.push(b'\n'),
66 b't' => out.push(b'\t'),
67 b'r' => out.push(b'\r'),
68 b'a' => out.push(0x07),
69 b'b' => out.push(0x08),
70 b'f' => out.push(0x0c),
71 b'v' => out.push(0x0b),
72 b'\\' => out.push(b'\\'),
73 b'"' => out.push(b'"'),
74 b'0'..=b'7' => {
75 let mut val = 0u32;
77 let mut n = 0;
78 while n < 3 {
79 match s.get(i) {
80 Some(&d @ b'0'..=b'7') => {
81 val = val * 8 + u32::from(d - b'0');
82 i += 1;
83 n += 1;
84 }
85 _ => break,
86 }
87 }
88 i -= 1; out.push(val as u8);
90 }
91 _ => return None,
92 }
93 i += 1;
94 }
95 c => {
96 out.push(c);
97 i += 1;
98 }
99 }
100 }
101 None }
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 #[test]
109 fn plain_path() {
110 assert_eq!(
111 parse_diff_git_path(b"a/src/main.rs b/src/main.rs").unwrap(),
112 b"src/main.rs"
113 );
114 }
115
116 #[test]
117 fn path_with_spaces() {
118 assert_eq!(
119 parse_diff_git_path(b"a/docs/my file.md b/docs/my file.md").unwrap(),
120 b"docs/my file.md"
121 );
122 }
123
124 #[test]
125 fn adversarial_space_b_slash() {
126 assert_eq!(parse_diff_git_path(b"a/x b/y b/x b/y").unwrap(), b"x b/y");
129 }
130
131 #[test]
132 fn quoted_path() {
133 assert_eq!(
134 parse_diff_git_path(br#""a/t\tab.txt" "b/t\tab.txt""#).unwrap(),
135 b"t\tab.txt"
136 );
137 }
138
139 #[test]
140 fn quoted_octal() {
141 let (v, rest) = unquote_c(br#""\303\251.txt" tail"#).unwrap();
142 assert_eq!(v, "é.txt".as_bytes());
143 assert_eq!(rest, b" tail");
144 }
145
146 #[test]
147 fn mismatched_halves_rejected() {
148 assert!(parse_diff_git_path(b"a/one b/two").is_none());
149 }
150}