1 // Copyright 2015 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/platform_util.h"
9 #include "base/files/file_util.h"
10 #include "base/files/scoped_temp_dir.h"
11 #include "base/functional/bind.h"
12 #include "base/functional/callback.h"
13 #include "base/memory/raw_ptr.h"
14 #include "base/run_loop.h"
15 #include "build/build_config.h"
16 #include "build/chromeos_buildflags.h"
17 #include "chrome/browser/platform_util_internal.h"
18 #include "testing/gtest/include/gtest/gtest.h"
20 #if BUILDFLAG(IS_CHROMEOS_ASH)
21 #include "base/json/json_string_value_serializer.h"
22 #include "base/values.h"
23 #include "chrome/browser/apps/app_service/app_service_proxy.h"
24 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
25 #include "chrome/browser/apps/app_service/app_service_test.h"
26 #include "chrome/browser/apps/app_service/intent_util.h"
27 #include "chrome/browser/ash/file_manager/app_id.h"
28 #include "chrome/browser/ash/fileapi/file_system_backend.h"
29 #include "chrome/browser/ash/fileapi/file_system_backend_delegate.h"
30 #include "chrome/browser/chrome_content_browser_client.h"
31 #include "chrome/browser/extensions/extension_special_storage_policy.h"
32 #include "chrome/test/base/browser_with_test_window_test.h"
33 #include "components/services/app_service/public/cpp/app_types.h"
34 #include "components/services/app_service/public/cpp/intent_filter.h"
35 #include "content/public/browser/browser_context.h"
36 #include "content/public/common/content_client.h"
37 #include "extensions/browser/extension_registry.h"
38 #include "extensions/common/extension.h"
39 #include "storage/browser/file_system/external_mount_points.h"
40 #include "storage/browser/test/mock_special_storage_policy.h"
41 #include "storage/common/file_system/file_system_types.h"
43 #include "content/public/test/browser_task_environment.h"
46 namespace platform_util {
50 #if BUILDFLAG(IS_CHROMEOS_ASH)
52 // ChromeContentBrowserClient subclass that sets up a custom file system backend
53 // that allows the test to grant file access to the file manager extension ID
54 // without having to install the extension.
55 class PlatformUtilTestContentBrowserClient : public ChromeContentBrowserClient {
57 void GetAdditionalFileSystemBackends(
58 content::BrowserContext* browser_context,
59 const base::FilePath& storage_partition_path,
60 std::vector<std::unique_ptr<storage::FileSystemBackend>>*
61 additional_backends) override {
62 storage::ExternalMountPoints* external_mount_points =
63 browser_context->GetMountPoints();
65 // New FileSystemBackend that uses our MockSpecialStoragePolicy.
66 additional_backends->push_back(std::make_unique<ash::FileSystemBackend>(
68 nullptr, // file_system_provider_delegate
69 nullptr, // mtp_delegate
70 nullptr, // arc_content_delegate
71 nullptr, // arc_documents_provider_delegate
72 nullptr, // drivefs_delegate
73 nullptr, // smbfs_delegate
74 external_mount_points,
75 storage::ExternalMountPoints::GetSystemInstance()));
79 // Base test fixture class to be used on Chrome OS.
80 class PlatformUtilTestBase : public BrowserWithTestWindowTest {
82 void SetUpPlatformFixture(const base::FilePath& test_directory) {
83 content_browser_client_ =
84 std::make_unique<PlatformUtilTestContentBrowserClient>();
85 old_content_browser_client_ =
86 content::SetBrowserClientForTesting(content_browser_client_.get());
88 app_service_test_.SetUp(GetProfile());
90 apps::AppServiceProxyFactory::GetForProfile(GetProfile());
91 ASSERT_TRUE(app_service_proxy_);
93 // The test_directory needs to be mounted for it to be accessible.
94 GetProfile()->GetMountPoints()->RegisterFileSystem(
95 "test", storage::kFileSystemTypeLocal, storage::FileSystemMountOption(),
98 // To test opening a file, we are going to register a mock extension that
99 // handles .txt files. The extension doesn't actually need to exist due to
100 // the DisableShellOperationsForTesting() call which prevents the extension
101 // from being invoked.
105 std::string json_manifest =
107 " \"manifest_version\": 2,"
108 " \"name\": \"Test extension\","
109 " \"version\": \"0\","
110 " \"app\": { \"background\": { \"scripts\": [\"main.js\"] }},"
111 " \"file_handlers\": {"
113 " \"extensions\": [ \"txt\" ],"
114 " \"title\": \"Text\""
118 JSONStringValueDeserializer json_string_deserializer(json_manifest);
119 std::unique_ptr<base::Value> manifest =
120 json_string_deserializer.Deserialize(&error_code, &error);
121 base::Value::Dict* manifest_dictionary = manifest->GetIfDict();
122 ASSERT_TRUE(manifest_dictionary);
124 scoped_refptr<extensions::Extension> extension =
125 extensions::Extension::Create(
126 test_directory.AppendASCII("invalid-extension"),
127 extensions::mojom::ManifestLocation::kInvalidLocation,
128 *manifest_dictionary, extensions::Extension::NO_FLAGS, &error);
129 ASSERT_TRUE(error.empty()) << error;
131 std::vector<apps::AppPtr> apps;
132 auto app = std::make_unique<apps::App>(apps::AppType::kChromeApp,
133 "invalid-chrome-app");
134 app->handles_intents = true;
135 app->readiness = apps::Readiness::kReady;
136 app->intent_filters =
137 apps_util::CreateIntentFiltersForChromeApp(extension.get());
138 apps.push_back(std::move(app));
139 app_service_proxy_->OnApps(std::move(apps), apps::AppType::kChromeApp,
140 /*should_notify_initialized=*/false);
143 void SetUp() override {
144 BrowserWithTestWindowTest::SetUp();
145 base::RunLoop().RunUntilIdle();
148 void TearDown() override {
149 content::ContentBrowserClient* content_browser_client =
150 content::SetBrowserClientForTesting(old_content_browser_client_);
151 old_content_browser_client_ = nullptr;
152 DCHECK_EQ(static_cast<content::ContentBrowserClient*>(
153 content_browser_client_.get()),
154 content_browser_client)
155 << "ContentBrowserClient changed during test.";
156 BrowserWithTestWindowTest::TearDown();
160 std::unique_ptr<content::ContentBrowserClient> content_browser_client_;
161 raw_ptr<content::ContentBrowserClient, ExperimentalAsh>
162 old_content_browser_client_ = nullptr;
163 apps::AppServiceTest app_service_test_;
164 raw_ptr<apps::AppServiceProxy, DanglingUntriaged | ExperimentalAsh>
165 app_service_proxy_ = nullptr;
170 // Test fixture used by all desktop platforms other than Chrome OS.
171 class PlatformUtilTestBase : public testing::Test {
173 Profile* GetProfile() { return nullptr; }
174 void SetUpPlatformFixture(const base::FilePath&) {}
177 content::BrowserTaskEnvironment task_environment_;
182 class PlatformUtilTest : public PlatformUtilTestBase {
184 void SetUp() override {
185 ASSERT_NO_FATAL_FAILURE(PlatformUtilTestBase::SetUp());
187 static const char kTestFileData[] = "Cow says moo!";
189 // This prevents platform_util from invoking any shell or external APIs
190 // during tests. Doing so may result in external applications being launched
191 // and intefering with tests.
192 internal::DisableShellOperationsForTesting();
194 ASSERT_TRUE(directory_.CreateUniqueTempDir());
197 existing_file_ = directory_.GetPath().AppendASCII("test_file.txt");
198 ASSERT_TRUE(base::WriteFile(existing_file_, kTestFileData));
201 existing_folder_ = directory_.GetPath().AppendASCII("test_folder");
202 ASSERT_TRUE(base::CreateDirectory(existing_folder_));
204 // A non-existent path.
205 nowhere_ = directory_.GetPath().AppendASCII("nowhere");
207 SetUpPlatformFixture(directory_.GetPath());
210 OpenOperationResult CallOpenItem(const base::FilePath& path,
211 OpenItemType item_type) {
212 base::RunLoop run_loop;
213 OpenOperationResult result = OPEN_SUCCEEDED;
214 OpenOperationCallback callback =
215 base::BindOnce(&OnOpenOperationDone, run_loop.QuitClosure(), &result);
216 OpenItem(GetProfile(), path, item_type, std::move(callback));
221 base::FilePath existing_file_;
222 base::FilePath existing_folder_;
223 base::FilePath nowhere_;
226 base::ScopedTempDir directory_;
229 std::unique_ptr<base::RunLoop> run_loop_;
231 static void OnOpenOperationDone(base::OnceClosure closure,
232 OpenOperationResult* store_result,
233 OpenOperationResult result) {
234 *store_result = result;
235 std::move(closure).Run();
241 TEST_F(PlatformUtilTest, OpenFile) {
242 EXPECT_EQ(OPEN_SUCCEEDED, CallOpenItem(existing_file_, OPEN_FILE));
243 EXPECT_EQ(OPEN_FAILED_INVALID_TYPE,
244 CallOpenItem(existing_folder_, OPEN_FILE));
245 EXPECT_EQ(OPEN_FAILED_PATH_NOT_FOUND, CallOpenItem(nowhere_, OPEN_FILE));
248 TEST_F(PlatformUtilTest, OpenFolder) {
249 EXPECT_EQ(OPEN_SUCCEEDED, CallOpenItem(existing_folder_, OPEN_FOLDER));
250 EXPECT_EQ(OPEN_FAILED_INVALID_TYPE,
251 CallOpenItem(existing_file_, OPEN_FOLDER));
252 EXPECT_EQ(OPEN_FAILED_PATH_NOT_FOUND, CallOpenItem(nowhere_, OPEN_FOLDER));
255 #if BUILDFLAG(IS_POSIX)
256 // Symbolic links are currently only supported on Posix. Windows technically
257 // supports it as well, but not on Windows XP.
258 class PlatformUtilPosixTest : public PlatformUtilTest {
260 void SetUp() override {
261 ASSERT_NO_FATAL_FAILURE(PlatformUtilTest::SetUp());
263 symlink_to_file_ = directory_.GetPath().AppendASCII("l_file.txt");
264 ASSERT_TRUE(base::CreateSymbolicLink(existing_file_, symlink_to_file_));
265 symlink_to_folder_ = directory_.GetPath().AppendASCII("l_folder");
266 ASSERT_TRUE(base::CreateSymbolicLink(existing_folder_, symlink_to_folder_));
267 symlink_to_nowhere_ = directory_.GetPath().AppendASCII("l_nowhere");
268 ASSERT_TRUE(base::CreateSymbolicLink(nowhere_, symlink_to_nowhere_));
272 base::FilePath symlink_to_file_;
273 base::FilePath symlink_to_folder_;
274 base::FilePath symlink_to_nowhere_;
276 #endif // BUILDFLAG(IS_POSIX)
278 #if BUILDFLAG(IS_CHROMEOS_ASH)
279 // ChromeOS doesn't follow symbolic links in sandboxed filesystems. So all the
280 // symbolic link tests should return PATH_NOT_FOUND.
282 TEST_F(PlatformUtilPosixTest, OpenFileWithPosixSymlinksChromeOS) {
283 EXPECT_EQ(OPEN_FAILED_PATH_NOT_FOUND,
284 CallOpenItem(symlink_to_file_, OPEN_FILE));
285 EXPECT_EQ(OPEN_FAILED_PATH_NOT_FOUND,
286 CallOpenItem(symlink_to_folder_, OPEN_FILE));
287 EXPECT_EQ(OPEN_FAILED_PATH_NOT_FOUND,
288 CallOpenItem(symlink_to_nowhere_, OPEN_FILE));
291 TEST_F(PlatformUtilPosixTest, OpenFolderWithPosixSymlinksChromeOS) {
292 EXPECT_EQ(OPEN_FAILED_PATH_NOT_FOUND,
293 CallOpenItem(symlink_to_folder_, OPEN_FOLDER));
294 EXPECT_EQ(OPEN_FAILED_PATH_NOT_FOUND,
295 CallOpenItem(symlink_to_file_, OPEN_FOLDER));
296 EXPECT_EQ(OPEN_FAILED_PATH_NOT_FOUND,
297 CallOpenItem(symlink_to_nowhere_, OPEN_FOLDER));
300 TEST_F(PlatformUtilTest, OpenFileWithUnhandledFileType) {
301 base::FilePath unhandled_file =
302 directory_.GetPath().AppendASCII("myfile.filetype");
303 ASSERT_TRUE(base::WriteFile(unhandled_file, "cat"));
304 EXPECT_EQ(OPEN_FAILED_NO_HANLDER_FOR_FILE_TYPE,
305 CallOpenItem(unhandled_file, OPEN_FILE));
307 #endif // BUILDFLAG(IS_CHROMEOS_ASH)
309 #if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_CHROMEOS_ASH)
310 // On all other Posix platforms, the symbolic link tests should work as
313 TEST_F(PlatformUtilPosixTest, OpenFileWithPosixSymlinks) {
314 EXPECT_EQ(OPEN_SUCCEEDED, CallOpenItem(symlink_to_file_, OPEN_FILE));
315 EXPECT_EQ(OPEN_FAILED_INVALID_TYPE,
316 CallOpenItem(symlink_to_folder_, OPEN_FILE));
317 EXPECT_EQ(OPEN_FAILED_PATH_NOT_FOUND,
318 CallOpenItem(symlink_to_nowhere_, OPEN_FILE));
321 TEST_F(PlatformUtilPosixTest, OpenFolderWithPosixSymlinks) {
322 EXPECT_EQ(OPEN_SUCCEEDED, CallOpenItem(symlink_to_folder_, OPEN_FOLDER));
323 EXPECT_EQ(OPEN_FAILED_INVALID_TYPE,
324 CallOpenItem(symlink_to_file_, OPEN_FOLDER));
325 EXPECT_EQ(OPEN_FAILED_PATH_NOT_FOUND,
326 CallOpenItem(symlink_to_nowhere_, OPEN_FOLDER));
328 #endif // BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_CHROMEOS_ASH)
330 } // namespace platform_util