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