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