From f475721ae343b9c61f4e4f9ae273680e2ee45c65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Fri, 22 Feb 2019 17:01:16 +0100 Subject: [PATCH] expand: tilde expansions shouldn't expand further To quote the latest version of the POSIX Shell spec: The pathname resulting from tilde expansion shall be treated as if quoted to prevent it being altered by field splitting and pathname expansion. That is, when HOME='/*', the command "echo ~" should just print "/*" and not do any globbing at all. This was usually not a problem on Unix-like systems, but Windows uses backslashes as path separators. Thus, HOME='C:\foo\bar' is a very normal occurrence. However, since we treated the result of tilde expansion as unquoted, "echo ~" would result in the output "C:foobar", since we'd eat the backslashes when taking care of character escapes. Fix that, and add a few tests for these edge cases. Fixes #356. --- expand/expand.go | 26 +++++++++++++++++--------- interp/interp_test.go | 6 ++++++ interp/test.go | 2 +- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/expand/expand.go b/expand/expand.go index 10437a51e..8b85bc5c7 100644 --- a/expand/expand.go +++ b/expand/expand.go @@ -414,7 +414,11 @@ func (cfg *Config) wordField(wps []syntax.WordPart, ql quoteLevel) ([]fieldPart, case *syntax.Lit: s := x.Value if i == 0 && ql == quoteNone { - s = cfg.expandUser(s) + if prefix, rest := cfg.expandUser(s); prefix != "" { + // TODO: return two separate fieldParts, + // like in wordFields? + s = prefix + rest + } } if ql == quoteDouble && strings.Contains(s, "\\") { buf := cfg.strBuilder() @@ -509,7 +513,12 @@ func (cfg *Config) wordFields(wps []syntax.WordPart) ([][]fieldPart, error) { case *syntax.Lit: s := x.Value if i == 0 { - s = cfg.expandUser(s) + prefix, rest := cfg.expandUser(s) + curField = append(curField, fieldPart{ + quote: quoteSingle, + val: prefix, + }) + s = rest } if strings.Contains(s, "\\") { buf := cfg.strBuilder() @@ -603,28 +612,27 @@ func (cfg *Config) quotedElems(pe *syntax.ParamExp) []string { return nil } -func (cfg *Config) expandUser(field string) string { +func (cfg *Config) expandUser(field string) (prefix, rest string) { if len(field) == 0 || field[0] != '~' { - return field + return "", field } name := field[1:] - rest := "" if i := strings.Index(name, "/"); i >= 0 { rest = name[i:] name = name[:i] } if name == "" { - return cfg.Env.Get("HOME").String() + rest + return cfg.Env.Get("HOME").String(), rest } if vr := cfg.Env.Get("HOME " + name); vr.IsSet() { - return vr.String() + rest + return vr.String(), rest } u, err := user.Lookup(name) if err != nil { - return field + return "", field } - return u.HomeDir + rest + return u.HomeDir, rest } func findAllIndex(pattern, name string, n int) [][]int { diff --git a/interp/interp_test.go b/interp/interp_test.go index 6c0da1efe..3bdebbd45 100644 --- a/interp/interp_test.go +++ b/interp/interp_test.go @@ -655,6 +655,12 @@ var runTests = []runTest{ {`[[ 'ab\c' == *\\* ]]`, ""}, {`[[ foo/bar == foo* ]]`, ""}, {"[[ a == [ab ]]", "exit status 1"}, + {`HOME='/*'; echo ~; echo "$HOME"`, "/*\n/*\n"}, + {`test -d ~`, ""}, + {`foo=~; test -d $foo`, ""}, + {`foo=~; test -d "$foo"`, ""}, + {`foo='~'; test -d $foo`, "exit status 1"}, + {`foo='~'; [ $foo == '~' ]`, ""}, { `[[ ~ == "$HOME" ]] && [[ ~/foo == "$HOME/foo" ]]`, "", diff --git a/interp/test.go b/interp/test.go index 2fa5db10d..53d8f7579 100644 --- a/interp/test.go +++ b/interp/test.go @@ -20,7 +20,7 @@ import ( func (r *Runner) bashTest(ctx context.Context, expr syntax.TestExpr, classic bool) string { switch x := expr.(type) { case *syntax.Word: - return r.literal(x) + return r.document(x) case *syntax.ParenTest: return r.bashTest(ctx, x.X, classic) case *syntax.BinaryTest: