Skip to content
Merged
15 changes: 15 additions & 0 deletions src/System.Management.Automation/namespaces/FileSystemProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1080,6 +1080,21 @@ protected override bool IsValidPath(string path)
}
}

// .NET introduced a change where invalid characters are accepted https://learn.microsoft.com/en-us/dotnet/core/compatibility/2.1#path-apis-dont-throw-an-exception-for-invalid-characters
// We need to check for invalid characters ourselves. `Path.GetInvalidFileNameChars()` is a supserset of `Path.GetInvalidPathChars()`

// Remove drive root first
string pathWithoutDriveRoot = path.Substring(Path.GetPathRoot(path).Length);
char[] invalidFileChars = Path.GetInvalidFileNameChars();

foreach (string segment in pathWithoutDriveRoot.Split(Path.DirectorySeparatorChar))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i suppose it may be a toss up between this and:
pathWithoutDriveRoot.IndexOfAny(Path.GetInvalidFileNameChars().Except(Path.DirectorySeparatorChar)) != 1)

i wondered if it might be faster to not split (but then, of course, there's the linq)

Copy link
Member Author

@SteveL-MSFT SteveL-MSFT Apr 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in general we want to use less Linq and not more. If there's a perf concern, it might be faster to just replace all the separators with a valid character and analyze the whole string at once, but at the expense of readability and only minor perf improvement I think

{
if (segment.IndexOfAny(invalidFileChars) != -1)
{
return false;
}
}

return true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,48 @@ Describe "Test-Path" -Tags "CI" {
Test-Path -Path $badPath -IsValid | Should -BeFalse
}

It 'Windows paths should be valid: <path>' -skip:(!$IsWindows) -TestCases @(
@{ path = "C:\Program Files" }
@{ path = "C:\Program Files (x86)\" }
@{ path = "filesystem::z:\foo" }
@{ path = "filesystem::z:\foo\" }
@{ path = "variable::psversiontable" }
@{ path = "c:\windows\cmd.exe:test" }
) {
param($path)
Test-Path -Path $path -IsValid | Should -BeTrue
}

It 'Windows paths should be inavlid: <variant>' -Skip:(!$IsWindows) -TestCases @(
@{ variant = "wildcard"; path = "C:\Program Files\foo*" }
@{ variant = "pipe symbol"; path = "C:\Program Files|p\foo" }
@{ variant = "null char"; path = "C:\Win`u{0000}dows\System32" }
) {
param($path)
Test-Path -Path $path -IsValid | Should -BeFalse
}

It 'Unix paths should be valid: <path>' -Skip:($IsWindows) -TestCases @(
@{ path = "/usr/bin" }
@{ path = "/usr/bin/" }
@{ path = "filesystem::/usr/bin" }
@{ path = "filesystem::/usr/bin/" }
@{ path = "variable::psversiontable" }
@{ path = "/usr/bi*n/test" }
@{ path = "/usr/bin/tes*t" }
) {
param($path)
Test-Path -Path $path -IsValid | Should -BeTrue
}

It 'Unix paths should be invalid: <variant>' -Skip:($IsWindows) -TestCases @(
@{ variant = "null in path"; path = "/usr/bi`u{0000}n/test" }
@{ variant = "null in filename"; path = "/usr/bin/t`u{0000}est" }
) {
param($path)
Test-Path -Path $path -IsValid | Should -BeFalse
}

It "Should return true on paths containing spaces when the path is surrounded in quotes" {
Test-Path -Path "/totally a valid/path" -IsValid | Should -BeTrue
}
Expand Down