How to determine if a path is fully qualified?

Viewed 1685

Given a path, I need to know if it is a fully qualified path (absolute path).

I know there is a method called System.IO.Path.IsPathRooted but this returns true for paths like:

  • C:Documents
  • /Documents

I have seen a method called IsPathFullyQualified which I am interested in, see here, but unfortunately, it seems it is not recognized under .NET Framework 4.5. So Is there any equivalent method for .NET 4.5?

3 Answers

Full disclaimer: I did not write this code myself and do not own any rights to it. This is based on the .NET Core Source by Microsoft. More info below.

TL;DR: For a Windows system, you may add the following class to your project and then call PathEx.IsPathFullyQualified() to check if the path is fully qualified:

Public Class PathEx
    Public Shared Function IsPathFullyQualified(path As String) As Boolean
        If path Is Nothing Then
            Throw New ArgumentNullException(NameOf(path))
        End If

        Return Not IsPartiallyQualified(path)
    End Function

    Friend Shared Function IsPartiallyQualified(path As String) As Boolean
        If path.Length < 2 Then
            ' It isn't fixed, it must be relative.  There is no way to specify a fixed
            ' path with one character (or less).
            Return True
        End If

        If IsDirectorySeparator(path.Chars(0)) Then
            ' There is no valid way to specify a relative path with two initial slashes or
            ' \? as ? isn't valid for drive relative paths and \??\ is equivalent to \\?\
            Return Not (path.Chars(1) = "?"c OrElse IsDirectorySeparator(path.Chars(1)))
        End If

        ' The only way to specify a fixed path that doesn't begin with two slashes
        ' is the drive, colon, slash format- i.e. C:\
        Return Not ((path.Length >= 3) AndAlso
                    (path.Chars(1) = IO.Path.VolumeSeparatorChar) AndAlso
                    IsDirectorySeparator(path.Chars(2)) AndAlso
                    IsValidDriveChar(path.Chars(0)))
        '           ^^^^^^^^^^^^^^^^
        ' To match old behavior we'll check the drive character for validity as
        ' the path is technically not qualified if you don't have a valid drive.
        ' "=:\" is the "=" file's default data stream.
    End Function

    Friend Shared Function IsDirectorySeparator(c As Char) As Boolean
        Return c = Path.DirectorySeparatorChar OrElse c = Path.AltDirectorySeparatorChar
    End Function

    Friend Shared Function IsValidDriveChar(value As Char) As Boolean
        Return (value >= "A"c AndAlso value <= "Z"c) OrElse
               (value >= "a"c AndAlso value <= "z"c)
    End Function
End Class

Where does this code (and comments) come from?

This was extracted from the .NET Core Source Browser which is publicly available, adapted to work with .NET Framework, and converted to VB.NET.

The .NET Core's IsPathFullyQualified() method uses an internal method called IsPartiallyQualified() to check if the path is fully qualified (not partially qualified). That internal method has different logic for different Operating Systems. This is the Windows version which the above code is based on.

Usage:

Console.WriteLine(PathEx.IsPathFullyQualified("C:Documents"))    ' False
Console.WriteLine(PathEx.IsPathFullyQualified("/Documents"))     ' False
Console.WriteLine(PathEx.IsPathFullyQualified("C:\Documents"))   ' True

Here's another possible method to determine if a Path is Fully qualified and also valid.
This method(*) tries to generate an Uri from a supplied path using Uri.TryCreate(). When this method succeeds, it then inspects the non-public IsDosPath Property, which is set internally by the Uri class when a new Uri is parsed to determine whether it's valid and what kind of resource it represents.

You can see in the .Net Source code that the PrivateParseMinimal() method performs a number of tests to validate the path and also checks whether a DosPath is rooted.

Imports System.Reflection

Private Function PathIsFullyQualified(path As String) As (Valid As Boolean, Parsed As String)
    Dim flags = BindingFlags.GetProperty Or BindingFlags.Instance Or BindingFlags.NonPublic
    Dim uri As Uri = Nothing
    If Uri.TryCreate(path, UriKind.Absolute, uri) Then
        Dim isDosPath = CBool(uri.GetType().GetProperty("IsDosPath", flags).GetValue(uri))
        Return (isDosPath, uri.LocalPath)
    End If
    Return (False, String.Empty)
End Function

* This method returns a Named Tuple: supported from Visual Basic 2017

I tested the following paths; they all return False when I assume they should, except "file://c:/Documents":, but the PathIsFullyQualified method also returns the corresponding Local path, c:\Documents:

Dim isOk1 = PathIsFullyQualified("C:Documents")            'False
Dim isOk2 = PathIsFullyQualified("/Documents")             'False
Dim isOk3 = PathIsFullyQualified("file://c:/Documents")    'True  => isOk3.Parsed = "c:\Documents"
Dim isOk4 = PathIsFullyQualified("\\Documents")            'False
Dim isOk5 = PathIsFullyQualified("..\Documents")           'False
Dim isOk6 = PathIsFullyQualified(".\Documents")            'False
Dim isOk7 = PathIsFullyQualified("\Documents")             'False
Dim isOk8 = PathIsFullyQualified("//Documents")            'False
Dim isOk9 = PathIsFullyQualified(".Documents")             'False
Dim isOkA = PathIsFullyQualified("..Documents")            'False
Dim isOkB = PathIsFullyQualified("http://C:/Documents")    'False
Dim isOkC = PathIsFullyQualified("Cd:\Documents")          'False
Dim isOkD = PathIsFullyQualified("1:\Documents")           'False
Dim isOkE = PathIsFullyQualified("Z:\\Another Path//docs") 'True => isOkE.Parsed = "Z:\Another Path\docs"
Dim isOkF = PathIsFullyQualified(":\\Another Path//docs")  'False

I came across this looking to use this method, but realized that the other answers are overcomplicated.

I looked at MS's source code here: https://source.dot.net/#System.Private.CoreLib/Path.cs,3b4bff90471c3a68

and here: https://referencesource.microsoft.com/#mscorlib/system/io/path.cs,807960f08fca497d

What I noticed was that in the second link, one of the ways to check for IsPathRooted was by checking this: (length >= 2 && path[1] == VolumeSeparatorChar). I simply adapted that check into a fleshed out method.

The .Net source code (link 1) is essentially IsPathFullyQualified => !IsPathRooted, which I don't think went far enough to sanity check. So here is my replacement.

public static bool IsPathFullyQualified(string path)
{
    if (path == null)
        throw new ArgumentNullException(nameof(path));
    return path.Length >= 3 && path[1] == System.IO.Path.VolumeSeparatorChar && ( path[2] == System.IO.Path.DirectorySeparatorChar | path[2] == System.IO.Path.AltDirectorySeparatorChar );
}

EDIT: I realized this short method above doesn't accept UNC paths. So the updated version (which admittedly is just a shortened version of the original answer by eliminating the IsPartiallyQualified method) is below:

public static bool IsPathFullyQualified(string path)
        {
            if (path == null) throw new ArgumentNullException(nameof(path));
            if (path.Length < 2) return false; //There is no way to specify a fixed path with one character (or less).
            if (path.Length == 2 && IsValidDriveChar(path[0]) && path[1] == System.IO.Path.VolumeSeparatorChar) return true; //Drive Root C:
            if (path.Length >= 3 && IsValidDriveChar(path[0]) &&  path[1] == System.IO.Path.VolumeSeparatorChar && IsDirectorySeperator(path[2])) return true; //Check for standard paths. C:\
            if (path.Length >= 3 && IsDirectorySeperator(path[0]) && IsDirectorySeperator(path[1])) return true; //This is start of a UNC path
            return false; //Default
        }

        private static bool IsDirectorySeperator(char c) => c == System.IO.Path.DirectorySeparatorChar | c == System.IO.Path.AltDirectorySeparatorChar;
        private static bool IsValidDriveChar(char c) => c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z';
Related