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