verbatim path prefix not normalized correctly
On Windows, calling path.to_slash() on a verbatim path like:
\\?\C:\Users\andrew\foo
produces:
\\?\C:/Users/andrew/foo
when it should produce:
\\?/C:/Users/andrew/foo
I tried following code on Windows
use std::path::Path;
fn main() {
let p = Path::new(r#"\\?\C:\Users\andrew\foo"#);
for c in p.components() {
println!("{:?}", c);
}
}
The output was
Prefix(PrefixComponent { raw: "\\\\?\\C:", parsed: VerbatimDisk(67) })
RootDir
Normal("Users")
Normal("andrew")
Normal("foo")
Since slash path is a path where / is used for path separator, only path separator of \ should be replaced with /. Is the third \ in \\?\C: a path separator? I'm not familiar with verbatim path prefix since I'm mainly not a Windows user, so I'll appreciate if you kindly share the documentation which indicates your suggestion is correct.
I'm mainly not a Windows user
Me neither =) I just do a lot of work with Windows paths for work. Unix is so much simpler.
I'm not an expert at all, but here is an example program to test if Windows can actually access certain file path formats:
use std::path::Path;
use path_slash::PathExt;
fn main() {
// normal path works
let p0 = Path::new(r"C:\Users\andrew\Documents\foo\test.txt");
// verbatim path works
let p1 = Path::new(r"\\?\C:\Users\andrew\Documents\foo\test.txt");
// verbatim unix does not work
let p2 = Path::new(r"\\?\C:\Users\andrew\Documents\foo\test.txt").to_slash_lossy();
let p2 = Path::new(&p2);
// changing \\?\ to \\?/ works
let p3 = Path::new(r"\\?/C:\Users\andrew\Documents\foo\test.txt").to_slash_lossy();
let p3 = Path::new(&p3);
let p4 = Path::new(r"\\?/UNC\DESKTOP-DRCNKJR\asdf\test.txt").to_slash_lossy();
let p4 = Path::new(&p4);
let paths = vec![p0, p1, p2, p3, p4];
for (i, path) in paths.iter().enumerate() {
match path.metadata() {
Ok(_) => println!("p{} works! ({:?})", i, path),
Err(e) => eprintln!("p{} doesn't work. {} ({:?})",i, e, path)
};
for c in path.components() {
dbg!(c);
}
}
}
Output:
p0 works! ("C:\\Users\\andrew\\Documents\\foo\\test.txt")
[src\main.rs:32] c = Prefix(
PrefixComponent {
raw: "C:",
parsed: Disk(
67,
),
},
)
[src\main.rs:32] c = RootDir
[src\main.rs:32] c = Normal(
"Users",
)
[src\main.rs:32] c = Normal(
"andrew",
)
[src\main.rs:32] c = Normal(
"Documents",
)
[src\main.rs:32] c = Normal(
"foo",
)
[src\main.rs:32] c = Normal(
"test.txt",
)
p1 works! ("\\\\?\\C:\\Users\\andrew\\Documents\\foo\\test.txt")
[src\main.rs:32] c = Prefix(
PrefixComponent {
raw: "\\\\?\\C:",
parsed: VerbatimDisk(
67,
),
},
)
[src\main.rs:32] c = RootDir
[src\main.rs:32] c = Normal(
"Users",
)
[src\main.rs:32] c = Normal(
"andrew",
)
[src\main.rs:32] c = Normal(
"Documents",
)
[src\main.rs:32] c = Normal(
"foo",
)
[src\main.rs:32] c = Normal(
"test.txt",
)
p2 doesn't work. The system cannot find the file specified. (os error 2) ("\\\\?\\C:/Users/andrew/Documents/foo/test.txt")
[src\main.rs:32] c = Prefix(
PrefixComponent {
raw: "\\\\?\\C:/Users/andrew/Documents/foo/test.txt",
parsed: Verbatim(
"C:/Users/andrew/Documents/foo/test.txt",
),
},
)
p3 works! ("\\\\?/C:/Users/andrew/Documents/foo/test.txt")
[src\main.rs:32] c = Prefix(
PrefixComponent {
raw: "\\\\?/C:",
parsed: UNC(
"?",
"C:",
),
},
)
[src\main.rs:32] c = RootDir
[src\main.rs:32] c = Normal(
"Users",
)
[src\main.rs:32] c = Normal(
"andrew",
)
[src\main.rs:32] c = Normal(
"Documents",
)
[src\main.rs:32] c = Normal(
"foo",
)
[src\main.rs:32] c = Normal(
"test.txt",
)
p4 works! ("\\\\?/UNC/DESKTOP-DRCNKJR/asdf/test.txt")
[src\main.rs:32] c = Prefix(
PrefixComponent {
raw: "\\\\?/UNC",
parsed: UNC(
"?",
"UNC",
),
},
)
[src\main.rs:32] c = RootDir
[src\main.rs:32] c = Normal(
"DESKTOP-DRCNKJR",
)
[src\main.rs:32] c = Normal(
"asdf",
)
[src\main.rs:32] c = Normal(
"test.txt",
)
The Windows documentation on verbatim / extended-length path prefixes says:
These prefixes are not used as part of the path itself. They indicate that the path should be passed to the system with minimal modification, which means that you cannot use forward slashes to represent path separators, or a period to represent the current directory, or double dots to represent the parent directory.
When it says you cannot use forward slashes to represent path separators, maybe it means specifically with \\?\ but you can use them with \\?/? I don't know. I haven't read about the latter anywhere, but I just discovered that does allow you to use forward slashes with verbatim paths. That being said, \\?/ prefixes with unix-style paths will not work for extended length paths, while \\?\ will.
So my thought is that since the official docs say unix-style paths are not valid with \\?\ anyway, and since that combination doesn't work with Windows, maybe path_slash should use \\?/ instead so that normalized verbatim paths work properly (just not for long paths).
Actually, thinking this through some more, Rust thought \\?/UNC/ was a UNC, not VerbatimUNC (which I understand since it uses forward slashes. Since it doesn't work for accessing extended-length paths, then I guess Windows doesn't consider it one either.
I guess the main thing I'm wondering for path_slash is since \\?\C:/Users/andrew/foo doesn't actually work for accessing the file, do you think it should, instead, produce some error type / not normalize verbatim paths at all since MS says you can't use forward slashes for them?
Just stumble at this when looking for .to_slash functionality.
The UNC is discuss in a rust github issue https://github.com/rust-lang/rust/issues/42869
They are working on making a 'friendly' fs::absolute function to avoid the UNC stuff since not even many windows app support it an prefer the old win32 (msdos¿) way of representing paths.
The workaround I use is simply use dunce. You could, for instance, use the funcs from that crate and then apply .to_slash().