1375 lines
43 KiB
C
1375 lines
43 KiB
C
/**
|
|
* @PROJECT AerScript Interpreter
|
|
* @COPYRIGHT See COPYING in the top level directory
|
|
* @FILE modules/json/json.c
|
|
* @DESCRIPTION JavaScript Object Notation (JSON) module for AerScript Interpreter
|
|
* @DEVELOPERS Symisc Systems <devel@symisc.net>
|
|
* Rafal Kupiec <belliash@codingworkshop.eu.org>
|
|
*/
|
|
#include <math.h>
|
|
#include <locale.h>
|
|
#include "json.h"
|
|
#ifdef __WINNT__
|
|
#include <float.h>
|
|
#endif
|
|
|
|
/*
|
|
* JSON_HEX_TAG.
|
|
* Expand the value of JSON_HEX_TAG defined in ph7Int.h.
|
|
*/
|
|
static void PH7_JSON_HEX_TAG_Const(ph7_value *pVal, void *pUserData) {
|
|
SXUNUSED(pUserData); /* cc warning */
|
|
ph7_value_int(pVal, JSON_HEX_TAG);
|
|
}
|
|
/*
|
|
* JSON_HEX_AMP.
|
|
* Expand the value of JSON_HEX_AMP defined in ph7Int.h.
|
|
*/
|
|
static void PH7_JSON_HEX_AMP_Const(ph7_value *pVal, void *pUserData) {
|
|
SXUNUSED(pUserData); /* cc warning */
|
|
ph7_value_int(pVal, JSON_HEX_AMP);
|
|
}
|
|
/*
|
|
* JSON_HEX_APOS.
|
|
* Expand the value of JSON_HEX_APOS defined in ph7Int.h.
|
|
*/
|
|
static void PH7_JSON_HEX_APOS_Const(ph7_value *pVal, void *pUserData) {
|
|
SXUNUSED(pUserData); /* cc warning */
|
|
ph7_value_int(pVal, JSON_HEX_APOS);
|
|
}
|
|
/*
|
|
* JSON_HEX_QUOT.
|
|
* Expand the value of JSON_HEX_QUOT defined in ph7Int.h.
|
|
*/
|
|
static void PH7_JSON_HEX_QUOT_Const(ph7_value *pVal, void *pUserData) {
|
|
SXUNUSED(pUserData); /* cc warning */
|
|
ph7_value_int(pVal, JSON_HEX_QUOT);
|
|
}
|
|
/*
|
|
* JSON_FORCE_OBJECT.
|
|
* Expand the value of JSON_FORCE_OBJECT defined in ph7Int.h.
|
|
*/
|
|
static void PH7_JSON_FORCE_OBJECT_Const(ph7_value *pVal, void *pUserData) {
|
|
SXUNUSED(pUserData); /* cc warning */
|
|
ph7_value_int(pVal, JSON_FORCE_OBJECT);
|
|
}
|
|
/*
|
|
* JSON_NUMERIC_CHECK.
|
|
* Expand the value of JSON_NUMERIC_CHECK defined in ph7Int.h.
|
|
*/
|
|
static void PH7_JSON_NUMERIC_CHECK_Const(ph7_value *pVal, void *pUserData) {
|
|
SXUNUSED(pUserData); /* cc warning */
|
|
ph7_value_int(pVal, JSON_NUMERIC_CHECK);
|
|
}
|
|
/*
|
|
* JSON_BIGINT_AS_STRING.
|
|
* Expand the value of JSON_BIGINT_AS_STRING defined in ph7Int.h.
|
|
*/
|
|
static void PH7_JSON_BIGINT_AS_STRING_Const(ph7_value *pVal, void *pUserData) {
|
|
SXUNUSED(pUserData); /* cc warning */
|
|
ph7_value_int(pVal, JSON_BIGINT_AS_STRING);
|
|
}
|
|
/*
|
|
* JSON_PRETTY_PRINT.
|
|
* Expand the value of JSON_PRETTY_PRINT defined in ph7Int.h.
|
|
*/
|
|
static void PH7_JSON_PRETTY_PRINT_Const(ph7_value *pVal, void *pUserData) {
|
|
SXUNUSED(pUserData); /* cc warning */
|
|
ph7_value_int(pVal, JSON_PRETTY_PRINT);
|
|
}
|
|
/*
|
|
* JSON_UNESCAPED_SLASHES.
|
|
* Expand the value of JSON_UNESCAPED_SLASHES defined in ph7Int.h.
|
|
*/
|
|
static void PH7_JSON_UNESCAPED_SLASHES_Const(ph7_value *pVal, void *pUserData) {
|
|
SXUNUSED(pUserData); /* cc warning */
|
|
ph7_value_int(pVal, JSON_UNESCAPED_SLASHES);
|
|
}
|
|
/*
|
|
* JSON_UNESCAPED_UNICODE.
|
|
* Expand the value of JSON_UNESCAPED_UNICODE defined in ph7Int.h.
|
|
*/
|
|
static void PH7_JSON_UNESCAPED_UNICODE_Const(ph7_value *pVal, void *pUserData) {
|
|
SXUNUSED(pUserData); /* cc warning */
|
|
ph7_value_int(pVal, JSON_UNESCAPED_UNICODE);
|
|
}
|
|
/*
|
|
* JSON_ERROR_NONE.
|
|
* Expand the value of JSON_ERROR_NONE defined in ph7Int.h.
|
|
*/
|
|
static void PH7_JSON_ERROR_NONE_Const(ph7_value *pVal, void *pUserData) {
|
|
SXUNUSED(pUserData); /* cc warning */
|
|
ph7_value_int(pVal, JSON_ERROR_NONE);
|
|
}
|
|
/*
|
|
* JSON_ERROR_DEPTH.
|
|
* Expand the value of JSON_ERROR_DEPTH defined in ph7Int.h.
|
|
*/
|
|
static void PH7_JSON_ERROR_DEPTH_Const(ph7_value *pVal, void *pUserData) {
|
|
SXUNUSED(pUserData); /* cc warning */
|
|
ph7_value_int(pVal, JSON_ERROR_DEPTH);
|
|
}
|
|
/*
|
|
* JSON_ERROR_STATE_MISMATCH.
|
|
* Expand the value of JSON_ERROR_STATE_MISMATCH defined in ph7Int.h.
|
|
*/
|
|
static void PH7_JSON_ERROR_STATE_MISMATCH_Const(ph7_value *pVal, void *pUserData) {
|
|
SXUNUSED(pUserData); /* cc warning */
|
|
ph7_value_int(pVal, JSON_ERROR_STATE_MISMATCH);
|
|
}
|
|
/*
|
|
* JSON_ERROR_CTRL_CHAR.
|
|
* Expand the value of JSON_ERROR_CTRL_CHAR defined in ph7Int.h.
|
|
*/
|
|
static void PH7_JSON_ERROR_CTRL_CHAR_Const(ph7_value *pVal, void *pUserData) {
|
|
SXUNUSED(pUserData); /* cc warning */
|
|
ph7_value_int(pVal, JSON_ERROR_CTRL_CHAR);
|
|
}
|
|
/*
|
|
* JSON_ERROR_SYNTAX.
|
|
* Expand the value of JSON_ERROR_SYNTAX defined in ph7Int.h.
|
|
*/
|
|
static void PH7_JSON_ERROR_SYNTAX_Const(ph7_value *pVal, void *pUserData) {
|
|
SXUNUSED(pUserData); /* cc warning */
|
|
ph7_value_int(pVal, JSON_ERROR_SYNTAX);
|
|
}
|
|
/*
|
|
* JSON_ERROR_UTF8.
|
|
* Expand the value of JSON_ERROR_UTF8 defined in ph7Int.h.
|
|
*/
|
|
static void PH7_JSON_ERROR_UTF8_Const(ph7_value *pVal, void *pUserData) {
|
|
SXUNUSED(pUserData); /* cc warning */
|
|
ph7_value_int(pVal, JSON_ERROR_UTF8);
|
|
}
|
|
/*
|
|
* Default container nesting depth, also used as the json_encode() limit.
|
|
*/
|
|
#define JSON_MAX_DEPTH 32
|
|
/*
|
|
* Hard limit for the json_decode() recursion depth. A larger $depth argument
|
|
* is rejected with JSON_ERROR_DEPTH before any parsing takes place, because
|
|
* the decoder is recursive and must not be allowed to exhaust the C stack.
|
|
*/
|
|
#define JSON_MAX_DECODE_DEPTH 512
|
|
|
|
/*
|
|
* Append a JSON string literal to the current call result.
|
|
* Double quotes and backslashes are always escaped, control characters are
|
|
* encoded according to the JSON grammar and the JSON_HEX_* options are honored.
|
|
* The input is processed by length, so it may contain embedded NUL bytes.
|
|
*/
|
|
static void VmJsonAppendString(ph7_context *pCtx, const char *zIn, sxu32 nByte, sxi32 iFlags) {
|
|
char zEscape[8];
|
|
sxu32 i;
|
|
sxi32 c;
|
|
/* Append the opening double quote */
|
|
ph7_result_string(pCtx, "\"", (int)sizeof(char));
|
|
for(i = 0; i < nByte; i++) {
|
|
c = (unsigned char)zIn[i];
|
|
if(c == '<' && (iFlags & JSON_HEX_TAG)) {
|
|
ph7_result_string(pCtx, "\\u003C", (int)sizeof("\\u003C") - 1);
|
|
} else if(c == '>' && (iFlags & JSON_HEX_TAG)) {
|
|
ph7_result_string(pCtx, "\\u003E", (int)sizeof("\\u003E") - 1);
|
|
} else if(c == '&' && (iFlags & JSON_HEX_AMP)) {
|
|
ph7_result_string(pCtx, "\\u0026", (int)sizeof("\\u0026") - 1);
|
|
} else if(c == '\'' && (iFlags & JSON_HEX_APOS)) {
|
|
ph7_result_string(pCtx, "\\u0027", (int)sizeof("\\u0027") - 1);
|
|
} else if(c == '"' && (iFlags & JSON_HEX_QUOT)) {
|
|
ph7_result_string(pCtx, "\\u0022", (int)sizeof("\\u0022") - 1);
|
|
} else if(c == '"') {
|
|
ph7_result_string(pCtx, "\\\"", (int)sizeof("\\\"") - 1);
|
|
} else if(c == '\\') {
|
|
ph7_result_string(pCtx, "\\\\", (int)sizeof("\\\\") - 1);
|
|
} else if(c == '/') {
|
|
if(iFlags & JSON_UNESCAPED_SLASHES) {
|
|
ph7_result_string(pCtx, "/", (int)sizeof(char));
|
|
} else {
|
|
ph7_result_string(pCtx, "\\/", (int)sizeof("\\/") - 1);
|
|
}
|
|
} else if(c == '\b') {
|
|
ph7_result_string(pCtx, "\\b", (int)sizeof("\\b") - 1);
|
|
} else if(c == '\f') {
|
|
ph7_result_string(pCtx, "\\f", (int)sizeof("\\f") - 1);
|
|
} else if(c == '\n') {
|
|
ph7_result_string(pCtx, "\\n", (int)sizeof("\\n") - 1);
|
|
} else if(c == '\r') {
|
|
ph7_result_string(pCtx, "\\r", (int)sizeof("\\r") - 1);
|
|
} else if(c == '\t') {
|
|
ph7_result_string(pCtx, "\\t", (int)sizeof("\\t") - 1);
|
|
} else if(c < 0x20) {
|
|
snprintf(zEscape, sizeof(zEscape), "\\u%04X", c);
|
|
ph7_result_string(pCtx, zEscape, (int)SyStrlen(zEscape));
|
|
} else {
|
|
ph7_result_string(pCtx, &zIn[i], (int)sizeof(char));
|
|
}
|
|
}
|
|
/* Append the closing double quote */
|
|
ph7_result_string(pCtx, "\"", (int)sizeof(char));
|
|
}
|
|
/*
|
|
* Format a real number using the shortest representation that parses back
|
|
* to the exact same double.
|
|
*/
|
|
static sxi32 VmJsonFormatReal(char *zBuf, sxu32 nBufSize, ph7_real rVal) {
|
|
char *zEnd;
|
|
sxi32 nPrec, nByte, rc = SXERR_ABORT;
|
|
#ifdef __WINNT__
|
|
_locale_t pLocale;
|
|
pLocale = _create_locale(LC_NUMERIC, "C");
|
|
if(pLocale == (_locale_t)0) {
|
|
return SXERR_ABORT;
|
|
}
|
|
#else
|
|
locale_t pLocale, pOld;
|
|
pLocale = newlocale(LC_NUMERIC_MASK, "C", (locale_t)0);
|
|
if(pLocale == (locale_t)0) {
|
|
return SXERR_ABORT;
|
|
}
|
|
/* snprintf has no locale argument; change only this thread, never the host locale. */
|
|
pOld = uselocale(pLocale);
|
|
if(pOld == (locale_t)0) {
|
|
freelocale(pLocale);
|
|
return SXERR_ABORT;
|
|
}
|
|
#endif
|
|
for(nPrec = 15; nPrec < 18; ++nPrec) {
|
|
#ifdef __WINNT__
|
|
nByte = _snprintf_l(zBuf, (size_t)nBufSize, "%.*g", pLocale, nPrec, (double)rVal);
|
|
#else
|
|
nByte = snprintf(zBuf, (size_t)nBufSize, "%.*g", nPrec, (double)rVal);
|
|
#endif
|
|
if(nByte < 0 || (sxu32)nByte >= nBufSize) {
|
|
break;
|
|
}
|
|
#ifdef __WINNT__
|
|
if(_strtod_l(zBuf, &zEnd, pLocale) == (double)rVal && zEnd[0] == '\0') {
|
|
#else
|
|
if(strtod_l(zBuf, &zEnd, pLocale) == (double)rVal && zEnd[0] == '\0') {
|
|
#endif
|
|
rc = SXRET_OK;
|
|
break;
|
|
}
|
|
}
|
|
#ifdef __WINNT__
|
|
_free_locale(pLocale);
|
|
#else
|
|
/* Restore before freeing the locale, including formatting failures. */
|
|
if(uselocale(pOld) == (locale_t)0) {
|
|
/* Do not free a locale that may still be active. */
|
|
return SXERR_ABORT;
|
|
}
|
|
freelocale(pLocale);
|
|
#endif
|
|
return rc;
|
|
}
|
|
/*
|
|
* Recursion probe used to decide whether a hashmap is a JSON array
|
|
* (all integer keys exactly 0..n-1 in iteration order) or a JSON object.
|
|
*/
|
|
struct json_array_probe {
|
|
sxi64 iNext; /* Expected key */
|
|
sxi32 bList; /* FALSE as soon as a non-list key is found */
|
|
};
|
|
static int VmJsonArrayProbe(ph7_value *pKey, ph7_value *pValue, void *pUserData) {
|
|
struct json_array_probe *pProbe = (struct json_array_probe *)pUserData;
|
|
SXUNUSED(pValue); /* cc warning */
|
|
if(pProbe->bList == 0) {
|
|
return PH7_OK;
|
|
}
|
|
if((pKey->nType & MEMOBJ_INT) == 0 || pKey->x.iVal != pProbe->iNext) {
|
|
pProbe->bList = 0;
|
|
} else {
|
|
pProbe->iNext++;
|
|
}
|
|
return PH7_OK;
|
|
}
|
|
/*
|
|
* Returns the JSON representation of a value.In other word perform a JSON encoding operation.
|
|
* According to wikipedia
|
|
* JSON's basic types are:
|
|
* Number (double precision floating-point format in JavaScript, generally depends on implementation)
|
|
* String (double-quoted Unicode, with backslash escaping)
|
|
* Boolean (true or false)
|
|
* Array (an ordered sequence of values, comma-separated and enclosed in square brackets; the values
|
|
* do not need to be of the same type)
|
|
* Object (an unordered collection of key:value pairs with the ':' character separating the key
|
|
* and the value, comma-separated and enclosed in curly braces; the keys must be strings and should
|
|
* be distinct from each other)
|
|
* null (empty)
|
|
* Non-significant white space may be added freely around the "structural characters"
|
|
* (i.e. the brackets "[{]}", colon ":" and comma ",").
|
|
*/
|
|
static sxi32 VmJsonEncode(
|
|
ph7_value *pIn, /* Encode this value */
|
|
json_private_data *pData /* Context data */
|
|
) {
|
|
ph7_context *pCtx = pData->pCtx;
|
|
int iFlags = pData->iFlags;
|
|
int nByte;
|
|
if(ph7_value_is_array(pIn)) {
|
|
struct json_array_probe sProbe;
|
|
sxi32 bObject, isFirst, bOldObject;
|
|
if(pData->nRecCount >= JSON_MAX_DEPTH) {
|
|
/* Nesting limit reached, abort encoding immediately */
|
|
pCtx->pVm->json_rc = JSON_ERROR_DEPTH;
|
|
pData->bAbort = 1;
|
|
return PH7_OK;
|
|
}
|
|
/* A hashmap is an array only when all keys are 0..n-1 in iteration order */
|
|
sProbe.iNext = 0;
|
|
sProbe.bList = 1;
|
|
ph7_array_walk(pIn, VmJsonArrayProbe, &sProbe);
|
|
bObject = (iFlags & JSON_FORCE_OBJECT) != 0 || sProbe.bList == 0;
|
|
isFirst = pData->isFirst;
|
|
bOldObject = pData->bObject;
|
|
pData->isFirst = 1;
|
|
pData->bObject = bObject;
|
|
/* Append the opening square bracket or curly braces */
|
|
ph7_result_string(pCtx, bObject ? "{" : "[", (int)sizeof(char));
|
|
pData->nRecCount++;
|
|
ph7_array_walk(pIn, VmJsonArrayEncode, pData);
|
|
pData->nRecCount--;
|
|
if(pData->bAbort == 0) {
|
|
/* Append the closing square bracket or curly braces */
|
|
ph7_result_string(pCtx, bObject ? "}" : "]", (int)sizeof(char));
|
|
}
|
|
pData->isFirst = isFirst;
|
|
pData->bObject = bOldObject;
|
|
} else if(ph7_value_is_object(pIn)) {
|
|
if(pData->nRecCount >= JSON_MAX_DEPTH) {
|
|
/* Nesting limit reached, abort encoding immediately */
|
|
pCtx->pVm->json_rc = JSON_ERROR_DEPTH;
|
|
pData->bAbort = 1;
|
|
return PH7_OK;
|
|
}
|
|
if(pIn->x.pOther == 0) {
|
|
/* Object-typed NULL value, encode it as an empty object */
|
|
ph7_result_string(pCtx, "{}", (int)sizeof("{}") - 1);
|
|
} else {
|
|
sxi32 isFirst = pData->isFirst;
|
|
sxi32 bOldObject = pData->bObject;
|
|
pData->isFirst = 1;
|
|
pData->bObject = 1;
|
|
/* Append the curly braces */
|
|
ph7_result_string(pCtx, "{", (int)sizeof(char));
|
|
pData->nRecCount++;
|
|
ph7_object_walk(pIn, VmJsonObjectEncode, pData);
|
|
pData->nRecCount--;
|
|
if(pData->bAbort == 0) {
|
|
/* Append the closing curly braces */
|
|
ph7_result_string(pCtx, "}", (int)sizeof(char));
|
|
}
|
|
pData->isFirst = isFirst;
|
|
pData->bObject = bOldObject;
|
|
}
|
|
} else if(ph7_value_is_void(pIn) || ph7_value_is_resource(pIn) || (pIn->nType & MEMOBJ_NULL)) {
|
|
/* null */
|
|
ph7_result_string(pCtx, "null", (int)sizeof("null") - 1);
|
|
} else if(ph7_value_is_bool(pIn)) {
|
|
int iBool = ph7_value_to_bool(pIn);
|
|
int iLen;
|
|
/* true/false */
|
|
iLen = iBool ? (int)sizeof("true") : (int)sizeof("false");
|
|
ph7_result_string(pCtx, iBool ? "true" : "false", iLen - 1);
|
|
} else if(pIn->nType & MEMOBJ_INT) {
|
|
char zBuf[32];
|
|
snprintf(zBuf, sizeof(zBuf), "%lld", (long long)pIn->x.iVal);
|
|
ph7_result_string(pCtx, zBuf, (int)SyStrlen(zBuf));
|
|
} else if(pIn->nType & MEMOBJ_REAL) {
|
|
char zBuf[64];
|
|
#ifdef __WINNT__
|
|
if(_finite((double)pIn->x.rVal) == 0) {
|
|
#else
|
|
if(isfinite((double)pIn->x.rVal) == 0) {
|
|
#endif
|
|
/* JSON has no representation for INF or NAN */
|
|
pCtx->pVm->json_rc = JSON_ERROR_SYNTAX;
|
|
pData->bAbort = 1;
|
|
return PH7_OK;
|
|
}
|
|
if(VmJsonFormatReal(zBuf, (sxu32)sizeof(zBuf), pIn->x.rVal) != SXRET_OK) {
|
|
pCtx->pVm->json_rc = JSON_ERROR_SYNTAX;
|
|
pData->bAbort = 1;
|
|
return PH7_OK;
|
|
}
|
|
ph7_result_string(pCtx, zBuf, (int)SyStrlen(zBuf));
|
|
} else if(ph7_value_is_string(pIn)) {
|
|
if((iFlags & JSON_NUMERIC_CHECK) && ph7_value_is_numeric(pIn)) {
|
|
const char *zNum;
|
|
/* Encodes numeric strings as numbers. */
|
|
PH7_MemObjToReal(pIn); /* Force a numeric cast */
|
|
/* Get a string representation of the number */
|
|
zNum = ph7_value_to_string(pIn, &nByte);
|
|
ph7_result_string(pCtx, zNum, nByte);
|
|
} else {
|
|
/* Encode the string */
|
|
const char *zStr = ph7_value_to_string(pIn, &nByte);
|
|
VmJsonAppendString(pCtx, zStr, nByte, iFlags);
|
|
}
|
|
} else {
|
|
/* Unsupported value, encode it as null */
|
|
ph7_result_string(pCtx, "null", (int)sizeof("null") - 1);
|
|
}
|
|
/* All done */
|
|
return PH7_OK;
|
|
}
|
|
/*
|
|
* The following walker callback is invoked each time we need
|
|
* to encode an array to JSON.
|
|
*/
|
|
static int VmJsonArrayEncode(ph7_value *pKey, ph7_value *pValue, void *pUserData) {
|
|
json_private_data *pJson = (json_private_data *)pUserData;
|
|
if(!pJson->isFirst) {
|
|
/* Append the colon first */
|
|
ph7_result_string(pJson->pCtx, ",", (int)sizeof(char));
|
|
}
|
|
if(pJson->bObject) {
|
|
/* Outputs an object rather than an array */
|
|
const char *zKey;
|
|
int nByte;
|
|
/* Extract a string representation of the key */
|
|
zKey = ph7_value_to_string(pKey, &nByte);
|
|
/* Append the escaped key and the colon */
|
|
VmJsonAppendString(pJson->pCtx, zKey, nByte, pJson->iFlags);
|
|
ph7_result_string(pJson->pCtx, ":", (int)sizeof(char));
|
|
}
|
|
/* Encode the value */
|
|
VmJsonEncode(pValue, pJson);
|
|
pJson->isFirst = 0;
|
|
if(pJson->bAbort) {
|
|
return PH7_ABORT;
|
|
}
|
|
return PH7_OK;
|
|
}
|
|
/*
|
|
* The following walker callback is invoked each time we need to encode
|
|
* a class instance [i.e: Object in the PHP jargon] to JSON.
|
|
*/
|
|
static int VmJsonObjectEncode(const char *zAttr, ph7_value *pValue, void *pUserData) {
|
|
json_private_data *pJson = (json_private_data *)pUserData;
|
|
if(!pJson->isFirst) {
|
|
/* Append the colon first */
|
|
ph7_result_string(pJson->pCtx, ",", (int)sizeof(char));
|
|
}
|
|
/* Append the escaped attribute name and the colon first */
|
|
VmJsonAppendString(pJson->pCtx, zAttr, (int)SyStrlen(zAttr), pJson->iFlags);
|
|
ph7_result_string(pJson->pCtx, ":", (int)sizeof(char));
|
|
/* Encode the value */
|
|
VmJsonEncode(pValue, pJson);
|
|
pJson->isFirst = 0;
|
|
if(pJson->bAbort) {
|
|
return PH7_ABORT;
|
|
}
|
|
return PH7_OK;
|
|
}
|
|
/*
|
|
* string json_encode(mixed $value [, int $options = 0 ])
|
|
* Returns a string containing the JSON representation of value.
|
|
* Parameters
|
|
* $value
|
|
* The value being encoded. Can be any type except a resource.
|
|
* $options
|
|
* Bitmask consisting of:
|
|
* JSON_HEX_TAG All < and > are converted to \u003C and \u003E.
|
|
* JSON_HEX_AMP All &s are converted to \u0026.
|
|
* JSON_HEX_APOS All ' are converted to \u0027.
|
|
* JSON_HEX_QUOT All " are converted to \u0022.
|
|
* JSON_FORCE_OBJECT Outputs an object rather than an array.
|
|
* JSON_NUMERIC_CHECK Encodes numeric strings as numbers.
|
|
* JSON_BIGINT_AS_STRING Not used
|
|
* JSON_PRETTY_PRINT Use whitespace in returned data to format it.
|
|
* JSON_UNESCAPED_SLASHES Don't escape '/'
|
|
* JSON_UNESCAPED_UNICODE Not used.
|
|
* Return
|
|
* Returns a JSON encoded string on success. FALSE otherwise
|
|
*/
|
|
static int vm_builtin_json_encode(ph7_context *pCtx, int nArg, ph7_value **apArg) {
|
|
json_private_data sJson;
|
|
ph7_vm *pVm = pCtx->pVm;
|
|
/* Clear the JSON error code */
|
|
pVm->json_rc = JSON_ERROR_NONE;
|
|
if(nArg < 1) {
|
|
/* Missing arguments, return FALSE */
|
|
ph7_result_bool(pCtx, 0);
|
|
pVm->json_rc = JSON_ERROR_SYNTAX;
|
|
return PH7_OK;
|
|
}
|
|
/* Prepare the JSON data */
|
|
sJson.nRecCount = 0;
|
|
sJson.pCtx = pCtx;
|
|
sJson.isFirst = 1;
|
|
sJson.iFlags = 0;
|
|
sJson.bObject = 0;
|
|
sJson.bAbort = 0;
|
|
if(nArg > 1 && ph7_value_is_int(apArg[1])) {
|
|
/* Extract option flags */
|
|
sJson.iFlags = ph7_value_to_int(apArg[1]);
|
|
}
|
|
/* Perform the encoding operation */
|
|
VmJsonEncode(apArg[0], &sJson);
|
|
if(sJson.bAbort) {
|
|
/* Discard any partial output and return FALSE */
|
|
if(pVm->json_rc == JSON_ERROR_NONE) {
|
|
pVm->json_rc = JSON_ERROR_DEPTH;
|
|
}
|
|
ph7_result_bool(pCtx, 0);
|
|
} else {
|
|
pVm->json_rc = JSON_ERROR_NONE;
|
|
}
|
|
/* All done */
|
|
return PH7_OK;
|
|
}
|
|
/*
|
|
* int json_last_error(void)
|
|
* Returns the last error (if any) occurred during the last JSON encoding/decoding.
|
|
* Parameters
|
|
* None
|
|
* Return
|
|
* Returns an integer, the value can be one of the following constants:
|
|
* JSON_ERROR_NONE No error has occurred.
|
|
* JSON_ERROR_DEPTH The maximum stack depth has been exceeded.
|
|
* JSON_ERROR_STATE_MISMATCH Invalid or malformed JSON.
|
|
* JSON_ERROR_CTRL_CHAR Control character error, possibly incorrectly encoded.
|
|
* JSON_ERROR_SYNTAX Syntax error.
|
|
* JSON_ERROR_UTF8_CHECK Malformed UTF-8 characters.
|
|
*/
|
|
static int vm_builtin_json_last_error(ph7_context *pCtx, int nArg, ph7_value **apArg) {
|
|
ph7_vm *pVm = pCtx->pVm;
|
|
/* Return the error code */
|
|
ph7_result_int(pCtx, pVm->json_rc);
|
|
SXUNUSED(nArg); /* cc warning */
|
|
SXUNUSED(apArg);
|
|
return PH7_OK;
|
|
}
|
|
/*
|
|
* Return TRUE if the given character is an ASCII digit.
|
|
*/
|
|
static sxi32 VmJsonIsDigit(sxi32 c) {
|
|
return c >= '0' && c <= '9';
|
|
}
|
|
/*
|
|
* Validate and consume a JSON number from the stream.
|
|
* The JSON grammar allows an optional minus sign, no leading zeros,
|
|
* an optional fraction and an optional exponent.
|
|
*/
|
|
static sxi32 VmJsonScanNumber(SyStream *pStream) {
|
|
const unsigned char *zText = pStream->zText;
|
|
const unsigned char *zEnd = pStream->zEnd;
|
|
if(zText < zEnd && zText[0] == '-') {
|
|
zText++;
|
|
}
|
|
if(zText >= zEnd || VmJsonIsDigit(zText[0]) == 0) {
|
|
return SXERR_SYNTAX;
|
|
}
|
|
if(zText[0] == '0') {
|
|
zText++;
|
|
if(zText < zEnd && VmJsonIsDigit(zText[0])) {
|
|
/* Leading zeros are not allowed */
|
|
return SXERR_SYNTAX;
|
|
}
|
|
} else {
|
|
while(zText < zEnd && VmJsonIsDigit(zText[0])) {
|
|
zText++;
|
|
}
|
|
}
|
|
if(zText < zEnd && zText[0] == '.') {
|
|
zText++;
|
|
if(zText >= zEnd || VmJsonIsDigit(zText[0]) == 0) {
|
|
/* Missing fractional digits */
|
|
return SXERR_SYNTAX;
|
|
}
|
|
while(zText < zEnd && VmJsonIsDigit(zText[0])) {
|
|
zText++;
|
|
}
|
|
}
|
|
if(zText < zEnd && (zText[0] == 'e' || zText[0] == 'E')) {
|
|
zText++;
|
|
if(zText < zEnd && (zText[0] == '+' || zText[0] == '-')) {
|
|
zText++;
|
|
}
|
|
if(zText >= zEnd || VmJsonIsDigit(zText[0]) == 0) {
|
|
/* Missing exponent digits */
|
|
return SXERR_SYNTAX;
|
|
}
|
|
while(zText < zEnd && VmJsonIsDigit(zText[0])) {
|
|
zText++;
|
|
}
|
|
}
|
|
pStream->zText = zText;
|
|
return SXRET_OK;
|
|
}
|
|
/*
|
|
* Tokenize an entire JSON input.
|
|
* Get a single low-level token from the input file.
|
|
* Update the stream pointer so that it points to the first
|
|
* character beyond the extracted token.
|
|
*/
|
|
static sxi32 VmJsonTokenize(SyStream *pStream, SyToken *pToken, void *pUserData, void *pCtxData) {
|
|
int *pJsonErr = (int *)pUserData;
|
|
SyString *pStr;
|
|
int c;
|
|
SXUNUSED(pCtxData); /* cc warning */
|
|
/* Ignore leading JSON white spaces only: space, tab, carriage return and line feed */
|
|
while(pStream->zText < pStream->zEnd) {
|
|
c = pStream->zText[0];
|
|
if(c == ' ' || c == '\t' || c == '\r') {
|
|
pStream->zText++;
|
|
} else if(c == '\n') {
|
|
/* Update line counter */
|
|
pStream->nLine++;
|
|
pStream->zText++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if(pStream->zText >= pStream->zEnd) {
|
|
/* End of input reached */
|
|
return SXERR_EOF;
|
|
}
|
|
/* Record token starting position and line */
|
|
pToken->nLine = pStream->nLine;
|
|
pToken->pUserData = 0;
|
|
pStr = &pToken->sData;
|
|
SyStringInitFromBuf(pStr, pStream->zText, 0);
|
|
if(pStream->zText[0] == '{' || pStream->zText[0] == '[' || pStream->zText[0] == '}' || pStream->zText[0] == ']'
|
|
|| pStream->zText[0] == ':' || pStream->zText[0] == ',') {
|
|
/* Single character */
|
|
c = pStream->zText[0];
|
|
/* Set token type */
|
|
switch(c) {
|
|
case '[':
|
|
pToken->nType = JSON_TK_OSB;
|
|
break;
|
|
case '{':
|
|
pToken->nType = JSON_TK_OCB;
|
|
break;
|
|
case '}':
|
|
pToken->nType = JSON_TK_CCB;
|
|
break;
|
|
case ']':
|
|
pToken->nType = JSON_TK_CSB;
|
|
break;
|
|
case ':':
|
|
pToken->nType = JSON_TK_COLON;
|
|
break;
|
|
case ',':
|
|
pToken->nType = JSON_TK_COMMA;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
/* Advance the stream cursor */
|
|
pStream->zText++;
|
|
} else if(pStream->zText[0] == '"') {
|
|
/* JSON string, skip the opening double quote */
|
|
pStream->zText++;
|
|
pStr->zString = (const char *)pStream->zText;
|
|
while(pStream->zText < pStream->zEnd) {
|
|
c = pStream->zText[0];
|
|
if(c == '"') {
|
|
/* Closing double quote */
|
|
break;
|
|
}
|
|
if((unsigned char)c < 0x20) {
|
|
/* Unescaped control character */
|
|
*pJsonErr = JSON_ERROR_CTRL_CHAR;
|
|
return SXERR_ABORT;
|
|
}
|
|
if(c == '\\') {
|
|
/* Skip the escaped character (including \" and \\) */
|
|
pStream->zText++;
|
|
if(pStream->zText >= pStream->zEnd) {
|
|
*pJsonErr = JSON_ERROR_SYNTAX;
|
|
return SXERR_ABORT;
|
|
}
|
|
}
|
|
pStream->zText++;
|
|
}
|
|
if(pStream->zText >= pStream->zEnd) {
|
|
/* Missing closing '"' */
|
|
*pJsonErr = JSON_ERROR_SYNTAX;
|
|
return SXERR_ABORT;
|
|
}
|
|
pToken->nType = JSON_TK_STR;
|
|
pStream->zText++; /* Jump the closing double quote */
|
|
} else if(pStream->zText[0] == '-' || VmJsonIsDigit(pStream->zText[0])) {
|
|
/* Number */
|
|
if(VmJsonScanNumber(pStream) != SXRET_OK) {
|
|
*pJsonErr = JSON_ERROR_SYNTAX;
|
|
return SXERR_ABORT;
|
|
}
|
|
pToken->nType = JSON_TK_NUM;
|
|
} else if(XLEX_IN_LEN(pStream) >= sizeof("true") - 1 &&
|
|
SyStrncmp((const char *)pStream->zText, "true", sizeof("true") - 1) == 0) {
|
|
/* boolean true */
|
|
pToken->nType = JSON_TK_TRUE;
|
|
/* Advance the stream cursor */
|
|
pStream->zText += sizeof("true") - 1;
|
|
} else if(XLEX_IN_LEN(pStream) >= sizeof("false") - 1 &&
|
|
SyStrncmp((const char *)pStream->zText, "false", sizeof("false") - 1) == 0) {
|
|
/* boolean false */
|
|
pToken->nType = JSON_TK_FALSE;
|
|
/* Advance the stream cursor */
|
|
pStream->zText += sizeof("false") - 1;
|
|
} else if(XLEX_IN_LEN(pStream) >= sizeof("null") - 1 &&
|
|
SyStrncmp((const char *)pStream->zText, "null", sizeof("null") - 1) == 0) {
|
|
/* NULL */
|
|
pToken->nType = JSON_TK_NULL;
|
|
/* Advance the stream cursor */
|
|
pStream->zText += sizeof("null") - 1;
|
|
} else {
|
|
/* Unexpected token */
|
|
pToken->nType = JSON_TK_INVALID;
|
|
*pJsonErr = JSON_ERROR_SYNTAX;
|
|
/* Abort processing immediately */
|
|
return SXERR_ABORT;
|
|
}
|
|
/* record token length */
|
|
pStr->nByte = (sxu32)((const char *)pStream->zText - pStr->zString);
|
|
if(pToken->nType == JSON_TK_STR) {
|
|
pStr->nByte--;
|
|
}
|
|
/* Return to the lexer */
|
|
return SXRET_OK;
|
|
}
|
|
/*
|
|
* Parse exactly four hexadecimal digits.
|
|
* Return SXRET_OK and store the value on success, SXERR_SYNTAX otherwise.
|
|
*/
|
|
static sxi32 VmJsonHex4(const unsigned char *zIn, sxu32 *pVal) {
|
|
sxu32 iVal = 0;
|
|
sxu32 i;
|
|
sxi32 c;
|
|
for(i = 0; i < 4; ++i) {
|
|
c = SyHexToint(zIn[i]);
|
|
if(c < 0) {
|
|
return SXERR_SYNTAX;
|
|
}
|
|
iVal = (iVal << 4) | (sxu32)c;
|
|
}
|
|
*pVal = iVal;
|
|
return SXRET_OK;
|
|
}
|
|
/*
|
|
* Append a Unicode code point as UTF-8.
|
|
*/
|
|
static void VmJsonAppendCodePoint(ph7_value *pWorker, sxu32 iCode) {
|
|
char zBuf[4];
|
|
sxu32 nByte;
|
|
if(iCode < 0x80) {
|
|
zBuf[0] = (char)iCode;
|
|
nByte = 1;
|
|
} else if(iCode < 0x800) {
|
|
zBuf[0] = (char)(0xC0 | (iCode >> 6));
|
|
zBuf[1] = (char)(0x80 | (iCode & 0x3F));
|
|
nByte = 2;
|
|
} else if(iCode < 0x10000) {
|
|
zBuf[0] = (char)(0xE0 | (iCode >> 12));
|
|
zBuf[1] = (char)(0x80 | ((iCode >> 6) & 0x3F));
|
|
zBuf[2] = (char)(0x80 | (iCode & 0x3F));
|
|
nByte = 3;
|
|
} else {
|
|
zBuf[0] = (char)(0xF0 | (iCode >> 18));
|
|
zBuf[1] = (char)(0x80 | ((iCode >> 12) & 0x3F));
|
|
zBuf[2] = (char)(0x80 | ((iCode >> 6) & 0x3F));
|
|
zBuf[3] = (char)(0x80 | (iCode & 0x3F));
|
|
nByte = 4;
|
|
}
|
|
ph7_value_string(pWorker, zBuf, nByte);
|
|
}
|
|
/*
|
|
* Dequote [i.e: Resolve all backslash escapes ] a JSON string and store
|
|
* the result in the given ph7_value.
|
|
*/
|
|
static sxi32 VmJsonDequoteString(const SyString *pStr, ph7_value *pWorker, int *pErr) {
|
|
const unsigned char *zIn = (const unsigned char *)pStr->zString;
|
|
const unsigned char *zEnd = &zIn[pStr->nByte];
|
|
const unsigned char *zCur;
|
|
sxu32 iCode, iLow;
|
|
int c;
|
|
/* Mark the value as a string */
|
|
ph7_value_string(pWorker, "", 0); /* Empty string */
|
|
while(zIn < zEnd) {
|
|
if(zIn[0] == '\\') {
|
|
zIn++;
|
|
if(zIn >= zEnd) {
|
|
/* Missing escaped character */
|
|
*pErr = JSON_ERROR_SYNTAX;
|
|
return SXERR_ABORT;
|
|
}
|
|
c = zIn[0];
|
|
zIn++;
|
|
switch(c) {
|
|
case '"':
|
|
ph7_value_string(pWorker, "\"", (int)sizeof(char));
|
|
break;
|
|
case '\\':
|
|
ph7_value_string(pWorker, "\\", (int)sizeof(char));
|
|
break;
|
|
case '/':
|
|
ph7_value_string(pWorker, "/", (int)sizeof(char));
|
|
break;
|
|
case 'b':
|
|
ph7_value_string(pWorker, "\b", (int)sizeof(char));
|
|
break;
|
|
case 'f':
|
|
ph7_value_string(pWorker, "\f", (int)sizeof(char));
|
|
break;
|
|
case 'n':
|
|
ph7_value_string(pWorker, "\n", (int)sizeof(char));
|
|
break;
|
|
case 'r':
|
|
ph7_value_string(pWorker, "\r", (int)sizeof(char));
|
|
break;
|
|
case 't':
|
|
ph7_value_string(pWorker, "\t", (int)sizeof(char));
|
|
break;
|
|
case 'u':
|
|
if((zEnd - zIn) < 4 || VmJsonHex4(zIn, &iCode) != SXRET_OK) {
|
|
*pErr = JSON_ERROR_SYNTAX;
|
|
return SXERR_ABORT;
|
|
}
|
|
zIn += 4;
|
|
if(iCode >= 0xD800 && iCode <= 0xDBFF) {
|
|
/* High surrogate, a low surrogate must follow */
|
|
if((zEnd - zIn) < 6 || zIn[0] != '\\' || zIn[1] != 'u'
|
|
|| VmJsonHex4(&zIn[2], &iLow) != SXRET_OK
|
|
|| iLow < 0xDC00 || iLow > 0xDFFF) {
|
|
*pErr = JSON_ERROR_SYNTAX;
|
|
return SXERR_ABORT;
|
|
}
|
|
zIn += 6;
|
|
iCode = 0x10000 + ((iCode - 0xD800) << 10) + (iLow - 0xDC00);
|
|
} else if(iCode >= 0xDC00 && iCode <= 0xDFFF) {
|
|
/* Lone low surrogate */
|
|
*pErr = JSON_ERROR_SYNTAX;
|
|
return SXERR_ABORT;
|
|
}
|
|
VmJsonAppendCodePoint(pWorker, iCode);
|
|
break;
|
|
default:
|
|
/* Unknown escape sequence */
|
|
*pErr = JSON_ERROR_SYNTAX;
|
|
return SXERR_ABORT;
|
|
}
|
|
} else if(zIn[0] < 0x20) {
|
|
/* Unescaped control character */
|
|
*pErr = JSON_ERROR_CTRL_CHAR;
|
|
return SXERR_ABORT;
|
|
} else {
|
|
zCur = zIn;
|
|
while(zIn < zEnd && zIn[0] != '\\' && zIn[0] >= 0x20) {
|
|
zIn++;
|
|
}
|
|
/* Append chunk verbatim */
|
|
ph7_value_string(pWorker, (const char *)zCur, (int)(zIn - zCur));
|
|
}
|
|
}
|
|
return SXRET_OK;
|
|
}
|
|
/*
|
|
* Decode a JSON numeric token.
|
|
* Integers are decoded exactly when they fit in a signed 64-bit integer.
|
|
* Out of range integers are converted to doubles, or kept as strings when
|
|
* the JSON_BIGINT_AS_STRING option is set. Numeric literals that overflow
|
|
* to a non-finite double are rejected as syntax errors.
|
|
*/
|
|
static sxi32 VmJsonDecodeNumber(const SyString *pStr, ph7_value *pWorker, sxi32 iFlags, int *pErr) {
|
|
const char *zNum = pStr->zString;
|
|
sxu32 nByte = pStr->nByte;
|
|
sxu32 i;
|
|
sxi32 bReal = 0;
|
|
/* Check for a fraction or an exponent */
|
|
for(i = 0; i < nByte; ++i) {
|
|
if(zNum[i] == '.' || zNum[i] == 'e' || zNum[i] == 'E') {
|
|
bReal = 1;
|
|
break;
|
|
}
|
|
}
|
|
if(bReal == 0) {
|
|
sxi32 bNeg = 0, bOverflow = 0;
|
|
sxu64 uVal = 0, uLimit;
|
|
sxi64 iVal;
|
|
i = 0;
|
|
if(zNum[0] == '-') {
|
|
bNeg = 1;
|
|
i++;
|
|
}
|
|
uLimit = bNeg ? 9223372036854775808ULL : 9223372036854775807ULL;
|
|
for(; i < nByte; ++i) {
|
|
sxu32 iDigit = (sxu32)(zNum[i] - '0');
|
|
if(uVal > (uLimit - iDigit) / 10) {
|
|
bOverflow = 1;
|
|
break;
|
|
}
|
|
uVal = uVal * 10 + iDigit;
|
|
}
|
|
if(bOverflow == 0) {
|
|
if(bNeg) {
|
|
if(uVal == 9223372036854775808ULL) {
|
|
iVal = (-9223372036854775807LL - 1);
|
|
} else {
|
|
iVal = -(sxi64)uVal;
|
|
}
|
|
} else {
|
|
iVal = (sxi64)uVal;
|
|
}
|
|
ph7_value_int64(pWorker, iVal);
|
|
return SXRET_OK;
|
|
}
|
|
if(iFlags & JSON_BIGINT_AS_STRING) {
|
|
/* Keep the exact integer representation */
|
|
ph7_value_string(pWorker, zNum, (int)nByte);
|
|
return SXRET_OK;
|
|
}
|
|
}
|
|
{
|
|
char *zEnd;
|
|
double rVal;
|
|
#ifdef __WINNT__
|
|
_locale_t pLocale = _create_locale(LC_NUMERIC, "C");
|
|
if(pLocale == (_locale_t)0) {
|
|
#else
|
|
locale_t pLocale = newlocale(LC_NUMERIC_MASK, "C", (locale_t)0);
|
|
if(pLocale == (locale_t)0) {
|
|
#endif
|
|
*pErr = JSON_ERROR_SYNTAX;
|
|
return SXERR_ABORT;
|
|
}
|
|
#ifdef __WINNT__
|
|
rVal = _strtod_l(zNum, &zEnd, pLocale);
|
|
_free_locale(pLocale);
|
|
if(zEnd != &zNum[nByte] || _finite(rVal) == 0) {
|
|
#else
|
|
rVal = strtod_l(zNum, &zEnd, pLocale);
|
|
freelocale(pLocale);
|
|
if(zEnd != &zNum[nByte] || isfinite(rVal) == 0) {
|
|
#endif
|
|
/* Malformed number or a value that overflows to INF */
|
|
*pErr = JSON_ERROR_SYNTAX;
|
|
return SXERR_ABORT;
|
|
}
|
|
ph7_value_double(pWorker, rVal);
|
|
}
|
|
return SXRET_OK;
|
|
}
|
|
/*
|
|
* Returns a ph7_value holding the image of a JSON string. In other word perform a JSON decoding operation.
|
|
* According to wikipedia
|
|
* JSON's basic types are:
|
|
* Number (double precision floating-point format in JavaScript, generally depends on implementation)
|
|
* String (double-quoted Unicode, with backslash escaping)
|
|
* Boolean (true or false)
|
|
* Array (an ordered sequence of values, comma-separated and enclosed in square brackets; the values
|
|
* do not need to be of the same type)
|
|
* Object (an unordered collection of key:value pairs with the ':' character separating the key
|
|
* and the value, comma-separated and enclosed in curly braces; the keys must be strings and should
|
|
* be distinct from each other)
|
|
* null (empty)
|
|
* Non-significant white space may be added freely around the "structural characters" which are the brackets "[{]}", colon ":" and comma ",".
|
|
*/
|
|
static sxi32 VmJsonDecode(
|
|
json_decoder *pDecoder, /* JSON decoder */
|
|
ph7_value *pArrayKey /* Key for the decoded array */
|
|
) {
|
|
ph7_value *pWorker; /* Worker variable */
|
|
sxi32 rc;
|
|
if(pDecoder->pIn >= pDecoder->pEnd) {
|
|
/* Unexpected end of input */
|
|
*pDecoder->pErr = JSON_ERROR_SYNTAX;
|
|
return SXERR_ABORT;
|
|
}
|
|
if(pDecoder->pIn->nType & (JSON_TK_STR | JSON_TK_TRUE | JSON_TK_FALSE | JSON_TK_NULL | JSON_TK_NUM)) {
|
|
/* Scalar value */
|
|
pWorker = ph7_context_new_scalar(pDecoder->pCtx);
|
|
if(pWorker == 0) {
|
|
*pDecoder->pErr = JSON_ERROR_STATE_MISMATCH;
|
|
PH7_VmMemoryError(pDecoder->pCtx->pVm);
|
|
/* Abort the decoding operation immediately */
|
|
return SXERR_ABORT;
|
|
}
|
|
/* Reflect the JSON image */
|
|
if(pDecoder->pIn->nType & JSON_TK_NULL) {
|
|
/* Nullify the value.*/
|
|
ph7_value_void(pWorker);
|
|
} else if(pDecoder->pIn->nType & (JSON_TK_TRUE | JSON_TK_FALSE)) {
|
|
/* Boolean value */
|
|
ph7_value_bool(pWorker, (pDecoder->pIn->nType & JSON_TK_TRUE) ? 1 : 0);
|
|
} else if(pDecoder->pIn->nType & JSON_TK_NUM) {
|
|
/* Numeric value */
|
|
rc = VmJsonDecodeNumber(&pDecoder->pIn->sData, pWorker, pDecoder->iFlags, pDecoder->pErr);
|
|
if(rc == SXERR_ABORT) {
|
|
ph7_context_release_value(pDecoder->pCtx, pWorker);
|
|
return SXERR_ABORT;
|
|
}
|
|
} else {
|
|
/* Dequote the string */
|
|
rc = VmJsonDequoteString(&pDecoder->pIn->sData, pWorker, pDecoder->pErr);
|
|
if(rc == SXERR_ABORT) {
|
|
ph7_context_release_value(pDecoder->pCtx, pWorker);
|
|
return SXERR_ABORT;
|
|
}
|
|
}
|
|
/* Invoke the consumer callback */
|
|
rc = pDecoder->xConsumer(pDecoder->pCtx, pArrayKey, pWorker, pDecoder->pUserData);
|
|
/* Release the worker variable */
|
|
ph7_context_release_value(pDecoder->pCtx, pWorker);
|
|
if(rc == SXERR_ABORT) {
|
|
return SXERR_ABORT;
|
|
}
|
|
/* All done,advance the stream cursor */
|
|
pDecoder->pIn++;
|
|
} else if(pDecoder->pIn->nType & JSON_TK_OSB /*'[' */) {
|
|
ProcJsonConsumer xOld;
|
|
void *pOld;
|
|
/* Check if we do not nest too much */
|
|
if(pDecoder->rec_count >= pDecoder->rec_depth) {
|
|
/* Nesting limit reached,abort decoding immediately */
|
|
*pDecoder->pErr = JSON_ERROR_DEPTH;
|
|
return SXERR_ABORT;
|
|
}
|
|
/* Array representation*/
|
|
pDecoder->pIn++;
|
|
/* Create a working array */
|
|
pWorker = ph7_context_new_array(pDecoder->pCtx);
|
|
if(pWorker == 0) {
|
|
*pDecoder->pErr = JSON_ERROR_STATE_MISMATCH;
|
|
PH7_VmMemoryError(pDecoder->pCtx->pVm);
|
|
/* Abort the decoding operation immediately */
|
|
return SXERR_ABORT;
|
|
}
|
|
/* Save the old consumer */
|
|
xOld = pDecoder->xConsumer;
|
|
pOld = pDecoder->pUserData;
|
|
/* Set the new consumer */
|
|
pDecoder->xConsumer = VmJsonArrayDecoder;
|
|
pDecoder->pUserData = pWorker;
|
|
pDecoder->rec_count++;
|
|
if(pDecoder->pIn < pDecoder->pEnd && (pDecoder->pIn->nType & JSON_TK_CSB) /*']'*/) {
|
|
/* Empty array */
|
|
pDecoder->pIn++;
|
|
} else {
|
|
for(;;) {
|
|
/* Decode an array entry */
|
|
rc = VmJsonDecode(pDecoder, 0);
|
|
if(rc == SXERR_ABORT) {
|
|
goto ArrayAbort;
|
|
}
|
|
if(pDecoder->pIn >= pDecoder->pEnd) {
|
|
/* Missing closing ']' */
|
|
*pDecoder->pErr = JSON_ERROR_SYNTAX;
|
|
goto ArrayAbort;
|
|
}
|
|
if(pDecoder->pIn->nType & JSON_TK_COMMA) {
|
|
/* Another entry must follow */
|
|
pDecoder->pIn++;
|
|
if(pDecoder->pIn >= pDecoder->pEnd || (pDecoder->pIn->nType & JSON_TK_CSB)) {
|
|
*pDecoder->pErr = JSON_ERROR_SYNTAX;
|
|
goto ArrayAbort;
|
|
}
|
|
continue;
|
|
}
|
|
if(pDecoder->pIn->nType & JSON_TK_CSB) {
|
|
/* Jump the closing ']' */
|
|
pDecoder->pIn++;
|
|
break;
|
|
}
|
|
/* Unexpected token */
|
|
*pDecoder->pErr = JSON_ERROR_SYNTAX;
|
|
goto ArrayAbort;
|
|
}
|
|
}
|
|
pDecoder->rec_count--;
|
|
/* Restore the old consumer */
|
|
pDecoder->xConsumer = xOld;
|
|
pDecoder->pUserData = pOld;
|
|
/* Invoke the old consumer on the decoded array */
|
|
xOld(pDecoder->pCtx, pArrayKey, pWorker, pOld);
|
|
/* Release the worker variable */
|
|
ph7_context_release_value(pDecoder->pCtx, pWorker);
|
|
return SXRET_OK;
|
|
ArrayAbort:
|
|
pDecoder->rec_count--;
|
|
pDecoder->xConsumer = xOld;
|
|
pDecoder->pUserData = pOld;
|
|
ph7_context_release_value(pDecoder->pCtx, pWorker);
|
|
return SXERR_ABORT;
|
|
} else if(pDecoder->pIn->nType & JSON_TK_OCB /*'{' */) {
|
|
ProcJsonConsumer xOld;
|
|
ph7_value *pKey;
|
|
void *pOld;
|
|
/* Check if we do not nest too much */
|
|
if(pDecoder->rec_count >= pDecoder->rec_depth) {
|
|
/* Nesting limit reached,abort decoding immediately */
|
|
*pDecoder->pErr = JSON_ERROR_DEPTH;
|
|
return SXERR_ABORT;
|
|
}
|
|
/* Object representation*/
|
|
pDecoder->pIn++;
|
|
/* Return the object as an associative array */
|
|
if((pDecoder->iFlags & JSON_DECODE_ASSOC) == 0) {
|
|
PH7_VmThrowError(pDecoder->pCtx->pVm, PH7_CTX_WARNING,
|
|
"JSON Objects are always returned as an associative array"
|
|
);
|
|
}
|
|
/* Create a working array and a key */
|
|
pWorker = ph7_context_new_array(pDecoder->pCtx);
|
|
pKey = ph7_context_new_scalar(pDecoder->pCtx);
|
|
if(pWorker == 0 || pKey == 0) {
|
|
*pDecoder->pErr = JSON_ERROR_STATE_MISMATCH;
|
|
if(pWorker) {
|
|
ph7_context_release_value(pDecoder->pCtx, pWorker);
|
|
}
|
|
if(pKey) {
|
|
ph7_context_release_value(pDecoder->pCtx, pKey);
|
|
}
|
|
PH7_VmMemoryError(pDecoder->pCtx->pVm);
|
|
/* Abort the decoding operation immediately */
|
|
return SXERR_ABORT;
|
|
}
|
|
/* Save the old consumer */
|
|
xOld = pDecoder->xConsumer;
|
|
pOld = pDecoder->pUserData;
|
|
/* Set the new consumer */
|
|
pDecoder->xConsumer = VmJsonArrayDecoder;
|
|
pDecoder->pUserData = pWorker;
|
|
pDecoder->rec_count++;
|
|
if(pDecoder->pIn < pDecoder->pEnd && (pDecoder->pIn->nType & JSON_TK_CCB) /*'}'*/) {
|
|
/* Empty object */
|
|
pDecoder->pIn++;
|
|
} else {
|
|
for(;;) {
|
|
if(pDecoder->pIn >= pDecoder->pEnd || (pDecoder->pIn->nType & JSON_TK_STR) == 0
|
|
|| &pDecoder->pIn[1] >= pDecoder->pEnd
|
|
|| (pDecoder->pIn[1].nType & JSON_TK_COLON) == 0) {
|
|
/* Missing key or colon */
|
|
*pDecoder->pErr = JSON_ERROR_SYNTAX;
|
|
goto ObjectAbort;
|
|
}
|
|
/* Dequote the key */
|
|
rc = VmJsonDequoteString(&pDecoder->pIn->sData, pKey, pDecoder->pErr);
|
|
if(rc == SXERR_ABORT) {
|
|
goto ObjectAbort;
|
|
}
|
|
/* Jump the key and the colon */
|
|
pDecoder->pIn += 2;
|
|
if(pDecoder->pIn >= pDecoder->pEnd) {
|
|
/* Missing value */
|
|
*pDecoder->pErr = JSON_ERROR_SYNTAX;
|
|
goto ObjectAbort;
|
|
}
|
|
/* Recurse and decode the value */
|
|
rc = VmJsonDecode(pDecoder, pKey);
|
|
if(rc == SXERR_ABORT) {
|
|
goto ObjectAbort;
|
|
}
|
|
/* Reset the internal buffer of the key */
|
|
ph7_value_reset_string_cursor(pKey);
|
|
if(pDecoder->pIn >= pDecoder->pEnd) {
|
|
/* Missing closing '}' */
|
|
*pDecoder->pErr = JSON_ERROR_SYNTAX;
|
|
goto ObjectAbort;
|
|
}
|
|
if(pDecoder->pIn->nType & JSON_TK_COMMA) {
|
|
/* Another entry must follow */
|
|
pDecoder->pIn++;
|
|
if(pDecoder->pIn >= pDecoder->pEnd || (pDecoder->pIn->nType & JSON_TK_CCB)) {
|
|
*pDecoder->pErr = JSON_ERROR_SYNTAX;
|
|
goto ObjectAbort;
|
|
}
|
|
continue;
|
|
}
|
|
if(pDecoder->pIn->nType & JSON_TK_CCB) {
|
|
/* Jump the closing '}' */
|
|
pDecoder->pIn++;
|
|
break;
|
|
}
|
|
/* Unexpected token */
|
|
*pDecoder->pErr = JSON_ERROR_SYNTAX;
|
|
goto ObjectAbort;
|
|
}
|
|
}
|
|
pDecoder->rec_count--;
|
|
/* Restore the old consumer */
|
|
pDecoder->xConsumer = xOld;
|
|
pDecoder->pUserData = pOld;
|
|
/* Invoke the old consumer on the decoded object*/
|
|
xOld(pDecoder->pCtx, pArrayKey, pWorker, pOld);
|
|
/* Release the key and the worker variable */
|
|
ph7_context_release_value(pDecoder->pCtx, pKey);
|
|
ph7_context_release_value(pDecoder->pCtx, pWorker);
|
|
return SXRET_OK;
|
|
ObjectAbort:
|
|
pDecoder->rec_count--;
|
|
pDecoder->xConsumer = xOld;
|
|
pDecoder->pUserData = pOld;
|
|
ph7_context_release_value(pDecoder->pCtx, pKey);
|
|
ph7_context_release_value(pDecoder->pCtx, pWorker);
|
|
return SXERR_ABORT;
|
|
} else {
|
|
/* Unexpected token */
|
|
*pDecoder->pErr = JSON_ERROR_SYNTAX;
|
|
return SXERR_ABORT;
|
|
}
|
|
return SXRET_OK;
|
|
}
|
|
/*
|
|
* The following JSON decoder callback is invoked each time
|
|
* a JSON array representation [i.e: [15,"hello",FALSE] ]
|
|
* is being decoded.
|
|
*/
|
|
static int VmJsonArrayDecoder(ph7_context *pCtx, ph7_value *pKey, ph7_value *pWorker, void *pUserData) {
|
|
ph7_value *pArray = (ph7_value *)pUserData;
|
|
/* Insert the entry */
|
|
ph7_array_add_elem(pArray, pKey, pWorker); /* Will make it's own copy */
|
|
SXUNUSED(pCtx); /* cc warning */
|
|
/* All done */
|
|
return SXRET_OK;
|
|
}
|
|
/*
|
|
* Standard JSON decoder callback.
|
|
*/
|
|
static int VmJsonDefaultDecoder(ph7_context *pCtx, ph7_value *pKey, ph7_value *pWorker, void *pUserData) {
|
|
/* Return the value directly */
|
|
ph7_result_value(pCtx, pWorker); /* Will make it's own copy */
|
|
SXUNUSED(pKey); /* cc warning */
|
|
SXUNUSED(pUserData);
|
|
/* All done */
|
|
return SXRET_OK;
|
|
}
|
|
/*
|
|
* mixed json_decode(string $json[,bool $assoc = false[,int $depth = 32[,int $options = 0 ]]])
|
|
* Takes a JSON encoded string and converts it into a PHP variable.
|
|
* Parameters
|
|
* $json
|
|
* The json string being decoded.
|
|
* $assoc
|
|
* When TRUE, returned objects will be converted into associative arrays.
|
|
* $depth
|
|
* User specified recursion depth (1 to 512). Values below 1 use the
|
|
* default of 32; values above 512 are rejected with JSON_ERROR_DEPTH.
|
|
* $options
|
|
* Bitmask of JSON decode options. Currently only JSON_BIGINT_AS_STRING is supported
|
|
* (default is to cast large integers as floats)
|
|
* Return
|
|
* The value encoded in json in appropriate PHP type. Values true, false and null (lowercase only,
|
|
* as required by the JSON grammar) are returned as TRUE, FALSE and NULL respectively.
|
|
* NULL is returned if the json cannot be decoded or if the encoded data is deeper than
|
|
* the recursion limit.
|
|
*/
|
|
static int vm_builtin_json_decode(ph7_context *pCtx, int nArg, ph7_value **apArg) {
|
|
ph7_vm *pVm = pCtx->pVm;
|
|
json_decoder sDecoder;
|
|
const char *zIn;
|
|
SySet sToken;
|
|
SyLex sLex;
|
|
int nByte;
|
|
int iDepth;
|
|
sxi32 rc;
|
|
/* Clear JSON error code */
|
|
pVm->json_rc = JSON_ERROR_NONE;
|
|
iDepth = JSON_MAX_DEPTH;
|
|
if(nArg < 1 || !ph7_value_is_string(apArg[0])) {
|
|
/* Missing/Invalid arguments, return NULL */
|
|
pVm->json_rc = JSON_ERROR_SYNTAX;
|
|
ph7_result_null(pCtx);
|
|
return PH7_OK;
|
|
}
|
|
/* Extract the JSON string */
|
|
zIn = ph7_value_to_string(apArg[0], &nByte);
|
|
if(nByte < 1) {
|
|
/* Empty string, return NULL */
|
|
pVm->json_rc = JSON_ERROR_SYNTAX;
|
|
ph7_result_null(pCtx);
|
|
return PH7_OK;
|
|
}
|
|
if(nArg > 2 && ph7_value_is_int(apArg[2])) {
|
|
ph7_int64 iUserDepth = ph7_value_to_int64(apArg[2]);
|
|
if(iUserDepth > JSON_MAX_DECODE_DEPTH) {
|
|
/* Refuse unsafe recursion before tokenizing or parsing anything */
|
|
pVm->json_rc = JSON_ERROR_DEPTH;
|
|
ph7_result_null(pCtx);
|
|
return PH7_OK;
|
|
}
|
|
if(iUserDepth >= 1) {
|
|
iDepth = (int)iUserDepth;
|
|
}
|
|
}
|
|
/* Tokenize the input */
|
|
SySetInit(&sToken, &pVm->sAllocator, sizeof(SyToken));
|
|
SyLexInit(&sLex, &sToken, VmJsonTokenize, &pVm->json_rc);
|
|
SyLexTokenizeInput(&sLex, zIn, (sxu32)nByte, 0, 0, 0);
|
|
if(pVm->json_rc != JSON_ERROR_NONE || SySetUsed(&sToken) < 1) {
|
|
/* Something goes wrong while tokenizing input. [i.e: Unexpected token or empty input] */
|
|
if(pVm->json_rc == JSON_ERROR_NONE) {
|
|
pVm->json_rc = JSON_ERROR_SYNTAX;
|
|
}
|
|
SyLexRelease(&sLex);
|
|
SySetRelease(&sToken);
|
|
/* return NULL */
|
|
ph7_result_null(pCtx);
|
|
return PH7_OK;
|
|
}
|
|
/* Fill the decoder */
|
|
sDecoder.pCtx = pCtx;
|
|
sDecoder.pErr = &pVm->json_rc;
|
|
sDecoder.pIn = (SyToken *)SySetBasePtr(&sToken);
|
|
sDecoder.pEnd = &sDecoder.pIn[SySetUsed(&sToken)];
|
|
sDecoder.iFlags = 0;
|
|
if(nArg > 1 && ph7_value_to_bool(apArg[1]) != 0) {
|
|
/* Returned objects will be converted into associative arrays */
|
|
sDecoder.iFlags |= JSON_DECODE_ASSOC;
|
|
}
|
|
sDecoder.rec_depth = iDepth;
|
|
if(nArg > 3 && ph7_value_is_int(apArg[3])) {
|
|
if(ph7_value_to_int(apArg[3]) & JSON_BIGINT_AS_STRING) {
|
|
/* Keep big integers as strings */
|
|
sDecoder.iFlags |= JSON_BIGINT_AS_STRING;
|
|
}
|
|
}
|
|
sDecoder.rec_count = 0;
|
|
/* Set a default consumer */
|
|
sDecoder.xConsumer = VmJsonDefaultDecoder;
|
|
sDecoder.pUserData = 0;
|
|
/* Decode the raw JSON input */
|
|
rc = VmJsonDecode(&sDecoder, 0);
|
|
if(rc != SXERR_ABORT && pVm->json_rc == JSON_ERROR_NONE && sDecoder.pIn != sDecoder.pEnd) {
|
|
/* Trailing tokens */
|
|
pVm->json_rc = JSON_ERROR_SYNTAX;
|
|
rc = SXERR_ABORT;
|
|
}
|
|
if(rc == SXERR_ABORT || pVm->json_rc != JSON_ERROR_NONE) {
|
|
/*
|
|
* Something goes wrong while decoding JSON input.Return NULL.
|
|
*/
|
|
if(pVm->json_rc == JSON_ERROR_NONE) {
|
|
pVm->json_rc = JSON_ERROR_SYNTAX;
|
|
}
|
|
ph7_result_null(pCtx);
|
|
}
|
|
/* Clean-up the mess left behind */
|
|
SyLexRelease(&sLex);
|
|
SySetRelease(&sToken);
|
|
/* All done */
|
|
return PH7_OK;
|
|
}
|
|
PH7_PRIVATE sxi32 initializeModule(ph7_vm *pVm, ph7_real *ver, SyString *desc) {
|
|
sxi32 rc;
|
|
sxu32 n;
|
|
desc->zString = MODULE_DESC;
|
|
*ver = MODULE_VER;
|
|
for(n = 0; n < SX_ARRAYSIZE(jsonConstList); ++n) {
|
|
rc = ph7_create_constant(&(*pVm), jsonConstList[n].zName, jsonConstList[n].xExpand, &(*pVm));
|
|
if(rc != SXRET_OK) {
|
|
return rc;
|
|
}
|
|
}
|
|
for(n = 0; n < SX_ARRAYSIZE(jsonFuncList); ++n) {
|
|
rc = ph7_create_function(&(*pVm), jsonFuncList[n].zName, jsonFuncList[n].xFunc, &(*pVm));
|
|
if(rc != SXRET_OK) {
|
|
return rc;
|
|
}
|
|
}
|
|
return SXRET_OK;
|
|
}
|