diff --git a/CHANGELOG.md b/CHANGELOG.md index fd7bdf9..2fbe06e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -312,5 +312,9 @@ The format is based on - Fix `Get-NTFSOrphanedAccess`, which reported an item that it couldn't read as an `AddAceError`; it now writes a `ReadSecurityError`, like `Get-NTFSAccess` +- Fix every cmdlet for a relative path that starts with a dot but not with + `.\`, such as `.gitignore`: the cmdlets dropped its first two characters + and read, changed, or removed the item with the shorter name, such as + `itignore`, when one existed [Unreleased]: https://github.com/raandree/NTFSSecurity/compare/4.2.6...HEAD diff --git a/Docs/FAQ.md b/Docs/FAQ.md index 498f7d4..8fd93fb 100644 --- a/Docs/FAQ.md +++ b/Docs/FAQ.md @@ -71,5 +71,7 @@ No. The cmdlets read and write the file system directly through the AlphaFS library, not through the PowerShell providers, so they don't know drives that `New-PSDrive` created, or drives of other providers such as `HKLM:`. Use the file system path instead, such as `C:\Data` or `\\server\share`. A -relative path is resolved against the current file system location. See +relative path is resolved against the current file system location, also a +name that starts with a dot, such as `.gitignore`; before 5.0.0-rc6, the +cmdlets dropped the first two characters of such a name. See [Long paths](Concepts.md#long-paths). diff --git a/NTFSSecurity/BaseCmdlets.cs b/NTFSSecurity/BaseCmdlets.cs index 2fb8503..9dd30f1 100644 --- a/NTFSSecurity/BaseCmdlets.cs +++ b/NTFSSecurity/BaseCmdlets.cs @@ -112,14 +112,14 @@ namespace NTFSSecurity { path = GetCurrentLocation(); } - else if (path.StartsWith("..")) + else if (path == ".." || path.StartsWith("..\\")) { var currentLocation = GetCurrentLocation(); path = System.IO.Path.Combine( string.Join("\\", currentLocation.Split('\\').Take(currentLocation.Split('\\').Count() - path.Split('\\').Count(s => s == "..")).ToArray()), string.Join("\\", path.Split('\\').Where(e => e != "..").ToArray())); } - else if (path.StartsWith(".")) + else if (path.StartsWith(".\\") || path.StartsWith("./")) { //combine . and .\path\subpath path = System.IO.Path.Combine(GetCurrentLocation(), path.Substring(2)); diff --git a/Tests/Owner.Tests.ps1 b/Tests/Owner.Tests.ps1 index 8767460..00e4697 100644 --- a/Tests/Owner.Tests.ps1 +++ b/Tests/Owner.Tests.ps1 @@ -106,6 +106,64 @@ Describe 'Current location' { } } +# Before 5.0.0-rc6, a relative path that started with a dot, but not with .\, lost its first two characters: a command +# on .Dotfile read or changed the item otfile in the same folder when one existed. +Describe 'Relative paths that start with a dot' { + BeforeAll { + $dotFolder = New-TestSandboxItem -Sandbox $sandbox -Name 'Dot' -Directory + Push-Location -LiteralPath $dotFolder + # The names that the defect made of the paths + foreach ($name in '.Dotfile', '..Dotfile', 'otfile', 'Dotfile') { + Assert-TestSandboxPath -Sandbox $sandbox -Path $name + Set-Content -LiteralPath $name -Value $name + } + } + + AfterAll { + Pop-Location + } + + It 'Should resolve to the item of the current location' -ForEach @( + @{ Path = '.Dotfile'; Expected = '.Dotfile' } + @{ Path = '..Dotfile'; Expected = '..Dotfile' } + @{ Path = '.\.Dotfile'; Expected = '.Dotfile' } + @{ Path = './.Dotfile'; Expected = '.Dotfile' } + @{ Path = '..\{0}\.Dotfile'; Expected = '.Dotfile' } + ) { + # {0} is the name of the current folder. + $relative = $Path -f (Split-Path -Path $dotFolder -Leaf) + + $result = Get-NTFSOwner -Path $relative -ErrorAction Stop + + $result.FullName | Should -Be (Join-Path -Path $dotFolder -ChildPath $Expected) + } + + It 'Remove-Item2 should remove the item that the path names, not another item' { + foreach ($name in '.RemoveMe', 'emoveMe') { + Assert-TestSandboxPath -Sandbox $sandbox -Path $name + Set-Content -LiteralPath $name -Value $name + } + + Remove-Item2 -Path '.RemoveMe' -ErrorAction Stop + + Join-Path -Path $dotFolder -ChildPath '.RemoveMe' | Should -Not -Exist + Join-Path -Path $dotFolder -ChildPath 'emoveMe' | Should -Exist + } + + It 'Copy-Item2 should copy to the destination that the path names, not over another item' { + foreach ($name in 'CopySource', 'opyTarget') { + Assert-TestSandboxPath -Sandbox $sandbox -Path $name + Set-Content -LiteralPath $name -Value $name + } + Assert-TestSandboxPath -Sandbox $sandbox -Path '.CopyTarget' + + Copy-Item2 -Path 'CopySource' -Destination '.CopyTarget' -Force -ErrorAction Stop + + Get-Content -LiteralPath (Join-Path -Path $dotFolder -ChildPath '.CopyTarget') | Should -Be 'CopySource' + Get-Content -LiteralPath (Join-Path -Path $dotFolder -ChildPath 'opyTarget') | Should -Be 'opyTarget' + } +} + Describe 'File and folder objects as arguments' { # Before 5.0.0, Windows PowerShell bound a folder object that was passed by position as its name, which the # cmdlets resolved against the current location (#88).