@@ -50,37 +50,101 @@ pub(crate) fn append_suffix(path: PathBuf, suffix: &OsStr) -> PathBuf {
50
50
path. into ( )
51
51
}
52
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
+
53
108
pub fn parse_prefix ( path : & OsStr ) -> Option < Prefix < ' _ > > {
54
109
use Prefix :: { DeviceNS , Disk , Verbatim , VerbatimDisk , VerbatimUNC , UNC } ;
55
110
56
- if let Some ( path) = strip_prefix ( path, r"\\" ) {
111
+ let parser = PrefixParser :: < 8 > :: new ( path) ;
112
+ let parser = parser. as_slice ( ) ;
113
+ if let Some ( parser) = parser. strip_prefix ( r"\\" ) {
57
114
// \\
58
- if let Some ( path) = strip_prefix ( path, r"?\" ) {
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'/' ) {
59
119
// \\?\
60
- if let Some ( path ) = strip_prefix ( path , r"UNC\" ) {
120
+ if let Some ( parser ) = parser . strip_prefix ( r"UNC\" ) {
61
121
// \\?\UNC\server\share
62
122
123
+ let path = parser. finish ( ) ;
63
124
let ( server, path) = parse_next_component ( path, true ) ;
64
125
let ( share, _) = parse_next_component ( path, true ) ;
65
126
66
127
Some ( VerbatimUNC ( server, share) )
67
128
} else {
68
- let ( prefix , _ ) = parse_next_component ( path , true ) ;
129
+ let path = parser . finish ( ) ;
69
130
70
131
// in verbatim paths only recognize an exact drive prefix
71
- if let Some ( drive) = parse_drive_exact ( prefix ) {
132
+ if let Some ( drive) = parse_drive_exact ( path ) {
72
133
// \\?\C:
73
134
Some ( VerbatimDisk ( drive) )
74
135
} else {
75
136
// \\?\prefix
137
+ let ( prefix, _) = parse_next_component ( path, true ) ;
76
138
Some ( Verbatim ( prefix) )
77
139
}
78
140
}
79
- } else if let Some ( path ) = strip_prefix ( path , r".\" ) {
141
+ } else if let Some ( parser ) = parser . strip_prefix ( r".\" ) {
80
142
// \\.\COM42
143
+ let path = parser. finish ( ) ;
81
144
let ( prefix, _) = parse_next_component ( path, false ) ;
82
145
Some ( DeviceNS ( prefix) )
83
146
} else {
147
+ let path = parser. finish ( ) ;
84
148
let ( server, path) = parse_next_component ( path, false ) ;
85
149
let ( share, _) = parse_next_component ( path, false ) ;
86
150
@@ -102,31 +166,26 @@ pub fn parse_prefix(path: &OsStr) -> Option<Prefix<'_>> {
102
166
}
103
167
104
168
// Parses a drive prefix, e.g. "C:" and "C:\whatever"
105
- fn parse_drive ( prefix : & OsStr ) -> Option < u8 > {
169
+ fn parse_drive ( path : & OsStr ) -> Option < u8 > {
106
170
// In most DOS systems, it is not possible to have more than 26 drive letters.
107
171
// See <https://en.wikipedia.org/wiki/Drive_letter_assignment#Common_assignments>.
108
172
fn is_valid_drive_letter ( drive : & u8 ) -> bool {
109
173
drive. is_ascii_alphabetic ( )
110
174
}
111
175
112
- match prefix . bytes ( ) {
176
+ match path . bytes ( ) {
113
177
[ drive, b':' , ..] if is_valid_drive_letter ( drive) => Some ( drive. to_ascii_uppercase ( ) ) ,
114
178
_ => None ,
115
179
}
116
180
}
117
181
118
182
// Parses a drive prefix exactly, e.g. "C:"
119
- fn parse_drive_exact ( prefix : & OsStr ) -> Option < u8 > {
183
+ fn parse_drive_exact ( path : & OsStr ) -> Option < u8 > {
120
184
// only parse two bytes: the drive letter and the drive separator
121
- if prefix. len ( ) == 2 { parse_drive ( prefix) } else { None }
122
- }
123
-
124
- fn strip_prefix < ' a > ( path : & ' a OsStr , prefix : & str ) -> Option < & ' a OsStr > {
125
- // `path` and `prefix` are valid wtf8 and utf8 encoded slices respectively, `path[prefix.len()]`
126
- // is thus a code point boundary and `path[prefix.len()..]` is a valid wtf8 encoded slice.
127
- match path. bytes ( ) . strip_prefix ( prefix. as_bytes ( ) ) {
128
- Some ( path) => unsafe { Some ( bytes_as_os_str ( path) ) } ,
129
- None => None ,
185
+ if path. bytes ( ) . get ( 2 ) . map ( |& x| is_sep_byte ( x) ) . unwrap_or ( true ) {
186
+ parse_drive ( path)
187
+ } else {
188
+ None
130
189
}
131
190
}
132
191
@@ -219,15 +278,7 @@ pub(crate) fn maybe_verbatim(path: &Path) -> io::Result<Vec<u16>> {
219
278
// SAFETY: `fill_utf16_buf` ensures the `buffer` and `size` are valid.
220
279
// `lpfilename` is a pointer to a null terminated string that is not
221
280
// invalidated until after `GetFullPathNameW` returns successfully.
222
- |buffer, size| unsafe {
223
- // While the docs for `GetFullPathNameW` have the standard note
224
- // about needing a `\\?\` path for a long lpfilename, this does not
225
- // appear to be true in practice.
226
- // See:
227
- // https://stackoverflow.com/questions/38036943/getfullpathnamew-and-long-windows-file-paths
228
- // https://googleprojectzero.blogspot.com/2016/02/the-definitive-guide-on-win32-to-nt.html
229
- c:: GetFullPathNameW ( lpfilename, size, buffer, ptr:: null_mut ( ) )
230
- } ,
281
+ |buffer, size| unsafe { c:: GetFullPathNameW ( lpfilename, size, buffer, ptr:: null_mut ( ) ) } ,
231
282
|mut absolute| {
232
283
path. clear ( ) ;
233
284
@@ -263,9 +314,20 @@ pub(crate) fn maybe_verbatim(path: &Path) -> io::Result<Vec<u16>> {
263
314
264
315
/// Make a Windows path absolute.
265
316
pub ( crate ) fn absolute ( path : & Path ) -> io:: Result < PathBuf > {
266
- if path. as_os_str ( ) . bytes ( ) . starts_with ( br"\\?\" ) {
267
- return Ok ( path. into ( ) ) ;
317
+ let path = path. as_os_str ( ) ;
318
+ let prefix = parse_prefix ( path) ;
319
+ // Verbatim paths should not be modified.
320
+ if prefix. map ( |x| x. is_verbatim ( ) ) . unwrap_or ( false ) {
321
+ // NULs in verbatim paths are rejected for consistency.
322
+ if path. bytes ( ) . contains ( & 0 ) {
323
+ return Err ( io:: const_io_error!(
324
+ io:: ErrorKind :: InvalidInput ,
325
+ "strings passed to WinAPI cannot contain NULs" ,
326
+ ) ) ;
327
+ }
328
+ return Ok ( path. to_owned ( ) . into ( ) ) ;
268
329
}
330
+
269
331
let path = to_u16s ( path) ?;
270
332
let lpfilename = path. as_ptr ( ) ;
271
333
fill_utf16_buf (
0 commit comments