netwerk/base/src/nsIncrementalDownload.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 #include "mozilla/Attributes.h"
michael@0 8
michael@0 9 #include "nsIIncrementalDownload.h"
michael@0 10 #include "nsIRequestObserver.h"
michael@0 11 #include "nsIProgressEventSink.h"
michael@0 12 #include "nsIChannelEventSink.h"
michael@0 13 #include "nsIAsyncVerifyRedirectCallback.h"
michael@0 14 #include "nsIInterfaceRequestor.h"
michael@0 15 #include "nsIObserverService.h"
michael@0 16 #include "nsIObserver.h"
michael@0 17 #include "nsIFile.h"
michael@0 18 #include "nsITimer.h"
michael@0 19 #include "nsNetUtil.h"
michael@0 20 #include "nsAutoPtr.h"
michael@0 21 #include "nsWeakReference.h"
michael@0 22 #include "prio.h"
michael@0 23 #include "prprf.h"
michael@0 24 #include <algorithm>
michael@0 25
michael@0 26 // Default values used to initialize a nsIncrementalDownload object.
michael@0 27 #define DEFAULT_CHUNK_SIZE (4096 * 16) // bytes
michael@0 28 #define DEFAULT_INTERVAL 60 // seconds
michael@0 29
michael@0 30 #define UPDATE_PROGRESS_INTERVAL PRTime(500 * PR_USEC_PER_MSEC) // 500ms
michael@0 31
michael@0 32 // Number of times to retry a failed byte-range request.
michael@0 33 #define MAX_RETRY_COUNT 20
michael@0 34
michael@0 35 //-----------------------------------------------------------------------------
michael@0 36
michael@0 37 static nsresult
michael@0 38 WriteToFile(nsIFile *lf, const char *data, uint32_t len, int32_t flags)
michael@0 39 {
michael@0 40 PRFileDesc *fd;
michael@0 41 int32_t mode = 0600;
michael@0 42 nsresult rv;
michael@0 43 #if defined(MOZ_WIDGET_GONK)
michael@0 44 // The sdcard on a B2G phone looks like:
michael@0 45 // d---rwx--- system sdcard_rw 1970-01-01 01:00:00 sdcard
michael@0 46 // On the emulator, xpcshell fails when using 0600 mode to open the file,
michael@0 47 // and 0660 works.
michael@0 48 nsCOMPtr<nsIFile> parent;
michael@0 49 rv = lf->GetParent(getter_AddRefs(parent));
michael@0 50 if (NS_FAILED(rv)) {
michael@0 51 return rv;
michael@0 52 }
michael@0 53 uint32_t parentPerm;
michael@0 54 rv = parent->GetPermissions(&parentPerm);
michael@0 55 if (NS_FAILED(rv)) {
michael@0 56 return rv;
michael@0 57 }
michael@0 58 if ((parentPerm & 0700) == 0) {
michael@0 59 // Parent directory has no owner-write, so try to use group permissions
michael@0 60 // instead of owner permissions.
michael@0 61 mode = 0660;
michael@0 62 }
michael@0 63 #endif
michael@0 64 rv = lf->OpenNSPRFileDesc(flags, mode, &fd);
michael@0 65 if (NS_FAILED(rv))
michael@0 66 return rv;
michael@0 67
michael@0 68 if (len)
michael@0 69 rv = PR_Write(fd, data, len) == int32_t(len) ? NS_OK : NS_ERROR_FAILURE;
michael@0 70
michael@0 71 PR_Close(fd);
michael@0 72 return rv;
michael@0 73 }
michael@0 74
michael@0 75 static nsresult
michael@0 76 AppendToFile(nsIFile *lf, const char *data, uint32_t len)
michael@0 77 {
michael@0 78 int32_t flags = PR_WRONLY | PR_CREATE_FILE | PR_APPEND;
michael@0 79 return WriteToFile(lf, data, len, flags);
michael@0 80 }
michael@0 81
michael@0 82 // maxSize may be -1 if unknown
michael@0 83 static void
michael@0 84 MakeRangeSpec(const int64_t &size, const int64_t &maxSize, int32_t chunkSize,
michael@0 85 bool fetchRemaining, nsCString &rangeSpec)
michael@0 86 {
michael@0 87 rangeSpec.AssignLiteral("bytes=");
michael@0 88 rangeSpec.AppendInt(int64_t(size));
michael@0 89 rangeSpec.Append('-');
michael@0 90
michael@0 91 if (fetchRemaining)
michael@0 92 return;
michael@0 93
michael@0 94 int64_t end = size + int64_t(chunkSize);
michael@0 95 if (maxSize != int64_t(-1) && end > maxSize)
michael@0 96 end = maxSize;
michael@0 97 end -= 1;
michael@0 98
michael@0 99 rangeSpec.AppendInt(int64_t(end));
michael@0 100 }
michael@0 101
michael@0 102 //-----------------------------------------------------------------------------
michael@0 103
michael@0 104 class nsIncrementalDownload MOZ_FINAL
michael@0 105 : public nsIIncrementalDownload
michael@0 106 , public nsIStreamListener
michael@0 107 , public nsIObserver
michael@0 108 , public nsIInterfaceRequestor
michael@0 109 , public nsIChannelEventSink
michael@0 110 , public nsSupportsWeakReference
michael@0 111 , public nsIAsyncVerifyRedirectCallback
michael@0 112 {
michael@0 113 public:
michael@0 114 NS_DECL_ISUPPORTS
michael@0 115 NS_DECL_NSIREQUEST
michael@0 116 NS_DECL_NSIINCREMENTALDOWNLOAD
michael@0 117 NS_DECL_NSIREQUESTOBSERVER
michael@0 118 NS_DECL_NSISTREAMLISTENER
michael@0 119 NS_DECL_NSIOBSERVER
michael@0 120 NS_DECL_NSIINTERFACEREQUESTOR
michael@0 121 NS_DECL_NSICHANNELEVENTSINK
michael@0 122 NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
michael@0 123
michael@0 124 nsIncrementalDownload();
michael@0 125
michael@0 126 private:
michael@0 127 ~nsIncrementalDownload() {}
michael@0 128 nsresult FlushChunk();
michael@0 129 void UpdateProgress();
michael@0 130 nsresult CallOnStartRequest();
michael@0 131 void CallOnStopRequest();
michael@0 132 nsresult StartTimer(int32_t interval);
michael@0 133 nsresult ProcessTimeout();
michael@0 134 nsresult ReadCurrentSize();
michael@0 135 nsresult ClearRequestHeader(nsIHttpChannel *channel);
michael@0 136
michael@0 137 nsCOMPtr<nsIRequestObserver> mObserver;
michael@0 138 nsCOMPtr<nsISupports> mObserverContext;
michael@0 139 nsCOMPtr<nsIProgressEventSink> mProgressSink;
michael@0 140 nsCOMPtr<nsIURI> mURI;
michael@0 141 nsCOMPtr<nsIURI> mFinalURI;
michael@0 142 nsCOMPtr<nsIFile> mDest;
michael@0 143 nsCOMPtr<nsIChannel> mChannel;
michael@0 144 nsCOMPtr<nsITimer> mTimer;
michael@0 145 nsAutoArrayPtr<char> mChunk;
michael@0 146 int32_t mChunkLen;
michael@0 147 int32_t mChunkSize;
michael@0 148 int32_t mInterval;
michael@0 149 int64_t mTotalSize;
michael@0 150 int64_t mCurrentSize;
michael@0 151 uint32_t mLoadFlags;
michael@0 152 int32_t mNonPartialCount;
michael@0 153 nsresult mStatus;
michael@0 154 bool mIsPending;
michael@0 155 bool mDidOnStartRequest;
michael@0 156 PRTime mLastProgressUpdate;
michael@0 157 nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback;
michael@0 158 nsCOMPtr<nsIChannel> mNewRedirectChannel;
michael@0 159 nsCString mPartialValidator;
michael@0 160 bool mCacheBust;
michael@0 161 };
michael@0 162
michael@0 163 nsIncrementalDownload::nsIncrementalDownload()
michael@0 164 : mChunkLen(0)
michael@0 165 , mChunkSize(DEFAULT_CHUNK_SIZE)
michael@0 166 , mInterval(DEFAULT_INTERVAL)
michael@0 167 , mTotalSize(-1)
michael@0 168 , mCurrentSize(-1)
michael@0 169 , mLoadFlags(LOAD_NORMAL)
michael@0 170 , mNonPartialCount(0)
michael@0 171 , mStatus(NS_OK)
michael@0 172 , mIsPending(false)
michael@0 173 , mDidOnStartRequest(false)
michael@0 174 , mLastProgressUpdate(0)
michael@0 175 , mRedirectCallback(nullptr)
michael@0 176 , mNewRedirectChannel(nullptr)
michael@0 177 , mCacheBust(false)
michael@0 178 {
michael@0 179 }
michael@0 180
michael@0 181 nsresult
michael@0 182 nsIncrementalDownload::FlushChunk()
michael@0 183 {
michael@0 184 NS_ASSERTION(mTotalSize != int64_t(-1), "total size should be known");
michael@0 185
michael@0 186 if (mChunkLen == 0)
michael@0 187 return NS_OK;
michael@0 188
michael@0 189 nsresult rv = AppendToFile(mDest, mChunk, mChunkLen);
michael@0 190 if (NS_FAILED(rv))
michael@0 191 return rv;
michael@0 192
michael@0 193 mCurrentSize += int64_t(mChunkLen);
michael@0 194 mChunkLen = 0;
michael@0 195
michael@0 196 return NS_OK;
michael@0 197 }
michael@0 198
michael@0 199 void
michael@0 200 nsIncrementalDownload::UpdateProgress()
michael@0 201 {
michael@0 202 mLastProgressUpdate = PR_Now();
michael@0 203
michael@0 204 if (mProgressSink)
michael@0 205 mProgressSink->OnProgress(this, mObserverContext,
michael@0 206 uint64_t(int64_t(mCurrentSize) + mChunkLen),
michael@0 207 uint64_t(int64_t(mTotalSize)));
michael@0 208 }
michael@0 209
michael@0 210 nsresult
michael@0 211 nsIncrementalDownload::CallOnStartRequest()
michael@0 212 {
michael@0 213 if (!mObserver || mDidOnStartRequest)
michael@0 214 return NS_OK;
michael@0 215
michael@0 216 mDidOnStartRequest = true;
michael@0 217 return mObserver->OnStartRequest(this, mObserverContext);
michael@0 218 }
michael@0 219
michael@0 220 void
michael@0 221 nsIncrementalDownload::CallOnStopRequest()
michael@0 222 {
michael@0 223 if (!mObserver)
michael@0 224 return;
michael@0 225
michael@0 226 // Ensure that OnStartRequest is always called once before OnStopRequest.
michael@0 227 nsresult rv = CallOnStartRequest();
michael@0 228 if (NS_SUCCEEDED(mStatus))
michael@0 229 mStatus = rv;
michael@0 230
michael@0 231 mIsPending = false;
michael@0 232
michael@0 233 mObserver->OnStopRequest(this, mObserverContext, mStatus);
michael@0 234 mObserver = nullptr;
michael@0 235 mObserverContext = nullptr;
michael@0 236 }
michael@0 237
michael@0 238 nsresult
michael@0 239 nsIncrementalDownload::StartTimer(int32_t interval)
michael@0 240 {
michael@0 241 nsresult rv;
michael@0 242 mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
michael@0 243 if (NS_FAILED(rv))
michael@0 244 return rv;
michael@0 245
michael@0 246 return mTimer->Init(this, interval * 1000, nsITimer::TYPE_ONE_SHOT);
michael@0 247 }
michael@0 248
michael@0 249 nsresult
michael@0 250 nsIncrementalDownload::ProcessTimeout()
michael@0 251 {
michael@0 252 NS_ASSERTION(!mChannel, "how can we have a channel?");
michael@0 253
michael@0 254 // Handle existing error conditions
michael@0 255 if (NS_FAILED(mStatus)) {
michael@0 256 CallOnStopRequest();
michael@0 257 return NS_OK;
michael@0 258 }
michael@0 259
michael@0 260 // Fetch next chunk
michael@0 261
michael@0 262 nsCOMPtr<nsIChannel> channel;
michael@0 263 nsresult rv = NS_NewChannel(getter_AddRefs(channel), mFinalURI, nullptr,
michael@0 264 nullptr, this, mLoadFlags);
michael@0 265 if (NS_FAILED(rv))
michael@0 266 return rv;
michael@0 267
michael@0 268 nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(channel, &rv);
michael@0 269 if (NS_FAILED(rv))
michael@0 270 return rv;
michael@0 271
michael@0 272 NS_ASSERTION(mCurrentSize != int64_t(-1),
michael@0 273 "we should know the current file size by now");
michael@0 274
michael@0 275 rv = ClearRequestHeader(http);
michael@0 276 if (NS_FAILED(rv))
michael@0 277 return rv;
michael@0 278
michael@0 279 // Don't bother making a range request if we are just going to fetch the
michael@0 280 // entire document.
michael@0 281 if (mInterval || mCurrentSize != int64_t(0)) {
michael@0 282 nsAutoCString range;
michael@0 283 MakeRangeSpec(mCurrentSize, mTotalSize, mChunkSize, mInterval == 0, range);
michael@0 284
michael@0 285 rv = http->SetRequestHeader(NS_LITERAL_CSTRING("Range"), range, false);
michael@0 286 if (NS_FAILED(rv))
michael@0 287 return rv;
michael@0 288
michael@0 289 if (!mPartialValidator.IsEmpty())
michael@0 290 http->SetRequestHeader(NS_LITERAL_CSTRING("If-Range"),
michael@0 291 mPartialValidator, false);
michael@0 292
michael@0 293 if (mCacheBust) {
michael@0 294 http->SetRequestHeader(NS_LITERAL_CSTRING("Cache-Control"),
michael@0 295 NS_LITERAL_CSTRING("no-cache"), false);
michael@0 296 http->SetRequestHeader(NS_LITERAL_CSTRING("Pragma"),
michael@0 297 NS_LITERAL_CSTRING("no-cache"), false);
michael@0 298 }
michael@0 299 }
michael@0 300
michael@0 301 rv = channel->AsyncOpen(this, nullptr);
michael@0 302 if (NS_FAILED(rv))
michael@0 303 return rv;
michael@0 304
michael@0 305 // Wait to assign mChannel when we know we are going to succeed. This is
michael@0 306 // important because we don't want to introduce a reference cycle between
michael@0 307 // mChannel and this until we know for a fact that AsyncOpen has succeeded,
michael@0 308 // thus ensuring that our stream listener methods will be invoked.
michael@0 309 mChannel = channel;
michael@0 310 return NS_OK;
michael@0 311 }
michael@0 312
michael@0 313 // Reads the current file size and validates it.
michael@0 314 nsresult
michael@0 315 nsIncrementalDownload::ReadCurrentSize()
michael@0 316 {
michael@0 317 int64_t size;
michael@0 318 nsresult rv = mDest->GetFileSize((int64_t *) &size);
michael@0 319 if (rv == NS_ERROR_FILE_NOT_FOUND ||
michael@0 320 rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
michael@0 321 mCurrentSize = 0;
michael@0 322 return NS_OK;
michael@0 323 }
michael@0 324 if (NS_FAILED(rv))
michael@0 325 return rv;
michael@0 326
michael@0 327 mCurrentSize = size;
michael@0 328 return NS_OK;
michael@0 329 }
michael@0 330
michael@0 331 // nsISupports
michael@0 332
michael@0 333 NS_IMPL_ISUPPORTS(nsIncrementalDownload,
michael@0 334 nsIIncrementalDownload,
michael@0 335 nsIRequest,
michael@0 336 nsIStreamListener,
michael@0 337 nsIRequestObserver,
michael@0 338 nsIObserver,
michael@0 339 nsIInterfaceRequestor,
michael@0 340 nsIChannelEventSink,
michael@0 341 nsISupportsWeakReference,
michael@0 342 nsIAsyncVerifyRedirectCallback)
michael@0 343
michael@0 344 // nsIRequest
michael@0 345
michael@0 346 NS_IMETHODIMP
michael@0 347 nsIncrementalDownload::GetName(nsACString &name)
michael@0 348 {
michael@0 349 NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_INITIALIZED);
michael@0 350
michael@0 351 return mURI->GetSpec(name);
michael@0 352 }
michael@0 353
michael@0 354 NS_IMETHODIMP
michael@0 355 nsIncrementalDownload::IsPending(bool *isPending)
michael@0 356 {
michael@0 357 *isPending = mIsPending;
michael@0 358 return NS_OK;
michael@0 359 }
michael@0 360
michael@0 361 NS_IMETHODIMP
michael@0 362 nsIncrementalDownload::GetStatus(nsresult *status)
michael@0 363 {
michael@0 364 *status = mStatus;
michael@0 365 return NS_OK;
michael@0 366 }
michael@0 367
michael@0 368 NS_IMETHODIMP
michael@0 369 nsIncrementalDownload::Cancel(nsresult status)
michael@0 370 {
michael@0 371 NS_ENSURE_ARG(NS_FAILED(status));
michael@0 372
michael@0 373 // Ignore this cancelation if we're already canceled.
michael@0 374 if (NS_FAILED(mStatus))
michael@0 375 return NS_OK;
michael@0 376
michael@0 377 mStatus = status;
michael@0 378
michael@0 379 // Nothing more to do if callbacks aren't pending.
michael@0 380 if (!mIsPending)
michael@0 381 return NS_OK;
michael@0 382
michael@0 383 if (mChannel) {
michael@0 384 mChannel->Cancel(mStatus);
michael@0 385 NS_ASSERTION(!mTimer, "what is this timer object doing here?");
michael@0 386 }
michael@0 387 else {
michael@0 388 // dispatch a timer callback event to drive invoking our listener's
michael@0 389 // OnStopRequest.
michael@0 390 if (mTimer)
michael@0 391 mTimer->Cancel();
michael@0 392 StartTimer(0);
michael@0 393 }
michael@0 394
michael@0 395 return NS_OK;
michael@0 396 }
michael@0 397
michael@0 398 NS_IMETHODIMP
michael@0 399 nsIncrementalDownload::Suspend()
michael@0 400 {
michael@0 401 return NS_ERROR_NOT_IMPLEMENTED;
michael@0 402 }
michael@0 403
michael@0 404 NS_IMETHODIMP
michael@0 405 nsIncrementalDownload::Resume()
michael@0 406 {
michael@0 407 return NS_ERROR_NOT_IMPLEMENTED;
michael@0 408 }
michael@0 409
michael@0 410 NS_IMETHODIMP
michael@0 411 nsIncrementalDownload::GetLoadFlags(nsLoadFlags *loadFlags)
michael@0 412 {
michael@0 413 *loadFlags = mLoadFlags;
michael@0 414 return NS_OK;
michael@0 415 }
michael@0 416
michael@0 417 NS_IMETHODIMP
michael@0 418 nsIncrementalDownload::SetLoadFlags(nsLoadFlags loadFlags)
michael@0 419 {
michael@0 420 mLoadFlags = loadFlags;
michael@0 421 return NS_OK;
michael@0 422 }
michael@0 423
michael@0 424 NS_IMETHODIMP
michael@0 425 nsIncrementalDownload::GetLoadGroup(nsILoadGroup **loadGroup)
michael@0 426 {
michael@0 427 return NS_ERROR_NOT_IMPLEMENTED;
michael@0 428 }
michael@0 429
michael@0 430 NS_IMETHODIMP
michael@0 431 nsIncrementalDownload::SetLoadGroup(nsILoadGroup *loadGroup)
michael@0 432 {
michael@0 433 return NS_ERROR_NOT_IMPLEMENTED;
michael@0 434 }
michael@0 435
michael@0 436 // nsIIncrementalDownload
michael@0 437
michael@0 438 NS_IMETHODIMP
michael@0 439 nsIncrementalDownload::Init(nsIURI *uri, nsIFile *dest,
michael@0 440 int32_t chunkSize, int32_t interval)
michael@0 441 {
michael@0 442 // Keep it simple: only allow initialization once
michael@0 443 NS_ENSURE_FALSE(mURI, NS_ERROR_ALREADY_INITIALIZED);
michael@0 444
michael@0 445 mDest = do_QueryInterface(dest);
michael@0 446 NS_ENSURE_ARG(mDest);
michael@0 447
michael@0 448 mURI = uri;
michael@0 449 mFinalURI = uri;
michael@0 450
michael@0 451 if (chunkSize > 0)
michael@0 452 mChunkSize = chunkSize;
michael@0 453 if (interval >= 0)
michael@0 454 mInterval = interval;
michael@0 455 return NS_OK;
michael@0 456 }
michael@0 457
michael@0 458 NS_IMETHODIMP
michael@0 459 nsIncrementalDownload::GetURI(nsIURI **result)
michael@0 460 {
michael@0 461 NS_IF_ADDREF(*result = mURI);
michael@0 462 return NS_OK;
michael@0 463 }
michael@0 464
michael@0 465 NS_IMETHODIMP
michael@0 466 nsIncrementalDownload::GetFinalURI(nsIURI **result)
michael@0 467 {
michael@0 468 NS_IF_ADDREF(*result = mFinalURI);
michael@0 469 return NS_OK;
michael@0 470 }
michael@0 471
michael@0 472 NS_IMETHODIMP
michael@0 473 nsIncrementalDownload::GetDestination(nsIFile **result)
michael@0 474 {
michael@0 475 if (!mDest) {
michael@0 476 *result = nullptr;
michael@0 477 return NS_OK;
michael@0 478 }
michael@0 479 // Return a clone of mDest so that callers may modify the resulting nsIFile
michael@0 480 // without corrupting our internal object. This also works around the fact
michael@0 481 // that some nsIFile impls may cache the result of stat'ing the filesystem.
michael@0 482 return mDest->Clone(result);
michael@0 483 }
michael@0 484
michael@0 485 NS_IMETHODIMP
michael@0 486 nsIncrementalDownload::GetTotalSize(int64_t *result)
michael@0 487 {
michael@0 488 *result = mTotalSize;
michael@0 489 return NS_OK;
michael@0 490 }
michael@0 491
michael@0 492 NS_IMETHODIMP
michael@0 493 nsIncrementalDownload::GetCurrentSize(int64_t *result)
michael@0 494 {
michael@0 495 *result = mCurrentSize;
michael@0 496 return NS_OK;
michael@0 497 }
michael@0 498
michael@0 499 NS_IMETHODIMP
michael@0 500 nsIncrementalDownload::Start(nsIRequestObserver *observer,
michael@0 501 nsISupports *context)
michael@0 502 {
michael@0 503 NS_ENSURE_ARG(observer);
michael@0 504 NS_ENSURE_FALSE(mIsPending, NS_ERROR_IN_PROGRESS);
michael@0 505
michael@0 506 // Observe system shutdown so we can be sure to release any reference held
michael@0 507 // between ourselves and the timer. We have the observer service hold a weak
michael@0 508 // reference to us, so that we don't have to worry about calling
michael@0 509 // RemoveObserver. XXX(darin): The timer code should do this for us.
michael@0 510 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
michael@0 511 if (obs)
michael@0 512 obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
michael@0 513
michael@0 514 nsresult rv = ReadCurrentSize();
michael@0 515 if (NS_FAILED(rv))
michael@0 516 return rv;
michael@0 517
michael@0 518 rv = StartTimer(0);
michael@0 519 if (NS_FAILED(rv))
michael@0 520 return rv;
michael@0 521
michael@0 522 mObserver = observer;
michael@0 523 mObserverContext = context;
michael@0 524 mProgressSink = do_QueryInterface(observer); // ok if null
michael@0 525
michael@0 526 mIsPending = true;
michael@0 527 return NS_OK;
michael@0 528 }
michael@0 529
michael@0 530 // nsIRequestObserver
michael@0 531
michael@0 532 NS_IMETHODIMP
michael@0 533 nsIncrementalDownload::OnStartRequest(nsIRequest *request,
michael@0 534 nsISupports *context)
michael@0 535 {
michael@0 536 nsresult rv;
michael@0 537
michael@0 538 nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(request, &rv);
michael@0 539 if (NS_FAILED(rv))
michael@0 540 return rv;
michael@0 541
michael@0 542 // Ensure that we are receiving a 206 response.
michael@0 543 uint32_t code;
michael@0 544 rv = http->GetResponseStatus(&code);
michael@0 545 if (NS_FAILED(rv))
michael@0 546 return rv;
michael@0 547 if (code != 206) {
michael@0 548 // We may already have the entire file downloaded, in which case
michael@0 549 // our request for a range beyond the end of the file would have
michael@0 550 // been met with an error response code.
michael@0 551 if (code == 416 && mTotalSize == int64_t(-1)) {
michael@0 552 mTotalSize = mCurrentSize;
michael@0 553 // Return an error code here to suppress OnDataAvailable.
michael@0 554 return NS_ERROR_DOWNLOAD_COMPLETE;
michael@0 555 }
michael@0 556 // The server may have decided to give us all of the data in one chunk. If
michael@0 557 // we requested a partial range, then we don't want to download all of the
michael@0 558 // data at once. So, we'll just try again, but if this keeps happening then
michael@0 559 // we'll eventually give up.
michael@0 560 if (code == 200) {
michael@0 561 if (mInterval) {
michael@0 562 mChannel = nullptr;
michael@0 563 if (++mNonPartialCount > MAX_RETRY_COUNT) {
michael@0 564 NS_WARNING("unable to fetch a byte range; giving up");
michael@0 565 return NS_ERROR_FAILURE;
michael@0 566 }
michael@0 567 // Increase delay with each failure.
michael@0 568 StartTimer(mInterval * mNonPartialCount);
michael@0 569 return NS_ERROR_DOWNLOAD_NOT_PARTIAL;
michael@0 570 }
michael@0 571 // Since we have been asked to download the rest of the file, we can deal
michael@0 572 // with a 200 response. This may result in downloading the beginning of
michael@0 573 // the file again, but that can't really be helped.
michael@0 574 } else {
michael@0 575 NS_WARNING("server response was unexpected");
michael@0 576 return NS_ERROR_UNEXPECTED;
michael@0 577 }
michael@0 578 } else {
michael@0 579 // We got a partial response, so clear this counter in case the next chunk
michael@0 580 // results in a 200 response.
michael@0 581 mNonPartialCount = 0;
michael@0 582
michael@0 583 // confirm that the content-range response header is consistent with
michael@0 584 // expectations on each 206. If it is not then drop this response and
michael@0 585 // retry with no-cache set.
michael@0 586 if (!mCacheBust) {
michael@0 587 nsAutoCString buf;
michael@0 588 int64_t startByte = 0;
michael@0 589 bool confirmedOK = false;
michael@0 590
michael@0 591 rv = http->GetResponseHeader(NS_LITERAL_CSTRING("Content-Range"), buf);
michael@0 592 if (NS_FAILED(rv))
michael@0 593 return rv; // it isn't a useful 206 without a CONTENT-RANGE of some sort
michael@0 594
michael@0 595 // Content-Range: bytes 0-299999/25604694
michael@0 596 int32_t p = buf.Find("bytes ");
michael@0 597
michael@0 598 // first look for the starting point of the content-range
michael@0 599 // to make sure it is what we expect
michael@0 600 if (p != -1) {
michael@0 601 char *endptr = nullptr;
michael@0 602 const char *s = buf.get() + p + 6;
michael@0 603 while (*s && *s == ' ')
michael@0 604 s++;
michael@0 605 startByte = strtol(s, &endptr, 10);
michael@0 606
michael@0 607 if (*s && endptr && (endptr != s) &&
michael@0 608 (mCurrentSize == startByte)) {
michael@0 609
michael@0 610 // ok the starting point is confirmed. We still need to check the
michael@0 611 // total size of the range for consistency if this isn't
michael@0 612 // the first chunk
michael@0 613 if (mTotalSize == int64_t(-1)) {
michael@0 614 // first chunk
michael@0 615 confirmedOK = true;
michael@0 616 } else {
michael@0 617 int32_t slash = buf.FindChar('/');
michael@0 618 int64_t rangeSize = 0;
michael@0 619 if (slash != kNotFound &&
michael@0 620 (PR_sscanf(buf.get() + slash + 1, "%lld", (int64_t *) &rangeSize) == 1) &&
michael@0 621 rangeSize == mTotalSize) {
michael@0 622 confirmedOK = true;
michael@0 623 }
michael@0 624 }
michael@0 625 }
michael@0 626 }
michael@0 627
michael@0 628 if (!confirmedOK) {
michael@0 629 NS_WARNING("unexpected content-range");
michael@0 630 mCacheBust = true;
michael@0 631 mChannel = nullptr;
michael@0 632 if (++mNonPartialCount > MAX_RETRY_COUNT) {
michael@0 633 NS_WARNING("unable to fetch a byte range; giving up");
michael@0 634 return NS_ERROR_FAILURE;
michael@0 635 }
michael@0 636 // Increase delay with each failure.
michael@0 637 StartTimer(mInterval * mNonPartialCount);
michael@0 638 return NS_ERROR_DOWNLOAD_NOT_PARTIAL;
michael@0 639 }
michael@0 640 }
michael@0 641 }
michael@0 642
michael@0 643 // Do special processing after the first response.
michael@0 644 if (mTotalSize == int64_t(-1)) {
michael@0 645 // Update knowledge of mFinalURI
michael@0 646 rv = http->GetURI(getter_AddRefs(mFinalURI));
michael@0 647 if (NS_FAILED(rv))
michael@0 648 return rv;
michael@0 649 http->GetResponseHeader(NS_LITERAL_CSTRING("Etag"), mPartialValidator);
michael@0 650 if (StringBeginsWith(mPartialValidator, NS_LITERAL_CSTRING("W/")))
michael@0 651 mPartialValidator.Truncate(); // don't use weak validators
michael@0 652 if (mPartialValidator.IsEmpty())
michael@0 653 http->GetResponseHeader(NS_LITERAL_CSTRING("Last-Modified"), mPartialValidator);
michael@0 654
michael@0 655 if (code == 206) {
michael@0 656 // OK, read the Content-Range header to determine the total size of this
michael@0 657 // download file.
michael@0 658 nsAutoCString buf;
michael@0 659 rv = http->GetResponseHeader(NS_LITERAL_CSTRING("Content-Range"), buf);
michael@0 660 if (NS_FAILED(rv))
michael@0 661 return rv;
michael@0 662 int32_t slash = buf.FindChar('/');
michael@0 663 if (slash == kNotFound) {
michael@0 664 NS_WARNING("server returned invalid Content-Range header!");
michael@0 665 return NS_ERROR_UNEXPECTED;
michael@0 666 }
michael@0 667 if (PR_sscanf(buf.get() + slash + 1, "%lld", (int64_t *) &mTotalSize) != 1)
michael@0 668 return NS_ERROR_UNEXPECTED;
michael@0 669 } else {
michael@0 670 rv = http->GetContentLength(&mTotalSize);
michael@0 671 if (NS_FAILED(rv))
michael@0 672 return rv;
michael@0 673 // We need to know the total size of the thing we're trying to download.
michael@0 674 if (mTotalSize == int64_t(-1)) {
michael@0 675 NS_WARNING("server returned no content-length header!");
michael@0 676 return NS_ERROR_UNEXPECTED;
michael@0 677 }
michael@0 678 // Need to truncate (or create, if it doesn't exist) the file since we
michael@0 679 // are downloading the whole thing.
michael@0 680 WriteToFile(mDest, nullptr, 0, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE);
michael@0 681 mCurrentSize = 0;
michael@0 682 }
michael@0 683
michael@0 684 // Notify observer that we are starting...
michael@0 685 rv = CallOnStartRequest();
michael@0 686 if (NS_FAILED(rv))
michael@0 687 return rv;
michael@0 688 }
michael@0 689
michael@0 690 // Adjust mChunkSize accordingly if mCurrentSize is close to mTotalSize.
michael@0 691 int64_t diff = mTotalSize - mCurrentSize;
michael@0 692 if (diff <= int64_t(0)) {
michael@0 693 NS_WARNING("about to set a bogus chunk size; giving up");
michael@0 694 return NS_ERROR_UNEXPECTED;
michael@0 695 }
michael@0 696
michael@0 697 if (diff < int64_t(mChunkSize))
michael@0 698 mChunkSize = uint32_t(diff);
michael@0 699
michael@0 700 mChunk = new char[mChunkSize];
michael@0 701 if (!mChunk)
michael@0 702 rv = NS_ERROR_OUT_OF_MEMORY;
michael@0 703
michael@0 704 return rv;
michael@0 705 }
michael@0 706
michael@0 707 NS_IMETHODIMP
michael@0 708 nsIncrementalDownload::OnStopRequest(nsIRequest *request,
michael@0 709 nsISupports *context,
michael@0 710 nsresult status)
michael@0 711 {
michael@0 712 // Not a real error; just a trick to kill off the channel without our
michael@0 713 // listener having to care.
michael@0 714 if (status == NS_ERROR_DOWNLOAD_NOT_PARTIAL)
michael@0 715 return NS_OK;
michael@0 716
michael@0 717 // Not a real error; just a trick used to suppress OnDataAvailable calls.
michael@0 718 if (status == NS_ERROR_DOWNLOAD_COMPLETE)
michael@0 719 status = NS_OK;
michael@0 720
michael@0 721 if (NS_SUCCEEDED(mStatus))
michael@0 722 mStatus = status;
michael@0 723
michael@0 724 if (mChunk) {
michael@0 725 if (NS_SUCCEEDED(mStatus))
michael@0 726 mStatus = FlushChunk();
michael@0 727
michael@0 728 mChunk = nullptr; // deletes memory
michael@0 729 mChunkLen = 0;
michael@0 730 UpdateProgress();
michael@0 731 }
michael@0 732
michael@0 733 mChannel = nullptr;
michael@0 734
michael@0 735 // Notify listener if we hit an error or finished
michael@0 736 if (NS_FAILED(mStatus) || mCurrentSize == mTotalSize) {
michael@0 737 CallOnStopRequest();
michael@0 738 return NS_OK;
michael@0 739 }
michael@0 740
michael@0 741 return StartTimer(mInterval); // Do next chunk
michael@0 742 }
michael@0 743
michael@0 744 // nsIStreamListener
michael@0 745
michael@0 746 NS_IMETHODIMP
michael@0 747 nsIncrementalDownload::OnDataAvailable(nsIRequest *request,
michael@0 748 nsISupports *context,
michael@0 749 nsIInputStream *input,
michael@0 750 uint64_t offset,
michael@0 751 uint32_t count)
michael@0 752 {
michael@0 753 while (count) {
michael@0 754 uint32_t space = mChunkSize - mChunkLen;
michael@0 755 uint32_t n, len = std::min(space, count);
michael@0 756
michael@0 757 nsresult rv = input->Read(mChunk + mChunkLen, len, &n);
michael@0 758 if (NS_FAILED(rv))
michael@0 759 return rv;
michael@0 760 if (n != len)
michael@0 761 return NS_ERROR_UNEXPECTED;
michael@0 762
michael@0 763 count -= n;
michael@0 764 mChunkLen += n;
michael@0 765
michael@0 766 if (mChunkLen == mChunkSize) {
michael@0 767 rv = FlushChunk();
michael@0 768 if (NS_FAILED(rv))
michael@0 769 return rv;
michael@0 770 }
michael@0 771 }
michael@0 772
michael@0 773 if (PR_Now() > mLastProgressUpdate + UPDATE_PROGRESS_INTERVAL)
michael@0 774 UpdateProgress();
michael@0 775
michael@0 776 return NS_OK;
michael@0 777 }
michael@0 778
michael@0 779 // nsIObserver
michael@0 780
michael@0 781 NS_IMETHODIMP
michael@0 782 nsIncrementalDownload::Observe(nsISupports *subject, const char *topic,
michael@0 783 const char16_t *data)
michael@0 784 {
michael@0 785 if (strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
michael@0 786 Cancel(NS_ERROR_ABORT);
michael@0 787
michael@0 788 // Since the app is shutting down, we need to go ahead and notify our
michael@0 789 // observer here. Otherwise, we would notify them after XPCOM has been
michael@0 790 // shutdown or not at all.
michael@0 791 CallOnStopRequest();
michael@0 792 }
michael@0 793 else if (strcmp(topic, NS_TIMER_CALLBACK_TOPIC) == 0) {
michael@0 794 mTimer = nullptr;
michael@0 795 nsresult rv = ProcessTimeout();
michael@0 796 if (NS_FAILED(rv))
michael@0 797 Cancel(rv);
michael@0 798 }
michael@0 799 return NS_OK;
michael@0 800 }
michael@0 801
michael@0 802 // nsIInterfaceRequestor
michael@0 803
michael@0 804 NS_IMETHODIMP
michael@0 805 nsIncrementalDownload::GetInterface(const nsIID &iid, void **result)
michael@0 806 {
michael@0 807 if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) {
michael@0 808 NS_ADDREF_THIS();
michael@0 809 *result = static_cast<nsIChannelEventSink *>(this);
michael@0 810 return NS_OK;
michael@0 811 }
michael@0 812
michael@0 813 nsCOMPtr<nsIInterfaceRequestor> ir = do_QueryInterface(mObserver);
michael@0 814 if (ir)
michael@0 815 return ir->GetInterface(iid, result);
michael@0 816
michael@0 817 return NS_ERROR_NO_INTERFACE;
michael@0 818 }
michael@0 819
michael@0 820 nsresult
michael@0 821 nsIncrementalDownload::ClearRequestHeader(nsIHttpChannel *channel)
michael@0 822 {
michael@0 823 NS_ENSURE_ARG(channel);
michael@0 824
michael@0 825 // We don't support encodings -- they make the Content-Length not equal
michael@0 826 // to the actual size of the data.
michael@0 827 return channel->SetRequestHeader(NS_LITERAL_CSTRING("Accept-Encoding"),
michael@0 828 NS_LITERAL_CSTRING(""), false);
michael@0 829 }
michael@0 830
michael@0 831 // nsIChannelEventSink
michael@0 832
michael@0 833 NS_IMETHODIMP
michael@0 834 nsIncrementalDownload::AsyncOnChannelRedirect(nsIChannel *oldChannel,
michael@0 835 nsIChannel *newChannel,
michael@0 836 uint32_t flags,
michael@0 837 nsIAsyncVerifyRedirectCallback *cb)
michael@0 838 {
michael@0 839 // In response to a redirect, we need to propagate the Range header. See bug
michael@0 840 // 311595. Any failure code returned from this function aborts the redirect.
michael@0 841
michael@0 842 nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(oldChannel);
michael@0 843 NS_ENSURE_STATE(http);
michael@0 844
michael@0 845 nsCOMPtr<nsIHttpChannel> newHttpChannel = do_QueryInterface(newChannel);
michael@0 846 NS_ENSURE_STATE(newHttpChannel);
michael@0 847
michael@0 848 NS_NAMED_LITERAL_CSTRING(rangeHdr, "Range");
michael@0 849
michael@0 850 nsresult rv = ClearRequestHeader(newHttpChannel);
michael@0 851 if (NS_FAILED(rv))
michael@0 852 return rv;
michael@0 853
michael@0 854 // If we didn't have a Range header, then we must be doing a full download.
michael@0 855 nsAutoCString rangeVal;
michael@0 856 http->GetRequestHeader(rangeHdr, rangeVal);
michael@0 857 if (!rangeVal.IsEmpty()) {
michael@0 858 rv = newHttpChannel->SetRequestHeader(rangeHdr, rangeVal, false);
michael@0 859 NS_ENSURE_SUCCESS(rv, rv);
michael@0 860 }
michael@0 861
michael@0 862 // A redirection changes the validator
michael@0 863 mPartialValidator.Truncate();
michael@0 864
michael@0 865 if (mCacheBust) {
michael@0 866 newHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Cache-Control"),
michael@0 867 NS_LITERAL_CSTRING("no-cache"), false);
michael@0 868 newHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Pragma"),
michael@0 869 NS_LITERAL_CSTRING("no-cache"), false);
michael@0 870 }
michael@0 871
michael@0 872 // Prepare to receive callback
michael@0 873 mRedirectCallback = cb;
michael@0 874 mNewRedirectChannel = newChannel;
michael@0 875
michael@0 876 // Give the observer a chance to see this redirect notification.
michael@0 877 nsCOMPtr<nsIChannelEventSink> sink = do_GetInterface(mObserver);
michael@0 878 if (sink) {
michael@0 879 rv = sink->AsyncOnChannelRedirect(oldChannel, newChannel, flags, this);
michael@0 880 if (NS_FAILED(rv)) {
michael@0 881 mRedirectCallback = nullptr;
michael@0 882 mNewRedirectChannel = nullptr;
michael@0 883 }
michael@0 884 return rv;
michael@0 885 }
michael@0 886 (void) OnRedirectVerifyCallback(NS_OK);
michael@0 887 return NS_OK;
michael@0 888 }
michael@0 889
michael@0 890 NS_IMETHODIMP
michael@0 891 nsIncrementalDownload::OnRedirectVerifyCallback(nsresult result)
michael@0 892 {
michael@0 893 NS_ASSERTION(mRedirectCallback, "mRedirectCallback not set in callback");
michael@0 894 NS_ASSERTION(mNewRedirectChannel, "mNewRedirectChannel not set in callback");
michael@0 895
michael@0 896 // Update mChannel, so we can Cancel the new channel.
michael@0 897 if (NS_SUCCEEDED(result))
michael@0 898 mChannel = mNewRedirectChannel;
michael@0 899
michael@0 900 mRedirectCallback->OnRedirectVerifyCallback(result);
michael@0 901 mRedirectCallback = nullptr;
michael@0 902 mNewRedirectChannel = nullptr;
michael@0 903 return NS_OK;
michael@0 904 }
michael@0 905
michael@0 906 extern nsresult
michael@0 907 net_NewIncrementalDownload(nsISupports *outer, const nsIID &iid, void **result)
michael@0 908 {
michael@0 909 if (outer)
michael@0 910 return NS_ERROR_NO_AGGREGATION;
michael@0 911
michael@0 912 nsIncrementalDownload *d = new nsIncrementalDownload();
michael@0 913 if (!d)
michael@0 914 return NS_ERROR_OUT_OF_MEMORY;
michael@0 915
michael@0 916 NS_ADDREF(d);
michael@0 917 nsresult rv = d->QueryInterface(iid, result);
michael@0 918 NS_RELEASE(d);
michael@0 919 return rv;
michael@0 920 }

mercurial