netwerk/base/src/nsIncrementalDownload.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/netwerk/base/src/nsIncrementalDownload.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,920 @@
     1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +/* vim:set ts=2 sw=2 sts=2 et cindent: */
     1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +#include "mozilla/Attributes.h"
    1.11 +
    1.12 +#include "nsIIncrementalDownload.h"
    1.13 +#include "nsIRequestObserver.h"
    1.14 +#include "nsIProgressEventSink.h"
    1.15 +#include "nsIChannelEventSink.h"
    1.16 +#include "nsIAsyncVerifyRedirectCallback.h"
    1.17 +#include "nsIInterfaceRequestor.h"
    1.18 +#include "nsIObserverService.h"
    1.19 +#include "nsIObserver.h"
    1.20 +#include "nsIFile.h"
    1.21 +#include "nsITimer.h"
    1.22 +#include "nsNetUtil.h"
    1.23 +#include "nsAutoPtr.h"
    1.24 +#include "nsWeakReference.h"
    1.25 +#include "prio.h"
    1.26 +#include "prprf.h"
    1.27 +#include <algorithm>
    1.28 +
    1.29 +// Default values used to initialize a nsIncrementalDownload object.
    1.30 +#define DEFAULT_CHUNK_SIZE (4096 * 16)  // bytes
    1.31 +#define DEFAULT_INTERVAL    60          // seconds
    1.32 +
    1.33 +#define UPDATE_PROGRESS_INTERVAL PRTime(500 * PR_USEC_PER_MSEC) // 500ms
    1.34 +
    1.35 +// Number of times to retry a failed byte-range request.
    1.36 +#define MAX_RETRY_COUNT 20
    1.37 +
    1.38 +//-----------------------------------------------------------------------------
    1.39 +
    1.40 +static nsresult
    1.41 +WriteToFile(nsIFile *lf, const char *data, uint32_t len, int32_t flags)
    1.42 +{
    1.43 +  PRFileDesc *fd;
    1.44 +  int32_t mode = 0600;
    1.45 +  nsresult rv;
    1.46 +#if defined(MOZ_WIDGET_GONK)
    1.47 +  // The sdcard on a B2G phone looks like:
    1.48 +  // d---rwx--- system   sdcard_rw          1970-01-01 01:00:00 sdcard
    1.49 +  // On the emulator, xpcshell fails when using 0600 mode to open the file,
    1.50 +  // and 0660 works.
    1.51 +  nsCOMPtr<nsIFile> parent;
    1.52 +  rv = lf->GetParent(getter_AddRefs(parent));
    1.53 +  if (NS_FAILED(rv)) {
    1.54 +    return rv;
    1.55 +  }
    1.56 +  uint32_t  parentPerm;
    1.57 +  rv = parent->GetPermissions(&parentPerm);
    1.58 +  if (NS_FAILED(rv)) {
    1.59 +    return rv;
    1.60 +  }
    1.61 +  if ((parentPerm & 0700) == 0) {
    1.62 +    // Parent directory has no owner-write, so try to use group permissions
    1.63 +    // instead of owner permissions.
    1.64 +    mode = 0660;
    1.65 +  }
    1.66 +#endif
    1.67 +  rv = lf->OpenNSPRFileDesc(flags, mode, &fd);
    1.68 +  if (NS_FAILED(rv))
    1.69 +    return rv;
    1.70 +
    1.71 +  if (len)
    1.72 +    rv = PR_Write(fd, data, len) == int32_t(len) ? NS_OK : NS_ERROR_FAILURE;
    1.73 +
    1.74 +  PR_Close(fd);
    1.75 +  return rv;
    1.76 +}
    1.77 +
    1.78 +static nsresult
    1.79 +AppendToFile(nsIFile *lf, const char *data, uint32_t len)
    1.80 +{
    1.81 +  int32_t flags = PR_WRONLY | PR_CREATE_FILE | PR_APPEND;
    1.82 +  return WriteToFile(lf, data, len, flags);
    1.83 +}
    1.84 +
    1.85 +// maxSize may be -1 if unknown
    1.86 +static void
    1.87 +MakeRangeSpec(const int64_t &size, const int64_t &maxSize, int32_t chunkSize,
    1.88 +              bool fetchRemaining, nsCString &rangeSpec)
    1.89 +{
    1.90 +  rangeSpec.AssignLiteral("bytes=");
    1.91 +  rangeSpec.AppendInt(int64_t(size));
    1.92 +  rangeSpec.Append('-');
    1.93 +
    1.94 +  if (fetchRemaining)
    1.95 +    return;
    1.96 +
    1.97 +  int64_t end = size + int64_t(chunkSize);
    1.98 +  if (maxSize != int64_t(-1) && end > maxSize)
    1.99 +    end = maxSize;
   1.100 +  end -= 1;
   1.101 +
   1.102 +  rangeSpec.AppendInt(int64_t(end));
   1.103 +}
   1.104 +
   1.105 +//-----------------------------------------------------------------------------
   1.106 +
   1.107 +class nsIncrementalDownload MOZ_FINAL
   1.108 +  : public nsIIncrementalDownload
   1.109 +  , public nsIStreamListener
   1.110 +  , public nsIObserver
   1.111 +  , public nsIInterfaceRequestor
   1.112 +  , public nsIChannelEventSink
   1.113 +  , public nsSupportsWeakReference
   1.114 +  , public nsIAsyncVerifyRedirectCallback
   1.115 +{
   1.116 +public:
   1.117 +  NS_DECL_ISUPPORTS
   1.118 +  NS_DECL_NSIREQUEST
   1.119 +  NS_DECL_NSIINCREMENTALDOWNLOAD
   1.120 +  NS_DECL_NSIREQUESTOBSERVER
   1.121 +  NS_DECL_NSISTREAMLISTENER
   1.122 +  NS_DECL_NSIOBSERVER
   1.123 +  NS_DECL_NSIINTERFACEREQUESTOR
   1.124 +  NS_DECL_NSICHANNELEVENTSINK
   1.125 +  NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
   1.126 +
   1.127 +  nsIncrementalDownload();
   1.128 +
   1.129 +private:
   1.130 +  ~nsIncrementalDownload() {}
   1.131 +  nsresult FlushChunk();
   1.132 +  void     UpdateProgress();
   1.133 +  nsresult CallOnStartRequest();
   1.134 +  void     CallOnStopRequest();
   1.135 +  nsresult StartTimer(int32_t interval);
   1.136 +  nsresult ProcessTimeout();
   1.137 +  nsresult ReadCurrentSize();
   1.138 +  nsresult ClearRequestHeader(nsIHttpChannel *channel);
   1.139 +
   1.140 +  nsCOMPtr<nsIRequestObserver>             mObserver;
   1.141 +  nsCOMPtr<nsISupports>                    mObserverContext;
   1.142 +  nsCOMPtr<nsIProgressEventSink>           mProgressSink;
   1.143 +  nsCOMPtr<nsIURI>                         mURI;
   1.144 +  nsCOMPtr<nsIURI>                         mFinalURI;
   1.145 +  nsCOMPtr<nsIFile>                        mDest;
   1.146 +  nsCOMPtr<nsIChannel>                     mChannel;
   1.147 +  nsCOMPtr<nsITimer>                       mTimer;
   1.148 +  nsAutoArrayPtr<char>                     mChunk;
   1.149 +  int32_t                                  mChunkLen;
   1.150 +  int32_t                                  mChunkSize;
   1.151 +  int32_t                                  mInterval;
   1.152 +  int64_t                                  mTotalSize;
   1.153 +  int64_t                                  mCurrentSize;
   1.154 +  uint32_t                                 mLoadFlags;
   1.155 +  int32_t                                  mNonPartialCount;
   1.156 +  nsresult                                 mStatus;
   1.157 +  bool                                     mIsPending;
   1.158 +  bool                                     mDidOnStartRequest;
   1.159 +  PRTime                                   mLastProgressUpdate;
   1.160 +  nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback;
   1.161 +  nsCOMPtr<nsIChannel>                     mNewRedirectChannel;
   1.162 +  nsCString                                mPartialValidator;
   1.163 +  bool                                     mCacheBust;
   1.164 +};
   1.165 +
   1.166 +nsIncrementalDownload::nsIncrementalDownload()
   1.167 +  : mChunkLen(0)
   1.168 +  , mChunkSize(DEFAULT_CHUNK_SIZE)
   1.169 +  , mInterval(DEFAULT_INTERVAL)
   1.170 +  , mTotalSize(-1)
   1.171 +  , mCurrentSize(-1)
   1.172 +  , mLoadFlags(LOAD_NORMAL)
   1.173 +  , mNonPartialCount(0)
   1.174 +  , mStatus(NS_OK)
   1.175 +  , mIsPending(false)
   1.176 +  , mDidOnStartRequest(false)
   1.177 +  , mLastProgressUpdate(0)
   1.178 +  , mRedirectCallback(nullptr)
   1.179 +  , mNewRedirectChannel(nullptr)
   1.180 +  , mCacheBust(false)  
   1.181 +{
   1.182 +}
   1.183 +
   1.184 +nsresult
   1.185 +nsIncrementalDownload::FlushChunk()
   1.186 +{
   1.187 +  NS_ASSERTION(mTotalSize != int64_t(-1), "total size should be known");
   1.188 +
   1.189 +  if (mChunkLen == 0)
   1.190 +    return NS_OK;
   1.191 +
   1.192 +  nsresult rv = AppendToFile(mDest, mChunk, mChunkLen);
   1.193 +  if (NS_FAILED(rv))
   1.194 +    return rv;
   1.195 +
   1.196 +  mCurrentSize += int64_t(mChunkLen);
   1.197 +  mChunkLen = 0;
   1.198 +
   1.199 +  return NS_OK;
   1.200 +}
   1.201 +
   1.202 +void
   1.203 +nsIncrementalDownload::UpdateProgress()
   1.204 +{
   1.205 +  mLastProgressUpdate = PR_Now();
   1.206 +
   1.207 +  if (mProgressSink)
   1.208 +    mProgressSink->OnProgress(this, mObserverContext,
   1.209 +                              uint64_t(int64_t(mCurrentSize) + mChunkLen),
   1.210 +                              uint64_t(int64_t(mTotalSize)));
   1.211 +}
   1.212 +
   1.213 +nsresult
   1.214 +nsIncrementalDownload::CallOnStartRequest()
   1.215 +{
   1.216 +  if (!mObserver || mDidOnStartRequest)
   1.217 +    return NS_OK;
   1.218 +
   1.219 +  mDidOnStartRequest = true;
   1.220 +  return mObserver->OnStartRequest(this, mObserverContext);
   1.221 +}
   1.222 +
   1.223 +void
   1.224 +nsIncrementalDownload::CallOnStopRequest()
   1.225 +{
   1.226 +  if (!mObserver)
   1.227 +    return;
   1.228 +
   1.229 +  // Ensure that OnStartRequest is always called once before OnStopRequest.
   1.230 +  nsresult rv = CallOnStartRequest();
   1.231 +  if (NS_SUCCEEDED(mStatus))
   1.232 +    mStatus = rv;
   1.233 +
   1.234 +  mIsPending = false;
   1.235 +
   1.236 +  mObserver->OnStopRequest(this, mObserverContext, mStatus);
   1.237 +  mObserver = nullptr;
   1.238 +  mObserverContext = nullptr;
   1.239 +}
   1.240 +
   1.241 +nsresult
   1.242 +nsIncrementalDownload::StartTimer(int32_t interval)
   1.243 +{
   1.244 +  nsresult rv;
   1.245 +  mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
   1.246 +  if (NS_FAILED(rv))
   1.247 +    return rv;
   1.248 +
   1.249 +  return mTimer->Init(this, interval * 1000, nsITimer::TYPE_ONE_SHOT);
   1.250 +}
   1.251 +
   1.252 +nsresult
   1.253 +nsIncrementalDownload::ProcessTimeout()
   1.254 +{
   1.255 +  NS_ASSERTION(!mChannel, "how can we have a channel?");
   1.256 +
   1.257 +  // Handle existing error conditions
   1.258 +  if (NS_FAILED(mStatus)) {
   1.259 +    CallOnStopRequest();
   1.260 +    return NS_OK;
   1.261 +  }
   1.262 +
   1.263 +  // Fetch next chunk
   1.264 +  
   1.265 +  nsCOMPtr<nsIChannel> channel;
   1.266 +  nsresult rv = NS_NewChannel(getter_AddRefs(channel), mFinalURI, nullptr,
   1.267 +                              nullptr, this, mLoadFlags);
   1.268 +  if (NS_FAILED(rv))
   1.269 +    return rv;
   1.270 +
   1.271 +  nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(channel, &rv);
   1.272 +  if (NS_FAILED(rv))
   1.273 +    return rv;
   1.274 +
   1.275 +  NS_ASSERTION(mCurrentSize != int64_t(-1),
   1.276 +      "we should know the current file size by now");
   1.277 +
   1.278 +  rv = ClearRequestHeader(http);
   1.279 +  if (NS_FAILED(rv))
   1.280 +    return rv;
   1.281 +
   1.282 +  // Don't bother making a range request if we are just going to fetch the
   1.283 +  // entire document.
   1.284 +  if (mInterval || mCurrentSize != int64_t(0)) {
   1.285 +    nsAutoCString range;
   1.286 +    MakeRangeSpec(mCurrentSize, mTotalSize, mChunkSize, mInterval == 0, range);
   1.287 +
   1.288 +    rv = http->SetRequestHeader(NS_LITERAL_CSTRING("Range"), range, false);
   1.289 +    if (NS_FAILED(rv))
   1.290 +      return rv;
   1.291 +
   1.292 +    if (!mPartialValidator.IsEmpty())
   1.293 +      http->SetRequestHeader(NS_LITERAL_CSTRING("If-Range"),
   1.294 +                             mPartialValidator, false);
   1.295 +
   1.296 +    if (mCacheBust) {
   1.297 +      http->SetRequestHeader(NS_LITERAL_CSTRING("Cache-Control"),
   1.298 +                             NS_LITERAL_CSTRING("no-cache"), false);
   1.299 +      http->SetRequestHeader(NS_LITERAL_CSTRING("Pragma"),
   1.300 +                             NS_LITERAL_CSTRING("no-cache"), false);
   1.301 +    }
   1.302 +  }
   1.303 +
   1.304 +  rv = channel->AsyncOpen(this, nullptr);
   1.305 +  if (NS_FAILED(rv))
   1.306 +    return rv;
   1.307 +
   1.308 +  // Wait to assign mChannel when we know we are going to succeed.  This is
   1.309 +  // important because we don't want to introduce a reference cycle between
   1.310 +  // mChannel and this until we know for a fact that AsyncOpen has succeeded,
   1.311 +  // thus ensuring that our stream listener methods will be invoked.
   1.312 +  mChannel = channel;
   1.313 +  return NS_OK;
   1.314 +}
   1.315 +
   1.316 +// Reads the current file size and validates it.
   1.317 +nsresult
   1.318 +nsIncrementalDownload::ReadCurrentSize()
   1.319 +{
   1.320 +  int64_t size;
   1.321 +  nsresult rv = mDest->GetFileSize((int64_t *) &size);
   1.322 +  if (rv == NS_ERROR_FILE_NOT_FOUND ||
   1.323 +      rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
   1.324 +    mCurrentSize = 0;
   1.325 +    return NS_OK;
   1.326 +  }
   1.327 +  if (NS_FAILED(rv))
   1.328 +    return rv;
   1.329 +
   1.330 +  mCurrentSize = size; 
   1.331 +  return NS_OK;
   1.332 +}
   1.333 +
   1.334 +// nsISupports
   1.335 +
   1.336 +NS_IMPL_ISUPPORTS(nsIncrementalDownload,
   1.337 +                  nsIIncrementalDownload,
   1.338 +                  nsIRequest,
   1.339 +                  nsIStreamListener,
   1.340 +                  nsIRequestObserver,
   1.341 +                  nsIObserver,
   1.342 +                  nsIInterfaceRequestor,
   1.343 +                  nsIChannelEventSink,
   1.344 +                  nsISupportsWeakReference,
   1.345 +                  nsIAsyncVerifyRedirectCallback)
   1.346 +
   1.347 +// nsIRequest
   1.348 +
   1.349 +NS_IMETHODIMP
   1.350 +nsIncrementalDownload::GetName(nsACString &name)
   1.351 +{
   1.352 +  NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_INITIALIZED);
   1.353 +
   1.354 +  return mURI->GetSpec(name);
   1.355 +}
   1.356 +
   1.357 +NS_IMETHODIMP
   1.358 +nsIncrementalDownload::IsPending(bool *isPending)
   1.359 +{
   1.360 +  *isPending = mIsPending;
   1.361 +  return NS_OK;
   1.362 +}
   1.363 +
   1.364 +NS_IMETHODIMP
   1.365 +nsIncrementalDownload::GetStatus(nsresult *status)
   1.366 +{
   1.367 +  *status = mStatus;
   1.368 +  return NS_OK;
   1.369 +}
   1.370 +
   1.371 +NS_IMETHODIMP
   1.372 +nsIncrementalDownload::Cancel(nsresult status)
   1.373 +{
   1.374 +  NS_ENSURE_ARG(NS_FAILED(status));
   1.375 +
   1.376 +  // Ignore this cancelation if we're already canceled.
   1.377 +  if (NS_FAILED(mStatus))
   1.378 +    return NS_OK;
   1.379 +
   1.380 +  mStatus = status;
   1.381 +
   1.382 +  // Nothing more to do if callbacks aren't pending.
   1.383 +  if (!mIsPending)
   1.384 +    return NS_OK;
   1.385 +
   1.386 +  if (mChannel) {
   1.387 +    mChannel->Cancel(mStatus);
   1.388 +    NS_ASSERTION(!mTimer, "what is this timer object doing here?");
   1.389 +  }
   1.390 +  else {
   1.391 +    // dispatch a timer callback event to drive invoking our listener's
   1.392 +    // OnStopRequest.
   1.393 +    if (mTimer)
   1.394 +      mTimer->Cancel();
   1.395 +    StartTimer(0);
   1.396 +  }
   1.397 +
   1.398 +  return NS_OK;
   1.399 +}
   1.400 +
   1.401 +NS_IMETHODIMP
   1.402 +nsIncrementalDownload::Suspend()
   1.403 +{
   1.404 +  return NS_ERROR_NOT_IMPLEMENTED;
   1.405 +}
   1.406 +
   1.407 +NS_IMETHODIMP
   1.408 +nsIncrementalDownload::Resume()
   1.409 +{
   1.410 +  return NS_ERROR_NOT_IMPLEMENTED;
   1.411 +}
   1.412 +
   1.413 +NS_IMETHODIMP
   1.414 +nsIncrementalDownload::GetLoadFlags(nsLoadFlags *loadFlags)
   1.415 +{
   1.416 +  *loadFlags = mLoadFlags;
   1.417 +  return NS_OK;
   1.418 +}
   1.419 +
   1.420 +NS_IMETHODIMP
   1.421 +nsIncrementalDownload::SetLoadFlags(nsLoadFlags loadFlags)
   1.422 +{
   1.423 +  mLoadFlags = loadFlags;
   1.424 +  return NS_OK;
   1.425 +}
   1.426 +
   1.427 +NS_IMETHODIMP
   1.428 +nsIncrementalDownload::GetLoadGroup(nsILoadGroup **loadGroup)
   1.429 +{
   1.430 +  return NS_ERROR_NOT_IMPLEMENTED;
   1.431 +}
   1.432 +
   1.433 +NS_IMETHODIMP
   1.434 +nsIncrementalDownload::SetLoadGroup(nsILoadGroup *loadGroup)
   1.435 +{
   1.436 +  return NS_ERROR_NOT_IMPLEMENTED;
   1.437 +}
   1.438 +
   1.439 +// nsIIncrementalDownload
   1.440 +
   1.441 +NS_IMETHODIMP
   1.442 +nsIncrementalDownload::Init(nsIURI *uri, nsIFile *dest,
   1.443 +                            int32_t chunkSize, int32_t interval)
   1.444 +{
   1.445 +  // Keep it simple: only allow initialization once
   1.446 +  NS_ENSURE_FALSE(mURI, NS_ERROR_ALREADY_INITIALIZED);
   1.447 +
   1.448 +  mDest = do_QueryInterface(dest);
   1.449 +  NS_ENSURE_ARG(mDest);
   1.450 +
   1.451 +  mURI = uri;
   1.452 +  mFinalURI = uri;
   1.453 +
   1.454 +  if (chunkSize > 0)
   1.455 +    mChunkSize = chunkSize;
   1.456 +  if (interval >= 0)
   1.457 +    mInterval = interval;
   1.458 +  return NS_OK;
   1.459 +}
   1.460 +
   1.461 +NS_IMETHODIMP
   1.462 +nsIncrementalDownload::GetURI(nsIURI **result)
   1.463 +{
   1.464 +  NS_IF_ADDREF(*result = mURI);
   1.465 +  return NS_OK;
   1.466 +}
   1.467 +
   1.468 +NS_IMETHODIMP
   1.469 +nsIncrementalDownload::GetFinalURI(nsIURI **result)
   1.470 +{
   1.471 +  NS_IF_ADDREF(*result = mFinalURI);
   1.472 +  return NS_OK;
   1.473 +}
   1.474 +
   1.475 +NS_IMETHODIMP
   1.476 +nsIncrementalDownload::GetDestination(nsIFile **result)
   1.477 +{
   1.478 +  if (!mDest) {
   1.479 +    *result = nullptr;
   1.480 +    return NS_OK;
   1.481 +  }
   1.482 +  // Return a clone of mDest so that callers may modify the resulting nsIFile
   1.483 +  // without corrupting our internal object.  This also works around the fact
   1.484 +  // that some nsIFile impls may cache the result of stat'ing the filesystem.
   1.485 +  return mDest->Clone(result);
   1.486 +}
   1.487 +
   1.488 +NS_IMETHODIMP
   1.489 +nsIncrementalDownload::GetTotalSize(int64_t *result)
   1.490 +{
   1.491 +  *result = mTotalSize;
   1.492 +  return NS_OK;
   1.493 +}
   1.494 +
   1.495 +NS_IMETHODIMP
   1.496 +nsIncrementalDownload::GetCurrentSize(int64_t *result)
   1.497 +{
   1.498 +  *result = mCurrentSize;
   1.499 +  return NS_OK;
   1.500 +}
   1.501 +
   1.502 +NS_IMETHODIMP
   1.503 +nsIncrementalDownload::Start(nsIRequestObserver *observer,
   1.504 +                             nsISupports *context)
   1.505 +{
   1.506 +  NS_ENSURE_ARG(observer);
   1.507 +  NS_ENSURE_FALSE(mIsPending, NS_ERROR_IN_PROGRESS);
   1.508 +
   1.509 +  // Observe system shutdown so we can be sure to release any reference held
   1.510 +  // between ourselves and the timer.  We have the observer service hold a weak
   1.511 +  // reference to us, so that we don't have to worry about calling
   1.512 +  // RemoveObserver.  XXX(darin): The timer code should do this for us.
   1.513 +  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   1.514 +  if (obs)
   1.515 +    obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
   1.516 +
   1.517 +  nsresult rv = ReadCurrentSize();
   1.518 +  if (NS_FAILED(rv))
   1.519 +    return rv;
   1.520 +
   1.521 +  rv = StartTimer(0);
   1.522 +  if (NS_FAILED(rv))
   1.523 +    return rv;
   1.524 +
   1.525 +  mObserver = observer;
   1.526 +  mObserverContext = context;
   1.527 +  mProgressSink = do_QueryInterface(observer);  // ok if null
   1.528 +
   1.529 +  mIsPending = true;
   1.530 +  return NS_OK;
   1.531 +}
   1.532 +
   1.533 +// nsIRequestObserver
   1.534 +
   1.535 +NS_IMETHODIMP
   1.536 +nsIncrementalDownload::OnStartRequest(nsIRequest *request,
   1.537 +                                      nsISupports *context)
   1.538 +{
   1.539 +  nsresult rv;
   1.540 +
   1.541 +  nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(request, &rv);
   1.542 +  if (NS_FAILED(rv))
   1.543 +    return rv;
   1.544 +
   1.545 +  // Ensure that we are receiving a 206 response.
   1.546 +  uint32_t code;
   1.547 +  rv = http->GetResponseStatus(&code);
   1.548 +  if (NS_FAILED(rv))
   1.549 +    return rv;
   1.550 +  if (code != 206) {
   1.551 +    // We may already have the entire file downloaded, in which case
   1.552 +    // our request for a range beyond the end of the file would have
   1.553 +    // been met with an error response code.
   1.554 +    if (code == 416 && mTotalSize == int64_t(-1)) {
   1.555 +      mTotalSize = mCurrentSize;
   1.556 +      // Return an error code here to suppress OnDataAvailable.
   1.557 +      return NS_ERROR_DOWNLOAD_COMPLETE;
   1.558 +    }
   1.559 +    // The server may have decided to give us all of the data in one chunk.  If
   1.560 +    // we requested a partial range, then we don't want to download all of the
   1.561 +    // data at once.  So, we'll just try again, but if this keeps happening then
   1.562 +    // we'll eventually give up.
   1.563 +    if (code == 200) {
   1.564 +      if (mInterval) {
   1.565 +        mChannel = nullptr;
   1.566 +        if (++mNonPartialCount > MAX_RETRY_COUNT) {
   1.567 +          NS_WARNING("unable to fetch a byte range; giving up");
   1.568 +          return NS_ERROR_FAILURE;
   1.569 +        }
   1.570 +        // Increase delay with each failure.
   1.571 +        StartTimer(mInterval * mNonPartialCount);
   1.572 +        return NS_ERROR_DOWNLOAD_NOT_PARTIAL;
   1.573 +      }
   1.574 +      // Since we have been asked to download the rest of the file, we can deal
   1.575 +      // with a 200 response.  This may result in downloading the beginning of
   1.576 +      // the file again, but that can't really be helped.
   1.577 +    } else {
   1.578 +      NS_WARNING("server response was unexpected");
   1.579 +      return NS_ERROR_UNEXPECTED;
   1.580 +    }
   1.581 +  } else {
   1.582 +    // We got a partial response, so clear this counter in case the next chunk
   1.583 +    // results in a 200 response.
   1.584 +    mNonPartialCount = 0;
   1.585 +
   1.586 +    // confirm that the content-range response header is consistent with
   1.587 +    // expectations on each 206. If it is not then drop this response and
   1.588 +    // retry with no-cache set.
   1.589 +    if (!mCacheBust) {
   1.590 +      nsAutoCString buf;
   1.591 +      int64_t startByte = 0;
   1.592 +      bool confirmedOK = false;
   1.593 +
   1.594 +      rv = http->GetResponseHeader(NS_LITERAL_CSTRING("Content-Range"), buf);
   1.595 +      if (NS_FAILED(rv))
   1.596 +        return rv; // it isn't a useful 206 without a CONTENT-RANGE of some sort
   1.597 +
   1.598 +      // Content-Range: bytes 0-299999/25604694
   1.599 +      int32_t p = buf.Find("bytes ");
   1.600 +
   1.601 +      // first look for the starting point of the content-range
   1.602 +      // to make sure it is what we expect
   1.603 +      if (p != -1) {
   1.604 +        char *endptr = nullptr;
   1.605 +        const char *s = buf.get() + p + 6;
   1.606 +        while (*s && *s == ' ')
   1.607 +          s++;
   1.608 +        startByte = strtol(s, &endptr, 10);
   1.609 +
   1.610 +        if (*s && endptr && (endptr != s) &&
   1.611 +            (mCurrentSize == startByte)) {
   1.612 +
   1.613 +          // ok the starting point is confirmed. We still need to check the
   1.614 +          // total size of the range for consistency if this isn't
   1.615 +          // the first chunk
   1.616 +          if (mTotalSize == int64_t(-1)) {
   1.617 +            // first chunk
   1.618 +            confirmedOK = true;
   1.619 +          } else {
   1.620 +            int32_t slash = buf.FindChar('/');
   1.621 +            int64_t rangeSize = 0;
   1.622 +            if (slash != kNotFound &&
   1.623 +                (PR_sscanf(buf.get() + slash + 1, "%lld", (int64_t *) &rangeSize) == 1) &&
   1.624 +                rangeSize == mTotalSize) {
   1.625 +              confirmedOK = true;
   1.626 +            }
   1.627 +          }
   1.628 +        }
   1.629 +      }
   1.630 +
   1.631 +      if (!confirmedOK) {
   1.632 +        NS_WARNING("unexpected content-range");
   1.633 +        mCacheBust = true;
   1.634 +        mChannel = nullptr;
   1.635 +        if (++mNonPartialCount > MAX_RETRY_COUNT) {
   1.636 +          NS_WARNING("unable to fetch a byte range; giving up");
   1.637 +          return NS_ERROR_FAILURE;
   1.638 +        }
   1.639 +        // Increase delay with each failure.
   1.640 +        StartTimer(mInterval * mNonPartialCount);
   1.641 +        return NS_ERROR_DOWNLOAD_NOT_PARTIAL;
   1.642 +      }
   1.643 +    }
   1.644 +  }
   1.645 +
   1.646 +  // Do special processing after the first response.
   1.647 +  if (mTotalSize == int64_t(-1)) {
   1.648 +    // Update knowledge of mFinalURI
   1.649 +    rv = http->GetURI(getter_AddRefs(mFinalURI));
   1.650 +    if (NS_FAILED(rv))
   1.651 +      return rv;
   1.652 +    http->GetResponseHeader(NS_LITERAL_CSTRING("Etag"), mPartialValidator);
   1.653 +    if (StringBeginsWith(mPartialValidator, NS_LITERAL_CSTRING("W/")))
   1.654 +      mPartialValidator.Truncate(); // don't use weak validators
   1.655 +    if (mPartialValidator.IsEmpty())
   1.656 +      http->GetResponseHeader(NS_LITERAL_CSTRING("Last-Modified"), mPartialValidator);
   1.657 +
   1.658 +    if (code == 206) {
   1.659 +      // OK, read the Content-Range header to determine the total size of this
   1.660 +      // download file.
   1.661 +      nsAutoCString buf;
   1.662 +      rv = http->GetResponseHeader(NS_LITERAL_CSTRING("Content-Range"), buf);
   1.663 +      if (NS_FAILED(rv))
   1.664 +        return rv;
   1.665 +      int32_t slash = buf.FindChar('/');
   1.666 +      if (slash == kNotFound) {
   1.667 +        NS_WARNING("server returned invalid Content-Range header!");
   1.668 +        return NS_ERROR_UNEXPECTED;
   1.669 +      }
   1.670 +      if (PR_sscanf(buf.get() + slash + 1, "%lld", (int64_t *) &mTotalSize) != 1)
   1.671 +        return NS_ERROR_UNEXPECTED;
   1.672 +    } else {
   1.673 +      rv = http->GetContentLength(&mTotalSize);
   1.674 +      if (NS_FAILED(rv))
   1.675 +        return rv;
   1.676 +      // We need to know the total size of the thing we're trying to download.
   1.677 +      if (mTotalSize == int64_t(-1)) {
   1.678 +        NS_WARNING("server returned no content-length header!");
   1.679 +        return NS_ERROR_UNEXPECTED;
   1.680 +      }
   1.681 +      // Need to truncate (or create, if it doesn't exist) the file since we
   1.682 +      // are downloading the whole thing.
   1.683 +      WriteToFile(mDest, nullptr, 0, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE);
   1.684 +      mCurrentSize = 0;
   1.685 +    }
   1.686 +
   1.687 +    // Notify observer that we are starting...
   1.688 +    rv = CallOnStartRequest();
   1.689 +    if (NS_FAILED(rv))
   1.690 +      return rv;
   1.691 +  }
   1.692 +
   1.693 +  // Adjust mChunkSize accordingly if mCurrentSize is close to mTotalSize.
   1.694 +  int64_t diff = mTotalSize - mCurrentSize;
   1.695 +  if (diff <= int64_t(0)) {
   1.696 +    NS_WARNING("about to set a bogus chunk size; giving up");
   1.697 +    return NS_ERROR_UNEXPECTED;
   1.698 +  }
   1.699 +
   1.700 +  if (diff < int64_t(mChunkSize))
   1.701 +    mChunkSize = uint32_t(diff);
   1.702 +
   1.703 +  mChunk = new char[mChunkSize];
   1.704 +  if (!mChunk)
   1.705 +    rv = NS_ERROR_OUT_OF_MEMORY;
   1.706 +
   1.707 +  return rv;
   1.708 +}
   1.709 +
   1.710 +NS_IMETHODIMP
   1.711 +nsIncrementalDownload::OnStopRequest(nsIRequest *request,
   1.712 +                                     nsISupports *context,
   1.713 +                                     nsresult status)
   1.714 +{
   1.715 +  // Not a real error; just a trick to kill off the channel without our
   1.716 +  // listener having to care.
   1.717 +  if (status == NS_ERROR_DOWNLOAD_NOT_PARTIAL)
   1.718 +    return NS_OK;
   1.719 +
   1.720 +  // Not a real error; just a trick used to suppress OnDataAvailable calls.
   1.721 +  if (status == NS_ERROR_DOWNLOAD_COMPLETE)
   1.722 +    status = NS_OK;
   1.723 +
   1.724 +  if (NS_SUCCEEDED(mStatus))
   1.725 +    mStatus = status;
   1.726 +
   1.727 +  if (mChunk) {
   1.728 +    if (NS_SUCCEEDED(mStatus))
   1.729 +      mStatus = FlushChunk();
   1.730 +
   1.731 +    mChunk = nullptr;  // deletes memory
   1.732 +    mChunkLen = 0;
   1.733 +    UpdateProgress();
   1.734 +  }
   1.735 +
   1.736 +  mChannel = nullptr;
   1.737 +
   1.738 +  // Notify listener if we hit an error or finished
   1.739 +  if (NS_FAILED(mStatus) || mCurrentSize == mTotalSize) {
   1.740 +    CallOnStopRequest();
   1.741 +    return NS_OK;
   1.742 +  }
   1.743 +
   1.744 +  return StartTimer(mInterval);  // Do next chunk
   1.745 +}
   1.746 +
   1.747 +// nsIStreamListener
   1.748 +
   1.749 +NS_IMETHODIMP
   1.750 +nsIncrementalDownload::OnDataAvailable(nsIRequest *request,
   1.751 +                                       nsISupports *context,
   1.752 +                                       nsIInputStream *input,
   1.753 +                                       uint64_t offset,
   1.754 +                                       uint32_t count)
   1.755 +{
   1.756 +  while (count) {
   1.757 +    uint32_t space = mChunkSize - mChunkLen;
   1.758 +    uint32_t n, len = std::min(space, count);
   1.759 +
   1.760 +    nsresult rv = input->Read(mChunk + mChunkLen, len, &n);
   1.761 +    if (NS_FAILED(rv))
   1.762 +      return rv;
   1.763 +    if (n != len)
   1.764 +      return NS_ERROR_UNEXPECTED;
   1.765 +
   1.766 +    count -= n;
   1.767 +    mChunkLen += n;
   1.768 +
   1.769 +    if (mChunkLen == mChunkSize) {
   1.770 +      rv = FlushChunk();
   1.771 +      if (NS_FAILED(rv))
   1.772 +        return rv;
   1.773 +    }
   1.774 +  }
   1.775 +
   1.776 +  if (PR_Now() > mLastProgressUpdate + UPDATE_PROGRESS_INTERVAL)
   1.777 +    UpdateProgress();
   1.778 +
   1.779 +  return NS_OK;
   1.780 +}
   1.781 +
   1.782 +// nsIObserver
   1.783 +
   1.784 +NS_IMETHODIMP
   1.785 +nsIncrementalDownload::Observe(nsISupports *subject, const char *topic,
   1.786 +                               const char16_t *data)
   1.787 +{
   1.788 +  if (strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
   1.789 +    Cancel(NS_ERROR_ABORT);
   1.790 +
   1.791 +    // Since the app is shutting down, we need to go ahead and notify our
   1.792 +    // observer here.  Otherwise, we would notify them after XPCOM has been
   1.793 +    // shutdown or not at all.
   1.794 +    CallOnStopRequest();
   1.795 +  }
   1.796 +  else if (strcmp(topic, NS_TIMER_CALLBACK_TOPIC) == 0) {
   1.797 +    mTimer = nullptr;
   1.798 +    nsresult rv = ProcessTimeout();
   1.799 +    if (NS_FAILED(rv))
   1.800 +      Cancel(rv);
   1.801 +  }
   1.802 +  return NS_OK;
   1.803 +}
   1.804 +
   1.805 +// nsIInterfaceRequestor
   1.806 +
   1.807 +NS_IMETHODIMP
   1.808 +nsIncrementalDownload::GetInterface(const nsIID &iid, void **result)
   1.809 +{
   1.810 +  if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) {
   1.811 +    NS_ADDREF_THIS();
   1.812 +    *result = static_cast<nsIChannelEventSink *>(this);
   1.813 +    return NS_OK;
   1.814 +  }
   1.815 +
   1.816 +  nsCOMPtr<nsIInterfaceRequestor> ir = do_QueryInterface(mObserver);
   1.817 +  if (ir)
   1.818 +    return ir->GetInterface(iid, result);
   1.819 +
   1.820 +  return NS_ERROR_NO_INTERFACE;
   1.821 +}
   1.822 +
   1.823 +nsresult 
   1.824 +nsIncrementalDownload::ClearRequestHeader(nsIHttpChannel *channel)
   1.825 +{
   1.826 +  NS_ENSURE_ARG(channel);
   1.827 +  
   1.828 +  // We don't support encodings -- they make the Content-Length not equal
   1.829 +  // to the actual size of the data. 
   1.830 +  return channel->SetRequestHeader(NS_LITERAL_CSTRING("Accept-Encoding"),
   1.831 +                                   NS_LITERAL_CSTRING(""), false);
   1.832 +}
   1.833 +
   1.834 +// nsIChannelEventSink
   1.835 +
   1.836 +NS_IMETHODIMP
   1.837 +nsIncrementalDownload::AsyncOnChannelRedirect(nsIChannel *oldChannel,
   1.838 +                                              nsIChannel *newChannel,
   1.839 +                                              uint32_t flags,
   1.840 +                                              nsIAsyncVerifyRedirectCallback *cb)
   1.841 +{
   1.842 +  // In response to a redirect, we need to propagate the Range header.  See bug
   1.843 +  // 311595.  Any failure code returned from this function aborts the redirect.
   1.844 + 
   1.845 +  nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(oldChannel);
   1.846 +  NS_ENSURE_STATE(http);
   1.847 +
   1.848 +  nsCOMPtr<nsIHttpChannel> newHttpChannel = do_QueryInterface(newChannel);
   1.849 +  NS_ENSURE_STATE(newHttpChannel);
   1.850 +
   1.851 +  NS_NAMED_LITERAL_CSTRING(rangeHdr, "Range");
   1.852 +
   1.853 +  nsresult rv = ClearRequestHeader(newHttpChannel);
   1.854 +  if (NS_FAILED(rv))
   1.855 +    return rv;
   1.856 +
   1.857 +  // If we didn't have a Range header, then we must be doing a full download.
   1.858 +  nsAutoCString rangeVal;
   1.859 +  http->GetRequestHeader(rangeHdr, rangeVal);
   1.860 +  if (!rangeVal.IsEmpty()) {
   1.861 +    rv = newHttpChannel->SetRequestHeader(rangeHdr, rangeVal, false);
   1.862 +    NS_ENSURE_SUCCESS(rv, rv);
   1.863 +  }
   1.864 +
   1.865 +  // A redirection changes the validator
   1.866 +  mPartialValidator.Truncate();
   1.867 +
   1.868 +  if (mCacheBust) {
   1.869 +    newHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Cache-Control"),
   1.870 +                                     NS_LITERAL_CSTRING("no-cache"), false);
   1.871 +    newHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Pragma"),
   1.872 +                                     NS_LITERAL_CSTRING("no-cache"), false);
   1.873 +  }
   1.874 +
   1.875 +  // Prepare to receive callback
   1.876 +  mRedirectCallback = cb;
   1.877 +  mNewRedirectChannel = newChannel;
   1.878 +
   1.879 +  // Give the observer a chance to see this redirect notification.
   1.880 +  nsCOMPtr<nsIChannelEventSink> sink = do_GetInterface(mObserver);
   1.881 +  if (sink) {
   1.882 +    rv = sink->AsyncOnChannelRedirect(oldChannel, newChannel, flags, this);
   1.883 +    if (NS_FAILED(rv)) {
   1.884 +        mRedirectCallback = nullptr;
   1.885 +        mNewRedirectChannel = nullptr;
   1.886 +    }
   1.887 +    return rv;
   1.888 +  }
   1.889 +  (void) OnRedirectVerifyCallback(NS_OK);
   1.890 +  return NS_OK;
   1.891 +}
   1.892 +
   1.893 +NS_IMETHODIMP
   1.894 +nsIncrementalDownload::OnRedirectVerifyCallback(nsresult result)
   1.895 +{
   1.896 +  NS_ASSERTION(mRedirectCallback, "mRedirectCallback not set in callback");
   1.897 +  NS_ASSERTION(mNewRedirectChannel, "mNewRedirectChannel not set in callback");
   1.898 +
   1.899 +  // Update mChannel, so we can Cancel the new channel.
   1.900 +  if (NS_SUCCEEDED(result))
   1.901 +    mChannel = mNewRedirectChannel;
   1.902 +
   1.903 +  mRedirectCallback->OnRedirectVerifyCallback(result);
   1.904 +  mRedirectCallback = nullptr;
   1.905 +  mNewRedirectChannel = nullptr;
   1.906 +  return NS_OK;
   1.907 +}
   1.908 +
   1.909 +extern nsresult
   1.910 +net_NewIncrementalDownload(nsISupports *outer, const nsIID &iid, void **result)
   1.911 +{
   1.912 +  if (outer)
   1.913 +    return NS_ERROR_NO_AGGREGATION;
   1.914 +
   1.915 +  nsIncrementalDownload *d = new nsIncrementalDownload();
   1.916 +  if (!d)
   1.917 +    return NS_ERROR_OUT_OF_MEMORY;
   1.918 +  
   1.919 +  NS_ADDREF(d);
   1.920 +  nsresult rv = d->QueryInterface(iid, result);
   1.921 +  NS_RELEASE(d);
   1.922 +  return rv;
   1.923 +}

mercurial