diff --git a/pkg/runner/expression.go b/pkg/runner/expression.go index 3daf09f..e9dc56b 100644 --- a/pkg/runner/expression.go +++ b/pkg/runner/expression.go @@ -16,10 +16,9 @@ import ( "gopkg.in/godo.v2/glob" ) -var contextPattern, expressionPattern, operatorPattern *regexp.Regexp +var expressionPattern, operatorPattern *regexp.Regexp func init() { - contextPattern = regexp.MustCompile(`^([^.]*(?:\[.+])*)(?:\.([\w-]+))?(.*)$`) expressionPattern = regexp.MustCompile(`\${{\s*(.+?)\s*}}`) operatorPattern = regexp.MustCompile("^[!=><|&]+$") } @@ -123,22 +122,88 @@ func (ee *expressionEvaluator) InterpolateWithStringCheck(in string) (string, bo // Rewrite tries to transform any javascript property accessor into its bracket notation. // For instance, "object.property" would become "object['property']". func (ee *expressionEvaluator) Rewrite(in string) string { - re := in + var buf strings.Builder + r := strings.NewReader(in) for { - matches := contextPattern.FindStringSubmatch(re) - if matches == nil { - // No global match, we're done! + c, _, err := r.ReadRune() + if err == io.EOF { break } - if matches[2] == "" { - // No property match, we're done! - break + //nolint + switch { + default: + buf.WriteRune(c) + case c == '\'': + buf.WriteRune(c) + ee.advString(&buf, r) + case c == '.': + buf.WriteString("['") + ee.advPropertyName(&buf, r) + buf.WriteString("']") } - - re = fmt.Sprintf("%s['%s']%s", matches[1], matches[2], matches[3]) } + return buf.String() +} - return re +func (*expressionEvaluator) advString(w *strings.Builder, r *strings.Reader) error { + for { + c, _, err := r.ReadRune() + if err != nil { + return err + } + if c != '\'' { + w.WriteRune(c) //nolint + continue + } + + // Handles a escaped string: ex. 'It''s ok' + c, _, err = r.ReadRune() + if err != nil { + w.WriteString("'") //nolint + return err + } + if c != '\'' { + w.WriteString("'") //nolint + if err := r.UnreadRune(); err != nil { + return err + } + break + } + w.WriteString(`\'`) //nolint + } + return nil +} + +func (*expressionEvaluator) advPropertyName(w *strings.Builder, r *strings.Reader) error { + for { + c, _, err := r.ReadRune() + if err != nil { + return err + } + if !isLetter(c) { + if err := r.UnreadRune(); err != nil { + return err + } + break + } + w.WriteRune(c) //nolint + } + return nil +} + +func isLetter(c rune) bool { + switch { + case c >= 'a' && c <= 'z': + return true + case c >= 'A' && c <= 'Z': + return true + case c >= '0' && c <= '9': + return true + case c == '_' || c == '-': + return true + default: + return false + } } func (rc *RunContext) newVM() *otto.Otto { diff --git a/pkg/runner/expression_test.go b/pkg/runner/expression_test.go index df0a50b..fb2063c 100644 --- a/pkg/runner/expression_test.go +++ b/pkg/runner/expression_test.go @@ -89,6 +89,8 @@ func TestEvaluate(t *testing.T) { {"(fromJson('{\"foo\":\"bar\"}')).foo", "bar", ""}, {"hashFiles('**/non-extant-files')", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", ""}, {"hashFiles('**/non-extant-files', '**/more-non-extant-files')", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", ""}, + {"hashFiles('**/non.extant.files')", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", ""}, + {"hashFiles('**/non''extant''files')", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", ""}, {"success()", "true", ""}, {"failure()", "false", ""}, {"always()", "true", ""},