1633 lines
47 KiB
C
1633 lines
47 KiB
C
/* Lua CJSON - JSON support for Lua
|
|
*
|
|
* Copyright (c) 2010-2012 Mark Pulford <mark@kyne.com.au>
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining
|
|
* a copy of this software and associated documentation files (the
|
|
* "Software"), to deal in the Software without restriction, including
|
|
* without limitation the rights to use, copy, modify, merge, publish,
|
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
* permit persons to whom the Software is furnished to do so, subject to
|
|
* the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be
|
|
* included in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
/* Caveats:
|
|
* - JSON "null" values are represented as lightuserdata since Lua
|
|
* tables cannot contain "nil". Compare with cjson.null.
|
|
* - Invalid UTF-8 characters are not detected and will be passed
|
|
* untouched. If required, UTF-8 error checking should be done
|
|
* outside this library.
|
|
* - Javascript comments are not part of the JSON spec, and are not
|
|
* currently supported.
|
|
*
|
|
* Note: Decoding is slower than encoding. Lua spends significant
|
|
* time (30%) managing tables when parsing JSON since it is
|
|
* difficult to know object/array sizes ahead of time.
|
|
*/
|
|
|
|
// #include <assert.h>
|
|
#include "module.h"
|
|
#include "c_string.h"
|
|
#include "c_math.h"
|
|
#include "c_limits.h"
|
|
#include "lauxlib.h"
|
|
#include "flash_api.h"
|
|
#include "ctype.h"
|
|
|
|
#include "strbuf.h"
|
|
#include "cjson_mem.h"
|
|
|
|
#define FPCONV_G_FMT_BUFSIZE 32
|
|
#define fpconv_strtod c_strtod
|
|
#define fpconv_init() ((void)0)
|
|
|
|
#ifndef CJSON_MODNAME
|
|
#define CJSON_MODNAME "cjson"
|
|
#endif
|
|
|
|
#ifndef CJSON_VERSION
|
|
#define CJSON_VERSION "2.1devel"
|
|
#endif
|
|
|
|
/* Workaround for Solaris platforms missing isinf() */
|
|
#if !defined(isinf) && (defined(USE_INTERNAL_ISINF) || defined(MISSING_ISINF))
|
|
#define isinf(x) (!isnan(x) && isnan((x) - (x)))
|
|
#endif
|
|
|
|
#define DEFAULT_SPARSE_CONVERT 0
|
|
#define DEFAULT_SPARSE_RATIO 2
|
|
#define DEFAULT_SPARSE_SAFE 10
|
|
#define DEFAULT_ENCODE_MAX_DEPTH 1000
|
|
#define DEFAULT_DECODE_MAX_DEPTH 1000
|
|
#define DEFAULT_ENCODE_INVALID_NUMBERS 0
|
|
#define DEFAULT_DECODE_INVALID_NUMBERS 1
|
|
#define DEFAULT_ENCODE_KEEP_BUFFER 0
|
|
#define DEFAULT_ENCODE_NUMBER_PRECISION 14
|
|
|
|
#ifdef DISABLE_INVALID_NUMBERS
|
|
#undef DEFAULT_DECODE_INVALID_NUMBERS
|
|
#define DEFAULT_DECODE_INVALID_NUMBERS 0
|
|
#endif
|
|
|
|
typedef enum {
|
|
T_OBJ_BEGIN,
|
|
T_OBJ_END,
|
|
T_ARR_BEGIN,
|
|
T_ARR_END,
|
|
T_STRING,
|
|
T_NUMBER,
|
|
T_BOOLEAN,
|
|
T_NULL,
|
|
T_COLON,
|
|
T_COMMA,
|
|
T_END,
|
|
T_WHITESPACE,
|
|
T_ERROR,
|
|
T_UNKNOWN
|
|
} json_token_type_t;
|
|
#if 0
|
|
static const char *json_token_type_name[] = {
|
|
"T_OBJ_BEGIN",
|
|
"T_OBJ_END",
|
|
"T_ARR_BEGIN",
|
|
"T_ARR_END",
|
|
"T_STRING",
|
|
"T_NUMBER",
|
|
"T_BOOLEAN",
|
|
"T_NULL",
|
|
"T_COLON",
|
|
"T_COMMA",
|
|
"T_END",
|
|
"T_WHITESPACE",
|
|
"T_ERROR",
|
|
"T_UNKNOWN",
|
|
NULL
|
|
};
|
|
#endif
|
|
static const char json_token_type_name[14][16] ICACHE_STORE_ATTR ICACHE_RODATA_ATTR = {
|
|
{'T','_','O','B','J','_','B','E','G','I','N',0},
|
|
{'T','_','O','B','J','_','E','N','D',0},
|
|
{'T','_','A','R','R','_','B','E','G','I','N',0},
|
|
{'T','_','A','R','R','_','E','N','D',0},
|
|
{'T','_','S','T','R','I','N','G',0},
|
|
{'T','_','N','U','M','B','E','R',0},
|
|
{'T','_','B','O','O','L','E','A','N',0},
|
|
{'T','_','N','U','L','L',0},
|
|
{'T','_','C','O','L','O','N',0},
|
|
{'T','_','C','O','M','M','A',0},
|
|
{'T','_','E','N','D',0},
|
|
{'T','_','W','H','I','T','E','S','P','A','C','E',0},
|
|
{'T','_','E','R','R','O','R',0},
|
|
{'T','_','U','N','K','N','O','W','N',0}
|
|
};
|
|
|
|
typedef struct {
|
|
// json_token_type_t ch2token[256]; // 256*4 = 1024 byte
|
|
// char escape2char[256]; /* Decoding */
|
|
|
|
/* encode_buf is only allocated and used when
|
|
* encode_keep_buffer is set */
|
|
strbuf_t encode_buf;
|
|
|
|
int encode_sparse_convert;
|
|
int encode_sparse_ratio;
|
|
int encode_sparse_safe;
|
|
int encode_max_depth;
|
|
int encode_invalid_numbers; /* 2 => Encode as "null" */
|
|
int encode_number_precision;
|
|
int encode_keep_buffer;
|
|
|
|
int decode_invalid_numbers;
|
|
int decode_max_depth;
|
|
} json_config_t;
|
|
|
|
typedef struct {
|
|
const char *data;
|
|
const char *ptr;
|
|
strbuf_t *tmp; /* Temporary storage for strings */
|
|
json_config_t *cfg;
|
|
int current_depth;
|
|
} json_parse_t;
|
|
|
|
typedef struct {
|
|
json_token_type_t type;
|
|
int index;
|
|
union {
|
|
const char *string;
|
|
double number;
|
|
int boolean;
|
|
} value;
|
|
int string_len;
|
|
} json_token_t;
|
|
|
|
#if 0
|
|
static const char *char2escape[256] = {
|
|
"\\u0000", "\\u0001", "\\u0002", "\\u0003",
|
|
"\\u0004", "\\u0005", "\\u0006", "\\u0007",
|
|
"\\b", "\\t", "\\n", "\\u000b",
|
|
"\\f", "\\r", "\\u000e", "\\u000f",
|
|
"\\u0010", "\\u0011", "\\u0012", "\\u0013",
|
|
"\\u0014", "\\u0015", "\\u0016", "\\u0017",
|
|
"\\u0018", "\\u0019", "\\u001a", "\\u001b",
|
|
"\\u001c", "\\u001d", "\\u001e", "\\u001f",
|
|
NULL, NULL, "\\\"", NULL, NULL, NULL, NULL, NULL,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, "\\/",
|
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
|
NULL, NULL, NULL, NULL, "\\\\", NULL, NULL, NULL,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, "\\u007f",
|
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
|
};
|
|
#endif
|
|
|
|
/* ===== HELPER FUNCTION ===== */
|
|
static const char escape_array[36][8] ICACHE_STORE_ATTR ICACHE_RODATA_ATTR = {
|
|
{'\\','u','0','0','0','0','\0','\0'},
|
|
{'\\','u','0','0','0','1','\0','\0'},
|
|
{'\\','u','0','0','0','2','\0','\0'},
|
|
{'\\','u','0','0','0','3','\0','\0'},
|
|
{'\\','u','0','0','0','4','\0','\0'},
|
|
{'\\','u','0','0','0','5','\0','\0'},
|
|
{'\\','u','0','0','0','6','\0','\0'},
|
|
{'\\','u','0','0','0','7','\0','\0'},
|
|
{'\\','b','\0','\0','\0','\0','\0','\0'},
|
|
{'\\','t','\0','\0','\0','\0','\0','\0'},
|
|
{'\\','n','\0','\0','\0','\0','\0','\0'},
|
|
{'\\','u','0','0','0','b','\0','\0'},
|
|
{'\\','f','\0','\0','\0','\0','\0','\0'},
|
|
{'\\','r','\0','\0','\0','\0','\0','\0'},
|
|
{'\\','u','0','0','0','e','\0','\0'},
|
|
{'\\','u','0','0','0','f','\0','\0'},
|
|
{'\\','u','0','0','1','0','\0','\0'},
|
|
{'\\','u','0','0','1','1','\0','\0'},
|
|
{'\\','u','0','0','1','2','\0','\0'},
|
|
{'\\','u','0','0','1','3','\0','\0'},
|
|
{'\\','u','0','0','1','4','\0','\0'},
|
|
{'\\','u','0','0','1','5','\0','\0'},
|
|
{'\\','u','0','0','1','6','\0','\0'},
|
|
{'\\','u','0','0','1','7','\0','\0'},
|
|
{'\\','u','0','0','1','8','\0','\0'},
|
|
{'\\','u','0','0','1','9','\0','\0'},
|
|
{'\\','u','0','0','1','a','\0','\0'},
|
|
{'\\','u','0','0','1','b','\0','\0'},
|
|
{'\\','u','0','0','1','c','\0','\0'},
|
|
{'\\','u','0','0','1','d','\0','\0'},
|
|
{'\\','u','0','0','1','e','\0','\0'},
|
|
{'\\','u','0','0','1','f','\0','\0'},
|
|
{'\\','\"','\0','\0','\0','\0','\0','\0'},
|
|
{'\\','/','\0','\0','\0','\0','\0','\0'},
|
|
{'\\','\\','\0','\0','\0','\0','\0','\0'},
|
|
{'\\','u','0','0','7','f','\0','\0'}
|
|
};
|
|
|
|
static const char *char2escape(unsigned char c){
|
|
if(c<32) return escape_array[c];
|
|
|
|
switch(c){
|
|
case 34: return escape_array[32];
|
|
case 47: return escape_array[33];
|
|
case 92: return escape_array[34];
|
|
case 127: return escape_array[35];
|
|
default:
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
static json_token_type_t ch2token(unsigned char c){
|
|
switch(c){
|
|
case '{': return T_OBJ_BEGIN;
|
|
case '}': return T_OBJ_END;
|
|
case '[': return T_ARR_BEGIN;
|
|
case ']': return T_ARR_END;
|
|
case ',': return T_COMMA;
|
|
case ':': return T_COLON;
|
|
case '\0': return T_END;
|
|
case ' ': return T_WHITESPACE;
|
|
case '\t': return T_WHITESPACE;
|
|
case '\n': return T_WHITESPACE;
|
|
case '\r': return T_WHITESPACE;
|
|
|
|
/* Update characters that require further processing */
|
|
case 'f': case 'i': case 'I': case 'n': case 'N': case 't': case '"': case '+': case '-':
|
|
case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
|
|
return T_UNKNOWN;
|
|
default:
|
|
return T_ERROR;
|
|
}
|
|
}
|
|
|
|
static char escape2char(unsigned char c){
|
|
switch(c){
|
|
case '"': return '"';
|
|
case '\\': return '\\';
|
|
case '/': return '/';
|
|
case 'b': return '\b';
|
|
case 't': return '\t';
|
|
case 'n': return '\n';
|
|
case 'f': return '\f';
|
|
case 'r': return '\r';
|
|
case 'u': return 'u';
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* ===== CONFIGURATION ===== */
|
|
#if 0
|
|
static json_config_t *json_fetch_config(lua_State *l)
|
|
{
|
|
json_config_t *cfg;
|
|
|
|
cfg = lua_touserdata(l, lua_upvalueindex(1));
|
|
if (!cfg)
|
|
luaL_error(l, "BUG: Unable to fetch CJSON configuration");
|
|
|
|
return cfg;
|
|
}
|
|
|
|
/* Ensure the correct number of arguments have been provided.
|
|
* Pad with nil to allow other functions to simply check arg[i]
|
|
* to find whether an argument was provided */
|
|
static json_config_t *json_arg_init(lua_State *l, int args)
|
|
{
|
|
luaL_argcheck(l, lua_gettop(l) <= args, args + 1,
|
|
"found too many arguments");
|
|
|
|
while (lua_gettop(l) < args)
|
|
lua_pushnil(l);
|
|
|
|
return json_fetch_config(l);
|
|
}
|
|
|
|
/* Process integer options for configuration functions */
|
|
static int json_integer_option(lua_State *l, int optindex, int *setting,
|
|
int min, int max)
|
|
{
|
|
char errmsg[64];
|
|
int value;
|
|
|
|
if (!lua_isnil(l, optindex)) {
|
|
value = luaL_checkinteger(l, optindex);
|
|
c_sprintf(errmsg, "expected integer between %d and %d", min, max);
|
|
luaL_argcheck(l, min <= value && value <= max, 1, errmsg);
|
|
*setting = value;
|
|
}
|
|
|
|
lua_pushinteger(l, *setting);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Process enumerated arguments for a configuration function */
|
|
static int json_enum_option(lua_State *l, int optindex, int *setting,
|
|
const char **options, int bool_true)
|
|
{
|
|
static const char *bool_options[] = { "off", "on", NULL };
|
|
|
|
if (!options) {
|
|
options = bool_options;
|
|
bool_true = 1;
|
|
}
|
|
|
|
if (!lua_isnil(l, optindex)) {
|
|
if (bool_true && lua_isboolean(l, optindex))
|
|
*setting = lua_toboolean(l, optindex) * bool_true;
|
|
else
|
|
*setting = luaL_checkoption(l, optindex, NULL, options);
|
|
}
|
|
|
|
if (bool_true && (*setting == 0 || *setting == bool_true))
|
|
lua_pushboolean(l, *setting);
|
|
else
|
|
lua_pushstring(l, options[*setting]);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Configures handling of extremely sparse arrays:
|
|
* convert: Convert extremely sparse arrays into objects? Otherwise error.
|
|
* ratio: 0: always allow sparse; 1: never allow sparse; >1: use ratio
|
|
* safe: Always use an array when the max index <= safe */
|
|
static int json_cfg_encode_sparse_array(lua_State *l)
|
|
{
|
|
json_config_t *cfg = json_arg_init(l, 3);
|
|
|
|
json_enum_option(l, 1, &cfg->encode_sparse_convert, NULL, 1);
|
|
json_integer_option(l, 2, &cfg->encode_sparse_ratio, 0, INT_MAX);
|
|
json_integer_option(l, 3, &cfg->encode_sparse_safe, 0, INT_MAX);
|
|
|
|
return 3;
|
|
}
|
|
|
|
/* Configures the maximum number of nested arrays/objects allowed when
|
|
* encoding */
|
|
static int json_cfg_encode_max_depth(lua_State *l)
|
|
{
|
|
json_config_t *cfg = json_arg_init(l, 1);
|
|
|
|
return json_integer_option(l, 1, &cfg->encode_max_depth, 1, INT_MAX);
|
|
}
|
|
|
|
/* Configures the maximum number of nested arrays/objects allowed when
|
|
* encoding */
|
|
static int json_cfg_decode_max_depth(lua_State *l)
|
|
{
|
|
json_config_t *cfg = json_arg_init(l, 1);
|
|
|
|
return json_integer_option(l, 1, &cfg->decode_max_depth, 1, INT_MAX);
|
|
}
|
|
|
|
/* Configures number precision when converting doubles to text */
|
|
static int json_cfg_encode_number_precision(lua_State *l)
|
|
{
|
|
json_config_t *cfg = json_arg_init(l, 1);
|
|
|
|
return json_integer_option(l, 1, &cfg->encode_number_precision, 1, 14);
|
|
}
|
|
|
|
/* Configures JSON encoding buffer persistence */
|
|
static int json_cfg_encode_keep_buffer(lua_State *l)
|
|
{
|
|
json_config_t *cfg = json_arg_init(l, 1);
|
|
int old_value;
|
|
|
|
old_value = cfg->encode_keep_buffer;
|
|
|
|
json_enum_option(l, 1, &cfg->encode_keep_buffer, NULL, 1);
|
|
|
|
/* Init / free the buffer if the setting has changed */
|
|
if (old_value ^ cfg->encode_keep_buffer) {
|
|
if (cfg->encode_keep_buffer){
|
|
if(-1==strbuf_init(&cfg->encode_buf, 0))
|
|
return luaL_error(l, "not enough memory");
|
|
}
|
|
else
|
|
strbuf_free(&cfg->encode_buf);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
#if defined(DISABLE_INVALID_NUMBERS) && !defined(USE_INTERNAL_FPCONV)
|
|
void json_verify_invalid_number_setting(lua_State *l, int *setting)
|
|
{
|
|
if (*setting == 1) {
|
|
*setting = 0;
|
|
luaL_error(l, "Infinity, NaN, and/or hexadecimal numbers are not supported.");
|
|
}
|
|
}
|
|
#else
|
|
#define json_verify_invalid_number_setting(l, s) do { } while(0)
|
|
#endif
|
|
|
|
static int json_cfg_encode_invalid_numbers(lua_State *l)
|
|
{
|
|
static const char *options[] = { "off", "on", "null", NULL };
|
|
json_config_t *cfg = json_arg_init(l, 1);
|
|
|
|
json_enum_option(l, 1, &cfg->encode_invalid_numbers, options, 1);
|
|
|
|
json_verify_invalid_number_setting(l, &cfg->encode_invalid_numbers);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int json_cfg_decode_invalid_numbers(lua_State *l)
|
|
{
|
|
json_config_t *cfg = json_arg_init(l, 1);
|
|
|
|
json_enum_option(l, 1, &cfg->decode_invalid_numbers, NULL, 1);
|
|
|
|
json_verify_invalid_number_setting(l, &cfg->encode_invalid_numbers);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int json_destroy_config(lua_State *l)
|
|
{
|
|
json_config_t *cfg;
|
|
|
|
cfg = lua_touserdata(l, 1);
|
|
if (cfg)
|
|
strbuf_free(&cfg->encode_buf);
|
|
cfg = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void json_create_config(lua_State *l)
|
|
{
|
|
json_config_t *cfg;
|
|
int i;
|
|
|
|
cfg = lua_newuserdata(l, sizeof(*cfg));
|
|
|
|
/* Create GC method to clean up strbuf */
|
|
lua_newtable(l);
|
|
lua_pushcfunction(l, json_destroy_config);
|
|
lua_setfield(l, -2, "__gc");
|
|
lua_setmetatable(l, -2);
|
|
|
|
cfg->encode_sparse_convert = DEFAULT_SPARSE_CONVERT;
|
|
cfg->encode_sparse_ratio = DEFAULT_SPARSE_RATIO;
|
|
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_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;
|
|
|
|
#if DEFAULT_ENCODE_KEEP_BUFFER > 0
|
|
strbuf_init(&cfg->encode_buf, 0);
|
|
#endif
|
|
|
|
/* Decoding init */
|
|
|
|
/* Tag all characters as an error */
|
|
for (i = 0; i < 256; i++)
|
|
cfg->ch2token[i] = T_ERROR;
|
|
|
|
/* Set tokens that require no further processing */
|
|
cfg->ch2token['{'] = T_OBJ_BEGIN;
|
|
cfg->ch2token['}'] = T_OBJ_END;
|
|
cfg->ch2token['['] = T_ARR_BEGIN;
|
|
cfg->ch2token[']'] = T_ARR_END;
|
|
cfg->ch2token[','] = T_COMMA;
|
|
cfg->ch2token[':'] = T_COLON;
|
|
cfg->ch2token['\0'] = T_END;
|
|
cfg->ch2token[' '] = T_WHITESPACE;
|
|
cfg->ch2token['\t'] = T_WHITESPACE;
|
|
cfg->ch2token['\n'] = T_WHITESPACE;
|
|
cfg->ch2token['\r'] = T_WHITESPACE;
|
|
|
|
/* Update characters that require further processing */
|
|
cfg->ch2token['f'] = T_UNKNOWN; /* false? */
|
|
cfg->ch2token['i'] = T_UNKNOWN; /* inf, ininity? */
|
|
cfg->ch2token['I'] = T_UNKNOWN;
|
|
cfg->ch2token['n'] = T_UNKNOWN; /* null, nan? */
|
|
cfg->ch2token['N'] = T_UNKNOWN;
|
|
cfg->ch2token['t'] = T_UNKNOWN; /* true? */
|
|
cfg->ch2token['"'] = T_UNKNOWN; /* string? */
|
|
cfg->ch2token['+'] = T_UNKNOWN; /* number? */
|
|
cfg->ch2token['-'] = T_UNKNOWN;
|
|
for (i = 0; i < 10; i++)
|
|
cfg->ch2token['0' + i] = T_UNKNOWN;
|
|
|
|
/* Lookup table for parsing escape characters */
|
|
for (i = 0; i < 256; i++)
|
|
cfg->escape2char[i] = 0; /* String error */
|
|
cfg->escape2char['"'] = '"';
|
|
cfg->escape2char['\\'] = '\\';
|
|
cfg->escape2char['/'] = '/';
|
|
cfg->escape2char['b'] = '\b';
|
|
cfg->escape2char['t'] = '\t';
|
|
cfg->escape2char['n'] = '\n';
|
|
cfg->escape2char['f'] = '\f';
|
|
cfg->escape2char['r'] = '\r';
|
|
cfg->escape2char['u'] = 'u'; /* Unicode parsing required */
|
|
}
|
|
#endif
|
|
|
|
json_config_t _cfg;
|
|
|
|
static json_config_t *json_fetch_config(lua_State *l)
|
|
{
|
|
return &_cfg;
|
|
}
|
|
|
|
static int cfg_init(json_config_t *cfg){
|
|
cfg->encode_sparse_convert = DEFAULT_SPARSE_CONVERT;
|
|
cfg->encode_sparse_ratio = DEFAULT_SPARSE_RATIO;
|
|
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_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;
|
|
|
|
#if DEFAULT_ENCODE_KEEP_BUFFER > 0
|
|
if(-1==strbuf_init(&cfg->encode_buf, 0)){
|
|
NODE_ERR("not enough memory\n");
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
/* ===== ENCODING ===== */
|
|
|
|
static void json_encode_exception(lua_State *l, json_config_t *cfg, strbuf_t *json, int lindex,
|
|
const char *reason)
|
|
{
|
|
if (!cfg->encode_keep_buffer)
|
|
strbuf_free(json);
|
|
luaL_error(l, "Cannot serialise %s: %s",
|
|
lua_typename(l, lua_type(l, lindex)), reason);
|
|
}
|
|
|
|
/* json_append_string args:
|
|
* - lua_State
|
|
* - JSON strbuf
|
|
* - String (Lua stack index)
|
|
*
|
|
* Returns nothing. Doesn't remove string from Lua stack */
|
|
static void json_append_string(lua_State *l, strbuf_t *json, int lindex)
|
|
{
|
|
const char *escstr;
|
|
int i;
|
|
const char *str;
|
|
size_t len;
|
|
|
|
str = lua_tolstring(l, lindex, &len);
|
|
|
|
/* Worst case is len * 6 (all unicode escapes).
|
|
* This buffer is reused constantly for small strings
|
|
* If there are any excess pages, they won't be hit anyway.
|
|
* This gains ~5% speedup. */
|
|
strbuf_ensure_empty_length(json, len * 6 + 2);
|
|
|
|
strbuf_append_char_unsafe(json, '\"');
|
|
for (i = 0; i < len; i++) {
|
|
escstr = char2escape((unsigned char)str[i]);
|
|
if (escstr){
|
|
int i;
|
|
char temp[8]; // for now, 8-bytes is enough.
|
|
for (i=0; i < 8; ++i)
|
|
{
|
|
temp[i] = byte_of_aligned_array(escstr, i);
|
|
if(temp[i]==0) break;
|
|
}
|
|
escstr = temp;
|
|
strbuf_append_string(json, escstr);
|
|
}
|
|
else
|
|
strbuf_append_char_unsafe(json, str[i]);
|
|
}
|
|
strbuf_append_char_unsafe(json, '\"');
|
|
}
|
|
|
|
/* Find the size of the array on the top of the Lua stack
|
|
* -1 object (not a pure array)
|
|
* >=0 elements in array
|
|
*/
|
|
static int lua_array_length(lua_State *l, json_config_t *cfg, strbuf_t *json)
|
|
{
|
|
double k;
|
|
int max;
|
|
int items;
|
|
|
|
max = 0;
|
|
items = 0;
|
|
|
|
lua_pushnil(l);
|
|
/* table, startkey */
|
|
while (lua_next(l, -2) != 0) {
|
|
/* table, key, value */
|
|
if (lua_type(l, -2) == LUA_TNUMBER &&
|
|
(k = lua_tonumber(l, -2))) {
|
|
/* Integer >= 1 ? */
|
|
if (floor(k) == k && k >= 1) {
|
|
if (k > max)
|
|
max = k;
|
|
items++;
|
|
lua_pop(l, 1);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* Must not be an array (non integer key) */
|
|
lua_pop(l, 2);
|
|
return -1;
|
|
}
|
|
|
|
/* Encode excessively sparse arrays as objects (if enabled) */
|
|
if (cfg->encode_sparse_ratio > 0 &&
|
|
max > items * cfg->encode_sparse_ratio &&
|
|
max > cfg->encode_sparse_safe) {
|
|
if (!cfg->encode_sparse_convert)
|
|
json_encode_exception(l, cfg, json, -1, "excessively sparse array");
|
|
|
|
return -1;
|
|
}
|
|
|
|
return max;
|
|
}
|
|
|
|
static void json_check_encode_depth(lua_State *l, json_config_t *cfg,
|
|
int current_depth, strbuf_t *json)
|
|
{
|
|
/* Ensure there are enough slots free to traverse a table (key,
|
|
* value) and push a string for a potential error message.
|
|
*
|
|
* Unlike "decode", the key and value are still on the stack when
|
|
* lua_checkstack() is called. Hence an extra slot for luaL_error()
|
|
* below is required just in case the next check to lua_checkstack()
|
|
* fails.
|
|
*
|
|
* While this won't cause a crash due to the EXTRA_STACK reserve
|
|
* slots, it would still be an improper use of the API. */
|
|
if (current_depth <= cfg->encode_max_depth && lua_checkstack(l, 3))
|
|
return;
|
|
|
|
if (!cfg->encode_keep_buffer)
|
|
strbuf_free(json);
|
|
|
|
luaL_error(l, "Cannot serialise, excessive nesting (%d)",
|
|
current_depth);
|
|
}
|
|
|
|
static void json_append_data(lua_State *l, json_config_t *cfg,
|
|
int current_depth, strbuf_t *json);
|
|
|
|
/* json_append_array args:
|
|
* - lua_State
|
|
* - JSON strbuf
|
|
* - Size of passwd Lua array (top of stack) */
|
|
static void json_append_array(lua_State *l, json_config_t *cfg, int current_depth,
|
|
strbuf_t *json, int array_length)
|
|
{
|
|
int comma, i;
|
|
|
|
strbuf_append_char(json, '[');
|
|
|
|
comma = 0;
|
|
for (i = 1; i <= array_length; i++) {
|
|
if (comma)
|
|
strbuf_append_char(json, ',');
|
|
else
|
|
comma = 1;
|
|
|
|
lua_rawgeti(l, -1, i);
|
|
json_append_data(l, cfg, current_depth, json);
|
|
lua_pop(l, 1);
|
|
}
|
|
|
|
strbuf_append_char(json, ']');
|
|
}
|
|
|
|
static void json_append_number(lua_State *l, json_config_t *cfg,
|
|
strbuf_t *json, int lindex)
|
|
{
|
|
double num = lua_tonumber(l, lindex);
|
|
int len;
|
|
|
|
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 Infinity");
|
|
} else if (cfg->encode_invalid_numbers == 1) {
|
|
/* Encode NaN/Infinity separately to ensure Javascript compatible
|
|
* values are used. */
|
|
if (isnan(num)) {
|
|
strbuf_append_mem(json, "NaN", 3);
|
|
return;
|
|
}
|
|
if (isinf(num)) {
|
|
if (num < 0)
|
|
strbuf_append_mem(json, "-Infinity", 9);
|
|
else
|
|
strbuf_append_mem(json, "Infinity", 8);
|
|
return;
|
|
}
|
|
} else {
|
|
/* 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);
|
|
c_sprintf(strbuf_empty_ptr(json), LUA_NUMBER_FMT, (LUA_NUMBER)num);
|
|
len = c_strlen(strbuf_empty_ptr(json));
|
|
|
|
strbuf_extend_length(json, len);
|
|
}
|
|
|
|
static void json_append_object(lua_State *l, json_config_t *cfg,
|
|
int current_depth, strbuf_t *json)
|
|
{
|
|
int comma, keytype;
|
|
|
|
/* Object */
|
|
strbuf_append_char(json, '{');
|
|
|
|
lua_pushnil(l);
|
|
/* table, startkey */
|
|
comma = 0;
|
|
while (lua_next(l, -2) != 0) {
|
|
if (comma)
|
|
strbuf_append_char(json, ',');
|
|
else
|
|
comma = 1;
|
|
|
|
/* table, key, value */
|
|
keytype = lua_type(l, -2);
|
|
if (keytype == LUA_TNUMBER) {
|
|
strbuf_append_char(json, '"');
|
|
json_append_number(l, cfg, json, -2);
|
|
strbuf_append_mem(json, "\":", 2);
|
|
} else if (keytype == LUA_TSTRING) {
|
|
json_append_string(l, json, -2);
|
|
strbuf_append_char(json, ':');
|
|
} else {
|
|
json_encode_exception(l, cfg, json, -2,
|
|
"table key must be a number or string");
|
|
/* never returns */
|
|
}
|
|
|
|
/* table, key, value */
|
|
json_append_data(l, cfg, current_depth, json);
|
|
lua_pop(l, 1);
|
|
/* table, key */
|
|
}
|
|
|
|
strbuf_append_char(json, '}');
|
|
}
|
|
|
|
/* Serialise Lua data into JSON string. */
|
|
static void json_append_data(lua_State *l, json_config_t *cfg,
|
|
int current_depth, strbuf_t *json)
|
|
{
|
|
int len;
|
|
|
|
switch (lua_type(l, -1)) {
|
|
case LUA_TSTRING:
|
|
json_append_string(l, json, -1);
|
|
break;
|
|
case LUA_TNUMBER:
|
|
json_append_number(l, cfg, json, -1);
|
|
break;
|
|
case LUA_TBOOLEAN:
|
|
if (lua_toboolean(l, -1))
|
|
strbuf_append_mem(json, "true", 4);
|
|
else
|
|
strbuf_append_mem(json, "false", 5);
|
|
break;
|
|
case LUA_TTABLE:
|
|
current_depth++;
|
|
json_check_encode_depth(l, cfg, current_depth, json);
|
|
len = lua_array_length(l, cfg, json);
|
|
if (len > 0)
|
|
json_append_array(l, cfg, current_depth, json, len);
|
|
else
|
|
json_append_object(l, cfg, current_depth, json);
|
|
break;
|
|
case LUA_TNIL:
|
|
strbuf_append_mem(json, "null", 4);
|
|
break;
|
|
case LUA_TLIGHTUSERDATA:
|
|
if (lua_touserdata(l, -1) == NULL) {
|
|
strbuf_append_mem(json, "null", 4);
|
|
break;
|
|
}
|
|
default:
|
|
/* Remaining types (LUA_TFUNCTION, LUA_TUSERDATA, LUA_TTHREAD,
|
|
* and LUA_TLIGHTUSERDATA) cannot be serialised */
|
|
json_encode_exception(l, cfg, json, -1, "type not supported");
|
|
/* never returns */
|
|
}
|
|
}
|
|
|
|
static int json_encode(lua_State *l)
|
|
{
|
|
json_config_t *cfg = json_fetch_config(l);
|
|
strbuf_t local_encode_buf;
|
|
strbuf_t *encode_buf;
|
|
char *json;
|
|
int len;
|
|
|
|
luaL_argcheck(l, lua_gettop(l) == 1, 1, "expected 1 argument");
|
|
|
|
if (!cfg->encode_keep_buffer) {
|
|
/* Use private buffer */
|
|
encode_buf = &local_encode_buf;
|
|
if(-1==strbuf_init(encode_buf, 0))
|
|
return luaL_error(l, "not enough memory");
|
|
} else {
|
|
/* Reuse existing buffer */
|
|
encode_buf = &cfg->encode_buf;
|
|
strbuf_reset(encode_buf);
|
|
}
|
|
|
|
json_append_data(l, cfg, 0, encode_buf);
|
|
json = strbuf_string(encode_buf, &len);
|
|
|
|
lua_pushlstring(l, json, len);
|
|
|
|
if (!cfg->encode_keep_buffer)
|
|
strbuf_free(encode_buf);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* ===== DECODING ===== */
|
|
|
|
static void json_process_value(lua_State *l, json_parse_t *json,
|
|
json_token_t *token);
|
|
|
|
static int hexdigit2int(char hex)
|
|
{
|
|
if ('0' <= hex && hex <= '9')
|
|
return hex - '0';
|
|
|
|
/* Force lowercase */
|
|
hex |= 0x20;
|
|
if ('a' <= hex && hex <= 'f')
|
|
return 10 + hex - 'a';
|
|
|
|
return -1;
|
|
}
|
|
|
|
static int decode_hex4(const char *hex)
|
|
{
|
|
int digit[4];
|
|
int i;
|
|
|
|
/* Convert ASCII hex digit to numeric digit
|
|
* Note: this returns an error for invalid hex digits, including
|
|
* NULL */
|
|
for (i = 0; i < 4; i++) {
|
|
digit[i] = hexdigit2int(hex[i]);
|
|
if (digit[i] < 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return (digit[0] << 12) +
|
|
(digit[1] << 8) +
|
|
(digit[2] << 4) +
|
|
digit[3];
|
|
}
|
|
|
|
/* Converts a Unicode codepoint to UTF-8.
|
|
* Returns UTF-8 string length, and up to 4 bytes in *utf8 */
|
|
static int codepoint_to_utf8(char *utf8, int codepoint)
|
|
{
|
|
/* 0xxxxxxx */
|
|
if (codepoint <= 0x7F) {
|
|
utf8[0] = codepoint;
|
|
return 1;
|
|
}
|
|
|
|
/* 110xxxxx 10xxxxxx */
|
|
if (codepoint <= 0x7FF) {
|
|
utf8[0] = (codepoint >> 6) | 0xC0;
|
|
utf8[1] = (codepoint & 0x3F) | 0x80;
|
|
return 2;
|
|
}
|
|
|
|
/* 1110xxxx 10xxxxxx 10xxxxxx */
|
|
if (codepoint <= 0xFFFF) {
|
|
utf8[0] = (codepoint >> 12) | 0xE0;
|
|
utf8[1] = ((codepoint >> 6) & 0x3F) | 0x80;
|
|
utf8[2] = (codepoint & 0x3F) | 0x80;
|
|
return 3;
|
|
}
|
|
|
|
/* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */
|
|
if (codepoint <= 0x1FFFFF) {
|
|
utf8[0] = (codepoint >> 18) | 0xF0;
|
|
utf8[1] = ((codepoint >> 12) & 0x3F) | 0x80;
|
|
utf8[2] = ((codepoint >> 6) & 0x3F) | 0x80;
|
|
utf8[3] = (codepoint & 0x3F) | 0x80;
|
|
return 4;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Called when index pointing to beginning of UTF-16 code escape: \uXXXX
|
|
* \u is guaranteed to exist, but the remaining hex characters may be
|
|
* missing.
|
|
* Translate to UTF-8 and append to temporary token string.
|
|
* Must advance index to the next character to be processed.
|
|
* Returns: 0 success
|
|
* -1 error
|
|
*/
|
|
static int json_append_unicode_escape(json_parse_t *json)
|
|
{
|
|
char utf8[4]; /* Surrogate pairs require 4 UTF-8 bytes */
|
|
int codepoint;
|
|
int surrogate_low;
|
|
int len;
|
|
int escape_len = 6;
|
|
|
|
/* Fetch UTF-16 code unit */
|
|
codepoint = decode_hex4(json->ptr + 2);
|
|
if (codepoint < 0)
|
|
return -1;
|
|
|
|
/* UTF-16 surrogate pairs take the following 2 byte form:
|
|
* 11011 x yyyyyyyyyy
|
|
* When x = 0: y is the high 10 bits of the codepoint
|
|
* x = 1: y is the low 10 bits of the codepoint
|
|
*
|
|
* Check for a surrogate pair (high or low) */
|
|
if ((codepoint & 0xF800) == 0xD800) {
|
|
/* Error if the 1st surrogate is not high */
|
|
if (codepoint & 0x400)
|
|
return -1;
|
|
|
|
/* Ensure the next code is a unicode escape */
|
|
if (*(json->ptr + escape_len) != '\\' ||
|
|
*(json->ptr + escape_len + 1) != 'u') {
|
|
return -1;
|
|
}
|
|
|
|
/* Fetch the next codepoint */
|
|
surrogate_low = decode_hex4(json->ptr + 2 + escape_len);
|
|
if (surrogate_low < 0)
|
|
return -1;
|
|
|
|
/* Error if the 2nd code is not a low surrogate */
|
|
if ((surrogate_low & 0xFC00) != 0xDC00)
|
|
return -1;
|
|
|
|
/* Calculate Unicode codepoint */
|
|
codepoint = (codepoint & 0x3FF) << 10;
|
|
surrogate_low &= 0x3FF;
|
|
codepoint = (codepoint | surrogate_low) + 0x10000;
|
|
escape_len = 12;
|
|
}
|
|
|
|
/* Convert codepoint to UTF-8 */
|
|
len = codepoint_to_utf8(utf8, codepoint);
|
|
if (!len)
|
|
return -1;
|
|
|
|
/* Append bytes and advance parse index */
|
|
strbuf_append_mem_unsafe(json->tmp, utf8, len);
|
|
json->ptr += escape_len;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void json_set_token_error(json_token_t *token, json_parse_t *json,
|
|
const char *errtype)
|
|
{
|
|
token->type = T_ERROR;
|
|
token->index = json->ptr - json->data;
|
|
token->value.string = errtype;
|
|
}
|
|
|
|
static void json_next_string_token(json_parse_t *json, json_token_t *token)
|
|
{
|
|
// char *escape2char = json->cfg->escape2char;
|
|
char ch;
|
|
|
|
/* Caller must ensure a string is next */
|
|
if(!(*json->ptr == '"')) return;
|
|
|
|
/* Skip " */
|
|
json->ptr++;
|
|
|
|
/* json->tmp is the temporary strbuf used to accumulate the
|
|
* decoded string value.
|
|
* json->tmp is sized to handle JSON containing only a string value.
|
|
*/
|
|
strbuf_reset(json->tmp);
|
|
|
|
while ((ch = *json->ptr) != '"') {
|
|
if (!ch) {
|
|
/* Premature end of the string */
|
|
json_set_token_error(token, json, "unexpected end of string");
|
|
return;
|
|
}
|
|
|
|
/* Handle escapes */
|
|
if (ch == '\\') {
|
|
/* Fetch escape character */
|
|
ch = *(json->ptr + 1);
|
|
|
|
/* Translate escape code and append to tmp string */
|
|
ch = escape2char((unsigned char)ch);
|
|
if (ch == 'u') {
|
|
if (json_append_unicode_escape(json) == 0)
|
|
continue;
|
|
|
|
json_set_token_error(token, json,
|
|
"invalid unicode escape code");
|
|
return;
|
|
}
|
|
if (!ch) {
|
|
json_set_token_error(token, json, "invalid escape code");
|
|
return;
|
|
}
|
|
|
|
/* Skip '\' */
|
|
json->ptr++;
|
|
}
|
|
/* Append normal character or translated single character
|
|
* Unicode escapes are handled above */
|
|
strbuf_append_char_unsafe(json->tmp, ch);
|
|
json->ptr++;
|
|
}
|
|
json->ptr++; /* Eat final quote (") */
|
|
|
|
strbuf_ensure_null(json->tmp);
|
|
|
|
token->type = T_STRING;
|
|
token->value.string = strbuf_string(json->tmp, &token->string_len);
|
|
}
|
|
|
|
/* JSON numbers should take the following form:
|
|
* -?(0|[1-9]|[1-9][0-9]+)(.[0-9]+)?([eE][-+]?[0-9]+)?
|
|
*
|
|
* json_next_number_token() uses strtod() which allows other forms:
|
|
* - numbers starting with '+'
|
|
* - NaN, -NaN, infinity, -infinity
|
|
* - hexadecimal numbers
|
|
* - numbers with leading zeros
|
|
*
|
|
* json_is_invalid_number() detects "numbers" which may pass strtod()'s
|
|
* error checking, but should not be allowed with strict JSON.
|
|
*
|
|
* json_is_invalid_number() may pass numbers which cause strtod()
|
|
* to generate an error.
|
|
*/
|
|
static int json_is_invalid_number(json_parse_t *json)
|
|
{
|
|
const char *p = json->ptr;
|
|
|
|
/* Reject numbers starting with + */
|
|
if (*p == '+')
|
|
return 1;
|
|
|
|
/* Skip minus sign if it exists */
|
|
if (*p == '-')
|
|
p++;
|
|
|
|
/* Reject numbers starting with 0x, or leading zeros */
|
|
if (*p == '0') {
|
|
int ch2 = *(p + 1);
|
|
|
|
if ((ch2 | 0x20) == 'x' || /* Hex */
|
|
('0' <= ch2 && ch2 <= '9')) /* Leading zero */
|
|
return 1;
|
|
|
|
return 0;
|
|
} else if (*p <= '9') {
|
|
return 0; /* Ordinary number */
|
|
}
|
|
|
|
char tmp[4]; // conv to lower. because c_strncasecmp == c_strcmp
|
|
int i;
|
|
for (i = 0; i < 3; ++i)
|
|
{
|
|
if(p[i]!=0)
|
|
tmp[i] = tolower(p[i]);
|
|
else
|
|
tmp[i] = 0;
|
|
}
|
|
tmp[3] = 0;
|
|
|
|
/* Reject inf/nan */
|
|
if (!c_strncasecmp(tmp, "inf", 3))
|
|
return 1;
|
|
if (!c_strncasecmp(tmp, "nan", 3))
|
|
return 1;
|
|
|
|
/* Pass all other numbers which may still be invalid, but
|
|
* strtod() will catch them. */
|
|
return 0;
|
|
}
|
|
|
|
static void json_next_number_token(json_parse_t *json, json_token_t *token)
|
|
{
|
|
char *endptr;
|
|
|
|
token->type = T_NUMBER;
|
|
token->value.number = fpconv_strtod(json->ptr, &endptr);
|
|
if (json->ptr == endptr)
|
|
json_set_token_error(token, json, "invalid number");
|
|
else
|
|
json->ptr = endptr; /* Skip the processed number */
|
|
|
|
return;
|
|
}
|
|
|
|
/* Fills in the token struct.
|
|
* T_STRING will return a pointer to the json_parse_t temporary string
|
|
* T_ERROR will leave the json->ptr pointer at the error.
|
|
*/
|
|
static void json_next_token(json_parse_t *json, json_token_t *token)
|
|
{
|
|
// const json_token_type_t *ch2token = json->cfg->ch2token;
|
|
int ch;
|
|
|
|
/* Eat whitespace. */
|
|
while (1) {
|
|
ch = (unsigned char)*(json->ptr);
|
|
token->type = ch2token(ch);
|
|
if (token->type != T_WHITESPACE)
|
|
break;
|
|
json->ptr++;
|
|
}
|
|
|
|
/* Store location of new token. Required when throwing errors
|
|
* for unexpected tokens (syntax errors). */
|
|
token->index = json->ptr - json->data;
|
|
|
|
/* Don't advance the pointer for an error or the end */
|
|
if (token->type == T_ERROR) {
|
|
json_set_token_error(token, json, "invalid token");
|
|
return;
|
|
}
|
|
|
|
if (token->type == T_END) {
|
|
return;
|
|
}
|
|
|
|
/* Found a known single character token, advance index and return */
|
|
if (token->type != T_UNKNOWN) {
|
|
json->ptr++;
|
|
return;
|
|
}
|
|
|
|
/* Process characters which triggered T_UNKNOWN
|
|
*
|
|
* Must use strncmp() to match the front of the JSON string.
|
|
* JSON identifier must be lowercase.
|
|
* When strict_numbers if disabled, either case is allowed for
|
|
* Infinity/NaN (since we are no longer following the spec..) */
|
|
if (ch == '"') {
|
|
json_next_string_token(json, token);
|
|
return;
|
|
} else if (ch == '-' || ('0' <= ch && ch <= '9')) {
|
|
if (!json->cfg->decode_invalid_numbers && json_is_invalid_number(json)) {
|
|
json_set_token_error(token, json, "invalid number");
|
|
return;
|
|
}
|
|
json_next_number_token(json, token);
|
|
return;
|
|
} else if (!c_strncmp(json->ptr, "true", 4)) {
|
|
token->type = T_BOOLEAN;
|
|
token->value.boolean = 1;
|
|
json->ptr += 4;
|
|
return;
|
|
} else if (!c_strncmp(json->ptr, "false", 5)) {
|
|
token->type = T_BOOLEAN;
|
|
token->value.boolean = 0;
|
|
json->ptr += 5;
|
|
return;
|
|
} else if (!c_strncmp(json->ptr, "null", 4)) {
|
|
token->type = T_NULL;
|
|
json->ptr += 4;
|
|
return;
|
|
} else if (json->cfg->decode_invalid_numbers &&
|
|
json_is_invalid_number(json)) {
|
|
/* 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"
|
|
*/
|
|
json_next_number_token(json, token);
|
|
return;
|
|
}
|
|
|
|
/* Token starts with t/f/n but isn't recognised above. */
|
|
json_set_token_error(token, json, "invalid token");
|
|
}
|
|
|
|
/* This function does not return.
|
|
* DO NOT CALL WITH DYNAMIC MEMORY ALLOCATED.
|
|
* The only supported exception is the temporary parser string
|
|
* json->tmp struct.
|
|
* json and token should exist on the stack somewhere.
|
|
* luaL_error() will long_jmp and release the stack */
|
|
static void json_throw_parse_error(lua_State *l, json_parse_t *json,
|
|
const char *exp, json_token_t *token)
|
|
{
|
|
const char *found;
|
|
char temp[16]; // for now, 16-bytes is enough.
|
|
|
|
strbuf_free(json->tmp);
|
|
|
|
if (token->type == T_ERROR)
|
|
found = token->value.string;
|
|
else
|
|
{
|
|
found = json_token_type_name[token->type];
|
|
int i;
|
|
for (i=0; i < 16; ++i)
|
|
{
|
|
temp[i] = byte_of_aligned_array(found, i);
|
|
if(temp[i]==0) break;
|
|
}
|
|
found = temp;
|
|
}
|
|
|
|
/* Note: token->index is 0 based, display starting from 1 */
|
|
luaL_error(l, "Expected %s but found %s at character %d",
|
|
exp, found, token->index + 1);
|
|
}
|
|
|
|
static inline void json_decode_ascend(json_parse_t *json)
|
|
{
|
|
json->current_depth--;
|
|
}
|
|
|
|
static void json_decode_descend(lua_State *l, json_parse_t *json, int slots)
|
|
{
|
|
json->current_depth++;
|
|
|
|
if (json->current_depth <= json->cfg->decode_max_depth &&
|
|
lua_checkstack(l, slots)) {
|
|
return;
|
|
}
|
|
|
|
strbuf_free(json->tmp);
|
|
luaL_error(l, "Found too many nested data structures (%d) at character %d",
|
|
json->current_depth, json->ptr - json->data);
|
|
}
|
|
|
|
static void json_parse_object_context(lua_State *l, json_parse_t *json)
|
|
{
|
|
json_token_t token;
|
|
|
|
/* 3 slots required:
|
|
* .., table, key, value */
|
|
json_decode_descend(l, json, 3);
|
|
|
|
lua_newtable(l);
|
|
|
|
json_next_token(json, &token);
|
|
|
|
/* Handle empty objects */
|
|
if (token.type == T_OBJ_END) {
|
|
json_decode_ascend(json);
|
|
return;
|
|
}
|
|
|
|
while (1) {
|
|
if (token.type != T_STRING)
|
|
json_throw_parse_error(l, json, "object key string", &token);
|
|
|
|
/* Push key */
|
|
lua_pushlstring(l, token.value.string, token.string_len);
|
|
|
|
json_next_token(json, &token);
|
|
if (token.type != T_COLON)
|
|
json_throw_parse_error(l, json, "colon", &token);
|
|
|
|
/* Fetch value */
|
|
json_next_token(json, &token);
|
|
json_process_value(l, json, &token);
|
|
|
|
/* Set key = value */
|
|
lua_rawset(l, -3);
|
|
|
|
json_next_token(json, &token);
|
|
|
|
if (token.type == T_OBJ_END) {
|
|
json_decode_ascend(json);
|
|
return;
|
|
}
|
|
|
|
if (token.type != T_COMMA)
|
|
json_throw_parse_error(l, json, "comma or object end", &token);
|
|
|
|
json_next_token(json, &token);
|
|
}
|
|
}
|
|
|
|
/* Handle the array context */
|
|
static void json_parse_array_context(lua_State *l, json_parse_t *json)
|
|
{
|
|
json_token_t token;
|
|
int i;
|
|
|
|
/* 2 slots required:
|
|
* .., table, value */
|
|
json_decode_descend(l, json, 2);
|
|
|
|
lua_newtable(l);
|
|
|
|
json_next_token(json, &token);
|
|
|
|
/* Handle empty arrays */
|
|
if (token.type == T_ARR_END) {
|
|
json_decode_ascend(json);
|
|
return;
|
|
}
|
|
|
|
for (i = 1; ; i++) {
|
|
json_process_value(l, json, &token);
|
|
lua_rawseti(l, -2, i); /* arr[i] = value */
|
|
|
|
json_next_token(json, &token);
|
|
|
|
if (token.type == T_ARR_END) {
|
|
json_decode_ascend(json);
|
|
return;
|
|
}
|
|
|
|
if (token.type != T_COMMA)
|
|
json_throw_parse_error(l, json, "comma or array end", &token);
|
|
|
|
json_next_token(json, &token);
|
|
}
|
|
}
|
|
|
|
/* Handle the "value" context */
|
|
static void json_process_value(lua_State *l, json_parse_t *json,
|
|
json_token_t *token)
|
|
{
|
|
switch (token->type) {
|
|
case T_STRING:
|
|
lua_pushlstring(l, token->value.string, token->string_len);
|
|
break;;
|
|
case T_NUMBER:
|
|
lua_pushnumber(l, token->value.number);
|
|
break;;
|
|
case T_BOOLEAN:
|
|
lua_pushboolean(l, token->value.boolean);
|
|
break;;
|
|
case T_OBJ_BEGIN:
|
|
json_parse_object_context(l, json);
|
|
break;;
|
|
case T_ARR_BEGIN:
|
|
json_parse_array_context(l, json);
|
|
break;;
|
|
case T_NULL:
|
|
/* In Lua, setting "t[k] = nil" will delete k from the table.
|
|
* Hence a NULL pointer lightuserdata object is used instead */
|
|
lua_pushlightuserdata(l, NULL);
|
|
break;;
|
|
default:
|
|
json_throw_parse_error(l, json, "value", token);
|
|
}
|
|
}
|
|
|
|
static int json_decode(lua_State *l)
|
|
{
|
|
json_parse_t json;
|
|
json_token_t token;
|
|
size_t json_len;
|
|
|
|
luaL_argcheck(l, lua_gettop(l) == 1, 1, "expected 1 argument");
|
|
|
|
json.cfg = json_fetch_config(l);
|
|
json.data = luaL_checklstring(l, 1, &json_len);
|
|
json.current_depth = 0;
|
|
json.ptr = json.data;
|
|
|
|
/* Detect Unicode other than UTF-8 (see RFC 4627, Sec 3)
|
|
*
|
|
* CJSON can support any simple data type, hence only the first
|
|
* character is guaranteed to be ASCII (at worst: '"'). This is
|
|
* still enough to detect whether the wrong encoding is in use. */
|
|
if (json_len >= 2 && (!json.data[0] || !json.data[1]))
|
|
luaL_error(l, "JSON parser does not support UTF-16 or UTF-32");
|
|
|
|
/* Ensure the temporary buffer can hold the entire string.
|
|
* This means we no longer need to do length checks since the decoded
|
|
* string must be smaller than the entire json string */
|
|
json.tmp = strbuf_new(json_len);
|
|
if(json.tmp == NULL){
|
|
return luaL_error(l, "not enough memory");
|
|
}
|
|
|
|
json_next_token(&json, &token);
|
|
json_process_value(l, &json, &token);
|
|
|
|
/* Ensure there is no more input left */
|
|
json_next_token(&json, &token);
|
|
|
|
if (token.type != T_END)
|
|
json_throw_parse_error(l, &json, "the end", &token);
|
|
|
|
strbuf_free(json.tmp);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* ===== INITIALISATION ===== */
|
|
#if 0
|
|
#if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 502
|
|
/* Compatibility for Lua 5.1.
|
|
*
|
|
* luaL_setfuncs() is used to create a module table where the functions have
|
|
* json_config_t as their first upvalue. Code borrowed from Lua 5.2 source. */
|
|
static void luaL_setfuncs (lua_State *l, const luaL_Reg *reg, int nup)
|
|
{
|
|
int i;
|
|
|
|
luaL_checkstack(l, nup, "too many upvalues");
|
|
for (; reg->name != NULL; reg++) { /* fill the table with given functions */
|
|
for (i = 0; i < nup; i++) /* copy upvalues to the top */
|
|
lua_pushvalue(l, -nup);
|
|
lua_pushcclosure(l, reg->func, nup); /* closure with those upvalues */
|
|
lua_setfield(l, -(nup + 2), reg->name);
|
|
}
|
|
lua_pop(l, nup); /* remove upvalues */
|
|
}
|
|
#endif
|
|
|
|
/* Call target function in protected mode with all supplied args.
|
|
* Assumes target function only returns a single non-nil value.
|
|
* Convert and return thrown errors as: nil, "error message" */
|
|
static int json_protect_conversion(lua_State *l)
|
|
{
|
|
int err;
|
|
|
|
/* Deliberately throw an error for invalid arguments */
|
|
luaL_argcheck(l, lua_gettop(l) == 1, 1, "expected 1 argument");
|
|
|
|
/* pcall() the function stored as upvalue(1) */
|
|
lua_pushvalue(l, lua_upvalueindex(1));
|
|
lua_insert(l, 1);
|
|
err = lua_pcall(l, 1, 1, 0);
|
|
if (!err)
|
|
return 1;
|
|
|
|
if (err == LUA_ERRRUN) {
|
|
lua_pushnil(l);
|
|
lua_insert(l, -2);
|
|
return 2;
|
|
}
|
|
|
|
/* Since we are not using a custom error handler, the only remaining
|
|
* errors are memory related */
|
|
return luaL_error(l, "Memory allocation error in CJSON protected call");
|
|
}
|
|
|
|
/* Return cjson module table */
|
|
static int lua_cjson_new(lua_State *l)
|
|
{
|
|
/* Initialise number conversions */
|
|
fpconv_init();
|
|
|
|
/* cjson module table */
|
|
lua_newtable(l);
|
|
|
|
/* Register functions with config data as upvalue */
|
|
json_create_config(l);
|
|
luaL_setfuncs(l, reg, 1);
|
|
|
|
/* Set cjson.null */
|
|
lua_pushlightuserdata(l, NULL);
|
|
lua_setfield(l, -2, "null");
|
|
|
|
/* Set module name / version fields */
|
|
lua_pushliteral(l, CJSON_MODNAME);
|
|
lua_setfield(l, -2, "_NAME");
|
|
lua_pushliteral(l, CJSON_VERSION);
|
|
lua_setfield(l, -2, "_VERSION");
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Return cjson.safe module table */
|
|
static int lua_cjson_safe_new(lua_State *l)
|
|
{
|
|
const char *func[] = { "decode", "encode", NULL };
|
|
int i;
|
|
|
|
lua_cjson_new(l);
|
|
|
|
/* Fix new() method */
|
|
lua_pushcfunction(l, lua_cjson_safe_new);
|
|
lua_setfield(l, -2, "new");
|
|
|
|
for (i = 0; func[i]; i++) {
|
|
lua_getfield(l, -1, func[i]);
|
|
lua_pushcclosure(l, json_protect_conversion, 1);
|
|
lua_setfield(l, -2, func[i]);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int luaopen_cjson(lua_State *l)
|
|
{
|
|
lua_cjson_new(l);
|
|
|
|
#ifdef ENABLE_CJSON_GLOBAL
|
|
/* Register a global "cjson" table. */
|
|
lua_pushvalue(l, -1);
|
|
lua_setglobal(l, CJSON_MODNAME);
|
|
#endif
|
|
|
|
/* Return cjson table */
|
|
return 1;
|
|
}
|
|
|
|
int luaopen_cjson_safe(lua_State *l)
|
|
{
|
|
lua_cjson_safe_new(l);
|
|
|
|
/* Return cjson.safe table */
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
// Module function map
|
|
static const LUA_REG_TYPE cjson_map[] = {
|
|
{ LSTRKEY( "encode" ), LFUNCVAL( json_encode ) },
|
|
{ LSTRKEY( "decode" ), LFUNCVAL( json_decode ) },
|
|
//{ LSTRKEY( "encode_sparse_array" ), LFUNCVAL( json_cfg_encode_sparse_array ) },
|
|
//{ LSTRKEY( "encode_max_depth" ), LFUNCVAL( json_cfg_encode_max_depth ) },
|
|
//{ LSTRKEY( "decode_max_depth" ), LFUNCVAL( json_cfg_decode_max_depth ) },
|
|
//{ LSTRKEY( "encode_number_precision" ), LFUNCVAL( json_cfg_encode_number_precision ) },
|
|
//{ LSTRKEY( "encode_keep_buffer" ), LFUNCVAL( json_cfg_encode_keep_buffer ) },
|
|
//{ LSTRKEY( "encode_invalid_numbers" ), LFUNCVAL( json_cfg_encode_invalid_numbers ) },
|
|
//{ LSTRKEY( "decode_invalid_numbers" ), LFUNCVAL( json_cfg_decode_invalid_numbers ) },
|
|
//{ LSTRKEY( "new" ), LFUNCVAL( lua_cjson_new ) },
|
|
{ LNILKEY, LNILVAL }
|
|
};
|
|
|
|
int luaopen_cjson( lua_State *L )
|
|
{
|
|
cjson_mem_setlua (L);
|
|
|
|
/* Initialise number conversions */
|
|
// fpconv_init(); // not needed for a specific cpu.
|
|
if(-1==cfg_init(&_cfg)){
|
|
return luaL_error(L, "BUG: Unable to init config for cjson");;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
NODEMCU_MODULE(CJSON, "cjson", cjson_map, luaopen_cjson);
|
|
/* vi:ai et sw=4 ts=4:
|
|
*/
|