blob: a60867646710098a8a8e215acbf3b286d238e327 [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/web_database/web_database_host_impl.h"
#include <memory>
#include <string>
#include <utility>
#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "components/services/storage/public/cpp/buckets/bucket_info.h"
#include "components/services/storage/public/cpp/buckets/constants.h"
#include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/isolation_context.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/mock_render_process_host.h"
#include "content/public/test/test_browser_context.h"
#include "mojo/public/cpp/system/functions.h"
#include "mojo/public/cpp/test_support/fake_message_dispatch_context.h"
#include "mojo/public/cpp/test_support/test_utils.h"
#include "storage/browser/database/database_tracker.h"
#include "storage/browser/quota/quota_manager_proxy.h"
#include "storage/browser/quota/special_storage_policy.h"
#include "storage/browser/test/mock_quota_manager.h"
#include "storage/browser/test/mock_quota_manager_proxy.h"
#include "storage/browser/test/mock_special_storage_policy.h"
#include "storage/browser/test/quota_manager_proxy_sync.h"
#include "storage/common/database/database_identifier.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace content {
namespace {
std::u16string ConstructVfsFileName(const url::Origin& origin,
const std::u16string& name,
const std::u16string& suffix) {
std::string identifier = storage::GetIdentifierFromOrigin(origin);
return base::UTF8ToUTF16(identifier) + u"/" + name + u"#" + suffix;
}
class WebDatabaseHostImplTest : public ::testing::Test {
public:
WebDatabaseHostImplTest()
: special_storage_policy_(
base::MakeRefCounted<storage::MockSpecialStoragePolicy>()) {}
WebDatabaseHostImplTest(const WebDatabaseHostImplTest&) = delete;
WebDatabaseHostImplTest& operator=(const WebDatabaseHostImplTest&) = delete;
~WebDatabaseHostImplTest() override = default;
void SetUp() override {
render_process_host_ =
std::make_unique<MockRenderProcessHost>(&browser_context_);
ASSERT_TRUE(data_dir_.CreateUniqueTempDir());
quota_manager_ = base::MakeRefCounted<storage::MockQuotaManager>(
/*is_incognito=*/false, data_dir_.GetPath(),
base::SingleThreadTaskRunner::GetCurrentDefault(),
special_storage_policy_);
quota_manager_proxy_ = base::MakeRefCounted<storage::MockQuotaManagerProxy>(
quota_manager_.get(),
base::SingleThreadTaskRunner::GetCurrentDefault());
db_tracker_ = storage::DatabaseTracker::Create(
base::FilePath(),
/*is_incognito=*/false,
/*special_storage_policy=*/nullptr, quota_manager_proxy_);
// Raw pointer usage is safe because `host_` stores a reference to the
// DatabaseTracker, keeping it alive for the duration of the test.
task_runner_ = db_tracker_->task_runner();
host_ = std::make_unique<WebDatabaseHostImpl>(process_id(), db_tracker_);
}
void TearDown() override {
base::RunLoop run_loop;
task_runner_->PostTask(FROM_HERE, base::BindLambdaForTesting([&]() {
db_tracker_->Shutdown();
run_loop.Quit();
}));
run_loop.Run();
task_runner_->DeleteSoon(FROM_HERE, std::move(host_));
RunUntilIdle();
}
protected:
template <typename Callable>
void CheckUnauthorizedOrigin(const Callable& func) {
mojo::test::BadMessageObserver bad_message_observer;
base::RunLoop run_loop;
task_runner()->PostTask(
FROM_HERE, base::BindLambdaForTesting([&]() {
mojo::FakeMessageDispatchContext fake_dispatch_context;
func();
run_loop.Quit();
}));
run_loop.Run();
RunUntilIdle();
EXPECT_EQ("WebDatabaseHost: Unauthorized origin.",
bad_message_observer.WaitForBadMessage());
}
template <typename Callable>
void CheckInvalidOrigin(const Callable& func) {
mojo::test::BadMessageObserver bad_message_observer;
base::RunLoop run_loop;
task_runner()->PostTask(
FROM_HERE, base::BindLambdaForTesting([&]() {
mojo::FakeMessageDispatchContext fake_dispatch_context;
func();
run_loop.Quit();
}));
run_loop.Run();
RunUntilIdle();
EXPECT_EQ("WebDatabaseHost: Invalid origin.",
bad_message_observer.WaitForBadMessage());
}
void CallRenderProcessHostCleanup() { render_process_host_.reset(); }
void RunUntilIdle() { task_environment_.RunUntilIdle(); }
WebDatabaseHostImpl* host() { return host_.get(); }
int process_id() const { return render_process_host_->GetID(); }
BrowserContext* browser_context() { return &browser_context_; }
base::SequencedTaskRunner* task_runner() { return task_runner_.get(); }
void LockProcessToURL(const GURL& url) {
ChildProcessSecurityPolicyImpl::GetInstance()->LockProcessForTesting(
IsolationContext(BrowsingInstanceId(1), browser_context(),
/*is_guest=*/false, /*is_fenced=*/false),
process_id(), url);
}
storage::MockQuotaManager* quota_manager() { return quota_manager_.get(); }
storage::QuotaManagerProxy* quota_manager_proxy() {
return quota_manager_proxy_.get();
}
private:
scoped_refptr<storage::MockSpecialStoragePolicy> special_storage_policy_;
base::ScopedTempDir data_dir_;
BrowserTaskEnvironment task_environment_;
TestBrowserContext browser_context_;
std::unique_ptr<MockRenderProcessHost> render_process_host_;
scoped_refptr<storage::DatabaseTracker> db_tracker_;
std::unique_ptr<WebDatabaseHostImpl> host_;
scoped_refptr<base::SequencedTaskRunner> task_runner_;
scoped_refptr<storage::MockQuotaManager> quota_manager_;
scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy_;
};
TEST_F(WebDatabaseHostImplTest, OpenFileCreatesBucket) {
const char* example_url = "http://example.com";
const GURL example_gurl(example_url);
const url::Origin example_origin = url::Origin::Create(example_gurl);
const std::u16string db_name = u"db_name";
const std::u16string suffix(u"suffix");
const std::u16string vfs_file_name =
ConstructVfsFileName(example_origin, db_name, suffix);
auto* security_policy = ChildProcessSecurityPolicyImpl::GetInstance();
security_policy->AddFutureIsolatedOrigins(
{example_origin}, ChildProcessSecurityPolicy::IsolatedOriginSource::TEST);
LockProcessToURL(example_gurl);
storage::QuotaManagerProxySync quota_manager_proxy_sync(
quota_manager_proxy());
base::RunLoop run_loop;
task_runner()->PostTask(
FROM_HERE, base::BindLambdaForTesting([&]() {
mojo::FakeMessageDispatchContext fake_dispatch_context;
host()->OpenFile(vfs_file_name, /*desired_flags=*/0,
base::BindLambdaForTesting(
[&](base::File file) { run_loop.Quit(); }));
}));
run_loop.Run();
// Check default bucket exists for https://example.com.
storage::QuotaErrorOr<storage::BucketInfo> result =
quota_manager_proxy_sync.GetBucket(
blink::StorageKey::CreateFromStringForTesting(example_url),
storage::kDefaultBucketName, blink::mojom::StorageType::kTemporary);
EXPECT_TRUE(result.ok());
EXPECT_EQ(result->name, storage::kDefaultBucketName);
EXPECT_EQ(result->storage_key,
blink::StorageKey::CreateFromStringForTesting(example_url));
EXPECT_GT(result->id.value(), 0);
}
TEST_F(WebDatabaseHostImplTest, GetOrCreateBucketError) {
const char* example_url = "http://example.com";
const GURL example_gurl(example_url);
const url::Origin example_origin = url::Origin::Create(example_gurl);
const std::u16string db_name = u"db_name";
const std::u16string suffix(u"suffix");
const std::u16string vfs_file_name =
ConstructVfsFileName(example_origin, db_name, suffix);
auto* security_policy = ChildProcessSecurityPolicyImpl::GetInstance();
security_policy->AddFutureIsolatedOrigins(
{example_origin}, ChildProcessSecurityPolicy::IsolatedOriginSource::TEST);
LockProcessToURL(example_gurl);
quota_manager()->SetDisableDatabase(true);
storage::QuotaManagerProxySync quota_manager_proxy_sync(
quota_manager_proxy());
base::RunLoop run_loop;
task_runner()->PostTask(
FROM_HERE, base::BindLambdaForTesting([&]() {
mojo::FakeMessageDispatchContext fake_dispatch_context;
host()->OpenFile(vfs_file_name, /*desired_flags=*/0,
base::BindLambdaForTesting([&](base::File file) {
EXPECT_FALSE(file.IsValid());
run_loop.Quit();
}));
}));
run_loop.Run();
}
TEST_F(WebDatabaseHostImplTest, BadMessagesUnauthorized) {
const GURL correct_url("http://correct.com");
const url::Origin correct_origin = url::Origin::Create(correct_url);
const url::Origin incorrect_origin =
url::Origin::Create(GURL("http://incorrect.net"));
const std::u16string db_name(u"db_name");
const std::u16string suffix(u"suffix");
const std::u16string bad_vfs_file_name =
ConstructVfsFileName(incorrect_origin, db_name, suffix);
auto* security_policy = ChildProcessSecurityPolicyImpl::GetInstance();
security_policy->AddFutureIsolatedOrigins(
{correct_origin, incorrect_origin},
ChildProcessSecurityPolicy::IsolatedOriginSource::TEST);
LockProcessToURL(correct_url);
ASSERT_TRUE(
security_policy->CanAccessDataForOrigin(process_id(), correct_origin));
ASSERT_FALSE(
security_policy->CanAccessDataForOrigin(process_id(), incorrect_origin));
CheckUnauthorizedOrigin([&]() {
host()->OpenFile(bad_vfs_file_name,
/*desired_flags=*/0, base::DoNothing());
});
CheckUnauthorizedOrigin([&]() {
host()->DeleteFile(bad_vfs_file_name,
/*sync_dir=*/false, base::DoNothing());
});
CheckUnauthorizedOrigin([&]() {
host()->GetFileAttributes(bad_vfs_file_name, base::DoNothing());
});
CheckUnauthorizedOrigin([&]() {
host()->SetFileSize(bad_vfs_file_name, /*expected_size=*/0,
base::DoNothing());
});
CheckUnauthorizedOrigin([&]() {
host()->GetSpaceAvailable(incorrect_origin, base::DoNothing());
});
CheckUnauthorizedOrigin(
[&]() { host()->Opened(incorrect_origin, db_name, u"description"); });
CheckUnauthorizedOrigin(
[&]() { host()->Modified(incorrect_origin, db_name); });
CheckUnauthorizedOrigin([&]() { host()->Closed(incorrect_origin, db_name); });
CheckUnauthorizedOrigin([&]() {
host()->HandleSqliteError(incorrect_origin, db_name, /*error=*/0);
});
}
TEST_F(WebDatabaseHostImplTest, BadMessagesInvalid) {
const url::Origin opaque_origin;
const std::u16string db_name(u"db_name");
CheckInvalidOrigin(
[&]() { host()->GetSpaceAvailable(opaque_origin, base::DoNothing()); });
CheckInvalidOrigin(
[&]() { host()->Opened(opaque_origin, db_name, u"description"); });
CheckInvalidOrigin([&]() { host()->Modified(opaque_origin, db_name); });
CheckInvalidOrigin([&]() { host()->Closed(opaque_origin, db_name); });
CheckInvalidOrigin([&]() {
host()->HandleSqliteError(opaque_origin, db_name, /*error=*/0);
});
}
TEST_F(WebDatabaseHostImplTest, ProcessShutdown) {
const GURL correct_url("http://correct.com");
const url::Origin correct_origin = url::Origin::Create(correct_url);
const url::Origin incorrect_origin =
url::Origin::Create(GURL("http://incorrect.net"));
const std::u16string db_name(u"db_name");
const std::u16string suffix(u"suffix");
const std::u16string bad_vfs_file_name =
ConstructVfsFileName(incorrect_origin, db_name, suffix);
auto* security_policy = ChildProcessSecurityPolicyImpl::GetInstance();
security_policy->AddFutureIsolatedOrigins(
{correct_origin, incorrect_origin},
ChildProcessSecurityPolicy::IsolatedOriginSource::TEST);
LockProcessToURL(correct_url);
bool success_callback_was_called = false;
auto success_callback = base::BindLambdaForTesting(
[&](base::File) { success_callback_was_called = true; });
absl::optional<std::string> error_callback_message;
mojo::SetDefaultProcessErrorHandler(base::BindLambdaForTesting(
[&](const std::string& message) { error_callback_message = message; }));
// Verify that an error occurs with OpenFile() call before process shutdown.
{
base::RunLoop run_loop;
task_runner()->PostTask(
FROM_HERE, base::BindLambdaForTesting([&]() {
mojo::FakeMessageDispatchContext fake_dispatch_context;
host()->OpenFile(bad_vfs_file_name,
/*desired_flags=*/0, success_callback);
run_loop.Quit();
}));
run_loop.Run();
RunUntilIdle();
EXPECT_FALSE(success_callback_was_called);
EXPECT_TRUE(error_callback_message.has_value());
EXPECT_EQ("WebDatabaseHost: Unauthorized origin.",
error_callback_message.value());
}
success_callback_was_called = false;
error_callback_message.reset();
// Start cleanup of the RenderProcessHost. This causes
// RenderProcessHost::FromID() to return nullptr for the process_id.
CallRenderProcessHostCleanup();
// Attempt the call again and verify that no callbacks were called.
{
base::RunLoop run_loop;
task_runner()->PostTask(
FROM_HERE, base::BindLambdaForTesting([&]() {
mojo::FakeMessageDispatchContext fake_dispatch_context;
host()->OpenFile(bad_vfs_file_name,
/*desired_flags=*/0, success_callback);
run_loop.Quit();
}));
run_loop.Run();
RunUntilIdle();
// Verify none of the callbacks were called.
EXPECT_FALSE(success_callback_was_called);
EXPECT_FALSE(error_callback_message.has_value());
}
mojo::SetDefaultProcessErrorHandler(base::NullCallback());
}
} // namespace
} // namespace content