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