From 975308f69b3666d84d0aa0db207e51d51b0e3f68 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Sun, 28 Feb 2021 13:24:33 -0300 Subject: [PATCH] fix: resolve ../ on pattern (#21) * fix: resolve ../ on pattern Signed-off-by: Carlos Alexandro Becker * fix: log Signed-off-by: Carlos Alexandro Becker * fix: do not break api Signed-off-by: Carlos Alexandro Becker * fix: lint Signed-off-by: Carlos Alexandro Becker * fix: docs Signed-off-by: Carlos Alexandro Becker * fix: tonixpath Signed-off-by: Carlos Alexandro Becker * chore: debug Signed-off-by: Carlos Alexandro Becker * chore: debug Signed-off-by: Carlos Alexandro Becker * chore: debug Signed-off-by: Carlos Alexandro Becker * chore: debug Signed-off-by: Carlos Alexandro Becker * chore: debug Signed-off-by: Carlos Alexandro Becker * fix: paths on windows Signed-off-by: Carlos Alexandro Becker * fix: tests Signed-off-by: Carlos Alexandro Becker * fix: tonixpath quotemeta Signed-off-by: Carlos Alexandro Becker * fix: toslash Signed-off-by: Carlos Alexandro Becker * fix: tests Signed-off-by: Carlos Alexandro Becker * fix: tests Signed-off-by: Carlos Alexandro Becker * fix: toslash Signed-off-by: Carlos Alexandro Becker --- glob.go | 52 ++++++++++++++++++++++++++++------------------- glob_test.go | 57 +++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 76 insertions(+), 33 deletions(-) diff --git a/glob.go b/glob.go index 7fe9302..3d0c3ca 100644 --- a/glob.go +++ b/glob.go @@ -6,7 +6,6 @@ import ( "io" "io/fs" "os" - "path" "path/filepath" "strings" @@ -27,6 +26,8 @@ type globOptions struct { matchDirectoriesDirectly bool prefix string + + pattern string } // OptFunc is a function that allow to customize Glob. @@ -43,22 +44,20 @@ func WithFs(f fs.FS) OptFunc { // volume (on windows) if the given pattern is an absolute path. // // Result will also be prepended with the root path or volume. -func MaybeRootFS(pattern string) OptFunc { - if !filepath.IsAbs(pattern) { - return func(opts *globOptions) {} +func MaybeRootFS(opts *globOptions) { + if !filepath.IsAbs(opts.pattern) { + return } - return func(opts *globOptions) { - prefix := "" - if strings.HasPrefix(pattern, separatorString) { - prefix = separatorString - } - if vol := filepath.VolumeName(pattern); vol != "" { - prefix = vol + "/" - } - if prefix != "" { - opts.prefix = prefix - opts.fs = os.DirFS(prefix) - } + prefix := "" + if strings.HasPrefix(opts.pattern, separatorString) { + prefix = separatorString + } + if vol := filepath.VolumeName(opts.pattern); vol != "" { + prefix = vol + "/" + } + if prefix != "" { + opts.prefix = prefix + opts.fs = os.DirFS(prefix) } } @@ -95,14 +94,24 @@ func QuoteMeta(pattern string) string { // toNixPath converts the path to the nix style path // Windows style path separators are escape characters so cause issues with the compiled glob. func toNixPath(s string) string { - return path.Clean(filepath.ToSlash(s)) + return filepath.ToSlash(filepath.Clean(s)) } // Glob returns all files that match the given pattern in the current directory. // If the given pattern indicates an absolute path, it will glob from `/`. +// If the given pattern starts with `../`, it will resolve to its absolute path and glob from `/`. func Glob(pattern string, opts ...OptFunc) ([]string, error) { // nolint:funlen,cyclop var matches []string - options := compileOptions(opts) + + if strings.HasPrefix(pattern, "../") { + p, err := filepath.Abs(pattern) + if err != nil { + return matches, fmt.Errorf("failed to resolve pattern: %s: %w", pattern, err) + } + pattern = filepath.ToSlash(p) + } + + options := compileOptions(opts, pattern) pattern = strings.TrimSuffix(strings.TrimPrefix(pattern, options.prefix), separatorString) matcher, err := glob.Compile(pattern, separatorRune) @@ -178,10 +187,11 @@ func Glob(pattern string, opts ...OptFunc) ([]string, error) { // nolint:funlen, return cleanFilepaths(matches, options.prefix), nil } -func compileOptions(optFuncs []OptFunc) *globOptions { +func compileOptions(optFuncs []OptFunc, pattern string) *globOptions { opts := &globOptions{ - fs: os.DirFS("."), - prefix: "./", + fs: os.DirFS("."), + prefix: "./", + pattern: pattern, } for _, apply := range optFuncs { diff --git a/glob_test.go b/glob_test.go index 92e805e..332f5d1 100644 --- a/glob_test.go +++ b/glob_test.go @@ -25,7 +25,7 @@ func TestGlob(t *testing.T) { // nolint:funlen "glob_test.go", "prefix_test.go", }, matches) - require.Equal(t, "&{fs:. matchDirectoriesDirectly:false prefix:./}", w.String()) + require.Equal(t, "&{fs:. matchDirectoriesDirectly:false prefix:./ pattern:*_test.go}", w.String()) }) t.Run("real with rootfs", func(t *testing.T) { @@ -35,20 +35,49 @@ func TestGlob(t *testing.T) { // nolint:funlen require.NoError(t, err) prefix := "/" - if runtime.GOOS == "windows" { + if isWindows() { prefix = filepath.VolumeName(wd) + "/" } pattern := toNixPath(filepath.Join(wd, "*_test.go")) var w bytes.Buffer - matches, err := Glob(pattern, MaybeRootFS(pattern), WriteOptions(&w)) + matches, err := Glob(pattern, MaybeRootFS, WriteOptions(&w)) require.NoError(t, err) require.Equal(t, []string{ toNixPath(filepath.Join(wd, "glob_test.go")), toNixPath(filepath.Join(wd, "prefix_test.go")), }, matches) - require.Equal(t, fmt.Sprintf("&{fs:%s matchDirectoriesDirectly:false prefix:%s}", prefix, prefix), w.String()) + require.Equal(t, fmt.Sprintf("&{fs:%s matchDirectoriesDirectly:false prefix:%s pattern:%s}", prefix, prefix, pattern), w.String()) + }) + + t.Run("real with rootfs on relative path to parent", func(t *testing.T) { + t.Parallel() + + wd, err := os.Getwd() + require.NoError(t, err) + + dir := filepath.Base(wd) + + prefix := "/" + if isWindows() { + prefix = filepath.VolumeName(wd) + "/" + } + + pattern := "../" + dir + "/*_test.go" + abs, err := filepath.Abs(pattern) + require.NoError(t, err) + + abs = toNixPath(abs) + + var w bytes.Buffer + matches, err := Glob(pattern, MaybeRootFS, WriteOptions(&w)) + require.NoError(t, err) + require.Equal(t, []string{ + toNixPath(filepath.Join(wd, "glob_test.go")), + toNixPath(filepath.Join(wd, "prefix_test.go")), + }, matches) + require.Equal(t, fmt.Sprintf("&{fs:%s matchDirectoriesDirectly:false prefix:%s pattern:%s}", prefix, prefix, abs), w.String()) }) t.Run("real with rootfs on relative path", func(t *testing.T) { @@ -57,13 +86,13 @@ func TestGlob(t *testing.T) { // nolint:funlen pattern := "./*_test.go" var w bytes.Buffer - matches, err := Glob(pattern, MaybeRootFS(pattern), WriteOptions(&w)) + matches, err := Glob(pattern, MaybeRootFS, WriteOptions(&w)) require.NoError(t, err) require.Equal(t, []string{ "glob_test.go", "prefix_test.go", }, matches) - require.Equal(t, "&{fs:. matchDirectoriesDirectly:false prefix:./}", w.String()) + require.Equal(t, "&{fs:. matchDirectoriesDirectly:false prefix:./ pattern:./*_test.go}", w.String()) }) t.Run("real with rootfs on relative path match dir", func(t *testing.T) { @@ -72,12 +101,12 @@ func TestGlob(t *testing.T) { // nolint:funlen pattern := ".github" var w bytes.Buffer - matches, err := Glob(pattern, MaybeRootFS(pattern), MatchDirectoryAsFile, WriteOptions(&w)) + matches, err := Glob(pattern, MaybeRootFS, MatchDirectoryAsFile, WriteOptions(&w)) require.NoError(t, err) require.Equal(t, []string{ ".github", }, matches) - require.Equal(t, "&{fs:. matchDirectoriesDirectly:true prefix:./}", w.String()) + require.Equal(t, "&{fs:. matchDirectoriesDirectly:true prefix:./ pattern:.github}", w.String()) }) t.Run("real with rootfs on relative path match dir", func(t *testing.T) { @@ -86,12 +115,12 @@ func TestGlob(t *testing.T) { // nolint:funlen pattern := ".github/workflows/" var w bytes.Buffer - matches, err := Glob(pattern, MaybeRootFS(pattern), MatchDirectoryAsFile, WriteOptions(&w)) + matches, err := Glob(pattern, MaybeRootFS, MatchDirectoryAsFile, WriteOptions(&w)) require.NoError(t, err) require.Equal(t, []string{ ".github/workflows", }, matches) - require.Equal(t, "&{fs:. matchDirectoriesDirectly:true prefix:./}", w.String()) + require.Equal(t, "&{fs:. matchDirectoriesDirectly:true prefix:./ pattern:.github/workflows/}", w.String()) }) t.Run("simple", func(t *testing.T) { @@ -113,7 +142,7 @@ func TestGlob(t *testing.T) { // nolint:funlen "a/d/file1.txt", "a/nope/file1.txt", }, matches) - require.Equal(t, fmt.Sprintf("&{fs:%+v matchDirectoriesDirectly:false prefix:./}", fsys), w.String()) + require.Equal(t, fmt.Sprintf("&{fs:%+v matchDirectoriesDirectly:false prefix:./ pattern:./a/*/*}", fsys), w.String()) }) t.Run("single file", func(t *testing.T) { @@ -270,7 +299,7 @@ func TestGlob(t *testing.T) { // nolint:funlen t.Run("escaped asterisk", func(t *testing.T) { t.Parallel() - if runtime.GOOS == "windows" { + if isWindows() { t.Skip("can't create paths with * on Windows") } matches, err := Glob("a/\\*/b", WithFs(testFs(t, []string{ @@ -408,3 +437,7 @@ func testFs(tb testing.TB, files, dirs []string) fs.FS { return tmpfs } + +func isWindows() bool { + return runtime.GOOS == "windows" +}