Path: blob/main/vendor/github.com/pelletier/go-toml/v2/decode.go
2880 views
package toml12import (3"fmt"4"math"5"strconv"6"time"78"github.com/pelletier/go-toml/v2/unstable"9)1011func parseInteger(b []byte) (int64, error) {12if len(b) > 2 && b[0] == '0' {13switch b[1] {14case 'x':15return parseIntHex(b)16case 'b':17return parseIntBin(b)18case 'o':19return parseIntOct(b)20default:21panic(fmt.Errorf("invalid base '%c', should have been checked by scanIntOrFloat", b[1]))22}23}2425return parseIntDec(b)26}2728func parseLocalDate(b []byte) (LocalDate, error) {29// full-date = date-fullyear "-" date-month "-" date-mday30// date-fullyear = 4DIGIT31// date-month = 2DIGIT ; 01-1232// date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on month/year33var date LocalDate3435if len(b) != 10 || b[4] != '-' || b[7] != '-' {36return date, unstable.NewParserError(b, "dates are expected to have the format YYYY-MM-DD")37}3839var err error4041date.Year, err = parseDecimalDigits(b[0:4])42if err != nil {43return LocalDate{}, err44}4546date.Month, err = parseDecimalDigits(b[5:7])47if err != nil {48return LocalDate{}, err49}5051date.Day, err = parseDecimalDigits(b[8:10])52if err != nil {53return LocalDate{}, err54}5556if !isValidDate(date.Year, date.Month, date.Day) {57return LocalDate{}, unstable.NewParserError(b, "impossible date")58}5960return date, nil61}6263func parseDecimalDigits(b []byte) (int, error) {64v := 06566for i, c := range b {67if c < '0' || c > '9' {68return 0, unstable.NewParserError(b[i:i+1], "expected digit (0-9)")69}70v *= 1071v += int(c - '0')72}7374return v, nil75}7677func parseDateTime(b []byte) (time.Time, error) {78// offset-date-time = full-date time-delim full-time79// full-time = partial-time time-offset80// time-offset = "Z" / time-numoffset81// time-numoffset = ( "+" / "-" ) time-hour ":" time-minute8283dt, b, err := parseLocalDateTime(b)84if err != nil {85return time.Time{}, err86}8788var zone *time.Location8990if len(b) == 0 {91// parser should have checked that when assigning the date time node92panic("date time should have a timezone")93}9495if b[0] == 'Z' || b[0] == 'z' {96b = b[1:]97zone = time.UTC98} else {99const dateTimeByteLen = 6100if len(b) != dateTimeByteLen {101return time.Time{}, unstable.NewParserError(b, "invalid date-time timezone")102}103var direction int104switch b[0] {105case '-':106direction = -1107case '+':108direction = +1109default:110return time.Time{}, unstable.NewParserError(b[:1], "invalid timezone offset character")111}112113if b[3] != ':' {114return time.Time{}, unstable.NewParserError(b[3:4], "expected a : separator")115}116117hours, err := parseDecimalDigits(b[1:3])118if err != nil {119return time.Time{}, err120}121if hours > 23 {122return time.Time{}, unstable.NewParserError(b[:1], "invalid timezone offset hours")123}124125minutes, err := parseDecimalDigits(b[4:6])126if err != nil {127return time.Time{}, err128}129if minutes > 59 {130return time.Time{}, unstable.NewParserError(b[:1], "invalid timezone offset minutes")131}132133seconds := direction * (hours*3600 + minutes*60)134if seconds == 0 {135zone = time.UTC136} else {137zone = time.FixedZone("", seconds)138}139b = b[dateTimeByteLen:]140}141142if len(b) > 0 {143return time.Time{}, unstable.NewParserError(b, "extra bytes at the end of the timezone")144}145146t := time.Date(147dt.Year,148time.Month(dt.Month),149dt.Day,150dt.Hour,151dt.Minute,152dt.Second,153dt.Nanosecond,154zone)155156return t, nil157}158159func parseLocalDateTime(b []byte) (LocalDateTime, []byte, error) {160var dt LocalDateTime161162const localDateTimeByteMinLen = 11163if len(b) < localDateTimeByteMinLen {164return dt, nil, unstable.NewParserError(b, "local datetimes are expected to have the format YYYY-MM-DDTHH:MM:SS[.NNNNNNNNN]")165}166167date, err := parseLocalDate(b[:10])168if err != nil {169return dt, nil, err170}171dt.LocalDate = date172173sep := b[10]174if sep != 'T' && sep != ' ' && sep != 't' {175return dt, nil, unstable.NewParserError(b[10:11], "datetime separator is expected to be T or a space")176}177178t, rest, err := parseLocalTime(b[11:])179if err != nil {180return dt, nil, err181}182dt.LocalTime = t183184return dt, rest, nil185}186187// parseLocalTime is a bit different because it also returns the remaining188// []byte that is didn't need. This is to allow parseDateTime to parse those189// remaining bytes as a timezone.190func parseLocalTime(b []byte) (LocalTime, []byte, error) {191var (192nspow = [10]int{0, 1e8, 1e7, 1e6, 1e5, 1e4, 1e3, 1e2, 1e1, 1e0}193t LocalTime194)195196// check if b matches to have expected format HH:MM:SS[.NNNNNN]197const localTimeByteLen = 8198if len(b) < localTimeByteLen {199return t, nil, unstable.NewParserError(b, "times are expected to have the format HH:MM:SS[.NNNNNN]")200}201202var err error203204t.Hour, err = parseDecimalDigits(b[0:2])205if err != nil {206return t, nil, err207}208209if t.Hour > 23 {210return t, nil, unstable.NewParserError(b[0:2], "hour cannot be greater 23")211}212if b[2] != ':' {213return t, nil, unstable.NewParserError(b[2:3], "expecting colon between hours and minutes")214}215216t.Minute, err = parseDecimalDigits(b[3:5])217if err != nil {218return t, nil, err219}220if t.Minute > 59 {221return t, nil, unstable.NewParserError(b[3:5], "minutes cannot be greater 59")222}223if b[5] != ':' {224return t, nil, unstable.NewParserError(b[5:6], "expecting colon between minutes and seconds")225}226227t.Second, err = parseDecimalDigits(b[6:8])228if err != nil {229return t, nil, err230}231232if t.Second > 60 {233return t, nil, unstable.NewParserError(b[6:8], "seconds cannot be greater 60")234}235236b = b[8:]237238if len(b) >= 1 && b[0] == '.' {239frac := 0240precision := 0241digits := 0242243for i, c := range b[1:] {244if !isDigit(c) {245if i == 0 {246return t, nil, unstable.NewParserError(b[0:1], "need at least one digit after fraction point")247}248break249}250digits++251252const maxFracPrecision = 9253if i >= maxFracPrecision {254// go-toml allows decoding fractional seconds255// beyond the supported precision of 9256// digits. It truncates the fractional component257// to the supported precision and ignores the258// remaining digits.259//260// https://github.com/pelletier/go-toml/discussions/707261continue262}263264frac *= 10265frac += int(c - '0')266precision++267}268269if precision == 0 {270return t, nil, unstable.NewParserError(b[:1], "nanoseconds need at least one digit")271}272273t.Nanosecond = frac * nspow[precision]274t.Precision = precision275276return t, b[1+digits:], nil277}278return t, b, nil279}280281//nolint:cyclop282func parseFloat(b []byte) (float64, error) {283if len(b) == 4 && (b[0] == '+' || b[0] == '-') && b[1] == 'n' && b[2] == 'a' && b[3] == 'n' {284return math.NaN(), nil285}286287cleaned, err := checkAndRemoveUnderscoresFloats(b)288if err != nil {289return 0, err290}291292if cleaned[0] == '.' {293return 0, unstable.NewParserError(b, "float cannot start with a dot")294}295296if cleaned[len(cleaned)-1] == '.' {297return 0, unstable.NewParserError(b, "float cannot end with a dot")298}299300dotAlreadySeen := false301for i, c := range cleaned {302if c == '.' {303if dotAlreadySeen {304return 0, unstable.NewParserError(b[i:i+1], "float can have at most one decimal point")305}306if !isDigit(cleaned[i-1]) {307return 0, unstable.NewParserError(b[i-1:i+1], "float decimal point must be preceded by a digit")308}309if !isDigit(cleaned[i+1]) {310return 0, unstable.NewParserError(b[i:i+2], "float decimal point must be followed by a digit")311}312dotAlreadySeen = true313}314}315316start := 0317if cleaned[0] == '+' || cleaned[0] == '-' {318start = 1319}320if cleaned[start] == '0' && len(cleaned) > start+1 && isDigit(cleaned[start+1]) {321return 0, unstable.NewParserError(b, "float integer part cannot have leading zeroes")322}323324f, err := strconv.ParseFloat(string(cleaned), 64)325if err != nil {326return 0, unstable.NewParserError(b, "unable to parse float: %w", err)327}328329return f, nil330}331332func parseIntHex(b []byte) (int64, error) {333cleaned, err := checkAndRemoveUnderscoresIntegers(b[2:])334if err != nil {335return 0, err336}337338i, err := strconv.ParseInt(string(cleaned), 16, 64)339if err != nil {340return 0, unstable.NewParserError(b, "couldn't parse hexadecimal number: %w", err)341}342343return i, nil344}345346func parseIntOct(b []byte) (int64, error) {347cleaned, err := checkAndRemoveUnderscoresIntegers(b[2:])348if err != nil {349return 0, err350}351352i, err := strconv.ParseInt(string(cleaned), 8, 64)353if err != nil {354return 0, unstable.NewParserError(b, "couldn't parse octal number: %w", err)355}356357return i, nil358}359360func parseIntBin(b []byte) (int64, error) {361cleaned, err := checkAndRemoveUnderscoresIntegers(b[2:])362if err != nil {363return 0, err364}365366i, err := strconv.ParseInt(string(cleaned), 2, 64)367if err != nil {368return 0, unstable.NewParserError(b, "couldn't parse binary number: %w", err)369}370371return i, nil372}373374func isSign(b byte) bool {375return b == '+' || b == '-'376}377378func parseIntDec(b []byte) (int64, error) {379cleaned, err := checkAndRemoveUnderscoresIntegers(b)380if err != nil {381return 0, err382}383384startIdx := 0385386if isSign(cleaned[0]) {387startIdx++388}389390if len(cleaned) > startIdx+1 && cleaned[startIdx] == '0' {391return 0, unstable.NewParserError(b, "leading zero not allowed on decimal number")392}393394i, err := strconv.ParseInt(string(cleaned), 10, 64)395if err != nil {396return 0, unstable.NewParserError(b, "couldn't parse decimal number: %w", err)397}398399return i, nil400}401402func checkAndRemoveUnderscoresIntegers(b []byte) ([]byte, error) {403start := 0404if b[start] == '+' || b[start] == '-' {405start++406}407408if len(b) == start {409return b, nil410}411412if b[start] == '_' {413return nil, unstable.NewParserError(b[start:start+1], "number cannot start with underscore")414}415416if b[len(b)-1] == '_' {417return nil, unstable.NewParserError(b[len(b)-1:], "number cannot end with underscore")418}419420// fast path421i := 0422for ; i < len(b); i++ {423if b[i] == '_' {424break425}426}427if i == len(b) {428return b, nil429}430431before := false432cleaned := make([]byte, i, len(b))433copy(cleaned, b)434435for i++; i < len(b); i++ {436c := b[i]437if c == '_' {438if !before {439return nil, unstable.NewParserError(b[i-1:i+1], "number must have at least one digit between underscores")440}441before = false442} else {443before = true444cleaned = append(cleaned, c)445}446}447448return cleaned, nil449}450451func checkAndRemoveUnderscoresFloats(b []byte) ([]byte, error) {452if b[0] == '_' {453return nil, unstable.NewParserError(b[0:1], "number cannot start with underscore")454}455456if b[len(b)-1] == '_' {457return nil, unstable.NewParserError(b[len(b)-1:], "number cannot end with underscore")458}459460// fast path461i := 0462for ; i < len(b); i++ {463if b[i] == '_' {464break465}466}467if i == len(b) {468return b, nil469}470471before := false472cleaned := make([]byte, 0, len(b))473474for i := 0; i < len(b); i++ {475c := b[i]476477switch c {478case '_':479if !before {480return nil, unstable.NewParserError(b[i-1:i+1], "number must have at least one digit between underscores")481}482if i < len(b)-1 && (b[i+1] == 'e' || b[i+1] == 'E') {483return nil, unstable.NewParserError(b[i+1:i+2], "cannot have underscore before exponent")484}485before = false486case '+', '-':487// signed exponents488cleaned = append(cleaned, c)489before = false490case 'e', 'E':491if i < len(b)-1 && b[i+1] == '_' {492return nil, unstable.NewParserError(b[i+1:i+2], "cannot have underscore after exponent")493}494cleaned = append(cleaned, c)495case '.':496if i < len(b)-1 && b[i+1] == '_' {497return nil, unstable.NewParserError(b[i+1:i+2], "cannot have underscore after decimal point")498}499if i > 0 && b[i-1] == '_' {500return nil, unstable.NewParserError(b[i-1:i], "cannot have underscore before decimal point")501}502cleaned = append(cleaned, c)503default:504before = true505cleaned = append(cleaned, c)506}507}508509return cleaned, nil510}511512// isValidDate checks if a provided date is a date that exists.513func isValidDate(year int, month int, day int) bool {514return month > 0 && month < 13 && day > 0 && day <= daysIn(month, year)515}516517// daysBefore[m] counts the number of days in a non-leap year518// before month m begins. There is an entry for m=12, counting519// the number of days before January of next year (365).520var daysBefore = [...]int32{5210,52231,52331 + 28,52431 + 28 + 31,52531 + 28 + 31 + 30,52631 + 28 + 31 + 30 + 31,52731 + 28 + 31 + 30 + 31 + 30,52831 + 28 + 31 + 30 + 31 + 30 + 31,52931 + 28 + 31 + 30 + 31 + 30 + 31 + 31,53031 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30,53131 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31,53231 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30,53331 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31,534}535536func daysIn(m int, year int) int {537if m == 2 && isLeap(year) {538return 29539}540return int(daysBefore[m] - daysBefore[m-1])541}542543func isLeap(year int) bool {544return year%4 == 0 && (year%100 != 0 || year%400 == 0)545}546547func isDigit(r byte) bool {548return r >= '0' && r <= '9'549}550551552