tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }