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 16:03:07 +0000 (17:03 +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 d1594a1996f007cf809777421c24e2e731acbc96..26eda20310b4fbfac665dbff37d916d404c13819 100644 (file)
@@ -18,12 +18,12 @@ import (
        "bytes"
        "io"
        "os"
-       "os/exec"
        "path/filepath"
        "strings"
 
        "github.com/pkg/errors"
 
+       "github.com/gohugoio/hugo/common/hexec"
        "github.com/gohugoio/hugo/hugofs/files"
 
        "github.com/gohugoio/hugo/hugofs"
@@ -105,7 +105,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 bcf7b320074a42f339eb9bedae43a49156e8367f..d3a5b4af4e374f35076b19b483070d8954bcecbb 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 cd66e59564754c689bdbad15f4dd0b1d15071c19..e46d649d91988ee7424853acc858832fdb890919 100644 (file)
--- a/go.sum
+++ b/go.sum
@@ -148,6 +148,8 @@ github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgk
 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+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/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s=
index fbbd335b32a7f040e32deaa049af5bee345f8ab3..8d8e015a6aca14fdcf08ca6c94d1ac10978e61df 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{})
@@ -193,7 +196,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 e56c037f14cde70c19fafbac9c4fdd3d46145d78..da03c83c78fc22b9f4dcae6936124e8cc648907f 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 4f93115f9d380cbf4f229a49e28b4eb0738e2a1d..b5baa4d0bb34364181b7485b6372664aee92ffff 100644 (file)
@@ -19,13 +19,15 @@ import (
        "io"
        "math/rand"
        "os"
-       "os/exec"
+
        "path/filepath"
        "runtime"
        "strings"
        "testing"
        "time"
 
+       "github.com/gohugoio/hugo/common/hexec"
+
        jww "github.com/spf13/jwalterweatherman"
 
        "github.com/gohugoio/hugo/common/herrors"
@@ -930,7 +932,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 e2e5b7865b41d906db087c337776d2329c5cf822..a5465fe9ff4116f68edfe686b5069c97fca16351 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"
@@ -193,7 +194,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 e8f86ae9417623ff3d5d5d36785ccacfdc4c95eb..0937afa343974b191ce2a21680d4ea972d8d0cbe 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"
 )
 
@@ -13,12 +15,16 @@ func ExternallyRenderContent(
        ctx converter.DocumentContext,
        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") {
@@ -40,9 +46,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 bbf619ce60f600610f2234712f75f25944727c0b..63bab2748f688508ceb63804360a77721b57b0d9 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"
 
@@ -65,7 +64,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 c397bf5fe62979d0696792ce0bcf48e1f06236d3..faed56276d58db97d542bf0fdd7f592b07e0c29a 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 88c1e933e4348e0fe976afc75941547c32a2c7b8..da14d58f4cf2c979a11637e44fc41fbc3b78f686 100644 (file)
@@ -28,6 +28,8 @@ import (
        "strings"
        "time"
 
+       "github.com/gohugoio/hugo/common/hexec"
+
        hglob "github.com/gohugoio/hugo/hugofs/glob"
 
        "github.com/gobwas/glob"
@@ -537,7 +539,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 7d2d43e2aa871f7bfa0bc55823c901ece79b2056..4db1c2329c908e3e113379d96f1d90b129399d58 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 97ffe1a2c33e7f8c9753509ac1e5552f568821bf..5c5a3478792f8f1454558ef95486a2a9fd885d3d 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 64d8667bd1af8fe22cec3f7989a8cc9d2808d8ab..2041537056f609bef05da061c68debfb27c59793 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"
@@ -108,10 +109,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 27864b0c57f795d0eb51fadfc427f268681f7833..652770078b531931212b707a525854f0e5a6f2d2 100644 (file)
@@ -19,13 +19,16 @@ import (
        "encoding/hex"
        "io"
        "io/ioutil"
-       "os/exec"
        "path"
        "path/filepath"
        "regexp"
        "strconv"
        "strings"
 
+       "github.com/cli/safeexec"
+
+       "github.com/gohugoio/hugo/common/hexec"
+
        "github.com/gohugoio/hugo/common/hugo"
 
        "github.com/gohugoio/hugo/common/loggers"
@@ -146,10 +149,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
                }
@@ -186,7 +189,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 c295ab734edbc957c769e7298f18b16b93abda8a..b66c8a111058e0bd096be58e580e57bd740b01ed 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"
@@ -203,7 +204,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))
@@ -211,7 +212,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))
@@ -219,7 +220,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 {