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