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 "ScriptLoadHandler.h"
11 #include "ScriptLoader.h"
12 #include "ScriptTrace.h"
13 #include "js/Transcoding.h"
14 #include "js/loader/ScriptLoadRequest.h"
15 #include "mozilla/Assertions.h"
16 #include "mozilla/CheckedInt.h"
17 #include "mozilla/DebugOnly.h"
18 #include "mozilla/Encoding.h"
19 #include "mozilla/Logging.h"
20 #include "mozilla/NotNull.h"
21 #include "mozilla/ScopeExit.h"
22 #include "mozilla/StaticPrefs_dom.h"
23 #include "mozilla/Utf8.h"
24 #include "mozilla/Vector.h"
25 #include "mozilla/dom/Document.h"
26 #include "mozilla/dom/SRICheck.h"
27 #include "mozilla/dom/ScriptDecoding.h"
29 #include "nsContentUtils.h"
31 #include "nsICacheInfoChannel.h"
32 #include "nsIChannel.h"
33 #include "nsIHttpChannel.h"
34 #include "nsIRequest.h"
35 #include "nsIScriptElement.h"
37 #include "nsJSUtils.h"
38 #include "nsMimeTypes.h"
47 MOZ_LOG(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug, args)
49 #define LOG_ENABLED() \
50 MOZ_LOG_TEST(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug)
52 ScriptLoadHandler::ScriptLoadHandler(
53 ScriptLoader
* aScriptLoader
, JS::loader::ScriptLoadRequest
* aRequest
,
54 UniquePtr
<SRICheckDataVerifier
>&& aSRIDataVerifier
)
55 : mScriptLoader(aScriptLoader
),
57 mSRIDataVerifier(std::move(aSRIDataVerifier
)),
60 MOZ_ASSERT(mRequest
->IsUnknownDataType());
61 MOZ_ASSERT(mRequest
->IsLoading());
64 ScriptLoadHandler::~ScriptLoadHandler() = default;
66 NS_IMPL_ISUPPORTS(ScriptLoadHandler
, nsIIncrementalStreamLoaderObserver
)
68 template <typename Unit
>
69 nsresult
ScriptLoadHandler::DecodeRawDataHelper(const uint8_t* aData
,
72 CheckedInt
<size_t> needed
=
73 ScriptDecoding
<Unit
>::MaxBufferLength(mDecoder
, aDataLength
);
74 if (!needed
.isValid()) {
75 return NS_ERROR_OUT_OF_MEMORY
;
78 // Reference to the script source buffer which we will update.
79 JS::loader::ScriptLoadRequest::ScriptTextBuffer
<Unit
>& scriptText
=
80 mRequest
->ScriptText
<Unit
>();
82 uint32_t haveRead
= scriptText
.length();
84 CheckedInt
<uint32_t> capacity
= haveRead
;
85 capacity
+= needed
.value();
87 if (!capacity
.isValid() || !scriptText
.resize(capacity
.value())) {
88 return NS_ERROR_OUT_OF_MEMORY
;
91 size_t written
= ScriptDecoding
<Unit
>::DecodeInto(
92 mDecoder
, Span(aData
, aDataLength
),
93 Span(scriptText
.begin() + haveRead
, needed
.value()), aEndOfStream
);
94 MOZ_ASSERT(written
<= needed
.value());
97 MOZ_ASSERT(haveRead
<= capacity
.value(),
98 "mDecoder produced more data than expected");
99 MOZ_ALWAYS_TRUE(scriptText
.resize(haveRead
));
100 mRequest
->mScriptTextLength
= scriptText
.length();
105 nsresult
ScriptLoadHandler::DecodeRawData(const uint8_t* aData
,
106 uint32_t aDataLength
,
108 if (mRequest
->IsUTF16Text()) {
109 return DecodeRawDataHelper
<char16_t
>(aData
, aDataLength
, aEndOfStream
);
112 return DecodeRawDataHelper
<Utf8Unit
>(aData
, aDataLength
, aEndOfStream
);
116 ScriptLoadHandler::OnIncrementalData(nsIIncrementalStreamLoader
* aLoader
,
117 nsISupports
* aContext
,
118 uint32_t aDataLength
, const uint8_t* aData
,
119 uint32_t* aConsumedLength
) {
120 nsCOMPtr
<nsIRequest
> channelRequest
;
121 aLoader
->GetRequest(getter_AddRefs(channelRequest
));
123 if (!mPreloadStartNotified
) {
124 mPreloadStartNotified
= true;
125 mRequest
->mLoadContext
->NotifyStart(channelRequest
);
128 if (mRequest
->IsCanceled()) {
129 // If request cancelled, ignore any incoming data.
130 *aConsumedLength
= aDataLength
;
135 if (mRequest
->IsUnknownDataType()) {
136 rv
= EnsureKnownDataType(aLoader
);
137 NS_ENSURE_SUCCESS(rv
, rv
);
140 if (mRequest
->IsTextSource()) {
141 if (!EnsureDecoder(aLoader
, aData
, aDataLength
,
142 /* aEndOfStream = */ false)) {
146 // Below we will/shall consume entire data chunk.
147 *aConsumedLength
= aDataLength
;
149 // Decoder has already been initialized. -- trying to decode all loaded
151 rv
= DecodeRawData(aData
, aDataLength
, /* aEndOfStream = */ false);
152 NS_ENSURE_SUCCESS(rv
, rv
);
154 // If SRI is required for this load, appending new bytes to the hash.
155 if (mSRIDataVerifier
&& NS_SUCCEEDED(mSRIStatus
)) {
156 mSRIStatus
= mSRIDataVerifier
->Update(aDataLength
, aData
);
159 MOZ_ASSERT(mRequest
->IsBytecode());
160 if (!mRequest
->mScriptBytecode
.append(aData
, aDataLength
)) {
161 return NS_ERROR_OUT_OF_MEMORY
;
164 *aConsumedLength
= aDataLength
;
165 uint32_t sriLength
= 0;
166 rv
= MaybeDecodeSRI(&sriLength
);
168 return channelRequest
->Cancel(mScriptLoader
->RestartLoad(mRequest
));
171 mRequest
->mBytecodeOffset
= JS::AlignTranscodingBytecodeOffset(sriLength
);
178 bool ScriptLoadHandler::TrySetDecoder(nsIIncrementalStreamLoader
* aLoader
,
179 const uint8_t* aData
,
180 uint32_t aDataLength
, bool aEndOfStream
) {
181 MOZ_ASSERT(mDecoder
== nullptr,
182 "can't have a decoder already if we're trying to set one");
184 // JavaScript modules are always UTF-8.
185 if (mRequest
->IsModuleRequest()) {
186 mDecoder
= UTF_8_ENCODING
->NewDecoderWithBOMRemoval();
190 // Determine if BOM check should be done. This occurs either
191 // if end-of-stream has been reached, or at least 3 bytes have
192 // been read from input.
193 if (!aEndOfStream
&& (aDataLength
< 3)) {
198 const Encoding
* encoding
;
199 std::tie(encoding
, std::ignore
) = Encoding::ForBOM(Span(aData
, aDataLength
));
201 mDecoder
= encoding
->NewDecoderWithBOMRemoval();
205 // BOM detection failed, check content stream for charset.
206 nsCOMPtr
<nsIRequest
> req
;
207 nsresult rv
= aLoader
->GetRequest(getter_AddRefs(req
));
208 NS_ASSERTION(req
, "StreamLoader's request went away prematurely");
209 NS_ENSURE_SUCCESS(rv
, false);
211 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(req
);
215 if (NS_SUCCEEDED(channel
->GetContentCharset(label
)) &&
216 (encoding
= Encoding::ForLabel(label
))) {
217 mDecoder
= encoding
->NewDecoderWithoutBOMHandling();
222 // Check the hint charset from the script element or preload
224 nsAutoString hintCharset
;
225 if (!mRequest
->mLoadContext
->IsPreload()) {
226 mRequest
->mLoadContext
->GetScriptElement()->GetScriptCharset(hintCharset
);
228 nsTArray
<ScriptLoader::PreloadInfo
>::index_type i
=
229 mScriptLoader
->mPreloads
.IndexOf(
230 mRequest
, 0, ScriptLoader::PreloadRequestComparator());
232 NS_ASSERTION(i
!= mScriptLoader
->mPreloads
.NoIndex
,
233 "Incorrect preload bookkeeping");
234 hintCharset
= mScriptLoader
->mPreloads
[i
].mCharset
;
237 if ((encoding
= Encoding::ForLabel(hintCharset
))) {
238 mDecoder
= encoding
->NewDecoderWithoutBOMHandling();
242 // Get the charset from the charset of the document.
243 if (mScriptLoader
->mDocument
) {
244 encoding
= mScriptLoader
->mDocument
->GetDocumentCharacterSet();
245 mDecoder
= encoding
->NewDecoderWithoutBOMHandling();
249 // Curiously, there are various callers that don't pass aDocument. The
250 // fallback in the old code was ISO-8859-1, which behaved like
252 mDecoder
= WINDOWS_1252_ENCODING
->NewDecoderWithoutBOMHandling();
256 nsresult
ScriptLoadHandler::MaybeDecodeSRI(uint32_t* sriLength
) {
259 if (!mSRIDataVerifier
|| mSRIDataVerifier
->IsComplete() ||
260 NS_FAILED(mSRIStatus
)) {
264 // Skip until the content is large enough to be decoded.
265 if (mRequest
->mScriptBytecode
.length() <=
266 mSRIDataVerifier
->DataSummaryLength()) {
270 mSRIStatus
= mSRIDataVerifier
->ImportDataSummary(
271 mRequest
->mScriptBytecode
.length(), mRequest
->mScriptBytecode
.begin());
273 if (NS_FAILED(mSRIStatus
)) {
274 // We are unable to decode the hash contained in the alternate data which
275 // contains the bytecode, or it does not use the same algorithm.
277 ("ScriptLoadHandler::MaybeDecodeSRI, failed to decode SRI, restart "
282 *sriLength
= mSRIDataVerifier
->DataSummaryLength();
283 MOZ_ASSERT(*sriLength
> 0);
287 nsresult
ScriptLoadHandler::EnsureKnownDataType(
288 nsIIncrementalStreamLoader
* aLoader
) {
289 MOZ_ASSERT(mRequest
->IsUnknownDataType());
290 MOZ_ASSERT(mRequest
->IsLoading());
292 nsCOMPtr
<nsIRequest
> req
;
293 nsresult rv
= aLoader
->GetRequest(getter_AddRefs(req
));
294 MOZ_ASSERT(req
, "StreamLoader's request went away prematurely");
295 NS_ENSURE_SUCCESS(rv
, rv
);
297 if (mRequest
->IsLoadingSource()) {
298 mRequest
->SetTextSource();
299 TRACE_FOR_TEST(mRequest
->mLoadContext
->GetScriptElement(),
300 "scriptloader_load_source");
304 nsCOMPtr
<nsICacheInfoChannel
> cic(do_QueryInterface(req
));
306 nsAutoCString altDataType
;
307 cic
->GetAlternativeDataType(altDataType
);
308 if (altDataType
.Equals(nsContentUtils::JSBytecodeMimeType())) {
309 mRequest
->SetBytecode();
310 TRACE_FOR_TEST(mRequest
->mLoadContext
->GetScriptElement(),
311 "scriptloader_load_bytecode");
314 MOZ_ASSERT(altDataType
.IsEmpty());
317 mRequest
->SetTextSource();
318 TRACE_FOR_TEST(mRequest
->mLoadContext
->GetScriptElement(),
319 "scriptloader_load_source");
321 MOZ_ASSERT(!mRequest
->IsUnknownDataType());
322 MOZ_ASSERT(mRequest
->IsLoading());
327 ScriptLoadHandler::OnStreamComplete(nsIIncrementalStreamLoader
* aLoader
,
328 nsISupports
* aContext
, nsresult aStatus
,
329 uint32_t aDataLength
,
330 const uint8_t* aData
) {
334 mRequest
->mURI
->GetAsciiSpec(url
);
335 LOG(("ScriptLoadRequest (%p): Stream complete (url = %s)", mRequest
.get(),
339 nsCOMPtr
<nsIRequest
> channelRequest
;
340 aLoader
->GetRequest(getter_AddRefs(channelRequest
));
342 if (!mPreloadStartNotified
) {
343 mPreloadStartNotified
= true;
344 mRequest
->mLoadContext
->NotifyStart(channelRequest
);
347 auto notifyStop
= MakeScopeExit(
348 [&] { mRequest
->mLoadContext
->NotifyStop(channelRequest
, rv
); });
350 if (!mRequest
->IsCanceled()) {
351 if (mRequest
->IsUnknownDataType()) {
352 rv
= EnsureKnownDataType(aLoader
);
353 NS_ENSURE_SUCCESS(rv
, rv
);
356 if (mRequest
->IsTextSource()) {
357 DebugOnly
<bool> encoderSet
=
358 EnsureDecoder(aLoader
, aData
, aDataLength
, /* aEndOfStream = */ true);
359 MOZ_ASSERT(encoderSet
);
360 rv
= DecodeRawData(aData
, aDataLength
, /* aEndOfStream = */ true);
361 NS_ENSURE_SUCCESS(rv
, rv
);
363 LOG(("ScriptLoadRequest (%p): Source length in code units = %u",
364 mRequest
.get(), unsigned(mRequest
->ScriptTextLength())));
366 // If SRI is required for this load, appending new bytes to the hash.
367 if (mSRIDataVerifier
&& NS_SUCCEEDED(mSRIStatus
)) {
368 mSRIStatus
= mSRIDataVerifier
->Update(aDataLength
, aData
);
371 MOZ_ASSERT(mRequest
->IsBytecode());
372 if (!mRequest
->mScriptBytecode
.append(aData
, aDataLength
)) {
373 return NS_ERROR_OUT_OF_MEMORY
;
376 LOG(("ScriptLoadRequest (%p): Bytecode length = %u", mRequest
.get(),
377 unsigned(mRequest
->mScriptBytecode
.length())));
379 // If we abort while decoding the SRI, we fallback on explictly requesting
380 // the source. Thus, we should not continue in
381 // ScriptLoader::OnStreamComplete, which removes the request from the
384 // We calculate the SRI length below.
386 rv
= MaybeDecodeSRI(&unused
);
388 return channelRequest
->Cancel(mScriptLoader
->RestartLoad(mRequest
));
391 // The bytecode cache always starts with the SRI hash, thus even if there
392 // is no SRI data verifier instance, we still want to skip the hash.
394 rv
= SRICheckDataVerifier::DataSummaryLength(
395 mRequest
->mScriptBytecode
.length(), mRequest
->mScriptBytecode
.begin(),
398 return channelRequest
->Cancel(mScriptLoader
->RestartLoad(mRequest
));
401 mRequest
->mBytecodeOffset
= JS::AlignTranscodingBytecodeOffset(sriLength
);
405 // Everything went well, keep the CacheInfoChannel alive such that we can
406 // later save the bytecode on the cache entry.
407 if (NS_SUCCEEDED(rv
) && mRequest
->IsSource() &&
408 StaticPrefs::dom_script_loader_bytecode_cache_enabled()) {
409 mRequest
->mCacheInfo
= do_QueryInterface(channelRequest
);
410 LOG(("ScriptLoadRequest (%p): nsICacheInfoChannel = %p", mRequest
.get(),
411 mRequest
->mCacheInfo
.get()));
414 // we have to mediate and use mRequest.
415 rv
= mScriptLoader
->OnStreamComplete(aLoader
, mRequest
, aStatus
, mSRIStatus
,
416 mSRIDataVerifier
.get());
418 // In case of failure, clear the mCacheInfoChannel to avoid keeping it alive.
420 mRequest
->mCacheInfo
= nullptr;
430 } // namespace mozilla