Source code

Revision control

Other Tools

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