parser: improve floats validation (#636)

This commit is contained in:
Thomas Pelletier
2021-10-20 08:49:28 -04:00
committed by GitHub
parent 1f33a6a476
commit 3000471a12
2 changed files with 84 additions and 14 deletions
+84 -6
View File
@@ -219,7 +219,7 @@ func parseFloat(b []byte) (float64, error) {
return math.NaN(), nil
}
cleaned, err := checkAndRemoveUnderscores(b)
cleaned, err := checkAndRemoveUnderscoresFloats(b)
if err != nil {
return 0, err
}
@@ -232,6 +232,30 @@ func parseFloat(b []byte) (float64, error) {
return 0, newDecodeError(b, "float cannot end with a dot")
}
dotAlreadySeen := false
for i, c := range cleaned {
if c == '.' {
if dotAlreadySeen {
return 0, newDecodeError(b[i:i+1], "float can have at most one decimal point")
}
if !isDigit(cleaned[i-1]) {
return 0, newDecodeError(b[i-1:i+1], "float decimal point must be preceded by a digit")
}
if !isDigit(cleaned[i+1]) {
return 0, newDecodeError(b[i:i+2], "float decimal point must be followed by a digit")
}
dotAlreadySeen = true
}
}
start := 0
if b[0] == '+' || b[0] == '-' {
start = 1
}
if b[start] == '0' && isDigit(b[start+1]) {
return 0, newDecodeError(b, "float integer part cannot have leading zeroes")
}
f, err := strconv.ParseFloat(string(cleaned), 64)
if err != nil {
return 0, newDecodeError(b, "unable to parse float: %w", err)
@@ -241,7 +265,7 @@ func parseFloat(b []byte) (float64, error) {
}
func parseIntHex(b []byte) (int64, error) {
cleaned, err := checkAndRemoveUnderscores(b[2:])
cleaned, err := checkAndRemoveUnderscoresIntegers(b[2:])
if err != nil {
return 0, err
}
@@ -255,7 +279,7 @@ func parseIntHex(b []byte) (int64, error) {
}
func parseIntOct(b []byte) (int64, error) {
cleaned, err := checkAndRemoveUnderscores(b[2:])
cleaned, err := checkAndRemoveUnderscoresIntegers(b[2:])
if err != nil {
return 0, err
}
@@ -269,7 +293,7 @@ func parseIntOct(b []byte) (int64, error) {
}
func parseIntBin(b []byte) (int64, error) {
cleaned, err := checkAndRemoveUnderscores(b[2:])
cleaned, err := checkAndRemoveUnderscoresIntegers(b[2:])
if err != nil {
return 0, err
}
@@ -283,7 +307,7 @@ func parseIntBin(b []byte) (int64, error) {
}
func parseIntDec(b []byte) (int64, error) {
cleaned, err := checkAndRemoveUnderscores(b)
cleaned, err := checkAndRemoveUnderscoresIntegers(b)
if err != nil {
return 0, err
}
@@ -296,7 +320,7 @@ func parseIntDec(b []byte) (int64, error) {
return i, nil
}
func checkAndRemoveUnderscores(b []byte) ([]byte, error) {
func checkAndRemoveUnderscoresIntegers(b []byte) ([]byte, error) {
if b[0] == '_' {
return nil, newDecodeError(b[0:1], "number cannot start with underscore")
}
@@ -336,6 +360,60 @@ func checkAndRemoveUnderscores(b []byte) ([]byte, error) {
return cleaned, nil
}
func checkAndRemoveUnderscoresFloats(b []byte) ([]byte, error) {
if b[0] == '_' {
return nil, newDecodeError(b[0:1], "number cannot start with underscore")
}
if b[len(b)-1] == '_' {
return nil, newDecodeError(b[len(b)-1:], "number cannot end with underscore")
}
// fast path
i := 0
for ; i < len(b); i++ {
if b[i] == '_' {
break
}
}
if i == len(b) {
return b, nil
}
before := false
cleaned := make([]byte, 0, len(b))
for i := 0; i < len(b); i++ {
c := b[i]
switch c {
case '_':
if !before {
return nil, newDecodeError(b[i-1:i+1], "number must have at least one digit between underscores")
}
before = false
case 'e', 'E':
if i < len(b)-1 && b[i+1] == '_' {
return nil, newDecodeError(b[i+1:i+2], "cannot have underscore after exponent")
}
cleaned = append(cleaned, c)
case '.':
if i < len(b)-1 && b[i+1] == '_' {
return nil, newDecodeError(b[i+1:i+2], "cannot have underscore after decimal point")
}
if i > 0 && b[i-1] == '_' {
return nil, newDecodeError(b[i-1:i], "cannot have underscore before decimal point")
}
cleaned = append(cleaned, c)
default:
before = true
cleaned = append(cleaned, c)
}
}
return cleaned, nil
}
// isValidDate checks if a provided date is a date that exists.
func isValidDate(year int, month int, day int) bool {
return day <= daysIn(month, year)