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