decode.go raw

   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' && len(cleaned) > start+1 && 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  }
 551