@@ -50,37 +50,101 @@ pub(crate) fn append_suffix(path: PathBuf, suffix: &OsStr) -> PathBuf {
5050 path. into ( )
5151}
5252
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+
53108pub fn parse_prefix ( path : & OsStr ) -> Option < Prefix < ' _ > > {
54109 use Prefix :: { DeviceNS , Disk , Verbatim , VerbatimDisk , VerbatimUNC , UNC } ;
55110
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"\\" ) {
57114 // \\
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'/' ) {
59119 // \\?\
60- if let Some ( path ) = strip_prefix ( path , r"UNC\" ) {
120+ if let Some ( parser ) = parser . strip_prefix ( r"UNC\" ) {
61121 // \\?\UNC\server\share
62122
123+ let path = parser. finish ( ) ;
63124 let ( server, path) = parse_next_component ( path, true ) ;
64125 let ( share, _) = parse_next_component ( path, true ) ;
65126
66127 Some ( VerbatimUNC ( server, share) )
67128 } else {
68- let ( prefix , _ ) = parse_next_component ( path , true ) ;
129+ let path = parser . finish ( ) ;
69130
70131 // 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 ) {
72133 // \\?\C:
73134 Some ( VerbatimDisk ( drive) )
74135 } else {
75136 // \\?\prefix
137+ let ( prefix, _) = parse_next_component ( path, true ) ;
76138 Some ( Verbatim ( prefix) )
77139 }
78140 }
79- } else if let Some ( path ) = strip_prefix ( path , r".\" ) {
141+ } else if let Some ( parser ) = parser . strip_prefix ( r".\" ) {
80142 // \\.\COM42
143+ let path = parser. finish ( ) ;
81144 let ( prefix, _) = parse_next_component ( path, false ) ;
82145 Some ( DeviceNS ( prefix) )
83146 } else {
147+ let path = parser. finish ( ) ;
84148 let ( server, path) = parse_next_component ( path, false ) ;
85149 let ( share, _) = parse_next_component ( path, false ) ;
86150
@@ -102,31 +166,26 @@ pub fn parse_prefix(path: &OsStr) -> Option<Prefix<'_>> {
102166}
103167
104168// 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 > {
106170 // In most DOS systems, it is not possible to have more than 26 drive letters.
107171 // See <https://en.wikipedia.org/wiki/Drive_letter_assignment#Common_assignments>.
108172 fn is_valid_drive_letter ( drive : & u8 ) -> bool {
109173 drive. is_ascii_alphabetic ( )
110174 }
111175
112- match prefix . bytes ( ) {
176+ match path . bytes ( ) {
113177 [ drive, b':' , ..] if is_valid_drive_letter ( drive) => Some ( drive. to_ascii_uppercase ( ) ) ,
114178 _ => None ,
115179 }
116180}
117181
118182// 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 > {
120184 // 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
130189 }
131190}
132191
@@ -219,15 +278,7 @@ pub(crate) fn maybe_verbatim(path: &Path) -> io::Result<Vec<u16>> {
219278 // SAFETY: `fill_utf16_buf` ensures the `buffer` and `size` are valid.
220279 // `lpfilename` is a pointer to a null terminated string that is not
221280 // 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 ( ) ) } ,
231282 |mut absolute| {
232283 path. clear ( ) ;
233284
@@ -263,9 +314,20 @@ pub(crate) fn maybe_verbatim(path: &Path) -> io::Result<Vec<u16>> {
263314
264315/// Make a Windows path absolute.
265316pub ( 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 ( ) ) ;
268329 }
330+
269331 let path = to_u16s ( path) ?;
270332 let lpfilename = path. as_ptr ( ) ;
271333 fill_utf16_buf (
0 commit comments