Tue, 06 Jan 2015 21:39:09 +0100
Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
1 /* -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #if defined(MOZ_LOGGING)
7 #define FORCE_PR_LOG
8 #endif
10 #include "nsOfflineCacheUpdate.h"
12 #include "nsCPrefetchService.h"
13 #include "nsCURILoader.h"
14 #include "nsIApplicationCacheContainer.h"
15 #include "nsIApplicationCacheChannel.h"
16 #include "nsIApplicationCacheService.h"
17 #include "nsICache.h"
18 #include "nsICacheService.h"
19 #include "nsICacheSession.h"
20 #include "nsICachingChannel.h"
21 #include "nsIContent.h"
22 #include "mozilla/dom/Element.h"
23 #include "nsIDocumentLoader.h"
24 #include "nsIDOMElement.h"
25 #include "nsIDOMWindow.h"
26 #include "nsIDOMOfflineResourceList.h"
27 #include "nsIDocument.h"
28 #include "nsIObserverService.h"
29 #include "nsIURL.h"
30 #include "nsIWebProgress.h"
31 #include "nsICryptoHash.h"
32 #include "nsICacheEntry.h"
33 #include "nsIPermissionManager.h"
34 #include "nsIPrincipal.h"
35 #include "nsNetCID.h"
36 #include "nsNetUtil.h"
37 #include "nsServiceManagerUtils.h"
38 #include "nsStreamUtils.h"
39 #include "nsThreadUtils.h"
40 #include "nsProxyRelease.h"
41 #include "nsIConsoleService.h"
42 #include "prlog.h"
43 #include "nsIAsyncVerifyRedirectCallback.h"
44 #include "mozilla/Preferences.h"
45 #include "mozilla/Attributes.h"
47 #include "nsXULAppAPI.h"
49 using namespace mozilla;
51 static const uint32_t kRescheduleLimit = 3;
52 // Max number of retries for every entry of pinned app.
53 static const uint32_t kPinnedEntryRetriesLimit = 3;
54 // Maximum number of parallel items loads
55 static const uint32_t kParallelLoadLimit = 15;
57 // Quota for offline apps when preloading
58 static const int32_t kCustomProfileQuota = 512000;
60 #if defined(PR_LOGGING)
61 //
62 // To enable logging (see prlog.h for full details):
63 //
64 // set NSPR_LOG_MODULES=nsOfflineCacheUpdate:5
65 // set NSPR_LOG_FILE=offlineupdate.log
66 //
67 // this enables PR_LOG_ALWAYS level information and places all output in
68 // the file offlineupdate.log
69 //
70 extern PRLogModuleInfo *gOfflineCacheUpdateLog;
71 #endif
73 #undef LOG
74 #define LOG(args) PR_LOG(gOfflineCacheUpdateLog, 4, args)
76 #undef LOG_ENABLED
77 #define LOG_ENABLED() PR_LOG_TEST(gOfflineCacheUpdateLog, 4)
79 class AutoFreeArray {
80 public:
81 AutoFreeArray(uint32_t count, char **values)
82 : mCount(count), mValues(values) {};
83 ~AutoFreeArray() { NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(mCount, mValues); }
84 private:
85 uint32_t mCount;
86 char **mValues;
87 };
89 namespace { // anon
91 nsresult
92 DropReferenceFromURL(nsIURI * aURI)
93 {
94 // XXXdholbert If this SetRef fails, callers of this method probably
95 // want to call aURI->CloneIgnoringRef() and use the result of that.
96 return aURI->SetRef(EmptyCString());
97 }
99 void
100 LogToConsole(const char * message, nsOfflineCacheUpdateItem * item = nullptr)
101 {
102 nsCOMPtr<nsIConsoleService> consoleService =
103 do_GetService(NS_CONSOLESERVICE_CONTRACTID);
104 if (consoleService)
105 {
106 nsAutoString messageUTF16 = NS_ConvertUTF8toUTF16(message);
107 if (item && item->mURI) {
108 nsAutoCString uriSpec;
109 item->mURI->GetSpec(uriSpec);
111 messageUTF16.Append(NS_LITERAL_STRING(", URL="));
112 messageUTF16.Append(NS_ConvertUTF8toUTF16(uriSpec));
113 }
114 consoleService->LogStringMessage(messageUTF16.get());
115 }
116 }
118 } // anon namespace
120 //-----------------------------------------------------------------------------
121 // nsManifestCheck
122 //-----------------------------------------------------------------------------
124 class nsManifestCheck MOZ_FINAL : public nsIStreamListener
125 , public nsIChannelEventSink
126 , public nsIInterfaceRequestor
127 {
128 public:
129 nsManifestCheck(nsOfflineCacheUpdate *aUpdate,
130 nsIURI *aURI,
131 nsIURI *aReferrerURI)
132 : mUpdate(aUpdate)
133 , mURI(aURI)
134 , mReferrerURI(aReferrerURI)
135 {}
137 NS_DECL_ISUPPORTS
138 NS_DECL_NSIREQUESTOBSERVER
139 NS_DECL_NSISTREAMLISTENER
140 NS_DECL_NSICHANNELEVENTSINK
141 NS_DECL_NSIINTERFACEREQUESTOR
143 nsresult Begin();
145 private:
147 static NS_METHOD ReadManifest(nsIInputStream *aInputStream,
148 void *aClosure,
149 const char *aFromSegment,
150 uint32_t aOffset,
151 uint32_t aCount,
152 uint32_t *aBytesConsumed);
154 nsRefPtr<nsOfflineCacheUpdate> mUpdate;
155 nsCOMPtr<nsIURI> mURI;
156 nsCOMPtr<nsIURI> mReferrerURI;
157 nsCOMPtr<nsICryptoHash> mManifestHash;
158 nsCOMPtr<nsIChannel> mChannel;
159 };
161 //-----------------------------------------------------------------------------
162 // nsManifestCheck::nsISupports
163 //-----------------------------------------------------------------------------
164 NS_IMPL_ISUPPORTS(nsManifestCheck,
165 nsIRequestObserver,
166 nsIStreamListener,
167 nsIChannelEventSink,
168 nsIInterfaceRequestor)
170 //-----------------------------------------------------------------------------
171 // nsManifestCheck <public>
172 //-----------------------------------------------------------------------------
174 nsresult
175 nsManifestCheck::Begin()
176 {
177 nsresult rv;
178 mManifestHash = do_CreateInstance("@mozilla.org/security/hash;1", &rv);
179 NS_ENSURE_SUCCESS(rv, rv);
181 rv = mManifestHash->Init(nsICryptoHash::MD5);
182 NS_ENSURE_SUCCESS(rv, rv);
184 rv = NS_NewChannel(getter_AddRefs(mChannel),
185 mURI,
186 nullptr, nullptr, nullptr,
187 nsIRequest::LOAD_BYPASS_CACHE);
188 NS_ENSURE_SUCCESS(rv, rv);
190 // configure HTTP specific stuff
191 nsCOMPtr<nsIHttpChannel> httpChannel =
192 do_QueryInterface(mChannel);
193 if (httpChannel) {
194 httpChannel->SetReferrer(mReferrerURI);
195 httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("X-Moz"),
196 NS_LITERAL_CSTRING("offline-resource"),
197 false);
198 }
200 rv = mChannel->AsyncOpen(this, nullptr);
201 NS_ENSURE_SUCCESS(rv, rv);
203 return NS_OK;
204 }
206 //-----------------------------------------------------------------------------
207 // nsManifestCheck <public>
208 //-----------------------------------------------------------------------------
210 /* static */
211 NS_METHOD
212 nsManifestCheck::ReadManifest(nsIInputStream *aInputStream,
213 void *aClosure,
214 const char *aFromSegment,
215 uint32_t aOffset,
216 uint32_t aCount,
217 uint32_t *aBytesConsumed)
218 {
219 nsManifestCheck *manifestCheck =
220 static_cast<nsManifestCheck*>(aClosure);
222 nsresult rv;
223 *aBytesConsumed = aCount;
225 rv = manifestCheck->mManifestHash->Update(
226 reinterpret_cast<const uint8_t *>(aFromSegment), aCount);
227 NS_ENSURE_SUCCESS(rv, rv);
229 return NS_OK;
230 }
232 //-----------------------------------------------------------------------------
233 // nsManifestCheck::nsIStreamListener
234 //-----------------------------------------------------------------------------
236 NS_IMETHODIMP
237 nsManifestCheck::OnStartRequest(nsIRequest *aRequest,
238 nsISupports *aContext)
239 {
240 return NS_OK;
241 }
243 NS_IMETHODIMP
244 nsManifestCheck::OnDataAvailable(nsIRequest *aRequest,
245 nsISupports *aContext,
246 nsIInputStream *aStream,
247 uint64_t aOffset,
248 uint32_t aCount)
249 {
250 uint32_t bytesRead;
251 aStream->ReadSegments(ReadManifest, this, aCount, &bytesRead);
252 return NS_OK;
253 }
255 NS_IMETHODIMP
256 nsManifestCheck::OnStopRequest(nsIRequest *aRequest,
257 nsISupports *aContext,
258 nsresult aStatus)
259 {
260 nsAutoCString manifestHash;
261 if (NS_SUCCEEDED(aStatus)) {
262 mManifestHash->Finish(true, manifestHash);
263 }
265 mUpdate->ManifestCheckCompleted(aStatus, manifestHash);
267 return NS_OK;
268 }
270 //-----------------------------------------------------------------------------
271 // nsManifestCheck::nsIInterfaceRequestor
272 //-----------------------------------------------------------------------------
274 NS_IMETHODIMP
275 nsManifestCheck::GetInterface(const nsIID &aIID, void **aResult)
276 {
277 if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
278 NS_ADDREF_THIS();
279 *aResult = static_cast<nsIChannelEventSink *>(this);
280 return NS_OK;
281 }
283 return NS_ERROR_NO_INTERFACE;
284 }
286 //-----------------------------------------------------------------------------
287 // nsManifestCheck::nsIChannelEventSink
288 //-----------------------------------------------------------------------------
290 NS_IMETHODIMP
291 nsManifestCheck::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
292 nsIChannel *aNewChannel,
293 uint32_t aFlags,
294 nsIAsyncVerifyRedirectCallback *callback)
295 {
296 // Redirects should cause the load (and therefore the update) to fail.
297 if (aFlags & nsIChannelEventSink::REDIRECT_INTERNAL) {
298 callback->OnRedirectVerifyCallback(NS_OK);
299 return NS_OK;
300 }
302 LogToConsole("Manifest check failed because its response is a redirect");
304 aOldChannel->Cancel(NS_ERROR_ABORT);
305 return NS_ERROR_ABORT;
306 }
308 //-----------------------------------------------------------------------------
309 // nsOfflineCacheUpdateItem::nsISupports
310 //-----------------------------------------------------------------------------
312 NS_IMPL_ISUPPORTS(nsOfflineCacheUpdateItem,
313 nsIRequestObserver,
314 nsIStreamListener,
315 nsIRunnable,
316 nsIInterfaceRequestor,
317 nsIChannelEventSink)
319 //-----------------------------------------------------------------------------
320 // nsOfflineCacheUpdateItem <public>
321 //-----------------------------------------------------------------------------
323 nsOfflineCacheUpdateItem::nsOfflineCacheUpdateItem(nsIURI *aURI,
324 nsIURI *aReferrerURI,
325 nsIApplicationCache *aApplicationCache,
326 nsIApplicationCache *aPreviousApplicationCache,
327 uint32_t type)
328 : mURI(aURI)
329 , mReferrerURI(aReferrerURI)
330 , mApplicationCache(aApplicationCache)
331 , mPreviousApplicationCache(aPreviousApplicationCache)
332 , mItemType(type)
333 , mChannel(nullptr)
334 , mState(LoadStatus::UNINITIALIZED)
335 , mBytesRead(0)
336 {
337 }
339 nsOfflineCacheUpdateItem::~nsOfflineCacheUpdateItem()
340 {
341 }
343 nsresult
344 nsOfflineCacheUpdateItem::OpenChannel(nsOfflineCacheUpdate *aUpdate)
345 {
346 #if defined(PR_LOGGING)
347 if (LOG_ENABLED()) {
348 nsAutoCString spec;
349 mURI->GetSpec(spec);
350 LOG(("%p: Opening channel for %s", this, spec.get()));
351 }
352 #endif
354 if (mUpdate) {
355 // Holding a reference to the update means this item is already
356 // in progress (has a channel, or is just in between OnStopRequest()
357 // and its Run() call. We must never open channel on this item again.
358 LOG((" %p is already running! ignoring", this));
359 return NS_ERROR_ALREADY_OPENED;
360 }
362 nsresult rv = nsOfflineCacheUpdate::GetCacheKey(mURI, mCacheKey);
363 NS_ENSURE_SUCCESS(rv, rv);
365 uint32_t flags = nsIRequest::LOAD_BACKGROUND |
366 nsICachingChannel::LOAD_ONLY_IF_MODIFIED |
367 nsICachingChannel::LOAD_CHECK_OFFLINE_CACHE;
369 if (mApplicationCache == mPreviousApplicationCache) {
370 // Same app cache to read from and to write to is used during
371 // an only-update-check procedure. Here we protect the existing
372 // cache from being modified.
373 flags |= nsIRequest::INHIBIT_CACHING;
374 }
376 rv = NS_NewChannel(getter_AddRefs(mChannel),
377 mURI,
378 nullptr, nullptr, this,
379 flags);
380 NS_ENSURE_SUCCESS(rv, rv);
382 nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel =
383 do_QueryInterface(mChannel, &rv);
385 // Support for nsIApplicationCacheChannel is required.
386 NS_ENSURE_SUCCESS(rv, rv);
388 // Use the existing application cache as the cache to check.
389 rv = appCacheChannel->SetApplicationCache(mPreviousApplicationCache);
390 NS_ENSURE_SUCCESS(rv, rv);
392 // Set the new application cache as the target for write.
393 rv = appCacheChannel->SetApplicationCacheForWrite(mApplicationCache);
394 NS_ENSURE_SUCCESS(rv, rv);
396 // configure HTTP specific stuff
397 nsCOMPtr<nsIHttpChannel> httpChannel =
398 do_QueryInterface(mChannel);
399 if (httpChannel) {
400 httpChannel->SetReferrer(mReferrerURI);
401 httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("X-Moz"),
402 NS_LITERAL_CSTRING("offline-resource"),
403 false);
404 }
406 rv = mChannel->AsyncOpen(this, nullptr);
407 NS_ENSURE_SUCCESS(rv, rv);
409 mUpdate = aUpdate;
411 mState = LoadStatus::REQUESTED;
413 return NS_OK;
414 }
416 nsresult
417 nsOfflineCacheUpdateItem::Cancel()
418 {
419 if (mChannel) {
420 mChannel->Cancel(NS_ERROR_ABORT);
421 mChannel = nullptr;
422 }
424 mState = LoadStatus::UNINITIALIZED;
426 return NS_OK;
427 }
429 //-----------------------------------------------------------------------------
430 // nsOfflineCacheUpdateItem::nsIStreamListener
431 //-----------------------------------------------------------------------------
433 NS_IMETHODIMP
434 nsOfflineCacheUpdateItem::OnStartRequest(nsIRequest *aRequest,
435 nsISupports *aContext)
436 {
437 mState = LoadStatus::RECEIVING;
439 return NS_OK;
440 }
442 NS_IMETHODIMP
443 nsOfflineCacheUpdateItem::OnDataAvailable(nsIRequest *aRequest,
444 nsISupports *aContext,
445 nsIInputStream *aStream,
446 uint64_t aOffset,
447 uint32_t aCount)
448 {
449 uint32_t bytesRead = 0;
450 aStream->ReadSegments(NS_DiscardSegment, nullptr, aCount, &bytesRead);
451 mBytesRead += bytesRead;
452 LOG(("loaded %u bytes into offline cache [offset=%llu]\n",
453 bytesRead, aOffset));
455 mUpdate->OnByteProgress(bytesRead);
457 return NS_OK;
458 }
460 NS_IMETHODIMP
461 nsOfflineCacheUpdateItem::OnStopRequest(nsIRequest *aRequest,
462 nsISupports *aContext,
463 nsresult aStatus)
464 {
465 #if defined(PR_LOGGING)
466 if (LOG_ENABLED()) {
467 nsAutoCString spec;
468 mURI->GetSpec(spec);
469 LOG(("%p: Done fetching offline item %s [status=%x]\n",
470 this, spec.get(), aStatus));
471 }
472 #endif
474 if (mBytesRead == 0 && aStatus == NS_OK) {
475 // we didn't need to read (because LOAD_ONLY_IF_MODIFIED was
476 // specified), but the object should report loadedSize as if it
477 // did.
478 mChannel->GetContentLength(&mBytesRead);
479 mUpdate->OnByteProgress(mBytesRead);
480 }
482 if (NS_FAILED(aStatus)) {
483 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
484 if (httpChannel) {
485 bool isNoStore;
486 if (NS_SUCCEEDED(httpChannel->IsNoStoreResponse(&isNoStore))
487 && isNoStore) {
488 LogToConsole("Offline cache manifest item has Cache-control: no-store header",
489 this);
490 }
491 }
492 }
494 // We need to notify the update that the load is complete, but we
495 // want to give the channel a chance to close the cache entries.
496 NS_DispatchToCurrentThread(this);
498 return NS_OK;
499 }
502 //-----------------------------------------------------------------------------
503 // nsOfflineCacheUpdateItem::nsIRunnable
504 //-----------------------------------------------------------------------------
505 NS_IMETHODIMP
506 nsOfflineCacheUpdateItem::Run()
507 {
508 // Set mState to LOADED here rather than in OnStopRequest to prevent
509 // race condition when checking state of all mItems in ProcessNextURI().
510 // If state would have been set in OnStopRequest we could mistakenly
511 // take this item as already finished and finish the update process too
512 // early when ProcessNextURI() would get called between OnStopRequest()
513 // and Run() of this item. Finish() would then have been called twice.
514 mState = LoadStatus::LOADED;
516 nsRefPtr<nsOfflineCacheUpdate> update;
517 update.swap(mUpdate);
518 update->LoadCompleted(this);
520 return NS_OK;
521 }
523 //-----------------------------------------------------------------------------
524 // nsOfflineCacheUpdateItem::nsIInterfaceRequestor
525 //-----------------------------------------------------------------------------
527 NS_IMETHODIMP
528 nsOfflineCacheUpdateItem::GetInterface(const nsIID &aIID, void **aResult)
529 {
530 if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
531 NS_ADDREF_THIS();
532 *aResult = static_cast<nsIChannelEventSink *>(this);
533 return NS_OK;
534 }
536 return NS_ERROR_NO_INTERFACE;
537 }
539 //-----------------------------------------------------------------------------
540 // nsOfflineCacheUpdateItem::nsIChannelEventSink
541 //-----------------------------------------------------------------------------
543 NS_IMETHODIMP
544 nsOfflineCacheUpdateItem::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
545 nsIChannel *aNewChannel,
546 uint32_t aFlags,
547 nsIAsyncVerifyRedirectCallback *cb)
548 {
549 if (!(aFlags & nsIChannelEventSink::REDIRECT_INTERNAL)) {
550 // Don't allow redirect in case of non-internal redirect and cancel
551 // the channel to clean the cache entry.
552 LogToConsole("Offline cache manifest failed because an item redirects", this);
554 aOldChannel->Cancel(NS_ERROR_ABORT);
555 return NS_ERROR_ABORT;
556 }
558 nsCOMPtr<nsIURI> newURI;
559 nsresult rv = aNewChannel->GetURI(getter_AddRefs(newURI));
560 if (NS_FAILED(rv))
561 return rv;
563 nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel =
564 do_QueryInterface(aNewChannel);
565 if (appCacheChannel) {
566 rv = appCacheChannel->SetApplicationCacheForWrite(mApplicationCache);
567 NS_ENSURE_SUCCESS(rv, rv);
568 }
570 nsAutoCString oldScheme;
571 mURI->GetScheme(oldScheme);
573 bool match;
574 if (NS_FAILED(newURI->SchemeIs(oldScheme.get(), &match)) || !match) {
575 LOG(("rejected: redirected to a different scheme\n"));
576 return NS_ERROR_ABORT;
577 }
579 // HTTP request headers are not automatically forwarded to the new channel.
580 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aNewChannel);
581 NS_ENSURE_STATE(httpChannel);
583 httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("X-Moz"),
584 NS_LITERAL_CSTRING("offline-resource"),
585 false);
587 mChannel = aNewChannel;
589 cb->OnRedirectVerifyCallback(NS_OK);
590 return NS_OK;
591 }
593 nsresult
594 nsOfflineCacheUpdateItem::GetRequestSucceeded(bool * succeeded)
595 {
596 *succeeded = false;
598 if (!mChannel)
599 return NS_OK;
601 nsresult rv;
602 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel, &rv);
603 NS_ENSURE_SUCCESS(rv, rv);
605 bool reqSucceeded;
606 rv = httpChannel->GetRequestSucceeded(&reqSucceeded);
607 if (NS_ERROR_NOT_AVAILABLE == rv)
608 return NS_OK;
609 NS_ENSURE_SUCCESS(rv, rv);
611 if (!reqSucceeded) {
612 LOG(("Request failed"));
613 return NS_OK;
614 }
616 nsresult channelStatus;
617 rv = httpChannel->GetStatus(&channelStatus);
618 NS_ENSURE_SUCCESS(rv, rv);
620 if (NS_FAILED(channelStatus)) {
621 LOG(("Channel status=0x%08x", channelStatus));
622 return NS_OK;
623 }
625 *succeeded = true;
626 return NS_OK;
627 }
629 bool
630 nsOfflineCacheUpdateItem::IsScheduled()
631 {
632 return mState == LoadStatus::UNINITIALIZED;
633 }
635 bool
636 nsOfflineCacheUpdateItem::IsInProgress()
637 {
638 return mState == LoadStatus::REQUESTED ||
639 mState == LoadStatus::RECEIVING;
640 }
642 bool
643 nsOfflineCacheUpdateItem::IsCompleted()
644 {
645 return mState == LoadStatus::LOADED;
646 }
648 nsresult
649 nsOfflineCacheUpdateItem::GetStatus(uint16_t *aStatus)
650 {
651 if (!mChannel) {
652 *aStatus = 0;
653 return NS_OK;
654 }
656 nsresult rv;
657 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel, &rv);
658 NS_ENSURE_SUCCESS(rv, rv);
660 uint32_t httpStatus;
661 rv = httpChannel->GetResponseStatus(&httpStatus);
662 if (rv == NS_ERROR_NOT_AVAILABLE) {
663 *aStatus = 0;
664 return NS_OK;
665 }
667 NS_ENSURE_SUCCESS(rv, rv);
668 *aStatus = uint16_t(httpStatus);
669 return NS_OK;
670 }
672 //-----------------------------------------------------------------------------
673 // nsOfflineManifestItem
674 //-----------------------------------------------------------------------------
676 //-----------------------------------------------------------------------------
677 // nsOfflineManifestItem <public>
678 //-----------------------------------------------------------------------------
680 nsOfflineManifestItem::nsOfflineManifestItem(nsIURI *aURI,
681 nsIURI *aReferrerURI,
682 nsIApplicationCache *aApplicationCache,
683 nsIApplicationCache *aPreviousApplicationCache)
684 : nsOfflineCacheUpdateItem(aURI, aReferrerURI,
685 aApplicationCache, aPreviousApplicationCache,
686 nsIApplicationCache::ITEM_MANIFEST)
687 , mParserState(PARSE_INIT)
688 , mNeedsUpdate(true)
689 , mManifestHashInitialized(false)
690 {
691 ReadStrictFileOriginPolicyPref();
692 }
694 nsOfflineManifestItem::~nsOfflineManifestItem()
695 {
696 }
698 //-----------------------------------------------------------------------------
699 // nsOfflineManifestItem <private>
700 //-----------------------------------------------------------------------------
702 /* static */
703 NS_METHOD
704 nsOfflineManifestItem::ReadManifest(nsIInputStream *aInputStream,
705 void *aClosure,
706 const char *aFromSegment,
707 uint32_t aOffset,
708 uint32_t aCount,
709 uint32_t *aBytesConsumed)
710 {
711 nsOfflineManifestItem *manifest =
712 static_cast<nsOfflineManifestItem*>(aClosure);
714 nsresult rv;
716 *aBytesConsumed = aCount;
718 if (manifest->mParserState == PARSE_ERROR) {
719 // parse already failed, ignore this
720 return NS_OK;
721 }
723 if (!manifest->mManifestHashInitialized) {
724 // Avoid re-creation of crypto hash when it fails from some reason the first time
725 manifest->mManifestHashInitialized = true;
727 manifest->mManifestHash = do_CreateInstance("@mozilla.org/security/hash;1", &rv);
728 if (NS_SUCCEEDED(rv)) {
729 rv = manifest->mManifestHash->Init(nsICryptoHash::MD5);
730 if (NS_FAILED(rv)) {
731 manifest->mManifestHash = nullptr;
732 LOG(("Could not initialize manifest hash for byte-to-byte check, rv=%08x", rv));
733 }
734 }
735 }
737 if (manifest->mManifestHash) {
738 rv = manifest->mManifestHash->Update(reinterpret_cast<const uint8_t *>(aFromSegment), aCount);
739 if (NS_FAILED(rv)) {
740 manifest->mManifestHash = nullptr;
741 LOG(("Could not update manifest hash, rv=%08x", rv));
742 }
743 }
745 manifest->mReadBuf.Append(aFromSegment, aCount);
747 nsCString::const_iterator begin, iter, end;
748 manifest->mReadBuf.BeginReading(begin);
749 manifest->mReadBuf.EndReading(end);
751 for (iter = begin; iter != end; iter++) {
752 if (*iter == '\r' || *iter == '\n') {
753 nsresult rv = manifest->HandleManifestLine(begin, iter);
755 if (NS_FAILED(rv)) {
756 LOG(("HandleManifestLine failed with 0x%08x", rv));
757 *aBytesConsumed = 0; // Avoid assertion failure in stream tee
758 return NS_ERROR_ABORT;
759 }
761 begin = iter;
762 begin++;
763 }
764 }
766 // any leftovers are saved for next time
767 manifest->mReadBuf = Substring(begin, end);
769 return NS_OK;
770 }
772 nsresult
773 nsOfflineManifestItem::AddNamespace(uint32_t namespaceType,
774 const nsCString &namespaceSpec,
775 const nsCString &data)
777 {
778 nsresult rv;
779 if (!mNamespaces) {
780 mNamespaces = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
781 NS_ENSURE_SUCCESS(rv, rv);
782 }
784 nsCOMPtr<nsIApplicationCacheNamespace> ns =
785 do_CreateInstance(NS_APPLICATIONCACHENAMESPACE_CONTRACTID, &rv);
786 NS_ENSURE_SUCCESS(rv, rv);
788 rv = ns->Init(namespaceType, namespaceSpec, data);
789 NS_ENSURE_SUCCESS(rv, rv);
791 rv = mNamespaces->AppendElement(ns, false);
792 NS_ENSURE_SUCCESS(rv, rv);
794 return NS_OK;
795 }
797 nsresult
798 nsOfflineManifestItem::HandleManifestLine(const nsCString::const_iterator &aBegin,
799 const nsCString::const_iterator &aEnd)
800 {
801 nsCString::const_iterator begin = aBegin;
802 nsCString::const_iterator end = aEnd;
804 // all lines ignore trailing spaces and tabs
805 nsCString::const_iterator last = end;
806 --last;
807 while (end != begin && (*last == ' ' || *last == '\t')) {
808 --end;
809 --last;
810 }
812 if (mParserState == PARSE_INIT) {
813 // Allow a UTF-8 BOM
814 if (begin != end && static_cast<unsigned char>(*begin) == 0xef) {
815 if (++begin == end || static_cast<unsigned char>(*begin) != 0xbb ||
816 ++begin == end || static_cast<unsigned char>(*begin) != 0xbf) {
817 mParserState = PARSE_ERROR;
818 LogToConsole("Offline cache manifest BOM error", this);
819 return NS_OK;
820 }
821 ++begin;
822 }
824 const nsCSubstring &magic = Substring(begin, end);
826 if (!magic.EqualsLiteral("CACHE MANIFEST")) {
827 mParserState = PARSE_ERROR;
828 LogToConsole("Offline cache manifest magic incorect", this);
829 return NS_OK;
830 }
832 mParserState = PARSE_CACHE_ENTRIES;
833 return NS_OK;
834 }
836 // lines other than the first ignore leading spaces and tabs
837 while (begin != end && (*begin == ' ' || *begin == '\t'))
838 begin++;
840 // ignore blank lines and comments
841 if (begin == end || *begin == '#')
842 return NS_OK;
844 const nsCSubstring &line = Substring(begin, end);
846 if (line.EqualsLiteral("CACHE:")) {
847 mParserState = PARSE_CACHE_ENTRIES;
848 return NS_OK;
849 }
851 if (line.EqualsLiteral("FALLBACK:")) {
852 mParserState = PARSE_FALLBACK_ENTRIES;
853 return NS_OK;
854 }
856 if (line.EqualsLiteral("NETWORK:")) {
857 mParserState = PARSE_BYPASS_ENTRIES;
858 return NS_OK;
859 }
861 // Every other section type we don't know must be silently ignored.
862 nsCString::const_iterator lastChar = end;
863 if (*(--lastChar) == ':') {
864 mParserState = PARSE_UNKNOWN_SECTION;
865 return NS_OK;
866 }
868 nsresult rv;
870 switch(mParserState) {
871 case PARSE_INIT:
872 case PARSE_ERROR: {
873 // this should have been dealt with earlier
874 return NS_ERROR_FAILURE;
875 }
877 case PARSE_UNKNOWN_SECTION: {
878 // just jump over
879 return NS_OK;
880 }
882 case PARSE_CACHE_ENTRIES: {
883 nsCOMPtr<nsIURI> uri;
884 rv = NS_NewURI(getter_AddRefs(uri), line, nullptr, mURI);
885 if (NS_FAILED(rv))
886 break;
887 if (NS_FAILED(DropReferenceFromURL(uri)))
888 break;
890 nsAutoCString scheme;
891 uri->GetScheme(scheme);
893 // Manifest URIs must have the same scheme as the manifest.
894 bool match;
895 if (NS_FAILED(mURI->SchemeIs(scheme.get(), &match)) || !match)
896 break;
898 mExplicitURIs.AppendObject(uri);
899 break;
900 }
902 case PARSE_FALLBACK_ENTRIES: {
903 int32_t separator = line.FindChar(' ');
904 if (separator == kNotFound) {
905 separator = line.FindChar('\t');
906 if (separator == kNotFound)
907 break;
908 }
910 nsCString namespaceSpec(Substring(line, 0, separator));
911 nsCString fallbackSpec(Substring(line, separator + 1));
912 namespaceSpec.CompressWhitespace();
913 fallbackSpec.CompressWhitespace();
915 nsCOMPtr<nsIURI> namespaceURI;
916 rv = NS_NewURI(getter_AddRefs(namespaceURI), namespaceSpec, nullptr, mURI);
917 if (NS_FAILED(rv))
918 break;
919 if (NS_FAILED(DropReferenceFromURL(namespaceURI)))
920 break;
921 rv = namespaceURI->GetAsciiSpec(namespaceSpec);
922 if (NS_FAILED(rv))
923 break;
926 nsCOMPtr<nsIURI> fallbackURI;
927 rv = NS_NewURI(getter_AddRefs(fallbackURI), fallbackSpec, nullptr, mURI);
928 if (NS_FAILED(rv))
929 break;
930 if (NS_FAILED(DropReferenceFromURL(fallbackURI)))
931 break;
932 rv = fallbackURI->GetAsciiSpec(fallbackSpec);
933 if (NS_FAILED(rv))
934 break;
936 // Manifest and namespace must be same origin
937 if (!NS_SecurityCompareURIs(mURI, namespaceURI,
938 mStrictFileOriginPolicy))
939 break;
941 // Fallback and namespace must be same origin
942 if (!NS_SecurityCompareURIs(namespaceURI, fallbackURI,
943 mStrictFileOriginPolicy))
944 break;
946 mFallbackURIs.AppendObject(fallbackURI);
948 AddNamespace(nsIApplicationCacheNamespace::NAMESPACE_FALLBACK,
949 namespaceSpec, fallbackSpec);
950 break;
951 }
953 case PARSE_BYPASS_ENTRIES: {
954 if (line[0] == '*' && (line.Length() == 1 || line[1] == ' ' || line[1] == '\t'))
955 {
956 // '*' indicates to make the online whitelist wildcard flag open,
957 // i.e. do allow load of resources not present in the offline cache
958 // or not conforming any namespace.
959 // We achive that simply by adding an 'empty' - i.e. universal
960 // namespace of BYPASS type into the cache.
961 AddNamespace(nsIApplicationCacheNamespace::NAMESPACE_BYPASS,
962 EmptyCString(), EmptyCString());
963 break;
964 }
966 nsCOMPtr<nsIURI> bypassURI;
967 rv = NS_NewURI(getter_AddRefs(bypassURI), line, nullptr, mURI);
968 if (NS_FAILED(rv))
969 break;
971 nsAutoCString scheme;
972 bypassURI->GetScheme(scheme);
973 bool equals;
974 if (NS_FAILED(mURI->SchemeIs(scheme.get(), &equals)) || !equals)
975 break;
976 if (NS_FAILED(DropReferenceFromURL(bypassURI)))
977 break;
978 nsCString spec;
979 if (NS_FAILED(bypassURI->GetAsciiSpec(spec)))
980 break;
982 AddNamespace(nsIApplicationCacheNamespace::NAMESPACE_BYPASS,
983 spec, EmptyCString());
984 break;
985 }
986 }
988 return NS_OK;
989 }
991 nsresult
992 nsOfflineManifestItem::GetOldManifestContentHash(nsIRequest *aRequest)
993 {
994 nsresult rv;
996 nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(aRequest, &rv);
997 NS_ENSURE_SUCCESS(rv, rv);
999 // load the main cache token that is actually the old offline cache token and
1000 // read previous manifest content hash value
1001 nsCOMPtr<nsISupports> cacheToken;
1002 cachingChannel->GetCacheToken(getter_AddRefs(cacheToken));
1003 if (cacheToken) {
1004 nsCOMPtr<nsICacheEntry> cacheDescriptor(do_QueryInterface(cacheToken, &rv));
1005 NS_ENSURE_SUCCESS(rv, rv);
1007 rv = cacheDescriptor->GetMetaDataElement("offline-manifest-hash", getter_Copies(mOldManifestHashValue));
1008 if (NS_FAILED(rv))
1009 mOldManifestHashValue.Truncate();
1010 }
1012 return NS_OK;
1013 }
1015 nsresult
1016 nsOfflineManifestItem::CheckNewManifestContentHash(nsIRequest *aRequest)
1017 {
1018 nsresult rv;
1020 if (!mManifestHash) {
1021 // Nothing to compare against...
1022 return NS_OK;
1023 }
1025 nsCString newManifestHashValue;
1026 rv = mManifestHash->Finish(true, mManifestHashValue);
1027 mManifestHash = nullptr;
1029 if (NS_FAILED(rv)) {
1030 LOG(("Could not finish manifest hash, rv=%08x", rv));
1031 // This is not critical error
1032 return NS_OK;
1033 }
1035 if (!ParseSucceeded()) {
1036 // Parsing failed, the hash is not valid
1037 return NS_OK;
1038 }
1040 if (mOldManifestHashValue == mManifestHashValue) {
1041 LOG(("Update not needed, downloaded manifest content is byte-for-byte identical"));
1042 mNeedsUpdate = false;
1043 }
1045 // Store the manifest content hash value to the new
1046 // offline cache token
1047 nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(aRequest, &rv);
1048 NS_ENSURE_SUCCESS(rv, rv);
1050 nsCOMPtr<nsISupports> cacheToken;
1051 cachingChannel->GetOfflineCacheToken(getter_AddRefs(cacheToken));
1052 if (cacheToken) {
1053 nsCOMPtr<nsICacheEntry> cacheDescriptor(do_QueryInterface(cacheToken, &rv));
1054 NS_ENSURE_SUCCESS(rv, rv);
1056 rv = cacheDescriptor->SetMetaDataElement("offline-manifest-hash", mManifestHashValue.get());
1057 NS_ENSURE_SUCCESS(rv, rv);
1058 }
1060 return NS_OK;
1061 }
1063 void
1064 nsOfflineManifestItem::ReadStrictFileOriginPolicyPref()
1065 {
1066 mStrictFileOriginPolicy =
1067 Preferences::GetBool("security.fileuri.strict_origin_policy", true);
1068 }
1070 NS_IMETHODIMP
1071 nsOfflineManifestItem::OnStartRequest(nsIRequest *aRequest,
1072 nsISupports *aContext)
1073 {
1074 nsresult rv;
1076 nsCOMPtr<nsIHttpChannel> channel = do_QueryInterface(aRequest, &rv);
1077 NS_ENSURE_SUCCESS(rv, rv);
1079 bool succeeded;
1080 rv = channel->GetRequestSucceeded(&succeeded);
1081 NS_ENSURE_SUCCESS(rv, rv);
1083 if (!succeeded) {
1084 LOG(("HTTP request failed"));
1085 LogToConsole("Offline cache manifest HTTP request failed", this);
1086 mParserState = PARSE_ERROR;
1087 return NS_ERROR_ABORT;
1088 }
1090 rv = GetOldManifestContentHash(aRequest);
1091 NS_ENSURE_SUCCESS(rv, rv);
1093 return nsOfflineCacheUpdateItem::OnStartRequest(aRequest, aContext);
1094 }
1096 NS_IMETHODIMP
1097 nsOfflineManifestItem::OnDataAvailable(nsIRequest *aRequest,
1098 nsISupports *aContext,
1099 nsIInputStream *aStream,
1100 uint64_t aOffset,
1101 uint32_t aCount)
1102 {
1103 uint32_t bytesRead = 0;
1104 aStream->ReadSegments(ReadManifest, this, aCount, &bytesRead);
1105 mBytesRead += bytesRead;
1107 if (mParserState == PARSE_ERROR) {
1108 LOG(("OnDataAvailable is canceling the request due a parse error\n"));
1109 return NS_ERROR_ABORT;
1110 }
1112 LOG(("loaded %u bytes into offline cache [offset=%u]\n",
1113 bytesRead, aOffset));
1115 // All the parent method does is read and discard, don't bother
1116 // chaining up.
1118 return NS_OK;
1119 }
1121 NS_IMETHODIMP
1122 nsOfflineManifestItem::OnStopRequest(nsIRequest *aRequest,
1123 nsISupports *aContext,
1124 nsresult aStatus)
1125 {
1126 if (mBytesRead == 0) {
1127 // We didn't need to read (because LOAD_ONLY_IF_MODIFIED was
1128 // specified).
1129 mNeedsUpdate = false;
1130 } else {
1131 // Handle any leftover manifest data.
1132 nsCString::const_iterator begin, end;
1133 mReadBuf.BeginReading(begin);
1134 mReadBuf.EndReading(end);
1135 nsresult rv = HandleManifestLine(begin, end);
1136 NS_ENSURE_SUCCESS(rv, rv);
1138 rv = CheckNewManifestContentHash(aRequest);
1139 NS_ENSURE_SUCCESS(rv, rv);
1140 }
1142 return nsOfflineCacheUpdateItem::OnStopRequest(aRequest, aContext, aStatus);
1143 }
1145 //-----------------------------------------------------------------------------
1146 // nsOfflineCacheUpdate::nsISupports
1147 //-----------------------------------------------------------------------------
1149 NS_IMPL_ISUPPORTS(nsOfflineCacheUpdate,
1150 nsIOfflineCacheUpdateObserver,
1151 nsIOfflineCacheUpdate,
1152 nsIRunnable)
1154 //-----------------------------------------------------------------------------
1155 // nsOfflineCacheUpdate <public>
1156 //-----------------------------------------------------------------------------
1158 nsOfflineCacheUpdate::nsOfflineCacheUpdate()
1159 : mState(STATE_UNINITIALIZED)
1160 , mAddedItems(false)
1161 , mPartialUpdate(false)
1162 , mOnlyCheckUpdate(false)
1163 , mSucceeded(true)
1164 , mObsolete(false)
1165 , mAppID(NECKO_NO_APP_ID)
1166 , mInBrowser(false)
1167 , mItemsInProgress(0)
1168 , mRescheduleCount(0)
1169 , mPinnedEntryRetriesCount(0)
1170 , mPinned(false)
1171 {
1172 }
1174 nsOfflineCacheUpdate::~nsOfflineCacheUpdate()
1175 {
1176 LOG(("nsOfflineCacheUpdate::~nsOfflineCacheUpdate [%p]", this));
1177 }
1179 /* static */
1180 nsresult
1181 nsOfflineCacheUpdate::GetCacheKey(nsIURI *aURI, nsACString &aKey)
1182 {
1183 aKey.Truncate();
1185 nsCOMPtr<nsIURI> newURI;
1186 nsresult rv = aURI->CloneIgnoringRef(getter_AddRefs(newURI));
1187 NS_ENSURE_SUCCESS(rv, rv);
1189 rv = newURI->GetAsciiSpec(aKey);
1190 NS_ENSURE_SUCCESS(rv, rv);
1192 return NS_OK;
1193 }
1195 nsresult
1196 nsOfflineCacheUpdate::InitInternal(nsIURI *aManifestURI)
1197 {
1198 nsresult rv;
1200 // Only http and https applications are supported.
1201 bool match;
1202 rv = aManifestURI->SchemeIs("http", &match);
1203 NS_ENSURE_SUCCESS(rv, rv);
1205 if (!match) {
1206 rv = aManifestURI->SchemeIs("https", &match);
1207 NS_ENSURE_SUCCESS(rv, rv);
1208 if (!match)
1209 return NS_ERROR_ABORT;
1210 }
1212 mManifestURI = aManifestURI;
1214 rv = mManifestURI->GetAsciiHost(mUpdateDomain);
1215 NS_ENSURE_SUCCESS(rv, rv);
1217 mPartialUpdate = false;
1219 return NS_OK;
1220 }
1222 nsresult
1223 nsOfflineCacheUpdate::Init(nsIURI *aManifestURI,
1224 nsIURI *aDocumentURI,
1225 nsIDOMDocument *aDocument,
1226 nsIFile *aCustomProfileDir,
1227 uint32_t aAppID,
1228 bool aInBrowser)
1229 {
1230 nsresult rv;
1232 // Make sure the service has been initialized
1233 nsOfflineCacheUpdateService* service =
1234 nsOfflineCacheUpdateService::EnsureService();
1235 if (!service)
1236 return NS_ERROR_FAILURE;
1238 LOG(("nsOfflineCacheUpdate::Init [%p]", this));
1240 rv = InitInternal(aManifestURI);
1241 NS_ENSURE_SUCCESS(rv, rv);
1243 nsCOMPtr<nsIApplicationCacheService> cacheService =
1244 do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv);
1245 NS_ENSURE_SUCCESS(rv, rv);
1247 mDocumentURI = aDocumentURI;
1249 if (aCustomProfileDir) {
1250 rv = cacheService->BuildGroupIDForApp(aManifestURI,
1251 aAppID, aInBrowser,
1252 mGroupID);
1253 NS_ENSURE_SUCCESS(rv, rv);
1255 // Create only a new offline application cache in the custom profile
1256 // This is a preload of a new cache.
1258 // XXX Custom updates don't support "updating" of an existing cache
1259 // in the custom profile at the moment. This support can be, though,
1260 // simply added as well when needed.
1261 mPreviousApplicationCache = nullptr;
1263 rv = cacheService->CreateCustomApplicationCache(mGroupID,
1264 aCustomProfileDir,
1265 kCustomProfileQuota,
1266 getter_AddRefs(mApplicationCache));
1267 NS_ENSURE_SUCCESS(rv, rv);
1269 mCustomProfileDir = aCustomProfileDir;
1270 }
1271 else {
1272 rv = cacheService->BuildGroupIDForApp(aManifestURI,
1273 aAppID, aInBrowser,
1274 mGroupID);
1275 NS_ENSURE_SUCCESS(rv, rv);
1277 rv = cacheService->GetActiveCache(mGroupID,
1278 getter_AddRefs(mPreviousApplicationCache));
1279 NS_ENSURE_SUCCESS(rv, rv);
1281 rv = cacheService->CreateApplicationCache(mGroupID,
1282 getter_AddRefs(mApplicationCache));
1283 NS_ENSURE_SUCCESS(rv, rv);
1284 }
1286 rv = nsOfflineCacheUpdateService::OfflineAppPinnedForURI(aDocumentURI,
1287 nullptr,
1288 &mPinned);
1289 NS_ENSURE_SUCCESS(rv, rv);
1291 mAppID = aAppID;
1292 mInBrowser = aInBrowser;
1294 mState = STATE_INITIALIZED;
1295 return NS_OK;
1296 }
1298 nsresult
1299 nsOfflineCacheUpdate::InitForUpdateCheck(nsIURI *aManifestURI,
1300 uint32_t aAppID,
1301 bool aInBrowser,
1302 nsIObserver *aObserver)
1303 {
1304 nsresult rv;
1306 // Make sure the service has been initialized
1307 nsOfflineCacheUpdateService* service =
1308 nsOfflineCacheUpdateService::EnsureService();
1309 if (!service)
1310 return NS_ERROR_FAILURE;
1312 LOG(("nsOfflineCacheUpdate::InitForUpdateCheck [%p]", this));
1314 rv = InitInternal(aManifestURI);
1315 NS_ENSURE_SUCCESS(rv, rv);
1317 nsCOMPtr<nsIApplicationCacheService> cacheService =
1318 do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv);
1319 NS_ENSURE_SUCCESS(rv, rv);
1321 rv = cacheService->BuildGroupIDForApp(aManifestURI,
1322 aAppID, aInBrowser,
1323 mGroupID);
1324 NS_ENSURE_SUCCESS(rv, rv);
1326 rv = cacheService->GetActiveCache(mGroupID,
1327 getter_AddRefs(mPreviousApplicationCache));
1328 NS_ENSURE_SUCCESS(rv, rv);
1330 // To load the manifest properly using current app cache to satisfy and
1331 // also to compare the cached content hash value we have to set 'some'
1332 // app cache to write to on the channel. Otherwise the cached version will
1333 // be used and no actual network request will be made. We use the same
1334 // app cache here. OpenChannel prevents caching in this case using
1335 // INHIBIT_CACHING load flag.
1336 mApplicationCache = mPreviousApplicationCache;
1338 rv = nsOfflineCacheUpdateService::OfflineAppPinnedForURI(aManifestURI,
1339 nullptr,
1340 &mPinned);
1341 NS_ENSURE_SUCCESS(rv, rv);
1343 mUpdateAvailableObserver = aObserver;
1344 mOnlyCheckUpdate = true;
1346 mState = STATE_INITIALIZED;
1347 return NS_OK;
1348 }
1350 nsresult
1351 nsOfflineCacheUpdate::InitPartial(nsIURI *aManifestURI,
1352 const nsACString& clientID,
1353 nsIURI *aDocumentURI)
1354 {
1355 nsresult rv;
1357 // Make sure the service has been initialized
1358 nsOfflineCacheUpdateService* service =
1359 nsOfflineCacheUpdateService::EnsureService();
1360 if (!service)
1361 return NS_ERROR_FAILURE;
1363 LOG(("nsOfflineCacheUpdate::InitPartial [%p]", this));
1365 mPartialUpdate = true;
1366 mDocumentURI = aDocumentURI;
1368 mManifestURI = aManifestURI;
1369 rv = mManifestURI->GetAsciiHost(mUpdateDomain);
1370 NS_ENSURE_SUCCESS(rv, rv);
1372 nsCOMPtr<nsIApplicationCacheService> cacheService =
1373 do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv);
1374 NS_ENSURE_SUCCESS(rv, rv);
1376 rv = cacheService->GetApplicationCache(clientID,
1377 getter_AddRefs(mApplicationCache));
1378 NS_ENSURE_SUCCESS(rv, rv);
1380 if (!mApplicationCache) {
1381 nsAutoCString manifestSpec;
1382 rv = GetCacheKey(mManifestURI, manifestSpec);
1383 NS_ENSURE_SUCCESS(rv, rv);
1385 rv = cacheService->CreateApplicationCache
1386 (manifestSpec, getter_AddRefs(mApplicationCache));
1387 NS_ENSURE_SUCCESS(rv, rv);
1388 }
1390 rv = mApplicationCache->GetManifestURI(getter_AddRefs(mManifestURI));
1391 NS_ENSURE_SUCCESS(rv, rv);
1393 nsAutoCString groupID;
1394 rv = mApplicationCache->GetGroupID(groupID);
1395 NS_ENSURE_SUCCESS(rv, rv);
1397 rv = nsOfflineCacheUpdateService::OfflineAppPinnedForURI(aDocumentURI,
1398 nullptr,
1399 &mPinned);
1400 NS_ENSURE_SUCCESS(rv, rv);
1402 mState = STATE_INITIALIZED;
1403 return NS_OK;
1404 }
1406 nsresult
1407 nsOfflineCacheUpdate::HandleManifest(bool *aDoUpdate)
1408 {
1409 // Be pessimistic
1410 *aDoUpdate = false;
1412 bool succeeded;
1413 nsresult rv = mManifestItem->GetRequestSucceeded(&succeeded);
1414 NS_ENSURE_SUCCESS(rv, rv);
1416 if (!succeeded || !mManifestItem->ParseSucceeded()) {
1417 return NS_ERROR_FAILURE;
1418 }
1420 if (!mManifestItem->NeedsUpdate()) {
1421 return NS_OK;
1422 }
1424 // Add items requested by the manifest.
1425 const nsCOMArray<nsIURI> &manifestURIs = mManifestItem->GetExplicitURIs();
1426 for (int32_t i = 0; i < manifestURIs.Count(); i++) {
1427 rv = AddURI(manifestURIs[i], nsIApplicationCache::ITEM_EXPLICIT);
1428 NS_ENSURE_SUCCESS(rv, rv);
1429 }
1431 const nsCOMArray<nsIURI> &fallbackURIs = mManifestItem->GetFallbackURIs();
1432 for (int32_t i = 0; i < fallbackURIs.Count(); i++) {
1433 rv = AddURI(fallbackURIs[i], nsIApplicationCache::ITEM_FALLBACK);
1434 NS_ENSURE_SUCCESS(rv, rv);
1435 }
1437 // The document that requested the manifest is implicitly included
1438 // as part of that manifest update.
1439 rv = AddURI(mDocumentURI, nsIApplicationCache::ITEM_IMPLICIT);
1440 NS_ENSURE_SUCCESS(rv, rv);
1442 // Add items previously cached implicitly
1443 rv = AddExistingItems(nsIApplicationCache::ITEM_IMPLICIT);
1444 NS_ENSURE_SUCCESS(rv, rv);
1446 // Add items requested by the script API
1447 rv = AddExistingItems(nsIApplicationCache::ITEM_DYNAMIC);
1448 NS_ENSURE_SUCCESS(rv, rv);
1450 // Add opportunistically cached items conforming current opportunistic
1451 // namespace list
1452 rv = AddExistingItems(nsIApplicationCache::ITEM_OPPORTUNISTIC,
1453 &mManifestItem->GetOpportunisticNamespaces());
1454 NS_ENSURE_SUCCESS(rv, rv);
1456 *aDoUpdate = true;
1458 return NS_OK;
1459 }
1461 bool
1462 nsOfflineCacheUpdate::CheckUpdateAvailability()
1463 {
1464 nsresult rv;
1466 bool succeeded;
1467 rv = mManifestItem->GetRequestSucceeded(&succeeded);
1468 NS_ENSURE_SUCCESS(rv, false);
1470 if (!succeeded || !mManifestItem->ParseSucceeded()) {
1471 return false;
1472 }
1474 if (!mPinned) {
1475 uint16_t status;
1476 rv = mManifestItem->GetStatus(&status);
1477 NS_ENSURE_SUCCESS(rv, false);
1479 // Treat these as there would be an update available,
1480 // since this is indication of demand to remove this
1481 // offline cache.
1482 if (status == 404 || status == 410) {
1483 return true;
1484 }
1485 }
1487 return mManifestItem->NeedsUpdate();
1488 }
1490 void
1491 nsOfflineCacheUpdate::LoadCompleted(nsOfflineCacheUpdateItem *aItem)
1492 {
1493 nsresult rv;
1495 LOG(("nsOfflineCacheUpdate::LoadCompleted [%p]", this));
1497 if (mState == STATE_FINISHED) {
1498 LOG((" after completion, ignoring"));
1499 return;
1500 }
1502 // Keep the object alive through a Finish() call.
1503 nsCOMPtr<nsIOfflineCacheUpdate> kungFuDeathGrip(this);
1505 if (mState == STATE_CANCELLED) {
1506 NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
1507 Finish();
1508 return;
1509 }
1511 if (mState == STATE_CHECKING) {
1512 // Manifest load finished.
1514 if (mOnlyCheckUpdate) {
1515 Finish();
1516 NotifyUpdateAvailability(CheckUpdateAvailability());
1517 return;
1518 }
1520 NS_ASSERTION(mManifestItem,
1521 "Must have a manifest item in STATE_CHECKING.");
1522 NS_ASSERTION(mManifestItem == aItem,
1523 "Unexpected aItem in nsOfflineCacheUpdate::LoadCompleted");
1525 // A 404 or 410 is interpreted as an intentional removal of
1526 // the manifest file, rather than a transient server error.
1527 // Obsolete this cache group if one of these is returned.
1528 uint16_t status;
1529 rv = mManifestItem->GetStatus(&status);
1530 if (status == 404 || status == 410) {
1531 LogToConsole("Offline cache manifest removed, cache cleared", mManifestItem);
1532 mSucceeded = false;
1533 if (mPreviousApplicationCache) {
1534 if (mPinned) {
1535 // Do not obsolete a pinned application.
1536 NotifyState(nsIOfflineCacheUpdateObserver::STATE_NOUPDATE);
1537 } else {
1538 NotifyState(nsIOfflineCacheUpdateObserver::STATE_OBSOLETE);
1539 mObsolete = true;
1540 }
1541 } else {
1542 NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
1543 mObsolete = true;
1544 }
1545 Finish();
1546 return;
1547 }
1549 bool doUpdate;
1550 if (NS_FAILED(HandleManifest(&doUpdate))) {
1551 mSucceeded = false;
1552 NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
1553 Finish();
1554 return;
1555 }
1557 if (!doUpdate) {
1558 LogToConsole("Offline cache doesn't need to update", mManifestItem);
1560 mSucceeded = false;
1562 AssociateDocuments(mPreviousApplicationCache);
1564 ScheduleImplicit();
1566 // If we didn't need an implicit update, we can
1567 // send noupdate and end the update now.
1568 if (!mImplicitUpdate) {
1569 NotifyState(nsIOfflineCacheUpdateObserver::STATE_NOUPDATE);
1570 Finish();
1571 }
1572 return;
1573 }
1575 rv = mApplicationCache->MarkEntry(mManifestItem->mCacheKey,
1576 mManifestItem->mItemType);
1577 if (NS_FAILED(rv)) {
1578 mSucceeded = false;
1579 NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
1580 Finish();
1581 return;
1582 }
1584 mState = STATE_DOWNLOADING;
1585 NotifyState(nsIOfflineCacheUpdateObserver::STATE_DOWNLOADING);
1587 // Start fetching resources.
1588 ProcessNextURI();
1590 return;
1591 }
1593 // Normal load finished.
1594 if (mItemsInProgress) // Just to be safe here!
1595 --mItemsInProgress;
1597 bool succeeded;
1598 rv = aItem->GetRequestSucceeded(&succeeded);
1600 if (mPinned && NS_SUCCEEDED(rv) && succeeded) {
1601 uint32_t dummy_cache_type;
1602 rv = mApplicationCache->GetTypes(aItem->mCacheKey, &dummy_cache_type);
1603 bool item_doomed = NS_FAILED(rv); // can not find it? -> doomed
1605 if (item_doomed &&
1606 mPinnedEntryRetriesCount < kPinnedEntryRetriesLimit &&
1607 (aItem->mItemType & (nsIApplicationCache::ITEM_EXPLICIT |
1608 nsIApplicationCache::ITEM_FALLBACK))) {
1609 rv = EvictOneNonPinned();
1610 if (NS_FAILED(rv)) {
1611 mSucceeded = false;
1612 NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
1613 Finish();
1614 return;
1615 }
1617 // This reverts the item state to UNINITIALIZED that makes it to
1618 // be scheduled for download again.
1619 rv = aItem->Cancel();
1620 if (NS_FAILED(rv)) {
1621 mSucceeded = false;
1622 NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
1623 Finish();
1624 return;
1625 }
1627 mPinnedEntryRetriesCount++;
1629 LogToConsole("An unpinned offline cache deleted");
1631 // Retry this item.
1632 ProcessNextURI();
1633 return;
1634 }
1635 }
1637 // According to parallelism this may imply more pinned retries count,
1638 // but that is not critical, since at one moment the algoritm will
1639 // stop anyway. Also, this code may soon be completely removed
1640 // after we have a separate storage for pinned apps.
1641 mPinnedEntryRetriesCount = 0;
1643 // Check for failures. 3XX, 4XX and 5XX errors on items explicitly
1644 // listed in the manifest will cause the update to fail.
1645 if (NS_FAILED(rv) || !succeeded) {
1646 if (aItem->mItemType &
1647 (nsIApplicationCache::ITEM_EXPLICIT |
1648 nsIApplicationCache::ITEM_FALLBACK)) {
1649 LogToConsole("Offline cache manifest item failed to load", aItem);
1650 mSucceeded = false;
1651 }
1652 } else {
1653 rv = mApplicationCache->MarkEntry(aItem->mCacheKey, aItem->mItemType);
1654 if (NS_FAILED(rv)) {
1655 mSucceeded = false;
1656 }
1657 }
1659 if (!mSucceeded) {
1660 NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
1661 Finish();
1662 return;
1663 }
1665 NotifyState(nsIOfflineCacheUpdateObserver::STATE_ITEMCOMPLETED);
1667 ProcessNextURI();
1668 }
1670 void
1671 nsOfflineCacheUpdate::ManifestCheckCompleted(nsresult aStatus,
1672 const nsCString &aManifestHash)
1673 {
1674 // Keep the object alive through a Finish() call.
1675 nsCOMPtr<nsIOfflineCacheUpdate> kungFuDeathGrip(this);
1677 if (NS_SUCCEEDED(aStatus)) {
1678 nsAutoCString firstManifestHash;
1679 mManifestItem->GetManifestHash(firstManifestHash);
1680 if (aManifestHash != firstManifestHash) {
1681 LOG(("Manifest has changed during cache items download [%p]", this));
1682 LogToConsole("Offline cache manifest changed during update", mManifestItem);
1683 aStatus = NS_ERROR_FAILURE;
1684 }
1685 }
1687 if (NS_FAILED(aStatus)) {
1688 mSucceeded = false;
1689 NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
1690 }
1692 if (NS_FAILED(aStatus) && mRescheduleCount < kRescheduleLimit) {
1693 // Do the final stuff but prevent notification of STATE_FINISHED.
1694 // That would disconnect listeners that are responsible for document
1695 // association after a successful update. Forwarding notifications
1696 // from a new update through this dead update to them is absolutely
1697 // correct.
1698 FinishNoNotify();
1700 nsRefPtr<nsOfflineCacheUpdate> newUpdate =
1701 new nsOfflineCacheUpdate();
1702 // Leave aDocument argument null. Only glues and children keep
1703 // document instances.
1704 newUpdate->Init(mManifestURI, mDocumentURI, nullptr,
1705 mCustomProfileDir, mAppID, mInBrowser);
1707 // In a rare case the manifest will not be modified on the next refetch
1708 // transfer all master document URIs to the new update to ensure that
1709 // all documents refering it will be properly cached.
1710 for (int32_t i = 0; i < mDocumentURIs.Count(); i++) {
1711 newUpdate->StickDocument(mDocumentURIs[i]);
1712 }
1714 newUpdate->mRescheduleCount = mRescheduleCount + 1;
1715 newUpdate->AddObserver(this, false);
1716 newUpdate->Schedule();
1717 }
1718 else {
1719 LogToConsole("Offline cache update done", mManifestItem);
1720 Finish();
1721 }
1722 }
1724 nsresult
1725 nsOfflineCacheUpdate::Begin()
1726 {
1727 LOG(("nsOfflineCacheUpdate::Begin [%p]", this));
1729 // Keep the object alive through a ProcessNextURI()/Finish() call.
1730 nsCOMPtr<nsIOfflineCacheUpdate> kungFuDeathGrip(this);
1732 mItemsInProgress = 0;
1734 if (mState == STATE_CANCELLED) {
1735 nsRefPtr<nsRunnableMethod<nsOfflineCacheUpdate> > errorNotification =
1736 NS_NewRunnableMethod(this,
1737 &nsOfflineCacheUpdate::AsyncFinishWithError);
1738 nsresult rv = NS_DispatchToMainThread(errorNotification);
1739 NS_ENSURE_SUCCESS(rv, rv);
1741 return NS_OK;
1742 }
1744 if (mPartialUpdate) {
1745 mState = STATE_DOWNLOADING;
1746 NotifyState(nsIOfflineCacheUpdateObserver::STATE_DOWNLOADING);
1747 ProcessNextURI();
1748 return NS_OK;
1749 }
1751 // Start checking the manifest.
1752 nsCOMPtr<nsIURI> uri;
1754 mManifestItem = new nsOfflineManifestItem(mManifestURI,
1755 mDocumentURI,
1756 mApplicationCache,
1757 mPreviousApplicationCache);
1758 if (!mManifestItem) {
1759 return NS_ERROR_OUT_OF_MEMORY;
1760 }
1762 mState = STATE_CHECKING;
1763 mByteProgress = 0;
1764 NotifyState(nsIOfflineCacheUpdateObserver::STATE_CHECKING);
1766 nsresult rv = mManifestItem->OpenChannel(this);
1767 if (NS_FAILED(rv)) {
1768 LoadCompleted(mManifestItem);
1769 }
1771 return NS_OK;
1772 }
1774 //-----------------------------------------------------------------------------
1775 // nsOfflineCacheUpdate <private>
1776 //-----------------------------------------------------------------------------
1778 nsresult
1779 nsOfflineCacheUpdate::AddExistingItems(uint32_t aType,
1780 nsTArray<nsCString>* namespaceFilter)
1781 {
1782 if (!mPreviousApplicationCache) {
1783 return NS_OK;
1784 }
1786 if (namespaceFilter && namespaceFilter->Length() == 0) {
1787 // Don't bother to walk entries when there are no namespaces
1788 // defined.
1789 return NS_OK;
1790 }
1792 uint32_t count = 0;
1793 char **keys = nullptr;
1794 nsresult rv = mPreviousApplicationCache->GatherEntries(aType,
1795 &count, &keys);
1796 NS_ENSURE_SUCCESS(rv, rv);
1798 AutoFreeArray autoFree(count, keys);
1800 for (uint32_t i = 0; i < count; i++) {
1801 if (namespaceFilter) {
1802 bool found = false;
1803 for (uint32_t j = 0; j < namespaceFilter->Length() && !found; j++) {
1804 found = StringBeginsWith(nsDependentCString(keys[i]),
1805 namespaceFilter->ElementAt(j));
1806 }
1808 if (!found)
1809 continue;
1810 }
1812 nsCOMPtr<nsIURI> uri;
1813 if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(uri), keys[i]))) {
1814 rv = AddURI(uri, aType);
1815 NS_ENSURE_SUCCESS(rv, rv);
1816 }
1817 }
1819 return NS_OK;
1820 }
1822 nsresult
1823 nsOfflineCacheUpdate::ProcessNextURI()
1824 {
1825 // Keep the object alive through a Finish() call.
1826 nsCOMPtr<nsIOfflineCacheUpdate> kungFuDeathGrip(this);
1828 LOG(("nsOfflineCacheUpdate::ProcessNextURI [%p, inprogress=%d, numItems=%d]",
1829 this, mItemsInProgress, mItems.Length()));
1831 if (mState != STATE_DOWNLOADING) {
1832 LOG((" should only be called from the DOWNLOADING state, ignoring"));
1833 return NS_ERROR_UNEXPECTED;
1834 }
1836 nsOfflineCacheUpdateItem * runItem = nullptr;
1837 uint32_t completedItems = 0;
1838 for (uint32_t i = 0; i < mItems.Length(); ++i) {
1839 nsOfflineCacheUpdateItem * item = mItems[i];
1841 if (item->IsScheduled()) {
1842 runItem = item;
1843 break;
1844 }
1846 if (item->IsCompleted())
1847 ++completedItems;
1848 }
1850 if (completedItems == mItems.Length()) {
1851 LOG(("nsOfflineCacheUpdate::ProcessNextURI [%p]: all items loaded", this));
1853 if (mPartialUpdate) {
1854 return Finish();
1855 } else {
1856 // Verify that the manifest wasn't changed during the
1857 // update, to prevent capturing a cache while the server
1858 // is being updated. The check will call
1859 // ManifestCheckCompleted() when it's done.
1860 nsRefPtr<nsManifestCheck> manifestCheck =
1861 new nsManifestCheck(this, mManifestURI, mDocumentURI);
1862 if (NS_FAILED(manifestCheck->Begin())) {
1863 mSucceeded = false;
1864 NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
1865 return Finish();
1866 }
1868 return NS_OK;
1869 }
1870 }
1872 if (!runItem) {
1873 LOG(("nsOfflineCacheUpdate::ProcessNextURI [%p]:"
1874 " No more items to include in parallel load", this));
1875 return NS_OK;
1876 }
1878 #if defined(PR_LOGGING)
1879 if (LOG_ENABLED()) {
1880 nsAutoCString spec;
1881 runItem->mURI->GetSpec(spec);
1882 LOG(("%p: Opening channel for %s", this, spec.get()));
1883 }
1884 #endif
1886 ++mItemsInProgress;
1887 NotifyState(nsIOfflineCacheUpdateObserver::STATE_ITEMSTARTED);
1889 nsresult rv = runItem->OpenChannel(this);
1890 if (NS_FAILED(rv)) {
1891 LoadCompleted(runItem);
1892 return rv;
1893 }
1895 if (mItemsInProgress >= kParallelLoadLimit) {
1896 LOG(("nsOfflineCacheUpdate::ProcessNextURI [%p]:"
1897 " At parallel load limit", this));
1898 return NS_OK;
1899 }
1901 // This calls this method again via a post triggering
1902 // a parallel item load
1903 return NS_DispatchToCurrentThread(this);
1904 }
1906 void
1907 nsOfflineCacheUpdate::GatherObservers(nsCOMArray<nsIOfflineCacheUpdateObserver> &aObservers)
1908 {
1909 for (int32_t i = 0; i < mWeakObservers.Count(); i++) {
1910 nsCOMPtr<nsIOfflineCacheUpdateObserver> observer =
1911 do_QueryReferent(mWeakObservers[i]);
1912 if (observer)
1913 aObservers.AppendObject(observer);
1914 else
1915 mWeakObservers.RemoveObjectAt(i--);
1916 }
1918 for (int32_t i = 0; i < mObservers.Count(); i++) {
1919 aObservers.AppendObject(mObservers[i]);
1920 }
1921 }
1923 void
1924 nsOfflineCacheUpdate::NotifyState(uint32_t state)
1925 {
1926 LOG(("nsOfflineCacheUpdate::NotifyState [%p, %d]", this, state));
1928 if (state == STATE_ERROR) {
1929 LogToConsole("Offline cache update error", mManifestItem);
1930 }
1932 nsCOMArray<nsIOfflineCacheUpdateObserver> observers;
1933 GatherObservers(observers);
1935 for (int32_t i = 0; i < observers.Count(); i++) {
1936 observers[i]->UpdateStateChanged(this, state);
1937 }
1938 }
1940 void
1941 nsOfflineCacheUpdate::NotifyUpdateAvailability(bool updateAvailable)
1942 {
1943 if (!mUpdateAvailableObserver)
1944 return;
1946 LOG(("nsOfflineCacheUpdate::NotifyUpdateAvailability [this=%p, avail=%d]",
1947 this, updateAvailable));
1949 const char* topic = updateAvailable
1950 ? "offline-cache-update-available"
1951 : "offline-cache-update-unavailable";
1953 nsCOMPtr<nsIObserver> observer;
1954 observer.swap(mUpdateAvailableObserver);
1955 observer->Observe(mManifestURI, topic, nullptr);
1956 }
1958 void
1959 nsOfflineCacheUpdate::AssociateDocuments(nsIApplicationCache* cache)
1960 {
1961 if (!cache) {
1962 LOG(("nsOfflineCacheUpdate::AssociateDocuments bypassed"
1963 ", no cache provided [this=%p]", this));
1964 return;
1965 }
1967 nsCOMArray<nsIOfflineCacheUpdateObserver> observers;
1968 GatherObservers(observers);
1970 for (int32_t i = 0; i < observers.Count(); i++) {
1971 observers[i]->ApplicationCacheAvailable(cache);
1972 }
1973 }
1975 void
1976 nsOfflineCacheUpdate::StickDocument(nsIURI *aDocumentURI)
1977 {
1978 if (!aDocumentURI)
1979 return;
1981 mDocumentURIs.AppendObject(aDocumentURI);
1982 }
1984 void
1985 nsOfflineCacheUpdate::SetOwner(nsOfflineCacheUpdateOwner *aOwner)
1986 {
1987 NS_ASSERTION(!mOwner, "Tried to set cache update owner twice.");
1988 mOwner = aOwner->asWeakPtr();
1989 }
1991 bool
1992 nsOfflineCacheUpdate::IsForGroupID(const nsCSubstring &groupID)
1993 {
1994 return mGroupID == groupID;
1995 }
1997 nsresult
1998 nsOfflineCacheUpdate::UpdateFinished(nsOfflineCacheUpdate *aUpdate)
1999 {
2000 // Keep the object alive through a Finish() call.
2001 nsCOMPtr<nsIOfflineCacheUpdate> kungFuDeathGrip(this);
2003 mImplicitUpdate = nullptr;
2005 NotifyState(nsIOfflineCacheUpdateObserver::STATE_NOUPDATE);
2006 Finish();
2008 return NS_OK;
2009 }
2011 void
2012 nsOfflineCacheUpdate::OnByteProgress(uint64_t byteIncrement)
2013 {
2014 mByteProgress += byteIncrement;
2015 NotifyState(nsIOfflineCacheUpdateObserver::STATE_ITEMPROGRESS);
2016 }
2018 nsresult
2019 nsOfflineCacheUpdate::ScheduleImplicit()
2020 {
2021 if (mDocumentURIs.Count() == 0)
2022 return NS_OK;
2024 nsresult rv;
2026 nsRefPtr<nsOfflineCacheUpdate> update = new nsOfflineCacheUpdate();
2027 NS_ENSURE_TRUE(update, NS_ERROR_OUT_OF_MEMORY);
2029 nsAutoCString clientID;
2030 if (mPreviousApplicationCache) {
2031 rv = mPreviousApplicationCache->GetClientID(clientID);
2032 NS_ENSURE_SUCCESS(rv, rv);
2033 }
2034 else if (mApplicationCache) {
2035 rv = mApplicationCache->GetClientID(clientID);
2036 NS_ENSURE_SUCCESS(rv, rv);
2037 }
2038 else {
2039 NS_ERROR("Offline cache update not having set mApplicationCache?");
2040 }
2042 rv = update->InitPartial(mManifestURI, clientID, mDocumentURI);
2043 NS_ENSURE_SUCCESS(rv, rv);
2045 for (int32_t i = 0; i < mDocumentURIs.Count(); i++) {
2046 rv = update->AddURI(mDocumentURIs[i],
2047 nsIApplicationCache::ITEM_IMPLICIT);
2048 NS_ENSURE_SUCCESS(rv, rv);
2049 }
2051 update->SetOwner(this);
2052 rv = update->Begin();
2053 NS_ENSURE_SUCCESS(rv, rv);
2055 mImplicitUpdate = update;
2057 return NS_OK;
2058 }
2060 nsresult
2061 nsOfflineCacheUpdate::FinishNoNotify()
2062 {
2063 LOG(("nsOfflineCacheUpdate::Finish [%p]", this));
2065 mState = STATE_FINISHED;
2067 if (!mPartialUpdate && !mOnlyCheckUpdate) {
2068 if (mSucceeded) {
2069 nsIArray *namespaces = mManifestItem->GetNamespaces();
2070 nsresult rv = mApplicationCache->AddNamespaces(namespaces);
2071 if (NS_FAILED(rv)) {
2072 NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
2073 mSucceeded = false;
2074 }
2076 rv = mApplicationCache->Activate();
2077 if (NS_FAILED(rv)) {
2078 NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
2079 mSucceeded = false;
2080 }
2082 AssociateDocuments(mApplicationCache);
2083 }
2085 if (mObsolete) {
2086 nsCOMPtr<nsIApplicationCacheService> appCacheService =
2087 do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID);
2088 if (appCacheService) {
2089 nsAutoCString groupID;
2090 mApplicationCache->GetGroupID(groupID);
2091 appCacheService->DeactivateGroup(groupID);
2092 }
2093 }
2095 if (!mSucceeded) {
2096 // Update was not merged, mark all the loads as failures
2097 for (uint32_t i = 0; i < mItems.Length(); i++) {
2098 mItems[i]->Cancel();
2099 }
2101 mApplicationCache->Discard();
2102 }
2103 }
2105 nsresult rv = NS_OK;
2107 if (mOwner) {
2108 rv = mOwner->UpdateFinished(this);
2109 // mozilla::WeakPtr is missing some key features, like setting it to
2110 // null explicitly.
2111 mOwner = mozilla::WeakPtr<nsOfflineCacheUpdateOwner>();
2112 }
2114 return rv;
2115 }
2117 nsresult
2118 nsOfflineCacheUpdate::Finish()
2119 {
2120 nsresult rv = FinishNoNotify();
2122 NotifyState(nsIOfflineCacheUpdateObserver::STATE_FINISHED);
2124 return rv;
2125 }
2127 void
2128 nsOfflineCacheUpdate::AsyncFinishWithError()
2129 {
2130 NotifyState(nsOfflineCacheUpdate::STATE_ERROR);
2131 Finish();
2132 }
2134 static nsresult
2135 EvictOneOfCacheGroups(nsIApplicationCacheService *cacheService,
2136 uint32_t count, const char * const *groups)
2137 {
2138 nsresult rv;
2139 unsigned int i;
2141 for (i = 0; i < count; i++) {
2142 nsCOMPtr<nsIURI> uri;
2143 rv = NS_NewURI(getter_AddRefs(uri), groups[i]);
2144 NS_ENSURE_SUCCESS(rv, rv);
2146 nsDependentCString group_name(groups[i]);
2147 nsCOMPtr<nsIApplicationCache> cache;
2148 rv = cacheService->GetActiveCache(group_name, getter_AddRefs(cache));
2149 // Maybe someone in another thread or process have deleted it.
2150 if (NS_FAILED(rv) || !cache)
2151 continue;
2153 bool pinned;
2154 rv = nsOfflineCacheUpdateService::OfflineAppPinnedForURI(uri,
2155 nullptr,
2156 &pinned);
2157 NS_ENSURE_SUCCESS(rv, rv);
2159 if (!pinned) {
2160 rv = cache->Discard();
2161 return NS_OK;
2162 }
2163 }
2165 return NS_ERROR_FILE_NOT_FOUND;
2166 }
2168 nsresult
2169 nsOfflineCacheUpdate::EvictOneNonPinned()
2170 {
2171 nsresult rv;
2173 nsCOMPtr<nsIApplicationCacheService> cacheService =
2174 do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv);
2175 NS_ENSURE_SUCCESS(rv, rv);
2177 uint32_t count;
2178 char **groups;
2179 rv = cacheService->GetGroupsTimeOrdered(&count, &groups);
2180 NS_ENSURE_SUCCESS(rv, rv);
2182 rv = EvictOneOfCacheGroups(cacheService, count, groups);
2184 NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, groups);
2185 return rv;
2186 }
2188 //-----------------------------------------------------------------------------
2189 // nsOfflineCacheUpdate::nsIOfflineCacheUpdate
2190 //-----------------------------------------------------------------------------
2192 NS_IMETHODIMP
2193 nsOfflineCacheUpdate::GetUpdateDomain(nsACString &aUpdateDomain)
2194 {
2195 NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
2197 aUpdateDomain = mUpdateDomain;
2198 return NS_OK;
2199 }
2201 NS_IMETHODIMP
2202 nsOfflineCacheUpdate::GetStatus(uint16_t *aStatus)
2203 {
2204 switch (mState) {
2205 case STATE_CHECKING :
2206 *aStatus = nsIDOMOfflineResourceList::CHECKING;
2207 return NS_OK;
2208 case STATE_DOWNLOADING :
2209 *aStatus = nsIDOMOfflineResourceList::DOWNLOADING;
2210 return NS_OK;
2211 default :
2212 *aStatus = nsIDOMOfflineResourceList::IDLE;
2213 return NS_OK;
2214 }
2216 return NS_ERROR_FAILURE;
2217 }
2219 NS_IMETHODIMP
2220 nsOfflineCacheUpdate::GetPartial(bool *aPartial)
2221 {
2222 *aPartial = mPartialUpdate || mOnlyCheckUpdate;
2223 return NS_OK;
2224 }
2226 NS_IMETHODIMP
2227 nsOfflineCacheUpdate::GetManifestURI(nsIURI **aManifestURI)
2228 {
2229 NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
2231 NS_IF_ADDREF(*aManifestURI = mManifestURI);
2232 return NS_OK;
2233 }
2235 NS_IMETHODIMP
2236 nsOfflineCacheUpdate::GetSucceeded(bool *aSucceeded)
2237 {
2238 NS_ENSURE_TRUE(mState == STATE_FINISHED, NS_ERROR_NOT_AVAILABLE);
2240 *aSucceeded = mSucceeded;
2242 return NS_OK;
2243 }
2245 NS_IMETHODIMP
2246 nsOfflineCacheUpdate::GetIsUpgrade(bool *aIsUpgrade)
2247 {
2248 NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
2250 *aIsUpgrade = (mPreviousApplicationCache != nullptr);
2252 return NS_OK;
2253 }
2255 nsresult
2256 nsOfflineCacheUpdate::AddURI(nsIURI *aURI, uint32_t aType)
2257 {
2258 NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
2260 if (mState >= STATE_DOWNLOADING)
2261 return NS_ERROR_NOT_AVAILABLE;
2263 // Resource URIs must have the same scheme as the manifest.
2264 nsAutoCString scheme;
2265 aURI->GetScheme(scheme);
2267 bool match;
2268 if (NS_FAILED(mManifestURI->SchemeIs(scheme.get(), &match)) || !match)
2269 return NS_ERROR_FAILURE;
2271 // Don't fetch the same URI twice.
2272 for (uint32_t i = 0; i < mItems.Length(); i++) {
2273 bool equals;
2274 if (NS_SUCCEEDED(mItems[i]->mURI->Equals(aURI, &equals)) && equals) {
2275 // retain both types.
2276 mItems[i]->mItemType |= aType;
2277 return NS_OK;
2278 }
2279 }
2281 nsRefPtr<nsOfflineCacheUpdateItem> item =
2282 new nsOfflineCacheUpdateItem(aURI,
2283 mDocumentURI,
2284 mApplicationCache,
2285 mPreviousApplicationCache,
2286 aType);
2287 if (!item) return NS_ERROR_OUT_OF_MEMORY;
2289 mItems.AppendElement(item);
2290 mAddedItems = true;
2292 return NS_OK;
2293 }
2295 NS_IMETHODIMP
2296 nsOfflineCacheUpdate::AddDynamicURI(nsIURI *aURI)
2297 {
2298 if (GeckoProcessType_Default != XRE_GetProcessType())
2299 return NS_ERROR_NOT_IMPLEMENTED;
2301 // If this is a partial update and the resource is already in the
2302 // cache, we should only mark the entry, not fetch it again.
2303 if (mPartialUpdate) {
2304 nsAutoCString key;
2305 GetCacheKey(aURI, key);
2307 uint32_t types;
2308 nsresult rv = mApplicationCache->GetTypes(key, &types);
2309 if (NS_SUCCEEDED(rv)) {
2310 if (!(types & nsIApplicationCache::ITEM_DYNAMIC)) {
2311 mApplicationCache->MarkEntry
2312 (key, nsIApplicationCache::ITEM_DYNAMIC);
2313 }
2314 return NS_OK;
2315 }
2316 }
2318 return AddURI(aURI, nsIApplicationCache::ITEM_DYNAMIC);
2319 }
2321 NS_IMETHODIMP
2322 nsOfflineCacheUpdate::Cancel()
2323 {
2324 LOG(("nsOfflineCacheUpdate::Cancel [%p]", this));
2326 if ((mState == STATE_FINISHED) || (mState == STATE_CANCELLED)) {
2327 return NS_ERROR_NOT_AVAILABLE;
2328 }
2330 mState = STATE_CANCELLED;
2331 mSucceeded = false;
2333 // Cancel all running downloads
2334 for (uint32_t i = 0; i < mItems.Length(); ++i) {
2335 nsOfflineCacheUpdateItem * item = mItems[i];
2337 if (item->IsInProgress())
2338 item->Cancel();
2339 }
2341 return NS_OK;
2342 }
2344 NS_IMETHODIMP
2345 nsOfflineCacheUpdate::AddObserver(nsIOfflineCacheUpdateObserver *aObserver,
2346 bool aHoldWeak)
2347 {
2348 LOG(("nsOfflineCacheUpdate::AddObserver [%p] to update [%p]", aObserver, this));
2350 NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
2352 if (aHoldWeak) {
2353 nsCOMPtr<nsIWeakReference> weakRef = do_GetWeakReference(aObserver);
2354 mWeakObservers.AppendObject(weakRef);
2355 } else {
2356 mObservers.AppendObject(aObserver);
2357 }
2359 return NS_OK;
2360 }
2362 NS_IMETHODIMP
2363 nsOfflineCacheUpdate::RemoveObserver(nsIOfflineCacheUpdateObserver *aObserver)
2364 {
2365 LOG(("nsOfflineCacheUpdate::RemoveObserver [%p] from update [%p]", aObserver, this));
2367 NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
2369 for (int32_t i = 0; i < mWeakObservers.Count(); i++) {
2370 nsCOMPtr<nsIOfflineCacheUpdateObserver> observer =
2371 do_QueryReferent(mWeakObservers[i]);
2372 if (observer == aObserver) {
2373 mWeakObservers.RemoveObjectAt(i);
2374 return NS_OK;
2375 }
2376 }
2378 for (int32_t i = 0; i < mObservers.Count(); i++) {
2379 if (mObservers[i] == aObserver) {
2380 mObservers.RemoveObjectAt(i);
2381 return NS_OK;
2382 }
2383 }
2385 return NS_OK;
2386 }
2388 NS_IMETHODIMP
2389 nsOfflineCacheUpdate::GetByteProgress(uint64_t * _result)
2390 {
2391 NS_ENSURE_ARG(_result);
2393 *_result = mByteProgress;
2394 return NS_OK;
2395 }
2397 NS_IMETHODIMP
2398 nsOfflineCacheUpdate::Schedule()
2399 {
2400 LOG(("nsOfflineCacheUpdate::Schedule [%p]", this));
2402 nsOfflineCacheUpdateService* service =
2403 nsOfflineCacheUpdateService::EnsureService();
2405 if (!service) {
2406 return NS_ERROR_FAILURE;
2407 }
2409 return service->ScheduleUpdate(this);
2410 }
2412 NS_IMETHODIMP
2413 nsOfflineCacheUpdate::UpdateStateChanged(nsIOfflineCacheUpdate *aUpdate,
2414 uint32_t aState)
2415 {
2416 if (aState == nsIOfflineCacheUpdateObserver::STATE_FINISHED) {
2417 // Take the mSucceeded flag from the underlying update, we will be
2418 // queried for it soon. mSucceeded of this update is false (manifest
2419 // check failed) but the subsequent re-fetch update might succeed
2420 bool succeeded;
2421 aUpdate->GetSucceeded(&succeeded);
2422 mSucceeded = succeeded;
2423 }
2425 NotifyState(aState);
2426 if (aState == nsIOfflineCacheUpdateObserver::STATE_FINISHED)
2427 aUpdate->RemoveObserver(this);
2429 return NS_OK;
2430 }
2432 NS_IMETHODIMP
2433 nsOfflineCacheUpdate::ApplicationCacheAvailable(nsIApplicationCache *applicationCache)
2434 {
2435 AssociateDocuments(applicationCache);
2436 return NS_OK;
2437 }
2439 //-----------------------------------------------------------------------------
2440 // nsOfflineCacheUpdate::nsIRunable
2441 //-----------------------------------------------------------------------------
2443 NS_IMETHODIMP
2444 nsOfflineCacheUpdate::Run()
2445 {
2446 ProcessNextURI();
2447 return NS_OK;
2448 }