netwerk/base/src/BackgroundFileSaver.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/netwerk/base/src/BackgroundFileSaver.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1274 @@
     1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +/* vim: set ts=2 et sw=2 tw=80: */
     1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +#include "pk11pub.h"
    1.11 +#include "prlog.h"
    1.12 +#include "ScopedNSSTypes.h"
    1.13 +#include "secoidt.h"
    1.14 +
    1.15 +#include "nsIAsyncInputStream.h"
    1.16 +#include "nsIFile.h"
    1.17 +#include "nsIMutableArray.h"
    1.18 +#include "nsIPipe.h"
    1.19 +#include "nsIX509Cert.h"
    1.20 +#include "nsIX509CertDB.h"
    1.21 +#include "nsIX509CertList.h"
    1.22 +#include "nsCOMArray.h"
    1.23 +#include "nsNetUtil.h"
    1.24 +#include "nsThreadUtils.h"
    1.25 +
    1.26 +#include "BackgroundFileSaver.h"
    1.27 +#include "mozilla/Telemetry.h"
    1.28 +
    1.29 +#ifdef XP_WIN
    1.30 +#include <windows.h>
    1.31 +#include <softpub.h>
    1.32 +#include <wintrust.h>
    1.33 +#endif // XP_WIN
    1.34 +
    1.35 +namespace mozilla {
    1.36 +namespace net {
    1.37 +
    1.38 +// NSPR_LOG_MODULES=BackgroundFileSaver:5
    1.39 +#if defined(PR_LOGGING)
    1.40 +PRLogModuleInfo *BackgroundFileSaver::prlog = nullptr;
    1.41 +#define LOG(args) PR_LOG(BackgroundFileSaver::prlog, PR_LOG_DEBUG, args)
    1.42 +#define LOG_ENABLED() PR_LOG_TEST(BackgroundFileSaver::prlog, 4)
    1.43 +#else
    1.44 +#define LOG(args)
    1.45 +#define LOG_ENABLED() (false)
    1.46 +#endif
    1.47 +
    1.48 +////////////////////////////////////////////////////////////////////////////////
    1.49 +//// Globals
    1.50 +
    1.51 +/**
    1.52 + * Buffer size for writing to the output file or reading from the input file.
    1.53 + */
    1.54 +#define BUFFERED_IO_SIZE (1024 * 32)
    1.55 +
    1.56 +/**
    1.57 + * When this upper limit is reached, the original request is suspended.
    1.58 + */
    1.59 +#define REQUEST_SUSPEND_AT (1024 * 1024 * 4)
    1.60 +
    1.61 +/**
    1.62 + * When this lower limit is reached, the original request is resumed.
    1.63 + */
    1.64 +#define REQUEST_RESUME_AT (1024 * 1024 * 2)
    1.65 +
    1.66 +////////////////////////////////////////////////////////////////////////////////
    1.67 +//// NotifyTargetChangeRunnable
    1.68 +
    1.69 +/**
    1.70 + * Runnable object used to notify the control thread that file contents will now
    1.71 + * be saved to the specified file.
    1.72 + */
    1.73 +class NotifyTargetChangeRunnable MOZ_FINAL : public nsRunnable
    1.74 +{
    1.75 +public:
    1.76 +  NotifyTargetChangeRunnable(BackgroundFileSaver *aSaver, nsIFile *aTarget)
    1.77 +  : mSaver(aSaver)
    1.78 +  , mTarget(aTarget)
    1.79 +  {
    1.80 +  }
    1.81 +
    1.82 +  NS_IMETHODIMP Run()
    1.83 +  {
    1.84 +    return mSaver->NotifyTargetChange(mTarget);
    1.85 +  }
    1.86 +
    1.87 +private:
    1.88 +  nsRefPtr<BackgroundFileSaver> mSaver;
    1.89 +  nsCOMPtr<nsIFile> mTarget;
    1.90 +};
    1.91 +
    1.92 +////////////////////////////////////////////////////////////////////////////////
    1.93 +//// BackgroundFileSaver
    1.94 +
    1.95 +uint32_t BackgroundFileSaver::sThreadCount = 0;
    1.96 +uint32_t BackgroundFileSaver::sTelemetryMaxThreadCount = 0;
    1.97 +
    1.98 +BackgroundFileSaver::BackgroundFileSaver()
    1.99 +: mControlThread(nullptr)
   1.100 +, mWorkerThread(nullptr)
   1.101 +, mPipeOutputStream(nullptr)
   1.102 +, mPipeInputStream(nullptr)
   1.103 +, mObserver(nullptr)
   1.104 +, mLock("BackgroundFileSaver.mLock")
   1.105 +, mWorkerThreadAttentionRequested(false)
   1.106 +, mFinishRequested(false)
   1.107 +, mComplete(false)
   1.108 +, mStatus(NS_OK)
   1.109 +, mAppend(false)
   1.110 +, mInitialTarget(nullptr)
   1.111 +, mInitialTargetKeepPartial(false)
   1.112 +, mRenamedTarget(nullptr)
   1.113 +, mRenamedTargetKeepPartial(false)
   1.114 +, mAsyncCopyContext(nullptr)
   1.115 +, mSha256Enabled(false)
   1.116 +, mSignatureInfoEnabled(false)
   1.117 +, mActualTarget(nullptr)
   1.118 +, mActualTargetKeepPartial(false)
   1.119 +, mDigestContext(nullptr)
   1.120 +{
   1.121 +#if defined(PR_LOGGING)
   1.122 +  if (!prlog)
   1.123 +    prlog = PR_NewLogModule("BackgroundFileSaver");
   1.124 +#endif
   1.125 +  LOG(("Created BackgroundFileSaver [this = %p]", this));
   1.126 +}
   1.127 +
   1.128 +BackgroundFileSaver::~BackgroundFileSaver()
   1.129 +{
   1.130 +  LOG(("Destroying BackgroundFileSaver [this = %p]", this));
   1.131 +  nsNSSShutDownPreventionLock lock;
   1.132 +  if (isAlreadyShutDown()) {
   1.133 +    return;
   1.134 +  }
   1.135 +  destructorSafeDestroyNSSReference();
   1.136 +  shutdown(calledFromObject);
   1.137 +}
   1.138 +
   1.139 +void
   1.140 +BackgroundFileSaver::destructorSafeDestroyNSSReference()
   1.141 +{
   1.142 +  if (mDigestContext) {
   1.143 +    mozilla::psm::PK11_DestroyContext_true(mDigestContext.forget());
   1.144 +    mDigestContext = nullptr;
   1.145 +  }
   1.146 +}
   1.147 +
   1.148 +void
   1.149 +BackgroundFileSaver::virtualDestroyNSSReference()
   1.150 +{
   1.151 +  destructorSafeDestroyNSSReference();
   1.152 +}
   1.153 +
   1.154 +// Called on the control thread.
   1.155 +nsresult
   1.156 +BackgroundFileSaver::Init()
   1.157 +{
   1.158 +  MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
   1.159 +
   1.160 +  nsresult rv;
   1.161 +
   1.162 +  rv = NS_NewPipe2(getter_AddRefs(mPipeInputStream),
   1.163 +                   getter_AddRefs(mPipeOutputStream), true, true, 0,
   1.164 +                   HasInfiniteBuffer() ? UINT32_MAX : 0);
   1.165 +  NS_ENSURE_SUCCESS(rv, rv);
   1.166 +
   1.167 +  rv = NS_GetCurrentThread(getter_AddRefs(mControlThread));
   1.168 +  NS_ENSURE_SUCCESS(rv, rv);
   1.169 +
   1.170 +  rv = NS_NewThread(getter_AddRefs(mWorkerThread));
   1.171 +  NS_ENSURE_SUCCESS(rv, rv);
   1.172 +
   1.173 +  sThreadCount++;
   1.174 +  if (sThreadCount > sTelemetryMaxThreadCount) {
   1.175 +    sTelemetryMaxThreadCount = sThreadCount;
   1.176 +  }
   1.177 +
   1.178 +  return NS_OK;
   1.179 +}
   1.180 +
   1.181 +// Called on the control thread.
   1.182 +NS_IMETHODIMP
   1.183 +BackgroundFileSaver::GetObserver(nsIBackgroundFileSaverObserver **aObserver)
   1.184 +{
   1.185 +  NS_ENSURE_ARG_POINTER(aObserver);
   1.186 +  *aObserver = mObserver;
   1.187 +  NS_IF_ADDREF(*aObserver);
   1.188 +  return NS_OK;
   1.189 +}
   1.190 +
   1.191 +// Called on the control thread.
   1.192 +NS_IMETHODIMP
   1.193 +BackgroundFileSaver::SetObserver(nsIBackgroundFileSaverObserver *aObserver)
   1.194 +{
   1.195 +  mObserver = aObserver;
   1.196 +  return NS_OK;
   1.197 +}
   1.198 +
   1.199 +// Called on the control thread.
   1.200 +NS_IMETHODIMP
   1.201 +BackgroundFileSaver::EnableAppend()
   1.202 +{
   1.203 +  MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
   1.204 +
   1.205 +  MutexAutoLock lock(mLock);
   1.206 +  mAppend = true;
   1.207 +
   1.208 +  return NS_OK;
   1.209 +}
   1.210 +
   1.211 +// Called on the control thread.
   1.212 +NS_IMETHODIMP
   1.213 +BackgroundFileSaver::SetTarget(nsIFile *aTarget, bool aKeepPartial)
   1.214 +{
   1.215 +  NS_ENSURE_ARG(aTarget);
   1.216 +  {
   1.217 +    MutexAutoLock lock(mLock);
   1.218 +    if (!mInitialTarget) {
   1.219 +      aTarget->Clone(getter_AddRefs(mInitialTarget));
   1.220 +      mInitialTargetKeepPartial = aKeepPartial;
   1.221 +    } else {
   1.222 +      aTarget->Clone(getter_AddRefs(mRenamedTarget));
   1.223 +      mRenamedTargetKeepPartial = aKeepPartial;
   1.224 +    }
   1.225 +  }
   1.226 +
   1.227 +  // After the worker thread wakes up because attention is requested, it will
   1.228 +  // rename or create the target file as requested, and start copying data.
   1.229 +  return GetWorkerThreadAttention(true);
   1.230 +}
   1.231 +
   1.232 +// Called on the control thread.
   1.233 +NS_IMETHODIMP
   1.234 +BackgroundFileSaver::Finish(nsresult aStatus)
   1.235 +{
   1.236 +  nsresult rv;
   1.237 +
   1.238 +  // This will cause the NS_AsyncCopy operation, if it's in progress, to consume
   1.239 +  // all the data that is still in the pipe, and then finish.
   1.240 +  rv = mPipeOutputStream->Close();
   1.241 +  NS_ENSURE_SUCCESS(rv, rv);
   1.242 +
   1.243 +  // Ensure that, when we get attention from the worker thread, if no pending
   1.244 +  // rename operation is waiting, the operation will complete.
   1.245 +  {
   1.246 +    MutexAutoLock lock(mLock);
   1.247 +    mFinishRequested = true;
   1.248 +    if (NS_SUCCEEDED(mStatus)) {
   1.249 +      mStatus = aStatus;
   1.250 +    }
   1.251 +  }
   1.252 +
   1.253 +  // After the worker thread wakes up because attention is requested, it will
   1.254 +  // process the completion conditions, detect that completion is requested, and
   1.255 +  // notify the main thread of the completion.  If this function was called with
   1.256 +  // a success code, we wait for the copy to finish before processing the
   1.257 +  // completion conditions, otherwise we interrupt the copy immediately.
   1.258 +  return GetWorkerThreadAttention(NS_FAILED(aStatus));
   1.259 +}
   1.260 +
   1.261 +NS_IMETHODIMP
   1.262 +BackgroundFileSaver::EnableSha256()
   1.263 +{
   1.264 +  MOZ_ASSERT(NS_IsMainThread(),
   1.265 +             "Can't enable sha256 or initialize NSS off the main thread");
   1.266 +  // Ensure Personal Security Manager is initialized. This is required for
   1.267 +  // PK11_* operations to work.
   1.268 +  nsresult rv;
   1.269 +  nsCOMPtr<nsISupports> nssDummy = do_GetService("@mozilla.org/psm;1", &rv);
   1.270 +  NS_ENSURE_SUCCESS(rv, rv);
   1.271 +  mSha256Enabled = true;
   1.272 +  return NS_OK;
   1.273 +}
   1.274 +
   1.275 +NS_IMETHODIMP
   1.276 +BackgroundFileSaver::GetSha256Hash(nsACString& aHash)
   1.277 +{
   1.278 +  MOZ_ASSERT(NS_IsMainThread(), "Can't inspect sha256 off the main thread");
   1.279 +  // We acquire a lock because mSha256 is written on the worker thread.
   1.280 +  MutexAutoLock lock(mLock);
   1.281 +  if (mSha256.IsEmpty()) {
   1.282 +    return NS_ERROR_NOT_AVAILABLE;
   1.283 +  }
   1.284 +  aHash = mSha256;
   1.285 +  return NS_OK;
   1.286 +}
   1.287 +
   1.288 +NS_IMETHODIMP
   1.289 +BackgroundFileSaver::EnableSignatureInfo()
   1.290 +{
   1.291 +  MOZ_ASSERT(NS_IsMainThread(),
   1.292 +             "Can't enable signature extraction off the main thread");
   1.293 +  // Ensure Personal Security Manager is initialized.
   1.294 +  nsresult rv;
   1.295 +  nsCOMPtr<nsISupports> nssDummy = do_GetService("@mozilla.org/psm;1", &rv);
   1.296 +  NS_ENSURE_SUCCESS(rv, rv);
   1.297 +  mSignatureInfoEnabled = true;
   1.298 +  return NS_OK;
   1.299 +}
   1.300 +
   1.301 +NS_IMETHODIMP
   1.302 +BackgroundFileSaver::GetSignatureInfo(nsIArray** aSignatureInfo)
   1.303 +{
   1.304 +  MOZ_ASSERT(NS_IsMainThread(), "Can't inspect signature off the main thread");
   1.305 +  // We acquire a lock because mSignatureInfo is written on the worker thread.
   1.306 +  MutexAutoLock lock(mLock);
   1.307 +  if (!mComplete || !mSignatureInfoEnabled) {
   1.308 +    return NS_ERROR_NOT_AVAILABLE;
   1.309 +  }
   1.310 +  nsCOMPtr<nsIMutableArray> sigArray = do_CreateInstance(NS_ARRAY_CONTRACTID);
   1.311 +  for (int i = 0; i < mSignatureInfo.Count(); ++i) {
   1.312 +    sigArray->AppendElement(mSignatureInfo[i], false);
   1.313 +  }
   1.314 +  *aSignatureInfo = sigArray;
   1.315 +  NS_IF_ADDREF(*aSignatureInfo);
   1.316 +  return NS_OK;
   1.317 +}
   1.318 +
   1.319 +// Called on the control thread.
   1.320 +nsresult
   1.321 +BackgroundFileSaver::GetWorkerThreadAttention(bool aShouldInterruptCopy)
   1.322 +{
   1.323 +  nsresult rv;
   1.324 +
   1.325 +  MutexAutoLock lock(mLock);
   1.326 +
   1.327 +  // We only require attention one time.  If this function is called two times
   1.328 +  // before the worker thread wakes up, and the first has aShouldInterruptCopy
   1.329 +  // false and the second true, we won't forcibly interrupt the copy from the
   1.330 +  // control thread.  However, that never happens, because calling Finish with a
   1.331 +  // success code is the only case that may result in aShouldInterruptCopy being
   1.332 +  // false.  In that case, we won't call this function again, because consumers
   1.333 +  // should not invoke other methods on the control thread after calling Finish.
   1.334 +  // And in any case, Finish already closes one end of the pipe, causing the
   1.335 +  // copy to finish properly on its own.
   1.336 +  if (mWorkerThreadAttentionRequested) {
   1.337 +    return NS_OK;
   1.338 +  }
   1.339 +
   1.340 +  if (!mAsyncCopyContext) {
   1.341 +    // Copy is not in progress, post an event to handle the change manually.
   1.342 +    nsCOMPtr<nsIRunnable> event =
   1.343 +      NS_NewRunnableMethod(this, &BackgroundFileSaver::ProcessAttention);
   1.344 +    NS_ENSURE_TRUE(event, NS_ERROR_FAILURE);
   1.345 +
   1.346 +    rv = mWorkerThread->Dispatch(event, NS_DISPATCH_NORMAL);
   1.347 +    NS_ENSURE_SUCCESS(rv, rv);
   1.348 +  } else if (aShouldInterruptCopy) {
   1.349 +    // Interrupt the copy.  The copy will be resumed, if needed, by the
   1.350 +    // ProcessAttention function, invoked by the AsyncCopyCallback function.
   1.351 +    NS_CancelAsyncCopy(mAsyncCopyContext, NS_ERROR_ABORT);
   1.352 +  }
   1.353 +
   1.354 +  // Indicate that attention has been requested successfully, there is no need
   1.355 +  // to post another event until the worker thread processes the current one.
   1.356 +  mWorkerThreadAttentionRequested = true;
   1.357 +
   1.358 +  return NS_OK;
   1.359 +}
   1.360 +
   1.361 +// Called on the worker thread.
   1.362 +// static
   1.363 +void
   1.364 +BackgroundFileSaver::AsyncCopyCallback(void *aClosure, nsresult aStatus)
   1.365 +{
   1.366 +  BackgroundFileSaver *self = (BackgroundFileSaver *)aClosure;
   1.367 +  {
   1.368 +    MutexAutoLock lock(self->mLock);
   1.369 +
   1.370 +    // Now that the copy was interrupted or terminated, any notification from
   1.371 +    // the control thread requires an event to be posted to the worker thread.
   1.372 +    self->mAsyncCopyContext = nullptr;
   1.373 +
   1.374 +    // When detecting failures, ignore the status code we use to interrupt.
   1.375 +    if (NS_FAILED(aStatus) && aStatus != NS_ERROR_ABORT &&
   1.376 +        NS_SUCCEEDED(self->mStatus)) {
   1.377 +      self->mStatus = aStatus;
   1.378 +    }
   1.379 +  }
   1.380 +
   1.381 +  (void)self->ProcessAttention();
   1.382 +
   1.383 +  // We called NS_ADDREF_THIS when NS_AsyncCopy started, to keep the object
   1.384 +  // alive even if other references disappeared.  At this point, we've finished
   1.385 +  // using the object and can safely release our reference.
   1.386 +  NS_RELEASE(self);
   1.387 +}
   1.388 +
   1.389 +// Called on the worker thread.
   1.390 +nsresult
   1.391 +BackgroundFileSaver::ProcessAttention()
   1.392 +{
   1.393 +  nsresult rv;
   1.394 +
   1.395 +  // This function is called whenever the attention of the worker thread has
   1.396 +  // been requested.  This may happen in these cases:
   1.397 +  // * We are about to start the copy for the first time.  In this case, we are
   1.398 +  //   called from an event posted on the worker thread from the control thread
   1.399 +  //   by GetWorkerThreadAttention, and mAsyncCopyContext is null.
   1.400 +  // * We have interrupted the copy for some reason.  In this case, we are
   1.401 +  //   called by AsyncCopyCallback, and mAsyncCopyContext is null.
   1.402 +  // * We are currently executing ProcessStateChange, and attention is requested
   1.403 +  //   by the control thread, for example because SetTarget or Finish have been
   1.404 +  //   called.  In this case, we are called from from an event posted through
   1.405 +  //   GetWorkerThreadAttention.  While mAsyncCopyContext was always null when
   1.406 +  //   the event was posted, at this point mAsyncCopyContext may not be null
   1.407 +  //   anymore, because ProcessStateChange may have started the copy before the
   1.408 +  //   event that called this function was processed on the worker thread.
   1.409 +  // If mAsyncCopyContext is not null, we interrupt the copy and re-enter
   1.410 +  // through AsyncCopyCallback.  This allows us to check if, for instance, we
   1.411 +  // should rename the target file.  We will then restart the copy if needed.
   1.412 +  if (mAsyncCopyContext) {
   1.413 +    NS_CancelAsyncCopy(mAsyncCopyContext, NS_ERROR_ABORT);
   1.414 +    return NS_OK;
   1.415 +  }
   1.416 +  // Use the current shared state to determine the next operation to execute.
   1.417 +  rv = ProcessStateChange();
   1.418 +  if (NS_FAILED(rv)) {
   1.419 +    // If something failed while processing, terminate the operation now.
   1.420 +    {
   1.421 +      MutexAutoLock lock(mLock);
   1.422 +
   1.423 +      if (NS_SUCCEEDED(mStatus)) {
   1.424 +        mStatus = rv;
   1.425 +      }
   1.426 +    }
   1.427 +    // Ensure we notify completion now that the operation failed.
   1.428 +    CheckCompletion();
   1.429 +  }
   1.430 +
   1.431 +  return NS_OK;
   1.432 +}
   1.433 +
   1.434 +// Called on the worker thread.
   1.435 +nsresult
   1.436 +BackgroundFileSaver::ProcessStateChange()
   1.437 +{
   1.438 +  nsresult rv;
   1.439 +
   1.440 +  // We might have been notified because the operation is complete, verify.
   1.441 +  if (CheckCompletion()) {
   1.442 +    return NS_OK;
   1.443 +  }
   1.444 +
   1.445 +  // Get a copy of the current shared state for the worker thread.
   1.446 +  nsCOMPtr<nsIFile> initialTarget;
   1.447 +  bool initialTargetKeepPartial;
   1.448 +  nsCOMPtr<nsIFile> renamedTarget;
   1.449 +  bool renamedTargetKeepPartial;
   1.450 +  bool sha256Enabled;
   1.451 +  bool append;
   1.452 +  {
   1.453 +    MutexAutoLock lock(mLock);
   1.454 +
   1.455 +    initialTarget = mInitialTarget;
   1.456 +    initialTargetKeepPartial = mInitialTargetKeepPartial;
   1.457 +    renamedTarget = mRenamedTarget;
   1.458 +    renamedTargetKeepPartial = mRenamedTargetKeepPartial;
   1.459 +    sha256Enabled = mSha256Enabled;
   1.460 +    append = mAppend;
   1.461 +
   1.462 +    // From now on, another attention event needs to be posted if state changes.
   1.463 +    mWorkerThreadAttentionRequested = false;
   1.464 +  }
   1.465 +
   1.466 +  // The initial target can only be null if it has never been assigned.  In this
   1.467 +  // case, there is nothing to do since we never created any output file.
   1.468 +  if (!initialTarget) {
   1.469 +    return NS_OK;
   1.470 +  }
   1.471 +
   1.472 +  // Determine if we are processing the attention request for the first time.
   1.473 +  bool isContinuation = !!mActualTarget;
   1.474 +  if (!isContinuation) {
   1.475 +    // Assign the target file for the first time.
   1.476 +    mActualTarget = initialTarget;
   1.477 +    mActualTargetKeepPartial = initialTargetKeepPartial;
   1.478 +  }
   1.479 +
   1.480 +  // Verify whether we have actually been instructed to use a different file.
   1.481 +  // This may happen the first time this function is executed, if SetTarget was
   1.482 +  // called two times before the worker thread processed the attention request.
   1.483 +  bool equalToCurrent = false;
   1.484 +  if (renamedTarget) {
   1.485 +    rv = mActualTarget->Equals(renamedTarget, &equalToCurrent);
   1.486 +    NS_ENSURE_SUCCESS(rv, rv);
   1.487 +    if (!equalToCurrent)
   1.488 +    {
   1.489 +      // If we were asked to rename the file but the initial file did not exist,
   1.490 +      // we simply create the file in the renamed location.  We avoid this check
   1.491 +      // if we have already started writing the output file ourselves.
   1.492 +      bool exists = true;
   1.493 +      if (!isContinuation) {
   1.494 +        rv = mActualTarget->Exists(&exists);
   1.495 +        NS_ENSURE_SUCCESS(rv, rv);
   1.496 +      }
   1.497 +      if (exists) {
   1.498 +        // We are moving the previous target file to a different location.
   1.499 +        nsCOMPtr<nsIFile> renamedTargetParentDir;
   1.500 +        rv = renamedTarget->GetParent(getter_AddRefs(renamedTargetParentDir));
   1.501 +        NS_ENSURE_SUCCESS(rv, rv);
   1.502 +
   1.503 +        nsAutoString renamedTargetName;
   1.504 +        rv = renamedTarget->GetLeafName(renamedTargetName);
   1.505 +        NS_ENSURE_SUCCESS(rv, rv);
   1.506 +
   1.507 +        // We must delete any existing target file before moving the current
   1.508 +        // one.
   1.509 +        rv = renamedTarget->Exists(&exists);
   1.510 +        NS_ENSURE_SUCCESS(rv, rv);
   1.511 +        if (exists) {
   1.512 +          rv = renamedTarget->Remove(false);
   1.513 +          NS_ENSURE_SUCCESS(rv, rv);
   1.514 +        }
   1.515 +
   1.516 +        // Move the file.  If this fails, we still reference the original file
   1.517 +        // in mActualTarget, so that it is deleted if requested.  If this
   1.518 +        // succeeds, the nsIFile instance referenced by mActualTarget mutates
   1.519 +        // and starts pointing to the new file, but we'll discard the reference.
   1.520 +        rv = mActualTarget->MoveTo(renamedTargetParentDir, renamedTargetName);
   1.521 +        NS_ENSURE_SUCCESS(rv, rv);
   1.522 +      }
   1.523 +
   1.524 +      // Now we can update the actual target file name.
   1.525 +      mActualTarget = renamedTarget;
   1.526 +      mActualTargetKeepPartial = renamedTargetKeepPartial;
   1.527 +    }
   1.528 +  }
   1.529 +
   1.530 +  // Notify if the target file name actually changed.
   1.531 +  if (!equalToCurrent) {
   1.532 +    // We must clone the nsIFile instance because mActualTarget is not
   1.533 +    // immutable, it may change if the target is renamed later.
   1.534 +    nsCOMPtr<nsIFile> actualTargetToNotify;
   1.535 +    rv = mActualTarget->Clone(getter_AddRefs(actualTargetToNotify));
   1.536 +    NS_ENSURE_SUCCESS(rv, rv);
   1.537 +
   1.538 +    nsRefPtr<NotifyTargetChangeRunnable> event =
   1.539 +      new NotifyTargetChangeRunnable(this, actualTargetToNotify);
   1.540 +    NS_ENSURE_TRUE(event, NS_ERROR_FAILURE);
   1.541 +
   1.542 +    rv = mControlThread->Dispatch(event, NS_DISPATCH_NORMAL);
   1.543 +    NS_ENSURE_SUCCESS(rv, rv);
   1.544 +  }
   1.545 +
   1.546 +  if (isContinuation) {
   1.547 +    // The pending rename operation might be the last task before finishing. We
   1.548 +    // may return here only if we have already created the target file.
   1.549 +    if (CheckCompletion()) {
   1.550 +      return NS_OK;
   1.551 +    }
   1.552 +
   1.553 +    // Even if the operation did not complete, the pipe input stream may be
   1.554 +    // empty and may have been closed already.  We detect this case using the
   1.555 +    // Available property, because it never returns an error if there is more
   1.556 +    // data to be consumed.  If the pipe input stream is closed, we just exit
   1.557 +    // and wait for more calls like SetTarget or Finish to be invoked on the
   1.558 +    // control thread.  However, we still truncate the file or create the
   1.559 +    // initial digest context if we are expected to do that.
   1.560 +    uint64_t available;
   1.561 +    rv = mPipeInputStream->Available(&available);
   1.562 +    if (NS_FAILED(rv)) {
   1.563 +      return NS_OK;
   1.564 +    }
   1.565 +  }
   1.566 +
   1.567 +  // Create the digest context if requested and NSS hasn't been shut down.
   1.568 +  if (sha256Enabled && !mDigestContext) {
   1.569 +    nsNSSShutDownPreventionLock lock;
   1.570 +    if (!isAlreadyShutDown()) {
   1.571 +      mDigestContext =
   1.572 +        PK11_CreateDigestContext(static_cast<SECOidTag>(SEC_OID_SHA256));
   1.573 +      NS_ENSURE_TRUE(mDigestContext, NS_ERROR_OUT_OF_MEMORY);
   1.574 +    }
   1.575 +  }
   1.576 +
   1.577 +  // When we are requested to append to an existing file, we should read the
   1.578 +  // existing data and ensure we include it as part of the final hash.
   1.579 +  if (mDigestContext && append && !isContinuation) {
   1.580 +    nsCOMPtr<nsIInputStream> inputStream;
   1.581 +    rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream),
   1.582 +                                    mActualTarget,
   1.583 +                                    PR_RDONLY | nsIFile::OS_READAHEAD);
   1.584 +    if (rv != NS_ERROR_FILE_NOT_FOUND) {
   1.585 +      NS_ENSURE_SUCCESS(rv, rv);
   1.586 +
   1.587 +      char buffer[BUFFERED_IO_SIZE];
   1.588 +      while (true) {
   1.589 +        uint32_t count;
   1.590 +        rv = inputStream->Read(buffer, BUFFERED_IO_SIZE, &count);
   1.591 +        NS_ENSURE_SUCCESS(rv, rv);
   1.592 +
   1.593 +        if (count == 0) {
   1.594 +          // We reached the end of the file.
   1.595 +          break;
   1.596 +        }
   1.597 +
   1.598 +        nsNSSShutDownPreventionLock lock;
   1.599 +        if (isAlreadyShutDown()) {
   1.600 +          return NS_ERROR_NOT_AVAILABLE;
   1.601 +        }
   1.602 +
   1.603 +        nsresult rv = MapSECStatus(PK11_DigestOp(mDigestContext,
   1.604 +                                                 uint8_t_ptr_cast(buffer),
   1.605 +                                                 count));
   1.606 +        NS_ENSURE_SUCCESS(rv, rv);
   1.607 +      }
   1.608 +
   1.609 +      rv = inputStream->Close();
   1.610 +      NS_ENSURE_SUCCESS(rv, rv);
   1.611 +    }
   1.612 +  }
   1.613 +
   1.614 +  // We will append to the initial target file only if it was requested by the
   1.615 +  // caller, but we'll always append on subsequent accesses to the target file.
   1.616 +  int32_t creationIoFlags;
   1.617 +  if (isContinuation) {
   1.618 +    creationIoFlags = PR_APPEND;
   1.619 +  } else {
   1.620 +    creationIoFlags = (append ? PR_APPEND : PR_TRUNCATE) | PR_CREATE_FILE;
   1.621 +  }
   1.622 +
   1.623 +  // Create the target file, or append to it if we already started writing it.
   1.624 +  nsCOMPtr<nsIOutputStream> outputStream;
   1.625 +  rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream),
   1.626 +                                   mActualTarget,
   1.627 +                                   PR_WRONLY | creationIoFlags, 0644);
   1.628 +  NS_ENSURE_SUCCESS(rv, rv);
   1.629 +
   1.630 +  outputStream = NS_BufferOutputStream(outputStream, BUFFERED_IO_SIZE);
   1.631 +  if (!outputStream) {
   1.632 +    return NS_ERROR_FAILURE;
   1.633 +  }
   1.634 +
   1.635 +  // Wrap the output stream so that it feeds the digest context if needed.
   1.636 +  if (mDigestContext) {
   1.637 +    // No need to acquire the NSS lock here, DigestOutputStream must acquire it
   1.638 +    // in any case before each asynchronous write. Constructing the
   1.639 +    // DigestOutputStream cannot fail. Passing mDigestContext to
   1.640 +    // DigestOutputStream is safe, because BackgroundFileSaver always outlives
   1.641 +    // the outputStream. BackgroundFileSaver is reference-counted before the
   1.642 +    // call to AsyncCopy, and mDigestContext is never destroyed before
   1.643 +    // AsyncCopyCallback.
   1.644 +    outputStream = new DigestOutputStream(outputStream, mDigestContext);
   1.645 +  }
   1.646 +
   1.647 +  // Start copying our input to the target file.  No errors can be raised past
   1.648 +  // this point if the copy starts, since they should be handled by the thread.
   1.649 +  {
   1.650 +    MutexAutoLock lock(mLock);
   1.651 +
   1.652 +    rv = NS_AsyncCopy(mPipeInputStream, outputStream, mWorkerThread,
   1.653 +                      NS_ASYNCCOPY_VIA_READSEGMENTS, 4096, AsyncCopyCallback,
   1.654 +                      this, false, true, getter_AddRefs(mAsyncCopyContext),
   1.655 +                      GetProgressCallback());
   1.656 +    if (NS_FAILED(rv)) {
   1.657 +      NS_WARNING("NS_AsyncCopy failed.");
   1.658 +      mAsyncCopyContext = nullptr;
   1.659 +      return rv;
   1.660 +    }
   1.661 +  }
   1.662 +
   1.663 +  // If the operation succeeded, we must ensure that we keep this object alive
   1.664 +  // for the entire duration of the copy, since only the raw pointer will be
   1.665 +  // provided as the argument of the AsyncCopyCallback function.  We can add the
   1.666 +  // reference now, after NS_AsyncCopy returned, because it always starts
   1.667 +  // processing asynchronously, and there is no risk that the callback is
   1.668 +  // invoked before we reach this point.  If the operation failed instead, then
   1.669 +  // AsyncCopyCallback will never be called.
   1.670 +  NS_ADDREF_THIS();
   1.671 +
   1.672 +  return NS_OK;
   1.673 +}
   1.674 +
   1.675 +// Called on the worker thread.
   1.676 +bool
   1.677 +BackgroundFileSaver::CheckCompletion()
   1.678 +{
   1.679 +  nsresult rv;
   1.680 +
   1.681 +  MOZ_ASSERT(!mAsyncCopyContext,
   1.682 +             "Should not be copying when checking completion conditions.");
   1.683 +
   1.684 +  bool failed = true;
   1.685 +  {
   1.686 +    MutexAutoLock lock(mLock);
   1.687 +
   1.688 +    if (mComplete) {
   1.689 +      return true;
   1.690 +    }
   1.691 +
   1.692 +    // If an error occurred, we don't need to do the checks in this code block,
   1.693 +    // and the operation can be completed immediately with a failure code.
   1.694 +    if (NS_SUCCEEDED(mStatus)) {
   1.695 +      failed = false;
   1.696 +
   1.697 +      // We did not incur in an error, so we must determine if we can stop now.
   1.698 +      // If the Finish method has not been called, we can just continue now.
   1.699 +      if (!mFinishRequested) {
   1.700 +        return false;
   1.701 +      }
   1.702 +
   1.703 +      // We can only stop when all the operations requested by the control
   1.704 +      // thread have been processed.  First, we check whether we have processed
   1.705 +      // the first SetTarget call, if any.  Then, we check whether we have
   1.706 +      // processed any rename requested by subsequent SetTarget calls.
   1.707 +      if ((mInitialTarget && !mActualTarget) ||
   1.708 +          (mRenamedTarget && mRenamedTarget != mActualTarget)) {
   1.709 +        return false;
   1.710 +      }
   1.711 +
   1.712 +      // If we still have data to write to the output file, allow the copy
   1.713 +      // operation to resume.  The Available getter may return an error if one
   1.714 +      // of the pipe's streams has been already closed.
   1.715 +      uint64_t available;
   1.716 +      rv = mPipeInputStream->Available(&available);
   1.717 +      if (NS_SUCCEEDED(rv) && available != 0) {
   1.718 +        return false;
   1.719 +      }
   1.720 +    }
   1.721 +
   1.722 +    mComplete = true;
   1.723 +  }
   1.724 +
   1.725 +  // Ensure we notify completion now that the operation finished.
   1.726 +  // Do a best-effort attempt to remove the file if required.
   1.727 +  if (failed && mActualTarget && !mActualTargetKeepPartial) {
   1.728 +    (void)mActualTarget->Remove(false);
   1.729 +  }
   1.730 +
   1.731 +  // Finish computing the hash
   1.732 +  if (!failed && mDigestContext) {
   1.733 +    nsNSSShutDownPreventionLock lock;
   1.734 +    if (!isAlreadyShutDown()) {
   1.735 +      Digest d;
   1.736 +      rv = d.End(SEC_OID_SHA256, mDigestContext);
   1.737 +      if (NS_SUCCEEDED(rv)) {
   1.738 +        MutexAutoLock lock(mLock);
   1.739 +        mSha256 = nsDependentCSubstring(char_ptr_cast(d.get().data),
   1.740 +                                        d.get().len);
   1.741 +      }
   1.742 +    }
   1.743 +  }
   1.744 +
   1.745 +  // Compute the signature of the binary. ExtractSignatureInfo doesn't do
   1.746 +  // anything on non-Windows platforms except return an empty nsIArray.
   1.747 +  if (!failed && mActualTarget) {
   1.748 +    nsString filePath;
   1.749 +    mActualTarget->GetTarget(filePath);
   1.750 +    nsresult rv = ExtractSignatureInfo(filePath);
   1.751 +    if (NS_FAILED(rv)) {
   1.752 +      LOG(("Unable to extract signature information [this = %p].", this));
   1.753 +    } else {
   1.754 +      LOG(("Signature extraction success! [this = %p]", this));
   1.755 +    }
   1.756 +  }
   1.757 +
   1.758 +  // Post an event to notify that the operation completed.
   1.759 +  nsCOMPtr<nsIRunnable> event =
   1.760 +    NS_NewRunnableMethod(this, &BackgroundFileSaver::NotifySaveComplete);
   1.761 +  if (!event ||
   1.762 +      NS_FAILED(mControlThread->Dispatch(event, NS_DISPATCH_NORMAL))) {
   1.763 +    NS_WARNING("Unable to post completion event to the control thread.");
   1.764 +  }
   1.765 +
   1.766 +  return true;
   1.767 +}
   1.768 +
   1.769 +// Called on the control thread.
   1.770 +nsresult
   1.771 +BackgroundFileSaver::NotifyTargetChange(nsIFile *aTarget)
   1.772 +{
   1.773 +  if (mObserver) {
   1.774 +    (void)mObserver->OnTargetChange(this, aTarget);
   1.775 +  }
   1.776 +
   1.777 +  return NS_OK;
   1.778 +}
   1.779 +
   1.780 +// Called on the control thread.
   1.781 +nsresult
   1.782 +BackgroundFileSaver::NotifySaveComplete()
   1.783 +{
   1.784 +  MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
   1.785 +
   1.786 +  nsresult status;
   1.787 +  {
   1.788 +    MutexAutoLock lock(mLock);
   1.789 +    status = mStatus;
   1.790 +  }
   1.791 +
   1.792 +  if (mObserver) {
   1.793 +    (void)mObserver->OnSaveComplete(this, status);
   1.794 +  }
   1.795 +
   1.796 +  // At this point, the worker thread will not process any more events, and we
   1.797 +  // can shut it down.  Shutting down a thread may re-enter the event loop on
   1.798 +  // this thread.  This is not a problem in this case, since this function is
   1.799 +  // called by a top-level event itself, and we have already invoked the
   1.800 +  // completion observer callback.  Re-entering the loop can only delay the
   1.801 +  // final release and destruction of this saver object, since we are keeping a
   1.802 +  // reference to it through the event object.
   1.803 +  mWorkerThread->Shutdown();
   1.804 +
   1.805 +  sThreadCount--;
   1.806 +
   1.807 +  // When there are no more active downloads, we consider the download session
   1.808 +  // finished. We record the maximum number of concurrent downloads reached
   1.809 +  // during the session in a telemetry histogram, and we reset the maximum
   1.810 +  // thread counter for the next download session
   1.811 +  if (sThreadCount == 0) {
   1.812 +    Telemetry::Accumulate(Telemetry::BACKGROUNDFILESAVER_THREAD_COUNT,
   1.813 +                          sTelemetryMaxThreadCount);
   1.814 +    sTelemetryMaxThreadCount = 0;
   1.815 +  }
   1.816 +
   1.817 +  return NS_OK;
   1.818 +}
   1.819 +
   1.820 +nsresult
   1.821 +BackgroundFileSaver::ExtractSignatureInfo(const nsAString& filePath)
   1.822 +{
   1.823 +  MOZ_ASSERT(!NS_IsMainThread(), "Cannot extract signature on main thread");
   1.824 +
   1.825 +  nsNSSShutDownPreventionLock nssLock;
   1.826 +  if (isAlreadyShutDown()) {
   1.827 +    return NS_ERROR_NOT_AVAILABLE;
   1.828 +  }
   1.829 +  {
   1.830 +    MutexAutoLock lock(mLock);
   1.831 +    if (!mSignatureInfoEnabled) {
   1.832 +      return NS_OK;
   1.833 +    }
   1.834 +  }
   1.835 +  nsresult rv;
   1.836 +  nsCOMPtr<nsIX509CertDB> certDB = do_GetService(NS_X509CERTDB_CONTRACTID, &rv);
   1.837 +  NS_ENSURE_SUCCESS(rv, rv);
   1.838 +#ifdef XP_WIN
   1.839 +  // Setup the file to check.
   1.840 +  WINTRUST_FILE_INFO fileToCheck = {0};
   1.841 +  fileToCheck.cbStruct = sizeof(WINTRUST_FILE_INFO);
   1.842 +  fileToCheck.pcwszFilePath = filePath.Data();
   1.843 +  fileToCheck.hFile = nullptr;
   1.844 +  fileToCheck.pgKnownSubject = nullptr;
   1.845 +
   1.846 +  // We want to check it is signed and trusted.
   1.847 +  WINTRUST_DATA trustData = {0};
   1.848 +  trustData.cbStruct = sizeof(trustData);
   1.849 +  trustData.pPolicyCallbackData = nullptr;
   1.850 +  trustData.pSIPClientData = nullptr;
   1.851 +  trustData.dwUIChoice = WTD_UI_NONE;
   1.852 +  trustData.fdwRevocationChecks = WTD_REVOKE_NONE;
   1.853 +  trustData.dwUnionChoice = WTD_CHOICE_FILE;
   1.854 +  trustData.dwStateAction = WTD_STATEACTION_VERIFY;
   1.855 +  trustData.hWVTStateData = nullptr;
   1.856 +  trustData.pwszURLReference = nullptr;
   1.857 +  // Disallow revocation checks over the network
   1.858 +  trustData.dwProvFlags = WTD_CACHE_ONLY_URL_RETRIEVAL;
   1.859 +  // no UI
   1.860 +  trustData.dwUIContext = 0;
   1.861 +  trustData.pFile = &fileToCheck;
   1.862 +
   1.863 +  // The WINTRUST_ACTION_GENERIC_VERIFY_V2 policy verifies that the certificate
   1.864 +  // chains up to a trusted root CA and has appropriate permissions to sign
   1.865 +  // code.
   1.866 +  GUID policyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2;
   1.867 +  // Check if the file is signed by something that is trusted. If the file is
   1.868 +  // not signed, this is a no-op.
   1.869 +  LONG ret = WinVerifyTrust(nullptr, &policyGUID, &trustData);
   1.870 +  CRYPT_PROVIDER_DATA* cryptoProviderData = nullptr;
   1.871 +  // According to the Windows documentation, we should check against 0 instead
   1.872 +  // of ERROR_SUCCESS, which is an HRESULT.
   1.873 +  if (ret == 0) {
   1.874 +    cryptoProviderData = WTHelperProvDataFromStateData(trustData.hWVTStateData);
   1.875 +  }
   1.876 +  if (cryptoProviderData) {
   1.877 +    // Lock because signature information is read on the main thread.
   1.878 +    MutexAutoLock lock(mLock);
   1.879 +    LOG(("Downloaded trusted and signed file [this = %p].", this));
   1.880 +    // A binary may have multiple signers. Each signer may have multiple certs
   1.881 +    // in the chain.
   1.882 +    for (DWORD i = 0; i < cryptoProviderData->csSigners; ++i) {
   1.883 +      const CERT_CHAIN_CONTEXT* certChainContext =
   1.884 +        cryptoProviderData->pasSigners[i].pChainContext;
   1.885 +      if (!certChainContext) {
   1.886 +        break;
   1.887 +      }
   1.888 +      for (DWORD j = 0; j < certChainContext->cChain; ++j) {
   1.889 +        const CERT_SIMPLE_CHAIN* certSimpleChain =
   1.890 +          certChainContext->rgpChain[j];
   1.891 +        if (!certSimpleChain) {
   1.892 +          break;
   1.893 +        }
   1.894 +        nsCOMPtr<nsIX509CertList> nssCertList =
   1.895 +          do_CreateInstance(NS_X509CERTLIST_CONTRACTID);
   1.896 +        if (!nssCertList) {
   1.897 +          break;
   1.898 +        }
   1.899 +        bool extractionSuccess = true;
   1.900 +        for (DWORD k = 0; k < certSimpleChain->cElement; ++k) {
   1.901 +          CERT_CHAIN_ELEMENT* certChainElement = certSimpleChain->rgpElement[k];
   1.902 +          if (certChainElement->pCertContext->dwCertEncodingType !=
   1.903 +            X509_ASN_ENCODING) {
   1.904 +              continue;
   1.905 +          }
   1.906 +          nsCOMPtr<nsIX509Cert> nssCert = nullptr;
   1.907 +          rv = certDB->ConstructX509(
   1.908 +            reinterpret_cast<char *>(
   1.909 +              certChainElement->pCertContext->pbCertEncoded),
   1.910 +            certChainElement->pCertContext->cbCertEncoded,
   1.911 +            getter_AddRefs(nssCert));
   1.912 +          if (!nssCert) {
   1.913 +            extractionSuccess = false;
   1.914 +            LOG(("Couldn't create NSS cert [this = %p]", this));
   1.915 +            break;
   1.916 +          }
   1.917 +          nssCertList->AddCert(nssCert);
   1.918 +          nsString subjectName;
   1.919 +          nssCert->GetSubjectName(subjectName);
   1.920 +          LOG(("Adding cert %s [this = %p]",
   1.921 +               NS_ConvertUTF16toUTF8(subjectName).get(), this));
   1.922 +        }
   1.923 +        if (extractionSuccess) {
   1.924 +          mSignatureInfo.AppendObject(nssCertList);
   1.925 +        }
   1.926 +      }
   1.927 +    }
   1.928 +    // Free the provider data if cryptoProviderData is not null.
   1.929 +    trustData.dwStateAction = WTD_STATEACTION_CLOSE;
   1.930 +    WinVerifyTrust(nullptr, &policyGUID, &trustData);
   1.931 +  } else {
   1.932 +    LOG(("Downloaded unsigned or untrusted file [this = %p].", this));
   1.933 +  }
   1.934 +#endif
   1.935 +  return NS_OK;
   1.936 +}
   1.937 +
   1.938 +////////////////////////////////////////////////////////////////////////////////
   1.939 +//// BackgroundFileSaverOutputStream
   1.940 +
   1.941 +NS_IMPL_ISUPPORTS(BackgroundFileSaverOutputStream,
   1.942 +                  nsIBackgroundFileSaver,
   1.943 +                  nsIOutputStream,
   1.944 +                  nsIAsyncOutputStream,
   1.945 +                  nsIOutputStreamCallback)
   1.946 +
   1.947 +BackgroundFileSaverOutputStream::BackgroundFileSaverOutputStream()
   1.948 +: BackgroundFileSaver()
   1.949 +, mAsyncWaitCallback(nullptr)
   1.950 +{
   1.951 +}
   1.952 +
   1.953 +BackgroundFileSaverOutputStream::~BackgroundFileSaverOutputStream()
   1.954 +{
   1.955 +}
   1.956 +
   1.957 +bool
   1.958 +BackgroundFileSaverOutputStream::HasInfiniteBuffer()
   1.959 +{
   1.960 +  return false;
   1.961 +}
   1.962 +
   1.963 +nsAsyncCopyProgressFun
   1.964 +BackgroundFileSaverOutputStream::GetProgressCallback()
   1.965 +{
   1.966 +  return nullptr;
   1.967 +}
   1.968 +
   1.969 +NS_IMETHODIMP
   1.970 +BackgroundFileSaverOutputStream::Close()
   1.971 +{
   1.972 +  return mPipeOutputStream->Close();
   1.973 +}
   1.974 +
   1.975 +NS_IMETHODIMP
   1.976 +BackgroundFileSaverOutputStream::Flush()
   1.977 +{
   1.978 +  return mPipeOutputStream->Flush();
   1.979 +}
   1.980 +
   1.981 +NS_IMETHODIMP
   1.982 +BackgroundFileSaverOutputStream::Write(const char *aBuf, uint32_t aCount,
   1.983 +                                       uint32_t *_retval)
   1.984 +{
   1.985 +  return mPipeOutputStream->Write(aBuf, aCount, _retval);
   1.986 +}
   1.987 +
   1.988 +NS_IMETHODIMP
   1.989 +BackgroundFileSaverOutputStream::WriteFrom(nsIInputStream *aFromStream,
   1.990 +                                           uint32_t aCount, uint32_t *_retval)
   1.991 +{
   1.992 +  return mPipeOutputStream->WriteFrom(aFromStream, aCount, _retval);
   1.993 +}
   1.994 +
   1.995 +NS_IMETHODIMP
   1.996 +BackgroundFileSaverOutputStream::WriteSegments(nsReadSegmentFun aReader,
   1.997 +                                               void *aClosure, uint32_t aCount,
   1.998 +                                               uint32_t *_retval)
   1.999 +{
  1.1000 +  return mPipeOutputStream->WriteSegments(aReader, aClosure, aCount, _retval);
  1.1001 +}
  1.1002 +
  1.1003 +NS_IMETHODIMP
  1.1004 +BackgroundFileSaverOutputStream::IsNonBlocking(bool *_retval)
  1.1005 +{
  1.1006 +  return mPipeOutputStream->IsNonBlocking(_retval);
  1.1007 +}
  1.1008 +
  1.1009 +NS_IMETHODIMP
  1.1010 +BackgroundFileSaverOutputStream::CloseWithStatus(nsresult reason)
  1.1011 +{
  1.1012 +  return mPipeOutputStream->CloseWithStatus(reason);
  1.1013 +}
  1.1014 +
  1.1015 +NS_IMETHODIMP
  1.1016 +BackgroundFileSaverOutputStream::AsyncWait(nsIOutputStreamCallback *aCallback,
  1.1017 +                                           uint32_t aFlags,
  1.1018 +                                           uint32_t aRequestedCount,
  1.1019 +                                           nsIEventTarget *aEventTarget)
  1.1020 +{
  1.1021 +  NS_ENSURE_STATE(!mAsyncWaitCallback);
  1.1022 +
  1.1023 +  mAsyncWaitCallback = aCallback;
  1.1024 +
  1.1025 +  return mPipeOutputStream->AsyncWait(this, aFlags, aRequestedCount,
  1.1026 +                                      aEventTarget);
  1.1027 +}
  1.1028 +
  1.1029 +NS_IMETHODIMP
  1.1030 +BackgroundFileSaverOutputStream::OnOutputStreamReady(
  1.1031 +                                 nsIAsyncOutputStream *aStream)
  1.1032 +{
  1.1033 +  NS_ENSURE_STATE(mAsyncWaitCallback);
  1.1034 +
  1.1035 +  nsCOMPtr<nsIOutputStreamCallback> asyncWaitCallback = nullptr;
  1.1036 +  asyncWaitCallback.swap(mAsyncWaitCallback);
  1.1037 +
  1.1038 +  return asyncWaitCallback->OnOutputStreamReady(this);
  1.1039 +}
  1.1040 +
  1.1041 +////////////////////////////////////////////////////////////////////////////////
  1.1042 +//// BackgroundFileSaverStreamListener
  1.1043 +
  1.1044 +NS_IMPL_ISUPPORTS(BackgroundFileSaverStreamListener,
  1.1045 +                  nsIBackgroundFileSaver,
  1.1046 +                  nsIRequestObserver,
  1.1047 +                  nsIStreamListener)
  1.1048 +
  1.1049 +BackgroundFileSaverStreamListener::BackgroundFileSaverStreamListener()
  1.1050 +: BackgroundFileSaver()
  1.1051 +, mSuspensionLock("BackgroundFileSaverStreamListener.mSuspensionLock")
  1.1052 +, mReceivedTooMuchData(false)
  1.1053 +, mRequest(nullptr)
  1.1054 +, mRequestSuspended(false)
  1.1055 +{
  1.1056 +}
  1.1057 +
  1.1058 +BackgroundFileSaverStreamListener::~BackgroundFileSaverStreamListener()
  1.1059 +{
  1.1060 +}
  1.1061 +
  1.1062 +bool
  1.1063 +BackgroundFileSaverStreamListener::HasInfiniteBuffer()
  1.1064 +{
  1.1065 +  return true;
  1.1066 +}
  1.1067 +
  1.1068 +nsAsyncCopyProgressFun
  1.1069 +BackgroundFileSaverStreamListener::GetProgressCallback()
  1.1070 +{
  1.1071 +  return AsyncCopyProgressCallback;
  1.1072 +}
  1.1073 +
  1.1074 +NS_IMETHODIMP
  1.1075 +BackgroundFileSaverStreamListener::OnStartRequest(nsIRequest *aRequest,
  1.1076 +                                                  nsISupports *aContext)
  1.1077 +{
  1.1078 +  NS_ENSURE_ARG(aRequest);
  1.1079 +
  1.1080 +  return NS_OK;
  1.1081 +}
  1.1082 +
  1.1083 +NS_IMETHODIMP
  1.1084 +BackgroundFileSaverStreamListener::OnStopRequest(nsIRequest *aRequest,
  1.1085 +                                                 nsISupports *aContext,
  1.1086 +                                                 nsresult aStatusCode)
  1.1087 +{
  1.1088 +  // If an error occurred, cancel the operation immediately.  On success, wait
  1.1089 +  // until the caller has determined whether the file should be renamed.
  1.1090 +  if (NS_FAILED(aStatusCode)) {
  1.1091 +    Finish(aStatusCode);
  1.1092 +  }
  1.1093 +
  1.1094 +  return NS_OK;
  1.1095 +}
  1.1096 +
  1.1097 +NS_IMETHODIMP
  1.1098 +BackgroundFileSaverStreamListener::OnDataAvailable(nsIRequest *aRequest,
  1.1099 +                                                   nsISupports *aContext,
  1.1100 +                                                   nsIInputStream *aInputStream,
  1.1101 +                                                   uint64_t aOffset,
  1.1102 +                                                   uint32_t aCount)
  1.1103 +{
  1.1104 +  nsresult rv;
  1.1105 +
  1.1106 +  NS_ENSURE_ARG(aRequest);
  1.1107 +
  1.1108 +  // Read the requested data.  Since the pipe has an infinite buffer, we don't
  1.1109 +  // expect any write error to occur here.
  1.1110 +  uint32_t writeCount;
  1.1111 +  rv = mPipeOutputStream->WriteFrom(aInputStream, aCount, &writeCount);
  1.1112 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1113 +
  1.1114 +  // If reading from the input stream fails for any reason, the pipe will return
  1.1115 +  // a success code, but without reading all the data.  Since we should be able
  1.1116 +  // to read the requested data when OnDataAvailable is called, raise an error.
  1.1117 +  if (writeCount < aCount) {
  1.1118 +    NS_WARNING("Reading from the input stream should not have failed.");
  1.1119 +    return NS_ERROR_UNEXPECTED;
  1.1120 +  }
  1.1121 +
  1.1122 +  bool stateChanged = false;
  1.1123 +  {
  1.1124 +    MutexAutoLock lock(mSuspensionLock);
  1.1125 +
  1.1126 +    if (!mReceivedTooMuchData) {
  1.1127 +      uint64_t available;
  1.1128 +      nsresult rv = mPipeInputStream->Available(&available);
  1.1129 +      if (NS_SUCCEEDED(rv) && available > REQUEST_SUSPEND_AT) {
  1.1130 +        mReceivedTooMuchData = true;
  1.1131 +        mRequest = aRequest;
  1.1132 +        stateChanged = true;
  1.1133 +      }
  1.1134 +    }
  1.1135 +  }
  1.1136 +
  1.1137 +  if (stateChanged) {
  1.1138 +    NotifySuspendOrResume();
  1.1139 +  }
  1.1140 +
  1.1141 +  return NS_OK;
  1.1142 +}
  1.1143 +
  1.1144 +// Called on the worker thread.
  1.1145 +// static
  1.1146 +void
  1.1147 +BackgroundFileSaverStreamListener::AsyncCopyProgressCallback(void *aClosure,
  1.1148 +                                                             uint32_t aCount)
  1.1149 +{
  1.1150 +  BackgroundFileSaverStreamListener *self =
  1.1151 +    (BackgroundFileSaverStreamListener *)aClosure;
  1.1152 +
  1.1153 +  // Wait if the control thread is in the process of suspending or resuming.
  1.1154 +  MutexAutoLock lock(self->mSuspensionLock);
  1.1155 +
  1.1156 +  // This function is called when some bytes are consumed by NS_AsyncCopy.  Each
  1.1157 +  // time this happens, verify if a suspended request should be resumed, because
  1.1158 +  // we have now consumed enough data.
  1.1159 +  if (self->mReceivedTooMuchData) {
  1.1160 +    uint64_t available;
  1.1161 +    nsresult rv = self->mPipeInputStream->Available(&available);
  1.1162 +    if (NS_FAILED(rv) || available < REQUEST_RESUME_AT) {
  1.1163 +      self->mReceivedTooMuchData = false;
  1.1164 +
  1.1165 +      // Post an event to verify if the request should be resumed.
  1.1166 +      nsCOMPtr<nsIRunnable> event = NS_NewRunnableMethod(self,
  1.1167 +        &BackgroundFileSaverStreamListener::NotifySuspendOrResume);
  1.1168 +      if (!event || NS_FAILED(self->mControlThread->Dispatch(event,
  1.1169 +                                                    NS_DISPATCH_NORMAL))) {
  1.1170 +        NS_WARNING("Unable to post resume event to the control thread.");
  1.1171 +      }
  1.1172 +    }
  1.1173 +  }
  1.1174 +}
  1.1175 +
  1.1176 +// Called on the control thread.
  1.1177 +nsresult
  1.1178 +BackgroundFileSaverStreamListener::NotifySuspendOrResume()
  1.1179 +{
  1.1180 +  // Prevent the worker thread from changing state while processing.
  1.1181 +  MutexAutoLock lock(mSuspensionLock);
  1.1182 +
  1.1183 +  if (mReceivedTooMuchData) {
  1.1184 +    if (!mRequestSuspended) {
  1.1185 +      // Try to suspend the request.  If this fails, don't try to resume later.
  1.1186 +      if (NS_SUCCEEDED(mRequest->Suspend())) {
  1.1187 +        mRequestSuspended = true;
  1.1188 +      } else {
  1.1189 +        NS_WARNING("Unable to suspend the request.");
  1.1190 +      }
  1.1191 +    }
  1.1192 +  } else {
  1.1193 +    if (mRequestSuspended) {
  1.1194 +      // Resume the request only if we succeeded in suspending it.
  1.1195 +      if (NS_SUCCEEDED(mRequest->Resume())) {
  1.1196 +        mRequestSuspended = false;
  1.1197 +      } else {
  1.1198 +        NS_WARNING("Unable to resume the request.");
  1.1199 +      }
  1.1200 +    }
  1.1201 +  }
  1.1202 +
  1.1203 +  return NS_OK;
  1.1204 +}
  1.1205 +
  1.1206 +////////////////////////////////////////////////////////////////////////////////
  1.1207 +//// DigestOutputStream
  1.1208 +NS_IMPL_ISUPPORTS(DigestOutputStream,
  1.1209 +                  nsIOutputStream)
  1.1210 +
  1.1211 +DigestOutputStream::DigestOutputStream(nsIOutputStream* aStream,
  1.1212 +                                       PK11Context* aContext) :
  1.1213 +  mOutputStream(aStream)
  1.1214 +  , mDigestContext(aContext)
  1.1215 +{
  1.1216 +  MOZ_ASSERT(mDigestContext, "Can't have null digest context");
  1.1217 +  MOZ_ASSERT(mOutputStream, "Can't have null output stream");
  1.1218 +}
  1.1219 +
  1.1220 +DigestOutputStream::~DigestOutputStream()
  1.1221 +{
  1.1222 +  shutdown(calledFromObject);
  1.1223 +}
  1.1224 +
  1.1225 +NS_IMETHODIMP
  1.1226 +DigestOutputStream::Close()
  1.1227 +{
  1.1228 +  return mOutputStream->Close();
  1.1229 +}
  1.1230 +
  1.1231 +NS_IMETHODIMP
  1.1232 +DigestOutputStream::Flush()
  1.1233 +{
  1.1234 +  return mOutputStream->Flush();
  1.1235 +}
  1.1236 +
  1.1237 +NS_IMETHODIMP
  1.1238 +DigestOutputStream::Write(const char* aBuf, uint32_t aCount, uint32_t* retval)
  1.1239 +{
  1.1240 +  nsNSSShutDownPreventionLock lock;
  1.1241 +  if (isAlreadyShutDown()) {
  1.1242 +    return NS_ERROR_NOT_AVAILABLE;
  1.1243 +  }
  1.1244 +
  1.1245 +  nsresult rv = MapSECStatus(PK11_DigestOp(mDigestContext,
  1.1246 +                                           uint8_t_ptr_cast(aBuf), aCount));
  1.1247 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1248 +
  1.1249 +  return mOutputStream->Write(aBuf, aCount, retval);
  1.1250 +}
  1.1251 +
  1.1252 +NS_IMETHODIMP
  1.1253 +DigestOutputStream::WriteFrom(nsIInputStream* aFromStream,
  1.1254 +                              uint32_t aCount, uint32_t* retval)
  1.1255 +{
  1.1256 +  // Not supported. We could read the stream to a buf, call DigestOp on the
  1.1257 +  // result, seek back and pass the stream on, but it's not worth it since our
  1.1258 +  // application (NS_AsyncCopy) doesn't invoke this on the sink.
  1.1259 +  MOZ_CRASH("DigestOutputStream::WriteFrom not implemented");
  1.1260 +}
  1.1261 +
  1.1262 +NS_IMETHODIMP
  1.1263 +DigestOutputStream::WriteSegments(nsReadSegmentFun aReader,
  1.1264 +                                  void *aClosure, uint32_t aCount,
  1.1265 +                                  uint32_t *retval)
  1.1266 +{
  1.1267 +  MOZ_CRASH("DigestOutputStream::WriteSegments not implemented");
  1.1268 +}
  1.1269 +
  1.1270 +NS_IMETHODIMP
  1.1271 +DigestOutputStream::IsNonBlocking(bool *retval)
  1.1272 +{
  1.1273 +  return mOutputStream->IsNonBlocking(retval);
  1.1274 +}
  1.1275 +
  1.1276 +} // namespace net
  1.1277 +} // namespace mozilla

mercurial