Skip to content

Commit

Permalink
Add exec template function.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
sba1 committed Mar 11, 2016
1 parent e9008b9 commit c84809f
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 0 deletions.
1 change: 1 addition & 0 deletions commands/hugo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions docs/content/overview/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions docs/content/templates/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
<!-- will output 42 -->

This assumes that `echo` is listed within the `execWhitelist`, which is not the
case by default.
32 changes: 32 additions & 0 deletions tpl/template_funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"html/template"
"math/rand"
"os"
_exec "os/exec"
"reflect"
"sort"
"strconv"
Expand All @@ -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
Expand Down Expand Up @@ -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("<p>")
var markdownTrimSuffix = []byte("</p>\n")

Expand Down Expand Up @@ -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,
Expand Down
50 changes: 50 additions & 0 deletions tpl/template_funcs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down

0 comments on commit c84809f

Please sign in to comment.