std\sys\path/windows.rs
1use crate::ffi::{OsStr, OsString};
2use crate::path::{Path, PathBuf};
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 use super::windows_prefix::parse_prefix;
11
12pub const MAIN_SEP_STR: &str = "\\";
13pub const MAIN_SEP: char = '\\';
14
15/// A null terminated wide string.
16#[repr(transparent)]
17pub struct WCStr([u16]);
18
19impl WCStr {
20 /// Convert a slice to a WCStr without checks.
21 ///
22 /// Though it is memory safe, the slice should also not contain interior nulls
23 /// as this may lead to unwanted truncation.
24 ///
25 /// # Safety
26 ///
27 /// The slice must end in a null.
28 pub unsafe fn from_wchars_with_null_unchecked(s: &[u16]) -> &Self {
29 unsafe { &*(s as *const [u16] as *const Self) }
30 }
31
32 pub fn as_ptr(&self) -> *const u16 {
33 self.0.as_ptr()
34 }
35
36 pub fn count_bytes(&self) -> usize {
37 self.0.len()
38 }
39}
40
41#[inline]
42pub fn with_native_path<T>(path: &Path, f: &dyn Fn(&WCStr) -> io::Result<T>) -> io::Result<T> {
43 let path = maybe_verbatim(path)?;
44 // SAFETY: maybe_verbatim returns null-terminated strings
45 let path = unsafe { WCStr::from_wchars_with_null_unchecked(&path) };
46 f(path)
47}
48
49#[inline]
50pub fn is_sep_byte(b: u8) -> bool {
51 b == b'/' || b == b'\\'
52}
53
54#[inline]
55pub fn is_verbatim_sep(b: u8) -> bool {
56 b == b'\\'
57}
58
59pub fn is_verbatim(path: &[u16]) -> bool {
60 path.starts_with(utf16!(r"\\?\")) || path.starts_with(utf16!(r"\??\"))
61}
62
63/// Returns true if `path` looks like a lone filename.
64pub(crate) fn is_file_name(path: &OsStr) -> bool {
65 !path.as_encoded_bytes().iter().copied().any(is_sep_byte)
66}
67pub(crate) fn has_trailing_slash(path: &OsStr) -> bool {
68 let is_verbatim = path.as_encoded_bytes().starts_with(br"\\?\");
69 let is_separator = if is_verbatim { is_verbatim_sep } else { is_sep_byte };
70 if let Some(&c) = path.as_encoded_bytes().last() { is_separator(c) } else { false }
71}
72
73/// Appends a suffix to a path.
74///
75/// Can be used to append an extension without removing an existing extension.
76pub(crate) fn append_suffix(path: PathBuf, suffix: &OsStr) -> PathBuf {
77 let mut path = OsString::from(path);
78 path.push(suffix);
79 path.into()
80}
81
82/// Returns a UTF-16 encoded path capable of bypassing the legacy `MAX_PATH` limits.
83///
84/// This path may or may not have a verbatim prefix.
85pub(crate) fn maybe_verbatim(path: &Path) -> io::Result<Vec<u16>> {
86 let path = to_u16s(path)?;
87 get_long_path(path, true)
88}
89
90/// Gets a normalized absolute path that can bypass path length limits.
91///
92/// Setting prefer_verbatim to true suggests a stronger preference for verbatim
93/// paths even when not strictly necessary. This allows the Windows API to avoid
94/// repeating our work. However, if the path may be given back to users or
95/// passed to other application then it's preferable to use non-verbatim paths
96/// when possible. Non-verbatim paths are better understood by users and handled
97/// by more software.
98pub(crate) fn get_long_path(mut path: Vec<u16>, prefer_verbatim: bool) -> io::Result<Vec<u16>> {
99 // Normally the MAX_PATH is 260 UTF-16 code units (including the NULL).
100 // However, for APIs such as CreateDirectory[1], the limit is 248.
101 //
102 // [1]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createdirectorya#parameters
103 const LEGACY_MAX_PATH: usize = 248;
104 // UTF-16 encoded code points, used in parsing and building UTF-16 paths.
105 // All of these are in the ASCII range so they can be cast directly to `u16`.
106 const SEP: u16 = b'\\' as _;
107 const ALT_SEP: u16 = b'/' as _;
108 const QUERY: u16 = b'?' as _;
109 const COLON: u16 = b':' as _;
110 const DOT: u16 = b'.' as _;
111 const U: u16 = b'U' as _;
112 const N: u16 = b'N' as _;
113 const C: u16 = b'C' as _;
114
115 // \\?\
116 const VERBATIM_PREFIX: &[u16] = &[SEP, SEP, QUERY, SEP];
117 // \??\
118 const NT_PREFIX: &[u16] = &[SEP, QUERY, QUERY, SEP];
119 // \\?\UNC\
120 const UNC_PREFIX: &[u16] = &[SEP, SEP, QUERY, SEP, U, N, C, SEP];
121
122 if path.starts_with(VERBATIM_PREFIX) || path.starts_with(NT_PREFIX) || path == [0] {
123 // Early return for paths that are already verbatim or empty.
124 return Ok(path);
125 } else if path.len() < LEGACY_MAX_PATH {
126 // Early return if an absolute path is less < 260 UTF-16 code units.
127 // This is an optimization to avoid calling `GetFullPathNameW` unnecessarily.
128 match path.as_slice() {
129 // Starts with `D:`, `D:\`, `D:/`, etc.
130 // Does not match if the path starts with a `\` or `/`.
131 [drive, COLON, 0] | [drive, COLON, SEP | ALT_SEP, ..]
132 if *drive != SEP && *drive != ALT_SEP =>
133 {
134 return Ok(path);
135 }
136 // Starts with `\\`, `//`, etc
137 [SEP | ALT_SEP, SEP | ALT_SEP, ..] => return Ok(path),
138 _ => {}
139 }
140 }
141
142 // Firstly, get the absolute path using `GetFullPathNameW`.
143 // https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfullpathnamew
144 let lpfilename = path.as_ptr();
145 fill_utf16_buf(
146 // SAFETY: `fill_utf16_buf` ensures the `buffer` and `size` are valid.
147 // `lpfilename` is a pointer to a null terminated string that is not
148 // invalidated until after `GetFullPathNameW` returns successfully.
149 |buffer, size| unsafe { c::GetFullPathNameW(lpfilename, size, buffer, ptr::null_mut()) },
150 |mut absolute| {
151 path.clear();
152
153 // Only prepend the prefix if needed.
154 if prefer_verbatim || absolute.len() + 1 >= LEGACY_MAX_PATH {
155 // Secondly, add the verbatim prefix. This is easier here because we know the
156 // path is now absolute and fully normalized (e.g. `/` has been changed to `\`).
157 let prefix = match absolute {
158 // C:\ => \\?\C:\
159 [_, COLON, SEP, ..] => VERBATIM_PREFIX,
160 // \\.\ => \\?\
161 [SEP, SEP, DOT, SEP, ..] => {
162 absolute = &absolute[4..];
163 VERBATIM_PREFIX
164 }
165 // Leave \\?\ and \??\ as-is.
166 [SEP, SEP, QUERY, SEP, ..] | [SEP, QUERY, QUERY, SEP, ..] => &[],
167 // \\ => \\?\UNC\
168 [SEP, SEP, ..] => {
169 absolute = &absolute[2..];
170 UNC_PREFIX
171 }
172 // Anything else we leave alone.
173 _ => &[],
174 };
175
176 path.reserve_exact(prefix.len() + absolute.len() + 1);
177 path.extend_from_slice(prefix);
178 } else {
179 path.reserve_exact(absolute.len() + 1);
180 }
181 path.extend_from_slice(absolute);
182 path.push(0);
183 },
184 )?;
185 Ok(path)
186}
187
188/// Make a Windows path absolute.
189pub(crate) fn absolute(path: &Path) -> io::Result<PathBuf> {
190 let path = path.as_os_str();
191 let prefix = parse_prefix(path);
192 // Verbatim paths should not be modified.
193 if prefix.map(|x| x.is_verbatim()).unwrap_or(false) {
194 // NULs in verbatim paths are rejected for consistency.
195 if path.as_encoded_bytes().contains(&0) {
196 return Err(io::const_error!(
197 io::ErrorKind::InvalidInput,
198 "strings passed to WinAPI cannot contain NULs",
199 ));
200 }
201 return Ok(path.to_owned().into());
202 }
203
204 let path = to_u16s(path)?;
205 let lpfilename = path.as_ptr();
206 fill_utf16_buf(
207 // SAFETY: `fill_utf16_buf` ensures the `buffer` and `size` are valid.
208 // `lpfilename` is a pointer to a null terminated string that is not
209 // invalidated until after `GetFullPathNameW` returns successfully.
210 |buffer, size| unsafe { c::GetFullPathNameW(lpfilename, size, buffer, ptr::null_mut()) },
211 os2path,
212 )
213}
214
215pub(crate) fn is_absolute(path: &Path) -> bool {
216 path.has_root() && path.prefix().is_some()
217}
218
219/// Test that the path is absolute, fully qualified and unchanged when processed by the Windows API.
220///
221/// For example:
222///
223/// - `C:\path\to\file` will return true.
224/// - `C:\path\to\nul` returns false because the Windows API will convert it to \\.\NUL
225/// - `C:\path\to\..\file` returns false because it will be resolved to `C:\path\file`.
226///
227/// This is a useful property because it means the path can be converted from and to and verbatim
228/// path just by changing the prefix.
229pub(crate) fn is_absolute_exact(path: &[u16]) -> bool {
230 // This is implemented by checking that passing the path through
231 // GetFullPathNameW does not change the path in any way.
232
233 // Windows paths are limited to i16::MAX length
234 // though the API here accepts a u32 for the length.
235 if path.is_empty() || path.len() > u32::MAX as usize || path.last() != Some(&0) {
236 return false;
237 }
238 // The path returned by `GetFullPathNameW` must be the same length as the
239 // given path, otherwise they're not equal.
240 let buffer_len = path.len();
241 let mut new_path = Vec::with_capacity(buffer_len);
242 let result = unsafe {
243 c::GetFullPathNameW(
244 path.as_ptr(),
245 new_path.capacity() as u32,
246 new_path.as_mut_ptr(),
247 crate::ptr::null_mut(),
248 )
249 };
250 // Note: if non-zero, the returned result is the length of the buffer without the null termination
251 if result == 0 || result as usize != buffer_len - 1 {
252 false
253 } else {
254 // SAFETY: `GetFullPathNameW` initialized `result` bytes and does not exceed `nBufferLength - 1` (capacity).
255 unsafe {
256 new_path.set_len((result as usize) + 1);
257 }
258 path == &new_path
259 }
260}