]> git.lizzy.rs Git - rust.git/blob - library/std/src/sys/windows/path.rs
Auto merge of #82973 - ijackson:exitstatuserror, r=yaahc
[rust.git] / library / std / src / sys / windows / path.rs
1 use crate::ffi::OsStr;
2 use crate::mem;
3 use crate::path::Prefix;
4
5 #[cfg(test)]
6 mod tests;
7
8 pub const MAIN_SEP_STR: &str = "\\";
9 pub const MAIN_SEP: char = '\\';
10
11 /// # Safety
12 ///
13 /// `bytes` must be a valid wtf8 encoded slice
14 #[inline]
15 unsafe fn bytes_as_os_str(bytes: &[u8]) -> &OsStr {
16     // &OsStr is layout compatible with &Slice, which is compatible with &Wtf8,
17     // which is compatible with &[u8].
18     mem::transmute(bytes)
19 }
20
21 #[inline]
22 pub fn is_sep_byte(b: u8) -> bool {
23     b == b'/' || b == b'\\'
24 }
25
26 #[inline]
27 pub fn is_verbatim_sep(b: u8) -> bool {
28     b == b'\\'
29 }
30
31 pub fn parse_prefix(path: &OsStr) -> Option<Prefix<'_>> {
32     use Prefix::{DeviceNS, Disk, Verbatim, VerbatimDisk, VerbatimUNC, UNC};
33
34     if let Some(path) = strip_prefix(path, r"\\") {
35         // \\
36         if let Some(path) = strip_prefix(path, r"?\") {
37             // \\?\
38             if let Some(path) = strip_prefix(path, r"UNC\") {
39                 // \\?\UNC\server\share
40
41                 let (server, path) = parse_next_component(path, true);
42                 let (share, _) = parse_next_component(path, true);
43
44                 Some(VerbatimUNC(server, share))
45             } else {
46                 let (prefix, _) = parse_next_component(path, true);
47
48                 // in verbatim paths only recognize an exact drive prefix
49                 if let Some(drive) = parse_drive_exact(prefix) {
50                     // \\?\C:
51                     Some(VerbatimDisk(drive))
52                 } else {
53                     // \\?\prefix
54                     Some(Verbatim(prefix))
55                 }
56             }
57         } else if let Some(path) = strip_prefix(path, r".\") {
58             // \\.\COM42
59             let (prefix, _) = parse_next_component(path, false);
60             Some(DeviceNS(prefix))
61         } else {
62             let (server, path) = parse_next_component(path, false);
63             let (share, _) = parse_next_component(path, false);
64
65             if !server.is_empty() && !share.is_empty() {
66                 // \\server\share
67                 Some(UNC(server, share))
68             } else {
69                 // no valid prefix beginning with "\\" recognized
70                 None
71             }
72         }
73     } else if let Some(drive) = parse_drive(path) {
74         // C:
75         Some(Disk(drive))
76     } else {
77         // no prefix
78         None
79     }
80 }
81
82 // Parses a drive prefix, e.g. "C:" and "C:\whatever"
83 fn parse_drive(prefix: &OsStr) -> Option<u8> {
84     // In most DOS systems, it is not possible to have more than 26 drive letters.
85     // See <https://en.wikipedia.org/wiki/Drive_letter_assignment#Common_assignments>.
86     fn is_valid_drive_letter(drive: &u8) -> bool {
87         drive.is_ascii_alphabetic()
88     }
89
90     match prefix.bytes() {
91         [drive, b':', ..] if is_valid_drive_letter(drive) => Some(drive.to_ascii_uppercase()),
92         _ => None,
93     }
94 }
95
96 // Parses a drive prefix exactly, e.g. "C:"
97 fn parse_drive_exact(prefix: &OsStr) -> Option<u8> {
98     // only parse two bytes: the drive letter and the drive separator
99     if prefix.len() == 2 { parse_drive(prefix) } else { None }
100 }
101
102 fn strip_prefix<'a>(path: &'a OsStr, prefix: &str) -> Option<&'a OsStr> {
103     // `path` and `prefix` are valid wtf8 and utf8 encoded slices respectively, `path[prefix.len()]`
104     // is thus a code point boundary and `path[prefix.len()..]` is a valid wtf8 encoded slice.
105     match path.bytes().strip_prefix(prefix.as_bytes()) {
106         Some(path) => unsafe { Some(bytes_as_os_str(path)) },
107         None => None,
108     }
109 }
110
111 // Parse the next path component.
112 //
113 // Returns the next component and the rest of the path excluding the component and separator.
114 // Does not recognize `/` as a separator character if `verbatim` is true.
115 fn parse_next_component(path: &OsStr, verbatim: bool) -> (&OsStr, &OsStr) {
116     let separator = if verbatim { is_verbatim_sep } else { is_sep_byte };
117
118     match path.bytes().iter().position(|&x| separator(x)) {
119         Some(separator_start) => {
120             let mut separator_end = separator_start + 1;
121
122             // a series of multiple separator characters is treated as a single separator,
123             // except in verbatim paths
124             while !verbatim && separator_end < path.len() && separator(path.bytes()[separator_end])
125             {
126                 separator_end += 1;
127             }
128
129             let component = &path.bytes()[..separator_start];
130
131             // Panic safe
132             // The max `separator_end` is `bytes.len()` and `bytes[bytes.len()..]` is a valid index.
133             let path = &path.bytes()[separator_end..];
134
135             // SAFETY: `path` is a valid wtf8 encoded slice and each of the separators ('/', '\')
136             // is encoded in a single byte, therefore `bytes[separator_start]` and
137             // `bytes[separator_end]` must be code point boundaries and thus
138             // `bytes[..separator_start]` and `bytes[separator_end..]` are valid wtf8 slices.
139             unsafe { (bytes_as_os_str(component), bytes_as_os_str(path)) }
140         }
141         None => (path, OsStr::new("")),
142     }
143 }