michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /** michael@0: * This file defines two implementations of the nsIBackgroundFileSaver michael@0: * interface. See the "test_backgroundfilesaver.js" file for usage examples. michael@0: */ michael@0: michael@0: #ifndef BackgroundFileSaver_h__ michael@0: #define BackgroundFileSaver_h__ michael@0: michael@0: #include "mozilla/Mutex.h" michael@0: #include "nsCOMArray.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsNSSShutDown.h" michael@0: #include "nsIAsyncOutputStream.h" michael@0: #include "nsIBackgroundFileSaver.h" michael@0: #include "nsIStreamListener.h" michael@0: #include "nsStreamUtils.h" michael@0: #include "ScopedNSSTypes.h" michael@0: michael@0: class nsIAsyncInputStream; michael@0: class nsIThread; michael@0: class nsIX509CertList; michael@0: class PRLogModuleInfo; michael@0: michael@0: namespace mozilla { michael@0: namespace net { michael@0: michael@0: class DigestOutputStream; michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// BackgroundFileSaver michael@0: michael@0: class BackgroundFileSaver : public nsIBackgroundFileSaver, michael@0: public nsNSSShutDownObject michael@0: { michael@0: public: michael@0: NS_DECL_NSIBACKGROUNDFILESAVER michael@0: michael@0: BackgroundFileSaver(); michael@0: michael@0: /** michael@0: * Initializes the pipe and the worker thread on XPCOM construction. michael@0: * michael@0: * This is called automatically by the XPCOM infrastructure, and if this michael@0: * fails, the factory will delete this object without returning a reference. michael@0: */ michael@0: nsresult Init(); michael@0: michael@0: /** michael@0: * Used by nsNSSShutDownList to manage nsNSSShutDownObjects. michael@0: */ michael@0: void virtualDestroyNSSReference(); michael@0: michael@0: /** michael@0: * Number of worker threads that are currently running. michael@0: */ michael@0: static uint32_t sThreadCount; michael@0: michael@0: /** michael@0: * Maximum number of worker threads reached during the current download session, michael@0: * used for telemetry. michael@0: * michael@0: * When there are no more worker threads running, we consider the download michael@0: * session finished, and this counter is reset. michael@0: */ michael@0: static uint32_t sTelemetryMaxThreadCount; michael@0: michael@0: michael@0: protected: michael@0: virtual ~BackgroundFileSaver(); michael@0: michael@0: static PRLogModuleInfo *prlog; michael@0: michael@0: /** michael@0: * Helper function for managing NSS objects (mDigestContext). michael@0: */ michael@0: void destructorSafeDestroyNSSReference(); michael@0: michael@0: /** michael@0: * Thread that constructed this object. michael@0: */ michael@0: nsCOMPtr mControlThread; michael@0: michael@0: /** michael@0: * Thread to which the actual input/output is delegated. michael@0: */ michael@0: nsCOMPtr mWorkerThread; michael@0: michael@0: /** michael@0: * Stream that receives data from derived classes. The received data will be michael@0: * available to the worker thread through mPipeInputStream. This is an michael@0: * instance of nsPipeOutputStream, not BackgroundFileSaverOutputStream. michael@0: */ michael@0: nsCOMPtr mPipeOutputStream; michael@0: michael@0: /** michael@0: * Used during initialization, determines if the pipe is created with an michael@0: * infinite buffer. An infinite buffer is required if the derived class michael@0: * implements nsIStreamListener, because this interface requires all the michael@0: * provided data to be consumed synchronously. michael@0: */ michael@0: virtual bool HasInfiniteBuffer() = 0; michael@0: michael@0: /** michael@0: * Used by derived classes if they need to be called back while copying. michael@0: */ michael@0: virtual nsAsyncCopyProgressFun GetProgressCallback() = 0; michael@0: michael@0: /** michael@0: * Stream used by the worker thread to read the data to be saved. michael@0: */ michael@0: nsCOMPtr mPipeInputStream; michael@0: michael@0: private: michael@0: friend class NotifyTargetChangeRunnable; michael@0: michael@0: /** michael@0: * Matches the nsIBackgroundFileSaver::observer property. michael@0: * michael@0: * @remarks This is a strong reference so that JavaScript callers don't need michael@0: * to worry about keeping another reference to the observer. michael@0: */ michael@0: nsCOMPtr mObserver; michael@0: michael@0: ////////////////////////////////////////////////////////////////////////////// michael@0: //// Shared state between control and worker threads michael@0: michael@0: /** michael@0: * Protects the shared state between control and worker threads. This mutex michael@0: * is always locked for a very short time, never during input/output. michael@0: */ michael@0: mozilla::Mutex mLock; michael@0: michael@0: /** michael@0: * True if the worker thread is already waiting to process a change in state. michael@0: */ michael@0: bool mWorkerThreadAttentionRequested; michael@0: michael@0: /** michael@0: * True if the operation should finish as soon as possibile. michael@0: */ michael@0: bool mFinishRequested; michael@0: michael@0: /** michael@0: * True if the operation completed, with either success or failure. michael@0: */ michael@0: bool mComplete; michael@0: michael@0: /** michael@0: * Holds the current file saver status. This is a success status while the michael@0: * object is working correctly, and remains such if the operation completes michael@0: * successfully. This becomes an error status when an error occurs on the michael@0: * worker thread, or when the operation is canceled. michael@0: */ michael@0: nsresult mStatus; michael@0: michael@0: /** michael@0: * True if we should append data to the initial target file, instead of michael@0: * overwriting it. michael@0: */ michael@0: bool mAppend; michael@0: michael@0: /** michael@0: * This is set by the first SetTarget call on the control thread, and contains michael@0: * the target file name that will be used by the worker thread, as soon as it michael@0: * is possible to update mActualTarget and open the file. This is null if no michael@0: * target was ever assigned to this object. michael@0: */ michael@0: nsCOMPtr mInitialTarget; michael@0: michael@0: /** michael@0: * This is set by the first SetTarget call on the control thread, and michael@0: * indicates whether mInitialTarget should be kept as partially completed, michael@0: * rather than deleted, if the operation fails or is canceled. michael@0: */ michael@0: bool mInitialTargetKeepPartial; michael@0: michael@0: /** michael@0: * This is set by subsequent SetTarget calls on the control thread, and michael@0: * contains the new target file name to which the worker thread will move the michael@0: * target file, as soon as it can be done. This is null if SetTarget was michael@0: * called only once, or no target was ever assigned to this object. michael@0: * michael@0: * The target file can be renamed multiple times, though only the most recent michael@0: * rename is guaranteed to be processed by the worker thread. michael@0: */ michael@0: nsCOMPtr mRenamedTarget; michael@0: michael@0: /** michael@0: * This is set by subsequent SetTarget calls on the control thread, and michael@0: * indicates whether mRenamedTarget should be kept as partially completed, michael@0: * rather than deleted, if the operation fails or is canceled. michael@0: */ michael@0: bool mRenamedTargetKeepPartial; michael@0: michael@0: /** michael@0: * While NS_AsyncCopy is in progress, allows canceling it. Null otherwise. michael@0: * This is read by both threads but only written by the worker thread. michael@0: */ michael@0: nsCOMPtr mAsyncCopyContext; michael@0: michael@0: /** michael@0: * The SHA 256 hash in raw bytes of the downloaded file. This is written michael@0: * by the worker thread but can be read on the main thread. michael@0: */ michael@0: nsAutoCString mSha256; michael@0: michael@0: /** michael@0: * Whether or not to compute the hash. Must be set on the main thread before michael@0: * setTarget is called. michael@0: */ michael@0: bool mSha256Enabled; michael@0: michael@0: /** michael@0: * Store the signature info. michael@0: */ michael@0: nsCOMArray mSignatureInfo; michael@0: michael@0: /** michael@0: * Whether or not to extract the signature. Must be set on the main thread michael@0: * before setTarget is called. michael@0: */ michael@0: bool mSignatureInfoEnabled; michael@0: michael@0: ////////////////////////////////////////////////////////////////////////////// michael@0: //// State handled exclusively by the worker thread michael@0: michael@0: /** michael@0: * Current target file associated to the input and output streams. michael@0: */ michael@0: nsCOMPtr mActualTarget; michael@0: michael@0: /** michael@0: * Indicates whether mActualTarget should be kept as partially completed, michael@0: * rather than deleted, if the operation fails or is canceled. michael@0: */ michael@0: bool mActualTargetKeepPartial; michael@0: michael@0: /** michael@0: * Used to calculate the file hash. This keeps state across file renames and michael@0: * is lazily initialized in ProcessStateChange. michael@0: */ michael@0: ScopedPK11Context mDigestContext; michael@0: michael@0: ////////////////////////////////////////////////////////////////////////////// michael@0: //// Private methods michael@0: michael@0: /** michael@0: * Called when NS_AsyncCopy completes. michael@0: * michael@0: * @param aClosure michael@0: * Populated with a raw pointer to the BackgroundFileSaver object. michael@0: * @param aStatus michael@0: * Success or failure status specified when the copy was interrupted. michael@0: */ michael@0: static void AsyncCopyCallback(void *aClosure, nsresult aStatus); michael@0: michael@0: /** michael@0: * Called on the control thread after state changes, to ensure that the worker michael@0: * thread will process the state change appropriately. michael@0: * michael@0: * @param aShouldInterruptCopy michael@0: * If true, the current NS_AsyncCopy, if any, is canceled. michael@0: */ michael@0: nsresult GetWorkerThreadAttention(bool aShouldInterruptCopy); michael@0: michael@0: /** michael@0: * Event called on the worker thread to begin processing a state change. michael@0: */ michael@0: nsresult ProcessAttention(); michael@0: michael@0: /** michael@0: * Called by ProcessAttention to execute the operations corresponding to the michael@0: * state change. If this results in an error, ProcessAttention will force the michael@0: * entire operation to be aborted. michael@0: */ michael@0: nsresult ProcessStateChange(); michael@0: michael@0: /** michael@0: * Returns true if completion conditions are met on the worker thread. The michael@0: * first time this happens, posts the completion event to the control thread. michael@0: */ michael@0: bool CheckCompletion(); michael@0: michael@0: /** michael@0: * Event called on the control thread to indicate that file contents will now michael@0: * be saved to the specified file. michael@0: */ michael@0: nsresult NotifyTargetChange(nsIFile *aTarget); michael@0: michael@0: /** michael@0: * Event called on the control thread to send the final notification. michael@0: */ michael@0: nsresult NotifySaveComplete(); michael@0: michael@0: /** michael@0: * Verifies the signature of the binary at the specified file path and stores michael@0: * the signature data in mSignatureInfo. We extract only X.509 certificates, michael@0: * since that is what Google's Safebrowsing protocol specifies. michael@0: */ michael@0: nsresult ExtractSignatureInfo(const nsAString& filePath); michael@0: }; michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// BackgroundFileSaverOutputStream michael@0: michael@0: class BackgroundFileSaverOutputStream : public BackgroundFileSaver michael@0: , public nsIAsyncOutputStream michael@0: , public nsIOutputStreamCallback michael@0: { michael@0: public: michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: NS_DECL_NSIOUTPUTSTREAM michael@0: NS_DECL_NSIASYNCOUTPUTSTREAM michael@0: NS_DECL_NSIOUTPUTSTREAMCALLBACK michael@0: michael@0: BackgroundFileSaverOutputStream(); michael@0: michael@0: protected: michael@0: virtual bool HasInfiniteBuffer() MOZ_OVERRIDE; michael@0: virtual nsAsyncCopyProgressFun GetProgressCallback() MOZ_OVERRIDE; michael@0: michael@0: private: michael@0: ~BackgroundFileSaverOutputStream(); michael@0: michael@0: /** michael@0: * Original callback provided to our AsyncWait wrapper. michael@0: */ michael@0: nsCOMPtr mAsyncWaitCallback; michael@0: }; michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// BackgroundFileSaverStreamListener. This class is instantiated by michael@0: // nsExternalHelperAppService, DownloadCore.jsm, and possibly others. michael@0: michael@0: class BackgroundFileSaverStreamListener : public BackgroundFileSaver michael@0: , public nsIStreamListener michael@0: { michael@0: public: michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: NS_DECL_NSIREQUESTOBSERVER michael@0: NS_DECL_NSISTREAMLISTENER michael@0: michael@0: BackgroundFileSaverStreamListener(); michael@0: michael@0: protected: michael@0: virtual bool HasInfiniteBuffer() MOZ_OVERRIDE; michael@0: virtual nsAsyncCopyProgressFun GetProgressCallback() MOZ_OVERRIDE; michael@0: michael@0: private: michael@0: ~BackgroundFileSaverStreamListener(); michael@0: michael@0: /** michael@0: * Protects the state related to whether the request should be suspended. michael@0: */ michael@0: mozilla::Mutex mSuspensionLock; michael@0: michael@0: /** michael@0: * Whether we should suspend the request because we received too much data. michael@0: */ michael@0: bool mReceivedTooMuchData; michael@0: michael@0: /** michael@0: * Request for which we received too much data. This is populated when michael@0: * mReceivedTooMuchData becomes true for the first time. michael@0: */ michael@0: nsCOMPtr mRequest; michael@0: michael@0: /** michael@0: * Whether mRequest is currently suspended. michael@0: */ michael@0: bool mRequestSuspended; michael@0: michael@0: /** michael@0: * Called while NS_AsyncCopy is copying data. michael@0: */ michael@0: static void AsyncCopyProgressCallback(void *aClosure, uint32_t aCount); michael@0: michael@0: /** michael@0: * Called on the control thread to suspend or resume the request. michael@0: */ michael@0: nsresult NotifySuspendOrResume(); michael@0: }; michael@0: michael@0: // A wrapper around nsIOutputStream, so that we can compute hashes on the michael@0: // stream without copying and without polluting pristine NSS code with XPCOM michael@0: // interfaces. michael@0: class DigestOutputStream : public nsNSSShutDownObject, michael@0: public nsIOutputStream michael@0: { michael@0: public: michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: NS_DECL_NSIOUTPUTSTREAM michael@0: // Constructor. Neither parameter may be null. The caller owns both. michael@0: DigestOutputStream(nsIOutputStream* outputStream, PK11Context* aContext); michael@0: ~DigestOutputStream(); michael@0: michael@0: // We don't own any NSS objects here, so no need to clean up michael@0: void virtualDestroyNSSReference() { } michael@0: michael@0: private: michael@0: // Calls to write are passed to this stream. michael@0: nsCOMPtr mOutputStream; michael@0: // Digest context used to compute the hash, owned by the caller. michael@0: PK11Context* mDigestContext; michael@0: michael@0: // Don't accidentally copy construct. michael@0: DigestOutputStream(const DigestOutputStream& d); michael@0: }; michael@0: } // namespace net michael@0: } // namespace mozilla michael@0: michael@0: #endif