Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

Commit

Permalink
IO: Add GetActualCasing to File and Directory.
Browse files Browse the repository at this point in the history
Uses kernel32 interoped method `GetLongPathName` to determine the
actual casing of file and directory paths on disk as well as network.

Issue-URL: #1086.
  • Loading branch information
Peter Jas committed Jul 1, 2015
1 parent 5a1cc5c commit 36b4113
Show file tree
Hide file tree
Showing 7 changed files with 176 additions and 3 deletions.
37 changes: 35 additions & 2 deletions src/System.IO.FileSystem/src/System/IO/Directory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,41 @@ public static DateTime GetCreationTimeUtc(String path)
{
return File.GetCreationTimeUtc(path);
}


// Gets actual casing of path. The result is string with exact path
// as disk or network.
//
// Your application must have Read permissions to the directory.
//
public static string GetActualCasing(string path)
{
// TODO: Check permissions and throw SecurityException
// once FIleIOPermissions is available on CoreFX.

StringBuilder builder = new StringBuilder(32767);

if (path.StartsWith(@"\\"))
path = @"\\?\UNC" + path.Substring(1);
else
path = @"\\?\" + path;

try
{
Interop.mincore.GetLongPathName(path, builder, builder.Capacity);

string returnedPathTrimmed = builder.Replace(@"\\?\UNC\", @"\\").Replace(@"\\?\", "").ToString();

if (Directory.Exists(returnedPathTrimmed))
return returnedPathTrimmed;
else
throw new Exception();
}
catch (Exception)
{
throw new DirectoryNotFoundException();
}
}

public static void SetLastWriteTime(String path, DateTime lastWriteTime)
{
String fullPath = PathHelpers.GetFullPathInternal(path);
Expand Down Expand Up @@ -604,4 +638,3 @@ public static void Delete(String path, bool recursive)
}
}
}

43 changes: 42 additions & 1 deletion src/System.IO.FileSystem/src/System/IO/File.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Diagnostics;
Expand Down Expand Up @@ -150,7 +151,7 @@ public static FileStream Create(String path, int bufferSize, FileOptions options
return new FileStream(path, FileMode.Create, FileAccess.ReadWrite,
FileShare.None, bufferSize, options);
}

// Deletes a file. The file specified by the designated path is deleted.
// If the file does not exist, Delete succeeds without throwing
// an exception.
Expand Down Expand Up @@ -329,6 +330,46 @@ public static FileAttributes GetAttributes(String path)
return FileSystem.Current.GetAttributes(fullPath);
}

// Gets actual casing of path. The result is string with exact path
// as disk or network.
//
// Your application must have Read permission to the file.
//
public static string GetActualCasing(string path)
{
// TODO: Check permissions and throw SecurityException
// once FIleIOPermissions is available on CoreFX.

StringBuilder builder = new StringBuilder(32767);

if (path.StartsWith(@"\\"))
path = @"\\?\UNC" + path.Substring(1);
else
path = @"\\?\" + path;

try
{
Interop.mincore.GetLongPathName(path, builder, builder.Capacity);

string returnedPathTrimmed = builder.Replace(@"\\?\UNC\", @"\\").Replace(@"\\?\", "").ToString();

if (Directory.Exists(returnedPathTrimmed))
throw new Exception();

// unfortunately, the file part is not corrected by GetLongPathName
FileInfo fileInfo = new FileInfo(returnedPathTrimmed);

return fileInfo.Directory
.GetFileSystemInfos()
.FirstOrDefault(s => s.FullName.Equals(fileInfo.FullName, StringComparison.CurrentCultureIgnoreCase))
.FullName;
}
catch (Exception)
{
throw new FileNotFoundException();
}
}

[System.Security.SecurityCritical]
public static void SetAttributes(String path, FileAttributes fileAttributes)
{
Expand Down
1 change: 1 addition & 0 deletions src/System.IO.FileSystem/src/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"System.Globalization": "4.0.10-beta-*",
"System.IO": "4.0.10-beta-*",
"System.IO.FileSystem.Primitives": "4.0.0-beta-*",
"System.Linq": "4.0.0-beta-*",
"System.Reflection": "4.0.10-beta-*",
"System.Reflection.Primitives": "4.0.0-beta-*",
"System.Resources.ResourceManager": "4.0.0-beta-*",
Expand Down
15 changes: 15 additions & 0 deletions src/System.IO.FileSystem/src/project.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,21 @@
"lib/dotnet/System.IO.FileSystem.Primitives.dll": {}
}
},
"System.Linq/4.0.0-beta-23024": {
"dependencies": {
"System.Runtime": "4.0.20-beta-23024",
"System.Collections": "4.0.10-beta-23024",
"System.Resources.ResourceManager": "4.0.0-beta-23024",
"System.Diagnostics.Debug": "4.0.10-beta-23024",
"System.Runtime.Extensions": "4.0.10-beta-23024"
},
"compile": {
"ref/dotnet/System.Linq.dll": {}
},
"runtime": {
"lib/dotnet/System.Linq.dll": {}
}
},
"System.Private.Uri/4.0.0-beta-23024": {
"runtime": {
"lib/DNXCore50/System.Private.Uri.dll": {}
Expand Down
37 changes: 37 additions & 0 deletions src/System.IO.FileSystem/tests/Directory/GetActualCasing.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.IO;
using Xunit;

public class Directory_GetActualPath
{
[Theory]
[PlatformSpecific(PlatformID.Windows)]
[InlineData(TestInfo.CurrentDirectory)]
[InlineData(@"\\localhost\" + TestInfo.CurrentDirectory.Replace(':', '$'))]
public static void TrueCasingTest_Truthy(string path)
{
Assert.Equal(path, Directory.GetActualCasing(path.ToUpper()));
}

[Theory]
[PlatformSpecific(PlatformID.Windows)]
[InlineData(Environment.SystemDirectory)]
[InlineData(Environment.SystemDirectory.ToUpper())]
public static void TrueCasingTest_Falsy(string path)
{
Assert.NotEqual(path, Directory.GetActualCasing(path));
}

[Theory]
[PlatformSpecific(PlatformID.Windows)]
[InlineData(@"\\rand0m-server\devnul")]
[InlineData(Environment.SystemDirectory + @"\KERNEL32.DlL")]
[InlineData(Environment.SystemDirectory + @"\RANDOM-NON_EXISTING-Slug\")]
public static void TrueCasingTest_Throwy(string path)
{
Assert.Throws(typeof(DirectoryNotFoundException), Directory.GetActualCasing(path));
}
}
44 changes: 44 additions & 0 deletions src/System.IO.FileSystem/tests/File/GetActualCasing.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.IO;
using Xunit;

public class File_GetActualPath
{
[Fact]
[PlatformSpecific(PlatformID.Windows)]
public static void TrueCasingTest_Truthy()
{
string unicodedFileName = Path.Combine(TestInfo.CurrentDirectory, "тратата.text.txt");

File.Create(unicodedFileName).Dispose();

Assert.Equal(unicodedFileName, File.GetActualCasing(unicodedFileName.ToUpper()));

File.Delete(unicodedFileName);

string path = @"\\localhost\" + Environment.SystemDirectory.Replace(':', '$') + @"\kernel32.dll";

Assert.Equal(path, File.GetActualCasing(path.ToUpper()));
}

[Theory]
[PlatformSpecific(PlatformID.Windows)]
[InlineData(Environment.SystemDirectory + @"\KERNEL32.DlL")]
[InlineData(Environment.SystemDirectory.ToUpper() + @"\kernel32.dll")]
public static void TrueCasingTest_Falsy(string path)
{
Assert.NotEqual(path, File.GetActualCasing(path));
}

[Theory]
[PlatformSpecific(PlatformID.Windows)]
[InlineData(@"\\rand0m-server\devnul\blah.txt")]
[InlineData(Environment.SystemDirectory + @"\RANDOM-NON_EXISTING-Slug\KERNEL32_INVALID.DlL")]
public static void TrueCasingTest_Throwy(string path)
{
Assert.Throws(typeof(FileNotFoundException), File.GetActualCasing(path));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
<Compile Include="DirectoryInfo\Delete.cs" />
<Compile Include="DirectoryInfo\Delete_bool.cs" />
<Compile Include="DirectoryInfo\Exists.cs" />
<Compile Include="Directory\GetActualCasing.cs" />
<Compile Include="FileStream\Flush.Sharing.cs" />
<Compile Include="FileStream\Flush_toDisk.cs" />
<Compile Include="FileStream\SafeFileHandle.cs" />
Expand Down Expand Up @@ -72,6 +73,7 @@
<Compile Include="FileStream\ctor_str_fm_fa_fs.cs" />
<Compile Include="FileStream\ctor_str_fm_fa.cs" />
<Compile Include="FileStream\ctor_str_fm.cs" />
<Compile Include="File\GetActualCasing.cs" />
<Compile Include="UnseekableFileStream.cs" />
<Compile Include="FSAssert.cs" />
<Compile Include="FileSystemTest.cs" />
Expand Down

0 comments on commit 36b4113

Please sign in to comment.