Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "jsapi.h"
8 : #include "js/CharacterEncoding.h"
9 : #include "nsJSON.h"
10 : #include "nsIXPConnect.h"
11 : #include "nsIXPCScriptable.h"
12 : #include "nsStreamUtils.h"
13 : #include "nsIInputStream.h"
14 : #include "nsStringStream.h"
15 : #include "nsNetUtil.h"
16 : #include "nsIURI.h"
17 : #include "nsComponentManagerUtils.h"
18 : #include "nsContentUtils.h"
19 : #include "nsIScriptError.h"
20 : #include "nsCRTGlue.h"
21 : #include "nsIScriptSecurityManager.h"
22 : #include "NullPrincipal.h"
23 : #include "mozilla/Maybe.h"
24 : #include <algorithm>
25 :
26 : using namespace mozilla;
27 :
28 : #define JSON_STREAM_BUFSIZE 4096
29 :
30 16 : NS_INTERFACE_MAP_BEGIN(nsJSON)
31 16 : NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIJSON)
32 14 : NS_INTERFACE_MAP_ENTRY(nsIJSON)
33 12 : NS_INTERFACE_MAP_END
34 :
35 8 : NS_IMPL_ADDREF(nsJSON)
36 4 : NS_IMPL_RELEASE(nsJSON)
37 :
38 2 : nsJSON::nsJSON()
39 : {
40 2 : }
41 :
42 0 : nsJSON::~nsJSON()
43 : {
44 0 : }
45 :
46 : enum DeprecationWarning { EncodeWarning, DecodeWarning };
47 :
48 : static nsresult
49 0 : WarnDeprecatedMethod(DeprecationWarning warning)
50 : {
51 0 : return nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
52 0 : NS_LITERAL_CSTRING("DOM Core"), nullptr,
53 : nsContentUtils::eDOM_PROPERTIES,
54 : warning == EncodeWarning
55 : ? "nsIJSONEncodeDeprecatedWarning"
56 0 : : "nsIJSONDecodeDeprecatedWarning");
57 : }
58 :
59 : NS_IMETHODIMP
60 0 : nsJSON::Encode(JS::Handle<JS::Value> aValue, JSContext* cx, uint8_t aArgc,
61 : nsAString &aJSON)
62 : {
63 : // This function should only be called from JS.
64 0 : nsresult rv = WarnDeprecatedMethod(EncodeWarning);
65 0 : if (NS_FAILED(rv))
66 0 : return rv;
67 :
68 0 : if (aArgc == 0) {
69 0 : aJSON.SetIsVoid(true);
70 0 : return NS_OK;
71 : }
72 :
73 0 : nsJSONWriter writer;
74 0 : rv = EncodeInternal(cx, aValue, &writer);
75 :
76 : // FIXME: bug 408838. Get exception types sorted out
77 0 : if (NS_SUCCEEDED(rv) || rv == NS_ERROR_INVALID_ARG) {
78 0 : rv = NS_OK;
79 : // if we didn't consume anything, it's not JSON, so return null
80 0 : if (!writer.DidWrite()) {
81 0 : aJSON.SetIsVoid(true);
82 : } else {
83 0 : writer.FlushBuffer();
84 0 : aJSON.Append(writer.mOutputString);
85 : }
86 : }
87 :
88 0 : return rv;
89 : }
90 :
91 : static const char UTF8BOM[] = "\xEF\xBB\xBF";
92 :
93 0 : static nsresult CheckCharset(const char* aCharset)
94 : {
95 : // Check that the charset is permissible
96 0 : if (!(strcmp(aCharset, "UTF-8") == 0)) {
97 0 : return NS_ERROR_INVALID_ARG;
98 : }
99 :
100 0 : return NS_OK;
101 : }
102 :
103 : NS_IMETHODIMP
104 0 : nsJSON::EncodeToStream(nsIOutputStream *aStream,
105 : const char* aCharset,
106 : const bool aWriteBOM,
107 : JS::Handle<JS::Value> val,
108 : JSContext* cx,
109 : uint8_t aArgc)
110 : {
111 : // This function should only be called from JS.
112 0 : NS_ENSURE_ARG(aStream);
113 : nsresult rv;
114 :
115 0 : rv = CheckCharset(aCharset);
116 0 : NS_ENSURE_SUCCESS(rv, rv);
117 :
118 : // Check to see if we have a buffered stream
119 0 : nsCOMPtr<nsIOutputStream> bufferedStream;
120 : // FIXME: bug 408514.
121 : // NS_OutputStreamIsBuffered(aStream) asserts on file streams...
122 : //if (!NS_OutputStreamIsBuffered(aStream)) {
123 0 : rv = NS_NewBufferedOutputStream(getter_AddRefs(bufferedStream),
124 0 : aStream, 4096);
125 0 : NS_ENSURE_SUCCESS(rv, rv);
126 : // aStream = bufferedStream;
127 : //}
128 :
129 : uint32_t ignored;
130 0 : if (aWriteBOM) {
131 0 : rv = aStream->Write(UTF8BOM, 3, &ignored);
132 0 : NS_ENSURE_SUCCESS(rv, rv);
133 : }
134 :
135 0 : nsJSONWriter writer(bufferedStream);
136 :
137 0 : if (aArgc == 0) {
138 0 : return NS_OK;
139 : }
140 :
141 0 : rv = EncodeInternal(cx, val, &writer);
142 0 : NS_ENSURE_SUCCESS(rv, rv);
143 :
144 0 : rv = bufferedStream->Flush();
145 :
146 0 : return rv;
147 : }
148 :
149 : static bool
150 0 : WriteCallback(const char16_t *buf, uint32_t len, void *data)
151 : {
152 0 : nsJSONWriter *writer = static_cast<nsJSONWriter*>(data);
153 0 : nsresult rv = writer->Write((const char16_t*)buf, (uint32_t)len);
154 0 : if (NS_FAILED(rv))
155 0 : return false;
156 :
157 0 : return true;
158 : }
159 :
160 : NS_IMETHODIMP
161 0 : nsJSON::EncodeFromJSVal(JS::Value *value, JSContext *cx, nsAString &result)
162 : {
163 0 : result.Truncate();
164 :
165 0 : mozilla::Maybe<JSAutoCompartment> ac;
166 0 : if (value->isObject()) {
167 0 : JS::Rooted<JSObject*> obj(cx, &value->toObject());
168 0 : ac.emplace(cx, obj);
169 : }
170 :
171 0 : nsJSONWriter writer;
172 0 : JS::Rooted<JS::Value> vp(cx, *value);
173 0 : if (!JS_Stringify(cx, &vp, nullptr, JS::NullHandleValue, WriteCallback, &writer)) {
174 0 : return NS_ERROR_XPC_BAD_CONVERT_JS;
175 : }
176 0 : *value = vp;
177 :
178 0 : NS_ENSURE_TRUE(writer.DidWrite(), NS_ERROR_UNEXPECTED);
179 0 : writer.FlushBuffer();
180 0 : result.Assign(writer.mOutputString);
181 0 : return NS_OK;
182 : }
183 :
184 : nsresult
185 0 : nsJSON::EncodeInternal(JSContext* cx, const JS::Value& aValue,
186 : nsJSONWriter* writer)
187 : {
188 : // Backward compatibility:
189 : // nsIJSON does not allow to serialize anything other than objects
190 0 : if (!aValue.isObject()) {
191 0 : return NS_ERROR_INVALID_ARG;
192 : }
193 0 : JS::Rooted<JSObject*> obj(cx, &aValue.toObject());
194 :
195 : /* Backward compatibility:
196 : * Manually call toJSON if implemented by the object and check that
197 : * the result is still an object
198 : * Note: It is perfectly fine to not implement toJSON, so it is
199 : * perfectly fine for GetMethod to fail
200 : */
201 0 : JS::Rooted<JS::Value> val(cx, aValue);
202 0 : JS::Rooted<JS::Value> toJSON(cx);
203 0 : if (JS_GetProperty(cx, obj, "toJSON", &toJSON) &&
204 0 : toJSON.isObject() && JS::IsCallable(&toJSON.toObject())) {
205 : // If toJSON is implemented, it must not throw
206 0 : if (!JS_CallFunctionValue(cx, obj, toJSON, JS::HandleValueArray::empty(), &val)) {
207 0 : if (JS_IsExceptionPending(cx))
208 : // passing NS_OK will throw the pending exception
209 0 : return NS_OK;
210 :
211 : // No exception, but still failed
212 0 : return NS_ERROR_FAILURE;
213 : }
214 :
215 : // Backward compatibility:
216 : // nsIJSON does not allow to serialize anything other than objects
217 0 : if (val.isPrimitive())
218 0 : return NS_ERROR_INVALID_ARG;
219 : }
220 : // GetMethod may have thrown
221 0 : else if (JS_IsExceptionPending(cx))
222 : // passing NS_OK will throw the pending exception
223 0 : return NS_OK;
224 :
225 : // Backward compatibility:
226 : // function shall not pass, just "plain" objects and arrays
227 0 : JSType type = JS_TypeOfValue(cx, val);
228 0 : if (type == JSTYPE_FUNCTION)
229 0 : return NS_ERROR_INVALID_ARG;
230 :
231 : // We're good now; try to stringify
232 0 : if (!JS_Stringify(cx, &val, nullptr, JS::NullHandleValue, WriteCallback, writer))
233 0 : return NS_ERROR_FAILURE;
234 :
235 0 : return NS_OK;
236 : }
237 :
238 :
239 0 : nsJSONWriter::nsJSONWriter() : mStream(nullptr),
240 : mBuffer(nullptr),
241 : mBufferCount(0),
242 : mDidWrite(false),
243 0 : mEncoder(nullptr)
244 : {
245 0 : }
246 :
247 0 : nsJSONWriter::nsJSONWriter(nsIOutputStream* aStream)
248 : : mStream(aStream)
249 : , mBuffer(nullptr)
250 : , mBufferCount(0)
251 : , mDidWrite(false)
252 0 : , mEncoder(UTF_8_ENCODING->NewEncoder())
253 : {
254 0 : }
255 :
256 0 : nsJSONWriter::~nsJSONWriter()
257 : {
258 0 : delete [] mBuffer;
259 0 : }
260 :
261 : nsresult
262 0 : nsJSONWriter::Write(const char16_t *aBuffer, uint32_t aLength)
263 : {
264 0 : if (mStream) {
265 0 : return WriteToStream(mStream, mEncoder.get(), aBuffer, aLength);
266 : }
267 :
268 0 : if (!mDidWrite) {
269 0 : mBuffer = new char16_t[JSON_STREAM_BUFSIZE];
270 0 : mDidWrite = true;
271 : }
272 :
273 0 : if (JSON_STREAM_BUFSIZE <= aLength + mBufferCount) {
274 0 : mOutputString.Append(mBuffer, mBufferCount);
275 0 : mBufferCount = 0;
276 : }
277 :
278 0 : if (JSON_STREAM_BUFSIZE <= aLength) {
279 : // we know mBufferCount is 0 because we know we hit the if above
280 0 : mOutputString.Append(aBuffer, aLength);
281 : } else {
282 0 : memcpy(&mBuffer[mBufferCount], aBuffer, aLength * sizeof(char16_t));
283 0 : mBufferCount += aLength;
284 : }
285 :
286 0 : return NS_OK;
287 : }
288 :
289 0 : bool nsJSONWriter::DidWrite()
290 : {
291 0 : return mDidWrite;
292 : }
293 :
294 : void
295 0 : nsJSONWriter::FlushBuffer()
296 : {
297 0 : mOutputString.Append(mBuffer, mBufferCount);
298 0 : }
299 :
300 : nsresult
301 0 : nsJSONWriter::WriteToStream(nsIOutputStream* aStream,
302 : Encoder* encoder,
303 : const char16_t* aBuffer,
304 : uint32_t aLength)
305 : {
306 : uint8_t buffer[1024];
307 0 : auto dst = MakeSpan(buffer);
308 0 : auto src = MakeSpan(aBuffer, aLength);
309 :
310 : for (;;) {
311 : uint32_t result;
312 : size_t read;
313 : size_t written;
314 : bool hadErrors;
315 0 : Tie(result, read, written, hadErrors) =
316 0 : encoder->EncodeFromUTF16(src, dst, false);
317 : Unused << hadErrors;
318 0 : src = src.From(read);
319 : uint32_t ignored;
320 : nsresult rv =
321 0 : aStream->Write(reinterpret_cast<const char*>(buffer), written, &ignored);
322 0 : if (NS_FAILED(rv)) {
323 0 : return rv;
324 : }
325 0 : if (result == kInputEmpty) {
326 0 : mDidWrite = true;
327 0 : return NS_OK;
328 : }
329 0 : }
330 : }
331 :
332 : NS_IMETHODIMP
333 0 : nsJSON::Decode(const nsAString& json, JSContext* cx,
334 : JS::MutableHandle<JS::Value> aRetval)
335 : {
336 0 : nsresult rv = WarnDeprecatedMethod(DecodeWarning);
337 0 : if (NS_FAILED(rv))
338 0 : return rv;
339 :
340 0 : const char16_t *data = json.BeginReading();
341 0 : uint32_t len = json.Length();
342 0 : nsCOMPtr<nsIInputStream> stream;
343 0 : rv = NS_NewByteInputStream(getter_AddRefs(stream),
344 : reinterpret_cast<const char*>(data),
345 : len * sizeof(char16_t),
346 0 : NS_ASSIGNMENT_DEPEND);
347 0 : NS_ENSURE_SUCCESS(rv, rv);
348 0 : return DecodeInternal(cx, stream, len, false, aRetval);
349 : }
350 :
351 : NS_IMETHODIMP
352 2 : nsJSON::DecodeFromStream(nsIInputStream *aStream, int32_t aContentLength,
353 : JSContext* cx, JS::MutableHandle<JS::Value> aRetval)
354 : {
355 2 : return DecodeInternal(cx, aStream, aContentLength, true, aRetval);
356 : }
357 :
358 : NS_IMETHODIMP
359 0 : nsJSON::DecodeToJSVal(const nsAString &str, JSContext *cx,
360 : JS::MutableHandle<JS::Value> result)
361 : {
362 0 : if (!JS_ParseJSON(cx, static_cast<const char16_t*>(PromiseFlatString(str).get()),
363 : str.Length(), result)) {
364 0 : return NS_ERROR_UNEXPECTED;
365 : }
366 0 : return NS_OK;
367 : }
368 :
369 : nsresult
370 2 : nsJSON::DecodeInternal(JSContext* cx,
371 : nsIInputStream *aStream,
372 : int32_t aContentLength,
373 : bool aNeedsConverter,
374 : JS::MutableHandle<JS::Value> aRetval)
375 : {
376 : // Consume the stream
377 4 : nsCOMPtr<nsIChannel> jsonChannel;
378 2 : if (!mURI) {
379 2 : NS_NewURI(getter_AddRefs(mURI), NS_LITERAL_CSTRING("about:blank"), 0, 0 );
380 2 : if (!mURI)
381 0 : return NS_ERROR_OUT_OF_MEMORY;
382 : }
383 :
384 : nsresult rv;
385 4 : nsCOMPtr<nsIPrincipal> nullPrincipal = NullPrincipal::Create();
386 :
387 : // The ::Decode function is deprecated [Bug 675797] and the following
388 : // channel is never openend, so it does not matter what securityFlags
389 : // we pass to NS_NewInputStreamChannel here.
390 6 : rv = NS_NewInputStreamChannel(getter_AddRefs(jsonChannel),
391 : mURI,
392 : aStream,
393 : nullPrincipal,
394 : nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED,
395 : nsIContentPolicy::TYPE_OTHER,
396 6 : NS_LITERAL_CSTRING("application/json"));
397 :
398 2 : if (!jsonChannel || NS_FAILED(rv))
399 0 : return NS_ERROR_FAILURE;
400 :
401 : RefPtr<nsJSONListener> jsonListener =
402 6 : new nsJSONListener(cx, aRetval.address(), aNeedsConverter);
403 :
404 : //XXX this stream pattern should be consolidated in netwerk
405 2 : rv = jsonListener->OnStartRequest(jsonChannel, nullptr);
406 2 : if (NS_FAILED(rv)) {
407 0 : jsonChannel->Cancel(rv);
408 0 : return rv;
409 : }
410 :
411 : nsresult status;
412 2 : jsonChannel->GetStatus(&status);
413 2 : uint64_t offset = 0;
414 6 : while (NS_SUCCEEDED(status)) {
415 : uint64_t available;
416 4 : rv = aStream->Available(&available);
417 4 : if (rv == NS_BASE_STREAM_CLOSED) {
418 0 : rv = NS_OK;
419 2 : break;
420 : }
421 4 : if (NS_FAILED(rv)) {
422 0 : jsonChannel->Cancel(rv);
423 0 : break;
424 : }
425 4 : if (!available)
426 2 : break; // blocking input stream has none available when done
427 :
428 2 : if (available > UINT32_MAX)
429 0 : available = UINT32_MAX;
430 :
431 4 : rv = jsonListener->OnDataAvailable(jsonChannel, nullptr,
432 : aStream,
433 : offset,
434 4 : (uint32_t)available);
435 2 : if (NS_FAILED(rv)) {
436 0 : jsonChannel->Cancel(rv);
437 0 : break;
438 : }
439 :
440 2 : offset += available;
441 2 : jsonChannel->GetStatus(&status);
442 : }
443 2 : NS_ENSURE_SUCCESS(rv, rv);
444 :
445 2 : rv = jsonListener->OnStopRequest(jsonChannel, nullptr, status);
446 2 : NS_ENSURE_SUCCESS(rv, rv);
447 :
448 2 : return NS_OK;
449 : }
450 :
451 : nsresult
452 2 : NS_NewJSON(nsISupports* aOuter, REFNSIID aIID, void** aResult)
453 : {
454 2 : nsJSON* json = new nsJSON();
455 2 : NS_ADDREF(json);
456 2 : *aResult = json;
457 :
458 2 : return NS_OK;
459 : }
460 :
461 2 : nsJSONListener::nsJSONListener(JSContext *cx, JS::Value *rootVal,
462 2 : bool needsConverter)
463 : : mNeedsConverter(needsConverter),
464 : mCx(cx),
465 2 : mRootVal(rootVal)
466 : {
467 2 : }
468 :
469 4 : nsJSONListener::~nsJSONListener()
470 : {
471 6 : }
472 :
473 0 : NS_INTERFACE_MAP_BEGIN(nsJSONListener)
474 0 : NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsJSONListener)
475 0 : NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
476 0 : NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
477 0 : NS_INTERFACE_MAP_END
478 :
479 2 : NS_IMPL_ADDREF(nsJSONListener)
480 2 : NS_IMPL_RELEASE(nsJSONListener)
481 :
482 : NS_IMETHODIMP
483 2 : nsJSONListener::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext)
484 : {
485 2 : mDecoder = nullptr;
486 :
487 2 : return NS_OK;
488 : }
489 :
490 : NS_IMETHODIMP
491 2 : nsJSONListener::OnStopRequest(nsIRequest *aRequest, nsISupports *aContext,
492 : nsresult aStatusCode)
493 : {
494 4 : JS::Rooted<JS::Value> reviver(mCx, JS::NullValue()), value(mCx);
495 :
496 2 : JS::ConstTwoByteChars chars(reinterpret_cast<const char16_t*>(mBufferedChars.Elements()),
497 4 : mBufferedChars.Length());
498 6 : bool ok = JS_ParseJSONWithReviver(mCx, chars.begin().get(),
499 2 : uint32_t(mBufferedChars.Length()),
500 2 : reviver, &value);
501 :
502 2 : *mRootVal = value;
503 2 : mBufferedChars.TruncateLength(0);
504 4 : return ok ? NS_OK : NS_ERROR_FAILURE;
505 : }
506 :
507 : NS_IMETHODIMP
508 2 : nsJSONListener::OnDataAvailable(nsIRequest *aRequest, nsISupports *aContext,
509 : nsIInputStream *aStream,
510 : uint64_t aOffset, uint32_t aLength)
511 : {
512 2 : nsresult rv = NS_OK;
513 :
514 : char buffer[JSON_STREAM_BUFSIZE];
515 2 : unsigned long bytesRemaining = aLength;
516 6 : while (bytesRemaining) {
517 : unsigned int bytesRead;
518 2 : rv = aStream->Read(buffer,
519 4 : std::min((unsigned long)sizeof(buffer), bytesRemaining),
520 4 : &bytesRead);
521 2 : NS_ENSURE_SUCCESS(rv, rv);
522 2 : rv = ProcessBytes(buffer, bytesRead);
523 2 : NS_ENSURE_SUCCESS(rv, rv);
524 2 : bytesRemaining -= bytesRead;
525 : }
526 :
527 2 : return rv;
528 : }
529 :
530 : nsresult
531 2 : nsJSONListener::ProcessBytes(const char* aBuffer, uint32_t aByteLength)
532 : {
533 2 : if (mNeedsConverter && !mDecoder) {
534 : // BOM sniffing is built into the decoder.
535 2 : mDecoder = UTF_8_ENCODING->NewDecoder();
536 : }
537 :
538 2 : if (!aBuffer)
539 0 : return NS_OK;
540 :
541 : nsresult rv;
542 2 : if (mNeedsConverter) {
543 2 : rv = ConsumeConverted(aBuffer, aByteLength);
544 : } else {
545 0 : uint32_t unichars = aByteLength / sizeof(char16_t);
546 0 : rv = Consume((char16_t *) aBuffer, unichars);
547 : }
548 :
549 2 : return rv;
550 : }
551 :
552 : nsresult
553 2 : nsJSONListener::ConsumeConverted(const char* aBuffer, uint32_t aByteLength)
554 : {
555 2 : CheckedInt<size_t> needed = mDecoder->MaxUTF16BufferLength(aByteLength);
556 2 : if (!needed.isValid()) {
557 0 : return NS_ERROR_OUT_OF_MEMORY;
558 : }
559 :
560 2 : CheckedInt<size_t> total(needed);
561 2 : total += mBufferedChars.Length();
562 2 : if (!total.isValid()) {
563 0 : return NS_ERROR_OUT_OF_MEMORY;
564 : }
565 :
566 2 : char16_t* endelems = mBufferedChars.AppendElements(needed.value(), fallible);
567 2 : if (!endelems) {
568 0 : return NS_ERROR_OUT_OF_MEMORY;
569 : }
570 :
571 2 : auto src = AsBytes(MakeSpan(aBuffer, aByteLength));
572 2 : auto dst = MakeSpan(endelems, needed.value());
573 : uint32_t result;
574 : size_t read;
575 : size_t written;
576 : bool hadErrors;
577 : // Ignoring EOF like the old code
578 4 : Tie(result, read, written, hadErrors) =
579 6 : mDecoder->DecodeToUTF16(src, dst, false);
580 2 : MOZ_ASSERT(result == kInputEmpty);
581 2 : MOZ_ASSERT(read == src.Length());
582 2 : MOZ_ASSERT(written <= needed.value());
583 : Unused << hadErrors;
584 2 : mBufferedChars.TruncateLength(total.value() - (needed.value() - written));
585 2 : return NS_OK;
586 : }
587 :
588 : nsresult
589 0 : nsJSONListener::Consume(const char16_t* aBuffer, uint32_t aByteLength)
590 : {
591 0 : if (!mBufferedChars.AppendElements(aBuffer, aByteLength))
592 0 : return NS_ERROR_FAILURE;
593 :
594 0 : return NS_OK;
595 : }
|