'check if given path is a subdirectory of another in golang

Say we have two paths:

c:\foo\bar\baz and c:\foo\bar

Is there any package/method that will help me determine if one is a subdirectory of another? I am looking at a cross-platform option.

go


Solution 1:[1]

You could try and use path.filepath.Rel():

func Rel(basepath, targpath string) (string, error)

Rel returns a relative path that is lexically equivalent to targpath when joined to basepath with an intervening separator.
That is, Join(basepath, Rel(basepath, targpath)) is equivalent to targpath itself

That means Rel("c:\foo\bar", "c:\foo\bar\baz") should be baz, meaning a subpath completely included in c:\foo\bar\baz, and without any '../'.
The same would apply for unix paths.

That would make c:\foo\bar\baz a subdirectory of c:\foo\bar.

Solution 2:[2]

You can use the function path.filepath.Match()

Match reports whether name matches the shell file name pattern.

For example:

pattern := "C:\foo\bar" + string(filepath.Separator) + "*"
matched, err := filepath.Match(pattern, "C:\foo\bar\baz")

Where matched should be true.

Solution 3:[3]

I haven't found a reliable solution for all types of paths, but the best you can get is by using filepath.Rel as VonC suggested.

It works if both filepaths are either absolute or relative (mixing is not allowed) and works on both Windows and Linux:

func SubElem(parent, sub string) (bool, error) {
    up := ".." + string(os.PathSeparator)

    // path-comparisons using filepath.Abs don't work reliably according to docs (no unique representation).
    rel, err := filepath.Rel(parent, sub)
    if err != nil {
        return false, err
    }
    if !strings.HasPrefix(rel, up) && rel != ".." {
        return true, nil
    }
    return false, nil
}

Absolute windows paths that start with a drive letter will require an additional check though.

Solution 4:[4]

Try this code. This checks if either is a sub-directory of the other. Try changing values of both base and path and the results should be valid.

package main

import (
    "fmt"
    "path/filepath"
    "strings"
)

func main() {
    base := "/b/c/"
    path := "/a/b/c/d"

    if len(base) > len(path) {
        base, path = path, base
    }

    rel, err := filepath.Rel(base, path)
    fmt.Printf("Base %q: Path %q: Rel %q Err %v\n", base, path, rel, err)
    if err != nil {
        fmt.Println("PROCEED")
        return
    }

    if strings.Contains(rel, "..") {
        fmt.Println("PROCEED")
        return
    }

    fmt.Println("DENY")
}

Solution 5:[5]

If you first canonicalize both paths by calling filepath.EvalSymlinks() and filepath.Abs() on them, you can simply append a '/' to each one, since the UNIX kernel itself forbids a '/' within a path component. At this point you can simply use strings.HasPrefix() on the two paths, in either order.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 VonC
Solution 2 Oleg Burov
Solution 3 maja
Solution 4 dpaks
Solution 5 AbuNassar