pub fn parse_diff_git_path(rest: &[u8]) -> Option<Vec<u8>> {
if rest.first() == Some(&b'"') {
let (a, after) = unquote_c(rest)?;
let after = after.strip_prefix(b" ")?;
let b = if after.first() == Some(&b'"') {
unquote_c(after)?.0
} else {
after.to_vec()
};
return strip_ab(&a, &b);
}
if let Some(pos) = find_sub(rest, b" \"b/") {
let a = &rest[..pos];
let (b, _) = unquote_c(&rest[pos + 1..])?;
return strip_ab(a, &b);
}
let mut idx = 0;
while let Some(off) = find_sub(&rest[idx..], b" b/") {
let pos = idx + off;
if let Some(p) = strip_ab(&rest[..pos], &rest[pos + 1..]) {
return Some(p);
}
idx = pos + 1;
}
None
}
fn strip_ab(a: &[u8], b: &[u8]) -> Option<Vec<u8>> {
let a = a.strip_prefix(b"a/")?;
let b = b.strip_prefix(b"b/")?;
(a == b).then(|| a.to_vec())
}
pub fn unquote_c(s: &[u8]) -> Option<(Vec<u8>, &[u8])> {
if s.first() != Some(&b'"') {
return None;
}
let mut out = Vec::new();
let mut i = 1;
while i < s.len() {
match s[i] {
b'"' => return Some((out, &s[i + 1..])),
b'\\' => {
i += 1;
let c = *s.get(i)?;
match c {
b'n' => out.push(b'\n'),
b't' => out.push(b'\t'),
b'r' => out.push(b'\r'),
b'a' => out.push(0x07),
b'b' => out.push(0x08),
b'f' => out.push(0x0c),
b'v' => out.push(0x0b),
b'\\' => out.push(b'\\'),
b'"' => out.push(b'"'),
b'0'..=b'7' => {
let mut val = 0u32;
let mut n = 0;
while n < 3 {
match s.get(i) {
Some(&d @ b'0'..=b'7') => {
val = val * 8 + u32::from(d - b'0');
i += 1;
n += 1;
}
_ => break,
}
}
i -= 1; out.push(val as u8);
}
_ => return None,
}
i += 1;
}
c => {
out.push(c);
i += 1;
}
}
}
None }
pub fn find_sub(haystack: &[u8], needle: &[u8]) -> Option<usize> {
if needle.is_empty() || haystack.len() < needle.len() {
return None;
}
haystack.windows(needle.len()).position(|w| w == needle)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn plain_path() {
assert_eq!(
parse_diff_git_path(b"a/src/main.rs b/src/main.rs").unwrap(),
b"src/main.rs"
);
}
#[test]
fn path_with_spaces() {
assert_eq!(
parse_diff_git_path(b"a/docs/my file.md b/docs/my file.md").unwrap(),
b"docs/my file.md"
);
}
#[test]
fn adversarial_space_b_slash() {
assert_eq!(parse_diff_git_path(b"a/x b/y b/x b/y").unwrap(), b"x b/y");
}
#[test]
fn quoted_path() {
assert_eq!(
parse_diff_git_path(br#""a/t\tab.txt" "b/t\tab.txt""#).unwrap(),
b"t\tab.txt"
);
}
#[test]
fn quoted_octal() {
let (v, rest) = unquote_c(br#""\303\251.txt" tail"#).unwrap();
assert_eq!(v, "é.txt".as_bytes());
assert_eq!(rest, b" tail");
}
#[test]
fn mismatched_halves_rejected() {
assert!(parse_diff_git_path(b"a/one b/two").is_none());
}
}