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
5 : *
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/
10 : *
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.
15 : *
16 : * The Original Code is mozilla.org code.
17 : *
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.
21 : *
22 : * Contributor(s):
23 : * Darin Fisher <darin@meer.net>
24 : *
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.
36 : *
37 : * ***** END LICENSE BLOCK ***** */
38 :
39 : #include "mozilla/Attributes.h"
40 :
41 : #include "nsIIncrementalDownload.h"
42 : #include "nsIRequestObserver.h"
43 : #include "nsIProgressEventSink.h"
44 : #include "nsIChannelEventSink.h"
45 : #include "nsIAsyncVerifyRedirectCallback.h"
46 : #include "nsIInterfaceRequestor.h"
47 : #include "nsIObserverService.h"
48 : #include "nsIObserver.h"
49 : #include "nsIPropertyBag2.h"
50 : #include "nsIServiceManager.h"
51 : #include "nsILocalFile.h"
52 : #include "nsITimer.h"
53 : #include "nsNetUtil.h"
54 : #include "nsAutoPtr.h"
55 : #include "nsWeakReference.h"
56 : #include "nsChannelProperties.h"
57 : #include "prio.h"
58 : #include "prprf.h"
59 :
60 : // Error code used internally by the incremental downloader to cancel the
61 : // network channel when the download is already complete.
62 : #define NS_ERROR_DOWNLOAD_COMPLETE \
63 : NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_GENERAL, 1)
64 :
65 : // Error code used internally by the incremental downloader to cancel the
66 : // network channel when the response to a range request is 200 instead of 206.
67 : #define NS_ERROR_DOWNLOAD_NOT_PARTIAL \
68 : NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_GENERAL, 2)
69 :
70 : // Default values used to initialize a nsIncrementalDownload object.
71 : #define DEFAULT_CHUNK_SIZE (4096 * 16) // bytes
72 : #define DEFAULT_INTERVAL 60 // seconds
73 :
74 : #define UPDATE_PROGRESS_INTERVAL PRTime(500 * PR_USEC_PER_MSEC) // 500ms
75 :
76 : // Number of times to retry a failed byte-range request.
77 : #define MAX_RETRY_COUNT 20
78 :
79 : //-----------------------------------------------------------------------------
80 :
81 : static nsresult
82 10 : WriteToFile(nsILocalFile *lf, const char *data, PRUint32 len, PRInt32 flags)
83 : {
84 : PRFileDesc *fd;
85 10 : nsresult rv = lf->OpenNSPRFileDesc(flags, 0600, &fd);
86 10 : if (NS_FAILED(rv))
87 0 : return rv;
88 :
89 10 : if (len)
90 5 : rv = PR_Write(fd, data, len) == PRInt32(len) ? NS_OK : NS_ERROR_FAILURE;
91 :
92 10 : PR_Close(fd);
93 10 : return rv;
94 : }
95 :
96 : static nsresult
97 5 : AppendToFile(nsILocalFile *lf, const char *data, PRUint32 len)
98 : {
99 5 : PRInt32 flags = PR_WRONLY | PR_CREATE_FILE | PR_APPEND;
100 5 : return WriteToFile(lf, data, len, flags);
101 : }
102 :
103 : // maxSize may be -1 if unknown
104 : static void
105 6 : MakeRangeSpec(const PRInt64 &size, const PRInt64 &maxSize, PRInt32 chunkSize,
106 : bool fetchRemaining, nsCString &rangeSpec)
107 : {
108 6 : rangeSpec.AssignLiteral("bytes=");
109 6 : rangeSpec.AppendInt(PRInt64(size));
110 6 : rangeSpec.Append('-');
111 :
112 6 : if (fetchRemaining)
113 5 : return;
114 :
115 1 : PRInt64 end = size + PRInt64(chunkSize);
116 1 : if (maxSize != PRInt64(-1) && end > maxSize)
117 0 : end = maxSize;
118 1 : end -= 1;
119 :
120 1 : rangeSpec.AppendInt(PRInt64(end));
121 : }
122 :
123 : //-----------------------------------------------------------------------------
124 :
125 : class nsIncrementalDownload MOZ_FINAL
126 : : public nsIIncrementalDownload
127 : , public nsIStreamListener
128 : , public nsIObserver
129 : , public nsIInterfaceRequestor
130 : , public nsIChannelEventSink
131 : , public nsSupportsWeakReference
132 : , public nsIAsyncVerifyRedirectCallback
133 : {
134 : public:
135 : NS_DECL_ISUPPORTS
136 : NS_DECL_NSIREQUEST
137 : NS_DECL_NSIINCREMENTALDOWNLOAD
138 : NS_DECL_NSIREQUESTOBSERVER
139 : NS_DECL_NSISTREAMLISTENER
140 : NS_DECL_NSIOBSERVER
141 : NS_DECL_NSIINTERFACEREQUESTOR
142 : NS_DECL_NSICHANNELEVENTSINK
143 : NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
144 :
145 : nsIncrementalDownload();
146 :
147 : private:
148 13 : ~nsIncrementalDownload() {}
149 : nsresult FlushChunk();
150 : void UpdateProgress();
151 : nsresult CallOnStartRequest();
152 : void CallOnStopRequest();
153 : nsresult StartTimer(PRInt32 interval);
154 : nsresult ProcessTimeout();
155 : nsresult ReadCurrentSize();
156 : nsresult ClearRequestHeader(nsIHttpChannel *channel);
157 :
158 : nsCOMPtr<nsIRequestObserver> mObserver;
159 : nsCOMPtr<nsISupports> mObserverContext;
160 : nsCOMPtr<nsIProgressEventSink> mProgressSink;
161 : nsCOMPtr<nsIURI> mURI;
162 : nsCOMPtr<nsIURI> mFinalURI;
163 : nsCOMPtr<nsILocalFile> mDest;
164 : nsCOMPtr<nsIChannel> mChannel;
165 : nsCOMPtr<nsITimer> mTimer;
166 : nsAutoArrayPtr<char> mChunk;
167 : PRInt32 mChunkLen;
168 : PRInt32 mChunkSize;
169 : PRInt32 mInterval;
170 : PRInt64 mTotalSize;
171 : PRInt64 mCurrentSize;
172 : PRUint32 mLoadFlags;
173 : PRInt32 mNonPartialCount;
174 : nsresult mStatus;
175 : bool mIsPending;
176 : bool mDidOnStartRequest;
177 : PRTime mLastProgressUpdate;
178 : nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback;
179 : nsCOMPtr<nsIChannel> mNewRedirectChannel;
180 : };
181 :
182 13 : nsIncrementalDownload::nsIncrementalDownload()
183 : : mChunkLen(0)
184 : , mChunkSize(DEFAULT_CHUNK_SIZE)
185 : , mInterval(DEFAULT_INTERVAL)
186 : , mTotalSize(-1)
187 : , mCurrentSize(-1)
188 : , mLoadFlags(LOAD_NORMAL)
189 : , mNonPartialCount(0)
190 : , mStatus(NS_OK)
191 : , mIsPending(false)
192 : , mDidOnStartRequest(false)
193 : , mLastProgressUpdate(0)
194 : , mRedirectCallback(nsnull)
195 13 : , mNewRedirectChannel(nsnull)
196 : {
197 13 : }
198 :
199 : nsresult
200 10 : nsIncrementalDownload::FlushChunk()
201 : {
202 10 : NS_ASSERTION(mTotalSize != PRInt64(-1), "total size should be known");
203 :
204 10 : if (mChunkLen == 0)
205 5 : return NS_OK;
206 :
207 5 : nsresult rv = AppendToFile(mDest, mChunk, mChunkLen);
208 5 : if (NS_FAILED(rv))
209 0 : return rv;
210 :
211 5 : mCurrentSize += PRInt64(mChunkLen);
212 5 : mChunkLen = 0;
213 :
214 5 : return NS_OK;
215 : }
216 :
217 : void
218 10 : nsIncrementalDownload::UpdateProgress()
219 : {
220 10 : mLastProgressUpdate = PR_Now();
221 :
222 10 : if (mProgressSink)
223 10 : mProgressSink->OnProgress(this, mObserverContext,
224 : PRUint64(PRInt64(mCurrentSize) + mChunkLen),
225 10 : PRUint64(PRInt64(mTotalSize)));
226 10 : }
227 :
228 : nsresult
229 18 : nsIncrementalDownload::CallOnStartRequest()
230 : {
231 18 : if (!mObserver || mDidOnStartRequest)
232 5 : return NS_OK;
233 :
234 13 : mDidOnStartRequest = true;
235 13 : return mObserver->OnStartRequest(this, mObserverContext);
236 : }
237 :
238 : void
239 14 : nsIncrementalDownload::CallOnStopRequest()
240 : {
241 14 : if (!mObserver)
242 1 : return;
243 :
244 : // Ensure that OnStartRequest is always called once before OnStopRequest.
245 13 : nsresult rv = CallOnStartRequest();
246 13 : if (NS_SUCCEEDED(mStatus))
247 10 : mStatus = rv;
248 :
249 13 : mIsPending = false;
250 :
251 13 : mObserver->OnStopRequest(this, mObserverContext, mStatus);
252 13 : mObserver = nsnull;
253 13 : mObserverContext = nsnull;
254 : }
255 :
256 : nsresult
257 14 : nsIncrementalDownload::StartTimer(PRInt32 interval)
258 : {
259 : nsresult rv;
260 14 : mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
261 14 : if (NS_FAILED(rv))
262 0 : return rv;
263 :
264 14 : return mTimer->Init(this, interval * 1000, nsITimer::TYPE_ONE_SHOT);
265 : }
266 :
267 : nsresult
268 13 : nsIncrementalDownload::ProcessTimeout()
269 : {
270 13 : NS_ASSERTION(!mChannel, "how can we have a channel?");
271 :
272 : // Handle existing error conditions
273 13 : if (NS_FAILED(mStatus)) {
274 1 : CallOnStopRequest();
275 1 : return NS_OK;
276 : }
277 :
278 : // Fetch next chunk
279 :
280 24 : nsCOMPtr<nsIChannel> channel;
281 12 : nsresult rv = NS_NewChannel(getter_AddRefs(channel), mFinalURI, nsnull,
282 12 : nsnull, this, mLoadFlags);
283 12 : if (NS_FAILED(rv))
284 0 : return rv;
285 :
286 24 : nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(channel, &rv);
287 12 : if (NS_FAILED(rv))
288 0 : return rv;
289 :
290 12 : NS_ASSERTION(mCurrentSize != PRInt64(-1),
291 : "we should know the current file size by now");
292 :
293 12 : rv = ClearRequestHeader(http);
294 12 : if (NS_FAILED(rv))
295 0 : return rv;
296 :
297 : // Don't bother making a range request if we are just going to fetch the
298 : // entire document.
299 12 : if (mInterval || mCurrentSize != PRInt64(0)) {
300 12 : nsCAutoString range;
301 6 : MakeRangeSpec(mCurrentSize, mTotalSize, mChunkSize, mInterval == 0, range);
302 :
303 6 : rv = http->SetRequestHeader(NS_LITERAL_CSTRING("Range"), range, false);
304 6 : if (NS_FAILED(rv))
305 0 : return rv;
306 : }
307 :
308 12 : rv = channel->AsyncOpen(this, nsnull);
309 12 : if (NS_FAILED(rv))
310 0 : return rv;
311 :
312 : // Wait to assign mChannel when we know we are going to succeed. This is
313 : // important because we don't want to introduce a reference cycle between
314 : // mChannel and this until we know for a fact that AsyncOpen has succeeded,
315 : // thus ensuring that our stream listener methods will be invoked.
316 12 : mChannel = channel;
317 12 : return NS_OK;
318 : }
319 :
320 : // Reads the current file size and validates it.
321 : nsresult
322 13 : nsIncrementalDownload::ReadCurrentSize()
323 : {
324 : PRInt64 size;
325 13 : nsresult rv = mDest->GetFileSize((PRInt64 *) &size);
326 13 : if (rv == NS_ERROR_FILE_NOT_FOUND ||
327 : rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
328 8 : mCurrentSize = 0;
329 8 : return NS_OK;
330 : }
331 5 : if (NS_FAILED(rv))
332 0 : return rv;
333 :
334 5 : mCurrentSize = size;
335 5 : return NS_OK;
336 : }
337 :
338 : // nsISupports
339 :
340 1017 : NS_IMPL_ISUPPORTS9(nsIncrementalDownload,
341 : nsIIncrementalDownload,
342 : nsIRequest,
343 : nsIStreamListener,
344 : nsIRequestObserver,
345 : nsIObserver,
346 : nsIInterfaceRequestor,
347 : nsIChannelEventSink,
348 : nsISupportsWeakReference,
349 : nsIAsyncVerifyRedirectCallback)
350 :
351 : // nsIRequest
352 :
353 : NS_IMETHODIMP
354 0 : nsIncrementalDownload::GetName(nsACString &name)
355 : {
356 0 : NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_INITIALIZED);
357 :
358 0 : return mURI->GetSpec(name);
359 : }
360 :
361 : NS_IMETHODIMP
362 0 : nsIncrementalDownload::IsPending(bool *isPending)
363 : {
364 0 : *isPending = mIsPending;
365 0 : return NS_OK;
366 : }
367 :
368 : NS_IMETHODIMP
369 0 : nsIncrementalDownload::GetStatus(nsresult *status)
370 : {
371 0 : *status = mStatus;
372 0 : return NS_OK;
373 : }
374 :
375 : NS_IMETHODIMP
376 2 : nsIncrementalDownload::Cancel(nsresult status)
377 : {
378 2 : NS_ENSURE_ARG(NS_FAILED(status));
379 :
380 : // Ignore this cancelation if we're already canceled.
381 2 : if (NS_FAILED(mStatus))
382 1 : return NS_OK;
383 :
384 1 : mStatus = status;
385 :
386 : // Nothing more to do if callbacks aren't pending.
387 1 : if (!mIsPending)
388 0 : return NS_OK;
389 :
390 1 : if (mChannel) {
391 0 : mChannel->Cancel(mStatus);
392 0 : NS_ASSERTION(!mTimer, "what is this timer object doing here?");
393 : }
394 : else {
395 : // dispatch a timer callback event to drive invoking our listener's
396 : // OnStopRequest.
397 1 : if (mTimer)
398 1 : mTimer->Cancel();
399 1 : StartTimer(0);
400 : }
401 :
402 1 : return NS_OK;
403 : }
404 :
405 : NS_IMETHODIMP
406 0 : nsIncrementalDownload::Suspend()
407 : {
408 0 : return NS_ERROR_NOT_IMPLEMENTED;
409 : }
410 :
411 : NS_IMETHODIMP
412 0 : nsIncrementalDownload::Resume()
413 : {
414 0 : return NS_ERROR_NOT_IMPLEMENTED;
415 : }
416 :
417 : NS_IMETHODIMP
418 0 : nsIncrementalDownload::GetLoadFlags(nsLoadFlags *loadFlags)
419 : {
420 0 : *loadFlags = mLoadFlags;
421 0 : return NS_OK;
422 : }
423 :
424 : NS_IMETHODIMP
425 0 : nsIncrementalDownload::SetLoadFlags(nsLoadFlags loadFlags)
426 : {
427 0 : mLoadFlags = loadFlags;
428 0 : return NS_OK;
429 : }
430 :
431 : NS_IMETHODIMP
432 0 : nsIncrementalDownload::GetLoadGroup(nsILoadGroup **loadGroup)
433 : {
434 0 : return NS_ERROR_NOT_IMPLEMENTED;
435 : }
436 :
437 : NS_IMETHODIMP
438 0 : nsIncrementalDownload::SetLoadGroup(nsILoadGroup *loadGroup)
439 : {
440 0 : return NS_ERROR_NOT_IMPLEMENTED;
441 : }
442 :
443 : // nsIIncrementalDownload
444 :
445 : NS_IMETHODIMP
446 13 : nsIncrementalDownload::Init(nsIURI *uri, nsIFile *dest,
447 : PRInt32 chunkSize, PRInt32 interval)
448 : {
449 : // Keep it simple: only allow initialization once
450 13 : NS_ENSURE_FALSE(mURI, NS_ERROR_ALREADY_INITIALIZED);
451 :
452 13 : mDest = do_QueryInterface(dest);
453 13 : NS_ENSURE_ARG(mDest);
454 :
455 13 : mURI = uri;
456 13 : mFinalURI = uri;
457 :
458 13 : if (chunkSize > 0)
459 13 : mChunkSize = chunkSize;
460 13 : if (interval >= 0)
461 13 : mInterval = interval;
462 13 : return NS_OK;
463 : }
464 :
465 : NS_IMETHODIMP
466 26 : nsIncrementalDownload::GetURI(nsIURI **result)
467 : {
468 26 : NS_IF_ADDREF(*result = mURI);
469 26 : return NS_OK;
470 : }
471 :
472 : NS_IMETHODIMP
473 39 : nsIncrementalDownload::GetFinalURI(nsIURI **result)
474 : {
475 39 : NS_IF_ADDREF(*result = mFinalURI);
476 39 : return NS_OK;
477 : }
478 :
479 : NS_IMETHODIMP
480 10 : nsIncrementalDownload::GetDestination(nsIFile **result)
481 : {
482 10 : if (!mDest) {
483 0 : *result = nsnull;
484 0 : return NS_OK;
485 : }
486 : // Return a clone of mDest so that callers may modify the resulting nsIFile
487 : // without corrupting our internal object. This also works around the fact
488 : // that some nsIFile impls may cache the result of stat'ing the filesystem.
489 10 : return mDest->Clone(result);
490 : }
491 :
492 : NS_IMETHODIMP
493 0 : nsIncrementalDownload::GetTotalSize(PRInt64 *result)
494 : {
495 0 : *result = mTotalSize;
496 0 : return NS_OK;
497 : }
498 :
499 : NS_IMETHODIMP
500 0 : nsIncrementalDownload::GetCurrentSize(PRInt64 *result)
501 : {
502 0 : *result = mCurrentSize;
503 0 : return NS_OK;
504 : }
505 :
506 : NS_IMETHODIMP
507 13 : nsIncrementalDownload::Start(nsIRequestObserver *observer,
508 : nsISupports *context)
509 : {
510 13 : NS_ENSURE_ARG(observer);
511 13 : NS_ENSURE_FALSE(mIsPending, NS_ERROR_IN_PROGRESS);
512 :
513 : // Observe system shutdown so we can be sure to release any reference held
514 : // between ourselves and the timer. We have the observer service hold a weak
515 : // reference to us, so that we don't have to worry about calling
516 : // RemoveObserver. XXX(darin): The timer code should do this for us.
517 26 : nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
518 13 : if (obs)
519 13 : obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
520 :
521 13 : nsresult rv = ReadCurrentSize();
522 13 : if (NS_FAILED(rv))
523 0 : return rv;
524 :
525 13 : rv = StartTimer(0);
526 13 : if (NS_FAILED(rv))
527 0 : return rv;
528 :
529 13 : mObserver = observer;
530 13 : mObserverContext = context;
531 13 : mProgressSink = do_QueryInterface(observer); // ok if null
532 :
533 13 : mIsPending = true;
534 13 : return NS_OK;
535 : }
536 :
537 : // nsIRequestObserver
538 :
539 : NS_IMETHODIMP
540 12 : nsIncrementalDownload::OnStartRequest(nsIRequest *request,
541 : nsISupports *context)
542 : {
543 : nsresult rv;
544 :
545 24 : nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(request, &rv);
546 12 : if (NS_FAILED(rv))
547 0 : return rv;
548 :
549 : // Ensure that we are receiving a 206 response.
550 : PRUint32 code;
551 12 : rv = http->GetResponseStatus(&code);
552 12 : if (NS_FAILED(rv))
553 1 : return rv;
554 11 : if (code != 206) {
555 : // We may already have the entire file downloaded, in which case
556 : // our request for a range beyond the end of the file would have
557 : // been met with an error response code.
558 11 : if (code == 416 && mTotalSize == PRInt64(-1)) {
559 5 : mTotalSize = mCurrentSize;
560 : // Return an error code here to suppress OnDataAvailable.
561 5 : return NS_ERROR_DOWNLOAD_COMPLETE;
562 : }
563 : // The server may have decided to give us all of the data in one chunk. If
564 : // we requested a partial range, then we don't want to download all of the
565 : // data at once. So, we'll just try again, but if this keeps happening then
566 : // we'll eventually give up.
567 6 : if (code == 200) {
568 5 : if (mInterval) {
569 0 : mChannel = nsnull;
570 0 : if (++mNonPartialCount > MAX_RETRY_COUNT) {
571 0 : NS_WARNING("unable to fetch a byte range; giving up");
572 0 : return NS_ERROR_FAILURE;
573 : }
574 : // Increase delay with each failure.
575 0 : StartTimer(mInterval * mNonPartialCount);
576 0 : return NS_ERROR_DOWNLOAD_NOT_PARTIAL;
577 : }
578 : // Since we have been asked to download the rest of the file, we can deal
579 : // with a 200 response. This may result in downloading the beginning of
580 : // the file again, but that can't really be helped.
581 : } else {
582 1 : NS_WARNING("server response was unexpected");
583 1 : return NS_ERROR_UNEXPECTED;
584 : }
585 : } else {
586 : // We got a partial response, so clear this counter in case the next chunk
587 : // results in a 200 response.
588 0 : mNonPartialCount = 0;
589 : }
590 :
591 : // Do special processing after the first response.
592 5 : if (mTotalSize == PRInt64(-1)) {
593 : // Update knowledge of mFinalURI
594 5 : rv = http->GetURI(getter_AddRefs(mFinalURI));
595 5 : if (NS_FAILED(rv))
596 0 : return rv;
597 :
598 5 : if (code == 206) {
599 : // OK, read the Content-Range header to determine the total size of this
600 : // download file.
601 0 : nsCAutoString buf;
602 0 : rv = http->GetResponseHeader(NS_LITERAL_CSTRING("Content-Range"), buf);
603 0 : if (NS_FAILED(rv))
604 0 : return rv;
605 0 : PRInt32 slash = buf.FindChar('/');
606 0 : if (slash == kNotFound) {
607 0 : NS_WARNING("server returned invalid Content-Range header!");
608 0 : return NS_ERROR_UNEXPECTED;
609 : }
610 0 : if (PR_sscanf(buf.get() + slash + 1, "%lld", (PRInt64 *) &mTotalSize) != 1)
611 0 : return NS_ERROR_UNEXPECTED;
612 : } else {
613 : // Use nsIPropertyBag2 to fetch the content length as it exposes the
614 : // value as a 64-bit number.
615 10 : nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(request, &rv);
616 5 : if (NS_FAILED(rv))
617 0 : return rv;
618 5 : rv = props->GetPropertyAsInt64(NS_CHANNEL_PROP_CONTENT_LENGTH,
619 5 : &mTotalSize);
620 : // We need to know the total size of the thing we're trying to download.
621 5 : if (mTotalSize == PRInt64(-1)) {
622 0 : NS_WARNING("server returned no content-length header!");
623 0 : return NS_ERROR_UNEXPECTED;
624 : }
625 : // Need to truncate (or create, if it doesn't exist) the file since we
626 : // are downloading the whole thing.
627 5 : WriteToFile(mDest, nsnull, 0, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE);
628 10 : mCurrentSize = 0;
629 : }
630 :
631 : // Notify observer that we are starting...
632 5 : rv = CallOnStartRequest();
633 5 : if (NS_FAILED(rv))
634 0 : return rv;
635 : }
636 :
637 : // Adjust mChunkSize accordingly if mCurrentSize is close to mTotalSize.
638 5 : PRInt64 diff = mTotalSize - mCurrentSize;
639 5 : if (diff <= PRInt64(0)) {
640 0 : NS_WARNING("about to set a bogus chunk size; giving up");
641 0 : return NS_ERROR_UNEXPECTED;
642 : }
643 :
644 5 : if (diff < PRInt64(mChunkSize))
645 5 : mChunkSize = PRUint32(diff);
646 :
647 10 : mChunk = new char[mChunkSize];
648 5 : if (!mChunk)
649 0 : rv = NS_ERROR_OUT_OF_MEMORY;
650 :
651 5 : return rv;
652 : }
653 :
654 : NS_IMETHODIMP
655 12 : nsIncrementalDownload::OnStopRequest(nsIRequest *request,
656 : nsISupports *context,
657 : nsresult status)
658 : {
659 : // Not a real error; just a trick to kill off the channel without our
660 : // listener having to care.
661 12 : if (status == NS_ERROR_DOWNLOAD_NOT_PARTIAL)
662 0 : return NS_OK;
663 :
664 : // Not a real error; just a trick used to suppress OnDataAvailable calls.
665 12 : if (status == NS_ERROR_DOWNLOAD_COMPLETE)
666 5 : status = NS_OK;
667 :
668 12 : if (NS_SUCCEEDED(mStatus))
669 12 : mStatus = status;
670 :
671 12 : if (mChunk) {
672 5 : if (NS_SUCCEEDED(mStatus))
673 5 : mStatus = FlushChunk();
674 :
675 5 : mChunk = nsnull; // deletes memory
676 5 : mChunkLen = 0;
677 5 : UpdateProgress();
678 : }
679 :
680 12 : mChannel = nsnull;
681 :
682 : // Notify listener if we hit an error or finished
683 12 : if (NS_FAILED(mStatus) || mCurrentSize == mTotalSize) {
684 12 : CallOnStopRequest();
685 12 : return NS_OK;
686 : }
687 :
688 0 : return StartTimer(mInterval); // Do next chunk
689 : }
690 :
691 : // nsIStreamListener
692 :
693 : NS_IMETHODIMP
694 5 : nsIncrementalDownload::OnDataAvailable(nsIRequest *request,
695 : nsISupports *context,
696 : nsIInputStream *input,
697 : PRUint32 offset,
698 : PRUint32 count)
699 : {
700 15 : while (count) {
701 5 : PRUint32 space = mChunkSize - mChunkLen;
702 5 : PRUint32 n, len = NS_MIN(space, count);
703 :
704 5 : nsresult rv = input->Read(mChunk + mChunkLen, len, &n);
705 5 : if (NS_FAILED(rv))
706 0 : return rv;
707 5 : if (n != len)
708 0 : return NS_ERROR_UNEXPECTED;
709 :
710 5 : count -= n;
711 5 : mChunkLen += n;
712 :
713 5 : if (mChunkLen == mChunkSize)
714 5 : FlushChunk();
715 : }
716 :
717 5 : if (PR_Now() > mLastProgressUpdate + UPDATE_PROGRESS_INTERVAL)
718 5 : UpdateProgress();
719 :
720 5 : return NS_OK;
721 : }
722 :
723 : // nsIObserver
724 :
725 : NS_IMETHODIMP
726 14 : nsIncrementalDownload::Observe(nsISupports *subject, const char *topic,
727 : const PRUnichar *data)
728 : {
729 14 : if (strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
730 1 : Cancel(NS_ERROR_ABORT);
731 :
732 : // Since the app is shutting down, we need to go ahead and notify our
733 : // observer here. Otherwise, we would notify them after XPCOM has been
734 : // shutdown or not at all.
735 1 : CallOnStopRequest();
736 : }
737 13 : else if (strcmp(topic, NS_TIMER_CALLBACK_TOPIC) == 0) {
738 13 : mTimer = nsnull;
739 13 : nsresult rv = ProcessTimeout();
740 13 : if (NS_FAILED(rv))
741 0 : Cancel(rv);
742 : }
743 14 : return NS_OK;
744 : }
745 :
746 : // nsIInterfaceRequestor
747 :
748 : NS_IMETHODIMP
749 20 : nsIncrementalDownload::GetInterface(const nsIID &iid, void **result)
750 : {
751 20 : if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) {
752 0 : NS_ADDREF_THIS();
753 0 : *result = static_cast<nsIChannelEventSink *>(this);
754 0 : return NS_OK;
755 : }
756 :
757 40 : nsCOMPtr<nsIInterfaceRequestor> ir = do_QueryInterface(mObserver);
758 20 : if (ir)
759 20 : return ir->GetInterface(iid, result);
760 :
761 0 : return NS_ERROR_NO_INTERFACE;
762 : }
763 :
764 : nsresult
765 12 : nsIncrementalDownload::ClearRequestHeader(nsIHttpChannel *channel)
766 : {
767 12 : NS_ENSURE_ARG(channel);
768 :
769 : // We don't support encodings -- they make the Content-Length not equal
770 : // to the actual size of the data.
771 12 : return channel->SetRequestHeader(NS_LITERAL_CSTRING("Accept-Encoding"),
772 24 : NS_LITERAL_CSTRING(""), false);
773 : }
774 :
775 : // nsIChannelEventSink
776 :
777 : NS_IMETHODIMP
778 0 : nsIncrementalDownload::AsyncOnChannelRedirect(nsIChannel *oldChannel,
779 : nsIChannel *newChannel,
780 : PRUint32 flags,
781 : nsIAsyncVerifyRedirectCallback *cb)
782 : {
783 : // In response to a redirect, we need to propagate the Range header. See bug
784 : // 311595. Any failure code returned from this function aborts the redirect.
785 :
786 0 : nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(oldChannel);
787 0 : NS_ENSURE_STATE(http);
788 :
789 0 : nsCOMPtr<nsIHttpChannel> newHttpChannel = do_QueryInterface(newChannel);
790 0 : NS_ENSURE_STATE(newHttpChannel);
791 :
792 0 : NS_NAMED_LITERAL_CSTRING(rangeHdr, "Range");
793 :
794 0 : nsresult rv = ClearRequestHeader(newHttpChannel);
795 0 : if (NS_FAILED(rv))
796 0 : return rv;
797 :
798 : // If we didn't have a Range header, then we must be doing a full download.
799 0 : nsCAutoString rangeVal;
800 0 : http->GetRequestHeader(rangeHdr, rangeVal);
801 0 : if (!rangeVal.IsEmpty()) {
802 0 : rv = newHttpChannel->SetRequestHeader(rangeHdr, rangeVal, false);
803 0 : NS_ENSURE_SUCCESS(rv, rv);
804 : }
805 :
806 : // Prepare to receive callback
807 0 : mRedirectCallback = cb;
808 0 : mNewRedirectChannel = newChannel;
809 :
810 : // Give the observer a chance to see this redirect notification.
811 0 : nsCOMPtr<nsIChannelEventSink> sink = do_GetInterface(mObserver);
812 0 : if (sink) {
813 0 : rv = sink->AsyncOnChannelRedirect(oldChannel, newChannel, flags, this);
814 0 : if (NS_FAILED(rv)) {
815 0 : mRedirectCallback = nsnull;
816 0 : mNewRedirectChannel = nsnull;
817 : }
818 0 : return rv;
819 : }
820 0 : (void) OnRedirectVerifyCallback(NS_OK);
821 0 : return NS_OK;
822 : }
823 :
824 : NS_IMETHODIMP
825 0 : nsIncrementalDownload::OnRedirectVerifyCallback(nsresult result)
826 : {
827 0 : NS_ASSERTION(mRedirectCallback, "mRedirectCallback not set in callback");
828 0 : NS_ASSERTION(mNewRedirectChannel, "mNewRedirectChannel not set in callback");
829 :
830 : // Update mChannel, so we can Cancel the new channel.
831 0 : if (NS_SUCCEEDED(result))
832 0 : mChannel = mNewRedirectChannel;
833 :
834 0 : mRedirectCallback->OnRedirectVerifyCallback(result);
835 0 : mRedirectCallback = nsnull;
836 0 : mNewRedirectChannel = nsnull;
837 0 : return NS_OK;
838 : }
839 :
840 : extern nsresult
841 13 : net_NewIncrementalDownload(nsISupports *outer, const nsIID &iid, void **result)
842 : {
843 13 : if (outer)
844 0 : return NS_ERROR_NO_AGGREGATION;
845 :
846 13 : nsIncrementalDownload *d = new nsIncrementalDownload();
847 13 : if (!d)
848 0 : return NS_ERROR_OUT_OF_MEMORY;
849 :
850 13 : NS_ADDREF(d);
851 13 : nsresult rv = d->QueryInterface(iid, result);
852 13 : NS_RELEASE(d);
853 13 : return rv;
854 : }
|