1/*
2 * This file is part of the KDE libraries
3 * Copyright (C) 2012 Bernd Buschinski (b.buschinski@googlemail.com)
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General Public License
16 * along with this library; see the file COPYING.LIB. If not, write to
17 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 *
20 */
21
22
23#include "jsonstringify.h"
24
25#include <algorithm>
26
27#include "object.h"
28#include "operations.h"
29#include "array_instance.h"
30#include "number_object.h"
31#include "bool_object.h"
32#include "string_object.h"
33#include "function.h"
34
35#include "wtf/Assertions.h"
36
37
38namespace KJS {
39
40static const unsigned int StackObjectLimit = 1500;
41
42JSONStringify::JSONStringify(ExecState* exec, JSValue* replacer, JSValue* spacer)
43 : m_state(Success)
44{
45 m_replacerObject = replacer->getObject();
46
47 if (!m_replacerObject)
48 m_replacerType = Invalid;
49 else if (replacer->implementsCall())
50 m_replacerType = Function;
51 else if (m_replacerObject->inherits(&ArrayInstance::info)) {
52 //get all whitelist names
53 m_replacerType = Array;
54 PropertyNameArray names;
55 m_replacerObject->getOwnPropertyNames(exec, names, PropertyMap::ExcludeDontEnumProperties);
56 const int size = names.size();
57 bool isValidIndex = false;
58 for (int i = 0; i < size; ++i) {
59 names[i].toArrayIndex(&isValidIndex);
60 if (!isValidIndex)
61 continue;
62 m_whitelistNames.add(Identifier(m_replacerObject->get(exec, names[i])->toString(exec)));
63 if (exec->hadException()) {
64 m_state = FailedException;
65 return;
66 }
67 }
68 } else {
69 m_replacerType = Invalid;
70 m_replacerObject = 0;
71 }
72
73 JSObject* spacerObject = spacer->getObject();
74 m_emtpySpacer = true;
75 if (spacer->isString() || (spacerObject && spacerObject->inherits(&StringInstance::info))) {
76 m_spacer = spacer->toString(exec);
77 if (exec->hadException()) {
78 m_state = FailedException;
79 return;
80 }
81 if (!m_spacer.isEmpty()) {
82 m_spacer = m_spacer.substr(0, 10);
83 m_emtpySpacer = false;
84 }
85 } else if (spacer->isNumber() || (spacerObject && spacerObject->inherits(&NumberInstance::info))) {
86 double spacesDouble = spacer->toInteger(exec);
87 if (exec->hadException()) {
88 m_state = FailedException;
89 return;
90 }
91
92 int spaces;
93 if (isNaN(spacesDouble) || isInf(spacesDouble))
94 spaces = 0;
95 else
96 spaces = static_cast<int>(spacesDouble);
97
98 if (spaces > 0) {
99 m_emtpySpacer = false;
100 int max = std::min<int>(spaces, 10);
101 for (int i = 0; i < max; ++i)
102 m_spacer.append(' ');
103 }
104 }
105 m_rootIsUndefined = false;
106}
107
108JSValue* JSONStringify::stringify(ExecState* exec, JSValue* object, StringifyState& state)
109{
110 JSObject* holder = static_cast<JSObject *>(exec->lexicalInterpreter()->builtinObject()->construct(exec, List::empty()));
111 UString ret = stringifyValue(exec, object, jsString(""), holder);
112 state = m_state;
113
114 if (m_rootIsUndefined)
115 return jsUndefined();
116
117 if (m_state == Success)
118 return jsString(ret);
119 return jsUndefined();
120}
121
122UString JSONStringify::quotedString(ExecState* exec, const UString& string)
123{
124 //Check if we already failed
125 if (m_state != Success)
126 return UString();
127
128 if (exec->hadException()) {
129 m_state = FailedException;
130 return UString();
131 }
132
133 const int size = string.size();
134 UString ret = "\"";
135
136 for (int i = 0; i < size; ++i) {
137 int start = i;
138 static const short unsigned blackSlashUC = '\\';
139 static const short unsigned quoteUC = '\"';
140 while (i < size && (string[i].uc > 0x001F && string[i].uc != blackSlashUC && string[i].uc != quoteUC))
141 ++i;
142 ret += string.substr(start, i-start);
143
144 if (i >= size)
145 break;
146
147 switch (string[i].uc) {
148 case '\t':
149 ret += "\\t";
150 break;
151 case '\r':
152 ret += "\\r";
153 break;
154 case '\n':
155 ret += "\\n";
156 break;
157 case '\f':
158 ret += "\\f";
159 break;
160 case '\b':
161 ret += "\\b";
162 break;
163 case '"':
164 ret += "\\\"";
165 break;
166 case '\\':
167 ret += "\\\\";
168 break;
169 default:
170 static const char hexDigits[] = "0123456789abcdef";
171 short unsigned ch = string[i].uc;
172 ret.append("\\u");
173 ret.append(hexDigits[(ch >> 12) & 0xF]);
174 ret.append(hexDigits[(ch >> 8) & 0xF]);
175 ret.append(hexDigits[(ch >> 4) & 0xF]);
176 ret.append(hexDigits[ch & 0xF]);
177 break;
178 }
179 }
180
181 ret.append('\"');
182 return ret;
183}
184
185bool JSONStringify::isWhiteListed(const Identifier& propertyName)
186{
187 if (m_replacerType != Array)
188 return true;
189
190 return m_whitelistNames.contains(propertyName);
191}
192
193UString JSONStringify::stringifyObject(KJS::ExecState* exec, KJS::JSValue* object, KJS::JSValue* propertyName, KJS::JSObject* /*holder*/)
194{
195 if (m_state != Success)
196 return UString();
197
198 // As stringifyObject is only called with object->type() == ObhectType, this can't be null
199 JSObject* jso = object->getObject();
200
201 if (jso->hasProperty(exec, exec->propertyNames().toJSON)) {
202 JSObject* toJSONFunc = 0;
203 toJSONFunc = jso->get(exec, exec->propertyNames().toJSON)->getObject();
204
205 if (toJSONFunc) {
206 m_objectStack.push_back(object);
207 List args;
208 args.append(propertyName);
209 JSValue* toJSONCall = toJSONFunc->call(exec, jso, args);
210 if (exec->hadException()) {
211 m_state = FailedException;
212 return UString();
213 }
214
215 //Check if the toJSON call returned a function
216 // we check it here because our stack already contains an object,
217 // but this is still the root object.
218 if (m_objectStack.size() == 1 && toJSONCall->implementsCall()) {
219 m_rootIsUndefined = true;
220 return UString();
221 }
222
223 return stringifyValue(exec, toJSONCall, propertyName, jso);
224 }
225 }
226
227 if (jso->inherits(&BooleanInstance::info)) {
228 return jso->toString(exec);
229 } else if (jso->inherits(&NumberInstance::info)) {
230 double val = jso->toNumber(exec);
231 if (isInf(val) || isNaN(val)) // !isfinite
232 return UString("null");
233 return UString::from(val);
234 } else if (jso->inherits(&StringInstance::info)) {
235 return quotedString(exec, jso->toString(exec));
236 } else if (jso->implementsCall()) {
237 return UString("null");
238 } else if (jso->inherits(&ArrayInstance::info)) { //stringify array object
239 m_objectStack.push_back(object);
240 PropertyNameArray names;
241 jso->getPropertyNames(exec, names, KJS::PropertyMap::ExcludeDontEnumProperties);
242 const int size = names.size();
243 if (size == 0)
244 return UString("[]");
245
246 //filter names
247 PropertyNameArray whiteListedNames;
248 bool isValidIndex = false;
249 for (int i = 0; i < size; ++i) {
250 if (isWhiteListed(names[i])) {
251 names[i].toArrayIndex(&isValidIndex);
252 if (isValidIndex)
253 whiteListedNames.add(names[i]);
254 }
255 }
256 const int sizeWhitelisted = whiteListedNames.size();
257 if (sizeWhitelisted == 0)
258 return UString("[]");
259
260 UString ret = "[";
261 for (int i = 0; i < sizeWhitelisted; ++i) {
262 JSValue* arrayVal = jso->get(exec, whiteListedNames[i]);
263 //do not render undefined, ECMA Edition 5.1r6 - 15.12.3 NOTE 2
264 if (arrayVal->isUndefined())
265 continue;
266
267 if (!m_emtpySpacer) {
268 ret.append('\n');
269 ret += m_spacer;
270 }
271 ret += stringifyValue(exec, arrayVal, propertyName, jso);
272 if (m_state != Success)
273 return UString();
274 if (i != sizeWhitelisted-1)
275 ret.append(',');
276 }
277
278 if (!m_emtpySpacer)
279 ret.append('\n');
280
281 ret.append(']');
282 m_objectStack.pop_back();
283 return ret;
284 } else { //stringify real object
285 m_objectStack.push_back(object);
286 PropertyNameArray names;
287 jso->getPropertyNames(exec, names, KJS::PropertyMap::ExcludeDontEnumProperties);
288 const int size = names.size();
289 if (size == 0)
290 return UString("{}");
291
292 //filter names
293 PropertyNameArray whiteListedNames;
294 for (int i = 0; i < size; ++i) {
295 if (isWhiteListed(names[i]))
296 whiteListedNames.add(names[i]);
297 }
298 const int sizeWhitelisted = whiteListedNames.size();
299 if (sizeWhitelisted == 0)
300 return UString("{}");
301
302 UString ret = "{";
303 for (int i = 0; i < sizeWhitelisted; ++i) {
304 JSValue* objectVal = jso->get(exec, whiteListedNames[i]);
305 //do not render undefined, ECMA Edition 5.1r6 - 15.12.3 NOTE 2
306 if (objectVal->isUndefined())
307 continue;
308
309 if (!m_emtpySpacer) {
310 ret.append('\n');
311 ret += m_spacer;
312 }
313 ret += quotedString(exec, whiteListedNames[i].ustring());
314 ret += ":";
315 ret += stringifyValue(exec, objectVal, jsString(whiteListedNames[i].ustring()), jso);
316 if (m_state != Success)
317 return UString();
318 if (i != sizeWhitelisted-1)
319 ret.append(',');
320 }
321
322 if (!m_emtpySpacer)
323 ret.append('\n');
324
325 ret.append('}');
326 m_objectStack.pop_back();
327 return ret;
328 }
329 return UString("null");
330}
331
332UString JSONStringify::stringifyValue(KJS::ExecState* exec, KJS::JSValue* object, KJS::JSValue* propertyName, KJS::JSObject* holder)
333{
334 //Check if we already failed
335 if (m_state != Success)
336 return UString();
337
338 if (exec->hadException()) {
339 m_state = FailedException;
340 return UString();
341 }
342
343 if (m_objectStack.size() > StackObjectLimit) {
344 m_state = FailedStackLimitExceeded;
345 return UString();
346 }
347
348 if (!m_objectStack.empty()) {
349 std::vector<JSValue*>::iterator found = std::find(m_objectStack.begin(), m_objectStack.end(), object);
350 if (found != m_objectStack.end()) {
351 m_state = FailedCyclic;
352 return UString();
353 }
354 }
355
356 if (m_replacerObject && m_replacerType == Function) {
357 List args;
358 args.append(propertyName);
359 args.append(object);
360 object = m_replacerObject->call(exec, holder, args);
361 if (exec->hadException()) {
362 m_state = FailedException;
363 return UString();
364 }
365 }
366
367 //Check if root object is a function, after replace
368 if (m_objectStack.empty() && object->implementsCall()) {
369 m_rootIsUndefined = true;
370 return UString();
371 }
372
373 JSType type = object->type();
374 switch (type) {
375 case ObjectType:
376 return stringifyObject(exec, object, propertyName, holder);
377 case NumberType: {
378 double val = object->getNumber();
379 if (isInf(val) || isNaN(val)) // !isfinite
380 return UString("null");
381 // fall through
382 }
383 case BooleanType:
384 return object->toString(exec);
385 case StringType:
386 return quotedString(exec, object->toString(exec));
387 break;
388 case UndefinedType:
389 // Special case: while we normally don't render undefined,
390 // this is not the case if our "root" object is undefined,
391 // or replaced to undefined.
392 // Hence check if root object, AFTER REPLACE, is undefined.
393 if (m_objectStack.empty()) {
394 m_rootIsUndefined = true;
395 return UString();
396 }
397 // beside from root Object we should never render Undefined
398 ASSERT_NOT_REACHED();
399 case NullType:
400 case UnspecifiedType:
401 case GetterSetterType:
402 default:
403 return UString("null");
404 }
405 ASSERT_NOT_REACHED();
406 return UString("null");
407}
408
409};
410