From 41fb410e2e6c618e50219d3b1bf1fe7f297da77d Mon Sep 17 00:00:00 2001 From: Jordan Armstrong Date: Sat, 26 Oct 2019 23:33:35 -0300 Subject: [PATCH 1/2] Strictly check if values to JSET are numbers Fixes #493 --- internal/server/json.go | 57 +++++++++++++++++++++++++++++++----- internal/server/json_test.go | 26 ++++++++++++++++ tests/json_test.go | 15 ++++++++++ 3 files changed, 91 insertions(+), 7 deletions(-) diff --git a/internal/server/json.go b/internal/server/json.go index 762fcf1e..d8d3ed8b 100644 --- a/internal/server/json.go +++ b/internal/server/json.go @@ -40,6 +40,55 @@ func jsonString(s string) string { b[len(b)-1] = '"' 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 { bbox := o.Rect() 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 { switch val { default: - if len(val) > 0 { - if (val[0] >= '0' && val[0] <= '9') || val[0] == '-' { - if _, err := strconv.ParseFloat(val, 64); err == nil { - raw = true - } - } - } + raw = isJsonNumber(val) case "true", "false", "null": raw = true } diff --git a/internal/server/json_test.go b/internal/server/json_test.go index f9ccd4ee..db2f50da 100644 --- a/internal/server/json_test.go +++ b/internal/server/json_test.go @@ -18,3 +18,29 @@ func BenchmarkJSONMarshal(t *testing.B) { 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") +} \ No newline at end of file diff --git a/tests/json_test.go b/tests/json_test.go index e0d02c1d..356a251a 100644 --- a/tests/json_test.go +++ b/tests/json_test.go @@ -5,6 +5,8 @@ import "testing" func subTestJSON(t *testing.T, mc *mockServer) { runStep(t, mc, "basic", json_JSET_basic_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 { return mc.DoBatch([][]interface{}{ @@ -39,3 +41,16 @@ func json_JSET_geojson_test(mc *mockServer) error { {"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}`}, + }) +} \ No newline at end of file From e0eca0d55e04cfbc52028cc1b4a30095f80a28ba Mon Sep 17 00:00:00 2001 From: Jordan Armstrong Date: Mon, 28 Oct 2019 22:35:44 -0300 Subject: [PATCH 2/2] Replace isJsonNumber with version from gjson --- internal/server/json.go | 72 ++++++++++++++++++++++++++++------------- 1 file changed, 50 insertions(+), 22 deletions(-) diff --git a/internal/server/json.go b/internal/server/json.go index d8d3ed8b..ee7429b8 100644 --- a/internal/server/json.go +++ b/internal/server/json.go @@ -41,52 +41,80 @@ func jsonString(s string) string { return string(b) } -func isJsonNumber(s string) bool { +func isJsonNumber(data 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 { + if data == "" { return false } i := 0 - if s[i] == '-' { + // sign + if data[i] == '-' { i++ } - switch { - case i == l: + if i == len(data) { return false - case s[i] == '0': // 0 must not be followed by any digits. + } + // int + if data[i] == '0' { i++ - break - case s[i] >= '1' && s[0] <= '9': - for i++; i < l && s[i] >= '0' && s[i] <= '9'; i++ { // scan over digits + } else { + for ; i < len(data); i++ { + if data[i] >= '0' && data[i] <= '9' { + continue + } + break } - default: - return false } - if i == l { + // frac + if i == len(data) { return true } - if l-i > 1 && s[i] == '.' { - for i++; i < l && s[i] >= '0' && s[i] <= '9'; i++ { + if data[i] == '.' { + i++ + if i == len(data) { + return false + } + if data[i] < '0' || data[i] > '9' { + return false + } + i++ + for ; i < len(data); i++ { + if data[i] >= '0' && data[i] <= '9' { + continue + } + break } } - if l-i > 1 && (s[i] == 'e' || s[i] == 'E') { + // exp + if i == len(data) { + return true + } + if data[i] == 'e' || data[i] == 'E' { i++ - if s[i] == '-' || s[i] == '+' { + if i == len(data) { + return false + } + if data[i] == '+' || data[i] == '-' { i++ } - switch { - case i == l: + if i == len(data) { return false - case s[i] >= '0' && s[0] <= '9': - for i++; i < l && s[i] >= '0' && s[i] <= '9'; i++ { + } + if data[i] < '0' || data[i] > '9' { + return false + } + i++ + for ; i < len(data); i++ { + if data[i] >= '0' && data[i] <= '9' { + continue } + break } } - return i == l + return i == len(data) } func appendJSONSimpleBounds(dst []byte, o geojson.Object) []byte {