diff --git a/rhel/dockerfile/dockerfile.go b/rhel/dockerfile/dockerfile.go index e49a3688a..3e24df4e0 100644 --- a/rhel/dockerfile/dockerfile.go +++ b/rhel/dockerfile/dockerfile.go @@ -100,9 +100,9 @@ func (p *labelParser) Run() error { if strings.Contains(v, `escape=`) { eq := strings.IndexByte(v, '=') if eq == -1 { - return fmt.Errorf("botched parser directive: %#q", i.val) + panic("string changed while parsing?") } - esc, _ := utf8.DecodeRuneInString(v[:eq+1]) + esc, _ := utf8.DecodeRuneInString(v[eq+1:]) p.lex.Escape(esc) p.unquote.Escape(esc) p.vars.Escape(esc) diff --git a/rhel/dockerfile/vars.go b/rhel/dockerfile/vars.go index 418908a07..3308c68ee 100644 --- a/rhel/dockerfile/vars.go +++ b/rhel/dockerfile/vars.go @@ -214,7 +214,11 @@ func (v *Vars) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error v.state = varEmit return nDst, nSrc, transform.ErrShortDst case v.state == varError: - return nDst, nSrc, fmt.Errorf("dockerfile: bad expansion of %q: %s", v.varName.String(), v.varExpand.String()) + return nDst, nSrc, fmt.Errorf("dockerfile: bad expansion of %q: %s (%v)", + v.varName.String(), + v.varExpand.String(), + v.expand, + ) } nDst += n v.state = varConsume @@ -222,6 +226,10 @@ func (v *Vars) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error panic("state botch") } } + if v.esc { + // Ended in a "bare" escape character. Just pass it through. + nDst += utf8.EncodeRune(dst[nDst:], v.escchar) + } if v.state == varBareword && atEOF { // Hit EOF, so variable name is complete. n, done := v.emit(dst[nDst:]) @@ -296,9 +304,22 @@ func (v *Vars) emit(dst []byte) (int, bool) { suffix := v.expand == varTrimSuffix || v.expand == varTrimSuffixGreedy re, err := convertPattern([]byte(word), greedy, suffix) if err != nil { - panic("TODO(hank): TrimPrefix/TrimSuffix: " + err.Error()) + v.state = varError + return 0, true + } + ms := re.FindStringSubmatch(val) + switch len(ms) { + case 0, 1: + // No match, do nothing. + case 2: + if suffix { + val = strings.TrimSuffix(val, ms[1]) + } else { + val = strings.TrimPrefix(val, ms[1]) + } + default: + panic(fmt.Sprintf("pattern compiler is acting up; got: %#v", ms)) } - val = re.ReplaceAllLiteralString(val, "") default: panic("expand state botch") } @@ -311,16 +332,28 @@ func (v *Vars) emit(dst []byte) (int, bool) { // ConvertPattern transforms "pat" from (something like) the POSIX sh pattern // language to a regular expression, then returns the compiled regexp. +// +// The resulting regexp reports the prefix/suffix to be removed as the first +// submatch when executed. +// +// This conversion is tricky, because extra hoops are needed to work around the +// leftmost-first behavior. func convertPattern(pat []byte, greedy bool, suffix bool) (_ *regexp.Regexp, err error) { var rePat strings.Builder rePat.Grow(len(pat) * 2) // 🤷 - - if !greedy { - rePat.WriteString(`(?U)`) + // This is needed to "push" a suffix pattern to the correct place. Note that + // the "greediness" is backwards: this is the input that's _not_ the + // pattern. + pad := `(?:.*)` + if greedy { + pad = `(?:.*?)` } - if !suffix { - rePat.WriteByte('^') + + rePat.WriteByte('^') + if suffix { + rePat.WriteString(pad) } + rePat.WriteByte('(') off := 0 r, sz := rune(0), 0 for ; off < len(pat); off += sz { @@ -332,6 +365,9 @@ func convertPattern(pat []byte, greedy bool, suffix bool) (_ *regexp.Regexp, err switch r { case '*': // Kleene star rePat.WriteString(`.*`) + if !suffix && !greedy { + rePat.WriteByte('?') + } case '?': // Single char rePat.WriteByte('.') case '\\': @@ -355,9 +391,11 @@ func convertPattern(pat []byte, greedy bool, suffix bool) (_ *regexp.Regexp, err rePat.WriteRune(r) } } - if suffix { - rePat.WriteByte('$') + rePat.WriteByte(')') + if !suffix { + rePat.WriteString(pad) } + rePat.WriteByte('$') return regexp.Compile(rePat.String()) } @@ -430,26 +468,17 @@ const ( type varExpand uint8 const ( - // Expand to the named variable or the empty string. - varExpandSimple varExpand = iota - // Expand to the named variable or the provided word. - varExpandDefault - varExpandDefaultNull - // Set the named variable to the provided word if unset, then expand to the named variable. - varSetDefault - varSetDefaultNull - // Expand to the provided word or the empty string. - varExpandAlternate - varExpandAlternateNull - // Error if unset. - varErrIfUnset - varErrIfUnsetNull - // Expand by interpreting "word" as a pattern, then expanding "parameter" and removing the smallest suffix. - varTrimSuffix - // Expand by interpreting "word" as a pattern, then expanding "parameter" and removing the largest suffix. - varTrimSuffixGreedy - // Expand by interpreting "word" as a pattern, then expanding "parameter" and removing the smallest prefix. - varTrimPrefix - // Expand by interpreting "word" as a pattern, then expanding "parameter" and removing the largest prefix. - varTrimPrefixGreedy + varExpandSimple varExpand = iota // simple expansion + varExpandDefault // default expansion + varExpandDefaultNull // default+null expansion + varSetDefault // set default + varSetDefaultNull // set default, incl. null + varExpandAlternate // alternate expansion + varExpandAlternateNull // alternate expanxion, incl. null + varErrIfUnset // error if unset + varErrIfUnsetNull // error if unset or null + varTrimSuffix // trim suffix + varTrimSuffixGreedy // greedy trim suffix + varTrimPrefix // trim prefix + varTrimPrefixGreedy // greedy trim prefix )