path_encode.rs
622 lines
| 17.8 KiB
| application/rls-services+xml
|
RustLexer
|
r48171 | use sha1::{Digest, Sha1}; | ||
|
r46110 | |||
#[derive(PartialEq, Debug)] | ||||
#[allow(non_camel_case_types)] | ||||
|
r50825 | #[allow(clippy::upper_case_acronyms)] | ||
|
r46110 | enum path_state { | ||
START, /* first byte of a path component */ | ||||
A, /* "AUX" */ | ||||
AU, | ||||
THIRD, /* third of a 3-byte sequence, e.g. "AUX", "NUL" */ | ||||
C, /* "CON" or "COMn" */ | ||||
CO, | ||||
COMLPT, /* "COM" or "LPT" */ | ||||
COMLPTn, | ||||
L, | ||||
LP, | ||||
N, | ||||
NU, | ||||
P, /* "PRN" */ | ||||
PR, | ||||
LDOT, /* leading '.' */ | ||||
DOT, /* '.' in a non-leading position */ | ||||
H, /* ".h" */ | ||||
HGDI, /* ".hg", ".d", or ".i" */ | ||||
SPACE, | ||||
DEFAULT, /* byte of a path component after the first */ | ||||
} | ||||
/* state machine for dir-encoding */ | ||||
#[allow(non_camel_case_types)] | ||||
|
r50825 | #[allow(clippy::upper_case_acronyms)] | ||
|
r46110 | enum dir_state { | ||
DDOT, | ||||
DH, | ||||
DHGDI, | ||||
DDEFAULT, | ||||
} | ||||
|
r51054 | trait Sink { | ||
fn write_byte(&mut self, c: u8); | ||||
fn write_bytes(&mut self, c: &[u8]); | ||||
} | ||||
|
r46110 | fn inset(bitset: &[u32; 8], c: u8) -> bool { | ||
bitset[(c as usize) >> 5] & (1 << (c & 31)) != 0 | ||||
} | ||||
|
r51055 | const MAXENCODE: usize = 4096 * 4; | ||
|
r51056 | struct DestArr<const N: usize> { | ||
buf: [u8; N], | ||||
|
r51055 | pub len: usize, | ||
} | ||||
|
r51056 | impl<const N: usize> DestArr<N> { | ||
|
r51055 | pub fn create() -> Self { | ||
DestArr { | ||||
|
r51056 | buf: [0; N], | ||
|
r51055 | len: 0, | ||
} | ||||
} | ||||
pub fn contents(&self) -> &[u8] { | ||||
&self.buf[..self.len] | ||||
} | ||||
} | ||||
|
r51056 | impl<const N: usize> Sink for DestArr<N> { | ||
|
r51055 | fn write_byte(&mut self, c: u8) { | ||
self.buf[self.len] = c; | ||||
self.len += 1; | ||||
} | ||||
fn write_bytes(&mut self, src: &[u8]) { | ||||
self.buf[self.len..self.len + src.len()].copy_from_slice(src); | ||||
self.len += src.len(); | ||||
} | ||||
} | ||||
|
r51058 | struct MeasureDest { | ||
|
r51054 | pub len: usize, | ||
|
r46110 | } | ||
|
r51059 | impl Sink for Vec<u8> { | ||
|
r51058 | fn write_byte(&mut self, c: u8) { | ||
|
r51059 | self.push(c) | ||
|
r51058 | } | ||
fn write_bytes(&mut self, src: &[u8]) { | ||||
|
r51059 | self.extend_from_slice(src) | ||
|
r46110 | } | ||
} | ||||
|
r51058 | impl MeasureDest { | ||
fn create() -> Self { | ||||
Self { len: 0 } | ||||
} | ||||
} | ||||
impl Sink for MeasureDest { | ||||
fn write_byte(&mut self, _c: u8) { | ||||
self.len += 1; | ||||
|
r51054 | } | ||
fn write_bytes(&mut self, src: &[u8]) { | ||||
self.len += src.len(); | ||||
} | ||||
} | ||||
fn hexencode(dest: &mut impl Sink, c: u8) { | ||||
|
r46110 | let hexdigit = b"0123456789abcdef"; | ||
|
r51054 | dest.write_byte(hexdigit[(c as usize) >> 4]); | ||
dest.write_byte(hexdigit[(c as usize) & 15]); | ||||
|
r46110 | } | ||
/* 3-byte escape: tilde followed by two hex digits */ | ||||
|
r51054 | fn escape3(dest: &mut impl Sink, c: u8) { | ||
dest.write_byte(b'~'); | ||||
hexencode(dest, c); | ||||
|
r46110 | } | ||
|
r51054 | fn encode_dir(dest: &mut impl Sink, src: &[u8]) { | ||
|
r46110 | let mut state = dir_state::DDEFAULT; | ||
let mut i = 0; | ||||
while i < src.len() { | ||||
match state { | ||||
dir_state::DDOT => match src[i] { | ||||
b'd' | b'i' => { | ||||
state = dir_state::DHGDI; | ||||
|
r51054 | dest.write_byte(src[i]); | ||
|
r46110 | i += 1; | ||
} | ||||
b'h' => { | ||||
state = dir_state::DH; | ||||
|
r51054 | dest.write_byte(src[i]); | ||
|
r46110 | i += 1; | ||
} | ||||
_ => { | ||||
state = dir_state::DDEFAULT; | ||||
} | ||||
}, | ||||
dir_state::DH => { | ||||
if src[i] == b'g' { | ||||
state = dir_state::DHGDI; | ||||
|
r51054 | dest.write_byte(src[i]); | ||
|
r46110 | i += 1; | ||
} else { | ||||
state = dir_state::DDEFAULT; | ||||
} | ||||
} | ||||
dir_state::DHGDI => { | ||||
if src[i] == b'/' { | ||||
|
r51054 | dest.write_bytes(b".hg"); | ||
dest.write_byte(src[i]); | ||||
|
r46110 | i += 1; | ||
} | ||||
state = dir_state::DDEFAULT; | ||||
} | ||||
dir_state::DDEFAULT => { | ||||
if src[i] == b'.' { | ||||
state = dir_state::DDOT | ||||
} | ||||
|
r51054 | dest.write_byte(src[i]); | ||
|
r46110 | i += 1; | ||
} | ||||
} | ||||
} | ||||
} | ||||
fn _encode( | ||||
twobytes: &[u32; 8], | ||||
onebyte: &[u32; 8], | ||||
|
r51054 | dest: &mut impl Sink, | ||
|
r46110 | src: &[u8], | ||
encodedir: bool, | ||||
|
r51054 | ) { | ||
|
r46110 | let mut state = path_state::START; | ||
let mut i = 0; | ||||
let len = src.len(); | ||||
while i < len { | ||||
match state { | ||||
path_state::START => match src[i] { | ||||
b'/' => { | ||||
|
r51054 | dest.write_byte(src[i]); | ||
|
r46110 | i += 1; | ||
} | ||||
b'.' => { | ||||
state = path_state::LDOT; | ||||
|
r51054 | escape3(dest, src[i]); | ||
|
r46110 | i += 1; | ||
} | ||||
b' ' => { | ||||
state = path_state::DEFAULT; | ||||
|
r51054 | escape3(dest, src[i]); | ||
|
r46110 | i += 1; | ||
} | ||||
b'a' => { | ||||
state = path_state::A; | ||||
|
r51054 | dest.write_byte(src[i]); | ||
|
r46110 | i += 1; | ||
} | ||||
b'c' => { | ||||
state = path_state::C; | ||||
|
r51054 | dest.write_byte(src[i]); | ||
|
r46110 | i += 1; | ||
} | ||||
b'l' => { | ||||
state = path_state::L; | ||||
|
r51054 | dest.write_byte(src[i]); | ||
|
r46110 | i += 1; | ||
} | ||||
b'n' => { | ||||
state = path_state::N; | ||||
|
r51054 | dest.write_byte(src[i]); | ||
|
r46110 | i += 1; | ||
} | ||||
b'p' => { | ||||
state = path_state::P; | ||||
|
r51054 | dest.write_byte(src[i]); | ||
|
r46110 | i += 1; | ||
} | ||||
_ => { | ||||
state = path_state::DEFAULT; | ||||
} | ||||
}, | ||||
path_state::A => { | ||||
if src[i] == b'u' { | ||||
state = path_state::AU; | ||||
|
r51054 | dest.write_byte(src[i]); | ||
|
r46110 | i += 1; | ||
} else { | ||||
state = path_state::DEFAULT; | ||||
} | ||||
} | ||||
path_state::AU => { | ||||
if src[i] == b'x' { | ||||
state = path_state::THIRD; | ||||
i += 1; | ||||
} else { | ||||
state = path_state::DEFAULT; | ||||
} | ||||
} | ||||
path_state::THIRD => { | ||||
state = path_state::DEFAULT; | ||||
match src[i] { | ||||
|
r51054 | b'.' | b'/' | b'\0' => escape3(dest, src[i - 1]), | ||
|
r46110 | _ => i -= 1, | ||
} | ||||
} | ||||
path_state::C => { | ||||
if src[i] == b'o' { | ||||
state = path_state::CO; | ||||
|
r51054 | dest.write_byte(src[i]); | ||
|
r46110 | i += 1; | ||
} else { | ||||
state = path_state::DEFAULT; | ||||
} | ||||
} | ||||
path_state::CO => { | ||||
if src[i] == b'm' { | ||||
state = path_state::COMLPT; | ||||
i += 1; | ||||
} else if src[i] == b'n' { | ||||
state = path_state::THIRD; | ||||
i += 1; | ||||
} else { | ||||
state = path_state::DEFAULT; | ||||
} | ||||
} | ||||
path_state::COMLPT => { | ||||
if src[i] >= b'1' && src[i] <= b'9' { | ||||
state = path_state::COMLPTn; | ||||
i += 1; | ||||
} else { | ||||
state = path_state::DEFAULT; | ||||
|
r51054 | dest.write_byte(src[i - 1]); | ||
|
r46110 | } | ||
} | ||||
path_state::COMLPTn => { | ||||
state = path_state::DEFAULT; | ||||
match src[i] { | ||||
b'.' | b'/' | b'\0' => { | ||||
|
r51054 | escape3(dest, src[i - 2]); | ||
dest.write_byte(src[i - 1]); | ||||
|
r46110 | } | ||
_ => { | ||||
|
r51054 | dest.write_bytes(&src[i - 2..i]); | ||
|
r46110 | } | ||
} | ||||
} | ||||
path_state::L => { | ||||
if src[i] == b'p' { | ||||
state = path_state::LP; | ||||
|
r51054 | dest.write_byte(src[i]); | ||
|
r46110 | i += 1; | ||
} else { | ||||
state = path_state::DEFAULT; | ||||
} | ||||
} | ||||
path_state::LP => { | ||||
if src[i] == b't' { | ||||
state = path_state::COMLPT; | ||||
i += 1; | ||||
} else { | ||||
state = path_state::DEFAULT; | ||||
} | ||||
} | ||||
path_state::N => { | ||||
if src[i] == b'u' { | ||||
state = path_state::NU; | ||||
|
r51054 | dest.write_byte(src[i]); | ||
|
r46110 | i += 1; | ||
} else { | ||||
state = path_state::DEFAULT; | ||||
} | ||||
} | ||||
path_state::NU => { | ||||
if src[i] == b'l' { | ||||
state = path_state::THIRD; | ||||
i += 1; | ||||
} else { | ||||
state = path_state::DEFAULT; | ||||
} | ||||
} | ||||
path_state::P => { | ||||
if src[i] == b'r' { | ||||
state = path_state::PR; | ||||
|
r51054 | dest.write_byte(src[i]); | ||
|
r46110 | i += 1; | ||
} else { | ||||
state = path_state::DEFAULT; | ||||
} | ||||
} | ||||
path_state::PR => { | ||||
if src[i] == b'n' { | ||||
state = path_state::THIRD; | ||||
i += 1; | ||||
} else { | ||||
state = path_state::DEFAULT; | ||||
} | ||||
} | ||||
path_state::LDOT => match src[i] { | ||||
b'd' | b'i' => { | ||||
state = path_state::HGDI; | ||||
|
r51054 | dest.write_byte(src[i]); | ||
|
r46110 | i += 1; | ||
} | ||||
b'h' => { | ||||
state = path_state::H; | ||||
|
r51054 | dest.write_byte(src[i]); | ||
|
r46110 | i += 1; | ||
} | ||||
_ => { | ||||
state = path_state::DEFAULT; | ||||
} | ||||
}, | ||||
path_state::DOT => match src[i] { | ||||
b'/' | b'\0' => { | ||||
state = path_state::START; | ||||
|
r51054 | dest.write_bytes(b"~2e"); | ||
dest.write_byte(src[i]); | ||||
|
r46110 | i += 1; | ||
} | ||||
b'd' | b'i' => { | ||||
state = path_state::HGDI; | ||||
|
r51054 | dest.write_byte(b'.'); | ||
dest.write_byte(src[i]); | ||||
|
r46110 | i += 1; | ||
} | ||||
b'h' => { | ||||
state = path_state::H; | ||||
|
r51054 | dest.write_bytes(b".h"); | ||
|
r46110 | i += 1; | ||
} | ||||
_ => { | ||||
state = path_state::DEFAULT; | ||||
|
r51054 | dest.write_byte(b'.'); | ||
|
r46110 | } | ||
}, | ||||
path_state::H => { | ||||
if src[i] == b'g' { | ||||
state = path_state::HGDI; | ||||
|
r51054 | dest.write_byte(src[i]); | ||
|
r46110 | i += 1; | ||
} else { | ||||
state = path_state::DEFAULT; | ||||
} | ||||
} | ||||
path_state::HGDI => { | ||||
if src[i] == b'/' { | ||||
state = path_state::START; | ||||
if encodedir { | ||||
|
r51054 | dest.write_bytes(b".hg"); | ||
|
r46110 | } | ||
|
r51054 | dest.write_byte(src[i]); | ||
|
r46110 | i += 1 | ||
} else { | ||||
state = path_state::DEFAULT; | ||||
} | ||||
} | ||||
path_state::SPACE => match src[i] { | ||||
b'/' | b'\0' => { | ||||
state = path_state::START; | ||||
|
r51054 | dest.write_bytes(b"~20"); | ||
dest.write_byte(src[i]); | ||||
|
r46110 | i += 1; | ||
} | ||||
_ => { | ||||
state = path_state::DEFAULT; | ||||
|
r51054 | dest.write_byte(b' '); | ||
|
r46110 | } | ||
}, | ||||
path_state::DEFAULT => { | ||||
while i != len && inset(onebyte, src[i]) { | ||||
|
r51054 | dest.write_byte(src[i]); | ||
|
r46110 | i += 1; | ||
} | ||||
if i == len { | ||||
break; | ||||
} | ||||
match src[i] { | ||||
b'.' => { | ||||
state = path_state::DOT; | ||||
i += 1 | ||||
} | ||||
b' ' => { | ||||
state = path_state::SPACE; | ||||
i += 1 | ||||
} | ||||
b'/' => { | ||||
state = path_state::START; | ||||
|
r51054 | dest.write_byte(b'/'); | ||
|
r46110 | i += 1; | ||
} | ||||
_ => { | ||||
if inset(onebyte, src[i]) { | ||||
loop { | ||||
|
r51054 | dest.write_byte(src[i]); | ||
|
r46110 | i += 1; | ||
if !(i < len && inset(onebyte, src[i])) { | ||||
break; | ||||
} | ||||
} | ||||
} else if inset(twobytes, src[i]) { | ||||
let c = src[i]; | ||||
i += 1; | ||||
|
r51054 | dest.write_byte(b'_'); | ||
dest.write_byte(if c == b'_' { | ||||
b'_' | ||||
} else { | ||||
c + 32 | ||||
}); | ||||
|
r46110 | } else { | ||
|
r51054 | escape3(dest, src[i]); | ||
|
r46110 | i += 1; | ||
} | ||||
} | ||||
} | ||||
} | ||||
} | ||||
} | ||||
match state { | ||||
path_state::START => (), | ||||
path_state::A => (), | ||||
path_state::AU => (), | ||||
|
r51054 | path_state::THIRD => escape3(dest, src[i - 1]), | ||
|
r46110 | path_state::C => (), | ||
path_state::CO => (), | ||||
|
r51054 | path_state::COMLPT => dest.write_byte(src[i - 1]), | ||
|
r46110 | path_state::COMLPTn => { | ||
|
r51054 | escape3(dest, src[i - 2]); | ||
dest.write_byte(src[i - 1]); | ||||
|
r46110 | } | ||
path_state::L => (), | ||||
path_state::LP => (), | ||||
path_state::N => (), | ||||
path_state::NU => (), | ||||
path_state::P => (), | ||||
path_state::PR => (), | ||||
path_state::LDOT => (), | ||||
path_state::DOT => { | ||||
|
r51054 | dest.write_bytes(b"~2e"); | ||
|
r46110 | } | ||
path_state::H => (), | ||||
path_state::HGDI => (), | ||||
path_state::SPACE => { | ||||
|
r51054 | dest.write_bytes(b"~20"); | ||
|
r46110 | } | ||
path_state::DEFAULT => (), | ||||
|
r51054 | } | ||
|
r46110 | } | ||
|
r51054 | fn basic_encode(dest: &mut impl Sink, src: &[u8]) { | ||
|
r46110 | let twobytes: [u32; 8] = [0, 0, 0x87ff_fffe, 0, 0, 0, 0, 0]; | ||
let onebyte: [u32; 8] = | ||||
[1, 0x2bff_3bfa, 0x6800_0001, 0x2fff_ffff, 0, 0, 0, 0]; | ||||
_encode(&twobytes, &onebyte, dest, src, true) | ||||
} | ||||
const MAXSTOREPATHLEN: usize = 120; | ||||
|
r51054 | fn lower_encode(dest: &mut impl Sink, src: &[u8]) { | ||
|
r46110 | let onebyte: [u32; 8] = | ||
[1, 0x2bff_fbfb, 0xe800_0001, 0x2fff_ffff, 0, 0, 0, 0]; | ||||
let lower: [u32; 8] = [0, 0, 0x07ff_fffe, 0, 0, 0, 0, 0]; | ||||
for c in src { | ||||
if inset(&onebyte, *c) { | ||||
|
r51054 | dest.write_byte(*c) | ||
|
r46110 | } else if inset(&lower, *c) { | ||
|
r51054 | dest.write_byte(*c + 32) | ||
|
r46110 | } else { | ||
|
r51054 | escape3(dest, *c) | ||
|
r46110 | } | ||
} | ||||
} | ||||
|
r51054 | fn aux_encode(dest: &mut impl Sink, src: &[u8]) { | ||
|
r46110 | let twobytes = [0; 8]; | ||
let onebyte: [u32; 8] = [!0, 0xffff_3ffe, !0, !0, !0, !0, !0, !0]; | ||||
_encode(&twobytes, &onebyte, dest, src, false) | ||||
} | ||||
fn hash_mangle(src: &[u8], sha: &[u8]) -> Vec<u8> { | ||||
let dirprefixlen = 8; | ||||
let maxshortdirslen = 68; | ||||
let last_slash = src.iter().rposition(|b| *b == b'/'); | ||||
|
r51062 | let basename_start = match last_slash { | ||
Some(slash) => slash + 1, | ||||
None => 0, | ||||
}; | ||||
let basename = &src[basename_start..]; | ||||
let ext = match basename.iter().rposition(|b| *b == b'.') { | ||||
None => &[], | ||||
Some(dot) => &basename[dot..], | ||||
|
r46110 | }; | ||
|
r51059 | let mut dest = Vec::with_capacity(MAXSTOREPATHLEN); | ||
|
r51054 | dest.write_bytes(b"dh/"); | ||
|
r46110 | |||
|
r50991 | if let Some(last_slash) = last_slash { | ||
for slice in src[..last_slash].split(|b| *b == b'/') { | ||||
|
r46110 | let slice = &slice[..std::cmp::min(slice.len(), dirprefixlen)]; | ||
|
r51059 | if dest.len() + slice.len() > maxshortdirslen + 3 { | ||
|
r46110 | break; | ||
} else { | ||||
|
r51054 | dest.write_bytes(slice); | ||
|
r46110 | } | ||
|
r51054 | dest.write_byte(b'/'); | ||
|
r46110 | } | ||
} | ||||
|
r51062 | let used = dest.len() + 40 + ext.len(); | ||
|
r46110 | |||
if MAXSTOREPATHLEN > used { | ||||
let slop = MAXSTOREPATHLEN - used; | ||||
|
r51062 | let len = std::cmp::min(basename.len(), slop); | ||
dest.write_bytes(&basename[..len]) | ||||
|
r46110 | } | ||
for c in sha { | ||||
|
r51054 | hexencode(&mut dest, *c); | ||
|
r46110 | } | ||
|
r51062 | dest.write_bytes(ext); | ||
|
r51061 | dest.shrink_to_fit(); | ||
|
r51059 | dest | ||
|
r46110 | } | ||
fn hash_encode(src: &[u8]) -> Vec<u8> { | ||||
|
r51056 | let mut dired: DestArr<MAXENCODE> = DestArr::create(); | ||
let mut lowered: DestArr<MAXENCODE> = DestArr::create(); | ||||
let mut auxed: DestArr<MAXENCODE> = DestArr::create(); | ||||
|
r46110 | let baselen = (src.len() - 5) * 3; | ||
if baselen >= MAXENCODE { | ||||
panic!("path_encode::hash_encore: string too long: {}", baselen) | ||||
}; | ||||
|
r51055 | encode_dir(&mut dired, src); | ||
let sha = Sha1::digest(dired.contents()); | ||||
lower_encode(&mut lowered, &dired.contents()[5..]); | ||||
aux_encode(&mut auxed, lowered.contents()); | ||||
hash_mangle(auxed.contents(), &sha) | ||||
|
r46110 | } | ||
pub fn path_encode(path: &[u8]) -> Vec<u8> { | ||||
let newlen = if path.len() <= MAXSTOREPATHLEN { | ||||
|
r51058 | let mut measure = MeasureDest::create(); | ||
|
r51054 | basic_encode(&mut measure, path); | ||
measure.len | ||||
|
r46110 | } else { | ||
|
r51060 | return hash_encode(path); | ||
|
r46110 | }; | ||
if newlen <= MAXSTOREPATHLEN { | ||||
if newlen == path.len() { | ||||
path.to_vec() | ||||
} else { | ||||
|
r51059 | let mut dest = Vec::with_capacity(newlen); | ||
|
r51054 | basic_encode(&mut dest, path); | ||
|
r51059 | assert!(dest.len() == newlen); | ||
dest | ||||
|
r46110 | } | ||
} else { | ||||
|
r50825 | hash_encode(path) | ||
|
r46110 | } | ||
} | ||||
|
r50990 | |||
#[cfg(test)] | ||||
mod tests { | ||||
use super::*; | ||||
use crate::utils::hg_path::HgPathBuf; | ||||
#[test] | ||||
fn test_long_filename_at_root() { | ||||
let input = b"data/ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJ.i"; | ||||
let expected = b"dh/abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij.i708243a2237a7afae259ea3545a72a2ef11c247b.i"; | ||||
let res = path_encode(input); | ||||
assert_eq!( | ||||
HgPathBuf::from_bytes(&res), | ||||
HgPathBuf::from_bytes(expected) | ||||
); | ||||
} | ||||
} | ||||