Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

css.TailwindCSS etc. #12619

Merged
merged 2 commits into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion commands/hugobuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -920,7 +920,7 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
if len(otherChanges) > 0 {
livereload.ForceRefresh()
// Allow some time for the live reload script to get reconnected.
time.Sleep(100 * time.Millisecond)
time.Sleep(200 * time.Millisecond)
}

for _, ev := range cssChanges {
Expand Down
90 changes: 70 additions & 20 deletions common/hexec/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ import (
"io"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"sync"

"github.com/cli/safeexec"
"github.com/gohugoio/hugo/config"
Expand Down Expand Up @@ -84,7 +86,7 @@ var WithEnviron = func(env []string) func(c *commandeer) {
}

// New creates a new Exec using the provided security config.
func New(cfg security.Config) *Exec {
func New(cfg security.Config, workingDir string) *Exec {
var baseEnviron []string
for _, v := range os.Environ() {
k, _ := config.SplitEnvVar(v)
Expand All @@ -95,6 +97,7 @@ func New(cfg security.Config) *Exec {

return &Exec{
sc: cfg,
workingDir: workingDir,
baseEnviron: baseEnviron,
}
}
Expand All @@ -119,15 +122,23 @@ func SafeCommand(name string, arg ...string) (*exec.Cmd, error) {

// Exec enforces a security policy for commands run via os/exec.
type Exec struct {
sc security.Config
sc security.Config
workingDir string

// os.Environ filtered by the Exec.OsEnviron whitelist filter.
baseEnviron []string

npxInit sync.Once
npxAvailable bool
}

func (e *Exec) New(name string, arg ...any) (Runner, error) {
return e.new(name, "", arg...)
}

// New will fail if name is not allowed according to the configured security policy.
// Else a configured Runner will be returned ready to be Run.
func (e *Exec) New(name string, arg ...any) (Runner, error) {
func (e *Exec) new(name string, fullyQualifiedName string, arg ...any) (Runner, error) {
if err := e.sc.CheckAllowedExec(name); err != nil {
return nil, err
}
Expand All @@ -136,27 +147,51 @@ func (e *Exec) New(name string, arg ...any) (Runner, error) {
copy(env, e.baseEnviron)

cm := &commandeer{
name: name,
env: env,
name: name,
fullyQualifiedName: fullyQualifiedName,
env: env,
}

return cm.command(arg...)
}

// Npx will try to run npx, and if that fails, it will
// try to run the binary directly.
// Npx will in order:
// 1. Try fo find the binary in the WORKINGDIR/node_modules/.bin directory.
// 2. If not found, and npx is available, run npx --no-install <name> <args>.
// 3. Fall back to the PATH.
func (e *Exec) Npx(name string, arg ...any) (Runner, error) {
r, err := e.npx(name, arg...)
// npx is slow, so first try the common case.
nodeBinFilename := filepath.Join(e.workingDir, nodeModulesBinPath, name)
_, err := safeexec.LookPath(nodeBinFilename)
if err == nil {
return r, nil
return e.new(name, nodeBinFilename, arg...)
}
e.checkNpx()
if e.npxAvailable {
r, err := e.npx(name, arg...)
if err == nil {
return r, nil
}
}
return e.New(name, arg...)
}

const (
npxNoInstall = "--no-install"
npxBinary = "npx"
nodeModulesBinPath = "node_modules/.bin"
)

func (e *Exec) checkNpx() {
e.npxInit.Do(func() {
e.npxAvailable = InPath(npxBinary)
})
}

// npx is a convenience method to create a Runner running npx --no-install <name> <args.
func (e *Exec) npx(name string, arg ...any) (Runner, error) {
arg = append(arg[:0], append([]any{"--no-install", name}, arg[0:]...)...)
return e.New("npx", arg...)
arg = append(arg[:0], append([]any{npxNoInstall, name}, arg[0:]...)...)
return e.New(npxBinary, arg...)
}

// Sec returns the security policies this Exec is configured with.
Expand All @@ -165,11 +200,12 @@ func (e *Exec) Sec() security.Config {
}

type NotFoundError struct {
name string
name string
method string
}

func (e *NotFoundError) Error() string {
return fmt.Sprintf("binary with name %q not found", e.name)
return fmt.Sprintf("binary with name %q not found %s", e.name, e.method)
}

// Runner wraps a *os.Cmd.
Expand All @@ -192,8 +228,14 @@ func (c *cmdWrapper) Run() error {
if err == nil {
return nil
}
name := c.name
method := "in PATH"
if name == npxBinary {
name = c.c.Args[2]
method = "using npx"
}
if notFoundRe.MatchString(c.outerr.String()) {
return &NotFoundError{name: c.name}
return &NotFoundError{name: name, method: method}
}
return fmt.Errorf("failed to execute binary %q with args %v: %s", c.name, c.c.Args[1:], c.outerr.String())
}
Expand All @@ -209,8 +251,9 @@ type commandeer struct {
dir string
ctx context.Context

name string
env []string
name string
fullyQualifiedName string
env []string
}

func (c *commandeer) command(arg ...any) (*cmdWrapper, error) {
Expand All @@ -230,10 +273,17 @@ func (c *commandeer) command(arg ...any) (*cmdWrapper, error) {
}
}

bin, err := safeexec.LookPath(c.name)
if err != nil {
return nil, &NotFoundError{
name: c.name,
var bin string
if c.fullyQualifiedName != "" {
bin = c.fullyQualifiedName
} else {
var err error
bin, err = safeexec.LookPath(c.name)
if err != nil {
return nil, &NotFoundError{
name: c.name,
method: "in PATH",
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion config/allconfig/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@ func (l *configLoader) loadModules(configs *Configs) (modules.ModulesConfig, *mo
ignoreVendor, _ = hglob.GetGlob(hglob.NormalizePath(s))
}

ex := hexec.New(conf.Security)
ex := hexec.New(conf.Security, workingDir)

hook := func(m *modules.ModulesConfig) error {
for _, tc := range m.AllModules {
Expand Down
1 change: 1 addition & 0 deletions config/security/securityConfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ var DefaultConfig = Config{
"^go$", // for Go Modules
"^npx$", // used by all Node tools (Babel, PostCSS).
"^postcss$",
"^tailwindcss$",
),
// These have been tested to work with Hugo's external programs
// on Windows, Linux and MacOS.
Expand Down
2 changes: 1 addition & 1 deletion config/security/securityConfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ func TestToTOML(t *testing.T) {
got := DefaultConfig.ToTOML()

c.Assert(got, qt.Equals,
"[security]\n enableInlineShortcodes = false\n\n [security.exec]\n allow = ['^(dart-)?sass(-embedded)?$', '^go$', '^npx$', '^postcss$']\n osEnv = ['(?i)^((HTTPS?|NO)_PROXY|PATH(EXT)?|APPDATA|TE?MP|TERM|GO\\w+|(XDG_CONFIG_)?HOME|USERPROFILE|SSH_AUTH_SOCK|DISPLAY|LANG|SYSTEMDRIVE)$']\n\n [security.funcs]\n getenv = ['^HUGO_', '^CI$']\n\n [security.http]\n methods = ['(?i)GET|POST']\n urls = ['.*']",
"[security]\n enableInlineShortcodes = false\n\n [security.exec]\n allow = ['^(dart-)?sass(-embedded)?$', '^go$', '^npx$', '^postcss$', '^tailwindcss$']\n osEnv = ['(?i)^((HTTPS?|NO)_PROXY|PATH(EXT)?|APPDATA|TE?MP|TERM|GO\\w+|(XDG_CONFIG_)?HOME|USERPROFILE|SSH_AUTH_SOCK|DISPLAY|LANG|SYSTEMDRIVE)$']\n\n [security.funcs]\n getenv = ['^HUGO_', '^CI$']\n\n [security.http]\n methods = ['(?i)GET|POST']\n urls = ['.*']",
)
}

Expand Down
2 changes: 1 addition & 1 deletion deps/deps.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ func (d *Deps) Init() error {
}

if d.ExecHelper == nil {
d.ExecHelper = hexec.New(d.Conf.GetConfigSection("security").(security.Config))
d.ExecHelper = hexec.New(d.Conf.GetConfigSection("security").(security.Config), d.Conf.WorkingDir())
}

if d.MemCache == nil {
Expand Down
2 changes: 1 addition & 1 deletion hugolib/integrationtest_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -659,7 +659,7 @@ func (s *IntegrationTestBuilder) initBuilder() error {
sc := security.DefaultConfig
sc.Exec.Allow, err = security.NewWhitelist("npm")
s.Assert(err, qt.IsNil)
ex := hexec.New(sc)
ex := hexec.New(sc, s.Cfg.WorkingDir)
command, err := ex.New("npm", "install")
s.Assert(err, qt.IsNil)
s.Assert(command.Run(), qt.IsNil)
Expand Down
2 changes: 1 addition & 1 deletion hugolib/testhelpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -834,7 +834,7 @@ func (s *sitesBuilder) NpmInstall() hexec.Runner {
var err error
sc.Exec.Allow, err = security.NewWhitelist("npm")
s.Assert(err, qt.IsNil)
ex := hexec.New(sc)
ex := hexec.New(sc, s.workingDir)
command, err := ex.New("npm", "install")
s.Assert(err, qt.IsNil)
return command
Expand Down
2 changes: 1 addition & 1 deletion markup/asciidocext/convert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ allow = ['asciidoctor']
converter.ProviderConfig{
Logger: loggers.NewDefault(),
Conf: conf,
Exec: hexec.New(securityConfig),
Exec: hexec.New(securityConfig, ""),
},
)
c.Assert(err, qt.IsNil)
Expand Down
2 changes: 1 addition & 1 deletion markup/pandoc/convert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func TestConvert(t *testing.T) {
var err error
sc.Exec.Allow, err = security.NewWhitelist("pandoc")
c.Assert(err, qt.IsNil)
p, err := Provider.New(converter.ProviderConfig{Exec: hexec.New(sc), Logger: loggers.NewDefault()})
p, err := Provider.New(converter.ProviderConfig{Exec: hexec.New(sc, ""), Logger: loggers.NewDefault()})
c.Assert(err, qt.IsNil)
conv, err := p.New(converter.DocumentContext{})
c.Assert(err, qt.IsNil)
Expand Down
2 changes: 1 addition & 1 deletion markup/rst/convert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func TestConvert(t *testing.T) {
p, err := Provider.New(
converter.ProviderConfig{
Logger: loggers.NewDefault(),
Exec: hexec.New(sc),
Exec: hexec.New(sc, ""),
})
c.Assert(err, qt.IsNil)
conv, err := p.New(converter.DocumentContext{})
Expand Down
2 changes: 1 addition & 1 deletion modules/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ github.com/gohugoio/hugoTestModules1_darwin/modh2_2@v1.4.0 github.com/gohugoio/h
WorkingDir: workingDir,
ThemesDir: themesDir,
PublishDir: publishDir,
Exec: hexec.New(security.DefaultConfig),
Exec: hexec.New(security.DefaultConfig, ""),
}

withConfig(&ccfg)
Expand Down
Loading
Loading