1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/netwerk/base/src/nsIncrementalDownload.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,920 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim:set ts=2 sw=2 sts=2 et cindent: */ 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 "mozilla/Attributes.h" 1.11 + 1.12 +#include "nsIIncrementalDownload.h" 1.13 +#include "nsIRequestObserver.h" 1.14 +#include "nsIProgressEventSink.h" 1.15 +#include "nsIChannelEventSink.h" 1.16 +#include "nsIAsyncVerifyRedirectCallback.h" 1.17 +#include "nsIInterfaceRequestor.h" 1.18 +#include "nsIObserverService.h" 1.19 +#include "nsIObserver.h" 1.20 +#include "nsIFile.h" 1.21 +#include "nsITimer.h" 1.22 +#include "nsNetUtil.h" 1.23 +#include "nsAutoPtr.h" 1.24 +#include "nsWeakReference.h" 1.25 +#include "prio.h" 1.26 +#include "prprf.h" 1.27 +#include <algorithm> 1.28 + 1.29 +// Default values used to initialize a nsIncrementalDownload object. 1.30 +#define DEFAULT_CHUNK_SIZE (4096 * 16) // bytes 1.31 +#define DEFAULT_INTERVAL 60 // seconds 1.32 + 1.33 +#define UPDATE_PROGRESS_INTERVAL PRTime(500 * PR_USEC_PER_MSEC) // 500ms 1.34 + 1.35 +// Number of times to retry a failed byte-range request. 1.36 +#define MAX_RETRY_COUNT 20 1.37 + 1.38 +//----------------------------------------------------------------------------- 1.39 + 1.40 +static nsresult 1.41 +WriteToFile(nsIFile *lf, const char *data, uint32_t len, int32_t flags) 1.42 +{ 1.43 + PRFileDesc *fd; 1.44 + int32_t mode = 0600; 1.45 + nsresult rv; 1.46 +#if defined(MOZ_WIDGET_GONK) 1.47 + // The sdcard on a B2G phone looks like: 1.48 + // d---rwx--- system sdcard_rw 1970-01-01 01:00:00 sdcard 1.49 + // On the emulator, xpcshell fails when using 0600 mode to open the file, 1.50 + // and 0660 works. 1.51 + nsCOMPtr<nsIFile> parent; 1.52 + rv = lf->GetParent(getter_AddRefs(parent)); 1.53 + if (NS_FAILED(rv)) { 1.54 + return rv; 1.55 + } 1.56 + uint32_t parentPerm; 1.57 + rv = parent->GetPermissions(&parentPerm); 1.58 + if (NS_FAILED(rv)) { 1.59 + return rv; 1.60 + } 1.61 + if ((parentPerm & 0700) == 0) { 1.62 + // Parent directory has no owner-write, so try to use group permissions 1.63 + // instead of owner permissions. 1.64 + mode = 0660; 1.65 + } 1.66 +#endif 1.67 + rv = lf->OpenNSPRFileDesc(flags, mode, &fd); 1.68 + if (NS_FAILED(rv)) 1.69 + return rv; 1.70 + 1.71 + if (len) 1.72 + rv = PR_Write(fd, data, len) == int32_t(len) ? NS_OK : NS_ERROR_FAILURE; 1.73 + 1.74 + PR_Close(fd); 1.75 + return rv; 1.76 +} 1.77 + 1.78 +static nsresult 1.79 +AppendToFile(nsIFile *lf, const char *data, uint32_t len) 1.80 +{ 1.81 + int32_t flags = PR_WRONLY | PR_CREATE_FILE | PR_APPEND; 1.82 + return WriteToFile(lf, data, len, flags); 1.83 +} 1.84 + 1.85 +// maxSize may be -1 if unknown 1.86 +static void 1.87 +MakeRangeSpec(const int64_t &size, const int64_t &maxSize, int32_t chunkSize, 1.88 + bool fetchRemaining, nsCString &rangeSpec) 1.89 +{ 1.90 + rangeSpec.AssignLiteral("bytes="); 1.91 + rangeSpec.AppendInt(int64_t(size)); 1.92 + rangeSpec.Append('-'); 1.93 + 1.94 + if (fetchRemaining) 1.95 + return; 1.96 + 1.97 + int64_t end = size + int64_t(chunkSize); 1.98 + if (maxSize != int64_t(-1) && end > maxSize) 1.99 + end = maxSize; 1.100 + end -= 1; 1.101 + 1.102 + rangeSpec.AppendInt(int64_t(end)); 1.103 +} 1.104 + 1.105 +//----------------------------------------------------------------------------- 1.106 + 1.107 +class nsIncrementalDownload MOZ_FINAL 1.108 + : public nsIIncrementalDownload 1.109 + , public nsIStreamListener 1.110 + , public nsIObserver 1.111 + , public nsIInterfaceRequestor 1.112 + , public nsIChannelEventSink 1.113 + , public nsSupportsWeakReference 1.114 + , public nsIAsyncVerifyRedirectCallback 1.115 +{ 1.116 +public: 1.117 + NS_DECL_ISUPPORTS 1.118 + NS_DECL_NSIREQUEST 1.119 + NS_DECL_NSIINCREMENTALDOWNLOAD 1.120 + NS_DECL_NSIREQUESTOBSERVER 1.121 + NS_DECL_NSISTREAMLISTENER 1.122 + NS_DECL_NSIOBSERVER 1.123 + NS_DECL_NSIINTERFACEREQUESTOR 1.124 + NS_DECL_NSICHANNELEVENTSINK 1.125 + NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK 1.126 + 1.127 + nsIncrementalDownload(); 1.128 + 1.129 +private: 1.130 + ~nsIncrementalDownload() {} 1.131 + nsresult FlushChunk(); 1.132 + void UpdateProgress(); 1.133 + nsresult CallOnStartRequest(); 1.134 + void CallOnStopRequest(); 1.135 + nsresult StartTimer(int32_t interval); 1.136 + nsresult ProcessTimeout(); 1.137 + nsresult ReadCurrentSize(); 1.138 + nsresult ClearRequestHeader(nsIHttpChannel *channel); 1.139 + 1.140 + nsCOMPtr<nsIRequestObserver> mObserver; 1.141 + nsCOMPtr<nsISupports> mObserverContext; 1.142 + nsCOMPtr<nsIProgressEventSink> mProgressSink; 1.143 + nsCOMPtr<nsIURI> mURI; 1.144 + nsCOMPtr<nsIURI> mFinalURI; 1.145 + nsCOMPtr<nsIFile> mDest; 1.146 + nsCOMPtr<nsIChannel> mChannel; 1.147 + nsCOMPtr<nsITimer> mTimer; 1.148 + nsAutoArrayPtr<char> mChunk; 1.149 + int32_t mChunkLen; 1.150 + int32_t mChunkSize; 1.151 + int32_t mInterval; 1.152 + int64_t mTotalSize; 1.153 + int64_t mCurrentSize; 1.154 + uint32_t mLoadFlags; 1.155 + int32_t mNonPartialCount; 1.156 + nsresult mStatus; 1.157 + bool mIsPending; 1.158 + bool mDidOnStartRequest; 1.159 + PRTime mLastProgressUpdate; 1.160 + nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback; 1.161 + nsCOMPtr<nsIChannel> mNewRedirectChannel; 1.162 + nsCString mPartialValidator; 1.163 + bool mCacheBust; 1.164 +}; 1.165 + 1.166 +nsIncrementalDownload::nsIncrementalDownload() 1.167 + : mChunkLen(0) 1.168 + , mChunkSize(DEFAULT_CHUNK_SIZE) 1.169 + , mInterval(DEFAULT_INTERVAL) 1.170 + , mTotalSize(-1) 1.171 + , mCurrentSize(-1) 1.172 + , mLoadFlags(LOAD_NORMAL) 1.173 + , mNonPartialCount(0) 1.174 + , mStatus(NS_OK) 1.175 + , mIsPending(false) 1.176 + , mDidOnStartRequest(false) 1.177 + , mLastProgressUpdate(0) 1.178 + , mRedirectCallback(nullptr) 1.179 + , mNewRedirectChannel(nullptr) 1.180 + , mCacheBust(false) 1.181 +{ 1.182 +} 1.183 + 1.184 +nsresult 1.185 +nsIncrementalDownload::FlushChunk() 1.186 +{ 1.187 + NS_ASSERTION(mTotalSize != int64_t(-1), "total size should be known"); 1.188 + 1.189 + if (mChunkLen == 0) 1.190 + return NS_OK; 1.191 + 1.192 + nsresult rv = AppendToFile(mDest, mChunk, mChunkLen); 1.193 + if (NS_FAILED(rv)) 1.194 + return rv; 1.195 + 1.196 + mCurrentSize += int64_t(mChunkLen); 1.197 + mChunkLen = 0; 1.198 + 1.199 + return NS_OK; 1.200 +} 1.201 + 1.202 +void 1.203 +nsIncrementalDownload::UpdateProgress() 1.204 +{ 1.205 + mLastProgressUpdate = PR_Now(); 1.206 + 1.207 + if (mProgressSink) 1.208 + mProgressSink->OnProgress(this, mObserverContext, 1.209 + uint64_t(int64_t(mCurrentSize) + mChunkLen), 1.210 + uint64_t(int64_t(mTotalSize))); 1.211 +} 1.212 + 1.213 +nsresult 1.214 +nsIncrementalDownload::CallOnStartRequest() 1.215 +{ 1.216 + if (!mObserver || mDidOnStartRequest) 1.217 + return NS_OK; 1.218 + 1.219 + mDidOnStartRequest = true; 1.220 + return mObserver->OnStartRequest(this, mObserverContext); 1.221 +} 1.222 + 1.223 +void 1.224 +nsIncrementalDownload::CallOnStopRequest() 1.225 +{ 1.226 + if (!mObserver) 1.227 + return; 1.228 + 1.229 + // Ensure that OnStartRequest is always called once before OnStopRequest. 1.230 + nsresult rv = CallOnStartRequest(); 1.231 + if (NS_SUCCEEDED(mStatus)) 1.232 + mStatus = rv; 1.233 + 1.234 + mIsPending = false; 1.235 + 1.236 + mObserver->OnStopRequest(this, mObserverContext, mStatus); 1.237 + mObserver = nullptr; 1.238 + mObserverContext = nullptr; 1.239 +} 1.240 + 1.241 +nsresult 1.242 +nsIncrementalDownload::StartTimer(int32_t interval) 1.243 +{ 1.244 + nsresult rv; 1.245 + mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv); 1.246 + if (NS_FAILED(rv)) 1.247 + return rv; 1.248 + 1.249 + return mTimer->Init(this, interval * 1000, nsITimer::TYPE_ONE_SHOT); 1.250 +} 1.251 + 1.252 +nsresult 1.253 +nsIncrementalDownload::ProcessTimeout() 1.254 +{ 1.255 + NS_ASSERTION(!mChannel, "how can we have a channel?"); 1.256 + 1.257 + // Handle existing error conditions 1.258 + if (NS_FAILED(mStatus)) { 1.259 + CallOnStopRequest(); 1.260 + return NS_OK; 1.261 + } 1.262 + 1.263 + // Fetch next chunk 1.264 + 1.265 + nsCOMPtr<nsIChannel> channel; 1.266 + nsresult rv = NS_NewChannel(getter_AddRefs(channel), mFinalURI, nullptr, 1.267 + nullptr, this, mLoadFlags); 1.268 + if (NS_FAILED(rv)) 1.269 + return rv; 1.270 + 1.271 + nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(channel, &rv); 1.272 + if (NS_FAILED(rv)) 1.273 + return rv; 1.274 + 1.275 + NS_ASSERTION(mCurrentSize != int64_t(-1), 1.276 + "we should know the current file size by now"); 1.277 + 1.278 + rv = ClearRequestHeader(http); 1.279 + if (NS_FAILED(rv)) 1.280 + return rv; 1.281 + 1.282 + // Don't bother making a range request if we are just going to fetch the 1.283 + // entire document. 1.284 + if (mInterval || mCurrentSize != int64_t(0)) { 1.285 + nsAutoCString range; 1.286 + MakeRangeSpec(mCurrentSize, mTotalSize, mChunkSize, mInterval == 0, range); 1.287 + 1.288 + rv = http->SetRequestHeader(NS_LITERAL_CSTRING("Range"), range, false); 1.289 + if (NS_FAILED(rv)) 1.290 + return rv; 1.291 + 1.292 + if (!mPartialValidator.IsEmpty()) 1.293 + http->SetRequestHeader(NS_LITERAL_CSTRING("If-Range"), 1.294 + mPartialValidator, false); 1.295 + 1.296 + if (mCacheBust) { 1.297 + http->SetRequestHeader(NS_LITERAL_CSTRING("Cache-Control"), 1.298 + NS_LITERAL_CSTRING("no-cache"), false); 1.299 + http->SetRequestHeader(NS_LITERAL_CSTRING("Pragma"), 1.300 + NS_LITERAL_CSTRING("no-cache"), false); 1.301 + } 1.302 + } 1.303 + 1.304 + rv = channel->AsyncOpen(this, nullptr); 1.305 + if (NS_FAILED(rv)) 1.306 + return rv; 1.307 + 1.308 + // Wait to assign mChannel when we know we are going to succeed. This is 1.309 + // important because we don't want to introduce a reference cycle between 1.310 + // mChannel and this until we know for a fact that AsyncOpen has succeeded, 1.311 + // thus ensuring that our stream listener methods will be invoked. 1.312 + mChannel = channel; 1.313 + return NS_OK; 1.314 +} 1.315 + 1.316 +// Reads the current file size and validates it. 1.317 +nsresult 1.318 +nsIncrementalDownload::ReadCurrentSize() 1.319 +{ 1.320 + int64_t size; 1.321 + nsresult rv = mDest->GetFileSize((int64_t *) &size); 1.322 + if (rv == NS_ERROR_FILE_NOT_FOUND || 1.323 + rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) { 1.324 + mCurrentSize = 0; 1.325 + return NS_OK; 1.326 + } 1.327 + if (NS_FAILED(rv)) 1.328 + return rv; 1.329 + 1.330 + mCurrentSize = size; 1.331 + return NS_OK; 1.332 +} 1.333 + 1.334 +// nsISupports 1.335 + 1.336 +NS_IMPL_ISUPPORTS(nsIncrementalDownload, 1.337 + nsIIncrementalDownload, 1.338 + nsIRequest, 1.339 + nsIStreamListener, 1.340 + nsIRequestObserver, 1.341 + nsIObserver, 1.342 + nsIInterfaceRequestor, 1.343 + nsIChannelEventSink, 1.344 + nsISupportsWeakReference, 1.345 + nsIAsyncVerifyRedirectCallback) 1.346 + 1.347 +// nsIRequest 1.348 + 1.349 +NS_IMETHODIMP 1.350 +nsIncrementalDownload::GetName(nsACString &name) 1.351 +{ 1.352 + NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_INITIALIZED); 1.353 + 1.354 + return mURI->GetSpec(name); 1.355 +} 1.356 + 1.357 +NS_IMETHODIMP 1.358 +nsIncrementalDownload::IsPending(bool *isPending) 1.359 +{ 1.360 + *isPending = mIsPending; 1.361 + return NS_OK; 1.362 +} 1.363 + 1.364 +NS_IMETHODIMP 1.365 +nsIncrementalDownload::GetStatus(nsresult *status) 1.366 +{ 1.367 + *status = mStatus; 1.368 + return NS_OK; 1.369 +} 1.370 + 1.371 +NS_IMETHODIMP 1.372 +nsIncrementalDownload::Cancel(nsresult status) 1.373 +{ 1.374 + NS_ENSURE_ARG(NS_FAILED(status)); 1.375 + 1.376 + // Ignore this cancelation if we're already canceled. 1.377 + if (NS_FAILED(mStatus)) 1.378 + return NS_OK; 1.379 + 1.380 + mStatus = status; 1.381 + 1.382 + // Nothing more to do if callbacks aren't pending. 1.383 + if (!mIsPending) 1.384 + return NS_OK; 1.385 + 1.386 + if (mChannel) { 1.387 + mChannel->Cancel(mStatus); 1.388 + NS_ASSERTION(!mTimer, "what is this timer object doing here?"); 1.389 + } 1.390 + else { 1.391 + // dispatch a timer callback event to drive invoking our listener's 1.392 + // OnStopRequest. 1.393 + if (mTimer) 1.394 + mTimer->Cancel(); 1.395 + StartTimer(0); 1.396 + } 1.397 + 1.398 + return NS_OK; 1.399 +} 1.400 + 1.401 +NS_IMETHODIMP 1.402 +nsIncrementalDownload::Suspend() 1.403 +{ 1.404 + return NS_ERROR_NOT_IMPLEMENTED; 1.405 +} 1.406 + 1.407 +NS_IMETHODIMP 1.408 +nsIncrementalDownload::Resume() 1.409 +{ 1.410 + return NS_ERROR_NOT_IMPLEMENTED; 1.411 +} 1.412 + 1.413 +NS_IMETHODIMP 1.414 +nsIncrementalDownload::GetLoadFlags(nsLoadFlags *loadFlags) 1.415 +{ 1.416 + *loadFlags = mLoadFlags; 1.417 + return NS_OK; 1.418 +} 1.419 + 1.420 +NS_IMETHODIMP 1.421 +nsIncrementalDownload::SetLoadFlags(nsLoadFlags loadFlags) 1.422 +{ 1.423 + mLoadFlags = loadFlags; 1.424 + return NS_OK; 1.425 +} 1.426 + 1.427 +NS_IMETHODIMP 1.428 +nsIncrementalDownload::GetLoadGroup(nsILoadGroup **loadGroup) 1.429 +{ 1.430 + return NS_ERROR_NOT_IMPLEMENTED; 1.431 +} 1.432 + 1.433 +NS_IMETHODIMP 1.434 +nsIncrementalDownload::SetLoadGroup(nsILoadGroup *loadGroup) 1.435 +{ 1.436 + return NS_ERROR_NOT_IMPLEMENTED; 1.437 +} 1.438 + 1.439 +// nsIIncrementalDownload 1.440 + 1.441 +NS_IMETHODIMP 1.442 +nsIncrementalDownload::Init(nsIURI *uri, nsIFile *dest, 1.443 + int32_t chunkSize, int32_t interval) 1.444 +{ 1.445 + // Keep it simple: only allow initialization once 1.446 + NS_ENSURE_FALSE(mURI, NS_ERROR_ALREADY_INITIALIZED); 1.447 + 1.448 + mDest = do_QueryInterface(dest); 1.449 + NS_ENSURE_ARG(mDest); 1.450 + 1.451 + mURI = uri; 1.452 + mFinalURI = uri; 1.453 + 1.454 + if (chunkSize > 0) 1.455 + mChunkSize = chunkSize; 1.456 + if (interval >= 0) 1.457 + mInterval = interval; 1.458 + return NS_OK; 1.459 +} 1.460 + 1.461 +NS_IMETHODIMP 1.462 +nsIncrementalDownload::GetURI(nsIURI **result) 1.463 +{ 1.464 + NS_IF_ADDREF(*result = mURI); 1.465 + return NS_OK; 1.466 +} 1.467 + 1.468 +NS_IMETHODIMP 1.469 +nsIncrementalDownload::GetFinalURI(nsIURI **result) 1.470 +{ 1.471 + NS_IF_ADDREF(*result = mFinalURI); 1.472 + return NS_OK; 1.473 +} 1.474 + 1.475 +NS_IMETHODIMP 1.476 +nsIncrementalDownload::GetDestination(nsIFile **result) 1.477 +{ 1.478 + if (!mDest) { 1.479 + *result = nullptr; 1.480 + return NS_OK; 1.481 + } 1.482 + // Return a clone of mDest so that callers may modify the resulting nsIFile 1.483 + // without corrupting our internal object. This also works around the fact 1.484 + // that some nsIFile impls may cache the result of stat'ing the filesystem. 1.485 + return mDest->Clone(result); 1.486 +} 1.487 + 1.488 +NS_IMETHODIMP 1.489 +nsIncrementalDownload::GetTotalSize(int64_t *result) 1.490 +{ 1.491 + *result = mTotalSize; 1.492 + return NS_OK; 1.493 +} 1.494 + 1.495 +NS_IMETHODIMP 1.496 +nsIncrementalDownload::GetCurrentSize(int64_t *result) 1.497 +{ 1.498 + *result = mCurrentSize; 1.499 + return NS_OK; 1.500 +} 1.501 + 1.502 +NS_IMETHODIMP 1.503 +nsIncrementalDownload::Start(nsIRequestObserver *observer, 1.504 + nsISupports *context) 1.505 +{ 1.506 + NS_ENSURE_ARG(observer); 1.507 + NS_ENSURE_FALSE(mIsPending, NS_ERROR_IN_PROGRESS); 1.508 + 1.509 + // Observe system shutdown so we can be sure to release any reference held 1.510 + // between ourselves and the timer. We have the observer service hold a weak 1.511 + // reference to us, so that we don't have to worry about calling 1.512 + // RemoveObserver. XXX(darin): The timer code should do this for us. 1.513 + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); 1.514 + if (obs) 1.515 + obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true); 1.516 + 1.517 + nsresult rv = ReadCurrentSize(); 1.518 + if (NS_FAILED(rv)) 1.519 + return rv; 1.520 + 1.521 + rv = StartTimer(0); 1.522 + if (NS_FAILED(rv)) 1.523 + return rv; 1.524 + 1.525 + mObserver = observer; 1.526 + mObserverContext = context; 1.527 + mProgressSink = do_QueryInterface(observer); // ok if null 1.528 + 1.529 + mIsPending = true; 1.530 + return NS_OK; 1.531 +} 1.532 + 1.533 +// nsIRequestObserver 1.534 + 1.535 +NS_IMETHODIMP 1.536 +nsIncrementalDownload::OnStartRequest(nsIRequest *request, 1.537 + nsISupports *context) 1.538 +{ 1.539 + nsresult rv; 1.540 + 1.541 + nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(request, &rv); 1.542 + if (NS_FAILED(rv)) 1.543 + return rv; 1.544 + 1.545 + // Ensure that we are receiving a 206 response. 1.546 + uint32_t code; 1.547 + rv = http->GetResponseStatus(&code); 1.548 + if (NS_FAILED(rv)) 1.549 + return rv; 1.550 + if (code != 206) { 1.551 + // We may already have the entire file downloaded, in which case 1.552 + // our request for a range beyond the end of the file would have 1.553 + // been met with an error response code. 1.554 + if (code == 416 && mTotalSize == int64_t(-1)) { 1.555 + mTotalSize = mCurrentSize; 1.556 + // Return an error code here to suppress OnDataAvailable. 1.557 + return NS_ERROR_DOWNLOAD_COMPLETE; 1.558 + } 1.559 + // The server may have decided to give us all of the data in one chunk. If 1.560 + // we requested a partial range, then we don't want to download all of the 1.561 + // data at once. So, we'll just try again, but if this keeps happening then 1.562 + // we'll eventually give up. 1.563 + if (code == 200) { 1.564 + if (mInterval) { 1.565 + mChannel = nullptr; 1.566 + if (++mNonPartialCount > MAX_RETRY_COUNT) { 1.567 + NS_WARNING("unable to fetch a byte range; giving up"); 1.568 + return NS_ERROR_FAILURE; 1.569 + } 1.570 + // Increase delay with each failure. 1.571 + StartTimer(mInterval * mNonPartialCount); 1.572 + return NS_ERROR_DOWNLOAD_NOT_PARTIAL; 1.573 + } 1.574 + // Since we have been asked to download the rest of the file, we can deal 1.575 + // with a 200 response. This may result in downloading the beginning of 1.576 + // the file again, but that can't really be helped. 1.577 + } else { 1.578 + NS_WARNING("server response was unexpected"); 1.579 + return NS_ERROR_UNEXPECTED; 1.580 + } 1.581 + } else { 1.582 + // We got a partial response, so clear this counter in case the next chunk 1.583 + // results in a 200 response. 1.584 + mNonPartialCount = 0; 1.585 + 1.586 + // confirm that the content-range response header is consistent with 1.587 + // expectations on each 206. If it is not then drop this response and 1.588 + // retry with no-cache set. 1.589 + if (!mCacheBust) { 1.590 + nsAutoCString buf; 1.591 + int64_t startByte = 0; 1.592 + bool confirmedOK = false; 1.593 + 1.594 + rv = http->GetResponseHeader(NS_LITERAL_CSTRING("Content-Range"), buf); 1.595 + if (NS_FAILED(rv)) 1.596 + return rv; // it isn't a useful 206 without a CONTENT-RANGE of some sort 1.597 + 1.598 + // Content-Range: bytes 0-299999/25604694 1.599 + int32_t p = buf.Find("bytes "); 1.600 + 1.601 + // first look for the starting point of the content-range 1.602 + // to make sure it is what we expect 1.603 + if (p != -1) { 1.604 + char *endptr = nullptr; 1.605 + const char *s = buf.get() + p + 6; 1.606 + while (*s && *s == ' ') 1.607 + s++; 1.608 + startByte = strtol(s, &endptr, 10); 1.609 + 1.610 + if (*s && endptr && (endptr != s) && 1.611 + (mCurrentSize == startByte)) { 1.612 + 1.613 + // ok the starting point is confirmed. We still need to check the 1.614 + // total size of the range for consistency if this isn't 1.615 + // the first chunk 1.616 + if (mTotalSize == int64_t(-1)) { 1.617 + // first chunk 1.618 + confirmedOK = true; 1.619 + } else { 1.620 + int32_t slash = buf.FindChar('/'); 1.621 + int64_t rangeSize = 0; 1.622 + if (slash != kNotFound && 1.623 + (PR_sscanf(buf.get() + slash + 1, "%lld", (int64_t *) &rangeSize) == 1) && 1.624 + rangeSize == mTotalSize) { 1.625 + confirmedOK = true; 1.626 + } 1.627 + } 1.628 + } 1.629 + } 1.630 + 1.631 + if (!confirmedOK) { 1.632 + NS_WARNING("unexpected content-range"); 1.633 + mCacheBust = true; 1.634 + mChannel = nullptr; 1.635 + if (++mNonPartialCount > MAX_RETRY_COUNT) { 1.636 + NS_WARNING("unable to fetch a byte range; giving up"); 1.637 + return NS_ERROR_FAILURE; 1.638 + } 1.639 + // Increase delay with each failure. 1.640 + StartTimer(mInterval * mNonPartialCount); 1.641 + return NS_ERROR_DOWNLOAD_NOT_PARTIAL; 1.642 + } 1.643 + } 1.644 + } 1.645 + 1.646 + // Do special processing after the first response. 1.647 + if (mTotalSize == int64_t(-1)) { 1.648 + // Update knowledge of mFinalURI 1.649 + rv = http->GetURI(getter_AddRefs(mFinalURI)); 1.650 + if (NS_FAILED(rv)) 1.651 + return rv; 1.652 + http->GetResponseHeader(NS_LITERAL_CSTRING("Etag"), mPartialValidator); 1.653 + if (StringBeginsWith(mPartialValidator, NS_LITERAL_CSTRING("W/"))) 1.654 + mPartialValidator.Truncate(); // don't use weak validators 1.655 + if (mPartialValidator.IsEmpty()) 1.656 + http->GetResponseHeader(NS_LITERAL_CSTRING("Last-Modified"), mPartialValidator); 1.657 + 1.658 + if (code == 206) { 1.659 + // OK, read the Content-Range header to determine the total size of this 1.660 + // download file. 1.661 + nsAutoCString buf; 1.662 + rv = http->GetResponseHeader(NS_LITERAL_CSTRING("Content-Range"), buf); 1.663 + if (NS_FAILED(rv)) 1.664 + return rv; 1.665 + int32_t slash = buf.FindChar('/'); 1.666 + if (slash == kNotFound) { 1.667 + NS_WARNING("server returned invalid Content-Range header!"); 1.668 + return NS_ERROR_UNEXPECTED; 1.669 + } 1.670 + if (PR_sscanf(buf.get() + slash + 1, "%lld", (int64_t *) &mTotalSize) != 1) 1.671 + return NS_ERROR_UNEXPECTED; 1.672 + } else { 1.673 + rv = http->GetContentLength(&mTotalSize); 1.674 + if (NS_FAILED(rv)) 1.675 + return rv; 1.676 + // We need to know the total size of the thing we're trying to download. 1.677 + if (mTotalSize == int64_t(-1)) { 1.678 + NS_WARNING("server returned no content-length header!"); 1.679 + return NS_ERROR_UNEXPECTED; 1.680 + } 1.681 + // Need to truncate (or create, if it doesn't exist) the file since we 1.682 + // are downloading the whole thing. 1.683 + WriteToFile(mDest, nullptr, 0, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE); 1.684 + mCurrentSize = 0; 1.685 + } 1.686 + 1.687 + // Notify observer that we are starting... 1.688 + rv = CallOnStartRequest(); 1.689 + if (NS_FAILED(rv)) 1.690 + return rv; 1.691 + } 1.692 + 1.693 + // Adjust mChunkSize accordingly if mCurrentSize is close to mTotalSize. 1.694 + int64_t diff = mTotalSize - mCurrentSize; 1.695 + if (diff <= int64_t(0)) { 1.696 + NS_WARNING("about to set a bogus chunk size; giving up"); 1.697 + return NS_ERROR_UNEXPECTED; 1.698 + } 1.699 + 1.700 + if (diff < int64_t(mChunkSize)) 1.701 + mChunkSize = uint32_t(diff); 1.702 + 1.703 + mChunk = new char[mChunkSize]; 1.704 + if (!mChunk) 1.705 + rv = NS_ERROR_OUT_OF_MEMORY; 1.706 + 1.707 + return rv; 1.708 +} 1.709 + 1.710 +NS_IMETHODIMP 1.711 +nsIncrementalDownload::OnStopRequest(nsIRequest *request, 1.712 + nsISupports *context, 1.713 + nsresult status) 1.714 +{ 1.715 + // Not a real error; just a trick to kill off the channel without our 1.716 + // listener having to care. 1.717 + if (status == NS_ERROR_DOWNLOAD_NOT_PARTIAL) 1.718 + return NS_OK; 1.719 + 1.720 + // Not a real error; just a trick used to suppress OnDataAvailable calls. 1.721 + if (status == NS_ERROR_DOWNLOAD_COMPLETE) 1.722 + status = NS_OK; 1.723 + 1.724 + if (NS_SUCCEEDED(mStatus)) 1.725 + mStatus = status; 1.726 + 1.727 + if (mChunk) { 1.728 + if (NS_SUCCEEDED(mStatus)) 1.729 + mStatus = FlushChunk(); 1.730 + 1.731 + mChunk = nullptr; // deletes memory 1.732 + mChunkLen = 0; 1.733 + UpdateProgress(); 1.734 + } 1.735 + 1.736 + mChannel = nullptr; 1.737 + 1.738 + // Notify listener if we hit an error or finished 1.739 + if (NS_FAILED(mStatus) || mCurrentSize == mTotalSize) { 1.740 + CallOnStopRequest(); 1.741 + return NS_OK; 1.742 + } 1.743 + 1.744 + return StartTimer(mInterval); // Do next chunk 1.745 +} 1.746 + 1.747 +// nsIStreamListener 1.748 + 1.749 +NS_IMETHODIMP 1.750 +nsIncrementalDownload::OnDataAvailable(nsIRequest *request, 1.751 + nsISupports *context, 1.752 + nsIInputStream *input, 1.753 + uint64_t offset, 1.754 + uint32_t count) 1.755 +{ 1.756 + while (count) { 1.757 + uint32_t space = mChunkSize - mChunkLen; 1.758 + uint32_t n, len = std::min(space, count); 1.759 + 1.760 + nsresult rv = input->Read(mChunk + mChunkLen, len, &n); 1.761 + if (NS_FAILED(rv)) 1.762 + return rv; 1.763 + if (n != len) 1.764 + return NS_ERROR_UNEXPECTED; 1.765 + 1.766 + count -= n; 1.767 + mChunkLen += n; 1.768 + 1.769 + if (mChunkLen == mChunkSize) { 1.770 + rv = FlushChunk(); 1.771 + if (NS_FAILED(rv)) 1.772 + return rv; 1.773 + } 1.774 + } 1.775 + 1.776 + if (PR_Now() > mLastProgressUpdate + UPDATE_PROGRESS_INTERVAL) 1.777 + UpdateProgress(); 1.778 + 1.779 + return NS_OK; 1.780 +} 1.781 + 1.782 +// nsIObserver 1.783 + 1.784 +NS_IMETHODIMP 1.785 +nsIncrementalDownload::Observe(nsISupports *subject, const char *topic, 1.786 + const char16_t *data) 1.787 +{ 1.788 + if (strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { 1.789 + Cancel(NS_ERROR_ABORT); 1.790 + 1.791 + // Since the app is shutting down, we need to go ahead and notify our 1.792 + // observer here. Otherwise, we would notify them after XPCOM has been 1.793 + // shutdown or not at all. 1.794 + CallOnStopRequest(); 1.795 + } 1.796 + else if (strcmp(topic, NS_TIMER_CALLBACK_TOPIC) == 0) { 1.797 + mTimer = nullptr; 1.798 + nsresult rv = ProcessTimeout(); 1.799 + if (NS_FAILED(rv)) 1.800 + Cancel(rv); 1.801 + } 1.802 + return NS_OK; 1.803 +} 1.804 + 1.805 +// nsIInterfaceRequestor 1.806 + 1.807 +NS_IMETHODIMP 1.808 +nsIncrementalDownload::GetInterface(const nsIID &iid, void **result) 1.809 +{ 1.810 + if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) { 1.811 + NS_ADDREF_THIS(); 1.812 + *result = static_cast<nsIChannelEventSink *>(this); 1.813 + return NS_OK; 1.814 + } 1.815 + 1.816 + nsCOMPtr<nsIInterfaceRequestor> ir = do_QueryInterface(mObserver); 1.817 + if (ir) 1.818 + return ir->GetInterface(iid, result); 1.819 + 1.820 + return NS_ERROR_NO_INTERFACE; 1.821 +} 1.822 + 1.823 +nsresult 1.824 +nsIncrementalDownload::ClearRequestHeader(nsIHttpChannel *channel) 1.825 +{ 1.826 + NS_ENSURE_ARG(channel); 1.827 + 1.828 + // We don't support encodings -- they make the Content-Length not equal 1.829 + // to the actual size of the data. 1.830 + return channel->SetRequestHeader(NS_LITERAL_CSTRING("Accept-Encoding"), 1.831 + NS_LITERAL_CSTRING(""), false); 1.832 +} 1.833 + 1.834 +// nsIChannelEventSink 1.835 + 1.836 +NS_IMETHODIMP 1.837 +nsIncrementalDownload::AsyncOnChannelRedirect(nsIChannel *oldChannel, 1.838 + nsIChannel *newChannel, 1.839 + uint32_t flags, 1.840 + nsIAsyncVerifyRedirectCallback *cb) 1.841 +{ 1.842 + // In response to a redirect, we need to propagate the Range header. See bug 1.843 + // 311595. Any failure code returned from this function aborts the redirect. 1.844 + 1.845 + nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(oldChannel); 1.846 + NS_ENSURE_STATE(http); 1.847 + 1.848 + nsCOMPtr<nsIHttpChannel> newHttpChannel = do_QueryInterface(newChannel); 1.849 + NS_ENSURE_STATE(newHttpChannel); 1.850 + 1.851 + NS_NAMED_LITERAL_CSTRING(rangeHdr, "Range"); 1.852 + 1.853 + nsresult rv = ClearRequestHeader(newHttpChannel); 1.854 + if (NS_FAILED(rv)) 1.855 + return rv; 1.856 + 1.857 + // If we didn't have a Range header, then we must be doing a full download. 1.858 + nsAutoCString rangeVal; 1.859 + http->GetRequestHeader(rangeHdr, rangeVal); 1.860 + if (!rangeVal.IsEmpty()) { 1.861 + rv = newHttpChannel->SetRequestHeader(rangeHdr, rangeVal, false); 1.862 + NS_ENSURE_SUCCESS(rv, rv); 1.863 + } 1.864 + 1.865 + // A redirection changes the validator 1.866 + mPartialValidator.Truncate(); 1.867 + 1.868 + if (mCacheBust) { 1.869 + newHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Cache-Control"), 1.870 + NS_LITERAL_CSTRING("no-cache"), false); 1.871 + newHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Pragma"), 1.872 + NS_LITERAL_CSTRING("no-cache"), false); 1.873 + } 1.874 + 1.875 + // Prepare to receive callback 1.876 + mRedirectCallback = cb; 1.877 + mNewRedirectChannel = newChannel; 1.878 + 1.879 + // Give the observer a chance to see this redirect notification. 1.880 + nsCOMPtr<nsIChannelEventSink> sink = do_GetInterface(mObserver); 1.881 + if (sink) { 1.882 + rv = sink->AsyncOnChannelRedirect(oldChannel, newChannel, flags, this); 1.883 + if (NS_FAILED(rv)) { 1.884 + mRedirectCallback = nullptr; 1.885 + mNewRedirectChannel = nullptr; 1.886 + } 1.887 + return rv; 1.888 + } 1.889 + (void) OnRedirectVerifyCallback(NS_OK); 1.890 + return NS_OK; 1.891 +} 1.892 + 1.893 +NS_IMETHODIMP 1.894 +nsIncrementalDownload::OnRedirectVerifyCallback(nsresult result) 1.895 +{ 1.896 + NS_ASSERTION(mRedirectCallback, "mRedirectCallback not set in callback"); 1.897 + NS_ASSERTION(mNewRedirectChannel, "mNewRedirectChannel not set in callback"); 1.898 + 1.899 + // Update mChannel, so we can Cancel the new channel. 1.900 + if (NS_SUCCEEDED(result)) 1.901 + mChannel = mNewRedirectChannel; 1.902 + 1.903 + mRedirectCallback->OnRedirectVerifyCallback(result); 1.904 + mRedirectCallback = nullptr; 1.905 + mNewRedirectChannel = nullptr; 1.906 + return NS_OK; 1.907 +} 1.908 + 1.909 +extern nsresult 1.910 +net_NewIncrementalDownload(nsISupports *outer, const nsIID &iid, void **result) 1.911 +{ 1.912 + if (outer) 1.913 + return NS_ERROR_NO_AGGREGATION; 1.914 + 1.915 + nsIncrementalDownload *d = new nsIncrementalDownload(); 1.916 + if (!d) 1.917 + return NS_ERROR_OUT_OF_MEMORY; 1.918 + 1.919 + NS_ADDREF(d); 1.920 + nsresult rv = d->QueryInterface(iid, result); 1.921 + NS_RELEASE(d); 1.922 + return rv; 1.923 +}