...

Source file src/github.com/pelletier/go-toml/v2/decode.go

Documentation: github.com/pelletier/go-toml/v2

     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  

View as plain text