Skip to content

Commit 674af60

Browse files
Merge #5756
5756: Sophisticate Windows path encoding r=matklad a=pragmatrix As discussed in #5475, path encoding should be agnostic of the drive letter casing on Windows. Compared to the problem it solves, the code added seems a lot and may introduce other problems. But I've not found a simpler way basing this on the public API surface that Rust offers. Fixes #5484. cc @Emilgardis Co-authored-by: Armin Sander <[email protected]>
2 parents f1f7364 + 8fc2545 commit 674af60

File tree

1 file changed

+132
-7
lines changed

1 file changed

+132
-7
lines changed

crates/vfs/src/vfs_path.rs

Lines changed: 132 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,30 +57,155 @@ impl VfsPath {
5757
};
5858
buf.push(tag);
5959
match &self.0 {
60-
VfsPathRepr::PathBuf(it) => {
61-
let path: &std::ffi::OsStr = it.as_os_str();
60+
VfsPathRepr::PathBuf(path) => {
6261
#[cfg(windows)]
6362
{
64-
use std::os::windows::ffi::OsStrExt;
65-
for wchar in path.encode_wide() {
66-
buf.extend(wchar.to_le_bytes().iter().copied());
63+
use windows_paths::Encode;
64+
let components = path.components();
65+
let mut add_sep = false;
66+
for component in components {
67+
if add_sep {
68+
windows_paths::SEP.encode(buf);
69+
}
70+
let len_before = buf.len();
71+
match component {
72+
std::path::Component::Prefix(prefix) => {
73+
// kind() returns a normalized and comparable path prefix.
74+
prefix.kind().encode(buf);
75+
}
76+
std::path::Component::RootDir => {
77+
if !add_sep {
78+
component.as_os_str().encode(buf);
79+
}
80+
}
81+
_ => component.as_os_str().encode(buf),
82+
}
83+
84+
// some components may be encoded empty
85+
add_sep = len_before != buf.len();
6786
}
6887
}
6988
#[cfg(unix)]
7089
{
7190
use std::os::unix::ffi::OsStrExt;
72-
buf.extend(path.as_bytes());
91+
buf.extend(path.as_os_str().as_bytes());
7392
}
7493
#[cfg(not(any(windows, unix)))]
7594
{
76-
buf.extend(path.to_string_lossy().as_bytes());
95+
buf.extend(path.as_os_str().to_string_lossy().as_bytes());
7796
}
7897
}
7998
VfsPathRepr::VirtualPath(VirtualPath(s)) => buf.extend(s.as_bytes()),
8099
}
81100
}
82101
}
83102

103+
#[cfg(windows)]
104+
mod windows_paths {
105+
pub trait Encode {
106+
fn encode(&self, buf: &mut Vec<u8>);
107+
}
108+
109+
impl Encode for std::ffi::OsStr {
110+
fn encode(&self, buf: &mut Vec<u8>) {
111+
use std::os::windows::ffi::OsStrExt;
112+
for wchar in self.encode_wide() {
113+
buf.extend(wchar.to_le_bytes().iter().copied());
114+
}
115+
}
116+
}
117+
118+
impl Encode for u8 {
119+
fn encode(&self, buf: &mut Vec<u8>) {
120+
let wide = *self as u16;
121+
buf.extend(wide.to_le_bytes().iter().copied())
122+
}
123+
}
124+
125+
impl Encode for &str {
126+
fn encode(&self, buf: &mut Vec<u8>) {
127+
debug_assert!(self.is_ascii());
128+
for b in self.as_bytes() {
129+
b.encode(buf)
130+
}
131+
}
132+
}
133+
134+
pub const SEP: &str = "\\";
135+
const VERBATIM: &str = "\\\\?\\";
136+
const UNC: &str = "UNC";
137+
const DEVICE: &str = "\\\\.\\";
138+
const COLON: &str = ":";
139+
140+
impl Encode for std::path::Prefix<'_> {
141+
fn encode(&self, buf: &mut Vec<u8>) {
142+
match self {
143+
std::path::Prefix::Verbatim(c) => {
144+
VERBATIM.encode(buf);
145+
c.encode(buf);
146+
}
147+
std::path::Prefix::VerbatimUNC(server, share) => {
148+
VERBATIM.encode(buf);
149+
UNC.encode(buf);
150+
SEP.encode(buf);
151+
server.encode(buf);
152+
SEP.encode(buf);
153+
share.encode(buf);
154+
}
155+
std::path::Prefix::VerbatimDisk(d) => {
156+
VERBATIM.encode(buf);
157+
d.encode(buf);
158+
COLON.encode(buf);
159+
}
160+
std::path::Prefix::DeviceNS(device) => {
161+
DEVICE.encode(buf);
162+
device.encode(buf);
163+
}
164+
std::path::Prefix::UNC(server, share) => {
165+
SEP.encode(buf);
166+
SEP.encode(buf);
167+
server.encode(buf);
168+
SEP.encode(buf);
169+
share.encode(buf);
170+
}
171+
std::path::Prefix::Disk(d) => {
172+
d.encode(buf);
173+
COLON.encode(buf);
174+
}
175+
}
176+
}
177+
}
178+
#[test]
179+
fn paths_encoding() {
180+
// drive letter casing agnostic
181+
test_eq("C:/x.rs", "c:/x.rs");
182+
// separator agnostic
183+
test_eq("C:/x/y.rs", "C:\\x\\y.rs");
184+
185+
fn test_eq(a: &str, b: &str) {
186+
let mut b1 = Vec::new();
187+
let mut b2 = Vec::new();
188+
vfs(a).encode(&mut b1);
189+
vfs(b).encode(&mut b2);
190+
assert_eq!(b1, b2);
191+
}
192+
}
193+
194+
#[test]
195+
fn test_sep_root_dir_encoding() {
196+
let mut buf = Vec::new();
197+
vfs("C:/x/y").encode(&mut buf);
198+
assert_eq!(&buf, &[0, 67, 0, 58, 0, 92, 0, 120, 0, 92, 0, 121, 0])
199+
}
200+
201+
#[cfg(test)]
202+
fn vfs(str: &str) -> super::VfsPath {
203+
use super::{AbsPathBuf, VfsPath};
204+
use std::convert::TryFrom;
205+
VfsPath::from(AbsPathBuf::try_from(str).unwrap())
206+
}
207+
}
208+
84209
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
85210
enum VfsPathRepr {
86211
PathBuf(AbsPathBuf),

0 commit comments

Comments
 (0)