Fix accessing trimmed paths (dotnet/corefx#26918)
authorJeremy Kuhne <jeremy.kuhne@microsoft.com>
Wed, 7 Feb 2018 21:01:16 +0000 (13:01 -0800)
committerGitHub <noreply@github.com>
Wed, 7 Feb 2018 21:01:16 +0000 (13:01 -0800)
* Fix accessing trimmed paths

Paths with trailing spaces or periods are normally trimmed by Windows. They can, however, be created using `\\?\` or be exposed via mapping of remote file systems. This change fixes the ability to work with returned *Info classes on files of this type.

This change also cleans up the Info classes:

- removes non-relevant comments
- removes "display" paths (limited trust holdover)

Also remove dead PathPair class.

* Fix helper for .NET Standard

Commit migrated from https://github.com/dotnet/corefx/commit/e2cd5a8fc468b63255f91f4f28bccddb3bb31e8f

21 files changed:
src/libraries/Common/src/Interop/Windows/kernel32/Interop.CopyFileEx.cs
src/libraries/Common/src/Interop/Windows/kernel32/Interop.CreateFile.cs
src/libraries/Common/src/Interop/Windows/kernel32/Interop.DeleteFile.cs
src/libraries/Common/src/Interop/Windows/kernel32/Interop.DeleteVolumeMountPoint.cs
src/libraries/Common/src/Interop/Windows/kernel32/Interop.FindFirstFileEx.cs
src/libraries/Common/src/Interop/Windows/kernel32/Interop.GetFileAttributesEx.cs
src/libraries/Common/src/Interop/Windows/kernel32/Interop.GetLongPathName.cs
src/libraries/Common/src/Interop/Windows/kernel32/Interop.MoveFileEx.cs
src/libraries/Common/src/Interop/Windows/kernel32/Interop.RemoveDirectory.cs
src/libraries/Common/src/Interop/Windows/kernel32/Interop.ReplaceFile.cs
src/libraries/Common/src/Interop/Windows/kernel32/Interop.SetFileAttributes.cs
src/libraries/Common/src/System/IO/PathInternal.Windows.cs
src/libraries/System.IO.FileSystem/src/System.IO.FileSystem.csproj
src/libraries/System.IO.FileSystem/src/System/IO/DirectoryInfo.cs
src/libraries/System.IO.FileSystem/src/System/IO/FileInfo.cs
src/libraries/System.IO.FileSystem/src/System/IO/FileSystemInfo.cs
src/libraries/System.IO.FileSystem/src/System/IO/PathHelpers.Windows.cs
src/libraries/System.IO.FileSystem/src/System/IO/PathPair.cs [deleted file]
src/libraries/System.IO.FileSystem/tests/DirectoryInfo/ToString.cs
src/libraries/System.IO.FileSystem/tests/Enumeration/TrimmedPaths.netcoreapp.cs [new file with mode: 0644]
src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj

index 7f8c10c..46efbc6 100644 (file)
@@ -30,8 +30,8 @@ internal partial class Interop
             ref int cancel,
             int flags)
         {
-            src = PathInternal.EnsureExtendedPrefixOverMaxPath(src);
-            dst = PathInternal.EnsureExtendedPrefixOverMaxPath(dst);
+            src = PathInternal.EnsureExtendedPrefixIfNeeded(src);
+            dst = PathInternal.EnsureExtendedPrefixIfNeeded(dst);
             return CopyFileExPrivate(src, dst, progressRoutine, progressData, ref cancel, flags);
         }
     }
index 9c3639d..3fe8360 100644 (file)
@@ -34,7 +34,7 @@ internal partial class Interop
             int dwFlagsAndAttributes,
             IntPtr hTemplateFile)
         {
-            lpFileName = PathInternal.EnsureExtendedPrefixOverMaxPath(lpFileName);
+            lpFileName = PathInternal.EnsureExtendedPrefixIfNeeded(lpFileName);
             fixed (SECURITY_ATTRIBUTES* sa = &securityAttrs)
             {
                 IntPtr handle = CreateFilePrivate(lpFileName, dwDesiredAccess, dwShareMode, sa, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
@@ -76,7 +76,7 @@ internal partial class Interop
             FileMode dwCreationDisposition,
             int dwFlagsAndAttributes)
         {
-            lpFileName = PathInternal.EnsureExtendedPrefixOverMaxPath(lpFileName);
+            lpFileName = PathInternal.EnsureExtendedPrefixIfNeeded(lpFileName);
             return CreateFilePrivate(lpFileName, dwDesiredAccess, dwShareMode, null, dwCreationDisposition, dwFlagsAndAttributes, IntPtr.Zero);
         }
     }
index 3609f0d..dbcae6e 100644 (file)
@@ -18,7 +18,7 @@ internal partial class Interop
 
         internal static bool DeleteFile(string path)
         {
-            path = PathInternal.EnsureExtendedPrefixOverMaxPath(path);
+            path = PathInternal.EnsureExtendedPrefixIfNeeded(path);
             return DeleteFilePrivate(path);
         }
     }
index ccf1015..49b9bfc 100644 (file)
@@ -19,7 +19,7 @@ internal partial class Interop
 
         internal static bool DeleteVolumeMountPoint(string mountPoint)
         {
-            mountPoint = PathInternal.EnsureExtendedPrefixOverMaxPath(mountPoint);
+            mountPoint = PathInternal.EnsureExtendedPrefixIfNeeded(mountPoint);
             return DeleteVolumeMountPointPrivate(mountPoint);
         }
     }
index 2d1ecef..a561132 100644 (file)
@@ -19,7 +19,7 @@ internal partial class Interop
 
         internal static SafeFindHandle FindFirstFile(string fileName, ref WIN32_FIND_DATA data)
         {
-            fileName = PathInternal.EnsureExtendedPrefixOverMaxPath(fileName);
+            fileName = PathInternal.EnsureExtendedPrefixIfNeeded(fileName);
 
             // use FindExInfoBasic since we don't care about short name and it has better perf
             return FindFirstFileExPrivate(fileName, FINDEX_INFO_LEVELS.FindExInfoBasic, ref data, FINDEX_SEARCH_OPS.FindExSearchNameMatch, IntPtr.Zero, 0);
index 1bd3bed..7740e85 100644 (file)
@@ -17,7 +17,7 @@ internal partial class Interop
 
         internal static bool GetFileAttributesEx(string name, GET_FILEEX_INFO_LEVELS fileInfoLevel, ref WIN32_FILE_ATTRIBUTE_DATA lpFileInformation)
         {
-            name = PathInternal.EnsureExtendedPrefixOverMaxPath(name);
+            name = PathInternal.EnsureExtendedPrefixIfNeeded(name);
             return GetFileAttributesExPrivate(name, fileInfoLevel, ref lpFileInformation);
         }
     }
index eda1482..185f0c7 100644 (file)
@@ -18,7 +18,7 @@ internal partial class Interop
 
         internal static int GetLongPathName(string path, [Out]StringBuilder longPathBuffer, int bufferLength)
         {
-            path = PathInternal.EnsureExtendedPrefixOverMaxPath(path);
+            path = PathInternal.EnsureExtendedPrefixIfNeeded(path);
             return GetLongPathNamePrivate(path, longPathBuffer, bufferLength);
         }
     }
index 3e38478..b76648b 100644 (file)
@@ -18,8 +18,8 @@ internal partial class Interop
 
         internal static bool MoveFile(string src, string dst)
         {
-            src = PathInternal.EnsureExtendedPrefixOverMaxPath(src);
-            dst = PathInternal.EnsureExtendedPrefixOverMaxPath(dst);
+            src = PathInternal.EnsureExtendedPrefixIfNeeded(src);
+            dst = PathInternal.EnsureExtendedPrefixIfNeeded(dst);
             return MoveFileExPrivate(src, dst, 2 /* MOVEFILE_COPY_ALLOWED */);
         }
     }
index af5f809..dd8e321 100644 (file)
@@ -18,7 +18,7 @@ internal partial class Interop
 
         internal static bool RemoveDirectory(string path)
         {
-            path = PathInternal.EnsureExtendedPrefixOverMaxPath(path);
+            path = PathInternal.EnsureExtendedPrefixIfNeeded(path);
             return RemoveDirectoryPrivate(path);
         }
     }
index 8c9dee4..4b9dd79 100644 (file)
@@ -19,9 +19,9 @@ internal partial class Interop
             string replacedFileName, string replacementFileName, string backupFileName,
             int dwReplaceFlags, IntPtr lpExclude, IntPtr lpReserved)
         {
-            replacedFileName = PathInternal.EnsureExtendedPrefixOverMaxPath(replacedFileName);
-            replacementFileName = PathInternal.EnsureExtendedPrefixOverMaxPath(replacementFileName);
-            backupFileName = PathInternal.EnsureExtendedPrefixOverMaxPath(backupFileName);
+            replacedFileName = PathInternal.EnsureExtendedPrefixIfNeeded(replacedFileName);
+            replacementFileName = PathInternal.EnsureExtendedPrefixIfNeeded(replacementFileName);
+            backupFileName = PathInternal.EnsureExtendedPrefixIfNeeded(backupFileName);
 
             return ReplaceFilePrivate(
                 replacedFileName, replacementFileName, backupFileName,
index 865730d..9f956dc 100644 (file)
@@ -17,7 +17,7 @@ internal partial class Interop
 
         internal static bool SetFileAttributes(string name, int attr)
         {
-            name = PathInternal.EnsureExtendedPrefixOverMaxPath(name);
+            name = PathInternal.EnsureExtendedPrefixIfNeeded(name);
             return SetFileAttributesPrivate(name, attr);
         }
     }
index 1d0dcba..7307969 100644 (file)
@@ -81,13 +81,25 @@ namespace System.IO
             return ((value >= 'A' && value <= 'Z') || (value >= 'a' && value <= 'z'));
         }
 
+        private static bool EndsWithPeriodOrSpace(string path)
+        {
+            if (string.IsNullOrEmpty(path))
+                return false;
+
+            char c = path[path.Length - 1];
+            return c == ' ' || c == '.';
+        }
+
         /// <summary>
         /// Adds the extended path prefix (\\?\) if not already a device path, IF the path is not relative,
-        /// AND the path is more than 259 characters. (> MAX_PATH + null)
+        /// AND the path is more than 259 characters. (> MAX_PATH + null). This will also insert the extended
+        /// prefix if the path ends with a period or a space. Trailing periods and spaces are normally eaten
+        /// away from paths during normalization, but if we see such a path at this point it should be
+        /// normalized and has retained the final characters. (Typically from one of the *Info classes)
         /// </summary>
-        internal static string EnsureExtendedPrefixOverMaxPath(string path)
+        internal static string EnsureExtendedPrefixIfNeeded(string path)
         {
-            if (path != null && path.Length >= MaxShortPath)
+            if (path != null && (path.Length >= MaxShortPath || EndsWithPeriodOrSpace(path)))
             {
                 return EnsureExtendedPrefix(path);
             }
index 57f03ab..257f089 100644 (file)
@@ -34,7 +34,6 @@
     <Compile Include="System\IO\Enumeration\EnumerationOptions.cs" />
     <Compile Include="System\IO\Iterator.cs" />
     <Compile Include="System\IO\PathHelpers.cs" />
-    <Compile Include="System\IO\PathPair.cs" />
     <Compile Include="System\IO\ReadLinesIterator.cs" />
     <Compile Include="System\IO\SearchOption.cs" />
     <Compile Include="System\IO\SearchTarget.cs" />
index 1970b89..d4ba432 100644 (file)
@@ -11,11 +11,9 @@ namespace System.IO
 {
     public sealed partial class DirectoryInfo : FileSystemInfo
     {
-        private string _name;
-
         public DirectoryInfo(string path)
         {
-            Init(originalPath: PathHelpers.ShouldReviseDirectoryPathToCurrent(path) ? "." : path,
+            Init(originalPath: path,
                   fullPath: Path.GetFullPath(path),
                   isNormalized: true);
         }
@@ -39,26 +37,16 @@ namespace System.IO
                     Path.GetFileName(PathHelpers.TrimEndingDirectorySeparator(fullPath)));
 
             FullPath = fullPath;
-            DisplayPath = PathHelpers.ShouldReviseDirectoryPathToCurrent(originalPath) ? "." : originalPath;
         }
 
-        public override string Name => _name;
-
         public DirectoryInfo Parent
         {
             get
             {
-                string s = FullPath;
-
-                // FullPath might end in either "parent\child" or "parent\child", and in either case we want 
+                // FullPath might end in either "parent\child" or "parent\child\", and in either case we want 
                 // the parent of child, not the child. Trim off an ending directory separator if there is one,
                 // but don't mangle the root.
-                if (!PathHelpers.IsRoot(s))
-                {
-                    s = PathHelpers.TrimEndingDirectorySeparator(s);
-                }
-
-                string parentName = Path.GetDirectoryName(s);
+                string parentName = Path.GetDirectoryName(PathHelpers.IsRoot(FullPath) ? FullPath : PathHelpers.TrimEndingDirectorySeparator(FullPath));
                 return parentName != null ? 
                     new DirectoryInfo(parentName, null) :
                     null;
@@ -70,53 +58,20 @@ namespace System.IO
             if (path == null)
                 throw new ArgumentNullException(nameof(path));
 
-            return CreateSubdirectoryHelper(path);
-        }
-
-        private DirectoryInfo CreateSubdirectoryHelper(string path)
-        {
-            Debug.Assert(path != null);
-
             PathHelpers.ThrowIfEmptyOrRootedPath(path);
 
-            string newDirs = Path.Combine(FullPath, path);
-            string fullPath = Path.GetFullPath(newDirs);
+            string fullPath = Path.GetFullPath(Path.Combine(FullPath, path));
 
             if (0 != string.Compare(FullPath, 0, fullPath, 0, FullPath.Length, PathInternal.StringComparison))
             {
-                throw new ArgumentException(SR.Format(SR.Argument_InvalidSubPath, path, DisplayPath), nameof(path));
+                throw new ArgumentException(SR.Format(SR.Argument_InvalidSubPath, path, FullPath), nameof(path));
             }
 
             FileSystem.CreateDirectory(fullPath);
-
-            // Check for read permission to directory we hand back by calling this constructor.
             return new DirectoryInfo(fullPath);
         }
 
-        public void Create()
-        {
-            FileSystem.CreateDirectory(FullPath);
-        }
-
-        // Tests if the given path refers to an existing DirectoryInfo on disk.
-        // 
-        // Your application must have Read permission to the directory's
-        // contents.
-        //
-        public override bool Exists
-        {
-            get
-            {
-                try
-                {
-                    return ExistsCore;
-                }
-                catch
-                {
-                    return false;
-                }
-            }
-        }
+        public void Create() => FileSystem.CreateDirectory(FullPath);
 
         // Returns an array of Files in the DirectoryInfo specified by path
         public FileInfo[] GetFiles() => GetFiles("*", enumerationOptions: EnumerationOptions.Compatible);
@@ -218,25 +173,7 @@ namespace System.IO
             }
         }
 
-        // Returns the root portion of the given path. The resulting string
-        // consists of those rightmost characters of the path that constitute the
-        // root of the path. Possible patterns for the resulting string are: An
-        // empty string (a relative path on the current drive), "\" (an absolute
-        // path on the current drive), "X:" (a relative path on a given drive,
-        // where X is the drive letter), "X:\" (an absolute path on a given drive),
-        // and "\\server\share" (a UNC path for a given server and share name).
-        // The resulting string is null if path is null.
-        //
-
-        public DirectoryInfo Root
-        {
-            get
-            {
-                string rootPath = Path.GetPathRoot(FullPath);
-
-                return new DirectoryInfo(rootPath);
-            }
-        }
+        public DirectoryInfo Root => new DirectoryInfo(Path.GetPathRoot(FullPath));
 
         public void MoveTo(string destDirName)
         {
@@ -244,26 +181,22 @@ namespace System.IO
                 throw new ArgumentNullException(nameof(destDirName));
             if (destDirName.Length == 0)
                 throw new ArgumentException(SR.Argument_EmptyFileName, nameof(destDirName));
-
             string destination = Path.GetFullPath(destDirName);
+
             string destinationWithSeparator = destination;
-            if (destinationWithSeparator[destinationWithSeparator.Length - 1] != Path.DirectorySeparatorChar)
+            if (!PathHelpers.EndsInDirectorySeparator(destinationWithSeparator))
                 destinationWithSeparator = destinationWithSeparator + PathHelpers.DirectorySeparatorCharAsString;
 
-            string fullSourcePath;
-            if (FullPath.Length > 0 && FullPath[FullPath.Length - 1] == Path.DirectorySeparatorChar)
-                fullSourcePath = FullPath;
-            else
-                fullSourcePath = FullPath + PathHelpers.DirectorySeparatorCharAsString;
+            string sourceWithSeparator = PathHelpers.EndsInDirectorySeparator(FullPath)
+                ? FullPath : FullPath + PathHelpers.DirectorySeparatorCharAsString;
 
-            StringComparison pathComparison = PathInternal.StringComparison;
-            if (string.Equals(fullSourcePath, destinationWithSeparator, pathComparison))
+            if (string.Equals(sourceWithSeparator, destinationWithSeparator, PathInternal.StringComparison))
                 throw new IOException(SR.IO_SourceDestMustBeDifferent);
 
-            string sourceRoot = Path.GetPathRoot(fullSourcePath);
+            string sourceRoot = Path.GetPathRoot(sourceWithSeparator);
             string destinationRoot = Path.GetPathRoot(destinationWithSeparator);
 
-            if (!string.Equals(sourceRoot, destinationRoot, pathComparison))
+            if (!string.Equals(sourceRoot, destinationRoot, PathInternal.StringComparison))
                 throw new IOException(SR.IO_SourceDestMustHaveSameRoot);
 
             // Windows will throw if the source file/directory doesn't exist, we preemptively check
@@ -271,7 +204,7 @@ namespace System.IO
             if (!Exists && !FileSystem.FileExists(FullPath))
                 throw new DirectoryNotFoundException(SR.Format(SR.IO_PathNotFound_Path, FullPath));
 
-            if (FileSystem.DirectoryExists(destinationWithSeparator))
+            if (FileSystem.DirectoryExists(destination))
                 throw new IOException(SR.Format(SR.IO_AlreadyExists_Name, destinationWithSeparator));
 
             FileSystem.MoveDirectory(FullPath, destination);
@@ -285,22 +218,8 @@ namespace System.IO
             Invalidate();
         }
 
-        public override void Delete()
-        {
-            FileSystem.RemoveDirectory(FullPath, false);
-        }
-
-        public void Delete(bool recursive)
-        {
-            FileSystem.RemoveDirectory(FullPath, recursive);
-        }
+        public override void Delete() => FileSystem.RemoveDirectory(FullPath, recursive: false);
 
-        /// <summary>
-        /// Returns the original path. Use FullPath or Name properties for the path / directory name.
-        /// </summary>
-        public override string ToString()
-        {
-            return DisplayPath;
-        }
+        public void Delete(bool recursive) => FileSystem.RemoveDirectory(FullPath, recursive);
     }
 }
index 5ee17e6..3811ed8 100644 (file)
@@ -11,8 +11,6 @@ namespace System.IO
     // routines such as Delete, etc.
     public sealed partial class FileInfo : FileSystemInfo
     {
-        private string _name;
-
         private FileInfo() { }
 
         public FileInfo(string fileName)
@@ -30,12 +28,6 @@ namespace System.IO
 
             FullPath = isNormalized ? fullPath ?? originalPath : Path.GetFullPath(fullPath);
             _name = fileName ?? Path.GetFileName(originalPath);
-            DisplayPath = originalPath;
-        }
-
-        public override string Name
-        {
-            get { return _name; }
         }
 
         public long Length
@@ -44,22 +36,14 @@ namespace System.IO
             {
                 if ((Attributes & FileAttributes.Directory) == FileAttributes.Directory)
                 {
-                    throw new FileNotFoundException(SR.Format(SR.IO_FileNotFound_FileName, DisplayPath), DisplayPath);
+                    throw new FileNotFoundException(SR.Format(SR.IO_FileNotFound_FileName, FullPath), FullPath);
                 }
                 return LengthCore;
             }
         }
 
-        /* Returns the name of the directory that the file is in */
-        public string DirectoryName
-        {
-            get
-            {
-                return Path.GetDirectoryName(FullPath);
-            }
-        }
+        public string DirectoryName => Path.GetDirectoryName(FullPath);
 
-        /* Creates an instance of the parent directory */
         public DirectoryInfo Directory
         {
             get
@@ -87,30 +71,14 @@ namespace System.IO
         }
 
         public StreamReader OpenText()
-        {
-            return new StreamReader(FullPath, Encoding.UTF8, detectEncodingFromByteOrderMarks: true);
-        }
+            => new StreamReader(FullPath, Encoding.UTF8, detectEncodingFromByteOrderMarks: true);
 
         public StreamWriter CreateText()
-        {
-            return new StreamWriter(FullPath, append: false);
-        }
+            => new StreamWriter(FullPath, append: false);
 
         public StreamWriter AppendText()
-        {
-            return new StreamWriter(FullPath, append: true);
-        }
+            => new StreamWriter(FullPath, append: true);
 
-
-        // Copies an existing file to a new file. An exception is raised if the
-        // destination file already exists. Use the 
-        // Copy(string, string, boolean) method to allow 
-        // overwriting an existing file.
-        //
-        // The caller must have certain FileIOPermissions.  The caller must have
-        // Read permission to sourceFileName 
-        // and Write permissions to destFileName.
-        // 
         public FileInfo CopyTo(string destFileName)
         {
             if (destFileName == null)
@@ -121,16 +89,6 @@ namespace System.IO
             return new FileInfo(File.InternalCopy(FullPath, destFileName, false), isNormalized: true);
         }
 
-
-        // Copies an existing file to a new file. If overwrite is 
-        // false, then an IOException is thrown if the destination file 
-        // already exists.  If overwrite is true, the file is 
-        // overwritten.
-        //
-        // The caller must have certain FileIOPermissions.  The caller must have
-        // Read permission to sourceFileName and Create
-        // and Write permissions to destFileName.
-        // 
         public FileInfo CopyTo(string destFileName, bool overwrite)
         {
             if (destFileName == null)
@@ -141,82 +99,27 @@ namespace System.IO
             return new FileInfo(File.InternalCopy(FullPath, destFileName, overwrite), isNormalized: true);
         }
 
-        public FileStream Create()
-        {
-            return File.Create(FullPath);
-        }
+        public FileStream Create() => File.Create(FullPath);
 
-        // Deletes a file. The file specified by the designated path is deleted. 
-        // If the file does not exist, Delete succeeds without throwing
-        // an exception.
-        // 
-        // On NT, Delete will fail for a file that is open for normal I/O
-        // or a file that is memory mapped.  On Win95, the file will be 
-        // deleted irregardless of whether the file is being used.
-        // 
-        // Your application must have Delete permission to the target file.
-        // 
-        public override void Delete()
-        {
-            FileSystem.DeleteFile(FullPath);
-        }
+        public override void Delete() => FileSystem.DeleteFile(FullPath);
 
-        // Tests if the given file exists. The result is true if the file
-        // given by the specified path exists; otherwise, the result is
-        // false.  
-        //
-        // Your application must have Read permission for the target directory.
-        public override bool Exists
-        {
-            get
-            {
-                try
-                {
-                    return ExistsCore;
-                }
-                catch
-                {
-                    return false;
-                }
-            }
-        }
-
-        // User must explicitly specify opening a new file or appending to one.
         public FileStream Open(FileMode mode)
-        {
-            return Open(mode, (mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite), FileShare.None);
-        }
+            => Open(mode, (mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite), FileShare.None);
 
         public FileStream Open(FileMode mode, FileAccess access)
-        {
-            return Open(mode, access, FileShare.None);
-        }
+            => Open(mode, access, FileShare.None);
 
         public FileStream Open(FileMode mode, FileAccess access, FileShare share)
-        {
-            return new FileStream(FullPath, mode, access, share);
-        }
+            => new FileStream(FullPath, mode, access, share);
 
         public FileStream OpenRead()
-        {
-            return new FileStream(FullPath, FileMode.Open, FileAccess.Read,
-                                  FileShare.Read, 4096, false);
-        }
+            => new FileStream(FullPath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, false);
 
         public FileStream OpenWrite()
-        {
-            return new FileStream(FullPath, FileMode.OpenOrCreate,
-                                  FileAccess.Write, FileShare.None);
-        }
+            => new FileStream(FullPath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None);
 
         // Moves a given file to a new location and potentially a new file name.
         // This method does work across volumes.
-        //
-        // The caller must have certain FileIOPermissions.  The caller must
-        // have Read and Write permission to 
-        // sourceFileName and Write 
-        // permissions to destFileName.
-        // 
         public void MoveTo(string destFileName)
         {
             if (destFileName == null)
@@ -230,28 +133,23 @@ namespace System.IO
             // as it does on Windows.These checks can be removed if a solution to #2460 is
             // found that doesn't require validity checks before making an API call.
             if (!new DirectoryInfo(Path.GetDirectoryName(FullName)).Exists)
-            {
                 throw new DirectoryNotFoundException(SR.Format(SR.IO_PathNotFound_Path, FullName));
-            }
+
             if (!Exists)
-            {
                 throw new FileNotFoundException(SR.Format(SR.IO_FileNotFound_FileName, FullName), FullName);
-            }
 
             FileSystem.MoveFile(FullPath, fullDestFileName);
 
             FullPath = fullDestFileName;
             OriginalPath = destFileName;
             _name = Path.GetFileName(fullDestFileName);
-            DisplayPath = destFileName;
+
             // Flush any cached information about the file.
             Invalidate();
         }
 
         public FileInfo Replace(string destinationFileName, string destinationBackupFileName)
-        {
-            return Replace(destinationFileName, destinationBackupFileName, ignoreMetadataErrors: false);
-        }
+            => Replace(destinationFileName, destinationBackupFileName, ignoreMetadataErrors: false);
 
         public FileInfo Replace(string destinationFileName, string destinationBackupFileName, bool ignoreMetadataErrors)
         {
@@ -259,21 +157,8 @@ namespace System.IO
             return new FileInfo(destinationFileName);
         }
 
-        // Returns the display path
-        public override string ToString()
-        {
-            return DisplayPath;
-        }
-
-        public void Decrypt()
-        {
-            File.Decrypt(FullPath);
-        }
-
-        public void Encrypt()
-        {
-            File.Encrypt(FullPath);
-        }
+        public void Decrypt() => File.Decrypt(FullPath);
 
+        public void Encrypt() => File.Encrypt(FullPath);
     }
 }
index 702dfbe..239377b 100644 (file)
@@ -2,22 +2,17 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
-using System;
-using System.Collections;
-using System.Security;
-using Microsoft.Win32;
-using System.Text;
-using System.Runtime.InteropServices;
 using System.Runtime.Serialization;
-using System.Runtime.Versioning;
 
 namespace System.IO
 {
     public abstract partial class FileSystemInfo : MarshalByRefObject, ISerializable
     {
+        // FullPath and OriginalPath are documented fields
         protected string FullPath;          // fully qualified path of the file or directory
         protected string OriginalPath;      // path passed in by the user
-        private string _displayPath = "";   // path that can be displayed to the user
+
+        internal string _name;
 
         protected FileSystemInfo()
         {
@@ -34,13 +29,7 @@ namespace System.IO
         }
 
         // Full path of the directory/file
-        public virtual string FullName
-        {
-            get
-            {
-                return FullPath;
-            }
-        }
+        public virtual string FullName => FullPath;
 
         public string Extension
         {
@@ -60,17 +49,22 @@ namespace System.IO
             }
         }
 
-        // For files name of the file is returned, for directories the last directory in hierarchy is returned if possible,
-        // otherwise the fully qualified name s returned
-        public abstract string Name
-        {
-            get;
-        }
+        public virtual string Name => _name;
 
         // Whether a file/directory exists
-        public abstract bool Exists
+        public virtual bool Exists
         {
-            get;
+            get
+            {
+                try
+                {
+                    return ExistsCore;
+                }
+                catch
+                {
+                    return false;
+                }
+            }
         }
 
         // Delete a file/directory
@@ -95,7 +89,6 @@ namespace System.IO
             {
                 return CreationTimeCore.UtcDateTime;
             }
-
             set
             {
                 CreationTimeCore = File.GetUtcDateTimeOffset(value);
@@ -107,7 +100,6 @@ namespace System.IO
         {
             get
             {
-                // depends on the security check in get_LastAccessTimeUtc
                 return LastAccessTimeUtc.ToLocalTime();
             }
             set
@@ -122,7 +114,6 @@ namespace System.IO
             {
                 return LastAccessTimeCore.UtcDateTime;
             }
-
             set
             {
                 LastAccessTimeCore = File.GetUtcDateTimeOffset(value);
@@ -133,10 +124,8 @@ namespace System.IO
         {
             get
             {
-                // depends on the security check in get_LastWriteTimeUtc
                 return LastWriteTimeUtc.ToLocalTime();
             }
-
             set
             {
                 LastWriteTimeUtc = value.ToUniversalTime();
@@ -149,23 +138,15 @@ namespace System.IO
             {
                 return LastWriteTimeCore.UtcDateTime;
             }
-
             set
             {
                 LastWriteTimeCore = File.GetUtcDateTimeOffset(value);
             }
         }
 
-        internal string DisplayPath
-        {
-            get
-            {
-                return _displayPath;
-            }
-            set
-            {
-                _displayPath = value;
-            }
-        }
+        /// <summary>
+        /// Returns the original path. Use FullName or Name properties for the full path or file/directory name.
+        /// </summary>
+        public override string ToString() => OriginalPath ?? string.Empty;
     }
 }
index 9cd0cf5..3c30eaf 100644 (file)
@@ -19,13 +19,6 @@ namespace System.IO
 
     internal static partial class PathHelpers
     {
-        internal static bool ShouldReviseDirectoryPathToCurrent(string path)
-        {
-            // In situations where this method is invoked, "<DriveLetter>:" should be special-cased
-            // to instead go to the current directory.
-            return path != null && path.Length == 2 && path[1] == ':';
-        }
-
         internal static string TrimEndingDirectorySeparator(string path) =>
             EndsInDirectorySeparator(path) ?
                 path.Substring(0, path.Length - 1) :
diff --git a/src/libraries/System.IO.FileSystem/src/System/IO/PathPair.cs b/src/libraries/System.IO.FileSystem/src/System/IO/PathPair.cs
deleted file mode 100644 (file)
index dfef1bc..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-namespace System.IO
-{
-    internal readonly struct PathPair
-    {
-        internal readonly string UserPath;
-        internal readonly string FullPath;
-
-        internal PathPair(string userPath, string fullPath)
-        {
-            UserPath = userPath;
-            FullPath = fullPath;
-        }
-    }
-}
index 732c71e..de2ea39 100644 (file)
@@ -38,12 +38,28 @@ namespace System.IO.Tests
         }
 
         [Fact]
+        [SkipOnTargetFramework(TargetFrameworkMonikers.Netcoreapp)]
         [PlatformSpecific(TestPlatforms.Windows)]  // Drive letter only
-        public void DriveOnlyReturnsPeriod_Windows()
+        public void DriveOnlyReturnsPeriod_Windows_Desktop()
         {
             string path = @"C:";
             var info = new DirectoryInfo(path);
             Assert.Equal(".", info.ToString());
         }
+
+        [Fact]
+        [SkipOnTargetFramework(~TargetFrameworkMonikers.Netcoreapp)]
+        [PlatformSpecific(TestPlatforms.Windows)]  // Drive letter only
+        public void DriveOnlyReturnsPeriod_Windows_Core()
+        {
+            // This was likely a limited trust hack that was strangely implemented.
+            // Getting the current directory for a specified drive relative path
+            // doesn't make a lot of sense. There is no reason to hide original paths
+            // when in full trust.
+            string path = @"C:";
+            var info = new DirectoryInfo(path);
+            Assert.Equal("C:", info.ToString());
+        }
+
     }
 }
diff --git a/src/libraries/System.IO.FileSystem/tests/Enumeration/TrimmedPaths.netcoreapp.cs b/src/libraries/System.IO.FileSystem/tests/Enumeration/TrimmedPaths.netcoreapp.cs
new file mode 100644 (file)
index 0000000..8621e8e
--- /dev/null
@@ -0,0 +1,64 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Linq;
+using Xunit;
+
+namespace System.IO.Tests.Enumeration
+{
+    public class TrimmedPaths : FileSystemTest
+    {
+        [Fact]
+        [PlatformSpecific(TestPlatforms.Windows)]
+        public void TrimmedPathsAreFound_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 find them
+            // and retain the filename in the info classes and string results.
+
+            var directory = Directory.CreateDirectory(GetTestFilePath());
+            File.Create(@"\\?\" + Path.Combine(directory.FullName, "Trailing space ")).Dispose();
+            File.Create(@"\\?\" + Path.Combine(directory.FullName, "Trailing period.")).Dispose();
+
+            var files = directory.GetFiles();
+            Assert.Equal(2, files.Count());
+            FSAssert.EqualWhenOrdered(new string[] { "Trailing space ", "Trailing period." }, files.Select(f => f.Name));
+
+            var paths = Directory.GetFiles(directory.FullName);
+            Assert.Equal(2, paths.Count());
+            FSAssert.EqualWhenOrdered(new string[] { "Trailing space ", "Trailing period." }, paths.Select(p => Path.GetFileName(p)));
+        }
+
+        [Fact]
+        [PlatformSpecific(TestPlatforms.Windows)]
+        public void TrimmedPathsDeletion_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 delete them
+            // from the info class.
+
+            var directory = Directory.CreateDirectory(GetTestFilePath());
+            File.Create(@"\\?\" + Path.Combine(directory.FullName, "Trailing space ")).Dispose();
+            File.Create(@"\\?\" + Path.Combine(directory.FullName, "Trailing period.")).Dispose();
+
+            // With just a path name, the trailing space/period will get eaten, so we
+            // can't delete without prepending- they won't "exist".
+            var paths = Directory.GetFiles(directory.FullName);
+            Assert.All(paths, p => Assert.False(File.Exists(p)));
+
+            var files = directory.GetFiles();
+            Assert.Equal(2, files.Count());
+            Assert.All(files, f => Assert.True(f.Exists));
+            foreach (var f in files)
+                f.Refresh();
+            Assert.All(files, f => Assert.True(f.Exists));
+            foreach (var f in files)
+            {
+                f.Delete();
+                f.Refresh();
+            }
+            Assert.All(files, f => Assert.False(f.Exists));
+        }
+    }
+}
index 6046f20..cdbe525 100644 (file)
@@ -58,6 +58,7 @@
     <Compile Include="Enumeration\SkipAttributeTests.netcoreapp.cs" />
     <Compile Include="Enumeration\DosMatcherTests.netcoreapp.cs" />
     <Compile Include="Enumeration\MatchCasingTests.netcoreapp.cs" />
+    <Compile Include="Enumeration\TrimmedPaths.netcoreapp.cs" />
   </ItemGroup>
   <ItemGroup>
     <!-- Rewritten -->