2
\$\begingroup\$

I am working on a "comprehensive" library for use in my internal applications and I have created a working method (as far as all of my testing has shown thus far) to ensure that a file/directory path is - or, at least, could be - legitimate and should be accessible to any user of the same application. NOTE: These are all internal systems not intended for public use or consumption.

I've tried to pull together bits of information/code I've found that address certain aspects of the issue into a "single" method, part of which involves converting an individual user's mapped drives to full UNC paths (U:\PublicFolder\SomeFile.txt becomes \\SERVERNAME\Share\PublicFolder\SomeFile.txt). On the other hand, if the drive is a local, physical drive on the user's machine, I don't want to convert that to UNC (\\COMPUTERNAME\C$\SomeFolder\SomeFile.txt), but instead retain the absolute path to the local drive (C:\SomeFolder\SomeFile.txt) to prevent issues with access privileges. This is what I've come up with, but I'm wondering if this code is a bit too ambitious or overly contrived.

Public Enum PathType File Directory End Enum Public Shared Function GetRealPath(ByVal file As IO.FileInfo) As String Return GetRealPath(file.FullName, PathType.File) End Function Public Shared Function GetRealPath(ByVal folder As IO.DirectoryInfo) As String Return GetRealPath(folder.FullName, PathType.Directory) End Function Public Shared Function GetRealPath(ByVal filePath As String, ByVal pathType As PathType) As String Dim FullPath As String = String.Empty If filePath Is Nothing OrElse String.IsNullOrEmpty(filePath) Then Throw New ArgumentNullException("No path specified") Else If filePath.IndexOfAny(IO.Path.GetInvalidPathChars) >= 0 Then Throw New ArgumentException("The specified path '" & filePath & "' is invalid") Else If pathType = PathType.File Then Try Dim TempFile As New IO.FileInfo(filePath) If TempFile.Name.IndexOfAny(Path.GetInvalidFileNameChars) >= 0 Then Throw New ArgumentException("The specified file name '" & filePath & "' is invalid") End If TempFile = Nothing Catch ex As Exception Throw New ArgumentException("The specified file name '" & filePath & "' is invalid", ex) End Try End If ' The path should not contain any invalid characters. Start trying to populate the FullPath variable. If IO.Path.IsPathRooted(filePath) Then FullPath = filePath Else Try FullPath = IO.Path.GetFullPath(filePath) Catch ex As Exception Throw New ArgumentException("The specified path '" & filePath & "' is invalid", ex) End Try End If If Not FullPath.StartsWith("\\") Then Dim PathRoot As String = IO.Path.GetPathRoot(FullPath) If PathRoot Is Nothing OrElse String.IsNullOrEmpty(PathRoot) Then FullPath = String.Empty Throw New ArgumentException("The specified path '" & filePath & "' is invalid") Else If Not IO.Directory.GetLogicalDrives.Contains(PathRoot) Then FullPath = String.Empty Throw New ArgumentException("The specified path '" & filePath & "' is invalid. Drive '" & PathRoot & "' does not exist.") Else Dim CurrentDrive As New System.IO.DriveInfo(PathRoot) If CurrentDrive.DriveType = DriveType.Network Then Using HKCU As Microsoft.Win32.RegistryKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey("Network\" & FullPath(0)) If Not HKCU Is Nothing Then FullPath = HKCU.GetValue("RemotePath").ToString() & FullPath.Remove(0, 2).ToString() End If End Using ElseIf Not CurrentDrive.DriveType = DriveType.NoRootDirectory AndAlso Not CurrentDrive.DriveType = DriveType.Unknown Then Dim SubstPath As String = String.Empty If IsSubstPath(FullPath, SubstPath) Then FullPath = SubstPath End If Else FullPath = String.Empty Throw New ArgumentException("The specified path '" & filePath & "' is invalid. Drive '" & CurrentDrive.Name & "' does not exist.") End If End If End If End If End If End If Return FullPath End Function <DllImport("kernel32.dll", SetLastError:=True)> Private Shared Function QueryDosDevice(ByVal lpDeviceName As String, ByVal lpTargetPath As System.Text.StringBuilder, ByVal ucchMax As Integer) As UInteger End Function Private Shared Function IsSubstPath(ByVal pathToTest As String, <Out> ByRef realPath As String) As Boolean Dim PathInformation As System.Text.StringBuilder = New System.Text.StringBuilder(250) Dim DriveLetter As String = Nothing Dim WinApiResult As UInteger = 0 realPath = Nothing Try ' Get the drive letter of the path DriveLetter = IO.Path.GetPathRoot(pathToTest).Replace("\", "") Catch ex As ArgumentException Return False End Try WinApiResult = QueryDosDevice(DriveLetter, PathInformation, 250) If WinApiResult = 0 Then ' For debugging Dim LastWinError As Integer = Marshal.GetLastWin32Error() Return False End If ' If drive is SUBST'ed, the result will be in the format of "\??\C:\RealPath\". If PathInformation.ToString().StartsWith("\??\") Then Dim RealRoot As String = PathInformation.ToString().Remove(0, 4) RealRoot += If(PathInformation.ToString().EndsWith("\"), "", "\") realPath = IO.Path.Combine(RealRoot, pathToTest.Replace(IO.Path.GetPathRoot(pathToTest), "")) Return True End If realPath = pathToTest Return False End Function 

TESTING DONE

I've run this through a few different tests, although I'm certain I've not been exhaustive in coming up with ways to make it break. Here are the details I can remember:

On my computer, drive S: is mapped to \\SERVERNAME\Accounts\

I've declared the following variables for use during my testing.

Dim TestFile As IO.FileInfo Dim TestFolder As IO.DirectoryInfo Dim Path As String 

INDIVIDUAL TESTS/RESULTS


' Existing Directory TestFolder = New IO.DirectoryInfo("S:\EXE\0984\") Path = Common.Utility.GetRealPath(TestFolder) 

Correctly returns \\SERVERNAME\Accounts\EXE\0984\


' Existing File TestFile = New IO.FileInfo("S:\EXE\0984\CPI.txt") Path = Common.Utility.GetRealPath(TestFile) 

Correctly returns \\SERVERNAME\Accounts\EXE\0984\CPI.txt


' Not actually a file, but it should return the UNC path TestFile = New IO.FileInfo("S:\EXE\0984") Path = Common.Utility.GetRealPath(TestFile) 

Correctly returns \\SERVERNAME\Accounts\EXE\0984


' Directory does not exist, but it should return the absolute path TestFolder = New IO.DirectoryInfo("C:\EXE\0984\") Path = Common.Utility.GetRealPath(TestFolder) 

Correctly returns C:\EXE\0984\


' Random String TestFile = New IO.FileInfo("Can I make it break?") 

Throws an immediate exception before getting to the GetRealPath() method due to illegal characters in the path (?)


' Random String Path = Common.Utility.GetRealPath("Can I make it break?", Common.Utility.PathType.File) 

Throws exception from inside the GetRealPath() method when attempting to convert the String value to an IO.FileInfo object (line 29 in the method's code posted above) due to illegal characters in the path (?)


' Random String Path = Common.Utility.GetRealPath("Can I make it break?", Common.Utility.PathType.Directory) 

Throws exception from inside the GetRealPath() method when attempting to call IO.Path.GetFullPath() on the String value (line 46 in the method's code posted above) due to illegal characters in the path (?)


' Random String Path = Common.Utility.GetRealPath("Can I make it break", Common.Utility.PathType.Directory) ' AND Path = Common.Utility.GetRealPath("Can I make it break", Common.Utility.PathType.File) 

"Correctly" returns the path to a subfolder of the Debug folder of my project: D:\Programming\TestApp\bin\Debug\Can I make it break

I'm not 100% certain that's the behavior I want, but it's technically correct, and it makes sense for situations where relative paths can come into play.


Heck, the act of posting these examples has already started answering a few questions in my own head and helped me to think through this a bit better.

Admittedly, I've thus far been unable to fully test the SUBST conditions because I don't have any drives that have been SUBSTed and I've been unable thus far to successfully SUBST a path that shows up as a valid drive on my Windows 10 machine.


EDIT

I've successfully tested the SUBST condition on my local machine (see how my ignorance and "over-confidence" caused me some grief in my question on SO). It looks like this is all working correctly, even though, in the end, I may choose to make a few minor modifications, including:

  • I may have to add a parameter to define whether or not I want to allow relative paths to be expanded, and/or possibly check for an appropriate character sequence (./, /, .., etc.) at the start of the string before "approving" the return value. Otherwise, pretty much any string value passed in could potentially result in a "legitimate" path.
  • I've been strongly considering making the "workhorse" overload (GetRealPath(String, PathType)) a Private method (along with the PathType Enum) to allow the validation intrinsic to the IO.FileInfo and IO.DirectoryInfo objects help prevent some of the "unexpected" or "unintended" results from allowing any random String input, such as in the last example.
\$\endgroup\$
4
  • 2
    \$\begingroup\$ If you wouldn't mind, please explain the downvote. If this question is not appropriate for this site in some way, I will gladly delete it. I'm honestly just looking for some insight from those more experienced than myself. \$\endgroup\$ Commented Oct 9, 2019 at 19:37
  • 1
    \$\begingroup\$ You tell us I have created a working method (as far as all of my testing has shown thus far): do you mind sharing these tests with us? Also, since you have a lot of edge cases, it's imperative you document the function to show consumers the specification. \$\endgroup\$ Commented Oct 9, 2019 at 19:52
  • \$\begingroup\$ I'll happily edit in some test/result information as far as I'm able to remember it. I haven't explicitly kept those tests, so I may have to "fudge" (and, of course, obfuscate) a little. \$\endgroup\$ Commented Oct 9, 2019 at 19:55
  • 1
    \$\begingroup\$ @dfhwze - Thank you for asking for the examples of testing. I've edited some into the question and, in so doing, I've already found some places where I can do better, as well as "remembered" some issues I had forgotten I wanted to address. \$\endgroup\$ Commented Oct 9, 2019 at 21:03

1 Answer 1

1
\$\begingroup\$

Focusing only on GetRealPath

  • You can save some level of indentation by returning early. The code would become easier to read.
  • The check If TempFile.Name.IndexOfAny(Path.GetInvalidFileNameChars) >= 0 Then is superflous because the constructor of FileInfo throws an ArgumentException if there are any invalid chars in the filename.
  • FileInfo doesn't hold unmanaged ressources hence you don't need to set it to Nothing.
  • It is always better to catch specific exceptions.
  • Throwing an Exception inside a If block makes the Else redundant.
  • Checking if a string Is Nothing OrElse IsNullOrEmpty can be replaced by just the call to IsNullOrEmpty.
  • You don't need to set FullPath = String.Empty if at the next line of code you are throwing an exception.
  • Althought VB.NET is case insensitiv you should name your variables using camelCase casing.

Summing up the mentioned changes (except for the specific exception part) will look like so

Public Shared Function GetRealPath(ByVal filePath As String, ByVal pathType As PathType) As String Dim fullPath As String = String.Empty If String.IsNullOrEmpty(filePath) Then Throw New ArgumentNullException("No path specified") End If If filePath.IndexOfAny(IO.Path.GetInvalidPathChars) >= 0 Then Throw New ArgumentException("The specified path '" & filePath & "' is invalid") End If If pathType = PathType.File Then Try Dim tempFile As New IO.FileInfo(filePath) Catch ex As Exception Throw New ArgumentException("The specified file name '" & filePath & "' is invalid", ex) End Try End If ' The path should not contain any invalid characters. Start trying to populate the FullPath variable. If IO.Path.IsPathRooted(filePath) Then fullPath = filePath Else Try fullPath = IO.Path.GetFullPath(filePath) Catch ex As Exception Throw New ArgumentException("The specified path '" & filePath & "' is invalid", ex) End Try End If If fullPath.StartsWith("\\") Then Return fullPath End If Dim pathRoot As String = IO.Path.GetPathRoot(fullPath) If String.IsNullOrEmpty(pathRoot) Then Throw New ArgumentException("The specified path '" & filePath & "' is invalid") End If If Not IO.Directory.GetLogicalDrives.Contains(pathRoot) Then Throw New ArgumentException("The specified path '" & filePath & "' is invalid. Drive '" & pathRoot & "' does not exist.") End If Dim currentDrive As New System.IO.DriveInfo(pathRoot) If currentDrive.DriveType = DriveType.Network Then Using HKCU As Microsoft.Win32.RegistryKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey("Network\" & fullPath(0)) If Not HKCU Is Nothing Then fullPath = HKCU.GetValue("RemotePath").ToString() & fullPath.Remove(0, 2).ToString() End If End Using ElseIf Not currentDrive.DriveType = DriveType.NoRootDirectory AndAlso Not currentDrive.DriveType = DriveType.Unknown Then Dim SubstPath As String = String.Empty If IsSubstPath(fullPath, SubstPath) Then fullPath = SubstPath End If Else Throw New ArgumentException("The specified path '" & filePath & "' is invalid. Drive '" & currentDrive.Name & "' does not exist.") End If Return fullPath End Function 
\$\endgroup\$

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.