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);
}
}
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);
FileMode dwCreationDisposition,
int dwFlagsAndAttributes)
{
- lpFileName = PathInternal.EnsureExtendedPrefixOverMaxPath(lpFileName);
+ lpFileName = PathInternal.EnsureExtendedPrefixIfNeeded(lpFileName);
return CreateFilePrivate(lpFileName, dwDesiredAccess, dwShareMode, null, dwCreationDisposition, dwFlagsAndAttributes, IntPtr.Zero);
}
}
internal static bool DeleteFile(string path)
{
- path = PathInternal.EnsureExtendedPrefixOverMaxPath(path);
+ path = PathInternal.EnsureExtendedPrefixIfNeeded(path);
return DeleteFilePrivate(path);
}
}
internal static bool DeleteVolumeMountPoint(string mountPoint)
{
- mountPoint = PathInternal.EnsureExtendedPrefixOverMaxPath(mountPoint);
+ mountPoint = PathInternal.EnsureExtendedPrefixIfNeeded(mountPoint);
return DeleteVolumeMountPointPrivate(mountPoint);
}
}
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);
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);
}
}
internal static int GetLongPathName(string path, [Out]StringBuilder longPathBuffer, int bufferLength)
{
- path = PathInternal.EnsureExtendedPrefixOverMaxPath(path);
+ path = PathInternal.EnsureExtendedPrefixIfNeeded(path);
return GetLongPathNamePrivate(path, longPathBuffer, bufferLength);
}
}
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 */);
}
}
internal static bool RemoveDirectory(string path)
{
- path = PathInternal.EnsureExtendedPrefixOverMaxPath(path);
+ path = PathInternal.EnsureExtendedPrefixIfNeeded(path);
return RemoveDirectoryPrivate(path);
}
}
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,
internal static bool SetFileAttributes(string name, int attr)
{
- name = PathInternal.EnsureExtendedPrefixOverMaxPath(name);
+ name = PathInternal.EnsureExtendedPrefixIfNeeded(name);
return SetFileAttributesPrivate(name, attr);
}
}
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);
}
<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" />
{
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);
}
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;
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);
}
}
- // 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)
{
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
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);
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);
}
}
// routines such as Delete, etc.
public sealed partial class FileInfo : FileSystemInfo
{
- private string _name;
-
private FileInfo() { }
public FileInfo(string fileName)
FullPath = isNormalized ? fullPath ?? originalPath : Path.GetFullPath(fullPath);
_name = fileName ?? Path.GetFileName(originalPath);
- DisplayPath = originalPath;
- }
-
- public override string Name
- {
- get { return _name; }
}
public long Length
{
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
}
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)
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)
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)
// 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)
{
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);
}
}
// 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()
{
}
// Full path of the directory/file
- public virtual string FullName
- {
- get
- {
- return FullPath;
- }
- }
+ public virtual string FullName => FullPath;
public string Extension
{
}
}
- // 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
{
return CreationTimeCore.UtcDateTime;
}
-
set
{
CreationTimeCore = File.GetUtcDateTimeOffset(value);
{
get
{
- // depends on the security check in get_LastAccessTimeUtc
return LastAccessTimeUtc.ToLocalTime();
}
set
{
return LastAccessTimeCore.UtcDateTime;
}
-
set
{
LastAccessTimeCore = File.GetUtcDateTimeOffset(value);
{
get
{
- // depends on the security check in get_LastWriteTimeUtc
return LastWriteTimeUtc.ToLocalTime();
}
-
set
{
LastWriteTimeUtc = value.ToUniversalTime();
{
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;
}
}
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) :
+++ /dev/null
-// 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;
- }
- }
-}
}
[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());
+ }
+
}
}
--- /dev/null
+// 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));
+ }
+ }
+}
<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 -->