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