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/. */
7 #include "BackgroundFileSaver.h"
9 #include "ScopedNSSTypes.h"
10 #include "mozilla/ArrayAlgorithm.h"
11 #include "mozilla/Casting.h"
12 #include "mozilla/Logging.h"
13 #include "mozilla/ScopeExit.h"
14 #include "mozilla/Telemetry.h"
15 #include "nsCOMArray.h"
16 #include "nsComponentManagerUtils.h"
17 #include "nsDependentSubstring.h"
18 #include "nsIAsyncInputStream.h"
20 #include "nsIMutableArray.h"
22 #include "nsNetUtil.h"
23 #include "nsThreadUtils.h"
30 # include <wintrust.h>
36 // MOZ_LOG=BackgroundFileSaver:5
37 static LazyLogModule
prlog("BackgroundFileSaver");
38 #define LOG(args) MOZ_LOG(prlog, mozilla::LogLevel::Debug, args)
39 #define LOG_ENABLED() MOZ_LOG_TEST(prlog, mozilla::LogLevel::Debug)
41 ////////////////////////////////////////////////////////////////////////////////
45 * Buffer size for writing to the output file or reading from the input file.
47 #define BUFFERED_IO_SIZE (1024 * 32)
50 * When this upper limit is reached, the original request is suspended.
52 #define REQUEST_SUSPEND_AT (1024 * 1024 * 4)
55 * When this lower limit is reached, the original request is resumed.
57 #define REQUEST_RESUME_AT (1024 * 1024 * 2)
59 ////////////////////////////////////////////////////////////////////////////////
60 //// NotifyTargetChangeRunnable
63 * Runnable object used to notify the control thread that file contents will now
64 * be saved to the specified file.
66 class NotifyTargetChangeRunnable final
: public Runnable
{
68 NotifyTargetChangeRunnable(BackgroundFileSaver
* aSaver
, nsIFile
* aTarget
)
69 : Runnable("net::NotifyTargetChangeRunnable"),
73 NS_IMETHOD
Run() override
{ return mSaver
->NotifyTargetChange(mTarget
); }
76 RefPtr
<BackgroundFileSaver
> mSaver
;
77 nsCOMPtr
<nsIFile
> mTarget
;
80 ////////////////////////////////////////////////////////////////////////////////
81 //// BackgroundFileSaver
83 uint32_t BackgroundFileSaver::sThreadCount
= 0;
84 uint32_t BackgroundFileSaver::sTelemetryMaxThreadCount
= 0;
86 BackgroundFileSaver::BackgroundFileSaver() {
87 LOG(("Created BackgroundFileSaver [this = %p]", this));
90 BackgroundFileSaver::~BackgroundFileSaver() {
91 LOG(("Destroying BackgroundFileSaver [this = %p]", this));
94 // Called on the control thread.
95 nsresult
BackgroundFileSaver::Init() {
96 MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
100 rv
= NS_NewPipe2(getter_AddRefs(mPipeInputStream
),
101 getter_AddRefs(mPipeOutputStream
), true, true, 0,
102 HasInfiniteBuffer() ? UINT32_MAX
: 0);
103 NS_ENSURE_SUCCESS(rv
, rv
);
105 mControlEventTarget
= GetCurrentEventTarget();
106 NS_ENSURE_TRUE(mControlEventTarget
, NS_ERROR_NOT_INITIALIZED
);
108 rv
= NS_CreateBackgroundTaskQueue("BgFileSaver",
109 getter_AddRefs(mBackgroundET
));
110 NS_ENSURE_SUCCESS(rv
, rv
);
113 if (sThreadCount
> sTelemetryMaxThreadCount
) {
114 sTelemetryMaxThreadCount
= sThreadCount
;
120 // Called on the control thread.
122 BackgroundFileSaver::GetObserver(nsIBackgroundFileSaverObserver
** aObserver
) {
123 NS_ENSURE_ARG_POINTER(aObserver
);
124 *aObserver
= mObserver
;
125 NS_IF_ADDREF(*aObserver
);
129 // Called on the control thread.
131 BackgroundFileSaver::SetObserver(nsIBackgroundFileSaverObserver
* aObserver
) {
132 mObserver
= aObserver
;
136 // Called on the control thread.
138 BackgroundFileSaver::EnableAppend() {
139 MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
141 MutexAutoLock
lock(mLock
);
147 // Called on the control thread.
149 BackgroundFileSaver::SetTarget(nsIFile
* aTarget
, bool aKeepPartial
) {
150 NS_ENSURE_ARG(aTarget
);
152 MutexAutoLock
lock(mLock
);
153 if (!mInitialTarget
) {
154 aTarget
->Clone(getter_AddRefs(mInitialTarget
));
155 mInitialTargetKeepPartial
= aKeepPartial
;
157 aTarget
->Clone(getter_AddRefs(mRenamedTarget
));
158 mRenamedTargetKeepPartial
= aKeepPartial
;
162 // After the worker thread wakes up because attention is requested, it will
163 // rename or create the target file as requested, and start copying data.
164 return GetWorkerThreadAttention(true);
167 // Called on the control thread.
169 BackgroundFileSaver::Finish(nsresult aStatus
) {
172 // This will cause the NS_AsyncCopy operation, if it's in progress, to consume
173 // all the data that is still in the pipe, and then finish.
174 rv
= mPipeOutputStream
->Close();
175 NS_ENSURE_SUCCESS(rv
, rv
);
177 // Ensure that, when we get attention from the worker thread, if no pending
178 // rename operation is waiting, the operation will complete.
180 MutexAutoLock
lock(mLock
);
181 mFinishRequested
= true;
182 if (NS_SUCCEEDED(mStatus
)) {
187 // After the worker thread wakes up because attention is requested, it will
188 // process the completion conditions, detect that completion is requested, and
189 // notify the main thread of the completion. If this function was called with
190 // a success code, we wait for the copy to finish before processing the
191 // completion conditions, otherwise we interrupt the copy immediately.
192 return GetWorkerThreadAttention(NS_FAILED(aStatus
));
196 BackgroundFileSaver::EnableSha256() {
197 MOZ_ASSERT(NS_IsMainThread(),
198 "Can't enable sha256 or initialize NSS off the main thread");
199 // Ensure Personal Security Manager is initialized. This is required for
200 // PK11_* operations to work.
202 nsCOMPtr
<nsISupports
> nssDummy
= do_GetService("@mozilla.org/psm;1", &rv
);
203 NS_ENSURE_SUCCESS(rv
, rv
);
204 mSha256Enabled
= true;
209 BackgroundFileSaver::GetSha256Hash(nsACString
& aHash
) {
210 MOZ_ASSERT(NS_IsMainThread(), "Can't inspect sha256 off the main thread");
211 // We acquire a lock because mSha256 is written on the worker thread.
212 MutexAutoLock
lock(mLock
);
213 if (mSha256
.IsEmpty()) {
214 return NS_ERROR_NOT_AVAILABLE
;
221 BackgroundFileSaver::EnableSignatureInfo() {
222 MOZ_ASSERT(NS_IsMainThread(),
223 "Can't enable signature extraction off the main thread");
224 // Ensure Personal Security Manager is initialized.
226 nsCOMPtr
<nsISupports
> nssDummy
= do_GetService("@mozilla.org/psm;1", &rv
);
227 NS_ENSURE_SUCCESS(rv
, rv
);
228 mSignatureInfoEnabled
= true;
233 BackgroundFileSaver::GetSignatureInfo(
234 nsTArray
<nsTArray
<nsTArray
<uint8_t>>>& aSignatureInfo
) {
235 MOZ_ASSERT(NS_IsMainThread(), "Can't inspect signature off the main thread");
236 // We acquire a lock because mSignatureInfo is written on the worker thread.
237 MutexAutoLock
lock(mLock
);
238 if (!mComplete
|| !mSignatureInfoEnabled
) {
239 return NS_ERROR_NOT_AVAILABLE
;
241 for (const auto& signatureChain
: mSignatureInfo
) {
242 aSignatureInfo
.AppendElement(TransformIntoNewArray(
243 signatureChain
, [](const auto& element
) { return element
.Clone(); }));
248 // Called on the control thread.
249 nsresult
BackgroundFileSaver::GetWorkerThreadAttention(
250 bool aShouldInterruptCopy
) {
253 MutexAutoLock
lock(mLock
);
255 // We only require attention one time. If this function is called two times
256 // before the worker thread wakes up, and the first has aShouldInterruptCopy
257 // false and the second true, we won't forcibly interrupt the copy from the
258 // control thread. However, that never happens, because calling Finish with a
259 // success code is the only case that may result in aShouldInterruptCopy being
260 // false. In that case, we won't call this function again, because consumers
261 // should not invoke other methods on the control thread after calling Finish.
262 // And in any case, Finish already closes one end of the pipe, causing the
263 // copy to finish properly on its own.
264 if (mWorkerThreadAttentionRequested
) {
268 if (!mAsyncCopyContext
) {
269 // Background event queues are not shutdown and could be called after
270 // the queue is reset to null. To match the behavior of nsIThread
271 // return NS_ERROR_UNEXPECTED
272 if (!mBackgroundET
) {
273 return NS_ERROR_UNEXPECTED
;
276 // Copy is not in progress, post an event to handle the change manually.
277 rv
= mBackgroundET
->Dispatch(
278 NewRunnableMethod("net::BackgroundFileSaver::ProcessAttention", this,
279 &BackgroundFileSaver::ProcessAttention
),
280 NS_DISPATCH_EVENT_MAY_BLOCK
);
281 NS_ENSURE_SUCCESS(rv
, rv
);
283 } else if (aShouldInterruptCopy
) {
284 // Interrupt the copy. The copy will be resumed, if needed, by the
285 // ProcessAttention function, invoked by the AsyncCopyCallback function.
286 NS_CancelAsyncCopy(mAsyncCopyContext
, NS_ERROR_ABORT
);
289 // Indicate that attention has been requested successfully, there is no need
290 // to post another event until the worker thread processes the current one.
291 mWorkerThreadAttentionRequested
= true;
296 // Called on the worker thread.
298 void BackgroundFileSaver::AsyncCopyCallback(void* aClosure
, nsresult aStatus
) {
299 // We called NS_ADDREF_THIS when NS_AsyncCopy started, to keep the object
300 // alive even if other references disappeared. At the end of this method,
301 // we've finished using the object and can safely release our reference.
302 RefPtr
<BackgroundFileSaver
> self
=
303 dont_AddRef((BackgroundFileSaver
*)aClosure
);
305 MutexAutoLock
lock(self
->mLock
);
307 // Now that the copy was interrupted or terminated, any notification from
308 // the control thread requires an event to be posted to the worker thread.
309 self
->mAsyncCopyContext
= nullptr;
311 // When detecting failures, ignore the status code we use to interrupt.
312 if (NS_FAILED(aStatus
) && aStatus
!= NS_ERROR_ABORT
&&
313 NS_SUCCEEDED(self
->mStatus
)) {
314 self
->mStatus
= aStatus
;
318 (void)self
->ProcessAttention();
321 // Called on the worker thread.
322 nsresult
BackgroundFileSaver::ProcessAttention() {
325 // This function is called whenever the attention of the worker thread has
326 // been requested. This may happen in these cases:
327 // * We are about to start the copy for the first time. In this case, we are
328 // called from an event posted on the worker thread from the control thread
329 // by GetWorkerThreadAttention, and mAsyncCopyContext is null.
330 // * We have interrupted the copy for some reason. In this case, we are
331 // called by AsyncCopyCallback, and mAsyncCopyContext is null.
332 // * We are currently executing ProcessStateChange, and attention is requested
333 // by the control thread, for example because SetTarget or Finish have been
334 // called. In this case, we are called from from an event posted through
335 // GetWorkerThreadAttention. While mAsyncCopyContext was always null when
336 // the event was posted, at this point mAsyncCopyContext may not be null
337 // anymore, because ProcessStateChange may have started the copy before the
338 // event that called this function was processed on the worker thread.
339 // If mAsyncCopyContext is not null, we interrupt the copy and re-enter
340 // through AsyncCopyCallback. This allows us to check if, for instance, we
341 // should rename the target file. We will then restart the copy if needed.
342 if (mAsyncCopyContext
) {
343 NS_CancelAsyncCopy(mAsyncCopyContext
, NS_ERROR_ABORT
);
346 // Use the current shared state to determine the next operation to execute.
347 rv
= ProcessStateChange();
349 // If something failed while processing, terminate the operation now.
351 MutexAutoLock
lock(mLock
);
353 if (NS_SUCCEEDED(mStatus
)) {
357 // Ensure we notify completion now that the operation failed.
364 // Called on the worker thread.
365 nsresult
BackgroundFileSaver::ProcessStateChange() {
368 // We might have been notified because the operation is complete, verify.
369 if (CheckCompletion()) {
373 // Get a copy of the current shared state for the worker thread.
374 nsCOMPtr
<nsIFile
> initialTarget
;
375 bool initialTargetKeepPartial
;
376 nsCOMPtr
<nsIFile
> renamedTarget
;
377 bool renamedTargetKeepPartial
;
381 MutexAutoLock
lock(mLock
);
383 initialTarget
= mInitialTarget
;
384 initialTargetKeepPartial
= mInitialTargetKeepPartial
;
385 renamedTarget
= mRenamedTarget
;
386 renamedTargetKeepPartial
= mRenamedTargetKeepPartial
;
387 sha256Enabled
= mSha256Enabled
;
390 // From now on, another attention event needs to be posted if state changes.
391 mWorkerThreadAttentionRequested
= false;
394 // The initial target can only be null if it has never been assigned. In this
395 // case, there is nothing to do since we never created any output file.
396 if (!initialTarget
) {
400 // Determine if we are processing the attention request for the first time.
401 bool isContinuation
= !!mActualTarget
;
402 if (!isContinuation
) {
403 // Assign the target file for the first time.
404 mActualTarget
= initialTarget
;
405 mActualTargetKeepPartial
= initialTargetKeepPartial
;
408 // Verify whether we have actually been instructed to use a different file.
409 // This may happen the first time this function is executed, if SetTarget was
410 // called two times before the worker thread processed the attention request.
411 bool equalToCurrent
= false;
413 rv
= mActualTarget
->Equals(renamedTarget
, &equalToCurrent
);
414 NS_ENSURE_SUCCESS(rv
, rv
);
415 if (!equalToCurrent
) {
416 // If we were asked to rename the file but the initial file did not exist,
417 // we simply create the file in the renamed location. We avoid this check
418 // if we have already started writing the output file ourselves.
420 if (!isContinuation
) {
421 rv
= mActualTarget
->Exists(&exists
);
422 NS_ENSURE_SUCCESS(rv
, rv
);
425 // We are moving the previous target file to a different location.
426 nsCOMPtr
<nsIFile
> renamedTargetParentDir
;
427 rv
= renamedTarget
->GetParent(getter_AddRefs(renamedTargetParentDir
));
428 NS_ENSURE_SUCCESS(rv
, rv
);
430 nsAutoString renamedTargetName
;
431 rv
= renamedTarget
->GetLeafName(renamedTargetName
);
432 NS_ENSURE_SUCCESS(rv
, rv
);
434 // We must delete any existing target file before moving the current
436 rv
= renamedTarget
->Exists(&exists
);
437 NS_ENSURE_SUCCESS(rv
, rv
);
439 rv
= renamedTarget
->Remove(false);
440 NS_ENSURE_SUCCESS(rv
, rv
);
443 // Move the file. If this fails, we still reference the original file
444 // in mActualTarget, so that it is deleted if requested. If this
445 // succeeds, the nsIFile instance referenced by mActualTarget mutates
446 // and starts pointing to the new file, but we'll discard the reference.
447 rv
= mActualTarget
->MoveTo(renamedTargetParentDir
, renamedTargetName
);
448 NS_ENSURE_SUCCESS(rv
, rv
);
451 // We should not only update the mActualTarget with renameTarget when
452 // they point to the different files.
453 // In this way, if mActualTarget and renamedTarget point to the same file
454 // with different addresses, "CheckCompletion()" will return false
458 // Update mActualTarget with renameTarget,
459 // even if they point to the same file.
460 mActualTarget
= renamedTarget
;
461 mActualTargetKeepPartial
= renamedTargetKeepPartial
;
464 // Notify if the target file name actually changed.
465 if (!equalToCurrent
) {
466 // We must clone the nsIFile instance because mActualTarget is not
467 // immutable, it may change if the target is renamed later.
468 nsCOMPtr
<nsIFile
> actualTargetToNotify
;
469 rv
= mActualTarget
->Clone(getter_AddRefs(actualTargetToNotify
));
470 NS_ENSURE_SUCCESS(rv
, rv
);
472 RefPtr
<NotifyTargetChangeRunnable
> event
=
473 new NotifyTargetChangeRunnable(this, actualTargetToNotify
);
474 NS_ENSURE_TRUE(event
, NS_ERROR_FAILURE
);
476 rv
= mControlEventTarget
->Dispatch(event
, NS_DISPATCH_NORMAL
);
477 NS_ENSURE_SUCCESS(rv
, rv
);
480 if (isContinuation
) {
481 // The pending rename operation might be the last task before finishing. We
482 // may return here only if we have already created the target file.
483 if (CheckCompletion()) {
487 // Even if the operation did not complete, the pipe input stream may be
488 // empty and may have been closed already. We detect this case using the
489 // Available property, because it never returns an error if there is more
490 // data to be consumed. If the pipe input stream is closed, we just exit
491 // and wait for more calls like SetTarget or Finish to be invoked on the
492 // control thread. However, we still truncate the file or create the
493 // initial digest context if we are expected to do that.
495 rv
= mPipeInputStream
->Available(&available
);
501 // Create the digest if requested and NSS hasn't been shut down.
502 if (sha256Enabled
&& mDigest
.isNothing()) {
503 mDigest
.emplace(Digest());
504 mDigest
->Begin(SEC_OID_SHA256
);
507 // When we are requested to append to an existing file, we should read the
508 // existing data and ensure we include it as part of the final hash.
509 if (mDigest
.isSome() && append
&& !isContinuation
) {
510 nsCOMPtr
<nsIInputStream
> inputStream
;
511 rv
= NS_NewLocalFileInputStream(getter_AddRefs(inputStream
), mActualTarget
,
512 PR_RDONLY
| nsIFile::OS_READAHEAD
);
513 if (rv
!= NS_ERROR_FILE_NOT_FOUND
) {
514 NS_ENSURE_SUCCESS(rv
, rv
);
516 // Try to clean up the inputStream if an error occurs.
518 mozilla::MakeScopeExit([&] { Unused
<< inputStream
->Close(); });
520 char buffer
[BUFFERED_IO_SIZE
];
523 rv
= inputStream
->Read(buffer
, BUFFERED_IO_SIZE
, &count
);
524 NS_ENSURE_SUCCESS(rv
, rv
);
527 // We reached the end of the file.
531 rv
= mDigest
->Update(BitwiseCast
<unsigned char*, char*>(buffer
), count
);
532 NS_ENSURE_SUCCESS(rv
, rv
);
534 // The pending resume operation may have been cancelled by the control
535 // thread while the worker thread was reading in the existing file.
536 // Abort reading in the original file in that case, as the digest will
537 // be discarded anyway.
538 MutexAutoLock
lock(mLock
);
539 if (NS_FAILED(mStatus
)) {
540 return NS_ERROR_ABORT
;
544 // Close explicitly to handle any errors.
545 closeGuard
.release();
546 rv
= inputStream
->Close();
547 NS_ENSURE_SUCCESS(rv
, rv
);
551 // We will append to the initial target file only if it was requested by the
552 // caller, but we'll always append on subsequent accesses to the target file.
553 int32_t creationIoFlags
;
554 if (isContinuation
) {
555 creationIoFlags
= PR_APPEND
;
557 creationIoFlags
= (append
? PR_APPEND
: PR_TRUNCATE
) | PR_CREATE_FILE
;
560 // Create the target file, or append to it if we already started writing it.
561 // The 0600 permissions are used while the file is being downloaded, and for
562 // interrupted downloads. Those may be located in the system temporary
563 // directory, as well as the target directory, and generally have a ".part"
564 // extension. Those part files should never be group or world-writable even
565 // if the umask allows it.
566 nsCOMPtr
<nsIOutputStream
> outputStream
;
567 rv
= NS_NewLocalFileOutputStream(getter_AddRefs(outputStream
), mActualTarget
,
568 PR_WRONLY
| creationIoFlags
, 0600);
569 NS_ENSURE_SUCCESS(rv
, rv
);
571 nsCOMPtr
<nsIOutputStream
> bufferedStream
;
572 rv
= NS_NewBufferedOutputStream(getter_AddRefs(bufferedStream
),
573 outputStream
.forget(), BUFFERED_IO_SIZE
);
574 NS_ENSURE_SUCCESS(rv
, rv
);
575 outputStream
= bufferedStream
;
577 // Wrap the output stream so that it feeds the digest if needed.
578 if (mDigest
.isSome()) {
579 // Constructing the DigestOutputStream cannot fail. Passing mDigest
580 // to DigestOutputStream is safe, because BackgroundFileSaver always
581 // outlives the outputStream. BackgroundFileSaver is reference-counted
582 // before the call to AsyncCopy, and mDigest is never destroyed
583 // before AsyncCopyCallback.
584 outputStream
= new DigestOutputStream(outputStream
, mDigest
.ref());
587 // Start copying our input to the target file. No errors can be raised past
588 // this point if the copy starts, since they should be handled by the thread.
590 MutexAutoLock
lock(mLock
);
592 rv
= NS_AsyncCopy(mPipeInputStream
, outputStream
, mBackgroundET
,
593 NS_ASYNCCOPY_VIA_READSEGMENTS
, 4096, AsyncCopyCallback
,
594 this, false, true, getter_AddRefs(mAsyncCopyContext
),
595 GetProgressCallback());
597 NS_WARNING("NS_AsyncCopy failed.");
598 mAsyncCopyContext
= nullptr;
603 // If the operation succeeded, we must ensure that we keep this object alive
604 // for the entire duration of the copy, since only the raw pointer will be
605 // provided as the argument of the AsyncCopyCallback function. We can add the
606 // reference now, after NS_AsyncCopy returned, because it always starts
607 // processing asynchronously, and there is no risk that the callback is
608 // invoked before we reach this point. If the operation failed instead, then
609 // AsyncCopyCallback will never be called.
615 // Called on the worker thread.
616 bool BackgroundFileSaver::CheckCompletion() {
619 MOZ_ASSERT(!mAsyncCopyContext
,
620 "Should not be copying when checking completion conditions.");
624 MutexAutoLock
lock(mLock
);
630 // If an error occurred, we don't need to do the checks in this code block,
631 // and the operation can be completed immediately with a failure code.
632 if (NS_SUCCEEDED(mStatus
)) {
635 // We did not incur in an error, so we must determine if we can stop now.
636 // If the Finish method has not been called, we can just continue now.
637 if (!mFinishRequested
) {
641 // We can only stop when all the operations requested by the control
642 // thread have been processed. First, we check whether we have processed
643 // the first SetTarget call, if any. Then, we check whether we have
644 // processed any rename requested by subsequent SetTarget calls.
645 if ((mInitialTarget
&& !mActualTarget
) ||
646 (mRenamedTarget
&& mRenamedTarget
!= mActualTarget
)) {
650 // If we still have data to write to the output file, allow the copy
651 // operation to resume. The Available getter may return an error if one
652 // of the pipe's streams has been already closed.
654 rv
= mPipeInputStream
->Available(&available
);
655 if (NS_SUCCEEDED(rv
) && available
!= 0) {
663 // Ensure we notify completion now that the operation finished.
664 // Do a best-effort attempt to remove the file if required.
665 if (failed
&& mActualTarget
&& !mActualTargetKeepPartial
) {
666 (void)mActualTarget
->Remove(false);
669 // Finish computing the hash
670 if (!failed
&& mDigest
.isSome()) {
671 nsTArray
<uint8_t> outArray
;
672 rv
= mDigest
->End(outArray
);
673 if (NS_SUCCEEDED(rv
)) {
674 MutexAutoLock
lock(mLock
);
675 mSha256
= nsDependentCSubstring(
676 BitwiseCast
<char*, uint8_t*>(outArray
.Elements()), outArray
.Length());
680 // Compute the signature of the binary. ExtractSignatureInfo doesn't do
681 // anything on non-Windows platforms except return an empty nsIArray.
682 if (!failed
&& mActualTarget
) {
684 mActualTarget
->GetTarget(filePath
);
685 nsresult rv
= ExtractSignatureInfo(filePath
);
687 LOG(("Unable to extract signature information [this = %p].", this));
689 LOG(("Signature extraction success! [this = %p]", this));
693 // Post an event to notify that the operation completed.
694 if (NS_FAILED(mControlEventTarget
->Dispatch(
695 NewRunnableMethod("BackgroundFileSaver::NotifySaveComplete", this,
696 &BackgroundFileSaver::NotifySaveComplete
),
697 NS_DISPATCH_NORMAL
))) {
698 NS_WARNING("Unable to post completion event to the control thread.");
704 // Called on the control thread.
705 nsresult
BackgroundFileSaver::NotifyTargetChange(nsIFile
* aTarget
) {
707 (void)mObserver
->OnTargetChange(this, aTarget
);
713 // Called on the control thread.
714 nsresult
BackgroundFileSaver::NotifySaveComplete() {
715 MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
719 MutexAutoLock
lock(mLock
);
724 (void)mObserver
->OnSaveComplete(this, status
);
725 // If mObserver keeps alive an enclosure that captures `this`, we'll have a
726 // cycle that won't be caught by the cycle-collector, so we need to break it
727 // when we're done here (see bug 1444265).
731 // At this point, the worker thread will not process any more events, and we
732 // can shut it down. Shutting down a thread may re-enter the event loop on
733 // this thread. This is not a problem in this case, since this function is
734 // called by a top-level event itself, and we have already invoked the
735 // completion observer callback. Re-entering the loop can only delay the
736 // final release and destruction of this saver object, since we are keeping a
737 // reference to it through the event object.
738 mBackgroundET
= nullptr;
742 // When there are no more active downloads, we consider the download session
743 // finished. We record the maximum number of concurrent downloads reached
744 // during the session in a telemetry histogram, and we reset the maximum
745 // thread counter for the next download session
746 if (sThreadCount
== 0) {
747 Telemetry::Accumulate(Telemetry::BACKGROUNDFILESAVER_THREAD_COUNT
,
748 sTelemetryMaxThreadCount
);
749 sTelemetryMaxThreadCount
= 0;
755 nsresult
BackgroundFileSaver::ExtractSignatureInfo(const nsAString
& filePath
) {
756 MOZ_ASSERT(!NS_IsMainThread(), "Cannot extract signature on main thread");
758 MutexAutoLock
lock(mLock
);
759 if (!mSignatureInfoEnabled
) {
764 // Setup the file to check.
765 WINTRUST_FILE_INFO fileToCheck
= {0};
766 fileToCheck
.cbStruct
= sizeof(WINTRUST_FILE_INFO
);
767 fileToCheck
.pcwszFilePath
= filePath
.Data();
768 fileToCheck
.hFile
= nullptr;
769 fileToCheck
.pgKnownSubject
= nullptr;
771 // We want to check it is signed and trusted.
772 WINTRUST_DATA trustData
= {0};
773 trustData
.cbStruct
= sizeof(trustData
);
774 trustData
.pPolicyCallbackData
= nullptr;
775 trustData
.pSIPClientData
= nullptr;
776 trustData
.dwUIChoice
= WTD_UI_NONE
;
777 trustData
.fdwRevocationChecks
= WTD_REVOKE_NONE
;
778 trustData
.dwUnionChoice
= WTD_CHOICE_FILE
;
779 trustData
.dwStateAction
= WTD_STATEACTION_VERIFY
;
780 trustData
.hWVTStateData
= nullptr;
781 trustData
.pwszURLReference
= nullptr;
782 // Disallow revocation checks over the network
783 trustData
.dwProvFlags
= WTD_CACHE_ONLY_URL_RETRIEVAL
;
785 trustData
.dwUIContext
= 0;
786 trustData
.pFile
= &fileToCheck
;
788 // The WINTRUST_ACTION_GENERIC_VERIFY_V2 policy verifies that the certificate
789 // chains up to a trusted root CA and has appropriate permissions to sign
791 GUID policyGUID
= WINTRUST_ACTION_GENERIC_VERIFY_V2
;
792 // Check if the file is signed by something that is trusted. If the file is
793 // not signed, this is a no-op.
794 LONG ret
= WinVerifyTrust(nullptr, &policyGUID
, &trustData
);
795 CRYPT_PROVIDER_DATA
* cryptoProviderData
= nullptr;
796 // According to the Windows documentation, we should check against 0 instead
797 // of ERROR_SUCCESS, which is an HRESULT.
799 cryptoProviderData
= WTHelperProvDataFromStateData(trustData
.hWVTStateData
);
801 if (cryptoProviderData
) {
802 // Lock because signature information is read on the main thread.
803 MutexAutoLock
lock(mLock
);
804 LOG(("Downloaded trusted and signed file [this = %p].", this));
805 // A binary may have multiple signers. Each signer may have multiple certs
807 for (DWORD i
= 0; i
< cryptoProviderData
->csSigners
; ++i
) {
808 const CERT_CHAIN_CONTEXT
* certChainContext
=
809 cryptoProviderData
->pasSigners
[i
].pChainContext
;
810 if (!certChainContext
) {
813 for (DWORD j
= 0; j
< certChainContext
->cChain
; ++j
) {
814 const CERT_SIMPLE_CHAIN
* certSimpleChain
=
815 certChainContext
->rgpChain
[j
];
816 if (!certSimpleChain
) {
820 nsTArray
<nsTArray
<uint8_t>> certList
;
821 bool extractionSuccess
= true;
822 for (DWORD k
= 0; k
< certSimpleChain
->cElement
; ++k
) {
823 CERT_CHAIN_ELEMENT
* certChainElement
= certSimpleChain
->rgpElement
[k
];
824 if (certChainElement
->pCertContext
->dwCertEncodingType
!=
828 nsTArray
<uint8_t> cert
;
829 cert
.AppendElements(certChainElement
->pCertContext
->pbCertEncoded
,
830 certChainElement
->pCertContext
->cbCertEncoded
);
831 certList
.AppendElement(std::move(cert
));
833 if (extractionSuccess
) {
834 mSignatureInfo
.AppendElement(std::move(certList
));
838 // Free the provider data if cryptoProviderData is not null.
839 trustData
.dwStateAction
= WTD_STATEACTION_CLOSE
;
840 WinVerifyTrust(nullptr, &policyGUID
, &trustData
);
842 LOG(("Downloaded unsigned or untrusted file [this = %p].", this));
848 ////////////////////////////////////////////////////////////////////////////////
849 //// BackgroundFileSaverOutputStream
851 NS_IMPL_ISUPPORTS(BackgroundFileSaverOutputStream
, nsIBackgroundFileSaver
,
852 nsIOutputStream
, nsIAsyncOutputStream
,
853 nsIOutputStreamCallback
)
855 BackgroundFileSaverOutputStream::BackgroundFileSaverOutputStream()
856 : BackgroundFileSaver(), mAsyncWaitCallback(nullptr) {}
858 bool BackgroundFileSaverOutputStream::HasInfiniteBuffer() { return false; }
860 nsAsyncCopyProgressFun
BackgroundFileSaverOutputStream::GetProgressCallback() {
865 BackgroundFileSaverOutputStream::Close() { return mPipeOutputStream
->Close(); }
868 BackgroundFileSaverOutputStream::Flush() { return mPipeOutputStream
->Flush(); }
871 BackgroundFileSaverOutputStream::Write(const char* aBuf
, uint32_t aCount
,
873 return mPipeOutputStream
->Write(aBuf
, aCount
, _retval
);
877 BackgroundFileSaverOutputStream::WriteFrom(nsIInputStream
* aFromStream
,
878 uint32_t aCount
, uint32_t* _retval
) {
879 return mPipeOutputStream
->WriteFrom(aFromStream
, aCount
, _retval
);
883 BackgroundFileSaverOutputStream::WriteSegments(nsReadSegmentFun aReader
,
884 void* aClosure
, uint32_t aCount
,
886 return mPipeOutputStream
->WriteSegments(aReader
, aClosure
, aCount
, _retval
);
890 BackgroundFileSaverOutputStream::IsNonBlocking(bool* _retval
) {
891 return mPipeOutputStream
->IsNonBlocking(_retval
);
895 BackgroundFileSaverOutputStream::CloseWithStatus(nsresult reason
) {
896 return mPipeOutputStream
->CloseWithStatus(reason
);
900 BackgroundFileSaverOutputStream::AsyncWait(nsIOutputStreamCallback
* aCallback
,
902 uint32_t aRequestedCount
,
903 nsIEventTarget
* aEventTarget
) {
904 NS_ENSURE_STATE(!mAsyncWaitCallback
);
906 mAsyncWaitCallback
= aCallback
;
908 return mPipeOutputStream
->AsyncWait(this, aFlags
, aRequestedCount
,
913 BackgroundFileSaverOutputStream::OnOutputStreamReady(
914 nsIAsyncOutputStream
* aStream
) {
915 NS_ENSURE_STATE(mAsyncWaitCallback
);
917 nsCOMPtr
<nsIOutputStreamCallback
> asyncWaitCallback
= nullptr;
918 asyncWaitCallback
.swap(mAsyncWaitCallback
);
920 return asyncWaitCallback
->OnOutputStreamReady(this);
923 ////////////////////////////////////////////////////////////////////////////////
924 //// BackgroundFileSaverStreamListener
926 NS_IMPL_ISUPPORTS(BackgroundFileSaverStreamListener
, nsIBackgroundFileSaver
,
927 nsIRequestObserver
, nsIStreamListener
)
929 bool BackgroundFileSaverStreamListener::HasInfiniteBuffer() { return true; }
931 nsAsyncCopyProgressFun
932 BackgroundFileSaverStreamListener::GetProgressCallback() {
933 return AsyncCopyProgressCallback
;
937 BackgroundFileSaverStreamListener::OnStartRequest(nsIRequest
* aRequest
) {
938 NS_ENSURE_ARG(aRequest
);
944 BackgroundFileSaverStreamListener::OnStopRequest(nsIRequest
* aRequest
,
945 nsresult aStatusCode
) {
946 // If an error occurred, cancel the operation immediately. On success, wait
947 // until the caller has determined whether the file should be renamed.
948 if (NS_FAILED(aStatusCode
)) {
956 BackgroundFileSaverStreamListener::OnDataAvailable(nsIRequest
* aRequest
,
957 nsIInputStream
* aInputStream
,
962 NS_ENSURE_ARG(aRequest
);
964 // Read the requested data. Since the pipe has an infinite buffer, we don't
965 // expect any write error to occur here.
967 rv
= mPipeOutputStream
->WriteFrom(aInputStream
, aCount
, &writeCount
);
968 NS_ENSURE_SUCCESS(rv
, rv
);
970 // If reading from the input stream fails for any reason, the pipe will return
971 // a success code, but without reading all the data. Since we should be able
972 // to read the requested data when OnDataAvailable is called, raise an error.
973 if (writeCount
< aCount
) {
974 NS_WARNING("Reading from the input stream should not have failed.");
975 return NS_ERROR_UNEXPECTED
;
978 bool stateChanged
= false;
980 MutexAutoLock
lock(mSuspensionLock
);
982 if (!mReceivedTooMuchData
) {
984 nsresult rv
= mPipeInputStream
->Available(&available
);
985 if (NS_SUCCEEDED(rv
) && available
> REQUEST_SUSPEND_AT
) {
986 mReceivedTooMuchData
= true;
994 NotifySuspendOrResume();
1000 // Called on the worker thread.
1002 void BackgroundFileSaverStreamListener::AsyncCopyProgressCallback(
1003 void* aClosure
, uint32_t aCount
) {
1004 BackgroundFileSaverStreamListener
* self
=
1005 (BackgroundFileSaverStreamListener
*)aClosure
;
1007 // Wait if the control thread is in the process of suspending or resuming.
1008 MutexAutoLock
lock(self
->mSuspensionLock
);
1010 // This function is called when some bytes are consumed by NS_AsyncCopy. Each
1011 // time this happens, verify if a suspended request should be resumed, because
1012 // we have now consumed enough data.
1013 if (self
->mReceivedTooMuchData
) {
1015 nsresult rv
= self
->mPipeInputStream
->Available(&available
);
1016 if (NS_FAILED(rv
) || available
< REQUEST_RESUME_AT
) {
1017 self
->mReceivedTooMuchData
= false;
1019 // Post an event to verify if the request should be resumed.
1020 if (NS_FAILED(self
->mControlEventTarget
->Dispatch(
1022 "BackgroundFileSaverStreamListener::NotifySuspendOrResume",
1024 &BackgroundFileSaverStreamListener::NotifySuspendOrResume
),
1025 NS_DISPATCH_NORMAL
))) {
1026 NS_WARNING("Unable to post resume event to the control thread.");
1032 // Called on the control thread.
1033 nsresult
BackgroundFileSaverStreamListener::NotifySuspendOrResume() {
1034 // Prevent the worker thread from changing state while processing.
1035 MutexAutoLock
lock(mSuspensionLock
);
1037 if (mReceivedTooMuchData
) {
1038 if (!mRequestSuspended
) {
1039 // Try to suspend the request. If this fails, don't try to resume later.
1040 if (NS_SUCCEEDED(mRequest
->Suspend())) {
1041 mRequestSuspended
= true;
1043 NS_WARNING("Unable to suspend the request.");
1047 if (mRequestSuspended
) {
1048 // Resume the request only if we succeeded in suspending it.
1049 if (NS_SUCCEEDED(mRequest
->Resume())) {
1050 mRequestSuspended
= false;
1052 NS_WARNING("Unable to resume the request.");
1060 ////////////////////////////////////////////////////////////////////////////////
1061 //// DigestOutputStream
1062 NS_IMPL_ISUPPORTS(DigestOutputStream
, nsIOutputStream
)
1064 DigestOutputStream::DigestOutputStream(nsIOutputStream
* aStream
,
1066 : mOutputStream(aStream
), mDigest(aDigest
) {
1067 MOZ_ASSERT(mOutputStream
, "Can't have null output stream");
1071 DigestOutputStream::Close() { return mOutputStream
->Close(); }
1074 DigestOutputStream::Flush() { return mOutputStream
->Flush(); }
1077 DigestOutputStream::Write(const char* aBuf
, uint32_t aCount
, uint32_t* retval
) {
1078 nsresult rv
= mDigest
.Update(
1079 BitwiseCast
<const unsigned char*, const char*>(aBuf
), aCount
);
1080 NS_ENSURE_SUCCESS(rv
, rv
);
1082 return mOutputStream
->Write(aBuf
, aCount
, retval
);
1086 DigestOutputStream::WriteFrom(nsIInputStream
* aFromStream
, uint32_t aCount
,
1088 // Not supported. We could read the stream to a buf, call DigestOp on the
1089 // result, seek back and pass the stream on, but it's not worth it since our
1090 // application (NS_AsyncCopy) doesn't invoke this on the sink.
1091 MOZ_CRASH("DigestOutputStream::WriteFrom not implemented");
1095 DigestOutputStream::WriteSegments(nsReadSegmentFun aReader
, void* aClosure
,
1096 uint32_t aCount
, uint32_t* retval
) {
1097 MOZ_CRASH("DigestOutputStream::WriteSegments not implemented");
1101 DigestOutputStream::IsNonBlocking(bool* retval
) {
1102 return mOutputStream
->IsNonBlocking(retval
);
1108 } // namespace mozilla