diff --git a/src/Common/src/CoreLib/System/IO/PathInternal.Windows.cs b/src/Common/src/CoreLib/System/IO/PathInternal.Windows.cs index 81d51ba4b4c2..c2ffaae4d5ee 100644 --- a/src/Common/src/CoreLib/System/IO/PathInternal.Windows.cs +++ b/src/Common/src/CoreLib/System/IO/PathInternal.Windows.cs @@ -70,7 +70,7 @@ internal static bool IsValidDriveChar(char value) return ((value >= 'A' && value <= 'Z') || (value >= 'a' && value <= 'z')); } - private static bool EndsWithPeriodOrSpace(string path) + internal static bool EndsWithPeriodOrSpace(string path) { if (string.IsNullOrEmpty(path)) return false; diff --git a/src/System.IO.FileSystem/src/System/IO/FileSystemInfo.Windows.cs b/src/System.IO.FileSystem/src/System/IO/FileSystemInfo.Windows.cs index 3ea48437e85d..01bd52e5e62b 100644 --- a/src/System.IO.FileSystem/src/System/IO/FileSystemInfo.Windows.cs +++ b/src/System.IO.FileSystem/src/System/IO/FileSystemInfo.Windows.cs @@ -14,7 +14,7 @@ partial class FileSystemInfo // Cache any error retrieving the file/directory information // We use this field in conjunction with the Refresh method which should never throw. - // If we succeed we store a zero, on failure we store the HResult so that we can + // If we succeed we store a zero, on failure we store the error code so that we can // throw an appropriate error when attempting to access the cached info. private int _dataInitialized = -1; @@ -153,6 +153,7 @@ public void Refresh() // If we're opened around a enumerated path that ends in a period or space we need to be able to // act on the path normally (open streams/writers/etc.) - internal string NormalizedPath => PathInternal.EnsureExtendedPrefixIfNeeded(FullPath); + internal string NormalizedPath + => PathInternal.EndsWithPeriodOrSpace(FullPath) ? PathInternal.EnsureExtendedPrefix(FullPath) : FullPath; } } diff --git a/src/System.IO.FileSystem/src/System/IO/FileSystemInfo.cs b/src/System.IO.FileSystem/src/System/IO/FileSystemInfo.cs index 25eab4f573d6..f8c3161777ed 100644 --- a/src/System.IO.FileSystem/src/System/IO/FileSystemInfo.cs +++ b/src/System.IO.FileSystem/src/System/IO/FileSystemInfo.cs @@ -31,7 +31,6 @@ public string Extension { get { - // GetFullPathInternal would have already stripped out the terminating "." if present. int length = FullPath.Length; for (int i = length; --i >= 0;) { @@ -68,76 +67,39 @@ public virtual bool Exists public DateTime CreationTime { - get - { - // depends on the security check in get_CreationTimeUtc - return CreationTimeUtc.ToLocalTime(); - } - set - { - CreationTimeUtc = value.ToUniversalTime(); - } + get => CreationTimeUtc.ToLocalTime(); + set => CreationTimeUtc = value.ToUniversalTime(); } public DateTime CreationTimeUtc { - get - { - return CreationTimeCore.UtcDateTime; - } - set - { - CreationTimeCore = File.GetUtcDateTimeOffset(value); - } + get => CreationTimeCore.UtcDateTime; + set => CreationTimeCore = File.GetUtcDateTimeOffset(value); } public DateTime LastAccessTime { - get - { - return LastAccessTimeUtc.ToLocalTime(); - } - set - { - LastAccessTimeUtc = value.ToUniversalTime(); - } + get => LastAccessTimeUtc.ToLocalTime(); + set => LastAccessTimeUtc = value.ToUniversalTime(); } public DateTime LastAccessTimeUtc { - get - { - return LastAccessTimeCore.UtcDateTime; - } - set - { - LastAccessTimeCore = File.GetUtcDateTimeOffset(value); - } + get => LastAccessTimeCore.UtcDateTime; + set => LastAccessTimeCore = File.GetUtcDateTimeOffset(value); } public DateTime LastWriteTime { - get - { - return LastWriteTimeUtc.ToLocalTime(); - } - set - { - LastWriteTimeUtc = value.ToUniversalTime(); - } + get => LastWriteTimeUtc.ToLocalTime(); + set => LastWriteTimeUtc = value.ToUniversalTime(); } public DateTime LastWriteTimeUtc { - get - { - return LastWriteTimeCore.UtcDateTime; - } - set - { - LastWriteTimeCore = File.GetUtcDateTimeOffset(value); - } + get => LastWriteTimeCore.UtcDateTime; + set => LastWriteTimeCore = File.GetUtcDateTimeOffset(value); } /// diff --git a/src/System.IO.FileSystem/tests/Enumeration/TrimmedPaths.netcoreapp.cs b/src/System.IO.FileSystem/tests/Enumeration/TrimmedPaths.netcoreapp.cs index 7ae7943b3b03..ed73117634be 100644 --- a/src/System.IO.FileSystem/tests/Enumeration/TrimmedPaths.netcoreapp.cs +++ b/src/System.IO.FileSystem/tests/Enumeration/TrimmedPaths.netcoreapp.cs @@ -225,5 +225,60 @@ public void TrimmedPathsReplace_Windows() } } } + + [Fact] + [PlatformSpecific(TestPlatforms.Windows)] + public void TrimmedPathsMoveTo_Windows() + { + // Trailing spaces and periods are eaten when normalizing in Windows, making them impossible + // to access without using the \\?\ device syntax. We should, however, be able to move them + // without the special syntax from the info class when enumerating. + + DirectoryInfo directory = Directory.CreateDirectory(GetTestFilePath()); + DirectoryInfo spaceDirectory = Directory.CreateDirectory(Path.Join(@"\\?\", directory.FullName, "Trailing space ")); + DirectoryInfo periodDirectory = Directory.CreateDirectory(Path.Join(@"\\?\", directory.FullName, "Trailing period.")); + string spaceFile = Path.Join(spaceDirectory.FullName, "space"); + string periodFile = Path.Join(periodDirectory.FullName, "period"); + File.Create(spaceFile).Dispose(); + File.Create(periodFile).Dispose(); + + DirectoryInfo[] directories = directory.GetDirectories(); + Assert.Equal(2, directories.Length); + foreach (DirectoryInfo di in directories) + { + if (di.Name == "Trailing space ") + { + di.MoveTo(Path.Join(directory.FullName, "WasSpace")); + } + else if (di.Name == "Trailing period.") + { + di.MoveTo(Path.Join(directory.FullName, "WasPeriod")); + } + else + { + Assert.False(true, $"Found unexpected name '{di.Name}'"); + } + } + + directories = directory.GetDirectories(); + Assert.Equal(2, directories.Length); + foreach (DirectoryInfo di in directories) + { + if (di.Name == "WasSpace") + { + FileInfo fi = di.GetFiles().Single(); + Assert.Equal("space", fi.Name); + } + else if (di.Name == "WasPeriod") + { + FileInfo fi = di.GetFiles().Single(); + Assert.Equal("period", fi.Name); + } + else + { + Assert.False(true, $"Found unexpected name '{di.Name}'"); + } + } + } } }