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

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

mercurial