Backed out 2 changesets (bug 1943998) for causing wd failures @ phases.py CLOSED...
[gecko.git] / netwerk / base / nsIncrementalDownload.cpp
blob03644f8a1891b0cf18f65d072954ef813f0c1641
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"
23 #include "nsIFile.h"
24 #include "nsIHttpChannel.h"
25 #include "nsIOService.h"
26 #include "nsITimer.h"
27 #include "nsIURI.h"
28 #include "nsIInputStream.h"
29 #include "nsNetUtil.h"
30 #include "nsWeakReference.h"
31 #include "prio.h"
32 #include "prprf.h"
33 #include <algorithm>
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");
52 #undef LOG
53 #define LOG(args) MOZ_LOG(gIDLog, mozilla::LogLevel::Debug, args)
55 //-----------------------------------------------------------------------------
57 static nsresult WriteToFile(nsIFile* lf, const char* data, uint32_t len,
58 int32_t flags) {
59 PRFileDesc* fd;
60 int32_t mode = 0600;
61 nsresult rv;
62 rv = lf->OpenNSPRFileDesc(flags, mode, &fd);
63 if (NS_FAILED(rv)) return rv;
65 if (len) {
66 rv = PR_Write(fd, data, len) == int32_t(len) ? NS_OK : NS_ERROR_FAILURE;
69 PR_Close(fd);
70 return rv;
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;
90 end -= 1;
92 rangeSpec.AppendInt(int64_t(end));
95 //-----------------------------------------------------------------------------
97 class nsIncrementalDownload final : public nsIIncrementalDownload,
98 public nsIThreadRetargetableStreamListener,
99 public nsIObserver,
100 public nsIInterfaceRequestor,
101 public nsIChannelEventSink,
102 public nsSupportsWeakReference,
103 public nsIAsyncVerifyRedirectCallback {
104 public:
105 NS_DECL_THREADSAFE_ISUPPORTS
106 NS_DECL_NSISTREAMLISTENER
107 NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
108 NS_DECL_NSIREQUEST
109 NS_DECL_NSIINCREMENTALDOWNLOAD
110 NS_DECL_NSIREQUESTOBSERVER
111 NS_DECL_NSIOBSERVER
112 NS_DECL_NSIINTERFACEREQUESTOR
113 NS_DECL_NSICHANNELEVENTSINK
114 NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
116 nsIncrementalDownload() = default;
118 private:
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 {
157 public:
158 NS_DECL_ISUPPORTS
159 NS_DECL_NSITIMERCALLBACK
160 NS_DECL_NSINAMED
162 explicit TimerCallback(nsIncrementalDownload* aIncrementalDownload);
164 private:
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);
180 mChunkLen = 0;
182 return NS_OK;
185 void nsIncrementalDownload::UpdateProgress() {
186 mLastProgressUpdate = PR_Now();
188 if (mProgressSink) {
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;
207 mIsPending = false;
209 mObserver->OnStopRequest(this, mStatus);
210 mObserver = nullptr;
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)) {
224 CallOnStopRequest();
225 return NS_OK;
228 // Fetch next chunk
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
238 this, // aCallbacks
239 mLoadFlags);
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
258 // entire document.
259 if (mInterval || mCurrentSize != int64_t(0)) {
260 nsAutoCString range;
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);
268 if (NS_FAILED(rv)) {
269 LOG(
270 ("nsIncrementalDownload::ProcessTimeout\n"
271 " failed to set request header: If-Range\n"));
275 if (mCacheBust) {
276 rv = http->SetRequestHeader("Cache-Control"_ns, "no-cache"_ns, false);
277 if (NS_FAILED(rv)) {
278 LOG(
279 ("nsIncrementalDownload::ProcessTimeout\n"
280 " failed to set request header: If-Range\n"));
282 rv = http->SetRequestHeader("Pragma"_ns, "no-cache"_ns, false);
283 if (NS_FAILED(rv)) {
284 LOG(
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.
298 mChannel = channel;
299 return NS_OK;
302 // Reads the current file size and validates it.
303 nsresult nsIncrementalDownload::ReadCurrentSize() {
304 int64_t size;
305 nsresult rv = mDest->GetFileSize((int64_t*)&size);
306 if (rv == NS_ERROR_FILE_NOT_FOUND) {
307 mCurrentSize = 0;
308 return NS_OK;
310 if (NS_FAILED(rv)) return rv;
312 mCurrentSize = size;
313 return NS_OK;
316 // nsISupports
317 NS_IMPL_ISUPPORTS(nsIncrementalDownload, nsIIncrementalDownload, nsIRequest,
318 nsIStreamListener, nsIThreadRetargetableStreamListener,
319 nsIRequestObserver, nsIObserver, nsIInterfaceRequestor,
320 nsIChannelEventSink, nsISupportsWeakReference,
321 nsIAsyncVerifyRedirectCallback)
323 // nsIRequest
325 NS_IMETHODIMP
326 nsIncrementalDownload::GetName(nsACString& name) {
327 NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_INITIALIZED);
329 return mURI->GetSpec(name);
332 NS_IMETHODIMP
333 nsIncrementalDownload::IsPending(bool* isPending) {
334 *isPending = mIsPending;
335 return NS_OK;
338 NS_IMETHODIMP
339 nsIncrementalDownload::GetStatus(nsresult* status) {
340 *status = mStatus;
341 return NS_OK;
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);
358 NS_IMETHODIMP
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;
365 mStatus = status;
367 // Nothing more to do if callbacks aren't pending.
368 if (!mIsPending) return NS_OK;
370 if (mChannel) {
371 mChannel->Cancel(mStatus);
372 NS_ASSERTION(!mTimer, "what is this timer object doing here?");
373 } else {
374 // dispatch a timer callback event to drive invoking our listener's
375 // OnStopRequest.
376 if (mTimer) mTimer->Cancel();
377 StartTimer(0);
380 return NS_OK;
383 NS_IMETHODIMP
384 nsIncrementalDownload::Suspend() { return NS_ERROR_NOT_IMPLEMENTED; }
386 NS_IMETHODIMP
387 nsIncrementalDownload::Resume() { return NS_ERROR_NOT_IMPLEMENTED; }
389 NS_IMETHODIMP
390 nsIncrementalDownload::GetLoadFlags(nsLoadFlags* loadFlags) {
391 *loadFlags = mLoadFlags;
392 return NS_OK;
395 NS_IMETHODIMP
396 nsIncrementalDownload::SetLoadFlags(nsLoadFlags loadFlags) {
397 mLoadFlags = loadFlags;
398 return NS_OK;
401 NS_IMETHODIMP
402 nsIncrementalDownload::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
403 return GetTRRModeImpl(aTRRMode);
406 NS_IMETHODIMP
407 nsIncrementalDownload::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
408 return SetTRRModeImpl(aTRRMode);
411 NS_IMETHODIMP
412 nsIncrementalDownload::GetLoadGroup(nsILoadGroup** loadGroup) {
413 return NS_ERROR_NOT_IMPLEMENTED;
416 NS_IMETHODIMP
417 nsIncrementalDownload::SetLoadGroup(nsILoadGroup* loadGroup) {
418 return NS_ERROR_NOT_IMPLEMENTED;
421 // nsIIncrementalDownload
423 NS_IMETHODIMP
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);
429 mDest = dest;
430 NS_ENSURE_ARG(mDest);
432 mURI = uri;
433 mFinalURI = uri;
435 if (chunkSize > 0) mChunkSize = chunkSize;
436 if (interval >= 0) mInterval = interval;
438 mExtraHeaders = extraHeaders;
440 return NS_OK;
443 NS_IMETHODIMP
444 nsIncrementalDownload::GetURI(nsIURI** result) {
445 nsCOMPtr<nsIURI> uri = mURI;
446 uri.forget(result);
447 return NS_OK;
450 NS_IMETHODIMP
451 nsIncrementalDownload::GetFinalURI(nsIURI** result) {
452 nsCOMPtr<nsIURI> uri = mFinalURI;
453 uri.forget(result);
454 return NS_OK;
457 NS_IMETHODIMP
458 nsIncrementalDownload::GetDestination(nsIFile** result) {
459 if (!mDest) {
460 *result = nullptr;
461 return NS_OK;
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);
469 NS_IMETHODIMP
470 nsIncrementalDownload::GetTotalSize(int64_t* result) {
471 *result = mTotalSize;
472 return NS_OK;
475 NS_IMETHODIMP
476 nsIncrementalDownload::GetCurrentSize(int64_t* result) {
477 *result = mCurrentSize;
478 return NS_OK;
481 NS_IMETHODIMP
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;
497 rv = StartTimer(0);
498 if (NS_FAILED(rv)) return rv;
500 mObserver = observer;
501 mProgressSink = do_QueryInterface(observer); // ok if null
503 mIsPending = true;
504 return NS_OK;
507 // nsIRequestObserver
509 NS_IMETHODIMP
510 nsIncrementalDownload::OnStartRequest(nsIRequest* aRequest) {
511 nsresult rv;
513 nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aRequest, &rv);
514 if (NS_FAILED(rv)) return rv;
516 // Ensure that we are receiving a 206 response.
517 uint32_t code;
518 rv = http->GetResponseStatus(&code);
519 if (NS_FAILED(rv)) return rv;
520 if (code != 206) {
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.
533 if (code == 200) {
534 if (mInterval) {
535 mChannel = nullptr;
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.
547 } else {
548 NS_WARNING("server response was unexpected");
549 return NS_ERROR_UNEXPECTED;
551 } else {
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.
559 if (!mCacheBust) {
560 nsAutoCString buf;
561 int64_t startByte = 0;
562 bool confirmedOK = false;
564 rv = http->GetResponseHeader("Content-Range"_ns, buf);
565 if (NS_FAILED(rv)) {
566 return rv; // it isn't a useful 206 without a CONTENT-RANGE of some
568 // sort
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
575 if (p != -1) {
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
584 // the first chunk
585 if (mTotalSize == int64_t(-1)) {
586 // first chunk
587 confirmedOK = true;
588 } else {
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) {
595 confirmedOK = true;
601 if (!confirmedOK) {
602 NS_WARNING("unexpected content-range");
603 mCacheBust = true;
604 mChannel = nullptr;
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);
627 if (NS_FAILED(rv)) {
628 LOG(
629 ("nsIncrementalDownload::OnStartRequest\n"
630 " empty validator\n"));
634 if (code == 206) {
635 // OK, read the Content-Range header to determine the total size of this
636 // download file.
637 nsAutoCString buf;
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) !=
646 1) {
647 return NS_ERROR_UNEXPECTED;
649 } else {
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);
660 mCurrentSize = 0;
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)) {
681 return rv;
684 if (nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(aRequest)) {
685 nsCOMPtr<nsIEventTarget> sts =
686 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
687 RefPtr queue =
688 TaskQueue::Create(sts.forget(), "nsIncrementalDownload Delivery Queue");
689 LOG(
690 ("nsIncrementalDownload::OnStartRequest\n"
691 " Retarget to stream transport service\n"));
692 rr->RetargetDeliveryTo(queue);
695 return rv;
698 NS_IMETHODIMP
699 nsIncrementalDownload::CheckListenerChain() { return NS_OK; }
701 NS_IMETHODIMP
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;
712 if (mChunk) {
713 if (NS_SUCCEEDED(mStatus)) mStatus = FlushChunk();
715 mChunk = nullptr; // deletes memory
716 mChunkLen = 0;
717 UpdateProgress();
720 mChannel = nullptr;
722 // Notify listener if we hit an error or finished
723 if (NS_FAILED(mStatus) || mCurrentSize == mTotalSize) {
724 CallOnStopRequest();
725 return NS_OK;
728 return StartTimer(mInterval); // Do next chunk
731 // nsIStreamListener
732 NS_IMETHODIMP
733 nsIncrementalDownload::OnDataAvailable(nsIRequest* request,
734 nsIInputStream* input, uint64_t offset,
735 uint32_t count) {
736 while (count) {
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;
744 count -= n;
745 mChunkLen += n;
747 if (mChunkLen == mChunkSize) {
748 rv = FlushChunk();
749 if (NS_FAILED(rv)) return rv;
753 if (PR_Now() > mLastProgressUpdate + UPDATE_PROGRESS_INTERVAL) {
754 if (NS_IsMainThread()) {
755 UpdateProgress();
756 } else {
757 NS_DispatchToMainThread(
758 NewRunnableMethod("nsIncrementalDownload::UpdateProgress", this,
759 &nsIncrementalDownload::UpdateProgress));
762 return NS_OK;
765 NS_IMETHODIMP
766 nsIncrementalDownload::OnDataFinished(nsresult aStatus) { return NS_OK; }
768 // nsIObserver
770 NS_IMETHODIMP
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.
779 CallOnStopRequest();
781 return NS_OK;
784 // nsITimerCallback
786 nsIncrementalDownload::TimerCallback::TimerCallback(
787 nsIncrementalDownload* aIncrementalDownload)
788 : mIncrementalDownload(aIncrementalDownload) {}
790 NS_IMPL_ISUPPORTS(nsIncrementalDownload::TimerCallback, nsITimerCallback,
791 nsINamed)
793 NS_IMETHODIMP
794 nsIncrementalDownload::TimerCallback::Notify(nsITimer* aTimer) {
795 mIncrementalDownload->mTimer = nullptr;
797 nsresult rv = mIncrementalDownload->ProcessTimeout();
798 if (NS_FAILED(rv)) mIncrementalDownload->Cancel(rv);
800 return NS_OK;
803 // nsINamed
805 NS_IMETHODIMP
806 nsIncrementalDownload::TimerCallback::GetName(nsACString& aName) {
807 aName.AssignLiteral("nsIncrementalDownload");
808 return NS_OK;
811 // nsIInterfaceRequestor
813 NS_IMETHODIMP
814 nsIncrementalDownload::GetInterface(const nsIID& iid, void** result) {
815 if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) {
816 NS_ADDREF_THIS();
817 *result = static_cast<nsIChannelEventSink*>(this);
818 return NS_OK;
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
837 NS_IMETHODIMP
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();
871 if (mCacheBust) {
872 rv = newHttpChannel->SetRequestHeader("Cache-Control"_ns, "no-cache"_ns,
873 false);
874 if (NS_FAILED(rv)) {
875 LOG(
876 ("nsIncrementalDownload::AsyncOnChannelRedirect\n"
877 " failed to set request header: Cache-Control\n"));
879 rv = newHttpChannel->SetRequestHeader("Pragma"_ns, "no-cache"_ns, false);
880 if (NS_FAILED(rv)) {
881 LOG(
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);
893 if (sink) {
894 rv = sink->AsyncOnChannelRedirect(oldChannel, newChannel, flags, this);
895 if (NS_FAILED(rv)) {
896 mRedirectCallback = nullptr;
897 mNewRedirectChannel = nullptr;
899 return rv;
901 (void)OnRedirectVerifyCallback(NS_OK);
902 return NS_OK;
905 NS_IMETHODIMP
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;
916 return NS_OK;
919 extern nsresult net_NewIncrementalDownload(const nsIID& iid, void** result) {
920 RefPtr<nsIncrementalDownload> d = new nsIncrementalDownload();
921 return d->QueryInterface(iid, result);