1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "content/browser/appcache/appcache_host.h"
6 
7 #include <stdint.h>
8 
9 #include <map>
10 #include <memory>
11 #include <string>
12 #include <utility>
13 
14 #include "base/bind.h"
15 #include "base/callback_helpers.h"
16 #include "base/test/task_environment.h"
17 #include "content/browser/appcache/appcache.h"
18 #include "content/browser/appcache/appcache_backend_impl.h"
19 #include "content/browser/appcache/appcache_group.h"
20 #include "content/browser/appcache/appcache_request_handler.h"
21 #include "content/browser/appcache/mock_appcache_policy.h"
22 #include "content/browser/appcache/mock_appcache_service.h"
23 #include "content/browser/child_process_security_policy_impl.h"
24 #include "content/browser/isolation_context.h"
25 #include "content/public/test/test_browser_context.h"
26 #include "content/public/test/test_renderer_host.h"
27 #include "content/test/test_web_contents.h"
28 #include "mojo/public/cpp/bindings/remote.h"
29 #include "mojo/public/cpp/test_support/test_utils.h"
30 #include "storage/browser/quota/quota_client_type.h"
31 #include "storage/browser/quota/quota_manager.h"
32 #include "testing/gtest/include/gtest/gtest.h"
33 #include "third_party/blink/public/mojom/appcache/appcache.mojom.h"
34 #include "third_party/blink/public/mojom/appcache/appcache_info.mojom.h"
35 #include "third_party/blink/public/mojom/devtools/console_message.mojom.h"
36 #include "url/origin.h"
37 
38 namespace content {
39 
40 namespace {
41 const int64_t kUnsetCacheId = -222;
42 }
43 
44 class AppCacheHostTest : public testing::Test {
45  public:
AppCacheHostTest()46   AppCacheHostTest()
47       : web_contents_(TestWebContents::Create(&browser_context_, nullptr)),
48         kProcessIdForTest(web_contents_->GetMainFrame()->GetProcess()->GetID()),
49         kRenderFrameIdForTest(web_contents_->GetMainFrame()->GetRoutingID()),
50         mock_frontend_(web_contents_.get()) {
51     get_status_callback_ = base::BindRepeating(
52         &AppCacheHostTest::GetStatusCallback, base::Unretained(this));
53     AppCacheRequestHandler::SetRunningInTests(true);
54   }
55 
~AppCacheHostTest()56   ~AppCacheHostTest() override {
57     AppCacheRequestHandler::SetRunningInTests(false);
58   }
59 
60   class MockFrontend : public blink::mojom::AppCacheFrontend,
61                        public WebContentsObserver {
62    public:
MockFrontend(WebContents * web_contents)63     explicit MockFrontend(WebContents* web_contents)
64         : WebContentsObserver(web_contents),
65           last_cache_id_(kUnsetCacheId),
66           last_status_(blink::mojom::AppCacheStatus::APPCACHE_STATUS_OBSOLETE),
67           last_event_id_(
68               blink::mojom::AppCacheEventID::APPCACHE_OBSOLETE_EVENT),
69           content_blocked_(false) {}
70 
CacheSelected(blink::mojom::AppCacheInfoPtr info)71     void CacheSelected(blink::mojom::AppCacheInfoPtr info) override {
72       last_cache_id_ = info->cache_id;
73       last_status_ = info->status;
74     }
75 
EventRaised(blink::mojom::AppCacheEventID event_id)76     void EventRaised(blink::mojom::AppCacheEventID event_id) override {
77       last_event_id_ = event_id;
78     }
79 
ErrorEventRaised(blink::mojom::AppCacheErrorDetailsPtr details)80     void ErrorEventRaised(
81         blink::mojom::AppCacheErrorDetailsPtr details) override {
82       last_event_id_ = blink::mojom::AppCacheEventID::APPCACHE_ERROR_EVENT;
83     }
84 
ProgressEventRaised(const GURL & url,int32_t num_total,int32_t num_complete)85     void ProgressEventRaised(const GURL& url,
86                              int32_t num_total,
87                              int32_t num_complete) override {
88       last_event_id_ = blink::mojom::AppCacheEventID::APPCACHE_PROGRESS_EVENT;
89     }
90 
LogMessage(blink::mojom::ConsoleMessageLevel log_level,const std::string & message)91     void LogMessage(blink::mojom::ConsoleMessageLevel log_level,
92                     const std::string& message) override {}
93 
SetSubresourceFactory(mojo::PendingRemote<network::mojom::URLLoaderFactory> url_loader_factory)94     void SetSubresourceFactory(
95         mojo::PendingRemote<network::mojom::URLLoaderFactory>
96             url_loader_factory) override {}
97 
98     // WebContentsObserver:
AppCacheAccessed(const GURL & manifest_url,bool blocked_by_policy)99     void AppCacheAccessed(const GURL& manifest_url,
100                           bool blocked_by_policy) override {
101       appcache_accessed_ = true;
102       if (blocked_by_policy)
103         content_blocked_ = true;
104     }
105 
106     int64_t last_cache_id_;
107     blink::mojom::AppCacheStatus last_status_;
108     blink::mojom::AppCacheEventID last_event_id_;
109     bool content_blocked_;
110     bool appcache_accessed_ = false;
111   };
112 
113   class MockQuotaManagerProxy : public storage::QuotaManagerProxy {
114    public:
MockQuotaManagerProxy()115     MockQuotaManagerProxy() : QuotaManagerProxy(nullptr, nullptr) {}
116 
117     // Not needed for our tests.
RegisterClient(scoped_refptr<storage::QuotaClient> client,storage::QuotaClientType client_type,const std::vector<blink::mojom::StorageType> & storage_types)118     void RegisterClient(
119         scoped_refptr<storage::QuotaClient> client,
120         storage::QuotaClientType client_type,
121         const std::vector<blink::mojom::StorageType>& storage_types) override {}
NotifyStorageAccessed(const url::Origin & origin,blink::mojom::StorageType type)122     void NotifyStorageAccessed(const url::Origin& origin,
123                                blink::mojom::StorageType type) override {}
NotifyStorageModified(storage::QuotaClientType client_id,const url::Origin & origin,blink::mojom::StorageType type,int64_t delta)124     void NotifyStorageModified(storage::QuotaClientType client_id,
125                                const url::Origin& origin,
126                                blink::mojom::StorageType type,
127                                int64_t delta) override {}
SetUsageCacheEnabled(storage::QuotaClientType client_id,const url::Origin & origin,blink::mojom::StorageType type,bool enabled)128     void SetUsageCacheEnabled(storage::QuotaClientType client_id,
129                               const url::Origin& origin,
130                               blink::mojom::StorageType type,
131                               bool enabled) override {}
GetUsageAndQuota(base::SequencedTaskRunner * original_task_runner,const url::Origin & origin,blink::mojom::StorageType type,UsageAndQuotaCallback callback)132     void GetUsageAndQuota(base::SequencedTaskRunner* original_task_runner,
133                           const url::Origin& origin,
134                           blink::mojom::StorageType type,
135                           UsageAndQuotaCallback callback) override {}
136 
NotifyOriginInUse(const url::Origin & origin)137     void NotifyOriginInUse(const url::Origin& origin) override {
138       inuse_[origin] += 1;
139     }
140 
NotifyOriginNoLongerInUse(const url::Origin & origin)141     void NotifyOriginNoLongerInUse(const url::Origin& origin) override {
142       inuse_[origin] -= 1;
143     }
144 
GetInUseCount(const url::Origin & origin)145     int GetInUseCount(const url::Origin& origin) { return inuse_[origin]; }
146 
is_empty() const147     bool is_empty() const { return inuse_.empty(); }
reset()148     void reset() { inuse_.clear(); }
149 
150     // Map from origin to count of inuse notifications.
151     std::map<url::Origin, int> inuse_;
152 
153    protected:
154     ~MockQuotaManagerProxy() override = default;
155   };
156 
GetStatusCallback(blink::mojom::AppCacheStatus status)157   void GetStatusCallback(blink::mojom::AppCacheStatus status) {
158     last_status_result_ = status;
159   }
160 
StartUpdateCallback(bool result)161   void StartUpdateCallback(bool result) { last_start_result_ = result; }
162 
SwapCacheCallback(bool result)163   void SwapCacheCallback(bool result) { last_swap_result_ = result; }
164 
LockProcessToURL(const GURL & url)165   void LockProcessToURL(const GURL& url) {
166     ChildProcessSecurityPolicyImpl::GetInstance()->LockProcessForTesting(
167         web_contents_->GetMainFrame()->GetSiteInstance()->GetIsolationContext(),
168         kProcessIdForTest, url);
169   }
170 
171   BrowserTaskEnvironment task_environment_;
172   RenderViewHostTestEnabler rvh_enabler_;
173   TestBrowserContext browser_context_;
174   std::unique_ptr<TestWebContents> web_contents_;
175 
176   const int kProcessIdForTest;
177   const int kRenderFrameIdForTest;
178   const base::UnguessableToken kHostIdForTest =
179       base::UnguessableToken::Create();
180 
181   // Mock classes for the 'host' to work with
182   MockAppCacheService service_;
183   MockFrontend mock_frontend_;
184 
185   // Mock callbacks we expect to receive from the 'host'
186   blink::mojom::AppCacheHost::GetStatusCallback get_status_callback_;
187 
188   blink::mojom::AppCacheStatus last_status_result_;
189   bool last_swap_result_;
190   bool last_start_result_;
191 };
192 
TEST_F(AppCacheHostTest,Basic)193 TEST_F(AppCacheHostTest, Basic) {
194   // Construct a host and test what state it appears to be in.
195   AppCacheHost host(kHostIdForTest, kProcessIdForTest, kRenderFrameIdForTest,
196                     ChildProcessSecurityPolicyImpl::GetInstance()->CreateHandle(
197                         kProcessIdForTest),
198                     mojo::NullRemote(), &service_);
199   host.set_frontend_for_testing(&mock_frontend_);
200   EXPECT_EQ(kHostIdForTest, host.host_id());
201   EXPECT_EQ(kProcessIdForTest, host.process_id());
202   EXPECT_TRUE(host.security_policy_handle());
203   EXPECT_TRUE(host.security_policy_handle()->is_valid());
204   EXPECT_EQ(&service_, host.service());
205   EXPECT_EQ(nullptr, host.associated_cache());
206   EXPECT_FALSE(host.is_selection_pending());
207 
208   // See that the callbacks are delivered immediately
209   // and respond as if there is no cache selected.
210   last_status_result_ = blink::mojom::AppCacheStatus::APPCACHE_STATUS_OBSOLETE;
211   host.GetStatus(std::move(get_status_callback_));
212   EXPECT_EQ(blink::mojom::AppCacheStatus::APPCACHE_STATUS_UNCACHED,
213             last_status_result_);
214 
215   last_start_result_ = true;
216   host.StartUpdate(base::BindOnce(&AppCacheHostTest::StartUpdateCallback,
217                                   base::Unretained(this)));
218   EXPECT_FALSE(last_start_result_);
219 
220   last_swap_result_ = true;
221   host.SwapCache(base::BindOnce(&AppCacheHostTest::SwapCacheCallback,
222                                 base::Unretained(this)));
223   EXPECT_FALSE(last_swap_result_);
224 }
225 
TEST_F(AppCacheHostTest,SelectNoCache)226 TEST_F(AppCacheHostTest, SelectNoCache) {
227   // Lock process with |kInitialDocumentURL| so we can only accept URLs that
228   // generate the same lock as |kInitialDocumentURL|.
229   const GURL kInitialDocumentURL("http://whatever/document");
230   LockProcessToURL(kInitialDocumentURL);
231 
232   const std::vector<GURL> kDocumentURLs = {
233       GURL("http://whatever/"),
234       GURL("blob:http://whatever/6f7dc725-2131-4f8b-85ed-4f43d175324e"),
235       GURL("about:blank"), GURL("about:srcdoc"),
236       GURL("blob:null/6f7dc725-2131-4f8b-85ed-4f43d175324e")};
237   for (const GURL& document_url : kDocumentURLs) {
238     scoped_refptr<MockQuotaManagerProxy> mock_quota_proxy =
239         base::MakeRefCounted<MockQuotaManagerProxy>();
240     service_.set_quota_manager_proxy(mock_quota_proxy.get());
241 
242     // Reset our mock frontend
243     mock_frontend_.last_cache_id_ = -333;
244     mock_frontend_.last_status_ =
245         blink::mojom::AppCacheStatus::APPCACHE_STATUS_OBSOLETE;
246 
247     const url::Origin kOrigin(url::Origin::Create(document_url));
248     {
249       AppCacheHost host(
250           kHostIdForTest, kProcessIdForTest, kRenderFrameIdForTest,
251           ChildProcessSecurityPolicyImpl::GetInstance()->CreateHandle(
252               kProcessIdForTest),
253           mojo::NullRemote(), &service_);
254       host.set_frontend_for_testing(&mock_frontend_);
255 
256       {
257         mojo::test::BadMessageObserver bad_message_observer;
258         host.SelectCache(document_url, blink::mojom::kAppCacheNoCacheId,
259                          GURL());
260 
261         base::RunLoop().RunUntilIdle();
262         EXPECT_FALSE(bad_message_observer.got_bad_message());
263       }
264 
265       if (kOrigin.opaque()) {
266         EXPECT_TRUE(mock_quota_proxy->is_empty());
267       } else {
268         EXPECT_EQ(1, mock_quota_proxy->GetInUseCount(kOrigin))
269             << " document_url " << document_url;
270       }
271 
272       // We should have received an OnCacheSelected msg
273       EXPECT_EQ(blink::mojom::kAppCacheNoCacheId,
274                 mock_frontend_.last_cache_id_);
275       EXPECT_EQ(blink::mojom::AppCacheStatus::APPCACHE_STATUS_UNCACHED,
276                 mock_frontend_.last_status_);
277 
278       // Otherwise, see that it respond as if there is no cache selected.
279       EXPECT_EQ(kHostIdForTest, host.host_id());
280       EXPECT_EQ(&service_, host.service());
281       EXPECT_EQ(nullptr, host.associated_cache());
282       EXPECT_FALSE(host.is_selection_pending());
283       EXPECT_TRUE(host.preferred_manifest_url().is_empty());
284     }
285     EXPECT_EQ(0, mock_quota_proxy->GetInUseCount(kOrigin));
286     service_.set_quota_manager_proxy(nullptr);
287   }
288 }
289 
TEST_F(AppCacheHostTest,ForeignEntry)290 TEST_F(AppCacheHostTest, ForeignEntry) {
291   // Reset our mock frontend
292   mock_frontend_.last_cache_id_ = -333;
293   mock_frontend_.last_status_ =
294       blink::mojom::AppCacheStatus::APPCACHE_STATUS_OBSOLETE;
295 
296   // Precondition, a cache with an entry that is not marked as foreign.
297   const int kCacheId = 22;
298   const GURL kDocumentURL("http://origin/document");
299   auto cache = base::MakeRefCounted<AppCache>(service_.storage(), kCacheId);
300   cache->AddEntry(kDocumentURL, AppCacheEntry(AppCacheEntry::EXPLICIT));
301 
302   AppCacheHost host(kHostIdForTest, kProcessIdForTest, kRenderFrameIdForTest,
303                     ChildProcessSecurityPolicyImpl::GetInstance()->CreateHandle(
304                         kProcessIdForTest),
305                     mojo::NullRemote(), &service_);
306   host.set_frontend_for_testing(&mock_frontend_);
307   host.MarkAsForeignEntry(kDocumentURL, kCacheId);
308 
309   // We should have received an OnCacheSelected msg for kAppCacheNoCacheId.
310   EXPECT_EQ(blink::mojom::kAppCacheNoCacheId, mock_frontend_.last_cache_id_);
311   EXPECT_EQ(blink::mojom::AppCacheStatus::APPCACHE_STATUS_UNCACHED,
312             mock_frontend_.last_status_);
313 
314   // See that it respond as if there is no cache selected.
315   EXPECT_EQ(kHostIdForTest, host.host_id());
316   EXPECT_EQ(&service_, host.service());
317   EXPECT_EQ(nullptr, host.associated_cache());
318   EXPECT_FALSE(host.is_selection_pending());
319 
320   // See that the entry was marked as foreign.
321   EXPECT_TRUE(cache->GetEntry(kDocumentURL)->IsForeign());
322 }
323 
TEST_F(AppCacheHostTest,ForeignFallbackEntry)324 TEST_F(AppCacheHostTest, ForeignFallbackEntry) {
325   // Reset our mock frontend
326   mock_frontend_.last_cache_id_ = -333;
327   mock_frontend_.last_status_ =
328       blink::mojom::AppCacheStatus::APPCACHE_STATUS_OBSOLETE;
329 
330   // Precondition, a cache with a fallback entry that is not marked as foreign.
331   const int kCacheId = 22;
332   const GURL kFallbackURL("http://origin/fallback_resource");
333   scoped_refptr<AppCache> cache =
334       base::MakeRefCounted<AppCache>(service_.storage(), kCacheId);
335   cache->AddEntry(kFallbackURL, AppCacheEntry(AppCacheEntry::FALLBACK));
336 
337   AppCacheHost host(kHostIdForTest, kProcessIdForTest, kRenderFrameIdForTest,
338                     ChildProcessSecurityPolicyImpl::GetInstance()->CreateHandle(
339                         kProcessIdForTest),
340                     mojo::NullRemote(), &service_);
341   host.set_frontend_for_testing(&mock_frontend_);
342   host.NotifyMainResourceIsNamespaceEntry(kFallbackURL);
343   host.MarkAsForeignEntry(GURL("http://origin/missing_document"), kCacheId);
344 
345   // We should have received an OnCacheSelected msg for kAppCacheNoCacheId.
346   EXPECT_EQ(blink::mojom::kAppCacheNoCacheId, mock_frontend_.last_cache_id_);
347   EXPECT_EQ(blink::mojom::AppCacheStatus::APPCACHE_STATUS_UNCACHED,
348             mock_frontend_.last_status_);
349 
350   // See that the fallback entry was marked as foreign.
351   EXPECT_TRUE(cache->GetEntry(kFallbackURL)->IsForeign());
352 }
353 
TEST_F(AppCacheHostTest,FailedCacheLoad)354 TEST_F(AppCacheHostTest, FailedCacheLoad) {
355   // Reset our mock frontend
356   mock_frontend_.last_cache_id_ = -333;
357   mock_frontend_.last_status_ =
358       blink::mojom::AppCacheStatus::APPCACHE_STATUS_OBSOLETE;
359 
360   AppCacheHost host(kHostIdForTest, kProcessIdForTest, kRenderFrameIdForTest,
361                     ChildProcessSecurityPolicyImpl::GetInstance()->CreateHandle(
362                         kProcessIdForTest),
363                     mojo::NullRemote(), &service_);
364   host.set_frontend_for_testing(&mock_frontend_);
365   EXPECT_FALSE(host.is_selection_pending());
366 
367   const int kMockCacheId = 333;
368 
369   // Put it in a state where we're waiting on a cache
370   // load prior to finishing cache selection.
371   host.pending_selected_cache_id_ = kMockCacheId;
372   EXPECT_TRUE(host.is_selection_pending());
373 
374   // The callback should not occur until we finish cache selection.
375   last_status_result_ = blink::mojom::AppCacheStatus::APPCACHE_STATUS_OBSOLETE;
376   host.GetStatus(std::move(get_status_callback_));
377   EXPECT_EQ(blink::mojom::AppCacheStatus::APPCACHE_STATUS_OBSOLETE,
378             last_status_result_);
379 
380   // Satisfy the load with NULL, a failure.
381   host.OnCacheLoaded(nullptr, kMockCacheId);
382 
383   // Cache selection should have finished
384   EXPECT_FALSE(host.is_selection_pending());
385   EXPECT_EQ(blink::mojom::kAppCacheNoCacheId, mock_frontend_.last_cache_id_);
386   EXPECT_EQ(blink::mojom::AppCacheStatus::APPCACHE_STATUS_UNCACHED,
387             mock_frontend_.last_status_);
388 
389   // Callback should have fired upon completing the cache load too.
390   EXPECT_EQ(blink::mojom::AppCacheStatus::APPCACHE_STATUS_UNCACHED,
391             last_status_result_);
392 }
393 
TEST_F(AppCacheHostTest,FailedGroupLoad)394 TEST_F(AppCacheHostTest, FailedGroupLoad) {
395   AppCacheHost host(kHostIdForTest, kProcessIdForTest, kRenderFrameIdForTest,
396                     ChildProcessSecurityPolicyImpl::GetInstance()->CreateHandle(
397                         kProcessIdForTest),
398                     mojo::NullRemote(), &service_);
399   host.set_frontend_for_testing(&mock_frontend_);
400 
401   const GURL kMockManifestUrl("http://foo.bar/baz");
402 
403   // Put it in a state where we're waiting on a cache
404   // load prior to finishing cache selection.
405   host.pending_selected_manifest_url_ = kMockManifestUrl;
406   EXPECT_TRUE(host.is_selection_pending());
407 
408   // The callback should not occur until we finish cache selection.
409   last_status_result_ = blink::mojom::AppCacheStatus::APPCACHE_STATUS_OBSOLETE;
410   host.GetStatus(std::move(get_status_callback_));
411   EXPECT_EQ(blink::mojom::AppCacheStatus::APPCACHE_STATUS_OBSOLETE,
412             last_status_result_);
413 
414   // Satisfy the load will NULL, a failure.
415   host.OnGroupLoaded(nullptr, kMockManifestUrl);
416 
417   // Cache selection should have finished
418   EXPECT_FALSE(host.is_selection_pending());
419   EXPECT_EQ(blink::mojom::kAppCacheNoCacheId, mock_frontend_.last_cache_id_);
420   EXPECT_EQ(blink::mojom::AppCacheStatus::APPCACHE_STATUS_UNCACHED,
421             mock_frontend_.last_status_);
422 
423   // Callback should have fired upon completing the group load.
424   EXPECT_EQ(blink::mojom::AppCacheStatus::APPCACHE_STATUS_UNCACHED,
425             last_status_result_);
426 }
427 
TEST_F(AppCacheHostTest,SetSwappableCache)428 TEST_F(AppCacheHostTest, SetSwappableCache) {
429   AppCacheHost host(kHostIdForTest, kProcessIdForTest, kRenderFrameIdForTest,
430                     ChildProcessSecurityPolicyImpl::GetInstance()->CreateHandle(
431                         kProcessIdForTest),
432                     mojo::NullRemote(), &service_);
433   host.set_frontend_for_testing(&mock_frontend_);
434   host.SetSwappableCache(nullptr);
435   EXPECT_FALSE(host.swappable_cache_.get());
436 
437   const GURL kGroup1ManifestUrl("http://bar.com");
438   scoped_refptr<AppCacheGroup> group1 = base::MakeRefCounted<AppCacheGroup>(
439       service_.storage(), kGroup1ManifestUrl, service_.storage()->NewGroupId());
440   host.SetSwappableCache(group1.get());
441   EXPECT_FALSE(host.swappable_cache_.get());
442 
443   scoped_refptr<AppCache> cache1 =
444       base::MakeRefCounted<AppCache>(service_.storage(), 111);
445   cache1->set_complete(true);
446   group1->AddCache(cache1.get());
447   host.SetSwappableCache(group1.get());
448   EXPECT_EQ(cache1, host.swappable_cache_.get());
449 
450   mock_frontend_.last_cache_id_ =
451       kUnsetCacheId;  // to verify we received OnCacheSelected
452 
453   host.AssociateCompleteCache(cache1.get());
454   EXPECT_FALSE(host.swappable_cache_.get());  // was same as associated cache
455   EXPECT_EQ(blink::mojom::AppCacheStatus::APPCACHE_STATUS_IDLE,
456             host.GetStatusSync());
457   // verify OnCacheSelected was called
458   EXPECT_EQ(cache1->cache_id(), mock_frontend_.last_cache_id_);
459   EXPECT_EQ(blink::mojom::AppCacheStatus::APPCACHE_STATUS_IDLE,
460             mock_frontend_.last_status_);
461 
462   scoped_refptr<AppCache> cache2 =
463       base::MakeRefCounted<AppCache>(service_.storage(), 222);
464   cache2->set_complete(true);
465   group1->AddCache(cache2.get());
466   EXPECT_EQ(cache2.get(), host.swappable_cache_.get());  // updated to newest
467 
468   const GURL kGroup2ManifestUrl("http://foo.com/");
469   scoped_refptr<AppCacheGroup> group2 = base::MakeRefCounted<AppCacheGroup>(
470       service_.storage(), kGroup2ManifestUrl, service_.storage()->NewGroupId());
471   scoped_refptr<AppCache> cache3 =
472       base::MakeRefCounted<AppCache>(service_.storage(), 333);
473   cache3->set_complete(true);
474   group2->AddCache(cache3.get());
475 
476   scoped_refptr<AppCache> cache4 =
477       base::MakeRefCounted<AppCache>(service_.storage(), 444);
478   cache4->set_complete(true);
479   group2->AddCache(cache4.get());
480   EXPECT_EQ(cache2.get(), host.swappable_cache_.get());  // unchanged
481 
482   cache1.reset();
483   cache2.reset();
484 
485   host.AssociateCompleteCache(cache3.get());
486   EXPECT_EQ(cache4.get(),
487             host.swappable_cache_.get());  // newest cache in group2
488   EXPECT_FALSE(group1->HasCache());  // both caches in group1 have refcount 0
489 
490   cache3.reset();
491   cache4.reset();
492 
493   host.AssociateNoCache(kGroup1ManifestUrl);
494   EXPECT_FALSE(host.swappable_cache_.get());
495   EXPECT_FALSE(group2->HasCache());  // both caches in group2 have refcount 0
496 
497   // Host adds reference to newest cache when an update is complete.
498   scoped_refptr<AppCache> cache5 =
499       base::MakeRefCounted<AppCache>(service_.storage(), 555);
500   cache5->set_complete(true);
501   group2->AddCache(cache5.get());
502   host.group_being_updated_ = group2;
503   host.OnUpdateComplete(group2.get());
504   EXPECT_FALSE(host.group_being_updated_.get());
505   EXPECT_EQ(cache5.get(), host.swappable_cache_.get());
506 
507   group2->RemoveCache(cache5.get());
508   EXPECT_FALSE(group2->HasCache());
509   host.group_being_updated_ = group2;
510   host.OnUpdateComplete(group2.get());
511   EXPECT_FALSE(host.group_being_updated_.get());
512   EXPECT_FALSE(host.swappable_cache_.get());  // group2 had no newest cache
513 }
514 
TEST_F(AppCacheHostTest,SelectCacheAllowed)515 TEST_F(AppCacheHostTest, SelectCacheAllowed) {
516   scoped_refptr<MockQuotaManagerProxy> mock_quota_proxy =
517       base::MakeRefCounted<MockQuotaManagerProxy>();
518   MockAppCachePolicy mock_appcache_policy;
519   mock_appcache_policy.can_create_return_value_ = true;
520   service_.set_quota_manager_proxy(mock_quota_proxy.get());
521   service_.set_appcache_policy(&mock_appcache_policy);
522 
523   // Reset our mock frontend
524   mock_frontend_.last_cache_id_ = -333;
525   mock_frontend_.last_status_ =
526       blink::mojom::AppCacheStatus::APPCACHE_STATUS_OBSOLETE;
527   mock_frontend_.last_event_id_ =
528       blink::mojom::AppCacheEventID::APPCACHE_OBSOLETE_EVENT;
529   mock_frontend_.content_blocked_ = false;
530   mock_frontend_.appcache_accessed_ = false;
531 
532   const GURL kDocAndOriginUrl("http://whatever/");
533   const url::Origin kOrigin(url::Origin::Create(kDocAndOriginUrl));
534   const GURL kManifestUrl("http://whatever/cache.manifest");
535   {
536     AppCacheHost host(
537         kHostIdForTest, kProcessIdForTest, kRenderFrameIdForTest,
538         ChildProcessSecurityPolicyImpl::GetInstance()->CreateHandle(
539             kProcessIdForTest),
540         mojo::NullRemote(), &service_);
541     host.set_frontend_for_testing(&mock_frontend_);
542     host.SetSiteForCookiesForTesting(
543         net::SiteForCookies::FromUrl(kDocAndOriginUrl));
544     host.SelectCache(kDocAndOriginUrl, blink::mojom::kAppCacheNoCacheId,
545                      kManifestUrl);
546     EXPECT_EQ(1, mock_quota_proxy->GetInUseCount(kOrigin));
547 
548     // MockAppCacheService::LoadOrCreateGroup is asynchronous, so we shouldn't
549     // have received an OnCacheSelected msg yet.
550     EXPECT_EQ(-333, mock_frontend_.last_cache_id_);
551     EXPECT_EQ(blink::mojom::AppCacheStatus::APPCACHE_STATUS_OBSOLETE,
552               mock_frontend_.last_status_);
553     // No error events either
554     EXPECT_EQ(blink::mojom::AppCacheEventID::APPCACHE_OBSOLETE_EVENT,
555               mock_frontend_.last_event_id_);
556     EXPECT_FALSE(mock_frontend_.content_blocked_);
557 
558     EXPECT_TRUE(host.is_selection_pending());
559 
560     base::RunLoop().RunUntilIdle();
561     EXPECT_FALSE(mock_frontend_.content_blocked_);
562     EXPECT_TRUE(mock_frontend_.appcache_accessed_);
563   }
564   EXPECT_EQ(0, mock_quota_proxy->GetInUseCount(kOrigin));
565   service_.set_quota_manager_proxy(nullptr);
566 }
567 
TEST_F(AppCacheHostTest,SelectCacheBlocked)568 TEST_F(AppCacheHostTest, SelectCacheBlocked) {
569   scoped_refptr<MockQuotaManagerProxy> mock_quota_proxy =
570       base::MakeRefCounted<MockQuotaManagerProxy>();
571   MockAppCachePolicy mock_appcache_policy;
572   mock_appcache_policy.can_create_return_value_ = false;
573   service_.set_quota_manager_proxy(mock_quota_proxy.get());
574   service_.set_appcache_policy(&mock_appcache_policy);
575 
576   // Reset our mock frontend
577   mock_frontend_.last_cache_id_ = -333;
578   mock_frontend_.last_status_ =
579       blink::mojom::AppCacheStatus::APPCACHE_STATUS_OBSOLETE;
580   mock_frontend_.last_event_id_ =
581       blink::mojom::AppCacheEventID::APPCACHE_OBSOLETE_EVENT;
582   mock_frontend_.content_blocked_ = false;
583   mock_frontend_.appcache_accessed_ = false;
584 
585   const GURL kDocAndOriginUrl(GURL("http://whatever/").GetOrigin());
586   const url::Origin kOrigin(url::Origin::Create(kDocAndOriginUrl));
587   const GURL kManifestUrl(GURL("http://whatever/cache.manifest"));
588   {
589     AppCacheHost host(
590         kHostIdForTest, kProcessIdForTest, kRenderFrameIdForTest,
591         ChildProcessSecurityPolicyImpl::GetInstance()->CreateHandle(
592             kProcessIdForTest),
593         mojo::NullRemote(), &service_);
594     host.set_frontend_for_testing(&mock_frontend_);
595     host.SetSiteForCookiesForTesting(
596         net::SiteForCookies::FromUrl(kDocAndOriginUrl));
597     host.SelectCache(kDocAndOriginUrl, blink::mojom::kAppCacheNoCacheId,
598                      kManifestUrl);
599     EXPECT_EQ(1, mock_quota_proxy->GetInUseCount(kOrigin));
600 
601     // We should have received an OnCacheSelected msg
602     EXPECT_EQ(blink::mojom::kAppCacheNoCacheId, mock_frontend_.last_cache_id_);
603     EXPECT_EQ(blink::mojom::AppCacheStatus::APPCACHE_STATUS_UNCACHED,
604               mock_frontend_.last_status_);
605 
606     // Also, an error event was raised
607     EXPECT_EQ(blink::mojom::AppCacheEventID::APPCACHE_ERROR_EVENT,
608               mock_frontend_.last_event_id_);
609 
610     // Otherwise, see that it respond as if there is no cache selected.
611     EXPECT_EQ(kHostIdForTest, host.host_id());
612     EXPECT_EQ(&service_, host.service());
613     EXPECT_EQ(nullptr, host.associated_cache());
614     EXPECT_FALSE(host.is_selection_pending());
615     EXPECT_TRUE(host.preferred_manifest_url().is_empty());
616 
617     base::RunLoop().RunUntilIdle();
618     EXPECT_TRUE(mock_frontend_.content_blocked_);
619     EXPECT_TRUE(mock_frontend_.appcache_accessed_);
620   }
621   EXPECT_EQ(0, mock_quota_proxy->GetInUseCount(kOrigin));
622   service_.set_quota_manager_proxy(nullptr);
623 }
624 
TEST_F(AppCacheHostTest,SelectCacheTwice)625 TEST_F(AppCacheHostTest, SelectCacheTwice) {
626   const GURL kDocAndOriginUrl(GURL("http://whatever/").GetOrigin());
627   AppCacheHost host(kHostIdForTest, kProcessIdForTest, kRenderFrameIdForTest,
628                     ChildProcessSecurityPolicyImpl::GetInstance()->CreateHandle(
629                         kProcessIdForTest),
630                     mojo::NullRemote(), &service_);
631   host.set_frontend_for_testing(&mock_frontend_);
632   mojo::Remote<blink::mojom::AppCacheHost> host_remote;
633   host.BindReceiver(host_remote.BindNewPipeAndPassReceiver());
634 
635   {
636     mojo::test::BadMessageObserver bad_message_observer;
637     host_remote->SelectCache(kDocAndOriginUrl, blink::mojom::kAppCacheNoCacheId,
638                              GURL());
639 
640     base::RunLoop().RunUntilIdle();
641     EXPECT_FALSE(bad_message_observer.got_bad_message());
642   }
643 
644   // Select methods should bail if cache has already been selected.
645   {
646     mojo::test::BadMessageObserver bad_message_observer;
647     host_remote->SelectCache(kDocAndOriginUrl, blink::mojom::kAppCacheNoCacheId,
648                              GURL());
649     EXPECT_EQ("ACH_SELECT_CACHE", bad_message_observer.WaitForBadMessage());
650   }
651   {
652     mojo::test::BadMessageObserver bad_message_observer;
653     host_remote->SelectCacheForWorker(blink::mojom::kAppCacheNoCacheId);
654     EXPECT_EQ("ACH_SELECT_CACHE_FOR_WORKER",
655               bad_message_observer.WaitForBadMessage());
656   }
657   {
658     mojo::test::BadMessageObserver bad_message_observer;
659     host_remote->MarkAsForeignEntry(kDocAndOriginUrl,
660                                     blink::mojom::kAppCacheNoCacheId);
661     EXPECT_EQ("ACH_MARK_AS_FOREIGN_ENTRY",
662               bad_message_observer.WaitForBadMessage());
663   }
664 }
665 
TEST_F(AppCacheHostTest,SelectCacheInvalidCacheId)666 TEST_F(AppCacheHostTest, SelectCacheInvalidCacheId) {
667   const GURL kDocAndOriginUrl(GURL("http://whatever/").GetOrigin());
668 
669   // A cache that the document wasn't actually loaded from. Trying to select it
670   // should cause a BadMessage.
671   const int kCacheId = 22;
672   const GURL kDocumentURL("http://origin/document");
673   auto cache = base::MakeRefCounted<AppCache>(service_.storage(), kCacheId);
674   AppCacheHost host(kHostIdForTest, kProcessIdForTest, kRenderFrameIdForTest,
675                     ChildProcessSecurityPolicyImpl::GetInstance()->CreateHandle(
676                         kProcessIdForTest),
677                     mojo::NullRemote(), &service_);
678   host.set_frontend_for_testing(&mock_frontend_);
679   mojo::Remote<blink::mojom::AppCacheHost> host_remote;
680   host.BindReceiver(host_remote.BindNewPipeAndPassReceiver());
681 
682   {
683     mojo::test::BadMessageObserver bad_message_observer;
684     host_remote->SelectCache(kDocAndOriginUrl, kCacheId, GURL());
685 
686     EXPECT_EQ("ACH_SELECT_CACHE_ID_NOT_OWNED",
687               bad_message_observer.WaitForBadMessage());
688   }
689 }
690 
TEST_F(AppCacheHostTest,SelectCacheURLsForWrongSite)691 TEST_F(AppCacheHostTest, SelectCacheURLsForWrongSite) {
692   // Lock process with |kInitialDocumentURL| so we can only accept URLs that
693   // generate the same lock as |kInitialDocumentURL|.
694   const GURL kInitialDocumentURL("http://foo.com/document");
695   LockProcessToURL(kInitialDocumentURL);
696 
697   AppCacheHost host(kHostIdForTest, kProcessIdForTest, kRenderFrameIdForTest,
698                     ChildProcessSecurityPolicyImpl::GetInstance()->CreateHandle(
699                         kProcessIdForTest),
700                     mojo::NullRemote(), &service_);
701   host.set_frontend_for_testing(&mock_frontend_);
702   mojo::Remote<blink::mojom::AppCacheHost> host_remote;
703   host.BindReceiver(host_remote.BindNewPipeAndPassReceiver());
704 
705   // Verify that a document URL from the wrong site triggers a bad message.
706   {
707     const GURL kWrongSiteDocumentURL("http://whatever/");
708     mojo::test::BadMessageObserver bad_message_observer;
709     host_remote->SelectCache(kWrongSiteDocumentURL,
710                              blink::mojom::kAppCacheNoCacheId, GURL());
711 
712     EXPECT_EQ("ACH_SELECT_CACHE_DOCUMENT_URL_ACCESS_NOT_ALLOWED",
713               bad_message_observer.WaitForBadMessage());
714   }
715 
716   // Verify that a document URL with an inner hostname from the wrong site
717   // triggers a bad message.
718   {
719     const GURL kDocumentURL = kInitialDocumentURL;
720     mojo::test::BadMessageObserver bad_message_observer;
721     host_remote->SelectCache(
722         kDocumentURL, blink::mojom::kAppCacheNoCacheId,
723         GURL("blob:http://whatever/6f7dc725-2131-4f8b-85ed-4f43d175324e"));
724 
725     EXPECT_EQ("ACH_SELECT_CACHE_MANIFEST_URL_ACCESS_NOT_ALLOWED",
726               bad_message_observer.WaitForBadMessage());
727   }
728 
729   // Verify that a manifest URL from the wrong site triggers a bad message.
730   {
731     const GURL kDocumentURL = kInitialDocumentURL;
732     const GURL kManifestURL("http://whatever/");
733     mojo::test::BadMessageObserver bad_message_observer;
734     host_remote->SelectCache(kDocumentURL, blink::mojom::kAppCacheNoCacheId,
735                              kManifestURL);
736 
737     EXPECT_EQ("ACH_SELECT_CACHE_MANIFEST_URL_ACCESS_NOT_ALLOWED",
738               bad_message_observer.WaitForBadMessage());
739   }
740 }
741 
TEST_F(AppCacheHostTest,ForeignEntryForWrongSite)742 TEST_F(AppCacheHostTest, ForeignEntryForWrongSite) {
743   // Lock process with |kInitialDocumentURL| so we can only accept URLs that
744   // generate the same lock as |kInitialDocumentURL|.
745   const GURL kInitialDocumentURL("http://foo.com");
746   LockProcessToURL(kInitialDocumentURL);
747 
748   AppCacheHost host(kHostIdForTest, kProcessIdForTest, kRenderFrameIdForTest,
749                     ChildProcessSecurityPolicyImpl::GetInstance()->CreateHandle(
750                         kProcessIdForTest),
751                     mojo::NullRemote(), &service_);
752   host.set_frontend_for_testing(&mock_frontend_);
753   mojo::Remote<blink::mojom::AppCacheHost> host_remote;
754   host.BindReceiver(host_remote.BindNewPipeAndPassReceiver());
755 
756   // Verify that a document URL from the wrong site triggers a bad message.
757   {
758     const GURL kWrongSiteDocumentURL("http://origin/document");
759     mojo::test::BadMessageObserver bad_message_observer;
760     host_remote->MarkAsForeignEntry(kWrongSiteDocumentURL,
761                                     blink::mojom::kAppCacheNoCacheId);
762     EXPECT_EQ("ACH_MARK_AS_FOREIGN_ENTRY_DOCUMENT_URL_ACCESS_NOT_ALLOWED",
763               bad_message_observer.WaitForBadMessage());
764   }
765 }
766 
TEST_F(AppCacheHostTest,SelectCacheAfterProcessCleanup)767 TEST_F(AppCacheHostTest, SelectCacheAfterProcessCleanup) {
768   // Lock process with |kDocumentURL| so we can only accept URLs that
769   // generate the same lock as |kDocumentURL|.
770   const GURL kDocumentURL("http://foo.com/document");
771   const GURL kManifestURL("http://foo.com/manifest");
772 
773   auto* security_policy = ChildProcessSecurityPolicyImpl::GetInstance();
774   LockProcessToURL(kDocumentURL);
775 
776   AppCacheHost host(kHostIdForTest, kProcessIdForTest, kRenderFrameIdForTest,
777                     ChildProcessSecurityPolicyImpl::GetInstance()->CreateHandle(
778                         kProcessIdForTest),
779                     mojo::NullRemote(), &service_);
780   host.set_frontend_for_testing(&mock_frontend_);
781   mojo::Remote<blink::mojom::AppCacheHost> host_remote;
782   host.BindReceiver(host_remote.BindNewPipeAndPassReceiver());
783 
784   EXPECT_TRUE(
785       security_policy->CanAccessDataForOrigin(kProcessIdForTest, kDocumentURL));
786 
787   // Destroy the WebContents so the process gets cleaned up.
788   web_contents_.reset();
789   base::RunLoop().RunUntilIdle();
790 
791   // Since |host| for kProcessIdForTest is still alive, the corresponding
792   // SecurityState in ChildProcessSecurityPolicy should also be kept alive,
793   // allowing access for kDocumentURL.
794   EXPECT_TRUE(
795       security_policy->CanAccessDataForOrigin(kProcessIdForTest, kDocumentURL));
796 
797   // Verify that the document and manifest URLs do not trigger a bad message.
798   {
799     mojo::test::BadMessageObserver bad_message_observer;
800 
801     EXPECT_EQ(kUnsetCacheId, mock_frontend_.last_cache_id_);
802     EXPECT_EQ(blink::mojom::AppCacheStatus::APPCACHE_STATUS_OBSOLETE,
803               mock_frontend_.last_status_);
804 
805     host_remote->SelectCache(kDocumentURL, blink::mojom::kAppCacheNoCacheId,
806                              kManifestURL);
807 
808     // Run loop to allow the bad message code to run if a bad message was
809     // triggered.
810     base::RunLoop().RunUntilIdle();
811     EXPECT_FALSE(bad_message_observer.got_bad_message());
812 
813     // Verify the frontend was still called.
814     EXPECT_EQ(blink::mojom::kAppCacheNoCacheId, mock_frontend_.last_cache_id_);
815     EXPECT_EQ(blink::mojom::AppCacheStatus::APPCACHE_STATUS_UNCACHED,
816               mock_frontend_.last_status_);
817   }
818 }
819 
TEST_F(AppCacheHostTest,ForeignEntryAfterProcessCleanup)820 TEST_F(AppCacheHostTest, ForeignEntryAfterProcessCleanup) {
821   // Lock process with |kDocumentURL| so we can only accept URLs that
822   // generate the same lock as |kDocumentURL|.
823   const GURL kDocumentURL("http://foo.com/document");
824 
825   auto* security_policy = ChildProcessSecurityPolicyImpl::GetInstance();
826   LockProcessToURL(kDocumentURL);
827 
828   AppCacheHost host(kHostIdForTest, kProcessIdForTest, kRenderFrameIdForTest,
829                     ChildProcessSecurityPolicyImpl::GetInstance()->CreateHandle(
830                         kProcessIdForTest),
831                     mojo::NullRemote(), &service_);
832   host.set_frontend_for_testing(&mock_frontend_);
833   mojo::Remote<blink::mojom::AppCacheHost> host_remote;
834   host.BindReceiver(host_remote.BindNewPipeAndPassReceiver());
835 
836   EXPECT_TRUE(
837       security_policy->CanAccessDataForOrigin(kProcessIdForTest, kDocumentURL));
838 
839   // Destroy the WebContents so the process gets cleaned up.
840   web_contents_.reset();
841   base::RunLoop().RunUntilIdle();
842 
843   // Since |host| for kProcessIdForTest is still alive, the corresponding
844   // SecurityState in ChildProcessSecurityPolicy should also be kept alive,
845   // allowing access for kDocumentURL.
846   EXPECT_TRUE(
847       security_policy->CanAccessDataForOrigin(kProcessIdForTest, kDocumentURL));
848 
849   // Verify that a document URL does not trigger a bad message.
850   {
851     mojo::test::BadMessageObserver bad_message_observer;
852 
853     EXPECT_EQ(kUnsetCacheId, mock_frontend_.last_cache_id_);
854     EXPECT_EQ(blink::mojom::AppCacheStatus::APPCACHE_STATUS_OBSOLETE,
855               mock_frontend_.last_status_);
856 
857     host_remote->MarkAsForeignEntry(kDocumentURL,
858                                     blink::mojom::kAppCacheNoCacheId);
859 
860     // Run loop to allow the bad message code to run if a bad message was
861     // triggered.
862     base::RunLoop().RunUntilIdle();
863     EXPECT_FALSE(bad_message_observer.got_bad_message());
864 
865     // Verify the frontend was still called.
866     EXPECT_EQ(blink::mojom::kAppCacheNoCacheId, mock_frontend_.last_cache_id_);
867     EXPECT_EQ(blink::mojom::AppCacheStatus::APPCACHE_STATUS_UNCACHED,
868               mock_frontend_.last_status_);
869   }
870 }
871 }  // namespace content
872