How to check if one path is a child of another path?
10,658
Solution 1
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;
}
Solution 2
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
Solution 3
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('/','\\') + "/";
}
Related videos on Youtube
Author by
user626528
Updated on June 04, 2022Comments
-
user626528 almost 2 years
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 -
Mårten Wikström over 11 yearsBut that won't work if on Windows and one path is lower case and the other upper case.
-
Captain Coder over 11 yearsYup, 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.
-
Chad almost 11 yearsI would recommend using the overload of "StartsWith" to ignore casing.
-
jpmc26 about 10 yearsDoes this work correctly if
A
isC:\my\dir
andB
isC:\my\dir2
? That should befalse
, but I thinkPath.GetFullPath(B).StartsWith(Path.GetFullPath(A))
would betrue
. -
pinkfloydx33 almost 10 yearsYou could append ending slashes onto actual directory names. Then starts with would work if not case sensitive
-
jpmc26 about 7 years
IsBaseOf
does not seem to work for this. Given the inputs'C:\somerandomdir'
and'C:\someotherdir'
, I'm getting a true result. -
techExplorer about 7 years@Chad GetFullPath will return in same case for both so no use to ignore case here
-
Oren Melzer almost 7 years@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.
-
jpmc26 almost 7 yearsWhy 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 over 6 years@jrh Only the user who asked the question can unaccept an answer.
-
Gene Pavlovsky about 5 yearsThis is the safest and best solution
-
Herman almost 5 yearsSwallowing 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.
-
Gerd K about 4 yearsThis also does not work for case sensitive paths.
IsSubDirectoryOf(@"c:\a\b", @"c:\A")
returns false -
Hele almost 4 years@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 almost 4 yearsThis 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) -
jrh almost 4 years@Hele jpmc26's comment 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 almost 4 years@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 almost 4 yearsJust 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
. -
Hele almost 4 years@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. -
jrh almost 4 years@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 almost 4 yearsNote 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.
-
Hele almost 4 years@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. -
user626528 over 3 yearsBoth the directories have to exist for this method to work, it's a serious limitation.
-
Peter Csala almost 3 yearsWelcome to StackOverflow. Please provide some explanation as well, not just code.
-
ErikE over 2 yearsThis 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`?