Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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"
9 #include "nsIIncrementalDownload.h"
10 #include "nsIRequestObserver.h"
11 #include "nsIProgressEventSink.h"
12 #include "nsIChannelEventSink.h"
13 #include "nsIAsyncVerifyRedirectCallback.h"
14 #include "nsIInterfaceRequestor.h"
15 #include "nsIObserverService.h"
16 #include "nsIObserver.h"
17 #include "nsIFile.h"
18 #include "nsITimer.h"
19 #include "nsNetUtil.h"
20 #include "nsAutoPtr.h"
21 #include "nsWeakReference.h"
22 #include "prio.h"
23 #include "prprf.h"
24 #include <algorithm>
26 // Default values used to initialize a nsIncrementalDownload object.
27 #define DEFAULT_CHUNK_SIZE (4096 * 16) // bytes
28 #define DEFAULT_INTERVAL 60 // seconds
30 #define UPDATE_PROGRESS_INTERVAL PRTime(500 * PR_USEC_PER_MSEC) // 500ms
32 // Number of times to retry a failed byte-range request.
33 #define MAX_RETRY_COUNT 20
35 //-----------------------------------------------------------------------------
37 static nsresult
38 WriteToFile(nsIFile *lf, const char *data, uint32_t len, int32_t flags)
39 {
40 PRFileDesc *fd;
41 int32_t mode = 0600;
42 nsresult rv;
43 #if defined(MOZ_WIDGET_GONK)
44 // The sdcard on a B2G phone looks like:
45 // d---rwx--- system sdcard_rw 1970-01-01 01:00:00 sdcard
46 // On the emulator, xpcshell fails when using 0600 mode to open the file,
47 // and 0660 works.
48 nsCOMPtr<nsIFile> parent;
49 rv = lf->GetParent(getter_AddRefs(parent));
50 if (NS_FAILED(rv)) {
51 return rv;
52 }
53 uint32_t parentPerm;
54 rv = parent->GetPermissions(&parentPerm);
55 if (NS_FAILED(rv)) {
56 return rv;
57 }
58 if ((parentPerm & 0700) == 0) {
59 // Parent directory has no owner-write, so try to use group permissions
60 // instead of owner permissions.
61 mode = 0660;
62 }
63 #endif
64 rv = lf->OpenNSPRFileDesc(flags, mode, &fd);
65 if (NS_FAILED(rv))
66 return rv;
68 if (len)
69 rv = PR_Write(fd, data, len) == int32_t(len) ? NS_OK : NS_ERROR_FAILURE;
71 PR_Close(fd);
72 return rv;
73 }
75 static nsresult
76 AppendToFile(nsIFile *lf, const char *data, uint32_t len)
77 {
78 int32_t flags = PR_WRONLY | PR_CREATE_FILE | PR_APPEND;
79 return WriteToFile(lf, data, len, flags);
80 }
82 // maxSize may be -1 if unknown
83 static void
84 MakeRangeSpec(const int64_t &size, const int64_t &maxSize, int32_t chunkSize,
85 bool fetchRemaining, nsCString &rangeSpec)
86 {
87 rangeSpec.AssignLiteral("bytes=");
88 rangeSpec.AppendInt(int64_t(size));
89 rangeSpec.Append('-');
91 if (fetchRemaining)
92 return;
94 int64_t end = size + int64_t(chunkSize);
95 if (maxSize != int64_t(-1) && end > maxSize)
96 end = maxSize;
97 end -= 1;
99 rangeSpec.AppendInt(int64_t(end));
100 }
102 //-----------------------------------------------------------------------------
104 class nsIncrementalDownload MOZ_FINAL
105 : public nsIIncrementalDownload
106 , public nsIStreamListener
107 , public nsIObserver
108 , public nsIInterfaceRequestor
109 , public nsIChannelEventSink
110 , public nsSupportsWeakReference
111 , public nsIAsyncVerifyRedirectCallback
112 {
113 public:
114 NS_DECL_ISUPPORTS
115 NS_DECL_NSIREQUEST
116 NS_DECL_NSIINCREMENTALDOWNLOAD
117 NS_DECL_NSIREQUESTOBSERVER
118 NS_DECL_NSISTREAMLISTENER
119 NS_DECL_NSIOBSERVER
120 NS_DECL_NSIINTERFACEREQUESTOR
121 NS_DECL_NSICHANNELEVENTSINK
122 NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
124 nsIncrementalDownload();
126 private:
127 ~nsIncrementalDownload() {}
128 nsresult FlushChunk();
129 void UpdateProgress();
130 nsresult CallOnStartRequest();
131 void CallOnStopRequest();
132 nsresult StartTimer(int32_t interval);
133 nsresult ProcessTimeout();
134 nsresult ReadCurrentSize();
135 nsresult ClearRequestHeader(nsIHttpChannel *channel);
137 nsCOMPtr<nsIRequestObserver> mObserver;
138 nsCOMPtr<nsISupports> mObserverContext;
139 nsCOMPtr<nsIProgressEventSink> mProgressSink;
140 nsCOMPtr<nsIURI> mURI;
141 nsCOMPtr<nsIURI> mFinalURI;
142 nsCOMPtr<nsIFile> mDest;
143 nsCOMPtr<nsIChannel> mChannel;
144 nsCOMPtr<nsITimer> mTimer;
145 nsAutoArrayPtr<char> mChunk;
146 int32_t mChunkLen;
147 int32_t mChunkSize;
148 int32_t mInterval;
149 int64_t mTotalSize;
150 int64_t mCurrentSize;
151 uint32_t mLoadFlags;
152 int32_t mNonPartialCount;
153 nsresult mStatus;
154 bool mIsPending;
155 bool mDidOnStartRequest;
156 PRTime mLastProgressUpdate;
157 nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback;
158 nsCOMPtr<nsIChannel> mNewRedirectChannel;
159 nsCString mPartialValidator;
160 bool mCacheBust;
161 };
163 nsIncrementalDownload::nsIncrementalDownload()
164 : mChunkLen(0)
165 , mChunkSize(DEFAULT_CHUNK_SIZE)
166 , mInterval(DEFAULT_INTERVAL)
167 , mTotalSize(-1)
168 , mCurrentSize(-1)
169 , mLoadFlags(LOAD_NORMAL)
170 , mNonPartialCount(0)
171 , mStatus(NS_OK)
172 , mIsPending(false)
173 , mDidOnStartRequest(false)
174 , mLastProgressUpdate(0)
175 , mRedirectCallback(nullptr)
176 , mNewRedirectChannel(nullptr)
177 , mCacheBust(false)
178 {
179 }
181 nsresult
182 nsIncrementalDownload::FlushChunk()
183 {
184 NS_ASSERTION(mTotalSize != int64_t(-1), "total size should be known");
186 if (mChunkLen == 0)
187 return NS_OK;
189 nsresult rv = AppendToFile(mDest, mChunk, mChunkLen);
190 if (NS_FAILED(rv))
191 return rv;
193 mCurrentSize += int64_t(mChunkLen);
194 mChunkLen = 0;
196 return NS_OK;
197 }
199 void
200 nsIncrementalDownload::UpdateProgress()
201 {
202 mLastProgressUpdate = PR_Now();
204 if (mProgressSink)
205 mProgressSink->OnProgress(this, mObserverContext,
206 uint64_t(int64_t(mCurrentSize) + mChunkLen),
207 uint64_t(int64_t(mTotalSize)));
208 }
210 nsresult
211 nsIncrementalDownload::CallOnStartRequest()
212 {
213 if (!mObserver || mDidOnStartRequest)
214 return NS_OK;
216 mDidOnStartRequest = true;
217 return mObserver->OnStartRequest(this, mObserverContext);
218 }
220 void
221 nsIncrementalDownload::CallOnStopRequest()
222 {
223 if (!mObserver)
224 return;
226 // Ensure that OnStartRequest is always called once before OnStopRequest.
227 nsresult rv = CallOnStartRequest();
228 if (NS_SUCCEEDED(mStatus))
229 mStatus = rv;
231 mIsPending = false;
233 mObserver->OnStopRequest(this, mObserverContext, mStatus);
234 mObserver = nullptr;
235 mObserverContext = nullptr;
236 }
238 nsresult
239 nsIncrementalDownload::StartTimer(int32_t interval)
240 {
241 nsresult rv;
242 mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
243 if (NS_FAILED(rv))
244 return rv;
246 return mTimer->Init(this, interval * 1000, nsITimer::TYPE_ONE_SHOT);
247 }
249 nsresult
250 nsIncrementalDownload::ProcessTimeout()
251 {
252 NS_ASSERTION(!mChannel, "how can we have a channel?");
254 // Handle existing error conditions
255 if (NS_FAILED(mStatus)) {
256 CallOnStopRequest();
257 return NS_OK;
258 }
260 // Fetch next chunk
262 nsCOMPtr<nsIChannel> channel;
263 nsresult rv = NS_NewChannel(getter_AddRefs(channel), mFinalURI, nullptr,
264 nullptr, this, mLoadFlags);
265 if (NS_FAILED(rv))
266 return rv;
268 nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(channel, &rv);
269 if (NS_FAILED(rv))
270 return rv;
272 NS_ASSERTION(mCurrentSize != int64_t(-1),
273 "we should know the current file size by now");
275 rv = ClearRequestHeader(http);
276 if (NS_FAILED(rv))
277 return rv;
279 // Don't bother making a range request if we are just going to fetch the
280 // entire document.
281 if (mInterval || mCurrentSize != int64_t(0)) {
282 nsAutoCString range;
283 MakeRangeSpec(mCurrentSize, mTotalSize, mChunkSize, mInterval == 0, range);
285 rv = http->SetRequestHeader(NS_LITERAL_CSTRING("Range"), range, false);
286 if (NS_FAILED(rv))
287 return rv;
289 if (!mPartialValidator.IsEmpty())
290 http->SetRequestHeader(NS_LITERAL_CSTRING("If-Range"),
291 mPartialValidator, false);
293 if (mCacheBust) {
294 http->SetRequestHeader(NS_LITERAL_CSTRING("Cache-Control"),
295 NS_LITERAL_CSTRING("no-cache"), false);
296 http->SetRequestHeader(NS_LITERAL_CSTRING("Pragma"),
297 NS_LITERAL_CSTRING("no-cache"), false);
298 }
299 }
301 rv = channel->AsyncOpen(this, nullptr);
302 if (NS_FAILED(rv))
303 return rv;
305 // Wait to assign mChannel when we know we are going to succeed. This is
306 // important because we don't want to introduce a reference cycle between
307 // mChannel and this until we know for a fact that AsyncOpen has succeeded,
308 // thus ensuring that our stream listener methods will be invoked.
309 mChannel = channel;
310 return NS_OK;
311 }
313 // Reads the current file size and validates it.
314 nsresult
315 nsIncrementalDownload::ReadCurrentSize()
316 {
317 int64_t size;
318 nsresult rv = mDest->GetFileSize((int64_t *) &size);
319 if (rv == NS_ERROR_FILE_NOT_FOUND ||
320 rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
321 mCurrentSize = 0;
322 return NS_OK;
323 }
324 if (NS_FAILED(rv))
325 return rv;
327 mCurrentSize = size;
328 return NS_OK;
329 }
331 // nsISupports
333 NS_IMPL_ISUPPORTS(nsIncrementalDownload,
334 nsIIncrementalDownload,
335 nsIRequest,
336 nsIStreamListener,
337 nsIRequestObserver,
338 nsIObserver,
339 nsIInterfaceRequestor,
340 nsIChannelEventSink,
341 nsISupportsWeakReference,
342 nsIAsyncVerifyRedirectCallback)
344 // nsIRequest
346 NS_IMETHODIMP
347 nsIncrementalDownload::GetName(nsACString &name)
348 {
349 NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_INITIALIZED);
351 return mURI->GetSpec(name);
352 }
354 NS_IMETHODIMP
355 nsIncrementalDownload::IsPending(bool *isPending)
356 {
357 *isPending = mIsPending;
358 return NS_OK;
359 }
361 NS_IMETHODIMP
362 nsIncrementalDownload::GetStatus(nsresult *status)
363 {
364 *status = mStatus;
365 return NS_OK;
366 }
368 NS_IMETHODIMP
369 nsIncrementalDownload::Cancel(nsresult status)
370 {
371 NS_ENSURE_ARG(NS_FAILED(status));
373 // Ignore this cancelation if we're already canceled.
374 if (NS_FAILED(mStatus))
375 return NS_OK;
377 mStatus = status;
379 // Nothing more to do if callbacks aren't pending.
380 if (!mIsPending)
381 return NS_OK;
383 if (mChannel) {
384 mChannel->Cancel(mStatus);
385 NS_ASSERTION(!mTimer, "what is this timer object doing here?");
386 }
387 else {
388 // dispatch a timer callback event to drive invoking our listener's
389 // OnStopRequest.
390 if (mTimer)
391 mTimer->Cancel();
392 StartTimer(0);
393 }
395 return NS_OK;
396 }
398 NS_IMETHODIMP
399 nsIncrementalDownload::Suspend()
400 {
401 return NS_ERROR_NOT_IMPLEMENTED;
402 }
404 NS_IMETHODIMP
405 nsIncrementalDownload::Resume()
406 {
407 return NS_ERROR_NOT_IMPLEMENTED;
408 }
410 NS_IMETHODIMP
411 nsIncrementalDownload::GetLoadFlags(nsLoadFlags *loadFlags)
412 {
413 *loadFlags = mLoadFlags;
414 return NS_OK;
415 }
417 NS_IMETHODIMP
418 nsIncrementalDownload::SetLoadFlags(nsLoadFlags loadFlags)
419 {
420 mLoadFlags = loadFlags;
421 return NS_OK;
422 }
424 NS_IMETHODIMP
425 nsIncrementalDownload::GetLoadGroup(nsILoadGroup **loadGroup)
426 {
427 return NS_ERROR_NOT_IMPLEMENTED;
428 }
430 NS_IMETHODIMP
431 nsIncrementalDownload::SetLoadGroup(nsILoadGroup *loadGroup)
432 {
433 return NS_ERROR_NOT_IMPLEMENTED;
434 }
436 // nsIIncrementalDownload
438 NS_IMETHODIMP
439 nsIncrementalDownload::Init(nsIURI *uri, nsIFile *dest,
440 int32_t chunkSize, int32_t interval)
441 {
442 // Keep it simple: only allow initialization once
443 NS_ENSURE_FALSE(mURI, NS_ERROR_ALREADY_INITIALIZED);
445 mDest = do_QueryInterface(dest);
446 NS_ENSURE_ARG(mDest);
448 mURI = uri;
449 mFinalURI = uri;
451 if (chunkSize > 0)
452 mChunkSize = chunkSize;
453 if (interval >= 0)
454 mInterval = interval;
455 return NS_OK;
456 }
458 NS_IMETHODIMP
459 nsIncrementalDownload::GetURI(nsIURI **result)
460 {
461 NS_IF_ADDREF(*result = mURI);
462 return NS_OK;
463 }
465 NS_IMETHODIMP
466 nsIncrementalDownload::GetFinalURI(nsIURI **result)
467 {
468 NS_IF_ADDREF(*result = mFinalURI);
469 return NS_OK;
470 }
472 NS_IMETHODIMP
473 nsIncrementalDownload::GetDestination(nsIFile **result)
474 {
475 if (!mDest) {
476 *result = nullptr;
477 return NS_OK;
478 }
479 // Return a clone of mDest so that callers may modify the resulting nsIFile
480 // without corrupting our internal object. This also works around the fact
481 // that some nsIFile impls may cache the result of stat'ing the filesystem.
482 return mDest->Clone(result);
483 }
485 NS_IMETHODIMP
486 nsIncrementalDownload::GetTotalSize(int64_t *result)
487 {
488 *result = mTotalSize;
489 return NS_OK;
490 }
492 NS_IMETHODIMP
493 nsIncrementalDownload::GetCurrentSize(int64_t *result)
494 {
495 *result = mCurrentSize;
496 return NS_OK;
497 }
499 NS_IMETHODIMP
500 nsIncrementalDownload::Start(nsIRequestObserver *observer,
501 nsISupports *context)
502 {
503 NS_ENSURE_ARG(observer);
504 NS_ENSURE_FALSE(mIsPending, NS_ERROR_IN_PROGRESS);
506 // Observe system shutdown so we can be sure to release any reference held
507 // between ourselves and the timer. We have the observer service hold a weak
508 // reference to us, so that we don't have to worry about calling
509 // RemoveObserver. XXX(darin): The timer code should do this for us.
510 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
511 if (obs)
512 obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
514 nsresult rv = ReadCurrentSize();
515 if (NS_FAILED(rv))
516 return rv;
518 rv = StartTimer(0);
519 if (NS_FAILED(rv))
520 return rv;
522 mObserver = observer;
523 mObserverContext = context;
524 mProgressSink = do_QueryInterface(observer); // ok if null
526 mIsPending = true;
527 return NS_OK;
528 }
530 // nsIRequestObserver
532 NS_IMETHODIMP
533 nsIncrementalDownload::OnStartRequest(nsIRequest *request,
534 nsISupports *context)
535 {
536 nsresult rv;
538 nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(request, &rv);
539 if (NS_FAILED(rv))
540 return rv;
542 // Ensure that we are receiving a 206 response.
543 uint32_t code;
544 rv = http->GetResponseStatus(&code);
545 if (NS_FAILED(rv))
546 return rv;
547 if (code != 206) {
548 // We may already have the entire file downloaded, in which case
549 // our request for a range beyond the end of the file would have
550 // been met with an error response code.
551 if (code == 416 && mTotalSize == int64_t(-1)) {
552 mTotalSize = mCurrentSize;
553 // Return an error code here to suppress OnDataAvailable.
554 return NS_ERROR_DOWNLOAD_COMPLETE;
555 }
556 // The server may have decided to give us all of the data in one chunk. If
557 // we requested a partial range, then we don't want to download all of the
558 // data at once. So, we'll just try again, but if this keeps happening then
559 // we'll eventually give up.
560 if (code == 200) {
561 if (mInterval) {
562 mChannel = nullptr;
563 if (++mNonPartialCount > MAX_RETRY_COUNT) {
564 NS_WARNING("unable to fetch a byte range; giving up");
565 return NS_ERROR_FAILURE;
566 }
567 // Increase delay with each failure.
568 StartTimer(mInterval * mNonPartialCount);
569 return NS_ERROR_DOWNLOAD_NOT_PARTIAL;
570 }
571 // Since we have been asked to download the rest of the file, we can deal
572 // with a 200 response. This may result in downloading the beginning of
573 // the file again, but that can't really be helped.
574 } else {
575 NS_WARNING("server response was unexpected");
576 return NS_ERROR_UNEXPECTED;
577 }
578 } else {
579 // We got a partial response, so clear this counter in case the next chunk
580 // results in a 200 response.
581 mNonPartialCount = 0;
583 // confirm that the content-range response header is consistent with
584 // expectations on each 206. If it is not then drop this response and
585 // retry with no-cache set.
586 if (!mCacheBust) {
587 nsAutoCString buf;
588 int64_t startByte = 0;
589 bool confirmedOK = false;
591 rv = http->GetResponseHeader(NS_LITERAL_CSTRING("Content-Range"), buf);
592 if (NS_FAILED(rv))
593 return rv; // it isn't a useful 206 without a CONTENT-RANGE of some sort
595 // Content-Range: bytes 0-299999/25604694
596 int32_t p = buf.Find("bytes ");
598 // first look for the starting point of the content-range
599 // to make sure it is what we expect
600 if (p != -1) {
601 char *endptr = nullptr;
602 const char *s = buf.get() + p + 6;
603 while (*s && *s == ' ')
604 s++;
605 startByte = strtol(s, &endptr, 10);
607 if (*s && endptr && (endptr != s) &&
608 (mCurrentSize == startByte)) {
610 // ok the starting point is confirmed. We still need to check the
611 // total size of the range for consistency if this isn't
612 // the first chunk
613 if (mTotalSize == int64_t(-1)) {
614 // first chunk
615 confirmedOK = true;
616 } else {
617 int32_t slash = buf.FindChar('/');
618 int64_t rangeSize = 0;
619 if (slash != kNotFound &&
620 (PR_sscanf(buf.get() + slash + 1, "%lld", (int64_t *) &rangeSize) == 1) &&
621 rangeSize == mTotalSize) {
622 confirmedOK = true;
623 }
624 }
625 }
626 }
628 if (!confirmedOK) {
629 NS_WARNING("unexpected content-range");
630 mCacheBust = true;
631 mChannel = nullptr;
632 if (++mNonPartialCount > MAX_RETRY_COUNT) {
633 NS_WARNING("unable to fetch a byte range; giving up");
634 return NS_ERROR_FAILURE;
635 }
636 // Increase delay with each failure.
637 StartTimer(mInterval * mNonPartialCount);
638 return NS_ERROR_DOWNLOAD_NOT_PARTIAL;
639 }
640 }
641 }
643 // Do special processing after the first response.
644 if (mTotalSize == int64_t(-1)) {
645 // Update knowledge of mFinalURI
646 rv = http->GetURI(getter_AddRefs(mFinalURI));
647 if (NS_FAILED(rv))
648 return rv;
649 http->GetResponseHeader(NS_LITERAL_CSTRING("Etag"), mPartialValidator);
650 if (StringBeginsWith(mPartialValidator, NS_LITERAL_CSTRING("W/")))
651 mPartialValidator.Truncate(); // don't use weak validators
652 if (mPartialValidator.IsEmpty())
653 http->GetResponseHeader(NS_LITERAL_CSTRING("Last-Modified"), mPartialValidator);
655 if (code == 206) {
656 // OK, read the Content-Range header to determine the total size of this
657 // download file.
658 nsAutoCString buf;
659 rv = http->GetResponseHeader(NS_LITERAL_CSTRING("Content-Range"), buf);
660 if (NS_FAILED(rv))
661 return rv;
662 int32_t slash = buf.FindChar('/');
663 if (slash == kNotFound) {
664 NS_WARNING("server returned invalid Content-Range header!");
665 return NS_ERROR_UNEXPECTED;
666 }
667 if (PR_sscanf(buf.get() + slash + 1, "%lld", (int64_t *) &mTotalSize) != 1)
668 return NS_ERROR_UNEXPECTED;
669 } else {
670 rv = http->GetContentLength(&mTotalSize);
671 if (NS_FAILED(rv))
672 return rv;
673 // We need to know the total size of the thing we're trying to download.
674 if (mTotalSize == int64_t(-1)) {
675 NS_WARNING("server returned no content-length header!");
676 return NS_ERROR_UNEXPECTED;
677 }
678 // Need to truncate (or create, if it doesn't exist) the file since we
679 // are downloading the whole thing.
680 WriteToFile(mDest, nullptr, 0, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE);
681 mCurrentSize = 0;
682 }
684 // Notify observer that we are starting...
685 rv = CallOnStartRequest();
686 if (NS_FAILED(rv))
687 return rv;
688 }
690 // Adjust mChunkSize accordingly if mCurrentSize is close to mTotalSize.
691 int64_t diff = mTotalSize - mCurrentSize;
692 if (diff <= int64_t(0)) {
693 NS_WARNING("about to set a bogus chunk size; giving up");
694 return NS_ERROR_UNEXPECTED;
695 }
697 if (diff < int64_t(mChunkSize))
698 mChunkSize = uint32_t(diff);
700 mChunk = new char[mChunkSize];
701 if (!mChunk)
702 rv = NS_ERROR_OUT_OF_MEMORY;
704 return rv;
705 }
707 NS_IMETHODIMP
708 nsIncrementalDownload::OnStopRequest(nsIRequest *request,
709 nsISupports *context,
710 nsresult status)
711 {
712 // Not a real error; just a trick to kill off the channel without our
713 // listener having to care.
714 if (status == NS_ERROR_DOWNLOAD_NOT_PARTIAL)
715 return NS_OK;
717 // Not a real error; just a trick used to suppress OnDataAvailable calls.
718 if (status == NS_ERROR_DOWNLOAD_COMPLETE)
719 status = NS_OK;
721 if (NS_SUCCEEDED(mStatus))
722 mStatus = status;
724 if (mChunk) {
725 if (NS_SUCCEEDED(mStatus))
726 mStatus = FlushChunk();
728 mChunk = nullptr; // deletes memory
729 mChunkLen = 0;
730 UpdateProgress();
731 }
733 mChannel = nullptr;
735 // Notify listener if we hit an error or finished
736 if (NS_FAILED(mStatus) || mCurrentSize == mTotalSize) {
737 CallOnStopRequest();
738 return NS_OK;
739 }
741 return StartTimer(mInterval); // Do next chunk
742 }
744 // nsIStreamListener
746 NS_IMETHODIMP
747 nsIncrementalDownload::OnDataAvailable(nsIRequest *request,
748 nsISupports *context,
749 nsIInputStream *input,
750 uint64_t offset,
751 uint32_t count)
752 {
753 while (count) {
754 uint32_t space = mChunkSize - mChunkLen;
755 uint32_t n, len = std::min(space, count);
757 nsresult rv = input->Read(mChunk + mChunkLen, len, &n);
758 if (NS_FAILED(rv))
759 return rv;
760 if (n != len)
761 return NS_ERROR_UNEXPECTED;
763 count -= n;
764 mChunkLen += n;
766 if (mChunkLen == mChunkSize) {
767 rv = FlushChunk();
768 if (NS_FAILED(rv))
769 return rv;
770 }
771 }
773 if (PR_Now() > mLastProgressUpdate + UPDATE_PROGRESS_INTERVAL)
774 UpdateProgress();
776 return NS_OK;
777 }
779 // nsIObserver
781 NS_IMETHODIMP
782 nsIncrementalDownload::Observe(nsISupports *subject, const char *topic,
783 const char16_t *data)
784 {
785 if (strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
786 Cancel(NS_ERROR_ABORT);
788 // Since the app is shutting down, we need to go ahead and notify our
789 // observer here. Otherwise, we would notify them after XPCOM has been
790 // shutdown or not at all.
791 CallOnStopRequest();
792 }
793 else if (strcmp(topic, NS_TIMER_CALLBACK_TOPIC) == 0) {
794 mTimer = nullptr;
795 nsresult rv = ProcessTimeout();
796 if (NS_FAILED(rv))
797 Cancel(rv);
798 }
799 return NS_OK;
800 }
802 // nsIInterfaceRequestor
804 NS_IMETHODIMP
805 nsIncrementalDownload::GetInterface(const nsIID &iid, void **result)
806 {
807 if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) {
808 NS_ADDREF_THIS();
809 *result = static_cast<nsIChannelEventSink *>(this);
810 return NS_OK;
811 }
813 nsCOMPtr<nsIInterfaceRequestor> ir = do_QueryInterface(mObserver);
814 if (ir)
815 return ir->GetInterface(iid, result);
817 return NS_ERROR_NO_INTERFACE;
818 }
820 nsresult
821 nsIncrementalDownload::ClearRequestHeader(nsIHttpChannel *channel)
822 {
823 NS_ENSURE_ARG(channel);
825 // We don't support encodings -- they make the Content-Length not equal
826 // to the actual size of the data.
827 return channel->SetRequestHeader(NS_LITERAL_CSTRING("Accept-Encoding"),
828 NS_LITERAL_CSTRING(""), false);
829 }
831 // nsIChannelEventSink
833 NS_IMETHODIMP
834 nsIncrementalDownload::AsyncOnChannelRedirect(nsIChannel *oldChannel,
835 nsIChannel *newChannel,
836 uint32_t flags,
837 nsIAsyncVerifyRedirectCallback *cb)
838 {
839 // In response to a redirect, we need to propagate the Range header. See bug
840 // 311595. Any failure code returned from this function aborts the redirect.
842 nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(oldChannel);
843 NS_ENSURE_STATE(http);
845 nsCOMPtr<nsIHttpChannel> newHttpChannel = do_QueryInterface(newChannel);
846 NS_ENSURE_STATE(newHttpChannel);
848 NS_NAMED_LITERAL_CSTRING(rangeHdr, "Range");
850 nsresult rv = ClearRequestHeader(newHttpChannel);
851 if (NS_FAILED(rv))
852 return rv;
854 // If we didn't have a Range header, then we must be doing a full download.
855 nsAutoCString rangeVal;
856 http->GetRequestHeader(rangeHdr, rangeVal);
857 if (!rangeVal.IsEmpty()) {
858 rv = newHttpChannel->SetRequestHeader(rangeHdr, rangeVal, false);
859 NS_ENSURE_SUCCESS(rv, rv);
860 }
862 // A redirection changes the validator
863 mPartialValidator.Truncate();
865 if (mCacheBust) {
866 newHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Cache-Control"),
867 NS_LITERAL_CSTRING("no-cache"), false);
868 newHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Pragma"),
869 NS_LITERAL_CSTRING("no-cache"), false);
870 }
872 // Prepare to receive callback
873 mRedirectCallback = cb;
874 mNewRedirectChannel = newChannel;
876 // Give the observer a chance to see this redirect notification.
877 nsCOMPtr<nsIChannelEventSink> sink = do_GetInterface(mObserver);
878 if (sink) {
879 rv = sink->AsyncOnChannelRedirect(oldChannel, newChannel, flags, this);
880 if (NS_FAILED(rv)) {
881 mRedirectCallback = nullptr;
882 mNewRedirectChannel = nullptr;
883 }
884 return rv;
885 }
886 (void) OnRedirectVerifyCallback(NS_OK);
887 return NS_OK;
888 }
890 NS_IMETHODIMP
891 nsIncrementalDownload::OnRedirectVerifyCallback(nsresult result)
892 {
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))
898 mChannel = mNewRedirectChannel;
900 mRedirectCallback->OnRedirectVerifyCallback(result);
901 mRedirectCallback = nullptr;
902 mNewRedirectChannel = nullptr;
903 return NS_OK;
904 }
906 extern nsresult
907 net_NewIncrementalDownload(nsISupports *outer, const nsIID &iid, void **result)
908 {
909 if (outer)
910 return NS_ERROR_NO_AGGREGATION;
912 nsIncrementalDownload *d = new nsIncrementalDownload();
913 if (!d)
914 return NS_ERROR_OUT_OF_MEMORY;
916 NS_ADDREF(d);
917 nsresult rv = d->QueryInterface(iid, result);
918 NS_RELEASE(d);
919 return rv;
920 }