forked from extern/nushell
fix: 3 or more dots in file paths (#8544)
# Description In cases that a path contained 3 or more `.` in succession followed by a path-separator, the dots would be expanded. This logic didn't take into account the cases where characters other than a separator could appear before the `...`, which would lead to `...` in filenames being expanded. This PR changes the behavior so that path-segments consisting of 3 or more dots only will be expanded. This PR fixes issue #8386 # User-Facing Changes Paths with filenames like `.../folder.../file.txt` will be correctly expanded. # Tests + Formatting I added tests that cover a number of cases.
This commit is contained in:
parent
1d5e7b441b
commit
fa957a1a07
@ -55,18 +55,38 @@ pub fn expand_ndots(path: impl AsRef<Path>) -> PathBuf {
|
|||||||
return path.as_ref().into();
|
return path.as_ref().into();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum Segment {
|
||||||
|
Empty,
|
||||||
|
OnlyDots,
|
||||||
|
OtherChars,
|
||||||
|
}
|
||||||
let mut dots_count = 0u8;
|
let mut dots_count = 0u8;
|
||||||
let mut expanded = String::new();
|
let mut path_segment = Segment::Empty;
|
||||||
|
let mut expanded = String::with_capacity(path_str.len() + 10);
|
||||||
for chr in path_str.chars() {
|
for chr in path_str.chars() {
|
||||||
if chr == '.' {
|
if chr == '.' {
|
||||||
|
if matches!(path_segment, Segment::Empty) {
|
||||||
|
path_segment = Segment::OnlyDots;
|
||||||
|
}
|
||||||
dots_count += 1;
|
dots_count += 1;
|
||||||
} else {
|
} else {
|
||||||
if is_separator(chr) {
|
if is_separator(chr) {
|
||||||
// check for dots expansion only at path component boundaries
|
if matches!(path_segment, Segment::OnlyDots) {
|
||||||
handle_dots_push(&mut expanded, dots_count);
|
// check for dots expansion only at path component boundaries
|
||||||
dots_count = 0;
|
handle_dots_push(&mut expanded, dots_count);
|
||||||
|
dots_count = 0;
|
||||||
|
} else {
|
||||||
|
// if at a path component boundary a secment consists of not only dots
|
||||||
|
// don't expand the dots and only append the the appropriate number of .
|
||||||
|
while dots_count > 0 {
|
||||||
|
expanded.push('.');
|
||||||
|
dots_count -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
path_segment = Segment::Empty;
|
||||||
} else {
|
} else {
|
||||||
// got non-dot within path component => do not expand any dots
|
// got non-dot within path component => do not expand any dots
|
||||||
|
path_segment = Segment::OtherChars;
|
||||||
while dots_count > 0 {
|
while dots_count > 0 {
|
||||||
expanded.push('.');
|
expanded.push('.');
|
||||||
dots_count -= 1;
|
dots_count -= 1;
|
||||||
@ -76,7 +96,14 @@ pub fn expand_ndots(path: impl AsRef<Path>) -> PathBuf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handle_dots_push(&mut expanded, dots_count);
|
// Here only the final dots without any following characters are handled
|
||||||
|
if matches!(path_segment, Segment::OnlyDots) {
|
||||||
|
handle_dots_push(&mut expanded, dots_count);
|
||||||
|
} else {
|
||||||
|
for _ in 0..dots_count {
|
||||||
|
expanded.push('.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
expanded.into()
|
expanded.into()
|
||||||
}
|
}
|
||||||
@ -135,6 +162,9 @@ mod tests {
|
|||||||
assert_eq!(PathBuf::from("/foo/bar/baz"), expand_dots(path));
|
assert_eq!(PathBuf::from("/foo/bar/baz"), expand_dots(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// track_caller refers, in the panic-message, to the line of the function call and not
|
||||||
|
// inside of the function, which is nice for a test-helper-function
|
||||||
|
#[track_caller]
|
||||||
fn check_ndots_expansion(expected: &str, s: &str) {
|
fn check_ndots_expansion(expected: &str, s: &str) {
|
||||||
let expanded = expand_ndots(Path::new(s));
|
let expanded = expand_ndots(Path::new(s));
|
||||||
assert_eq!(Path::new(expected), &expanded);
|
assert_eq!(Path::new(expected), &expanded);
|
||||||
@ -161,6 +191,42 @@ mod tests {
|
|||||||
check_ndots_expansion("a.b", "a.b");
|
check_ndots_expansion("a.b", "a.b");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn string_starts_with_dots() {
|
||||||
|
check_ndots_expansion(".file", ".file");
|
||||||
|
check_ndots_expansion("..file", "..file");
|
||||||
|
check_ndots_expansion("...file", "...file");
|
||||||
|
check_ndots_expansion("....file", "....file");
|
||||||
|
check_ndots_expansion(".....file", ".....file");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn string_ends_with_dots() {
|
||||||
|
check_ndots_expansion("file.", "file.");
|
||||||
|
check_ndots_expansion("file..", "file..");
|
||||||
|
check_ndots_expansion("file...", "file...");
|
||||||
|
check_ndots_expansion("file....", "file....");
|
||||||
|
check_ndots_expansion("file.....", "file.....");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn string_starts_and_ends_with_dots() {
|
||||||
|
check_ndots_expansion(".file.", ".file.");
|
||||||
|
check_ndots_expansion("..file..", "..file..");
|
||||||
|
check_ndots_expansion("...file...", "...file...");
|
||||||
|
check_ndots_expansion("....file....", "....file....");
|
||||||
|
check_ndots_expansion(".....file.....", ".....file.....");
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn expand_multiple_dots() {
|
||||||
|
check_ndots_expansion("../..", "...");
|
||||||
|
check_ndots_expansion("../../..", "....");
|
||||||
|
check_ndots_expansion("../../../..", ".....");
|
||||||
|
check_ndots_expansion("../../../../", ".../...");
|
||||||
|
check_ndots_expansion("../../file name/../../", ".../file name/...");
|
||||||
|
check_ndots_expansion("../../../file name/../../../", "..../file name/....");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn expand_dots_double_dots_no_change() {
|
fn expand_dots_double_dots_no_change() {
|
||||||
// Can't resolve this as we don't know our parent dir
|
// Can't resolve this as we don't know our parent dir
|
||||||
@ -229,7 +295,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn string_with_three_ndots_and_garbage() {
|
fn string_with_three_ndots_and_garbage() {
|
||||||
check_ndots_expansion(r"ls ..\../ garbage.*[", "ls .../ garbage.*[");
|
check_ndots_expansion(r"file .../ garbage.*[", "file .../ garbage.*[");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,7 +322,14 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn string_with_three_ndots_and_garbage() {
|
fn string_with_three_ndots_and_garbage() {
|
||||||
check_ndots_expansion("ls ../../ garbage.*[", "ls .../ garbage.*[");
|
// filenames can contain spaces, in these cases the ... .... etc.
|
||||||
|
// that are part of a filepath should not be expanded
|
||||||
|
check_ndots_expansion("not_a_cmd .../ garbage.*[", "not_a_cmd .../ garbage.*[");
|
||||||
|
check_ndots_expansion("/not_a_cmd .../ garbage.*[", "/not_a_cmd .../ garbage.*[");
|
||||||
|
check_ndots_expansion("./not_a_cmd .../ garbage.*[", "./not_a_cmd .../ garbage.*[");
|
||||||
|
check_ndots_expansion("../../nac .../ garbage.*[", ".../nac .../ garbage.*[");
|
||||||
|
check_ndots_expansion("../../nac .../ garbage.*[...", ".../nac .../ garbage.*[...");
|
||||||
|
check_ndots_expansion("../../ garbage.*[", ".../ garbage.*[");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user