diff options
author | Mark Pulford <mark@kyne.com.au> | 2012-01-12 14:58:39 +0400 |
---|---|---|
committer | Mark Pulford <mark@kyne.com.au> | 2012-03-04 12:24:34 +0400 |
commit | 418ee3fe24150c59c9afa6746aab7f09edcb894c (patch) | |
tree | e23bb79ca8f0146bcc9714658eed6da76d1b293c | |
parent | 5e36beccab38fa40b2c324c9339bbd5f1a3f24e3 (diff) |
Add option to encode invalid numbers as "null"
Deprecate and replace refuse_invalid_numbers() with
encode_invalid_numbers() and decode_invalid_numbers().
-rw-r--r-- | lua_cjson.c | 174 | ||||
-rw-r--r-- | manual.txt | 96 | ||||
-rwxr-xr-x | tests/test.lua | 21 |
3 files changed, 180 insertions, 111 deletions
diff --git a/lua_cjson.c b/lua_cjson.c index bdfa7d4..1e9e91d 100644 --- a/lua_cjson.c +++ b/lua_cjson.c @@ -64,14 +64,14 @@ #define DEFAULT_SPARSE_SAFE 10 #define DEFAULT_ENCODE_MAX_DEPTH 20 #define DEFAULT_DECODE_MAX_DEPTH 20 -#define DEFAULT_ENCODE_REFUSE_BADNUM 1 -#define DEFAULT_DECODE_REFUSE_BADNUM 0 +#define DEFAULT_ENCODE_INVALID_NUMBERS 0 +#define DEFAULT_DECODE_INVALID_NUMBERS 1 #define DEFAULT_ENCODE_KEEP_BUFFER 1 #define DEFAULT_ENCODE_NUMBER_PRECISION 14 #ifdef DISABLE_INVALID_NUMBERS -#undef DEFAULT_DECODE_REFUSE_BADNUM -#define DEFAULT_DECODE_REFUSE_BADNUM 1 +#undef DEFAULT_DECODE_INVALID_NUMBERS +#define DEFAULT_DECODE_INVALID_NUMBERS 0 #endif typedef enum { @@ -121,11 +121,11 @@ typedef struct { int encode_sparse_ratio; int encode_sparse_safe; int encode_max_depth; - int encode_refuse_badnum; + int encode_invalid_numbers; /* 2 => Encode as "null" */ int encode_number_precision; int encode_keep_buffer; - int decode_refuse_badnum; + int decode_invalid_numbers; int decode_max_depth; } json_config_t; @@ -259,6 +259,29 @@ static int json_integer_option(lua_State *l, int *setting, int min, int max) return 1; } +/* Process enumerated arguments for a configuration function */ +static int json_enum_option(lua_State *l, int *setting, + const char **options, int bool_value) +{ + static const char *bool_options[] = { "off", "on", NULL }; + + if (!options) { + options = bool_options; + bool_value = 1; + } + + if (lua_gettop(l)) { + if (lua_isboolean(l, 1)) + *setting = lua_toboolean(l, 1) * bool_value; + else + *setting = luaL_checkoption(l, 1, NULL, options); + } + + lua_pushstring(l, options[*setting]); + + return 1; +} + /* Configures the maximum number of nested arrays/objects allowed when * encoding */ static int json_cfg_encode_max_depth(lua_State *l) @@ -288,18 +311,12 @@ static int json_cfg_encode_number_precision(lua_State *l) /* Configures JSON encoding buffer persistence */ static int json_cfg_encode_keep_buffer(lua_State *l) { - json_config_t *cfg; + json_config_t *cfg = json_fetch_config(l); int old_value; - json_verify_arg_count(l, 1); - cfg = json_fetch_config(l); - old_value = cfg->encode_keep_buffer; - if (lua_gettop(l)) { - luaL_checktype(l, 1, LUA_TBOOLEAN); - cfg->encode_keep_buffer = lua_toboolean(l, 1); - } + json_enum_option(l, &cfg->encode_keep_buffer, NULL, 1); /* Init / free the buffer if the setting has changed */ if (old_value ^ cfg->encode_keep_buffer) { @@ -309,62 +326,76 @@ static int json_cfg_encode_keep_buffer(lua_State *l) strbuf_free(&cfg->encode_buf); } - lua_pushboolean(l, cfg->encode_keep_buffer); - return 1; } -/* On argument: decode enum and set config variables - * **options must point to a NULL terminated array of 4 enums - * Returns: current enum value */ -static void json_enum_option(lua_State *l, const char **options, - int *opt1, int *opt2) +static int json_cfg_encode_invalid_numbers(lua_State *l) { - int setting; + static const char *options[] = { "off", "on", "null", NULL }; + json_config_t *cfg = json_fetch_config(l); - if (lua_gettop(l)) { - if (lua_isboolean(l, 1)) - setting = lua_toboolean(l, 1) * 3; - else - setting = luaL_checkoption(l, 1, NULL, options); + json_enum_option(l, &cfg->encode_invalid_numbers, options, 1); - *opt1 = setting & 1 ? 1 : 0; - *opt2 = setting & 2 ? 1 : 0; - } else { - setting = *opt1 | (*opt2 << 1); +#if DISABLE_INVALID_NUMBERS + if (cfg->encode_invalid_numbers == 1) { + cfg->encode_invalid_numbers = 0; + luaL_error(l, "Infinity, NaN, and/or hexadecimal numbers are not supported."); } +#endif - if (setting) - lua_pushstring(l, options[setting]); - else - lua_pushboolean(l, 0); + return 1; } +static int json_cfg_decode_invalid_numbers(lua_State *l) +{ + json_config_t *cfg = json_fetch_config(l); + + json_enum_option(l, &cfg->decode_invalid_numbers, NULL, 1); + +#if DISABLE_INVALID_NUMBERS + if (cfg->decode_invalid_numbers) { + cfg->decode_invalid_numbers = 0; + luaL_error(l, "Infinity, NaN, and/or hexadecimal numbers are not supported."); + } +#endif -/* When enabled, rejects: NaN, Infinity, hexadecimal numbers */ + return 1; +} + +/* When enabled, rejects: NaN, Infinity, hexadecimal numbers. + * + * This function has been deprecated and may be removed in future. */ static int json_cfg_refuse_invalid_numbers(lua_State *l) { - static const char *options_enc_dec[] = { "none", "encode", "decode", - "both", NULL }; - json_config_t *cfg; + static const char *options[] = { "none", "encode", "decode", "both", NULL }; + json_config_t *cfg = json_fetch_config(l); + int have_arg, setting; - json_verify_arg_count(l, 1); - cfg = json_fetch_config(l); + have_arg = lua_gettop(l); - json_enum_option(l, options_enc_dec, - &cfg->encode_refuse_badnum, - &cfg->decode_refuse_badnum); + /* Map config variables to options list index */ + setting = !cfg->encode_invalid_numbers + /* bit 0 */ + (!cfg->decode_invalid_numbers << 1); /* bit 1 */ + + json_enum_option(l, &setting, options, 3); + + /* Map options list index to config variables + * + * Only update the config variables when an argument has been provided. + * Otherwise a "null" encoding setting may inadvertently be disabled. */ + if (have_arg) { + cfg->encode_invalid_numbers = !(setting & 1); + cfg->decode_invalid_numbers = !(setting & 2); #if DISABLE_INVALID_NUMBERS - /* Some non-POSIX platforms don't handle double <-> string translations - * for Infinity/NaN/hexadecimal properly. Throw an error if the - * user attempts to enable them. */ - if (!cfg->encode_refuse_badnum || !cfg->decode_refuse_badnum) { - cfg->encode_refuse_badnum = cfg->decode_refuse_badnum = 1; - luaL_error(l, "Infinity, NaN, and/or hexadecimal numbers are not supported."); - } + if (cfg->encode_invalid_numbers || cfg->decode_invalid_numbers) { + cfg->encode_invalid_numbers = cfg->decode_invalid_numbers = 0; + luaL_error(l, "Infinity, NaN, and/or hexadecimal numbers are not supported."); + } #endif + } + return 1; } @@ -398,8 +429,8 @@ static void json_create_config(lua_State *l) cfg->encode_sparse_safe = DEFAULT_SPARSE_SAFE; cfg->encode_max_depth = DEFAULT_ENCODE_MAX_DEPTH; cfg->decode_max_depth = DEFAULT_DECODE_MAX_DEPTH; - cfg->encode_refuse_badnum = DEFAULT_ENCODE_REFUSE_BADNUM; - cfg->decode_refuse_badnum = DEFAULT_DECODE_REFUSE_BADNUM; + cfg->encode_invalid_numbers = DEFAULT_ENCODE_INVALID_NUMBERS; + cfg->decode_invalid_numbers = DEFAULT_DECODE_INVALID_NUMBERS; cfg->encode_keep_buffer = DEFAULT_ENCODE_KEEP_BUFFER; cfg->encode_number_precision = DEFAULT_ENCODE_NUMBER_PRECISION; @@ -589,17 +620,28 @@ static void json_append_number(lua_State *l, json_config_t *cfg, double num = lua_tonumber(l, lindex); int len; - if (cfg->encode_refuse_badnum && (isinf(num) || isnan(num))) - json_encode_exception(l, cfg, json, lindex, "must not be NaN or Inf"); - - if (isnan(num)) { - /* Some platforms may print -nan, just hard code it */ - strbuf_append_mem(json, "nan", 3); + if (cfg->encode_invalid_numbers == 0) { + /* Prevent encoding invalid numbers */ + if (isinf(num) || isnan(num)) + json_encode_exception(l, cfg, json, lindex, "must not be NaN or Inf"); + } else if (cfg->encode_invalid_numbers == 1) { + /* Encode invalid numbers, but handle "nan" separately + * since some platforms may encode as "-nan". */ + if (isnan(num)) { + strbuf_append_mem(json, "nan", 3); + return; + } } else { - strbuf_ensure_empty_length(json, FPCONV_G_FMT_BUFSIZE); - len = fpconv_g_fmt(strbuf_empty_ptr(json), num, cfg->encode_number_precision); - strbuf_extend_length(json, len); + /* Encode invalid numbers as "null" */ + if (isinf(num) || isnan(num)) { + strbuf_append_mem(json, "null", 4); + return; + } } + + strbuf_ensure_empty_length(json, FPCONV_G_FMT_BUFSIZE); + len = fpconv_g_fmt(strbuf_empty_ptr(json), num, cfg->encode_number_precision); + strbuf_extend_length(json, len); } static void json_append_object(lua_State *l, json_config_t *cfg, @@ -1046,7 +1088,7 @@ static void json_next_token(json_parse_t *json, json_token_t *token) json_next_string_token(json, token); return; } else if (ch == '-' || ('0' <= ch && ch <= '9')) { - if (json->cfg->decode_refuse_badnum && json_is_invalid_number(json)) { + if (!json->cfg->decode_invalid_numbers && json_is_invalid_number(json)) { json_set_token_error(token, json, "invalid number"); return; } @@ -1066,9 +1108,9 @@ static void json_next_token(json_parse_t *json, json_token_t *token) token->type = T_NULL; json->ptr += 4; return; - } else if (!json->cfg->decode_refuse_badnum && + } else if (json->cfg->decode_invalid_numbers && json_is_invalid_number(json)) { - /* When refuse_badnum is disabled, only attempt to process + /* When decode_invalid_numbers is enabled, only attempt to process * numbers we know are invalid JSON (Inf, NaN, hex) * This is required to generate an appropriate token error, * otherwise all bad tokens will register as "invalid number" @@ -1322,6 +1364,8 @@ static int lua_cjson_new(lua_State *l) { "decode_max_depth", json_cfg_decode_max_depth }, { "encode_number_precision", json_cfg_encode_number_precision }, { "encode_keep_buffer", json_cfg_encode_keep_buffer }, + { "encode_invalid_numbers", json_cfg_encode_invalid_numbers }, + { "decode_invalid_numbers", json_cfg_decode_invalid_numbers }, { "refuse_invalid_numbers", json_cfg_refuse_invalid_numbers }, { "new", lua_cjson_new }, { NULL, NULL } @@ -117,8 +117,10 @@ USE_INTERNAL_ISINF:: Workaround for Solaris platforms missing ++isinf++(3). DISABLE_CJSON_GLOBAL:: Do not store module table in global "cjson" variable. Redundant from Lua 5.2 onwards. DISABLE_INVALID_NUMBERS:: Recommended on platforms where ++strtod++(3) / - ++sprintf++(3) are not POSIX compliant (Eg, Windows MinGW). Restricts - the +cjson.refuse_invalid_numbers+ runtime configuration to +true+. + ++sprintf++(3) are not POSIX compliant (Eg, Windows MinGW). Prevents + +cjson.encode_invalid_numbers+ and +cjson.decode_invalid_numbers+ + from being enabled. However, +cjson.encode_invalid_numbers+ may be + set to +"null"+. Built-in dtoa() support @@ -154,11 +156,12 @@ text = cjson.encode(value) value = cjson.decode(text) -- Get and/or set Lua CJSON configuration -setting = cjson.refuse_invalid_numbers([setting]) +setting = cjson.decode_invalid_numbers([setting]) +setting = cjson.encode_invalid_numbers([setting]) depth = cjson.encode_max_depth([depth]) +depth = cjson.decode_max_depth([depth]) convert, ratio, safe = cjson.encode_sparse_array([convert[, ratio[, safe]]]) keep = cjson.encode_keep_buffer([keep]) -depth = cjson.decode_max_depth([depth]) ------------ @@ -226,7 +229,7 @@ can be compared with +cjson.null+ for convenience. By default, numbers incompatible with the JSON specification (NaN, Infinity, Hexadecimal) can be decoded. This default can be changed -with +cjson.refuse_invalid_numbers+. +with +cjson.decode_invalid_numbers+. .Example: Decoding [source,lua] @@ -240,6 +243,30 @@ numeric key will be stored as a Lua +string+. Any code assuming type +number+ may break. +[[decode_invalid_numbers]] +decode_invalid_numbers +~~~~~~~~~~~~~~~~~~~~~~ + +[source,lua] +------------ +setting = cjson.decode_invalid_numbers([setting]) +-- "setting" must be on of: +-- "off", "on", false, true +------------ + +Lua CJSON can throw an error when trying to parse numbers outside of +the JSON specification (_invalid numbers_): + +- Infinity +- Not-a-number (NaN) +- Hexadecimal + +By default Lua CJSON will decode _invalid numbers_. + +This setting is only changed when an argument is provided. The current +setting is always returned. + + [[decode_max_depth]] decode_max_depth ~~~~~~~~~~~~~~~~ @@ -352,7 +379,7 @@ These defaults can be changed with: - <<encode_max_depth,+cjson.encode_max_depth+>> - <<encode_sparse_array,+cjson.encode_sparse_array+>> -- <<refuse_invalid_numbers,+cjson.refuse_invalid_numbers+>> +- <<encode_invalid_numbers,+cjson.encode_invalid_numbers+>> .Example: Encoding [source,lua] @@ -361,6 +388,30 @@ json_text = cjson.encode(value) -- Returns: '[true,{"foo":"bar"}]' +[[encode_invalid_numbers]] +encode_invalid_numbers +~~~~~~~~~~~~~~~~~~~~~~ +[source,lua] +------------ +setting = cjson.encode_invalid_numbers([setting]) +-- "setting" must be on of: +-- "off", "on", "null", false, true +------------ + +By default, Lua CJSON will throw an error when trying to encode +numbers outside of the JSON specification (_invalid numbers_): + +- Infinity +- Not-a-number (NaN) + +When set to +"null"+, Lua CJSON will encode _invalid numbers_ as a +JSON +null+ value. This allows Infinity and NaN to be represented as +valid JSON. + +This setting is only changed when an argument is provided. The current +setting is always returned. + + encode_keep_buffer ~~~~~~~~~~~~~~~~~~ @@ -473,39 +524,6 @@ cjson.encode({ [1000] = "excessively sparse" }) -- Returns: '{"1000":"excessively sparse"}' -[[refuse_invalid_numbers]] -refuse_invalid_numbers -~~~~~~~~~~~~~~~~~~~~~~ - -[source,lua] ------------- -setting = cjson.refuse_invalid_numbers([setting]) --- "setting" must be on of: --- false, "encode", "decode", "both", true ------------- - -Lua CJSON can throw an error for numbers outside of the JSON -specification (_invalid numbers_): - -- Infinity -- NaN -- Hexadecimal - -By default Lua CJSON will decode _invalid numbers_, but will refuse to -encode them. - -This setting is only changed when an argument is provided. The current -setting is always returned. - -This setting can be configured separately for encoding and/or -decoding: - -[horizontal] -Enabled:: An error will be generated if an _invalid number_ is found. -Disabled for encoding:: NaN and Infinity can be encoded. -Disabled for decoding:: All numbers supported by +strtod+(3) will be parsed. - - API (Variables) --------------- diff --git a/tests/test.lua b/tests/test.lua index 0434a38..a4ebafe 100755 --- a/tests/test.lua +++ b/tests/test.lua @@ -138,22 +138,28 @@ local encode_error_tests = { { json.encode, { function () end }, false, { "Cannot serialise function: type not supported" } }, function () - json.refuse_invalid_numbers("encode") - return 'Setting refuse_invalid_numbers("encode")' + json.encode_invalid_numbers(false) + return 'Setting encode_invalid_numbers(false)' end, { json.encode, { NaN }, false, { "Cannot serialise number: must not be NaN or Inf" } }, { json.encode, { Inf }, false, { "Cannot serialise number: must not be NaN or Inf" } }, function () - json.refuse_invalid_numbers(false) - return 'Setting refuse_invalid_numbers(false).' + json.encode_invalid_numbers("null") + return 'Setting encode_invalid_numbers("null").' + end, + { json.encode, { NaN }, true, { "null" } }, + { json.encode, { Inf }, true, { "null" } }, + function () + json.encode_invalid_numbers(true) + return 'Setting encode_invalid_numbers(true).' end, { json.encode, { NaN }, true, { "nan" } }, { json.encode, { Inf }, true, { "inf" } }, function () - json.refuse_invalid_numbers("encode") - return 'Setting refuse_invalid_numbers("encode")' + json.encode_invalid_numbers(false) + return 'Setting encode_invalid_numbers(false)' end, } @@ -233,7 +239,8 @@ util.run_test_group("encode error", encode_error_tests) util.run_test_group("escape", escape_tests) util.run_test_group("locale", locale_tests) -json.refuse_invalid_numbers(false) +json.encode_invalid_numbers(true) +json.decode_invalid_numbers(true) json.encode_max_depth(20) for i = 1, #arg do util.run_test("decode cycle " .. arg[i], test_decode_cycle, { arg[i] }, |