1 : /* -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is mozilla.org code.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Mozilla Corporation
19 : * Portions created by the Initial Developer are Copyright (C) 2007
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Dave Camp <dcamp@mozilla.com>
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 "nsOfflineCacheUpdate.h"
40 :
41 : #include "nsCPrefetchService.h"
42 : #include "nsCURILoader.h"
43 : #include "nsIApplicationCacheContainer.h"
44 : #include "nsIApplicationCacheChannel.h"
45 : #include "nsIApplicationCacheService.h"
46 : #include "nsICache.h"
47 : #include "nsICacheService.h"
48 : #include "nsICacheSession.h"
49 : #include "nsICachingChannel.h"
50 : #include "nsIContent.h"
51 : #include "mozilla/dom/Element.h"
52 : #include "nsIDocumentLoader.h"
53 : #include "nsIDOMElement.h"
54 : #include "nsIDOMWindow.h"
55 : #include "nsIDOMOfflineResourceList.h"
56 : #include "nsIDocument.h"
57 : #include "nsIObserverService.h"
58 : #include "nsIURL.h"
59 : #include "nsIWebProgress.h"
60 : #include "nsICryptoHash.h"
61 : #include "nsICacheEntryDescriptor.h"
62 : #include "nsIPermissionManager.h"
63 : #include "nsIPrincipal.h"
64 : #include "nsNetCID.h"
65 : #include "nsNetUtil.h"
66 : #include "nsServiceManagerUtils.h"
67 : #include "nsStreamUtils.h"
68 : #include "nsThreadUtils.h"
69 : #include "nsProxyRelease.h"
70 : #include "prlog.h"
71 : #include "nsIAsyncVerifyRedirectCallback.h"
72 : #include "mozilla/Preferences.h"
73 :
74 : #include "nsXULAppAPI.h"
75 :
76 : using namespace mozilla;
77 :
78 : static const PRUint32 kRescheduleLimit = 3;
79 :
80 : #if defined(PR_LOGGING)
81 : //
82 : // To enable logging (see prlog.h for full details):
83 : //
84 : // set NSPR_LOG_MODULES=nsOfflineCacheUpdate:5
85 : // set NSPR_LOG_FILE=offlineupdate.log
86 : //
87 : // this enables PR_LOG_ALWAYS level information and places all output in
88 : // the file offlineupdate.log
89 : //
90 : extern PRLogModuleInfo *gOfflineCacheUpdateLog;
91 : #endif
92 : #define LOG(args) PR_LOG(gOfflineCacheUpdateLog, 4, args)
93 : #define LOG_ENABLED() PR_LOG_TEST(gOfflineCacheUpdateLog, 4)
94 :
95 : class AutoFreeArray {
96 : public:
97 0 : AutoFreeArray(PRUint32 count, char **values)
98 0 : : mCount(count), mValues(values) {};
99 0 : ~AutoFreeArray() { NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(mCount, mValues); }
100 : private:
101 : PRUint32 mCount;
102 : char **mValues;
103 : };
104 :
105 : static nsresult
106 16 : DropReferenceFromURL(nsIURI * aURI)
107 : {
108 : // XXXdholbert If this SetRef fails, callers of this method probably
109 : // want to call aURI->CloneIgnoringRef() and use the result of that.
110 16 : return aURI->SetRef(EmptyCString());
111 : }
112 :
113 : //-----------------------------------------------------------------------------
114 : // nsManifestCheck
115 : //-----------------------------------------------------------------------------
116 :
117 : class nsManifestCheck : public nsIStreamListener
118 : , public nsIChannelEventSink
119 : , public nsIInterfaceRequestor
120 8 : {
121 : public:
122 8 : nsManifestCheck(nsOfflineCacheUpdate *aUpdate,
123 : nsIURI *aURI,
124 : nsIURI *aReferrerURI)
125 : : mUpdate(aUpdate)
126 : , mURI(aURI)
127 8 : , mReferrerURI(aReferrerURI)
128 8 : {}
129 :
130 : NS_DECL_ISUPPORTS
131 : NS_DECL_NSIREQUESTOBSERVER
132 : NS_DECL_NSISTREAMLISTENER
133 : NS_DECL_NSICHANNELEVENTSINK
134 : NS_DECL_NSIINTERFACEREQUESTOR
135 :
136 : nsresult Begin();
137 :
138 : private:
139 :
140 : static NS_METHOD ReadManifest(nsIInputStream *aInputStream,
141 : void *aClosure,
142 : const char *aFromSegment,
143 : PRUint32 aOffset,
144 : PRUint32 aCount,
145 : PRUint32 *aBytesConsumed);
146 :
147 : nsRefPtr<nsOfflineCacheUpdate> mUpdate;
148 : nsCOMPtr<nsIURI> mURI;
149 : nsCOMPtr<nsIURI> mReferrerURI;
150 : nsCOMPtr<nsICryptoHash> mManifestHash;
151 : nsCOMPtr<nsIChannel> mChannel;
152 : };
153 :
154 : //-----------------------------------------------------------------------------
155 : // nsManifestCheck::nsISupports
156 : //-----------------------------------------------------------------------------
157 104 : NS_IMPL_ISUPPORTS4(nsManifestCheck,
158 : nsIRequestObserver,
159 : nsIStreamListener,
160 : nsIChannelEventSink,
161 : nsIInterfaceRequestor)
162 :
163 : //-----------------------------------------------------------------------------
164 : // nsManifestCheck <public>
165 : //-----------------------------------------------------------------------------
166 :
167 : nsresult
168 8 : nsManifestCheck::Begin()
169 : {
170 : nsresult rv;
171 8 : mManifestHash = do_CreateInstance("@mozilla.org/security/hash;1", &rv);
172 8 : NS_ENSURE_SUCCESS(rv, rv);
173 :
174 8 : rv = mManifestHash->Init(nsICryptoHash::MD5);
175 8 : NS_ENSURE_SUCCESS(rv, rv);
176 :
177 8 : rv = NS_NewChannel(getter_AddRefs(mChannel),
178 : mURI,
179 : nsnull, nsnull, nsnull,
180 8 : nsIRequest::LOAD_BYPASS_CACHE);
181 8 : NS_ENSURE_SUCCESS(rv, rv);
182 :
183 : // configure HTTP specific stuff
184 : nsCOMPtr<nsIHttpChannel> httpChannel =
185 16 : do_QueryInterface(mChannel);
186 8 : if (httpChannel) {
187 8 : httpChannel->SetReferrer(mReferrerURI);
188 16 : httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("X-Moz"),
189 8 : NS_LITERAL_CSTRING("offline-resource"),
190 8 : false);
191 : }
192 :
193 8 : rv = mChannel->AsyncOpen(this, nsnull);
194 8 : NS_ENSURE_SUCCESS(rv, rv);
195 :
196 8 : return NS_OK;
197 : }
198 :
199 : //-----------------------------------------------------------------------------
200 : // nsManifestCheck <public>
201 : //-----------------------------------------------------------------------------
202 :
203 : /* static */
204 : NS_METHOD
205 8 : nsManifestCheck::ReadManifest(nsIInputStream *aInputStream,
206 : void *aClosure,
207 : const char *aFromSegment,
208 : PRUint32 aOffset,
209 : PRUint32 aCount,
210 : PRUint32 *aBytesConsumed)
211 : {
212 : nsManifestCheck *manifestCheck =
213 8 : static_cast<nsManifestCheck*>(aClosure);
214 :
215 : nsresult rv;
216 8 : *aBytesConsumed = aCount;
217 :
218 8 : rv = manifestCheck->mManifestHash->Update(
219 8 : reinterpret_cast<const PRUint8 *>(aFromSegment), aCount);
220 8 : NS_ENSURE_SUCCESS(rv, rv);
221 :
222 8 : return NS_OK;
223 : }
224 :
225 : //-----------------------------------------------------------------------------
226 : // nsManifestCheck::nsIStreamListener
227 : //-----------------------------------------------------------------------------
228 :
229 : NS_IMETHODIMP
230 8 : nsManifestCheck::OnStartRequest(nsIRequest *aRequest,
231 : nsISupports *aContext)
232 : {
233 8 : return NS_OK;
234 : }
235 :
236 : NS_IMETHODIMP
237 8 : nsManifestCheck::OnDataAvailable(nsIRequest *aRequest,
238 : nsISupports *aContext,
239 : nsIInputStream *aStream,
240 : PRUint32 aOffset,
241 : PRUint32 aCount)
242 : {
243 : PRUint32 bytesRead;
244 8 : aStream->ReadSegments(ReadManifest, this, aCount, &bytesRead);
245 8 : return NS_OK;
246 : }
247 :
248 : NS_IMETHODIMP
249 8 : nsManifestCheck::OnStopRequest(nsIRequest *aRequest,
250 : nsISupports *aContext,
251 : nsresult aStatus)
252 : {
253 16 : nsCAutoString manifestHash;
254 8 : if (NS_SUCCEEDED(aStatus)) {
255 8 : mManifestHash->Finish(true, manifestHash);
256 : }
257 :
258 8 : mUpdate->ManifestCheckCompleted(aStatus, manifestHash);
259 :
260 8 : return NS_OK;
261 : }
262 :
263 : //-----------------------------------------------------------------------------
264 : // nsManifestCheck::nsIInterfaceRequestor
265 : //-----------------------------------------------------------------------------
266 :
267 : NS_IMETHODIMP
268 0 : nsManifestCheck::GetInterface(const nsIID &aIID, void **aResult)
269 : {
270 0 : if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
271 0 : NS_ADDREF_THIS();
272 0 : *aResult = static_cast<nsIChannelEventSink *>(this);
273 0 : return NS_OK;
274 : }
275 :
276 0 : return NS_ERROR_NO_INTERFACE;
277 : }
278 :
279 : //-----------------------------------------------------------------------------
280 : // nsManifestCheck::nsIChannelEventSink
281 : //-----------------------------------------------------------------------------
282 :
283 : NS_IMETHODIMP
284 0 : nsManifestCheck::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
285 : nsIChannel *aNewChannel,
286 : PRUint32 aFlags,
287 : nsIAsyncVerifyRedirectCallback *callback)
288 : {
289 : // Redirects should cause the load (and therefore the update) to fail.
290 0 : if (aFlags & nsIChannelEventSink::REDIRECT_INTERNAL) {
291 0 : callback->OnRedirectVerifyCallback(NS_OK);
292 0 : return NS_OK;
293 : }
294 0 : aOldChannel->Cancel(NS_ERROR_ABORT);
295 0 : return NS_ERROR_ABORT;
296 : }
297 :
298 : //-----------------------------------------------------------------------------
299 : // nsOfflineCacheUpdateItem::nsISupports
300 : //-----------------------------------------------------------------------------
301 :
302 712 : NS_IMPL_ISUPPORTS6(nsOfflineCacheUpdateItem,
303 : nsIDOMLoadStatus,
304 : nsIRequestObserver,
305 : nsIStreamListener,
306 : nsIRunnable,
307 : nsIInterfaceRequestor,
308 : nsIChannelEventSink)
309 :
310 : //-----------------------------------------------------------------------------
311 : // nsOfflineCacheUpdateItem <public>
312 : //-----------------------------------------------------------------------------
313 :
314 24 : nsOfflineCacheUpdateItem::nsOfflineCacheUpdateItem(nsOfflineCacheUpdate *aUpdate,
315 : nsIURI *aURI,
316 : nsIURI *aReferrerURI,
317 : nsIApplicationCache *aPreviousApplicationCache,
318 : const nsACString &aClientID,
319 : PRUint32 type)
320 : : mURI(aURI)
321 : , mReferrerURI(aReferrerURI)
322 : , mPreviousApplicationCache(aPreviousApplicationCache)
323 : , mClientID(aClientID)
324 : , mItemType(type)
325 : , mUpdate(aUpdate)
326 : , mChannel(nsnull)
327 : , mState(nsIDOMLoadStatus::UNINITIALIZED)
328 24 : , mBytesRead(0)
329 : {
330 24 : }
331 :
332 40 : nsOfflineCacheUpdateItem::~nsOfflineCacheUpdateItem()
333 : {
334 80 : }
335 :
336 : nsresult
337 24 : nsOfflineCacheUpdateItem::OpenChannel()
338 : {
339 : #if defined(PR_LOGGING)
340 24 : if (LOG_ENABLED()) {
341 0 : nsCAutoString spec;
342 0 : mURI->GetSpec(spec);
343 0 : LOG(("%p: Opening channel for %s", this, spec.get()));
344 : }
345 : #endif
346 :
347 24 : nsresult rv = nsOfflineCacheUpdate::GetCacheKey(mURI, mCacheKey);
348 24 : NS_ENSURE_SUCCESS(rv, rv);
349 :
350 24 : rv = NS_NewChannel(getter_AddRefs(mChannel),
351 : mURI,
352 : nsnull, nsnull, this,
353 : nsIRequest::LOAD_BACKGROUND |
354 : nsICachingChannel::LOAD_ONLY_IF_MODIFIED |
355 24 : nsICachingChannel::LOAD_CHECK_OFFLINE_CACHE);
356 24 : NS_ENSURE_SUCCESS(rv, rv);
357 :
358 : nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel =
359 48 : do_QueryInterface(mChannel, &rv);
360 :
361 : // Support for nsIApplicationCacheChannel is required.
362 24 : NS_ENSURE_SUCCESS(rv, rv);
363 :
364 : // Use the existing application cache as the cache to check.
365 24 : rv = appCacheChannel->SetApplicationCache(mPreviousApplicationCache);
366 24 : NS_ENSURE_SUCCESS(rv, rv);
367 :
368 : // configure HTTP specific stuff
369 : nsCOMPtr<nsIHttpChannel> httpChannel =
370 48 : do_QueryInterface(mChannel);
371 24 : if (httpChannel) {
372 24 : httpChannel->SetReferrer(mReferrerURI);
373 48 : httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("X-Moz"),
374 24 : NS_LITERAL_CSTRING("offline-resource"),
375 24 : false);
376 : }
377 :
378 : nsCOMPtr<nsICachingChannel> cachingChannel =
379 48 : do_QueryInterface(mChannel);
380 24 : if (cachingChannel) {
381 24 : rv = cachingChannel->SetCacheForOfflineUse(true);
382 24 : NS_ENSURE_SUCCESS(rv, rv);
383 :
384 24 : if (!mClientID.IsEmpty()) {
385 24 : rv = cachingChannel->SetOfflineCacheClientID(mClientID);
386 24 : NS_ENSURE_SUCCESS(rv, rv);
387 : }
388 : }
389 :
390 24 : rv = mChannel->AsyncOpen(this, nsnull);
391 24 : NS_ENSURE_SUCCESS(rv, rv);
392 :
393 24 : mState = nsIDOMLoadStatus::REQUESTED;
394 :
395 24 : return NS_OK;
396 : }
397 :
398 : nsresult
399 0 : nsOfflineCacheUpdateItem::Cancel()
400 : {
401 0 : if (mChannel) {
402 0 : mChannel->Cancel(NS_ERROR_ABORT);
403 0 : mChannel = nsnull;
404 : }
405 :
406 0 : mState = nsIDOMLoadStatus::UNINITIALIZED;
407 :
408 0 : return NS_OK;
409 : }
410 :
411 : //-----------------------------------------------------------------------------
412 : // nsOfflineCacheUpdateItem::nsIStreamListener
413 : //-----------------------------------------------------------------------------
414 :
415 : NS_IMETHODIMP
416 24 : nsOfflineCacheUpdateItem::OnStartRequest(nsIRequest *aRequest,
417 : nsISupports *aContext)
418 : {
419 24 : mState = nsIDOMLoadStatus::RECEIVING;
420 :
421 24 : return NS_OK;
422 : }
423 :
424 : NS_IMETHODIMP
425 16 : nsOfflineCacheUpdateItem::OnDataAvailable(nsIRequest *aRequest,
426 : nsISupports *aContext,
427 : nsIInputStream *aStream,
428 : PRUint32 aOffset,
429 : PRUint32 aCount)
430 : {
431 16 : PRUint32 bytesRead = 0;
432 16 : aStream->ReadSegments(NS_DiscardSegment, nsnull, aCount, &bytesRead);
433 16 : mBytesRead += bytesRead;
434 16 : LOG(("loaded %u bytes into offline cache [offset=%u]\n",
435 : bytesRead, aOffset));
436 16 : return NS_OK;
437 : }
438 :
439 : NS_IMETHODIMP
440 24 : nsOfflineCacheUpdateItem::OnStopRequest(nsIRequest *aRequest,
441 : nsISupports *aContext,
442 : nsresult aStatus)
443 : {
444 24 : LOG(("done fetching offline item [status=%x]\n", aStatus));
445 :
446 24 : mState = nsIDOMLoadStatus::LOADED;
447 :
448 24 : if (mBytesRead == 0 && aStatus == NS_OK) {
449 : // we didn't need to read (because LOAD_ONLY_IF_MODIFIED was
450 : // specified), but the object should report loadedSize as if it
451 : // did.
452 0 : mChannel->GetContentLength(&mBytesRead);
453 : }
454 :
455 : // We need to notify the update that the load is complete, but we
456 : // want to give the channel a chance to close the cache entries.
457 24 : NS_DispatchToCurrentThread(this);
458 :
459 24 : return NS_OK;
460 : }
461 :
462 :
463 : //-----------------------------------------------------------------------------
464 : // nsOfflineCacheUpdateItem::nsIRunnable
465 : //-----------------------------------------------------------------------------
466 : NS_IMETHODIMP
467 24 : nsOfflineCacheUpdateItem::Run()
468 : {
469 24 : mUpdate->LoadCompleted();
470 :
471 24 : return NS_OK;
472 : }
473 :
474 : //-----------------------------------------------------------------------------
475 : // nsOfflineCacheUpdateItem::nsIInterfaceRequestor
476 : //-----------------------------------------------------------------------------
477 :
478 : NS_IMETHODIMP
479 110 : nsOfflineCacheUpdateItem::GetInterface(const nsIID &aIID, void **aResult)
480 : {
481 110 : if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
482 0 : NS_ADDREF_THIS();
483 0 : *aResult = static_cast<nsIChannelEventSink *>(this);
484 0 : return NS_OK;
485 : }
486 :
487 110 : return NS_ERROR_NO_INTERFACE;
488 : }
489 :
490 : //-----------------------------------------------------------------------------
491 : // nsOfflineCacheUpdateItem::nsIChannelEventSink
492 : //-----------------------------------------------------------------------------
493 :
494 : NS_IMETHODIMP
495 0 : nsOfflineCacheUpdateItem::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
496 : nsIChannel *aNewChannel,
497 : PRUint32 aFlags,
498 : nsIAsyncVerifyRedirectCallback *cb)
499 : {
500 0 : if (!(aFlags & nsIChannelEventSink::REDIRECT_INTERNAL)) {
501 : // Don't allow redirect in case of non-internal redirect and cancel
502 : // the channel to clean the cache entry.
503 0 : aOldChannel->Cancel(NS_ERROR_ABORT);
504 0 : return NS_ERROR_ABORT;
505 : }
506 :
507 0 : nsCOMPtr<nsIURI> newURI;
508 0 : nsresult rv = aNewChannel->GetURI(getter_AddRefs(newURI));
509 0 : if (NS_FAILED(rv))
510 0 : return rv;
511 :
512 : nsCOMPtr<nsICachingChannel> newCachingChannel =
513 0 : do_QueryInterface(aNewChannel);
514 0 : if (newCachingChannel) {
515 0 : rv = newCachingChannel->SetCacheForOfflineUse(true);
516 0 : NS_ENSURE_SUCCESS(rv, rv);
517 0 : if (!mClientID.IsEmpty()) {
518 0 : rv = newCachingChannel->SetOfflineCacheClientID(mClientID);
519 0 : NS_ENSURE_SUCCESS(rv, rv);
520 : }
521 : }
522 :
523 0 : nsCAutoString oldScheme;
524 0 : mURI->GetScheme(oldScheme);
525 :
526 : bool match;
527 0 : if (NS_FAILED(newURI->SchemeIs(oldScheme.get(), &match)) || !match) {
528 0 : LOG(("rejected: redirected to a different scheme\n"));
529 0 : return NS_ERROR_ABORT;
530 : }
531 :
532 : // HTTP request headers are not automatically forwarded to the new channel.
533 0 : nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aNewChannel);
534 0 : NS_ENSURE_STATE(httpChannel);
535 :
536 0 : httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("X-Moz"),
537 0 : NS_LITERAL_CSTRING("offline-resource"),
538 0 : false);
539 :
540 0 : mChannel = aNewChannel;
541 :
542 0 : cb->OnRedirectVerifyCallback(NS_OK);
543 0 : return NS_OK;
544 : }
545 :
546 : //-----------------------------------------------------------------------------
547 : // nsOfflineCacheUpdateItem::nsIDOMLoadStatus
548 : //-----------------------------------------------------------------------------
549 :
550 : NS_IMETHODIMP
551 0 : nsOfflineCacheUpdateItem::GetSource(nsIDOMNode **aSource)
552 : {
553 0 : *aSource = nsnull;
554 0 : return NS_OK;
555 : }
556 :
557 : NS_IMETHODIMP
558 0 : nsOfflineCacheUpdateItem::GetUri(nsAString &aURI)
559 : {
560 0 : nsCAutoString spec;
561 0 : nsresult rv = mURI->GetSpec(spec);
562 0 : NS_ENSURE_SUCCESS(rv, rv);
563 :
564 0 : CopyUTF8toUTF16(spec, aURI);
565 0 : return NS_OK;
566 : }
567 :
568 : NS_IMETHODIMP
569 0 : nsOfflineCacheUpdateItem::GetTotalSize(PRInt32 *aTotalSize)
570 : {
571 0 : if (mChannel) {
572 0 : return mChannel->GetContentLength(aTotalSize);
573 : }
574 :
575 0 : *aTotalSize = -1;
576 0 : return NS_OK;
577 : }
578 :
579 : NS_IMETHODIMP
580 0 : nsOfflineCacheUpdateItem::GetLoadedSize(PRInt32 *aLoadedSize)
581 : {
582 0 : *aLoadedSize = mBytesRead;
583 0 : return NS_OK;
584 : }
585 :
586 : NS_IMETHODIMP
587 0 : nsOfflineCacheUpdateItem::GetReadyState(PRUint16 *aReadyState)
588 : {
589 0 : *aReadyState = mState;
590 0 : return NS_OK;
591 : }
592 :
593 : nsresult
594 24 : nsOfflineCacheUpdateItem::GetRequestSucceeded(bool * succeeded)
595 : {
596 24 : *succeeded = false;
597 :
598 24 : if (!mChannel)
599 0 : return NS_OK;
600 :
601 : nsresult rv;
602 48 : nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel, &rv);
603 24 : NS_ENSURE_SUCCESS(rv, rv);
604 :
605 : bool reqSucceeded;
606 24 : rv = httpChannel->GetRequestSucceeded(&reqSucceeded);
607 24 : if (NS_ERROR_NOT_AVAILABLE == rv)
608 0 : return NS_OK;
609 24 : NS_ENSURE_SUCCESS(rv, rv);
610 :
611 24 : if (!reqSucceeded) {
612 0 : LOG(("Request failed"));
613 0 : return NS_OK;
614 : }
615 :
616 : nsresult channelStatus;
617 24 : rv = httpChannel->GetStatus(&channelStatus);
618 24 : NS_ENSURE_SUCCESS(rv, rv);
619 :
620 24 : if (NS_FAILED(channelStatus)) {
621 0 : LOG(("Channel status=0x%08x", channelStatus));
622 0 : return NS_OK;
623 : }
624 :
625 24 : *succeeded = true;
626 24 : return NS_OK;
627 : }
628 :
629 : NS_IMETHODIMP
630 8 : nsOfflineCacheUpdateItem::GetStatus(PRUint16 *aStatus)
631 : {
632 8 : if (!mChannel) {
633 0 : *aStatus = 0;
634 0 : return NS_OK;
635 : }
636 :
637 : nsresult rv;
638 16 : nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel, &rv);
639 8 : NS_ENSURE_SUCCESS(rv, rv);
640 :
641 : PRUint32 httpStatus;
642 8 : rv = httpChannel->GetResponseStatus(&httpStatus);
643 8 : if (rv == NS_ERROR_NOT_AVAILABLE) {
644 0 : *aStatus = 0;
645 0 : return NS_OK;
646 : }
647 :
648 8 : NS_ENSURE_SUCCESS(rv, rv);
649 8 : *aStatus = PRUint16(httpStatus);
650 8 : return NS_OK;
651 : }
652 :
653 : //-----------------------------------------------------------------------------
654 : // nsOfflineManifestItem
655 : //-----------------------------------------------------------------------------
656 :
657 : //-----------------------------------------------------------------------------
658 : // nsOfflineManifestItem <public>
659 : //-----------------------------------------------------------------------------
660 :
661 8 : nsOfflineManifestItem::nsOfflineManifestItem(nsOfflineCacheUpdate *aUpdate,
662 : nsIURI *aURI,
663 : nsIURI *aReferrerURI,
664 : nsIApplicationCache *aPreviousApplicationCache,
665 : const nsACString &aClientID)
666 : : nsOfflineCacheUpdateItem(aUpdate, aURI, aReferrerURI,
667 : aPreviousApplicationCache, aClientID,
668 : nsIApplicationCache::ITEM_MANIFEST)
669 : , mParserState(PARSE_INIT)
670 : , mNeedsUpdate(true)
671 8 : , mManifestHashInitialized(false)
672 : {
673 8 : ReadStrictFileOriginPolicyPref();
674 8 : }
675 :
676 16 : nsOfflineManifestItem::~nsOfflineManifestItem()
677 : {
678 32 : }
679 :
680 : //-----------------------------------------------------------------------------
681 : // nsOfflineManifestItem <private>
682 : //-----------------------------------------------------------------------------
683 :
684 : /* static */
685 : NS_METHOD
686 8 : nsOfflineManifestItem::ReadManifest(nsIInputStream *aInputStream,
687 : void *aClosure,
688 : const char *aFromSegment,
689 : PRUint32 aOffset,
690 : PRUint32 aCount,
691 : PRUint32 *aBytesConsumed)
692 : {
693 : nsOfflineManifestItem *manifest =
694 8 : static_cast<nsOfflineManifestItem*>(aClosure);
695 :
696 : nsresult rv;
697 :
698 8 : *aBytesConsumed = aCount;
699 :
700 8 : if (manifest->mParserState == PARSE_ERROR) {
701 : // parse already failed, ignore this
702 0 : return NS_OK;
703 : }
704 :
705 8 : if (!manifest->mManifestHashInitialized) {
706 : // Avoid re-creation of crypto hash when it fails from some reason the first time
707 8 : manifest->mManifestHashInitialized = true;
708 :
709 8 : manifest->mManifestHash = do_CreateInstance("@mozilla.org/security/hash;1", &rv);
710 8 : if (NS_SUCCEEDED(rv)) {
711 8 : rv = manifest->mManifestHash->Init(nsICryptoHash::MD5);
712 8 : if (NS_FAILED(rv)) {
713 0 : manifest->mManifestHash = nsnull;
714 0 : LOG(("Could not initialize manifest hash for byte-to-byte check, rv=%08x", rv));
715 : }
716 : }
717 : }
718 :
719 8 : if (manifest->mManifestHash) {
720 8 : rv = manifest->mManifestHash->Update(reinterpret_cast<const PRUint8 *>(aFromSegment), aCount);
721 8 : if (NS_FAILED(rv)) {
722 0 : manifest->mManifestHash = nsnull;
723 0 : LOG(("Could not update manifest hash, rv=%08x", rv));
724 : }
725 : }
726 :
727 8 : manifest->mReadBuf.Append(aFromSegment, aCount);
728 :
729 8 : nsCString::const_iterator begin, iter, end;
730 8 : manifest->mReadBuf.BeginReading(begin);
731 8 : manifest->mReadBuf.EndReading(end);
732 :
733 346 : for (iter = begin; iter != end; iter++) {
734 338 : if (*iter == '\r' || *iter == '\n') {
735 24 : nsresult rv = manifest->HandleManifestLine(begin, iter);
736 :
737 24 : if (NS_FAILED(rv)) {
738 0 : LOG(("HandleManifestLine failed with 0x%08x", rv));
739 0 : return NS_ERROR_ABORT;
740 : }
741 :
742 24 : begin = iter;
743 24 : begin++;
744 : }
745 : }
746 :
747 : // any leftovers are saved for next time
748 8 : manifest->mReadBuf = Substring(begin, end);
749 :
750 8 : return NS_OK;
751 : }
752 :
753 : nsresult
754 8 : nsOfflineManifestItem::AddNamespace(PRUint32 namespaceType,
755 : const nsCString &namespaceSpec,
756 : const nsCString &data)
757 :
758 : {
759 : nsresult rv;
760 8 : if (!mNamespaces) {
761 8 : mNamespaces = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
762 8 : NS_ENSURE_SUCCESS(rv, rv);
763 : }
764 :
765 : nsCOMPtr<nsIApplicationCacheNamespace> ns =
766 16 : do_CreateInstance(NS_APPLICATIONCACHENAMESPACE_CONTRACTID, &rv);
767 8 : NS_ENSURE_SUCCESS(rv, rv);
768 :
769 8 : rv = ns->Init(namespaceType, namespaceSpec, data);
770 8 : NS_ENSURE_SUCCESS(rv, rv);
771 :
772 8 : rv = mNamespaces->AppendElement(ns, false);
773 8 : NS_ENSURE_SUCCESS(rv, rv);
774 :
775 8 : return NS_OK;
776 : }
777 :
778 : nsresult
779 32 : nsOfflineManifestItem::HandleManifestLine(const nsCString::const_iterator &aBegin,
780 : const nsCString::const_iterator &aEnd)
781 : {
782 32 : nsCString::const_iterator begin = aBegin;
783 32 : nsCString::const_iterator end = aEnd;
784 :
785 : // all lines ignore trailing spaces and tabs
786 32 : nsCString::const_iterator last = end;
787 32 : --last;
788 64 : while (end != begin && (*last == ' ' || *last == '\t')) {
789 0 : --end;
790 0 : --last;
791 : }
792 :
793 32 : if (mParserState == PARSE_INIT) {
794 : // Allow a UTF-8 BOM
795 8 : if (begin != end && static_cast<unsigned char>(*begin) == 0xef) {
796 0 : if (++begin == end || static_cast<unsigned char>(*begin) != 0xbb ||
797 0 : ++begin == end || static_cast<unsigned char>(*begin) != 0xbf) {
798 0 : mParserState = PARSE_ERROR;
799 0 : return NS_OK;
800 : }
801 0 : ++begin;
802 : }
803 :
804 16 : const nsCSubstring &magic = Substring(begin, end);
805 :
806 8 : if (!magic.EqualsLiteral("CACHE MANIFEST")) {
807 0 : mParserState = PARSE_ERROR;
808 0 : return NS_OK;
809 : }
810 :
811 8 : mParserState = PARSE_CACHE_ENTRIES;
812 8 : return NS_OK;
813 : }
814 :
815 : // lines other than the first ignore leading spaces and tabs
816 48 : while (begin != end && (*begin == ' ' || *begin == '\t'))
817 0 : begin++;
818 :
819 : // ignore blank lines and comments
820 24 : if (begin == end || *begin == '#')
821 8 : return NS_OK;
822 :
823 32 : const nsCSubstring &line = Substring(begin, end);
824 :
825 16 : if (line.EqualsLiteral("CACHE:")) {
826 0 : mParserState = PARSE_CACHE_ENTRIES;
827 0 : return NS_OK;
828 : }
829 :
830 16 : if (line.EqualsLiteral("FALLBACK:")) {
831 8 : mParserState = PARSE_FALLBACK_ENTRIES;
832 8 : return NS_OK;
833 : }
834 :
835 8 : if (line.EqualsLiteral("NETWORK:")) {
836 0 : mParserState = PARSE_BYPASS_ENTRIES;
837 0 : return NS_OK;
838 : }
839 :
840 : nsresult rv;
841 :
842 8 : switch(mParserState) {
843 : case PARSE_INIT:
844 : case PARSE_ERROR: {
845 : // this should have been dealt with earlier
846 0 : return NS_ERROR_FAILURE;
847 : }
848 :
849 : case PARSE_CACHE_ENTRIES: {
850 0 : nsCOMPtr<nsIURI> uri;
851 0 : rv = NS_NewURI(getter_AddRefs(uri), line, nsnull, mURI);
852 0 : if (NS_FAILED(rv))
853 : break;
854 0 : if (NS_FAILED(DropReferenceFromURL(uri)))
855 : break;
856 :
857 0 : nsCAutoString scheme;
858 0 : uri->GetScheme(scheme);
859 :
860 : // Manifest URIs must have the same scheme as the manifest.
861 : bool match;
862 0 : if (NS_FAILED(mURI->SchemeIs(scheme.get(), &match)) || !match)
863 : break;
864 :
865 0 : mExplicitURIs.AppendObject(uri);
866 : break;
867 : }
868 :
869 : case PARSE_FALLBACK_ENTRIES: {
870 8 : PRInt32 separator = line.FindChar(' ');
871 8 : if (separator == kNotFound) {
872 0 : separator = line.FindChar('\t');
873 0 : if (separator == kNotFound)
874 0 : break;
875 : }
876 :
877 16 : nsCString namespaceSpec(Substring(line, 0, separator));
878 16 : nsCString fallbackSpec(Substring(line, separator + 1));
879 8 : namespaceSpec.CompressWhitespace();
880 8 : fallbackSpec.CompressWhitespace();
881 :
882 16 : nsCOMPtr<nsIURI> namespaceURI;
883 8 : rv = NS_NewURI(getter_AddRefs(namespaceURI), namespaceSpec, nsnull, mURI);
884 8 : if (NS_FAILED(rv))
885 : break;
886 8 : if (NS_FAILED(DropReferenceFromURL(namespaceURI)))
887 : break;
888 8 : rv = namespaceURI->GetAsciiSpec(namespaceSpec);
889 8 : if (NS_FAILED(rv))
890 : break;
891 :
892 :
893 16 : nsCOMPtr<nsIURI> fallbackURI;
894 8 : rv = NS_NewURI(getter_AddRefs(fallbackURI), fallbackSpec, nsnull, mURI);
895 8 : if (NS_FAILED(rv))
896 : break;
897 8 : if (NS_FAILED(DropReferenceFromURL(fallbackURI)))
898 : break;
899 8 : rv = fallbackURI->GetAsciiSpec(fallbackSpec);
900 8 : if (NS_FAILED(rv))
901 : break;
902 :
903 : // Manifest and namespace must be same origin
904 8 : if (!NS_SecurityCompareURIs(mURI, namespaceURI,
905 8 : mStrictFileOriginPolicy))
906 : break;
907 :
908 : // Fallback and namespace must be same origin
909 8 : if (!NS_SecurityCompareURIs(namespaceURI, fallbackURI,
910 8 : mStrictFileOriginPolicy))
911 : break;
912 :
913 8 : mFallbackURIs.AppendObject(fallbackURI);
914 :
915 : AddNamespace(nsIApplicationCacheNamespace::NAMESPACE_FALLBACK,
916 8 : namespaceSpec, fallbackSpec);
917 : break;
918 : }
919 :
920 : case PARSE_BYPASS_ENTRIES: {
921 0 : if (line[0] == '*' && (line.Length() == 1 || line[1] == ' ' || line[1] == '\t'))
922 : {
923 : // '*' indicates to make the online whitelist wildcard flag open,
924 : // i.e. do allow load of resources not present in the offline cache
925 : // or not conforming any namespace.
926 : // We achive that simply by adding an 'empty' - i.e. universal
927 : // namespace of BYPASS type into the cache.
928 : AddNamespace(nsIApplicationCacheNamespace::NAMESPACE_BYPASS,
929 0 : EmptyCString(), EmptyCString());
930 0 : break;
931 : }
932 :
933 0 : nsCOMPtr<nsIURI> bypassURI;
934 0 : rv = NS_NewURI(getter_AddRefs(bypassURI), line, nsnull, mURI);
935 0 : if (NS_FAILED(rv))
936 : break;
937 :
938 0 : nsCAutoString scheme;
939 0 : bypassURI->GetScheme(scheme);
940 : bool equals;
941 0 : if (NS_FAILED(mURI->SchemeIs(scheme.get(), &equals)) || !equals)
942 : break;
943 0 : if (NS_FAILED(DropReferenceFromURL(bypassURI)))
944 : break;
945 0 : nsCString spec;
946 0 : if (NS_FAILED(bypassURI->GetAsciiSpec(spec)))
947 : break;
948 :
949 : AddNamespace(nsIApplicationCacheNamespace::NAMESPACE_BYPASS,
950 0 : spec, EmptyCString());
951 : break;
952 : }
953 : }
954 :
955 8 : return NS_OK;
956 : }
957 :
958 : nsresult
959 8 : nsOfflineManifestItem::GetOldManifestContentHash(nsIRequest *aRequest)
960 : {
961 : nsresult rv;
962 :
963 16 : nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(aRequest, &rv);
964 8 : NS_ENSURE_SUCCESS(rv, rv);
965 :
966 : // load the main cache token that is actually the old offline cache token and
967 : // read previous manifest content hash value
968 16 : nsCOMPtr<nsISupports> cacheToken;
969 8 : cachingChannel->GetCacheToken(getter_AddRefs(cacheToken));
970 8 : if (cacheToken) {
971 16 : nsCOMPtr<nsICacheEntryDescriptor> cacheDescriptor(do_QueryInterface(cacheToken, &rv));
972 8 : NS_ENSURE_SUCCESS(rv, rv);
973 :
974 8 : rv = cacheDescriptor->GetMetaDataElement("offline-manifest-hash", getter_Copies(mOldManifestHashValue));
975 8 : if (NS_FAILED(rv))
976 8 : mOldManifestHashValue.Truncate();
977 : }
978 :
979 8 : return NS_OK;
980 : }
981 :
982 : nsresult
983 8 : nsOfflineManifestItem::CheckNewManifestContentHash(nsIRequest *aRequest)
984 : {
985 : nsresult rv;
986 :
987 8 : if (!mManifestHash) {
988 : // Nothing to compare against...
989 0 : return NS_OK;
990 : }
991 :
992 16 : nsCString newManifestHashValue;
993 8 : rv = mManifestHash->Finish(true, mManifestHashValue);
994 8 : mManifestHash = nsnull;
995 :
996 8 : if (NS_FAILED(rv)) {
997 0 : LOG(("Could not finish manifest hash, rv=%08x", rv));
998 : // This is not critical error
999 0 : return NS_OK;
1000 : }
1001 :
1002 8 : if (!ParseSucceeded()) {
1003 : // Parsing failed, the hash is not valid
1004 0 : return NS_OK;
1005 : }
1006 :
1007 8 : if (mOldManifestHashValue == mManifestHashValue) {
1008 0 : LOG(("Update not needed, downloaded manifest content is byte-for-byte identical"));
1009 0 : mNeedsUpdate = false;
1010 : }
1011 :
1012 : // Store the manifest content hash value to the new
1013 : // offline cache token
1014 16 : nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(aRequest, &rv);
1015 8 : NS_ENSURE_SUCCESS(rv, rv);
1016 :
1017 16 : nsCOMPtr<nsISupports> cacheToken;
1018 8 : cachingChannel->GetOfflineCacheToken(getter_AddRefs(cacheToken));
1019 8 : if (cacheToken) {
1020 16 : nsCOMPtr<nsICacheEntryDescriptor> cacheDescriptor(do_QueryInterface(cacheToken, &rv));
1021 8 : NS_ENSURE_SUCCESS(rv, rv);
1022 :
1023 8 : rv = cacheDescriptor->SetMetaDataElement("offline-manifest-hash", mManifestHashValue.get());
1024 8 : NS_ENSURE_SUCCESS(rv, rv);
1025 : }
1026 :
1027 8 : return NS_OK;
1028 : }
1029 :
1030 : void
1031 8 : nsOfflineManifestItem::ReadStrictFileOriginPolicyPref()
1032 : {
1033 : mStrictFileOriginPolicy =
1034 8 : Preferences::GetBool("security.fileuri.strict_origin_policy", true);
1035 8 : }
1036 :
1037 : NS_IMETHODIMP
1038 8 : nsOfflineManifestItem::OnStartRequest(nsIRequest *aRequest,
1039 : nsISupports *aContext)
1040 : {
1041 : nsresult rv;
1042 :
1043 16 : nsCOMPtr<nsIHttpChannel> channel = do_QueryInterface(aRequest, &rv);
1044 8 : NS_ENSURE_SUCCESS(rv, rv);
1045 :
1046 : bool succeeded;
1047 8 : rv = channel->GetRequestSucceeded(&succeeded);
1048 8 : NS_ENSURE_SUCCESS(rv, rv);
1049 :
1050 8 : if (!succeeded) {
1051 0 : LOG(("HTTP request failed"));
1052 0 : mParserState = PARSE_ERROR;
1053 0 : return NS_ERROR_ABORT;
1054 : }
1055 :
1056 16 : nsCAutoString contentType;
1057 8 : rv = channel->GetContentType(contentType);
1058 8 : NS_ENSURE_SUCCESS(rv, rv);
1059 :
1060 8 : if (!contentType.EqualsLiteral("text/cache-manifest")) {
1061 0 : LOG(("Rejected cache manifest with Content-Type %s (expecting text/cache-manifest)",
1062 : contentType.get()));
1063 0 : mParserState = PARSE_ERROR;
1064 0 : return NS_ERROR_ABORT;
1065 : }
1066 :
1067 8 : rv = GetOldManifestContentHash(aRequest);
1068 8 : NS_ENSURE_SUCCESS(rv, rv);
1069 :
1070 8 : return nsOfflineCacheUpdateItem::OnStartRequest(aRequest, aContext);
1071 : }
1072 :
1073 : NS_IMETHODIMP
1074 8 : nsOfflineManifestItem::OnDataAvailable(nsIRequest *aRequest,
1075 : nsISupports *aContext,
1076 : nsIInputStream *aStream,
1077 : PRUint32 aOffset,
1078 : PRUint32 aCount)
1079 : {
1080 8 : PRUint32 bytesRead = 0;
1081 8 : aStream->ReadSegments(ReadManifest, this, aCount, &bytesRead);
1082 8 : mBytesRead += bytesRead;
1083 :
1084 8 : if (mParserState == PARSE_ERROR) {
1085 0 : LOG(("OnDataAvailable is canceling the request due a parse error\n"));
1086 0 : return NS_ERROR_ABORT;
1087 : }
1088 :
1089 8 : LOG(("loaded %u bytes into offline cache [offset=%u]\n",
1090 : bytesRead, aOffset));
1091 :
1092 : // All the parent method does is read and discard, don't bother
1093 : // chaining up.
1094 :
1095 8 : return NS_OK;
1096 : }
1097 :
1098 : NS_IMETHODIMP
1099 8 : nsOfflineManifestItem::OnStopRequest(nsIRequest *aRequest,
1100 : nsISupports *aContext,
1101 : nsresult aStatus)
1102 : {
1103 : // handle any leftover manifest data
1104 8 : nsCString::const_iterator begin, end;
1105 8 : mReadBuf.BeginReading(begin);
1106 8 : mReadBuf.EndReading(end);
1107 8 : nsresult rv = HandleManifestLine(begin, end);
1108 8 : NS_ENSURE_SUCCESS(rv, rv);
1109 :
1110 8 : if (mBytesRead == 0) {
1111 : // we didn't need to read (because LOAD_ONLY_IF_MODIFIED was
1112 : // specified.)
1113 0 : mNeedsUpdate = false;
1114 : } else {
1115 8 : rv = CheckNewManifestContentHash(aRequest);
1116 8 : NS_ENSURE_SUCCESS(rv, rv);
1117 : }
1118 :
1119 8 : return nsOfflineCacheUpdateItem::OnStopRequest(aRequest, aContext, aStatus);
1120 : }
1121 :
1122 : //-----------------------------------------------------------------------------
1123 : // nsOfflineCacheUpdate::nsISupports
1124 : //-----------------------------------------------------------------------------
1125 :
1126 392 : NS_IMPL_ISUPPORTS2(nsOfflineCacheUpdate,
1127 : nsIOfflineCacheUpdateObserver,
1128 : nsIOfflineCacheUpdate)
1129 :
1130 : //-----------------------------------------------------------------------------
1131 : // nsOfflineCacheUpdate <public>
1132 : //-----------------------------------------------------------------------------
1133 :
1134 8 : nsOfflineCacheUpdate::nsOfflineCacheUpdate()
1135 : : mState(STATE_UNINITIALIZED)
1136 : , mOwner(nsnull)
1137 : , mAddedItems(false)
1138 : , mPartialUpdate(false)
1139 : , mSucceeded(true)
1140 : , mObsolete(false)
1141 : , mCurrentItem(-1)
1142 8 : , mRescheduleCount(0)
1143 : {
1144 8 : }
1145 :
1146 16 : nsOfflineCacheUpdate::~nsOfflineCacheUpdate()
1147 : {
1148 8 : LOG(("nsOfflineCacheUpdate::~nsOfflineCacheUpdate [%p]", this));
1149 8 : }
1150 :
1151 : /* static */
1152 : nsresult
1153 32 : nsOfflineCacheUpdate::GetCacheKey(nsIURI *aURI, nsACString &aKey)
1154 : {
1155 32 : aKey.Truncate();
1156 :
1157 64 : nsCOMPtr<nsIURI> newURI;
1158 32 : nsresult rv = aURI->CloneIgnoringRef(getter_AddRefs(newURI));
1159 32 : NS_ENSURE_SUCCESS(rv, rv);
1160 :
1161 32 : rv = newURI->GetAsciiSpec(aKey);
1162 32 : NS_ENSURE_SUCCESS(rv, rv);
1163 :
1164 32 : return NS_OK;
1165 : }
1166 :
1167 : nsresult
1168 8 : nsOfflineCacheUpdate::Init(nsIURI *aManifestURI,
1169 : nsIURI *aDocumentURI,
1170 : nsIDOMDocument *aDocument)
1171 : {
1172 : nsresult rv;
1173 :
1174 : // Make sure the service has been initialized
1175 : nsOfflineCacheUpdateService* service =
1176 8 : nsOfflineCacheUpdateService::EnsureService();
1177 8 : if (!service)
1178 0 : return NS_ERROR_FAILURE;
1179 :
1180 8 : LOG(("nsOfflineCacheUpdate::Init [%p]", this));
1181 :
1182 8 : mPartialUpdate = false;
1183 :
1184 : // Only http and https applications are supported.
1185 : bool match;
1186 8 : rv = aManifestURI->SchemeIs("http", &match);
1187 8 : NS_ENSURE_SUCCESS(rv, rv);
1188 :
1189 8 : if (!match) {
1190 0 : rv = aManifestURI->SchemeIs("https", &match);
1191 0 : NS_ENSURE_SUCCESS(rv, rv);
1192 0 : if (!match)
1193 0 : return NS_ERROR_ABORT;
1194 : }
1195 :
1196 8 : mManifestURI = aManifestURI;
1197 :
1198 8 : rv = mManifestURI->GetAsciiHost(mUpdateDomain);
1199 8 : NS_ENSURE_SUCCESS(rv, rv);
1200 :
1201 16 : nsCAutoString manifestSpec;
1202 :
1203 8 : rv = GetCacheKey(mManifestURI, manifestSpec);
1204 8 : NS_ENSURE_SUCCESS(rv, rv);
1205 :
1206 8 : mDocumentURI = aDocumentURI;
1207 :
1208 : nsCOMPtr<nsIApplicationCacheService> cacheService =
1209 16 : do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv);
1210 8 : NS_ENSURE_SUCCESS(rv, rv);
1211 :
1212 8 : rv = cacheService->GetActiveCache(manifestSpec,
1213 8 : getter_AddRefs(mPreviousApplicationCache));
1214 8 : NS_ENSURE_SUCCESS(rv, rv);
1215 :
1216 8 : rv = cacheService->CreateApplicationCache(manifestSpec,
1217 8 : getter_AddRefs(mApplicationCache));
1218 8 : NS_ENSURE_SUCCESS(rv, rv);
1219 :
1220 8 : rv = mApplicationCache->GetClientID(mClientID);
1221 8 : NS_ENSURE_SUCCESS(rv, rv);
1222 :
1223 8 : mState = STATE_INITIALIZED;
1224 8 : return NS_OK;
1225 : }
1226 :
1227 : nsresult
1228 0 : nsOfflineCacheUpdate::InitPartial(nsIURI *aManifestURI,
1229 : const nsACString& clientID,
1230 : nsIURI *aDocumentURI)
1231 : {
1232 : nsresult rv;
1233 :
1234 : // Make sure the service has been initialized
1235 : nsOfflineCacheUpdateService* service =
1236 0 : nsOfflineCacheUpdateService::EnsureService();
1237 0 : if (!service)
1238 0 : return NS_ERROR_FAILURE;
1239 :
1240 0 : LOG(("nsOfflineCacheUpdate::InitPartial [%p]", this));
1241 :
1242 0 : mPartialUpdate = true;
1243 0 : mClientID = clientID;
1244 0 : mDocumentURI = aDocumentURI;
1245 :
1246 0 : mManifestURI = aManifestURI;
1247 0 : rv = mManifestURI->GetAsciiHost(mUpdateDomain);
1248 0 : NS_ENSURE_SUCCESS(rv, rv);
1249 :
1250 : nsCOMPtr<nsIApplicationCacheService> cacheService =
1251 0 : do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv);
1252 0 : NS_ENSURE_SUCCESS(rv, rv);
1253 :
1254 0 : rv = cacheService->GetApplicationCache(mClientID,
1255 0 : getter_AddRefs(mApplicationCache));
1256 0 : NS_ENSURE_SUCCESS(rv, rv);
1257 :
1258 0 : if (!mApplicationCache) {
1259 0 : nsCAutoString manifestSpec;
1260 0 : rv = GetCacheKey(mManifestURI, manifestSpec);
1261 0 : NS_ENSURE_SUCCESS(rv, rv);
1262 :
1263 0 : rv = cacheService->CreateApplicationCache
1264 0 : (manifestSpec, getter_AddRefs(mApplicationCache));
1265 0 : NS_ENSURE_SUCCESS(rv, rv);
1266 : }
1267 :
1268 0 : nsCAutoString groupID;
1269 0 : rv = mApplicationCache->GetGroupID(groupID);
1270 0 : NS_ENSURE_SUCCESS(rv, rv);
1271 :
1272 0 : rv = NS_NewURI(getter_AddRefs(mManifestURI), groupID);
1273 0 : NS_ENSURE_SUCCESS(rv, rv);
1274 :
1275 0 : mState = STATE_INITIALIZED;
1276 0 : return NS_OK;
1277 : }
1278 :
1279 : nsresult
1280 8 : nsOfflineCacheUpdate::HandleManifest(bool *aDoUpdate)
1281 : {
1282 : // Be pessimistic
1283 8 : *aDoUpdate = false;
1284 :
1285 : bool succeeded;
1286 8 : nsresult rv = mManifestItem->GetRequestSucceeded(&succeeded);
1287 8 : NS_ENSURE_SUCCESS(rv, rv);
1288 :
1289 8 : if (!succeeded || !mManifestItem->ParseSucceeded()) {
1290 0 : return NS_ERROR_FAILURE;
1291 : }
1292 :
1293 8 : if (!mManifestItem->NeedsUpdate()) {
1294 0 : return NS_OK;
1295 : }
1296 :
1297 : // Add items requested by the manifest.
1298 8 : const nsCOMArray<nsIURI> &manifestURIs = mManifestItem->GetExplicitURIs();
1299 8 : for (PRInt32 i = 0; i < manifestURIs.Count(); i++) {
1300 0 : rv = AddURI(manifestURIs[i], nsIApplicationCache::ITEM_EXPLICIT);
1301 0 : NS_ENSURE_SUCCESS(rv, rv);
1302 : }
1303 :
1304 8 : const nsCOMArray<nsIURI> &fallbackURIs = mManifestItem->GetFallbackURIs();
1305 16 : for (PRInt32 i = 0; i < fallbackURIs.Count(); i++) {
1306 8 : rv = AddURI(fallbackURIs[i], nsIApplicationCache::ITEM_FALLBACK);
1307 8 : NS_ENSURE_SUCCESS(rv, rv);
1308 : }
1309 :
1310 : // The document that requested the manifest is implicitly included
1311 : // as part of that manifest update.
1312 8 : rv = AddURI(mDocumentURI, nsIApplicationCache::ITEM_IMPLICIT);
1313 8 : NS_ENSURE_SUCCESS(rv, rv);
1314 :
1315 : // Add items previously cached implicitly
1316 8 : rv = AddExistingItems(nsIApplicationCache::ITEM_IMPLICIT);
1317 8 : NS_ENSURE_SUCCESS(rv, rv);
1318 :
1319 : // Add items requested by the script API
1320 8 : rv = AddExistingItems(nsIApplicationCache::ITEM_DYNAMIC);
1321 8 : NS_ENSURE_SUCCESS(rv, rv);
1322 :
1323 : // Add opportunistically cached items conforming current opportunistic
1324 : // namespace list
1325 : rv = AddExistingItems(nsIApplicationCache::ITEM_OPPORTUNISTIC,
1326 8 : &mManifestItem->GetOpportunisticNamespaces());
1327 8 : NS_ENSURE_SUCCESS(rv, rv);
1328 :
1329 8 : *aDoUpdate = true;
1330 :
1331 8 : return NS_OK;
1332 : }
1333 :
1334 : void
1335 24 : nsOfflineCacheUpdate::LoadCompleted()
1336 : {
1337 : nsresult rv;
1338 :
1339 : // Keep the object alive through a Finish() call.
1340 48 : nsCOMPtr<nsIOfflineCacheUpdate> kungFuDeathGrip(this);
1341 :
1342 24 : LOG(("nsOfflineCacheUpdate::LoadCompleted [%p]", this));
1343 :
1344 24 : if (mState == STATE_CANCELLED) {
1345 0 : Finish();
1346 : return;
1347 : }
1348 :
1349 24 : if (mState == STATE_CHECKING) {
1350 : // Manifest load finished.
1351 :
1352 8 : NS_ASSERTION(mManifestItem,
1353 : "Must have a manifest item in STATE_CHECKING.");
1354 :
1355 : // A 404 or 410 is interpreted as an intentional removal of
1356 : // the manifest file, rather than a transient server error.
1357 : // Obsolete this cache group if one of these is returned.
1358 : PRUint16 status;
1359 8 : rv = mManifestItem->GetStatus(&status);
1360 8 : if (status == 404 || status == 410) {
1361 0 : mSucceeded = false;
1362 0 : mObsolete = true;
1363 0 : if (mPreviousApplicationCache) {
1364 0 : NotifyState(nsIOfflineCacheUpdateObserver::STATE_OBSOLETE);
1365 : } else {
1366 0 : NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
1367 : }
1368 0 : Finish();
1369 : return;
1370 : }
1371 :
1372 : bool doUpdate;
1373 8 : if (NS_FAILED(HandleManifest(&doUpdate))) {
1374 0 : mSucceeded = false;
1375 0 : NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
1376 0 : Finish();
1377 : return;
1378 : }
1379 :
1380 8 : if (!doUpdate) {
1381 0 : mSucceeded = false;
1382 :
1383 0 : AssociateDocuments(mPreviousApplicationCache);
1384 :
1385 0 : ScheduleImplicit();
1386 :
1387 : // If we didn't need an implicit update, we can
1388 : // send noupdate and end the update now.
1389 0 : if (!mImplicitUpdate) {
1390 0 : NotifyState(nsIOfflineCacheUpdateObserver::STATE_NOUPDATE);
1391 0 : Finish();
1392 : }
1393 : return;
1394 : }
1395 :
1396 16 : rv = mApplicationCache->MarkEntry(mManifestItem->mCacheKey,
1397 16 : mManifestItem->mItemType);
1398 8 : if (NS_FAILED(rv)) {
1399 0 : mSucceeded = false;
1400 0 : NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
1401 0 : Finish();
1402 : return;
1403 : }
1404 :
1405 8 : mState = STATE_DOWNLOADING;
1406 8 : NotifyState(nsIOfflineCacheUpdateObserver::STATE_DOWNLOADING);
1407 :
1408 : // Start fetching resources.
1409 8 : ProcessNextURI();
1410 :
1411 : return;
1412 : }
1413 :
1414 : // Normal load finished.
1415 :
1416 32 : nsRefPtr<nsOfflineCacheUpdateItem> item = mItems[mCurrentItem];
1417 16 : mCurrentItem++;
1418 :
1419 : bool succeeded;
1420 16 : rv = item->GetRequestSucceeded(&succeeded);
1421 :
1422 : // Check for failures. 3XX, 4XX and 5XX errors on items explicitly
1423 : // listed in the manifest will cause the update to fail.
1424 16 : if (NS_FAILED(rv) || !succeeded) {
1425 0 : if (item->mItemType &
1426 : (nsIApplicationCache::ITEM_EXPLICIT |
1427 : nsIApplicationCache::ITEM_FALLBACK)) {
1428 0 : mSucceeded = false;
1429 : }
1430 : } else {
1431 16 : rv = mApplicationCache->MarkEntry(item->mCacheKey, item->mItemType);
1432 16 : if (NS_FAILED(rv)) {
1433 0 : mSucceeded = false;
1434 : }
1435 : }
1436 :
1437 16 : if (!mSucceeded) {
1438 0 : NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
1439 0 : Finish();
1440 : return;
1441 : }
1442 :
1443 16 : rv = NotifyState(nsIOfflineCacheUpdateObserver::STATE_ITEMCOMPLETED);
1444 16 : if (NS_FAILED(rv)) return;
1445 :
1446 32 : ProcessNextURI();
1447 : }
1448 :
1449 : void
1450 8 : nsOfflineCacheUpdate::ManifestCheckCompleted(nsresult aStatus,
1451 : const nsCString &aManifestHash)
1452 : {
1453 : // Keep the object alive through a Finish() call.
1454 16 : nsCOMPtr<nsIOfflineCacheUpdate> kungFuDeathGrip(this);
1455 :
1456 8 : if (NS_SUCCEEDED(aStatus)) {
1457 16 : nsCAutoString firstManifestHash;
1458 8 : mManifestItem->GetManifestHash(firstManifestHash);
1459 8 : if (aManifestHash != firstManifestHash) {
1460 0 : LOG(("Manifest has changed during cache items download [%p]", this));
1461 0 : aStatus = NS_ERROR_FAILURE;
1462 : }
1463 : }
1464 :
1465 8 : if (NS_FAILED(aStatus)) {
1466 0 : mSucceeded = false;
1467 0 : NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
1468 : }
1469 :
1470 8 : if (NS_FAILED(aStatus) && mRescheduleCount < kRescheduleLimit) {
1471 : // Do the final stuff but prevent notification of STATE_FINISHED.
1472 : // That would disconnect listeners that are responsible for document
1473 : // association after a successful update. Forwarding notifications
1474 : // from a new update through this dead update to them is absolutely
1475 : // correct.
1476 0 : FinishNoNotify();
1477 :
1478 : nsRefPtr<nsOfflineCacheUpdate> newUpdate =
1479 0 : new nsOfflineCacheUpdate();
1480 : // Leave aDocument argument null. Only glues and children keep
1481 : // document instances.
1482 0 : newUpdate->Init(mManifestURI, mDocumentURI, nsnull);
1483 :
1484 : // In a rare case the manifest will not be modified on the next refetch
1485 : // transfer all master document URIs to the new update to ensure that
1486 : // all documents refering it will be properly cached.
1487 0 : for (PRInt32 i = 0; i < mDocumentURIs.Count(); i++) {
1488 0 : newUpdate->StickDocument(mDocumentURIs[i]);
1489 : }
1490 :
1491 0 : newUpdate->mRescheduleCount = mRescheduleCount + 1;
1492 0 : newUpdate->AddObserver(this, false);
1493 0 : newUpdate->Schedule();
1494 : }
1495 : else {
1496 8 : Finish();
1497 : }
1498 8 : }
1499 :
1500 : nsresult
1501 8 : nsOfflineCacheUpdate::Begin()
1502 : {
1503 8 : LOG(("nsOfflineCacheUpdate::Begin [%p]", this));
1504 :
1505 : // Keep the object alive through a ProcessNextURI()/Finish() call.
1506 16 : nsCOMPtr<nsIOfflineCacheUpdate> kungFuDeathGrip(this);
1507 :
1508 8 : mCurrentItem = 0;
1509 :
1510 8 : if (mPartialUpdate) {
1511 0 : mState = STATE_DOWNLOADING;
1512 0 : NotifyState(nsIOfflineCacheUpdateObserver::STATE_DOWNLOADING);
1513 0 : ProcessNextURI();
1514 0 : return NS_OK;
1515 : }
1516 :
1517 : // Start checking the manifest.
1518 16 : nsCOMPtr<nsIURI> uri;
1519 :
1520 : mManifestItem = new nsOfflineManifestItem(this, mManifestURI,
1521 : mDocumentURI,
1522 : mPreviousApplicationCache,
1523 16 : mClientID);
1524 8 : if (!mManifestItem) {
1525 0 : return NS_ERROR_OUT_OF_MEMORY;
1526 : }
1527 :
1528 8 : mState = STATE_CHECKING;
1529 8 : NotifyState(nsIOfflineCacheUpdateObserver::STATE_CHECKING);
1530 :
1531 8 : nsresult rv = mManifestItem->OpenChannel();
1532 8 : if (NS_FAILED(rv)) {
1533 0 : LoadCompleted();
1534 : }
1535 :
1536 8 : return NS_OK;
1537 : }
1538 :
1539 : nsresult
1540 0 : nsOfflineCacheUpdate::Cancel()
1541 : {
1542 0 : LOG(("nsOfflineCacheUpdate::Cancel [%p]", this));
1543 :
1544 0 : mState = STATE_CANCELLED;
1545 0 : mSucceeded = false;
1546 :
1547 0 : if (mCurrentItem >= 0 &&
1548 0 : mCurrentItem < static_cast<PRInt32>(mItems.Length())) {
1549 : // Load might be running
1550 0 : mItems[mCurrentItem]->Cancel();
1551 : }
1552 :
1553 0 : return NS_OK;
1554 : }
1555 :
1556 : //-----------------------------------------------------------------------------
1557 : // nsOfflineCacheUpdate <private>
1558 : //-----------------------------------------------------------------------------
1559 :
1560 : nsresult
1561 24 : nsOfflineCacheUpdate::AddExistingItems(PRUint32 aType,
1562 : nsTArray<nsCString>* namespaceFilter)
1563 : {
1564 24 : if (!mPreviousApplicationCache) {
1565 24 : return NS_OK;
1566 : }
1567 :
1568 0 : if (namespaceFilter && namespaceFilter->Length() == 0) {
1569 : // Don't bother to walk entries when there are no namespaces
1570 : // defined.
1571 0 : return NS_OK;
1572 : }
1573 :
1574 0 : PRUint32 count = 0;
1575 0 : char **keys = nsnull;
1576 0 : nsresult rv = mPreviousApplicationCache->GatherEntries(aType,
1577 0 : &count, &keys);
1578 0 : NS_ENSURE_SUCCESS(rv, rv);
1579 :
1580 0 : AutoFreeArray autoFree(count, keys);
1581 :
1582 0 : for (PRUint32 i = 0; i < count; i++) {
1583 0 : if (namespaceFilter) {
1584 0 : bool found = false;
1585 0 : for (PRUint32 j = 0; j < namespaceFilter->Length() && !found; j++) {
1586 0 : found = StringBeginsWith(nsDependentCString(keys[i]),
1587 0 : namespaceFilter->ElementAt(j));
1588 : }
1589 :
1590 0 : if (!found)
1591 0 : continue;
1592 : }
1593 :
1594 0 : nsCOMPtr<nsIURI> uri;
1595 0 : if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(uri), keys[i]))) {
1596 0 : rv = AddURI(uri, aType);
1597 0 : NS_ENSURE_SUCCESS(rv, rv);
1598 : }
1599 : }
1600 :
1601 0 : return NS_OK;
1602 : }
1603 :
1604 : nsresult
1605 24 : nsOfflineCacheUpdate::ProcessNextURI()
1606 : {
1607 : // Keep the object alive through a Finish() call.
1608 48 : nsCOMPtr<nsIOfflineCacheUpdate> kungFuDeathGrip(this);
1609 :
1610 24 : LOG(("nsOfflineCacheUpdate::ProcessNextURI [%p, current=%d, numItems=%d]",
1611 : this, mCurrentItem, mItems.Length()));
1612 :
1613 24 : NS_ASSERTION(mState == STATE_DOWNLOADING,
1614 : "ProcessNextURI should only be called from the DOWNLOADING state");
1615 :
1616 24 : if (mCurrentItem >= static_cast<PRInt32>(mItems.Length())) {
1617 8 : if (mPartialUpdate) {
1618 0 : return Finish();
1619 : } else {
1620 : // Verify that the manifest wasn't changed during the
1621 : // update, to prevent capturing a cache while the server
1622 : // is being updated. The check will call
1623 : // ManifestCheckCompleted() when it's done.
1624 : nsRefPtr<nsManifestCheck> manifestCheck =
1625 24 : new nsManifestCheck(this, mManifestURI, mDocumentURI);
1626 8 : if (NS_FAILED(manifestCheck->Begin())) {
1627 0 : mSucceeded = false;
1628 0 : NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
1629 0 : return Finish();
1630 : }
1631 :
1632 8 : return NS_OK;
1633 : }
1634 : }
1635 :
1636 : #if defined(PR_LOGGING)
1637 16 : if (LOG_ENABLED()) {
1638 0 : nsCAutoString spec;
1639 0 : mItems[mCurrentItem]->mURI->GetSpec(spec);
1640 0 : LOG(("%p: Opening channel for %s", this, spec.get()));
1641 : }
1642 : #endif
1643 :
1644 16 : NotifyState(nsIOfflineCacheUpdateObserver::STATE_ITEMSTARTED);
1645 :
1646 16 : nsresult rv = mItems[mCurrentItem]->OpenChannel();
1647 16 : if (NS_FAILED(rv)) {
1648 0 : LoadCompleted();
1649 0 : return rv;
1650 : }
1651 :
1652 16 : return NS_OK;
1653 : }
1654 :
1655 : nsresult
1656 64 : nsOfflineCacheUpdate::GatherObservers(nsCOMArray<nsIOfflineCacheUpdateObserver> &aObservers)
1657 : {
1658 64 : for (PRInt32 i = 0; i < mWeakObservers.Count(); i++) {
1659 : nsCOMPtr<nsIOfflineCacheUpdateObserver> observer =
1660 0 : do_QueryReferent(mWeakObservers[i]);
1661 0 : if (observer)
1662 0 : aObservers.AppendObject(observer);
1663 : else
1664 0 : mWeakObservers.RemoveObjectAt(i--);
1665 : }
1666 :
1667 128 : for (PRInt32 i = 0; i < mObservers.Count(); i++) {
1668 64 : aObservers.AppendObject(mObservers[i]);
1669 : }
1670 :
1671 64 : return NS_OK;
1672 : }
1673 :
1674 : nsresult
1675 56 : nsOfflineCacheUpdate::NotifyState(PRUint32 state)
1676 : {
1677 56 : LOG(("nsOfflineCacheUpdate::NotifyState [%p, %d]", this, state));
1678 :
1679 112 : nsCOMArray<nsIOfflineCacheUpdateObserver> observers;
1680 56 : nsresult rv = GatherObservers(observers);
1681 56 : NS_ENSURE_SUCCESS(rv, rv);
1682 :
1683 112 : for (PRInt32 i = 0; i < observers.Count(); i++) {
1684 56 : observers[i]->UpdateStateChanged(this, state);
1685 : }
1686 :
1687 56 : return NS_OK;
1688 : }
1689 :
1690 : nsresult
1691 8 : nsOfflineCacheUpdate::AssociateDocuments(nsIApplicationCache* cache)
1692 : {
1693 16 : nsCOMArray<nsIOfflineCacheUpdateObserver> observers;
1694 8 : nsresult rv = GatherObservers(observers);
1695 8 : NS_ENSURE_SUCCESS(rv, rv);
1696 :
1697 16 : for (PRInt32 i = 0; i < observers.Count(); i++) {
1698 8 : observers[i]->ApplicationCacheAvailable(cache);
1699 : }
1700 :
1701 8 : return NS_OK;
1702 : }
1703 :
1704 : void
1705 0 : nsOfflineCacheUpdate::StickDocument(nsIURI *aDocumentURI)
1706 : {
1707 0 : if (!aDocumentURI)
1708 0 : return;
1709 :
1710 0 : mDocumentURIs.AppendObject(aDocumentURI);
1711 : }
1712 :
1713 : void
1714 8 : nsOfflineCacheUpdate::SetOwner(nsOfflineCacheUpdateOwner *aOwner)
1715 : {
1716 8 : NS_ASSERTION(!mOwner, "Tried to set cache update owner twice.");
1717 8 : mOwner = aOwner;
1718 8 : }
1719 :
1720 : nsresult
1721 0 : nsOfflineCacheUpdate::UpdateFinished(nsOfflineCacheUpdate *aUpdate)
1722 : {
1723 : // Keep the object alive through a Finish() call.
1724 0 : nsCOMPtr<nsIOfflineCacheUpdate> kungFuDeathGrip(this);
1725 :
1726 0 : mImplicitUpdate = nsnull;
1727 :
1728 0 : NotifyState(nsIOfflineCacheUpdateObserver::STATE_NOUPDATE);
1729 0 : Finish();
1730 :
1731 0 : return NS_OK;
1732 : }
1733 :
1734 : nsresult
1735 0 : nsOfflineCacheUpdate::ScheduleImplicit()
1736 : {
1737 0 : if (mDocumentURIs.Count() == 0)
1738 0 : return NS_OK;
1739 :
1740 : nsresult rv;
1741 :
1742 0 : nsRefPtr<nsOfflineCacheUpdate> update = new nsOfflineCacheUpdate();
1743 0 : NS_ENSURE_TRUE(update, NS_ERROR_OUT_OF_MEMORY);
1744 :
1745 0 : nsCAutoString clientID;
1746 0 : if (mPreviousApplicationCache) {
1747 0 : rv = mPreviousApplicationCache->GetClientID(clientID);
1748 0 : NS_ENSURE_SUCCESS(rv, rv);
1749 : }
1750 : else {
1751 0 : clientID = mClientID;
1752 : }
1753 :
1754 0 : rv = update->InitPartial(mManifestURI, clientID, mDocumentURI);
1755 0 : NS_ENSURE_SUCCESS(rv, rv);
1756 :
1757 0 : for (PRInt32 i = 0; i < mDocumentURIs.Count(); i++) {
1758 : rv = update->AddURI(mDocumentURIs[i],
1759 0 : nsIApplicationCache::ITEM_IMPLICIT);
1760 0 : NS_ENSURE_SUCCESS(rv, rv);
1761 : }
1762 :
1763 0 : update->SetOwner(this);
1764 0 : rv = update->Begin();
1765 0 : NS_ENSURE_SUCCESS(rv, rv);
1766 :
1767 0 : mImplicitUpdate = update;
1768 :
1769 0 : return NS_OK;
1770 : }
1771 :
1772 : nsresult
1773 8 : nsOfflineCacheUpdate::FinishNoNotify()
1774 : {
1775 8 : LOG(("nsOfflineCacheUpdate::Finish [%p]", this));
1776 :
1777 8 : mState = STATE_FINISHED;
1778 :
1779 8 : if (!mPartialUpdate) {
1780 8 : if (mSucceeded) {
1781 8 : nsIArray *namespaces = mManifestItem->GetNamespaces();
1782 8 : nsresult rv = mApplicationCache->AddNamespaces(namespaces);
1783 8 : if (NS_FAILED(rv)) {
1784 0 : NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
1785 0 : mSucceeded = false;
1786 : }
1787 :
1788 8 : rv = mApplicationCache->Activate();
1789 8 : if (NS_FAILED(rv)) {
1790 0 : NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
1791 0 : mSucceeded = false;
1792 : }
1793 :
1794 8 : AssociateDocuments(mApplicationCache);
1795 : }
1796 :
1797 8 : if (mObsolete) {
1798 : nsCOMPtr<nsIApplicationCacheService> appCacheService =
1799 0 : do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID);
1800 0 : if (appCacheService) {
1801 0 : nsCAutoString groupID;
1802 0 : mApplicationCache->GetGroupID(groupID);
1803 0 : appCacheService->DeactivateGroup(groupID);
1804 : }
1805 : }
1806 :
1807 8 : if (!mSucceeded) {
1808 : // Update was not merged, mark all the loads as failures
1809 0 : for (PRUint32 i = 0; i < mItems.Length(); i++) {
1810 0 : mItems[i]->Cancel();
1811 : }
1812 :
1813 0 : mApplicationCache->Discard();
1814 : }
1815 : }
1816 :
1817 8 : nsresult rv = NS_OK;
1818 :
1819 8 : if (mOwner) {
1820 8 : rv = mOwner->UpdateFinished(this);
1821 8 : mOwner = nsnull;
1822 : }
1823 :
1824 8 : return rv;
1825 : }
1826 :
1827 : nsresult
1828 8 : nsOfflineCacheUpdate::Finish()
1829 : {
1830 8 : nsresult rv = FinishNoNotify();
1831 :
1832 8 : NotifyState(nsIOfflineCacheUpdateObserver::STATE_FINISHED);
1833 :
1834 8 : return rv;
1835 : }
1836 :
1837 : //-----------------------------------------------------------------------------
1838 : // nsOfflineCacheUpdate::nsIOfflineCacheUpdate
1839 : //-----------------------------------------------------------------------------
1840 :
1841 : NS_IMETHODIMP
1842 0 : nsOfflineCacheUpdate::GetUpdateDomain(nsACString &aUpdateDomain)
1843 : {
1844 0 : NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
1845 :
1846 0 : aUpdateDomain = mUpdateDomain;
1847 0 : return NS_OK;
1848 : }
1849 :
1850 : NS_IMETHODIMP
1851 0 : nsOfflineCacheUpdate::GetStatus(PRUint16 *aStatus)
1852 : {
1853 0 : switch (mState) {
1854 : case STATE_CHECKING :
1855 0 : *aStatus = nsIDOMOfflineResourceList::CHECKING;
1856 0 : return NS_OK;
1857 : case STATE_DOWNLOADING :
1858 0 : *aStatus = nsIDOMOfflineResourceList::DOWNLOADING;
1859 0 : return NS_OK;
1860 : default :
1861 0 : *aStatus = nsIDOMOfflineResourceList::IDLE;
1862 0 : return NS_OK;
1863 : }
1864 :
1865 : return NS_ERROR_FAILURE;
1866 : }
1867 :
1868 : NS_IMETHODIMP
1869 0 : nsOfflineCacheUpdate::GetPartial(bool *aPartial)
1870 : {
1871 0 : *aPartial = mPartialUpdate;
1872 0 : return NS_OK;
1873 : }
1874 :
1875 : NS_IMETHODIMP
1876 0 : nsOfflineCacheUpdate::GetManifestURI(nsIURI **aManifestURI)
1877 : {
1878 0 : NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
1879 :
1880 0 : NS_IF_ADDREF(*aManifestURI = mManifestURI);
1881 0 : return NS_OK;
1882 : }
1883 :
1884 : NS_IMETHODIMP
1885 0 : nsOfflineCacheUpdate::GetSucceeded(bool *aSucceeded)
1886 : {
1887 0 : NS_ENSURE_TRUE(mState == STATE_FINISHED, NS_ERROR_NOT_AVAILABLE);
1888 :
1889 0 : *aSucceeded = mSucceeded;
1890 :
1891 0 : return NS_OK;
1892 : }
1893 :
1894 : NS_IMETHODIMP
1895 0 : nsOfflineCacheUpdate::GetIsUpgrade(bool *aIsUpgrade)
1896 : {
1897 0 : NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
1898 :
1899 0 : *aIsUpgrade = (mPreviousApplicationCache != nsnull);
1900 :
1901 0 : return NS_OK;
1902 : }
1903 :
1904 : nsresult
1905 16 : nsOfflineCacheUpdate::AddURI(nsIURI *aURI, PRUint32 aType)
1906 : {
1907 16 : NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
1908 :
1909 16 : if (mState >= STATE_DOWNLOADING)
1910 0 : return NS_ERROR_NOT_AVAILABLE;
1911 :
1912 : // Resource URIs must have the same scheme as the manifest.
1913 32 : nsCAutoString scheme;
1914 16 : aURI->GetScheme(scheme);
1915 :
1916 : bool match;
1917 16 : if (NS_FAILED(mManifestURI->SchemeIs(scheme.get(), &match)) || !match)
1918 0 : return NS_ERROR_FAILURE;
1919 :
1920 : // Don't fetch the same URI twice.
1921 24 : for (PRUint32 i = 0; i < mItems.Length(); i++) {
1922 : bool equals;
1923 8 : if (NS_SUCCEEDED(mItems[i]->mURI->Equals(aURI, &equals)) && equals) {
1924 : // retain both types.
1925 0 : mItems[i]->mItemType |= aType;
1926 0 : return NS_OK;
1927 : }
1928 : }
1929 :
1930 : nsRefPtr<nsOfflineCacheUpdateItem> item =
1931 : new nsOfflineCacheUpdateItem(this, aURI, mDocumentURI,
1932 : mPreviousApplicationCache, mClientID,
1933 48 : aType);
1934 16 : if (!item) return NS_ERROR_OUT_OF_MEMORY;
1935 :
1936 16 : mItems.AppendElement(item);
1937 16 : mAddedItems = true;
1938 :
1939 16 : return NS_OK;
1940 : }
1941 :
1942 : NS_IMETHODIMP
1943 0 : nsOfflineCacheUpdate::AddDynamicURI(nsIURI *aURI)
1944 : {
1945 0 : if (GeckoProcessType_Default != XRE_GetProcessType())
1946 0 : return NS_ERROR_NOT_IMPLEMENTED;
1947 :
1948 : // If this is a partial update and the resource is already in the
1949 : // cache, we should only mark the entry, not fetch it again.
1950 0 : if (mPartialUpdate) {
1951 0 : nsCAutoString key;
1952 0 : GetCacheKey(aURI, key);
1953 :
1954 : PRUint32 types;
1955 0 : nsresult rv = mApplicationCache->GetTypes(key, &types);
1956 0 : if (NS_SUCCEEDED(rv)) {
1957 0 : if (!(types & nsIApplicationCache::ITEM_DYNAMIC)) {
1958 0 : mApplicationCache->MarkEntry
1959 0 : (key, nsIApplicationCache::ITEM_DYNAMIC);
1960 : }
1961 0 : return NS_OK;
1962 : }
1963 : }
1964 :
1965 0 : return AddURI(aURI, nsIApplicationCache::ITEM_DYNAMIC);
1966 : }
1967 :
1968 : NS_IMETHODIMP
1969 8 : nsOfflineCacheUpdate::AddObserver(nsIOfflineCacheUpdateObserver *aObserver,
1970 : bool aHoldWeak)
1971 : {
1972 8 : LOG(("nsOfflineCacheUpdate::AddObserver [%p] to update [%p]", aObserver, this));
1973 :
1974 8 : NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
1975 :
1976 8 : if (aHoldWeak) {
1977 0 : nsCOMPtr<nsIWeakReference> weakRef = do_GetWeakReference(aObserver);
1978 0 : mWeakObservers.AppendObject(weakRef);
1979 : } else {
1980 8 : mObservers.AppendObject(aObserver);
1981 : }
1982 :
1983 8 : return NS_OK;
1984 : }
1985 :
1986 : NS_IMETHODIMP
1987 8 : nsOfflineCacheUpdate::RemoveObserver(nsIOfflineCacheUpdateObserver *aObserver)
1988 : {
1989 8 : LOG(("nsOfflineCacheUpdate::RemoveObserver [%p] from update [%p]", aObserver, this));
1990 :
1991 8 : NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
1992 :
1993 8 : for (PRInt32 i = 0; i < mWeakObservers.Count(); i++) {
1994 : nsCOMPtr<nsIOfflineCacheUpdateObserver> observer =
1995 0 : do_QueryReferent(mWeakObservers[i]);
1996 0 : if (observer == aObserver) {
1997 0 : mWeakObservers.RemoveObjectAt(i);
1998 0 : return NS_OK;
1999 : }
2000 : }
2001 :
2002 8 : for (PRInt32 i = 0; i < mObservers.Count(); i++) {
2003 8 : if (mObservers[i] == aObserver) {
2004 8 : mObservers.RemoveObjectAt(i);
2005 8 : return NS_OK;
2006 : }
2007 : }
2008 :
2009 0 : return NS_OK;
2010 : }
2011 :
2012 :
2013 : NS_IMETHODIMP
2014 8 : nsOfflineCacheUpdate::Schedule()
2015 : {
2016 8 : LOG(("nsOfflineCacheUpdate::Schedule [%p]", this));
2017 :
2018 : nsOfflineCacheUpdateService* service =
2019 8 : nsOfflineCacheUpdateService::EnsureService();
2020 :
2021 8 : if (!service) {
2022 0 : return NS_ERROR_FAILURE;
2023 : }
2024 :
2025 8 : return service->ScheduleUpdate(this);
2026 : }
2027 :
2028 : NS_IMETHODIMP
2029 0 : nsOfflineCacheUpdate::UpdateStateChanged(nsIOfflineCacheUpdate *aUpdate,
2030 : PRUint32 aState)
2031 : {
2032 0 : if (aState == nsIOfflineCacheUpdateObserver::STATE_FINISHED) {
2033 : // Take the mSucceeded flag from the underlying update, we will be
2034 : // queried for it soon. mSucceeded of this update is false (manifest
2035 : // check failed) but the subsequent re-fetch update might succeed
2036 : bool succeeded;
2037 0 : aUpdate->GetSucceeded(&succeeded);
2038 0 : mSucceeded = succeeded;
2039 : }
2040 :
2041 0 : nsresult rv = NotifyState(aState);
2042 0 : if (aState == nsIOfflineCacheUpdateObserver::STATE_FINISHED)
2043 0 : aUpdate->RemoveObserver(this);
2044 :
2045 0 : return rv;
2046 : }
2047 :
2048 : NS_IMETHODIMP
2049 0 : nsOfflineCacheUpdate::ApplicationCacheAvailable(nsIApplicationCache *applicationCache)
2050 : {
2051 0 : return AssociateDocuments(applicationCache);
2052 : }
|