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/. */
7 #include "mozilla/dom/JSValidatorChild.h"
9 #include "mozilla/dom/JSOracleChild.h"
11 #include "mozilla/Encoding.h"
12 #include "mozilla/dom/ScriptDecoding.h"
13 #include "mozilla/ipc/Endpoint.h"
15 #include "js/experimental/JSStencil.h"
16 #include "js/SourceText.h"
17 #include "js/Exception.h"
18 #include "js/GlobalObject.h"
19 #include "js/CompileOptions.h"
20 #include "js/RealmOptions.h"
22 using namespace mozilla::dom
;
23 using Encoding
= mozilla::Encoding
;
25 mozilla::UniquePtr
<mozilla::Decoder
> TryGetDecoder(
26 const mozilla::Span
<const uint8_t>& aSourceBytes
,
27 const nsACString
& aContentCharset
, const nsAString
& aHintCharset
,
28 const nsAString
& aDocumentCharset
) {
29 const Encoding
* encoding
;
30 mozilla::UniquePtr
<mozilla::Decoder
> unicodeDecoder
;
32 std::tie(encoding
, std::ignore
) = Encoding::ForBOM(aSourceBytes
);
34 unicodeDecoder
= encoding
->NewDecoderWithBOMRemoval();
37 if (!unicodeDecoder
) {
38 encoding
= Encoding::ForLabel(aContentCharset
);
40 unicodeDecoder
= encoding
->NewDecoderWithoutBOMHandling();
43 if (!unicodeDecoder
) {
44 encoding
= Encoding::ForLabel(aHintCharset
);
46 unicodeDecoder
= encoding
->NewDecoderWithoutBOMHandling();
50 if (!unicodeDecoder
) {
51 encoding
= Encoding::ForLabel(aDocumentCharset
);
53 unicodeDecoder
= encoding
->NewDecoderWithoutBOMHandling();
58 if (!unicodeDecoder
&& !IsUtf8(mozilla::Span(reinterpret_cast<const char*>(
59 aSourceBytes
.Elements()),
60 aSourceBytes
.Length()))) {
61 // Curiously, there are various callers that don't pass aDocument. The
62 // fallback in the old code was ISO-8859-1, which behaved like
64 unicodeDecoder
= WINDOWS_1252_ENCODING
->NewDecoderWithoutBOMHandling();
67 return unicodeDecoder
;
70 mozilla::ipc::IPCResult
JSValidatorChild::RecvIsOpaqueResponseAllowed(
71 IsOpaqueResponseAllowedResolver
&& aResolver
) {
72 mResolver
.emplace(aResolver
);
77 mozilla::ipc::IPCResult
JSValidatorChild::RecvOnDataAvailable(Shmem
&& aData
) {
79 MOZ_ASSERT(!CanSend());
83 if (!mSourceBytes
.Append(Span(aData
.get
<char>(), aData
.Size
<char>()),
85 // To prevent an attacker from flood the validation process,
86 // we don't validate here.
87 Resolve(ValidatorResult::Failure
);
94 mozilla::ipc::IPCResult
JSValidatorChild::RecvOnStopRequest(
95 const nsresult
& aReason
, const nsACString
& aContentCharset
,
96 const nsAString
& aHintCharset
, const nsAString
& aDocumentCharset
) {
101 if (NS_FAILED(aReason
)) {
102 Resolve(ValidatorResult::Failure
);
103 } else if (mSourceBytes
.IsEmpty()) {
104 // The empty document parses as JavaScript.
105 Resolve(ValidatorResult::JavaScript
);
107 UniquePtr
<Decoder
> unicodeDecoder
= TryGetDecoder(
108 mSourceBytes
, aContentCharset
, aHintCharset
, aDocumentCharset
);
110 if (!unicodeDecoder
) {
111 Resolve(ShouldAllowJS(mSourceBytes
));
113 BufferUniquePtr
<Utf8Unit
[]> buffer
;
114 auto result
= GetUTF8EncodedContent(mSourceBytes
, buffer
, unicodeDecoder
);
115 if (result
.isErr()) {
116 Resolve(ValidatorResult::Failure
);
118 Resolve(ShouldAllowJS(result
.unwrap()));
126 void JSValidatorChild::ActorDestroy(ActorDestroyReason aReason
) {
128 Resolve(ValidatorResult::Failure
);
132 void JSValidatorChild::Resolve(ValidatorResult aResult
) {
133 MOZ_ASSERT(mResolver
);
134 Maybe
<Shmem
> data
= Nothing();
135 if (aResult
== ValidatorResult::JavaScript
&& !mSourceBytes
.IsEmpty()) {
138 JSValidatorUtils::CopyCStringToShmem(this, mSourceBytes
, sharedData
);
139 if (NS_SUCCEEDED(rv
)) {
140 data
= Some(std::move(sharedData
));
144 mResolver
.ref()(std::tuple
<mozilla::Maybe
<Shmem
>&&, const ValidatorResult
&>(
145 std::move(data
), aResult
));
149 mozilla::Result
<mozilla::Span
<const char>, nsresult
>
150 JSValidatorChild::GetUTF8EncodedContent(
151 const mozilla::Span
<const uint8_t>& aData
,
152 BufferUniquePtr
<Utf8Unit
[]>& aBuffer
, UniquePtr
<Decoder
>& aDecoder
) {
153 MOZ_ASSERT(aDecoder
);
154 // We need the output buffer to be UTF8
155 CheckedInt
<size_t> bufferLength
=
156 ScriptDecoding
<Utf8Unit
>::MaxBufferLength(aDecoder
, aData
.Length());
157 if (!bufferLength
.isValid()) {
158 return mozilla::Err(NS_ERROR_FAILURE
);
161 CheckedInt
<size_t> bufferByteSize
= bufferLength
* sizeof(Utf8Unit
);
162 if (!bufferByteSize
.isValid()) {
163 return mozilla::Err(NS_ERROR_FAILURE
);
166 aBuffer
.reset(static_cast<Utf8Unit
*>(js_malloc(bufferByteSize
.value())));
168 return mozilla::Err(NS_ERROR_FAILURE
);
171 size_t written
= ScriptDecoding
<Utf8Unit
>::DecodeInto(
172 aDecoder
, aData
, Span(aBuffer
.get(), bufferLength
.value()),
173 /* aEndOfSource = */ true);
174 MOZ_ASSERT(written
<= bufferLength
.value());
176 IsUtf8(Span(reinterpret_cast<const char*>(aBuffer
.get()), written
)));
178 return Span(reinterpret_cast<const char*>(aBuffer
.get()), written
);
181 JSValidatorChild::ValidatorResult
JSValidatorChild::ShouldAllowJS(
182 const mozilla::Span
<const char>& aSpan
) const {
183 MOZ_ASSERT(!aSpan
.IsEmpty());
185 MOZ_DIAGNOSTIC_ASSERT(IsUtf8(aSpan
));
187 JSContext
* cx
= JSOracleChild::JSContext();
189 return ValidatorResult::Failure
;
192 JS::Rooted
<JSObject
*> global(cx
, JSOracleChild::JSObject());
194 return ValidatorResult::Failure
;
197 JS::SourceText
<Utf8Unit
> srcBuf
;
198 if (!srcBuf
.init(cx
, aSpan
.Elements(), aSpan
.Length(),
199 JS::SourceOwnership::Borrowed
)) {
200 JS_ClearPendingException(cx
);
201 return ValidatorResult::Failure
;
204 JSAutoRealm
ar(cx
, global
);
206 // Parse to JavaScript
207 RefPtr
<JS::Stencil
> stencil
=
208 CompileGlobalScriptToStencil(cx
, JS::CompileOptions(cx
), srcBuf
);
211 JS_ClearPendingException(cx
);
212 return ValidatorResult::Other
;
215 MOZ_ASSERT(!aSpan
.IsEmpty());
218 JS::Rooted
<JS::Value
> json(cx
);
219 if (IsAscii(aSpan
)) {
220 // Ascii is a subset of Latin1, and JS_ParseJSON can take Latin1 directly
222 reinterpret_cast<const JS::Latin1Char
*>(aSpan
.Elements()),
223 aSpan
.Length(), &json
)) {
224 return ValidatorResult::JSON
;
228 nsresult rv
= UTF_8_ENCODING
->DecodeWithBOMRemoval(
229 Span(reinterpret_cast<const uint8_t*>(aSpan
.Elements()),
233 return ValidatorResult::Failure
;
236 if (JS_ParseJSON(cx
, decoded
.BeginReading(), decoded
.Length(), &json
)) {
237 return ValidatorResult::JSON
;
241 // Since the JSON parsing failed, we confirmed the file is Javascript and not
243 if (JS_IsExceptionPending(cx
)) {
244 JS_ClearPendingException(cx
);
246 return ValidatorResult::JavaScript
;