tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

BackgroundFileSaver.cpp (39968B)


      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 #include "BackgroundFileSaver.h"
      8 
      9 #include "ScopedNSSTypes.h"
     10 #include "mozilla/ArrayAlgorithm.h"
     11 #include "mozilla/Components.h"
     12 #include "mozilla/Casting.h"
     13 #include "mozilla/Logging.h"
     14 #include "mozilla/ScopeExit.h"
     15 #include "mozilla/glean/NetwerkMetrics.h"
     16 #include "nsCOMArray.h"
     17 #include "nsComponentManagerUtils.h"
     18 #include "nsDependentSubstring.h"
     19 #include "nsIAsyncInputStream.h"
     20 #include "nsIFile.h"
     21 #include "nsIMutableArray.h"
     22 #include "nsIPipe.h"
     23 #include "nsNetUtil.h"
     24 #include "nsThreadUtils.h"
     25 #include "pk11pub.h"
     26 #include "secoidt.h"
     27 
     28 #ifdef XP_WIN
     29 #  include <windows.h>
     30 #  include <softpub.h>
     31 #  include <wintrust.h>
     32 #endif  // XP_WIN
     33 
     34 namespace mozilla {
     35 namespace net {
     36 
     37 // MOZ_LOG=BackgroundFileSaver:5
     38 static LazyLogModule prlog("BackgroundFileSaver");
     39 #define LOG(args) MOZ_LOG(prlog, mozilla::LogLevel::Debug, args)
     40 #define LOG_ENABLED() MOZ_LOG_TEST(prlog, mozilla::LogLevel::Debug)
     41 
     42 ////////////////////////////////////////////////////////////////////////////////
     43 //// Globals
     44 
     45 /**
     46 * Buffer size for writing to the output file or reading from the input file.
     47 */
     48 #define BUFFERED_IO_SIZE (1024 * 32)
     49 
     50 /**
     51 * When this upper limit is reached, the original request is suspended.
     52 */
     53 #define REQUEST_SUSPEND_AT (1024 * 1024 * 4)
     54 
     55 /**
     56 * When this lower limit is reached, the original request is resumed.
     57 */
     58 #define REQUEST_RESUME_AT (1024 * 1024 * 2)
     59 
     60 ////////////////////////////////////////////////////////////////////////////////
     61 //// NotifyTargetChangeRunnable
     62 
     63 /**
     64 * Runnable object used to notify the control thread that file contents will now
     65 * be saved to the specified file.
     66 */
     67 class NotifyTargetChangeRunnable final : public Runnable {
     68 public:
     69  NotifyTargetChangeRunnable(BackgroundFileSaver* aSaver, nsIFile* aTarget)
     70      : Runnable("net::NotifyTargetChangeRunnable"),
     71        mSaver(aSaver),
     72        mTarget(aTarget) {}
     73 
     74  NS_IMETHOD Run() override { return mSaver->NotifyTargetChange(mTarget); }
     75 
     76 private:
     77  RefPtr<BackgroundFileSaver> mSaver;
     78  nsCOMPtr<nsIFile> mTarget;
     79 };
     80 
     81 ////////////////////////////////////////////////////////////////////////////////
     82 //// BackgroundFileSaver
     83 
     84 uint32_t BackgroundFileSaver::sThreadCount = 0;
     85 uint32_t BackgroundFileSaver::sTelemetryMaxThreadCount = 0;
     86 
     87 BackgroundFileSaver::BackgroundFileSaver() {
     88  LOG(("Created BackgroundFileSaver [this = %p]", this));
     89 }
     90 
     91 BackgroundFileSaver::~BackgroundFileSaver() {
     92  LOG(("Destroying BackgroundFileSaver [this = %p]", this));
     93 }
     94 
     95 // Called on the control thread.
     96 nsresult BackgroundFileSaver::Init() {
     97  MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
     98 
     99  NS_NewPipe2(getter_AddRefs(mPipeInputStream),
    100              getter_AddRefs(mPipeOutputStream), true, true, 0,
    101              HasInfiniteBuffer() ? UINT32_MAX : 0);
    102 
    103  mControlEventTarget = GetCurrentSerialEventTarget();
    104  NS_ENSURE_TRUE(mControlEventTarget, NS_ERROR_NOT_INITIALIZED);
    105 
    106  nsresult rv = NS_CreateBackgroundTaskQueue("BgFileSaver",
    107                                             getter_AddRefs(mBackgroundET));
    108  NS_ENSURE_SUCCESS(rv, rv);
    109 
    110  sThreadCount++;
    111  if (sThreadCount > sTelemetryMaxThreadCount) {
    112    sTelemetryMaxThreadCount = sThreadCount;
    113  }
    114 
    115  return NS_OK;
    116 }
    117 
    118 // Called on the control thread.
    119 NS_IMETHODIMP
    120 BackgroundFileSaver::GetObserver(nsIBackgroundFileSaverObserver** aObserver) {
    121  NS_ENSURE_ARG_POINTER(aObserver);
    122  *aObserver = do_AddRef(mObserver).take();
    123  return NS_OK;
    124 }
    125 
    126 // Called on the control thread.
    127 NS_IMETHODIMP
    128 BackgroundFileSaver::SetObserver(nsIBackgroundFileSaverObserver* aObserver) {
    129  mObserver = aObserver;
    130  return NS_OK;
    131 }
    132 
    133 // Called on the control thread.
    134 NS_IMETHODIMP
    135 BackgroundFileSaver::EnableAppend() {
    136  MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
    137 
    138  MutexAutoLock lock(mLock);
    139  mAppend = true;
    140 
    141  return NS_OK;
    142 }
    143 
    144 // Called on the control thread.
    145 NS_IMETHODIMP
    146 BackgroundFileSaver::SetTarget(nsIFile* aTarget, bool aKeepPartial) {
    147  NS_ENSURE_ARG(aTarget);
    148  {
    149    MutexAutoLock lock(mLock);
    150    if (!mInitialTarget) {
    151      aTarget->Clone(getter_AddRefs(mInitialTarget));
    152      mInitialTargetKeepPartial = aKeepPartial;
    153    } else {
    154      aTarget->Clone(getter_AddRefs(mRenamedTarget));
    155      mRenamedTargetKeepPartial = aKeepPartial;
    156    }
    157  }
    158 
    159  // After the worker thread wakes up because attention is requested, it will
    160  // rename or create the target file as requested, and start copying data.
    161  return GetWorkerThreadAttention(true);
    162 }
    163 
    164 // Called on the control thread.
    165 NS_IMETHODIMP
    166 BackgroundFileSaver::Finish(nsresult aStatus) {
    167  nsresult rv;
    168 
    169  // This will cause the NS_AsyncCopy operation, if it's in progress, to consume
    170  // all the data that is still in the pipe, and then finish.
    171  rv = mPipeOutputStream->Close();
    172  NS_ENSURE_SUCCESS(rv, rv);
    173 
    174  // Ensure that, when we get attention from the worker thread, if no pending
    175  // rename operation is waiting, the operation will complete.
    176  {
    177    MutexAutoLock lock(mLock);
    178    mFinishRequested = true;
    179    if (NS_SUCCEEDED(mStatus)) {
    180      mStatus = aStatus;
    181    }
    182  }
    183 
    184  // After the worker thread wakes up because attention is requested, it will
    185  // process the completion conditions, detect that completion is requested, and
    186  // notify the main thread of the completion.  If this function was called with
    187  // a success code, we wait for the copy to finish before processing the
    188  // completion conditions, otherwise we interrupt the copy immediately.
    189  return GetWorkerThreadAttention(NS_FAILED(aStatus));
    190 }
    191 
    192 NS_IMETHODIMP
    193 BackgroundFileSaver::EnableSha256() {
    194  MOZ_ASSERT(NS_IsMainThread(),
    195             "Can't enable sha256 or initialize NSS off the main thread");
    196  // Ensure Personal Security Manager is initialized. This is required for
    197  // PK11_* operations to work.
    198  nsresult rv = NS_OK;
    199  mozilla::components::NSSComponent::Service(&rv);
    200  NS_ENSURE_SUCCESS(rv, rv);
    201  MutexAutoLock lock(mLock);
    202  mSha256Enabled = true;  // this will be read by the worker thread
    203  return NS_OK;
    204 }
    205 
    206 NS_IMETHODIMP
    207 BackgroundFileSaver::GetSha256Hash(nsACString& aHash) {
    208  MOZ_ASSERT(NS_IsMainThread(), "Can't inspect sha256 off the main thread");
    209  // We acquire a lock because mSha256 is written on the worker thread.
    210  MutexAutoLock lock(mLock);
    211  if (mSha256.IsEmpty()) {
    212    return NS_ERROR_NOT_AVAILABLE;
    213  }
    214  aHash = mSha256;
    215  return NS_OK;
    216 }
    217 
    218 NS_IMETHODIMP
    219 BackgroundFileSaver::EnableSignatureInfo() {
    220  MOZ_ASSERT(NS_IsMainThread(),
    221             "Can't enable signature extraction off the main thread");
    222  // Ensure Personal Security Manager is initialized.
    223  nsresult rv = NS_OK;
    224  mozilla::components::NSSComponent::Service(&rv);
    225  NS_ENSURE_SUCCESS(rv, rv);
    226  MutexAutoLock lock(mLock);
    227  mSignatureInfoEnabled = true;
    228  return NS_OK;
    229 }
    230 
    231 NS_IMETHODIMP
    232 BackgroundFileSaver::GetSignatureInfo(
    233    nsTArray<nsTArray<nsTArray<uint8_t>>>& aSignatureInfo) {
    234  MOZ_ASSERT(NS_IsMainThread(), "Can't inspect signature off the main thread");
    235  // We acquire a lock because mSignatureInfo is written on the worker thread.
    236  MutexAutoLock lock(mLock);
    237  if (!mComplete || !mSignatureInfoEnabled) {
    238    return NS_ERROR_NOT_AVAILABLE;
    239  }
    240  for (const auto& signatureChain : mSignatureInfo) {
    241    aSignatureInfo.AppendElement(TransformIntoNewArray(
    242        signatureChain, [](const auto& element) { return element.Clone(); }));
    243  }
    244  return NS_OK;
    245 }
    246 
    247 // Called on the control thread.
    248 nsresult BackgroundFileSaver::GetWorkerThreadAttention(
    249    bool aShouldInterruptCopy) {
    250  nsresult rv;
    251 
    252  MutexAutoLock lock(mLock);
    253 
    254  // We only require attention one time.  If this function is called two times
    255  // before the worker thread wakes up, and the first has aShouldInterruptCopy
    256  // false and the second true, we won't forcibly interrupt the copy from the
    257  // control thread.  However, that never happens, because calling Finish with a
    258  // success code is the only case that may result in aShouldInterruptCopy being
    259  // false.  In that case, we won't call this function again, because consumers
    260  // should not invoke other methods on the control thread after calling Finish.
    261  // And in any case, Finish already closes one end of the pipe, causing the
    262  // copy to finish properly on its own.
    263  if (mWorkerThreadAttentionRequested) {
    264    return NS_OK;
    265  }
    266 
    267  if (!mAsyncCopyContext) {
    268    // Background event queues are not shutdown and could be called after
    269    // the queue is reset to null.  To match the behavior of nsIThread
    270    // return NS_ERROR_UNEXPECTED
    271    if (!mBackgroundET) {
    272      return NS_ERROR_UNEXPECTED;
    273    }
    274 
    275    // Copy is not in progress, post an event to handle the change manually.
    276    rv = mBackgroundET->Dispatch(
    277        NewRunnableMethod("net::BackgroundFileSaver::ProcessAttention", this,
    278                          &BackgroundFileSaver::ProcessAttention),
    279        NS_DISPATCH_EVENT_MAY_BLOCK);
    280    NS_ENSURE_SUCCESS(rv, rv);
    281 
    282  } else if (aShouldInterruptCopy) {
    283    // Interrupt the copy.  The copy will be resumed, if needed, by the
    284    // ProcessAttention function, invoked by the AsyncCopyCallback function.
    285    NS_CancelAsyncCopy(mAsyncCopyContext, NS_ERROR_ABORT);
    286  }
    287 
    288  // Indicate that attention has been requested successfully, there is no need
    289  // to post another event until the worker thread processes the current one.
    290  mWorkerThreadAttentionRequested = true;
    291 
    292  return NS_OK;
    293 }
    294 
    295 // Called on the worker thread.
    296 // static
    297 void BackgroundFileSaver::AsyncCopyCallback(void* aClosure, nsresult aStatus) {
    298  // We called NS_ADDREF_THIS when NS_AsyncCopy started, to keep the object
    299  // alive even if other references disappeared.  At the end of this method,
    300  // we've finished using the object and can safely release our reference.
    301  RefPtr<BackgroundFileSaver> self =
    302      dont_AddRef((BackgroundFileSaver*)aClosure);
    303  {
    304    MutexAutoLock lock(self->mLock);
    305 
    306    // Now that the copy was interrupted or terminated, any notification from
    307    // the control thread requires an event to be posted to the worker thread.
    308    self->mAsyncCopyContext = nullptr;
    309 
    310    // When detecting failures, ignore the status code we use to interrupt.
    311    if (NS_FAILED(aStatus) && aStatus != NS_ERROR_ABORT &&
    312        NS_SUCCEEDED(self->mStatus)) {
    313      self->mStatus = aStatus;
    314    }
    315  }
    316 
    317  (void)self->ProcessAttention();
    318 }
    319 
    320 // Called on the worker thread.
    321 nsresult BackgroundFileSaver::ProcessAttention() {
    322  nsresult rv;
    323 
    324  // This function is called whenever the attention of the worker thread has
    325  // been requested.  This may happen in these cases:
    326  // * We are about to start the copy for the first time.  In this case, we are
    327  //   called from an event posted on the worker thread from the control thread
    328  //   by GetWorkerThreadAttention, and mAsyncCopyContext is null.
    329  // * We have interrupted the copy for some reason.  In this case, we are
    330  //   called by AsyncCopyCallback, and mAsyncCopyContext is null.
    331  // * We are currently executing ProcessStateChange, and attention is requested
    332  //   by the control thread, for example because SetTarget or Finish have been
    333  //   called.  In this case, we are called from from an event posted through
    334  //   GetWorkerThreadAttention.  While mAsyncCopyContext was always null when
    335  //   the event was posted, at this point mAsyncCopyContext may not be null
    336  //   anymore, because ProcessStateChange may have started the copy before the
    337  //   event that called this function was processed on the worker thread.
    338  // If mAsyncCopyContext is not null, we interrupt the copy and re-enter
    339  // through AsyncCopyCallback.  This allows us to check if, for instance, we
    340  // should rename the target file.  We will then restart the copy if needed.
    341 
    342  // mAsyncCopyContext is only written on the worker thread (which we are on)
    343  MOZ_ASSERT(!NS_IsMainThread());
    344  {
    345    // Even though we're the only thread that writes this, we have to take the
    346    // lock
    347    MutexAutoLock lock(mLock);
    348    if (mAsyncCopyContext) {
    349      NS_CancelAsyncCopy(mAsyncCopyContext, NS_ERROR_ABORT);
    350      return NS_OK;
    351    }
    352  }
    353  // Use the current shared state to determine the next operation to execute.
    354  rv = ProcessStateChange();
    355  if (NS_FAILED(rv)) {
    356    // If something failed while processing, terminate the operation now.
    357    {
    358      MutexAutoLock lock(mLock);
    359 
    360      if (NS_SUCCEEDED(mStatus)) {
    361        mStatus = rv;
    362      }
    363    }
    364    // Ensure we notify completion now that the operation failed.
    365    CheckCompletion();
    366  }
    367 
    368  return NS_OK;
    369 }
    370 
    371 // Called on the worker thread.
    372 nsresult BackgroundFileSaver::ProcessStateChange() {
    373  nsresult rv;
    374 
    375  // We might have been notified because the operation is complete, verify.
    376  if (CheckCompletion()) {
    377    return NS_OK;
    378  }
    379 
    380  // Get a copy of the current shared state for the worker thread.
    381  nsCOMPtr<nsIFile> initialTarget;
    382  bool initialTargetKeepPartial;
    383  nsCOMPtr<nsIFile> renamedTarget;
    384  bool renamedTargetKeepPartial;
    385  bool sha256Enabled;
    386  bool append;
    387  {
    388    MutexAutoLock lock(mLock);
    389 
    390    initialTarget = mInitialTarget;
    391    initialTargetKeepPartial = mInitialTargetKeepPartial;
    392    renamedTarget = mRenamedTarget;
    393    renamedTargetKeepPartial = mRenamedTargetKeepPartial;
    394    sha256Enabled = mSha256Enabled;
    395    append = mAppend;
    396 
    397    // From now on, another attention event needs to be posted if state changes.
    398    mWorkerThreadAttentionRequested = false;
    399  }
    400 
    401  // The initial target can only be null if it has never been assigned.  In this
    402  // case, there is nothing to do since we never created any output file.
    403  if (!initialTarget) {
    404    return NS_OK;
    405  }
    406 
    407  // Determine if we are processing the attention request for the first time.
    408  bool isContinuation = !!mActualTarget;
    409  if (!isContinuation) {
    410    // Assign the target file for the first time.
    411    mActualTarget = initialTarget;
    412    mActualTargetKeepPartial = initialTargetKeepPartial;
    413  }
    414 
    415  // Verify whether we have actually been instructed to use a different file.
    416  // This may happen the first time this function is executed, if SetTarget was
    417  // called two times before the worker thread processed the attention request.
    418  bool equalToCurrent = false;
    419  if (renamedTarget) {
    420    rv = mActualTarget->Equals(renamedTarget, &equalToCurrent);
    421    NS_ENSURE_SUCCESS(rv, rv);
    422    if (!equalToCurrent) {
    423      // If we were asked to rename the file but the initial file did not exist,
    424      // we simply create the file in the renamed location.  We avoid this check
    425      // if we have already started writing the output file ourselves.
    426      bool exists = true;
    427      if (!isContinuation) {
    428        rv = mActualTarget->Exists(&exists);
    429        NS_ENSURE_SUCCESS(rv, rv);
    430      }
    431      if (exists) {
    432        // We are moving the previous target file to a different location.
    433        nsCOMPtr<nsIFile> renamedTargetParentDir;
    434        rv = renamedTarget->GetParent(getter_AddRefs(renamedTargetParentDir));
    435        NS_ENSURE_SUCCESS(rv, rv);
    436 
    437        nsAutoString renamedTargetName;
    438        rv = renamedTarget->GetLeafName(renamedTargetName);
    439        NS_ENSURE_SUCCESS(rv, rv);
    440 
    441        // We must delete any existing target file before moving the current
    442        // one.
    443        rv = renamedTarget->Exists(&exists);
    444        NS_ENSURE_SUCCESS(rv, rv);
    445        if (exists) {
    446          rv = renamedTarget->Remove(false);
    447          NS_ENSURE_SUCCESS(rv, rv);
    448        }
    449 
    450        // Move the file.  If this fails, we still reference the original file
    451        // in mActualTarget, so that it is deleted if requested.  If this
    452        // succeeds, the nsIFile instance referenced by mActualTarget mutates
    453        // and starts pointing to the new file, but we'll discard the reference.
    454        rv = mActualTarget->MoveTo(renamedTargetParentDir, renamedTargetName);
    455        NS_ENSURE_SUCCESS(rv, rv);
    456      }
    457 
    458      // We should not only update the mActualTarget with renameTarget when
    459      // they point to the different files.
    460      // In this way, if mActualTarget and renamedTarget point to the same file
    461      // with different addresses, "CheckCompletion()" will return false
    462      // forever.
    463    }
    464 
    465    // Update mActualTarget with renameTarget,
    466    // even if they point to the same file.
    467    mActualTarget = renamedTarget;
    468    mActualTargetKeepPartial = renamedTargetKeepPartial;
    469  }
    470 
    471  // Notify if the target file name actually changed.
    472  if (!equalToCurrent) {
    473    // We must clone the nsIFile instance because mActualTarget is not
    474    // immutable, it may change if the target is renamed later.
    475    nsCOMPtr<nsIFile> actualTargetToNotify;
    476    rv = mActualTarget->Clone(getter_AddRefs(actualTargetToNotify));
    477    NS_ENSURE_SUCCESS(rv, rv);
    478 
    479    RefPtr<NotifyTargetChangeRunnable> event =
    480        new NotifyTargetChangeRunnable(this, actualTargetToNotify);
    481    NS_ENSURE_TRUE(event, NS_ERROR_FAILURE);
    482 
    483    rv = mControlEventTarget->Dispatch(event, NS_DISPATCH_NORMAL);
    484    NS_ENSURE_SUCCESS(rv, rv);
    485  }
    486 
    487  if (isContinuation) {
    488    // The pending rename operation might be the last task before finishing. We
    489    // may return here only if we have already created the target file.
    490    if (CheckCompletion()) {
    491      return NS_OK;
    492    }
    493 
    494    // Even if the operation did not complete, the pipe input stream may be
    495    // empty and may have been closed already.  We detect this case using the
    496    // Available property, because it never returns an error if there is more
    497    // data to be consumed.  If the pipe input stream is closed, we just exit
    498    // and wait for more calls like SetTarget or Finish to be invoked on the
    499    // control thread.  However, we still truncate the file or create the
    500    // initial digest context if we are expected to do that.
    501    uint64_t available;
    502    rv = mPipeInputStream->Available(&available);
    503    if (NS_FAILED(rv)) {
    504      return NS_OK;
    505    }
    506  }
    507 
    508  // Create the digest if requested and NSS hasn't been shut down.
    509  if (sha256Enabled && mDigest.isNothing()) {
    510    mDigest.emplace(Digest());
    511    mDigest->Begin(SEC_OID_SHA256);
    512  }
    513 
    514  // When we are requested to append to an existing file, we should read the
    515  // existing data and ensure we include it as part of the final hash.
    516  if (mDigest.isSome() && append && !isContinuation) {
    517    nsCOMPtr<nsIInputStream> inputStream;
    518    rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), mActualTarget,
    519                                    PR_RDONLY | nsIFile::OS_READAHEAD);
    520    if (rv != NS_ERROR_FILE_NOT_FOUND) {
    521      NS_ENSURE_SUCCESS(rv, rv);
    522 
    523      // Try to clean up the inputStream if an error occurs.
    524      auto closeGuard =
    525          mozilla::MakeScopeExit([&] { (void)inputStream->Close(); });
    526 
    527      char buffer[BUFFERED_IO_SIZE];
    528      while (true) {
    529        uint32_t count;
    530        rv = inputStream->Read(buffer, BUFFERED_IO_SIZE, &count);
    531        NS_ENSURE_SUCCESS(rv, rv);
    532 
    533        if (count == 0) {
    534          // We reached the end of the file.
    535          break;
    536        }
    537 
    538        rv = mDigest->Update(BitwiseCast<unsigned char*, char*>(buffer), count);
    539        NS_ENSURE_SUCCESS(rv, rv);
    540 
    541        // The pending resume operation may have been cancelled by the control
    542        // thread while the worker thread was reading in the existing file.
    543        // Abort reading in the original file in that case, as the digest will
    544        // be discarded anyway.
    545        MutexAutoLock lock(mLock);
    546        if (NS_FAILED(mStatus)) {
    547          return NS_ERROR_ABORT;
    548        }
    549      }
    550 
    551      // Close explicitly to handle any errors.
    552      closeGuard.release();
    553      rv = inputStream->Close();
    554      NS_ENSURE_SUCCESS(rv, rv);
    555    }
    556  }
    557 
    558  // We will append to the initial target file only if it was requested by the
    559  // caller, but we'll always append on subsequent accesses to the target file.
    560  int32_t creationIoFlags;
    561  if (isContinuation) {
    562    creationIoFlags = PR_APPEND;
    563  } else {
    564    creationIoFlags = (append ? PR_APPEND : PR_TRUNCATE) | PR_CREATE_FILE;
    565  }
    566 
    567  // Create the target file, or append to it if we already started writing it.
    568  // The 0600 permissions are used while the file is being downloaded, and for
    569  // interrupted downloads. Those may be located in the system temporary
    570  // directory, as well as the target directory, and generally have a ".part"
    571  // extension. Those part files should never be group or world-writable even
    572  // if the umask allows it.
    573  nsCOMPtr<nsIOutputStream> outputStream;
    574  rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), mActualTarget,
    575                                   PR_WRONLY | creationIoFlags, 0600);
    576  NS_ENSURE_SUCCESS(rv, rv);
    577 
    578  nsCOMPtr<nsIOutputStream> bufferedStream;
    579  rv = NS_NewBufferedOutputStream(getter_AddRefs(bufferedStream),
    580                                  outputStream.forget(), BUFFERED_IO_SIZE);
    581  NS_ENSURE_SUCCESS(rv, rv);
    582  outputStream = bufferedStream;
    583 
    584  // Wrap the output stream so that it feeds the digest if needed.
    585  if (mDigest.isSome()) {
    586    // Constructing the DigestOutputStream cannot fail. Passing mDigest
    587    // to DigestOutputStream is safe, because BackgroundFileSaver always
    588    // outlives the outputStream. BackgroundFileSaver is reference-counted
    589    // before the call to AsyncCopy, and mDigest is never destroyed
    590    // before AsyncCopyCallback.
    591    outputStream = new DigestOutputStream(outputStream, mDigest.ref());
    592  }
    593 
    594  // Start copying our input to the target file.  No errors can be raised past
    595  // this point if the copy starts, since they should be handled by the thread.
    596  {
    597    MutexAutoLock lock(mLock);
    598 
    599    rv = NS_AsyncCopy(mPipeInputStream, outputStream, mBackgroundET,
    600                      NS_ASYNCCOPY_VIA_READSEGMENTS, 4096, AsyncCopyCallback,
    601                      this, false, true, getter_AddRefs(mAsyncCopyContext),
    602                      GetProgressCallback());
    603    if (NS_FAILED(rv)) {
    604      NS_WARNING("NS_AsyncCopy failed.");
    605      mAsyncCopyContext = nullptr;
    606      return rv;
    607    }
    608  }
    609 
    610  // If the operation succeeded, we must ensure that we keep this object alive
    611  // for the entire duration of the copy, since only the raw pointer will be
    612  // provided as the argument of the AsyncCopyCallback function.  We can add the
    613  // reference now, after NS_AsyncCopy returned, because it always starts
    614  // processing asynchronously, and there is no risk that the callback is
    615  // invoked before we reach this point.  If the operation failed instead, then
    616  // AsyncCopyCallback will never be called.
    617  NS_ADDREF_THIS();
    618 
    619  return NS_OK;
    620 }
    621 
    622 // Called on the worker thread.
    623 bool BackgroundFileSaver::CheckCompletion() {
    624  nsresult rv;
    625 
    626  bool failed = true;
    627  {
    628    MutexAutoLock lock(mLock);
    629    MOZ_ASSERT(!mAsyncCopyContext,
    630               "Should not be copying when checking completion conditions.");
    631 
    632    if (mComplete) {
    633      return true;
    634    }
    635 
    636    // If an error occurred, we don't need to do the checks in this code block,
    637    // and the operation can be completed immediately with a failure code.
    638    if (NS_SUCCEEDED(mStatus)) {
    639      failed = false;
    640 
    641      // We did not incur in an error, so we must determine if we can stop now.
    642      // If the Finish method has not been called, we can just continue now.
    643      if (!mFinishRequested) {
    644        return false;
    645      }
    646 
    647      // We can only stop when all the operations requested by the control
    648      // thread have been processed.  First, we check whether we have processed
    649      // the first SetTarget call, if any.  Then, we check whether we have
    650      // processed any rename requested by subsequent SetTarget calls.
    651      if ((mInitialTarget && !mActualTarget) ||
    652          (mRenamedTarget && mRenamedTarget != mActualTarget)) {
    653        return false;
    654      }
    655 
    656      // If we still have data to write to the output file, allow the copy
    657      // operation to resume.  The Available getter may return an error if one
    658      // of the pipe's streams has been already closed.
    659      uint64_t available;
    660      rv = mPipeInputStream->Available(&available);
    661      if (NS_SUCCEEDED(rv) && available != 0) {
    662        return false;
    663      }
    664    }
    665 
    666    mComplete = true;
    667  }
    668 
    669  // Ensure we notify completion now that the operation finished.
    670  // Do a best-effort attempt to remove the file if required.
    671  if (failed && mActualTarget && !mActualTargetKeepPartial) {
    672    (void)mActualTarget->Remove(false);
    673  }
    674 
    675  // Finish computing the hash
    676  if (!failed && mDigest.isSome()) {
    677    nsTArray<uint8_t> outArray;
    678    rv = mDigest->End(outArray);
    679    if (NS_SUCCEEDED(rv)) {
    680      MutexAutoLock lock(mLock);
    681      mSha256 = nsDependentCSubstring(
    682          BitwiseCast<char*, uint8_t*>(outArray.Elements()), outArray.Length());
    683    }
    684  }
    685 
    686  // Compute the signature of the binary. ExtractSignatureInfo doesn't do
    687  // anything on non-Windows platforms except return an empty nsIArray.
    688  if (!failed && mActualTarget) {
    689    nsString filePath;
    690    mActualTarget->GetTarget(filePath);
    691    nsresult rv = ExtractSignatureInfo(filePath);
    692    if (NS_FAILED(rv)) {
    693      LOG(("Unable to extract signature information [this = %p].", this));
    694    } else {
    695      LOG(("Signature extraction success! [this = %p]", this));
    696    }
    697  }
    698 
    699  // Post an event to notify that the operation completed.
    700  if (NS_FAILED(mControlEventTarget->Dispatch(
    701          NewRunnableMethod("BackgroundFileSaver::NotifySaveComplete", this,
    702                            &BackgroundFileSaver::NotifySaveComplete),
    703          NS_DISPATCH_NORMAL))) {
    704    NS_WARNING("Unable to post completion event to the control thread.");
    705  }
    706 
    707  return true;
    708 }
    709 
    710 // Called on the control thread.
    711 nsresult BackgroundFileSaver::NotifyTargetChange(nsIFile* aTarget) {
    712  if (mObserver) {
    713    (void)mObserver->OnTargetChange(this, aTarget);
    714  }
    715 
    716  return NS_OK;
    717 }
    718 
    719 // Called on the control thread.
    720 nsresult BackgroundFileSaver::NotifySaveComplete() {
    721  MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
    722 
    723  nsresult status;
    724  {
    725    MutexAutoLock lock(mLock);
    726    status = mStatus;
    727  }
    728 
    729  if (mObserver) {
    730    (void)mObserver->OnSaveComplete(this, status);
    731    // If mObserver keeps alive an enclosure that captures `this`, we'll have a
    732    // cycle that won't be caught by the cycle-collector, so we need to break it
    733    // when we're done here (see bug 1444265).
    734    mObserver = nullptr;
    735  }
    736 
    737  // At this point, the worker thread will not process any more events, and we
    738  // can shut it down.  Shutting down a thread may re-enter the event loop on
    739  // this thread.  This is not a problem in this case, since this function is
    740  // called by a top-level event itself, and we have already invoked the
    741  // completion observer callback.  Re-entering the loop can only delay the
    742  // final release and destruction of this saver object, since we are keeping a
    743  // reference to it through the event object.
    744  mBackgroundET = nullptr;
    745 
    746  sThreadCount--;
    747 
    748  // When there are no more active downloads, we consider the download session
    749  // finished. We record the maximum number of concurrent downloads reached
    750  // during the session in a telemetry histogram, and we reset the maximum
    751  // thread counter for the next download session
    752  if (sThreadCount == 0) {
    753    glean::network::backgroundfilesaver_thread_count.AccumulateSingleSample(
    754        sTelemetryMaxThreadCount);
    755    sTelemetryMaxThreadCount = 0;
    756  }
    757 
    758  return NS_OK;
    759 }
    760 
    761 nsresult BackgroundFileSaver::ExtractSignatureInfo(const nsAString& filePath) {
    762  MOZ_ASSERT(!NS_IsMainThread(), "Cannot extract signature on main thread");
    763  {
    764    MutexAutoLock lock(mLock);
    765    if (!mSignatureInfoEnabled) {
    766      return NS_OK;
    767    }
    768  }
    769 #ifdef XP_WIN
    770  // Setup the file to check.
    771  WINTRUST_FILE_INFO fileToCheck = {0};
    772  fileToCheck.cbStruct = sizeof(WINTRUST_FILE_INFO);
    773  fileToCheck.pcwszFilePath = filePath.Data();
    774  fileToCheck.hFile = nullptr;
    775  fileToCheck.pgKnownSubject = nullptr;
    776 
    777  // We want to check it is signed and trusted.
    778  WINTRUST_DATA trustData = {0};
    779  trustData.cbStruct = sizeof(trustData);
    780  trustData.pPolicyCallbackData = nullptr;
    781  trustData.pSIPClientData = nullptr;
    782  trustData.dwUIChoice = WTD_UI_NONE;
    783  trustData.fdwRevocationChecks = WTD_REVOKE_NONE;
    784  trustData.dwUnionChoice = WTD_CHOICE_FILE;
    785  trustData.dwStateAction = WTD_STATEACTION_VERIFY;
    786  trustData.hWVTStateData = nullptr;
    787  trustData.pwszURLReference = nullptr;
    788  // Disallow revocation checks over the network
    789  trustData.dwProvFlags = WTD_CACHE_ONLY_URL_RETRIEVAL;
    790  // no UI
    791  trustData.dwUIContext = 0;
    792  trustData.pFile = &fileToCheck;
    793 
    794  // The WINTRUST_ACTION_GENERIC_VERIFY_V2 policy verifies that the certificate
    795  // chains up to a trusted root CA and has appropriate permissions to sign
    796  // code.
    797  GUID policyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2;
    798  // Check if the file is signed by something that is trusted. If the file is
    799  // not signed, this is a no-op.
    800  LONG ret = WinVerifyTrust(nullptr, &policyGUID, &trustData);
    801  CRYPT_PROVIDER_DATA* cryptoProviderData = nullptr;
    802  // According to the Windows documentation, we should check against 0 instead
    803  // of ERROR_SUCCESS, which is an HRESULT.
    804  if (ret == 0) {
    805    cryptoProviderData = WTHelperProvDataFromStateData(trustData.hWVTStateData);
    806  }
    807  if (cryptoProviderData) {
    808    // Lock because signature information is read on the main thread.
    809    MutexAutoLock lock(mLock);
    810    LOG(("Downloaded trusted and signed file [this = %p].", this));
    811    // A binary may have multiple signers. Each signer may have multiple certs
    812    // in the chain.
    813    for (DWORD i = 0; i < cryptoProviderData->csSigners; ++i) {
    814      const CERT_CHAIN_CONTEXT* certChainContext =
    815          cryptoProviderData->pasSigners[i].pChainContext;
    816      if (!certChainContext) {
    817        break;
    818      }
    819      for (DWORD j = 0; j < certChainContext->cChain; ++j) {
    820        const CERT_SIMPLE_CHAIN* certSimpleChain =
    821            certChainContext->rgpChain[j];
    822        if (!certSimpleChain) {
    823          break;
    824        }
    825 
    826        nsTArray<nsTArray<uint8_t>> certList;
    827        bool extractionSuccess = true;
    828        for (DWORD k = 0; k < certSimpleChain->cElement; ++k) {
    829          CERT_CHAIN_ELEMENT* certChainElement = certSimpleChain->rgpElement[k];
    830          if (certChainElement->pCertContext->dwCertEncodingType !=
    831              X509_ASN_ENCODING) {
    832            continue;
    833          }
    834          nsTArray<uint8_t> cert;
    835          cert.AppendElements(certChainElement->pCertContext->pbCertEncoded,
    836                              certChainElement->pCertContext->cbCertEncoded);
    837          certList.AppendElement(std::move(cert));
    838        }
    839        if (extractionSuccess) {
    840          mSignatureInfo.AppendElement(std::move(certList));
    841        }
    842      }
    843    }
    844    // Free the provider data if cryptoProviderData is not null.
    845    trustData.dwStateAction = WTD_STATEACTION_CLOSE;
    846    WinVerifyTrust(nullptr, &policyGUID, &trustData);
    847  } else {
    848    LOG(("Downloaded unsigned or untrusted file [this = %p].", this));
    849  }
    850 #endif
    851  return NS_OK;
    852 }
    853 
    854 ////////////////////////////////////////////////////////////////////////////////
    855 //// BackgroundFileSaverOutputStream
    856 
    857 NS_IMPL_ISUPPORTS(BackgroundFileSaverOutputStream, nsIBackgroundFileSaver,
    858                  nsIOutputStream, nsIAsyncOutputStream,
    859                  nsIOutputStreamCallback)
    860 
    861 BackgroundFileSaverOutputStream::BackgroundFileSaverOutputStream()
    862    : mAsyncWaitCallback(nullptr) {}
    863 
    864 bool BackgroundFileSaverOutputStream::HasInfiniteBuffer() { return false; }
    865 
    866 nsAsyncCopyProgressFun BackgroundFileSaverOutputStream::GetProgressCallback() {
    867  return nullptr;
    868 }
    869 
    870 NS_IMETHODIMP
    871 BackgroundFileSaverOutputStream::Close() { return mPipeOutputStream->Close(); }
    872 
    873 NS_IMETHODIMP
    874 BackgroundFileSaverOutputStream::Flush() { return mPipeOutputStream->Flush(); }
    875 
    876 NS_IMETHODIMP
    877 BackgroundFileSaverOutputStream::StreamStatus() {
    878  return mPipeOutputStream->StreamStatus();
    879 }
    880 
    881 NS_IMETHODIMP
    882 BackgroundFileSaverOutputStream::Write(const char* aBuf, uint32_t aCount,
    883                                       uint32_t* _retval) {
    884  return mPipeOutputStream->Write(aBuf, aCount, _retval);
    885 }
    886 
    887 NS_IMETHODIMP
    888 BackgroundFileSaverOutputStream::WriteFrom(nsIInputStream* aFromStream,
    889                                           uint32_t aCount, uint32_t* _retval) {
    890  return mPipeOutputStream->WriteFrom(aFromStream, aCount, _retval);
    891 }
    892 
    893 NS_IMETHODIMP
    894 BackgroundFileSaverOutputStream::WriteSegments(nsReadSegmentFun aReader,
    895                                               void* aClosure, uint32_t aCount,
    896                                               uint32_t* _retval) {
    897  return mPipeOutputStream->WriteSegments(aReader, aClosure, aCount, _retval);
    898 }
    899 
    900 NS_IMETHODIMP
    901 BackgroundFileSaverOutputStream::IsNonBlocking(bool* _retval) {
    902  return mPipeOutputStream->IsNonBlocking(_retval);
    903 }
    904 
    905 NS_IMETHODIMP
    906 BackgroundFileSaverOutputStream::CloseWithStatus(nsresult reason) {
    907  return mPipeOutputStream->CloseWithStatus(reason);
    908 }
    909 
    910 NS_IMETHODIMP
    911 BackgroundFileSaverOutputStream::AsyncWait(nsIOutputStreamCallback* aCallback,
    912                                           uint32_t aFlags,
    913                                           uint32_t aRequestedCount,
    914                                           nsIEventTarget* aEventTarget) {
    915  NS_ENSURE_STATE(!mAsyncWaitCallback);
    916 
    917  mAsyncWaitCallback = aCallback;
    918 
    919  return mPipeOutputStream->AsyncWait(this, aFlags, aRequestedCount,
    920                                      aEventTarget);
    921 }
    922 
    923 NS_IMETHODIMP
    924 BackgroundFileSaverOutputStream::OnOutputStreamReady(
    925    nsIAsyncOutputStream* aStream) {
    926  NS_ENSURE_STATE(mAsyncWaitCallback);
    927 
    928  nsCOMPtr<nsIOutputStreamCallback> asyncWaitCallback = nullptr;
    929  asyncWaitCallback.swap(mAsyncWaitCallback);
    930 
    931  return asyncWaitCallback->OnOutputStreamReady(this);
    932 }
    933 
    934 ////////////////////////////////////////////////////////////////////////////////
    935 //// BackgroundFileSaverStreamListener
    936 
    937 NS_IMPL_ISUPPORTS(BackgroundFileSaverStreamListener, nsIBackgroundFileSaver,
    938                  nsIRequestObserver, nsIStreamListener)
    939 
    940 bool BackgroundFileSaverStreamListener::HasInfiniteBuffer() { return true; }
    941 
    942 nsAsyncCopyProgressFun
    943 BackgroundFileSaverStreamListener::GetProgressCallback() {
    944  return AsyncCopyProgressCallback;
    945 }
    946 
    947 NS_IMETHODIMP
    948 BackgroundFileSaverStreamListener::OnStartRequest(nsIRequest* aRequest) {
    949  NS_ENSURE_ARG(aRequest);
    950 
    951  return NS_OK;
    952 }
    953 
    954 NS_IMETHODIMP
    955 BackgroundFileSaverStreamListener::OnStopRequest(nsIRequest* aRequest,
    956                                                 nsresult aStatusCode) {
    957  // If an error occurred, cancel the operation immediately.  On success, wait
    958  // until the caller has determined whether the file should be renamed.
    959  if (NS_FAILED(aStatusCode)) {
    960    Finish(aStatusCode);
    961  }
    962 
    963  return NS_OK;
    964 }
    965 
    966 NS_IMETHODIMP
    967 BackgroundFileSaverStreamListener::OnDataAvailable(nsIRequest* aRequest,
    968                                                   nsIInputStream* aInputStream,
    969                                                   uint64_t aOffset,
    970                                                   uint32_t aCount) {
    971  nsresult rv;
    972 
    973  NS_ENSURE_ARG(aRequest);
    974 
    975  // Read the requested data.  Since the pipe has an infinite buffer, we don't
    976  // expect any write error to occur here.
    977  uint32_t writeCount;
    978  rv = mPipeOutputStream->WriteFrom(aInputStream, aCount, &writeCount);
    979  NS_ENSURE_SUCCESS(rv, rv);
    980 
    981  // If reading from the input stream fails for any reason, the pipe will return
    982  // a success code, but without reading all the data.  Since we should be able
    983  // to read the requested data when OnDataAvailable is called, raise an error.
    984  if (writeCount < aCount) {
    985    NS_WARNING("Reading from the input stream should not have failed.");
    986    return NS_ERROR_UNEXPECTED;
    987  }
    988 
    989  bool stateChanged = false;
    990  {
    991    MutexAutoLock lock(mSuspensionLock);
    992 
    993    if (!mReceivedTooMuchData) {
    994      uint64_t available;
    995      nsresult rv = mPipeInputStream->Available(&available);
    996      if (NS_SUCCEEDED(rv) && available > REQUEST_SUSPEND_AT) {
    997        mReceivedTooMuchData = true;
    998        mRequest = aRequest;
    999        stateChanged = true;
   1000      }
   1001    }
   1002  }
   1003 
   1004  if (stateChanged) {
   1005    NotifySuspendOrResume();
   1006  }
   1007 
   1008  return NS_OK;
   1009 }
   1010 
   1011 // Called on the worker thread.
   1012 // static
   1013 void BackgroundFileSaverStreamListener::AsyncCopyProgressCallback(
   1014    void* aClosure, uint32_t aCount) {
   1015  BackgroundFileSaverStreamListener* self =
   1016      (BackgroundFileSaverStreamListener*)aClosure;
   1017 
   1018  // Wait if the control thread is in the process of suspending or resuming.
   1019  MutexAutoLock lock(self->mSuspensionLock);
   1020 
   1021  // This function is called when some bytes are consumed by NS_AsyncCopy.  Each
   1022  // time this happens, verify if a suspended request should be resumed, because
   1023  // we have now consumed enough data.
   1024  if (self->mReceivedTooMuchData) {
   1025    uint64_t available;
   1026    nsresult rv = self->mPipeInputStream->Available(&available);
   1027    if (NS_FAILED(rv) || available < REQUEST_RESUME_AT) {
   1028      self->mReceivedTooMuchData = false;
   1029 
   1030      // Post an event to verify if the request should be resumed.
   1031      if (NS_FAILED(self->mControlEventTarget->Dispatch(
   1032              NewRunnableMethod(
   1033                  "BackgroundFileSaverStreamListener::NotifySuspendOrResume",
   1034                  self,
   1035                  &BackgroundFileSaverStreamListener::NotifySuspendOrResume),
   1036              NS_DISPATCH_NORMAL))) {
   1037        NS_WARNING("Unable to post resume event to the control thread.");
   1038      }
   1039    }
   1040  }
   1041 }
   1042 
   1043 // Called on the control thread.
   1044 nsresult BackgroundFileSaverStreamListener::NotifySuspendOrResume() {
   1045  // Prevent the worker thread from changing state while processing.
   1046  MutexAutoLock lock(mSuspensionLock);
   1047 
   1048  if (mReceivedTooMuchData) {
   1049    if (!mRequestSuspended) {
   1050      // Try to suspend the request.  If this fails, don't try to resume later.
   1051      if (NS_SUCCEEDED(mRequest->Suspend())) {
   1052        mRequestSuspended = true;
   1053      } else {
   1054        NS_WARNING("Unable to suspend the request.");
   1055      }
   1056    }
   1057  } else {
   1058    if (mRequestSuspended) {
   1059      // Resume the request only if we succeeded in suspending it.
   1060      if (NS_SUCCEEDED(mRequest->Resume())) {
   1061        mRequestSuspended = false;
   1062      } else {
   1063        NS_WARNING("Unable to resume the request.");
   1064      }
   1065    }
   1066  }
   1067 
   1068  return NS_OK;
   1069 }
   1070 
   1071 ////////////////////////////////////////////////////////////////////////////////
   1072 //// DigestOutputStream
   1073 NS_IMPL_ISUPPORTS(DigestOutputStream, nsIOutputStream)
   1074 
   1075 DigestOutputStream::DigestOutputStream(nsIOutputStream* aStream,
   1076                                       Digest& aDigest)
   1077    : mOutputStream(aStream), mDigest(aDigest) {
   1078  MOZ_ASSERT(mOutputStream, "Can't have null output stream");
   1079 }
   1080 
   1081 NS_IMETHODIMP
   1082 DigestOutputStream::Close() { return mOutputStream->Close(); }
   1083 
   1084 NS_IMETHODIMP
   1085 DigestOutputStream::Flush() { return mOutputStream->Flush(); }
   1086 
   1087 NS_IMETHODIMP
   1088 DigestOutputStream::StreamStatus() { return mOutputStream->StreamStatus(); }
   1089 
   1090 NS_IMETHODIMP
   1091 DigestOutputStream::Write(const char* aBuf, uint32_t aCount, uint32_t* retval) {
   1092  nsresult rv = mDigest.Update(
   1093      BitwiseCast<const unsigned char*, const char*>(aBuf), aCount);
   1094  NS_ENSURE_SUCCESS(rv, rv);
   1095 
   1096  return mOutputStream->Write(aBuf, aCount, retval);
   1097 }
   1098 
   1099 NS_IMETHODIMP
   1100 DigestOutputStream::WriteFrom(nsIInputStream* aFromStream, uint32_t aCount,
   1101                              uint32_t* retval) {
   1102  // Not supported. We could read the stream to a buf, call DigestOp on the
   1103  // result, seek back and pass the stream on, but it's not worth it since our
   1104  // application (NS_AsyncCopy) doesn't invoke this on the sink.
   1105  MOZ_CRASH("DigestOutputStream::WriteFrom not implemented");
   1106 }
   1107 
   1108 NS_IMETHODIMP
   1109 DigestOutputStream::WriteSegments(nsReadSegmentFun aReader, void* aClosure,
   1110                                  uint32_t aCount, uint32_t* retval) {
   1111  MOZ_CRASH("DigestOutputStream::WriteSegments not implemented");
   1112 }
   1113 
   1114 NS_IMETHODIMP
   1115 DigestOutputStream::IsNonBlocking(bool* retval) {
   1116  return mOutputStream->IsNonBlocking(retval);
   1117 }
   1118 
   1119 #undef LOG_ENABLED
   1120 
   1121 }  // namespace net
   1122 }  // namespace mozilla