Commit e2983dd0 authored by Matt Stancliff's avatar Matt Stancliff Committed by antirez
Browse files

Lua: Upgrade cjson to 2.1.0 (2012-03-01)

The new cjson has some improvements over our current version including
increased platform compatability, a new resource limit to restrict
decode depth, and better invalid number handling.

One minor change was required to deps/Makefile because this version
of cjson doesn't export itself globally, so we added a quick little
define of -DENABLE_CJSON_GLOBAL.

cjson now has an optional higher performing float parsing interface,
but we are not including it (g_fmt.c, dtoa.c) because it requires
endianness declaration during compile time.

This commit is exactly lua_cjson.c from 2.1.0 with one minor
change of altering the two Lua includes for local search
instead of system-wide importing.
parent 2828bbd5
...@@ -58,7 +58,7 @@ ifeq ($(uname_S),SunOS) ...@@ -58,7 +58,7 @@ ifeq ($(uname_S),SunOS)
LUA_CFLAGS= -D__C99FEATURES__=1 LUA_CFLAGS= -D__C99FEATURES__=1
endif endif
LUA_CFLAGS+= -O2 -Wall -DLUA_ANSI $(CFLAGS) LUA_CFLAGS+= -O2 -Wall -DLUA_ANSI -DENABLE_CJSON_GLOBAL $(CFLAGS)
LUA_LDFLAGS+= $(LDFLAGS) LUA_LDFLAGS+= $(LDFLAGS)
# lua's Makefile defines AR="ar rcu", which is unusual, and makes it more # lua's Makefile defines AR="ar rcu", which is unusual, and makes it more
# challenging to cross-compile lua (and redis). These defines make it easier # challenging to cross-compile lua (and redis). These defines make it easier
......
...@@ -25,7 +25,7 @@ PLATS= aix ansi bsd freebsd generic linux macosx mingw posix solaris ...@@ -25,7 +25,7 @@ PLATS= aix ansi bsd freebsd generic linux macosx mingw posix solaris
LUA_A= liblua.a LUA_A= liblua.a
CORE_O= lapi.o lcode.o ldebug.o ldo.o ldump.o lfunc.o lgc.o llex.o lmem.o \ CORE_O= lapi.o lcode.o ldebug.o ldo.o ldump.o lfunc.o lgc.o llex.o lmem.o \
lobject.o lopcodes.o lparser.o lstate.o lstring.o ltable.o ltm.o \ lobject.o lopcodes.o lparser.o lstate.o lstring.o ltable.o ltm.o \
lundump.o lvm.o lzio.o strbuf.o lundump.o lvm.o lzio.o strbuf.o fpconv.o
LIB_O= lauxlib.o lbaselib.o ldblib.o liolib.o lmathlib.o loslib.o ltablib.o \ LIB_O= lauxlib.o lbaselib.o ldblib.o liolib.o lmathlib.o loslib.o ltablib.o \
lstrlib.o loadlib.o linit.o lua_cjson.o lua_struct.o lua_cmsgpack.o \ lstrlib.o loadlib.o linit.o lua_cjson.o lua_struct.o lua_cmsgpack.o \
lua_bit.o lua_bit.o
......
/* fpconv - Floating point conversion routines
*
* Copyright (c) 2011-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.
*/
/* JSON uses a '.' decimal separator. strtod() / sprintf() under C libraries
* with locale support will break when the decimal separator is a comma.
*
* fpconv_* will around these issues with a translation buffer if required.
*/
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include "fpconv.h"
/* Lua CJSON assumes the locale is the same for all threads within a
* process and doesn't change after initialisation.
*
* This avoids the need for per thread storage or expensive checks
* for call. */
static char locale_decimal_point = '.';
/* In theory multibyte decimal_points are possible, but
* Lua CJSON only supports UTF-8 and known locales only have
* single byte decimal points ([.,]).
*
* localconv() may not be thread safe (=>crash), and nl_langinfo() is
* not supported on some platforms. Use sprintf() instead - if the
* locale does change, at least Lua CJSON won't crash. */
static void fpconv_update_locale()
{
char buf[8];
snprintf(buf, sizeof(buf), "%g", 0.5);
/* Failing this test might imply the platform has a buggy dtoa
* implementation or wide characters */
if (buf[0] != '0' || buf[2] != '5' || buf[3] != 0) {
fprintf(stderr, "Error: wide characters found or printf() bug.");
abort();
}
locale_decimal_point = buf[1];
}
/* Check for a valid number character: [-+0-9a-yA-Y.]
* Eg: -0.6e+5, infinity, 0xF0.F0pF0
*
* Used to find the probable end of a number. It doesn't matter if
* invalid characters are counted - strtod() will find the valid
* number if it exists. The risk is that slightly more memory might
* be allocated before a parse error occurs. */
static inline int valid_number_character(char ch)
{
char lower_ch;
if ('0' <= ch && ch <= '9')
return 1;
if (ch == '-' || ch == '+' || ch == '.')
return 1;
/* Hex digits, exponent (e), base (p), "infinity",.. */
lower_ch = ch | 0x20;
if ('a' <= lower_ch && lower_ch <= 'y')
return 1;
return 0;
}
/* Calculate the size of the buffer required for a strtod locale
* conversion. */
static int strtod_buffer_size(const char *s)
{
const char *p = s;
while (valid_number_character(*p))
p++;
return p - s;
}
/* Similar to strtod(), but must be passed the current locale's decimal point
* character. Guaranteed to be called at the start of any valid number in a string */
double fpconv_strtod(const char *nptr, char **endptr)
{
char localbuf[FPCONV_G_FMT_BUFSIZE];
char *buf, *endbuf, *dp;
int buflen;
double value;
/* System strtod() is fine when decimal point is '.' */
if (locale_decimal_point == '.')
return strtod(nptr, endptr);
buflen = strtod_buffer_size(nptr);
if (!buflen) {
/* No valid characters found, standard strtod() return */
*endptr = (char *)nptr;
return 0;
}
/* Duplicate number into buffer */
if (buflen >= FPCONV_G_FMT_BUFSIZE) {
/* Handle unusually large numbers */
buf = malloc(buflen + 1);
if (!buf) {
fprintf(stderr, "Out of memory");
abort();
}
} else {
/* This is the common case.. */
buf = localbuf;
}
memcpy(buf, nptr, buflen);
buf[buflen] = 0;
/* Update decimal point character if found */
dp = strchr(buf, '.');
if (dp)
*dp = locale_decimal_point;
value = strtod(buf, &endbuf);
*endptr = (char *)&nptr[endbuf - buf];
if (buflen >= FPCONV_G_FMT_BUFSIZE)
free(buf);
return value;
}
/* "fmt" must point to a buffer of at least 6 characters */
static void set_number_format(char *fmt, int precision)
{
int d1, d2, i;
assert(1 <= precision && precision <= 14);
/* Create printf format (%.14g) from precision */
d1 = precision / 10;
d2 = precision % 10;
fmt[0] = '%';
fmt[1] = '.';
i = 2;
if (d1) {
fmt[i++] = '0' + d1;
}
fmt[i++] = '0' + d2;
fmt[i++] = 'g';
fmt[i] = 0;
}
/* Assumes there is always at least 32 characters available in the target buffer */
int fpconv_g_fmt(char *str, double num, int precision)
{
char buf[FPCONV_G_FMT_BUFSIZE];
char fmt[6];
int len;
char *b;
set_number_format(fmt, precision);
/* Pass through when decimal point character is dot. */
if (locale_decimal_point == '.')
return snprintf(str, FPCONV_G_FMT_BUFSIZE, fmt, num);
/* snprintf() to a buffer then translate for other decimal point characters */
len = snprintf(buf, FPCONV_G_FMT_BUFSIZE, fmt, num);
/* Copy into target location. Translate decimal point if required */
b = buf;
do {
*str++ = (*b == locale_decimal_point ? '.' : *b);
} while(*b++);
return len;
}
void fpconv_init()
{
fpconv_update_locale();
}
/* vi:ai et sw=4 ts=4:
*/
/* Lua CJSON floating point conversion routines */
/* Buffer required to store the largest string representation of a double.
*
* Longest double printed with %.14g is 21 characters long:
* -1.7976931348623e+308 */
# define FPCONV_G_FMT_BUFSIZE 32
#ifdef USE_INTERNAL_FPCONV
static inline void fpconv_init()
{
/* Do nothing - not required */
}
#else
extern inline void fpconv_init();
#endif
extern int fpconv_g_fmt(char*, double, int);
extern double fpconv_strtod(const char*, char**);
/* vi:ai et sw=4 ts=4:
*/
#define VERSION "1.0.3" /* Lua CJSON - JSON support for Lua
/* CJSON - JSON support for Lua
* *
* Copyright (c) 2010-2011 Mark Pulford <mark@kyne.com.au> * Copyright (c) 2010-2012 Mark Pulford <mark@kyne.com.au>
* *
* Permission is hereby granted, free of charge, to any person obtaining * Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the * a copy of this software and associated documentation files (the
...@@ -41,22 +39,40 @@ ...@@ -41,22 +39,40 @@
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>
#include <math.h> #include <math.h>
#include "lua.h" #include <limits.h>
#include "lauxlib.h" #include <lua.h>
#include <lauxlib.h>
#include "strbuf.h" #include "strbuf.h"
#include "fpconv.h"
#ifndef CJSON_MODNAME
#define CJSON_MODNAME "cjson"
#endif
#ifdef MISSING_ISINF #ifndef CJSON_VERSION
#define CJSON_VERSION "2.1.0"
#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))) #define isinf(x) (!isnan(x) && isnan((x) - (x)))
#endif #endif
#define DEFAULT_SPARSE_CONVERT 0 #define DEFAULT_SPARSE_CONVERT 0
#define DEFAULT_SPARSE_RATIO 2 #define DEFAULT_SPARSE_RATIO 2
#define DEFAULT_SPARSE_SAFE 10 #define DEFAULT_SPARSE_SAFE 10
#define DEFAULT_MAX_DEPTH 20 #define DEFAULT_ENCODE_MAX_DEPTH 1000
#define DEFAULT_ENCODE_REFUSE_BADNUM 1 #define DEFAULT_DECODE_MAX_DEPTH 1000
#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_KEEP_BUFFER 1
#define DEFAULT_ENCODE_NUMBER_PRECISION 14
#ifdef DISABLE_INVALID_NUMBERS
#undef DEFAULT_DECODE_INVALID_NUMBERS
#define DEFAULT_DECODE_INVALID_NUMBERS 0
#endif
typedef enum { typedef enum {
T_OBJ_BEGIN, T_OBJ_BEGIN,
...@@ -96,29 +112,29 @@ static const char *json_token_type_name[] = { ...@@ -96,29 +112,29 @@ static const char *json_token_type_name[] = {
typedef struct { typedef struct {
json_token_type_t ch2token[256]; json_token_type_t ch2token[256];
char escape2char[256]; /* Decoding */ char escape2char[256]; /* Decoding */
#if 0
char escapes[35][8]; /* Pre-generated escape string buffer */ /* encode_buf is only allocated and used when
char *char2escape[256]; /* Encoding */ * encode_keep_buffer is set */
#endif
strbuf_t encode_buf; strbuf_t encode_buf;
char number_fmt[8]; /* "%.XXg\0" */
int current_depth;
int encode_sparse_convert; int encode_sparse_convert;
int encode_sparse_ratio; int encode_sparse_ratio;
int encode_sparse_safe; int encode_sparse_safe;
int encode_max_depth; int encode_max_depth;
int encode_refuse_badnum; int encode_invalid_numbers; /* 2 => Encode as "null" */
int decode_refuse_badnum;
int encode_keep_buffer;
int encode_number_precision; int encode_number_precision;
int encode_keep_buffer;
int decode_invalid_numbers;
int decode_max_depth;
} json_config_t; } json_config_t;
typedef struct { typedef struct {
const char *data; const char *data;
int index; const char *ptr;
strbuf_t *tmp; /* Temporary storage for strings */ strbuf_t *tmp; /* Temporary storage for strings */
json_config_t *cfg; json_config_t *cfg;
int current_depth;
} json_parse_t; } json_parse_t;
typedef struct { typedef struct {
...@@ -171,29 +187,76 @@ static const char *char2escape[256] = { ...@@ -171,29 +187,76 @@ static const char *char2escape[256] = {
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
}; };
static int json_config_key;
/* ===== CONFIGURATION ===== */ /* ===== CONFIGURATION ===== */
static json_config_t *json_fetch_config(lua_State *l) static json_config_t *json_fetch_config(lua_State *l)
{ {
json_config_t *cfg; json_config_t *cfg;
lua_pushlightuserdata(l, &json_config_key); cfg = lua_touserdata(l, lua_upvalueindex(1));
lua_gettable(l, LUA_REGISTRYINDEX);
cfg = lua_touserdata(l, -1);
if (!cfg) if (!cfg)
luaL_error(l, "BUG: Unable to fetch CJSON configuration"); luaL_error(l, "BUG: Unable to fetch CJSON configuration");
lua_pop(l, 1);
return cfg; return cfg;
} }
static void json_verify_arg_count(lua_State *l, int args) /* 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, luaL_argcheck(l, lua_gettop(l) <= args, args + 1,
"found too many arguments"); "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);
snprintf(errmsg, sizeof(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: /* Configures handling of extremely sparse arrays:
...@@ -202,29 +265,11 @@ static void json_verify_arg_count(lua_State *l, int args) ...@@ -202,29 +265,11 @@ static void json_verify_arg_count(lua_State *l, int args)
* safe: Always use an array when the max index <= safe */ * safe: Always use an array when the max index <= safe */
static int json_cfg_encode_sparse_array(lua_State *l) static int json_cfg_encode_sparse_array(lua_State *l)
{ {
json_config_t *cfg; json_config_t *cfg = json_arg_init(l, 3);
int val;
json_verify_arg_count(l, 3);
cfg = json_fetch_config(l);
switch (lua_gettop(l)) {
case 3:
val = luaL_checkinteger(l, 3);
luaL_argcheck(l, val >= 0, 3, "expected integer >= 0");
cfg->encode_sparse_safe = val;
case 2:
val = luaL_checkinteger(l, 2);
luaL_argcheck(l, val >= 0, 2, "expected integer >= 0");
cfg->encode_sparse_ratio = val;
case 1:
luaL_argcheck(l, lua_isboolean(l, 1), 1, "expected boolean");
cfg->encode_sparse_convert = lua_toboolean(l, 1);
}
lua_pushboolean(l, cfg->encode_sparse_convert); json_enum_option(l, 1, &cfg->encode_sparse_convert, NULL, 1);
lua_pushinteger(l, cfg->encode_sparse_ratio); json_integer_option(l, 2, &cfg->encode_sparse_ratio, 0, INT_MAX);
lua_pushinteger(l, cfg->encode_sparse_safe); json_integer_option(l, 3, &cfg->encode_sparse_safe, 0, INT_MAX);
return 3; return 3;
} }
...@@ -233,108 +278,80 @@ static int json_cfg_encode_sparse_array(lua_State *l) ...@@ -233,108 +278,80 @@ static int json_cfg_encode_sparse_array(lua_State *l)
* encoding */ * encoding */
static int json_cfg_encode_max_depth(lua_State *l) static int json_cfg_encode_max_depth(lua_State *l)
{ {
json_config_t *cfg; json_config_t *cfg = json_arg_init(l, 1);
int depth;
json_verify_arg_count(l, 1);
cfg = json_fetch_config(l);
if (lua_gettop(l)) { return json_integer_option(l, 1, &cfg->encode_max_depth, 1, INT_MAX);
depth = luaL_checkinteger(l, 1);
luaL_argcheck(l, depth > 0, 1, "expected positive integer");
cfg->encode_max_depth = depth;
}
lua_pushinteger(l, cfg->encode_max_depth);
return 1;
} }
static void json_set_number_precision(json_config_t *cfg, int prec) /* Configures the maximum number of nested arrays/objects allowed when
* encoding */
static int json_cfg_decode_max_depth(lua_State *l)
{ {
cfg->encode_number_precision = prec; json_config_t *cfg = json_arg_init(l, 1);
sprintf(cfg->number_fmt, "%%.%dg", prec);
return json_integer_option(l, 1, &cfg->decode_max_depth, 1, INT_MAX);
} }
/* Configures number precision when converting doubles to text */ /* Configures number precision when converting doubles to text */
static int json_cfg_encode_number_precision(lua_State *l) static int json_cfg_encode_number_precision(lua_State *l)
{ {
json_config_t *cfg; json_config_t *cfg = json_arg_init(l, 1);
int precision;
json_verify_arg_count(l, 1);
cfg = json_fetch_config(l);
if (lua_gettop(l)) {
precision = luaL_checkinteger(l, 1);
luaL_argcheck(l, 1 <= precision && precision <= 14, 1,
"expected integer between 1 and 14");
json_set_number_precision(cfg, precision);
}
lua_pushinteger(l, cfg->encode_number_precision); return json_integer_option(l, 1, &cfg->encode_number_precision, 1, 14);
return 1;
} }
/* Configures JSON encoding buffer persistence */ /* Configures JSON encoding buffer persistence */
static int json_cfg_encode_keep_buffer(lua_State *l) static int json_cfg_encode_keep_buffer(lua_State *l)
{ {
json_config_t *cfg; json_config_t *cfg = json_arg_init(l, 1);
int old_value;
json_verify_arg_count(l, 1); old_value = cfg->encode_keep_buffer;
cfg = json_fetch_config(l);
if (lua_gettop(l)) { json_enum_option(l, 1, &cfg->encode_keep_buffer, NULL, 1);
luaL_checktype(l, 1, LUA_TBOOLEAN);
cfg->encode_keep_buffer = lua_toboolean(l, 1);
}
lua_pushboolean(l, cfg->encode_keep_buffer); /* Init / free the buffer if the setting has changed */
if (old_value ^ cfg->encode_keep_buffer) {
if (cfg->encode_keep_buffer)
strbuf_init(&cfg->encode_buf, 0);
else
strbuf_free(&cfg->encode_buf);
}
return 1; return 1;
} }
/* On argument: decode enum and set config variables #if defined(DISABLE_INVALID_NUMBERS) && !defined(USE_INTERNAL_FPCONV)
* **options must point to a NULL terminated array of 4 enums void json_verify_invalid_number_setting(lua_State *l, int *setting)
* Returns: current enum value */
static void json_enum_option(lua_State *l, const char **options,
int *opt1, int *opt2)
{ {
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
if (lua_gettop(l)) { static int json_cfg_encode_invalid_numbers(lua_State *l)
if (lua_isboolean(l, 1)) {
setting = lua_toboolean(l, 1) * 3; static const char *options[] = { "off", "on", "null", NULL };
else json_config_t *cfg = json_arg_init(l, 1);
setting = luaL_checkoption(l, 1, NULL, options);
*opt1 = setting & 1 ? 1 : 0; json_enum_option(l, 1, &cfg->encode_invalid_numbers, options, 1);
*opt2 = setting & 2 ? 1 : 0;
} else {
setting = *opt1 | (*opt2 << 1);
}
if (setting) json_verify_invalid_number_setting(l, &cfg->encode_invalid_numbers);
lua_pushstring(l, options[setting]);
else
lua_pushboolean(l, 0);
}
return 1;
}
/* When enabled, rejects: NaN, Infinity, hexidecimal numbers */ static int json_cfg_decode_invalid_numbers(lua_State *l)
static int json_cfg_refuse_invalid_numbers(lua_State *l)
{ {
static const char *options_enc_dec[] = { "none", "encode", "decode", json_config_t *cfg = json_arg_init(l, 1);
"both", NULL };
json_config_t *cfg;
json_verify_arg_count(l, 1); json_enum_option(l, 1, &cfg->decode_invalid_numbers, NULL, 1);
cfg = json_fetch_config(l);
json_enum_option(l, options_enc_dec, json_verify_invalid_number_setting(l, &cfg->encode_invalid_numbers);
&cfg->encode_refuse_badnum,
&cfg->decode_refuse_badnum);
return 1; return 1;
} }
...@@ -364,16 +381,19 @@ static void json_create_config(lua_State *l) ...@@ -364,16 +381,19 @@ static void json_create_config(lua_State *l)
lua_setfield(l, -2, "__gc"); lua_setfield(l, -2, "__gc");
lua_setmetatable(l, -2); lua_setmetatable(l, -2);
strbuf_init(&cfg->encode_buf, 0);
cfg->encode_sparse_convert = DEFAULT_SPARSE_CONVERT; cfg->encode_sparse_convert = DEFAULT_SPARSE_CONVERT;
cfg->encode_sparse_ratio = DEFAULT_SPARSE_RATIO; cfg->encode_sparse_ratio = DEFAULT_SPARSE_RATIO;
cfg->encode_sparse_safe = DEFAULT_SPARSE_SAFE; cfg->encode_sparse_safe = DEFAULT_SPARSE_SAFE;
cfg->encode_max_depth = DEFAULT_MAX_DEPTH; cfg->encode_max_depth = DEFAULT_ENCODE_MAX_DEPTH;
cfg->encode_refuse_badnum = DEFAULT_ENCODE_REFUSE_BADNUM; cfg->decode_max_depth = DEFAULT_DECODE_MAX_DEPTH;
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_keep_buffer = DEFAULT_ENCODE_KEEP_BUFFER;
json_set_number_precision(cfg, 14); cfg->encode_number_precision = DEFAULT_ENCODE_NUMBER_PRECISION;
#if DEFAULT_ENCODE_KEEP_BUFFER > 0
strbuf_init(&cfg->encode_buf, 0);
#endif
/* Decoding init */ /* Decoding init */
...@@ -419,41 +439,15 @@ static void json_create_config(lua_State *l) ...@@ -419,41 +439,15 @@ static void json_create_config(lua_State *l)
cfg->escape2char['f'] = '\f'; cfg->escape2char['f'] = '\f';
cfg->escape2char['r'] = '\r'; cfg->escape2char['r'] = '\r';
cfg->escape2char['u'] = 'u'; /* Unicode parsing required */ cfg->escape2char['u'] = 'u'; /* Unicode parsing required */
#if 0
/* Initialise separate storage for pre-generated escape codes.
* Escapes 0-31 map directly, 34, 92, 127 follow afterwards to
* save memory. */
for (i = 0 ; i < 32; i++)
sprintf(cfg->escapes[i], "\\u%04x", i);
strcpy(cfg->escapes[8], "\b"); /* Override simpler escapes */
strcpy(cfg->escapes[9], "\t");
strcpy(cfg->escapes[10], "\n");
strcpy(cfg->escapes[12], "\f");
strcpy(cfg->escapes[13], "\r");
strcpy(cfg->escapes[32], "\\\""); /* chr(34) */
strcpy(cfg->escapes[33], "\\\\"); /* chr(92) */
sprintf(cfg->escapes[34], "\\u%04x", 127); /* char(127) */
/* Initialise encoding escape lookup table */
for (i = 0; i < 32; i++)
cfg->char2escape[i] = cfg->escapes[i];
for (i = 32; i < 256; i++)
cfg->char2escape[i] = NULL;
cfg->char2escape[34] = cfg->escapes[32];
cfg->char2escape[92] = cfg->escapes[33];
cfg->char2escape[127] = cfg->escapes[34];
#endif
} }
/* ===== ENCODING ===== */ /* ===== ENCODING ===== */
static void json_encode_exception(lua_State *l, json_config_t *cfg, int lindex, static void json_encode_exception(lua_State *l, json_config_t *cfg, strbuf_t *json, int lindex,
const char *reason) const char *reason)
{ {
if (!cfg->encode_keep_buffer) if (!cfg->encode_keep_buffer)
strbuf_free(&cfg->encode_buf); strbuf_free(json);
luaL_error(l, "Cannot serialise %s: %s", luaL_error(l, "Cannot serialise %s: %s",
lua_typename(l, lua_type(l, lindex)), reason); lua_typename(l, lua_type(l, lindex)), reason);
} }
...@@ -494,7 +488,7 @@ static void json_append_string(lua_State *l, strbuf_t *json, int lindex) ...@@ -494,7 +488,7 @@ static void json_append_string(lua_State *l, strbuf_t *json, int lindex)
* -1 object (not a pure array) * -1 object (not a pure array)
* >=0 elements in array * >=0 elements in array
*/ */
static int lua_array_length(lua_State *l, json_config_t *cfg) static int lua_array_length(lua_State *l, json_config_t *cfg, strbuf_t *json)
{ {
double k; double k;
int max; int max;
...@@ -529,7 +523,7 @@ static int lua_array_length(lua_State *l, json_config_t *cfg) ...@@ -529,7 +523,7 @@ static int lua_array_length(lua_State *l, json_config_t *cfg)
max > items * cfg->encode_sparse_ratio && max > items * cfg->encode_sparse_ratio &&
max > cfg->encode_sparse_safe) { max > cfg->encode_sparse_safe) {
if (!cfg->encode_sparse_convert) if (!cfg->encode_sparse_convert)
json_encode_exception(l, cfg, -1, "excessively sparse array"); json_encode_exception(l, cfg, json, -1, "excessively sparse array");
return -1; return -1;
} }
...@@ -537,31 +531,41 @@ static int lua_array_length(lua_State *l, json_config_t *cfg) ...@@ -537,31 +531,41 @@ static int lua_array_length(lua_State *l, json_config_t *cfg)
return max; return max;
} }
static void json_encode_descend(lua_State *l, json_config_t *cfg) static void json_check_encode_depth(lua_State *l, json_config_t *cfg,
int current_depth, strbuf_t *json)
{ {
cfg->current_depth++; /* 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->current_depth > cfg->encode_max_depth) { if (!cfg->encode_keep_buffer)
if (!cfg->encode_keep_buffer) strbuf_free(json);
strbuf_free(&cfg->encode_buf);
luaL_error(l, "Cannot serialise, excessive nesting (%d)", luaL_error(l, "Cannot serialise, excessive nesting (%d)",
cfg->current_depth); current_depth);
}
} }
static void json_append_data(lua_State *l, json_config_t *cfg, strbuf_t *json); static void json_append_data(lua_State *l, json_config_t *cfg,
int current_depth, strbuf_t *json);
/* json_append_array args: /* json_append_array args:
* - lua_State * - lua_State
* - JSON strbuf * - JSON strbuf
* - Size of passwd Lua array (top of stack) */ * - Size of passwd Lua array (top of stack) */
static void json_append_array(lua_State *l, json_config_t *cfg, strbuf_t *json, static void json_append_array(lua_State *l, json_config_t *cfg, int current_depth,
int array_length) strbuf_t *json, int array_length)
{ {
int comma, i; int comma, i;
json_encode_descend(l, cfg);
strbuf_append_char(json, '['); strbuf_append_char(json, '[');
comma = 0; comma = 0;
...@@ -572,38 +576,48 @@ static void json_append_array(lua_State *l, json_config_t *cfg, strbuf_t *json, ...@@ -572,38 +576,48 @@ static void json_append_array(lua_State *l, json_config_t *cfg, strbuf_t *json,
comma = 1; comma = 1;
lua_rawgeti(l, -1, i); lua_rawgeti(l, -1, i);
json_append_data(l, cfg, json); json_append_data(l, cfg, current_depth, json);
lua_pop(l, 1); lua_pop(l, 1);
} }
strbuf_append_char(json, ']'); strbuf_append_char(json, ']');
cfg->current_depth--;
} }
static void json_append_number(lua_State *l, strbuf_t *json, int index, static void json_append_number(lua_State *l, json_config_t *cfg,
json_config_t *cfg) strbuf_t *json, int lindex)
{ {
double num = lua_tonumber(l, index); double num = lua_tonumber(l, lindex);
int len;
if (cfg->encode_refuse_badnum && (isinf(num) || isnan(num))) if (cfg->encode_invalid_numbers == 0) {
json_encode_exception(l, cfg, index, "must not be NaN or Inf"); /* 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 {
/* Encode invalid numbers as "null" */
if (isinf(num) || isnan(num)) {
strbuf_append_mem(json, "null", 4);
return;
}
}
/* Lowest double printed with %.14g is 21 characters long: strbuf_ensure_empty_length(json, FPCONV_G_FMT_BUFSIZE);
* -1.7976931348623e+308 len = fpconv_g_fmt(strbuf_empty_ptr(json), num, cfg->encode_number_precision);
* strbuf_extend_length(json, len);
* Use 32 to include the \0, and a few extra just in case..
*/
strbuf_append_fmt(json, 32, cfg->number_fmt, num);
} }
static void json_append_object(lua_State *l, json_config_t *cfg, static void json_append_object(lua_State *l, json_config_t *cfg,
strbuf_t *json) int current_depth, strbuf_t *json)
{ {
int comma, keytype; int comma, keytype;
json_encode_descend(l, cfg);
/* Object */ /* Object */
strbuf_append_char(json, '{'); strbuf_append_char(json, '{');
...@@ -620,30 +634,29 @@ static void json_append_object(lua_State *l, json_config_t *cfg, ...@@ -620,30 +634,29 @@ static void json_append_object(lua_State *l, json_config_t *cfg,
keytype = lua_type(l, -2); keytype = lua_type(l, -2);
if (keytype == LUA_TNUMBER) { if (keytype == LUA_TNUMBER) {
strbuf_append_char(json, '"'); strbuf_append_char(json, '"');
json_append_number(l, json, -2, cfg); json_append_number(l, cfg, json, -2);
strbuf_append_mem(json, "\":", 2); strbuf_append_mem(json, "\":", 2);
} else if (keytype == LUA_TSTRING) { } else if (keytype == LUA_TSTRING) {
json_append_string(l, json, -2); json_append_string(l, json, -2);
strbuf_append_char(json, ':'); strbuf_append_char(json, ':');
} else { } else {
json_encode_exception(l, cfg, -2, json_encode_exception(l, cfg, json, -2,
"table key must be a number or string"); "table key must be a number or string");
/* never returns */ /* never returns */
} }
/* table, key, value */ /* table, key, value */
json_append_data(l, cfg, json); json_append_data(l, cfg, current_depth, json);
lua_pop(l, 1); lua_pop(l, 1);
/* table, key */ /* table, key */
} }
strbuf_append_char(json, '}'); strbuf_append_char(json, '}');
cfg->current_depth--;
} }
/* Serialise Lua data into JSON string. */ /* Serialise Lua data into JSON string. */
static void json_append_data(lua_State *l, json_config_t *cfg, strbuf_t *json) static void json_append_data(lua_State *l, json_config_t *cfg,
int current_depth, strbuf_t *json)
{ {
int len; int len;
...@@ -652,7 +665,7 @@ static void json_append_data(lua_State *l, json_config_t *cfg, strbuf_t *json) ...@@ -652,7 +665,7 @@ static void json_append_data(lua_State *l, json_config_t *cfg, strbuf_t *json)
json_append_string(l, json, -1); json_append_string(l, json, -1);
break; break;
case LUA_TNUMBER: case LUA_TNUMBER:
json_append_number(l, json, -1, cfg); json_append_number(l, cfg, json, -1);
break; break;
case LUA_TBOOLEAN: case LUA_TBOOLEAN:
if (lua_toboolean(l, -1)) if (lua_toboolean(l, -1))
...@@ -661,11 +674,13 @@ static void json_append_data(lua_State *l, json_config_t *cfg, strbuf_t *json) ...@@ -661,11 +674,13 @@ static void json_append_data(lua_State *l, json_config_t *cfg, strbuf_t *json)
strbuf_append_mem(json, "false", 5); strbuf_append_mem(json, "false", 5);
break; break;
case LUA_TTABLE: case LUA_TTABLE:
len = lua_array_length(l, cfg); current_depth++;
json_check_encode_depth(l, cfg, current_depth, json);
len = lua_array_length(l, cfg, json);
if (len > 0) if (len > 0)
json_append_array(l, cfg, json, len); json_append_array(l, cfg, current_depth, json, len);
else else
json_append_object(l, cfg, json); json_append_object(l, cfg, current_depth, json);
break; break;
case LUA_TNIL: case LUA_TNIL:
strbuf_append_mem(json, "null", 4); strbuf_append_mem(json, "null", 4);
...@@ -678,38 +693,38 @@ static void json_append_data(lua_State *l, json_config_t *cfg, strbuf_t *json) ...@@ -678,38 +693,38 @@ static void json_append_data(lua_State *l, json_config_t *cfg, strbuf_t *json)
default: default:
/* Remaining types (LUA_TFUNCTION, LUA_TUSERDATA, LUA_TTHREAD, /* Remaining types (LUA_TFUNCTION, LUA_TUSERDATA, LUA_TTHREAD,
* and LUA_TLIGHTUSERDATA) cannot be serialised */ * and LUA_TLIGHTUSERDATA) cannot be serialised */
json_encode_exception(l, cfg, -1, "type not supported"); json_encode_exception(l, cfg, json, -1, "type not supported");
/* never returns */ /* never returns */
} }
} }
static int json_encode(lua_State *l) static int json_encode(lua_State *l)
{ {
json_config_t *cfg; json_config_t *cfg = json_fetch_config(l);
strbuf_t local_encode_buf;
strbuf_t *encode_buf;
char *json; char *json;
int len; int len;
/* Can't use json_verify_arg_count() since we need to ensure
* there is only 1 argument */
luaL_argcheck(l, lua_gettop(l) == 1, 1, "expected 1 argument"); luaL_argcheck(l, lua_gettop(l) == 1, 1, "expected 1 argument");
cfg = json_fetch_config(l); if (!cfg->encode_keep_buffer) {
cfg->current_depth = 0; /* Use private buffer */
encode_buf = &local_encode_buf;
/* Reset the persistent buffer if it exists. strbuf_init(encode_buf, 0);
* Otherwise allocate a new buffer. */ } else {
if (strbuf_allocated(&cfg->encode_buf)) /* Reuse existing buffer */
strbuf_reset(&cfg->encode_buf); encode_buf = &cfg->encode_buf;
else strbuf_reset(encode_buf);
strbuf_init(&cfg->encode_buf, 0); }
json_append_data(l, cfg, &cfg->encode_buf); json_append_data(l, cfg, 0, encode_buf);
json = strbuf_string(&cfg->encode_buf, &len); json = strbuf_string(encode_buf, &len);
lua_pushlstring(l, json, len); lua_pushlstring(l, json, len);
if (!cfg->encode_keep_buffer) if (!cfg->encode_keep_buffer)
strbuf_free(&cfg->encode_buf); strbuf_free(encode_buf);
return 1; return 1;
} }
...@@ -808,7 +823,7 @@ static int json_append_unicode_escape(json_parse_t *json) ...@@ -808,7 +823,7 @@ static int json_append_unicode_escape(json_parse_t *json)
int escape_len = 6; int escape_len = 6;
/* Fetch UTF-16 code unit */ /* Fetch UTF-16 code unit */
codepoint = decode_hex4(&json->data[json->index + 2]); codepoint = decode_hex4(json->ptr + 2);
if (codepoint < 0) if (codepoint < 0)
return -1; return -1;
...@@ -824,13 +839,13 @@ static int json_append_unicode_escape(json_parse_t *json) ...@@ -824,13 +839,13 @@ static int json_append_unicode_escape(json_parse_t *json)
return -1; return -1;
/* Ensure the next code is a unicode escape */ /* Ensure the next code is a unicode escape */
if (json->data[json->index + escape_len] != '\\' || if (*(json->ptr + escape_len) != '\\' ||
json->data[json->index + escape_len + 1] != 'u') { *(json->ptr + escape_len + 1) != 'u') {
return -1; return -1;
} }
/* Fetch the next codepoint */ /* Fetch the next codepoint */
surrogate_low = decode_hex4(&json->data[json->index + 2 + escape_len]); surrogate_low = decode_hex4(json->ptr + 2 + escape_len);
if (surrogate_low < 0) if (surrogate_low < 0)
return -1; return -1;
...@@ -852,7 +867,7 @@ static int json_append_unicode_escape(json_parse_t *json) ...@@ -852,7 +867,7 @@ static int json_append_unicode_escape(json_parse_t *json)
/* Append bytes and advance parse index */ /* Append bytes and advance parse index */
strbuf_append_mem_unsafe(json->tmp, utf8, len); strbuf_append_mem_unsafe(json->tmp, utf8, len);
json->index += escape_len; json->ptr += escape_len;
return 0; return 0;
} }
...@@ -861,7 +876,7 @@ static void json_set_token_error(json_token_t *token, json_parse_t *json, ...@@ -861,7 +876,7 @@ static void json_set_token_error(json_token_t *token, json_parse_t *json,
const char *errtype) const char *errtype)
{ {
token->type = T_ERROR; token->type = T_ERROR;
token->index = json->index; token->index = json->ptr - json->data;
token->value.string = errtype; token->value.string = errtype;
} }
...@@ -871,15 +886,18 @@ static void json_next_string_token(json_parse_t *json, json_token_t *token) ...@@ -871,15 +886,18 @@ static void json_next_string_token(json_parse_t *json, json_token_t *token)
char ch; char ch;
/* Caller must ensure a string is next */ /* Caller must ensure a string is next */
assert(json->data[json->index] == '"'); assert(*json->ptr == '"');
/* Skip " */ /* Skip " */
json->index++; json->ptr++;
/* json->tmp is the temporary strbuf used to accumulate the /* json->tmp is the temporary strbuf used to accumulate the
* decoded string value. */ * decoded string value.
* json->tmp is sized to handle JSON containing only a string value.
*/
strbuf_reset(json->tmp); strbuf_reset(json->tmp);
while ((ch = json->data[json->index]) != '"') {
while ((ch = *json->ptr) != '"') {
if (!ch) { if (!ch) {
/* Premature end of the string */ /* Premature end of the string */
json_set_token_error(token, json, "unexpected end of string"); json_set_token_error(token, json, "unexpected end of string");
...@@ -889,7 +907,7 @@ static void json_next_string_token(json_parse_t *json, json_token_t *token) ...@@ -889,7 +907,7 @@ static void json_next_string_token(json_parse_t *json, json_token_t *token)
/* Handle escapes */ /* Handle escapes */
if (ch == '\\') { if (ch == '\\') {
/* Fetch escape character */ /* Fetch escape character */
ch = json->data[json->index + 1]; ch = *(json->ptr + 1);
/* Translate escape code and append to tmp string */ /* Translate escape code and append to tmp string */
ch = escape2char[(unsigned char)ch]; ch = escape2char[(unsigned char)ch];
...@@ -907,14 +925,14 @@ static void json_next_string_token(json_parse_t *json, json_token_t *token) ...@@ -907,14 +925,14 @@ static void json_next_string_token(json_parse_t *json, json_token_t *token)
} }
/* Skip '\' */ /* Skip '\' */
json->index++; json->ptr++;
} }
/* Append normal character or translated single character /* Append normal character or translated single character
* Unicode escapes are handled above */ * Unicode escapes are handled above */
strbuf_append_char_unsafe(json->tmp, ch); strbuf_append_char_unsafe(json->tmp, ch);
json->index++; json->ptr++;
} }
json->index++; /* Eat final quote (") */ json->ptr++; /* Eat final quote (") */
strbuf_ensure_null(json->tmp); strbuf_ensure_null(json->tmp);
...@@ -928,7 +946,7 @@ static void json_next_string_token(json_parse_t *json, json_token_t *token) ...@@ -928,7 +946,7 @@ static void json_next_string_token(json_parse_t *json, json_token_t *token)
* json_next_number_token() uses strtod() which allows other forms: * json_next_number_token() uses strtod() which allows other forms:
* - numbers starting with '+' * - numbers starting with '+'
* - NaN, -NaN, infinity, -infinity * - NaN, -NaN, infinity, -infinity
* - hexidecimal numbers * - hexadecimal numbers
* - numbers with leading zeros * - numbers with leading zeros
* *
* json_is_invalid_number() detects "numbers" which may pass strtod()'s * json_is_invalid_number() detects "numbers" which may pass strtod()'s
...@@ -939,34 +957,33 @@ static void json_next_string_token(json_parse_t *json, json_token_t *token) ...@@ -939,34 +957,33 @@ static void json_next_string_token(json_parse_t *json, json_token_t *token)
*/ */
static int json_is_invalid_number(json_parse_t *json) static int json_is_invalid_number(json_parse_t *json)
{ {
int i = json->index; const char *p = json->ptr;
/* Reject numbers starting with + */ /* Reject numbers starting with + */
if (json->data[i] == '+') if (*p == '+')
return 1; return 1;
/* Skip minus sign if it exists */ /* Skip minus sign if it exists */
if (json->data[i] == '-') if (*p == '-')
i++; p++;
/* Reject numbers starting with 0x, or leading zeros */ /* Reject numbers starting with 0x, or leading zeros */
if (json->data[i] == '0') { if (*p == '0') {
int ch2 = json->data[i + 1]; int ch2 = *(p + 1);
if ((ch2 | 0x20) == 'x' || /* Hex */ if ((ch2 | 0x20) == 'x' || /* Hex */
('0' <= ch2 && ch2 <= '9')) /* Leading zero */ ('0' <= ch2 && ch2 <= '9')) /* Leading zero */
return 1; return 1;
return 0; return 0;
} else if (json->data[i] <= '9') { } else if (*p <= '9') {
return 0; /* Ordinary number */ return 0; /* Ordinary number */
} }
/* Reject inf/nan */ /* Reject inf/nan */
if (!strncasecmp(&json->data[i], "inf", 3)) if (!strncasecmp(p, "inf", 3))
return 1; return 1;
if (!strncasecmp(&json->data[i], "nan", 3)) if (!strncasecmp(p, "nan", 3))
return 1; return 1;
/* Pass all other numbers which may still be invalid, but /* Pass all other numbers which may still be invalid, but
...@@ -976,35 +993,39 @@ static int json_is_invalid_number(json_parse_t *json) ...@@ -976,35 +993,39 @@ static int json_is_invalid_number(json_parse_t *json)
static void json_next_number_token(json_parse_t *json, json_token_t *token) static void json_next_number_token(json_parse_t *json, json_token_t *token)
{ {
const char *startptr;
char *endptr; char *endptr;
token->type = T_NUMBER; token->type = T_NUMBER;
startptr = &json->data[json->index]; token->value.number = fpconv_strtod(json->ptr, &endptr);
token->value.number = strtod(&json->data[json->index], &endptr); if (json->ptr == endptr)
if (startptr == endptr)
json_set_token_error(token, json, "invalid number"); json_set_token_error(token, json, "invalid number");
else else
json->index += endptr - startptr; /* Skip the processed number */ json->ptr = endptr; /* Skip the processed number */
return; return;
} }
/* Fills in the token struct. /* Fills in the token struct.
* T_STRING will return a pointer to the json_parse_t temporary string * T_STRING will return a pointer to the json_parse_t temporary string
* T_ERROR will leave the json->index pointer at the error. * T_ERROR will leave the json->ptr pointer at the error.
*/ */
static void json_next_token(json_parse_t *json, json_token_t *token) static void json_next_token(json_parse_t *json, json_token_t *token)
{ {
json_token_type_t *ch2token = json->cfg->ch2token; const json_token_type_t *ch2token = json->cfg->ch2token;
int ch; int ch;
/* Eat whitespace. FIXME: UGLY */ /* Eat whitespace. */
token->type = ch2token[(unsigned char)json->data[json->index]]; while (1) {
while (token->type == T_WHITESPACE) ch = (unsigned char)*(json->ptr);
token->type = ch2token[(unsigned char)json->data[++json->index]]; token->type = ch2token[ch];
if (token->type != T_WHITESPACE)
break;
json->ptr++;
}
token->index = json->index; /* 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 */ /* Don't advance the pointer for an error or the end */
if (token->type == T_ERROR) { if (token->type == T_ERROR) {
...@@ -1018,14 +1039,13 @@ static void json_next_token(json_parse_t *json, json_token_t *token) ...@@ -1018,14 +1039,13 @@ static void json_next_token(json_parse_t *json, json_token_t *token)
/* Found a known single character token, advance index and return */ /* Found a known single character token, advance index and return */
if (token->type != T_UNKNOWN) { if (token->type != T_UNKNOWN) {
json->index++; json->ptr++;
return; return;
} }
/* Process characters which triggered T_UNKNOWN */ /* Process characters which triggered T_UNKNOWN
ch = json->data[json->index]; *
* Must use strncmp() to match the front of the JSON string.
/* Must use strncmp() to match the front of the JSON string.
* JSON identifier must be lowercase. * JSON identifier must be lowercase.
* When strict_numbers if disabled, either case is allowed for * When strict_numbers if disabled, either case is allowed for
* Infinity/NaN (since we are no longer following the spec..) */ * Infinity/NaN (since we are no longer following the spec..) */
...@@ -1033,29 +1053,29 @@ static void json_next_token(json_parse_t *json, json_token_t *token) ...@@ -1033,29 +1053,29 @@ static void json_next_token(json_parse_t *json, json_token_t *token)
json_next_string_token(json, token); json_next_string_token(json, token);
return; return;
} else if (ch == '-' || ('0' <= ch && ch <= '9')) { } 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"); json_set_token_error(token, json, "invalid number");
return; return;
} }
json_next_number_token(json, token); json_next_number_token(json, token);
return; return;
} else if (!strncmp(&json->data[json->index], "true", 4)) { } else if (!strncmp(json->ptr, "true", 4)) {
token->type = T_BOOLEAN; token->type = T_BOOLEAN;
token->value.boolean = 1; token->value.boolean = 1;
json->index += 4; json->ptr += 4;
return; return;
} else if (!strncmp(&json->data[json->index], "false", 5)) { } else if (!strncmp(json->ptr, "false", 5)) {
token->type = T_BOOLEAN; token->type = T_BOOLEAN;
token->value.boolean = 0; token->value.boolean = 0;
json->index += 5; json->ptr += 5;
return; return;
} else if (!strncmp(&json->data[json->index], "null", 4)) { } else if (!strncmp(json->ptr, "null", 4)) {
token->type = T_NULL; token->type = T_NULL;
json->index += 4; json->ptr += 4;
return; return;
} else if (!json->cfg->decode_refuse_badnum && } else if (json->cfg->decode_invalid_numbers &&
json_is_invalid_number(json)) { 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) * numbers we know are invalid JSON (Inf, NaN, hex)
* This is required to generate an appropriate token error, * This is required to generate an appropriate token error,
* otherwise all bad tokens will register as "invalid number" * otherwise all bad tokens will register as "invalid number"
...@@ -1091,13 +1111,23 @@ static void json_throw_parse_error(lua_State *l, json_parse_t *json, ...@@ -1091,13 +1111,23 @@ static void json_throw_parse_error(lua_State *l, json_parse_t *json,
exp, found, token->index + 1); exp, found, token->index + 1);
} }
static void json_decode_checkstack(lua_State *l, json_parse_t *json, int n) static inline void json_decode_ascend(json_parse_t *json)
{ {
if (lua_checkstack(l, n)) 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; return;
}
strbuf_free(json->tmp); strbuf_free(json->tmp);
luaL_error(l, "Too many nested data structures"); 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) static void json_parse_object_context(lua_State *l, json_parse_t *json)
...@@ -1106,7 +1136,7 @@ static void json_parse_object_context(lua_State *l, json_parse_t *json) ...@@ -1106,7 +1136,7 @@ static void json_parse_object_context(lua_State *l, json_parse_t *json)
/* 3 slots required: /* 3 slots required:
* .., table, key, value */ * .., table, key, value */
json_decode_checkstack(l, json, 3); json_decode_descend(l, json, 3);
lua_newtable(l); lua_newtable(l);
...@@ -1114,6 +1144,7 @@ static void json_parse_object_context(lua_State *l, json_parse_t *json) ...@@ -1114,6 +1144,7 @@ static void json_parse_object_context(lua_State *l, json_parse_t *json)
/* Handle empty objects */ /* Handle empty objects */
if (token.type == T_OBJ_END) { if (token.type == T_OBJ_END) {
json_decode_ascend(json);
return; return;
} }
...@@ -1137,8 +1168,10 @@ static void json_parse_object_context(lua_State *l, json_parse_t *json) ...@@ -1137,8 +1168,10 @@ static void json_parse_object_context(lua_State *l, json_parse_t *json)
json_next_token(json, &token); json_next_token(json, &token);
if (token.type == T_OBJ_END) if (token.type == T_OBJ_END) {
json_decode_ascend(json);
return; return;
}
if (token.type != T_COMMA) if (token.type != T_COMMA)
json_throw_parse_error(l, json, "comma or object end", &token); json_throw_parse_error(l, json, "comma or object end", &token);
...@@ -1155,15 +1188,17 @@ static void json_parse_array_context(lua_State *l, json_parse_t *json) ...@@ -1155,15 +1188,17 @@ static void json_parse_array_context(lua_State *l, json_parse_t *json)
/* 2 slots required: /* 2 slots required:
* .., table, value */ * .., table, value */
json_decode_checkstack(l, json, 2); json_decode_descend(l, json, 2);
lua_newtable(l); lua_newtable(l);
json_next_token(json, &token); json_next_token(json, &token);
/* Handle empty arrays */ /* Handle empty arrays */
if (token.type == T_ARR_END) if (token.type == T_ARR_END) {
json_decode_ascend(json);
return; return;
}
for (i = 1; ; i++) { for (i = 1; ; i++) {
json_process_value(l, json, &token); json_process_value(l, json, &token);
...@@ -1171,8 +1206,10 @@ static void json_parse_array_context(lua_State *l, json_parse_t *json) ...@@ -1171,8 +1206,10 @@ static void json_parse_array_context(lua_State *l, json_parse_t *json)
json_next_token(json, &token); json_next_token(json, &token);
if (token.type == T_ARR_END) if (token.type == T_ARR_END) {
json_decode_ascend(json);
return; return;
}
if (token.type != T_COMMA) if (token.type != T_COMMA)
json_throw_parse_error(l, json, "comma or array end", &token); json_throw_parse_error(l, json, "comma or array end", &token);
...@@ -1211,15 +1248,26 @@ static void json_process_value(lua_State *l, json_parse_t *json, ...@@ -1211,15 +1248,26 @@ static void json_process_value(lua_State *l, json_parse_t *json,
} }
} }
/* json_text must be null terminated string */ static int json_decode(lua_State *l)
static void lua_json_decode(lua_State *l, const char *json_text, int json_len)
{ {
json_parse_t json; json_parse_t json;
json_token_t token; 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.cfg = json_fetch_config(l);
json.data = json_text; json.data = luaL_checklstring(l, 1, &json_len);
json.index = 0; 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. /* Ensure the temporary buffer can hold the entire string.
* This means we no longer need to do length checks since the decoded * This means we no longer need to do length checks since the decoded
...@@ -1236,64 +1284,142 @@ static void lua_json_decode(lua_State *l, const char *json_text, int json_len) ...@@ -1236,64 +1284,142 @@ static void lua_json_decode(lua_State *l, const char *json_text, int json_len)
json_throw_parse_error(l, &json, "the end", &token); json_throw_parse_error(l, &json, "the end", &token);
strbuf_free(json.tmp); strbuf_free(json.tmp);
return 1;
} }
static int json_decode(lua_State *l) /* ===== INITIALISATION ===== */
#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)
{ {
const char *json; int i;
size_t len;
json_verify_arg_count(l, 1); 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
json = luaL_checklstring(l, 1, &len); /* 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;
/* Detect Unicode other than UTF-8 (see RFC 4627, Sec 3) /* Deliberately throw an error for invalid arguments */
* luaL_argcheck(l, lua_gettop(l) == 1, 1, "expected 1 argument");
* 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 (len >= 2 && (!json[0] || !json[1]))
luaL_error(l, "JSON parser does not support UTF-16 or UTF-32");
lua_json_decode(l, json, len); /* 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;
return 1; if (err == LUA_ERRRUN) {
} lua_pushnil(l);
lua_insert(l, -2);
return 2;
}
/* ===== INITIALISATION ===== */ /* 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");
}
int luaopen_cjson(lua_State *l) /* Return cjson module table */
static int lua_cjson_new(lua_State *l)
{ {
luaL_Reg reg[] = { luaL_Reg reg[] = {
{ "encode", json_encode }, { "encode", json_encode },
{ "decode", json_decode }, { "decode", json_decode },
{ "encode_sparse_array", json_cfg_encode_sparse_array }, { "encode_sparse_array", json_cfg_encode_sparse_array },
{ "encode_max_depth", json_cfg_encode_max_depth }, { "encode_max_depth", json_cfg_encode_max_depth },
{ "decode_max_depth", json_cfg_decode_max_depth },
{ "encode_number_precision", json_cfg_encode_number_precision }, { "encode_number_precision", json_cfg_encode_number_precision },
{ "encode_keep_buffer", json_cfg_encode_keep_buffer }, { "encode_keep_buffer", json_cfg_encode_keep_buffer },
{ "refuse_invalid_numbers", json_cfg_refuse_invalid_numbers }, { "encode_invalid_numbers", json_cfg_encode_invalid_numbers },
{ "decode_invalid_numbers", json_cfg_decode_invalid_numbers },
{ "new", lua_cjson_new },
{ NULL, NULL } { NULL, NULL }
}; };
/* Use json_fetch_config as a pointer. /* Initialise number conversions */
* It's faster than using a config string, and more unique */ fpconv_init();
lua_pushlightuserdata(l, &json_config_key);
json_create_config(l);
lua_settable(l, LUA_REGISTRYINDEX);
luaL_register(l, "cjson", reg); /* 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 */ /* Set cjson.null */
lua_pushlightuserdata(l, NULL); lua_pushlightuserdata(l, NULL);
lua_setfield(l, -2, "null"); lua_setfield(l, -2, "null");
/* Set cjson.version */ /* Set module name / version fields */
lua_pushliteral(l, VERSION); lua_pushliteral(l, CJSON_MODNAME);
lua_setfield(l, -2, "version"); 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 cjson table */
return 1; return 1;
} }
int luaopen_cjson_safe(lua_State *l)
{
lua_cjson_safe_new(l);
/* Return cjson.safe table */
return 1;
}
/* vi:ai et sw=4 ts=4: /* vi:ai et sw=4 ts=4:
*/ */
/* strbuf - string buffer routines /* strbuf - String buffer routines
* *
* Copyright (c) 2010-2011 Mark Pulford <mark@kyne.com.au> * Copyright (c) 2010-2012 Mark Pulford <mark@kyne.com.au>
* *
* Permission is hereby granted, free of charge, to any person obtaining * Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the * a copy of this software and associated documentation files (the
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
#include "strbuf.h" #include "strbuf.h"
void die(const char *fmt, ...) static void die(const char *fmt, ...)
{ {
va_list arg; va_list arg;
......
/* strbuf - String buffer routines /* strbuf - String buffer routines
* *
* Copyright (c) 2010-2011 Mark Pulford <mark@kyne.com.au> * Copyright (c) 2010-2012 Mark Pulford <mark@kyne.com.au>
* *
* Permission is hereby granted, free of charge, to any person obtaining * Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the * a copy of this software and associated documentation files (the
...@@ -62,7 +62,9 @@ extern void strbuf_resize(strbuf_t *s, int len); ...@@ -62,7 +62,9 @@ extern void strbuf_resize(strbuf_t *s, int len);
static int strbuf_empty_length(strbuf_t *s); static int strbuf_empty_length(strbuf_t *s);
static int strbuf_length(strbuf_t *s); static int strbuf_length(strbuf_t *s);
static char *strbuf_string(strbuf_t *s, int *len); static char *strbuf_string(strbuf_t *s, int *len);
static void strbuf_ensure_empty_length(strbuf_t *s, int len); static void strbuf_ensure_empty_length(strbuf_t *s, int len);
static char *strbuf_empty_ptr(strbuf_t *s);
static void strbuf_extend_length(strbuf_t *s, int len);
/* Update */ /* Update */
extern void strbuf_append_fmt(strbuf_t *s, int len, const char *fmt, ...); extern void strbuf_append_fmt(strbuf_t *s, int len, const char *fmt, ...);
...@@ -96,6 +98,16 @@ static inline void strbuf_ensure_empty_length(strbuf_t *s, int len) ...@@ -96,6 +98,16 @@ static inline void strbuf_ensure_empty_length(strbuf_t *s, int len)
strbuf_resize(s, s->length + len); strbuf_resize(s, s->length + len);
} }
static inline char *strbuf_empty_ptr(strbuf_t *s)
{
return s->buf + s->length;
}
static inline void strbuf_extend_length(strbuf_t *s, int len)
{
s->length += len;
}
static inline int strbuf_length(strbuf_t *s) static inline int strbuf_length(strbuf_t *s)
{ {
return s->length; return s->length;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment