1use crate::ffi::{OsStr, OsString};
2use crate::path::{Path, PathBuf, Prefix};
3use crate::sys::api::utf16;
4use crate::sys::pal::{c, fill_utf16_buf, os2path, to_u16s};
5use crate::{io, ptr};
6
7#[cfg(test)]
8mod tests;
9
10pub const MAIN_SEP_STR: &str = "\\";
11pub const MAIN_SEP: char = '\\';
12
13#[inline]
14pub fn is_sep_byte(b: u8) -> bool {
15 b == b'/' || b == b'\\'
16}
17
18#[inline]
19pub fn is_verbatim_sep(b: u8) -> bool {
20 b == b'\\'
21}
22
23pub fn is_verbatim(path: &[u16]) -> bool {
24 path.starts_with(utf16!(r"\\?\")) || path.starts_with(utf16!(r"\??\"))
25}
26
27pub(crate) fn is_file_name(path: &OsStr) -> bool {
29 !path.as_encoded_bytes().iter().copied().any(is_sep_byte)
30}
31pub(crate) fn has_trailing_slash(path: &OsStr) -> bool {
32 let is_verbatim = path.as_encoded_bytes().starts_with(br"\\?\");
33 let is_separator = if is_verbatim { is_verbatim_sep } else { is_sep_byte };
34 if let Some(&c) = path.as_encoded_bytes().last() { is_separator(c) } else { false }
35}
36
37pub(crate) fn append_suffix(path: PathBuf, suffix: &OsStr) -> PathBuf {
41 let mut path = OsString::from(path);
42 path.push(suffix);
43 path.into()
44}
45
46struct PrefixParser<'a, const LEN: usize> {
47 path: &'a OsStr,
48 prefix: [u8; LEN],
49}
50
51impl<'a, const LEN: usize> PrefixParser<'a, LEN> {
52 #[inline]
53 fn get_prefix(path: &OsStr) -> [u8; LEN] {
54 let mut prefix = [0; LEN];
55 for (i, &ch) in path.as_encoded_bytes().iter().take(LEN).enumerate() {
57 prefix[i] = if ch == b'/' { b'\\' } else { ch };
58 }
59 prefix
60 }
61
62 fn new(path: &'a OsStr) -> Self {
63 Self { path, prefix: Self::get_prefix(path) }
64 }
65
66 fn as_slice(&self) -> PrefixParserSlice<'a, '_> {
67 PrefixParserSlice {
68 path: self.path,
69 prefix: &self.prefix[..LEN.min(self.path.len())],
70 index: 0,
71 }
72 }
73}
74
75struct PrefixParserSlice<'a, 'b> {
76 path: &'a OsStr,
77 prefix: &'b [u8],
78 index: usize,
79}
80
81impl<'a> PrefixParserSlice<'a, '_> {
82 fn strip_prefix(&self, prefix: &str) -> Option<Self> {
83 self.prefix[self.index..]
84 .starts_with(prefix.as_bytes())
85 .then_some(Self { index: self.index + prefix.len(), ..*self })
86 }
87
88 fn prefix_bytes(&self) -> &'a [u8] {
89 &self.path.as_encoded_bytes()[..self.index]
90 }
91
92 fn finish(self) -> &'a OsStr {
93 unsafe { OsStr::from_encoded_bytes_unchecked(&self.path.as_encoded_bytes()[self.index..]) }
98 }
99}
100
101pub fn parse_prefix(path: &OsStr) -> Option<Prefix<'_>> {
102 use Prefix::{DeviceNS, Disk, UNC, Verbatim, VerbatimDisk, VerbatimUNC};
103
104 let parser = PrefixParser::<8>::new(path);
105 let parser = parser.as_slice();
106 if let Some(parser) = parser.strip_prefix(r"\\") {
107 if let Some(parser) = parser.strip_prefix(r"?\")
112 && !parser.prefix_bytes().iter().any(|&x| x == b'/')
113 {
114 if let Some(parser) = parser.strip_prefix(r"UNC\") {
116 let path = parser.finish();
119 let (server, path) = parse_next_component(path, true);
120 let (share, _) = parse_next_component(path, true);
121
122 Some(VerbatimUNC(server, share))
123 } else {
124 let path = parser.finish();
125
126 if let Some(drive) = parse_drive_exact(path) {
128 Some(VerbatimDisk(drive))
130 } else {
131 let (prefix, _) = parse_next_component(path, true);
133 Some(Verbatim(prefix))
134 }
135 }
136 } else if let Some(parser) = parser.strip_prefix(r".\") {
137 let path = parser.finish();
139 let (prefix, _) = parse_next_component(path, false);
140 Some(DeviceNS(prefix))
141 } else {
142 let path = parser.finish();
143 let (server, path) = parse_next_component(path, false);
144 let (share, _) = parse_next_component(path, false);
145
146 if !server.is_empty() && !share.is_empty() {
147 Some(UNC(server, share))
149 } else {
150 None
152 }
153 }
154 } else {
155 parse_drive(path).map(Disk)
158 }
159}
160
161fn parse_drive(path: &OsStr) -> Option<u8> {
163 fn is_valid_drive_letter(drive: &u8) -> bool {
166 drive.is_ascii_alphabetic()
167 }
168
169 match path.as_encoded_bytes() {
170 [drive, b':', ..] if is_valid_drive_letter(drive) => Some(drive.to_ascii_uppercase()),
171 _ => None,
172 }
173}
174
175fn parse_drive_exact(path: &OsStr) -> Option<u8> {
177 if path.as_encoded_bytes().get(2).map(|&x| is_sep_byte(x)).unwrap_or(true) {
179 parse_drive(path)
180 } else {
181 None
182 }
183}
184
185fn parse_next_component(path: &OsStr, verbatim: bool) -> (&OsStr, &OsStr) {
190 let separator = if verbatim { is_verbatim_sep } else { is_sep_byte };
191
192 match path.as_encoded_bytes().iter().position(|&x| separator(x)) {
193 Some(separator_start) => {
194 let separator_end = separator_start + 1;
195
196 let component = &path.as_encoded_bytes()[..separator_start];
197
198 let path = &path.as_encoded_bytes()[separator_end..];
201
202 unsafe {
207 (
208 OsStr::from_encoded_bytes_unchecked(component),
209 OsStr::from_encoded_bytes_unchecked(path),
210 )
211 }
212 }
213 None => (path, OsStr::new("")),
214 }
215}
216
217pub(crate) fn maybe_verbatim(path: &Path) -> io::Result<Vec<u16>> {
221 let path = to_u16s(path)?;
222 get_long_path(path, true)
223}
224
225pub(crate) fn get_long_path(mut path: Vec<u16>, prefer_verbatim: bool) -> io::Result<Vec<u16>> {
234 const LEGACY_MAX_PATH: usize = 248;
239 const SEP: u16 = b'\\' as _;
242 const ALT_SEP: u16 = b'/' as _;
243 const QUERY: u16 = b'?' as _;
244 const COLON: u16 = b':' as _;
245 const DOT: u16 = b'.' as _;
246 const U: u16 = b'U' as _;
247 const N: u16 = b'N' as _;
248 const C: u16 = b'C' as _;
249
250 const VERBATIM_PREFIX: &[u16] = &[SEP, SEP, QUERY, SEP];
252 const NT_PREFIX: &[u16] = &[SEP, QUERY, QUERY, SEP];
254 const UNC_PREFIX: &[u16] = &[SEP, SEP, QUERY, SEP, U, N, C, SEP];
256
257 if path.starts_with(VERBATIM_PREFIX) || path.starts_with(NT_PREFIX) || path == [0] {
258 return Ok(path);
260 } else if path.len() < LEGACY_MAX_PATH {
261 match path.as_slice() {
264 [drive, COLON, 0] | [drive, COLON, SEP | ALT_SEP, ..]
267 if *drive != SEP && *drive != ALT_SEP =>
268 {
269 return Ok(path);
270 }
271 [SEP | ALT_SEP, SEP | ALT_SEP, ..] => return Ok(path),
273 _ => {}
274 }
275 }
276
277 let lpfilename = path.as_ptr();
280 fill_utf16_buf(
281 |buffer, size| unsafe { c::GetFullPathNameW(lpfilename, size, buffer, ptr::null_mut()) },
285 |mut absolute| {
286 path.clear();
287
288 if prefer_verbatim || absolute.len() + 1 >= LEGACY_MAX_PATH {
290 let prefix = match absolute {
293 [_, COLON, SEP, ..] => VERBATIM_PREFIX,
295 [SEP, SEP, DOT, SEP, ..] => {
297 absolute = &absolute[4..];
298 VERBATIM_PREFIX
299 }
300 [SEP, SEP, QUERY, SEP, ..] | [SEP, QUERY, QUERY, SEP, ..] => &[],
302 [SEP, SEP, ..] => {
304 absolute = &absolute[2..];
305 UNC_PREFIX
306 }
307 _ => &[],
309 };
310
311 path.reserve_exact(prefix.len() + absolute.len() + 1);
312 path.extend_from_slice(prefix);
313 } else {
314 path.reserve_exact(absolute.len() + 1);
315 }
316 path.extend_from_slice(absolute);
317 path.push(0);
318 },
319 )?;
320 Ok(path)
321}
322
323pub(crate) fn absolute(path: &Path) -> io::Result<PathBuf> {
325 let path = path.as_os_str();
326 let prefix = parse_prefix(path);
327 if prefix.map(|x| x.is_verbatim()).unwrap_or(false) {
329 if path.as_encoded_bytes().contains(&0) {
331 return Err(io::const_error!(
332 io::ErrorKind::InvalidInput,
333 "strings passed to WinAPI cannot contain NULs",
334 ));
335 }
336 return Ok(path.to_owned().into());
337 }
338
339 let path = to_u16s(path)?;
340 let lpfilename = path.as_ptr();
341 fill_utf16_buf(
342 |buffer, size| unsafe { c::GetFullPathNameW(lpfilename, size, buffer, ptr::null_mut()) },
346 os2path,
347 )
348}
349
350pub(crate) fn is_absolute(path: &Path) -> bool {
351 path.has_root() && path.prefix().is_some()
352}