netwerk/base/src/BackgroundFileSaver.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

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

mercurial