Strictly check if values to JSET are numbers

Fixes #493
This commit is contained in:
Jordan Armstrong 2019-10-26 23:33:35 -03:00
parent d2cfb87c89
commit 41fb410e2e
3 changed files with 91 additions and 7 deletions

View File

@ -40,6 +40,55 @@ func jsonString(s string) string {
b[len(b)-1] = '"' b[len(b)-1] = '"'
return string(b) return string(b)
} }
func isJsonNumber(s string) bool {
// Returns true if the given string can be encoded as a JSON number value.
// See:
// https://json.org
// http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf
l := len(s)
if l == 0 {
return false
}
i := 0
if s[i] == '-' {
i++
}
switch {
case i == l:
return false
case s[i] == '0': // 0 must not be followed by any digits.
i++
break
case s[i] >= '1' && s[0] <= '9':
for i++; i < l && s[i] >= '0' && s[i] <= '9'; i++ { // scan over digits
}
default:
return false
}
if i == l {
return true
}
if l-i > 1 && s[i] == '.' {
for i++; i < l && s[i] >= '0' && s[i] <= '9'; i++ {
}
}
if l-i > 1 && (s[i] == 'e' || s[i] == 'E') {
i++
if s[i] == '-' || s[i] == '+' {
i++
}
switch {
case i == l:
return false
case s[i] >= '0' && s[0] <= '9':
for i++; i < l && s[i] >= '0' && s[i] <= '9'; i++ {
}
}
}
return i == l
}
func appendJSONSimpleBounds(dst []byte, o geojson.Object) []byte { func appendJSONSimpleBounds(dst []byte, o geojson.Object) []byte {
bbox := o.Rect() bbox := o.Rect()
dst = append(dst, `{"sw":{"lat":`...) dst = append(dst, `{"sw":{"lat":`...)
@ -183,13 +232,7 @@ func (c *Server) cmdJset(msg *Message) (res resp.Value, d commandDetails, err er
if !str && !raw { if !str && !raw {
switch val { switch val {
default: default:
if len(val) > 0 { raw = isJsonNumber(val)
if (val[0] >= '0' && val[0] <= '9') || val[0] == '-' {
if _, err := strconv.ParseFloat(val, 64); err == nil {
raw = true
}
}
}
case "true", "false", "null": case "true", "false", "null":
raw = true raw = true
} }

View File

@ -18,3 +18,29 @@ func BenchmarkJSONMarshal(t *testing.B) {
json.Marshal(s) json.Marshal(s)
} }
} }
func TestIsJsonNumber(t *testing.T) {
test := func(expected bool, val string) {
actual := isJsonNumber(val)
if expected != actual {
t.Fatalf("Expected %t == isJsonNumber(\"%s\") but was %t", expected, val, actual)
}
}
test(false, "")
test(false, "-")
test(false, "foo")
test(false, "0123")
test(false, "1.")
test(false, "1.0e")
test(false, "1.0e-")
test(false, "1.0E10NaN")
test(false, "1.0ENaN")
test(true, "-1")
test(true, "0")
test(true, "0.0")
test(true, "42")
test(true, "1.0E10")
test(true, "1.0e10")
test(true, "1E+5")
test(true, "1E-10")
}

View File

@ -5,6 +5,8 @@ import "testing"
func subTestJSON(t *testing.T, mc *mockServer) { func subTestJSON(t *testing.T, mc *mockServer) {
runStep(t, mc, "basic", json_JSET_basic_test) runStep(t, mc, "basic", json_JSET_basic_test)
runStep(t, mc, "geojson", json_JSET_geojson_test) runStep(t, mc, "geojson", json_JSET_geojson_test)
runStep(t, mc, "number", json_JSET_number_test)
} }
func json_JSET_basic_test(mc *mockServer) error { func json_JSET_basic_test(mc *mockServer) error {
return mc.DoBatch([][]interface{}{ return mc.DoBatch([][]interface{}{
@ -39,3 +41,16 @@ func json_JSET_geojson_test(mc *mockServer) error {
{"JDEL", "mykey", "myid1", "type"}, {"ERR missing type"}, {"JDEL", "mykey", "myid1", "type"}, {"ERR missing type"},
}) })
} }
func json_JSET_number_test(mc *mockServer) error {
return mc.DoBatch([][]interface{}{
{"JSET", "mykey", "myid1", "hello", "0"}, {"OK"},
{"JGET", "mykey", "myid1"}, {`{"hello":0}`},
{"JSET", "mykey", "myid1", "hello", "0123"}, {"OK"},
{"JGET", "mykey", "myid1"}, {`{"hello":"0123"}`},
{"JSET", "mykey", "myid1", "hello", "3.14"}, {"OK"},
{"JGET", "mykey", "myid1"}, {`{"hello":3.14}`},
{"JSET", "mykey", "myid1", "hello", "1.0e10"}, {"OK"},
{"JGET", "mykey", "myid1"}, {`{"hello":1.0e10}`},
})
}