Browse Source

fix(items): run the enumeration of Get-ChildItem2 with the invariant culture

The change of the previous commit was necessary but not enough. AlphaFS 2.2
also matches the filter: it builds a case-insensitive regular expression from
it that takes the culture of the thread when it is created. With tr-TR the
enumeration itself returned nothing for index* in a folder with Index.txt, in
both editions (Directory.EnumerateFileSystemEntries of AlphaFS returned an
empty list, .NET returned Index.txt), so the new test still failed on a build
that had the previous change alone.

The enumeration, that is the creation of the enumerator and every MoveNext, now
runs with the invariant culture, which is restored before an item reaches the
pipeline, so that no later command sees it. No other cmdlet passes a pattern of
the user to AlphaFS.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
ai/coverage-unknowns
Raimund Andree 1 day ago
parent
commit
fbe02896ef
  1. 60
      NTFSSecurity/ItemCmdlets/GetChildItem2.cs
  2. 8
      Tests/ItemCmdlets.Tests.ps1

60
NTFSSecurity/ItemCmdlets/GetChildItem2.cs

@ -150,8 +150,8 @@ namespace NTFSSecurity
// for itself. Before 5.0.0, [1] was read as a character class, so a file with brackets in its name was not
// returned for its name. The enumeration returns every item for *.* as Windows does, so the comparison does
// too; with the dot as an ordinary character, it would drop the items without a dot, most folders.
// CultureInvariant: the enumeration ignores case without the culture of the thread, and with tr-TR the
// comparison would otherwise treat I and i as different letters and drop items that the enumeration matched.
// CultureInvariant: like the enumeration (see MoveNextInvariant), the comparison would otherwise treat I and i as
// different letters with tr-TR and drop items that the enumeration returned.
var pattern = filter == "*.*" ? "*" : filter;
wildcard = new WildcardPattern(pattern.Replace("`", "``").Replace("[", "`[").Replace("]", "`]"), WildcardOptions.Compiled | WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant);
@ -208,18 +208,33 @@ namespace NTFSSecurity
{
if (directory)
{
var files = di.EnumerateDirectories(filter, global::System.IO.SearchOption.TopDirectoryOnly);
WriteFileSystemInfoCollection(files.GetEnumerator());
IEnumerator files;
using (new InvariantCultureScope())
{
files = di.EnumerateDirectories(filter, global::System.IO.SearchOption.TopDirectoryOnly).GetEnumerator();
}
WriteFileSystemInfoCollection(files);
}
else if (file)
{
var files = di.EnumerateFiles(filter, global::System.IO.SearchOption.TopDirectoryOnly);
WriteFileSystemInfoCollection(files.GetEnumerator());
IEnumerator files;
using (new InvariantCultureScope())
{
files = di.EnumerateFiles(filter, global::System.IO.SearchOption.TopDirectoryOnly).GetEnumerator();
}
WriteFileSystemInfoCollection(files);
}
else
{
var files = di.EnumerateFileSystemInfos(filter, global::System.IO.SearchOption.TopDirectoryOnly);
WriteFileSystemInfoCollection(files.GetEnumerator());
IEnumerator files;
using (new InvariantCultureScope())
{
files = di.EnumerateFileSystemInfos(filter, global::System.IO.SearchOption.TopDirectoryOnly).GetEnumerator();
}
WriteFileSystemInfoCollection(files);
}
}
@ -286,7 +301,7 @@ namespace NTFSSecurity
protected void WriteFileSystemInfoCollection(IEnumerator fileSystemInfos)
{
while (fileSystemInfos.MoveNext())
while (MoveNextInvariant(fileSystemInfos))
{
FileSystemInfo current = (FileSystemInfo)fileSystemInfos.Current;
if (!wildcard.IsMatch(current.Name))
@ -359,5 +374,32 @@ namespace NTFSSecurity
{
base.EndProcessing();
}
// AlphaFS 2.2 matches the filter with a regular expression that takes the culture of the thread, and with tr-TR the
// letters I and i differ: index* did not match Index.txt. The enumeration therefore runs with the invariant culture,
// which is back before an item reaches the pipeline, so that no later command sees it.
private static bool MoveNextInvariant(IEnumerator enumerator)
{
using (new InvariantCultureScope())
{
return enumerator.MoveNext();
}
}
private sealed class InvariantCultureScope : IDisposable
{
private readonly System.Globalization.CultureInfo previous;
public InvariantCultureScope()
{
previous = System.Threading.Thread.CurrentThread.CurrentCulture;
System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture;
}
public void Dispose()
{
System.Threading.Thread.CurrentThread.CurrentCulture = previous;
}
}
}
}

8
Tests/ItemCmdlets.Tests.ps1

@ -159,10 +159,10 @@ Describe 'Get-ChildItem2' {
($result.Name | Sort-Object) -join ',' | Should -Be (($Expected | Sort-Object) -join ',')
}
# With the culture of the thread tr-TR, a case-insensitive comparison that is not culture invariant treats I and i as
# different letters: the cmdlet dropped the items whose names differ in the case of an I from the pattern, which
# the enumeration had matched without regard to the culture. INDEX* is the control: both sides turn I into the
# dotless letter.
# With the culture of the thread tr-TR, a case-insensitive regular expression that is not culture invariant treats I and i
# as different letters. AlphaFS matches the filter that way, and so did the comparison of the cmdlet, so the cmdlet
# dropped the items whose names differ from the filter in the case of an I. INDEX* is the control: both sides turn I
# into the dotless letter.
It 'Should match -Filter <Filter> ignoring case in Turkish, where I and i are different letters' -ForEach @(
@{ Filter = 'index*'; Expected = 'Index.txt' }
@{ Filter = 'INDEX*'; Expected = 'Index.txt' }

Loading…
Cancel
Save