Skip to content

Commit

Permalink
[run] fix escaping arguments that include double-quotes
Browse files Browse the repository at this point in the history
  • Loading branch information
savil committed Jun 12, 2023
1 parent 1967cd4 commit 4648877
Show file tree
Hide file tree
Showing 8 changed files with 130 additions and 20 deletions.
3 changes: 2 additions & 1 deletion devbox.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"[email protected]"
],
"env": {
"CONFIG_VAR1": "abc",
"PATH": "$PATH:$PWD/dist"
},
"shell": {
Expand All @@ -23,4 +24,4 @@
"nixpkgs": {
"commit": "3364b5b117f65fe1ce65a3cdd5612a078a3b31e3"
}
}
}
13 changes: 13 additions & 0 deletions internal/impl/devbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (

"github.com/pkg/errors"
"github.com/samber/lo"
"go.jetpack.io/devbox/internal/sheller"
"golang.org/x/exp/slices"

"go.jetpack.io/devbox/internal/boxcli/featureflag"
Expand Down Expand Up @@ -242,6 +243,8 @@ func (d *Devbox) RunScript(ctx context.Context, cmdName string, cmdArgs []string
ctx, task := trace.NewTask(ctx, "devboxRun")
defer task.End()

fmt.Printf("cmdName: %q and cmdArgs %q\n", cmdName, cmdArgs)

if err := d.ensurePackagesAreInstalled(ctx, ensure); err != nil {
return err
}
Expand All @@ -263,6 +266,16 @@ func (d *Devbox) RunScript(ctx context.Context, cmdName string, cmdArgs []string
return err
}

// We want to wrap each argument in double-quotes, so that users can
// pass arguments to other utilities (via `devbox run`) where the arguments
// may have a double-quote.
for idx, cmdArg := range cmdArgs {
cmdArgs[idx] = sheller.QuoteWrap(cmdArg)
//cmdArgs[idx] = strconv.Quote(cmdArg)
}
fmt.Printf("after QuoteWrap, cmdArgs %v\n", cmdArgs)
//fmt.Printf("after strconv.Quote, cmdArgs %v\n", cmdArgs)

var cmdWithArgs []string
if _, ok := d.cfg.Scripts()[cmdName]; ok {
// it's a script, so replace the command with the script file's path.
Expand Down
16 changes: 5 additions & 11 deletions internal/impl/envvars.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"os"
"sort"
"strings"

"go.jetpack.io/devbox/internal/sheller"
)

const devboxSetPrefix = "__DEVBOX_SET_"
Expand Down Expand Up @@ -45,17 +47,9 @@ func exportify(vars map[string]string) string {
for _, k := range keys {
strb.WriteString("export ")
strb.WriteString(k)
strb.WriteString(`="`)
for _, r := range vars[k] {
switch r {
// Special characters inside double quotes:
// https://pubs.opengroup.org/onlinepubs/009604499/utilities/xcu_chap02.html#tag_02_02_03
case '$', '`', '"', '\\', '\n':
strb.WriteRune('\\')
}
strb.WriteRune(r)
}
strb.WriteString("\";\n")
strb.WriteString(`=`)
strb.WriteString(sheller.QuoteWrap(vars[k]))
strb.WriteString(";\n")
}
return strings.TrimSpace(strb.String())
}
Expand Down
25 changes: 25 additions & 0 deletions internal/sheller/sheller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package sheller

import (
"strings"
)

// package sheller holds utilities that enable working with unix shells

// QuoteWrap will wrap the word in double-quotes and escape special characters
// as per:
// https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02_03
func QuoteWrap(word string) string {
strb := strings.Builder{}
strb.WriteString(`"`)
for _, r := range word {
switch r {
// Special characters inside double quotes:
case '$', '`', '"', '\\', '\n':
strb.WriteRune('\\')
}
strb.WriteRune(r)
}
strb.WriteString(`"`)
return strb.String()
}
49 changes: 49 additions & 0 deletions internal/sheller/sheller_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package sheller

import (
"testing"
)

func TestQuoteWrap(t *testing.T) {
testCases := []struct {
input string
expected string
}{
// regular string
{
input: "Hello",
expected: `"Hello"`,
},
// embedded double-quotes
{
input: `Hello "World"`,
expected: `"Hello \"World\""`,
},
// embedded backtick-quotes
{
input: "Hello `World`",
expected: "\"Hello \\`World\\`\"",
},
// embedded single-quotes,
{
input: "Hello 'World'",
expected: "\"Hello 'World'\"",
},
// escaping $, backslash (\), and \n
{
input: `A $pecial charac\ter\n`,
expected: `"A \$pecial charac\\ter\\n"`,
},
}

for _, tc := range testCases {
t.Run(
tc.input, func(t *testing.T) {
result := QuoteWrap(tc.input)
if result != tc.expected {
t.Errorf("Expected %q, but got %q", tc.expected, result)
}
},
)
}
}
16 changes: 8 additions & 8 deletions testscripts/run/env.test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,29 @@
env HOMETEST=/home/test
env USER=test-user
env FOO=bar
exec devbox run echo '$HOMETEST'
exec devbox run echo "$HOMETEST"
stdout '/home/test'
exec devbox run echo '$USER'
exec devbox run echo "$USER"
stdout 'test-user'
exec devbox run echo '$FOO'
exec devbox run echo "$FOO"
stdout 'bar'

# Fixed/hard-coded vars are set
exec devbox run echo '$__ETC_PROFILE_NIX_SOURCED'
# TODO savil: why does double-quote fail?
exec devbox run echo "$__ETC_PROFILE_NIX_SOURCED"
stdout '1'

# DEVBOX_* vars are passed through
env DEVBOX_FOO=baz
exec devbox run echo '$DEVBOX_FOO'
exec devbox run echo "$DEVBOX_FOO"
stdout 'baz'

# Vars defined in devbox.json are passed through
env DEVBOX_FEATURE_ENV_CONFIG=1
exec devbox run echo '$CONFIG_VAR1'
# TODO savil: why does double-quote fail?
exec devbox run echo "$CONFIG_VAR1"
stdout 'abc'

# Vars defined in devbox.json that reference another variable are set
env DEVBOX_FEATURE_ENV_CONFIG=1
env DEVBOX_FOO=baz
exec devbox run echo '$CONFIG_VAR2'
stdout 'baz'
Expand Down
17 changes: 17 additions & 0 deletions testscripts/run/env_debug.test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
exec devbox run echo '$__ETC_PROFILE_NIX_SOURCED'
stdout '1'

exec devbox run echo "$__ETC_PROFILE_NIX_SOURCED"
stdout ''


-- devbox.json --
{
"packages": ["nginx@latest"],
"env": {
"CONFIG_VAR1": "abc",
"CONFIG_VAR2": "$DEVBOX_FOO",
"CONFIG_VAR3": "${PWD}",
"NGINX_CONFDIR": "devbox-json-override"
}
}
11 changes: 11 additions & 0 deletions testscripts/run/quote_escaping.test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# ensure that we escape the arguments to `devbox run`

exec devbox init
exec devbox run -- echo 'this is a "hello world"'
stdout 'this is a "hello world"'

env FOO=bar
exec devbox run echo '$FOO'
stdout '$FOO'
exec devbox run echo "$FOO"
stdout 'bar'

0 comments on commit 4648877

Please sign in to comment.