From e86b61c7b653a3946af9c06cd86a00ec05e27255 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Mon, 12 Dec 2022 16:39:42 +0100 Subject: [PATCH] Allow relative PATH entries on Go 1.19+ This ignores the new type of error introduced in Go 1.19: `exec.IsDot` --- .github/workflows/go.yml | 2 +- lookpath.go | 14 ++++++++--- lookpath_1.18.go | 10 ++++++++ lookpath_test.go | 52 +++++++++++++++++++++++++++++++++------- 4 files changed, 65 insertions(+), 13 deletions(-) create mode 100644 lookpath_1.18.go diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 9930285..de99252 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -9,7 +9,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest] - go: [1.13, 1.14, 1.15] + go: [1.13, 1.14, 1.15, 1.16, 1.17, 1.18, 1.19, 1.20] runs-on: ${{matrix.os}} steps: diff --git a/lookpath.go b/lookpath.go index 41b7770..e649ca7 100644 --- a/lookpath.go +++ b/lookpath.go @@ -1,9 +1,17 @@ -// +build !windows +//go:build !windows && go1.19 +// +build !windows,go1.19 package safeexec -import "os/exec" +import ( + "errors" + "os/exec" +) func LookPath(file string) (string, error) { - return exec.LookPath(file) + path, err := exec.LookPath(file) + if errors.Is(err, exec.ErrDot) { + return path, nil + } + return path, err } diff --git a/lookpath_1.18.go b/lookpath_1.18.go new file mode 100644 index 0000000..bb4a27e --- /dev/null +++ b/lookpath_1.18.go @@ -0,0 +1,10 @@ +//go:build !windows && !go1.19 +// +build !windows,!go1.19 + +package safeexec + +import "os/exec" + +func LookPath(file string) (string, error) { + return exec.LookPath(file) +} diff --git a/lookpath_test.go b/lookpath_test.go index ee442c0..56b2c80 100644 --- a/lookpath_test.go +++ b/lookpath_test.go @@ -16,25 +16,24 @@ func TestLookPath(t *testing.T) { t.Fatal(wderr) } - paths := []string{ - filepath.Join(root, "_fixtures", "nonexist"), - filepath.Join(root, "_fixtures", "system"), - } - os.Setenv("PATH", strings.Join(paths, string(filepath.ListSeparator))) - if err := os.Chdir(filepath.Join(root, "_fixtures", "cwd")); err != nil { t.Fatal(err) } testCases := []struct { desc string + path []string pathext string arg string wants string wantErr bool }{ { - desc: "no extension", + desc: "no extension", + path: []string{ + filepath.Join(root, "_fixtures", "nonexist"), + filepath.Join(root, "_fixtures", "system"), + }, pathext: "", arg: "ls", wants: filepath.Join(root, "_fixtures", "system", "ls"+winonly(".exe")), @@ -42,6 +41,7 @@ func TestLookPath(t *testing.T) { }, { desc: "with extension", + path: []string{filepath.Join(root, "_fixtures", "system")}, pathext: "", arg: "ls.exe", wants: filepath.Join(root, "_fixtures", "system", "ls.exe"), @@ -49,6 +49,7 @@ func TestLookPath(t *testing.T) { }, { desc: "with path", + path: []string{filepath.Join(root, "_fixtures", "system")}, pathext: "", arg: filepath.Join("..", "system", "ls"), wants: filepath.Join("..", "system", "ls"+winonly(".exe")), @@ -56,6 +57,7 @@ func TestLookPath(t *testing.T) { }, { desc: "with path+extension", + path: []string{filepath.Join(root, "_fixtures", "system")}, pathext: "", arg: filepath.Join("..", "system", "ls.bat"), wants: filepath.Join("..", "system", "ls.bat"), @@ -63,6 +65,7 @@ func TestLookPath(t *testing.T) { }, { desc: "no extension, PATHEXT", + path: []string{filepath.Join(root, "_fixtures", "system")}, pathext: ".com;.bat", arg: "ls", wants: filepath.Join(root, "_fixtures", "system", "ls"+winonly(".bat")), @@ -70,13 +73,18 @@ func TestLookPath(t *testing.T) { }, { desc: "with extension, PATHEXT", + path: []string{filepath.Join(root, "_fixtures", "system")}, pathext: ".com;.bat", arg: "ls.exe", wants: filepath.Join(root, "_fixtures", "system", "ls.exe"), wantErr: false, }, { - desc: "no extension, not found", + desc: "no extension, not found", + path: []string{ + filepath.Join(root, "_fixtures", "nonexist"), + filepath.Join(root, "_fixtures", "system"), + }, pathext: "", arg: "cat", wants: "", @@ -84,6 +92,7 @@ func TestLookPath(t *testing.T) { }, { desc: "with extension, not found", + path: []string{filepath.Join(root, "_fixtures", "system")}, pathext: "", arg: "cat.exe", wants: "", @@ -91,6 +100,7 @@ func TestLookPath(t *testing.T) { }, { desc: "no extension, PATHEXT, not found", + path: []string{filepath.Join(root, "_fixtures", "system")}, pathext: ".com;.bat", arg: "cat", wants: "", @@ -98,15 +108,25 @@ func TestLookPath(t *testing.T) { }, { desc: "with extension, PATHEXT, not found", + path: []string{filepath.Join(root, "_fixtures", "system")}, pathext: ".com;.bat", arg: "cat.exe", wants: "", wantErr: true, }, + { + desc: "relative path", + path: []string{filepath.Join("..", "system")}, + pathext: "", + arg: "ls", + wants: filepath.Join("..", "system", "ls"+winonly(".exe")), + wantErr: false, + }, } for _, tC := range testCases { t.Run(tC.desc, func(t *testing.T) { - os.Setenv("PATHEXT", tC.pathext) + setenv(t, "PATH", strings.Join(tC.path, string(filepath.ListSeparator))) + setenv(t, "PATHEXT", tC.pathext) got, err := LookPath(tC.arg) if tC.wantErr != (err != nil) { @@ -122,6 +142,20 @@ func TestLookPath(t *testing.T) { } } +func setenv(t *testing.T, name, newValue string) { + oldValue, hasOldValue := os.LookupEnv(name) + if err := os.Setenv(name, newValue); err != nil { + t.Errorf("error setting environment variable %s: %v", name, err) + } + t.Cleanup(func() { + if hasOldValue { + _ = os.Setenv(name, oldValue) + } else { + _ = os.Unsetenv(name) + } + }) +} + func winonly(s string) string { if runtime.GOOS == "windows" { return s