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#[repr(transparent)]
15pub struct WCStr([u16]);
16
17impl WCStr {
18 pub unsafe fn from_wchars_with_null_unchecked(s: &[u16]) -> &Self {
27 unsafe { &*(s as *const [u16] as *const Self) }
28 }
29
30 pub fn as_ptr(&self) -> *const u16 {
31 self.0.as_ptr()
32 }
33
34 pub fn count_bytes(&self) -> usize {
35 self.0.len()
36 }
37}
38
39#[inline]
40pub fn with_native_path<T>(path: &Path, f: &dyn Fn(&WCStr) -> io::Result<T>) -> io::Result<T> {
41 let path = maybe_verbatim(path)?;
42 let path = unsafe { WCStr::from_wchars_with_null_unchecked(&path) };
44 f(path)
45}
46
47#[inline]
48pub fn is_sep_byte(b: u8) -> bool {
49 b == b'/' || b == b'\\'
50}
51
52#[inline]
53pub fn is_verbatim_sep(b: u8) -> bool {
54 b == b'\\'
55}
56
57pub fn is_verbatim(path: &[u16]) -> bool {
58 path.starts_with(utf16!(r"\\?\")) || path.starts_with(utf16!(r"\??\"))
59}
60
61pub(crate) fn is_file_name(path: &OsStr) -> bool {
63 !path.as_encoded_bytes().iter().copied().any(is_sep_byte)
64}
65pub(crate) fn has_trailing_slash(path: &OsStr) -> bool {
66 let is_verbatim = path.as_encoded_bytes().starts_with(br"\\?\");
67 let is_separator = if is_verbatim { is_verbatim_sep } else { is_sep_byte };
68 if let Some(&c) = path.as_encoded_bytes().last() { is_separator(c) } else { false }
69}
70
71pub(crate) fn append_suffix(path: PathBuf, suffix: &OsStr) -> PathBuf {
75 let mut path = OsString::from(path);
76 path.push(suffix);
77 path.into()
78}
79
80struct PrefixParser<'a, const LEN: usize> {
81 path: &'a OsStr,
82 prefix: [u8; LEN],
83}
84
85impl<'a, const LEN: usize> PrefixParser<'a, LEN> {
86 #[inline]
87 fn get_prefix(path: &OsStr) -> [u8; LEN] {
88 let mut prefix = [0; LEN];
89 for (i, &ch) in path.as_encoded_bytes().iter().take(LEN).enumerate() {
91 prefix[i] = if ch == b'/' { b'\\' } else { ch };
92 }
93 prefix
94 }
95
96 fn new(path: &'a OsStr) -> Self {
97 Self { path, prefix: Self::get_prefix(path) }
98 }
99
100 fn as_slice(&self) -> PrefixParserSlice<'a, '_> {
101 PrefixParserSlice {
102 path: self.path,
103 prefix: &self.prefix[..LEN.min(self.path.len())],
104 index: 0,
105 }
106 }
107}
108
109struct PrefixParserSlice<'a, 'b> {
110 path: &'a OsStr,
111 prefix: &'b [u8],
112 index: usize,
113}
114
115impl<'a> PrefixParserSlice<'a, '_> {
116 fn strip_prefix(&self, prefix: &str) -> Option<Self> {
117 self.prefix[self.index..]
118 .starts_with(prefix.as_bytes())
119 .then_some(Self { index: self.index + prefix.len(), ..*self })
120 }
121
122 fn prefix_bytes(&self) -> &'a [u8] {
123 &self.path.as_encoded_bytes()[..self.index]
124 }
125
126 fn finish(self) -> &'a OsStr {
127 unsafe { OsStr::from_encoded_bytes_unchecked(&self.path.as_encoded_bytes()[self.index..]) }
132 }
133}
134
135pub fn parse_prefix(path: &OsStr) -> Option<Prefix<'_>> {
136 use Prefix::{DeviceNS, Disk, UNC, Verbatim, VerbatimDisk, VerbatimUNC};
137
138 let parser = PrefixParser::<8>::new(path);
139 let parser = parser.as_slice();
140 if let Some(parser) = parser.strip_prefix(r"\\") {
141 if let Some(parser) = parser.strip_prefix(r"?\")
146 && !parser.prefix_bytes().iter().any(|&x| x == b'/')
147 {
148 if let Some(parser) = parser.strip_prefix(r"UNC\") {
150 let path = parser.finish();
153 let (server, path) = parse_next_component(path, true);
154 let (share, _) = parse_next_component(path, true);
155
156 Some(VerbatimUNC(server, share))
157 } else {
158 let path = parser.finish();
159
160 if let Some(drive) = parse_drive_exact(path) {
162 Some(VerbatimDisk(drive))
164 } else {
165 let (prefix, _) = parse_next_component(path, true);
167 Some(Verbatim(prefix))
168 }
169 }
170 } else if let Some(parser) = parser.strip_prefix(r".\") {
171 let path = parser.finish();
173 let (prefix, _) = parse_next_component(path, false);
174 Some(DeviceNS(prefix))
175 } else {
176 let path = parser.finish();
177 let (server, path) = parse_next_component(path, false);
178 let (share, _) = parse_next_component(path, false);
179
180 if !server.is_empty() && !share.is_empty() {
181 Some(UNC(server, share))
183 } else {
184 None
186 }
187 }
188 } else {
189 parse_drive(path).map(Disk)
192 }
193}
194
195fn parse_drive(path: &OsStr) -> Option<u8> {
197 fn is_valid_drive_letter(drive: &u8) -> bool {
200 drive.is_ascii_alphabetic()
201 }
202
203 match path.as_encoded_bytes() {
204 [drive, b':', ..] if is_valid_drive_letter(drive) => Some(drive.to_ascii_uppercase()),
205 _ => None,
206 }
207}
208
209fn parse_drive_exact(path: &OsStr) -> Option<u8> {
211 if path.as_encoded_bytes().get(2).map(|&x| is_sep_byte(x)).unwrap_or(true) {
213 parse_drive(path)
214 } else {
215 None
216 }
217}
218
219fn parse_next_component(path: &OsStr, verbatim: bool) -> (&OsStr, &OsStr) {
224 let separator = if verbatim { is_verbatim_sep } else { is_sep_byte };
225
226 match path.as_encoded_bytes().iter().position(|&x| separator(x)) {
227 Some(separator_start) => {
228 let separator_end = separator_start + 1;
229
230 let component = &path.as_encoded_bytes()[..separator_start];
231
232 let path = &path.as_encoded_bytes()[separator_end..];
235
236 unsafe {
241 (
242 OsStr::from_encoded_bytes_unchecked(component),
243 OsStr::from_encoded_bytes_unchecked(path),
244 )
245 }
246 }
247 None => (path, OsStr::new("")),
248 }
249}
250
251pub(crate) fn maybe_verbatim(path: &Path) -> io::Result<Vec<u16>> {
255 let path = to_u16s(path)?;
256 get_long_path(path, true)
257}
258
259pub(crate) fn get_long_path(mut path: Vec<u16>, prefer_verbatim: bool) -> io::Result<Vec<u16>> {
268 const LEGACY_MAX_PATH: usize = 248;
273 const SEP: u16 = b'\\' as _;
276 const ALT_SEP: u16 = b'/' as _;
277 const QUERY: u16 = b'?' as _;
278 const COLON: u16 = b':' as _;
279 const DOT: u16 = b'.' as _;
280 const U: u16 = b'U' as _;
281 const N: u16 = b'N' as _;
282 const C: u16 = b'C' as _;
283
284 const VERBATIM_PREFIX: &[u16] = &[SEP, SEP, QUERY, SEP];
286 const NT_PREFIX: &[u16] = &[SEP, QUERY, QUERY, SEP];
288 const UNC_PREFIX: &[u16] = &[SEP, SEP, QUERY, SEP, U, N, C, SEP];
290
291 if path.starts_with(VERBATIM_PREFIX) || path.starts_with(NT_PREFIX) || path == [0] {
292 return Ok(path);
294 } else if path.len() < LEGACY_MAX_PATH {
295 match path.as_slice() {
298 [drive, COLON, 0] | [drive, COLON, SEP | ALT_SEP, ..]
301 if *drive != SEP && *drive != ALT_SEP =>
302 {
303 return Ok(path);
304 }
305 [SEP | ALT_SEP, SEP | ALT_SEP, ..] => return Ok(path),
307 _ => {}
308 }
309 }
310
311 let lpfilename = path.as_ptr();
314 fill_utf16_buf(
315 |buffer, size| unsafe { c::GetFullPathNameW(lpfilename, size, buffer, ptr::null_mut()) },
319 |mut absolute| {
320 path.clear();
321
322 if prefer_verbatim || absolute.len() + 1 >= LEGACY_MAX_PATH {
324 let prefix = match absolute {
327 [_, COLON, SEP, ..] => VERBATIM_PREFIX,
329 [SEP, SEP, DOT, SEP, ..] => {
331 absolute = &absolute[4..];
332 VERBATIM_PREFIX
333 }
334 [SEP, SEP, QUERY, SEP, ..] | [SEP, QUERY, QUERY, SEP, ..] => &[],
336 [SEP, SEP, ..] => {
338 absolute = &absolute[2..];
339 UNC_PREFIX
340 }
341 _ => &[],
343 };
344
345 path.reserve_exact(prefix.len() + absolute.len() + 1);
346 path.extend_from_slice(prefix);
347 } else {
348 path.reserve_exact(absolute.len() + 1);
349 }
350 path.extend_from_slice(absolute);
351 path.push(0);
352 },
353 )?;
354 Ok(path)
355}
356
357pub(crate) fn absolute(path: &Path) -> io::Result<PathBuf> {
359 let path = path.as_os_str();
360 let prefix = parse_prefix(path);
361 if prefix.map(|x| x.is_verbatim()).unwrap_or(false) {
363 if path.as_encoded_bytes().contains(&0) {
365 return Err(io::const_error!(
366 io::ErrorKind::InvalidInput,
367 "strings passed to WinAPI cannot contain NULs",
368 ));
369 }
370 return Ok(path.to_owned().into());
371 }
372
373 let path = to_u16s(path)?;
374 let lpfilename = path.as_ptr();
375 fill_utf16_buf(
376 |buffer, size| unsafe { c::GetFullPathNameW(lpfilename, size, buffer, ptr::null_mut()) },
380 os2path,
381 )
382}
383
384pub(crate) fn is_absolute(path: &Path) -> bool {
385 path.has_root() && path.prefix().is_some()
386}
387
388pub(crate) fn is_absolute_exact(path: &[u16]) -> bool {
399 if path.is_empty() || path.len() > u32::MAX as usize || path.last() != Some(&0) {
405 return false;
406 }
407 let buffer_len = path.len();
410 let mut new_path = Vec::with_capacity(buffer_len);
411 let result = unsafe {
412 c::GetFullPathNameW(
413 path.as_ptr(),
414 new_path.capacity() as u32,
415 new_path.as_mut_ptr(),
416 crate::ptr::null_mut(),
417 )
418 };
419 if result == 0 || result as usize != buffer_len - 1 {
421 false
422 } else {
423 unsafe {
425 new_path.set_len((result as usize) + 1);
426 }
427 path == &new_path
428 }
429}