diff options
Diffstat (limited to '')
-rw-r--r-- | netwerk/base/BackgroundFileSaver.h | 397 |
1 files changed, 397 insertions, 0 deletions
diff --git a/netwerk/base/BackgroundFileSaver.h b/netwerk/base/BackgroundFileSaver.h new file mode 100644 index 0000000000..214aa31d10 --- /dev/null +++ b/netwerk/base/BackgroundFileSaver.h @@ -0,0 +1,397 @@ +/* -*- 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/. */ + +/** + * This file defines two implementations of the nsIBackgroundFileSaver + * interface. See the "test_backgroundfilesaver.js" file for usage examples. + */ + +#ifndef BackgroundFileSaver_h__ +#define BackgroundFileSaver_h__ + +#include "ScopedNSSTypes.h" +#include "mozilla/Mutex.h" +#include "nsCOMArray.h" +#include "nsCOMPtr.h" +#include "nsIAsyncOutputStream.h" +#include "nsIBackgroundFileSaver.h" +#include "nsIStreamListener.h" +#include "nsStreamUtils.h" +#include "nsString.h" + +class nsIAsyncInputStream; +class nsISerialEventTarget; + +namespace mozilla { +namespace net { + +class DigestOutputStream; + +//////////////////////////////////////////////////////////////////////////////// +//// BackgroundFileSaver + +class BackgroundFileSaver : public nsIBackgroundFileSaver { + public: + NS_DECL_NSIBACKGROUNDFILESAVER + + BackgroundFileSaver(); + + /** + * Initializes the pipe and the worker thread on XPCOM construction. + * + * This is called automatically by the XPCOM infrastructure, and if this + * fails, the factory will delete this object without returning a reference. + */ + nsresult Init(); + + /** + * Number of worker threads that are currently running. + */ + static uint32_t sThreadCount; + + /** + * Maximum number of worker threads reached during the current download + * session, used for telemetry. + * + * When there are no more worker threads running, we consider the download + * session finished, and this counter is reset. + */ + static uint32_t sTelemetryMaxThreadCount; + + protected: + virtual ~BackgroundFileSaver(); + + /** + * Thread that constructed this object. + */ + nsCOMPtr<nsIEventTarget> mControlEventTarget; + + /** + * Thread to which the actual input/output is delegated. + */ + nsCOMPtr<nsISerialEventTarget> mBackgroundET; + + /** + * Stream that receives data from derived classes. The received data will be + * available to the worker thread through mPipeInputStream. This is an + * instance of nsPipeOutputStream, not BackgroundFileSaverOutputStream. + */ + nsCOMPtr<nsIAsyncOutputStream> mPipeOutputStream; + + /** + * Used during initialization, determines if the pipe is created with an + * infinite buffer. An infinite buffer is required if the derived class + * implements nsIStreamListener, because this interface requires all the + * provided data to be consumed synchronously. + */ + virtual bool HasInfiniteBuffer() = 0; + + /** + * Used by derived classes if they need to be called back while copying. + */ + virtual nsAsyncCopyProgressFun GetProgressCallback() = 0; + + /** + * Stream used by the worker thread to read the data to be saved. + */ + nsCOMPtr<nsIAsyncInputStream> mPipeInputStream; + + private: + friend class NotifyTargetChangeRunnable; + + /** + * Matches the nsIBackgroundFileSaver::observer property. + * + * @remarks This is a strong reference so that JavaScript callers don't need + * to worry about keeping another reference to the observer. + */ + nsCOMPtr<nsIBackgroundFileSaverObserver> mObserver; + + ////////////////////////////////////////////////////////////////////////////// + //// Shared state between control and worker threads + + /** + * Protects the shared state between control and worker threads. This mutex + * is always locked for a very short time, never during input/output. + */ + mozilla::Mutex mLock{"BackgroundFileSaver.mLock"}; + + /** + * True if the worker thread is already waiting to process a change in state. + */ + bool mWorkerThreadAttentionRequested MOZ_GUARDED_BY(mLock){false}; + + /** + * True if the operation should finish as soon as possibile. + */ + bool mFinishRequested MOZ_GUARDED_BY(mLock){false}; + + /** + * True if the operation completed, with either success or failure. + */ + bool mComplete MOZ_GUARDED_BY(mLock){false}; + + /** + * Holds the current file saver status. This is a success status while the + * object is working correctly, and remains such if the operation completes + * successfully. This becomes an error status when an error occurs on the + * worker thread, or when the operation is canceled. + */ + nsresult mStatus MOZ_GUARDED_BY(mLock){NS_OK}; + + /** + * True if we should append data to the initial target file, instead of + * overwriting it. + */ + bool mAppend MOZ_GUARDED_BY(mLock){false}; + + /** + * This is set by the first SetTarget call on the control thread, and contains + * the target file name that will be used by the worker thread, as soon as it + * is possible to update mActualTarget and open the file. This is null if no + * target was ever assigned to this object. + */ + nsCOMPtr<nsIFile> mInitialTarget MOZ_GUARDED_BY(mLock); + + /** + * This is set by the first SetTarget call on the control thread, and + * indicates whether mInitialTarget should be kept as partially completed, + * rather than deleted, if the operation fails or is canceled. + */ + bool mInitialTargetKeepPartial MOZ_GUARDED_BY(mLock){false}; + + /** + * This is set by subsequent SetTarget calls on the control thread, and + * contains the new target file name to which the worker thread will move the + * target file, as soon as it can be done. This is null if SetTarget was + * called only once, or no target was ever assigned to this object. + * + * The target file can be renamed multiple times, though only the most recent + * rename is guaranteed to be processed by the worker thread. + */ + nsCOMPtr<nsIFile> mRenamedTarget MOZ_GUARDED_BY(mLock); + + /** + * This is set by subsequent SetTarget calls on the control thread, and + * indicates whether mRenamedTarget should be kept as partially completed, + * rather than deleted, if the operation fails or is canceled. + */ + bool mRenamedTargetKeepPartial MOZ_GUARDED_BY(mLock){false}; + + /** + * While NS_AsyncCopy is in progress, allows canceling it. Null otherwise. + * This is read by both threads but only written by the worker thread. + */ + nsCOMPtr<nsISupports> mAsyncCopyContext MOZ_GUARDED_BY(mLock); + + /** + * The SHA 256 hash in raw bytes of the downloaded file. This is written + * by the worker thread but can be read on the main thread. + */ + nsCString mSha256 MOZ_GUARDED_BY(mLock); + + /** + * Whether or not to compute the hash. Must be set on the main thread before + * setTarget is called. + */ + bool mSha256Enabled MOZ_GUARDED_BY(mLock){false}; + + /** + * Store the signature info. + */ + nsTArray<nsTArray<nsTArray<uint8_t>>> mSignatureInfo MOZ_GUARDED_BY(mLock); + + /** + * Whether or not to extract the signature. Must be set on the main thread + * before setTarget is called. + */ + bool mSignatureInfoEnabled MOZ_GUARDED_BY(mLock){false}; + + ////////////////////////////////////////////////////////////////////////////// + //// State handled exclusively by the worker thread + + /** + * Current target file associated to the input and output streams. + */ + nsCOMPtr<nsIFile> mActualTarget; + + /** + * Indicates whether mActualTarget should be kept as partially completed, + * rather than deleted, if the operation fails or is canceled. + */ + bool mActualTargetKeepPartial{false}; + + /** + * Used to calculate the file hash. This keeps state across file renames and + * is lazily initialized in ProcessStateChange. + */ + Maybe<Digest> mDigest; + + ////////////////////////////////////////////////////////////////////////////// + //// Private methods + + /** + * Called when NS_AsyncCopy completes. + * + * @param aClosure + * Populated with a raw pointer to the BackgroundFileSaver object. + * @param aStatus + * Success or failure status specified when the copy was interrupted. + */ + static void AsyncCopyCallback(void* aClosure, nsresult aStatus); + + /** + * Called on the control thread after state changes, to ensure that the worker + * thread will process the state change appropriately. + * + * @param aShouldInterruptCopy + * If true, the current NS_AsyncCopy, if any, is canceled. + */ + nsresult GetWorkerThreadAttention(bool aShouldInterruptCopy); + + /** + * Event called on the worker thread to begin processing a state change. + */ + nsresult ProcessAttention(); + + /** + * Called by ProcessAttention to execute the operations corresponding to the + * state change. If this results in an error, ProcessAttention will force the + * entire operation to be aborted. + */ + nsresult ProcessStateChange(); + + /** + * Returns true if completion conditions are met on the worker thread. The + * first time this happens, posts the completion event to the control thread. + */ + bool CheckCompletion(); + + /** + * Event called on the control thread to indicate that file contents will now + * be saved to the specified file. + */ + nsresult NotifyTargetChange(nsIFile* aTarget); + + /** + * Event called on the control thread to send the final notification. + */ + nsresult NotifySaveComplete(); + + /** + * Verifies the signature of the binary at the specified file path and stores + * the signature data in mSignatureInfo. We extract only X.509 certificates, + * since that is what Google's Safebrowsing protocol specifies. + */ + nsresult ExtractSignatureInfo(const nsAString& filePath); +}; + +//////////////////////////////////////////////////////////////////////////////// +//// BackgroundFileSaverOutputStream + +class BackgroundFileSaverOutputStream : public BackgroundFileSaver, + public nsIAsyncOutputStream, + public nsIOutputStreamCallback { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIOUTPUTSTREAM + NS_DECL_NSIASYNCOUTPUTSTREAM + NS_DECL_NSIOUTPUTSTREAMCALLBACK + + BackgroundFileSaverOutputStream(); + + protected: + virtual bool HasInfiniteBuffer() override; + virtual nsAsyncCopyProgressFun GetProgressCallback() override; + + private: + ~BackgroundFileSaverOutputStream() = default; + + /** + * Original callback provided to our AsyncWait wrapper. + */ + nsCOMPtr<nsIOutputStreamCallback> mAsyncWaitCallback; +}; + +//////////////////////////////////////////////////////////////////////////////// +//// BackgroundFileSaverStreamListener. This class is instantiated by +// nsExternalHelperAppService, DownloadCore.sys.mjs, and possibly others. + +class BackgroundFileSaverStreamListener final : public BackgroundFileSaver, + public nsIStreamListener { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + + BackgroundFileSaverStreamListener() = default; + + protected: + virtual bool HasInfiniteBuffer() override; + virtual nsAsyncCopyProgressFun GetProgressCallback() override; + + private: + ~BackgroundFileSaverStreamListener() = default; + + /** + * Protects the state related to whether the request should be suspended. + */ + mozilla::Mutex mSuspensionLock{ + "BackgroundFileSaverStreamListener.mSuspensionLock"}; + + /** + * Whether we should suspend the request because we received too much data. + */ + bool mReceivedTooMuchData MOZ_GUARDED_BY(mSuspensionLock){false}; + + /** + * Request for which we received too much data. This is populated when + * mReceivedTooMuchData becomes true for the first time. + */ + nsCOMPtr<nsIRequest> mRequest MOZ_GUARDED_BY(mSuspensionLock); + + /** + * Whether mRequest is currently suspended. + */ + bool mRequestSuspended MOZ_GUARDED_BY(mSuspensionLock){false}; + + /** + * Called while NS_AsyncCopy is copying data. + */ + static void AsyncCopyProgressCallback(void* aClosure, uint32_t aCount); + + /** + * Called on the control thread to suspend or resume the request. + */ + nsresult NotifySuspendOrResume(); +}; + +// A wrapper around nsIOutputStream, so that we can compute hashes on the +// stream without copying and without polluting pristine NSS code with XPCOM +// interfaces. +class DigestOutputStream : public nsIOutputStream { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIOUTPUTSTREAM + // Constructor. Neither parameter may be null. The caller owns both. + DigestOutputStream(nsIOutputStream* aStream, Digest& aDigest); + + private: + virtual ~DigestOutputStream() = default; + + // Calls to write are passed to this stream. + nsCOMPtr<nsIOutputStream> mOutputStream; + // Digest used to compute the hash, owned by the caller. + Digest& mDigest; + + // Don't accidentally copy construct. + DigestOutputStream(const DigestOutputStream& d) = delete; +}; + +} // namespace net +} // namespace mozilla + +#endif |