File indexing completed on 2024-05-12 15:43:25

0001 /*
0002  *  This file is part of the KDE libraries
0003  *  Copyright (C) 2012 Bernd Buschinski (b.buschinski@googlemail.com)
0004  *
0005  *  This library is free software; you can redistribute it and/or
0006  *  modify it under the terms of the GNU Library General Public
0007  *  License as published by the Free Software Foundation; either
0008  *  version 2 of the License, or (at your option) any later version.
0009  *
0010  *  This library is distributed in the hope that it will be useful,
0011  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
0012  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0013  *  Library General Public License for more details.
0014  *
0015  *  You should have received a copy of the GNU Library General Public License
0016  *  along with this library; see the file COPYING.LIB.  If not, write to
0017  *  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0018  *  Boston, MA 02110-1301, USA.
0019  *
0020  */
0021 
0022 #include "jsonstringify.h"
0023 
0024 #include <algorithm>
0025 
0026 #include "object.h"
0027 #include "operations.h"
0028 #include "array_instance.h"
0029 #include "number_object.h"
0030 #include "bool_object.h"
0031 #include "string_object.h"
0032 #include "function.h"
0033 
0034 #include "wtf/Assertions.h"
0035 
0036 namespace KJS
0037 {
0038 
0039 static const unsigned int StackObjectLimit = 1500;
0040 
0041 JSONStringify::JSONStringify(ExecState *exec, JSValue *replacer, JSValue *spacer)
0042     : m_state(Success)
0043 {
0044     m_replacerObject = JSValue::getObject(replacer);
0045 
0046     if (!m_replacerObject) {
0047         m_replacerType = Invalid;
0048     } else if (JSValue::implementsCall(replacer)) {
0049         m_replacerType = Function;
0050     } else if (m_replacerObject->inherits(&ArrayInstance::info)) {
0051         //get all whitelist names
0052         m_replacerType = Array;
0053         PropertyNameArray names;
0054         m_replacerObject->getOwnPropertyNames(exec, names, PropertyMap::ExcludeDontEnumProperties);
0055         const int size = names.size();
0056         bool isValidIndex = false;
0057         for (int i = 0; i < size; ++i) {
0058             names[i].toArrayIndex(&isValidIndex);
0059             if (!isValidIndex) {
0060                 continue;
0061             }
0062             m_whitelistNames.add(Identifier(JSValue::toString(m_replacerObject->get(exec, names[i]), exec)));
0063             if (exec->hadException()) {
0064                 m_state = FailedException;
0065                 return;
0066             }
0067         }
0068     } else {
0069         m_replacerType = Invalid;
0070         m_replacerObject = nullptr;
0071     }
0072 
0073     JSObject *spacerObject = JSValue::getObject(spacer);
0074     m_emtpySpacer = true;
0075     if (JSValue::isString(spacer) || (spacerObject && spacerObject->inherits(&StringInstance::info))) {
0076         m_spacer = JSValue::toString(spacer, exec);
0077         if (exec->hadException()) {
0078             m_state = FailedException;
0079             return;
0080         }
0081         if (!m_spacer.isEmpty()) {
0082             m_spacer = m_spacer.substr(0, 10);
0083             m_emtpySpacer = false;
0084         }
0085     } else if (JSValue::isNumber(spacer) || (spacerObject && spacerObject->inherits(&NumberInstance::info))) {
0086         double spacesDouble = JSValue::toInteger(spacer, exec);
0087         if (exec->hadException()) {
0088             m_state = FailedException;
0089             return;
0090         }
0091 
0092         int spaces;
0093         if (isNaN(spacesDouble) || isInf(spacesDouble)) {
0094             spaces = 0;
0095         } else {
0096             spaces = static_cast<int>(spacesDouble);
0097         }
0098 
0099         if (spaces > 0) {
0100             m_emtpySpacer = false;
0101             int max = std::min<int>(spaces, 10);
0102             for (int i = 0; i < max; ++i) {
0103                 m_spacer.append(' ');
0104             }
0105         }
0106     }
0107     m_rootIsUndefined = false;
0108 }
0109 
0110 JSValue *JSONStringify::stringify(ExecState *exec, JSValue *object, StringifyState &state)
0111 {
0112     JSObject *holder = static_cast<JSObject *>(exec->lexicalInterpreter()->builtinObject()->construct(exec, List::empty()));
0113     UString ret = stringifyValue(exec, object, jsString(""), holder);
0114     state = m_state;
0115 
0116     if (m_rootIsUndefined) {
0117         return jsUndefined();
0118     }
0119 
0120     if (m_state == Success) {
0121         return jsString(ret);
0122     }
0123     return jsUndefined();
0124 }
0125 
0126 UString JSONStringify::quotedString(ExecState *exec, const UString &string)
0127 {
0128     //Check if we already failed
0129     if (m_state != Success) {
0130         return UString();
0131     }
0132 
0133     if (exec->hadException()) {
0134         m_state = FailedException;
0135         return UString();
0136     }
0137 
0138     const int size = string.size();
0139     UString ret = "\"";
0140 
0141     for (int i = 0; i < size; ++i) {
0142         int start = i;
0143         static const short unsigned blackSlashUC = '\\';
0144         static const short unsigned quoteUC = '\"';
0145         while (i < size && (string[i].uc > 0x001F && string[i].uc != blackSlashUC && string[i].uc != quoteUC)) {
0146             ++i;
0147         }
0148         ret += string.substr(start, i - start);
0149 
0150         if (i >= size) {
0151             break;
0152         }
0153 
0154         switch (string[i].uc) {
0155         case '\t':
0156             ret += "\\t";
0157             break;
0158         case '\r':
0159             ret += "\\r";
0160             break;
0161         case '\n':
0162             ret += "\\n";
0163             break;
0164         case '\f':
0165             ret += "\\f";
0166             break;
0167         case '\b':
0168             ret += "\\b";
0169             break;
0170         case '"':
0171             ret += "\\\"";
0172             break;
0173         case '\\':
0174             ret += "\\\\";
0175             break;
0176         default:
0177             static const char hexDigits[] = "0123456789abcdef";
0178             short unsigned ch = string[i].uc;
0179             ret.append("\\u");
0180             ret.append(hexDigits[(ch >> 12) & 0xF]);
0181             ret.append(hexDigits[(ch >> 8) & 0xF]);
0182             ret.append(hexDigits[(ch >> 4) & 0xF]);
0183             ret.append(hexDigits[ch & 0xF]);
0184             break;
0185         }
0186     }
0187 
0188     ret.append('\"');
0189     return ret;
0190 }
0191 
0192 bool JSONStringify::isWhiteListed(const Identifier &propertyName)
0193 {
0194     if (m_replacerType != Array) {
0195         return true;
0196     }
0197 
0198     return m_whitelistNames.contains(propertyName);
0199 }
0200 
0201 UString JSONStringify::stringifyObject(KJS::ExecState *exec, KJS::JSValue *object, KJS::JSValue *propertyName, KJS::JSObject * /*holder*/)
0202 {
0203     if (m_state != Success) {
0204         return UString();
0205     }
0206 
0207     // As stringifyObject is only called with JSValue::type(object) == ObhectType, this can't be null
0208     JSObject *jso = JSValue::getObject(object);
0209 
0210     if (jso->hasProperty(exec, exec->propertyNames().toJSON)) {
0211         JSObject *toJSONFunc = nullptr;
0212         toJSONFunc = JSValue::getObject(jso->get(exec, exec->propertyNames().toJSON));
0213 
0214         if (toJSONFunc) {
0215             m_objectStack.push_back(object);
0216             List args;
0217             args.append(propertyName);
0218             JSValue *toJSONCall = toJSONFunc->call(exec, jso, args);
0219             if (exec->hadException()) {
0220                 m_state = FailedException;
0221                 return UString();
0222             }
0223 
0224             //Check if the toJSON call returned a function
0225             // we check it here because our stack already contains an object,
0226             // but this is still the root object.
0227             if (m_objectStack.size() == 1 && JSValue::implementsCall(toJSONCall)) {
0228                 m_rootIsUndefined = true;
0229                 return UString();
0230             }
0231 
0232             return stringifyValue(exec, toJSONCall, propertyName, jso);
0233         }
0234     }
0235 
0236     if (jso->inherits(&BooleanInstance::info)) {
0237         return jso->toString(exec);
0238     } else if (jso->inherits(&NumberInstance::info)) {
0239         double val = jso->toNumber(exec);
0240         if (isInf(val) || isNaN(val)) { // !isfinite
0241             return UString("null");
0242         }
0243         return UString::from(val);
0244     } else if (jso->inherits(&StringInstance::info)) {
0245         return quotedString(exec, jso->toString(exec));
0246     } else if (jso->implementsCall()) {
0247         return UString("null");
0248     } else if (jso->inherits(&ArrayInstance::info)) { //stringify array object
0249         m_objectStack.push_back(object);
0250         PropertyNameArray names;
0251         jso->getPropertyNames(exec, names, KJS::PropertyMap::ExcludeDontEnumProperties);
0252         const int size = names.size();
0253         if (size == 0) {
0254             return UString("[]");
0255         }
0256 
0257         //filter names
0258         PropertyNameArray whiteListedNames;
0259         bool isValidIndex = false;
0260         for (int i = 0; i < size; ++i) {
0261             if (isWhiteListed(names[i])) {
0262                 names[i].toArrayIndex(&isValidIndex);
0263                 if (isValidIndex) {
0264                     whiteListedNames.add(names[i]);
0265                 }
0266             }
0267         }
0268         const int sizeWhitelisted = whiteListedNames.size();
0269         if (sizeWhitelisted == 0) {
0270             return UString("[]");
0271         }
0272 
0273         UString ret = "[";
0274         for (int i = 0; i < sizeWhitelisted; ++i) {
0275             JSValue *arrayVal = jso->get(exec, whiteListedNames[i]);
0276             //do not render undefined, ECMA Edition 5.1r6 - 15.12.3 NOTE 2
0277             if (JSValue::isUndefined(arrayVal)) {
0278                 continue;
0279             }
0280 
0281             if (!m_emtpySpacer) {
0282                 ret.append('\n');
0283                 ret += m_spacer;
0284             }
0285             ret += stringifyValue(exec, arrayVal, propertyName, jso);
0286             if (m_state != Success) {
0287                 return UString();
0288             }
0289             if (i != sizeWhitelisted - 1) {
0290                 ret.append(',');
0291             }
0292         }
0293 
0294         if (!m_emtpySpacer) {
0295             ret.append('\n');
0296         }
0297 
0298         ret.append(']');
0299         m_objectStack.pop_back();
0300         return ret;
0301     } else { //stringify real object
0302         m_objectStack.push_back(object);
0303         PropertyNameArray names;
0304         jso->getPropertyNames(exec, names, KJS::PropertyMap::ExcludeDontEnumProperties);
0305         const int size = names.size();
0306         if (size == 0) {
0307             return UString("{}");
0308         }
0309 
0310         //filter names
0311         PropertyNameArray whiteListedNames;
0312         for (int i = 0; i < size; ++i) {
0313             if (isWhiteListed(names[i])) {
0314                 whiteListedNames.add(names[i]);
0315             }
0316         }
0317         const int sizeWhitelisted = whiteListedNames.size();
0318         if (sizeWhitelisted == 0) {
0319             return UString("{}");
0320         }
0321 
0322         UString ret = "{";
0323         for (int i = 0; i < sizeWhitelisted; ++i) {
0324             JSValue *objectVal = jso->get(exec, whiteListedNames[i]);
0325             //do not render undefined, ECMA Edition 5.1r6 - 15.12.3 NOTE 2
0326             if (JSValue::isUndefined(objectVal)) {
0327                 continue;
0328             }
0329 
0330             if (!m_emtpySpacer) {
0331                 ret.append('\n');
0332                 ret += m_spacer;
0333             }
0334             ret += quotedString(exec, whiteListedNames[i].ustring());
0335             ret += ":";
0336             ret += stringifyValue(exec, objectVal, jsString(whiteListedNames[i].ustring()), jso);
0337             if (m_state != Success) {
0338                 return UString();
0339             }
0340             if (i != sizeWhitelisted - 1) {
0341                 ret.append(',');
0342             }
0343         }
0344 
0345         if (!m_emtpySpacer) {
0346             ret.append('\n');
0347         }
0348 
0349         ret.append('}');
0350         m_objectStack.pop_back();
0351         return ret;
0352     }
0353     return UString("null");
0354 }
0355 
0356 UString JSONStringify::stringifyValue(KJS::ExecState *exec, KJS::JSValue *object, KJS::JSValue *propertyName, KJS::JSObject *holder)
0357 {
0358     //Check if we already failed
0359     if (m_state != Success) {
0360         return UString();
0361     }
0362 
0363     if (exec->hadException()) {
0364         m_state = FailedException;
0365         return UString();
0366     }
0367 
0368     if (m_objectStack.size() > StackObjectLimit) {
0369         m_state = FailedStackLimitExceeded;
0370         return UString();
0371     }
0372 
0373     if (!m_objectStack.empty()) {
0374         std::vector<JSValue *>::iterator found = std::find(m_objectStack.begin(), m_objectStack.end(), object);
0375         if (found != m_objectStack.end()) {
0376             m_state = FailedCyclic;
0377             return UString();
0378         }
0379     }
0380 
0381     if (m_replacerObject && m_replacerType == Function) {
0382         List args;
0383         args.append(propertyName);
0384         args.append(object);
0385         object = m_replacerObject->call(exec, holder, args);
0386         if (exec->hadException()) {
0387             m_state = FailedException;
0388             return UString();
0389         }
0390     }
0391 
0392     //Check if root object is a function, after replace
0393     if (m_objectStack.empty() && JSValue::implementsCall(object)) {
0394         m_rootIsUndefined = true;
0395         return UString();
0396     }
0397 
0398     JSType type = JSValue::type(object);
0399     switch (type) {
0400     case ObjectType:
0401         return stringifyObject(exec, object, propertyName, holder);
0402     case NumberType: {
0403         double val = JSValue::getNumber(object);
0404         if (isInf(val) || isNaN(val)) { // !isfinite
0405             return UString("null");
0406         }
0407         // fall through
0408     }
0409     case BooleanType:
0410         return JSValue::toString(object, exec);
0411     case StringType:
0412         return quotedString(exec, JSValue::toString(object, exec));
0413         break;
0414     case UndefinedType:
0415         // Special case: while we normally don't render undefined,
0416         // this is not the case if our "root" object is undefined,
0417         // or replaced to undefined.
0418         // Hence check if root object, AFTER REPLACE, is undefined.
0419         if (m_objectStack.empty()) {
0420             m_rootIsUndefined = true;
0421             return UString();
0422         }
0423         // beside from root Object we should never render Undefined
0424         ASSERT_NOT_REACHED();
0425     case NullType:
0426     case UnspecifiedType:
0427     case GetterSetterType:
0428     default:
0429         return UString("null");
0430     }
0431     ASSERT_NOT_REACHED();
0432     return UString("null");
0433 }
0434 
0435 }