Improve shell quoting

This commit is contained in:
Přemysl Eric Janouch 2024-04-16 07:38:23 +02:00
parent fe81d713e1
commit b594ff78b2
Signed by: p
GPG Key ID: A0420B94F92B9493
5 changed files with 64 additions and 6 deletions

View File

@ -9,5 +9,7 @@ acid: acid.go
go build -ldflags "-X 'main.projectVersion=$(version)'" -o $@ go build -ldflags "-X 'main.projectVersion=$(version)'" -o $@
acid.1: acid.adoc acid.1: acid.adoc
asciidoctor -b manpage -a release-version=$(version) -o $@ acid.adoc asciidoctor -b manpage -a release-version=$(version) -o $@ acid.adoc
test: all
go test
clean: clean:
rm -f $(outputs) rm -f $(outputs)

View File

@ -71,6 +71,9 @@ which has the following fields:
*CloneURL*:: *CloneURL*::
Gitea link for cloning the repository over HTTP. Gitea link for cloning the repository over HTTP.
The special *quote* template function quotes fields for safe usage
in *sh*(1) command arguments.
Runners Runners
------- -------
Runners receive the following additional environment variables: Runners receive the following additional environment variables:

25
acid.go
View File

@ -93,10 +93,30 @@ func parseConfig(path string) error {
} }
var err error var err error
gNotifyScript, err = ttemplate.New("notify").Parse(gConfig.Notify) gNotifyScript, err =
ttemplate.New("notify").Funcs(shellFuncs).Parse(gConfig.Notify)
return err return err
} }
var shellFuncs = ttemplate.FuncMap{
"quote": func(word string) string {
// History expansion is annoying, don't let it cut us.
if strings.IndexRune(word, '!') >= 0 {
return "'" + strings.ReplaceAll(word, "'", `'"'"'`) + "'"
}
const special = "$`\"\\"
quoted := []rune{'"'}
for _, r := range word {
if strings.IndexRune(special, r) >= 0 {
quoted = append(quoted, '\\')
}
quoted = append(quoted, r)
}
return string(append(quoted, '"'))
},
}
// --- Utilities --------------------------------------------------------------- // --- Utilities ---------------------------------------------------------------
func giteaSign(b []byte) string { func giteaSign(b []byte) string {
@ -910,7 +930,8 @@ func executorRunTask(ctx context.Context, task Task) error {
// - we might have to clone submodules as well. // - we might have to clone submodules as well.
// Otherwise, we could download a source archive from Gitea, // Otherwise, we could download a source archive from Gitea,
// and use SFTP to upload it to the runner. // and use SFTP to upload it to the runner.
tmplScript, err := ttemplate.New("script").Parse(rt.Runner.Setup + "\n" + tmplScript, err := ttemplate.New("script").Funcs(shellFuncs).
Parse(rt.Runner.Setup + "\n" +
rt.ProjectRunner.Setup + "\n" + rt.ProjectRunner.Build) rt.ProjectRunner.Setup + "\n" + rt.ProjectRunner.Build)
if err != nil { if err != nil {
return fmt.Errorf("script: %w", err) return fmt.Errorf("script: %w", err)

View File

@ -44,9 +44,9 @@ runners:
setup: | setup: |
set -ex set -ex
sudo pacman -Syu --noconfirm git sudo pacman -Syu --noconfirm git
git clone --recursive '{{.CloneURL}}' '{{.Repo}}' git clone --recursive {{quote .CloneURL}} {{quote .Repo}}
cd '{{.Repo}}' cd {{quote .Repo}}
git -c advice.detachedHead=false checkout '{{.Hash}}' git -c advice.detachedHead=false checkout {{quote .Hash}}
# Configuration for individual Gitea repositories. # Configuration for individual Gitea repositories.
projects: projects:

32
acid_test.go Normal file
View File

@ -0,0 +1,32 @@
package main
import (
"bytes"
"testing"
ttemplate "text/template"
)
func TestTemplateQuote(t *testing.T) {
// Ideally, we should back-parse it using sh syntax.
// This is an unnecessarily fragile test.
for _, test := range []struct {
input, output string
}{
{`!!`, `'!!'`},
{``, `""`},
{`${var}`, `"\${var}"`},
{"`cat`", "\"\\`cat\\`\""},
{`"魚\"`, `"\"魚\\\""`},
} {
var b bytes.Buffer
err := ttemplate.Must(ttemplate.New("test").
Funcs(shellFuncs).Parse("{{quote .}}")).Execute(&b, test.input)
if err != nil {
t.Errorf("template execution error: %s\n", err)
}
if b.String() != test.output {
t.Errorf("%q should be quoted os %q, not %q\n",
test.input, test.output, b.String())
}
}
}