From 1db6960bda9164c9bbc4942670bb93728717940a Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Wed, 4 Dec 2019 17:38:21 +0100 Subject: [PATCH 1/3] Export MaybeShellQuote --- internal/chezmoi/maybeshellquote.go | 4 ++-- internal/chezmoi/maybeshellquote_test.go | 2 +- internal/chezmoi/verbosemutator.go | 16 ++++++++-------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/internal/chezmoi/maybeshellquote.go b/internal/chezmoi/maybeshellquote.go index 7ba63fa91e3..4086aa9c6b7 100644 --- a/internal/chezmoi/maybeshellquote.go +++ b/internal/chezmoi/maybeshellquote.go @@ -11,8 +11,8 @@ const ( singleQuote = '\'' ) -// maybeShellQuote returns s quoted as a shell argument, if necessary. -func maybeShellQuote(s string) string { +// MaybeShellQuote returns s quoted as a shell argument, if necessary. +func MaybeShellQuote(s string) string { switch { case s == "": return "''" diff --git a/internal/chezmoi/maybeshellquote_test.go b/internal/chezmoi/maybeshellquote_test.go index 195ddc8e4e7..30c2dbc16fb 100644 --- a/internal/chezmoi/maybeshellquote_test.go +++ b/internal/chezmoi/maybeshellquote_test.go @@ -19,6 +19,6 @@ func TestMaybeShellQuote(t *testing.T) { `a/b`: `a/b`, `a b`: `'a b'`, } { - assert.Equal(t, expected, maybeShellQuote(s), "quoting %q", s) + assert.Equal(t, expected, MaybeShellQuote(s), "quoting %q", s) } } diff --git a/internal/chezmoi/verbosemutator.go b/internal/chezmoi/verbosemutator.go index 543a1c1bde1..390c7ac6f41 100644 --- a/internal/chezmoi/verbosemutator.go +++ b/internal/chezmoi/verbosemutator.go @@ -34,7 +34,7 @@ func NewVerboseMutator(w io.Writer, m Mutator, colored bool) *VerboseMutator { // Chmod implements Mutator.Chmod. func (m *VerboseMutator) Chmod(name string, mode os.FileMode) error { - action := fmt.Sprintf("chmod %o %s", mode, maybeShellQuote(name)) + action := fmt.Sprintf("chmod %o %s", mode, MaybeShellQuote(name)) err := m.m.Chmod(name, mode) if err == nil { _, _ = fmt.Fprintln(m.w, action) @@ -58,7 +58,7 @@ func (m *VerboseMutator) IdempotentCmdOutput(cmd *exec.Cmd) ([]byte, error) { // Mkdir implements Mutator.Mkdir. func (m *VerboseMutator) Mkdir(name string, perm os.FileMode) error { - action := fmt.Sprintf("mkdir -m %o %s", perm, maybeShellQuote(name)) + action := fmt.Sprintf("mkdir -m %o %s", perm, MaybeShellQuote(name)) err := m.m.Mkdir(name, perm) if err == nil { _, _ = fmt.Fprintln(m.w, action) @@ -70,7 +70,7 @@ func (m *VerboseMutator) Mkdir(name string, perm os.FileMode) error { // RemoveAll implements Mutator.RemoveAll. func (m *VerboseMutator) RemoveAll(name string) error { - action := fmt.Sprintf("rm -rf %s", maybeShellQuote(name)) + action := fmt.Sprintf("rm -rf %s", MaybeShellQuote(name)) err := m.m.RemoveAll(name) if err == nil { _, _ = fmt.Fprintln(m.w, action) @@ -82,7 +82,7 @@ func (m *VerboseMutator) RemoveAll(name string) error { // Rename implements Mutator.Rename. func (m *VerboseMutator) Rename(oldpath, newpath string) error { - action := fmt.Sprintf("mv %s %s", maybeShellQuote(oldpath), maybeShellQuote(newpath)) + action := fmt.Sprintf("mv %s %s", MaybeShellQuote(oldpath), MaybeShellQuote(newpath)) err := m.m.Rename(oldpath, newpath) if err == nil { _, _ = fmt.Fprintln(m.w, action) @@ -111,7 +111,7 @@ func (m *VerboseMutator) Stat(name string) (os.FileInfo, error) { // WriteFile implements Mutator.WriteFile. func (m *VerboseMutator) WriteFile(name string, data []byte, perm os.FileMode, currData []byte) error { - action := fmt.Sprintf("install -m %o /dev/null %s", perm, maybeShellQuote(name)) + action := fmt.Sprintf("install -m %o /dev/null %s", perm, MaybeShellQuote(name)) err := m.m.WriteFile(name, data, perm, currData) if err == nil { _, _ = fmt.Fprintln(m.w, action) @@ -147,7 +147,7 @@ func (m *VerboseMutator) WriteFile(name string, data []byte, perm os.FileMode, c // WriteSymlink implements Mutator.WriteSymlink. func (m *VerboseMutator) WriteSymlink(oldname, newname string) error { - action := fmt.Sprintf("ln -sf %s %s", maybeShellQuote(oldname), maybeShellQuote(newname)) + action := fmt.Sprintf("ln -sf %s %s", MaybeShellQuote(oldname), MaybeShellQuote(newname)) err := m.m.WriteSymlink(oldname, newname) if err == nil { _, _ = fmt.Fprintln(m.w, action) @@ -161,13 +161,13 @@ func (m *VerboseMutator) WriteSymlink(oldname, newname string) error { func cmdString(cmd *exec.Cmd) string { components := append([]string{cmd.Path}, cmd.Args[1:]...) for i, component := range components { - components[i] = maybeShellQuote(component) + components[i] = MaybeShellQuote(component) } s := strings.Join(components, " ") if cmd.Dir == "" { return s } - return fmt.Sprintf("( cd %s && %s )", maybeShellQuote(cmd.Dir), s) + return fmt.Sprintf("( cd %s && %s )", MaybeShellQuote(cmd.Dir), s) } func isBinary(data []byte) bool { From cd50d7b91a81a696cd05f63e52474969a2a83afd Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Wed, 4 Dec 2019 17:38:45 +0100 Subject: [PATCH 2/3] Add ShellQuoteArgs --- internal/chezmoi/maybeshellquote.go | 10 ++++++++++ internal/chezmoi/verbosemutator.go | 6 +----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/internal/chezmoi/maybeshellquote.go b/internal/chezmoi/maybeshellquote.go index 4086aa9c6b7..2491086f41e 100644 --- a/internal/chezmoi/maybeshellquote.go +++ b/internal/chezmoi/maybeshellquote.go @@ -2,6 +2,7 @@ package chezmoi import ( "regexp" + "strings" ) var needShellQuoteRegexp = regexp.MustCompile(`[^+\-./0-9A-Z_a-z]`) @@ -49,3 +50,12 @@ func MaybeShellQuote(s string) string { return s } } + +// ShellQuoteArgs returs args shell quoted and joined into a single string. +func ShellQuoteArgs(args []string) string { + shellQuotedArgs := make([]string, 0, len(args)) + for _, arg := range args { + shellQuotedArgs = append(shellQuotedArgs, MaybeShellQuote(arg)) + } + return strings.Join(shellQuotedArgs, " ") +} diff --git a/internal/chezmoi/verbosemutator.go b/internal/chezmoi/verbosemutator.go index 390c7ac6f41..f1839900224 100644 --- a/internal/chezmoi/verbosemutator.go +++ b/internal/chezmoi/verbosemutator.go @@ -159,11 +159,7 @@ func (m *VerboseMutator) WriteSymlink(oldname, newname string) error { // cmdString returns a string representation of cmd. func cmdString(cmd *exec.Cmd) string { - components := append([]string{cmd.Path}, cmd.Args[1:]...) - for i, component := range components { - components[i] = MaybeShellQuote(component) - } - s := strings.Join(components, " ") + s := ShellQuoteArgs(append([]string{cmd.Path}, cmd.Args[1:]...)) if cmd.Dir == "" { return s } From cf457c437172435de950d99ad6298784bc7cf3d7 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Wed, 4 Dec 2019 17:41:56 +0100 Subject: [PATCH 3/3] Quote command arguments if needed in template error messages --- cmd/secretbitwarden.go | 5 +++-- cmd/secretgeneric.go | 7 ++++--- cmd/secretgopass.go | 4 ++-- cmd/secretkeepassxc.go | 7 ++++--- cmd/secretonepassword.go | 8 ++++---- cmd/secretpass.go | 4 ++-- cmd/secretvault.go | 6 +++--- 7 files changed, 22 insertions(+), 19 deletions(-) diff --git a/cmd/secretbitwarden.go b/cmd/secretbitwarden.go index e1f2a36ce90..86d4d70d8a9 100644 --- a/cmd/secretbitwarden.go +++ b/cmd/secretbitwarden.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/spf13/cobra" + "github.com/twpayne/chezmoi/internal/chezmoi" ) var bitwardenCmd = &cobra.Command{ @@ -46,11 +47,11 @@ func (c *Config) bitwardenFunc(args ...string) interface{} { cmd.Stderr = os.Stderr output, err := c.mutator.IdempotentCmdOutput(cmd) if err != nil { - panic(fmt.Errorf("bitwarden: %s %s: %w\n%s", name, strings.Join(args, " "), err, output)) + panic(fmt.Errorf("bitwarden: %s %s: %w\n%s", name, chezmoi.ShellQuoteArgs(args), err, output)) } var data interface{} if err := json.Unmarshal(output, &data); err != nil { - panic(fmt.Errorf("bitwarden: %s %s: %w\n%s", name, strings.Join(args, " "), err, output)) + panic(fmt.Errorf("bitwarden: %s %s: %w\n%s", name, chezmoi.ShellQuoteArgs(args), err, output)) } bitwardenCache[key] = data return data diff --git a/cmd/secretgeneric.go b/cmd/secretgeneric.go index 7c587b15981..67b96b17a6b 100644 --- a/cmd/secretgeneric.go +++ b/cmd/secretgeneric.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/spf13/cobra" + "github.com/twpayne/chezmoi/internal/chezmoi" ) var genericSecretCmd = &cobra.Command{ @@ -49,7 +50,7 @@ func (c *Config) secretFunc(args ...string) interface{} { cmd.Stderr = os.Stderr output, err := c.mutator.IdempotentCmdOutput(cmd) if err != nil { - panic(fmt.Errorf("secret: %s %s: %w\n%s", name, strings.Join(args, " "), err, output)) + panic(fmt.Errorf("secret: %s %s: %w\n%s", name, chezmoi.ShellQuoteArgs(args), err, output)) } value := bytes.TrimSpace(output) secretCache[key] = value @@ -67,11 +68,11 @@ func (c *Config) secretJSONFunc(args ...string) interface{} { cmd.Stderr = os.Stderr output, err := c.mutator.IdempotentCmdOutput(cmd) if err != nil { - panic(fmt.Errorf("secretJSON: %s %s: %w\n%s", name, strings.Join(args, " "), err, output)) + panic(fmt.Errorf("secretJSON: %s %s: %w\n%s", name, chezmoi.ShellQuoteArgs(args), err, output)) } var value interface{} if err := json.Unmarshal(output, &value); err != nil { - panic(fmt.Errorf("secretJSON: %s %s: %w\n%s", name, strings.Join(args, " "), err, output)) + panic(fmt.Errorf("secretJSON: %s %s: %w\n%s", name, chezmoi.ShellQuoteArgs(args), err, output)) } secretJSONCache[key] = value return value diff --git a/cmd/secretgopass.go b/cmd/secretgopass.go index f07d59854ef..f459e9efe21 100644 --- a/cmd/secretgopass.go +++ b/cmd/secretgopass.go @@ -4,9 +4,9 @@ import ( "bytes" "fmt" "os/exec" - "strings" "github.com/spf13/cobra" + "github.com/twpayne/chezmoi/internal/chezmoi" ) var gopassCmd = &cobra.Command{ @@ -42,7 +42,7 @@ func (c *Config) gopassFunc(id string) string { cmd := exec.Command(name, args...) output, err := c.mutator.IdempotentCmdOutput(cmd) if err != nil { - panic(fmt.Errorf("gopass: %s %s: %w", name, strings.Join(args, " "), err)) + panic(fmt.Errorf("gopass: %s %s: %w", name, chezmoi.ShellQuoteArgs(args), err)) } var password string if index := bytes.IndexByte(output, '\n'); index != -1 { diff --git a/cmd/secretkeepassxc.go b/cmd/secretkeepassxc.go index 292d38eea58..91b4c343e10 100644 --- a/cmd/secretkeepassxc.go +++ b/cmd/secretkeepassxc.go @@ -11,6 +11,7 @@ import ( "strings" "github.com/spf13/cobra" + "github.com/twpayne/chezmoi/internal/chezmoi" "golang.org/x/crypto/ssh/terminal" ) @@ -64,11 +65,11 @@ func (c *Config) keePassXCFunc(entry string) map[string]string { args = append(args, c.KeePassXC.Database, entry) output, err := c.runKeePassXCCLICommand(name, args) if err != nil { - panic(fmt.Errorf("keepassxc: %s %s: %w", name, strings.Join(args, " "), err)) + panic(fmt.Errorf("keepassxc: %s %s: %w", name, chezmoi.ShellQuoteArgs(args), err)) } data, err := parseKeyPassXCOutput(output) if err != nil { - panic(fmt.Errorf("keepassxc: %s %s: %w", name, strings.Join(args, " "), err)) + panic(fmt.Errorf("keepassxc: %s %s: %w", name, chezmoi.ShellQuoteArgs(args), err)) } keePassXCCache[entry] = data return data @@ -91,7 +92,7 @@ func (c *Config) keePassXCAttributeFunc(entry, attribute string) string { args = append(args, c.KeePassXC.Database, entry) output, err := c.runKeePassXCCLICommand(name, args) if err != nil { - panic(fmt.Errorf("keepassxc: %s %s: %w", name, strings.Join(args, " "), err)) + panic(fmt.Errorf("keepassxc: %s %s: %w", name, chezmoi.ShellQuoteArgs(args), err)) } outputStr := strings.TrimSpace(string(output)) keePassXCAttributeCache[key] = outputStr diff --git a/cmd/secretonepassword.go b/cmd/secretonepassword.go index fd90c1fc8c3..7b511a875ed 100644 --- a/cmd/secretonepassword.go +++ b/cmd/secretonepassword.go @@ -5,9 +5,9 @@ import ( "fmt" "os" "os/exec" - "strings" "github.com/spf13/cobra" + "github.com/twpayne/chezmoi/internal/chezmoi" ) var onepasswordCmd = &cobra.Command{ @@ -49,11 +49,11 @@ func (c *Config) onepasswordFunc(item string) interface{} { cmd.Stderr = os.Stderr output, err := c.mutator.IdempotentCmdOutput(cmd) if err != nil { - panic(fmt.Errorf("onepassword: %s %s: %w\n%s", name, strings.Join(args, " "), err, output)) + panic(fmt.Errorf("onepassword: %s %s: %w\n%s", name, chezmoi.ShellQuoteArgs(args), err, output)) } var data interface{} if err := json.Unmarshal(output, &data); err != nil { - panic(fmt.Errorf("onepassword: %s %s: %w\n%s", name, strings.Join(args, " "), err, output)) + panic(fmt.Errorf("onepassword: %s %s: %w\n%s", name, chezmoi.ShellQuoteArgs(args), err, output)) } onepasswordCache[item] = data return data @@ -70,7 +70,7 @@ func (c *Config) onepasswordDocumentFunc(item string) interface{} { cmd.Stderr = os.Stderr output, err := c.mutator.IdempotentCmdOutput(cmd) if err != nil { - panic(fmt.Errorf("onepassword: %s %s: %w\n%s", name, strings.Join(args, " "), err, output)) + panic(fmt.Errorf("onepassword: %s %s: %w\n%s", name, chezmoi.ShellQuoteArgs(args), err, output)) } onepasswordDocumentCache[item] = string(output) return string(output) diff --git a/cmd/secretpass.go b/cmd/secretpass.go index 61e45262e06..1a64318208d 100644 --- a/cmd/secretpass.go +++ b/cmd/secretpass.go @@ -4,9 +4,9 @@ import ( "bytes" "fmt" "os/exec" - "strings" "github.com/spf13/cobra" + "github.com/twpayne/chezmoi/internal/chezmoi" ) var passCmd = &cobra.Command{ @@ -42,7 +42,7 @@ func (c *Config) passFunc(id string) string { cmd := exec.Command(name, args...) output, err := c.mutator.IdempotentCmdOutput(cmd) if err != nil { - panic(fmt.Errorf("pass: %s %s: %w", name, strings.Join(args, " "), err)) + panic(fmt.Errorf("pass: %s %s: %w", name, chezmoi.ShellQuoteArgs(args), err)) } var password string if index := bytes.IndexByte(output, '\n'); index != -1 { diff --git a/cmd/secretvault.go b/cmd/secretvault.go index 8e01e4486b9..d00ed0953f5 100644 --- a/cmd/secretvault.go +++ b/cmd/secretvault.go @@ -5,9 +5,9 @@ import ( "fmt" "os" "os/exec" - "strings" "github.com/spf13/cobra" + "github.com/twpayne/chezmoi/internal/chezmoi" ) var vaultCmd = &cobra.Command{ @@ -45,11 +45,11 @@ func (c *Config) vaultFunc(key string) interface{} { cmd.Stderr = os.Stderr output, err := c.mutator.IdempotentCmdOutput(cmd) if err != nil { - panic(fmt.Errorf("vault: %s %s: %w\n%s", name, strings.Join(args, " "), err, output)) + panic(fmt.Errorf("vault: %s %s: %w\n%s", name, chezmoi.ShellQuoteArgs(args), err, output)) } var data interface{} if err := json.Unmarshal(output, &data); err != nil { - panic(fmt.Errorf("vault: %s %s: %w\n%s", name, strings.Join(args, " "), err, output)) + panic(fmt.Errorf("vault: %s %s: %w\n%s", name, chezmoi.ShellQuoteArgs(args), err, output)) } vaultCache[key] = data return data