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};
152 nsCString mExtraHeaders
;
154 // nsITimerCallback is implemented on a subclass so that the name attribute
155 // doesn't conflict with the name attribute of the nsIRequest interface.
156 class TimerCallback final
: public nsITimerCallback
, public nsINamed
{
159 NS_DECL_NSITIMERCALLBACK
162 explicit TimerCallback(nsIncrementalDownload
* aIncrementalDownload
);
165 ~TimerCallback() = default;
167 RefPtr
<nsIncrementalDownload
> mIncrementalDownload
;
171 nsresult
nsIncrementalDownload::FlushChunk() {
172 NS_ASSERTION(mTotalSize
!= int64_t(-1), "total size should be known");
174 if (mChunkLen
== 0) return NS_OK
;
176 nsresult rv
= AppendToFile(mDest
, mChunk
.get(), mChunkLen
);
177 if (NS_FAILED(rv
)) return rv
;
179 mCurrentSize
+= int64_t(mChunkLen
);
185 void nsIncrementalDownload::UpdateProgress() {
186 mLastProgressUpdate
= PR_Now();
189 mProgressSink
->OnProgress(this, mCurrentSize
+ mChunkLen
, mTotalSize
);
193 nsresult
nsIncrementalDownload::CallOnStartRequest() {
194 if (!mObserver
|| mDidOnStartRequest
) return NS_OK
;
196 mDidOnStartRequest
= true;
197 return mObserver
->OnStartRequest(this);
200 void nsIncrementalDownload::CallOnStopRequest() {
201 if (!mObserver
) return;
203 // Ensure that OnStartRequest is always called once before OnStopRequest.
204 nsresult rv
= CallOnStartRequest();
205 if (NS_SUCCEEDED(mStatus
)) mStatus
= rv
;
209 mObserver
->OnStopRequest(this, mStatus
);
213 nsresult
nsIncrementalDownload::StartTimer(int32_t interval
) {
214 auto callback
= MakeRefPtr
<TimerCallback
>(this);
215 return NS_NewTimerWithCallback(getter_AddRefs(mTimer
), callback
,
216 interval
* 1000, nsITimer::TYPE_ONE_SHOT
);
219 nsresult
nsIncrementalDownload::ProcessTimeout() {
220 NS_ASSERTION(!mChannel
, "how can we have a channel?");
222 // Handle existing error conditions
223 if (NS_FAILED(mStatus
)) {
230 nsCOMPtr
<nsIChannel
> channel
;
231 nsresult rv
= NS_NewChannel(
232 getter_AddRefs(channel
), mFinalURI
, nsContentUtils::GetSystemPrincipal(),
233 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL
,
234 nsIContentPolicy::TYPE_OTHER
,
235 nullptr, // nsICookieJarSettings
236 nullptr, // PerformanceStorage
237 nullptr, // loadGroup
241 if (NS_FAILED(rv
)) return rv
;
243 nsCOMPtr
<nsIHttpChannel
> http
= do_QueryInterface(channel
, &rv
);
244 if (NS_FAILED(rv
)) return rv
;
246 NS_ASSERTION(mCurrentSize
!= int64_t(-1),
247 "we should know the current file size by now");
249 rv
= ClearRequestHeader(http
);
250 if (NS_FAILED(rv
)) return rv
;
252 if (!mExtraHeaders
.IsEmpty()) {
253 rv
= AddExtraHeaders(http
, mExtraHeaders
);
254 if (NS_FAILED(rv
)) return rv
;
257 // Don't bother making a range request if we are just going to fetch the
259 if (mInterval
|| mCurrentSize
!= int64_t(0)) {
261 MakeRangeSpec(mCurrentSize
, mTotalSize
, mChunkSize
, mInterval
== 0, range
);
263 rv
= http
->SetRequestHeader("Range"_ns
, range
, false);
264 if (NS_FAILED(rv
)) return rv
;
266 if (!mPartialValidator
.IsEmpty()) {
267 rv
= http
->SetRequestHeader("If-Range"_ns
, mPartialValidator
, false);
270 ("nsIncrementalDownload::ProcessTimeout\n"
271 " failed to set request header: If-Range\n"));
276 rv
= http
->SetRequestHeader("Cache-Control"_ns
, "no-cache"_ns
, false);
279 ("nsIncrementalDownload::ProcessTimeout\n"
280 " failed to set request header: If-Range\n"));
282 rv
= http
->SetRequestHeader("Pragma"_ns
, "no-cache"_ns
, false);
285 ("nsIncrementalDownload::ProcessTimeout\n"
286 " failed to set request header: If-Range\n"));
291 rv
= channel
->AsyncOpen(this);
292 if (NS_FAILED(rv
)) return rv
;
294 // Wait to assign mChannel when we know we are going to succeed. This is
295 // important because we don't want to introduce a reference cycle between
296 // mChannel and this until we know for a fact that AsyncOpen has succeeded,
297 // thus ensuring that our stream listener methods will be invoked.
302 // Reads the current file size and validates it.
303 nsresult
nsIncrementalDownload::ReadCurrentSize() {
305 nsresult rv
= mDest
->GetFileSize((int64_t*)&size
);
306 if (rv
== NS_ERROR_FILE_NOT_FOUND
) {
310 if (NS_FAILED(rv
)) return rv
;
317 NS_IMPL_ISUPPORTS(nsIncrementalDownload
, nsIIncrementalDownload
, nsIRequest
,
318 nsIStreamListener
, nsIThreadRetargetableStreamListener
,
319 nsIRequestObserver
, nsIObserver
, nsIInterfaceRequestor
,
320 nsIChannelEventSink
, nsISupportsWeakReference
,
321 nsIAsyncVerifyRedirectCallback
)
326 nsIncrementalDownload::GetName(nsACString
& name
) {
327 NS_ENSURE_TRUE(mURI
, NS_ERROR_NOT_INITIALIZED
);
329 return mURI
->GetSpec(name
);
333 nsIncrementalDownload::IsPending(bool* isPending
) {
334 *isPending
= mIsPending
;
339 nsIncrementalDownload::GetStatus(nsresult
* status
) {
344 NS_IMETHODIMP
nsIncrementalDownload::SetCanceledReason(
345 const nsACString
& aReason
) {
346 return SetCanceledReasonImpl(aReason
);
349 NS_IMETHODIMP
nsIncrementalDownload::GetCanceledReason(nsACString
& aReason
) {
350 return GetCanceledReasonImpl(aReason
);
353 NS_IMETHODIMP
nsIncrementalDownload::CancelWithReason(
354 nsresult aStatus
, const nsACString
& aReason
) {
355 return CancelWithReasonImpl(aStatus
, aReason
);
359 nsIncrementalDownload::Cancel(nsresult status
) {
360 NS_ENSURE_ARG(NS_FAILED(status
));
362 // Ignore this cancelation if we're already canceled.
363 if (NS_FAILED(mStatus
)) return NS_OK
;
367 // Nothing more to do if callbacks aren't pending.
368 if (!mIsPending
) return NS_OK
;
371 mChannel
->Cancel(mStatus
);
372 NS_ASSERTION(!mTimer
, "what is this timer object doing here?");
374 // dispatch a timer callback event to drive invoking our listener's
376 if (mTimer
) mTimer
->Cancel();
384 nsIncrementalDownload::Suspend() { return NS_ERROR_NOT_IMPLEMENTED
; }
387 nsIncrementalDownload::Resume() { return NS_ERROR_NOT_IMPLEMENTED
; }
390 nsIncrementalDownload::GetLoadFlags(nsLoadFlags
* loadFlags
) {
391 *loadFlags
= mLoadFlags
;
396 nsIncrementalDownload::SetLoadFlags(nsLoadFlags loadFlags
) {
397 mLoadFlags
= loadFlags
;
402 nsIncrementalDownload::GetTRRMode(nsIRequest::TRRMode
* aTRRMode
) {
403 return GetTRRModeImpl(aTRRMode
);
407 nsIncrementalDownload::SetTRRMode(nsIRequest::TRRMode aTRRMode
) {
408 return SetTRRModeImpl(aTRRMode
);
412 nsIncrementalDownload::GetLoadGroup(nsILoadGroup
** loadGroup
) {
413 return NS_ERROR_NOT_IMPLEMENTED
;
417 nsIncrementalDownload::SetLoadGroup(nsILoadGroup
* loadGroup
) {
418 return NS_ERROR_NOT_IMPLEMENTED
;
421 // nsIIncrementalDownload
424 nsIncrementalDownload::Init(nsIURI
* uri
, nsIFile
* dest
, int32_t chunkSize
,
425 int32_t interval
, const nsACString
& extraHeaders
) {
426 // Keep it simple: only allow initialization once
427 NS_ENSURE_FALSE(mURI
, NS_ERROR_ALREADY_INITIALIZED
);
430 NS_ENSURE_ARG(mDest
);
435 if (chunkSize
> 0) mChunkSize
= chunkSize
;
436 if (interval
>= 0) mInterval
= interval
;
438 mExtraHeaders
= extraHeaders
;
444 nsIncrementalDownload::GetURI(nsIURI
** result
) {
445 nsCOMPtr
<nsIURI
> uri
= mURI
;
451 nsIncrementalDownload::GetFinalURI(nsIURI
** result
) {
452 nsCOMPtr
<nsIURI
> uri
= mFinalURI
;
458 nsIncrementalDownload::GetDestination(nsIFile
** result
) {
463 // Return a clone of mDest so that callers may modify the resulting nsIFile
464 // without corrupting our internal object. This also works around the fact
465 // that some nsIFile impls may cache the result of stat'ing the filesystem.
466 return mDest
->Clone(result
);
470 nsIncrementalDownload::GetTotalSize(int64_t* result
) {
471 *result
= mTotalSize
;
476 nsIncrementalDownload::GetCurrentSize(int64_t* result
) {
477 *result
= mCurrentSize
;
482 nsIncrementalDownload::Start(nsIRequestObserver
* observer
,
483 nsISupports
* context
) {
484 NS_ENSURE_ARG(observer
);
485 NS_ENSURE_FALSE(mIsPending
, NS_ERROR_IN_PROGRESS
);
487 // Observe system shutdown so we can be sure to release any reference held
488 // between ourselves and the timer. We have the observer service hold a weak
489 // reference to us, so that we don't have to worry about calling
490 // RemoveObserver. XXX(darin): The timer code should do this for us.
491 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
492 if (obs
) obs
->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID
, true);
494 nsresult rv
= ReadCurrentSize();
495 if (NS_FAILED(rv
)) return rv
;
498 if (NS_FAILED(rv
)) return rv
;
500 mObserver
= observer
;
501 mProgressSink
= do_QueryInterface(observer
); // ok if null
507 // nsIRequestObserver
510 nsIncrementalDownload::OnStartRequest(nsIRequest
* aRequest
) {
513 nsCOMPtr
<nsIHttpChannel
> http
= do_QueryInterface(aRequest
, &rv
);
514 if (NS_FAILED(rv
)) return rv
;
516 // Ensure that we are receiving a 206 response.
518 rv
= http
->GetResponseStatus(&code
);
519 if (NS_FAILED(rv
)) return rv
;
521 // We may already have the entire file downloaded, in which case
522 // our request for a range beyond the end of the file would have
523 // been met with an error response code.
524 if (code
== 416 && mTotalSize
== int64_t(-1)) {
525 mTotalSize
= mCurrentSize
;
526 // Return an error code here to suppress OnDataAvailable.
527 return NS_ERROR_DOWNLOAD_COMPLETE
;
529 // The server may have decided to give us all of the data in one chunk. If
530 // we requested a partial range, then we don't want to download all of the
531 // data at once. So, we'll just try again, but if this keeps happening then
532 // we'll eventually give up.
536 if (++mNonPartialCount
> MAX_RETRY_COUNT
) {
537 NS_WARNING("unable to fetch a byte range; giving up");
538 return NS_ERROR_FAILURE
;
540 // Increase delay with each failure.
541 StartTimer(mInterval
* mNonPartialCount
);
542 return NS_ERROR_DOWNLOAD_NOT_PARTIAL
;
544 // Since we have been asked to download the rest of the file, we can deal
545 // with a 200 response. This may result in downloading the beginning of
546 // the file again, but that can't really be helped.
548 NS_WARNING("server response was unexpected");
549 return NS_ERROR_UNEXPECTED
;
552 // We got a partial response, so clear this counter in case the next chunk
553 // results in a 200 response.
554 mNonPartialCount
= 0;
556 // confirm that the content-range response header is consistent with
557 // expectations on each 206. If it is not then drop this response and
558 // retry with no-cache set.
561 int64_t startByte
= 0;
562 bool confirmedOK
= false;
564 rv
= http
->GetResponseHeader("Content-Range"_ns
, buf
);
566 return rv
; // it isn't a useful 206 without a CONTENT-RANGE of some
570 // Content-Range: bytes 0-299999/25604694
571 int32_t p
= buf
.Find("bytes ");
573 // first look for the starting point of the content-range
574 // to make sure it is what we expect
576 char* endptr
= nullptr;
577 const char* s
= buf
.get() + p
+ 6;
578 while (*s
&& *s
== ' ') s
++;
579 startByte
= strtol(s
, &endptr
, 10);
581 if (*s
&& endptr
&& (endptr
!= s
) && (mCurrentSize
== startByte
)) {
582 // ok the starting point is confirmed. We still need to check the
583 // total size of the range for consistency if this isn't
585 if (mTotalSize
== int64_t(-1)) {
589 int32_t slash
= buf
.FindChar('/');
590 int64_t rangeSize
= 0;
591 if (slash
!= kNotFound
&&
592 (PR_sscanf(buf
.get() + slash
+ 1, "%lld",
593 (int64_t*)&rangeSize
) == 1) &&
594 rangeSize
== mTotalSize
) {
602 NS_WARNING("unexpected content-range");
605 if (++mNonPartialCount
> MAX_RETRY_COUNT
) {
606 NS_WARNING("unable to fetch a byte range; giving up");
607 return NS_ERROR_FAILURE
;
609 // Increase delay with each failure.
610 StartTimer(mInterval
* mNonPartialCount
);
611 return NS_ERROR_DOWNLOAD_NOT_PARTIAL
;
616 // Do special processing after the first response.
617 if (mTotalSize
== int64_t(-1)) {
618 // Update knowledge of mFinalURI
619 rv
= http
->GetURI(getter_AddRefs(mFinalURI
));
620 if (NS_FAILED(rv
)) return rv
;
621 Unused
<< http
->GetResponseHeader("Etag"_ns
, mPartialValidator
);
622 if (StringBeginsWith(mPartialValidator
, "W/"_ns
)) {
623 mPartialValidator
.Truncate(); // don't use weak validators
625 if (mPartialValidator
.IsEmpty()) {
626 rv
= http
->GetResponseHeader("Last-Modified"_ns
, mPartialValidator
);
629 ("nsIncrementalDownload::OnStartRequest\n"
630 " empty validator\n"));
635 // OK, read the Content-Range header to determine the total size of this
638 rv
= http
->GetResponseHeader("Content-Range"_ns
, buf
);
639 if (NS_FAILED(rv
)) return rv
;
640 int32_t slash
= buf
.FindChar('/');
641 if (slash
== kNotFound
) {
642 NS_WARNING("server returned invalid Content-Range header!");
643 return NS_ERROR_UNEXPECTED
;
645 if (PR_sscanf(buf
.get() + slash
+ 1, "%lld", (int64_t*)&mTotalSize
) !=
647 return NS_ERROR_UNEXPECTED
;
650 rv
= http
->GetContentLength(&mTotalSize
);
651 if (NS_FAILED(rv
)) return rv
;
652 // We need to know the total size of the thing we're trying to download.
653 if (mTotalSize
== int64_t(-1)) {
654 NS_WARNING("server returned no content-length header!");
655 return NS_ERROR_UNEXPECTED
;
657 // Need to truncate (or create, if it doesn't exist) the file since we
658 // are downloading the whole thing.
659 WriteToFile(mDest
, nullptr, 0, PR_WRONLY
| PR_CREATE_FILE
| PR_TRUNCATE
);
663 // Notify observer that we are starting...
664 rv
= CallOnStartRequest();
665 if (NS_FAILED(rv
)) return rv
;
668 // Adjust mChunkSize accordingly if mCurrentSize is close to mTotalSize.
669 int64_t diff
= mTotalSize
- mCurrentSize
;
670 if (diff
<= int64_t(0)) {
671 NS_WARNING("about to set a bogus chunk size; giving up");
672 return NS_ERROR_UNEXPECTED
;
675 if (diff
< int64_t(mChunkSize
)) mChunkSize
= uint32_t(diff
);
677 mChunk
= mozilla::MakeUniqueFallible
<char[]>(mChunkSize
);
678 if (!mChunk
) rv
= NS_ERROR_OUT_OF_MEMORY
;
680 if (nsIOService::UseSocketProcess() || NS_FAILED(rv
)) {
684 if (nsCOMPtr
<nsIThreadRetargetableRequest
> rr
= do_QueryInterface(aRequest
)) {
685 nsCOMPtr
<nsIEventTarget
> sts
=
686 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID
);
688 TaskQueue::Create(sts
.forget(), "nsIncrementalDownload Delivery Queue");
690 ("nsIncrementalDownload::OnStartRequest\n"
691 " Retarget to stream transport service\n"));
692 rr
->RetargetDeliveryTo(queue
);
699 nsIncrementalDownload::CheckListenerChain() { return NS_OK
; }
702 nsIncrementalDownload::OnStopRequest(nsIRequest
* request
, nsresult status
) {
703 // Not a real error; just a trick to kill off the channel without our
704 // listener having to care.
705 if (status
== NS_ERROR_DOWNLOAD_NOT_PARTIAL
) return NS_OK
;
707 // Not a real error; just a trick used to suppress OnDataAvailable calls.
708 if (status
== NS_ERROR_DOWNLOAD_COMPLETE
) status
= NS_OK
;
710 if (NS_SUCCEEDED(mStatus
)) mStatus
= status
;
713 if (NS_SUCCEEDED(mStatus
)) mStatus
= FlushChunk();
715 mChunk
= nullptr; // deletes memory
722 // Notify listener if we hit an error or finished
723 if (NS_FAILED(mStatus
) || mCurrentSize
== mTotalSize
) {
728 return StartTimer(mInterval
); // Do next chunk
733 nsIncrementalDownload::OnDataAvailable(nsIRequest
* request
,
734 nsIInputStream
* input
, uint64_t offset
,
737 uint32_t space
= mChunkSize
- mChunkLen
;
738 uint32_t n
, len
= std::min(space
, count
);
740 nsresult rv
= input
->Read(&mChunk
[mChunkLen
], len
, &n
);
741 if (NS_FAILED(rv
)) return rv
;
742 if (n
!= len
) return NS_ERROR_UNEXPECTED
;
747 if (mChunkLen
== mChunkSize
) {
749 if (NS_FAILED(rv
)) return rv
;
753 if (PR_Now() > mLastProgressUpdate
+ UPDATE_PROGRESS_INTERVAL
) {
754 if (NS_IsMainThread()) {
757 NS_DispatchToMainThread(
758 NewRunnableMethod("nsIncrementalDownload::UpdateProgress", this,
759 &nsIncrementalDownload::UpdateProgress
));
766 nsIncrementalDownload::OnDataFinished(nsresult aStatus
) { return NS_OK
; }
771 nsIncrementalDownload::Observe(nsISupports
* subject
, const char* topic
,
772 const char16_t
* data
) {
773 if (strcmp(topic
, NS_XPCOM_SHUTDOWN_OBSERVER_ID
) == 0) {
774 Cancel(NS_ERROR_ABORT
);
776 // Since the app is shutting down, we need to go ahead and notify our
777 // observer here. Otherwise, we would notify them after XPCOM has been
778 // shutdown or not at all.
786 nsIncrementalDownload::TimerCallback::TimerCallback(
787 nsIncrementalDownload
* aIncrementalDownload
)
788 : mIncrementalDownload(aIncrementalDownload
) {}
790 NS_IMPL_ISUPPORTS(nsIncrementalDownload::TimerCallback
, nsITimerCallback
,
794 nsIncrementalDownload::TimerCallback::Notify(nsITimer
* aTimer
) {
795 mIncrementalDownload
->mTimer
= nullptr;
797 nsresult rv
= mIncrementalDownload
->ProcessTimeout();
798 if (NS_FAILED(rv
)) mIncrementalDownload
->Cancel(rv
);
806 nsIncrementalDownload::TimerCallback::GetName(nsACString
& aName
) {
807 aName
.AssignLiteral("nsIncrementalDownload");
811 // nsIInterfaceRequestor
814 nsIncrementalDownload::GetInterface(const nsIID
& iid
, void** result
) {
815 if (iid
.Equals(NS_GET_IID(nsIChannelEventSink
))) {
817 *result
= static_cast<nsIChannelEventSink
*>(this);
821 nsCOMPtr
<nsIInterfaceRequestor
> ir
= do_QueryInterface(mObserver
);
822 if (ir
) return ir
->GetInterface(iid
, result
);
824 return NS_ERROR_NO_INTERFACE
;
827 nsresult
nsIncrementalDownload::ClearRequestHeader(nsIHttpChannel
* channel
) {
828 NS_ENSURE_ARG(channel
);
830 // We don't support encodings -- they make the Content-Length not equal
831 // to the actual size of the data.
832 return channel
->SetRequestHeader("Accept-Encoding"_ns
, ""_ns
, false);
835 // nsIChannelEventSink
838 nsIncrementalDownload::AsyncOnChannelRedirect(
839 nsIChannel
* oldChannel
, nsIChannel
* newChannel
, uint32_t flags
,
840 nsIAsyncVerifyRedirectCallback
* cb
) {
841 // In response to a redirect, we need to propagate the Range header. See bug
842 // 311595. Any failure code returned from this function aborts the redirect.
844 nsCOMPtr
<nsIHttpChannel
> http
= do_QueryInterface(oldChannel
);
845 NS_ENSURE_STATE(http
);
847 nsCOMPtr
<nsIHttpChannel
> newHttpChannel
= do_QueryInterface(newChannel
);
848 NS_ENSURE_STATE(newHttpChannel
);
850 constexpr auto rangeHdr
= "Range"_ns
;
852 nsresult rv
= ClearRequestHeader(newHttpChannel
);
853 if (NS_FAILED(rv
)) return rv
;
855 if (!mExtraHeaders
.IsEmpty()) {
856 rv
= AddExtraHeaders(http
, mExtraHeaders
);
857 if (NS_FAILED(rv
)) return rv
;
860 // If we didn't have a Range header, then we must be doing a full download.
861 nsAutoCString rangeVal
;
862 Unused
<< http
->GetRequestHeader(rangeHdr
, rangeVal
);
863 if (!rangeVal
.IsEmpty()) {
864 rv
= newHttpChannel
->SetRequestHeader(rangeHdr
, rangeVal
, false);
865 NS_ENSURE_SUCCESS(rv
, rv
);
868 // A redirection changes the validator
869 mPartialValidator
.Truncate();
872 rv
= newHttpChannel
->SetRequestHeader("Cache-Control"_ns
, "no-cache"_ns
,
876 ("nsIncrementalDownload::AsyncOnChannelRedirect\n"
877 " failed to set request header: Cache-Control\n"));
879 rv
= newHttpChannel
->SetRequestHeader("Pragma"_ns
, "no-cache"_ns
, false);
882 ("nsIncrementalDownload::AsyncOnChannelRedirect\n"
883 " failed to set request header: Pragma\n"));
887 // Prepare to receive callback
888 mRedirectCallback
= cb
;
889 mNewRedirectChannel
= newChannel
;
891 // Give the observer a chance to see this redirect notification.
892 nsCOMPtr
<nsIChannelEventSink
> sink
= do_GetInterface(mObserver
);
894 rv
= sink
->AsyncOnChannelRedirect(oldChannel
, newChannel
, flags
, this);
896 mRedirectCallback
= nullptr;
897 mNewRedirectChannel
= nullptr;
901 (void)OnRedirectVerifyCallback(NS_OK
);
906 nsIncrementalDownload::OnRedirectVerifyCallback(nsresult result
) {
907 NS_ASSERTION(mRedirectCallback
, "mRedirectCallback not set in callback");
908 NS_ASSERTION(mNewRedirectChannel
, "mNewRedirectChannel not set in callback");
910 // Update mChannel, so we can Cancel the new channel.
911 if (NS_SUCCEEDED(result
)) mChannel
= mNewRedirectChannel
;
913 mRedirectCallback
->OnRedirectVerifyCallback(result
);
914 mRedirectCallback
= nullptr;
915 mNewRedirectChannel
= nullptr;
919 extern nsresult
net_NewIncrementalDownload(const nsIID
& iid
, void** result
) {
920 RefPtr
<nsIncrementalDownload
> d
= new nsIncrementalDownload();
921 return d
->QueryInterface(iid
, result
);