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 )