Bug 1323100 - Assign names to all remaining threads that are created through NS_NewThread and create them using NS_NewNamedThread instead. r=froydnj
MozReview-Commit-ID: 7W1dt2BBKJZ

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- *//* vim: set ts=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"BackgroundFileSaver.h"#include"ScopedNSSTypes.h"#include"mozilla/Casting.h"#include"mozilla/Logging.h"#include"mozilla/Telemetry.h"#include"nsCOMArray.h"#include"nsIAsyncInputStream.h"#include"nsIFile.h"#include"nsIMutableArray.h"#include"nsIPipe.h"#include"nsIX509Cert.h"#include"nsIX509CertDB.h"#include"nsIX509CertList.h"#include"nsNetUtil.h"#include"nsThreadUtils.h"#include"pk11pub.h"#include"secoidt.h"#ifdef XP_WIN#include<windows.h>#include<softpub.h>#include<wintrust.h>#endif // XP_WINnamespacemozilla{namespacenet{// MOZ_LOG=BackgroundFileSaver:5staticLazyLogModuleprlog("BackgroundFileSaver");#define LOG(args) MOZ_LOG(prlog, mozilla::LogLevel::Debug, args)#define LOG_ENABLED() MOZ_LOG_TEST(prlog, mozilla::LogLevel::Debug)//////////////////////////////////////////////////////////////////////////////////// Globals/** * Buffer size for writing to the output file or reading from the input file. */#define BUFFERED_IO_SIZE (1024 * 32)/** * When this upper limit is reached, the original request is suspended. */#define REQUEST_SUSPEND_AT (1024 * 1024 * 4)/** * When this lower limit is reached, the original request is resumed. */#define REQUEST_RESUME_AT (1024 * 1024 * 2)//////////////////////////////////////////////////////////////////////////////////// NotifyTargetChangeRunnable/** * Runnable object used to notify the control thread that file contents will now * be saved to the specified file. */classNotifyTargetChangeRunnablefinal:publicRunnable{public:NotifyTargetChangeRunnable(BackgroundFileSaver*aSaver,nsIFile*aTarget):mSaver(aSaver),mTarget(aTarget){}NS_IMETHODRun()override{returnmSaver->NotifyTargetChange(mTarget);}private:RefPtr<BackgroundFileSaver>mSaver;nsCOMPtr<nsIFile>mTarget;};//////////////////////////////////////////////////////////////////////////////////// BackgroundFileSaveruint32_tBackgroundFileSaver::sThreadCount=0;uint32_tBackgroundFileSaver::sTelemetryMaxThreadCount=0;BackgroundFileSaver::BackgroundFileSaver():mControlThread(nullptr),mWorkerThread(nullptr),mPipeOutputStream(nullptr),mPipeInputStream(nullptr),mObserver(nullptr),mLock("BackgroundFileSaver.mLock"),mWorkerThreadAttentionRequested(false),mFinishRequested(false),mComplete(false),mStatus(NS_OK),mAppend(false),mInitialTarget(nullptr),mInitialTargetKeepPartial(false),mRenamedTarget(nullptr),mRenamedTargetKeepPartial(false),mAsyncCopyContext(nullptr),mSha256Enabled(false),mSignatureInfoEnabled(false),mActualTarget(nullptr),mActualTargetKeepPartial(false),mDigestContext(nullptr){LOG(("Created BackgroundFileSaver [this = %p]",this));}BackgroundFileSaver::~BackgroundFileSaver(){LOG(("Destroying BackgroundFileSaver [this = %p]",this));nsNSSShutDownPreventionLocklock;if(isAlreadyShutDown()){return;}destructorSafeDestroyNSSReference();shutdown(ShutdownCalledFrom::Object);}voidBackgroundFileSaver::destructorSafeDestroyNSSReference(){mDigestContext=nullptr;}voidBackgroundFileSaver::virtualDestroyNSSReference(){destructorSafeDestroyNSSReference();}// Called on the control thread.nsresultBackgroundFileSaver::Init(){MOZ_ASSERT(NS_IsMainThread(),"This should be called on the main thread");nsresultrv;rv=NS_NewPipe2(getter_AddRefs(mPipeInputStream),getter_AddRefs(mPipeOutputStream),true,true,0,HasInfiniteBuffer()?UINT32_MAX:0);NS_ENSURE_SUCCESS(rv,rv);rv=NS_GetCurrentThread(getter_AddRefs(mControlThread));NS_ENSURE_SUCCESS(rv,rv);rv=NS_NewNamedThread("BgFileSaver",getter_AddRefs(mWorkerThread));NS_ENSURE_SUCCESS(rv,rv);sThreadCount++;if(sThreadCount>sTelemetryMaxThreadCount){sTelemetryMaxThreadCount=sThreadCount;}returnNS_OK;}// Called on the control thread.NS_IMETHODIMPBackgroundFileSaver::GetObserver(nsIBackgroundFileSaverObserver**aObserver){NS_ENSURE_ARG_POINTER(aObserver);*aObserver=mObserver;NS_IF_ADDREF(*aObserver);returnNS_OK;}// Called on the control thread.NS_IMETHODIMPBackgroundFileSaver::SetObserver(nsIBackgroundFileSaverObserver*aObserver){mObserver=aObserver;returnNS_OK;}// Called on the control thread.NS_IMETHODIMPBackgroundFileSaver::EnableAppend(){MOZ_ASSERT(NS_IsMainThread(),"This should be called on the main thread");MutexAutoLocklock(mLock);mAppend=true;returnNS_OK;}// Called on the control thread.NS_IMETHODIMPBackgroundFileSaver::SetTarget(nsIFile*aTarget,boolaKeepPartial){NS_ENSURE_ARG(aTarget);{MutexAutoLocklock(mLock);if(!mInitialTarget){aTarget->Clone(getter_AddRefs(mInitialTarget));mInitialTargetKeepPartial=aKeepPartial;}else{aTarget->Clone(getter_AddRefs(mRenamedTarget));mRenamedTargetKeepPartial=aKeepPartial;}}// After the worker thread wakes up because attention is requested, it will// rename or create the target file as requested, and start copying data.returnGetWorkerThreadAttention(true);}// Called on the control thread.NS_IMETHODIMPBackgroundFileSaver::Finish(nsresultaStatus){nsresultrv;// This will cause the NS_AsyncCopy operation, if it's in progress, to consume// all the data that is still in the pipe, and then finish.rv=mPipeOutputStream->Close();NS_ENSURE_SUCCESS(rv,rv);// Ensure that, when we get attention from the worker thread, if no pending// rename operation is waiting, the operation will complete.{MutexAutoLocklock(mLock);mFinishRequested=true;if(NS_SUCCEEDED(mStatus)){mStatus=aStatus;}}// After the worker thread wakes up because attention is requested, it will// process the completion conditions, detect that completion is requested, and// notify the main thread of the completion. If this function was called with// a success code, we wait for the copy to finish before processing the// completion conditions, otherwise we interrupt the copy immediately.returnGetWorkerThreadAttention(NS_FAILED(aStatus));}NS_IMETHODIMPBackgroundFileSaver::EnableSha256(){MOZ_ASSERT(NS_IsMainThread(),"Can't enable sha256 or initialize NSS off the main thread");// Ensure Personal Security Manager is initialized. This is required for// PK11_* operations to work.nsresultrv;nsCOMPtr<nsISupports>nssDummy=do_GetService("@mozilla.org/psm;1",&rv);NS_ENSURE_SUCCESS(rv,rv);mSha256Enabled=true;returnNS_OK;}NS_IMETHODIMPBackgroundFileSaver::GetSha256Hash(nsACString&aHash){MOZ_ASSERT(NS_IsMainThread(),"Can't inspect sha256 off the main thread");// We acquire a lock because mSha256 is written on the worker thread.MutexAutoLocklock(mLock);if(mSha256.IsEmpty()){returnNS_ERROR_NOT_AVAILABLE;}aHash=mSha256;returnNS_OK;}NS_IMETHODIMPBackgroundFileSaver::EnableSignatureInfo(){MOZ_ASSERT(NS_IsMainThread(),"Can't enable signature extraction off the main thread");// Ensure Personal Security Manager is initialized.nsresultrv;nsCOMPtr<nsISupports>nssDummy=do_GetService("@mozilla.org/psm;1",&rv);NS_ENSURE_SUCCESS(rv,rv);mSignatureInfoEnabled=true;returnNS_OK;}NS_IMETHODIMPBackgroundFileSaver::GetSignatureInfo(nsIArray**aSignatureInfo){MOZ_ASSERT(NS_IsMainThread(),"Can't inspect signature off the main thread");// We acquire a lock because mSignatureInfo is written on the worker thread.MutexAutoLocklock(mLock);if(!mComplete||!mSignatureInfoEnabled){returnNS_ERROR_NOT_AVAILABLE;}nsCOMPtr<nsIMutableArray>sigArray=do_CreateInstance(NS_ARRAY_CONTRACTID);for(inti=0;i<mSignatureInfo.Count();++i){sigArray->AppendElement(mSignatureInfo[i],false);}*aSignatureInfo=sigArray;NS_IF_ADDREF(*aSignatureInfo);returnNS_OK;}// Called on the control thread.nsresultBackgroundFileSaver::GetWorkerThreadAttention(boolaShouldInterruptCopy){nsresultrv;MutexAutoLocklock(mLock);// We only require attention one time. If this function is called two times// before the worker thread wakes up, and the first has aShouldInterruptCopy// false and the second true, we won't forcibly interrupt the copy from the// control thread. However, that never happens, because calling Finish with a// success code is the only case that may result in aShouldInterruptCopy being// false. In that case, we won't call this function again, because consumers// should not invoke other methods on the control thread after calling Finish.// And in any case, Finish already closes one end of the pipe, causing the// copy to finish properly on its own.if(mWorkerThreadAttentionRequested){returnNS_OK;}if(!mAsyncCopyContext){// Copy is not in progress, post an event to handle the change manually.rv=mWorkerThread->Dispatch(NewRunnableMethod(this,&BackgroundFileSaver::ProcessAttention),NS_DISPATCH_NORMAL);NS_ENSURE_SUCCESS(rv,rv);}elseif(aShouldInterruptCopy){// Interrupt the copy. The copy will be resumed, if needed, by the// ProcessAttention function, invoked by the AsyncCopyCallback function.NS_CancelAsyncCopy(mAsyncCopyContext,NS_ERROR_ABORT);}// Indicate that attention has been requested successfully, there is no need// to post another event until the worker thread processes the current one.mWorkerThreadAttentionRequested=true;returnNS_OK;}// Called on the worker thread.// staticvoidBackgroundFileSaver::AsyncCopyCallback(void*aClosure,nsresultaStatus){BackgroundFileSaver*self=(BackgroundFileSaver*)aClosure;{MutexAutoLocklock(self->mLock);// Now that the copy was interrupted or terminated, any notification from// the control thread requires an event to be posted to the worker thread.self->mAsyncCopyContext=nullptr;// When detecting failures, ignore the status code we use to interrupt.if(NS_FAILED(aStatus)&&aStatus!=NS_ERROR_ABORT&&NS_SUCCEEDED(self->mStatus)){self->mStatus=aStatus;}}(void)self->ProcessAttention();// We called NS_ADDREF_THIS when NS_AsyncCopy started, to keep the object// alive even if other references disappeared. At this point, we've finished// using the object and can safely release our reference.NS_RELEASE(self);}// Called on the worker thread.nsresultBackgroundFileSaver::ProcessAttention(){nsresultrv;// This function is called whenever the attention of the worker thread has// been requested. This may happen in these cases:// * We are about to start the copy for the first time. In this case, we are// called from an event posted on the worker thread from the control thread// by GetWorkerThreadAttention, and mAsyncCopyContext is null.// * We have interrupted the copy for some reason. In this case, we are// called by AsyncCopyCallback, and mAsyncCopyContext is null.// * We are currently executing ProcessStateChange, and attention is requested// by the control thread, for example because SetTarget or Finish have been// called. In this case, we are called from from an event posted through// GetWorkerThreadAttention. While mAsyncCopyContext was always null when// the event was posted, at this point mAsyncCopyContext may not be null// anymore, because ProcessStateChange may have started the copy before the// event that called this function was processed on the worker thread.// If mAsyncCopyContext is not null, we interrupt the copy and re-enter// through AsyncCopyCallback. This allows us to check if, for instance, we// should rename the target file. We will then restart the copy if needed.if(mAsyncCopyContext){NS_CancelAsyncCopy(mAsyncCopyContext,NS_ERROR_ABORT);returnNS_OK;}// Use the current shared state to determine the next operation to execute.rv=ProcessStateChange();if(NS_FAILED(rv)){// If something failed while processing, terminate the operation now.{MutexAutoLocklock(mLock);if(NS_SUCCEEDED(mStatus)){mStatus=rv;}}// Ensure we notify completion now that the operation failed.CheckCompletion();}returnNS_OK;}// Called on the worker thread.nsresultBackgroundFileSaver::ProcessStateChange(){nsresultrv;// We might have been notified because the operation is complete, verify.if(CheckCompletion()){returnNS_OK;}// Get a copy of the current shared state for the worker thread.nsCOMPtr<nsIFile>initialTarget;boolinitialTargetKeepPartial;nsCOMPtr<nsIFile>renamedTarget;boolrenamedTargetKeepPartial;boolsha256Enabled;boolappend;{MutexAutoLocklock(mLock);initialTarget=mInitialTarget;initialTargetKeepPartial=mInitialTargetKeepPartial;renamedTarget=mRenamedTarget;renamedTargetKeepPartial=mRenamedTargetKeepPartial;sha256Enabled=mSha256Enabled;append=mAppend;// From now on, another attention event needs to be posted if state changes.mWorkerThreadAttentionRequested=false;}// The initial target can only be null if it has never been assigned. In this// case, there is nothing to do since we never created any output file.if(!initialTarget){returnNS_OK;}// Determine if we are processing the attention request for the first time.boolisContinuation=!!mActualTarget;if(!isContinuation){// Assign the target file for the first time.mActualTarget=initialTarget;mActualTargetKeepPartial=initialTargetKeepPartial;}// Verify whether we have actually been instructed to use a different file.// This may happen the first time this function is executed, if SetTarget was// called two times before the worker thread processed the attention request.boolequalToCurrent=false;if(renamedTarget){rv=mActualTarget->Equals(renamedTarget,&equalToCurrent);NS_ENSURE_SUCCESS(rv,rv);if(!equalToCurrent){// If we were asked to rename the file but the initial file did not exist,// we simply create the file in the renamed location. We avoid this check// if we have already started writing the output file ourselves.boolexists=true;if(!isContinuation){rv=mActualTarget->Exists(&exists);NS_ENSURE_SUCCESS(rv,rv);}if(exists){// We are moving the previous target file to a different location.nsCOMPtr<nsIFile>renamedTargetParentDir;rv=renamedTarget->GetParent(getter_AddRefs(renamedTargetParentDir));NS_ENSURE_SUCCESS(rv,rv);nsAutoStringrenamedTargetName;rv=renamedTarget->GetLeafName(renamedTargetName);NS_ENSURE_SUCCESS(rv,rv);// We must delete any existing target file before moving the current// one.rv=renamedTarget->Exists(&exists);NS_ENSURE_SUCCESS(rv,rv);if(exists){rv=renamedTarget->Remove(false);NS_ENSURE_SUCCESS(rv,rv);}// Move the file. If this fails, we still reference the original file// in mActualTarget, so that it is deleted if requested. If this// succeeds, the nsIFile instance referenced by mActualTarget mutates// and starts pointing to the new file, but we'll discard the reference.rv=mActualTarget->MoveTo(renamedTargetParentDir,renamedTargetName);NS_ENSURE_SUCCESS(rv,rv);}// Now we can update the actual target file name.mActualTarget=renamedTarget;mActualTargetKeepPartial=renamedTargetKeepPartial;}}// Notify if the target file name actually changed.if(!equalToCurrent){// We must clone the nsIFile instance because mActualTarget is not// immutable, it may change if the target is renamed later.nsCOMPtr<nsIFile>actualTargetToNotify;rv=mActualTarget->Clone(getter_AddRefs(actualTargetToNotify));NS_ENSURE_SUCCESS(rv,rv);RefPtr<NotifyTargetChangeRunnable>event=newNotifyTargetChangeRunnable(this,actualTargetToNotify);NS_ENSURE_TRUE(event,NS_ERROR_FAILURE);rv=mControlThread->Dispatch(event,NS_DISPATCH_NORMAL);NS_ENSURE_SUCCESS(rv,rv);}if(isContinuation){// The pending rename operation might be the last task before finishing. We// may return here only if we have already created the target file.if(CheckCompletion()){returnNS_OK;}// Even if the operation did not complete, the pipe input stream may be// empty and may have been closed already. We detect this case using the// Available property, because it never returns an error if there is more// data to be consumed. If the pipe input stream is closed, we just exit// and wait for more calls like SetTarget or Finish to be invoked on the// control thread. However, we still truncate the file or create the// initial digest context if we are expected to do that.uint64_tavailable;rv=mPipeInputStream->Available(&available);if(NS_FAILED(rv)){returnNS_OK;}}// Create the digest context if requested and NSS hasn't been shut down.if(sha256Enabled&&!mDigestContext){nsNSSShutDownPreventionLocklock;if(!isAlreadyShutDown()){mDigestContext=UniquePK11Context(PK11_CreateDigestContext(SEC_OID_SHA256));NS_ENSURE_TRUE(mDigestContext,NS_ERROR_OUT_OF_MEMORY);}}// When we are requested to append to an existing file, we should read the// existing data and ensure we include it as part of the final hash.if(mDigestContext&&append&&!isContinuation){nsCOMPtr<nsIInputStream>inputStream;rv=NS_NewLocalFileInputStream(getter_AddRefs(inputStream),mActualTarget,PR_RDONLY|nsIFile::OS_READAHEAD);if(rv!=NS_ERROR_FILE_NOT_FOUND){NS_ENSURE_SUCCESS(rv,rv);charbuffer[BUFFERED_IO_SIZE];while(true){uint32_tcount;rv=inputStream->Read(buffer,BUFFERED_IO_SIZE,&count);NS_ENSURE_SUCCESS(rv,rv);if(count==0){// We reached the end of the file.break;}nsNSSShutDownPreventionLocklock;if(isAlreadyShutDown()){returnNS_ERROR_NOT_AVAILABLE;}nsresultrv=MapSECStatus(PK11_DigestOp(mDigestContext.get(),BitwiseCast<unsignedchar*,char*>(buffer),count));NS_ENSURE_SUCCESS(rv,rv);}rv=inputStream->Close();NS_ENSURE_SUCCESS(rv,rv);}}// We will append to the initial target file only if it was requested by the// caller, but we'll always append on subsequent accesses to the target file.int32_tcreationIoFlags;if(isContinuation){creationIoFlags=PR_APPEND;}else{creationIoFlags=(append?PR_APPEND:PR_TRUNCATE)|PR_CREATE_FILE;}// Create the target file, or append to it if we already started writing it.// The 0600 permissions are used while the file is being downloaded, and for// interrupted downloads. Those may be located in the system temporary// directory, as well as the target directory, and generally have a ".part"// extension. Those part files should never be group or world-writable even// if the umask allows it.nsCOMPtr<nsIOutputStream>outputStream;rv=NS_NewLocalFileOutputStream(getter_AddRefs(outputStream),mActualTarget,PR_WRONLY|creationIoFlags,0600);NS_ENSURE_SUCCESS(rv,rv);outputStream=NS_BufferOutputStream(outputStream,BUFFERED_IO_SIZE);if(!outputStream){returnNS_ERROR_FAILURE;}// Wrap the output stream so that it feeds the digest context if needed.if(mDigestContext){// No need to acquire the NSS lock here, DigestOutputStream must acquire it// in any case before each asynchronous write. Constructing the// DigestOutputStream cannot fail. Passing mDigestContext to// DigestOutputStream is safe, because BackgroundFileSaver always outlives// the outputStream. BackgroundFileSaver is reference-counted before the// call to AsyncCopy, and mDigestContext is never destroyed before// AsyncCopyCallback.outputStream=newDigestOutputStream(outputStream,mDigestContext.get());}// Start copying our input to the target file. No errors can be raised past// this point if the copy starts, since they should be handled by the thread.{MutexAutoLocklock(mLock);rv=NS_AsyncCopy(mPipeInputStream,outputStream,mWorkerThread,NS_ASYNCCOPY_VIA_READSEGMENTS,4096,AsyncCopyCallback,this,false,true,getter_AddRefs(mAsyncCopyContext),GetProgressCallback());if(NS_FAILED(rv)){NS_WARNING("NS_AsyncCopy failed.");mAsyncCopyContext=nullptr;returnrv;}}// If the operation succeeded, we must ensure that we keep this object alive// for the entire duration of the copy, since only the raw pointer will be// provided as the argument of the AsyncCopyCallback function. We can add the// reference now, after NS_AsyncCopy returned, because it always starts// processing asynchronously, and there is no risk that the callback is// invoked before we reach this point. If the operation failed instead, then// AsyncCopyCallback will never be called.NS_ADDREF_THIS();returnNS_OK;}// Called on the worker thread.boolBackgroundFileSaver::CheckCompletion(){nsresultrv;MOZ_ASSERT(!mAsyncCopyContext,"Should not be copying when checking completion conditions.");boolfailed=true;{MutexAutoLocklock(mLock);if(mComplete){returntrue;}// If an error occurred, we don't need to do the checks in this code block,// and the operation can be completed immediately with a failure code.if(NS_SUCCEEDED(mStatus)){failed=false;// We did not incur in an error, so we must determine if we can stop now.// If the Finish method has not been called, we can just continue now.if(!mFinishRequested){returnfalse;}// We can only stop when all the operations requested by the control// thread have been processed. First, we check whether we have processed// the first SetTarget call, if any. Then, we check whether we have// processed any rename requested by subsequent SetTarget calls.if((mInitialTarget&&!mActualTarget)||(mRenamedTarget&&mRenamedTarget!=mActualTarget)){returnfalse;}// If we still have data to write to the output file, allow the copy// operation to resume. The Available getter may return an error if one// of the pipe's streams has been already closed.uint64_tavailable;rv=mPipeInputStream->Available(&available);if(NS_SUCCEEDED(rv)&&available!=0){returnfalse;}}mComplete=true;}// Ensure we notify completion now that the operation finished.// Do a best-effort attempt to remove the file if required.if(failed&&mActualTarget&&!mActualTargetKeepPartial){(void)mActualTarget->Remove(false);}// Finish computing the hashif(!failed&&mDigestContext){nsNSSShutDownPreventionLocklock;if(!isAlreadyShutDown()){Digestd;rv=d.End(SEC_OID_SHA256,mDigestContext);if(NS_SUCCEEDED(rv)){MutexAutoLocklock(mLock);mSha256=nsDependentCSubstring(BitwiseCast<char*,unsignedchar*>(d.get().data),d.get().len);}}}// Compute the signature of the binary. ExtractSignatureInfo doesn't do// anything on non-Windows platforms except return an empty nsIArray.if(!failed&&mActualTarget){nsStringfilePath;mActualTarget->GetTarget(filePath);nsresultrv=ExtractSignatureInfo(filePath);if(NS_FAILED(rv)){LOG(("Unable to extract signature information [this = %p].",this));}else{LOG(("Signature extraction success! [this = %p]",this));}}// Post an event to notify that the operation completed.if(NS_FAILED(mControlThread->Dispatch(NewRunnableMethod(this,&BackgroundFileSaver::NotifySaveComplete),NS_DISPATCH_NORMAL))){NS_WARNING("Unable to post completion event to the control thread.");}returntrue;}// Called on the control thread.nsresultBackgroundFileSaver::NotifyTargetChange(nsIFile*aTarget){if(mObserver){(void)mObserver->OnTargetChange(this,aTarget);}returnNS_OK;}// Called on the control thread.nsresultBackgroundFileSaver::NotifySaveComplete(){MOZ_ASSERT(NS_IsMainThread(),"This should be called on the main thread");nsresultstatus;{MutexAutoLocklock(mLock);status=mStatus;}if(mObserver){(void)mObserver->OnSaveComplete(this,status);}// At this point, the worker thread will not process any more events, and we// can shut it down. Shutting down a thread may re-enter the event loop on// this thread. This is not a problem in this case, since this function is// called by a top-level event itself, and we have already invoked the// completion observer callback. Re-entering the loop can only delay the// final release and destruction of this saver object, since we are keeping a// reference to it through the event object.mWorkerThread->Shutdown();sThreadCount--;// When there are no more active downloads, we consider the download session// finished. We record the maximum number of concurrent downloads reached// during the session in a telemetry histogram, and we reset the maximum// thread counter for the next download sessionif(sThreadCount==0){Telemetry::Accumulate(Telemetry::BACKGROUNDFILESAVER_THREAD_COUNT,sTelemetryMaxThreadCount);sTelemetryMaxThreadCount=0;}returnNS_OK;}nsresultBackgroundFileSaver::ExtractSignatureInfo(constnsAString&filePath){MOZ_ASSERT(!NS_IsMainThread(),"Cannot extract signature on main thread");nsNSSShutDownPreventionLocknssLock;if(isAlreadyShutDown()){returnNS_ERROR_NOT_AVAILABLE;}{MutexAutoLocklock(mLock);if(!mSignatureInfoEnabled){returnNS_OK;}}nsresultrv;nsCOMPtr<nsIX509CertDB>certDB=do_GetService(NS_X509CERTDB_CONTRACTID,&rv);NS_ENSURE_SUCCESS(rv,rv);#ifdef XP_WIN// Setup the file to check.WINTRUST_FILE_INFOfileToCheck={0};fileToCheck.cbStruct=sizeof(WINTRUST_FILE_INFO);fileToCheck.pcwszFilePath=filePath.Data();fileToCheck.hFile=nullptr;fileToCheck.pgKnownSubject=nullptr;// We want to check it is signed and trusted.WINTRUST_DATAtrustData={0};trustData.cbStruct=sizeof(trustData);trustData.pPolicyCallbackData=nullptr;trustData.pSIPClientData=nullptr;trustData.dwUIChoice=WTD_UI_NONE;trustData.fdwRevocationChecks=WTD_REVOKE_NONE;trustData.dwUnionChoice=WTD_CHOICE_FILE;trustData.dwStateAction=WTD_STATEACTION_VERIFY;trustData.hWVTStateData=nullptr;trustData.pwszURLReference=nullptr;// Disallow revocation checks over the networktrustData.dwProvFlags=WTD_CACHE_ONLY_URL_RETRIEVAL;// no UItrustData.dwUIContext=0;trustData.pFile=&fileToCheck;// The WINTRUST_ACTION_GENERIC_VERIFY_V2 policy verifies that the certificate// chains up to a trusted root CA and has appropriate permissions to sign// code.GUIDpolicyGUID=WINTRUST_ACTION_GENERIC_VERIFY_V2;// Check if the file is signed by something that is trusted. If the file is// not signed, this is a no-op.LONGret=WinVerifyTrust(nullptr,&policyGUID,&trustData);CRYPT_PROVIDER_DATA*cryptoProviderData=nullptr;// According to the Windows documentation, we should check against 0 instead// of ERROR_SUCCESS, which is an HRESULT.if(ret==0){cryptoProviderData=WTHelperProvDataFromStateData(trustData.hWVTStateData);}if(cryptoProviderData){// Lock because signature information is read on the main thread.MutexAutoLocklock(mLock);LOG(("Downloaded trusted and signed file [this = %p].",this));// A binary may have multiple signers. Each signer may have multiple certs// in the chain.for(DWORDi=0;i<cryptoProviderData->csSigners;++i){constCERT_CHAIN_CONTEXT*certChainContext=cryptoProviderData->pasSigners[i].pChainContext;if(!certChainContext){break;}for(DWORDj=0;j<certChainContext->cChain;++j){constCERT_SIMPLE_CHAIN*certSimpleChain=certChainContext->rgpChain[j];if(!certSimpleChain){break;}nsCOMPtr<nsIX509CertList>nssCertList=do_CreateInstance(NS_X509CERTLIST_CONTRACTID);if(!nssCertList){break;}boolextractionSuccess=true;for(DWORDk=0;k<certSimpleChain->cElement;++k){CERT_CHAIN_ELEMENT*certChainElement=certSimpleChain->rgpElement[k];if(certChainElement->pCertContext->dwCertEncodingType!=X509_ASN_ENCODING){continue;}nsCOMPtr<nsIX509Cert>nssCert=nullptr;rv=certDB->ConstructX509(reinterpret_cast<char*>(certChainElement->pCertContext->pbCertEncoded),certChainElement->pCertContext->cbCertEncoded,getter_AddRefs(nssCert));if(!nssCert){extractionSuccess=false;LOG(("Couldn't create NSS cert [this = %p]",this));break;}nssCertList->AddCert(nssCert);nsStringsubjectName;nssCert->GetSubjectName(subjectName);LOG(("Adding cert %s [this = %p]",NS_ConvertUTF16toUTF8(subjectName).get(),this));}if(extractionSuccess){mSignatureInfo.AppendObject(nssCertList);}}}// Free the provider data if cryptoProviderData is not null.trustData.dwStateAction=WTD_STATEACTION_CLOSE;WinVerifyTrust(nullptr,&policyGUID,&trustData);}else{LOG(("Downloaded unsigned or untrusted file [this = %p].",this));}#endifreturnNS_OK;}//////////////////////////////////////////////////////////////////////////////////// BackgroundFileSaverOutputStreamNS_IMPL_ISUPPORTS(BackgroundFileSaverOutputStream,nsIBackgroundFileSaver,nsIOutputStream,nsIAsyncOutputStream,nsIOutputStreamCallback)BackgroundFileSaverOutputStream::BackgroundFileSaverOutputStream():BackgroundFileSaver(),mAsyncWaitCallback(nullptr){}BackgroundFileSaverOutputStream::~BackgroundFileSaverOutputStream(){}boolBackgroundFileSaverOutputStream::HasInfiniteBuffer(){returnfalse;}nsAsyncCopyProgressFunBackgroundFileSaverOutputStream::GetProgressCallback(){returnnullptr;}NS_IMETHODIMPBackgroundFileSaverOutputStream::Close(){returnmPipeOutputStream->Close();}NS_IMETHODIMPBackgroundFileSaverOutputStream::Flush(){returnmPipeOutputStream->Flush();}NS_IMETHODIMPBackgroundFileSaverOutputStream::Write(constchar*aBuf,uint32_taCount,uint32_t*_retval){returnmPipeOutputStream->Write(aBuf,aCount,_retval);}NS_IMETHODIMPBackgroundFileSaverOutputStream::WriteFrom(nsIInputStream*aFromStream,uint32_taCount,uint32_t*_retval){returnmPipeOutputStream->WriteFrom(aFromStream,aCount,_retval);}NS_IMETHODIMPBackgroundFileSaverOutputStream::WriteSegments(nsReadSegmentFunaReader,void*aClosure,uint32_taCount,uint32_t*_retval){returnmPipeOutputStream->WriteSegments(aReader,aClosure,aCount,_retval);}NS_IMETHODIMPBackgroundFileSaverOutputStream::IsNonBlocking(bool*_retval){returnmPipeOutputStream->IsNonBlocking(_retval);}NS_IMETHODIMPBackgroundFileSaverOutputStream::CloseWithStatus(nsresultreason){returnmPipeOutputStream->CloseWithStatus(reason);}NS_IMETHODIMPBackgroundFileSaverOutputStream::AsyncWait(nsIOutputStreamCallback*aCallback,uint32_taFlags,uint32_taRequestedCount,nsIEventTarget*aEventTarget){NS_ENSURE_STATE(!mAsyncWaitCallback);mAsyncWaitCallback=aCallback;returnmPipeOutputStream->AsyncWait(this,aFlags,aRequestedCount,aEventTarget);}NS_IMETHODIMPBackgroundFileSaverOutputStream::OnOutputStreamReady(nsIAsyncOutputStream*aStream){NS_ENSURE_STATE(mAsyncWaitCallback);nsCOMPtr<nsIOutputStreamCallback>asyncWaitCallback=nullptr;asyncWaitCallback.swap(mAsyncWaitCallback);returnasyncWaitCallback->OnOutputStreamReady(this);}//////////////////////////////////////////////////////////////////////////////////// BackgroundFileSaverStreamListenerNS_IMPL_ISUPPORTS(BackgroundFileSaverStreamListener,nsIBackgroundFileSaver,nsIRequestObserver,nsIStreamListener)BackgroundFileSaverStreamListener::BackgroundFileSaverStreamListener():BackgroundFileSaver(),mSuspensionLock("BackgroundFileSaverStreamListener.mSuspensionLock"),mReceivedTooMuchData(false),mRequest(nullptr),mRequestSuspended(false){}BackgroundFileSaverStreamListener::~BackgroundFileSaverStreamListener(){}boolBackgroundFileSaverStreamListener::HasInfiniteBuffer(){returntrue;}nsAsyncCopyProgressFunBackgroundFileSaverStreamListener::GetProgressCallback(){returnAsyncCopyProgressCallback;}NS_IMETHODIMPBackgroundFileSaverStreamListener::OnStartRequest(nsIRequest*aRequest,nsISupports*aContext){NS_ENSURE_ARG(aRequest);returnNS_OK;}NS_IMETHODIMPBackgroundFileSaverStreamListener::OnStopRequest(nsIRequest*aRequest,nsISupports*aContext,nsresultaStatusCode){// If an error occurred, cancel the operation immediately. On success, wait// until the caller has determined whether the file should be renamed.if(NS_FAILED(aStatusCode)){Finish(aStatusCode);}returnNS_OK;}NS_IMETHODIMPBackgroundFileSaverStreamListener::OnDataAvailable(nsIRequest*aRequest,nsISupports*aContext,nsIInputStream*aInputStream,uint64_taOffset,uint32_taCount){nsresultrv;NS_ENSURE_ARG(aRequest);// Read the requested data. Since the pipe has an infinite buffer, we don't// expect any write error to occur here.uint32_twriteCount;rv=mPipeOutputStream->WriteFrom(aInputStream,aCount,&writeCount);NS_ENSURE_SUCCESS(rv,rv);// If reading from the input stream fails for any reason, the pipe will return// a success code, but without reading all the data. Since we should be able// to read the requested data when OnDataAvailable is called, raise an error.if(writeCount<aCount){NS_WARNING("Reading from the input stream should not have failed.");returnNS_ERROR_UNEXPECTED;}boolstateChanged=false;{MutexAutoLocklock(mSuspensionLock);if(!mReceivedTooMuchData){uint64_tavailable;nsresultrv=mPipeInputStream->Available(&available);if(NS_SUCCEEDED(rv)&&available>REQUEST_SUSPEND_AT){mReceivedTooMuchData=true;mRequest=aRequest;stateChanged=true;}}}if(stateChanged){NotifySuspendOrResume();}returnNS_OK;}// Called on the worker thread.// staticvoidBackgroundFileSaverStreamListener::AsyncCopyProgressCallback(void*aClosure,uint32_taCount){BackgroundFileSaverStreamListener*self=(BackgroundFileSaverStreamListener*)aClosure;// Wait if the control thread is in the process of suspending or resuming.MutexAutoLocklock(self->mSuspensionLock);// This function is called when some bytes are consumed by NS_AsyncCopy. Each// time this happens, verify if a suspended request should be resumed, because// we have now consumed enough data.if(self->mReceivedTooMuchData){uint64_tavailable;nsresultrv=self->mPipeInputStream->Available(&available);if(NS_FAILED(rv)||available<REQUEST_RESUME_AT){self->mReceivedTooMuchData=false;// Post an event to verify if the request should be resumed.if(NS_FAILED(self->mControlThread->Dispatch(NewRunnableMethod(self,&BackgroundFileSaverStreamListener::NotifySuspendOrResume),NS_DISPATCH_NORMAL))){NS_WARNING("Unable to post resume event to the control thread.");}}}}// Called on the control thread.nsresultBackgroundFileSaverStreamListener::NotifySuspendOrResume(){// Prevent the worker thread from changing state while processing.MutexAutoLocklock(mSuspensionLock);if(mReceivedTooMuchData){if(!mRequestSuspended){// Try to suspend the request. If this fails, don't try to resume later.if(NS_SUCCEEDED(mRequest->Suspend())){mRequestSuspended=true;}else{NS_WARNING("Unable to suspend the request.");}}}else{if(mRequestSuspended){// Resume the request only if we succeeded in suspending it.if(NS_SUCCEEDED(mRequest->Resume())){mRequestSuspended=false;}else{NS_WARNING("Unable to resume the request.");}}}returnNS_OK;}//////////////////////////////////////////////////////////////////////////////////// DigestOutputStreamNS_IMPL_ISUPPORTS(DigestOutputStream,nsIOutputStream)DigestOutputStream::DigestOutputStream(nsIOutputStream*aStream,PK11Context*aContext):mOutputStream(aStream),mDigestContext(aContext){MOZ_ASSERT(mDigestContext,"Can't have null digest context");MOZ_ASSERT(mOutputStream,"Can't have null output stream");}DigestOutputStream::~DigestOutputStream(){nsNSSShutDownPreventionLocklocker;if(isAlreadyShutDown()){return;}shutdown(ShutdownCalledFrom::Object);}NS_IMETHODIMPDigestOutputStream::Close(){returnmOutputStream->Close();}NS_IMETHODIMPDigestOutputStream::Flush(){returnmOutputStream->Flush();}NS_IMETHODIMPDigestOutputStream::Write(constchar*aBuf,uint32_taCount,uint32_t*retval){nsNSSShutDownPreventionLocklock;if(isAlreadyShutDown()){returnNS_ERROR_NOT_AVAILABLE;}nsresultrv=MapSECStatus(PK11_DigestOp(mDigestContext,BitwiseCast<constunsignedchar*,constchar*>(aBuf),aCount));NS_ENSURE_SUCCESS(rv,rv);returnmOutputStream->Write(aBuf,aCount,retval);}NS_IMETHODIMPDigestOutputStream::WriteFrom(nsIInputStream*aFromStream,uint32_taCount,uint32_t*retval){// Not supported. We could read the stream to a buf, call DigestOp on the// result, seek back and pass the stream on, but it's not worth it since our// application (NS_AsyncCopy) doesn't invoke this on the sink.MOZ_CRASH("DigestOutputStream::WriteFrom not implemented");}NS_IMETHODIMPDigestOutputStream::WriteSegments(nsReadSegmentFunaReader,void*aClosure,uint32_taCount,uint32_t*retval){MOZ_CRASH("DigestOutputStream::WriteSegments not implemented");}NS_IMETHODIMPDigestOutputStream::IsNonBlocking(bool*retval){returnmOutputStream->IsNonBlocking(retval);}#undef LOG_ENABLED}// namespace net}// namespace mozilla