Skip to content

Commit

Permalink
Unescape special chars in params
Browse files Browse the repository at this point in the history
  • Loading branch information
nineinchnick committed Apr 27, 2021
1 parent 6228ccb commit 9792fc8
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 44 deletions.
34 changes: 28 additions & 6 deletions env/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ import (
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
"unicode/utf8"

"github.com/xo/dburl"
"github.com/xo/usql/text"
Expand Down Expand Up @@ -366,18 +368,38 @@ func Exec(s string) (string, error) {
return string(buf), nil
}

var cleanDoubleRE = regexp.MustCompile(`''`)
var cleanDoubleRE = regexp.MustCompile(`(^|[^\\])''`)

// Dequote unquotes a string.
func Dequote(s string, c byte) (string, error) {
if len(s) < 2 || s[len(s)-1] != c {
func Dequote(s string, quote byte) (string, error) {
if len(s) < 2 || s[len(s)-1] != quote {
return "", text.ErrUnterminatedQuotedString
}
s = s[1 : len(s)-1]
if c != '\'' {
return s, nil
if quote == '\'' {
s = cleanDoubleRE.ReplaceAllString(s, "$1\\'")
}
return cleanDoubleRE.ReplaceAllString(s, "'"), nil

// this is the last part of strconv.Unquote
var runeTmp [utf8.UTFMax]byte
buf := make([]byte, 0, 3*len(s)/2) // Try to avoid more allocations.
for len(s) > 0 {
c, multibyte, ss, err := strconv.UnquoteChar(s, quote)
switch {
case err != nil && err == strconv.ErrSyntax:
return "", text.ErrInvalidQuotedString
case err != nil:
return "", err
}
s = ss
if c < utf8.RuneSelf || !multibyte {
buf = append(buf, byte(c))
} else {
n := utf8.EncodeRune(runeTmp[:], c)
buf = append(buf, runeTmp[:n]...)
}
}
return string(buf), nil
}

// Getvar retrieves an environment variable.
Expand Down
79 changes: 41 additions & 38 deletions stmt/params_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,50 +48,53 @@ func TestDecodeParamsGetAll(t *testing.T) {
tests := []struct {
s string
exp []string
err bool
err error
}{
{``, nil, false},
{` `, nil, false},
{` :foo`, []string{`bar`}, false},
{` :'foo`, nil, true},
{`:'foo'`, []string{`'bar'`}, false},
{`:'foo':foo`, []string{`'bar'bar`}, false},
{`:'foo':foo:"foo"`, []string{`'bar'bar"bar"`}, false},
{`:'foo':foo:foo`, []string{`'bar'barbar`}, false},
{` :'foo':foo:foo`, []string{`'bar'barbar`}, false},
{` :'foo':yes:foo`, []string{`'bar':yesbar`}, false},
{` :foo `, []string{`bar`}, false},
{`:foo:foo`, []string{`barbar`}, false},
{` :foo:foo `, []string{`barbar`}, false},
{` :foo:foo `, []string{`barbar`}, false},
{`'hello'`, []string{`hello`}, false}, // 14
{` 'hello''yes' `, []string{`hello'yes`}, false},
{` 'hello':'yes' `, []string{`hello:'yes'`}, false},
{` :'foo `, nil, true},
{` :'foo bar`, nil, true},
{` :'foo bar`, nil, true},
{` :'foo bar `, nil, true},
{" `foo", nil, true},
{" `foo bar`", []string{"foo bar"}, false},
{" `foo :foo`", []string{"foo :foo"}, false},
{` :'foo':"foo"`, []string{`'bar'"bar"`}, false},
{` :'foo' :"foo" `, []string{`'bar'`, `"bar"`}, false},
{` :'foo' :"foo"`, []string{`'bar'`, `"bar"`}, false},
{` :'foo' :"foo"`, []string{`'bar'`, `"bar"`}, false},
{` :'foo' :"foo" `, []string{`'bar'`, `"bar"`}, false},
{` :'foo' :"foo" :foo `, []string{`'bar'`, `"bar"`, `bar`}, false},
{` :'foo':foo:"foo" `, []string{`'bar'bar"bar"`}, false}, // 30
{` :'foo''yes':'foo' `, []string{`'bar'yes'bar'`}, false},
{` :'foo' 'yes' :'foo' `, []string{`'bar'`, `yes`, `'bar'`}, false},
{` 'yes':'foo':"foo"'blah''no' "\ntest" `, []string{`yes'bar'"bar"blah'no`, `\ntest`}, false},
{``, nil, nil},
{` `, nil, nil},
{` :foo`, []string{`bar`}, nil},
{` :'foo`, nil, text.ErrUnterminatedQuotedString},
{`:'foo'`, []string{`'bar'`}, nil},
{`:'foo':foo`, []string{`'bar'bar`}, nil},
{`:'foo':foo:"foo"`, []string{`'bar'bar"bar"`}, nil},
{`:'foo':foo:foo`, []string{`'bar'barbar`}, nil},
{` :'foo':foo:foo`, []string{`'bar'barbar`}, nil},
{` :'foo':yes:foo`, []string{`'bar':yesbar`}, nil},
{` :foo `, []string{`bar`}, nil},
{`:foo:foo`, []string{`barbar`}, nil},
{` :foo:foo `, []string{`barbar`}, nil},
{` :foo:foo `, []string{`barbar`}, nil},
{`'hello'`, []string{`hello`}, nil}, // 14
{` 'hello''yes' `, []string{`hello'yes`}, nil},
{` 'hello\'...\'yes' `, []string{`hello'...'yes`}, nil},
{` "hello\'...\'yes" `, nil, text.ErrInvalidQuotedString},
{` "hello\"...\"yes" `, nil, text.ErrInvalidQuotedString},
{` 'hello':'yes' `, []string{`hello:'yes'`}, nil},
{` :'foo `, nil, text.ErrUnterminatedQuotedString},
{` :'foo bar`, nil, text.ErrUnterminatedQuotedString},
{` :'foo bar`, nil, text.ErrUnterminatedQuotedString},
{` :'foo bar `, nil, text.ErrUnterminatedQuotedString},
{" `foo", nil, text.ErrUnterminatedQuotedString},
{" `foo bar`", []string{"foo bar"}, nil},
{" `foo :foo`", []string{"foo :foo"}, nil},
{` :'foo':"foo"`, []string{`'bar'"bar"`}, nil},
{` :'foo' :"foo" `, []string{`'bar'`, `"bar"`}, nil},
{` :'foo' :"foo"`, []string{`'bar'`, `"bar"`}, nil},
{` :'foo' :"foo"`, []string{`'bar'`, `"bar"`}, nil},
{` :'foo' :"foo" `, []string{`'bar'`, `"bar"`}, nil},
{` :'foo' :"foo" :foo `, []string{`'bar'`, `"bar"`, `bar`}, nil},
{` :'foo':foo:"foo" `, []string{`'bar'bar"bar"`}, nil}, // 30
{` :'foo''yes':'foo' `, []string{`'bar'yes'bar'`}, nil},
{` :'foo' 'yes' :'foo' `, []string{`'bar'`, `yes`, `'bar'`}, nil},
{` 'yes':'foo':"foo"'blah''no' "\ntest" `, []string{`yes'bar'"bar"blah'no`, "\ntest"}, nil},
}
for i, test := range tests {
vals, err := DecodeParams(test.s).GetAll(testUnquote(u, t, i, test.s))
if test.err && err != text.ErrUnterminatedQuotedString {
t.Fatalf("test %d expected unterminated quoted string error, got: %v", i, err)
if err != test.err {
t.Fatalf("test %d for %q expected err %v, got: %v", i, test.s, test.err, err)
}
if !reflect.DeepEqual(vals, test.exp) {
t.Errorf("test %d expected %v, got: %v", i, test.exp, vals)
t.Errorf("test %d for %q expected %v, got: %v", i, test.s, test.exp, vals)
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions stmt/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ func readString(r []rune, i, end int, quote rune, tag string) (int, bool) {
for ; i < end; i++ {
c, next = r[i], grab(r, i+1, end)
switch {
case quote == '\'' && c == '\\':
i++
prev = 0
continue
case quote == '\'' && c == '\'' && next == '\'':
case quote == '\'' && c == '\'' && prev != '\'',
quote == '"' && c == '"',
Expand Down

0 comments on commit 9792fc8

Please sign in to comment.