mirror of
https://bitbucket.org/smil3y/kdelibs.git
synced 2025-02-24 10:52:49 +00:00
409 lines
13 KiB
C++
409 lines
13 KiB
C++
/*
|
|
* This file is part of the KDE libraries
|
|
* Copyright (C) 2012 Bernd Buschinski (b.buschinski@googlemail.com)
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public License
|
|
* along with this library; see the file COPYING.LIB. If not, write to
|
|
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*
|
|
*/
|
|
|
|
|
|
#include "jsonstringify.h"
|
|
|
|
#include <algorithm>
|
|
|
|
#include "object.h"
|
|
#include "operations.h"
|
|
#include "array_instance.h"
|
|
#include "number_object.h"
|
|
#include "bool_object.h"
|
|
#include "string_object.h"
|
|
#include "function.h"
|
|
|
|
#include "wtf/Assertions.h"
|
|
|
|
|
|
namespace KJS {
|
|
|
|
static const unsigned int StackObjectLimit = 1500;
|
|
|
|
JSONStringify::JSONStringify(ExecState* exec, JSValue* replacer, JSValue* spacer)
|
|
: m_state(Success)
|
|
{
|
|
m_replacerObject = replacer->getObject();
|
|
|
|
if (!m_replacerObject)
|
|
m_replacerType = Invalid;
|
|
else if (replacer->implementsCall())
|
|
m_replacerType = Function;
|
|
else if (m_replacerObject->inherits(&ArrayInstance::info)) {
|
|
//get all whitelist names
|
|
m_replacerType = Array;
|
|
PropertyNameArray names;
|
|
m_replacerObject->getOwnPropertyNames(exec, names, PropertyMap::ExcludeDontEnumProperties);
|
|
const int size = names.size();
|
|
bool isValidIndex = false;
|
|
for (int i = 0; i < size; ++i) {
|
|
names[i].toArrayIndex(&isValidIndex);
|
|
if (!isValidIndex)
|
|
continue;
|
|
m_whitelistNames.add(Identifier(m_replacerObject->get(exec, names[i])->toString(exec)));
|
|
if (exec->hadException()) {
|
|
m_state = FailedException;
|
|
return;
|
|
}
|
|
}
|
|
} else {
|
|
m_replacerType = Invalid;
|
|
m_replacerObject = 0;
|
|
}
|
|
|
|
JSObject* spacerObject = spacer->getObject();
|
|
m_emtpySpacer = true;
|
|
if (spacer->isString() || (spacerObject && spacerObject->inherits(&StringInstance::info))) {
|
|
m_spacer = spacer->toString(exec);
|
|
if (exec->hadException()) {
|
|
m_state = FailedException;
|
|
return;
|
|
}
|
|
if (!m_spacer.isEmpty()) {
|
|
m_spacer = m_spacer.substr(0, 10);
|
|
m_emtpySpacer = false;
|
|
}
|
|
} else if (spacer->isNumber() || (spacerObject && spacerObject->inherits(&NumberInstance::info))) {
|
|
double spacesDouble = spacer->toInteger(exec);
|
|
if (exec->hadException()) {
|
|
m_state = FailedException;
|
|
return;
|
|
}
|
|
|
|
int spaces;
|
|
if (isNaN(spacesDouble) || isInf(spacesDouble))
|
|
spaces = 0;
|
|
else
|
|
spaces = static_cast<int>(spacesDouble);
|
|
|
|
if (spaces > 0) {
|
|
m_emtpySpacer = false;
|
|
int max = std::min<int>(spaces, 10);
|
|
for (int i = 0; i < max; ++i)
|
|
m_spacer.append(' ');
|
|
}
|
|
}
|
|
m_rootIsUndefined = false;
|
|
}
|
|
|
|
JSValue* JSONStringify::stringify(ExecState* exec, JSValue* object, StringifyState& state)
|
|
{
|
|
JSObject* holder = static_cast<JSObject *>(exec->lexicalInterpreter()->builtinObject()->construct(exec, List::empty()));
|
|
UString ret = stringifyValue(exec, object, jsString(""), holder);
|
|
state = m_state;
|
|
|
|
if (m_rootIsUndefined)
|
|
return jsUndefined();
|
|
|
|
if (m_state == Success)
|
|
return jsString(ret);
|
|
return jsUndefined();
|
|
}
|
|
|
|
UString JSONStringify::quotedString(ExecState* exec, const UString& string)
|
|
{
|
|
//Check if we already failed
|
|
if (m_state != Success)
|
|
return UString();
|
|
|
|
if (exec->hadException()) {
|
|
m_state = FailedException;
|
|
return UString();
|
|
}
|
|
|
|
const int size = string.size();
|
|
UString ret = "\"";
|
|
|
|
for (int i = 0; i < size; ++i) {
|
|
int start = i;
|
|
static const short unsigned blackSlashUC = '\\';
|
|
static const short unsigned quoteUC = '\"';
|
|
while (i < size && (string[i].uc > 0x001F && string[i].uc != blackSlashUC && string[i].uc != quoteUC))
|
|
++i;
|
|
ret += string.substr(start, i-start);
|
|
|
|
if (i >= size)
|
|
break;
|
|
|
|
switch (string[i].uc) {
|
|
case '\t':
|
|
ret += "\\t";
|
|
break;
|
|
case '\r':
|
|
ret += "\\r";
|
|
break;
|
|
case '\n':
|
|
ret += "\\n";
|
|
break;
|
|
case '\f':
|
|
ret += "\\f";
|
|
break;
|
|
case '\b':
|
|
ret += "\\b";
|
|
break;
|
|
case '"':
|
|
ret += "\\\"";
|
|
break;
|
|
case '\\':
|
|
ret += "\\\\";
|
|
break;
|
|
default:
|
|
static const char hexDigits[] = "0123456789abcdef";
|
|
short unsigned ch = string[i].uc;
|
|
ret.append("\\u");
|
|
ret.append(hexDigits[(ch >> 12) & 0xF]);
|
|
ret.append(hexDigits[(ch >> 8) & 0xF]);
|
|
ret.append(hexDigits[(ch >> 4) & 0xF]);
|
|
ret.append(hexDigits[ch & 0xF]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
ret.append('\"');
|
|
return ret;
|
|
}
|
|
|
|
bool JSONStringify::isWhiteListed(const Identifier& propertyName)
|
|
{
|
|
if (m_replacerType != Array)
|
|
return true;
|
|
|
|
return m_whitelistNames.contains(propertyName);
|
|
}
|
|
|
|
UString JSONStringify::stringifyObject(KJS::ExecState* exec, KJS::JSValue* object, KJS::JSValue* propertyName, KJS::JSObject* /*holder*/)
|
|
{
|
|
if (m_state != Success)
|
|
return UString();
|
|
|
|
// As stringifyObject is only called with object->type() == ObhectType, this can't be null
|
|
JSObject* jso = object->getObject();
|
|
|
|
if (jso->hasProperty(exec, exec->propertyNames().toJSON)) {
|
|
JSObject* toJSONFunc = 0;
|
|
toJSONFunc = jso->get(exec, exec->propertyNames().toJSON)->getObject();
|
|
|
|
if (toJSONFunc) {
|
|
m_objectStack.push_back(object);
|
|
List args;
|
|
args.append(propertyName);
|
|
JSValue* toJSONCall = toJSONFunc->call(exec, jso, args);
|
|
if (exec->hadException()) {
|
|
m_state = FailedException;
|
|
return UString();
|
|
}
|
|
|
|
//Check if the toJSON call returned a function
|
|
// we check it here because our stack already contains an object,
|
|
// but this is still the root object.
|
|
if (m_objectStack.size() == 1 && toJSONCall->implementsCall()) {
|
|
m_rootIsUndefined = true;
|
|
return UString();
|
|
}
|
|
|
|
return stringifyValue(exec, toJSONCall, propertyName, jso);
|
|
}
|
|
}
|
|
|
|
if (jso->inherits(&BooleanInstance::info)) {
|
|
return jso->toString(exec);
|
|
} else if (jso->inherits(&NumberInstance::info)) {
|
|
double val = jso->toNumber(exec);
|
|
if (isInf(val) || isNaN(val)) // !isfinite
|
|
return UString("null");
|
|
return UString::from(val);
|
|
} else if (jso->inherits(&StringInstance::info)) {
|
|
return quotedString(exec, jso->toString(exec));
|
|
} else if (jso->implementsCall()) {
|
|
return UString("null");
|
|
} else if (jso->inherits(&ArrayInstance::info)) { //stringify array object
|
|
m_objectStack.push_back(object);
|
|
PropertyNameArray names;
|
|
jso->getPropertyNames(exec, names, KJS::PropertyMap::ExcludeDontEnumProperties);
|
|
const int size = names.size();
|
|
if (size == 0)
|
|
return UString("[]");
|
|
|
|
//filter names
|
|
PropertyNameArray whiteListedNames;
|
|
bool isValidIndex = false;
|
|
for (int i = 0; i < size; ++i) {
|
|
if (isWhiteListed(names[i])) {
|
|
names[i].toArrayIndex(&isValidIndex);
|
|
if (isValidIndex)
|
|
whiteListedNames.add(names[i]);
|
|
}
|
|
}
|
|
const int sizeWhitelisted = whiteListedNames.size();
|
|
if (sizeWhitelisted == 0)
|
|
return UString("[]");
|
|
|
|
UString ret = "[";
|
|
for (int i = 0; i < sizeWhitelisted; ++i) {
|
|
JSValue* arrayVal = jso->get(exec, whiteListedNames[i]);
|
|
//do not render undefined, ECMA Edition 5.1r6 - 15.12.3 NOTE 2
|
|
if (arrayVal->isUndefined())
|
|
continue;
|
|
|
|
if (!m_emtpySpacer) {
|
|
ret.append('\n');
|
|
ret += m_spacer;
|
|
}
|
|
ret += stringifyValue(exec, arrayVal, propertyName, jso);
|
|
if (m_state != Success)
|
|
return UString();
|
|
if (i != sizeWhitelisted-1)
|
|
ret.append(',');
|
|
}
|
|
|
|
if (!m_emtpySpacer)
|
|
ret.append('\n');
|
|
|
|
ret.append(']');
|
|
m_objectStack.pop_back();
|
|
return ret;
|
|
} else { //stringify real object
|
|
m_objectStack.push_back(object);
|
|
PropertyNameArray names;
|
|
jso->getPropertyNames(exec, names, KJS::PropertyMap::ExcludeDontEnumProperties);
|
|
const int size = names.size();
|
|
if (size == 0)
|
|
return UString("{}");
|
|
|
|
//filter names
|
|
PropertyNameArray whiteListedNames;
|
|
for (int i = 0; i < size; ++i) {
|
|
if (isWhiteListed(names[i]))
|
|
whiteListedNames.add(names[i]);
|
|
}
|
|
const int sizeWhitelisted = whiteListedNames.size();
|
|
if (sizeWhitelisted == 0)
|
|
return UString("{}");
|
|
|
|
UString ret = "{";
|
|
for (int i = 0; i < sizeWhitelisted; ++i) {
|
|
JSValue* objectVal = jso->get(exec, whiteListedNames[i]);
|
|
//do not render undefined, ECMA Edition 5.1r6 - 15.12.3 NOTE 2
|
|
if (objectVal->isUndefined())
|
|
continue;
|
|
|
|
if (!m_emtpySpacer) {
|
|
ret.append('\n');
|
|
ret += m_spacer;
|
|
}
|
|
ret += quotedString(exec, whiteListedNames[i].ustring());
|
|
ret += ":";
|
|
ret += stringifyValue(exec, objectVal, jsString(whiteListedNames[i].ustring()), jso);
|
|
if (m_state != Success)
|
|
return UString();
|
|
if (i != sizeWhitelisted-1)
|
|
ret.append(',');
|
|
}
|
|
|
|
if (!m_emtpySpacer)
|
|
ret.append('\n');
|
|
|
|
ret.append('}');
|
|
m_objectStack.pop_back();
|
|
return ret;
|
|
}
|
|
return UString("null");
|
|
}
|
|
|
|
UString JSONStringify::stringifyValue(KJS::ExecState* exec, KJS::JSValue* object, KJS::JSValue* propertyName, KJS::JSObject* holder)
|
|
{
|
|
//Check if we already failed
|
|
if (m_state != Success)
|
|
return UString();
|
|
|
|
if (exec->hadException()) {
|
|
m_state = FailedException;
|
|
return UString();
|
|
}
|
|
|
|
if (m_objectStack.size() > StackObjectLimit) {
|
|
m_state = FailedStackLimitExceeded;
|
|
return UString();
|
|
}
|
|
|
|
if (!m_objectStack.empty()) {
|
|
std::vector<JSValue*>::iterator found = std::find(m_objectStack.begin(), m_objectStack.end(), object);
|
|
if (found != m_objectStack.end()) {
|
|
m_state = FailedCyclic;
|
|
return UString();
|
|
}
|
|
}
|
|
|
|
if (m_replacerObject && m_replacerType == Function) {
|
|
List args;
|
|
args.append(propertyName);
|
|
args.append(object);
|
|
object = m_replacerObject->call(exec, holder, args);
|
|
if (exec->hadException()) {
|
|
m_state = FailedException;
|
|
return UString();
|
|
}
|
|
}
|
|
|
|
//Check if root object is a function, after replace
|
|
if (m_objectStack.empty() && object->implementsCall()) {
|
|
m_rootIsUndefined = true;
|
|
return UString();
|
|
}
|
|
|
|
JSType type = object->type();
|
|
switch (type) {
|
|
case ObjectType:
|
|
return stringifyObject(exec, object, propertyName, holder);
|
|
case NumberType: {
|
|
double val = object->getNumber();
|
|
if (isInf(val) || isNaN(val)) // !isfinite
|
|
return UString("null");
|
|
// fall through
|
|
}
|
|
case BooleanType:
|
|
return object->toString(exec);
|
|
case StringType:
|
|
return quotedString(exec, object->toString(exec));
|
|
break;
|
|
case UndefinedType:
|
|
// Special case: while we normally don't render undefined,
|
|
// this is not the case if our "root" object is undefined,
|
|
// or replaced to undefined.
|
|
// Hence check if root object, AFTER REPLACE, is undefined.
|
|
if (m_objectStack.empty()) {
|
|
m_rootIsUndefined = true;
|
|
return UString();
|
|
}
|
|
// beside from root Object we should never render Undefined
|
|
ASSERT_NOT_REACHED();
|
|
case NullType:
|
|
case UnspecifiedType:
|
|
case GetterSetterType:
|
|
default:
|
|
return UString("null");
|
|
}
|
|
ASSERT_NOT_REACHED();
|
|
return UString("null");
|
|
}
|
|
|
|
};
|