Line data Source code
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/. */
6 :
7 : #include "ScriptLoadHandler.h"
8 : #include "ScriptLoader.h"
9 : #include "ScriptTrace.h"
10 :
11 : #include "nsContentUtils.h"
12 :
13 : #include "mozilla/Telemetry.h"
14 :
15 : namespace mozilla {
16 : namespace dom {
17 :
18 : #undef LOG
19 : #define LOG(args) \
20 : MOZ_LOG(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug, args)
21 :
22 : #define LOG_ENABLED() \
23 : MOZ_LOG_TEST(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug)
24 :
25 4 : ScriptLoadHandler::ScriptLoadHandler(ScriptLoader* aScriptLoader,
26 : ScriptLoadRequest* aRequest,
27 4 : SRICheckDataVerifier* aSRIDataVerifier)
28 : : mScriptLoader(aScriptLoader),
29 : mRequest(aRequest),
30 : mSRIDataVerifier(aSRIDataVerifier),
31 : mSRIStatus(NS_OK),
32 4 : mDecoder()
33 : {
34 4 : MOZ_ASSERT(mRequest->IsUnknownDataType());
35 4 : MOZ_ASSERT(mRequest->IsLoading());
36 4 : }
37 :
38 8 : ScriptLoadHandler::~ScriptLoadHandler()
39 12 : {}
40 :
41 28 : NS_IMPL_ISUPPORTS(ScriptLoadHandler, nsIIncrementalStreamLoaderObserver)
42 :
43 : NS_IMETHODIMP
44 4 : ScriptLoadHandler::OnIncrementalData(nsIIncrementalStreamLoader* aLoader,
45 : nsISupports* aContext,
46 : uint32_t aDataLength,
47 : const uint8_t* aData,
48 : uint32_t* aConsumedLength)
49 : {
50 4 : if (mRequest->IsCanceled()) {
51 : // If request cancelled, ignore any incoming data.
52 0 : *aConsumedLength = aDataLength;
53 0 : return NS_OK;
54 : }
55 :
56 4 : nsresult rv = NS_OK;
57 4 : if (mRequest->IsUnknownDataType()) {
58 4 : rv = EnsureKnownDataType(aLoader);
59 4 : NS_ENSURE_SUCCESS(rv, rv);
60 : }
61 :
62 4 : if (mRequest->IsSource()) {
63 4 : if (!EnsureDecoder(aLoader, aData, aDataLength,
64 : /* aEndOfStream = */ false)) {
65 0 : return NS_OK;
66 : }
67 :
68 : // Below we will/shall consume entire data chunk.
69 4 : *aConsumedLength = aDataLength;
70 :
71 : // Decoder has already been initialized. -- trying to decode all loaded bytes.
72 4 : rv = DecodeRawData(aData, aDataLength, /* aEndOfStream = */ false);
73 4 : NS_ENSURE_SUCCESS(rv, rv);
74 :
75 : // If SRI is required for this load, appending new bytes to the hash.
76 4 : if (mSRIDataVerifier && NS_SUCCEEDED(mSRIStatus)) {
77 0 : mSRIStatus = mSRIDataVerifier->Update(aDataLength, aData);
78 : }
79 : } else {
80 0 : MOZ_ASSERT(mRequest->IsBytecode());
81 0 : if (!mRequest->mScriptBytecode.append(aData, aDataLength)) {
82 0 : return NS_ERROR_OUT_OF_MEMORY;
83 : }
84 :
85 0 : *aConsumedLength = aDataLength;
86 0 : rv = MaybeDecodeSRI();
87 0 : if (NS_FAILED(rv)) {
88 0 : nsCOMPtr<nsIRequest> channelRequest;
89 0 : aLoader->GetRequest(getter_AddRefs(channelRequest));
90 0 : return channelRequest->Cancel(mScriptLoader->RestartLoad(mRequest));
91 : }
92 : }
93 :
94 4 : return rv;
95 : }
96 :
97 : nsresult
98 8 : ScriptLoadHandler::DecodeRawData(const uint8_t* aData,
99 : uint32_t aDataLength,
100 : bool aEndOfStream)
101 : {
102 8 : CheckedInt<size_t> needed = mDecoder->MaxUTF16BufferLength(aDataLength);
103 8 : if (!needed.isValid()) {
104 0 : return NS_ERROR_OUT_OF_MEMORY;
105 : }
106 :
107 8 : uint32_t haveRead = mRequest->mScriptText.length();
108 :
109 8 : CheckedInt<uint32_t> capacity = haveRead;
110 8 : capacity += needed.value();
111 :
112 8 : if (!capacity.isValid() || !mRequest->mScriptText.reserve(capacity.value())) {
113 0 : return NS_ERROR_OUT_OF_MEMORY;
114 : }
115 :
116 : uint32_t result;
117 : size_t read;
118 : size_t written;
119 : bool hadErrors;
120 24 : Tie(result, read, written, hadErrors) = mDecoder->DecodeToUTF16(
121 : MakeSpan(aData, aDataLength),
122 8 : MakeSpan(mRequest->mScriptText.begin() + haveRead, needed.value()),
123 8 : aEndOfStream);
124 8 : MOZ_ASSERT(result == kInputEmpty);
125 8 : MOZ_ASSERT(read == aDataLength);
126 8 : MOZ_ASSERT(written <= needed.value());
127 : Unused << hadErrors;
128 :
129 8 : haveRead += written;
130 8 : MOZ_ASSERT(haveRead <= capacity.value(), "mDecoder produced more data than expected");
131 8 : MOZ_ALWAYS_TRUE(mRequest->mScriptText.resizeUninitialized(haveRead));
132 :
133 8 : return NS_OK;
134 : }
135 :
136 : bool
137 8 : ScriptLoadHandler::EnsureDecoder(nsIIncrementalStreamLoader* aLoader,
138 : const uint8_t* aData,
139 : uint32_t aDataLength,
140 : bool aEndOfStream)
141 : {
142 : // Check if decoder has already been created.
143 8 : if (mDecoder) {
144 4 : return true;
145 : }
146 :
147 8 : nsAutoCString charset;
148 4 : if (!EnsureDecoder(aLoader, aData, aDataLength, aEndOfStream, charset)) {
149 0 : return false;
150 : }
151 4 : if (charset.Length() == 0) {
152 0 : charset = "?";
153 : }
154 : mozilla::Telemetry::Accumulate(mozilla::Telemetry::DOM_SCRIPT_SRC_ENCODING,
155 4 : charset);
156 4 : return true;
157 : }
158 :
159 : bool
160 4 : ScriptLoadHandler::EnsureDecoder(nsIIncrementalStreamLoader* aLoader,
161 : const uint8_t* aData,
162 : uint32_t aDataLength,
163 : bool aEndOfStream,
164 : nsCString& oCharset)
165 : {
166 : // JavaScript modules are always UTF-8.
167 4 : if (mRequest->IsModuleRequest()) {
168 0 : oCharset = "UTF-8";
169 0 : mDecoder = UTF_8_ENCODING->NewDecoderWithBOMRemoval();
170 0 : return true;
171 : }
172 :
173 : // Determine if BOM check should be done. This occurs either
174 : // if end-of-stream has been reached, or at least 3 bytes have
175 : // been read from input.
176 4 : if (!aEndOfStream && (aDataLength < 3)) {
177 0 : return false;
178 : }
179 :
180 : // Do BOM detection.
181 : const Encoding* encoding;
182 : size_t bomLength;
183 4 : Tie(encoding, bomLength) = Encoding::ForBOM(MakeSpan(aData, aDataLength));
184 4 : if (encoding) {
185 0 : mDecoder = encoding->NewDecoderWithBOMRemoval();
186 0 : encoding->Name(oCharset);
187 0 : return true;
188 : }
189 :
190 : // BOM detection failed, check content stream for charset.
191 8 : nsCOMPtr<nsIRequest> req;
192 4 : nsresult rv = aLoader->GetRequest(getter_AddRefs(req));
193 4 : NS_ASSERTION(req, "StreamLoader's request went away prematurely");
194 4 : NS_ENSURE_SUCCESS(rv, false);
195 :
196 8 : nsCOMPtr<nsIChannel> channel = do_QueryInterface(req);
197 :
198 4 : if (channel) {
199 6 : nsAutoCString label;
200 8 : if (NS_SUCCEEDED(channel->GetContentCharset(label)) &&
201 4 : (encoding = Encoding::ForLabel(label))) {
202 2 : mDecoder = encoding->NewDecoderWithoutBOMHandling();
203 2 : encoding->Name(oCharset);
204 2 : return true;
205 : }
206 : }
207 :
208 : // Check the hint charset from the script element or preload
209 : // request.
210 4 : nsAutoString hintCharset;
211 2 : if (!mRequest->IsPreload()) {
212 2 : mRequest->mElement->GetScriptCharset(hintCharset);
213 : } else {
214 : nsTArray<ScriptLoader::PreloadInfo>::index_type i =
215 0 : mScriptLoader->mPreloads.IndexOf(mRequest, 0,
216 0 : ScriptLoader::PreloadRequestComparator());
217 :
218 0 : NS_ASSERTION(i != mScriptLoader->mPreloads.NoIndex,
219 : "Incorrect preload bookkeeping");
220 0 : hintCharset = mScriptLoader->mPreloads[i].mCharset;
221 : }
222 :
223 2 : if ((encoding = Encoding::ForLabel(hintCharset))) {
224 0 : mDecoder = encoding->NewDecoderWithoutBOMHandling();
225 0 : encoding->Name(oCharset);
226 0 : return true;
227 : }
228 :
229 : // Get the charset from the charset of the document.
230 2 : if (mScriptLoader->mDocument) {
231 2 : encoding = mScriptLoader->mDocument->GetDocumentCharacterSet();
232 2 : mDecoder = encoding->NewDecoderWithoutBOMHandling();
233 2 : encoding->Name(oCharset);
234 2 : return true;
235 : }
236 :
237 : // Curiously, there are various callers that don't pass aDocument. The
238 : // fallback in the old code was ISO-8859-1, which behaved like
239 : // windows-1252.
240 0 : oCharset = "windows-1252";
241 0 : mDecoder = WINDOWS_1252_ENCODING->NewDecoderWithoutBOMHandling();
242 0 : return true;
243 : }
244 :
245 : nsresult
246 0 : ScriptLoadHandler::MaybeDecodeSRI()
247 : {
248 0 : if (!mSRIDataVerifier || mSRIDataVerifier->IsComplete() || NS_FAILED(mSRIStatus)) {
249 0 : return NS_OK;
250 : }
251 :
252 : // Skip until the content is large enough to be decoded.
253 0 : if (mRequest->mScriptBytecode.length() <= mSRIDataVerifier->DataSummaryLength()) {
254 0 : return NS_OK;
255 : }
256 :
257 0 : mSRIStatus = mSRIDataVerifier->ImportDataSummary(
258 0 : mRequest->mScriptBytecode.length(), mRequest->mScriptBytecode.begin());
259 :
260 0 : if (NS_FAILED(mSRIStatus)) {
261 : // We are unable to decode the hash contained in the alternate data which
262 : // contains the bytecode, or it does not use the same algorithm.
263 0 : LOG(("ScriptLoadHandler::MaybeDecodeSRI, failed to decode SRI, restart request"));
264 0 : return mSRIStatus;
265 : }
266 :
267 0 : mRequest->mBytecodeOffset = mSRIDataVerifier->DataSummaryLength();
268 0 : return NS_OK;
269 : }
270 :
271 : nsresult
272 4 : ScriptLoadHandler::EnsureKnownDataType(nsIIncrementalStreamLoader* aLoader)
273 : {
274 4 : MOZ_ASSERT(mRequest->IsUnknownDataType());
275 4 : MOZ_ASSERT(mRequest->IsLoading());
276 4 : if (mRequest->IsLoadingSource()) {
277 0 : mRequest->mDataType = ScriptLoadRequest::DataType::Source;
278 0 : TRACE_FOR_TEST(mRequest->mElement, "scriptloader_load_source");
279 0 : return NS_OK;
280 : }
281 :
282 8 : nsCOMPtr<nsIRequest> req;
283 4 : nsresult rv = aLoader->GetRequest(getter_AddRefs(req));
284 4 : MOZ_ASSERT(req, "StreamLoader's request went away prematurely");
285 4 : NS_ENSURE_SUCCESS(rv, rv);
286 :
287 8 : nsCOMPtr<nsICacheInfoChannel> cic(do_QueryInterface(req));
288 4 : if (cic) {
289 4 : nsAutoCString altDataType;
290 2 : cic->GetAlternativeDataType(altDataType);
291 2 : if (altDataType.Equals(nsContentUtils::JSBytecodeMimeType())) {
292 0 : mRequest->mDataType = ScriptLoadRequest::DataType::Bytecode;
293 0 : TRACE_FOR_TEST(mRequest->mElement, "scriptloader_load_bytecode");
294 : } else {
295 2 : MOZ_ASSERT(altDataType.IsEmpty());
296 2 : mRequest->mDataType = ScriptLoadRequest::DataType::Source;
297 2 : TRACE_FOR_TEST(mRequest->mElement, "scriptloader_load_source");
298 : }
299 : } else {
300 2 : mRequest->mDataType = ScriptLoadRequest::DataType::Source;
301 2 : TRACE_FOR_TEST(mRequest->mElement, "scriptloader_load_source");
302 : }
303 4 : MOZ_ASSERT(!mRequest->IsUnknownDataType());
304 4 : MOZ_ASSERT(mRequest->IsLoading());
305 4 : return NS_OK;
306 : }
307 :
308 : NS_IMETHODIMP
309 4 : ScriptLoadHandler::OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
310 : nsISupports* aContext,
311 : nsresult aStatus,
312 : uint32_t aDataLength,
313 : const uint8_t* aData)
314 : {
315 4 : nsresult rv = NS_OK;
316 4 : if (LOG_ENABLED()) {
317 0 : nsAutoCString url;
318 0 : mRequest->mURI->GetAsciiSpec(url);
319 0 : LOG(("ScriptLoadRequest (%p): Stream complete (url = %s)",
320 : mRequest.get(), url.get()));
321 : }
322 :
323 8 : nsCOMPtr<nsIRequest> channelRequest;
324 4 : aLoader->GetRequest(getter_AddRefs(channelRequest));
325 :
326 4 : if (!mRequest->IsCanceled()) {
327 4 : if (mRequest->IsUnknownDataType()) {
328 0 : rv = EnsureKnownDataType(aLoader);
329 0 : NS_ENSURE_SUCCESS(rv, rv);
330 : }
331 :
332 4 : if (mRequest->IsSource()) {
333 : DebugOnly<bool> encoderSet =
334 8 : EnsureDecoder(aLoader, aData, aDataLength, /* aEndOfStream = */ true);
335 4 : MOZ_ASSERT(encoderSet);
336 4 : rv = DecodeRawData(aData, aDataLength, /* aEndOfStream = */ true);
337 4 : NS_ENSURE_SUCCESS(rv, rv);
338 :
339 4 : LOG(("ScriptLoadRequest (%p): Source length = %u",
340 : mRequest.get(), unsigned(mRequest->mScriptText.length())));
341 :
342 : // If SRI is required for this load, appending new bytes to the hash.
343 4 : if (mSRIDataVerifier && NS_SUCCEEDED(mSRIStatus)) {
344 0 : mSRIStatus = mSRIDataVerifier->Update(aDataLength, aData);
345 : }
346 : } else {
347 0 : MOZ_ASSERT(mRequest->IsBytecode());
348 0 : if (!mRequest->mScriptBytecode.append(aData, aDataLength)) {
349 0 : return NS_ERROR_OUT_OF_MEMORY;
350 : }
351 :
352 0 : LOG(("ScriptLoadRequest (%p): Bytecode length = %u",
353 : mRequest.get(), unsigned(mRequest->mScriptBytecode.length())));
354 :
355 : // If we abort while decoding the SRI, we fallback on explictly requesting
356 : // the source. Thus, we should not continue in
357 : // ScriptLoader::OnStreamComplete, which removes the request from the
358 : // waiting lists.
359 0 : rv = MaybeDecodeSRI();
360 0 : if (NS_FAILED(rv)) {
361 0 : return channelRequest->Cancel(mScriptLoader->RestartLoad(mRequest));
362 : }
363 :
364 : // The bytecode cache always starts with the SRI hash, thus even if there
365 : // is no SRI data verifier instance, we still want to skip the hash.
366 0 : rv = SRICheckDataVerifier::DataSummaryLength(mRequest->mScriptBytecode.length(),
367 0 : mRequest->mScriptBytecode.begin(),
368 0 : &mRequest->mBytecodeOffset);
369 0 : if (NS_FAILED(rv)) {
370 0 : return channelRequest->Cancel(mScriptLoader->RestartLoad(mRequest));
371 : }
372 : }
373 : }
374 :
375 : // Everything went well, keep the CacheInfoChannel alive such that we can
376 : // later save the bytecode on the cache entry.
377 8 : if (NS_SUCCEEDED(rv) && mRequest->IsSource() &&
378 4 : nsContentUtils::IsBytecodeCacheEnabled()) {
379 0 : mRequest->mCacheInfo = do_QueryInterface(channelRequest);
380 0 : LOG(("ScriptLoadRequest (%p): nsICacheInfoChannel = %p",
381 : mRequest.get(), mRequest->mCacheInfo.get()));
382 : }
383 :
384 : // we have to mediate and use mRequest.
385 4 : rv = mScriptLoader->OnStreamComplete(aLoader, mRequest, aStatus, mSRIStatus,
386 4 : mSRIDataVerifier);
387 :
388 : // In case of failure, clear the mCacheInfoChannel to avoid keeping it alive.
389 4 : if (NS_FAILED(rv)) {
390 0 : mRequest->mCacheInfo = nullptr;
391 : }
392 :
393 4 : return rv;
394 : }
395 :
396 : #undef LOG_ENABLED
397 : #undef LOG
398 :
399 : } // dom namespace
400 : } // mozilla namespace
|