Coverage Report

Created: 2018-09-25 14:53

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