Backed out changeset 2bbc01486e2f (bug 1910796) for causing multiple failures. CLOSED...
[gecko.git] / netwerk / base / nsIncrementalDownload.cpp
blob8396725dee32244703f5cdda9622a891a18a000d
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};
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 {
156 public:
157 NS_DECL_ISUPPORTS
158 NS_DECL_NSITIMERCALLBACK
159 NS_DECL_NSINAMED
161 explicit TimerCallback(nsIncrementalDownload* aIncrementalDownload);
163 private:
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);
179 mChunkLen = 0;
181 return NS_OK;
184 void nsIncrementalDownload::UpdateProgress() {
185 mLastProgressUpdate = PR_Now();
187 if (mProgressSink) {
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;
206 mIsPending = false;
208 mObserver->OnStopRequest(this, mStatus);
209 mObserver = nullptr;
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)) {
223 CallOnStopRequest();
224 return NS_OK;
227 // Fetch next chunk
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
237 this, // aCallbacks
238 mLoadFlags);
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
252 // entire document.
253 if (mInterval || mCurrentSize != int64_t(0)) {
254 nsAutoCString range;
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);
262 if (NS_FAILED(rv)) {
263 LOG(
264 ("nsIncrementalDownload::ProcessTimeout\n"
265 " failed to set request header: If-Range\n"));
269 if (mCacheBust) {
270 rv = http->SetRequestHeader("Cache-Control"_ns, "no-cache"_ns, false);
271 if (NS_FAILED(rv)) {
272 LOG(
273 ("nsIncrementalDownload::ProcessTimeout\n"
274 " failed to set request header: If-Range\n"));
276 rv = http->SetRequestHeader("Pragma"_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"));
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.
292 mChannel = channel;
293 return NS_OK;
296 // Reads the current file size and validates it.
297 nsresult nsIncrementalDownload::ReadCurrentSize() {
298 int64_t size;
299 nsresult rv = mDest->GetFileSize((int64_t*)&size);
300 if (rv == NS_ERROR_FILE_NOT_FOUND) {
301 mCurrentSize = 0;
302 return NS_OK;
304 if (NS_FAILED(rv)) return rv;
306 mCurrentSize = size;
307 return NS_OK;
310 // nsISupports
311 NS_IMPL_ISUPPORTS(nsIncrementalDownload, nsIIncrementalDownload, nsIRequest,
312 nsIStreamListener, nsIThreadRetargetableStreamListener,
313 nsIRequestObserver, nsIObserver, nsIInterfaceRequestor,
314 nsIChannelEventSink, nsISupportsWeakReference,
315 nsIAsyncVerifyRedirectCallback)
317 // nsIRequest
319 NS_IMETHODIMP
320 nsIncrementalDownload::GetName(nsACString& name) {
321 NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_INITIALIZED);
323 return mURI->GetSpec(name);
326 NS_IMETHODIMP
327 nsIncrementalDownload::IsPending(bool* isPending) {
328 *isPending = mIsPending;
329 return NS_OK;
332 NS_IMETHODIMP
333 nsIncrementalDownload::GetStatus(nsresult* status) {
334 *status = mStatus;
335 return NS_OK;
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);
352 NS_IMETHODIMP
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;
359 mStatus = status;
361 // Nothing more to do if callbacks aren't pending.
362 if (!mIsPending) return NS_OK;
364 if (mChannel) {
365 mChannel->Cancel(mStatus);
366 NS_ASSERTION(!mTimer, "what is this timer object doing here?");
367 } else {
368 // dispatch a timer callback event to drive invoking our listener's
369 // OnStopRequest.
370 if (mTimer) mTimer->Cancel();
371 StartTimer(0);
374 return NS_OK;
377 NS_IMETHODIMP
378 nsIncrementalDownload::Suspend() { return NS_ERROR_NOT_IMPLEMENTED; }
380 NS_IMETHODIMP
381 nsIncrementalDownload::Resume() { return NS_ERROR_NOT_IMPLEMENTED; }
383 NS_IMETHODIMP
384 nsIncrementalDownload::GetLoadFlags(nsLoadFlags* loadFlags) {
385 *loadFlags = mLoadFlags;
386 return NS_OK;
389 NS_IMETHODIMP
390 nsIncrementalDownload::SetLoadFlags(nsLoadFlags loadFlags) {
391 mLoadFlags = loadFlags;
392 return NS_OK;
395 NS_IMETHODIMP
396 nsIncrementalDownload::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
397 return GetTRRModeImpl(aTRRMode);
400 NS_IMETHODIMP
401 nsIncrementalDownload::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
402 return SetTRRModeImpl(aTRRMode);
405 NS_IMETHODIMP
406 nsIncrementalDownload::GetLoadGroup(nsILoadGroup** loadGroup) {
407 return NS_ERROR_NOT_IMPLEMENTED;
410 NS_IMETHODIMP
411 nsIncrementalDownload::SetLoadGroup(nsILoadGroup* loadGroup) {
412 return NS_ERROR_NOT_IMPLEMENTED;
415 // nsIIncrementalDownload
417 NS_IMETHODIMP
418 nsIncrementalDownload::Init(nsIURI* uri, nsIFile* dest, int32_t chunkSize,
419 int32_t interval) {
420 // Keep it simple: only allow initialization once
421 NS_ENSURE_FALSE(mURI, NS_ERROR_ALREADY_INITIALIZED);
423 mDest = dest;
424 NS_ENSURE_ARG(mDest);
426 mURI = uri;
427 mFinalURI = uri;
429 if (chunkSize > 0) mChunkSize = chunkSize;
430 if (interval >= 0) mInterval = interval;
431 return NS_OK;
434 NS_IMETHODIMP
435 nsIncrementalDownload::GetURI(nsIURI** result) {
436 nsCOMPtr<nsIURI> uri = mURI;
437 uri.forget(result);
438 return NS_OK;
441 NS_IMETHODIMP
442 nsIncrementalDownload::GetFinalURI(nsIURI** result) {
443 nsCOMPtr<nsIURI> uri = mFinalURI;
444 uri.forget(result);
445 return NS_OK;
448 NS_IMETHODIMP
449 nsIncrementalDownload::GetDestination(nsIFile** result) {
450 if (!mDest) {
451 *result = nullptr;
452 return NS_OK;
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);
460 NS_IMETHODIMP
461 nsIncrementalDownload::GetTotalSize(int64_t* result) {
462 *result = mTotalSize;
463 return NS_OK;
466 NS_IMETHODIMP
467 nsIncrementalDownload::GetCurrentSize(int64_t* result) {
468 *result = mCurrentSize;
469 return NS_OK;
472 NS_IMETHODIMP
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;
488 rv = StartTimer(0);
489 if (NS_FAILED(rv)) return rv;
491 mObserver = observer;
492 mProgressSink = do_QueryInterface(observer); // ok if null
494 mIsPending = true;
495 return NS_OK;
498 // nsIRequestObserver
500 NS_IMETHODIMP
501 nsIncrementalDownload::OnStartRequest(nsIRequest* aRequest) {
502 nsresult rv;
504 nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aRequest, &rv);
505 if (NS_FAILED(rv)) return rv;
507 // Ensure that we are receiving a 206 response.
508 uint32_t code;
509 rv = http->GetResponseStatus(&code);
510 if (NS_FAILED(rv)) return rv;
511 if (code != 206) {
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.
524 if (code == 200) {
525 if (mInterval) {
526 mChannel = nullptr;
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.
538 } else {
539 NS_WARNING("server response was unexpected");
540 return NS_ERROR_UNEXPECTED;
542 } else {
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.
550 if (!mCacheBust) {
551 nsAutoCString buf;
552 int64_t startByte = 0;
553 bool confirmedOK = false;
555 rv = http->GetResponseHeader("Content-Range"_ns, buf);
556 if (NS_FAILED(rv)) {
557 return rv; // it isn't a useful 206 without a CONTENT-RANGE of some
559 // sort
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
566 if (p != -1) {
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
575 // the first chunk
576 if (mTotalSize == int64_t(-1)) {
577 // first chunk
578 confirmedOK = true;
579 } else {
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) {
586 confirmedOK = true;
592 if (!confirmedOK) {
593 NS_WARNING("unexpected content-range");
594 mCacheBust = true;
595 mChannel = nullptr;
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);
618 if (NS_FAILED(rv)) {
619 LOG(
620 ("nsIncrementalDownload::OnStartRequest\n"
621 " empty validator\n"));
625 if (code == 206) {
626 // OK, read the Content-Range header to determine the total size of this
627 // download file.
628 nsAutoCString buf;
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) !=
637 1) {
638 return NS_ERROR_UNEXPECTED;
640 } else {
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);
651 mCurrentSize = 0;
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)) {
672 return rv;
675 if (nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(aRequest)) {
676 nsCOMPtr<nsIEventTarget> sts =
677 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
678 RefPtr queue =
679 TaskQueue::Create(sts.forget(), "nsIncrementalDownload Delivery Queue");
680 LOG(
681 ("nsIncrementalDownload::OnStartRequest\n"
682 " Retarget to stream transport service\n"));
683 rr->RetargetDeliveryTo(queue);
686 return rv;
689 NS_IMETHODIMP
690 nsIncrementalDownload::CheckListenerChain() { return NS_OK; }
692 NS_IMETHODIMP
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;
703 if (mChunk) {
704 if (NS_SUCCEEDED(mStatus)) mStatus = FlushChunk();
706 mChunk = nullptr; // deletes memory
707 mChunkLen = 0;
708 UpdateProgress();
711 mChannel = nullptr;
713 // Notify listener if we hit an error or finished
714 if (NS_FAILED(mStatus) || mCurrentSize == mTotalSize) {
715 CallOnStopRequest();
716 return NS_OK;
719 return StartTimer(mInterval); // Do next chunk
722 // nsIStreamListener
723 NS_IMETHODIMP
724 nsIncrementalDownload::OnDataAvailable(nsIRequest* request,
725 nsIInputStream* input, uint64_t offset,
726 uint32_t count) {
727 while (count) {
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;
735 count -= n;
736 mChunkLen += n;
738 if (mChunkLen == mChunkSize) {
739 rv = FlushChunk();
740 if (NS_FAILED(rv)) return rv;
744 if (PR_Now() > mLastProgressUpdate + UPDATE_PROGRESS_INTERVAL) {
745 if (NS_IsMainThread()) {
746 UpdateProgress();
747 } else {
748 NS_DispatchToMainThread(
749 NewRunnableMethod("nsIncrementalDownload::UpdateProgress", this,
750 &nsIncrementalDownload::UpdateProgress));
753 return NS_OK;
756 NS_IMETHODIMP
757 nsIncrementalDownload::OnDataFinished(nsresult aStatus) { return NS_OK; }
759 // nsIObserver
761 NS_IMETHODIMP
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.
770 CallOnStopRequest();
772 return NS_OK;
775 // nsITimerCallback
777 nsIncrementalDownload::TimerCallback::TimerCallback(
778 nsIncrementalDownload* aIncrementalDownload)
779 : mIncrementalDownload(aIncrementalDownload) {}
781 NS_IMPL_ISUPPORTS(nsIncrementalDownload::TimerCallback, nsITimerCallback,
782 nsINamed)
784 NS_IMETHODIMP
785 nsIncrementalDownload::TimerCallback::Notify(nsITimer* aTimer) {
786 mIncrementalDownload->mTimer = nullptr;
788 nsresult rv = mIncrementalDownload->ProcessTimeout();
789 if (NS_FAILED(rv)) mIncrementalDownload->Cancel(rv);
791 return NS_OK;
794 // nsINamed
796 NS_IMETHODIMP
797 nsIncrementalDownload::TimerCallback::GetName(nsACString& aName) {
798 aName.AssignLiteral("nsIncrementalDownload");
799 return NS_OK;
802 // nsIInterfaceRequestor
804 NS_IMETHODIMP
805 nsIncrementalDownload::GetInterface(const nsIID& iid, void** result) {
806 if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) {
807 NS_ADDREF_THIS();
808 *result = static_cast<nsIChannelEventSink*>(this);
809 return NS_OK;
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
828 NS_IMETHODIMP
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();
857 if (mCacheBust) {
858 rv = newHttpChannel->SetRequestHeader("Cache-Control"_ns, "no-cache"_ns,
859 false);
860 if (NS_FAILED(rv)) {
861 LOG(
862 ("nsIncrementalDownload::AsyncOnChannelRedirect\n"
863 " failed to set request header: Cache-Control\n"));
865 rv = newHttpChannel->SetRequestHeader("Pragma"_ns, "no-cache"_ns, false);
866 if (NS_FAILED(rv)) {
867 LOG(
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);
879 if (sink) {
880 rv = sink->AsyncOnChannelRedirect(oldChannel, newChannel, flags, this);
881 if (NS_FAILED(rv)) {
882 mRedirectCallback = nullptr;
883 mNewRedirectChannel = nullptr;
885 return rv;
887 (void)OnRedirectVerifyCallback(NS_OK);
888 return NS_OK;
891 NS_IMETHODIMP
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;
902 return NS_OK;
905 extern nsresult net_NewIncrementalDownload(const nsIID& iid, void** result) {
906 RefPtr<nsIncrementalDownload> d = new nsIncrementalDownload();
907 return d->QueryInterface(iid, result);