diff options
author | Armin Sander <[email protected]> | 2020-08-14 00:19:46 +0100 |
---|---|---|
committer | Armin Sander <[email protected]> | 2020-08-14 01:03:54 +0100 |
commit | 8fc254597f7351e06b19052933aa01a044583b71 (patch) | |
tree | b1c9f3e698780720aa2c92dbfb9c2e3ae07a4bdd | |
parent | f1f73649a686dc6e6449afc35e0fa6fed00e225d (diff) |
Sophisticate Windows path encoding
-rw-r--r-- | crates/vfs/src/vfs_path.rs | 139 |
1 files changed, 132 insertions, 7 deletions
diff --git a/crates/vfs/src/vfs_path.rs b/crates/vfs/src/vfs_path.rs index 04a42264e..944a702df 100644 --- a/crates/vfs/src/vfs_path.rs +++ b/crates/vfs/src/vfs_path.rs | |||
@@ -57,23 +57,42 @@ impl VfsPath { | |||
57 | }; | 57 | }; |
58 | buf.push(tag); | 58 | buf.push(tag); |
59 | match &self.0 { | 59 | match &self.0 { |
60 | VfsPathRepr::PathBuf(it) => { | 60 | VfsPathRepr::PathBuf(path) => { |
61 | let path: &std::ffi::OsStr = it.as_os_str(); | ||
62 | #[cfg(windows)] | 61 | #[cfg(windows)] |
63 | { | 62 | { |
64 | use std::os::windows::ffi::OsStrExt; | 63 | use windows_paths::Encode; |
65 | for wchar in path.encode_wide() { | 64 | let components = path.components(); |
66 | buf.extend(wchar.to_le_bytes().iter().copied()); | 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(); | ||
67 | } | 86 | } |
68 | } | 87 | } |
69 | #[cfg(unix)] | 88 | #[cfg(unix)] |
70 | { | 89 | { |
71 | use std::os::unix::ffi::OsStrExt; | 90 | use std::os::unix::ffi::OsStrExt; |
72 | buf.extend(path.as_bytes()); | 91 | buf.extend(path.as_os_str().as_bytes()); |
73 | } | 92 | } |
74 | #[cfg(not(any(windows, unix)))] | 93 | #[cfg(not(any(windows, unix)))] |
75 | { | 94 | { |
76 | buf.extend(path.to_string_lossy().as_bytes()); | 95 | buf.extend(path.as_os_str().to_string_lossy().as_bytes()); |
77 | } | 96 | } |
78 | } | 97 | } |
79 | VfsPathRepr::VirtualPath(VirtualPath(s)) => buf.extend(s.as_bytes()), | 98 | VfsPathRepr::VirtualPath(VirtualPath(s)) => buf.extend(s.as_bytes()), |
@@ -81,6 +100,112 @@ impl VfsPath { | |||
81 | } | 100 | } |
82 | } | 101 | } |
83 | 102 | ||
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 | |||
84 | #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] | 209 | #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] |
85 | enum VfsPathRepr { | 210 | enum VfsPathRepr { |
86 | PathBuf(AbsPathBuf), | 211 | PathBuf(AbsPathBuf), |