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"
21 #include "nsIHttpChannel.h"
24 #include "nsIInputStream.h"
25 #include "nsNetUtil.h"
26 #include "nsWeakReference.h"
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");
49 #define LOG(args) MOZ_LOG(gIDLog, mozilla::LogLevel::Debug, args)
51 //-----------------------------------------------------------------------------
53 static nsresult
WriteToFile(nsIFile
* lf
, const char* data
, uint32_t len
,
58 rv
= lf
->OpenNSPRFileDesc(flags
, mode
, &fd
);
59 if (NS_FAILED(rv
)) return rv
;
62 rv
= PR_Write(fd
, data
, len
) == int32_t(len
) ? NS_OK
: NS_ERROR_FAILURE
;
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
;
88 rangeSpec
.AppendInt(int64_t(end
));
91 //-----------------------------------------------------------------------------
93 class nsIncrementalDownload final
: public nsIIncrementalDownload
,
94 public nsIStreamListener
,
96 public nsIInterfaceRequestor
,
97 public nsIChannelEventSink
,
98 public nsSupportsWeakReference
,
99 public nsIAsyncVerifyRedirectCallback
{
103 NS_DECL_NSIINCREMENTALDOWNLOAD
104 NS_DECL_NSIREQUESTOBSERVER
105 NS_DECL_NSISTREAMLISTENER
107 NS_DECL_NSIINTERFACEREQUESTOR
108 NS_DECL_NSICHANNELEVENTSINK
109 NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
111 nsIncrementalDownload() = default;
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
{
153 NS_DECL_NSITIMERCALLBACK
156 explicit TimerCallback(nsIncrementalDownload
* aIncrementalDownload
);
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
);
179 void nsIncrementalDownload::UpdateProgress() {
180 mLastProgressUpdate
= PR_Now();
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
;
203 mObserver
->OnStopRequest(this, mStatus
);
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
)) {
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
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
248 if (mInterval
|| mCurrentSize
!= int64_t(0)) {
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);
259 ("nsIncrementalDownload::ProcessTimeout\n"
260 " failed to set request header: If-Range\n"));
265 rv
= http
->SetRequestHeader("Cache-Control"_ns
, "no-cache"_ns
, false);
268 ("nsIncrementalDownload::ProcessTimeout\n"
269 " failed to set request header: If-Range\n"));
271 rv
= http
->SetRequestHeader("Pragma"_ns
, "no-cache"_ns
, false);
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.
291 // Reads the current file size and validates it.
292 nsresult
nsIncrementalDownload::ReadCurrentSize() {
294 nsresult rv
= mDest
->GetFileSize((int64_t*)&size
);
295 if (rv
== NS_ERROR_FILE_NOT_FOUND
) {
299 if (NS_FAILED(rv
)) return rv
;
307 NS_IMPL_ISUPPORTS(nsIncrementalDownload
, nsIIncrementalDownload
, nsIRequest
,
308 nsIStreamListener
, nsIRequestObserver
, nsIObserver
,
309 nsIInterfaceRequestor
, nsIChannelEventSink
,
310 nsISupportsWeakReference
, nsIAsyncVerifyRedirectCallback
)
315 nsIncrementalDownload::GetName(nsACString
& name
) {
316 NS_ENSURE_TRUE(mURI
, NS_ERROR_NOT_INITIALIZED
);
318 return mURI
->GetSpec(name
);
322 nsIncrementalDownload::IsPending(bool* isPending
) {
323 *isPending
= mIsPending
;
328 nsIncrementalDownload::GetStatus(nsresult
* status
) {
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
);
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
;
356 // Nothing more to do if callbacks aren't pending.
357 if (!mIsPending
) return NS_OK
;
360 mChannel
->Cancel(mStatus
);
361 NS_ASSERTION(!mTimer
, "what is this timer object doing here?");
363 // dispatch a timer callback event to drive invoking our listener's
365 if (mTimer
) mTimer
->Cancel();
373 nsIncrementalDownload::Suspend() { return NS_ERROR_NOT_IMPLEMENTED
; }
376 nsIncrementalDownload::Resume() { return NS_ERROR_NOT_IMPLEMENTED
; }
379 nsIncrementalDownload::GetLoadFlags(nsLoadFlags
* loadFlags
) {
380 *loadFlags
= mLoadFlags
;
385 nsIncrementalDownload::SetLoadFlags(nsLoadFlags loadFlags
) {
386 mLoadFlags
= loadFlags
;
391 nsIncrementalDownload::GetTRRMode(nsIRequest::TRRMode
* aTRRMode
) {
392 return GetTRRModeImpl(aTRRMode
);
396 nsIncrementalDownload::SetTRRMode(nsIRequest::TRRMode aTRRMode
) {
397 return SetTRRModeImpl(aTRRMode
);
401 nsIncrementalDownload::GetLoadGroup(nsILoadGroup
** loadGroup
) {
402 return NS_ERROR_NOT_IMPLEMENTED
;
406 nsIncrementalDownload::SetLoadGroup(nsILoadGroup
* loadGroup
) {
407 return NS_ERROR_NOT_IMPLEMENTED
;
410 // nsIIncrementalDownload
413 nsIncrementalDownload::Init(nsIURI
* uri
, nsIFile
* dest
, int32_t chunkSize
,
415 // Keep it simple: only allow initialization once
416 NS_ENSURE_FALSE(mURI
, NS_ERROR_ALREADY_INITIALIZED
);
419 NS_ENSURE_ARG(mDest
);
424 if (chunkSize
> 0) mChunkSize
= chunkSize
;
425 if (interval
>= 0) mInterval
= interval
;
430 nsIncrementalDownload::GetURI(nsIURI
** result
) {
431 nsCOMPtr
<nsIURI
> uri
= mURI
;
437 nsIncrementalDownload::GetFinalURI(nsIURI
** result
) {
438 nsCOMPtr
<nsIURI
> uri
= mFinalURI
;
444 nsIncrementalDownload::GetDestination(nsIFile
** result
) {
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
);
456 nsIncrementalDownload::GetTotalSize(int64_t* result
) {
457 *result
= mTotalSize
;
462 nsIncrementalDownload::GetCurrentSize(int64_t* result
) {
463 *result
= mCurrentSize
;
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
;
484 if (NS_FAILED(rv
)) return rv
;
486 mObserver
= observer
;
487 mProgressSink
= do_QueryInterface(observer
); // ok if null
493 // nsIRequestObserver
496 nsIncrementalDownload::OnStartRequest(nsIRequest
* request
) {
499 nsCOMPtr
<nsIHttpChannel
> http
= do_QueryInterface(request
, &rv
);
500 if (NS_FAILED(rv
)) return rv
;
502 // Ensure that we are receiving a 206 response.
504 rv
= http
->GetResponseStatus(&code
);
505 if (NS_FAILED(rv
)) return rv
;
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.
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.
534 NS_WARNING("server response was unexpected");
535 return NS_ERROR_UNEXPECTED
;
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.
547 int64_t startByte
= 0;
548 bool confirmedOK
= false;
550 rv
= http
->GetResponseHeader("Content-Range"_ns
, buf
);
552 return rv
; // it isn't a useful 206 without a CONTENT-RANGE of some
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
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
571 if (mTotalSize
== int64_t(-1)) {
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
) {
588 NS_WARNING("unexpected content-range");
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
);
615 ("nsIncrementalDownload::OnStartRequest\n"
616 " empty validator\n"));
621 // OK, read the Content-Range header to determine the total size of this
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
) !=
633 return NS_ERROR_UNEXPECTED
;
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
);
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
;
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
;
681 if (NS_SUCCEEDED(mStatus
)) mStatus
= FlushChunk();
683 mChunk
= nullptr; // deletes memory
690 // Notify listener if we hit an error or finished
691 if (NS_FAILED(mStatus
) || mCurrentSize
== mTotalSize
) {
696 return StartTimer(mInterval
); // Do next chunk
702 nsIncrementalDownload::OnDataAvailable(nsIRequest
* request
,
703 nsIInputStream
* input
, uint64_t offset
,
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
;
716 if (mChunkLen
== mChunkSize
) {
718 if (NS_FAILED(rv
)) return rv
;
722 if (PR_Now() > mLastProgressUpdate
+ UPDATE_PROGRESS_INTERVAL
) {
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.
747 nsIncrementalDownload::TimerCallback::TimerCallback(
748 nsIncrementalDownload
* aIncrementalDownload
)
749 : mIncrementalDownload(aIncrementalDownload
) {}
751 NS_IMPL_ISUPPORTS(nsIncrementalDownload::TimerCallback
, nsITimerCallback
,
755 nsIncrementalDownload::TimerCallback::Notify(nsITimer
* aTimer
) {
756 mIncrementalDownload
->mTimer
= nullptr;
758 nsresult rv
= mIncrementalDownload
->ProcessTimeout();
759 if (NS_FAILED(rv
)) mIncrementalDownload
->Cancel(rv
);
767 nsIncrementalDownload::TimerCallback::GetName(nsACString
& aName
) {
768 aName
.AssignLiteral("nsIncrementalDownload");
772 // nsIInterfaceRequestor
775 nsIncrementalDownload::GetInterface(const nsIID
& iid
, void** result
) {
776 if (iid
.Equals(NS_GET_IID(nsIChannelEventSink
))) {
778 *result
= static_cast<nsIChannelEventSink
*>(this);
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
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();
828 rv
= newHttpChannel
->SetRequestHeader("Cache-Control"_ns
, "no-cache"_ns
,
832 ("nsIncrementalDownload::AsyncOnChannelRedirect\n"
833 " failed to set request header: Cache-Control\n"));
835 rv
= newHttpChannel
->SetRequestHeader("Pragma"_ns
, "no-cache"_ns
, false);
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
);
850 rv
= sink
->AsyncOnChannelRedirect(oldChannel
, newChannel
, flags
, this);
852 mRedirectCallback
= nullptr;
853 mNewRedirectChannel
= nullptr;
857 (void)OnRedirectVerifyCallback(NS_OK
);
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;
875 extern nsresult
net_NewIncrementalDownload(const nsIID
& iid
, void** result
) {
876 RefPtr
<nsIncrementalDownload
> d
= new nsIncrementalDownload();
877 return d
->QueryInterface(iid
, result
);