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/. */
5 
6 #include "BackgroundUtils.h"
7 #include "OfflineCacheUpdateChild.h"
8 #include "nsOfflineCacheUpdate.h"
9 #include "mozilla/dom/ContentChild.h"
10 #include "mozilla/dom/TabChild.h"
11 #include "mozilla/ipc/URIUtils.h"
12 #include "mozilla/net/NeckoCommon.h"
13 
14 #include "nsIApplicationCacheContainer.h"
15 #include "nsIApplicationCacheChannel.h"
16 #include "nsIApplicationCacheService.h"
17 #include "nsIDocShell.h"
18 #include "nsIDocShellTreeItem.h"
19 #include "nsIDocShellTreeOwner.h"
20 #include "nsPIDOMWindow.h"
21 #include "nsIDOMOfflineResourceList.h"
22 #include "nsIDocument.h"
23 #include "nsIObserverService.h"
24 #include "nsIURL.h"
25 #include "nsITabChild.h"
26 #include "nsNetCID.h"
27 #include "nsNetUtil.h"
28 #include "nsServiceManagerUtils.h"
29 #include "nsStreamUtils.h"
30 #include "nsThreadUtils.h"
31 #include "nsProxyRelease.h"
32 #include "mozilla/Logging.h"
33 #include "nsIAsyncVerifyRedirectCallback.h"
34 
35 using namespace mozilla::ipc;
36 using namespace mozilla::net;
37 using mozilla::dom::ContentChild;
38 using mozilla::dom::TabChild;
39 
40 //
41 // To enable logging (see mozilla/Logging.h for full details):
42 //
43 //    set MOZ_LOG=nsOfflineCacheUpdate:5
44 //    set MOZ_LOG_FILE=offlineupdate.log
45 //
46 // this enables LogLevel::Debug level information and places all output in
47 // the file offlineupdate.log
48 //
49 extern mozilla::LazyLogModule gOfflineCacheUpdateLog;
50 
51 #undef LOG
52 #define LOG(args) \
53   MOZ_LOG(gOfflineCacheUpdateLog, mozilla::LogLevel::Debug, args)
54 
55 #undef LOG_ENABLED
56 #define LOG_ENABLED() \
57   MOZ_LOG_TEST(gOfflineCacheUpdateLog, mozilla::LogLevel::Debug)
58 
59 namespace mozilla {
60 namespace docshell {
61 
62 //-----------------------------------------------------------------------------
63 // OfflineCacheUpdateChild::nsISupports
64 //-----------------------------------------------------------------------------
65 
66 NS_INTERFACE_MAP_BEGIN(OfflineCacheUpdateChild)
NS_INTERFACE_MAP_ENTRY(nsISupports)67   NS_INTERFACE_MAP_ENTRY(nsISupports)
68   NS_INTERFACE_MAP_ENTRY(nsIOfflineCacheUpdate)
69 NS_INTERFACE_MAP_END
70 
71 NS_IMPL_ADDREF(OfflineCacheUpdateChild)
72 NS_IMPL_RELEASE(OfflineCacheUpdateChild)
73 
74 //-----------------------------------------------------------------------------
75 // OfflineCacheUpdateChild <public>
76 //-----------------------------------------------------------------------------
77 
78 OfflineCacheUpdateChild::OfflineCacheUpdateChild(nsPIDOMWindowInner *aWindow)
79     : mState(STATE_UNINITIALIZED),
80       mIsUpgrade(false),
81       mSucceeded(false),
82       mWindow(aWindow),
83       mByteProgress(0) {}
84 
~OfflineCacheUpdateChild()85 OfflineCacheUpdateChild::~OfflineCacheUpdateChild() {
86   LOG(("OfflineCacheUpdateChild::~OfflineCacheUpdateChild [%p]", this));
87 }
88 
GatherObservers(nsCOMArray<nsIOfflineCacheUpdateObserver> & aObservers)89 void OfflineCacheUpdateChild::GatherObservers(
90     nsCOMArray<nsIOfflineCacheUpdateObserver> &aObservers) {
91   for (int32_t i = 0; i < mWeakObservers.Count(); i++) {
92     nsCOMPtr<nsIOfflineCacheUpdateObserver> observer =
93         do_QueryReferent(mWeakObservers[i]);
94     if (observer)
95       aObservers.AppendObject(observer);
96     else
97       mWeakObservers.RemoveObjectAt(i--);
98   }
99 
100   for (int32_t i = 0; i < mObservers.Count(); i++) {
101     aObservers.AppendObject(mObservers[i]);
102   }
103 }
104 
SetDocument(nsIDOMDocument * aDocument)105 void OfflineCacheUpdateChild::SetDocument(nsIDOMDocument *aDocument) {
106   // The design is one document for one cache update on the content process.
107   NS_ASSERTION(
108       !mDocument,
109       "Setting more then a single document on a child offline cache update");
110 
111   LOG(("Document %p added to update child %p", aDocument, this));
112 
113   // Add document only if it was not loaded from an offline cache.
114   // If it were loaded from an offline cache then it has already
115   // been associated with it and must not be again cached as
116   // implicit (which are the reasons we collect documents here).
117   nsCOMPtr<nsIDocument> document = do_QueryInterface(aDocument);
118   if (!document) return;
119 
120   nsIChannel *channel = document->GetChannel();
121   nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel =
122       do_QueryInterface(channel);
123   if (!appCacheChannel) return;
124 
125   bool loadedFromAppCache;
126   appCacheChannel->GetLoadedFromApplicationCache(&loadedFromAppCache);
127   if (loadedFromAppCache) return;
128 
129   mDocument = aDocument;
130 }
131 
AssociateDocument(nsIDOMDocument * aDocument,nsIApplicationCache * aApplicationCache)132 nsresult OfflineCacheUpdateChild::AssociateDocument(
133     nsIDOMDocument *aDocument, nsIApplicationCache *aApplicationCache) {
134   // Check that the document that requested this update was
135   // previously associated with an application cache.  If not, it
136   // should be associated with the new one.
137   nsCOMPtr<nsIApplicationCacheContainer> container =
138       do_QueryInterface(aDocument);
139   if (!container) return NS_OK;
140 
141   nsCOMPtr<nsIApplicationCache> existingCache;
142   nsresult rv = container->GetApplicationCache(getter_AddRefs(existingCache));
143   NS_ENSURE_SUCCESS(rv, rv);
144 
145   if (!existingCache) {
146     if (LOG_ENABLED()) {
147       nsAutoCString clientID;
148       if (aApplicationCache) {
149         aApplicationCache->GetClientID(clientID);
150       }
151       LOG(("Update %p: associating app cache %s to document %p", this,
152            clientID.get(), aDocument));
153     }
154 
155     rv = container->SetApplicationCache(aApplicationCache);
156     NS_ENSURE_SUCCESS(rv, rv);
157   }
158 
159   return NS_OK;
160 }
161 
162 //-----------------------------------------------------------------------------
163 // OfflineCacheUpdateChild::nsIOfflineCacheUpdate
164 //-----------------------------------------------------------------------------
165 
166 NS_IMETHODIMP
Init(nsIURI * aManifestURI,nsIURI * aDocumentURI,nsIPrincipal * aLoadingPrincipal,nsIDOMDocument * aDocument,nsIFile * aCustomProfileDir)167 OfflineCacheUpdateChild::Init(nsIURI *aManifestURI, nsIURI *aDocumentURI,
168                               nsIPrincipal *aLoadingPrincipal,
169                               nsIDOMDocument *aDocument,
170                               nsIFile *aCustomProfileDir) {
171   nsresult rv;
172 
173   // Make sure the service has been initialized
174   nsOfflineCacheUpdateService *service =
175       nsOfflineCacheUpdateService::EnsureService();
176   if (!service) return NS_ERROR_FAILURE;
177 
178   if (aCustomProfileDir) {
179     NS_ERROR("Custom Offline Cache Update not supported on child process");
180     return NS_ERROR_NOT_IMPLEMENTED;
181   }
182 
183   LOG(("OfflineCacheUpdateChild::Init [%p]", this));
184 
185   // Only http and https applications are supported.
186   bool match;
187   rv = aManifestURI->SchemeIs("http", &match);
188   NS_ENSURE_SUCCESS(rv, rv);
189 
190   if (!match) {
191     rv = aManifestURI->SchemeIs("https", &match);
192     NS_ENSURE_SUCCESS(rv, rv);
193     if (!match) return NS_ERROR_ABORT;
194   }
195 
196   mManifestURI = aManifestURI;
197 
198   rv = mManifestURI->GetAsciiHost(mUpdateDomain);
199   NS_ENSURE_SUCCESS(rv, rv);
200 
201   mDocumentURI = aDocumentURI;
202   mLoadingPrincipal = aLoadingPrincipal;
203 
204   mState = STATE_INITIALIZED;
205 
206   if (aDocument) SetDocument(aDocument);
207 
208   return NS_OK;
209 }
210 
211 NS_IMETHODIMP
InitPartial(nsIURI * aManifestURI,const nsACString & clientID,nsIURI * aDocumentURI,nsIPrincipal * aLoadingPrincipal)212 OfflineCacheUpdateChild::InitPartial(nsIURI *aManifestURI,
213                                      const nsACString &clientID,
214                                      nsIURI *aDocumentURI,
215                                      nsIPrincipal *aLoadingPrincipal) {
216   NS_NOTREACHED(
217       "Not expected to do partial offline cache updates"
218       " on the child process");
219   // For now leaving this method, we may discover we need it.
220   return NS_ERROR_NOT_IMPLEMENTED;
221 }
222 
223 NS_IMETHODIMP
InitForUpdateCheck(nsIURI * aManifestURI,nsIPrincipal * aLoadingPrincipal,nsIObserver * aObserver)224 OfflineCacheUpdateChild::InitForUpdateCheck(nsIURI *aManifestURI,
225                                             nsIPrincipal *aLoadingPrincipal,
226                                             nsIObserver *aObserver) {
227   NS_NOTREACHED(
228       "Not expected to do only update checks"
229       " from the child process");
230   return NS_ERROR_NOT_IMPLEMENTED;
231 }
232 
233 NS_IMETHODIMP
GetUpdateDomain(nsACString & aUpdateDomain)234 OfflineCacheUpdateChild::GetUpdateDomain(nsACString &aUpdateDomain) {
235   NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
236 
237   aUpdateDomain = mUpdateDomain;
238   return NS_OK;
239 }
240 
241 NS_IMETHODIMP
GetStatus(uint16_t * aStatus)242 OfflineCacheUpdateChild::GetStatus(uint16_t *aStatus) {
243   switch (mState) {
244     case STATE_CHECKING:
245       *aStatus = nsIDOMOfflineResourceList::CHECKING;
246       return NS_OK;
247     case STATE_DOWNLOADING:
248       *aStatus = nsIDOMOfflineResourceList::DOWNLOADING;
249       return NS_OK;
250     default:
251       *aStatus = nsIDOMOfflineResourceList::IDLE;
252       return NS_OK;
253   }
254 
255   return NS_ERROR_FAILURE;
256 }
257 
258 NS_IMETHODIMP
GetPartial(bool * aPartial)259 OfflineCacheUpdateChild::GetPartial(bool *aPartial) {
260   *aPartial = false;
261   return NS_OK;
262 }
263 
264 NS_IMETHODIMP
GetManifestURI(nsIURI ** aManifestURI)265 OfflineCacheUpdateChild::GetManifestURI(nsIURI **aManifestURI) {
266   NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
267 
268   NS_IF_ADDREF(*aManifestURI = mManifestURI);
269   return NS_OK;
270 }
271 
272 NS_IMETHODIMP
GetSucceeded(bool * aSucceeded)273 OfflineCacheUpdateChild::GetSucceeded(bool *aSucceeded) {
274   NS_ENSURE_TRUE(mState == STATE_FINISHED, NS_ERROR_NOT_AVAILABLE);
275 
276   *aSucceeded = mSucceeded;
277 
278   return NS_OK;
279 }
280 
281 NS_IMETHODIMP
GetIsUpgrade(bool * aIsUpgrade)282 OfflineCacheUpdateChild::GetIsUpgrade(bool *aIsUpgrade) {
283   NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
284 
285   *aIsUpgrade = mIsUpgrade;
286 
287   return NS_OK;
288 }
289 
290 NS_IMETHODIMP
AddDynamicURI(nsIURI * aURI)291 OfflineCacheUpdateChild::AddDynamicURI(nsIURI *aURI) {
292   return NS_ERROR_NOT_IMPLEMENTED;
293 }
294 
295 NS_IMETHODIMP
Cancel()296 OfflineCacheUpdateChild::Cancel() { return NS_ERROR_NOT_IMPLEMENTED; }
297 
298 NS_IMETHODIMP
AddObserver(nsIOfflineCacheUpdateObserver * aObserver,bool aHoldWeak)299 OfflineCacheUpdateChild::AddObserver(nsIOfflineCacheUpdateObserver *aObserver,
300                                      bool aHoldWeak) {
301   LOG(("OfflineCacheUpdateChild::AddObserver [%p]", this));
302 
303   NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
304 
305   if (aHoldWeak) {
306     nsCOMPtr<nsIWeakReference> weakRef = do_GetWeakReference(aObserver);
307     mWeakObservers.AppendObject(weakRef);
308   } else {
309     mObservers.AppendObject(aObserver);
310   }
311 
312   return NS_OK;
313 }
314 
315 NS_IMETHODIMP
RemoveObserver(nsIOfflineCacheUpdateObserver * aObserver)316 OfflineCacheUpdateChild::RemoveObserver(
317     nsIOfflineCacheUpdateObserver *aObserver) {
318   LOG(("OfflineCacheUpdateChild::RemoveObserver [%p]", this));
319 
320   NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
321 
322   for (int32_t i = 0; i < mWeakObservers.Count(); i++) {
323     nsCOMPtr<nsIOfflineCacheUpdateObserver> observer =
324         do_QueryReferent(mWeakObservers[i]);
325     if (observer == aObserver) {
326       mWeakObservers.RemoveObjectAt(i);
327       return NS_OK;
328     }
329   }
330 
331   for (int32_t i = 0; i < mObservers.Count(); i++) {
332     if (mObservers[i] == aObserver) {
333       mObservers.RemoveObjectAt(i);
334       return NS_OK;
335     }
336   }
337 
338   return NS_OK;
339 }
340 
341 NS_IMETHODIMP
GetByteProgress(uint64_t * _result)342 OfflineCacheUpdateChild::GetByteProgress(uint64_t *_result) {
343   NS_ENSURE_ARG(_result);
344 
345   *_result = mByteProgress;
346   return NS_OK;
347 }
348 
349 NS_IMETHODIMP
Schedule()350 OfflineCacheUpdateChild::Schedule() {
351   LOG(("OfflineCacheUpdateChild::Schedule [%p]", this));
352 
353   NS_ASSERTION(mWindow,
354                "Window must be provided to the offline cache update child");
355 
356   nsCOMPtr<nsPIDOMWindowInner> window = mWindow.forget();
357   nsCOMPtr<nsIDocShell> docshell = window->GetDocShell();
358   if (!docshell) {
359     NS_WARNING("doc shell tree item is null");
360     return NS_ERROR_FAILURE;
361   }
362 
363   nsCOMPtr<nsITabChild> tabchild = docshell->GetTabChild();
364   // because owner implements nsITabChild, we can assume that it is
365   // the one and only TabChild.
366   TabChild *child =
367       tabchild ? static_cast<TabChild *>(tabchild.get()) : nullptr;
368 
369   if (MissingRequiredTabChild(child, "offlinecacheupdate")) {
370     return NS_ERROR_FAILURE;
371   }
372 
373   URIParams manifestURI, documentURI;
374   SerializeURI(mManifestURI, manifestURI);
375   SerializeURI(mDocumentURI, documentURI);
376 
377   nsresult rv = NS_OK;
378   PrincipalInfo loadingPrincipalInfo;
379   rv = PrincipalToPrincipalInfo(mLoadingPrincipal, &loadingPrincipalInfo);
380   NS_ENSURE_SUCCESS(rv, rv);
381 
382   nsCOMPtr<nsIObserverService> observerService =
383       mozilla::services::GetObserverService();
384   if (observerService) {
385     LOG(("Calling offline-cache-update-added"));
386     observerService->NotifyObservers(static_cast<nsIOfflineCacheUpdate *>(this),
387                                      "offline-cache-update-added", nullptr);
388     LOG(("Done offline-cache-update-added"));
389   }
390 
391   // mDocument is non-null if both:
392   // 1. this update was initiated by a document that referred a manifest
393   // 2. the document has not already been loaded from the application cache
394   // This tells the update to cache this document even in case the manifest
395   // has not been changed since the last fetch.
396   // See also nsOfflineCacheUpdate::ScheduleImplicit.
397   bool stickDocument = mDocument != nullptr;
398 
399   // Need to addref ourself here, because the IPC stack doesn't hold
400   // a reference to us. Will be released in RecvFinish() that identifies
401   // the work has been done.
402   ContentChild::GetSingleton()->SendPOfflineCacheUpdateConstructor(
403       this, manifestURI, documentURI, loadingPrincipalInfo, stickDocument);
404 
405   // ContentChild::DeallocPOfflineCacheUpdate will release this.
406   NS_ADDREF_THIS();
407 
408   return NS_OK;
409 }
410 
RecvAssociateDocuments(const nsCString & cacheGroupId,const nsCString & cacheClientId)411 mozilla::ipc::IPCResult OfflineCacheUpdateChild::RecvAssociateDocuments(
412     const nsCString &cacheGroupId, const nsCString &cacheClientId) {
413   LOG(("OfflineCacheUpdateChild::RecvAssociateDocuments [%p, cache=%s]", this,
414        cacheClientId.get()));
415 
416   nsresult rv;
417 
418   nsCOMPtr<nsIApplicationCache> cache =
419       do_CreateInstance(NS_APPLICATIONCACHE_CONTRACTID, &rv);
420   if (NS_FAILED(rv)) return IPC_OK();
421 
422   cache->InitAsHandle(cacheGroupId, cacheClientId);
423 
424   if (mDocument) {
425     AssociateDocument(mDocument, cache);
426   }
427 
428   nsCOMArray<nsIOfflineCacheUpdateObserver> observers;
429   GatherObservers(observers);
430 
431   for (int32_t i = 0; i < observers.Count(); i++)
432     observers[i]->ApplicationCacheAvailable(cache);
433 
434   return IPC_OK();
435 }
436 
RecvNotifyStateEvent(const uint32_t & event,const uint64_t & byteProgress)437 mozilla::ipc::IPCResult OfflineCacheUpdateChild::RecvNotifyStateEvent(
438     const uint32_t &event, const uint64_t &byteProgress) {
439   LOG(("OfflineCacheUpdateChild::RecvNotifyStateEvent [%p]", this));
440 
441   mByteProgress = byteProgress;
442 
443   // Convert the public observer state to our internal state
444   switch (event) {
445     case nsIOfflineCacheUpdateObserver::STATE_CHECKING:
446       mState = STATE_CHECKING;
447       break;
448 
449     case nsIOfflineCacheUpdateObserver::STATE_DOWNLOADING:
450       mState = STATE_DOWNLOADING;
451       break;
452 
453     default:
454       break;
455   }
456 
457   nsCOMArray<nsIOfflineCacheUpdateObserver> observers;
458   GatherObservers(observers);
459 
460   for (int32_t i = 0; i < observers.Count(); i++)
461     observers[i]->UpdateStateChanged(this, event);
462 
463   return IPC_OK();
464 }
465 
RecvFinish(const bool & succeeded,const bool & isUpgrade)466 mozilla::ipc::IPCResult OfflineCacheUpdateChild::RecvFinish(
467     const bool &succeeded, const bool &isUpgrade) {
468   LOG(("OfflineCacheUpdateChild::RecvFinish [%p]", this));
469 
470   RefPtr<OfflineCacheUpdateChild> kungFuDeathGrip(this);
471 
472   mState = STATE_FINISHED;
473   mSucceeded = succeeded;
474   mIsUpgrade = isUpgrade;
475 
476   nsCOMPtr<nsIObserverService> observerService =
477       mozilla::services::GetObserverService();
478   if (observerService) {
479     LOG(("Calling offline-cache-update-completed"));
480     observerService->NotifyObservers(static_cast<nsIOfflineCacheUpdate *>(this),
481                                      "offline-cache-update-completed", nullptr);
482     LOG(("Done offline-cache-update-completed"));
483   }
484 
485   // This is by contract the last notification from the parent, release
486   // us now. This is corresponding to AddRef in Schedule().
487   // TabChild::DeallocPOfflineCacheUpdate will call Release.
488   OfflineCacheUpdateChild::Send__delete__(this);
489 
490   return IPC_OK();
491 }
492 
493 }  // namespace docshell
494 }  // namespace mozilla
495