Skip to content

Commit

Permalink
Rollup merge of #89270 - seanyoung:join_fold, r=m-ou-se
Browse files Browse the repository at this point in the history
path.push() should work as expected on windows verbatim paths

On Windows, std::fs::canonicalize() returns an so-called UNC path.  UNC paths differ with regular paths because:

- This type of path can much longer than a non-UNC path (32k vs 260 characters).
- The prefix for a UNC path is ``Component::Prefix(Prefix::DiskVerbatim(..)))``
- No `/` is allowed
- No `.` is allowed
- No `..` is allowed

Rust has poor handling of such paths. If you join a UNC path with a path with any of the above, then this will not work.

I've implemented a new method `fn join_fold()` which joins paths and also removes any `.` and `..` from it, and replaces `/` with `\` on Windows. Using this function it is possible to use UNC paths without issue. In addition, this function is useful on Linux too; paths can be appended without having to call `canonicalize()` to remove the `.` and `..`.

This PR needs test cases, which can I add. I hope this will a start of a discussion.
  • Loading branch information
workingjubilee authored Oct 5, 2021
2 parents 234fa90 + fa4072f commit 7aa9ce5
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 7 deletions.
53 changes: 46 additions & 7 deletions library/std/src/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1231,20 +1231,59 @@ impl PathBuf {
let mut need_sep = self.as_mut_vec().last().map(|c| !is_sep_byte(*c)).unwrap_or(false);

// in the special case of `C:` on Windows, do *not* add a separator
let comps = self.components();

if comps.prefix_len() > 0
&& comps.prefix_len() == comps.path.len()
&& comps.prefix.unwrap().is_drive()
{
let comps = self.components();
if comps.prefix_len() > 0
&& comps.prefix_len() == comps.path.len()
&& comps.prefix.unwrap().is_drive()
{
need_sep = false
}
need_sep = false
}

// absolute `path` replaces `self`
if path.is_absolute() || path.prefix().is_some() {
self.as_mut_vec().truncate(0);

// verbatim paths need . and .. removed
} else if comps.prefix_verbatim() {
let mut buf: Vec<_> = comps.collect();
for c in path.components() {
match c {
Component::RootDir => {
buf.truncate(1);
buf.push(c);
}
Component::CurDir => (),
Component::ParentDir => {
if let Some(Component::Normal(_)) = buf.last() {
buf.pop();
}
}
_ => buf.push(c),
}
}

let mut res = OsString::new();
let mut need_sep = false;

for c in buf {
if need_sep && c != Component::RootDir {
res.push(MAIN_SEP_STR);
}
res.push(c.as_os_str());

need_sep = match c {
Component::RootDir => false,
Component::Prefix(prefix) => {
!prefix.parsed.is_drive() && prefix.parsed.len() > 0
}
_ => true,
}
}

self.inner = res;
return;

// `path` has a root but no prefix, e.g., `\windows` (Windows only)
} else if path.has_root() {
let prefix_len = self.components().prefix_remaining();
Expand Down
9 changes: 9 additions & 0 deletions library/std/src/path/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1262,6 +1262,15 @@ pub fn test_push() {
tp!("\\\\.\\foo", "..\\bar", "\\\\.\\foo\\..\\bar");

tp!("\\\\?\\C:", "foo", "\\\\?\\C:\\foo"); // this is a weird one

tp!(r"\\?\C:\bar", "../foo", r"\\?\C:\foo");
tp!(r"\\?\C:\bar", "../../foo", r"\\?\C:\foo");
tp!(r"\\?\C:\", "../foo", r"\\?\C:\foo");
tp!(r"\\?\C:", r"D:\foo/./", r"D:\foo/./");
tp!(r"\\?\C:", r"\\?\D:\foo\.\", r"\\?\D:\foo\.\");
tp!(r"\\?\A:\x\y", "/foo", r"\\?\A:\foo");
tp!(r"\\?\A:", r"..\foo\.", r"\\?\A:\foo");
tp!(r"\\?\A:\x\y", r".\foo\.", r"\\?\A:\x\y\foo");
}
}

Expand Down

0 comments on commit 7aa9ce5

Please sign in to comment.