18

How to check if one path is a child of another path?
Just checking for substring is not a way to go, because there can items such as . and .., etc

Stecya
  • 22,896
  • 10
  • 72
  • 102
user626528
  • 13,999
  • 30
  • 78
  • 146

9 Answers9

19

Unfortunately it's not as simple as StartsWith.

Here's a better answer, adapted from this duplicate question. I've made it an extension method for ease of use. Also using a brute-force catch as just about any method that accesses the file system can fail based on user permissions.

public static bool IsSubDirectoryOf(this string candidate, string other)
{
    var isChild = false;
    try
    {
        var candidateInfo = new DirectoryInfo(candidate);
        var otherInfo = new DirectoryInfo(other);

        while (candidateInfo.Parent != null)
        {
            if (candidateInfo.Parent.FullName == otherInfo.FullName)
            {
                isChild = true;
                break;
            }
            else candidateInfo = candidateInfo.Parent;
        }
    }
    catch (Exception error)
    {
        var message = String.Format("Unable to check directories {0} and {1}: {2}", candidate, other, error);
        Trace.WriteLine(message);
    }

    return isChild;
}
Community
  • 1
  • 1
Charlie
  • 15,069
  • 3
  • 64
  • 70
  • 2
    Swallowing the exception (without knowing how this method is used) seems like a bad practice. For example assume the method is used to prevent uploading information from a certain folder containing sensitive data, now when an exception happens the data will be uploaded. – Herman Jun 10 '19 at 11:02
  • 2
    This also does not work for case sensitive paths. `IsSubDirectoryOf(@"c:\a\b", @"c:\A")` returns false – Gerd K Mar 12 '20 at 10:39
  • 5
    This answer does not address why the seemingly obvious solution `.StartsWith` doesn't work, rather it just states that and explains the more complex solution instead (-1) – Hele May 13 '20 at 23:50
  • Note that this will not work if you attempt to compare paths of different casing and some other edge cases, unfortunately DirectoryInfo [does not handle all of the quirks of Windows's file systems.](https://stackoverflow.com/q/1794025) – jrh May 17 '20 at 15:44
  • 2
    Both the directories have to exist for this method to work, it's a serious limitation. – user626528 Dec 01 '20 at 21:32
  • Why not use `System.IO.Path.GetFullPath`? – GoWiser Nov 11 '22 at 09:54
14

Any string-based solution is potentially subject to directory traversal attacks or correctness issues with things like trailing slashes. Unfortunately, the .NET Path class does not provide this functionality, however the Uri class does, in the form of Uri.IsBaseOf().

    Uri potentialBase = new Uri(@"c:\dir1\");

    Uri regular = new Uri(@"c:\dir1\dir2");

    Uri confusing = new Uri(@"c:\temp\..\dir1\dir2");

    Uri malicious = new Uri(@"c:\dir1\..\windows\system32\");

    Console.WriteLine(potentialBase.IsBaseOf(regular));   // True
    Console.WriteLine(potentialBase.IsBaseOf(confusing)); // True
    Console.WriteLine(potentialBase.IsBaseOf(malicious)); // False
Oren Melzer
  • 749
  • 4
  • 7
  • 4
    `IsBaseOf` does not seem to work for this. Given the inputs `'C:\somerandomdir'` and `'C:\someotherdir'`, I'm getting a true result. – jpmc26 Apr 03 '17 at 18:11
  • 3
    @jpmc26, That's because you don't have a trailing slash. There is no way to know that 'somerandomdir' is a directory name and not a file name. If you want to handle this case, add a trailing slash prior to the call. – Oren Melzer May 05 '17 at 23:50
  • 11
    Why does it matter? A file can't be the base of another file, anyway. Why does `IsBaseOf` even make such a weird guess as chopping off what it thinks is a file name when that clearly isn't the question the caller asked? If there's caveats and weird details like this to worry about, your answer should at least address them. – jpmc26 May 06 '17 at 00:04
  • This is the safest and best solution – Gene Pavlovsky Apr 01 '19 at 15:13
  • 1
    Why not use `System.IO.Path.GetFullPath`? You imply that it can't be used. I am curious as to why. – GoWiser Nov 11 '22 at 09:51
2

I've used an extension method like this:

    /// <summary>
    /// Check if a directory is the base of another
    /// </summary>
    /// <param name="root">Candidate root</param>
    /// <param name="child">Child folder</param>
    public static bool IsBaseOf(this DirectoryInfo root, DirectoryInfo child)
    {
        var directoryPath = EndsWithSeparator(new Uri(child.FullName).AbsolutePath);
        var rootPath = EndsWithSeparator(new Uri(root.FullName).AbsolutePath);
        return directoryPath.StartsWith(rootPath, StringComparison.OrdinalIgnoreCase);
    }

    private static string EndsWithSeparator(string absolutePath)
    {
        return absolutePath?.TrimEnd('/','\\') + "/";
    }
Siderite Zackwehdex
  • 6,293
  • 3
  • 30
  • 46
  • Why not use `System.IO.Path.GetFullPath`? – GoWiser Nov 11 '22 at 09:53
  • Quoting from the documentation: "if path does exist, the caller must have permission to obtain path information for path. Note that unlike most members of the Path class, this method accesses the file system." Granted, since my extension method receives DirectoryInfo instances, that's already a given and FullName returns kind of the same thing. There is an argument to creating an IsBase extension that works on strings alone, but I am too lazy for that. – Siderite Zackwehdex Nov 11 '22 at 13:22
  • I created it below after asking you the question. It works with non-existing paths too. Thank you for your reply. – GoWiser Nov 11 '22 at 14:19
0

Here is my solution:

// length parentDirectory <= length subDirectory
static private bool isParentDirectory(string parentDirectory, string subDirectory)
{
    if (!subDirectory.Contains(parentDirectory)) return false;

    string[] folders1 = subDirectory.Split('\\');
    string[] folders2 = parentDirectory.Split('\\');

    for (int i = 0; i < folders2.Count(); i++)
    {
        if (folders2[i] != folders1[i])
            return false;
    }
    return true;
}
Peter Csala
  • 17,736
  • 16
  • 35
  • 75
  • Welcome to StackOverflow. Please provide some explanation as well, not just code. – Peter Csala Jun 21 '21 at 11:01
  • This is problematic for one thing in that it assumes the file separator is `\`, when it could be .Net Core running on linux where the file separator is `/`. It also seems pointless to split, then compare each segment. Isn't that the same as a `StartsWith`? – ErikE Dec 13 '21 at 20:53
0

IsSubPath returns true if child is a subdirectory of parent.

public static bool IsSubPath(string parent, string child)
{
    try
    {
        parent = Path.GetFullPath(parent);
        if (!parent.EndsWith(Path.DirectorySeparatorChar.ToString()))
            parent = parent + Path.DirectorySeparatorChar;
        child = Path.GetFullPath(child);
        if (child.Length <= parent.Length)
            return false;
        if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            return child.StartsWith(parent, StringComparison.OrdinalIgnoreCase);
        else
            return child.StartsWith(parent);
    }
    catch
    {
        return false;
    }
}

The function also works with non-existing paths.

GoWiser
  • 857
  • 6
  • 20
-1

I have found that this works for windows:

if (pathA.Equals(pathB, StringComparison.OrdinalIgnoreCase) ||
    pathA.StartsWith(pathB + "\\", StringComparison.OrdinalIgnoreCase))

If your paths might have trailing characters, you could normalize them like this first:

pathA = Path.GetFullPath(pathA);
pathB = Path.GetFullPath(pathB);
frankl5150
  • 21
  • 1
  • 3
-1

In C# you can do it like this:

string cp = Path.GetFullPath(childPath);
string pp = Path.GetFullPath(parentPath);

if(pp.StartsWith(cp))
    return true;
else
    return false;
techExplorer
  • 810
  • 7
  • 16
  • 3
    I would recommend using the overload of "StartsWith" to ignore casing. – Chad Jul 25 '13 at 01:21
  • @Chad GetFullPath will return in same case for both so no use to ignore case here – techExplorer Apr 08 '17 at 18:56
  • @techExplorer I just tested if `GetFullPath` returns the same case (when the case differs) and it does not. There is also the problem of comparing parent `C:\asd1` with child `C:\asd11`. – GoWiser Nov 11 '22 at 11:17
-2

Had the same issue. You can use StartWith() if you have the path as string

if (pathA.StartsWith (pathB + "\\")) {

Though I am not sure if it is cross platform or not, but it does work on PC

Akhil Gupta
  • 101
  • 1
  • 8
-3

This would be one way to go, you have path A and B, convert them to full paths with the Path.GetFullPath() function. Next check if one of the full paths is a starting substring of the other.

So that would be

if (Path.GetFullPath(A).StartsWith(Path.GetFullPath(B)) ||
    Path.GetFullPath(B).StartsWith(Path.GetFullPath(A)))
   { /* ... do your magic ... */ }
Captain Coder
  • 798
  • 5
  • 12
  • But that won't work if on Windows and one path is lower case and the other upper case. – Mårten Wikström Aug 27 '12 at 17:57
  • Yup, its a pickle, the simple solution would be to ToUpperInvariant() the lot before comparing, but that should _only_ be done on Windows. I've played around with GetFullPath() for a bit, and it does take elements from the input string into the output. It doesn't look at the filesystem to correctly case elements of the input string. So there is no way to do it platform independantly with GetFullPath()+ToUpperInvariant(). I'm not a fan of giving up compatibility, so I would suggest checking the platform in the Environment class and then use different checks to fix this issue. – Captain Coder Sep 04 '12 at 06:53
  • 15
    Does this work correctly if `A` is `C:\my\dir` and `B` is `C:\my\dir2`? That should be `false`, but I think `Path.GetFullPath(B).StartsWith(Path.GetFullPath(A))` would be `true`. – jpmc26 Apr 01 '14 at 19:00
  • 2
    You could append ending slashes onto actual directory names. Then starts with would work if not case sensitive – pinkfloydx33 Apr 29 '14 at 03:30
  • @jrh Only the user who asked the question can unaccept an answer. – jpmc26 Aug 08 '17 at 20:28
  • 2
    @jrh I believe @pinkfloydx33's suggestion fixes the issue in this answer. It would be great if the answer was edited to include it but `.StartsWith` seems to be a decent, simpler alternative to other answers posted here. More importantly, other answers don't provide any solid reason why `.StartsWith` won't work. The second highest voted answer by @Charlie doesn't address why `.StartsWith` doesn't work, it just declares it. – Hele May 13 '20 at 23:46
  • @Hele [jpmc26's comment](https://stackoverflow.com/questions/8091829/how-to-check-if-one-path-is-a-child-of-another-path/8091865?noredirect=1#comment34757816_8091865) covered one edge case, `Does this work correctly if A is C:\my\dir and B is C:\my\dir2? That should be false, but I think Path.GetFullPath(B).StartsWith(Path.GetFullPath(A)) would be true.` -- Another example, C:\abcd would be treated as a child of C:\ab, even though they have no relation to another. C:\abcd is not a subfolder of C:\ab, but StartsWith can't tell the difference, hope that explanation helps. – jrh May 14 '20 at 02:12
  • @Hele appending ending slashes would fix that specific C:\ab and C:\abcd problem but paths don't necessarily only have \\ and directory names in them, sometimes they have ".." and I don't remember if GetFullPath resolves those, so now another condition is this will only work on absolute paths, IMO string compare operations are not the way to go for handling paths. DirectoryInfo is what I would recommend. – jrh May 14 '20 at 02:25
  • Just checked, Path.GetFullPath does resolve "..", but I'm still not thrilled with the solution of manually concatenating slashes and there might've been another thing (it's been a while), it looks like Path.GetFullPath won't change the ending slash, so if you pass a path with an ending slash, the path it returns will have one, otherwise it won't. IMO the slash concatenation is harder to work with than `DirectoryInfo`. – jrh May 14 '20 at 02:47
  • 1
    @jrh As you found, GetFullPath handles "..". And the trailing slash issue is solved simply by doing `$"{input.TrimEnd(Path.DirectorySeparatorChar)}{Path.DirectorySeparatorChar)}"`. I understand why you feel that DirectoryInfo is the "right" way of doing it, but it's much slower and often unnecessary. In addition, `.StartsWith` seems to be the intuitive solution and I don't think this answer deserves a negative score. – Hele May 16 '20 at 00:06
  • @Hele Since I unfortunately can't remember my original test cases, I have to admit that if this answer was edited to have the manual `\\` concatenation, there isn't anything concrete that I can think of offhand that would go wrong. Without the edit, IMO this answer certainly deserves its downvotes as it's highly deceptive (it will appear to work, until it's presented with C:\abcd vs C:\ab). Personally I disagree that string concatenation is intuitive for paths in general; also how did you measure and find out that DirectoryInfo is much slower? I'm surprised that there's much overhead involved. – jrh May 16 '20 at 19:55
  • @jrh You're right, without the edit this answer still deserves a negative score. What I intended to convey in my previous comment is that the idea of using `.StartsWith` in itself does not deserve a negative score. I didn't measure the performance of DirectoryInfo, but it is expected to be much slower than a simple string compare since it involves disk accesses (_especially_ if the disk is a network drive). Even if DirectoryInfo does not access disk at all (which is very unlikely), that's still an implementation detail and may change with future .NET versions. – Hele May 23 '20 at 08:21