summaryrefslogtreecommitdiffstats
path: root/netwerk/base/BackgroundFileSaver.h
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /netwerk/base/BackgroundFileSaver.h
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'netwerk/base/BackgroundFileSaver.h')
-rw-r--r--netwerk/base/BackgroundFileSaver.h397
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