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