forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			432 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			432 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "ScriptLoadHandler.h"
 | |
| #include "ScriptLoader.h"
 | |
| #include "ScriptTrace.h"
 | |
| 
 | |
| #include "nsContentUtils.h"
 | |
| #include "nsIEncodedChannel.h"
 | |
| #include "nsIStringEnumerator.h"
 | |
| #include "nsMimeTypes.h"
 | |
| 
 | |
| #include "mozilla/Telemetry.h"
 | |
| 
 | |
| namespace mozilla {
 | |
| namespace dom {
 | |
| 
 | |
| #undef LOG
 | |
| #define LOG(args) \
 | |
|   MOZ_LOG(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug, args)
 | |
| 
 | |
| #define LOG_ENABLED() \
 | |
|   MOZ_LOG_TEST(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug)
 | |
| 
 | |
| ScriptLoadHandler::ScriptLoadHandler(ScriptLoader* aScriptLoader,
 | |
|                                      ScriptLoadRequest* aRequest,
 | |
|                                      SRICheckDataVerifier* aSRIDataVerifier)
 | |
|     : mScriptLoader(aScriptLoader),
 | |
|       mRequest(aRequest),
 | |
|       mSRIDataVerifier(aSRIDataVerifier),
 | |
|       mSRIStatus(NS_OK),
 | |
|       mDecoder() {
 | |
|   MOZ_ASSERT(mRequest->IsUnknownDataType());
 | |
|   MOZ_ASSERT(mRequest->IsLoading());
 | |
| }
 | |
| 
 | |
| ScriptLoadHandler::~ScriptLoadHandler() {}
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(ScriptLoadHandler, nsIIncrementalStreamLoaderObserver)
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| ScriptLoadHandler::OnIncrementalData(nsIIncrementalStreamLoader* aLoader,
 | |
|                                      nsISupports* aContext,
 | |
|                                      uint32_t aDataLength, const uint8_t* aData,
 | |
|                                      uint32_t* aConsumedLength) {
 | |
|   if (mRequest->IsCanceled()) {
 | |
|     // If request cancelled, ignore any incoming data.
 | |
|     *aConsumedLength = aDataLength;
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   nsresult rv = NS_OK;
 | |
|   if (mRequest->IsUnknownDataType()) {
 | |
|     rv = EnsureKnownDataType(aLoader);
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
|   }
 | |
| 
 | |
|   if (mRequest->IsTextSource()) {
 | |
|     if (!EnsureDecoder(aLoader, aData, aDataLength,
 | |
|                        /* aEndOfStream = */ false)) {
 | |
|       return NS_OK;
 | |
|     }
 | |
| 
 | |
|     // Below we will/shall consume entire data chunk.
 | |
|     *aConsumedLength = aDataLength;
 | |
| 
 | |
|     // Decoder has already been initialized. -- trying to decode all loaded
 | |
|     // bytes.
 | |
|     rv = DecodeRawData(aData, aDataLength, /* aEndOfStream = */ false);
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|     // If SRI is required for this load, appending new bytes to the hash.
 | |
|     if (mSRIDataVerifier && NS_SUCCEEDED(mSRIStatus)) {
 | |
|       mSRIStatus = mSRIDataVerifier->Update(aDataLength, aData);
 | |
|     }
 | |
|   } else if (mRequest->IsBinASTSource()) {
 | |
|     if (!mRequest->ScriptBinASTData().append(aData, aDataLength)) {
 | |
|       return NS_ERROR_OUT_OF_MEMORY;
 | |
|     }
 | |
| 
 | |
|     // Below we will/shall consume entire data chunk.
 | |
|     *aConsumedLength = aDataLength;
 | |
| 
 | |
|     // If SRI is required for this load, appending new bytes to the hash.
 | |
|     if (mSRIDataVerifier && NS_SUCCEEDED(mSRIStatus)) {
 | |
|       mSRIStatus = mSRIDataVerifier->Update(aDataLength, aData);
 | |
|     }
 | |
|   } else {
 | |
|     MOZ_ASSERT(mRequest->IsBytecode());
 | |
|     if (!mRequest->mScriptBytecode.append(aData, aDataLength)) {
 | |
|       return NS_ERROR_OUT_OF_MEMORY;
 | |
|     }
 | |
| 
 | |
|     *aConsumedLength = aDataLength;
 | |
|     rv = MaybeDecodeSRI();
 | |
|     if (NS_FAILED(rv)) {
 | |
|       nsCOMPtr<nsIRequest> channelRequest;
 | |
|       aLoader->GetRequest(getter_AddRefs(channelRequest));
 | |
|       return channelRequest->Cancel(mScriptLoader->RestartLoad(mRequest));
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return rv;
 | |
| }
 | |
| 
 | |
| nsresult ScriptLoadHandler::DecodeRawData(const uint8_t* aData,
 | |
|                                           uint32_t aDataLength,
 | |
|                                           bool aEndOfStream) {
 | |
|   CheckedInt<size_t> needed = mDecoder->MaxUTF16BufferLength(aDataLength);
 | |
|   if (!needed.isValid()) {
 | |
|     return NS_ERROR_OUT_OF_MEMORY;
 | |
|   }
 | |
| 
 | |
|   // Reference to the script source buffer which we will update.
 | |
|   ScriptLoadRequest::ScriptTextBuffer& scriptText = mRequest->ScriptText();
 | |
| 
 | |
|   uint32_t haveRead = scriptText.length();
 | |
| 
 | |
|   CheckedInt<uint32_t> capacity = haveRead;
 | |
|   capacity += needed.value();
 | |
| 
 | |
|   if (!capacity.isValid() || !scriptText.resize(capacity.value())) {
 | |
|     return NS_ERROR_OUT_OF_MEMORY;
 | |
|   }
 | |
| 
 | |
|   uint32_t result;
 | |
|   size_t read;
 | |
|   size_t written;
 | |
|   bool hadErrors;
 | |
|   Tie(result, read, written, hadErrors) = mDecoder->DecodeToUTF16(
 | |
|       MakeSpan(aData, aDataLength),
 | |
|       MakeSpan(scriptText.begin() + haveRead, needed.value()), aEndOfStream);
 | |
|   MOZ_ASSERT(result == kInputEmpty);
 | |
|   MOZ_ASSERT(read == aDataLength);
 | |
|   MOZ_ASSERT(written <= needed.value());
 | |
|   Unused << hadErrors;
 | |
| 
 | |
|   haveRead += written;
 | |
|   MOZ_ASSERT(haveRead <= capacity.value(),
 | |
|              "mDecoder produced more data than expected");
 | |
|   MOZ_ALWAYS_TRUE(scriptText.resize(haveRead));
 | |
|   mRequest->mScriptTextLength = scriptText.length();
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| bool ScriptLoadHandler::EnsureDecoder(nsIIncrementalStreamLoader* aLoader,
 | |
|                                       const uint8_t* aData,
 | |
|                                       uint32_t aDataLength, bool aEndOfStream) {
 | |
|   // Check if decoder has already been created.
 | |
|   if (mDecoder) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   nsAutoCString charset;
 | |
|   if (!EnsureDecoder(aLoader, aData, aDataLength, aEndOfStream, charset)) {
 | |
|     return false;
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool ScriptLoadHandler::EnsureDecoder(nsIIncrementalStreamLoader* aLoader,
 | |
|                                       const uint8_t* aData,
 | |
|                                       uint32_t aDataLength, bool aEndOfStream,
 | |
|                                       nsCString& oCharset) {
 | |
|   // JavaScript modules are always UTF-8.
 | |
|   if (mRequest->IsModuleRequest()) {
 | |
|     oCharset = "UTF-8";
 | |
|     mDecoder = UTF_8_ENCODING->NewDecoderWithBOMRemoval();
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // Determine if BOM check should be done.  This occurs either
 | |
|   // if end-of-stream has been reached, or at least 3 bytes have
 | |
|   // been read from input.
 | |
|   if (!aEndOfStream && (aDataLength < 3)) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // Do BOM detection.
 | |
|   const Encoding* encoding;
 | |
|   size_t bomLength;
 | |
|   Tie(encoding, bomLength) = Encoding::ForBOM(MakeSpan(aData, aDataLength));
 | |
|   if (encoding) {
 | |
|     mDecoder = encoding->NewDecoderWithBOMRemoval();
 | |
|     encoding->Name(oCharset);
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // BOM detection failed, check content stream for charset.
 | |
|   nsCOMPtr<nsIRequest> req;
 | |
|   nsresult rv = aLoader->GetRequest(getter_AddRefs(req));
 | |
|   NS_ASSERTION(req, "StreamLoader's request went away prematurely");
 | |
|   NS_ENSURE_SUCCESS(rv, false);
 | |
| 
 | |
|   nsCOMPtr<nsIChannel> channel = do_QueryInterface(req);
 | |
| 
 | |
|   if (channel) {
 | |
|     nsAutoCString label;
 | |
|     if (NS_SUCCEEDED(channel->GetContentCharset(label)) &&
 | |
|         (encoding = Encoding::ForLabel(label))) {
 | |
|       mDecoder = encoding->NewDecoderWithoutBOMHandling();
 | |
|       encoding->Name(oCharset);
 | |
|       return true;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Check the hint charset from the script element or preload
 | |
|   // request.
 | |
|   nsAutoString hintCharset;
 | |
|   if (!mRequest->IsPreload()) {
 | |
|     mRequest->Element()->GetScriptCharset(hintCharset);
 | |
|   } else {
 | |
|     nsTArray<ScriptLoader::PreloadInfo>::index_type i =
 | |
|         mScriptLoader->mPreloads.IndexOf(
 | |
|             mRequest, 0, ScriptLoader::PreloadRequestComparator());
 | |
| 
 | |
|     NS_ASSERTION(i != mScriptLoader->mPreloads.NoIndex,
 | |
|                  "Incorrect preload bookkeeping");
 | |
|     hintCharset = mScriptLoader->mPreloads[i].mCharset;
 | |
|   }
 | |
| 
 | |
|   if ((encoding = Encoding::ForLabel(hintCharset))) {
 | |
|     mDecoder = encoding->NewDecoderWithoutBOMHandling();
 | |
|     encoding->Name(oCharset);
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // Get the charset from the charset of the document.
 | |
|   if (mScriptLoader->mDocument) {
 | |
|     encoding = mScriptLoader->mDocument->GetDocumentCharacterSet();
 | |
|     mDecoder = encoding->NewDecoderWithoutBOMHandling();
 | |
|     encoding->Name(oCharset);
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // Curiously, there are various callers that don't pass aDocument. The
 | |
|   // fallback in the old code was ISO-8859-1, which behaved like
 | |
|   // windows-1252.
 | |
|   oCharset = "windows-1252";
 | |
|   mDecoder = WINDOWS_1252_ENCODING->NewDecoderWithoutBOMHandling();
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| nsresult ScriptLoadHandler::MaybeDecodeSRI() {
 | |
|   if (!mSRIDataVerifier || mSRIDataVerifier->IsComplete() ||
 | |
|       NS_FAILED(mSRIStatus)) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // Skip until the content is large enough to be decoded.
 | |
|   if (mRequest->mScriptBytecode.length() <=
 | |
|       mSRIDataVerifier->DataSummaryLength()) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   mSRIStatus = mSRIDataVerifier->ImportDataSummary(
 | |
|       mRequest->mScriptBytecode.length(), mRequest->mScriptBytecode.begin());
 | |
| 
 | |
|   if (NS_FAILED(mSRIStatus)) {
 | |
|     // We are unable to decode the hash contained in the alternate data which
 | |
|     // contains the bytecode, or it does not use the same algorithm.
 | |
|     LOG(
 | |
|         ("ScriptLoadHandler::MaybeDecodeSRI, failed to decode SRI, restart "
 | |
|          "request"));
 | |
|     return mSRIStatus;
 | |
|   }
 | |
| 
 | |
|   mRequest->mBytecodeOffset = mSRIDataVerifier->DataSummaryLength();
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult ScriptLoadHandler::EnsureKnownDataType(
 | |
|     nsIIncrementalStreamLoader* aLoader) {
 | |
|   MOZ_ASSERT(mRequest->IsUnknownDataType());
 | |
|   MOZ_ASSERT(mRequest->IsLoading());
 | |
| 
 | |
|   nsCOMPtr<nsIRequest> req;
 | |
|   nsresult rv = aLoader->GetRequest(getter_AddRefs(req));
 | |
|   MOZ_ASSERT(req, "StreamLoader's request went away prematurely");
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   if (mRequest->IsLoadingSource()) {
 | |
|     mRequest->SetTextSource();
 | |
|     TRACE_FOR_TEST(mRequest->Element(), "scriptloader_load_source");
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsICacheInfoChannel> cic(do_QueryInterface(req));
 | |
|   if (cic) {
 | |
|     nsAutoCString altDataType;
 | |
|     cic->GetAlternativeDataType(altDataType);
 | |
|     if (altDataType.Equals(nsContentUtils::JSBytecodeMimeType())) {
 | |
|       mRequest->SetBytecode();
 | |
|       TRACE_FOR_TEST(mRequest->Element(), "scriptloader_load_bytecode");
 | |
|       return NS_OK;
 | |
|     }
 | |
|     MOZ_ASSERT(altDataType.IsEmpty());
 | |
|   }
 | |
| 
 | |
|   if (nsJSUtils::BinASTEncodingEnabled()) {
 | |
|     nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(req);
 | |
|     if (httpChannel) {
 | |
|       nsAutoCString mimeType;
 | |
|       httpChannel->GetContentType(mimeType);
 | |
|       if (mimeType.LowerCaseEqualsASCII(APPLICATION_JAVASCRIPT_BINAST)) {
 | |
|         if (mRequest->ShouldAcceptBinASTEncoding()) {
 | |
|           mRequest->SetBinASTSource();
 | |
|           TRACE_FOR_TEST(mRequest->Element(), "scriptloader_load_source");
 | |
|           return NS_OK;
 | |
|         }
 | |
| 
 | |
|         // If the request isn't allowed to accept BinAST, fallback to text
 | |
|         // source.  The possibly binary source will be passed to normal
 | |
|         // JS parser and will throw error there.
 | |
|         mRequest->SetTextSource();
 | |
|         return NS_OK;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   mRequest->SetTextSource();
 | |
|   TRACE_FOR_TEST(mRequest->Element(), "scriptloader_load_source");
 | |
| 
 | |
|   MOZ_ASSERT(!mRequest->IsUnknownDataType());
 | |
|   MOZ_ASSERT(mRequest->IsLoading());
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| ScriptLoadHandler::OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
 | |
|                                     nsISupports* aContext, nsresult aStatus,
 | |
|                                     uint32_t aDataLength,
 | |
|                                     const uint8_t* aData) {
 | |
|   nsresult rv = NS_OK;
 | |
|   if (LOG_ENABLED()) {
 | |
|     nsAutoCString url;
 | |
|     mRequest->mURI->GetAsciiSpec(url);
 | |
|     LOG(("ScriptLoadRequest (%p): Stream complete (url = %s)", mRequest.get(),
 | |
|          url.get()));
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIRequest> channelRequest;
 | |
|   aLoader->GetRequest(getter_AddRefs(channelRequest));
 | |
| 
 | |
|   if (!mRequest->IsCanceled()) {
 | |
|     if (mRequest->IsUnknownDataType()) {
 | |
|       rv = EnsureKnownDataType(aLoader);
 | |
|       NS_ENSURE_SUCCESS(rv, rv);
 | |
|     }
 | |
| 
 | |
|     if (mRequest->IsTextSource()) {
 | |
|       DebugOnly<bool> encoderSet =
 | |
|           EnsureDecoder(aLoader, aData, aDataLength, /* aEndOfStream = */ true);
 | |
|       MOZ_ASSERT(encoderSet);
 | |
|       rv = DecodeRawData(aData, aDataLength, /* aEndOfStream = */ true);
 | |
|       NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|       LOG(("ScriptLoadRequest (%p): Source length = %u", mRequest.get(),
 | |
|            unsigned(mRequest->ScriptText().length())));
 | |
| 
 | |
|       // If SRI is required for this load, appending new bytes to the hash.
 | |
|       if (mSRIDataVerifier && NS_SUCCEEDED(mSRIStatus)) {
 | |
|         mSRIStatus = mSRIDataVerifier->Update(aDataLength, aData);
 | |
|       }
 | |
|     } else if (mRequest->IsBinASTSource()) {
 | |
|       if (!mRequest->ScriptBinASTData().append(aData, aDataLength)) {
 | |
|         return NS_ERROR_OUT_OF_MEMORY;
 | |
|       }
 | |
| 
 | |
|       // If SRI is required for this load, appending new bytes to the hash.
 | |
|       if (mSRIDataVerifier && NS_SUCCEEDED(mSRIStatus)) {
 | |
|         mSRIStatus = mSRIDataVerifier->Update(aDataLength, aData);
 | |
|       }
 | |
|     } else {
 | |
|       MOZ_ASSERT(mRequest->IsBytecode());
 | |
|       if (!mRequest->mScriptBytecode.append(aData, aDataLength)) {
 | |
|         return NS_ERROR_OUT_OF_MEMORY;
 | |
|       }
 | |
| 
 | |
|       LOG(("ScriptLoadRequest (%p): Bytecode length = %u", mRequest.get(),
 | |
|            unsigned(mRequest->mScriptBytecode.length())));
 | |
| 
 | |
|       // If we abort while decoding the SRI, we fallback on explictly requesting
 | |
|       // the source. Thus, we should not continue in
 | |
|       // ScriptLoader::OnStreamComplete, which removes the request from the
 | |
|       // waiting lists.
 | |
|       rv = MaybeDecodeSRI();
 | |
|       if (NS_FAILED(rv)) {
 | |
|         return channelRequest->Cancel(mScriptLoader->RestartLoad(mRequest));
 | |
|       }
 | |
| 
 | |
|       // The bytecode cache always starts with the SRI hash, thus even if there
 | |
|       // is no SRI data verifier instance, we still want to skip the hash.
 | |
|       rv = SRICheckDataVerifier::DataSummaryLength(
 | |
|           mRequest->mScriptBytecode.length(), mRequest->mScriptBytecode.begin(),
 | |
|           &mRequest->mBytecodeOffset);
 | |
|       if (NS_FAILED(rv)) {
 | |
|         return channelRequest->Cancel(mScriptLoader->RestartLoad(mRequest));
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Everything went well, keep the CacheInfoChannel alive such that we can
 | |
|   // later save the bytecode on the cache entry.
 | |
|   if (NS_SUCCEEDED(rv) && mRequest->IsSource() &&
 | |
|       nsContentUtils::IsBytecodeCacheEnabled()) {
 | |
|     mRequest->mCacheInfo = do_QueryInterface(channelRequest);
 | |
|     LOG(("ScriptLoadRequest (%p): nsICacheInfoChannel = %p", mRequest.get(),
 | |
|          mRequest->mCacheInfo.get()));
 | |
|   }
 | |
| 
 | |
|   // we have to mediate and use mRequest.
 | |
|   rv = mScriptLoader->OnStreamComplete(aLoader, mRequest, aStatus, mSRIStatus,
 | |
|                                        mSRIDataVerifier);
 | |
| 
 | |
|   // In case of failure, clear the mCacheInfoChannel to avoid keeping it alive.
 | |
|   if (NS_FAILED(rv)) {
 | |
|     mRequest->mCacheInfo = nullptr;
 | |
|   }
 | |
| 
 | |
|   return rv;
 | |
| }
 | |
| 
 | |
| #undef LOG_ENABLED
 | |
| #undef LOG
 | |
| 
 | |
| }  // namespace dom
 | |
| }  // namespace mozilla
 | 
