Backed out 10 changesets (bug 1803810) for xpcshell failures on test_import_global...
[gecko.git] / netwerk / base / nsIncrementalDownload.cpp
blobd7cb65809239df93e9d8c5de3bde51007813684f
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/Attributes.h"
8 #include "mozilla/UniquePtrExtensions.h"
9 #include "mozilla/UniquePtr.h"
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 "nsIHttpChannel.h"
22 #include "nsITimer.h"
23 #include "nsIURI.h"
24 #include "nsIInputStream.h"
25 #include "nsNetUtil.h"
26 #include "nsWeakReference.h"
27 #include "prio.h"
28 #include "prprf.h"
29 #include <algorithm>
30 #include "nsIContentPolicy.h"
31 #include "nsContentUtils.h"
32 #include "mozilla/Logging.h"
33 #include "mozilla/UniquePtr.h"
35 // Default values used to initialize a nsIncrementalDownload object.
36 #define DEFAULT_CHUNK_SIZE (4096 * 16) // bytes
37 #define DEFAULT_INTERVAL 60 // seconds
39 #define UPDATE_PROGRESS_INTERVAL PRTime(100 * PR_USEC_PER_MSEC) // 100ms
41 // Number of times to retry a failed byte-range request.
42 #define MAX_RETRY_COUNT 20
44 using namespace mozilla;
45 using namespace mozilla::net;
47 static LazyLogModule gIDLog("IncrementalDownload");
48 #undef LOG
49 #define LOG(args) MOZ_LOG(gIDLog, mozilla::LogLevel::Debug, args)
51 //-----------------------------------------------------------------------------
53 static nsresult WriteToFile(nsIFile* lf, const char* data, uint32_t len,
54 int32_t flags) {
55 PRFileDesc* fd;
56 int32_t mode = 0600;
57 nsresult rv;
58 rv = lf->OpenNSPRFileDesc(flags, mode, &fd);
59 if (NS_FAILED(rv)) return rv;
61 if (len) {
62 rv = PR_Write(fd, data, len) == int32_t(len) ? NS_OK : NS_ERROR_FAILURE;
65 PR_Close(fd);
66 return rv;
69 static nsresult AppendToFile(nsIFile* lf, const char* data, uint32_t len) {
70 int32_t flags = PR_WRONLY | PR_CREATE_FILE | PR_APPEND;
71 return WriteToFile(lf, data, len, flags);
74 // maxSize may be -1 if unknown
75 static void MakeRangeSpec(const int64_t& size, const int64_t& maxSize,
76 int32_t chunkSize, bool fetchRemaining,
77 nsCString& rangeSpec) {
78 rangeSpec.AssignLiteral("bytes=");
79 rangeSpec.AppendInt(int64_t(size));
80 rangeSpec.Append('-');
82 if (fetchRemaining) return;
84 int64_t end = size + int64_t(chunkSize);
85 if (maxSize != int64_t(-1) && end > maxSize) end = maxSize;
86 end -= 1;
88 rangeSpec.AppendInt(int64_t(end));
91 //-----------------------------------------------------------------------------
93 class nsIncrementalDownload final : public nsIIncrementalDownload,
94 public nsIStreamListener,
95 public nsIObserver,
96 public nsIInterfaceRequestor,
97 public nsIChannelEventSink,
98 public nsSupportsWeakReference,
99 public nsIAsyncVerifyRedirectCallback {
100 public:
101 NS_DECL_ISUPPORTS
102 NS_DECL_NSIREQUEST
103 NS_DECL_NSIINCREMENTALDOWNLOAD
104 NS_DECL_NSIREQUESTOBSERVER
105 NS_DECL_NSISTREAMLISTENER
106 NS_DECL_NSIOBSERVER
107 NS_DECL_NSIINTERFACEREQUESTOR
108 NS_DECL_NSICHANNELEVENTSINK
109 NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
111 nsIncrementalDownload() = default;
113 private:
114 ~nsIncrementalDownload() = default;
115 nsresult FlushChunk();
116 void UpdateProgress();
117 nsresult CallOnStartRequest();
118 void CallOnStopRequest();
119 nsresult StartTimer(int32_t interval);
120 nsresult ProcessTimeout();
121 nsresult ReadCurrentSize();
122 nsresult ClearRequestHeader(nsIHttpChannel* channel);
124 nsCOMPtr<nsIRequestObserver> mObserver;
125 nsCOMPtr<nsIProgressEventSink> mProgressSink;
126 nsCOMPtr<nsIURI> mURI;
127 nsCOMPtr<nsIURI> mFinalURI;
128 nsCOMPtr<nsIFile> mDest;
129 nsCOMPtr<nsIChannel> mChannel;
130 nsCOMPtr<nsITimer> mTimer;
131 mozilla::UniquePtr<char[]> mChunk;
132 int32_t mChunkLen{0};
133 int32_t mChunkSize{DEFAULT_CHUNK_SIZE};
134 int32_t mInterval{DEFAULT_INTERVAL};
135 int64_t mTotalSize{-1};
136 int64_t mCurrentSize{-1};
137 uint32_t mLoadFlags{LOAD_NORMAL};
138 int32_t mNonPartialCount{0};
139 nsresult mStatus{NS_OK};
140 bool mIsPending{false};
141 bool mDidOnStartRequest{false};
142 PRTime mLastProgressUpdate{0};
143 nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback;
144 nsCOMPtr<nsIChannel> mNewRedirectChannel;
145 nsCString mPartialValidator;
146 bool mCacheBust{false};
148 // nsITimerCallback is implemented on a subclass so that the name attribute
149 // doesn't conflict with the name attribute of the nsIRequest interface.
150 class TimerCallback final : public nsITimerCallback, public nsINamed {
151 public:
152 NS_DECL_ISUPPORTS
153 NS_DECL_NSITIMERCALLBACK
154 NS_DECL_NSINAMED
156 explicit TimerCallback(nsIncrementalDownload* aIncrementalDownload);
158 private:
159 ~TimerCallback() = default;
161 RefPtr<nsIncrementalDownload> mIncrementalDownload;
165 nsresult nsIncrementalDownload::FlushChunk() {
166 NS_ASSERTION(mTotalSize != int64_t(-1), "total size should be known");
168 if (mChunkLen == 0) return NS_OK;
170 nsresult rv = AppendToFile(mDest, mChunk.get(), mChunkLen);
171 if (NS_FAILED(rv)) return rv;
173 mCurrentSize += int64_t(mChunkLen);
174 mChunkLen = 0;
176 return NS_OK;
179 void nsIncrementalDownload::UpdateProgress() {
180 mLastProgressUpdate = PR_Now();
182 if (mProgressSink) {
183 mProgressSink->OnProgress(this, mCurrentSize + mChunkLen, mTotalSize);
187 nsresult nsIncrementalDownload::CallOnStartRequest() {
188 if (!mObserver || mDidOnStartRequest) return NS_OK;
190 mDidOnStartRequest = true;
191 return mObserver->OnStartRequest(this);
194 void nsIncrementalDownload::CallOnStopRequest() {
195 if (!mObserver) return;
197 // Ensure that OnStartRequest is always called once before OnStopRequest.
198 nsresult rv = CallOnStartRequest();
199 if (NS_SUCCEEDED(mStatus)) mStatus = rv;
201 mIsPending = false;
203 mObserver->OnStopRequest(this, mStatus);
204 mObserver = nullptr;
207 nsresult nsIncrementalDownload::StartTimer(int32_t interval) {
208 auto callback = MakeRefPtr<TimerCallback>(this);
209 return NS_NewTimerWithCallback(getter_AddRefs(mTimer), callback,
210 interval * 1000, nsITimer::TYPE_ONE_SHOT);
213 nsresult nsIncrementalDownload::ProcessTimeout() {
214 NS_ASSERTION(!mChannel, "how can we have a channel?");
216 // Handle existing error conditions
217 if (NS_FAILED(mStatus)) {
218 CallOnStopRequest();
219 return NS_OK;
222 // Fetch next chunk
224 nsCOMPtr<nsIChannel> channel;
225 nsresult rv = NS_NewChannel(
226 getter_AddRefs(channel), mFinalURI, nsContentUtils::GetSystemPrincipal(),
227 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
228 nsIContentPolicy::TYPE_OTHER,
229 nullptr, // nsICookieJarSettings
230 nullptr, // PerformanceStorage
231 nullptr, // loadGroup
232 this, // aCallbacks
233 mLoadFlags);
235 if (NS_FAILED(rv)) return rv;
237 nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(channel, &rv);
238 if (NS_FAILED(rv)) return rv;
240 NS_ASSERTION(mCurrentSize != int64_t(-1),
241 "we should know the current file size by now");
243 rv = ClearRequestHeader(http);
244 if (NS_FAILED(rv)) return rv;
246 // Don't bother making a range request if we are just going to fetch the
247 // entire document.
248 if (mInterval || mCurrentSize != int64_t(0)) {
249 nsAutoCString range;
250 MakeRangeSpec(mCurrentSize, mTotalSize, mChunkSize, mInterval == 0, range);
252 rv = http->SetRequestHeader("Range"_ns, range, false);
253 if (NS_FAILED(rv)) return rv;
255 if (!mPartialValidator.IsEmpty()) {
256 rv = http->SetRequestHeader("If-Range"_ns, mPartialValidator, false);
257 if (NS_FAILED(rv)) {
258 LOG(
259 ("nsIncrementalDownload::ProcessTimeout\n"
260 " failed to set request header: If-Range\n"));
264 if (mCacheBust) {
265 rv = http->SetRequestHeader("Cache-Control"_ns, "no-cache"_ns, false);
266 if (NS_FAILED(rv)) {
267 LOG(
268 ("nsIncrementalDownload::ProcessTimeout\n"
269 " failed to set request header: If-Range\n"));
271 rv = http->SetRequestHeader("Pragma"_ns, "no-cache"_ns, false);
272 if (NS_FAILED(rv)) {
273 LOG(
274 ("nsIncrementalDownload::ProcessTimeout\n"
275 " failed to set request header: If-Range\n"));
280 rv = channel->AsyncOpen(this);
281 if (NS_FAILED(rv)) return rv;
283 // Wait to assign mChannel when we know we are going to succeed. This is
284 // important because we don't want to introduce a reference cycle between
285 // mChannel and this until we know for a fact that AsyncOpen has succeeded,
286 // thus ensuring that our stream listener methods will be invoked.
287 mChannel = channel;
288 return NS_OK;
291 // Reads the current file size and validates it.
292 nsresult nsIncrementalDownload::ReadCurrentSize() {
293 int64_t size;
294 nsresult rv = mDest->GetFileSize((int64_t*)&size);
295 if (rv == NS_ERROR_FILE_NOT_FOUND) {
296 mCurrentSize = 0;
297 return NS_OK;
299 if (NS_FAILED(rv)) return rv;
301 mCurrentSize = size;
302 return NS_OK;
305 // nsISupports
307 NS_IMPL_ISUPPORTS(nsIncrementalDownload, nsIIncrementalDownload, nsIRequest,
308 nsIStreamListener, nsIRequestObserver, nsIObserver,
309 nsIInterfaceRequestor, nsIChannelEventSink,
310 nsISupportsWeakReference, nsIAsyncVerifyRedirectCallback)
312 // nsIRequest
314 NS_IMETHODIMP
315 nsIncrementalDownload::GetName(nsACString& name) {
316 NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_INITIALIZED);
318 return mURI->GetSpec(name);
321 NS_IMETHODIMP
322 nsIncrementalDownload::IsPending(bool* isPending) {
323 *isPending = mIsPending;
324 return NS_OK;
327 NS_IMETHODIMP
328 nsIncrementalDownload::GetStatus(nsresult* status) {
329 *status = mStatus;
330 return NS_OK;
333 NS_IMETHODIMP nsIncrementalDownload::SetCanceledReason(
334 const nsACString& aReason) {
335 return SetCanceledReasonImpl(aReason);
338 NS_IMETHODIMP nsIncrementalDownload::GetCanceledReason(nsACString& aReason) {
339 return GetCanceledReasonImpl(aReason);
342 NS_IMETHODIMP nsIncrementalDownload::CancelWithReason(
343 nsresult aStatus, const nsACString& aReason) {
344 return CancelWithReasonImpl(aStatus, aReason);
347 NS_IMETHODIMP
348 nsIncrementalDownload::Cancel(nsresult status) {
349 NS_ENSURE_ARG(NS_FAILED(status));
351 // Ignore this cancelation if we're already canceled.
352 if (NS_FAILED(mStatus)) return NS_OK;
354 mStatus = status;
356 // Nothing more to do if callbacks aren't pending.
357 if (!mIsPending) return NS_OK;
359 if (mChannel) {
360 mChannel->Cancel(mStatus);
361 NS_ASSERTION(!mTimer, "what is this timer object doing here?");
362 } else {
363 // dispatch a timer callback event to drive invoking our listener's
364 // OnStopRequest.
365 if (mTimer) mTimer->Cancel();
366 StartTimer(0);
369 return NS_OK;
372 NS_IMETHODIMP
373 nsIncrementalDownload::Suspend() { return NS_ERROR_NOT_IMPLEMENTED; }
375 NS_IMETHODIMP
376 nsIncrementalDownload::Resume() { return NS_ERROR_NOT_IMPLEMENTED; }
378 NS_IMETHODIMP
379 nsIncrementalDownload::GetLoadFlags(nsLoadFlags* loadFlags) {
380 *loadFlags = mLoadFlags;
381 return NS_OK;
384 NS_IMETHODIMP
385 nsIncrementalDownload::SetLoadFlags(nsLoadFlags loadFlags) {
386 mLoadFlags = loadFlags;
387 return NS_OK;
390 NS_IMETHODIMP
391 nsIncrementalDownload::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
392 return GetTRRModeImpl(aTRRMode);
395 NS_IMETHODIMP
396 nsIncrementalDownload::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
397 return SetTRRModeImpl(aTRRMode);
400 NS_IMETHODIMP
401 nsIncrementalDownload::GetLoadGroup(nsILoadGroup** loadGroup) {
402 return NS_ERROR_NOT_IMPLEMENTED;
405 NS_IMETHODIMP
406 nsIncrementalDownload::SetLoadGroup(nsILoadGroup* loadGroup) {
407 return NS_ERROR_NOT_IMPLEMENTED;
410 // nsIIncrementalDownload
412 NS_IMETHODIMP
413 nsIncrementalDownload::Init(nsIURI* uri, nsIFile* dest, int32_t chunkSize,
414 int32_t interval) {
415 // Keep it simple: only allow initialization once
416 NS_ENSURE_FALSE(mURI, NS_ERROR_ALREADY_INITIALIZED);
418 mDest = dest;
419 NS_ENSURE_ARG(mDest);
421 mURI = uri;
422 mFinalURI = uri;
424 if (chunkSize > 0) mChunkSize = chunkSize;
425 if (interval >= 0) mInterval = interval;
426 return NS_OK;
429 NS_IMETHODIMP
430 nsIncrementalDownload::GetURI(nsIURI** result) {
431 nsCOMPtr<nsIURI> uri = mURI;
432 uri.forget(result);
433 return NS_OK;
436 NS_IMETHODIMP
437 nsIncrementalDownload::GetFinalURI(nsIURI** result) {
438 nsCOMPtr<nsIURI> uri = mFinalURI;
439 uri.forget(result);
440 return NS_OK;
443 NS_IMETHODIMP
444 nsIncrementalDownload::GetDestination(nsIFile** result) {
445 if (!mDest) {
446 *result = nullptr;
447 return NS_OK;
449 // Return a clone of mDest so that callers may modify the resulting nsIFile
450 // without corrupting our internal object. This also works around the fact
451 // that some nsIFile impls may cache the result of stat'ing the filesystem.
452 return mDest->Clone(result);
455 NS_IMETHODIMP
456 nsIncrementalDownload::GetTotalSize(int64_t* result) {
457 *result = mTotalSize;
458 return NS_OK;
461 NS_IMETHODIMP
462 nsIncrementalDownload::GetCurrentSize(int64_t* result) {
463 *result = mCurrentSize;
464 return NS_OK;
467 NS_IMETHODIMP
468 nsIncrementalDownload::Start(nsIRequestObserver* observer,
469 nsISupports* context) {
470 NS_ENSURE_ARG(observer);
471 NS_ENSURE_FALSE(mIsPending, NS_ERROR_IN_PROGRESS);
473 // Observe system shutdown so we can be sure to release any reference held
474 // between ourselves and the timer. We have the observer service hold a weak
475 // reference to us, so that we don't have to worry about calling
476 // RemoveObserver. XXX(darin): The timer code should do this for us.
477 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
478 if (obs) obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
480 nsresult rv = ReadCurrentSize();
481 if (NS_FAILED(rv)) return rv;
483 rv = StartTimer(0);
484 if (NS_FAILED(rv)) return rv;
486 mObserver = observer;
487 mProgressSink = do_QueryInterface(observer); // ok if null
489 mIsPending = true;
490 return NS_OK;
493 // nsIRequestObserver
495 NS_IMETHODIMP
496 nsIncrementalDownload::OnStartRequest(nsIRequest* request) {
497 nsresult rv;
499 nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(request, &rv);
500 if (NS_FAILED(rv)) return rv;
502 // Ensure that we are receiving a 206 response.
503 uint32_t code;
504 rv = http->GetResponseStatus(&code);
505 if (NS_FAILED(rv)) return rv;
506 if (code != 206) {
507 // We may already have the entire file downloaded, in which case
508 // our request for a range beyond the end of the file would have
509 // been met with an error response code.
510 if (code == 416 && mTotalSize == int64_t(-1)) {
511 mTotalSize = mCurrentSize;
512 // Return an error code here to suppress OnDataAvailable.
513 return NS_ERROR_DOWNLOAD_COMPLETE;
515 // The server may have decided to give us all of the data in one chunk. If
516 // we requested a partial range, then we don't want to download all of the
517 // data at once. So, we'll just try again, but if this keeps happening then
518 // we'll eventually give up.
519 if (code == 200) {
520 if (mInterval) {
521 mChannel = nullptr;
522 if (++mNonPartialCount > MAX_RETRY_COUNT) {
523 NS_WARNING("unable to fetch a byte range; giving up");
524 return NS_ERROR_FAILURE;
526 // Increase delay with each failure.
527 StartTimer(mInterval * mNonPartialCount);
528 return NS_ERROR_DOWNLOAD_NOT_PARTIAL;
530 // Since we have been asked to download the rest of the file, we can deal
531 // with a 200 response. This may result in downloading the beginning of
532 // the file again, but that can't really be helped.
533 } else {
534 NS_WARNING("server response was unexpected");
535 return NS_ERROR_UNEXPECTED;
537 } else {
538 // We got a partial response, so clear this counter in case the next chunk
539 // results in a 200 response.
540 mNonPartialCount = 0;
542 // confirm that the content-range response header is consistent with
543 // expectations on each 206. If it is not then drop this response and
544 // retry with no-cache set.
545 if (!mCacheBust) {
546 nsAutoCString buf;
547 int64_t startByte = 0;
548 bool confirmedOK = false;
550 rv = http->GetResponseHeader("Content-Range"_ns, buf);
551 if (NS_FAILED(rv)) {
552 return rv; // it isn't a useful 206 without a CONTENT-RANGE of some
554 // sort
556 // Content-Range: bytes 0-299999/25604694
557 int32_t p = buf.Find("bytes ");
559 // first look for the starting point of the content-range
560 // to make sure it is what we expect
561 if (p != -1) {
562 char* endptr = nullptr;
563 const char* s = buf.get() + p + 6;
564 while (*s && *s == ' ') s++;
565 startByte = strtol(s, &endptr, 10);
567 if (*s && endptr && (endptr != s) && (mCurrentSize == startByte)) {
568 // ok the starting point is confirmed. We still need to check the
569 // total size of the range for consistency if this isn't
570 // the first chunk
571 if (mTotalSize == int64_t(-1)) {
572 // first chunk
573 confirmedOK = true;
574 } else {
575 int32_t slash = buf.FindChar('/');
576 int64_t rangeSize = 0;
577 if (slash != kNotFound &&
578 (PR_sscanf(buf.get() + slash + 1, "%lld",
579 (int64_t*)&rangeSize) == 1) &&
580 rangeSize == mTotalSize) {
581 confirmedOK = true;
587 if (!confirmedOK) {
588 NS_WARNING("unexpected content-range");
589 mCacheBust = true;
590 mChannel = nullptr;
591 if (++mNonPartialCount > MAX_RETRY_COUNT) {
592 NS_WARNING("unable to fetch a byte range; giving up");
593 return NS_ERROR_FAILURE;
595 // Increase delay with each failure.
596 StartTimer(mInterval * mNonPartialCount);
597 return NS_ERROR_DOWNLOAD_NOT_PARTIAL;
602 // Do special processing after the first response.
603 if (mTotalSize == int64_t(-1)) {
604 // Update knowledge of mFinalURI
605 rv = http->GetURI(getter_AddRefs(mFinalURI));
606 if (NS_FAILED(rv)) return rv;
607 Unused << http->GetResponseHeader("Etag"_ns, mPartialValidator);
608 if (StringBeginsWith(mPartialValidator, "W/"_ns)) {
609 mPartialValidator.Truncate(); // don't use weak validators
611 if (mPartialValidator.IsEmpty()) {
612 rv = http->GetResponseHeader("Last-Modified"_ns, mPartialValidator);
613 if (NS_FAILED(rv)) {
614 LOG(
615 ("nsIncrementalDownload::OnStartRequest\n"
616 " empty validator\n"));
620 if (code == 206) {
621 // OK, read the Content-Range header to determine the total size of this
622 // download file.
623 nsAutoCString buf;
624 rv = http->GetResponseHeader("Content-Range"_ns, buf);
625 if (NS_FAILED(rv)) return rv;
626 int32_t slash = buf.FindChar('/');
627 if (slash == kNotFound) {
628 NS_WARNING("server returned invalid Content-Range header!");
629 return NS_ERROR_UNEXPECTED;
631 if (PR_sscanf(buf.get() + slash + 1, "%lld", (int64_t*)&mTotalSize) !=
632 1) {
633 return NS_ERROR_UNEXPECTED;
635 } else {
636 rv = http->GetContentLength(&mTotalSize);
637 if (NS_FAILED(rv)) return rv;
638 // We need to know the total size of the thing we're trying to download.
639 if (mTotalSize == int64_t(-1)) {
640 NS_WARNING("server returned no content-length header!");
641 return NS_ERROR_UNEXPECTED;
643 // Need to truncate (or create, if it doesn't exist) the file since we
644 // are downloading the whole thing.
645 WriteToFile(mDest, nullptr, 0, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE);
646 mCurrentSize = 0;
649 // Notify observer that we are starting...
650 rv = CallOnStartRequest();
651 if (NS_FAILED(rv)) return rv;
654 // Adjust mChunkSize accordingly if mCurrentSize is close to mTotalSize.
655 int64_t diff = mTotalSize - mCurrentSize;
656 if (diff <= int64_t(0)) {
657 NS_WARNING("about to set a bogus chunk size; giving up");
658 return NS_ERROR_UNEXPECTED;
661 if (diff < int64_t(mChunkSize)) mChunkSize = uint32_t(diff);
663 mChunk = mozilla::MakeUniqueFallible<char[]>(mChunkSize);
664 if (!mChunk) rv = NS_ERROR_OUT_OF_MEMORY;
666 return rv;
669 NS_IMETHODIMP
670 nsIncrementalDownload::OnStopRequest(nsIRequest* request, nsresult status) {
671 // Not a real error; just a trick to kill off the channel without our
672 // listener having to care.
673 if (status == NS_ERROR_DOWNLOAD_NOT_PARTIAL) return NS_OK;
675 // Not a real error; just a trick used to suppress OnDataAvailable calls.
676 if (status == NS_ERROR_DOWNLOAD_COMPLETE) status = NS_OK;
678 if (NS_SUCCEEDED(mStatus)) mStatus = status;
680 if (mChunk) {
681 if (NS_SUCCEEDED(mStatus)) mStatus = FlushChunk();
683 mChunk = nullptr; // deletes memory
684 mChunkLen = 0;
685 UpdateProgress();
688 mChannel = nullptr;
690 // Notify listener if we hit an error or finished
691 if (NS_FAILED(mStatus) || mCurrentSize == mTotalSize) {
692 CallOnStopRequest();
693 return NS_OK;
696 return StartTimer(mInterval); // Do next chunk
699 // nsIStreamListener
701 NS_IMETHODIMP
702 nsIncrementalDownload::OnDataAvailable(nsIRequest* request,
703 nsIInputStream* input, uint64_t offset,
704 uint32_t count) {
705 while (count) {
706 uint32_t space = mChunkSize - mChunkLen;
707 uint32_t n, len = std::min(space, count);
709 nsresult rv = input->Read(&mChunk[mChunkLen], len, &n);
710 if (NS_FAILED(rv)) return rv;
711 if (n != len) return NS_ERROR_UNEXPECTED;
713 count -= n;
714 mChunkLen += n;
716 if (mChunkLen == mChunkSize) {
717 rv = FlushChunk();
718 if (NS_FAILED(rv)) return rv;
722 if (PR_Now() > mLastProgressUpdate + UPDATE_PROGRESS_INTERVAL) {
723 UpdateProgress();
726 return NS_OK;
729 // nsIObserver
731 NS_IMETHODIMP
732 nsIncrementalDownload::Observe(nsISupports* subject, const char* topic,
733 const char16_t* data) {
734 if (strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
735 Cancel(NS_ERROR_ABORT);
737 // Since the app is shutting down, we need to go ahead and notify our
738 // observer here. Otherwise, we would notify them after XPCOM has been
739 // shutdown or not at all.
740 CallOnStopRequest();
742 return NS_OK;
745 // nsITimerCallback
747 nsIncrementalDownload::TimerCallback::TimerCallback(
748 nsIncrementalDownload* aIncrementalDownload)
749 : mIncrementalDownload(aIncrementalDownload) {}
751 NS_IMPL_ISUPPORTS(nsIncrementalDownload::TimerCallback, nsITimerCallback,
752 nsINamed)
754 NS_IMETHODIMP
755 nsIncrementalDownload::TimerCallback::Notify(nsITimer* aTimer) {
756 mIncrementalDownload->mTimer = nullptr;
758 nsresult rv = mIncrementalDownload->ProcessTimeout();
759 if (NS_FAILED(rv)) mIncrementalDownload->Cancel(rv);
761 return NS_OK;
764 // nsINamed
766 NS_IMETHODIMP
767 nsIncrementalDownload::TimerCallback::GetName(nsACString& aName) {
768 aName.AssignLiteral("nsIncrementalDownload");
769 return NS_OK;
772 // nsIInterfaceRequestor
774 NS_IMETHODIMP
775 nsIncrementalDownload::GetInterface(const nsIID& iid, void** result) {
776 if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) {
777 NS_ADDREF_THIS();
778 *result = static_cast<nsIChannelEventSink*>(this);
779 return NS_OK;
782 nsCOMPtr<nsIInterfaceRequestor> ir = do_QueryInterface(mObserver);
783 if (ir) return ir->GetInterface(iid, result);
785 return NS_ERROR_NO_INTERFACE;
788 nsresult nsIncrementalDownload::ClearRequestHeader(nsIHttpChannel* channel) {
789 NS_ENSURE_ARG(channel);
791 // We don't support encodings -- they make the Content-Length not equal
792 // to the actual size of the data.
793 return channel->SetRequestHeader("Accept-Encoding"_ns, ""_ns, false);
796 // nsIChannelEventSink
798 NS_IMETHODIMP
799 nsIncrementalDownload::AsyncOnChannelRedirect(
800 nsIChannel* oldChannel, nsIChannel* newChannel, uint32_t flags,
801 nsIAsyncVerifyRedirectCallback* cb) {
802 // In response to a redirect, we need to propagate the Range header. See bug
803 // 311595. Any failure code returned from this function aborts the redirect.
805 nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(oldChannel);
806 NS_ENSURE_STATE(http);
808 nsCOMPtr<nsIHttpChannel> newHttpChannel = do_QueryInterface(newChannel);
809 NS_ENSURE_STATE(newHttpChannel);
811 constexpr auto rangeHdr = "Range"_ns;
813 nsresult rv = ClearRequestHeader(newHttpChannel);
814 if (NS_FAILED(rv)) return rv;
816 // If we didn't have a Range header, then we must be doing a full download.
817 nsAutoCString rangeVal;
818 Unused << http->GetRequestHeader(rangeHdr, rangeVal);
819 if (!rangeVal.IsEmpty()) {
820 rv = newHttpChannel->SetRequestHeader(rangeHdr, rangeVal, false);
821 NS_ENSURE_SUCCESS(rv, rv);
824 // A redirection changes the validator
825 mPartialValidator.Truncate();
827 if (mCacheBust) {
828 rv = newHttpChannel->SetRequestHeader("Cache-Control"_ns, "no-cache"_ns,
829 false);
830 if (NS_FAILED(rv)) {
831 LOG(
832 ("nsIncrementalDownload::AsyncOnChannelRedirect\n"
833 " failed to set request header: Cache-Control\n"));
835 rv = newHttpChannel->SetRequestHeader("Pragma"_ns, "no-cache"_ns, false);
836 if (NS_FAILED(rv)) {
837 LOG(
838 ("nsIncrementalDownload::AsyncOnChannelRedirect\n"
839 " failed to set request header: Pragma\n"));
843 // Prepare to receive callback
844 mRedirectCallback = cb;
845 mNewRedirectChannel = newChannel;
847 // Give the observer a chance to see this redirect notification.
848 nsCOMPtr<nsIChannelEventSink> sink = do_GetInterface(mObserver);
849 if (sink) {
850 rv = sink->AsyncOnChannelRedirect(oldChannel, newChannel, flags, this);
851 if (NS_FAILED(rv)) {
852 mRedirectCallback = nullptr;
853 mNewRedirectChannel = nullptr;
855 return rv;
857 (void)OnRedirectVerifyCallback(NS_OK);
858 return NS_OK;
861 NS_IMETHODIMP
862 nsIncrementalDownload::OnRedirectVerifyCallback(nsresult result) {
863 NS_ASSERTION(mRedirectCallback, "mRedirectCallback not set in callback");
864 NS_ASSERTION(mNewRedirectChannel, "mNewRedirectChannel not set in callback");
866 // Update mChannel, so we can Cancel the new channel.
867 if (NS_SUCCEEDED(result)) mChannel = mNewRedirectChannel;
869 mRedirectCallback->OnRedirectVerifyCallback(result);
870 mRedirectCallback = nullptr;
871 mNewRedirectChannel = nullptr;
872 return NS_OK;
875 extern nsresult net_NewIncrementalDownload(const nsIID& iid, void** result) {
876 RefPtr<nsIncrementalDownload> d = new nsIncrementalDownload();
877 return d->QueryInterface(iid, result);