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 }