Improve LookPath
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Fri, 18 Dec 2020 17:20:12 +0000 (18:20 +0100)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Sat, 19 Dec 2020 15:32:22 +0000 (16:32 +0100)
18 files changed:
common/hexec/safeCommand.go [new file with mode: 0644]
create/content.go
go.mod
go.sum
hugolib/js_test.go
hugolib/resource_chain_babel_test.go
hugolib/resource_chain_test.go
markup/asciidocext/convert.go
markup/internal/external.go
markup/pandoc/convert.go
markup/rst/convert.go
modules/client.go
releaser/git.go
releaser/releaser.go
resources/resource_transformers/babel/babel.go
resources/resource_transformers/postcss/postcss.go
scripts/fork_go_templates/main.go
tpl/internal/go_templates/testenv/testenv.go

diff --git a/common/hexec/safeCommand.go b/common/hexec/safeCommand.go
new file mode 100644 (file)
index 0000000..6d5c739
--- /dev/null
@@ -0,0 +1,45 @@
+// Copyright 2020 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package hexec
+
+import (
+       "context"
+
+       "os/exec"
+
+       "github.com/cli/safeexec"
+)
+
+// SafeCommand is a wrapper around os/exec Command which uses a LookPath
+// implementation that does not search in current directory before looking in PATH.
+// See https://github.com/cli/safeexec and the linked issues.
+func SafeCommand(name string, arg ...string) (*exec.Cmd, error) {
+       bin, err := safeexec.LookPath(name)
+       if err != nil {
+               return nil, err
+       }
+
+       return exec.Command(bin, arg...), nil
+}
+
+// SafeCommandContext wraps CommandContext
+// See SafeCommand for more context.
+func SafeCommandContext(ctx context.Context, name string, arg ...string) (*exec.Cmd, error) {
+       bin, err := safeexec.LookPath(name)
+       if err != nil {
+               return nil, err
+       }
+
+       return exec.CommandContext(ctx, bin, arg...), nil
+}
index 13e66820198ab7ad7d9363068e8ac7c70539f72d..9f65a1b4a2bc7a220b6be9d2eb51650ab21730e8 100644 (file)
@@ -21,10 +21,10 @@ import (
 
        "io"
        "os"
-       "os/exec"
        "path/filepath"
        "strings"
 
+       "github.com/gohugoio/hugo/common/hexec"
        "github.com/gohugoio/hugo/hugofs/files"
 
        "github.com/gohugoio/hugo/hugofs"
@@ -106,7 +106,10 @@ func NewContent(
                jww.FEEDBACK.Printf("Editing %s with %q ...\n", targetPath, editor)
 
                editorCmd := append(strings.Fields(editor), contentPath)
-               cmd := exec.Command(editorCmd[0], editorCmd[1:]...)
+               cmd, err := hexec.SafeCommand(editorCmd[0], editorCmd[1:]...)
+               if err != nil {
+                       return err
+               }
                cmd.Stdin = os.Stdin
                cmd.Stdout = os.Stdout
                cmd.Stderr = os.Stderr
diff --git a/go.mod b/go.mod
index ee27120bcd807ebd5ca34b296b0ef25ed20ae17f..3410dbaac20bb102770d4150819fe1f663fd7989 100644 (file)
--- a/go.mod
+++ b/go.mod
@@ -13,6 +13,7 @@ require (
        github.com/bep/gitmap v1.1.2
        github.com/bep/golibsass v0.7.0
        github.com/bep/tmc v0.5.1
+       github.com/cli/safeexec v1.0.0
        github.com/disintegration/gift v1.2.1
        github.com/dlclark/regexp2 v1.4.0 // indirect
        github.com/dustin/go-humanize v1.0.0
diff --git a/go.sum b/go.sum
index 6225d9a2483794e374a4c5417d6535619f16f596..0c9ac732322a305035cb2abfde0a473f51a81e62 100644 (file)
--- a/go.sum
+++ b/go.sum
@@ -100,6 +100,8 @@ github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 h1:SKI1/fuSdodxmNNyVBR8d7X/HuLnRpvvFO0AgyQk764=
 github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
+github.com/cli/safeexec v1.0.0 h1:0VngyaIyqACHdcMNWfo6+KdUYnqEr2Sg+bSP1pdF+dI=
+github.com/cli/safeexec v1.0.0/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
 github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s=
index b4f1d21577cd93b28ff1cf87be2b524f0fca0a77..05d9454b9176509c185653d5d9c61181d9d770de 100644 (file)
@@ -16,11 +16,12 @@ package hugolib
 import (
        "fmt"
        "os"
-       "os/exec"
        "path/filepath"
        "runtime"
        "testing"
 
+       "github.com/gohugoio/hugo/common/hexec"
+
        "github.com/gohugoio/hugo/htesting"
 
        "github.com/spf13/viper"
@@ -125,7 +126,9 @@ TS: {{ template "print" $ts }}
 
        b.WithSourceFile("assets/js/included.js", includedJS)
 
-       out, err := exec.Command("npm", "install").CombinedOutput()
+       cmd, err := hexec.SafeCommand("npm", "install")
+       b.Assert(err, qt.IsNil)
+       out, err := cmd.CombinedOutput()
        b.Assert(err, qt.IsNil, qt.Commentf(string(out)))
 
        b.Build(BuildCfg{})
@@ -194,7 +197,8 @@ require github.com/gohugoio/hugoTestProjectJSModImports v0.5.0 // indirect
 }`)
 
        b.Assert(os.Chdir(workDir), qt.IsNil)
-       _, err = exec.Command("npm", "install").CombinedOutput()
+       cmd, _ := hexec.SafeCommand("npm", "install")
+       _, err = cmd.CombinedOutput()
        b.Assert(err, qt.IsNil)
 
        b.Build(BuildCfg{})
index e6e4ed8d3c6815c2313f4826c712169366508075..cd1751c21a83ca7fae9bfb54cba82a0f6aefa2f7 100644 (file)
@@ -16,11 +16,12 @@ package hugolib
 import (
        "bytes"
        "os"
-       "os/exec"
        "path/filepath"
        "runtime"
        "testing"
 
+       "github.com/gohugoio/hugo/common/hexec"
+
        jww "github.com/spf13/jwalterweatherman"
 
        "github.com/gohugoio/hugo/htesting"
@@ -111,7 +112,8 @@ Transpiled: {{ $transpiled.Content | safeJS }}
        b.WithSourceFile("babel.config.js", babelConfig)
 
        b.Assert(os.Chdir(workDir), qt.IsNil)
-       _, err = exec.Command("npm", "install").CombinedOutput()
+       cmd, _ := hexec.SafeCommand("npm", "install")
+       _, err = cmd.CombinedOutput()
        b.Assert(err, qt.IsNil)
 
        b.Build(BuildCfg{})
index 7573199aacdaf0a4e4f3a76dac96ef4d4e02fc2b..67ce227c44264a19223351bc00e9d0ef93f1f889 100644 (file)
@@ -23,7 +23,8 @@ import (
        "math/rand"
        "os"
 
-       "os/exec"
+       "github.com/gohugoio/hugo/common/hexec"
+
        "path/filepath"
        "runtime"
        "strings"
@@ -952,7 +953,8 @@ class-in-b {
        b.WithSourceFile("postcss.config.js", postcssConfig)
 
        b.Assert(os.Chdir(workDir), qt.IsNil)
-       _, err = exec.Command("npm", "install").CombinedOutput()
+       cmd, err := hexec.SafeCommand("npm", "install")
+       _, err = cmd.CombinedOutput()
        b.Assert(err, qt.IsNil)
        b.Build(BuildCfg{})
 
index a92b6f9e3ceec7b88a08d0f1dde652bdf886ff8e..33c73bee607744542b24cb06d7550b6a18e52808 100644 (file)
@@ -18,9 +18,10 @@ package asciidocext
 
 import (
        "bytes"
-       "os/exec"
        "path/filepath"
 
+       "github.com/cli/safeexec"
+
        "github.com/gohugoio/hugo/identity"
        "github.com/gohugoio/hugo/markup/asciidocext/asciidocext_config"
        "github.com/gohugoio/hugo/markup/converter"
@@ -194,7 +195,7 @@ func (a *asciidocConverter) appendArg(args []string, option, value, defaultValue
 }
 
 func getAsciidoctorExecPath() string {
-       path, err := exec.LookPath("asciidoctor")
+       path, err := safeexec.LookPath("asciidoctor")
        if err != nil {
                return ""
        }
index fc7fddb23536c64889a2038cca3732b330a508ff..d0ad8b411b78693d3655f48a98db8fe7d7f01131 100644 (file)
@@ -2,9 +2,11 @@ package internal
 
 import (
        "bytes"
-       "os/exec"
        "strings"
 
+       "github.com/cli/safeexec"
+       "github.com/gohugoio/hugo/common/hexec"
+
        "github.com/gohugoio/hugo/markup/converter"
 )
 
@@ -14,12 +16,16 @@ func ExternallyRenderContent(
        content []byte, path string, args []string) []byte {
 
        logger := cfg.Logger
-       cmd := exec.Command(path, args...)
+       cmd, err := hexec.SafeCommand(path, args...)
+       if err != nil {
+               logger.Errorf("%s rendering %s: %v", path, ctx.DocumentName, err)
+               return nil
+       }
        cmd.Stdin = bytes.NewReader(content)
        var out, cmderr bytes.Buffer
        cmd.Stdout = &out
        cmd.Stderr = &cmderr
-       err := cmd.Run()
+       err = cmd.Run()
        // Most external helpers exit w/ non-zero exit code only if severe, i.e.
        // halting errors occurred. -> log stderr output regardless of state of err
        for _, item := range strings.Split(cmderr.String(), "\n") {
@@ -41,9 +47,9 @@ func normalizeExternalHelperLineFeeds(content []byte) []byte {
 }
 
 func GetPythonExecPath() string {
-       path, err := exec.LookPath("python")
+       path, err := safeexec.LookPath("python")
        if err != nil {
-               path, err = exec.LookPath("python.exe")
+               path, err = safeexec.LookPath("python.exe")
                if err != nil {
                        return ""
                }
index 074e97d96ec944c9b06a92f15f787c3c9a863b1c..74c71c7be96a2cad454bba62b624cdd4a72e9adc 100644 (file)
@@ -15,8 +15,7 @@
 package pandoc
 
 import (
-       "os/exec"
-
+       "github.com/cli/safeexec"
        "github.com/gohugoio/hugo/identity"
        "github.com/gohugoio/hugo/markup/internal"
 
@@ -66,7 +65,7 @@ func (c *pandocConverter) getPandocContent(src []byte, ctx converter.DocumentCon
 }
 
 func getPandocExecPath() string {
-       path, err := exec.LookPath("pandoc")
+       path, err := safeexec.LookPath("pandoc")
        if err != nil {
                return ""
        }
index cbc15c81a4711e1cbe8484a1ab0c47fea718cfbc..89d60c1347dfa781be3684bd5fff6bc6b0878126 100644 (file)
@@ -16,9 +16,10 @@ package rst
 
 import (
        "bytes"
-       "os/exec"
        "runtime"
 
+       "github.com/cli/safeexec"
+
        "github.com/gohugoio/hugo/identity"
        "github.com/gohugoio/hugo/markup/internal"
 
@@ -96,9 +97,9 @@ func (c *rstConverter) getRstContent(src []byte, ctx converter.DocumentContext)
 }
 
 func getRstExecPath() string {
-       path, err := exec.LookPath("rst2html")
+       path, err := safeexec.LookPath("rst2html")
        if err != nil {
-               path, err = exec.LookPath("rst2html.py")
+               path, err = safeexec.LookPath("rst2html.py")
                if err != nil {
                        return ""
                }
index 7d2175c94f3861a280d600ab49c969dcbdd7adb6..da3a4fff4387ae83fd2e80c2b5affb1d1f77d738 100644 (file)
@@ -26,6 +26,8 @@ import (
        "path/filepath"
        "regexp"
 
+       "github.com/gohugoio/hugo/common/hexec"
+
        hglob "github.com/gohugoio/hugo/hugofs/glob"
 
        "github.com/gobwas/glob"
@@ -545,7 +547,10 @@ func (c *Client) runGo(
        }
 
        stderr := new(bytes.Buffer)
-       cmd := exec.CommandContext(ctx, "go", args...)
+       cmd, err := hexec.SafeCommandContext(ctx, "go", args...)
+       if err != nil {
+               return err
+       }
 
        cmd.Env = c.environ
        cmd.Dir = c.ccfg.WorkingDir
index 19cf072eed63cb81a61027bb132b0e7dd101c4c4..0ae9d1cc8077819bca318746612866f5b2c32e53 100644 (file)
@@ -15,11 +15,12 @@ package releaser
 
 import (
        "fmt"
-       "os/exec"
        "regexp"
        "sort"
        "strconv"
        "strings"
+
+       "github.com/gohugoio/hugo/common/hexec"
 )
 
 var issueRe = regexp.MustCompile(`(?i)[Updates?|Closes?|Fix.*|See] #(\d+)`)
@@ -148,7 +149,7 @@ func extractIssues(body string) []int {
 type gitInfos []gitInfo
 
 func git(args ...string) (string, error) {
-       cmd := exec.Command("git", args...)
+       cmd, _ := hexec.SafeCommand("git", args...)
        out, err := cmd.CombinedOutput()
        if err != nil {
                return "", fmt.Errorf("git failed: %q: %q (%q)", err, out, args)
index 61b9d211ffecff0c926c7c604904fe6cfcbbc2eb..13748d7925b03f2f356318f438a118df3b1d95b9 100644 (file)
@@ -20,11 +20,12 @@ import (
        "io/ioutil"
        "log"
        "os"
-       "os/exec"
        "path/filepath"
        "regexp"
        "strings"
 
+       "github.com/gohugoio/hugo/common/hexec"
+
        "github.com/gohugoio/hugo/common/hugo"
        "github.com/pkg/errors"
 )
@@ -266,7 +267,7 @@ func (r *ReleaseHandler) release(releaseNotesFile string) error {
                args = append(args, "--skip-publish")
        }
 
-       cmd := exec.Command("goreleaser", args...)
+       cmd, _ := hexec.SafeCommand("goreleaser", args...)
        cmd.Stdout = os.Stdout
        cmd.Stderr = os.Stderr
        err := cmd.Run()
index a9d39d31b16edf8cb4b4caa865c6666546475e13..4c822790e86c3f93c57b9f5e8e66ca4ceb50b21b 100644 (file)
@@ -16,10 +16,11 @@ package babel
 import (
        "bytes"
        "io"
-       "os/exec"
        "path/filepath"
        "strconv"
 
+       "github.com/cli/safeexec"
+       "github.com/gohugoio/hugo/common/hexec"
        "github.com/gohugoio/hugo/common/loggers"
 
        "github.com/gohugoio/hugo/common/hugo"
@@ -107,10 +108,10 @@ func (t *babelTransformation) Transform(ctx *resources.ResourceTransformationCtx
 
        binary := csiBinPath
 
-       if _, err := exec.LookPath(binary); err != nil {
+       if _, err := safeexec.LookPath(binary); err != nil {
                // Try PATH
                binary = binaryName
-               if _, err := exec.LookPath(binary); err != nil {
+               if _, err := safeexec.LookPath(binary); err != nil {
 
                        // This may be on a CI server etc. Will fall back to pre-built assets.
                        return herrors.ErrFeatureNotAvailable
@@ -152,7 +153,10 @@ func (t *babelTransformation) Transform(ctx *resources.ResourceTransformationCtx
        }
        cmdArgs = append(cmdArgs, "--filename="+ctx.SourcePath)
 
-       cmd := exec.Command(binary, cmdArgs...)
+       cmd, err := hexec.SafeCommand(binary, cmdArgs...)
+       if err != nil {
+               return err
+       }
 
        cmd.Stdout = ctx.To
        cmd.Stderr = io.MultiWriter(infoW, &errBuf)
index daeb7212a40e714340e78b919529320dd5d0c086..4743be3d5883dcd6092ba1ce18269c61d13c1f74 100644 (file)
@@ -25,6 +25,10 @@ import (
        "strconv"
        "strings"
 
+       "github.com/cli/safeexec"
+
+       "github.com/gohugoio/hugo/common/hexec"
+
        "github.com/gohugoio/hugo/common/hugo"
 
        "github.com/gohugoio/hugo/common/loggers"
@@ -36,8 +40,6 @@ import (
        "github.com/gohugoio/hugo/hugofs"
        "github.com/pkg/errors"
 
-       "os/exec"
-
        "github.com/mitchellh/mapstructure"
 
        "github.com/gohugoio/hugo/common/herrors"
@@ -148,10 +150,10 @@ func (t *postcssTransformation) Transform(ctx *resources.ResourceTransformationC
 
        binary := csiBinPath
 
-       if _, err := exec.LookPath(binary); err != nil {
+       if _, err := safeexec.LookPath(binary); err != nil {
                // Try PATH
                binary = binaryName
-               if _, err := exec.LookPath(binary); err != nil {
+               if _, err := safeexec.LookPath(binary); err != nil {
                        // This may be on a CI server etc. Will fall back to pre-built assets.
                        return herrors.ErrFeatureNotAvailable
                }
@@ -189,7 +191,10 @@ func (t *postcssTransformation) Transform(ctx *resources.ResourceTransformationC
                cmdArgs = append(cmdArgs, optArgs...)
        }
 
-       cmd := exec.Command(binary, cmdArgs...)
+       cmd, err := hexec.SafeCommand(binary, cmdArgs...)
+       if err != nil {
+               return err
+       }
 
        var errBuf bytes.Buffer
        infoW := loggers.LoggerToWriterWithPrefix(logger.Info(), "postcss")
index 04202b2544469f43e1ed4fba6e36f3f357d4311d..f995f1c3c7bba5c11982918e9385127f238b3a16 100644 (file)
@@ -5,11 +5,12 @@ import (
        "io/ioutil"
        "log"
        "os"
-       "os/exec"
        "path/filepath"
        "regexp"
        "strings"
 
+       "github.com/gohugoio/hugo/common/hexec"
+
        "github.com/gohugoio/hugo/common/hugio"
 
        "github.com/spf13/afero"
@@ -200,7 +201,7 @@ func removeAll(expression, content string) string {
 }
 
 func rewrite(filename, rule string) {
-       cmf := exec.Command("gofmt", "-w", "-r", rule, filename)
+       cmf, _ := hexec.SafeCommand("gofmt", "-w", "-r", rule, filename)
        out, err := cmf.CombinedOutput()
        if err != nil {
                log.Fatal("gofmt failed:", string(out))
@@ -208,7 +209,7 @@ func rewrite(filename, rule string) {
 }
 
 func goimports(dir string) {
-       cmf := exec.Command("goimports", "-w", dir)
+       cmf, _ := hexec.SafeCommand("goimports", "-w", dir)
        out, err := cmf.CombinedOutput()
        if err != nil {
                log.Fatal("goimports failed:", string(out))
@@ -216,7 +217,7 @@ func goimports(dir string) {
 }
 
 func gofmt(dir string) {
-       cmf := exec.Command("gofmt", "-w", dir)
+       cmf, _ := hexec.SafeCommand("gofmt", "-w", dir)
        out, err := cmf.CombinedOutput()
        if err != nil {
                log.Fatal("gofmt failed:", string(out))
index 90044570d3762bffe10176524da593b159ee2db2..f5ea398fb887b1a19dc16b773adb80c586be32d9 100644 (file)
@@ -13,7 +13,6 @@ package testenv
 import (
        "errors"
        "flag"
-       "github.com/gohugoio/hugo/tpl/internal/go_templates/cfg"
        "os"
        "os/exec"
        "path/filepath"
@@ -22,6 +21,9 @@ import (
        "strings"
        "sync"
        "testing"
+
+       "github.com/cli/safeexec"
+       "github.com/gohugoio/hugo/tpl/internal/go_templates/cfg"
 )
 
 // Builder reports the name of the builder running this test
@@ -111,7 +113,7 @@ func GoTool() (string, error) {
        if _, err := os.Stat(path); err == nil {
                return path, nil
        }
-       goBin, err := exec.LookPath("go" + exeSuffix)
+       goBin, err := safeexec.LookPath("go" + exeSuffix)
        if err != nil {
                return "", errors.New("cannot find go tool: " + err.Error())
        }
@@ -162,7 +164,7 @@ func MustHaveExecPath(t testing.TB, path string) {
 
        err, found := execPaths.Load(path)
        if !found {
-               _, err = exec.LookPath(path)
+               _, err = safeexec.LookPath(path)
                err, _ = execPaths.LoadOrStore(path, err)
        }
        if err != nil {