blob: a223db35b17ad9bd5a42c579f6ea13e30712b7f7 [file] [log] [blame]
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "storage/browser/file_system/file_system_context.h"
#include <stddef.h>
#include <array>
#include "base/files/scoped_temp_dir.h"
#include "base/memory/scoped_refptr.h"
#include "base/strings/stringprintf.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/test/gmock_expected_support.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "build/build_config.h"
#include "components/services/storage/public/cpp/quota_error_or.h"
#include "storage/browser/file_system/external_mount_points.h"
#include "storage/browser/file_system/file_system_backend.h"
#include "storage/browser/file_system/isolated_context.h"
#include "storage/browser/file_system/open_file_system_mode.h"
#include "storage/browser/quota/quota_manager_proxy.h"
#include "storage/browser/test/mock_quota_manager.h"
#include "storage/browser/test/mock_special_storage_policy.h"
#include "storage/browser/test/test_file_system_backend.h"
#include "storage/browser/test/test_file_system_options.h"
#include "testing/gtest/include/gtest/gtest.h"
#define FPL(x) FILE_PATH_LITERAL(x)
#if defined(FILE_PATH_USES_DRIVE_LETTERS)
#define DRIVE FPL("C:")
#else
#define DRIVE
#endif
namespace storage {
namespace {
const char kTestOrigin[] = "http://chromium.org/";
GURL CreateRawFileSystemURL(const std::string& type_str,
const std::string& fs_id) {
std::string url_str =
base::StringPrintf("filesystem:http://chromium.org/%s/%s/root/file",
type_str.c_str(), fs_id.c_str());
return GURL(url_str);
}
class FileSystemContextTest : public testing::Test {
public:
FileSystemContextTest() = default;
void SetUp() override {
ASSERT_TRUE(data_dir_.CreateUniqueTempDir());
storage_policy_ = base::MakeRefCounted<MockSpecialStoragePolicy>();
mock_quota_manager_ = base::MakeRefCounted<MockQuotaManager>(
false /* is_incognito */, data_dir_.GetPath(),
base::SingleThreadTaskRunner::GetCurrentDefault().get(),
storage_policy_.get());
}
protected:
scoped_refptr<FileSystemContext> CreateFileSystemContextForTest(
scoped_refptr<ExternalMountPoints> external_mount_points) {
std::vector<std::unique_ptr<storage::FileSystemBackend>>
additional_providers;
additional_providers.push_back(std::make_unique<TestFileSystemBackend>(
base::SingleThreadTaskRunner::GetCurrentDefault().get(),
data_dir_.GetPath()));
return FileSystemContext::Create(
base::SingleThreadTaskRunner::GetCurrentDefault(),
base::SingleThreadTaskRunner::GetCurrentDefault(),
std::move(external_mount_points), storage_policy_,
mock_quota_manager_->proxy(), std::move(additional_providers),
std::vector<URLRequestAutoMountHandler>(), data_dir_.GetPath(),
CreateAllowFileAccessOptions());
}
QuotaManagerProxy* proxy() { return mock_quota_manager_->proxy(); }
// Verifies a *valid* filesystem url has expected values.
void ExpectFileSystemURLMatches(const FileSystemURL& url,
const GURL& expect_origin,
FileSystemType expect_mount_type,
FileSystemType expect_type,
const base::FilePath& expect_path,
const base::FilePath& expect_virtual_path,
const std::string& expect_filesystem_id) {
EXPECT_TRUE(url.is_valid());
EXPECT_EQ(expect_origin, url.origin().GetURL());
EXPECT_EQ(expect_mount_type, url.mount_type());
EXPECT_EQ(expect_type, url.type());
EXPECT_EQ(expect_path, url.path());
EXPECT_EQ(expect_virtual_path, url.virtual_path());
EXPECT_EQ(expect_filesystem_id, url.filesystem_id());
}
inline static std::optional<FileSystemURL> last_resolved_url_ = std::nullopt;
private:
base::ScopedTempDir data_dir_;
base::test::TaskEnvironment task_environment_;
scoped_refptr<SpecialStoragePolicy> storage_policy_;
scoped_refptr<MockQuotaManager> mock_quota_manager_;
// Mock FileSystemBackend implementation which saves the last resolved URL.
class TestFileSystemBackend : public storage::TestFileSystemBackend {
public:
TestFileSystemBackend(base::SequencedTaskRunner* task_runner,
const base::FilePath& base_path)
: storage::TestFileSystemBackend(task_runner, base_path) {}
void ResolveURL(const FileSystemURL& url,
OpenFileSystemMode mode,
ResolveURLCallback callback) override {
last_resolved_url_ = url;
}
};
};
// It is not valid to pass nullptr ExternalMountPoints to FileSystemContext on
// ChromeOS.
#if !BUILDFLAG(IS_CHROMEOS)
TEST_F(FileSystemContextTest, NullExternalMountPoints) {
scoped_refptr<FileSystemContext> file_system_context =
CreateFileSystemContextForTest(/*external_mount_points=*/nullptr);
// Cracking system external mount and isolated mount points should work.
std::string isolated_name = "root";
IsolatedContext::ScopedFSHandle isolated_fs =
IsolatedContext::GetInstance()->RegisterFileSystemForPath(
kFileSystemTypeLocal, std::string(),
base::FilePath(DRIVE FPL("/test/isolated/root")), &isolated_name);
std::string isolated_id = isolated_fs.id();
// Register system external mount point.
ASSERT_TRUE(ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
"system", kFileSystemTypeLocal, FileSystemMountOption(),
base::FilePath(DRIVE FPL("/test/sys/"))));
FileSystemURL cracked_isolated =
file_system_context->CrackURLInFirstPartyContext(
CreateRawFileSystemURL("isolated", isolated_id));
ExpectFileSystemURLMatches(
cracked_isolated, GURL(kTestOrigin), kFileSystemTypeIsolated,
kFileSystemTypeLocal,
base::FilePath(DRIVE FPL("/test/isolated/root/file"))
.NormalizePathSeparators(),
base::FilePath::FromUTF8Unsafe(isolated_id)
.Append(FPL("root/file"))
.NormalizePathSeparators(),
isolated_id);
FileSystemURL cracked_external =
file_system_context->CrackURLInFirstPartyContext(
CreateRawFileSystemURL("external", "system"));
ExpectFileSystemURLMatches(
cracked_external, GURL(kTestOrigin), kFileSystemTypeExternal,
kFileSystemTypeLocal,
base::FilePath(DRIVE FPL("/test/sys/root/file"))
.NormalizePathSeparators(),
base::FilePath(FPL("system/root/file")).NormalizePathSeparators(),
"system");
IsolatedContext::GetInstance()->RevokeFileSystem(isolated_id);
ExternalMountPoints::GetSystemInstance()->RevokeFileSystem("system");
}
#endif // !defiend(OS_CHROMEOS)
TEST_F(FileSystemContextTest, FileSystemContextKeepsMountPointsAlive) {
scoped_refptr<ExternalMountPoints> mount_points =
ExternalMountPoints::CreateRefCounted();
// Register system external mount point.
ASSERT_TRUE(mount_points->RegisterFileSystem(
"system", kFileSystemTypeLocal, FileSystemMountOption(),
base::FilePath(DRIVE FPL("/test/sys/"))));
scoped_refptr<FileSystemContext> file_system_context =
CreateFileSystemContextForTest(std::move(mount_points));
// FileSystemContext should keep a reference to the |mount_points|, so it
// should be able to resolve the URL.
FileSystemURL cracked_external =
file_system_context->CrackURLInFirstPartyContext(
CreateRawFileSystemURL("external", "system"));
ExpectFileSystemURLMatches(
cracked_external, GURL(kTestOrigin), kFileSystemTypeExternal,
kFileSystemTypeLocal,
base::FilePath(DRIVE FPL("/test/sys/root/file"))
.NormalizePathSeparators(),
base::FilePath(FPL("system/root/file")).NormalizePathSeparators(),
"system");
// No need to revoke the registered filesystem since |mount_points| lifetime
// is bound to this test.
}
TEST_F(FileSystemContextTest, ResolveURLOnOpenFileSystem_CustomBucket) {
scoped_refptr<FileSystemContext> file_system_context =
CreateFileSystemContextForTest(/*external_mount_points=*/nullptr);
const auto open_callback = base::BindLambdaForTesting(
[&](const FileSystemURL& root_url, const std::string& name,
base::File::Error error) { return; });
const auto storage_key =
blink::StorageKey::CreateFromStringForTesting("http://host/test.crswap");
base::test::TestFuture<storage::QuotaErrorOr<storage::BucketInfo>>
bucket_future;
proxy()->CreateBucketForTesting(
storage_key, "custom_bucket",
base::SequencedTaskRunner::GetCurrentDefault(),
bucket_future.GetCallback());
ASSERT_OK_AND_ASSIGN(auto bucket, bucket_future.Take());
ASSERT_FALSE(last_resolved_url_.has_value());
file_system_context->ResolveURLOnOpenFileSystemForTesting(
storage_key, bucket.ToBucketLocator(), kFileSystemTypeTest,
OpenFileSystemMode::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
std::move(open_callback));
ASSERT_TRUE(last_resolved_url_.has_value());
ASSERT_EQ(last_resolved_url_.value().bucket(), bucket.ToBucketLocator());
}
TEST_F(FileSystemContextTest, CrackFileSystemURL) {
scoped_refptr<ExternalMountPoints> external_mount_points =
ExternalMountPoints::CreateRefCounted();
scoped_refptr<FileSystemContext> file_system_context =
CreateFileSystemContextForTest(external_mount_points);
// Register an isolated mount point.
std::string isolated_file_system_name = "root";
IsolatedContext::ScopedFSHandle isolated_fs =
IsolatedContext::GetInstance()->RegisterFileSystemForPath(
kFileSystemTypeLocal, std::string(),
base::FilePath(DRIVE FPL("/test/isolated/root")),
&isolated_file_system_name);
const std::string kIsolatedFileSystemID = isolated_fs.id();
// Register system external mount point.
ASSERT_TRUE(ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
"system", kFileSystemTypeLocal, FileSystemMountOption(),
base::FilePath(DRIVE FPL("/test/sys/"))));
ASSERT_TRUE(ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
"ext", kFileSystemTypeLocal, FileSystemMountOption(),
base::FilePath(DRIVE FPL("/test/ext"))));
// Register a system external mount point with the same name/id as the
// registered isolated mount point.
ASSERT_TRUE(ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
kIsolatedFileSystemID, kFileSystemTypeLocal, FileSystemMountOption(),
base::FilePath(DRIVE FPL("/test/system/isolated"))));
// Add a mount points with the same name as a system mount point to
// FileSystemContext's external mount points.
ASSERT_TRUE(external_mount_points->RegisterFileSystem(
"ext", kFileSystemTypeLocal, FileSystemMountOption(),
base::FilePath(DRIVE FPL("/test/local/ext/"))));
const base::FilePath kVirtualPathNoRoot = base::FilePath(FPL("root/file"));
struct TestCase {
// Test case values.
std::string root;
std::string type_str;
// Expected test results.
bool expect_is_valid;
FileSystemType expect_mount_type;
FileSystemType expect_type;
const base::FilePath::CharType* expect_path;
std::string expect_filesystem_id;
};
const auto kTestCases = std::to_array<TestCase>({
// Following should not be handled by the url crackers:
{
"pers_mount", "persistent", true /* is_valid */,
kFileSystemTypePersistent, kFileSystemTypePersistent,
FPL("pers_mount/root/file"), std::string() /* filesystem id */
},
{
"temp_mount", "temporary", true /* is_valid */,
kFileSystemTypeTemporary, kFileSystemTypeTemporary,
FPL("temp_mount/root/file"), std::string() /* filesystem id */
},
// Should be cracked by isolated mount points:
{kIsolatedFileSystemID, "isolated", true /* is_valid */,
kFileSystemTypeIsolated, kFileSystemTypeLocal,
DRIVE FPL("/test/isolated/root/file"), kIsolatedFileSystemID},
// Should be cracked by system mount points:
{"system", "external", true /* is_valid */, kFileSystemTypeExternal,
kFileSystemTypeLocal, DRIVE FPL("/test/sys/root/file"), "system"},
{kIsolatedFileSystemID, "external", true /* is_valid */,
kFileSystemTypeExternal, kFileSystemTypeLocal,
DRIVE FPL("/test/system/isolated/root/file"), kIsolatedFileSystemID},
// Should be cracked by FileSystemContext's ExternalMountPoints.
{"ext", "external", true /* is_valid */, kFileSystemTypeExternal,
kFileSystemTypeLocal, DRIVE FPL("/test/local/ext/root/file"), "ext"},
// Test for invalid filesystem url (made invalid by adding invalid
// filesystem type).
{"sytem", "external", false /* is_valid */,
// The rest of values will be ignored.
kFileSystemTypeUnknown, kFileSystemTypeUnknown, FPL(""), std::string()},
// Test for URL with non-existing filesystem id.
{"invalid", "external", false /* is_valid */,
// The rest of values will be ignored.
kFileSystemTypeUnknown, kFileSystemTypeUnknown, FPL(""), std::string()},
});
for (size_t i = 0; i < std::size(kTestCases); ++i) {
const base::FilePath virtual_path =
base::FilePath::FromASCII(kTestCases[i].root)
.Append(kVirtualPathNoRoot);
GURL raw_url =
CreateRawFileSystemURL(kTestCases[i].type_str, kTestCases[i].root);
FileSystemURL cracked_url =
file_system_context->CrackURLInFirstPartyContext(raw_url);
SCOPED_TRACE(testing::Message() << "Test case " << i << ": "
<< "Cracking URL: " << raw_url);
EXPECT_EQ(kTestCases[i].expect_is_valid, cracked_url.is_valid());
if (!kTestCases[i].expect_is_valid)
continue;
ExpectFileSystemURLMatches(
cracked_url, GURL(kTestOrigin), kTestCases[i].expect_mount_type,
kTestCases[i].expect_type,
base::FilePath(kTestCases[i].expect_path).NormalizePathSeparators(),
virtual_path.NormalizePathSeparators(),
kTestCases[i].expect_filesystem_id);
}
IsolatedContext::GetInstance()->RevokeFileSystemByPath(
base::FilePath(DRIVE FPL("/test/isolated/root")));
ExternalMountPoints::GetSystemInstance()->RevokeFileSystem("system");
ExternalMountPoints::GetSystemInstance()->RevokeFileSystem("ext");
ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(
kIsolatedFileSystemID);
}
TEST_F(FileSystemContextTest, CanServeURLRequest) {
scoped_refptr<ExternalMountPoints> external_mount_points =
ExternalMountPoints::CreateRefCounted();
scoped_refptr<FileSystemContext> context =
CreateFileSystemContextForTest(std::move(external_mount_points));
// A request for a sandbox mount point should be served.
FileSystemURL cracked_url = context->CrackURLInFirstPartyContext(
CreateRawFileSystemURL("persistent", "pers_mount"));
EXPECT_EQ(kFileSystemTypePersistent, cracked_url.mount_type());
EXPECT_TRUE(context->CanServeURLRequest(cracked_url));
// A request for an isolated mount point should NOT be served.
std::string isolated_fs_name = "root";
IsolatedContext::ScopedFSHandle isolated_fs =
IsolatedContext::GetInstance()->RegisterFileSystemForPath(
kFileSystemTypeLocal, std::string(),
base::FilePath(DRIVE FPL("/test/isolated/root")), &isolated_fs_name);
std::string isolated_fs_id = isolated_fs.id();
cracked_url = context->CrackURLInFirstPartyContext(
CreateRawFileSystemURL("isolated", isolated_fs_id));
EXPECT_EQ(kFileSystemTypeIsolated, cracked_url.mount_type());
EXPECT_FALSE(context->CanServeURLRequest(cracked_url));
// A request for an external mount point should be served.
const std::string kExternalMountName = "ext_mount";
ASSERT_TRUE(ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
kExternalMountName, kFileSystemTypeLocal, FileSystemMountOption(),
base::FilePath()));
cracked_url = context->CrackURLInFirstPartyContext(
CreateRawFileSystemURL("external", kExternalMountName));
EXPECT_EQ(kFileSystemTypeExternal, cracked_url.mount_type());
EXPECT_TRUE(context->CanServeURLRequest(cracked_url));
ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(
kExternalMountName);
IsolatedContext::GetInstance()->RevokeFileSystem(isolated_fs_id);
}
// Ensures that a backend exists for each common isolated file system type.
// See http://crbug.com/447027
TEST_F(FileSystemContextTest, IsolatedFileSystemsTypesHandled) {
// This does not provide any "additional" file system handlers. In particular,
// on Chrome OS it does not provide ash::FileSystemBackend.
scoped_refptr<FileSystemContext> file_system_context =
CreateFileSystemContextForTest(/*external_mount_points=*/nullptr);
// Isolated file system types are handled.
EXPECT_TRUE(
file_system_context->GetFileSystemBackend(kFileSystemTypeIsolated));
EXPECT_TRUE(
file_system_context->GetFileSystemBackend(kFileSystemTypeDragged));
EXPECT_TRUE(file_system_context->GetFileSystemBackend(
kFileSystemTypeForTransientFile));
EXPECT_TRUE(file_system_context->GetFileSystemBackend(kFileSystemTypeLocal));
EXPECT_TRUE(file_system_context->GetFileSystemBackend(
kFileSystemTypeLocalForPlatformApp));
}
} // namespace
} // namespace storage