get.pme.sh/pnats@v0.0.0-20240304004023-26bb5a137ed0/conf/parse.go (about) 1 // Copyright 2013-2018 The NATS Authors 2 // Licensed under the Apache License, Version 2.0 (the "License"); 3 // you may not use this file except in compliance with the License. 4 // You may obtain a copy of the License at 5 // 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 // Package conf supports a configuration file format used by gnatsd. It is 15 // a flexible format that combines the best of traditional 16 // configuration formats and newer styles such as JSON and YAML. 17 package conf 18 19 // The format supported is less restrictive than today's formats. 20 // Supports mixed Arrays [], nested Maps {}, multiple comment types (# and //) 21 // Also supports key value assignments using '=' or ':' or whiteSpace() 22 // e.g. foo = 2, foo : 2, foo 2 23 // maps can be assigned with no key separator as well 24 // semicolons as value terminators in key/value assignments are optional 25 // 26 // see parse_test.go for more examples. 27 28 import ( 29 "fmt" 30 "os" 31 "path/filepath" 32 "strconv" 33 "strings" 34 "time" 35 "unicode" 36 ) 37 38 type parser struct { 39 mapping map[string]interface{} 40 lx *lexer 41 42 // The current scoped context, can be array or map 43 ctx interface{} 44 45 // stack of contexts, either map or array/slice stack 46 ctxs []interface{} 47 48 // Keys stack 49 keys []string 50 51 // Keys stack as items 52 ikeys []item 53 54 // The config file path, empty by default. 55 fp string 56 57 // pedantic reports error when configuration is not correct. 58 pedantic bool 59 } 60 61 // Parse will return a map of keys to interface{}, although concrete types 62 // underly them. The values supported are string, bool, int64, float64, DateTime. 63 // Arrays and nested Maps are also supported. 64 func Parse(data string) (map[string]interface{}, error) { 65 p, err := parse(data, "", false) 66 if err != nil { 67 return nil, err 68 } 69 return p.mapping, nil 70 } 71 72 // ParseFile is a helper to open file, etc. and parse the contents. 73 func ParseFile(fp string) (map[string]interface{}, error) { 74 data, err := os.ReadFile(fp) 75 if err != nil { 76 return nil, fmt.Errorf("error opening config file: %v", err) 77 } 78 79 p, err := parse(string(data), fp, false) 80 if err != nil { 81 return nil, err 82 } 83 return p.mapping, nil 84 } 85 86 // ParseFileWithChecks is equivalent to ParseFile but runs in pedantic mode. 87 func ParseFileWithChecks(fp string) (map[string]interface{}, error) { 88 data, err := os.ReadFile(fp) 89 if err != nil { 90 return nil, err 91 } 92 93 p, err := parse(string(data), fp, true) 94 if err != nil { 95 return nil, err 96 } 97 98 return p.mapping, nil 99 } 100 101 type token struct { 102 item item 103 value interface{} 104 usedVariable bool 105 sourceFile string 106 } 107 108 func (t *token) Value() interface{} { 109 return t.value 110 } 111 112 func (t *token) Line() int { 113 return t.item.line 114 } 115 116 func (t *token) IsUsedVariable() bool { 117 return t.usedVariable 118 } 119 120 func (t *token) SourceFile() string { 121 return t.sourceFile 122 } 123 124 func (t *token) Position() int { 125 return t.item.pos 126 } 127 128 func parse(data, fp string, pedantic bool) (p *parser, err error) { 129 p = &parser{ 130 mapping: make(map[string]interface{}), 131 lx: lex(data), 132 ctxs: make([]interface{}, 0, 4), 133 keys: make([]string, 0, 4), 134 ikeys: make([]item, 0, 4), 135 fp: filepath.Dir(fp), 136 pedantic: pedantic, 137 } 138 p.pushContext(p.mapping) 139 140 var prevItem item 141 for { 142 it := p.next() 143 if it.typ == itemEOF { 144 // Here we allow the final character to be a bracket '}' 145 // in order to support JSON like configurations. 146 if prevItem.typ == itemKey && prevItem.val != mapEndString { 147 return nil, fmt.Errorf("config is invalid (%s:%d:%d)", fp, it.line, it.pos) 148 } 149 break 150 } 151 prevItem = it 152 if err := p.processItem(it, fp); err != nil { 153 return nil, err 154 } 155 } 156 return p, nil 157 } 158 159 func (p *parser) next() item { 160 return p.lx.nextItem() 161 } 162 163 func (p *parser) pushContext(ctx interface{}) { 164 p.ctxs = append(p.ctxs, ctx) 165 p.ctx = ctx 166 } 167 168 func (p *parser) popContext() interface{} { 169 if len(p.ctxs) == 0 { 170 panic("BUG in parser, context stack empty") 171 } 172 li := len(p.ctxs) - 1 173 last := p.ctxs[li] 174 p.ctxs = p.ctxs[0:li] 175 p.ctx = p.ctxs[len(p.ctxs)-1] 176 return last 177 } 178 179 func (p *parser) pushKey(key string) { 180 p.keys = append(p.keys, key) 181 } 182 183 func (p *parser) popKey() string { 184 if len(p.keys) == 0 { 185 panic("BUG in parser, keys stack empty") 186 } 187 li := len(p.keys) - 1 188 last := p.keys[li] 189 p.keys = p.keys[0:li] 190 return last 191 } 192 193 func (p *parser) pushItemKey(key item) { 194 p.ikeys = append(p.ikeys, key) 195 } 196 197 func (p *parser) popItemKey() item { 198 if len(p.ikeys) == 0 { 199 panic("BUG in parser, item keys stack empty") 200 } 201 li := len(p.ikeys) - 1 202 last := p.ikeys[li] 203 p.ikeys = p.ikeys[0:li] 204 return last 205 } 206 207 func (p *parser) processItem(it item, fp string) error { 208 setValue := func(it item, v interface{}) { 209 if p.pedantic { 210 p.setValue(&token{it, v, false, fp}) 211 } else { 212 p.setValue(v) 213 } 214 } 215 216 switch it.typ { 217 case itemError: 218 return fmt.Errorf("Parse error on line %d: '%s'", it.line, it.val) 219 case itemKey: 220 // Keep track of the keys as items and strings, 221 // we do this in order to be able to still support 222 // includes without many breaking changes. 223 p.pushKey(it.val) 224 225 if p.pedantic { 226 p.pushItemKey(it) 227 } 228 case itemMapStart: 229 newCtx := make(map[string]interface{}) 230 p.pushContext(newCtx) 231 case itemMapEnd: 232 setValue(it, p.popContext()) 233 case itemString: 234 // FIXME(dlc) sanitize string? 235 setValue(it, it.val) 236 case itemInteger: 237 lastDigit := 0 238 for _, r := range it.val { 239 if !unicode.IsDigit(r) && r != '-' { 240 break 241 } 242 lastDigit++ 243 } 244 numStr := it.val[:lastDigit] 245 num, err := strconv.ParseInt(numStr, 10, 64) 246 if err != nil { 247 if e, ok := err.(*strconv.NumError); ok && 248 e.Err == strconv.ErrRange { 249 return fmt.Errorf("integer '%s' is out of the range", it.val) 250 } 251 return fmt.Errorf("expected integer, but got '%s'", it.val) 252 } 253 // Process a suffix 254 suffix := strings.ToLower(strings.TrimSpace(it.val[lastDigit:])) 255 256 switch suffix { 257 case "": 258 setValue(it, num) 259 case "k": 260 setValue(it, num*1000) 261 case "kb", "ki", "kib": 262 setValue(it, num*1024) 263 case "m": 264 setValue(it, num*1000*1000) 265 case "mb", "mi", "mib": 266 setValue(it, num*1024*1024) 267 case "g": 268 setValue(it, num*1000*1000*1000) 269 case "gb", "gi", "gib": 270 setValue(it, num*1024*1024*1024) 271 case "t": 272 setValue(it, num*1000*1000*1000*1000) 273 case "tb", "ti", "tib": 274 setValue(it, num*1024*1024*1024*1024) 275 case "p": 276 setValue(it, num*1000*1000*1000*1000*1000) 277 case "pb", "pi", "pib": 278 setValue(it, num*1024*1024*1024*1024*1024) 279 case "e": 280 setValue(it, num*1000*1000*1000*1000*1000*1000) 281 case "eb", "ei", "eib": 282 setValue(it, num*1024*1024*1024*1024*1024*1024) 283 } 284 case itemFloat: 285 num, err := strconv.ParseFloat(it.val, 64) 286 if err != nil { 287 if e, ok := err.(*strconv.NumError); ok && 288 e.Err == strconv.ErrRange { 289 return fmt.Errorf("float '%s' is out of the range", it.val) 290 } 291 return fmt.Errorf("expected float, but got '%s'", it.val) 292 } 293 setValue(it, num) 294 case itemBool: 295 switch strings.ToLower(it.val) { 296 case "true", "yes", "on": 297 setValue(it, true) 298 case "false", "no", "off": 299 setValue(it, false) 300 default: 301 return fmt.Errorf("expected boolean value, but got '%s'", it.val) 302 } 303 304 case itemDatetime: 305 dt, err := time.Parse("2006-01-02T15:04:05Z", it.val) 306 if err != nil { 307 return fmt.Errorf( 308 "expected Zulu formatted DateTime, but got '%s'", it.val) 309 } 310 setValue(it, dt) 311 case itemArrayStart: 312 var array = make([]interface{}, 0) 313 p.pushContext(array) 314 case itemArrayEnd: 315 array := p.ctx 316 p.popContext() 317 setValue(it, array) 318 case itemVariable: 319 value, found, err := p.lookupVariable(it.val) 320 if err != nil { 321 return fmt.Errorf("variable reference for '%s' on line %d could not be parsed: %s", 322 it.val, it.line, err) 323 } 324 if !found { 325 return fmt.Errorf("variable reference for '%s' on line %d can not be found", 326 it.val, it.line) 327 } 328 329 if p.pedantic { 330 switch tk := value.(type) { 331 case *token: 332 // Mark the looked up variable as used, and make 333 // the variable reference become handled as a token. 334 tk.usedVariable = true 335 p.setValue(&token{it, tk.Value(), false, fp}) 336 default: 337 // Special case to add position context to bcrypt references. 338 p.setValue(&token{it, value, false, fp}) 339 } 340 } else { 341 p.setValue(value) 342 } 343 case itemInclude: 344 var ( 345 m map[string]interface{} 346 err error 347 ) 348 if p.pedantic { 349 m, err = ParseFileWithChecks(filepath.Join(p.fp, it.val)) 350 } else { 351 m, err = ParseFile(filepath.Join(p.fp, it.val)) 352 } 353 if err != nil { 354 return fmt.Errorf("error parsing include file '%s', %v", it.val, err) 355 } 356 for k, v := range m { 357 p.pushKey(k) 358 359 if p.pedantic { 360 switch tk := v.(type) { 361 case *token: 362 p.pushItemKey(tk.item) 363 } 364 } 365 p.setValue(v) 366 } 367 } 368 369 return nil 370 } 371 372 // Used to map an environment value into a temporary map to pass to secondary Parse call. 373 const pkey = "pk" 374 375 // We special case raw strings here that are bcrypt'd. This allows us not to force quoting the strings 376 const bcryptPrefix = "2a$" 377 378 // lookupVariable will lookup a variable reference. It will use block scoping on keys 379 // it has seen before, with the top level scoping being the environment variables. We 380 // ignore array contexts and only process the map contexts.. 381 // 382 // Returns true for ok if it finds something, similar to map. 383 func (p *parser) lookupVariable(varReference string) (interface{}, bool, error) { 384 // Do special check to see if it is a raw bcrypt string. 385 if strings.HasPrefix(varReference, bcryptPrefix) { 386 return "$" + varReference, true, nil 387 } 388 389 // Loop through contexts currently on the stack. 390 for i := len(p.ctxs) - 1; i >= 0; i-- { 391 ctx := p.ctxs[i] 392 // Process if it is a map context 393 if m, ok := ctx.(map[string]interface{}); ok { 394 if v, ok := m[varReference]; ok { 395 return v, ok, nil 396 } 397 } 398 } 399 400 // If we are here, we have exhausted our context maps and still not found anything. 401 // Parse from the environment. 402 if vStr, ok := os.LookupEnv(varReference); ok { 403 // Everything we get here will be a string value, so we need to process as a parser would. 404 if vmap, err := Parse(fmt.Sprintf("%s=%s", pkey, vStr)); err == nil { 405 v, ok := vmap[pkey] 406 return v, ok, nil 407 } else { 408 return nil, false, err 409 } 410 } 411 return nil, false, nil 412 } 413 414 func (p *parser) setValue(val interface{}) { 415 // Test to see if we are on an array or a map 416 417 // Array processing 418 if ctx, ok := p.ctx.([]interface{}); ok { 419 p.ctx = append(ctx, val) 420 p.ctxs[len(p.ctxs)-1] = p.ctx 421 } 422 423 // Map processing 424 if ctx, ok := p.ctx.(map[string]interface{}); ok { 425 key := p.popKey() 426 427 if p.pedantic { 428 // Change the position to the beginning of the key 429 // since more useful when reporting errors. 430 switch v := val.(type) { 431 case *token: 432 it := p.popItemKey() 433 v.item.pos = it.pos 434 v.item.line = it.line 435 ctx[key] = v 436 } 437 } else { 438 // FIXME(dlc), make sure to error if redefining same key? 439 ctx[key] = val 440 } 441 } 442 }