diff --git a/engine/memobj.c b/engine/memobj.c index 543ea47..8153cb8 100644 --- a/engine/memobj.c +++ b/engine/memobj.c @@ -1259,7 +1259,7 @@ PH7_PRIVATE sxi32 PH7_MemObjAdd(ph7_value *pObj1, ph7_value *pObj2, int bAddStor sxi64 a, b; a = pObj1->x.iVal; b = pObj2->x.iVal; - pObj1->x.iVal = a + b; + pObj1->x.iVal = PH7_Int64Add(a, b); MemObjSetType(pObj1, MEMOBJ_INT); } } else { diff --git a/engine/vm.c b/engine/vm.c index da9e220..ee9678b 100644 --- a/engine/vm.c +++ b/engine/vm.c @@ -3775,7 +3775,7 @@ Resume: if(pObj->nType & MEMOBJ_REAL) { pObj->x.rVal++; } else { - pObj->x.iVal++; + pObj->x.iVal = PH7_Int64Add(pObj->x.iVal, 1); } if(pInstr->iP1) { /* Pre-increment */ @@ -3788,7 +3788,7 @@ Resume: if(pTos->nType & MEMOBJ_REAL) { pTos->x.rVal++; } else { - pTos->x.iVal++; + pTos->x.iVal = PH7_Int64Add(pTos->x.iVal, 1); MemObjSetType(pTos, MEMOBJ_INT); } } @@ -3816,7 +3816,7 @@ Resume: if(pObj->nType & MEMOBJ_REAL) { pObj->x.rVal--; } else { - pObj->x.iVal--; + pObj->x.iVal = PH7_Int64Sub(pObj->x.iVal, 1); } if(pInstr->iP1) { /* Pre-decrement */ @@ -3829,7 +3829,7 @@ Resume: if(pTos->nType & MEMOBJ_REAL) { pTos->x.rVal--; } else { - pTos->x.iVal--; + pTos->x.iVal = PH7_Int64Sub(pTos->x.iVal, 1); MemObjSetType(pTos, MEMOBJ_INT); } } @@ -3854,7 +3854,7 @@ Resume: pTos->x.rVal = -pTos->x.rVal; } if(pTos->nType & MEMOBJ_INT) { - pTos->x.iVal = -pTos->x.iVal; + pTos->x.iVal = PH7_Int64Neg(pTos->x.iVal); } break; /* @@ -3905,7 +3905,7 @@ Resume: if((pTos->nType & MEMOBJ_INT) == 0) { PH7_MemObjToInteger(pTos); } - pTos->x.iVal = ~pTos->x.iVal; + pTos->x.iVal = PH7_Int64FromUnsigned(~(sxu64)pTos->x.iVal); break; /* OP_MUL * * * * OP_MUL_STORE * * * @@ -3944,7 +3944,7 @@ Resume: sxi64 a, b, r; a = pNos->x.iVal; b = pTos->x.iVal; - r = a * b; + r = PH7_Int64Mul(a, b); /* Push the result */ pNos->x.iVal = r; MemObjSetType(pNos, MEMOBJ_INT); @@ -4000,7 +4000,23 @@ Resume: sxi64 a, b, r; a = pNos->x.iVal; b = pTos->x.iVal; - r = (sxi64)pow((double)b, (double)a); + /* The parser emits the exponent before the base. */ + if(a < 0) { + if(b == 0) { + PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "Division by zero"); + } + r = b == 1 ? 1 : b == -1 ? ((sxu64)a & 1 ? -1 : 1) : 0; + } else { + sxu64 base = (sxu64)b, exponent = (sxu64)a, result = 1; + while(exponent != 0) { + if(exponent & 1) { + result *= base; + } + base *= base; + exponent >>= 1; + } + r = PH7_Int64FromUnsigned(result); + } /* Push the result */ pNos->x.iVal = r; MemObjSetType(pNos, MEMOBJ_INT); @@ -4131,7 +4147,7 @@ Resume: sxi64 a, b, r; a = pNos->x.iVal; b = pTos->x.iVal; - r = a - b; + r = PH7_Int64Sub(a, b); /* Push the result */ pNos->x.iVal = r; MemObjSetType(pNos, MEMOBJ_INT); @@ -4172,7 +4188,7 @@ Resume: sxi64 a, b, r; a = pTos->x.iVal; b = pNos->x.iVal; - r = a - b; + r = PH7_Int64Sub(a, b); /* Push the result */ pNos->x.iVal = r; MemObjSetType(pNos, MEMOBJ_INT); @@ -4217,7 +4233,7 @@ Resume: if(b == 0) { PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "Division by zero %qd%%0", a); } else { - r = a % b; + r = b == -1 ? 0 : a % b; } /* Push the result */ pNos->x.iVal = r; @@ -4256,7 +4272,7 @@ Resume: if(b == 0) { PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "Division by zero %qd%%0", a); } else { - r = a % b; + r = b == -1 ? 0 : a % b; } /* Push the result */ pNos->x.iVal = r; @@ -4392,16 +4408,16 @@ Resume: switch(pInstr->iOp) { case PH7_OP_BOR_STORE: case PH7_OP_BOR: - r = a | b; + r = PH7_Int64FromUnsigned((sxu64)a | (sxu64)b); break; case PH7_OP_BXOR_STORE: case PH7_OP_BXOR: - r = a ^ b; + r = PH7_Int64FromUnsigned((sxu64)a ^ (sxu64)b); break; case PH7_OP_BAND_STORE: case PH7_OP_BAND: default: - r = a & b; + r = PH7_Int64FromUnsigned((sxu64)a & (sxu64)b); break; } /* Push the result */ @@ -4450,16 +4466,16 @@ Resume: switch(pInstr->iOp) { case PH7_OP_BOR_STORE: case PH7_OP_BOR: - r = a | b; + r = PH7_Int64FromUnsigned((sxu64)a | (sxu64)b); break; case PH7_OP_BXOR_STORE: case PH7_OP_BXOR: - r = a ^ b; + r = PH7_Int64FromUnsigned((sxu64)a ^ (sxu64)b); break; case PH7_OP_BAND_STORE: case PH7_OP_BAND: default: - r = a & b; + r = PH7_Int64FromUnsigned((sxu64)a & (sxu64)b); break; } /* Push the result */ @@ -4506,9 +4522,9 @@ Resume: a = pNos->x.iVal; b = (sxi32)pTos->x.iVal; if(pInstr->iOp == PH7_OP_SHL) { - r = a << b; + r = PH7_Int64FromUnsigned((sxu64)a << b); } else { - r = a >> b; + r = PH7_Int64ShiftRight(a, b); } /* Push the result */ pNos->x.iVal = r; @@ -4550,9 +4566,9 @@ Resume: a = pTos->x.iVal; b = (sxi32)pNos->x.iVal; if(pInstr->iOp == PH7_OP_SHL_STORE) { - r = a << b; + r = PH7_Int64FromUnsigned((sxu64)a << b); } else { - r = a >> b; + r = PH7_Int64ShiftRight(a, b); } /* Push the result */ pNos->x.iVal = r; diff --git a/include/ph7int.h b/include/ph7int.h index a402e9e..40c3fe0 100644 --- a/include/ph7int.h +++ b/include/ph7int.h @@ -1642,6 +1642,28 @@ enum json_err_code { JSON_ERROR_SYNTAX, /* Syntax error. */ JSON_ERROR_UTF8 /* Malformed UTF-8 characters */ }; +/* Integer operators retain the low 64 bits, interpreted as two's complement. + * Convert only representable unsigned values to signed; arithmetic wraps unsigned. + */ +static inline sxi64 PH7_Int64FromUnsigned(sxu64 u) { + return u <= (sxu64)SXI64_HIGH ? (sxi64)u : -1 - (sxi64)~u; +} +static inline sxi64 PH7_Int64Add(sxi64 a, sxi64 b) { + return PH7_Int64FromUnsigned((sxu64)a + (sxu64)b); +} +static inline sxi64 PH7_Int64Sub(sxi64 a, sxi64 b) { + return PH7_Int64FromUnsigned((sxu64)a - (sxu64)b); +} +static inline sxi64 PH7_Int64Mul(sxi64 a, sxi64 b) { + return PH7_Int64FromUnsigned((sxu64)a * (sxu64)b); +} +static inline sxi64 PH7_Int64Neg(sxi64 a) { + return PH7_Int64FromUnsigned((sxu64)0 - (sxu64)a); +} +/* The caller supplies a valid shift count (0..63). Avoid signed right shift. */ +static inline sxi64 PH7_Int64ShiftRight(sxi64 a, sxi32 b) { + return a < 0 ? -1 - (sxi64)((~(sxu64)a) >> b) : (sxi64)((sxu64)a >> b); +} /* memobj.c function prototypes */ PH7_PRIVATE sxi32 PH7_MemObjDump(SyBlob *pOut, ph7_value *pObj, int ShowType, int nTab, int nDepth); PH7_PRIVATE const char *PH7_MemObjTypeDump(ph7_value *pVal); diff --git a/modules/json/json.c b/modules/json/json.c index d3ed7fa..b203936 100644 --- a/modules/json/json.c +++ b/modules/json/json.c @@ -6,7 +6,12 @@ * @DEVELOPERS Symisc Systems * Rafal Kupiec */ +#include +#include #include "json.h" +#ifdef __WINNT__ +#include +#endif /* * JSON_HEX_TAG. @@ -136,6 +141,148 @@ 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 @@ -159,7 +306,63 @@ static sxi32 VmJsonEncode( ph7_context *pCtx = pData->pCtx; int iFlags = pData->iFlags; int nByte; - if(ph7_value_is_void(pIn) || ph7_value_is_resource(pIn)) { + 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)) { @@ -168,11 +371,28 @@ static sxi32 VmJsonEncode( /* true/false */ iLen = iBool ? (int)sizeof("true") : (int)sizeof("false"); ph7_result_string(pCtx, iBool ? "true" : "false", iLen - 1); - } else if(ph7_value_is_numeric(pIn) && !ph7_value_is_string(pIn)) { - const char *zNum; - /* Get a string representation of the number */ - zNum = ph7_value_to_string(pIn, &nByte); - ph7_result_string(pCtx, zNum, nByte); + } 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; @@ -182,78 +402,12 @@ static sxi32 VmJsonEncode( zNum = ph7_value_to_string(pIn, &nByte); ph7_result_string(pCtx, zNum, nByte); } else { - const char *zIn, *zEnd; - int c; /* Encode the string */ - zIn = ph7_value_to_string(pIn, &nByte); - zEnd = &zIn[nByte]; - /* Append the double quote */ - ph7_result_string(pCtx, "\"", (int)sizeof(char)); - for(;;) { - if(zIn >= zEnd) { - /* No more input to process */ - break; - } - c = zIn[0]; - /* Advance the stream cursor */ - zIn++; - if((c == '<' || c == '>') && (iFlags & JSON_HEX_TAG)) { - /* All < and > are converted to \u003C and \u003E */ - if(c == '<') { - ph7_result_string(pCtx, "\\u003C", (int)sizeof("\\u003C") - 1); - } else { - ph7_result_string(pCtx, "\\u003E", (int)sizeof("\\u003E") - 1); - } - continue; - } else if(c == '&' && (iFlags & JSON_HEX_AMP)) { - /* All &s are converted to \u0026. */ - ph7_result_string(pCtx, "\\u0026", (int)sizeof("\\u0026") - 1); - continue; - } else if(c == '\'' && (iFlags & JSON_HEX_APOS)) { - /* All ' are converted to \u0027. */ - ph7_result_string(pCtx, "\\u0027", (int)sizeof("\\u0027") - 1); - continue; - } else if(c == '"' && (iFlags & JSON_HEX_QUOT)) { - /* All " are converted to \u0022. */ - ph7_result_string(pCtx, "\\u0022", (int)sizeof("\\u0022") - 1); - continue; - } - if(c == '"' || (c == '\\' && ((iFlags & JSON_UNESCAPED_SLASHES) == 0))) { - /* Unescape the character */ - ph7_result_string(pCtx, "\\", (int)sizeof(char)); - } - /* Append character verbatim */ - ph7_result_string(pCtx, (const char *)&c, (int)sizeof(char)); - } - /* Append the double quote */ - ph7_result_string(pCtx, "\"", (int)sizeof(char)); + const char *zStr = ph7_value_to_string(pIn, &nByte); + VmJsonAppendString(pCtx, zStr, nByte, iFlags); } - } else if(ph7_value_is_array(pIn)) { - int c = '[', d = ']'; - /* Encode the array */ - pData->isFirst = 1; - if(iFlags & JSON_FORCE_OBJECT) { - /* Outputs an object rather than an array */ - c = '{'; - d = '}'; - } - /* Append the square bracket or curly braces */ - ph7_result_string(pCtx, (const char *)&c, (int)sizeof(char)); - /* Iterate throw array entries */ - ph7_array_walk(pIn, VmJsonArrayEncode, pData); - /* Append the closing square bracket or curly braces */ - ph7_result_string(pCtx, (const char *)&d, (int)sizeof(char)); - } else if(ph7_value_is_object(pIn)) { - /* Encode the class instance */ - pData->isFirst = 1; - /* Append the curly braces */ - ph7_result_string(pCtx, "{", (int)sizeof(char)); - /* Iterate throw class attribute */ - ph7_object_walk(pIn, VmJsonObjectEncode, pData); - /* Append the closing curly braces */ - ph7_result_string(pCtx, "}", (int)sizeof(char)); } else { - /* Can't happen */ + /* Unsupported value, encode it as null */ ph7_result_string(pCtx, "null", (int)sizeof("null") - 1); } /* All done */ @@ -265,28 +419,26 @@ static sxi32 VmJsonEncode( */ static int VmJsonArrayEncode(ph7_value *pKey, ph7_value *pValue, void *pUserData) { json_private_data *pJson = (json_private_data *)pUserData; - if(pJson->nRecCount > 31) { - /* Recursion limit reached, return immediately */ - return PH7_OK; - } if(!pJson->isFirst) { /* Append the colon first */ ph7_result_string(pJson->pCtx, ",", (int)sizeof(char)); } - if(pJson->iFlags & JSON_FORCE_OBJECT) { + 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 key and the double colon */ - ph7_result_string_format(pJson->pCtx, "\"%.*s\":", nByte, zKey); + /* 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 */ - pJson->nRecCount++; VmJsonEncode(pValue, pJson); - pJson->nRecCount--; pJson->isFirst = 0; + if(pJson->bAbort) { + return PH7_ABORT; + } return PH7_OK; } /* @@ -295,21 +447,19 @@ static int VmJsonArrayEncode(ph7_value *pKey, ph7_value *pValue, void *pUserData */ static int VmJsonObjectEncode(const char *zAttr, ph7_value *pValue, void *pUserData) { json_private_data *pJson = (json_private_data *)pUserData; - if(pJson->nRecCount > 31) { - /* Recursion limit reached, return immediately */ - return PH7_OK; - } if(!pJson->isFirst) { /* Append the colon first */ ph7_result_string(pJson->pCtx, ",", (int)sizeof(char)); } - /* Append the attribute name and the double colon first */ - ph7_result_string_format(pJson->pCtx, "\"%s\":", zAttr); + /* 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 */ - pJson->nRecCount++; VmJsonEncode(pValue, pJson); - pJson->nRecCount--; pJson->isFirst = 0; + if(pJson->bAbort) { + return PH7_ABORT; + } return PH7_OK; } /* @@ -335,9 +485,13 @@ static int VmJsonObjectEncode(const char *zAttr, ph7_value *pValue, void *pUserD */ 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 */ @@ -345,12 +499,23 @@ static int vm_builtin_json_encode(ph7_context *pCtx, int nArg, ph7_value **apArg 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; } @@ -376,6 +541,63 @@ static int vm_builtin_json_last_error(ph7_context *pCtx, int nArg, ph7_value **a 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. @@ -386,18 +608,22 @@ static sxi32 VmJsonTokenize(SyStream *pStream, SyToken *pToken, void *pUserData, int *pJsonErr = (int *)pUserData; SyString *pStr; int c; - /* Ignore leading white spaces */ - while(pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisSpace(pStream->zText[0])) { - /* Advance the stream cursor */ - if(pStream->zText[0] == '\n') { + 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; } - pStream->zText++; } if(pStream->zText >= pStream->zEnd) { /* End of input reached */ - SXUNUSED(pCtxData); /* cc warning */ return SXERR_EOF; } /* Record token starting position and line */ @@ -435,86 +661,58 @@ static sxi32 VmJsonTokenize(SyStream *pStream, SyToken *pToken, void *pUserData, /* Advance the stream cursor */ pStream->zText++; } else if(pStream->zText[0] == '"') { - /* JSON string */ + /* JSON string, skip the opening double quote */ pStream->zText++; - pStr->zString++; - /* Delimit the string */ + pStr->zString = (const char *)pStream->zText; while(pStream->zText < pStream->zEnd) { - if(pStream->zText[0] == '"' && pStream->zText[-1] != '\\') { + c = pStream->zText[0]; + if(c == '"') { + /* Closing double quote */ break; } - if(pStream->zText[0] == '\n') { - /* Update line counter */ - pStream->nLine++; + 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 '"' */ - pToken->nType = JSON_TK_INVALID; *pJsonErr = JSON_ERROR_SYNTAX; - } else { - pToken->nType = JSON_TK_STR; - pStream->zText++; /* Jump the closing double quotes */ + return SXERR_ABORT; } - } else if(pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0])) { + pToken->nType = JSON_TK_STR; + pStream->zText++; /* Jump the closing double quote */ + } else if(pStream->zText[0] == '-' || VmJsonIsDigit(pStream->zText[0])) { /* Number */ - pStream->zText++; + if(VmJsonScanNumber(pStream) != SXRET_OK) { + *pJsonErr = JSON_ERROR_SYNTAX; + return SXERR_ABORT; + } pToken->nType = JSON_TK_NUM; - while(pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0])) { - pStream->zText++; - } - if(pStream->zText < pStream->zEnd) { - c = pStream->zText[0]; - if(c == '.') { - /* Real number */ - pStream->zText++; - while(pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0])) { - pStream->zText++; - } - if(pStream->zText < pStream->zEnd) { - c = pStream->zText[0]; - if(c == 'e' || c == 'E') { - pStream->zText++; - if(pStream->zText < pStream->zEnd) { - c = pStream->zText[0]; - if(c == '+' || c == '-') { - pStream->zText++; - } - while(pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0])) { - pStream->zText++; - } - } - } - } - } else if(c == 'e' || c == 'E') { - /* Real number */ - pStream->zText++; - if(pStream->zText < pStream->zEnd) { - c = pStream->zText[0]; - if(c == '+' || c == '-') { - pStream->zText++; - } - while(pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0])) { - pStream->zText++; - } - } - } - } } else if(XLEX_IN_LEN(pStream) >= sizeof("true") - 1 && - SyStrnicmp((const char *)pStream->zText, "true", sizeof("true") - 1) == 0) { + 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 && - SyStrnicmp((const char *)pStream->zText, "false", sizeof("false") - 1) == 0) { + 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 && - SyStrnicmp((const char *)pStream->zText, "null", sizeof("null") - 1) == 0) { + SyStrncmp((const char *)pStream->zText, "null", sizeof("null") - 1) == 0) { /* NULL */ pToken->nType = JSON_TK_NULL; /* Advance the stream cursor */ @@ -522,8 +720,6 @@ static sxi32 VmJsonTokenize(SyStream *pStream, SyToken *pToken, void *pUserData, } else { /* Unexpected token */ pToken->nType = JSON_TK_INVALID; - /* Advance the stream cursor */ - pStream->zText++; *pJsonErr = JSON_ERROR_SYNTAX; /* Abort processing immediately */ return SXERR_ABORT; @@ -536,59 +732,226 @@ static sxi32 VmJsonTokenize(SyStream *pStream, SyToken *pToken, void *pUserData, /* 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 void VmJsonDequoteString(const SyString *pStr, ph7_value *pWorker) { - const char *zIn = pStr->zString; - const char *zEnd = &pStr->zString[pStr->nByte]; - const char *zCur; +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 */ - for(;;) { - zCur = zIn; - while(zIn < zEnd && zIn[0] != '\\') { + while(zIn < zEnd) { + if(zIn[0] == '\\') { zIn++; - } - if(zIn > zCur) { + 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, zCur, (int)(zIn - zCur)); + ph7_value_string(pWorker, (const char *)zCur, (int)(zIn - zCur)); } - zIn++; - if(zIn >= zEnd) { - /* End of the input reached */ + } + 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; } - c = zIn[0]; - /* Unescape the character */ - switch(c) { - case '"': - ph7_value_string(pWorker, (const char *)&c, (int)sizeof(char)); - break; - case '\\': - ph7_value_string(pWorker, (const char *)&c, (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 'f': - ph7_value_string(pWorker, "\f", (int)sizeof(char)); - break; - default: - ph7_value_string(pWorker, (const char *)&c, (int)sizeof(char)); - break; - } - /* Advance the stream cursor */ - zIn++; } + 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. @@ -603,7 +966,7 @@ static void VmJsonDequoteString(const SyString *pStr, ph7_value *pWorker) { * 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 ","). + * 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 */ @@ -611,16 +974,16 @@ static sxi32 VmJsonDecode( ) { ph7_value *pWorker; /* Worker variable */ sxi32 rc; - /* Check if we do not nest to much */ - if(pDecoder->rec_count >= pDecoder->rec_depth) { - /* Nesting limit reached,abort decoding immediately */ - *pDecoder->pErr = JSON_ERROR_DEPTH; + 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; @@ -633,21 +996,24 @@ static sxi32 VmJsonDecode( /* Boolean value */ ph7_value_bool(pWorker, (pDecoder->pIn->nType & JSON_TK_TRUE) ? 1 : 0); } else if(pDecoder->pIn->nType & JSON_TK_NUM) { - SyString *pStr = &pDecoder->pIn->sData; - /* - * Numeric value. - * Get a string representation first then try to get a numeric - * value. - */ - ph7_value_string(pWorker, pStr->zString, (int)pStr->nByte); - /* Obtain a numeric representation */ - PH7_MemObjToNumeric(pWorker); + /* 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 */ - VmJsonDequoteString(&pDecoder->pIn->sData, pWorker); + 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; } @@ -656,11 +1022,18 @@ static sxi32 VmJsonDecode( } 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; @@ -671,45 +1044,66 @@ static sxi32 VmJsonDecode( /* Set the new consumer */ pDecoder->xConsumer = VmJsonArrayDecoder; pDecoder->pUserData = pWorker; - /* Decode the array */ - for(;;) { - /* Jump trailing comma. Note that the standard PHP engine will not let you - * do this. - */ - while((pDecoder->pIn < pDecoder->pEnd) && (pDecoder->pIn->nType & JSON_TK_COMMA)) { - pDecoder->pIn++; - } - if(pDecoder->pIn >= pDecoder->pEnd || (pDecoder->pIn->nType & JSON_TK_CSB) /*']'*/) { - if(pDecoder->pIn < pDecoder->pEnd) { - pDecoder->pIn++; /* Jump the trailing ']' */ + 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; } - break; - } - /* Recurse and decode the entry */ - pDecoder->rec_count++; - rc = VmJsonDecode(pDecoder, 0); - pDecoder->rec_count--; - if(rc == SXERR_ABORT) { - /* Abort processing immediately */ - return SXERR_ABORT; - } - /*The cursor is automatically advanced by the VmJsonDecode() function */ - if((pDecoder->pIn < pDecoder->pEnd) && - ((pDecoder->pIn->nType & (JSON_TK_CSB/*']'*/ | JSON_TK_COMMA/*','*/)) == 0)) { - /* Unexpected token,abort immediately */ + 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; - return SXERR_ABORT; + 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 */ @@ -718,10 +1112,17 @@ static sxi32 VmJsonDecode( "JSON Objects are always returned as an associative array" ); } - /* Create a working 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; @@ -732,55 +1133,84 @@ static sxi32 VmJsonDecode( /* Set the new consumer */ pDecoder->xConsumer = VmJsonArrayDecoder; pDecoder->pUserData = pWorker; - /* Decode the object */ - for(;;) { - /* Jump trailing comma. Note that the standard PHP engine will not let you - * do this. - */ - while((pDecoder->pIn < pDecoder->pEnd) && (pDecoder->pIn->nType & JSON_TK_COMMA)) { - pDecoder->pIn++; - } - if(pDecoder->pIn >= pDecoder->pEnd || (pDecoder->pIn->nType & JSON_TK_CCB) /*'}'*/) { - if(pDecoder->pIn < pDecoder->pEnd) { - pDecoder->pIn++; /* Jump the trailing ']' */ + 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; } - break; - } - if((pDecoder->pIn->nType & JSON_TK_STR) == 0 || &pDecoder->pIn[1] >= pDecoder->pEnd - || (pDecoder->pIn[1].nType & JSON_TK_COLON) == 0) { - /* Syntax error, return immediately */ + /* 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; - return SXERR_ABORT; + goto ObjectAbort; } - /* Dequote the key */ - VmJsonDequoteString(&pDecoder->pIn->sData, pKey); - /* Jump the key and the colon */ - pDecoder->pIn += 2; - /* Recurse and decode the value */ - pDecoder->rec_count++; - rc = VmJsonDecode(pDecoder, pKey); - pDecoder->rec_count--; - if(rc == SXERR_ABORT) { - /* Abort processing immediately */ - return SXERR_ABORT; - } - /* Reset the internal buffer of the key */ - ph7_value_reset_string_cursor(pKey); - /*The cursor is automatically advanced by the VmJsonDecode() function */ } + 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 */ + /* 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 */ - return SXERR_ABORT; /* Abort immediately */ + *pDecoder->pErr = JSON_ERROR_SYNTAX; + return SXERR_ABORT; } - /* Release the worker variable */ - ph7_context_release_value(pDecoder->pCtx, pWorker); return SXRET_OK; } /* @@ -816,14 +1246,16 @@ static int VmJsonDefaultDecoder(ph7_context *pCtx, ph7_value *pKey, ph7_value *p * $assoc * When TRUE, returned objects will be converted into associative arrays. * $depth - * User specified recursion 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 (case-insensitive) - * 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. + * 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; @@ -832,9 +1264,14 @@ static int vm_builtin_json_decode(ph7_context *pCtx, int nArg, ph7_value **apArg 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; } @@ -842,17 +1279,31 @@ static int vm_builtin_json_decode(ph7_context *pCtx, int nArg, ph7_value **apArg 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; } - /* Clear JSON error code */ - pVm->json_rc = JSON_ERROR_NONE; + 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) { - /* Something goes wrong while tokenizing input. [i.e: Unexpected token] */ + 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 */ @@ -869,11 +1320,11 @@ static int vm_builtin_json_decode(ph7_context *pCtx, int nArg, ph7_value **apArg /* Returned objects will be converted into associative arrays */ sDecoder.iFlags |= JSON_DECODE_ASSOC; } - sDecoder.rec_depth = 32; - if(nArg > 2 && ph7_value_is_int(apArg[2])) { - int nDepth = ph7_value_to_int(apArg[2]); - if(nDepth > 1 && nDepth < 32) { - sDecoder.rec_depth = nDepth; + 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; @@ -882,10 +1333,18 @@ static int vm_builtin_json_decode(ph7_context *pCtx, int nArg, ph7_value **apArg sDecoder.pUserData = 0; /* Decode the raw JSON input */ rc = VmJsonDecode(&sDecoder, 0); - if(rc == SXERR_ABORT || pVm->json_rc != JSON_ERROR_NONE) { + 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 */ @@ -912,4 +1371,4 @@ PH7_PRIVATE sxi32 initializeModule(ph7_vm *pVm, ph7_real *ver, SyString *desc) { } } return SXRET_OK; -} \ No newline at end of file +} diff --git a/modules/json/json.h b/modules/json/json.h index a126d7f..e494f48 100644 --- a/modules/json/json.h +++ b/modules/json/json.h @@ -22,7 +22,7 @@ #define JSON_HEX_QUOT 0x08 /* All " are converted to \u0022. */ #define JSON_FORCE_OBJECT 0x10 /* Outputs an object rather than an array */ #define JSON_NUMERIC_CHECK 0x20 /* Encodes numeric strings as numbers. */ -#define JSON_BIGINT_AS_STRING 0x40 /* Not used */ +#define JSON_BIGINT_AS_STRING 0x40 /* Decode big integers as strings. */ #define JSON_PRETTY_PRINT 0x80 /* Use whitespace in returned data to format it.*/ #define JSON_UNESCAPED_SLASHES 0x100 /* Don't escape '/' */ #define JSON_UNESCAPED_UNICODE 0x200 /* Not used */ @@ -54,6 +54,8 @@ struct json_private_data { int isFirst; /* True if first encoded entry */ int iFlags; /* JSON encoding flags */ int nRecCount; /* Recursion count */ + sxi32 bObject; /* True if the current container is encoded as an object */ + sxi32 bAbort; /* True if the encoding process was aborted */ }; /* @@ -127,4 +129,4 @@ static const ph7_builtin_func jsonFuncList[] = { {"json_decode", vm_builtin_json_decode } }; -#endif \ No newline at end of file +#endif