From c84809f98b154a9e029d51031c5f59f5ea1fccc2 Mon Sep 17 00:00:00 2001 From: Sebastian Bauer Date: Fri, 11 Mar 2016 20:09:22 +0100 Subject: [PATCH] Add exec template function. A new template function called exec is added. This function can be used to include the standard output of an external command. Commands that are allowed to be executed must be listed within the newly introduced execWhiteList setting. --- commands/hugo.go | 1 + docs/content/overview/configuration.md | 2 ++ docs/content/templates/functions.md | 12 +++++++ tpl/template_funcs.go | 32 +++++++++++++++++ tpl/template_funcs_test.go | 50 ++++++++++++++++++++++++++ 5 files changed, 97 insertions(+) diff --git a/commands/hugo.go b/commands/hugo.go index dd3ca289e48..21242a32cef 100644 --- a/commands/hugo.go +++ b/commands/hugo.go @@ -301,6 +301,7 @@ func LoadDefaultSettings() { viper.SetDefault("SectionPagesMenu", "") viper.SetDefault("DisablePathToLower", false) viper.SetDefault("HasCJKLanguage", false) + viper.SetDefault("ExecWhitelist", []string{}) } // InitializeConfig initializes a config file with sensible default configuration flags. diff --git a/docs/content/overview/configuration.md b/docs/content/overview/configuration.md index 18f4b14bd0e..2e58574bca7 100644 --- a/docs/content/overview/configuration.md +++ b/docs/content/overview/configuration.md @@ -150,6 +150,8 @@ Following is a list of Hugo-defined variables that you can configure and their c verboseLog: false # watch filesystem for changes and recreate as needed watch: true + # commands that can be executed via the exec command + execWhitelist: [] --- ## Ignore files on build diff --git a/docs/content/templates/functions.md b/docs/content/templates/functions.md index 599e61bda6a..55ad864ffe2 100644 --- a/docs/content/templates/functions.md +++ b/docs/content/templates/functions.md @@ -822,3 +822,15 @@ responses of APIs. {{ $resp.content | base64Decode | markdownify }} The response of the GitHub API contains the base64-encoded version of the [README.md](https://github.com/spf13/hugo/blob/master/README.md) in the Hugo repository. Now we can decode it and parse the Markdown. The final output will look similar to the rendered version on GitHub. + +### exec + +Executes an external command and include its standard output as content. The +actual commands that can be executed need to be given using the `execWhitelist` +configuration settings. + + {{ echo 42 }} + + +This assumes that `echo` is listed within the `execWhitelist`, which is not the +case by default. diff --git a/tpl/template_funcs.go b/tpl/template_funcs.go index 3e21e81d621..ff3a2099ff7 100644 --- a/tpl/template_funcs.go +++ b/tpl/template_funcs.go @@ -28,6 +28,7 @@ import ( "html/template" "math/rand" "os" + _exec "os/exec" "reflect" "sort" "strconv" @@ -40,6 +41,7 @@ import ( "github.com/spf13/cast" "github.com/spf13/hugo/helpers" jww "github.com/spf13/jwalterweatherman" + "github.com/spf13/viper" ) var funcMap template.FuncMap @@ -1137,6 +1139,35 @@ func highlight(in interface{}, lang, opts string) (template.HTML, error) { return template.HTML(helpers.Highlight(html.UnescapeString(str), lang, opts)), nil } +// exec executes a command and returns its output as string. +func exec(in interface{}, args ...string) (string, error) { + str, err := cast.ToStringE(in) + + if err != nil { + return "", err + } + + // Check if command is accepted (in white list) + commandAccepted := false + for _, validCommand := range viper.GetStringSlice("ExecWhiteList") { + if str == validCommand { + commandAccepted = true + break + } + } + + if !commandAccepted { + return "", fmt.Errorf("Executing %s is not allowed. Check execWhiteList settings.", str) + } + + out, err := _exec.Command(strings.TrimSpace(str), args...).Output() + if err != nil { + return "", err + } + + return string(out[:]), nil +} + var markdownTrimPrefix = []byte("

") var markdownTrimSuffix = []byte("

\n") @@ -1684,6 +1715,7 @@ func init() { "div": func(a, b interface{}) (interface{}, error) { return doArithmetic(a, b, '/') }, "echoParam": returnWhenSet, "eq": eq, + "exec": exec, "first": first, "ge": ge, "getCSV": getCSV, diff --git a/tpl/template_funcs_test.go b/tpl/template_funcs_test.go index 96d0c013b70..1d76e6bd5ad 100644 --- a/tpl/template_funcs_test.go +++ b/tpl/template_funcs_test.go @@ -58,6 +58,56 @@ func tstIsLt(tp tstCompareType) bool { return tp == tstLt || tp == tstLe } +func TestExecFuncInTemplate(t *testing.T) { + + viper.Reset() + defer viper.Reset() + + viper.Set("ExecWhitelist", []string{"echo"}) + + in := "{{exec \"echo\" \"test\"}}" + expected := "test\n" + + templ, err := New().New("test").Parse(in) + if err != nil { + t.Fatal("Got error on parse", err) + } + + var b bytes.Buffer + err = templ.Execute(&b, nil) + + if err != nil { + t.Fatal("Got error on execute", err) + } + + if b.String() != expected { + t.Errorf("Got\n%q\nExpected\n>%q<", b.String(), expected) + } +} + +func TestExecFuncInTemplateCmdNotInWhitelist(t *testing.T) { + + viper.Reset() + defer viper.Reset() + + viper.Set("ExecWhitelist", []string{}) + + in := "{{exec \"echo\" \"test\"}}" + expectedErrSuffix := "Executing echo is not allowed. Check execWhiteList settings." + + templ, err := New().New("test").Parse(in) + if err != nil { + t.Fatal("Got error on parse", err) + } + + var b bytes.Buffer + err = templ.Execute(&b, nil) + + if !strings.HasSuffix(err.Error(), expectedErrSuffix) { + t.Errorf("Expected suffix %q in %q", expectedErrSuffix, err.Error()) + } +} + func TestFuncsInTemplate(t *testing.T) { viper.Reset()