Bug 1824753 [wpt PR 39216] - [FLEDGE] Add WPT test that FLEDGE is not allowed in...
[gecko.git] / dom / ipc / JSValidatorChild.cpp
blob1b9b8e04ad78cf7c2540e5ee657cd33058d30f06
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"
8 #include "js/JSON.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);
33 if (encoding) {
34 unicodeDecoder = encoding->NewDecoderWithBOMRemoval();
37 if (!unicodeDecoder) {
38 encoding = Encoding::ForLabel(aContentCharset);
39 if (encoding) {
40 unicodeDecoder = encoding->NewDecoderWithoutBOMHandling();
43 if (!unicodeDecoder) {
44 encoding = Encoding::ForLabel(aHintCharset);
45 if (encoding) {
46 unicodeDecoder = encoding->NewDecoderWithoutBOMHandling();
50 if (!unicodeDecoder) {
51 encoding = Encoding::ForLabel(aDocumentCharset);
52 if (encoding) {
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
63 // windows-1252.
64 unicodeDecoder = WINDOWS_1252_ENCODING->NewDecoderWithoutBOMHandling();
67 return unicodeDecoder;
70 mozilla::ipc::IPCResult JSValidatorChild::RecvIsOpaqueResponseAllowed(
71 IsOpaqueResponseAllowedResolver&& aResolver) {
72 mResolver.emplace(aResolver);
74 return IPC_OK();
77 mozilla::ipc::IPCResult JSValidatorChild::RecvOnDataAvailable(Shmem&& aData) {
78 if (!mResolver) {
79 MOZ_ASSERT(!CanSend());
80 return IPC_OK();
83 if (!mSourceBytes.Append(Span(aData.get<char>(), aData.Size<char>()),
84 mozilla::fallible)) {
85 // To prevent an attacker from flood the validation process,
86 // we don't validate here.
87 Resolve(ValidatorResult::Failure);
89 DeallocShmem(aData);
91 return IPC_OK();
94 mozilla::ipc::IPCResult JSValidatorChild::RecvOnStopRequest(
95 const nsresult& aReason, const nsACString& aContentCharset,
96 const nsAString& aHintCharset, const nsAString& aDocumentCharset) {
97 if (!mResolver) {
98 return IPC_OK();
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);
106 } else {
107 UniquePtr<Decoder> unicodeDecoder = TryGetDecoder(
108 mSourceBytes, aContentCharset, aHintCharset, aDocumentCharset);
110 if (!unicodeDecoder) {
111 Resolve(ShouldAllowJS(mSourceBytes));
112 } else {
113 BufferUniquePtr<Utf8Unit[]> buffer;
114 auto result = GetUTF8EncodedContent(mSourceBytes, buffer, unicodeDecoder);
115 if (result.isErr()) {
116 Resolve(ValidatorResult::Failure);
117 } else {
118 Resolve(ShouldAllowJS(result.unwrap()));
123 return IPC_OK();
126 void JSValidatorChild::ActorDestroy(ActorDestroyReason aReason) {
127 if (mResolver) {
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()) {
136 Shmem sharedData;
137 nsresult rv =
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));
146 mResolver.reset();
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())));
167 if (!aBuffer) {
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());
175 MOZ_ASSERT(
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();
188 if (!cx) {
189 return ValidatorResult::Failure;
192 JS::Rooted<JSObject*> global(cx, JSOracleChild::JSObject());
193 if (!global) {
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);
210 if (!stencil) {
211 JS_ClearPendingException(cx);
212 return ValidatorResult::Other;
215 MOZ_ASSERT(!aSpan.IsEmpty());
217 // Parse to JSON
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
221 if (JS_ParseJSON(cx,
222 reinterpret_cast<const JS::Latin1Char*>(aSpan.Elements()),
223 aSpan.Length(), &json)) {
224 return ValidatorResult::JSON;
226 } else {
227 nsString decoded;
228 nsresult rv = UTF_8_ENCODING->DecodeWithBOMRemoval(
229 Span(reinterpret_cast<const uint8_t*>(aSpan.Elements()),
230 aSpan.Length()),
231 decoded);
232 if (NS_FAILED(rv)) {
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
242 // JSON.
243 if (JS_IsExceptionPending(cx)) {
244 JS_ClearPendingException(cx);
246 return ValidatorResult::JavaScript;