Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 sw=2 et tw=79: */
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/. */
7 #include "jsapi.h"
8 #include "js/CharacterEncoding.h"
9 #include "js/OldDebugAPI.h"
10 #include "nsJSON.h"
11 #include "nsIXPConnect.h"
12 #include "nsIXPCScriptable.h"
13 #include "nsStreamUtils.h"
14 #include "nsIInputStream.h"
15 #include "nsStringStream.h"
16 #include "mozilla/dom/EncodingUtils.h"
17 #include "nsIUnicodeEncoder.h"
18 #include "nsIUnicodeDecoder.h"
19 #include "nsXPCOMStrings.h"
20 #include "nsNetUtil.h"
21 #include "nsContentUtils.h"
22 #include "nsIScriptError.h"
23 #include "nsCRTGlue.h"
24 #include "nsAutoPtr.h"
25 #include "nsIScriptSecurityManager.h"
26 #include "mozilla/Maybe.h"
27 #include <algorithm>
29 using mozilla::dom::EncodingUtils;
31 #define JSON_STREAM_BUFSIZE 4096
33 NS_INTERFACE_MAP_BEGIN(nsJSON)
34 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIJSON)
35 NS_INTERFACE_MAP_ENTRY(nsIJSON)
36 NS_INTERFACE_MAP_END
38 NS_IMPL_ADDREF(nsJSON)
39 NS_IMPL_RELEASE(nsJSON)
41 nsJSON::nsJSON()
42 {
43 }
45 nsJSON::~nsJSON()
46 {
47 }
49 enum DeprecationWarning { EncodeWarning, DecodeWarning };
51 static nsresult
52 WarnDeprecatedMethod(DeprecationWarning warning)
53 {
54 return nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
55 NS_LITERAL_CSTRING("DOM Core"), nullptr,
56 nsContentUtils::eDOM_PROPERTIES,
57 warning == EncodeWarning
58 ? "nsIJSONEncodeDeprecatedWarning"
59 : "nsIJSONDecodeDeprecatedWarning");
60 }
62 NS_IMETHODIMP
63 nsJSON::Encode(JS::Handle<JS::Value> aValue, JSContext* cx, uint8_t aArgc,
64 nsAString &aJSON)
65 {
66 // This function should only be called from JS.
67 nsresult rv = WarnDeprecatedMethod(EncodeWarning);
68 if (NS_FAILED(rv))
69 return rv;
71 if (aArgc == 0) {
72 aJSON.Truncate();
73 aJSON.SetIsVoid(true);
74 return NS_OK;
75 }
77 nsJSONWriter writer;
78 rv = EncodeInternal(cx, aValue, &writer);
80 // FIXME: bug 408838. Get exception types sorted out
81 if (NS_SUCCEEDED(rv) || rv == NS_ERROR_INVALID_ARG) {
82 rv = NS_OK;
83 // if we didn't consume anything, it's not JSON, so return null
84 if (!writer.DidWrite()) {
85 aJSON.Truncate();
86 aJSON.SetIsVoid(true);
87 } else {
88 writer.FlushBuffer();
89 aJSON.Append(writer.mOutputString);
90 }
91 }
93 return rv;
94 }
96 static const char UTF8BOM[] = "\xEF\xBB\xBF";
97 static const char UTF16LEBOM[] = "\xFF\xFE";
98 static const char UTF16BEBOM[] = "\xFE\xFF";
100 static nsresult CheckCharset(const char* aCharset)
101 {
102 // Check that the charset is permissible
103 if (!(strcmp(aCharset, "UTF-8") == 0 ||
104 strcmp(aCharset, "UTF-16LE") == 0 ||
105 strcmp(aCharset, "UTF-16BE") == 0)) {
106 return NS_ERROR_INVALID_ARG;
107 }
109 return NS_OK;
110 }
112 NS_IMETHODIMP
113 nsJSON::EncodeToStream(nsIOutputStream *aStream,
114 const char* aCharset,
115 const bool aWriteBOM,
116 JS::Handle<JS::Value> val,
117 JSContext* cx,
118 uint8_t aArgc)
119 {
120 // This function should only be called from JS.
121 NS_ENSURE_ARG(aStream);
122 nsresult rv;
124 rv = CheckCharset(aCharset);
125 NS_ENSURE_SUCCESS(rv, rv);
127 // Check to see if we have a buffered stream
128 nsCOMPtr<nsIOutputStream> bufferedStream;
129 // FIXME: bug 408514.
130 // NS_OutputStreamIsBuffered(aStream) asserts on file streams...
131 //if (!NS_OutputStreamIsBuffered(aStream)) {
132 rv = NS_NewBufferedOutputStream(getter_AddRefs(bufferedStream),
133 aStream, 4096);
134 NS_ENSURE_SUCCESS(rv, rv);
135 // aStream = bufferedStream;
136 //}
138 uint32_t ignored;
139 if (aWriteBOM) {
140 if (strcmp(aCharset, "UTF-8") == 0)
141 rv = aStream->Write(UTF8BOM, 3, &ignored);
142 else if (strcmp(aCharset, "UTF-16LE") == 0)
143 rv = aStream->Write(UTF16LEBOM, 2, &ignored);
144 else if (strcmp(aCharset, "UTF-16BE") == 0)
145 rv = aStream->Write(UTF16BEBOM, 2, &ignored);
146 NS_ENSURE_SUCCESS(rv, rv);
147 }
149 nsJSONWriter writer(bufferedStream);
150 rv = writer.SetCharset(aCharset);
151 NS_ENSURE_SUCCESS(rv, rv);
153 if (aArgc == 0) {
154 return NS_OK;
155 }
157 rv = EncodeInternal(cx, val, &writer);
158 NS_ENSURE_SUCCESS(rv, rv);
160 rv = bufferedStream->Flush();
162 return rv;
163 }
165 static bool
166 WriteCallback(const jschar *buf, uint32_t len, void *data)
167 {
168 nsJSONWriter *writer = static_cast<nsJSONWriter*>(data);
169 nsresult rv = writer->Write((const char16_t*)buf, (uint32_t)len);
170 if (NS_FAILED(rv))
171 return false;
173 return true;
174 }
176 NS_IMETHODIMP
177 nsJSON::EncodeFromJSVal(JS::Value *value, JSContext *cx, nsAString &result)
178 {
179 result.Truncate();
181 mozilla::Maybe<JSAutoCompartment> ac;
182 if (value->isObject()) {
183 JS::Rooted<JSObject*> obj(cx, &value->toObject());
184 ac.construct(cx, obj);
185 }
187 nsJSONWriter writer;
188 JS::Rooted<JS::Value> vp(cx, *value);
189 if (!JS_Stringify(cx, &vp, JS::NullPtr(), JS::NullHandleValue, WriteCallback, &writer)) {
190 return NS_ERROR_XPC_BAD_CONVERT_JS;
191 }
192 *value = vp;
194 NS_ENSURE_TRUE(writer.DidWrite(), NS_ERROR_UNEXPECTED);
195 writer.FlushBuffer();
196 result.Assign(writer.mOutputString);
197 return NS_OK;
198 }
200 nsresult
201 nsJSON::EncodeInternal(JSContext* cx, const JS::Value& aValue,
202 nsJSONWriter* writer)
203 {
204 // Backward compatibility:
205 // nsIJSON does not allow to serialize anything other than objects
206 if (!aValue.isObject()) {
207 return NS_ERROR_INVALID_ARG;
208 }
209 JS::Rooted<JSObject*> obj(cx, &aValue.toObject());
211 /* Backward compatibility:
212 * Manually call toJSON if implemented by the object and check that
213 * the result is still an object
214 * Note: It is perfectly fine to not implement toJSON, so it is
215 * perfectly fine for GetMethod to fail
216 */
217 JS::Rooted<JS::Value> val(cx, aValue);
218 JS::Rooted<JS::Value> toJSON(cx);
219 if (JS_GetProperty(cx, obj, "toJSON", &toJSON) &&
220 toJSON.isObject() &&
221 JS_ObjectIsCallable(cx, &toJSON.toObject())) {
222 // If toJSON is implemented, it must not throw
223 if (!JS_CallFunctionValue(cx, obj, toJSON, JS::HandleValueArray::empty(), &val)) {
224 if (JS_IsExceptionPending(cx))
225 // passing NS_OK will throw the pending exception
226 return NS_OK;
228 // No exception, but still failed
229 return NS_ERROR_FAILURE;
230 }
232 // Backward compatibility:
233 // nsIJSON does not allow to serialize anything other than objects
234 if (val.isPrimitive())
235 return NS_ERROR_INVALID_ARG;
236 }
237 // GetMethod may have thrown
238 else if (JS_IsExceptionPending(cx))
239 // passing NS_OK will throw the pending exception
240 return NS_OK;
242 // Backward compatibility:
243 // function shall not pass, just "plain" objects and arrays
244 JSType type = JS_TypeOfValue(cx, val);
245 if (type == JSTYPE_FUNCTION)
246 return NS_ERROR_INVALID_ARG;
248 // We're good now; try to stringify
249 if (!JS_Stringify(cx, &val, JS::NullPtr(), JS::NullHandleValue, WriteCallback, writer))
250 return NS_ERROR_FAILURE;
252 return NS_OK;
253 }
256 nsJSONWriter::nsJSONWriter() : mStream(nullptr),
257 mBuffer(nullptr),
258 mBufferCount(0),
259 mDidWrite(false),
260 mEncoder(nullptr)
261 {
262 }
264 nsJSONWriter::nsJSONWriter(nsIOutputStream *aStream) : mStream(aStream),
265 mBuffer(nullptr),
266 mBufferCount(0),
267 mDidWrite(false),
268 mEncoder(nullptr)
269 {
270 }
272 nsJSONWriter::~nsJSONWriter()
273 {
274 delete [] mBuffer;
275 }
277 nsresult
278 nsJSONWriter::SetCharset(const char* aCharset)
279 {
280 nsresult rv = NS_OK;
281 if (mStream) {
282 mEncoder = EncodingUtils::EncoderForEncoding(aCharset);
283 rv = mEncoder->SetOutputErrorBehavior(nsIUnicodeEncoder::kOnError_Signal,
284 nullptr, '\0');
285 NS_ENSURE_SUCCESS(rv, rv);
286 }
288 return rv;
289 }
291 nsresult
292 nsJSONWriter::Write(const char16_t *aBuffer, uint32_t aLength)
293 {
294 if (mStream) {
295 return WriteToStream(mStream, mEncoder, aBuffer, aLength);
296 }
298 if (!mDidWrite) {
299 mBuffer = new char16_t[JSON_STREAM_BUFSIZE];
300 if (!mBuffer)
301 return NS_ERROR_OUT_OF_MEMORY;
302 mDidWrite = true;
303 }
305 if (JSON_STREAM_BUFSIZE <= aLength + mBufferCount) {
306 mOutputString.Append(mBuffer, mBufferCount);
307 mBufferCount = 0;
308 }
310 if (JSON_STREAM_BUFSIZE <= aLength) {
311 // we know mBufferCount is 0 because we know we hit the if above
312 mOutputString.Append(aBuffer, aLength);
313 } else {
314 memcpy(&mBuffer[mBufferCount], aBuffer, aLength * sizeof(char16_t));
315 mBufferCount += aLength;
316 }
318 return NS_OK;
319 }
321 bool nsJSONWriter::DidWrite()
322 {
323 return mDidWrite;
324 }
326 void
327 nsJSONWriter::FlushBuffer()
328 {
329 mOutputString.Append(mBuffer, mBufferCount);
330 }
332 nsresult
333 nsJSONWriter::WriteToStream(nsIOutputStream *aStream,
334 nsIUnicodeEncoder *encoder,
335 const char16_t *aBuffer,
336 uint32_t aLength)
337 {
338 nsresult rv;
339 int32_t srcLength = aLength;
340 uint32_t bytesWritten;
342 // The bytes written to the stream might differ from the char16_t size
343 int32_t aDestLength;
344 rv = encoder->GetMaxLength(aBuffer, srcLength, &aDestLength);
345 NS_ENSURE_SUCCESS(rv, rv);
347 // create the buffer we need
348 char* destBuf = (char *) NS_Alloc(aDestLength);
349 if (!destBuf)
350 return NS_ERROR_OUT_OF_MEMORY;
352 rv = encoder->Convert(aBuffer, &srcLength, destBuf, &aDestLength);
353 if (NS_SUCCEEDED(rv))
354 rv = aStream->Write(destBuf, aDestLength, &bytesWritten);
356 NS_Free(destBuf);
357 mDidWrite = true;
359 return rv;
360 }
362 NS_IMETHODIMP
363 nsJSON::Decode(const nsAString& json, JSContext* cx,
364 JS::MutableHandle<JS::Value> aRetval)
365 {
366 nsresult rv = WarnDeprecatedMethod(DecodeWarning);
367 if (NS_FAILED(rv))
368 return rv;
370 const char16_t *data;
371 uint32_t len = NS_StringGetData(json, &data);
372 nsCOMPtr<nsIInputStream> stream;
373 rv = NS_NewByteInputStream(getter_AddRefs(stream),
374 reinterpret_cast<const char*>(data),
375 len * sizeof(char16_t),
376 NS_ASSIGNMENT_DEPEND);
377 NS_ENSURE_SUCCESS(rv, rv);
378 return DecodeInternal(cx, stream, len, false, aRetval);
379 }
381 NS_IMETHODIMP
382 nsJSON::DecodeFromStream(nsIInputStream *aStream, int32_t aContentLength,
383 JSContext* cx, JS::MutableHandle<JS::Value> aRetval)
384 {
385 return DecodeInternal(cx, aStream, aContentLength, true, aRetval);
386 }
388 NS_IMETHODIMP
389 nsJSON::DecodeToJSVal(const nsAString &str, JSContext *cx,
390 JS::MutableHandle<JS::Value> result)
391 {
392 if (!JS_ParseJSON(cx, static_cast<const jschar*>(PromiseFlatString(str).get()),
393 str.Length(), result)) {
394 return NS_ERROR_UNEXPECTED;
395 }
396 return NS_OK;
397 }
399 nsresult
400 nsJSON::DecodeInternal(JSContext* cx,
401 nsIInputStream *aStream,
402 int32_t aContentLength,
403 bool aNeedsConverter,
404 JS::MutableHandle<JS::Value> aRetval)
405 {
406 // Consume the stream
407 nsCOMPtr<nsIChannel> jsonChannel;
408 if (!mURI) {
409 NS_NewURI(getter_AddRefs(mURI), NS_LITERAL_CSTRING("about:blank"), 0, 0 );
410 if (!mURI)
411 return NS_ERROR_OUT_OF_MEMORY;
412 }
414 nsresult rv =
415 NS_NewInputStreamChannel(getter_AddRefs(jsonChannel), mURI, aStream,
416 NS_LITERAL_CSTRING("application/json"));
417 if (!jsonChannel || NS_FAILED(rv))
418 return NS_ERROR_FAILURE;
420 nsRefPtr<nsJSONListener> jsonListener =
421 new nsJSONListener(cx, aRetval.address(), aNeedsConverter);
423 //XXX this stream pattern should be consolidated in netwerk
424 rv = jsonListener->OnStartRequest(jsonChannel, nullptr);
425 if (NS_FAILED(rv)) {
426 jsonChannel->Cancel(rv);
427 return rv;
428 }
430 nsresult status;
431 jsonChannel->GetStatus(&status);
432 uint64_t offset = 0;
433 while (NS_SUCCEEDED(status)) {
434 uint64_t available;
435 rv = aStream->Available(&available);
436 if (rv == NS_BASE_STREAM_CLOSED) {
437 rv = NS_OK;
438 break;
439 }
440 if (NS_FAILED(rv)) {
441 jsonChannel->Cancel(rv);
442 break;
443 }
444 if (!available)
445 break; // blocking input stream has none available when done
447 if (available > UINT32_MAX)
448 available = UINT32_MAX;
450 rv = jsonListener->OnDataAvailable(jsonChannel, nullptr,
451 aStream,
452 offset,
453 (uint32_t)available);
454 if (NS_FAILED(rv)) {
455 jsonChannel->Cancel(rv);
456 break;
457 }
459 offset += available;
460 jsonChannel->GetStatus(&status);
461 }
462 NS_ENSURE_SUCCESS(rv, rv);
464 rv = jsonListener->OnStopRequest(jsonChannel, nullptr, status);
465 NS_ENSURE_SUCCESS(rv, rv);
467 return NS_OK;
468 }
470 nsresult
471 NS_NewJSON(nsISupports* aOuter, REFNSIID aIID, void** aResult)
472 {
473 nsJSON* json = new nsJSON();
474 if (!json)
475 return NS_ERROR_OUT_OF_MEMORY;
477 NS_ADDREF(json);
478 *aResult = json;
480 return NS_OK;
481 }
483 nsJSONListener::nsJSONListener(JSContext *cx, JS::Value *rootVal,
484 bool needsConverter)
485 : mNeedsConverter(needsConverter),
486 mCx(cx),
487 mRootVal(rootVal)
488 {
489 }
491 nsJSONListener::~nsJSONListener()
492 {
493 }
495 NS_INTERFACE_MAP_BEGIN(nsJSONListener)
496 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsJSONListener)
497 NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
498 NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
499 NS_INTERFACE_MAP_END
501 NS_IMPL_ADDREF(nsJSONListener)
502 NS_IMPL_RELEASE(nsJSONListener)
504 NS_IMETHODIMP
505 nsJSONListener::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext)
506 {
507 mSniffBuffer.Truncate();
508 mDecoder = nullptr;
510 return NS_OK;
511 }
513 NS_IMETHODIMP
514 nsJSONListener::OnStopRequest(nsIRequest *aRequest, nsISupports *aContext,
515 nsresult aStatusCode)
516 {
517 nsresult rv;
519 // This can happen with short UTF-8 messages (<4 bytes)
520 if (!mSniffBuffer.IsEmpty()) {
521 // Just consume mSniffBuffer
522 rv = ProcessBytes(nullptr, 0);
523 NS_ENSURE_SUCCESS(rv, rv);
524 }
526 JS::Rooted<JS::Value> reviver(mCx, JS::NullValue()), value(mCx);
528 JS::ConstTwoByteChars chars(reinterpret_cast<const jschar*>(mBufferedChars.Elements()),
529 mBufferedChars.Length());
530 bool ok = JS_ParseJSONWithReviver(mCx, chars.get(),
531 uint32_t(mBufferedChars.Length()),
532 reviver, &value);
534 *mRootVal = value;
535 mBufferedChars.TruncateLength(0);
536 return ok ? NS_OK : NS_ERROR_FAILURE;
537 }
539 NS_IMETHODIMP
540 nsJSONListener::OnDataAvailable(nsIRequest *aRequest, nsISupports *aContext,
541 nsIInputStream *aStream,
542 uint64_t aOffset, uint32_t aLength)
543 {
544 nsresult rv = NS_OK;
546 if (mNeedsConverter && mSniffBuffer.Length() < 4) {
547 uint32_t readCount = (aLength < 4) ? aLength : 4;
548 rv = NS_ConsumeStream(aStream, readCount, mSniffBuffer);
549 NS_ENSURE_SUCCESS(rv, rv);
551 if (mSniffBuffer.Length() < 4)
552 return NS_OK;
553 }
555 char buffer[JSON_STREAM_BUFSIZE];
556 unsigned long bytesRemaining = aLength - mSniffBuffer.Length();
557 while (bytesRemaining) {
558 unsigned int bytesRead;
559 rv = aStream->Read(buffer,
560 std::min((unsigned long)sizeof(buffer), bytesRemaining),
561 &bytesRead);
562 NS_ENSURE_SUCCESS(rv, rv);
563 rv = ProcessBytes(buffer, bytesRead);
564 NS_ENSURE_SUCCESS(rv, rv);
565 bytesRemaining -= bytesRead;
566 }
568 return rv;
569 }
571 nsresult
572 nsJSONListener::ProcessBytes(const char* aBuffer, uint32_t aByteLength)
573 {
574 nsresult rv;
575 // Check for BOM, or sniff charset
576 nsAutoCString charset;
577 if (mNeedsConverter && !mDecoder) {
578 if (!nsContentUtils::CheckForBOM((const unsigned char*) mSniffBuffer.get(),
579 mSniffBuffer.Length(), charset)) {
580 // OK, found no BOM, sniff the first character to see what this is
581 // See section 3 of RFC4627 for details on why this works.
582 const char *buffer = mSniffBuffer.get();
583 if (mSniffBuffer.Length() >= 4) {
584 if (buffer[0] == 0x00 && buffer[1] != 0x00 &&
585 buffer[2] == 0x00 && buffer[3] != 0x00) {
586 charset = "UTF-16BE";
587 } else if (buffer[0] != 0x00 && buffer[1] == 0x00 &&
588 buffer[2] != 0x00 && buffer[3] == 0x00) {
589 charset = "UTF-16LE";
590 } else if (buffer[0] != 0x00 && buffer[1] != 0x00 &&
591 buffer[2] != 0x00 && buffer[3] != 0x00) {
592 charset = "UTF-8";
593 }
594 } else {
595 // Not enough bytes to sniff, assume UTF-8
596 charset = "UTF-8";
597 }
598 }
600 // We should have a unicode charset by now
601 rv = CheckCharset(charset.get());
602 NS_ENSURE_SUCCESS(rv, rv);
603 mDecoder = EncodingUtils::DecoderForEncoding(charset);
605 // consume the sniffed bytes
606 rv = ConsumeConverted(mSniffBuffer.get(), mSniffBuffer.Length());
607 NS_ENSURE_SUCCESS(rv, rv);
608 mSniffBuffer.Truncate();
609 }
611 if (!aBuffer)
612 return NS_OK;
614 if (mNeedsConverter) {
615 rv = ConsumeConverted(aBuffer, aByteLength);
616 } else {
617 uint32_t unichars = aByteLength / sizeof(char16_t);
618 rv = Consume((char16_t *) aBuffer, unichars);
619 }
621 return rv;
622 }
624 nsresult
625 nsJSONListener::ConsumeConverted(const char* aBuffer, uint32_t aByteLength)
626 {
627 nsresult rv;
628 int32_t unicharLength = 0;
629 int32_t srcLen = aByteLength;
631 rv = mDecoder->GetMaxLength(aBuffer, srcLen, &unicharLength);
632 NS_ENSURE_SUCCESS(rv, rv);
634 char16_t* endelems = mBufferedChars.AppendElements(unicharLength);
635 int32_t preLength = unicharLength;
636 rv = mDecoder->Convert(aBuffer, &srcLen, endelems, &unicharLength);
637 if (NS_FAILED(rv))
638 return rv;
639 NS_ABORT_IF_FALSE(preLength >= unicharLength, "GetMaxLength lied");
640 if (preLength > unicharLength)
641 mBufferedChars.TruncateLength(mBufferedChars.Length() - (preLength - unicharLength));
642 return NS_OK;
643 }
645 nsresult
646 nsJSONListener::Consume(const char16_t* aBuffer, uint32_t aByteLength)
647 {
648 if (!mBufferedChars.AppendElements(aBuffer, aByteLength))
649 return NS_ERROR_FAILURE;
651 return NS_OK;
652 }