decode.go (12813B)
1 package toml 2 3 import ( 4 "fmt" 5 "math" 6 "strconv" 7 "time" 8 9 "github.com/pelletier/go-toml/v2/unstable" 10 ) 11 12 func parseInteger(b []byte) (int64, error) { 13 if len(b) > 2 && b[0] == '0' { 14 switch b[1] { 15 case 'x': 16 return parseIntHex(b) 17 case 'b': 18 return parseIntBin(b) 19 case 'o': 20 return parseIntOct(b) 21 default: 22 panic(fmt.Errorf("invalid base '%c', should have been checked by scanIntOrFloat", b[1])) 23 } 24 } 25 26 return parseIntDec(b) 27 } 28 29 func parseLocalDate(b []byte) (LocalDate, error) { 30 // full-date = date-fullyear "-" date-month "-" date-mday 31 // date-fullyear = 4DIGIT 32 // date-month = 2DIGIT ; 01-12 33 // date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on month/year 34 var date LocalDate 35 36 if len(b) != 10 || b[4] != '-' || b[7] != '-' { 37 return date, unstable.NewParserError(b, "dates are expected to have the format YYYY-MM-DD") 38 } 39 40 var err error 41 42 date.Year, err = parseDecimalDigits(b[0:4]) 43 if err != nil { 44 return LocalDate{}, err 45 } 46 47 date.Month, err = parseDecimalDigits(b[5:7]) 48 if err != nil { 49 return LocalDate{}, err 50 } 51 52 date.Day, err = parseDecimalDigits(b[8:10]) 53 if err != nil { 54 return LocalDate{}, err 55 } 56 57 if !isValidDate(date.Year, date.Month, date.Day) { 58 return LocalDate{}, unstable.NewParserError(b, "impossible date") 59 } 60 61 return date, nil 62 } 63 64 func parseDecimalDigits(b []byte) (int, error) { 65 v := 0 66 67 for i, c := range b { 68 if c < '0' || c > '9' { 69 return 0, unstable.NewParserError(b[i:i+1], "expected digit (0-9)") 70 } 71 v *= 10 72 v += int(c - '0') 73 } 74 75 return v, nil 76 } 77 78 func parseDateTime(b []byte) (time.Time, error) { 79 // offset-date-time = full-date time-delim full-time 80 // full-time = partial-time time-offset 81 // time-offset = "Z" / time-numoffset 82 // time-numoffset = ( "+" / "-" ) time-hour ":" time-minute 83 84 dt, b, err := parseLocalDateTime(b) 85 if err != nil { 86 return time.Time{}, err 87 } 88 89 var zone *time.Location 90 91 if len(b) == 0 { 92 // parser should have checked that when assigning the date time node 93 panic("date time should have a timezone") 94 } 95 96 if b[0] == 'Z' || b[0] == 'z' { 97 b = b[1:] 98 zone = time.UTC 99 } else { 100 const dateTimeByteLen = 6 101 if len(b) != dateTimeByteLen { 102 return time.Time{}, unstable.NewParserError(b, "invalid date-time timezone") 103 } 104 var direction int 105 switch b[0] { 106 case '-': 107 direction = -1 108 case '+': 109 direction = +1 110 default: 111 return time.Time{}, unstable.NewParserError(b[:1], "invalid timezone offset character") 112 } 113 114 if b[3] != ':' { 115 return time.Time{}, unstable.NewParserError(b[3:4], "expected a : separator") 116 } 117 118 hours, err := parseDecimalDigits(b[1:3]) 119 if err != nil { 120 return time.Time{}, err 121 } 122 if hours > 23 { 123 return time.Time{}, unstable.NewParserError(b[:1], "invalid timezone offset hours") 124 } 125 126 minutes, err := parseDecimalDigits(b[4:6]) 127 if err != nil { 128 return time.Time{}, err 129 } 130 if minutes > 59 { 131 return time.Time{}, unstable.NewParserError(b[:1], "invalid timezone offset minutes") 132 } 133 134 seconds := direction * (hours*3600 + minutes*60) 135 if seconds == 0 { 136 zone = time.UTC 137 } else { 138 zone = time.FixedZone("", seconds) 139 } 140 b = b[dateTimeByteLen:] 141 } 142 143 if len(b) > 0 { 144 return time.Time{}, unstable.NewParserError(b, "extra bytes at the end of the timezone") 145 } 146 147 t := time.Date( 148 dt.Year, 149 time.Month(dt.Month), 150 dt.Day, 151 dt.Hour, 152 dt.Minute, 153 dt.Second, 154 dt.Nanosecond, 155 zone) 156 157 return t, nil 158 } 159 160 func parseLocalDateTime(b []byte) (LocalDateTime, []byte, error) { 161 var dt LocalDateTime 162 163 const localDateTimeByteMinLen = 11 164 if len(b) < localDateTimeByteMinLen { 165 return dt, nil, unstable.NewParserError(b, "local datetimes are expected to have the format YYYY-MM-DDTHH:MM:SS[.NNNNNNNNN]") 166 } 167 168 date, err := parseLocalDate(b[:10]) 169 if err != nil { 170 return dt, nil, err 171 } 172 dt.LocalDate = date 173 174 sep := b[10] 175 if sep != 'T' && sep != ' ' && sep != 't' { 176 return dt, nil, unstable.NewParserError(b[10:11], "datetime separator is expected to be T or a space") 177 } 178 179 t, rest, err := parseLocalTime(b[11:]) 180 if err != nil { 181 return dt, nil, err 182 } 183 dt.LocalTime = t 184 185 return dt, rest, nil 186 } 187 188 // parseLocalTime is a bit different because it also returns the remaining 189 // []byte that is didn't need. This is to allow parseDateTime to parse those 190 // remaining bytes as a timezone. 191 func parseLocalTime(b []byte) (LocalTime, []byte, error) { 192 var ( 193 nspow = [10]int{0, 1e8, 1e7, 1e6, 1e5, 1e4, 1e3, 1e2, 1e1, 1e0} 194 t LocalTime 195 ) 196 197 // check if b matches to have expected format HH:MM:SS[.NNNNNN] 198 const localTimeByteLen = 8 199 if len(b) < localTimeByteLen { 200 return t, nil, unstable.NewParserError(b, "times are expected to have the format HH:MM:SS[.NNNNNN]") 201 } 202 203 var err error 204 205 t.Hour, err = parseDecimalDigits(b[0:2]) 206 if err != nil { 207 return t, nil, err 208 } 209 210 if t.Hour > 23 { 211 return t, nil, unstable.NewParserError(b[0:2], "hour cannot be greater 23") 212 } 213 if b[2] != ':' { 214 return t, nil, unstable.NewParserError(b[2:3], "expecting colon between hours and minutes") 215 } 216 217 t.Minute, err = parseDecimalDigits(b[3:5]) 218 if err != nil { 219 return t, nil, err 220 } 221 if t.Minute > 59 { 222 return t, nil, unstable.NewParserError(b[3:5], "minutes cannot be greater 59") 223 } 224 if b[5] != ':' { 225 return t, nil, unstable.NewParserError(b[5:6], "expecting colon between minutes and seconds") 226 } 227 228 t.Second, err = parseDecimalDigits(b[6:8]) 229 if err != nil { 230 return t, nil, err 231 } 232 233 if t.Second > 60 { 234 return t, nil, unstable.NewParserError(b[6:8], "seconds cannot be greater 60") 235 } 236 237 b = b[8:] 238 239 if len(b) >= 1 && b[0] == '.' { 240 frac := 0 241 precision := 0 242 digits := 0 243 244 for i, c := range b[1:] { 245 if !isDigit(c) { 246 if i == 0 { 247 return t, nil, unstable.NewParserError(b[0:1], "need at least one digit after fraction point") 248 } 249 break 250 } 251 digits++ 252 253 const maxFracPrecision = 9 254 if i >= maxFracPrecision { 255 // go-toml allows decoding fractional seconds 256 // beyond the supported precision of 9 257 // digits. It truncates the fractional component 258 // to the supported precision and ignores the 259 // remaining digits. 260 // 261 // https://github.com/pelletier/go-toml/discussions/707 262 continue 263 } 264 265 frac *= 10 266 frac += int(c - '0') 267 precision++ 268 } 269 270 if precision == 0 { 271 return t, nil, unstable.NewParserError(b[:1], "nanoseconds need at least one digit") 272 } 273 274 t.Nanosecond = frac * nspow[precision] 275 t.Precision = precision 276 277 return t, b[1+digits:], nil 278 } 279 return t, b, nil 280 } 281 282 //nolint:cyclop 283 func parseFloat(b []byte) (float64, error) { 284 if len(b) == 4 && (b[0] == '+' || b[0] == '-') && b[1] == 'n' && b[2] == 'a' && b[3] == 'n' { 285 return math.NaN(), nil 286 } 287 288 cleaned, err := checkAndRemoveUnderscoresFloats(b) 289 if err != nil { 290 return 0, err 291 } 292 293 if cleaned[0] == '.' { 294 return 0, unstable.NewParserError(b, "float cannot start with a dot") 295 } 296 297 if cleaned[len(cleaned)-1] == '.' { 298 return 0, unstable.NewParserError(b, "float cannot end with a dot") 299 } 300 301 dotAlreadySeen := false 302 for i, c := range cleaned { 303 if c == '.' { 304 if dotAlreadySeen { 305 return 0, unstable.NewParserError(b[i:i+1], "float can have at most one decimal point") 306 } 307 if !isDigit(cleaned[i-1]) { 308 return 0, unstable.NewParserError(b[i-1:i+1], "float decimal point must be preceded by a digit") 309 } 310 if !isDigit(cleaned[i+1]) { 311 return 0, unstable.NewParserError(b[i:i+2], "float decimal point must be followed by a digit") 312 } 313 dotAlreadySeen = true 314 } 315 } 316 317 start := 0 318 if cleaned[0] == '+' || cleaned[0] == '-' { 319 start = 1 320 } 321 if cleaned[start] == '0' && isDigit(cleaned[start+1]) { 322 return 0, unstable.NewParserError(b, "float integer part cannot have leading zeroes") 323 } 324 325 f, err := strconv.ParseFloat(string(cleaned), 64) 326 if err != nil { 327 return 0, unstable.NewParserError(b, "unable to parse float: %w", err) 328 } 329 330 return f, nil 331 } 332 333 func parseIntHex(b []byte) (int64, error) { 334 cleaned, err := checkAndRemoveUnderscoresIntegers(b[2:]) 335 if err != nil { 336 return 0, err 337 } 338 339 i, err := strconv.ParseInt(string(cleaned), 16, 64) 340 if err != nil { 341 return 0, unstable.NewParserError(b, "couldn't parse hexadecimal number: %w", err) 342 } 343 344 return i, nil 345 } 346 347 func parseIntOct(b []byte) (int64, error) { 348 cleaned, err := checkAndRemoveUnderscoresIntegers(b[2:]) 349 if err != nil { 350 return 0, err 351 } 352 353 i, err := strconv.ParseInt(string(cleaned), 8, 64) 354 if err != nil { 355 return 0, unstable.NewParserError(b, "couldn't parse octal number: %w", err) 356 } 357 358 return i, nil 359 } 360 361 func parseIntBin(b []byte) (int64, error) { 362 cleaned, err := checkAndRemoveUnderscoresIntegers(b[2:]) 363 if err != nil { 364 return 0, err 365 } 366 367 i, err := strconv.ParseInt(string(cleaned), 2, 64) 368 if err != nil { 369 return 0, unstable.NewParserError(b, "couldn't parse binary number: %w", err) 370 } 371 372 return i, nil 373 } 374 375 func isSign(b byte) bool { 376 return b == '+' || b == '-' 377 } 378 379 func parseIntDec(b []byte) (int64, error) { 380 cleaned, err := checkAndRemoveUnderscoresIntegers(b) 381 if err != nil { 382 return 0, err 383 } 384 385 startIdx := 0 386 387 if isSign(cleaned[0]) { 388 startIdx++ 389 } 390 391 if len(cleaned) > startIdx+1 && cleaned[startIdx] == '0' { 392 return 0, unstable.NewParserError(b, "leading zero not allowed on decimal number") 393 } 394 395 i, err := strconv.ParseInt(string(cleaned), 10, 64) 396 if err != nil { 397 return 0, unstable.NewParserError(b, "couldn't parse decimal number: %w", err) 398 } 399 400 return i, nil 401 } 402 403 func checkAndRemoveUnderscoresIntegers(b []byte) ([]byte, error) { 404 start := 0 405 if b[start] == '+' || b[start] == '-' { 406 start++ 407 } 408 409 if len(b) == start { 410 return b, nil 411 } 412 413 if b[start] == '_' { 414 return nil, unstable.NewParserError(b[start:start+1], "number cannot start with underscore") 415 } 416 417 if b[len(b)-1] == '_' { 418 return nil, unstable.NewParserError(b[len(b)-1:], "number cannot end with underscore") 419 } 420 421 // fast path 422 i := 0 423 for ; i < len(b); i++ { 424 if b[i] == '_' { 425 break 426 } 427 } 428 if i == len(b) { 429 return b, nil 430 } 431 432 before := false 433 cleaned := make([]byte, i, len(b)) 434 copy(cleaned, b) 435 436 for i++; i < len(b); i++ { 437 c := b[i] 438 if c == '_' { 439 if !before { 440 return nil, unstable.NewParserError(b[i-1:i+1], "number must have at least one digit between underscores") 441 } 442 before = false 443 } else { 444 before = true 445 cleaned = append(cleaned, c) 446 } 447 } 448 449 return cleaned, nil 450 } 451 452 func checkAndRemoveUnderscoresFloats(b []byte) ([]byte, error) { 453 if b[0] == '_' { 454 return nil, unstable.NewParserError(b[0:1], "number cannot start with underscore") 455 } 456 457 if b[len(b)-1] == '_' { 458 return nil, unstable.NewParserError(b[len(b)-1:], "number cannot end with underscore") 459 } 460 461 // fast path 462 i := 0 463 for ; i < len(b); i++ { 464 if b[i] == '_' { 465 break 466 } 467 } 468 if i == len(b) { 469 return b, nil 470 } 471 472 before := false 473 cleaned := make([]byte, 0, len(b)) 474 475 for i := 0; i < len(b); i++ { 476 c := b[i] 477 478 switch c { 479 case '_': 480 if !before { 481 return nil, unstable.NewParserError(b[i-1:i+1], "number must have at least one digit between underscores") 482 } 483 if i < len(b)-1 && (b[i+1] == 'e' || b[i+1] == 'E') { 484 return nil, unstable.NewParserError(b[i+1:i+2], "cannot have underscore before exponent") 485 } 486 before = false 487 case '+', '-': 488 // signed exponents 489 cleaned = append(cleaned, c) 490 before = false 491 case 'e', 'E': 492 if i < len(b)-1 && b[i+1] == '_' { 493 return nil, unstable.NewParserError(b[i+1:i+2], "cannot have underscore after exponent") 494 } 495 cleaned = append(cleaned, c) 496 case '.': 497 if i < len(b)-1 && b[i+1] == '_' { 498 return nil, unstable.NewParserError(b[i+1:i+2], "cannot have underscore after decimal point") 499 } 500 if i > 0 && b[i-1] == '_' { 501 return nil, unstable.NewParserError(b[i-1:i], "cannot have underscore before decimal point") 502 } 503 cleaned = append(cleaned, c) 504 default: 505 before = true 506 cleaned = append(cleaned, c) 507 } 508 } 509 510 return cleaned, nil 511 } 512 513 // isValidDate checks if a provided date is a date that exists. 514 func isValidDate(year int, month int, day int) bool { 515 return month > 0 && month < 13 && day > 0 && day <= daysIn(month, year) 516 } 517 518 // daysBefore[m] counts the number of days in a non-leap year 519 // before month m begins. There is an entry for m=12, counting 520 // the number of days before January of next year (365). 521 var daysBefore = [...]int32{ 522 0, 523 31, 524 31 + 28, 525 31 + 28 + 31, 526 31 + 28 + 31 + 30, 527 31 + 28 + 31 + 30 + 31, 528 31 + 28 + 31 + 30 + 31 + 30, 529 31 + 28 + 31 + 30 + 31 + 30 + 31, 530 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31, 531 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30, 532 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31, 533 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30, 534 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31, 535 } 536 537 func daysIn(m int, year int) int { 538 if m == 2 && isLeap(year) { 539 return 29 540 } 541 return int(daysBefore[m] - daysBefore[m-1]) 542 } 543 544 func isLeap(year int) bool { 545 return year%4 == 0 && (year%100 != 0 || year%400 == 0) 546 } 547 548 func isDigit(r byte) bool { 549 return r >= '0' && r <= '9' 550 }