github.com/quay/claircore@v1.5.28/rhel/dockerfile/vars.go (about)

     1  package dockerfile
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"strings"
     8  	"unicode"
     9  	"unicode/utf8"
    10  
    11  	"golang.org/x/text/transform"
    12  )
    13  
    14  // Vars is a text transformer that does variable expansion as described in the
    15  // Dockerfile Reference document.
    16  //
    17  // It supports POSIX sh-like expansions but not in the general forms, only the
    18  // ":-" (expand if unset) and ":+" (expand if set) versions.
    19  //
    20  // The transformation algorithm uses an escape metacharacter in front of the
    21  // variable metacharacter to allow a literal metacharacter to be passed through.
    22  // Any unrecognized escapes are passed through unmodified.
    23  type Vars struct {
    24  	v       map[string]string
    25  	escchar rune
    26  
    27  	state     varState
    28  	expand    varExpand
    29  	esc       bool
    30  	varName   strings.Builder
    31  	varExpand strings.Builder
    32  }
    33  
    34  // NewVars returns a Vars with the metacharacter set to '\' and no variables
    35  // defined.
    36  func NewVars() *Vars {
    37  	v := Vars{
    38  		escchar: '\\',
    39  		v:       make(map[string]string),
    40  	}
    41  	v.Reset()
    42  	return &v
    43  }
    44  
    45  // Escape changes the escape metacharacter.
    46  //
    47  // This is possible to do at any time, but may be inadvisable.
    48  func (v *Vars) Escape(r rune) {
    49  	v.escchar = r
    50  }
    51  
    52  // Set sets the variable "key" to "val".
    53  func (v *Vars) Set(key, val string) {
    54  	v.v[key] = val
    55  }
    56  
    57  // Clear unsets all variables.
    58  func (v *Vars) Clear() {
    59  	v.v = make(map[string]string)
    60  }
    61  
    62  // Assert that this is a Transformer.
    63  var _ transform.Transformer = (*Vars)(nil)
    64  
    65  // Reset implements transform.Transformer.
    66  //
    67  // This method does not reset calls to Set. Use Clear to reset stored variable
    68  // expansions.
    69  func (v *Vars) Reset() {
    70  	v.state = varConsume
    71  	v.expand = varExNone
    72  	v.esc = false
    73  	v.varName.Reset()
    74  	v.varExpand.Reset()
    75  }
    76  
    77  // Transform implements transform.Transformer.
    78  func (v *Vars) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
    79  	varStart := -1
    80  	r, sz := rune(0), 0
    81  	if v.state == varEmit {
    82  		// If we're here, we need to emit first thing.
    83  		var done bool
    84  		n, done := v.emit(dst)
    85  		if !done {
    86  			return 0, 0, transform.ErrShortDst
    87  		}
    88  		v.state = varConsume
    89  		return n, 0, nil
    90  	}
    91  	for ; nSrc < len(src); nSrc += sz {
    92  		r, sz = utf8.DecodeRune(src[nSrc:])
    93  		if r == utf8.RuneError {
    94  			err = transform.ErrShortSrc
    95  			return
    96  		}
    97  		if len(dst) == nDst {
    98  			err = transform.ErrShortDst
    99  			return
   100  		}
   101  		switch v.state {
   102  		case varConsume:
   103  			// Copy runes until there's an interesting one. This arm is the only
   104  			// one that deals with escape handling.
   105  			switch {
   106  			case !v.esc && r == v.escchar:
   107  				v.esc = true
   108  				continue
   109  			case v.esc && r == VarMeta:
   110  				v.esc = false
   111  			case v.esc: // Odd escape sequence, so just add back in the escape.
   112  				v.esc = false
   113  				nDst += utf8.EncodeRune(dst[nDst:], v.escchar)
   114  			case r == VarMeta:
   115  				// Record current position in case the destination is too small
   116  				// and the process backs out.
   117  				varStart = nSrc + sz
   118  				v.varName.Reset()
   119  				v.varExpand.Reset()
   120  				v.state = varBegin
   121  				continue
   122  			}
   123  			nDst += utf8.EncodeRune(dst[nDst:], r)
   124  		case varBegin:
   125  			// This arm is one rune beyond the metacharacter.
   126  			v.expand = varExNone
   127  			if r == '{' {
   128  				v.state = varBraceName
   129  				continue
   130  			}
   131  			v.state = varBareword
   132  			sz = 0 // Re-handle this rune.
   133  		case varBareword:
   134  			// This arm handles a bare variable, so no special expansion or
   135  			// braces.
   136  			if validName(r) {
   137  				v.varName.WriteRune(r)
   138  				continue
   139  			}
   140  			sz = 0 // Re-handle this rune.
   141  			n, done := v.emit(dst[nDst:])
   142  			if !done {
   143  				nSrc += sz
   144  				v.state = varEmit
   145  				return nDst, nSrc, transform.ErrShortDst
   146  			}
   147  			nDst += n
   148  			v.state = varConsume
   149  		case varBraceName:
   150  			// This arm begins on the rune after the opening brace.
   151  			switch r {
   152  			case ':':
   153  				// POSIX variable expansion has ':' as a modifier on the forms
   154  				// of expansion ('-', '=', '+'), but the Dockerfile reference
   155  				// only mentions ':-' and ':+'.
   156  				peek, psz := utf8.DecodeRune(src[nSrc+sz:])
   157  				switch peek {
   158  				case '-':
   159  					v.expand = varExDefault
   160  				case '+':
   161  					v.expand = varExIfSet
   162  				default:
   163  					nSrc = varStart
   164  					return nDst, nSrc, fmt.Errorf("bad default spec at %d", nSrc+sz)
   165  				}
   166  				sz += psz
   167  				v.state = varBraceExpand
   168  			case '}':
   169  				n, done := v.emit(dst[nDst:])
   170  				if !done {
   171  					nSrc += sz
   172  					v.state = varEmit
   173  					return nDst, nSrc, transform.ErrShortDst
   174  				}
   175  				nDst += n
   176  				v.state = varConsume
   177  			default:
   178  				v.varName.WriteRune(r)
   179  			}
   180  		case varBraceExpand:
   181  			// This arm begins on the rune after the expansion specifier.
   182  			if r != '}' {
   183  				v.varExpand.WriteRune(r)
   184  				continue
   185  			}
   186  			n, done := v.emit(dst[nDst:])
   187  			if !done {
   188  				nSrc += sz
   189  				v.state = varEmit
   190  				return nDst, nSrc, transform.ErrShortDst
   191  			}
   192  			nDst += n
   193  			v.state = varConsume
   194  		default:
   195  			panic("state botch")
   196  		}
   197  	}
   198  	if v.state == varBareword && atEOF {
   199  		// Hit EOF, so variable name is complete.
   200  		n, done := v.emit(dst[nDst:])
   201  		if !done {
   202  			v.state = varEmit
   203  			return nDst, nSrc, transform.ErrShortDst
   204  		}
   205  		nDst += n
   206  	}
   207  	return nDst, nSrc, nil
   208  }
   209  
   210  // ValidName tests whether the rune is valid in a variable name.
   211  func validName(r rune) bool {
   212  	return unicode.In(r, unicode.Letter, unicode.Digit) || r == '_' || r == '-'
   213  }
   214  
   215  // Emit writes out the expanded variable, using state accumulated in the
   216  // receiver. It does not reset state. It reports 0, false if there was not
   217  // enough space in dst.
   218  func (v *Vars) emit(dst []byte) (int, bool) {
   219  	dstSz := len(dst)
   220  	var w string
   221  	res, ok := v.v[v.varName.String()]
   222  	switch v.expand {
   223  	case varExNone: // Use what's returned from the lookup.
   224  		w = res
   225  	case varExDefault: // Use lookup or default.
   226  		if ok {
   227  			w = res
   228  			break
   229  		}
   230  		w = v.varExpand.String()
   231  	case varExIfSet: // Use the expando or nothing.
   232  		if ok {
   233  			w = v.varExpand.String()
   234  		}
   235  	default:
   236  		panic("expand state botch")
   237  	}
   238  	if dstSz < len(w) {
   239  		return 0, false
   240  	}
   241  	n := copy(dst, w)
   242  	return n, true
   243  }
   244  
   245  // Assert that this is a SpanningTransformer.
   246  var _ transform.SpanningTransformer = (*Vars)(nil)
   247  
   248  // Span implements transform.SpanningTransfomer.
   249  //
   250  // Callers can use this to avoid copying.
   251  func (v *Vars) Span(src []byte, atEOF bool) (int, error) {
   252  	// Look for meta.
   253  	i := bytes.IndexFunc(src, v.findMeta)
   254  	if i == -1 {
   255  		return len(src), nil
   256  	}
   257  	r, sz := utf8.DecodeRune(src[i:])
   258  	_, lsz := utf8.DecodeLastRune(src)
   259  	li := len(src) - lsz
   260  	switch {
   261  	case i == li && atEOF && r == v.escchar:
   262  		// Last rune was an escchar there's nothing else.
   263  		return i, errors.New("dangling escape")
   264  	case i == li && atEOF && r == VarMeta:
   265  		// Last rune was a meta there's nothing else.
   266  		return i, errors.New("dangling metacharacter")
   267  	case i == li && !atEOF:
   268  		// Last rune was an escchar or meta and there's more.
   269  		return li, transform.ErrEndOfSpan
   270  	case r == VarMeta:
   271  	default:
   272  		// Peek at the next rune to see if it's a valid escape.
   273  		nr, nsz := utf8.DecodeRune(src[i+sz:])
   274  		if r == v.escchar && nr == VarMeta {
   275  			// transforming escape
   276  			break
   277  		}
   278  		off := i + sz + nsz
   279  		n, err := v.Span(src[off:], atEOF)
   280  		n += off
   281  		return n, err
   282  	}
   283  	return i, transform.ErrEndOfSpan
   284  }
   285  
   286  // FindMeta is meant to be used with bytes.IndexFunc. It returns true on the
   287  // first possible meta character. Depending on the direction, the character may
   288  // be escaped.
   289  func (v *Vars) findMeta(r rune) bool {
   290  	return r == v.escchar || r == VarMeta
   291  }
   292  
   293  // VarMeta is the metacharacter for variables. It's not configurable.
   294  const VarMeta = '$'
   295  
   296  // VarState tracks what state the variable transformer is in.
   297  type varState uint8
   298  
   299  const (
   300  	varConsume varState = iota
   301  	varBegin
   302  	varBareword
   303  	varBraceName
   304  	varBraceExpand
   305  	varEmit
   306  )
   307  
   308  // VarExpand tracks how the current brace expression expects to be expanded.
   309  type varExpand uint8
   310  
   311  const (
   312  	// Expand to the named variable or the empty string.
   313  	varExNone varExpand = iota
   314  	// Expand to the named variable or the provided word.
   315  	varExDefault
   316  	// Expand to the provided word or the empty string.
   317  	varExIfSet
   318  )