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