]> git.lizzy.rs Git - rust.git/blob - library/std/src/sys/windows/path.rs
Auto merge of #99918 - WaffleLapkin:fnFnfun, r=estebank
[rust.git] / library / std / src / sys / windows / path.rs
1 use super::{c, fill_utf16_buf, to_u16s};
2 use crate::ffi::{OsStr, OsString};
3 use crate::io;
4 use crate::mem;
5 use crate::path::{Path, PathBuf, Prefix};
6 use crate::ptr;
7
8 #[cfg(test)]
9 mod tests;
10
11 pub const MAIN_SEP_STR: &str = "\\";
12 pub const MAIN_SEP: char = '\\';
13
14 /// # Safety
15 ///
16 /// `bytes` must be a valid wtf8 encoded slice
17 #[inline]
18 unsafe fn bytes_as_os_str(bytes: &[u8]) -> &OsStr {
19     // &OsStr is layout compatible with &Slice, which is compatible with &Wtf8,
20     // which is compatible with &[u8].
21     mem::transmute(bytes)
22 }
23
24 #[inline]
25 pub fn is_sep_byte(b: u8) -> bool {
26     b == b'/' || b == b'\\'
27 }
28
29 #[inline]
30 pub fn is_verbatim_sep(b: u8) -> bool {
31     b == b'\\'
32 }
33
34 /// Returns true if `path` looks like a lone filename.
35 pub(crate) fn is_file_name(path: &OsStr) -> bool {
36     !path.bytes().iter().copied().any(is_sep_byte)
37 }
38 pub(crate) fn has_trailing_slash(path: &OsStr) -> bool {
39     let is_verbatim = path.bytes().starts_with(br"\\?\");
40     let is_separator = if is_verbatim { is_verbatim_sep } else { is_sep_byte };
41     if let Some(&c) = path.bytes().last() { is_separator(c) } else { false }
42 }
43
44 /// Appends a suffix to a path.
45 ///
46 /// Can be used to append an extension without removing an existing extension.
47 pub(crate) fn append_suffix(path: PathBuf, suffix: &OsStr) -> PathBuf {
48     let mut path = OsString::from(path);
49     path.push(suffix);
50     path.into()
51 }
52
53 struct PrefixParser<'a, const LEN: usize> {
54     path: &'a OsStr,
55     prefix: [u8; LEN],
56 }
57
58 impl<'a, const LEN: usize> PrefixParser<'a, LEN> {
59     #[inline]
60     fn get_prefix(path: &OsStr) -> [u8; LEN] {
61         let mut prefix = [0; LEN];
62         // SAFETY: Only ASCII characters are modified.
63         for (i, &ch) in path.bytes().iter().take(LEN).enumerate() {
64             prefix[i] = if ch == b'/' { b'\\' } else { ch };
65         }
66         prefix
67     }
68
69     fn new(path: &'a OsStr) -> Self {
70         Self { path, prefix: Self::get_prefix(path) }
71     }
72
73     fn as_slice(&self) -> PrefixParserSlice<'a, '_> {
74         PrefixParserSlice {
75             path: self.path,
76             prefix: &self.prefix[..LEN.min(self.path.len())],
77             index: 0,
78         }
79     }
80 }
81
82 struct PrefixParserSlice<'a, 'b> {
83     path: &'a OsStr,
84     prefix: &'b [u8],
85     index: usize,
86 }
87
88 impl<'a> PrefixParserSlice<'a, '_> {
89     fn strip_prefix(&self, prefix: &str) -> Option<Self> {
90         self.prefix[self.index..]
91             .starts_with(prefix.as_bytes())
92             .then(|| Self { index: self.index + prefix.len(), ..*self })
93     }
94
95     fn prefix_bytes(&self) -> &'a [u8] {
96         &self.path.bytes()[..self.index]
97     }
98
99     fn finish(self) -> &'a OsStr {
100         // SAFETY: The unsafety here stems from converting between &OsStr and
101         // &[u8] and back. This is safe to do because (1) we only look at ASCII
102         // contents of the encoding and (2) new &OsStr values are produced only
103         // from ASCII-bounded slices of existing &OsStr values.
104         unsafe { bytes_as_os_str(&self.path.bytes()[self.index..]) }
105     }
106 }
107
108 pub fn parse_prefix(path: &OsStr) -> Option<Prefix<'_>> {
109     use Prefix::{DeviceNS, Disk, Verbatim, VerbatimDisk, VerbatimUNC, UNC};
110
111     let parser = PrefixParser::<8>::new(path);
112     let parser = parser.as_slice();
113     if let Some(parser) = parser.strip_prefix(r"\\") {
114         // \\
115
116         // The meaning of verbatim paths can change when they use a different
117         // separator.
118         if let Some(parser) = parser.strip_prefix(r"?\") && !parser.prefix_bytes().iter().any(|&x| x == b'/') {
119             // \\?\
120             if let Some(parser) = parser.strip_prefix(r"UNC\") {
121                 // \\?\UNC\server\share
122
123                 let path = parser.finish();
124                 let (server, path) = parse_next_component(path, true);
125                 let (share, _) = parse_next_component(path, true);
126
127                 Some(VerbatimUNC(server, share))
128             } else {
129                 let path = parser.finish();
130
131                 // in verbatim paths only recognize an exact drive prefix
132                 if let Some(drive) = parse_drive_exact(path) {
133                     // \\?\C:
134                     Some(VerbatimDisk(drive))
135                 } else {
136                     // \\?\prefix
137                     let (prefix, _) = parse_next_component(path, true);
138                     Some(Verbatim(prefix))
139                 }
140             }
141         } else if let Some(parser) = parser.strip_prefix(r".\") {
142             // \\.\COM42
143             let path = parser.finish();
144             let (prefix, _) = parse_next_component(path, false);
145             Some(DeviceNS(prefix))
146         } else {
147             let path = parser.finish();
148             let (server, path) = parse_next_component(path, false);
149             let (share, _) = parse_next_component(path, false);
150
151             if !server.is_empty() && !share.is_empty() {
152                 // \\server\share
153                 Some(UNC(server, share))
154             } else {
155                 // no valid prefix beginning with "\\" recognized
156                 None
157             }
158         }
159     } else if let Some(drive) = parse_drive(path) {
160         // C:
161         Some(Disk(drive))
162     } else {
163         // no prefix
164         None
165     }
166 }
167
168 // Parses a drive prefix, e.g. "C:" and "C:\whatever"
169 fn parse_drive(path: &OsStr) -> Option<u8> {
170     // In most DOS systems, it is not possible to have more than 26 drive letters.
171     // See <https://en.wikipedia.org/wiki/Drive_letter_assignment#Common_assignments>.
172     fn is_valid_drive_letter(drive: &u8) -> bool {
173         drive.is_ascii_alphabetic()
174     }
175
176     match path.bytes() {
177         [drive, b':', ..] if is_valid_drive_letter(drive) => Some(drive.to_ascii_uppercase()),
178         _ => None,
179     }
180 }
181
182 // Parses a drive prefix exactly, e.g. "C:"
183 fn parse_drive_exact(path: &OsStr) -> Option<u8> {
184     // only parse two bytes: the drive letter and the drive separator
185     if path.bytes().get(2).map(|&x| is_sep_byte(x)).unwrap_or(true) {
186         parse_drive(path)
187     } else {
188         None
189     }
190 }
191
192 // Parse the next path component.
193 //
194 // Returns the next component and the rest of the path excluding the component and separator.
195 // Does not recognize `/` as a separator character if `verbatim` is true.
196 fn parse_next_component(path: &OsStr, verbatim: bool) -> (&OsStr, &OsStr) {
197     let separator = if verbatim { is_verbatim_sep } else { is_sep_byte };
198
199     match path.bytes().iter().position(|&x| separator(x)) {
200         Some(separator_start) => {
201             let separator_end = separator_start + 1;
202
203             let component = &path.bytes()[..separator_start];
204
205             // Panic safe
206             // The max `separator_end` is `bytes.len()` and `bytes[bytes.len()..]` is a valid index.
207             let path = &path.bytes()[separator_end..];
208
209             // SAFETY: `path` is a valid wtf8 encoded slice and each of the separators ('/', '\')
210             // is encoded in a single byte, therefore `bytes[separator_start]` and
211             // `bytes[separator_end]` must be code point boundaries and thus
212             // `bytes[..separator_start]` and `bytes[separator_end..]` are valid wtf8 slices.
213             unsafe { (bytes_as_os_str(component), bytes_as_os_str(path)) }
214         }
215         None => (path, OsStr::new("")),
216     }
217 }
218
219 /// Returns a UTF-16 encoded path capable of bypassing the legacy `MAX_PATH` limits.
220 ///
221 /// This path may or may not have a verbatim prefix.
222 pub(crate) fn maybe_verbatim(path: &Path) -> io::Result<Vec<u16>> {
223     // Normally the MAX_PATH is 260 UTF-16 code units (including the NULL).
224     // However, for APIs such as CreateDirectory[1], the limit is 248.
225     //
226     // [1]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createdirectorya#parameters
227     const LEGACY_MAX_PATH: usize = 248;
228     // UTF-16 encoded code points, used in parsing and building UTF-16 paths.
229     // All of these are in the ASCII range so they can be cast directly to `u16`.
230     const SEP: u16 = b'\\' as _;
231     const ALT_SEP: u16 = b'/' as _;
232     const QUERY: u16 = b'?' as _;
233     const COLON: u16 = b':' as _;
234     const DOT: u16 = b'.' as _;
235     const U: u16 = b'U' as _;
236     const N: u16 = b'N' as _;
237     const C: u16 = b'C' as _;
238
239     // \\?\
240     const VERBATIM_PREFIX: &[u16] = &[SEP, SEP, QUERY, SEP];
241     // \??\
242     const NT_PREFIX: &[u16] = &[SEP, QUERY, QUERY, SEP];
243     // \\?\UNC\
244     const UNC_PREFIX: &[u16] = &[SEP, SEP, QUERY, SEP, U, N, C, SEP];
245
246     let mut path = to_u16s(path)?;
247     if path.starts_with(VERBATIM_PREFIX) || path.starts_with(NT_PREFIX) || path == &[0] {
248         // Early return for paths that are already verbatim or empty.
249         return Ok(path);
250     } else if path.len() < LEGACY_MAX_PATH {
251         // Early return if an absolute path is less < 260 UTF-16 code units.
252         // This is an optimization to avoid calling `GetFullPathNameW` unnecessarily.
253         match path.as_slice() {
254             // Starts with `D:`, `D:\`, `D:/`, etc.
255             // Does not match if the path starts with a `\` or `/`.
256             [drive, COLON, 0] | [drive, COLON, SEP | ALT_SEP, ..]
257                 if *drive != SEP && *drive != ALT_SEP =>
258             {
259                 return Ok(path);
260             }
261             // Starts with `\\`, `//`, etc
262             [SEP | ALT_SEP, SEP | ALT_SEP, ..] => return Ok(path),
263             _ => {}
264         }
265     }
266
267     // Firstly, get the absolute path using `GetFullPathNameW`.
268     // https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfullpathnamew
269     let lpfilename = path.as_ptr();
270     fill_utf16_buf(
271         // SAFETY: `fill_utf16_buf` ensures the `buffer` and `size` are valid.
272         // `lpfilename` is a pointer to a null terminated string that is not
273         // invalidated until after `GetFullPathNameW` returns successfully.
274         |buffer, size| unsafe { c::GetFullPathNameW(lpfilename, size, buffer, ptr::null_mut()) },
275         |mut absolute| {
276             path.clear();
277
278             // Secondly, add the verbatim prefix. This is easier here because we know the
279             // path is now absolute and fully normalized (e.g. `/` has been changed to `\`).
280             let prefix = match absolute {
281                 // C:\ => \\?\C:\
282                 [_, COLON, SEP, ..] => VERBATIM_PREFIX,
283                 // \\.\ => \\?\
284                 [SEP, SEP, DOT, SEP, ..] => {
285                     absolute = &absolute[4..];
286                     VERBATIM_PREFIX
287                 }
288                 // Leave \\?\ and \??\ as-is.
289                 [SEP, SEP, QUERY, SEP, ..] | [SEP, QUERY, QUERY, SEP, ..] => &[],
290                 // \\ => \\?\UNC\
291                 [SEP, SEP, ..] => {
292                     absolute = &absolute[2..];
293                     UNC_PREFIX
294                 }
295                 // Anything else we leave alone.
296                 _ => &[],
297             };
298
299             path.reserve_exact(prefix.len() + absolute.len() + 1);
300             path.extend_from_slice(prefix);
301             path.extend_from_slice(absolute);
302             path.push(0);
303         },
304     )?;
305     Ok(path)
306 }
307
308 /// Make a Windows path absolute.
309 pub(crate) fn absolute(path: &Path) -> io::Result<PathBuf> {
310     let path = path.as_os_str();
311     let prefix = parse_prefix(path);
312     // Verbatim paths should not be modified.
313     if prefix.map(|x| x.is_verbatim()).unwrap_or(false) {
314         // NULs in verbatim paths are rejected for consistency.
315         if path.bytes().contains(&0) {
316             return Err(io::const_io_error!(
317                 io::ErrorKind::InvalidInput,
318                 "strings passed to WinAPI cannot contain NULs",
319             ));
320         }
321         return Ok(path.to_owned().into());
322     }
323
324     let path = to_u16s(path)?;
325     let lpfilename = path.as_ptr();
326     fill_utf16_buf(
327         // SAFETY: `fill_utf16_buf` ensures the `buffer` and `size` are valid.
328         // `lpfilename` is a pointer to a null terminated string that is not
329         // invalidated until after `GetFullPathNameW` returns successfully.
330         |buffer, size| unsafe { c::GetFullPathNameW(lpfilename, size, buffer, ptr::null_mut()) },
331         super::os2path,
332     )
333 }