-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
"io/fs/filepath" package: utility functions for filename path (#6)
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
Showing
2 changed files
with
146 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |