Skip to content

Commit

Permalink
expand: tilde expansions shouldn't expand further
Browse files Browse the repository at this point in the history
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 mvdan#356.
  • Loading branch information
mvdan committed Feb 22, 2019
1 parent 8655a14 commit f475721
Show file tree
Hide file tree
Showing 3 changed files with 24 additions and 10 deletions.
26 changes: 17 additions & 9 deletions expand/expand.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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 {
Expand Down
6 changes: 6 additions & 0 deletions interp/interp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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" ]]`,
"",
Expand Down
2 changes: 1 addition & 1 deletion interp/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down

0 comments on commit f475721

Please sign in to comment.