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