Skip to content

Commit

Permalink
"io/fs/filepath" package: utility functions for filename path (#6)
Browse files Browse the repository at this point in the history
The standard library provides the `path/filepath` [1] package that
simplifies some common filesystem path handling tasks like "glob"
pattern matching [2] and many more useful functions.

golib now provides a `io/fs/filepath` package that extends the
`filepath` [1] package with more utilities. For more advanced and
extended features see packages like github.com/spf13/afero [3] instead.
Please note that some functions interact with the underlying filesystem
through on-disk operations!

- IsSubDir(parentPath, subPath string, evalSymlinks bool) (bool, error)
  checks if a path is a subdirectory of another path.

[1]: https://golang.org/pkg/path/filepath
[2]: https://golang.org/pkg/path/filepath/#Glob
[3]: https://github.com/spf13/afero

Closes GH-5
  • Loading branch information
svengreb authored Nov 19, 2020
1 parent d1c7cc2 commit 008c997
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 0 deletions.
71 changes: 71 additions & 0 deletions pkg/io/fs/filepath/filepath.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright (c) 2020-present Sven Greb <development@svengreb.de>
// This source code is licensed under the MIT license found in the LICENSE file.

// Package filepath provides utility functions for manipulating filename paths for the target operating system-defined
// file paths, using either forward slashes or backslashes.
// It extends the "path/filepath" Go standard library package with more utilities. Please note that some functions
// interact with the underlying filesystem through on-disk operations.
package filepath

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

"github.com/svengreb/golib/pkg/io/fs"
)

// IsSubDir checks if a path is a subdirectory of another path.
// The parent path must be an absolute path while the sub-path can either be relative to the parent path or an absolute
// path.
//
// Note that this function interacts with the underlying filesystem through on-disk operations when the "evalSymlinks"
// parameter is "true" in order to achieve required file information!
//nolint:wrapcheck // Returning standard library errors is perfectly fine.
func IsSubDir(parentPath, subPath string, evalSymlinks bool) (bool, error) {
if !filepath.IsAbs(parentPath) {
return false, errors.New("parent path is not an absolute path")
}

pp := parentPath
sp := subPath
if evalSymlinks {
ppExists, fsErr := fs.DirExists(pp)
if fsErr != nil {
return false, fsErr
}
if ppExists {
pp, fsErr = filepath.EvalSymlinks(pp)
if fsErr != nil {
return false, fsErr
}
}

spExists, fsErr := fs.DirExists(sp)
if fsErr != nil {
return false, fsErr
}
if spExists {
sp, fsErr = filepath.EvalSymlinks(sp)
if fsErr != nil {
return false, fsErr
}
}
}

if !filepath.IsAbs(sp) {
sp = filepath.Join(pp, sp)
}

rel, relErr := filepath.Rel(pp, sp)
if relErr != nil {
return false, relErr
}

if !strings.HasPrefix(rel, fmt.Sprintf("..%s", string(filepath.Separator))) && rel != ".." {
return true, nil
}

return false, nil
}
75 changes: 75 additions & 0 deletions pkg/io/fs/filepath/filepath_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright (c) 2020-present Sven Greb <development@svengreb.de>
// This source code is licensed under the MIT license found in the LICENSE file.

package filepath_test

import (
"io/ioutil"
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"

glFilepath "github.com/svengreb/golib/pkg/io/fs/filepath"
)

func TestIsSubDir(t *testing.T) {
dir := t.TempDir()
testCases := []struct {
parentPath string
subPath string
isSubDir bool
isSymlink bool
isInvalid bool
}{
{dir, filepath.Join(dir, "validSubDir"), true, false, false},
{dir, ".", true, false, false},
{dir, t.TempDir(), false, false, false},
{dir, filepath.Join(dir, ".."), false, false, false},
{dir, filepath.Join(t.TempDir(), "symlink"), false, true, false},
{dir, filepath.Join(dir, "..", filepath.Base(dir), filepath.Base(t.TempDir())), true, true, false},
}

for _, tc := range testCases {
if tc.isSymlink && tc.isSubDir {
if err := os.Symlink(tc.parentPath, tc.subPath); err != nil {
assert.Fail(t, "failed to create symlink", "source: %q\ndest: %q\nerror: %v", tc.parentPath, tc.subPath, err)
}
}

isSubDir, err := glFilepath.IsSubDir(tc.parentPath, tc.subPath, tc.isSymlink)

assert.Equal(t, tc.isSubDir, isSubDir)
assert.NoError(t, err)
}
}

func TestIsSubDir_FailWithRegularFileAsDirs(t *testing.T) {
dir := t.TempDir()
file, err := ioutil.TempFile(dir, "*")
if err != nil {
assert.Fail(t, "failed to create file", "error: %v", err)
}
defer func() {
if closeErr := file.Close(); closeErr != nil {
assert.Fail(t, "failed to close file", "file: %q\nerror: %v", file.Name(), closeErr)
}
}()

isSubDirParentDirFile, err := glFilepath.IsSubDir(file.Name(), dir, true)
assert.False(t, isSubDirParentDirFile)
assert.Error(t, err)

isSubDirSubDirFile, err := glFilepath.IsSubDir(dir, file.Name(), true)
assert.False(t, isSubDirSubDirFile)
assert.Error(t, err)
}

func TestIsSubDir_FailWithRelativeParentDir(t *testing.T) {
dir := t.TempDir()
isSubDir, err := glFilepath.IsSubDir(filepath.Base(dir), dir, false)

assert.False(t, isSubDir)
assert.Error(t, err)
}

0 comments on commit 008c997

Please # to comment.