1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 /** 8 * This file defines two implementations of the nsIBackgroundFileSaver 9 * interface. See the "test_backgroundfilesaver.js" file for usage examples. 10 */ 11 12 #ifndef BackgroundFileSaver_h__ 13 #define BackgroundFileSaver_h__ 14 15 #include "ScopedNSSTypes.h" 16 #include "mozilla/Mutex.h" 17 #include "nsCOMArray.h" 18 #include "nsCOMPtr.h" 19 #include "nsIAsyncOutputStream.h" 20 #include "nsIBackgroundFileSaver.h" 21 #include "nsIStreamListener.h" 22 #include "nsStreamUtils.h" 23 #include "nsString.h" 24 25 class nsIAsyncInputStream; 26 class nsISerialEventTarget; 27 28 namespace mozilla { 29 namespace net { 30 31 class DigestOutputStream; 32 33 //////////////////////////////////////////////////////////////////////////////// 34 //// BackgroundFileSaver 35 36 class BackgroundFileSaver : public nsIBackgroundFileSaver { 37 public: 38 NS_DECL_NSIBACKGROUNDFILESAVER 39 40 BackgroundFileSaver(); 41 42 /** 43 * Initializes the pipe and the worker thread on XPCOM construction. 44 * 45 * This is called automatically by the XPCOM infrastructure, and if this 46 * fails, the factory will delete this object without returning a reference. 47 */ 48 nsresult Init(); 49 50 /** 51 * Number of worker threads that are currently running. 52 */ 53 static uint32_t sThreadCount; 54 55 /** 56 * Maximum number of worker threads reached during the current download 57 * session, used for telemetry. 58 * 59 * When there are no more worker threads running, we consider the download 60 * session finished, and this counter is reset. 61 */ 62 static uint32_t sTelemetryMaxThreadCount; 63 64 protected: 65 virtual ~BackgroundFileSaver(); 66 67 /** 68 * Thread that constructed this object. 69 */ 70 nsCOMPtr<nsIEventTarget> mControlEventTarget; 71 72 /** 73 * Thread to which the actual input/output is delegated. 74 */ 75 nsCOMPtr<nsISerialEventTarget> mBackgroundET; 76 77 /** 78 * Stream that receives data from derived classes. The received data will be 79 * available to the worker thread through mPipeInputStream. This is an 80 * instance of nsPipeOutputStream, not BackgroundFileSaverOutputStream. 81 */ 82 nsCOMPtr<nsIAsyncOutputStream> mPipeOutputStream; 83 84 /** 85 * Used during initialization, determines if the pipe is created with an 86 * infinite buffer. An infinite buffer is required if the derived class 87 * implements nsIStreamListener, because this interface requires all the 88 * provided data to be consumed synchronously. 89 */ 90 virtual bool HasInfiniteBuffer() = 0; 91 92 /** 93 * Used by derived classes if they need to be called back while copying. 94 */ 95 virtual nsAsyncCopyProgressFun GetProgressCallback() = 0; 96 97 /** 98 * Stream used by the worker thread to read the data to be saved. 99 */ 100 nsCOMPtr<nsIAsyncInputStream> mPipeInputStream; 101 102 private: 103 friend class NotifyTargetChangeRunnable; 104 105 /** 106 * Matches the nsIBackgroundFileSaver::observer property. 107 * 108 * @remarks This is a strong reference so that JavaScript callers don't need 109 * to worry about keeping another reference to the observer. 110 */ 111 nsCOMPtr<nsIBackgroundFileSaverObserver> mObserver; 112 113 ////////////////////////////////////////////////////////////////////////////// 114 //// Shared state between control and worker threads 115 116 /** 117 * Protects the shared state between control and worker threads. This mutex 118 * is always locked for a very short time, never during input/output. 119 */ 120 mozilla::Mutex mLock; 121 122 /** 123 * True if the worker thread is already waiting to process a change in state. 124 */ 125 bool mWorkerThreadAttentionRequested; 126 127 /** 128 * True if the operation should finish as soon as possibile. 129 */ 130 bool mFinishRequested; 131 132 /** 133 * True if the operation completed, with either success or failure. 134 */ 135 bool mComplete; 136 137 /** 138 * Holds the current file saver status. This is a success status while the 139 * object is working correctly, and remains such if the operation completes 140 * successfully. This becomes an error status when an error occurs on the 141 * worker thread, or when the operation is canceled. 142 */ 143 nsresult mStatus; 144 145 /** 146 * True if we should append data to the initial target file, instead of 147 * overwriting it. 148 */ 149 bool mAppend; 150 151 /** 152 * This is set by the first SetTarget call on the control thread, and contains 153 * the target file name that will be used by the worker thread, as soon as it 154 * is possible to update mActualTarget and open the file. This is null if no 155 * target was ever assigned to this object. 156 */ 157 nsCOMPtr<nsIFile> mInitialTarget; 158 159 /** 160 * This is set by the first SetTarget call on the control thread, and 161 * indicates whether mInitialTarget should be kept as partially completed, 162 * rather than deleted, if the operation fails or is canceled. 163 */ 164 bool mInitialTargetKeepPartial; 165 166 /** 167 * This is set by subsequent SetTarget calls on the control thread, and 168 * contains the new target file name to which the worker thread will move the 169 * target file, as soon as it can be done. This is null if SetTarget was 170 * called only once, or no target was ever assigned to this object. 171 * 172 * The target file can be renamed multiple times, though only the most recent 173 * rename is guaranteed to be processed by the worker thread. 174 */ 175 nsCOMPtr<nsIFile> mRenamedTarget; 176 177 /** 178 * This is set by subsequent SetTarget calls on the control thread, and 179 * indicates whether mRenamedTarget should be kept as partially completed, 180 * rather than deleted, if the operation fails or is canceled. 181 */ 182 bool mRenamedTargetKeepPartial; 183 184 /** 185 * While NS_AsyncCopy is in progress, allows canceling it. Null otherwise. 186 * This is read by both threads but only written by the worker thread. 187 */ 188 nsCOMPtr<nsISupports> mAsyncCopyContext; 189 190 /** 191 * The SHA 256 hash in raw bytes of the downloaded file. This is written 192 * by the worker thread but can be read on the main thread. 193 */ 194 nsCString mSha256; 195 196 /** 197 * Whether or not to compute the hash. Must be set on the main thread before 198 * setTarget is called. 199 */ 200 bool mSha256Enabled; 201 202 /** 203 * Store the signature info. 204 */ 205 nsTArray<nsTArray<nsTArray<uint8_t>>> mSignatureInfo; 206 207 /** 208 * Whether or not to extract the signature. Must be set on the main thread 209 * before setTarget is called. 210 */ 211 bool mSignatureInfoEnabled; 212 213 ////////////////////////////////////////////////////////////////////////////// 214 //// State handled exclusively by the worker thread 215 216 /** 217 * Current target file associated to the input and output streams. 218 */ 219 nsCOMPtr<nsIFile> mActualTarget; 220 221 /** 222 * Indicates whether mActualTarget should be kept as partially completed, 223 * rather than deleted, if the operation fails or is canceled. 224 */ 225 bool mActualTargetKeepPartial; 226 227 /** 228 * Used to calculate the file hash. This keeps state across file renames and 229 * is lazily initialized in ProcessStateChange. 230 */ 231 UniquePK11Context mDigestContext; 232 233 ////////////////////////////////////////////////////////////////////////////// 234 //// Private methods 235 236 /** 237 * Called when NS_AsyncCopy completes. 238 * 239 * @param aClosure 240 * Populated with a raw pointer to the BackgroundFileSaver object. 241 * @param aStatus 242 * Success or failure status specified when the copy was interrupted. 243 */ 244 static void AsyncCopyCallback(void* aClosure, nsresult aStatus); 245 246 /** 247 * Called on the control thread after state changes, to ensure that the worker 248 * thread will process the state change appropriately. 249 * 250 * @param aShouldInterruptCopy 251 * If true, the current NS_AsyncCopy, if any, is canceled. 252 */ 253 nsresult GetWorkerThreadAttention(bool aShouldInterruptCopy); 254 255 /** 256 * Event called on the worker thread to begin processing a state change. 257 */ 258 nsresult ProcessAttention(); 259 260 /** 261 * Called by ProcessAttention to execute the operations corresponding to the 262 * state change. If this results in an error, ProcessAttention will force the 263 * entire operation to be aborted. 264 */ 265 nsresult ProcessStateChange(); 266 267 /** 268 * Returns true if completion conditions are met on the worker thread. The 269 * first time this happens, posts the completion event to the control thread. 270 */ 271 bool CheckCompletion(); 272 273 /** 274 * Event called on the control thread to indicate that file contents will now 275 * be saved to the specified file. 276 */ 277 nsresult NotifyTargetChange(nsIFile* aTarget); 278 279 /** 280 * Event called on the control thread to send the final notification. 281 */ 282 nsresult NotifySaveComplete(); 283 284 /** 285 * Verifies the signature of the binary at the specified file path and stores 286 * the signature data in mSignatureInfo. We extract only X.509 certificates, 287 * since that is what Google's Safebrowsing protocol specifies. 288 */ 289 nsresult ExtractSignatureInfo(const nsAString& filePath); 290 }; 291 292 //////////////////////////////////////////////////////////////////////////////// 293 //// BackgroundFileSaverOutputStream 294 295 class BackgroundFileSaverOutputStream : public BackgroundFileSaver, 296 public nsIAsyncOutputStream, 297 public nsIOutputStreamCallback { 298 public: 299 NS_DECL_THREADSAFE_ISUPPORTS 300 NS_DECL_NSIOUTPUTSTREAM 301 NS_DECL_NSIASYNCOUTPUTSTREAM 302 NS_DECL_NSIOUTPUTSTREAMCALLBACK 303 304 BackgroundFileSaverOutputStream(); 305 306 protected: 307 virtual bool HasInfiniteBuffer() override; 308 virtual nsAsyncCopyProgressFun GetProgressCallback() override; 309 310 private: 311 ~BackgroundFileSaverOutputStream() = default; 312 313 /** 314 * Original callback provided to our AsyncWait wrapper. 315 */ 316 nsCOMPtr<nsIOutputStreamCallback> mAsyncWaitCallback; 317 }; 318 319 //////////////////////////////////////////////////////////////////////////////// 320 //// BackgroundFileSaverStreamListener. This class is instantiated by 321 // nsExternalHelperAppService, DownloadCore.jsm, and possibly others. 322 323 class BackgroundFileSaverStreamListener final : public BackgroundFileSaver, 324 public nsIStreamListener { 325 public: 326 NS_DECL_THREADSAFE_ISUPPORTS 327 NS_DECL_NSIREQUESTOBSERVER 328 NS_DECL_NSISTREAMLISTENER 329 330 BackgroundFileSaverStreamListener(); 331 332 protected: 333 virtual bool HasInfiniteBuffer() override; 334 virtual nsAsyncCopyProgressFun GetProgressCallback() override; 335 336 private: 337 ~BackgroundFileSaverStreamListener() = default; 338 339 /** 340 * Protects the state related to whether the request should be suspended. 341 */ 342 mozilla::Mutex mSuspensionLock; 343 344 /** 345 * Whether we should suspend the request because we received too much data. 346 */ 347 bool mReceivedTooMuchData; 348 349 /** 350 * Request for which we received too much data. This is populated when 351 * mReceivedTooMuchData becomes true for the first time. 352 */ 353 nsCOMPtr<nsIRequest> mRequest; 354 355 /** 356 * Whether mRequest is currently suspended. 357 */ 358 bool mRequestSuspended; 359 360 /** 361 * Called while NS_AsyncCopy is copying data. 362 */ 363 static void AsyncCopyProgressCallback(void* aClosure, uint32_t aCount); 364 365 /** 366 * Called on the control thread to suspend or resume the request. 367 */ 368 nsresult NotifySuspendOrResume(); 369 }; 370 371 // A wrapper around nsIOutputStream, so that we can compute hashes on the 372 // stream without copying and without polluting pristine NSS code with XPCOM 373 // interfaces. 374 class DigestOutputStream : public nsIOutputStream { 375 public: 376 NS_DECL_THREADSAFE_ISUPPORTS 377 NS_DECL_NSIOUTPUTSTREAM 378 // Constructor. Neither parameter may be null. The caller owns both. 379 DigestOutputStream(nsIOutputStream* outputStream, PK11Context* aContext); 380 381 private: 382 virtual ~DigestOutputStream() = default; 383 384 // Calls to write are passed to this stream. 385 nsCOMPtr<nsIOutputStream> mOutputStream; 386 // Digest context used to compute the hash, owned by the caller. 387 PK11Context* mDigestContext; 388 389 // Don't accidentally copy construct. 390 DigestOutputStream(const DigestOutputStream& d) = delete; 391 }; 392 393 } // namespace net 394 } // namespace mozilla 395 396 #endif 397