Bug 1936278 - Prevent search mode chiclet from being dismissed when clicking in page...
[gecko.git] / dom / ipc / JSValidatorChild.cpp
blob86ddeb34349e0772e4dcb325dd3de0c9f6fbc126
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 "mozilla/dom/JSOracleChild.h"
10 #include "mozilla/Encoding.h"
11 #include "mozilla/dom/ScriptDecoding.h"
12 #include "mozilla/ipc/Endpoint.h"
14 #include "js/CompileOptions.h"
15 #include "js/JSON.h"
16 #include "js/SourceText.h"
17 #include "js/experimental/CompileScript.h"
18 #include "js/experimental/JSStencil.h"
19 #include "xpcpublic.h"
21 using namespace mozilla::dom;
22 using Encoding = mozilla::Encoding;
24 mozilla::UniquePtr<mozilla::Decoder> TryGetDecoder(
25 const mozilla::Span<const uint8_t>& aSourceBytes,
26 const nsACString& aContentCharset, const nsAString& aHintCharset,
27 const nsAString& aDocumentCharset) {
28 const Encoding* encoding;
29 mozilla::UniquePtr<mozilla::Decoder> unicodeDecoder;
31 std::tie(encoding, std::ignore) = Encoding::ForBOM(aSourceBytes);
32 if (encoding) {
33 unicodeDecoder = encoding->NewDecoderWithBOMRemoval();
36 if (!unicodeDecoder) {
37 encoding = Encoding::ForLabel(aContentCharset);
38 if (encoding) {
39 unicodeDecoder = encoding->NewDecoderWithoutBOMHandling();
42 if (!unicodeDecoder) {
43 encoding = Encoding::ForLabel(aHintCharset);
44 if (encoding) {
45 unicodeDecoder = encoding->NewDecoderWithoutBOMHandling();
49 if (!unicodeDecoder) {
50 encoding = Encoding::ForLabel(aDocumentCharset);
51 if (encoding) {
52 unicodeDecoder = encoding->NewDecoderWithoutBOMHandling();
57 if (!unicodeDecoder && !IsUtf8(mozilla::Span(reinterpret_cast<const char*>(
58 aSourceBytes.Elements()),
59 aSourceBytes.Length()))) {
60 // Curiously, there are various callers that don't pass aDocument. The
61 // fallback in the old code was ISO-8859-1, which behaved like
62 // windows-1252.
63 unicodeDecoder = WINDOWS_1252_ENCODING->NewDecoderWithoutBOMHandling();
66 return unicodeDecoder;
69 mozilla::ipc::IPCResult JSValidatorChild::RecvIsOpaqueResponseAllowed(
70 IsOpaqueResponseAllowedResolver&& aResolver) {
71 mResolver.emplace(aResolver);
73 return IPC_OK();
76 mozilla::ipc::IPCResult JSValidatorChild::RecvOnDataAvailable(Shmem&& aData) {
77 if (!mResolver) {
78 MOZ_ASSERT(!CanSend());
79 return IPC_OK();
82 if (!mSourceBytes.Append(Span(aData.get<char>(), aData.Size<char>()),
83 mozilla::fallible)) {
84 // To prevent an attacker from flood the validation process,
85 // we don't validate here.
86 Resolve(ValidatorResult::Failure);
88 DeallocShmem(aData);
90 return IPC_OK();
93 mozilla::ipc::IPCResult JSValidatorChild::RecvOnStopRequest(
94 const nsresult& aReason, const nsACString& aContentCharset,
95 const nsAString& aHintCharset, const nsAString& aDocumentCharset) {
96 if (!mResolver) {
97 return IPC_OK();
100 if (NS_FAILED(aReason)) {
101 Resolve(ValidatorResult::Failure);
102 } else if (mSourceBytes.IsEmpty()) {
103 // The empty document parses as JavaScript.
104 Resolve(ValidatorResult::JavaScript);
105 } else {
106 UniquePtr<Decoder> unicodeDecoder = TryGetDecoder(
107 mSourceBytes, aContentCharset, aHintCharset, aDocumentCharset);
109 if (!unicodeDecoder) {
110 Resolve(ShouldAllowJS(mSourceBytes));
111 } else {
112 BufferUniquePtr<Utf8Unit[]> buffer;
113 auto result = GetUTF8EncodedContent(mSourceBytes, buffer, unicodeDecoder);
114 if (result.isErr()) {
115 Resolve(ValidatorResult::Failure);
116 } else {
117 Resolve(ShouldAllowJS(result.unwrap()));
122 return IPC_OK();
125 void JSValidatorChild::ActorDestroy(ActorDestroyReason aReason) {
126 if (mResolver) {
127 Resolve(ValidatorResult::Failure);
131 void JSValidatorChild::Resolve(ValidatorResult aResult) {
132 MOZ_ASSERT(mResolver);
133 Maybe<Shmem> data = Nothing();
134 if (aResult == ValidatorResult::JavaScript && !mSourceBytes.IsEmpty()) {
135 Shmem sharedData;
136 nsresult rv =
137 JSValidatorUtils::CopyCStringToShmem(this, mSourceBytes, sharedData);
138 if (NS_SUCCEEDED(rv)) {
139 data = Some(std::move(sharedData));
143 mResolver.ref()(std::tuple<mozilla::Maybe<Shmem>&&, const ValidatorResult&>(
144 std::move(data), aResult));
145 mResolver.reset();
148 mozilla::Result<mozilla::Span<const char>, nsresult>
149 JSValidatorChild::GetUTF8EncodedContent(
150 const mozilla::Span<const uint8_t>& aData,
151 BufferUniquePtr<Utf8Unit[]>& aBuffer, UniquePtr<Decoder>& aDecoder) {
152 MOZ_ASSERT(aDecoder);
153 // We need the output buffer to be UTF8
154 CheckedInt<size_t> bufferLength =
155 ScriptDecoding<Utf8Unit>::MaxBufferLength(aDecoder, aData.Length());
156 if (!bufferLength.isValid()) {
157 return mozilla::Err(NS_ERROR_FAILURE);
160 CheckedInt<size_t> bufferByteSize = bufferLength * sizeof(Utf8Unit);
161 if (!bufferByteSize.isValid()) {
162 return mozilla::Err(NS_ERROR_FAILURE);
165 aBuffer.reset(static_cast<Utf8Unit*>(js_malloc(bufferByteSize.value())));
166 if (!aBuffer) {
167 return mozilla::Err(NS_ERROR_FAILURE);
170 size_t written = ScriptDecoding<Utf8Unit>::DecodeInto(
171 aDecoder, aData, Span(aBuffer.get(), bufferLength.value()),
172 /* aEndOfSource = */ true);
173 MOZ_ASSERT(written <= bufferLength.value());
174 MOZ_ASSERT(
175 IsUtf8(Span(reinterpret_cast<const char*>(aBuffer.get()), written)));
177 return Span(reinterpret_cast<const char*>(aBuffer.get()), written);
180 JSValidatorChild::ValidatorResult JSValidatorChild::ShouldAllowJS(
181 const mozilla::Span<const char>& aSpan) const {
182 // It's possible that the data we get is not valid UTF-8, so aSpan
183 // ends empty here. We should treat it as a failure because this
184 // is not valid JS.
185 if (aSpan.IsEmpty()) {
186 return ValidatorResult::Failure;
189 MOZ_DIAGNOSTIC_ASSERT(IsUtf8(aSpan));
191 JS::FrontendContext* fc = JSOracleChild::JSFrontendContext();
192 if (!fc) {
193 return ValidatorResult::Failure;
196 JS::SourceText<Utf8Unit> srcBuf;
197 if (!srcBuf.init(fc, aSpan.Elements(), aSpan.Length(),
198 JS::SourceOwnership::Borrowed)) {
199 JS::ClearFrontendErrors(fc);
200 return ValidatorResult::Failure;
203 // Parse to JavaScript
204 JS::PrefableCompileOptions prefableOptions;
205 xpc::SetPrefableCompileOptions(prefableOptions);
206 // For the syntax validation purpose, asm.js doesn't need to be enabled.
207 prefableOptions.setAsmJSOption(JS::AsmJSOption::DisabledByAsmJSPref);
209 JS::CompileOptions options(prefableOptions);
210 RefPtr<JS::Stencil> stencil =
211 JS::CompileGlobalScriptToStencil(fc, options, srcBuf);
213 if (!stencil) {
214 JS::ClearFrontendErrors(fc);
215 return ValidatorResult::Other;
218 MOZ_ASSERT(!aSpan.IsEmpty());
220 // Parse to JSON
221 if (IsAscii(aSpan)) {
222 // Ascii is a subset of Latin1, and JS_ParseJSON can take Latin1 directly
223 if (JS::IsValidJSON(
224 reinterpret_cast<const JS::Latin1Char*>(aSpan.Elements()),
225 aSpan.Length())) {
226 return ValidatorResult::JSON;
228 } else {
229 nsString decoded;
230 nsresult rv = UTF_8_ENCODING->DecodeWithBOMRemoval(
231 Span(reinterpret_cast<const uint8_t*>(aSpan.Elements()),
232 aSpan.Length()),
233 decoded);
234 if (NS_FAILED(rv)) {
235 return ValidatorResult::Failure;
238 if (JS::IsValidJSON(decoded.BeginReading(), decoded.Length())) {
239 return ValidatorResult::JSON;
243 // Since the JSON parsing failed, we confirmed the file is Javascript and not
244 // JSON.
245 return ValidatorResult::JavaScript;