Bug 458256. Use LoadLibraryW instead of LoadLibrary (patch by DougT). r+sr=vlad
[wine-gecko.git] / netwerk / base / src / nsIncrementalDownload.cpp
blob748b13b898fad950f34a2f6c110cb34a4c00ae1b
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 /* ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
14 * License.
16 * The Original Code is mozilla.org code.
18 * The Initial Developer of the Original Code is Google Inc.
19 * Portions created by the Initial Developer are Copyright (C) 2005
20 * the Initial Developer. All Rights Reserved.
22 * Contributor(s):
23 * Darin Fisher <darin@meer.net>
25 * Alternatively, the contents of this file may be used under the terms of
26 * either the GNU General Public License Version 2 or later (the "GPL"), or
27 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
37 * ***** END LICENSE BLOCK ***** */
39 #include "nsIIncrementalDownload.h"
40 #include "nsIRequestObserver.h"
41 #include "nsIProgressEventSink.h"
42 #include "nsIChannelEventSink.h"
43 #include "nsIInterfaceRequestor.h"
44 #include "nsIObserverService.h"
45 #include "nsIObserver.h"
46 #include "nsIPropertyBag2.h"
47 #include "nsIServiceManager.h"
48 #include "nsILocalFile.h"
49 #include "nsITimer.h"
50 #include "nsInt64.h"
51 #include "nsNetUtil.h"
52 #include "nsAutoPtr.h"
53 #include "nsWeakReference.h"
54 #include "nsChannelProperties.h"
55 #include "prio.h"
56 #include "prprf.h"
58 // Error code used internally by the incremental downloader to cancel the
59 // network channel when the download is already complete.
60 #define NS_ERROR_DOWNLOAD_COMPLETE \
61 NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_GENERAL, 1)
63 // Error code used internally by the incremental downloader to cancel the
64 // network channel when the response to a range request is 200 instead of 206.
65 #define NS_ERROR_DOWNLOAD_NOT_PARTIAL \
66 NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_GENERAL, 2)
68 // Default values used to initialize a nsIncrementalDownload object.
69 #define DEFAULT_CHUNK_SIZE (4096 * 16) // bytes
70 #define DEFAULT_INTERVAL 60 // seconds
72 // Number of times to retry a failed byte-range request.
73 #define MAX_RETRY_COUNT 20
75 //-----------------------------------------------------------------------------
77 static nsresult
78 WriteToFile(nsILocalFile *lf, const char *data, PRUint32 len, PRInt32 flags)
80 PRFileDesc *fd;
81 nsresult rv = lf->OpenNSPRFileDesc(flags, 0600, &fd);
82 if (NS_FAILED(rv))
83 return rv;
85 if (len)
86 rv = PR_Write(fd, data, len) == PRInt32(len) ? NS_OK : NS_ERROR_FAILURE;
88 PR_Close(fd);
89 return rv;
92 static nsresult
93 AppendToFile(nsILocalFile *lf, const char *data, PRUint32 len)
95 PRInt32 flags = PR_WRONLY | PR_CREATE_FILE | PR_APPEND;
96 return WriteToFile(lf, data, len, flags);
99 // maxSize may be -1 if unknown
100 static void
101 MakeRangeSpec(const nsInt64 &size, const nsInt64 &maxSize, PRInt32 chunkSize,
102 PRBool fetchRemaining, nsCString &rangeSpec)
104 rangeSpec.AssignLiteral("bytes=");
105 rangeSpec.AppendInt(PRInt64(size));
106 rangeSpec.Append('-');
108 if (fetchRemaining)
109 return;
111 nsInt64 end = size + nsInt64(chunkSize);
112 if (maxSize != nsInt64(-1) && end > maxSize)
113 end = maxSize;
114 end -= 1;
116 rangeSpec.AppendInt(PRInt64(end));
119 //-----------------------------------------------------------------------------
121 class nsIncrementalDownload : public nsIIncrementalDownload
122 , public nsIStreamListener
123 , public nsIObserver
124 , public nsIInterfaceRequestor
125 , public nsIChannelEventSink
126 , public nsSupportsWeakReference
128 public:
129 NS_DECL_ISUPPORTS
130 NS_DECL_NSIREQUEST
131 NS_DECL_NSIINCREMENTALDOWNLOAD
132 NS_DECL_NSIREQUESTOBSERVER
133 NS_DECL_NSISTREAMLISTENER
134 NS_DECL_NSIOBSERVER
135 NS_DECL_NSIINTERFACEREQUESTOR
136 NS_DECL_NSICHANNELEVENTSINK
138 nsIncrementalDownload();
140 private:
141 ~nsIncrementalDownload() {}
142 nsresult FlushChunk();
143 nsresult CallOnStartRequest();
144 void CallOnStopRequest();
145 nsresult StartTimer(PRInt32 interval);
146 nsresult ProcessTimeout();
147 nsresult ReadCurrentSize();
148 nsresult ClearRequestHeader(nsIHttpChannel *channel);
150 nsCOMPtr<nsIRequestObserver> mObserver;
151 nsCOMPtr<nsISupports> mObserverContext;
152 nsCOMPtr<nsIProgressEventSink> mProgressSink;
153 nsCOMPtr<nsIURI> mURI;
154 nsCOMPtr<nsIURI> mFinalURI;
155 nsCOMPtr<nsILocalFile> mDest;
156 nsCOMPtr<nsIChannel> mChannel;
157 nsCOMPtr<nsITimer> mTimer;
158 nsAutoArrayPtr<char> mChunk;
159 PRInt32 mChunkLen;
160 PRInt32 mChunkSize;
161 PRInt32 mInterval;
162 nsInt64 mTotalSize;
163 nsInt64 mCurrentSize;
164 PRUint32 mLoadFlags;
165 PRInt32 mNonPartialCount;
166 nsresult mStatus;
167 PRPackedBool mIsPending;
168 PRPackedBool mDidOnStartRequest;
171 nsIncrementalDownload::nsIncrementalDownload()
172 : mChunkLen(0)
173 , mChunkSize(DEFAULT_CHUNK_SIZE)
174 , mInterval(DEFAULT_INTERVAL)
175 , mTotalSize(-1)
176 , mCurrentSize(-1)
177 , mLoadFlags(LOAD_NORMAL)
178 , mNonPartialCount(0)
179 , mStatus(NS_OK)
180 , mIsPending(PR_FALSE)
181 , mDidOnStartRequest(PR_FALSE)
185 nsresult
186 nsIncrementalDownload::FlushChunk()
188 NS_ASSERTION(mTotalSize != nsInt64(-1), "total size should be known");
190 if (mChunkLen == 0)
191 return NS_OK;
193 nsresult rv = AppendToFile(mDest, mChunk, mChunkLen);
194 if (NS_FAILED(rv))
195 return rv;
197 mCurrentSize += nsInt64(mChunkLen);
198 mChunkLen = 0;
200 if (mProgressSink)
201 mProgressSink->OnProgress(this, mObserverContext,
202 PRUint64(PRInt64(mCurrentSize)),
203 PRUint64(PRInt64(mTotalSize)));
204 return NS_OK;
207 nsresult
208 nsIncrementalDownload::CallOnStartRequest()
210 if (!mObserver || mDidOnStartRequest)
211 return NS_OK;
213 mDidOnStartRequest = PR_TRUE;
214 return mObserver->OnStartRequest(this, mObserverContext);
217 void
218 nsIncrementalDownload::CallOnStopRequest()
220 if (!mObserver)
221 return;
223 // Ensure that OnStartRequest is always called once before OnStopRequest.
224 nsresult rv = CallOnStartRequest();
225 if (NS_SUCCEEDED(mStatus))
226 mStatus = rv;
228 mIsPending = PR_FALSE;
230 mObserver->OnStopRequest(this, mObserverContext, mStatus);
231 mObserver = nsnull;
232 mObserverContext = nsnull;
235 nsresult
236 nsIncrementalDownload::StartTimer(PRInt32 interval)
238 nsresult rv;
239 mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
240 if (NS_FAILED(rv))
241 return rv;
243 return mTimer->Init(this, interval * 1000, nsITimer::TYPE_ONE_SHOT);
246 nsresult
247 nsIncrementalDownload::ProcessTimeout()
249 NS_ASSERTION(!mChannel, "how can we have a channel?");
251 // Handle existing error conditions
252 if (NS_FAILED(mStatus)) {
253 CallOnStopRequest();
254 return NS_OK;
257 // Fetch next chunk
259 nsCOMPtr<nsIChannel> channel;
260 nsresult rv = NS_NewChannel(getter_AddRefs(channel), mFinalURI, nsnull,
261 nsnull, this, mLoadFlags);
262 if (NS_FAILED(rv))
263 return rv;
265 nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(channel, &rv);
266 if (NS_FAILED(rv))
267 return rv;
269 NS_ASSERTION(mCurrentSize != nsInt64(-1),
270 "we should know the current file size by now");
272 rv = ClearRequestHeader(http);
273 if (NS_FAILED(rv))
274 return rv;
276 // Don't bother making a range request if we are just going to fetch the
277 // entire document.
278 if (mInterval || mCurrentSize != nsInt64(0)) {
279 nsCAutoString range;
280 MakeRangeSpec(mCurrentSize, mTotalSize, mChunkSize, mInterval == 0, range);
282 rv = http->SetRequestHeader(NS_LITERAL_CSTRING("Range"), range, PR_FALSE);
283 if (NS_FAILED(rv))
284 return rv;
287 rv = channel->AsyncOpen(this, nsnull);
288 if (NS_FAILED(rv))
289 return rv;
291 // Wait to assign mChannel when we know we are going to succeed. This is
292 // important because we don't want to introduce a reference cycle between
293 // mChannel and this until we know for a fact that AsyncOpen has succeeded,
294 // thus ensuring that our stream listener methods will be invoked.
295 mChannel = channel;
296 return NS_OK;
299 // Reads the current file size and validates it.
300 nsresult
301 nsIncrementalDownload::ReadCurrentSize()
303 nsInt64 size;
304 nsresult rv = mDest->GetFileSize((PRInt64 *) &size);
305 if (rv == NS_ERROR_FILE_NOT_FOUND ||
306 rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
307 mCurrentSize = 0;
308 return NS_OK;
310 if (NS_FAILED(rv))
311 return rv;
313 mCurrentSize = size;
314 return NS_OK;
317 // nsISupports
319 NS_IMPL_ISUPPORTS8(nsIncrementalDownload,
320 nsIIncrementalDownload,
321 nsIRequest,
322 nsIStreamListener,
323 nsIRequestObserver,
324 nsIObserver,
325 nsIInterfaceRequestor,
326 nsIChannelEventSink,
327 nsISupportsWeakReference)
329 // nsIRequest
331 NS_IMETHODIMP
332 nsIncrementalDownload::GetName(nsACString &name)
334 NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_INITIALIZED);
336 return mURI->GetSpec(name);
339 NS_IMETHODIMP
340 nsIncrementalDownload::IsPending(PRBool *isPending)
342 *isPending = mIsPending;
343 return NS_OK;
346 NS_IMETHODIMP
347 nsIncrementalDownload::GetStatus(nsresult *status)
349 *status = mStatus;
350 return NS_OK;
353 NS_IMETHODIMP
354 nsIncrementalDownload::Cancel(nsresult status)
356 NS_ENSURE_ARG(NS_FAILED(status));
358 // Ignore this cancelation if we're already canceled.
359 if (NS_FAILED(mStatus))
360 return NS_OK;
362 mStatus = status;
364 // Nothing more to do if callbacks aren't pending.
365 if (!mIsPending)
366 return NS_OK;
368 if (mChannel) {
369 mChannel->Cancel(mStatus);
370 NS_ASSERTION(!mTimer, "what is this timer object doing here?");
372 else {
373 // dispatch a timer callback event to drive invoking our listener's
374 // OnStopRequest.
375 if (mTimer)
376 mTimer->Cancel();
377 StartTimer(0);
380 return NS_OK;
383 NS_IMETHODIMP
384 nsIncrementalDownload::Suspend()
386 return NS_ERROR_NOT_IMPLEMENTED;
389 NS_IMETHODIMP
390 nsIncrementalDownload::Resume()
392 return NS_ERROR_NOT_IMPLEMENTED;
395 NS_IMETHODIMP
396 nsIncrementalDownload::GetLoadFlags(nsLoadFlags *loadFlags)
398 *loadFlags = mLoadFlags;
399 return NS_OK;
402 NS_IMETHODIMP
403 nsIncrementalDownload::SetLoadFlags(nsLoadFlags loadFlags)
405 mLoadFlags = loadFlags;
406 return NS_OK;
409 NS_IMETHODIMP
410 nsIncrementalDownload::GetLoadGroup(nsILoadGroup **loadGroup)
412 return NS_ERROR_NOT_IMPLEMENTED;
415 NS_IMETHODIMP
416 nsIncrementalDownload::SetLoadGroup(nsILoadGroup *loadGroup)
418 return NS_ERROR_NOT_IMPLEMENTED;
421 // nsIIncrementalDownload
423 NS_IMETHODIMP
424 nsIncrementalDownload::Init(nsIURI *uri, nsIFile *dest,
425 PRInt32 chunkSize, PRInt32 interval)
427 // Keep it simple: only allow initialization once
428 NS_ENSURE_FALSE(mURI, NS_ERROR_ALREADY_INITIALIZED);
430 mDest = do_QueryInterface(dest);
431 NS_ENSURE_ARG(mDest);
433 mURI = uri;
434 mFinalURI = uri;
436 if (chunkSize > 0)
437 mChunkSize = chunkSize;
438 if (interval >= 0)
439 mInterval = interval;
440 return NS_OK;
443 NS_IMETHODIMP
444 nsIncrementalDownload::GetURI(nsIURI **result)
446 NS_IF_ADDREF(*result = mURI);
447 return NS_OK;
450 NS_IMETHODIMP
451 nsIncrementalDownload::GetFinalURI(nsIURI **result)
453 NS_IF_ADDREF(*result = mFinalURI);
454 return NS_OK;
457 NS_IMETHODIMP
458 nsIncrementalDownload::GetDestination(nsIFile **result)
460 if (!mDest) {
461 *result = nsnull;
462 return NS_OK;
464 // Return a clone of mDest so that callers may modify the resulting nsIFile
465 // without corrupting our internal object. This also works around the fact
466 // that some nsIFile impls may cache the result of stat'ing the filesystem.
467 return mDest->Clone(result);
470 NS_IMETHODIMP
471 nsIncrementalDownload::GetTotalSize(PRInt64 *result)
473 *result = mTotalSize;
474 return NS_OK;
477 NS_IMETHODIMP
478 nsIncrementalDownload::GetCurrentSize(PRInt64 *result)
480 *result = mCurrentSize;
481 return NS_OK;
484 NS_IMETHODIMP
485 nsIncrementalDownload::Start(nsIRequestObserver *observer,
486 nsISupports *context)
488 NS_ENSURE_ARG(observer);
489 NS_ENSURE_FALSE(mIsPending, NS_ERROR_IN_PROGRESS);
491 // Observe system shutdown so we can be sure to release any reference held
492 // between ourselves and the timer. We have the observer service hold a weak
493 // reference to us, so that we don't have to worry about calling
494 // RemoveObserver. XXX(darin): The timer code should do this for us.
495 nsCOMPtr<nsIObserverService> obs =
496 do_GetService("@mozilla.org/observer-service;1");
497 if (obs)
498 obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, PR_TRUE);
500 nsresult rv = ReadCurrentSize();
501 if (NS_FAILED(rv))
502 return rv;
504 rv = StartTimer(0);
505 if (NS_FAILED(rv))
506 return rv;
508 mObserver = observer;
509 mObserverContext = context;
510 mProgressSink = do_QueryInterface(observer); // ok if null
512 mIsPending = PR_TRUE;
513 return NS_OK;
516 // nsIRequestObserver
518 NS_IMETHODIMP
519 nsIncrementalDownload::OnStartRequest(nsIRequest *request,
520 nsISupports *context)
522 nsresult rv;
524 nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(request, &rv);
525 if (NS_FAILED(rv))
526 return rv;
528 // Ensure that we are receiving a 206 response.
529 PRUint32 code;
530 rv = http->GetResponseStatus(&code);
531 if (NS_FAILED(rv))
532 return rv;
533 if (code != 206) {
534 // We may already have the entire file downloaded, in which case
535 // our request for a range beyond the end of the file would have
536 // been met with an error response code.
537 if (code == 416 && mTotalSize == nsInt64(-1)) {
538 mTotalSize = mCurrentSize;
539 // Return an error code here to suppress OnDataAvailable.
540 return NS_ERROR_DOWNLOAD_COMPLETE;
542 // The server may have decided to give us all of the data in one chunk. If
543 // we requested a partial range, then we don't want to download all of the
544 // data at once. So, we'll just try again, but if this keeps happening then
545 // we'll eventually give up.
546 if (code == 200) {
547 if (mInterval) {
548 mChannel = nsnull;
549 if (++mNonPartialCount > MAX_RETRY_COUNT) {
550 NS_WARNING("unable to fetch a byte range; giving up");
551 return NS_ERROR_FAILURE;
553 // Increase delay with each failure.
554 StartTimer(mInterval * mNonPartialCount);
555 return NS_ERROR_DOWNLOAD_NOT_PARTIAL;
557 // Since we have been asked to download the rest of the file, we can deal
558 // with a 200 response. This may result in downloading the beginning of
559 // the file again, but that can't really be helped.
560 } else {
561 NS_WARNING("server response was unexpected");
562 return NS_ERROR_UNEXPECTED;
564 } else {
565 // We got a partial response, so clear this counter in case the next chunk
566 // results in a 200 response.
567 mNonPartialCount = 0;
570 // Do special processing after the first response.
571 if (mTotalSize == nsInt64(-1)) {
572 // Update knowledge of mFinalURI
573 rv = http->GetURI(getter_AddRefs(mFinalURI));
574 if (NS_FAILED(rv))
575 return rv;
577 if (code == 206) {
578 // OK, read the Content-Range header to determine the total size of this
579 // download file.
580 nsCAutoString buf;
581 rv = http->GetResponseHeader(NS_LITERAL_CSTRING("Content-Range"), buf);
582 if (NS_FAILED(rv))
583 return rv;
584 PRInt32 slash = buf.FindChar('/');
585 if (slash == kNotFound) {
586 NS_WARNING("server returned invalid Content-Range header!");
587 return NS_ERROR_UNEXPECTED;
589 if (PR_sscanf(buf.get() + slash + 1, "%lld", (PRInt64 *) &mTotalSize) != 1)
590 return NS_ERROR_UNEXPECTED;
591 } else {
592 // Use nsIPropertyBag2 to fetch the content length as it exposes the
593 // value as a 64-bit number.
594 nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(request, &rv);
595 if (NS_FAILED(rv))
596 return rv;
597 rv = props->GetPropertyAsInt64(NS_CHANNEL_PROP_CONTENT_LENGTH,
598 &mTotalSize.mValue);
599 // We need to know the total size of the thing we're trying to download.
600 if (mTotalSize == nsInt64(-1)) {
601 NS_WARNING("server returned no content-length header!");
602 return NS_ERROR_UNEXPECTED;
604 // Need to truncate (or create, if it doesn't exist) the file since we
605 // are downloading the whole thing.
606 WriteToFile(mDest, nsnull, 0, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE);
607 mCurrentSize = 0;
610 // Notify observer that we are starting...
611 rv = CallOnStartRequest();
612 if (NS_FAILED(rv))
613 return rv;
616 // Adjust mChunkSize accordingly if mCurrentSize is close to mTotalSize.
617 nsInt64 diff = mTotalSize - mCurrentSize;
618 if (diff <= nsInt64(0)) {
619 NS_WARNING("about to set a bogus chunk size; giving up");
620 return NS_ERROR_UNEXPECTED;
623 if (diff < nsInt64(mChunkSize))
624 mChunkSize = PRUint32(diff);
626 mChunk = new char[mChunkSize];
627 if (!mChunk)
628 rv = NS_ERROR_OUT_OF_MEMORY;
630 return rv;
633 NS_IMETHODIMP
634 nsIncrementalDownload::OnStopRequest(nsIRequest *request,
635 nsISupports *context,
636 nsresult status)
638 // Not a real error; just a trick to kill off the channel without our
639 // listener having to care.
640 if (status == NS_ERROR_DOWNLOAD_NOT_PARTIAL)
641 return NS_OK;
643 // Not a real error; just a trick used to suppress OnDataAvailable calls.
644 if (status == NS_ERROR_DOWNLOAD_COMPLETE)
645 status = NS_OK;
647 if (NS_SUCCEEDED(mStatus))
648 mStatus = status;
650 if (mChunk) {
651 if (NS_SUCCEEDED(mStatus))
652 mStatus = FlushChunk();
654 mChunk = nsnull; // deletes memory
655 mChunkLen = 0;
658 mChannel = nsnull;
660 // Notify listener if we hit an error or finished
661 if (NS_FAILED(mStatus) || mCurrentSize == mTotalSize) {
662 CallOnStopRequest();
663 return NS_OK;
666 return StartTimer(mInterval); // Do next chunk
669 // nsIStreamListener
671 NS_IMETHODIMP
672 nsIncrementalDownload::OnDataAvailable(nsIRequest *request,
673 nsISupports *context,
674 nsIInputStream *input,
675 PRUint32 offset,
676 PRUint32 count)
678 while (count) {
679 PRUint32 space = mChunkSize - mChunkLen;
680 PRUint32 n, len = PR_MIN(space, count);
682 nsresult rv = input->Read(mChunk + mChunkLen, len, &n);
683 if (NS_FAILED(rv))
684 return rv;
685 if (n != len)
686 return NS_ERROR_UNEXPECTED;
688 count -= n;
689 mChunkLen += n;
691 if (mChunkLen == mChunkSize)
692 FlushChunk();
695 return NS_OK;
698 // nsIObserver
700 NS_IMETHODIMP
701 nsIncrementalDownload::Observe(nsISupports *subject, const char *topic,
702 const PRUnichar *data)
704 if (strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
705 Cancel(NS_ERROR_ABORT);
707 // Since the app is shutting down, we need to go ahead and notify our
708 // observer here. Otherwise, we would notify them after XPCOM has been
709 // shutdown or not at all.
710 CallOnStopRequest();
712 else if (strcmp(topic, NS_TIMER_CALLBACK_TOPIC) == 0) {
713 mTimer = nsnull;
714 nsresult rv = ProcessTimeout();
715 if (NS_FAILED(rv))
716 Cancel(rv);
718 return NS_OK;
721 // nsIInterfaceRequestor
723 NS_IMETHODIMP
724 nsIncrementalDownload::GetInterface(const nsIID &iid, void **result)
726 if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) {
727 NS_ADDREF_THIS();
728 *result = static_cast<nsIChannelEventSink *>(this);
729 return NS_OK;
732 nsCOMPtr<nsIInterfaceRequestor> ir = do_QueryInterface(mObserver);
733 if (ir)
734 return ir->GetInterface(iid, result);
736 return NS_ERROR_NO_INTERFACE;
739 nsresult
740 nsIncrementalDownload::ClearRequestHeader(nsIHttpChannel *channel)
742 NS_ENSURE_ARG(channel);
744 // We don't support encodings -- they make the Content-Length not equal
745 // to the actual size of the data.
746 return channel->SetRequestHeader(NS_LITERAL_CSTRING("Accept-Encoding"),
747 NS_LITERAL_CSTRING(""), PR_FALSE);
750 // nsIChannelEventSink
752 NS_IMETHODIMP
753 nsIncrementalDownload::OnChannelRedirect(nsIChannel *oldChannel,
754 nsIChannel *newChannel,
755 PRUint32 flags)
757 // In response to a redirect, we need to propagate the Range header. See bug
758 // 311595. Any failure code returned from this function aborts the redirect.
760 nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(oldChannel);
761 NS_ENSURE_STATE(http);
763 nsCOMPtr<nsIHttpChannel> newHttpChannel = do_QueryInterface(newChannel);
764 NS_ENSURE_STATE(newHttpChannel);
766 NS_NAMED_LITERAL_CSTRING(rangeHdr, "Range");
768 nsresult rv = ClearRequestHeader(newHttpChannel);
769 if (NS_FAILED(rv))
770 return rv;
772 // If we didn't have a Range header, then we must be doing a full download.
773 nsCAutoString rangeVal;
774 http->GetRequestHeader(rangeHdr, rangeVal);
775 if (!rangeVal.IsEmpty()) {
776 rv = newHttpChannel->SetRequestHeader(rangeHdr, rangeVal, PR_FALSE);
777 NS_ENSURE_SUCCESS(rv, rv);
780 // Give the observer a chance to see this redirect notification.
781 nsCOMPtr<nsIChannelEventSink> sink = do_GetInterface(mObserver);
782 if (sink)
783 rv = sink->OnChannelRedirect(oldChannel, newChannel, flags);
785 // Update mChannel, so we can Cancel the new channel.
786 if (NS_SUCCEEDED(rv))
787 mChannel = newChannel;
789 return rv;
792 extern NS_METHOD
793 net_NewIncrementalDownload(nsISupports *outer, const nsIID &iid, void **result)
795 if (outer)
796 return NS_ERROR_NO_AGGREGATION;
798 nsIncrementalDownload *d = new nsIncrementalDownload();
799 if (!d)
800 return NS_ERROR_OUT_OF_MEMORY;
802 NS_ADDREF(d);
803 nsresult rv = d->QueryInterface(iid, result);
804 NS_RELEASE(d);
805 return rv;