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/TaskQueue.h"
9 #include "mozilla/UniquePtrExtensions.h"
10 #include "mozilla/UniquePtr.h"
12 #include "nsIIncrementalDownload.h"
13 #include "nsIRequestObserver.h"
14 #include "nsIProgressEventSink.h"
15 #include "nsIChannelEventSink.h"
16 #include "nsIAsyncVerifyRedirectCallback.h"
17 #include "nsIInterfaceRequestor.h"
18 #include "nsIObserverService.h"
19 #include "nsIObserver.h"
20 #include "nsIStreamListener.h"
21 #include "nsIThreadRetargetableRequest.h"
22 #include "nsIThreadRetargetableStreamListener.h"
24 #include "nsIHttpChannel.h"
25 #include "nsIOService.h"
28 #include "nsIInputStream.h"
29 #include "nsNetUtil.h"
30 #include "nsWeakReference.h"
34 #include "nsIContentPolicy.h"
35 #include "nsContentUtils.h"
36 #include "mozilla/Logging.h"
37 #include "mozilla/UniquePtr.h"
39 // Default values used to initialize a nsIncrementalDownload object.
40 #define DEFAULT_CHUNK_SIZE (4096 * 16) // bytes
41 #define DEFAULT_INTERVAL 60 // seconds
43 #define UPDATE_PROGRESS_INTERVAL PRTime(100 * PR_USEC_PER_MSEC) // 100ms
45 // Number of times to retry a failed byte-range request.
46 #define MAX_RETRY_COUNT 20
48 using namespace mozilla
;
49 using namespace mozilla::net
;
51 static LazyLogModule
gIDLog("IncrementalDownload");
53 #define LOG(args) MOZ_LOG(gIDLog, mozilla::LogLevel::Debug, args)
55 //-----------------------------------------------------------------------------
57 static nsresult
WriteToFile(nsIFile
* lf
, const char* data
, uint32_t len
,
62 rv
= lf
->OpenNSPRFileDesc(flags
, mode
, &fd
);
63 if (NS_FAILED(rv
)) return rv
;
66 rv
= PR_Write(fd
, data
, len
) == int32_t(len
) ? NS_OK
: NS_ERROR_FAILURE
;
73 static nsresult
AppendToFile(nsIFile
* lf
, const char* data
, uint32_t len
) {
74 int32_t flags
= PR_WRONLY
| PR_CREATE_FILE
| PR_APPEND
;
75 return WriteToFile(lf
, data
, len
, flags
);
78 // maxSize may be -1 if unknown
79 static void MakeRangeSpec(const int64_t& size
, const int64_t& maxSize
,
80 int32_t chunkSize
, bool fetchRemaining
,
81 nsCString
& rangeSpec
) {
82 rangeSpec
.AssignLiteral("bytes=");
83 rangeSpec
.AppendInt(int64_t(size
));
84 rangeSpec
.Append('-');
86 if (fetchRemaining
) return;
88 int64_t end
= size
+ int64_t(chunkSize
);
89 if (maxSize
!= int64_t(-1) && end
> maxSize
) end
= maxSize
;
92 rangeSpec
.AppendInt(int64_t(end
));
95 //-----------------------------------------------------------------------------
97 class nsIncrementalDownload final
: public nsIIncrementalDownload
,
98 public nsIThreadRetargetableStreamListener
,
100 public nsIInterfaceRequestor
,
101 public nsIChannelEventSink
,
102 public nsSupportsWeakReference
,
103 public nsIAsyncVerifyRedirectCallback
{
105 NS_DECL_THREADSAFE_ISUPPORTS
106 NS_DECL_NSISTREAMLISTENER
107 NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
109 NS_DECL_NSIINCREMENTALDOWNLOAD
110 NS_DECL_NSIREQUESTOBSERVER
112 NS_DECL_NSIINTERFACEREQUESTOR
113 NS_DECL_NSICHANNELEVENTSINK
114 NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
116 nsIncrementalDownload() = default;
119 ~nsIncrementalDownload() = default;
120 nsresult
FlushChunk();
121 void UpdateProgress();
122 nsresult
CallOnStartRequest();
123 void CallOnStopRequest();
124 nsresult
StartTimer(int32_t interval
);
125 nsresult
ProcessTimeout();
126 nsresult
ReadCurrentSize();
127 nsresult
ClearRequestHeader(nsIHttpChannel
* channel
);
129 nsCOMPtr
<nsIRequestObserver
> mObserver
;
130 nsCOMPtr
<nsIProgressEventSink
> mProgressSink
;
131 nsCOMPtr
<nsIURI
> mURI
;
132 nsCOMPtr
<nsIURI
> mFinalURI
;
133 nsCOMPtr
<nsIFile
> mDest
;
134 nsCOMPtr
<nsIChannel
> mChannel
;
135 nsCOMPtr
<nsITimer
> mTimer
;
136 mozilla::UniquePtr
<char[]> mChunk
;
137 int32_t mChunkLen
{0};
138 int32_t mChunkSize
{DEFAULT_CHUNK_SIZE
};
139 int32_t mInterval
{DEFAULT_INTERVAL
};
140 int64_t mTotalSize
{-1};
141 int64_t mCurrentSize
{-1};
142 uint32_t mLoadFlags
{LOAD_NORMAL
};
143 int32_t mNonPartialCount
{0};
144 nsresult mStatus
{NS_OK
};
145 bool mIsPending
{false};
146 bool mDidOnStartRequest
{false};
147 PRTime mLastProgressUpdate
{0};
148 nsCOMPtr
<nsIAsyncVerifyRedirectCallback
> mRedirectCallback
;
149 nsCOMPtr
<nsIChannel
> mNewRedirectChannel
;
150 nsCString mPartialValidator
;
151 bool mCacheBust
{false};
153 // nsITimerCallback is implemented on a subclass so that the name attribute
154 // doesn't conflict with the name attribute of the nsIRequest interface.
155 class TimerCallback final
: public nsITimerCallback
, public nsINamed
{
158 NS_DECL_NSITIMERCALLBACK
161 explicit TimerCallback(nsIncrementalDownload
* aIncrementalDownload
);
164 ~TimerCallback() = default;
166 RefPtr
<nsIncrementalDownload
> mIncrementalDownload
;
170 nsresult
nsIncrementalDownload::FlushChunk() {
171 NS_ASSERTION(mTotalSize
!= int64_t(-1), "total size should be known");
173 if (mChunkLen
== 0) return NS_OK
;
175 nsresult rv
= AppendToFile(mDest
, mChunk
.get(), mChunkLen
);
176 if (NS_FAILED(rv
)) return rv
;
178 mCurrentSize
+= int64_t(mChunkLen
);
184 void nsIncrementalDownload::UpdateProgress() {
185 mLastProgressUpdate
= PR_Now();
188 mProgressSink
->OnProgress(this, mCurrentSize
+ mChunkLen
, mTotalSize
);
192 nsresult
nsIncrementalDownload::CallOnStartRequest() {
193 if (!mObserver
|| mDidOnStartRequest
) return NS_OK
;
195 mDidOnStartRequest
= true;
196 return mObserver
->OnStartRequest(this);
199 void nsIncrementalDownload::CallOnStopRequest() {
200 if (!mObserver
) return;
202 // Ensure that OnStartRequest is always called once before OnStopRequest.
203 nsresult rv
= CallOnStartRequest();
204 if (NS_SUCCEEDED(mStatus
)) mStatus
= rv
;
208 mObserver
->OnStopRequest(this, mStatus
);
212 nsresult
nsIncrementalDownload::StartTimer(int32_t interval
) {
213 auto callback
= MakeRefPtr
<TimerCallback
>(this);
214 return NS_NewTimerWithCallback(getter_AddRefs(mTimer
), callback
,
215 interval
* 1000, nsITimer::TYPE_ONE_SHOT
);
218 nsresult
nsIncrementalDownload::ProcessTimeout() {
219 NS_ASSERTION(!mChannel
, "how can we have a channel?");
221 // Handle existing error conditions
222 if (NS_FAILED(mStatus
)) {
229 nsCOMPtr
<nsIChannel
> channel
;
230 nsresult rv
= NS_NewChannel(
231 getter_AddRefs(channel
), mFinalURI
, nsContentUtils::GetSystemPrincipal(),
232 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL
,
233 nsIContentPolicy::TYPE_OTHER
,
234 nullptr, // nsICookieJarSettings
235 nullptr, // PerformanceStorage
236 nullptr, // loadGroup
240 if (NS_FAILED(rv
)) return rv
;
242 nsCOMPtr
<nsIHttpChannel
> http
= do_QueryInterface(channel
, &rv
);
243 if (NS_FAILED(rv
)) return rv
;
245 NS_ASSERTION(mCurrentSize
!= int64_t(-1),
246 "we should know the current file size by now");
248 rv
= ClearRequestHeader(http
);
249 if (NS_FAILED(rv
)) return rv
;
251 // Don't bother making a range request if we are just going to fetch the
253 if (mInterval
|| mCurrentSize
!= int64_t(0)) {
255 MakeRangeSpec(mCurrentSize
, mTotalSize
, mChunkSize
, mInterval
== 0, range
);
257 rv
= http
->SetRequestHeader("Range"_ns
, range
, false);
258 if (NS_FAILED(rv
)) return rv
;
260 if (!mPartialValidator
.IsEmpty()) {
261 rv
= http
->SetRequestHeader("If-Range"_ns
, mPartialValidator
, false);
264 ("nsIncrementalDownload::ProcessTimeout\n"
265 " failed to set request header: If-Range\n"));
270 rv
= http
->SetRequestHeader("Cache-Control"_ns
, "no-cache"_ns
, false);
273 ("nsIncrementalDownload::ProcessTimeout\n"
274 " failed to set request header: If-Range\n"));
276 rv
= http
->SetRequestHeader("Pragma"_ns
, "no-cache"_ns
, false);
279 ("nsIncrementalDownload::ProcessTimeout\n"
280 " failed to set request header: If-Range\n"));
285 rv
= channel
->AsyncOpen(this);
286 if (NS_FAILED(rv
)) return rv
;
288 // Wait to assign mChannel when we know we are going to succeed. This is
289 // important because we don't want to introduce a reference cycle between
290 // mChannel and this until we know for a fact that AsyncOpen has succeeded,
291 // thus ensuring that our stream listener methods will be invoked.
296 // Reads the current file size and validates it.
297 nsresult
nsIncrementalDownload::ReadCurrentSize() {
299 nsresult rv
= mDest
->GetFileSize((int64_t*)&size
);
300 if (rv
== NS_ERROR_FILE_NOT_FOUND
) {
304 if (NS_FAILED(rv
)) return rv
;
311 NS_IMPL_ISUPPORTS(nsIncrementalDownload
, nsIIncrementalDownload
, nsIRequest
,
312 nsIStreamListener
, nsIThreadRetargetableStreamListener
,
313 nsIRequestObserver
, nsIObserver
, nsIInterfaceRequestor
,
314 nsIChannelEventSink
, nsISupportsWeakReference
,
315 nsIAsyncVerifyRedirectCallback
)
320 nsIncrementalDownload::GetName(nsACString
& name
) {
321 NS_ENSURE_TRUE(mURI
, NS_ERROR_NOT_INITIALIZED
);
323 return mURI
->GetSpec(name
);
327 nsIncrementalDownload::IsPending(bool* isPending
) {
328 *isPending
= mIsPending
;
333 nsIncrementalDownload::GetStatus(nsresult
* status
) {
338 NS_IMETHODIMP
nsIncrementalDownload::SetCanceledReason(
339 const nsACString
& aReason
) {
340 return SetCanceledReasonImpl(aReason
);
343 NS_IMETHODIMP
nsIncrementalDownload::GetCanceledReason(nsACString
& aReason
) {
344 return GetCanceledReasonImpl(aReason
);
347 NS_IMETHODIMP
nsIncrementalDownload::CancelWithReason(
348 nsresult aStatus
, const nsACString
& aReason
) {
349 return CancelWithReasonImpl(aStatus
, aReason
);
353 nsIncrementalDownload::Cancel(nsresult status
) {
354 NS_ENSURE_ARG(NS_FAILED(status
));
356 // Ignore this cancelation if we're already canceled.
357 if (NS_FAILED(mStatus
)) return NS_OK
;
361 // Nothing more to do if callbacks aren't pending.
362 if (!mIsPending
) return NS_OK
;
365 mChannel
->Cancel(mStatus
);
366 NS_ASSERTION(!mTimer
, "what is this timer object doing here?");
368 // dispatch a timer callback event to drive invoking our listener's
370 if (mTimer
) mTimer
->Cancel();
378 nsIncrementalDownload::Suspend() { return NS_ERROR_NOT_IMPLEMENTED
; }
381 nsIncrementalDownload::Resume() { return NS_ERROR_NOT_IMPLEMENTED
; }
384 nsIncrementalDownload::GetLoadFlags(nsLoadFlags
* loadFlags
) {
385 *loadFlags
= mLoadFlags
;
390 nsIncrementalDownload::SetLoadFlags(nsLoadFlags loadFlags
) {
391 mLoadFlags
= loadFlags
;
396 nsIncrementalDownload::GetTRRMode(nsIRequest::TRRMode
* aTRRMode
) {
397 return GetTRRModeImpl(aTRRMode
);
401 nsIncrementalDownload::SetTRRMode(nsIRequest::TRRMode aTRRMode
) {
402 return SetTRRModeImpl(aTRRMode
);
406 nsIncrementalDownload::GetLoadGroup(nsILoadGroup
** loadGroup
) {
407 return NS_ERROR_NOT_IMPLEMENTED
;
411 nsIncrementalDownload::SetLoadGroup(nsILoadGroup
* loadGroup
) {
412 return NS_ERROR_NOT_IMPLEMENTED
;
415 // nsIIncrementalDownload
418 nsIncrementalDownload::Init(nsIURI
* uri
, nsIFile
* dest
, int32_t chunkSize
,
420 // Keep it simple: only allow initialization once
421 NS_ENSURE_FALSE(mURI
, NS_ERROR_ALREADY_INITIALIZED
);
424 NS_ENSURE_ARG(mDest
);
429 if (chunkSize
> 0) mChunkSize
= chunkSize
;
430 if (interval
>= 0) mInterval
= interval
;
435 nsIncrementalDownload::GetURI(nsIURI
** result
) {
436 nsCOMPtr
<nsIURI
> uri
= mURI
;
442 nsIncrementalDownload::GetFinalURI(nsIURI
** result
) {
443 nsCOMPtr
<nsIURI
> uri
= mFinalURI
;
449 nsIncrementalDownload::GetDestination(nsIFile
** result
) {
454 // Return a clone of mDest so that callers may modify the resulting nsIFile
455 // without corrupting our internal object. This also works around the fact
456 // that some nsIFile impls may cache the result of stat'ing the filesystem.
457 return mDest
->Clone(result
);
461 nsIncrementalDownload::GetTotalSize(int64_t* result
) {
462 *result
= mTotalSize
;
467 nsIncrementalDownload::GetCurrentSize(int64_t* result
) {
468 *result
= mCurrentSize
;
473 nsIncrementalDownload::Start(nsIRequestObserver
* observer
,
474 nsISupports
* context
) {
475 NS_ENSURE_ARG(observer
);
476 NS_ENSURE_FALSE(mIsPending
, NS_ERROR_IN_PROGRESS
);
478 // Observe system shutdown so we can be sure to release any reference held
479 // between ourselves and the timer. We have the observer service hold a weak
480 // reference to us, so that we don't have to worry about calling
481 // RemoveObserver. XXX(darin): The timer code should do this for us.
482 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
483 if (obs
) obs
->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID
, true);
485 nsresult rv
= ReadCurrentSize();
486 if (NS_FAILED(rv
)) return rv
;
489 if (NS_FAILED(rv
)) return rv
;
491 mObserver
= observer
;
492 mProgressSink
= do_QueryInterface(observer
); // ok if null
498 // nsIRequestObserver
501 nsIncrementalDownload::OnStartRequest(nsIRequest
* aRequest
) {
504 nsCOMPtr
<nsIHttpChannel
> http
= do_QueryInterface(aRequest
, &rv
);
505 if (NS_FAILED(rv
)) return rv
;
507 // Ensure that we are receiving a 206 response.
509 rv
= http
->GetResponseStatus(&code
);
510 if (NS_FAILED(rv
)) return rv
;
512 // We may already have the entire file downloaded, in which case
513 // our request for a range beyond the end of the file would have
514 // been met with an error response code.
515 if (code
== 416 && mTotalSize
== int64_t(-1)) {
516 mTotalSize
= mCurrentSize
;
517 // Return an error code here to suppress OnDataAvailable.
518 return NS_ERROR_DOWNLOAD_COMPLETE
;
520 // The server may have decided to give us all of the data in one chunk. If
521 // we requested a partial range, then we don't want to download all of the
522 // data at once. So, we'll just try again, but if this keeps happening then
523 // we'll eventually give up.
527 if (++mNonPartialCount
> MAX_RETRY_COUNT
) {
528 NS_WARNING("unable to fetch a byte range; giving up");
529 return NS_ERROR_FAILURE
;
531 // Increase delay with each failure.
532 StartTimer(mInterval
* mNonPartialCount
);
533 return NS_ERROR_DOWNLOAD_NOT_PARTIAL
;
535 // Since we have been asked to download the rest of the file, we can deal
536 // with a 200 response. This may result in downloading the beginning of
537 // the file again, but that can't really be helped.
539 NS_WARNING("server response was unexpected");
540 return NS_ERROR_UNEXPECTED
;
543 // We got a partial response, so clear this counter in case the next chunk
544 // results in a 200 response.
545 mNonPartialCount
= 0;
547 // confirm that the content-range response header is consistent with
548 // expectations on each 206. If it is not then drop this response and
549 // retry with no-cache set.
552 int64_t startByte
= 0;
553 bool confirmedOK
= false;
555 rv
= http
->GetResponseHeader("Content-Range"_ns
, buf
);
557 return rv
; // it isn't a useful 206 without a CONTENT-RANGE of some
561 // Content-Range: bytes 0-299999/25604694
562 int32_t p
= buf
.Find("bytes ");
564 // first look for the starting point of the content-range
565 // to make sure it is what we expect
567 char* endptr
= nullptr;
568 const char* s
= buf
.get() + p
+ 6;
569 while (*s
&& *s
== ' ') s
++;
570 startByte
= strtol(s
, &endptr
, 10);
572 if (*s
&& endptr
&& (endptr
!= s
) && (mCurrentSize
== startByte
)) {
573 // ok the starting point is confirmed. We still need to check the
574 // total size of the range for consistency if this isn't
576 if (mTotalSize
== int64_t(-1)) {
580 int32_t slash
= buf
.FindChar('/');
581 int64_t rangeSize
= 0;
582 if (slash
!= kNotFound
&&
583 (PR_sscanf(buf
.get() + slash
+ 1, "%lld",
584 (int64_t*)&rangeSize
) == 1) &&
585 rangeSize
== mTotalSize
) {
593 NS_WARNING("unexpected content-range");
596 if (++mNonPartialCount
> MAX_RETRY_COUNT
) {
597 NS_WARNING("unable to fetch a byte range; giving up");
598 return NS_ERROR_FAILURE
;
600 // Increase delay with each failure.
601 StartTimer(mInterval
* mNonPartialCount
);
602 return NS_ERROR_DOWNLOAD_NOT_PARTIAL
;
607 // Do special processing after the first response.
608 if (mTotalSize
== int64_t(-1)) {
609 // Update knowledge of mFinalURI
610 rv
= http
->GetURI(getter_AddRefs(mFinalURI
));
611 if (NS_FAILED(rv
)) return rv
;
612 Unused
<< http
->GetResponseHeader("Etag"_ns
, mPartialValidator
);
613 if (StringBeginsWith(mPartialValidator
, "W/"_ns
)) {
614 mPartialValidator
.Truncate(); // don't use weak validators
616 if (mPartialValidator
.IsEmpty()) {
617 rv
= http
->GetResponseHeader("Last-Modified"_ns
, mPartialValidator
);
620 ("nsIncrementalDownload::OnStartRequest\n"
621 " empty validator\n"));
626 // OK, read the Content-Range header to determine the total size of this
629 rv
= http
->GetResponseHeader("Content-Range"_ns
, buf
);
630 if (NS_FAILED(rv
)) return rv
;
631 int32_t slash
= buf
.FindChar('/');
632 if (slash
== kNotFound
) {
633 NS_WARNING("server returned invalid Content-Range header!");
634 return NS_ERROR_UNEXPECTED
;
636 if (PR_sscanf(buf
.get() + slash
+ 1, "%lld", (int64_t*)&mTotalSize
) !=
638 return NS_ERROR_UNEXPECTED
;
641 rv
= http
->GetContentLength(&mTotalSize
);
642 if (NS_FAILED(rv
)) return rv
;
643 // We need to know the total size of the thing we're trying to download.
644 if (mTotalSize
== int64_t(-1)) {
645 NS_WARNING("server returned no content-length header!");
646 return NS_ERROR_UNEXPECTED
;
648 // Need to truncate (or create, if it doesn't exist) the file since we
649 // are downloading the whole thing.
650 WriteToFile(mDest
, nullptr, 0, PR_WRONLY
| PR_CREATE_FILE
| PR_TRUNCATE
);
654 // Notify observer that we are starting...
655 rv
= CallOnStartRequest();
656 if (NS_FAILED(rv
)) return rv
;
659 // Adjust mChunkSize accordingly if mCurrentSize is close to mTotalSize.
660 int64_t diff
= mTotalSize
- mCurrentSize
;
661 if (diff
<= int64_t(0)) {
662 NS_WARNING("about to set a bogus chunk size; giving up");
663 return NS_ERROR_UNEXPECTED
;
666 if (diff
< int64_t(mChunkSize
)) mChunkSize
= uint32_t(diff
);
668 mChunk
= mozilla::MakeUniqueFallible
<char[]>(mChunkSize
);
669 if (!mChunk
) rv
= NS_ERROR_OUT_OF_MEMORY
;
671 if (nsIOService::UseSocketProcess() || NS_FAILED(rv
)) {
675 if (nsCOMPtr
<nsIThreadRetargetableRequest
> rr
= do_QueryInterface(aRequest
)) {
676 nsCOMPtr
<nsIEventTarget
> sts
=
677 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID
);
679 TaskQueue::Create(sts
.forget(), "nsIncrementalDownload Delivery Queue");
681 ("nsIncrementalDownload::OnStartRequest\n"
682 " Retarget to stream transport service\n"));
683 rr
->RetargetDeliveryTo(queue
);
690 nsIncrementalDownload::CheckListenerChain() { return NS_OK
; }
693 nsIncrementalDownload::OnStopRequest(nsIRequest
* request
, nsresult status
) {
694 // Not a real error; just a trick to kill off the channel without our
695 // listener having to care.
696 if (status
== NS_ERROR_DOWNLOAD_NOT_PARTIAL
) return NS_OK
;
698 // Not a real error; just a trick used to suppress OnDataAvailable calls.
699 if (status
== NS_ERROR_DOWNLOAD_COMPLETE
) status
= NS_OK
;
701 if (NS_SUCCEEDED(mStatus
)) mStatus
= status
;
704 if (NS_SUCCEEDED(mStatus
)) mStatus
= FlushChunk();
706 mChunk
= nullptr; // deletes memory
713 // Notify listener if we hit an error or finished
714 if (NS_FAILED(mStatus
) || mCurrentSize
== mTotalSize
) {
719 return StartTimer(mInterval
); // Do next chunk
724 nsIncrementalDownload::OnDataAvailable(nsIRequest
* request
,
725 nsIInputStream
* input
, uint64_t offset
,
728 uint32_t space
= mChunkSize
- mChunkLen
;
729 uint32_t n
, len
= std::min(space
, count
);
731 nsresult rv
= input
->Read(&mChunk
[mChunkLen
], len
, &n
);
732 if (NS_FAILED(rv
)) return rv
;
733 if (n
!= len
) return NS_ERROR_UNEXPECTED
;
738 if (mChunkLen
== mChunkSize
) {
740 if (NS_FAILED(rv
)) return rv
;
744 if (PR_Now() > mLastProgressUpdate
+ UPDATE_PROGRESS_INTERVAL
) {
745 if (NS_IsMainThread()) {
748 NS_DispatchToMainThread(
749 NewRunnableMethod("nsIncrementalDownload::UpdateProgress", this,
750 &nsIncrementalDownload::UpdateProgress
));
757 nsIncrementalDownload::OnDataFinished(nsresult aStatus
) { return NS_OK
; }
762 nsIncrementalDownload::Observe(nsISupports
* subject
, const char* topic
,
763 const char16_t
* data
) {
764 if (strcmp(topic
, NS_XPCOM_SHUTDOWN_OBSERVER_ID
) == 0) {
765 Cancel(NS_ERROR_ABORT
);
767 // Since the app is shutting down, we need to go ahead and notify our
768 // observer here. Otherwise, we would notify them after XPCOM has been
769 // shutdown or not at all.
777 nsIncrementalDownload::TimerCallback::TimerCallback(
778 nsIncrementalDownload
* aIncrementalDownload
)
779 : mIncrementalDownload(aIncrementalDownload
) {}
781 NS_IMPL_ISUPPORTS(nsIncrementalDownload::TimerCallback
, nsITimerCallback
,
785 nsIncrementalDownload::TimerCallback::Notify(nsITimer
* aTimer
) {
786 mIncrementalDownload
->mTimer
= nullptr;
788 nsresult rv
= mIncrementalDownload
->ProcessTimeout();
789 if (NS_FAILED(rv
)) mIncrementalDownload
->Cancel(rv
);
797 nsIncrementalDownload::TimerCallback::GetName(nsACString
& aName
) {
798 aName
.AssignLiteral("nsIncrementalDownload");
802 // nsIInterfaceRequestor
805 nsIncrementalDownload::GetInterface(const nsIID
& iid
, void** result
) {
806 if (iid
.Equals(NS_GET_IID(nsIChannelEventSink
))) {
808 *result
= static_cast<nsIChannelEventSink
*>(this);
812 nsCOMPtr
<nsIInterfaceRequestor
> ir
= do_QueryInterface(mObserver
);
813 if (ir
) return ir
->GetInterface(iid
, result
);
815 return NS_ERROR_NO_INTERFACE
;
818 nsresult
nsIncrementalDownload::ClearRequestHeader(nsIHttpChannel
* channel
) {
819 NS_ENSURE_ARG(channel
);
821 // We don't support encodings -- they make the Content-Length not equal
822 // to the actual size of the data.
823 return channel
->SetRequestHeader("Accept-Encoding"_ns
, ""_ns
, false);
826 // nsIChannelEventSink
829 nsIncrementalDownload::AsyncOnChannelRedirect(
830 nsIChannel
* oldChannel
, nsIChannel
* newChannel
, uint32_t flags
,
831 nsIAsyncVerifyRedirectCallback
* cb
) {
832 // In response to a redirect, we need to propagate the Range header. See bug
833 // 311595. Any failure code returned from this function aborts the redirect.
835 nsCOMPtr
<nsIHttpChannel
> http
= do_QueryInterface(oldChannel
);
836 NS_ENSURE_STATE(http
);
838 nsCOMPtr
<nsIHttpChannel
> newHttpChannel
= do_QueryInterface(newChannel
);
839 NS_ENSURE_STATE(newHttpChannel
);
841 constexpr auto rangeHdr
= "Range"_ns
;
843 nsresult rv
= ClearRequestHeader(newHttpChannel
);
844 if (NS_FAILED(rv
)) return rv
;
846 // If we didn't have a Range header, then we must be doing a full download.
847 nsAutoCString rangeVal
;
848 Unused
<< http
->GetRequestHeader(rangeHdr
, rangeVal
);
849 if (!rangeVal
.IsEmpty()) {
850 rv
= newHttpChannel
->SetRequestHeader(rangeHdr
, rangeVal
, false);
851 NS_ENSURE_SUCCESS(rv
, rv
);
854 // A redirection changes the validator
855 mPartialValidator
.Truncate();
858 rv
= newHttpChannel
->SetRequestHeader("Cache-Control"_ns
, "no-cache"_ns
,
862 ("nsIncrementalDownload::AsyncOnChannelRedirect\n"
863 " failed to set request header: Cache-Control\n"));
865 rv
= newHttpChannel
->SetRequestHeader("Pragma"_ns
, "no-cache"_ns
, false);
868 ("nsIncrementalDownload::AsyncOnChannelRedirect\n"
869 " failed to set request header: Pragma\n"));
873 // Prepare to receive callback
874 mRedirectCallback
= cb
;
875 mNewRedirectChannel
= newChannel
;
877 // Give the observer a chance to see this redirect notification.
878 nsCOMPtr
<nsIChannelEventSink
> sink
= do_GetInterface(mObserver
);
880 rv
= sink
->AsyncOnChannelRedirect(oldChannel
, newChannel
, flags
, this);
882 mRedirectCallback
= nullptr;
883 mNewRedirectChannel
= nullptr;
887 (void)OnRedirectVerifyCallback(NS_OK
);
892 nsIncrementalDownload::OnRedirectVerifyCallback(nsresult result
) {
893 NS_ASSERTION(mRedirectCallback
, "mRedirectCallback not set in callback");
894 NS_ASSERTION(mNewRedirectChannel
, "mNewRedirectChannel not set in callback");
896 // Update mChannel, so we can Cancel the new channel.
897 if (NS_SUCCEEDED(result
)) mChannel
= mNewRedirectChannel
;
899 mRedirectCallback
->OnRedirectVerifyCallback(result
);
900 mRedirectCallback
= nullptr;
901 mNewRedirectChannel
= nullptr;
905 extern nsresult
net_NewIncrementalDownload(const nsIID
& iid
, void** result
) {
906 RefPtr
<nsIncrementalDownload
> d
= new nsIncrementalDownload();
907 return d
->QueryInterface(iid
, result
);