LCOV - code coverage report
Current view: top level - netwerk/base - nsIncrementalDownload.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 0 409 0.0 %
Date: 2017-07-14 16:53:18 Functions: 0 41 0.0 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.13