1 // Copyright 2013 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 "chrome/browser/chromeos/file_manager/file_tasks.h"
6
7 #include <algorithm>
8 #include <set>
9 #include <utility>
10
11 #include "base/bind.h"
12 #include "base/command_line.h"
13 #include "base/run_loop.h"
14 #include "base/values.h"
15 #include "chrome/browser/chromeos/crostini/crostini_mime_types_service.h"
16 #include "chrome/browser/chromeos/crostini/crostini_mime_types_service_factory.h"
17 #include "chrome/browser/chromeos/crostini/crostini_pref_names.h"
18 #include "chrome/browser/chromeos/crostini/crostini_test_helper.h"
19 #include "chrome/browser/chromeos/crostini/fake_crostini_features.h"
20 #include "chrome/browser/chromeos/drive/file_system_util.h"
21 #include "chrome/browser/chromeos/file_manager/app_id.h"
22 #include "chrome/browser/chromeos/file_manager/path_util.h"
23 #include "chrome/browser/chromeos/login/users/scoped_test_user_manager.h"
24 #include "chrome/browser/chromeos/settings/scoped_cros_settings_test_helper.h"
25 #include "chrome/browser/extensions/extension_service.h"
26 #include "chrome/browser/extensions/test_extension_system.h"
27 #include "chrome/common/chrome_features.h"
28 #include "chrome/common/pref_names.h"
29 #include "chrome/test/base/testing_profile.h"
30 #include "chromeos/dbus/dbus_thread_manager.h"
31 #include "chromeos/dbus/fake_concierge_client.h"
32 #include "components/prefs/pref_registry_simple.h"
33 #include "components/prefs/pref_service.h"
34 #include "components/prefs/testing_pref_service.h"
35 #include "components/services/app_service/public/cpp/file_handler.h"
36 #include "components/services/app_service/public/cpp/file_handler_info.h"
37 #include "content/public/test/browser_task_environment.h"
38 #include "extensions/browser/entry_info.h"
39 #include "extensions/browser/extension_prefs.h"
40 #include "extensions/browser/extension_registry.h"
41 #include "extensions/browser/extension_system.h"
42 #include "extensions/common/constants.h"
43 #include "extensions/common/extension_builder.h"
44 #include "extensions/common/manifest.h"
45 #include "google_apis/drive/drive_api_parser.h"
46 #include "net/base/escape.h"
47 #include "storage/browser/file_system/external_mount_points.h"
48 #include "testing/gtest/include/gtest/gtest.h"
49 #include "third_party/blink/public/common/features.h"
50 #include "url/gurl.h"
51
52 using extensions::api::file_manager_private::Verb;
53
54 namespace file_manager {
55 namespace file_tasks {
56 namespace {
57
58 // Registers the default task preferences. Used for testing
59 // ChooseAndSetDefaultTask().
RegisterDefaultTaskPreferences(TestingPrefServiceSimple * pref_service)60 void RegisterDefaultTaskPreferences(TestingPrefServiceSimple* pref_service) {
61 DCHECK(pref_service);
62
63 pref_service->registry()->RegisterDictionaryPref(
64 prefs::kDefaultTasksByMimeType);
65 pref_service->registry()->RegisterDictionaryPref(
66 prefs::kDefaultTasksBySuffix);
67 }
68
69 // Updates the default task preferences per the given dictionary values. Used
70 // for testing ChooseAndSetDefaultTask.
UpdateDefaultTaskPreferences(TestingPrefServiceSimple * pref_service,const base::DictionaryValue & mime_types,const base::DictionaryValue & suffixes)71 void UpdateDefaultTaskPreferences(TestingPrefServiceSimple* pref_service,
72 const base::DictionaryValue& mime_types,
73 const base::DictionaryValue& suffixes) {
74 DCHECK(pref_service);
75
76 pref_service->Set(prefs::kDefaultTasksByMimeType, mime_types);
77 pref_service->Set(prefs::kDefaultTasksBySuffix, suffixes);
78 }
79
80 } // namespace
81
TEST(FileManagerFileTasksTest,FullTaskDescriptor_WithIconAndDefault)82 TEST(FileManagerFileTasksTest, FullTaskDescriptor_WithIconAndDefault) {
83 FullTaskDescriptor full_descriptor(
84 TaskDescriptor("app-id", TASK_TYPE_FILE_BROWSER_HANDLER, "action-id"),
85 "task title", Verb::VERB_OPEN_WITH, GURL("http://example.com/icon.png"),
86 true /* is_default */, false /* is_generic_file_handler */,
87 false /* is_file_extension_match */);
88
89 const std::string task_id =
90 TaskDescriptorToId(full_descriptor.task_descriptor());
91 EXPECT_EQ("app-id|file|action-id", task_id);
92 EXPECT_EQ("http://example.com/icon.png", full_descriptor.icon_url().spec());
93 EXPECT_EQ("task title", full_descriptor.task_title());
94 EXPECT_EQ(Verb::VERB_OPEN_WITH, full_descriptor.task_verb());
95 EXPECT_TRUE(full_descriptor.is_default());
96 }
97
TEST(FileManagerFileTasksTest,MakeTaskID)98 TEST(FileManagerFileTasksTest, MakeTaskID) {
99 EXPECT_EQ("app-id|file|action-id",
100 MakeTaskID("app-id", TASK_TYPE_FILE_BROWSER_HANDLER, "action-id"));
101 EXPECT_EQ("app-id|app|action-id",
102 MakeTaskID("app-id", TASK_TYPE_FILE_HANDLER, "action-id"));
103 }
104
TEST(FileManagerFileTasksTest,TaskDescriptorToId)105 TEST(FileManagerFileTasksTest, TaskDescriptorToId) {
106 EXPECT_EQ("app-id|file|action-id",
107 TaskDescriptorToId(TaskDescriptor("app-id",
108 TASK_TYPE_FILE_BROWSER_HANDLER,
109 "action-id")));
110 }
111
TEST(FileManagerFileTasksTest,ParseTaskID_FileBrowserHandler)112 TEST(FileManagerFileTasksTest, ParseTaskID_FileBrowserHandler) {
113 TaskDescriptor task;
114 EXPECT_TRUE(ParseTaskID("app-id|file|action-id", &task));
115 EXPECT_EQ("app-id", task.app_id);
116 EXPECT_EQ(TASK_TYPE_FILE_BROWSER_HANDLER, task.task_type);
117 EXPECT_EQ("action-id", task.action_id);
118 }
119
TEST(FileManagerFileTasksTest,ParseTaskID_FileHandler)120 TEST(FileManagerFileTasksTest, ParseTaskID_FileHandler) {
121 TaskDescriptor task;
122 EXPECT_TRUE(ParseTaskID("app-id|app|action-id", &task));
123 EXPECT_EQ("app-id", task.app_id);
124 EXPECT_EQ(TASK_TYPE_FILE_HANDLER, task.task_type);
125 EXPECT_EQ("action-id", task.action_id);
126 }
127
TEST(FileManagerFileTasksTest,ParseTaskID_Legacy)128 TEST(FileManagerFileTasksTest, ParseTaskID_Legacy) {
129 TaskDescriptor task;
130 // A legacy task ID only has two parts. The task type should be
131 // TASK_TYPE_FILE_BROWSER_HANDLER.
132 EXPECT_TRUE(ParseTaskID("app-id|action-id", &task));
133 EXPECT_EQ("app-id", task.app_id);
134 EXPECT_EQ(TASK_TYPE_FILE_BROWSER_HANDLER, task.task_type);
135 EXPECT_EQ("action-id", task.action_id);
136 }
137
TEST(FileManagerFileTasksTest,ParseTaskID_Invalid)138 TEST(FileManagerFileTasksTest, ParseTaskID_Invalid) {
139 TaskDescriptor task;
140 EXPECT_FALSE(ParseTaskID("invalid", &task));
141 }
142
TEST(FileManagerFileTasksTest,ParseTaskID_UnknownTaskType)143 TEST(FileManagerFileTasksTest, ParseTaskID_UnknownTaskType) {
144 TaskDescriptor task;
145 EXPECT_FALSE(ParseTaskID("app-id|unknown|action-id", &task));
146 }
147
148 // Test that the right task is chosen from multiple choices per mime types
149 // and file extensions.
TEST(FileManagerFileTasksTest,ChooseAndSetDefaultTask_MultipleTasks)150 TEST(FileManagerFileTasksTest, ChooseAndSetDefaultTask_MultipleTasks) {
151 TestingPrefServiceSimple pref_service;
152 RegisterDefaultTaskPreferences(&pref_service);
153
154 // Text.app and Nice.app were found for "foo.txt".
155 TaskDescriptor text_app_task("text-app-id",
156 TASK_TYPE_FILE_HANDLER,
157 "action-id");
158 TaskDescriptor nice_app_task("nice-app-id",
159 TASK_TYPE_FILE_HANDLER,
160 "action-id");
161 std::vector<FullTaskDescriptor> tasks;
162 tasks.emplace_back(
163 text_app_task, "Text.app", Verb::VERB_OPEN_WITH,
164 GURL("http://example.com/text_app.png"), false /* is_default */,
165 false /* is_generic_file_handler */, false /* is_file_extension_match */);
166 tasks.emplace_back(
167 nice_app_task, "Nice.app", Verb::VERB_ADD_TO,
168 GURL("http://example.com/nice_app.png"), false /* is_default */,
169 false /* is_generic_file_handler */, false /* is_file_extension_match */);
170 std::vector<extensions::EntryInfo> entries;
171 entries.emplace_back(base::FilePath::FromUTF8Unsafe("foo.txt"), "text/plain",
172 false);
173
174 // None of them should be chosen as default, as nothing is set in the
175 // preferences.
176 ChooseAndSetDefaultTask(pref_service, entries, &tasks);
177 EXPECT_FALSE(tasks[0].is_default());
178 EXPECT_FALSE(tasks[1].is_default());
179
180 // Set Text.app as default for "text/plain" in the preferences.
181 base::DictionaryValue empty;
182 base::DictionaryValue mime_types;
183 mime_types.SetKey("text/plain",
184 base::Value(TaskDescriptorToId(text_app_task)));
185 UpdateDefaultTaskPreferences(&pref_service, mime_types, empty);
186
187 // Text.app should be chosen as default.
188 ChooseAndSetDefaultTask(pref_service, entries, &tasks);
189 EXPECT_TRUE(tasks[0].is_default());
190 EXPECT_FALSE(tasks[1].is_default());
191
192 // Change it back to non-default for testing further.
193 tasks[0].set_is_default(false);
194
195 // Clear the preferences and make sure none of them are default.
196 UpdateDefaultTaskPreferences(&pref_service, empty, empty);
197 ChooseAndSetDefaultTask(pref_service, entries, &tasks);
198 EXPECT_FALSE(tasks[0].is_default());
199 EXPECT_FALSE(tasks[1].is_default());
200
201 // Set Nice.app as default for ".txt" in the preferences.
202 base::DictionaryValue suffixes;
203 suffixes.SetKey(".txt", base::Value(TaskDescriptorToId(nice_app_task)));
204 UpdateDefaultTaskPreferences(&pref_service, empty, suffixes);
205
206 // Now Nice.app should be chosen as default.
207 ChooseAndSetDefaultTask(pref_service, entries, &tasks);
208 EXPECT_FALSE(tasks[0].is_default());
209 EXPECT_TRUE(tasks[1].is_default());
210 }
211
212 // Test that internal file browser handler of the Files app is chosen as
213 // default even if nothing is set in the preferences.
TEST(FileManagerFileTasksTest,ChooseAndSetDefaultTask_FallbackFileBrowser)214 TEST(FileManagerFileTasksTest, ChooseAndSetDefaultTask_FallbackFileBrowser) {
215 TestingPrefServiceSimple pref_service;
216 RegisterDefaultTaskPreferences(&pref_service);
217
218 // The internal file browser handler of the Files app was found for "foo.txt".
219 TaskDescriptor files_app_task(kFileManagerAppId,
220 TASK_TYPE_FILE_BROWSER_HANDLER,
221 "view-in-browser");
222 std::vector<FullTaskDescriptor> tasks;
223 tasks.emplace_back(
224 files_app_task, "View in browser", Verb::VERB_OPEN_WITH,
225 GURL("http://example.com/some_icon.png"), false /* is_default */,
226 false /* is_generic_file_handler */, false /* is_file_extension_match */);
227 std::vector<extensions::EntryInfo> entries;
228 entries.emplace_back(base::FilePath::FromUTF8Unsafe("foo.txt"), "text/plain",
229 false);
230
231 // The internal file browser handler should be chosen as default, as it's a
232 // fallback file browser handler.
233 ChooseAndSetDefaultTask(pref_service, entries, &tasks);
234 EXPECT_TRUE(tasks[0].is_default());
235 }
236
237 // Test that Text.app is chosen as default instead of the Files app
238 // even if nothing is set in the preferences.
TEST(FileManagerFileTasksTest,ChooseAndSetDefaultTask_FallbackTextApp)239 TEST(FileManagerFileTasksTest, ChooseAndSetDefaultTask_FallbackTextApp) {
240 TestingPrefServiceSimple pref_service;
241 RegisterDefaultTaskPreferences(&pref_service);
242
243 // Define the browser handler of the Files app for "foo.txt".
244 TaskDescriptor files_app_task(
245 kFileManagerAppId, TASK_TYPE_FILE_BROWSER_HANDLER, "view-in-browser");
246 // Define the text editor app for "foo.txt".
247 TaskDescriptor text_app_task(kTextEditorAppId, TASK_TYPE_FILE_HANDLER,
248 "Text");
249 std::vector<FullTaskDescriptor> tasks;
250 tasks.emplace_back(
251 files_app_task, "View in browser", Verb::VERB_OPEN_WITH,
252 GURL("http://example.com/some_icon.png"), false /* is_default */,
253 false /* is_generic_file_handler */, false /* is_file_extension_match */);
254 tasks.emplace_back(
255 text_app_task, "Text", Verb::VERB_OPEN_WITH,
256 GURL("chrome://extension-icon/mmfbcljfglbokpmkimbfghdkjmjhdgbg/16/1"),
257 false /* is_default */, false /* is_generic_file_handler */,
258 false /* is_file_extension_match */);
259 std::vector<extensions::EntryInfo> entries;
260 entries.emplace_back(base::FilePath::FromUTF8Unsafe("foo.txt"), "text/plain",
261 false);
262
263 // The text editor app should be chosen as default, as it's a fallback file
264 // browser handler.
265 ChooseAndSetDefaultTask(pref_service, entries, &tasks);
266 EXPECT_TRUE(tasks[1].is_default());
267 }
268
269 // Test that browser is chosen as default for HTML files instead of the Text
270 // app even if nothing is set in the preferences.
TEST(FileManagerFileTasksTest,ChooseAndSetDefaultTask_FallbackHtmlTextApp)271 TEST(FileManagerFileTasksTest, ChooseAndSetDefaultTask_FallbackHtmlTextApp) {
272 TestingPrefServiceSimple pref_service;
273 RegisterDefaultTaskPreferences(&pref_service);
274
275 // Define the browser handler of the Files app for "foo.html".
276 TaskDescriptor files_app_task(
277 kFileManagerAppId, TASK_TYPE_FILE_BROWSER_HANDLER, "view-in-browser");
278 // Define the text editor app for "foo.html".
279 TaskDescriptor text_app_task(kTextEditorAppId, TASK_TYPE_FILE_HANDLER,
280 "Text");
281 std::vector<FullTaskDescriptor> tasks;
282 tasks.emplace_back(
283 files_app_task, "View in browser", Verb::VERB_OPEN_WITH,
284 GURL("http://example.com/some_icon.png"), false /* is_default */,
285 false /* is_generic_file_handler */, false /* is_file_extension_match */);
286 tasks.emplace_back(
287 text_app_task, "Text", Verb::VERB_OPEN_WITH,
288 GURL("chrome://extension-icon/mmfbcljfglbokpmkimbfghdkjmjhdgbg/16/1"),
289 false /* is_default */, false /* is_generic_file_handler */,
290 false /* is_file_extension_match */);
291 std::vector<extensions::EntryInfo> entries;
292 entries.emplace_back(base::FilePath::FromUTF8Unsafe("foo.html"), "text/html",
293 false);
294
295 // The internal file browser handler should be chosen as default,
296 // as it's a fallback file browser handler.
297 ChooseAndSetDefaultTask(pref_service, entries, &tasks);
298 EXPECT_TRUE(tasks[0].is_default());
299 }
300
301 // Test that Audio Player is chosen as default even if nothing is set in the
302 // preferences.
TEST(FileManagerFileTasksTest,ChooseAndSetDefaultTask_FallbackAudioPlayer)303 TEST(FileManagerFileTasksTest, ChooseAndSetDefaultTask_FallbackAudioPlayer) {
304 TestingPrefServiceSimple pref_service;
305 RegisterDefaultTaskPreferences(&pref_service);
306
307 // The Audio Player app was found for "sound.wav".
308 TaskDescriptor files_app_task(kAudioPlayerAppId, TASK_TYPE_FILE_HANDLER,
309 "Audio Player");
310 std::vector<FullTaskDescriptor> tasks;
311 tasks.emplace_back(
312 files_app_task, "Audio Player", Verb::VERB_OPEN_WITH,
313 GURL("chrome://extension-icon/cjbfomnbifhcdnihkgipgfcihmgjfhbf/32/1"),
314 false /* is_default */, false /* is_generic_file_handler */,
315 false /* is_file_extension_match */);
316 std::vector<extensions::EntryInfo> entries;
317 entries.emplace_back(base::FilePath::FromUTF8Unsafe("sound.wav"), "audio/wav",
318 false);
319
320 // The Audio Player app should be chosen as default, as it's a fallback file
321 // browser handler.
322 ChooseAndSetDefaultTask(pref_service, entries, &tasks);
323 EXPECT_TRUE(tasks[0].is_default());
324 }
325
326 // Test that Office Editing is chosen as default even if nothing is set in the
327 // preferences.
TEST(FileManagerFileTasksTest,ChooseAndSetDefaultTask_FallbackOfficeEditing)328 TEST(FileManagerFileTasksTest, ChooseAndSetDefaultTask_FallbackOfficeEditing) {
329 TestingPrefServiceSimple pref_service;
330 RegisterDefaultTaskPreferences(&pref_service);
331
332 // The Office Editing app was found for "slides.pptx".
333 TaskDescriptor files_app_task(
334 extension_misc::kQuickOfficeComponentExtensionId, TASK_TYPE_FILE_HANDLER,
335 "Office Editing for Docs, Sheets & Slides");
336 std::vector<FullTaskDescriptor> tasks;
337 tasks.emplace_back(
338 files_app_task, "Office Editing for Docs, Sheets & Slides",
339 Verb::VERB_OPEN_WITH,
340 GURL("chrome://extension-icon/bpmcpldpdmajfigpchkicefoigmkfalc/32/1"),
341 false /* is_default */, false /* is_generic_file_handler */,
342 false /* is_file_extension_match */);
343 std::vector<extensions::EntryInfo> entries;
344 entries.emplace_back(base::FilePath::FromUTF8Unsafe("slides.pptx"), "",
345 false);
346
347 // The Office Editing app should be chosen as default, as it's a fallback
348 // file browser handler.
349 ChooseAndSetDefaultTask(pref_service, entries, &tasks);
350 EXPECT_TRUE(tasks[0].is_default());
351 }
352
353 // Test IsFileHandlerEnabled which returns whether a file handler should be
354 // used.
TEST(FileManagerFileTasksTest,IsFileHandlerEnabled)355 TEST(FileManagerFileTasksTest, IsFileHandlerEnabled) {
356 content::BrowserTaskEnvironment task_environment;
357 TestingProfile test_profile;
358 crostini::FakeCrostiniFeatures crostini_features;
359
360 apps::FileHandlerInfo test_handler;
361 test_handler.id = "test";
362
363 // Test import-crostini-image.
364 apps::FileHandlerInfo crostini_import_handler;
365 crostini_import_handler.id = "import-crostini-image";
366 crostini_features.set_export_import_ui_allowed(true);
367 EXPECT_TRUE(IsFileHandlerEnabled(&test_profile, crostini_import_handler));
368 EXPECT_TRUE(IsFileHandlerEnabled(&test_profile, test_handler));
369
370 crostini_features.set_export_import_ui_allowed(false);
371 EXPECT_FALSE(IsFileHandlerEnabled(&test_profile, crostini_import_handler));
372 EXPECT_TRUE(IsFileHandlerEnabled(&test_profile, test_handler));
373
374 // Test install-linux-package.
375 apps::FileHandlerInfo install_linux_handler;
376 install_linux_handler.id = "install-linux-package";
377 crostini_features.set_root_access_allowed(true);
378 EXPECT_TRUE(IsFileHandlerEnabled(&test_profile, install_linux_handler));
379 EXPECT_TRUE(IsFileHandlerEnabled(&test_profile, test_handler));
380
381 crostini_features.set_root_access_allowed(false);
382 EXPECT_FALSE(IsFileHandlerEnabled(&test_profile, install_linux_handler));
383 EXPECT_TRUE(IsFileHandlerEnabled(&test_profile, test_handler));
384 }
385
386 // Test IsGoodMatchFileHandler which returns whether a file handle info matches
387 // with files as good match or not.
TEST(FileManagerFileTasksTest,IsGoodMatchFileHandler)388 TEST(FileManagerFileTasksTest, IsGoodMatchFileHandler) {
389 using FileHandlerInfo = apps::FileHandlerInfo;
390
391 std::vector<extensions::EntryInfo> entries_1;
392 entries_1.emplace_back(base::FilePath(FILE_PATH_LITERAL("foo.jpg")),
393 "image/jpeg", false);
394 entries_1.emplace_back(base::FilePath(FILE_PATH_LITERAL("bar.txt")),
395 "text/plain", false);
396
397 std::vector<extensions::EntryInfo> entries_2;
398 entries_2.emplace_back(base::FilePath(FILE_PATH_LITERAL("foo.ics")),
399 "text/calendar", false);
400
401 // extensions: ["*"]
402 FileHandlerInfo file_handler_info_1;
403 file_handler_info_1.extensions.insert("*");
404 EXPECT_FALSE(IsGoodMatchFileHandler(file_handler_info_1, entries_1));
405
406 // extensions: ["*", "jpg"]
407 FileHandlerInfo file_handler_info_2;
408 file_handler_info_2.extensions.insert("*");
409 file_handler_info_2.extensions.insert("jpg");
410 EXPECT_FALSE(IsGoodMatchFileHandler(file_handler_info_2, entries_1));
411
412 // extensions: ["jpg"]
413 FileHandlerInfo file_handler_info_3;
414 file_handler_info_3.extensions.insert("jpg");
415 EXPECT_TRUE(IsGoodMatchFileHandler(file_handler_info_3, entries_1));
416
417 // types: ["*"]
418 FileHandlerInfo file_handler_info_4;
419 file_handler_info_4.types.insert("*");
420 EXPECT_FALSE(IsGoodMatchFileHandler(file_handler_info_4, entries_1));
421
422 // types: ["*/*"]
423 FileHandlerInfo file_handler_info_5;
424 file_handler_info_5.types.insert("*/*");
425 EXPECT_FALSE(IsGoodMatchFileHandler(file_handler_info_5, entries_1));
426
427 // types: ["image/*"]
428 FileHandlerInfo file_handler_info_6;
429 file_handler_info_6.types.insert("image/*");
430 // Partial wild card is not generic.
431 EXPECT_TRUE(IsGoodMatchFileHandler(file_handler_info_6, entries_1));
432
433 // types: ["*", "image/*"]
434 FileHandlerInfo file_handler_info_7;
435 file_handler_info_7.types.insert("*");
436 file_handler_info_7.types.insert("image/*");
437 EXPECT_FALSE(IsGoodMatchFileHandler(file_handler_info_7, entries_1));
438
439 // extensions: ["*"], types: ["image/*"]
440 FileHandlerInfo file_handler_info_8;
441 file_handler_info_8.extensions.insert("*");
442 file_handler_info_8.types.insert("image/*");
443 EXPECT_FALSE(IsGoodMatchFileHandler(file_handler_info_8, entries_1));
444
445 // types: ["text/*"] and target files contain unsupported text mime type, e.g.
446 // text/calendar.
447 FileHandlerInfo file_handler_info_9;
448 file_handler_info_9.types.insert("text/*");
449 EXPECT_FALSE(IsGoodMatchFileHandler(file_handler_info_9, entries_2));
450
451 // types: ["text/*"] and target files don't contain unsupported text mime
452 // type.
453 FileHandlerInfo file_handler_info_10;
454 file_handler_info_10.types.insert("text/*");
455 EXPECT_TRUE(IsGoodMatchFileHandler(file_handler_info_10, entries_1));
456
457 // path_directory_set not empty.
458 FileHandlerInfo file_handler_info_11;
459 std::vector<extensions::EntryInfo> entries_3;
460 entries_3.emplace_back(base::FilePath(FILE_PATH_LITERAL("dir1")), "", true);
461 EXPECT_FALSE(IsGoodMatchFileHandler(file_handler_info_11, entries_3));
462 }
463
464 // Test IsGoodMatchAppsFileHandler, which returns whether an apps::FileHandler
465 // is capable of handling all of a set of files.
TEST(FileManagerFileTasksTest,IsGoodMatchAppsFileHandler)466 TEST(FileManagerFileTasksTest, IsGoodMatchAppsFileHandler) {
467 std::vector<extensions::EntryInfo> entries_1;
468 entries_1.emplace_back(base::FilePath(FILE_PATH_LITERAL("foo.jpg")),
469 "image/jpeg", false);
470 entries_1.emplace_back(base::FilePath(FILE_PATH_LITERAL("bar.txt")),
471 "text/plain", false);
472
473 std::vector<extensions::EntryInfo> entries_2;
474 entries_2.emplace_back(base::FilePath(FILE_PATH_LITERAL("foo.ics")),
475 "text/calendar", false);
476
477 // file_extensions: ["*"]
478 {
479 apps::FileHandler file_handler;
480 apps::FileHandler::AcceptEntry accept_entry;
481 accept_entry.file_extensions.insert("*");
482 file_handler.accept.push_back(accept_entry);
483 EXPECT_FALSE(IsGoodMatchAppsFileHandler(file_handler, entries_1));
484 }
485
486 // file_extensions: ["*", ".jpg"]
487 {
488 apps::FileHandler file_handler;
489 apps::FileHandler::AcceptEntry accept_entry;
490 accept_entry.file_extensions.insert("*");
491 accept_entry.file_extensions.insert(".jpg");
492 file_handler.accept.push_back(accept_entry);
493 EXPECT_FALSE(IsGoodMatchAppsFileHandler(file_handler, entries_1));
494 }
495
496 // file_extensions: [".jpg"]
497 {
498 apps::FileHandler file_handler;
499 apps::FileHandler::AcceptEntry accept_entry;
500 accept_entry.file_extensions.insert(".jpg");
501 file_handler.accept.push_back(accept_entry);
502 EXPECT_TRUE(IsGoodMatchAppsFileHandler(file_handler, entries_1));
503 }
504
505 // mime_type: "*"
506 {
507 apps::FileHandler file_handler;
508 apps::FileHandler::AcceptEntry accept_entry;
509 accept_entry.mime_type = "*";
510 file_handler.accept.push_back(accept_entry);
511 EXPECT_FALSE(IsGoodMatchAppsFileHandler(file_handler, entries_1));
512 }
513
514 // mime_type: "*/*"
515 {
516 apps::FileHandler file_handler;
517 apps::FileHandler::AcceptEntry accept_entry;
518 accept_entry.mime_type = "*/*";
519 file_handler.accept.push_back(accept_entry);
520 EXPECT_FALSE(IsGoodMatchAppsFileHandler(file_handler, entries_1));
521 }
522
523 // mime_type: "image/*"
524 {
525 apps::FileHandler file_handler;
526 apps::FileHandler::AcceptEntry accept_entry;
527 accept_entry.mime_type = "image/*";
528 file_handler.accept.push_back(accept_entry);
529 // Partial wild card is not generic.
530 EXPECT_TRUE(IsGoodMatchAppsFileHandler(file_handler, entries_1));
531 }
532
533 // mime_type: "*" and "image/*"
534 {
535 apps::FileHandler file_handler;
536 apps::FileHandler::AcceptEntry accept_entry_1;
537 accept_entry_1.mime_type = "*";
538 file_handler.accept.push_back(accept_entry_1);
539 apps::FileHandler::AcceptEntry accept_entry_2;
540 accept_entry_2.mime_type = "image/*";
541 file_handler.accept.push_back(accept_entry_2);
542 EXPECT_FALSE(IsGoodMatchAppsFileHandler(file_handler, entries_1));
543 }
544
545 // file_extensions: ["*"], mime_type: ["image/*"]
546 {
547 apps::FileHandler file_handler;
548 apps::FileHandler::AcceptEntry accept_entry;
549 accept_entry.mime_type = "image/*";
550 accept_entry.file_extensions.insert("*");
551 file_handler.accept.push_back(accept_entry);
552 EXPECT_FALSE(IsGoodMatchAppsFileHandler(file_handler, entries_1));
553 }
554
555 // mime_type: "text/*" and target files contain unsupported text MIME type
556 // (e.g. text/calendar).
557 {
558 apps::FileHandler file_handler;
559 apps::FileHandler::AcceptEntry accept_entry;
560 accept_entry.mime_type = "text/*";
561 file_handler.accept.push_back(accept_entry);
562 EXPECT_FALSE(IsGoodMatchAppsFileHandler(file_handler, entries_2));
563 }
564
565 // mime_type: "text/*" and target files don't contain unsupported text MIME
566 // type.
567 {
568 apps::FileHandler file_handler;
569 apps::FileHandler::AcceptEntry accept_entry;
570 accept_entry.mime_type = "text/*";
571 file_handler.accept.push_back(accept_entry);
572 EXPECT_TRUE(IsGoodMatchAppsFileHandler(file_handler, entries_1));
573 }
574
575 // path_directory_set not empty.
576 {
577 apps::FileHandler file_handler;
578 std::vector<extensions::EntryInfo> entries_3;
579 entries_3.emplace_back(base::FilePath(FILE_PATH_LITERAL("dir1")), "", true);
580 EXPECT_FALSE(IsGoodMatchAppsFileHandler(file_handler, entries_3));
581 }
582 }
583
584 // Test using the test extension system, which needs lots of setup.
585 class FileManagerFileTasksComplexTest : public testing::Test {
586 protected:
FileManagerFileTasksComplexTest()587 FileManagerFileTasksComplexTest()
588 : command_line_(base::CommandLine::NO_PROGRAM),
589 extension_service_(nullptr) {
590 extensions::TestExtensionSystem* test_extension_system =
591 static_cast<extensions::TestExtensionSystem*>(
592 extensions::ExtensionSystem::Get(&test_profile_));
593 extension_service_ = test_extension_system->CreateExtensionService(
594 &command_line_,
595 base::FilePath() /* install_directory */,
596 false /* autoupdate_enabled*/);
597 }
598
599 // Helper class for calling FindAllTypesOfTask synchronously.
600 class FindAllTypesOfTasksSynchronousWrapper {
601 public:
Call(Profile * profile,const std::vector<extensions::EntryInfo> & entries,const std::vector<GURL> & file_urls,std::vector<FullTaskDescriptor> * result)602 void Call(Profile* profile,
603 const std::vector<extensions::EntryInfo>& entries,
604 const std::vector<GURL>& file_urls,
605 std::vector<FullTaskDescriptor>* result) {
606 FindAllTypesOfTasks(
607 profile, entries, file_urls,
608 base::BindOnce(&FindAllTypesOfTasksSynchronousWrapper::OnReply,
609 base::Unretained(this), result));
610 run_loop_.Run();
611 }
612
613 private:
OnReply(std::vector<FullTaskDescriptor> * out,std::unique_ptr<std::vector<FullTaskDescriptor>> result)614 void OnReply(std::vector<FullTaskDescriptor>* out,
615 std::unique_ptr<std::vector<FullTaskDescriptor>> result) {
616 *out = *result;
617 run_loop_.Quit();
618 }
619
620 base::RunLoop run_loop_;
621 };
622
623 content::BrowserTaskEnvironment task_environment_;
624 chromeos::ScopedCrosSettingsTestHelper cros_settings_test_helper_;
625 chromeos::ScopedTestUserManager test_user_manager_;
626 TestingProfile test_profile_;
627 base::CommandLine command_line_;
628 extensions::ExtensionService* extension_service_; // Owned by test_profile_;
629 };
630
TEST_F(FileManagerFileTasksComplexTest,FindFileHandlerTasks)631 TEST_F(FileManagerFileTasksComplexTest, FindFileHandlerTasks) {
632 // Random IDs generated by
633 // % ruby -le 'print (0...32).to_a.map{(?a + rand(16)).chr}.join'
634 const char kFooId[] = "hhgbjpmdppecanaaogonaigmmifgpaph";
635 const char kBarId[] = "odlhccgofgkadkkhcmhgnhgahonahoca";
636
637 // Foo.app can handle "text/plain" and "text/html".
638 extensions::ExtensionBuilder foo_app;
639 foo_app.SetManifest(
640 extensions::DictionaryBuilder()
641 .Set("name", "Foo")
642 .Set("version", "1.0.0")
643 .Set("manifest_version", 2)
644 .Set("app", extensions::DictionaryBuilder()
645 .Set("background",
646 extensions::DictionaryBuilder()
647 .Set("scripts", extensions::ListBuilder()
648 .Append("background.js")
649 .Build())
650 .Build())
651 .Build())
652 .Set("file_handlers",
653 extensions::DictionaryBuilder()
654 .Set("text", extensions::DictionaryBuilder()
655 .Set("title", "Text")
656 .Set("types", extensions::ListBuilder()
657 .Append("text/plain")
658 .Append("text/html")
659 .Build())
660 .Build())
661 .Build())
662 .Build());
663 foo_app.SetID(kFooId);
664 extension_service_->AddExtension(foo_app.Build().get());
665
666 // Bar.app can only handle "text/plain".
667 extensions::ExtensionBuilder bar_app;
668 bar_app.SetManifest(
669 extensions::DictionaryBuilder()
670 .Set("name", "Bar")
671 .Set("version", "1.0.0")
672 .Set("manifest_version", 2)
673 .Set("app", extensions::DictionaryBuilder()
674 .Set("background",
675 extensions::DictionaryBuilder()
676 .Set("scripts", extensions::ListBuilder()
677 .Append("background.js")
678 .Build())
679 .Build())
680 .Build())
681 .Set("file_handlers",
682 extensions::DictionaryBuilder()
683 .Set("text", extensions::DictionaryBuilder()
684 .Set("title", "Text")
685 .Set("types", extensions::ListBuilder()
686 .Append("text/plain")
687 .Build())
688 .Build())
689 .Build())
690 .Build());
691 bar_app.SetID(kBarId);
692 extension_service_->AddExtension(bar_app.Build().get());
693
694 // Find apps for a "text/plain" file. Foo.app and Bar.app should be found.
695 std::vector<extensions::EntryInfo> entries;
696 entries.emplace_back(
697 util::GetMyFilesFolderForProfile(&test_profile_).AppendASCII("foo.txt"),
698 "text/plain", false);
699
700 std::vector<FullTaskDescriptor> tasks;
701 FindFileHandlerTasks(&test_profile_, entries, &tasks);
702 ASSERT_EQ(2U, tasks.size());
703 // Sort the app IDs, as the order is not guaranteed.
704 std::vector<std::string> app_ids;
705 app_ids.push_back(tasks[0].task_descriptor().app_id);
706 app_ids.push_back(tasks[1].task_descriptor().app_id);
707 std::sort(app_ids.begin(), app_ids.end());
708 // Confirm that both Foo.app and Bar.app are found.
709 EXPECT_EQ(kFooId, app_ids[0]);
710 EXPECT_EQ(kBarId, app_ids[1]);
711
712 // Find apps for "text/plain" and "text/html" files. Only Foo.app should be
713 // found.
714 entries.clear();
715 entries.emplace_back(
716 util::GetMyFilesFolderForProfile(&test_profile_).AppendASCII("foo.txt"),
717 "text/plain", false);
718 entries.emplace_back(
719 util::GetMyFilesFolderForProfile(&test_profile_).AppendASCII("foo.html"),
720 "text/html", false);
721 tasks.clear();
722 FindFileHandlerTasks(&test_profile_, entries, &tasks);
723 ASSERT_EQ(1U, tasks.size());
724 // Confirm that only Foo.app is found.
725 EXPECT_EQ(kFooId, tasks[0].task_descriptor().app_id);
726
727 // Add an "image/png" file. No tasks should be found.
728 entries.emplace_back(base::FilePath::FromUTF8Unsafe("foo.png"), "image/png",
729 false);
730 tasks.clear();
731 FindFileHandlerTasks(&test_profile_, entries, &tasks);
732 // Confirm no tasks are found.
733 ASSERT_TRUE(tasks.empty());
734 }
735
TEST_F(FileManagerFileTasksComplexTest,BookmarkAppsAreNotListedInFileHandlerTasks)736 TEST_F(FileManagerFileTasksComplexTest,
737 BookmarkAppsAreNotListedInFileHandlerTasks) {
738 const char kGraphrId[] = "ppcpljkgngnngojbghcdiojhbneibgdg";
739 const char kGraphrFileAction[] = "https://graphr.tld/open-files/?name=raw";
740 extensions::ExtensionBuilder graphr;
741 graphr.SetManifest(
742 extensions::DictionaryBuilder()
743 .Set("name", "Graphr")
744 .Set("version", "1.0.0")
745 .Set("manifest_version", 2)
746 .Set("app",
747 extensions::DictionaryBuilder()
748 .Set("launch", extensions::DictionaryBuilder()
749 .Set("web_url", "https://graphr.tld")
750 .Build())
751 .Build())
752 .Set(
753 "file_handlers",
754 extensions::DictionaryBuilder()
755 .Set(kGraphrFileAction,
756 extensions::DictionaryBuilder()
757 .Set("title", "Raw")
758 .Set("types", extensions::ListBuilder()
759 .Append("text/csv")
760 .Build())
761 .Set("extensions",
762 extensions::ListBuilder().Append("csv").Build())
763 .Build())
764 .Build())
765 .Build());
766 graphr.SetID(kGraphrId);
767 graphr.AddFlags(extensions::Extension::InitFromValueFlags::FROM_BOOKMARK);
768
769 extension_service_->AddExtension(graphr.Build().get());
770 extensions::ExtensionRegistry* registry =
771 extensions::ExtensionRegistry::Get(&test_profile_);
772 const extensions::Extension* extension = registry->GetExtensionById(
773 kGraphrId, extensions::ExtensionRegistry::ENABLED);
774
775 ASSERT_EQ(extension->GetType(), extensions::Manifest::Type::TYPE_HOSTED_APP);
776 ASSERT_TRUE(extension->from_bookmark());
777
778 std::vector<FullTaskDescriptor> tasks;
779 std::vector<extensions::EntryInfo> entries;
780 entries.emplace_back(
781 util::GetMyFilesFolderForProfile(&test_profile_).AppendASCII("foo.csv"),
782 "text/csv", false);
783
784 base::test::ScopedFeatureList scoped_feature_list;
785 scoped_feature_list.InitWithFeatures({blink::features::kFileHandlingAPI}, {});
786 FindFileHandlerTasks(&test_profile_, entries, &tasks);
787 EXPECT_EQ(0u, tasks.size());
788 }
789
790 // The basic logic is similar to a test case for FindFileHandlerTasks above.
TEST_F(FileManagerFileTasksComplexTest,FindFileBrowserHandlerTasks)791 TEST_F(FileManagerFileTasksComplexTest, FindFileBrowserHandlerTasks) {
792 // Copied from FindFileHandlerTasks test above.
793 const char kFooId[] = "hhgbjpmdppecanaaogonaigmmifgpaph";
794 const char kBarId[] = "odlhccgofgkadkkhcmhgnhgahonahoca";
795
796 // Foo.app can handle ".txt" and ".html".
797 // This one is an extension, and has "file_browser_handlers"
798 extensions::ExtensionBuilder foo_app;
799 foo_app.SetManifest(
800 extensions::DictionaryBuilder()
801 .Set("name", "Foo")
802 .Set("version", "1.0.0")
803 .Set("manifest_version", 2)
804 .Set("permissions",
805 extensions::ListBuilder().Append("fileBrowserHandler").Build())
806 .Set("file_browser_handlers",
807 extensions::ListBuilder()
808 .Append(
809 extensions::DictionaryBuilder()
810 .Set("id", "open")
811 .Set("default_title", "open")
812 .Set("file_filters", extensions::ListBuilder()
813 .Append("filesystem:*.txt")
814 .Append("filesystem:*.html")
815 .Build())
816 .Build())
817 .Build())
818 .Build());
819 foo_app.SetID(kFooId);
820 extension_service_->AddExtension(foo_app.Build().get());
821
822 // Bar.app can only handle ".txt".
823 extensions::ExtensionBuilder bar_app;
824 bar_app.SetManifest(
825 extensions::DictionaryBuilder()
826 .Set("name", "Bar")
827 .Set("version", "1.0.0")
828 .Set("manifest_version", 2)
829 .Set("permissions",
830 extensions::ListBuilder().Append("fileBrowserHandler").Build())
831 .Set("file_browser_handlers",
832 extensions::ListBuilder()
833 .Append(
834 extensions::DictionaryBuilder()
835 .Set("id", "open")
836 .Set("default_title", "open")
837 .Set("file_filters", extensions::ListBuilder()
838 .Append("filesystem:*.txt")
839 .Build())
840 .Build())
841 .Build())
842 .Build());
843 bar_app.SetID(kBarId);
844 extension_service_->AddExtension(bar_app.Build().get());
845
846 // Find apps for a ".txt" file. Foo.app and Bar.app should be found.
847 std::vector<GURL> file_urls;
848 file_urls.emplace_back("filesystem:chrome-extension://id/dir/foo.txt");
849
850 std::vector<FullTaskDescriptor> tasks;
851 FindFileBrowserHandlerTasks(&test_profile_, file_urls, &tasks);
852 ASSERT_EQ(2U, tasks.size());
853 // Sort the app IDs, as the order is not guaranteed.
854 std::vector<std::string> app_ids;
855 app_ids.push_back(tasks[0].task_descriptor().app_id);
856 app_ids.push_back(tasks[1].task_descriptor().app_id);
857 std::sort(app_ids.begin(), app_ids.end());
858 // Confirm that both Foo.app and Bar.app are found.
859 EXPECT_EQ(kFooId, app_ids[0]);
860 EXPECT_EQ(kBarId, app_ids[1]);
861
862 // Find apps for ".txt" and ".html" files. Only Foo.app should be found.
863 file_urls.clear();
864 file_urls.emplace_back("filesystem:chrome-extension://id/dir/foo.txt");
865 file_urls.emplace_back("filesystem:chrome-extension://id/dir/foo.html");
866 tasks.clear();
867 FindFileBrowserHandlerTasks(&test_profile_, file_urls, &tasks);
868 ASSERT_EQ(1U, tasks.size());
869 // Confirm that only Foo.app is found.
870 EXPECT_EQ(kFooId, tasks[0].task_descriptor().app_id);
871
872 // Add an ".png" file. No tasks should be found.
873 file_urls.emplace_back("filesystem:chrome-extension://id/dir/foo.png");
874 tasks.clear();
875 FindFileBrowserHandlerTasks(&test_profile_, file_urls, &tasks);
876 // Confirm no tasks are found.
877 ASSERT_TRUE(tasks.empty());
878 }
879
880 // Test that all kinds of apps (file handler and file browser handler) are
881 // returned.
TEST_F(FileManagerFileTasksComplexTest,FindAllTypesOfTasks)882 TEST_F(FileManagerFileTasksComplexTest, FindAllTypesOfTasks) {
883 // kFooId and kBarId copied from FindFileHandlerTasks test above.
884 const char kFooId[] = "hhgbjpmdppecanaaogonaigmmifgpaph";
885 const char kBarId[] = "odlhccgofgkadkkhcmhgnhgahonahoca";
886
887 // Foo.app can handle "text/plain".
888 // This is a packaged app (file handler).
889 extensions::ExtensionBuilder foo_app;
890 foo_app.SetManifest(
891 extensions::DictionaryBuilder()
892 .Set("name", "Foo")
893 .Set("version", "1.0.0")
894 .Set("manifest_version", 2)
895 .Set("app", extensions::DictionaryBuilder()
896 .Set("background",
897 extensions::DictionaryBuilder()
898 .Set("scripts", extensions::ListBuilder()
899 .Append("background.js")
900 .Build())
901 .Build())
902 .Build())
903 .Set("file_handlers",
904 extensions::DictionaryBuilder()
905 .Set("text", extensions::DictionaryBuilder()
906 .Set("title", "Text")
907 .Set("types", extensions::ListBuilder()
908 .Append("text/plain")
909 .Build())
910 .Build())
911 .Build())
912 .Build());
913 foo_app.SetID(kFooId);
914 extension_service_->AddExtension(foo_app.Build().get());
915
916 // Bar.app can only handle ".txt".
917 // This is an extension (file browser handler).
918 extensions::ExtensionBuilder bar_app;
919 bar_app.SetManifest(
920 extensions::DictionaryBuilder()
921 .Set("name", "Bar")
922 .Set("version", "1.0.0")
923 .Set("manifest_version", 2)
924 .Set("permissions",
925 extensions::ListBuilder().Append("fileBrowserHandler").Build())
926 .Set("file_browser_handlers",
927 extensions::ListBuilder()
928 .Append(
929 extensions::DictionaryBuilder()
930 .Set("id", "open")
931 .Set("default_title", "open")
932 .Set("file_filters", extensions::ListBuilder()
933 .Append("filesystem:*.txt")
934 .Build())
935 .Build())
936 .Build())
937 .Build());
938 bar_app.SetID(kBarId);
939 extension_service_->AddExtension(bar_app.Build().get());
940
941 // Find apps for "foo.txt". All apps should be found.
942 std::vector<extensions::EntryInfo> entries;
943 std::vector<GURL> file_urls;
944 entries.emplace_back(
945 util::GetMyFilesFolderForProfile(&test_profile_).AppendASCII("foo.txt"),
946 "text/plain", false);
947 file_urls.emplace_back("filesystem:chrome-extension://id/dir/foo.txt");
948
949 std::vector<FullTaskDescriptor> tasks;
950 FindAllTypesOfTasksSynchronousWrapper().Call(&test_profile_, entries,
951 file_urls, &tasks);
952 ASSERT_EQ(2U, tasks.size());
953
954 // Sort the app IDs, as the order is not guaranteed.
955 std::vector<std::string> app_ids;
956 app_ids.push_back(tasks[0].task_descriptor().app_id);
957 app_ids.push_back(tasks[1].task_descriptor().app_id);
958 std::sort(app_ids.begin(), app_ids.end());
959 // Confirm that all apps are found.
960 EXPECT_EQ(kFooId, app_ids[0]);
961 EXPECT_EQ(kBarId, app_ids[1]);
962 }
963
TEST_F(FileManagerFileTasksComplexTest,FindAllTypesOfTasks_GoogleDocument)964 TEST_F(FileManagerFileTasksComplexTest, FindAllTypesOfTasks_GoogleDocument) {
965 // kFooId and kBarId copied from FindFileHandlerTasks test above.
966 const char kBarId[] = "odlhccgofgkadkkhcmhgnhgahonahoca";
967
968 // Bar.app can handle ".gdoc" files.
969 // This is an extension (file browser handler).
970 extensions::ExtensionBuilder bar_app;
971 bar_app.SetManifest(
972 extensions::DictionaryBuilder()
973 .Set("name", "Bar")
974 .Set("version", "1.0.0")
975 .Set("manifest_version", 2)
976 .Set("permissions",
977 extensions::ListBuilder().Append("fileBrowserHandler").Build())
978 .Set("file_browser_handlers",
979 extensions::ListBuilder()
980 .Append(
981 extensions::DictionaryBuilder()
982 .Set("id", "open")
983 .Set("default_title", "open")
984 .Set("file_filters", extensions::ListBuilder()
985 .Append("filesystem:*.gdoc")
986 .Build())
987 .Build())
988 .Build())
989 .Build());
990 bar_app.SetID(kBarId);
991 extension_service_->AddExtension(bar_app.Build().get());
992
993 // The Files app can handle ".gdoc" files.
994 // The ID "kFileManagerAppId" used here is precisely the one that identifies
995 // the Chrome OS Files app application.
996 extensions::ExtensionBuilder files_app;
997 files_app.SetManifest(
998 extensions::DictionaryBuilder()
999 .Set("name", "Files")
1000 .Set("version", "1.0.0")
1001 .Set("manifest_version", 2)
1002 .Set("permissions",
1003 extensions::ListBuilder().Append("fileBrowserHandler").Build())
1004 .Set("file_browser_handlers",
1005 extensions::ListBuilder()
1006 .Append(
1007 extensions::DictionaryBuilder()
1008 .Set("id", "open")
1009 .Set("default_title", "open")
1010 .Set("file_filters", extensions::ListBuilder()
1011 .Append("filesystem:*.gdoc")
1012 .Build())
1013 .Build())
1014 .Build())
1015 .Build());
1016 files_app.SetID(kFileManagerAppId);
1017 extension_service_->AddExtension(files_app.Build().get());
1018
1019 // Find apps for a ".gdoc file". Only the built-in handler of the Files apps
1020 // should be found.
1021 std::vector<extensions::EntryInfo> entries;
1022 std::vector<GURL> file_urls;
1023 entries.emplace_back(
1024 util::GetMyFilesFolderForProfile(&test_profile_).AppendASCII("foo.gdoc"),
1025 "application/vnd.google-apps.document", false);
1026 file_urls.emplace_back("filesystem:chrome-extension://id/dir/foo.gdoc");
1027
1028 std::vector<FullTaskDescriptor> tasks;
1029 FindAllTypesOfTasksSynchronousWrapper().Call(&test_profile_, entries,
1030 file_urls, &tasks);
1031 ASSERT_EQ(1U, tasks.size());
1032 EXPECT_EQ(kFileManagerAppId, tasks[0].task_descriptor().app_id);
1033 }
1034
TEST_F(FileManagerFileTasksComplexTest,FindFileHandlerTask_Generic)1035 TEST_F(FileManagerFileTasksComplexTest, FindFileHandlerTask_Generic) {
1036 // Since we want to keep the order of the result as foo,bar,baz,qux,
1037 // keep the ids in alphabetical order.
1038 const char kFooId[] = "hhgbjpmdppecanaaogonaigmmifgpaph";
1039 const char kBarId[] = "odlhccgofgkadkkhcmhgnhgahonahoca";
1040 const char kBazId[] = "plifkpkakemokpflgbnnigcoldgcbdmc";
1041 const char kQuxId[] = "pmifkpkakgkadkkhcmhgnigmmifgpaph";
1042
1043 // Foo app provides file handler for text/plain and all file types.
1044 extensions::ExtensionBuilder foo_app;
1045 foo_app.SetManifest(
1046 extensions::DictionaryBuilder()
1047 .Set("name", "Foo")
1048 .Set("version", "1.0.0")
1049 .Set("manifest_version", 2)
1050 .Set("app", extensions::DictionaryBuilder()
1051 .Set("background",
1052 extensions::DictionaryBuilder()
1053 .Set("scripts", extensions::ListBuilder()
1054 .Append("background.js")
1055 .Build())
1056 .Build())
1057 .Build())
1058 .Set(
1059 "file_handlers",
1060 extensions::DictionaryBuilder()
1061 .Set("any",
1062 extensions::DictionaryBuilder()
1063 .Set("types",
1064 extensions::ListBuilder().Append("*/*").Build())
1065 .Build())
1066 .Set("text", extensions::DictionaryBuilder()
1067 .Set("types", extensions::ListBuilder()
1068 .Append("text/plain")
1069 .Build())
1070 .Build())
1071 .Build())
1072 .Build());
1073 foo_app.SetID(kFooId);
1074 extension_service_->AddExtension(foo_app.Build().get());
1075
1076 // Bar app provides file handler for .txt and not provide generic file
1077 // handler, but handles directories.
1078 extensions::ExtensionBuilder bar_app;
1079 bar_app.SetManifest(
1080 extensions::DictionaryBuilder()
1081 .Set("name", "Bar")
1082 .Set("version", "1.0.0")
1083 .Set("manifest_version", 2)
1084 .Set("app", extensions::DictionaryBuilder()
1085 .Set("background",
1086 extensions::DictionaryBuilder()
1087 .Set("scripts", extensions::ListBuilder()
1088 .Append("background.js")
1089 .Build())
1090 .Build())
1091 .Build())
1092 .Set(
1093 "file_handlers",
1094 extensions::DictionaryBuilder()
1095 .Set("text",
1096 extensions::DictionaryBuilder()
1097 .Set("include_directories", true)
1098 .Set("extensions",
1099 extensions::ListBuilder().Append("txt").Build())
1100 .Build())
1101 .Build())
1102 .Build());
1103 bar_app.SetID(kBarId);
1104 extension_service_->AddExtension(bar_app.Build().get());
1105
1106 // Baz app provides file handler for all extensions and images.
1107 extensions::ExtensionBuilder baz_app;
1108 baz_app.SetManifest(
1109 extensions::DictionaryBuilder()
1110 .Set("name", "Baz")
1111 .Set("version", "1.0.0")
1112 .Set("manifest_version", 2)
1113 .Set("app", extensions::DictionaryBuilder()
1114 .Set("background",
1115 extensions::DictionaryBuilder()
1116 .Set("scripts", extensions::ListBuilder()
1117 .Append("background.js")
1118 .Build())
1119 .Build())
1120 .Build())
1121 .Set("file_handlers",
1122 extensions::DictionaryBuilder()
1123 .Set("any", extensions::DictionaryBuilder()
1124 .Set("extensions", extensions::ListBuilder()
1125 .Append("*")
1126 .Append("bar")
1127 .Build())
1128 .Build())
1129 .Set("image", extensions::DictionaryBuilder()
1130 .Set("types", extensions::ListBuilder()
1131 .Append("image/*")
1132 .Build())
1133 .Build())
1134 .Build())
1135 .Build());
1136 baz_app.SetID(kBazId);
1137 extension_service_->AddExtension(baz_app.Build().get());
1138
1139 // Qux app provides file handler for all types.
1140 extensions::ExtensionBuilder qux_app;
1141 qux_app.SetManifest(
1142 extensions::DictionaryBuilder()
1143 .Set("name", "Qux")
1144 .Set("version", "1.0.0")
1145 .Set("manifest_version", 2)
1146 .Set("app", extensions::DictionaryBuilder()
1147 .Set("background",
1148 extensions::DictionaryBuilder()
1149 .Set("scripts", extensions::ListBuilder()
1150 .Append("background.js")
1151 .Build())
1152 .Build())
1153 .Build())
1154 .Set("file_handlers",
1155 extensions::DictionaryBuilder()
1156 .Set("any",
1157 extensions::DictionaryBuilder()
1158 .Set("types",
1159 extensions::ListBuilder().Append("*").Build())
1160 .Build())
1161 .Build())
1162 .Build());
1163 qux_app.SetID(kQuxId);
1164 extension_service_->AddExtension(qux_app.Build().get());
1165
1166 // Test case with .txt file
1167 std::vector<extensions::EntryInfo> txt_entries;
1168 txt_entries.emplace_back(
1169 util::GetMyFilesFolderForProfile(&test_profile_).AppendASCII("foo.txt"),
1170 "text/plain", false);
1171 std::vector<FullTaskDescriptor> txt_result;
1172 FindFileHandlerTasks(&test_profile_, txt_entries, &txt_result);
1173 EXPECT_EQ(4U, txt_result.size());
1174 // Foo app provides a handler for text/plain.
1175 EXPECT_EQ("Foo", txt_result[0].task_title());
1176 EXPECT_FALSE(txt_result[0].is_generic_file_handler());
1177 // Bar app provides a handler for .txt.
1178 EXPECT_EQ("Bar", txt_result[1].task_title());
1179 EXPECT_FALSE(txt_result[1].is_generic_file_handler());
1180 // Baz app provides a handler for all extensions.
1181 EXPECT_EQ("Baz", txt_result[2].task_title());
1182 EXPECT_TRUE(txt_result[2].is_generic_file_handler());
1183 // Qux app provides a handler for all types.
1184 EXPECT_EQ("Qux", txt_result[3].task_title());
1185 EXPECT_TRUE(txt_result[3].is_generic_file_handler());
1186
1187 // Test case with .jpg file
1188 std::vector<extensions::EntryInfo> jpg_entries;
1189 jpg_entries.emplace_back(
1190 util::GetMyFilesFolderForProfile(&test_profile_).AppendASCII("foo.jpg"),
1191 "image/jpeg", false);
1192 std::vector<FullTaskDescriptor> jpg_result;
1193 FindFileHandlerTasks(&test_profile_, jpg_entries, &jpg_result);
1194 EXPECT_EQ(3U, jpg_result.size());
1195 // Foo app provides a handler for all types.
1196 EXPECT_EQ("Foo", jpg_result[0].task_title());
1197 EXPECT_TRUE(jpg_result[0].is_generic_file_handler());
1198 // Baz app provides a handler for image/*. A partial wildcarded handler is
1199 // treated as non-generic handler.
1200 EXPECT_EQ("Baz", jpg_result[1].task_title());
1201 EXPECT_FALSE(jpg_result[1].is_generic_file_handler());
1202 // Qux app provides a handler for all types.
1203 EXPECT_EQ("Qux", jpg_result[2].task_title());
1204 EXPECT_TRUE(jpg_result[2].is_generic_file_handler());
1205
1206 // Test case with directories.
1207 std::vector<extensions::EntryInfo> dir_entries;
1208 dir_entries.emplace_back(
1209 util::GetMyFilesFolderForProfile(&test_profile_).AppendASCII("dir"), "",
1210 true);
1211 std::vector<FullTaskDescriptor> dir_result;
1212 FindFileHandlerTasks(&test_profile_, dir_entries, &dir_result);
1213 ASSERT_EQ(1U, dir_result.size());
1214 // Confirm that only Bar.app is found and that it is a generic file handler.
1215 EXPECT_EQ(kBarId, dir_result[0].task_descriptor().app_id);
1216 EXPECT_TRUE(dir_result[0].is_generic_file_handler());
1217 }
1218
1219 // The basic logic is similar to a test case for FindFileHandlerTasks above.
TEST_F(FileManagerFileTasksComplexTest,FindFileHandlerTask_Verbs)1220 TEST_F(FileManagerFileTasksComplexTest, FindFileHandlerTask_Verbs) {
1221 // kFooId copied from FindFileHandlerTasks test above.
1222 const char kFooId[] = "hhgbjpmdppecanaaogonaigmmifgpaph";
1223
1224 // Foo.app can handle "text/plain" and "text/html".
1225 extensions::ExtensionBuilder foo_app;
1226 foo_app.SetManifest(
1227 extensions::DictionaryBuilder()
1228 .Set("name", "Foo")
1229 .Set("version", "1.0.0")
1230 .Set("manifest_version", 2)
1231 .Set("app", extensions::DictionaryBuilder()
1232 .Set("background",
1233 extensions::DictionaryBuilder()
1234 .Set("scripts", extensions::ListBuilder()
1235 .Append("background.js")
1236 .Build())
1237 .Build())
1238 .Build())
1239 .Set(
1240 "file_handlers",
1241 extensions::DictionaryBuilder()
1242 .Set("any",
1243 extensions::DictionaryBuilder()
1244 .Set("types",
1245 extensions::ListBuilder().Append("*").Build())
1246 .Set("verb", "add_to")
1247 .Build())
1248 .Set("any_with_directories",
1249 extensions::DictionaryBuilder()
1250 .Set("include_directories", true)
1251 .Set("types",
1252 extensions::ListBuilder().Append("*").Build())
1253 .Set("verb", "pack_with")
1254 .Build())
1255 .Set("all_text", extensions::DictionaryBuilder()
1256 .Set("title", "Text")
1257 .Set("types", extensions::ListBuilder()
1258 .Append("text/plain")
1259 .Append("text/html")
1260 .Build())
1261 .Set("verb", "add_to")
1262 .Build())
1263 .Set("plain_text", extensions::DictionaryBuilder()
1264 .Set("title", "Plain")
1265 .Set("types", extensions::ListBuilder()
1266 .Append("text/plain")
1267 .Build())
1268 .Set("verb", "open_with")
1269 .Build())
1270 .Set("html_text_duplicate_verb",
1271 extensions::DictionaryBuilder()
1272 .Set("title", "Html")
1273 .Set("types", extensions::ListBuilder()
1274 .Append("text/html")
1275 .Build())
1276 .Set("verb", "add_to")
1277 .Build())
1278 .Set("share_plain_text",
1279 extensions::DictionaryBuilder()
1280 .Set("title", "Share Plain")
1281 .Set("types", extensions::ListBuilder()
1282 .Append("text/plain")
1283 .Build())
1284 .Set("verb", "share_with")
1285 .Build())
1286 .Build())
1287 .Build());
1288 foo_app.SetID(kFooId);
1289 extension_service_->AddExtension(foo_app.Build().get());
1290
1291 // Find app with corresponding verbs for a "text/plain" file.
1292 // Foo.app with ADD_TO, OPEN_WITH, PACK_WITH and SHARE_WITH should be found,
1293 // but only one ADD_TO that is not a generic handler will be taken into
1294 // account, even though there are 2 ADD_TO matches for "text/plain".
1295 std::vector<extensions::EntryInfo> entries;
1296 entries.emplace_back(
1297 util::GetMyFilesFolderForProfile(&test_profile_).AppendASCII("foo.txt"),
1298 "text/plain", false);
1299
1300 std::vector<FullTaskDescriptor> tasks;
1301 FindFileHandlerTasks(&test_profile_, entries, &tasks);
1302
1303 ASSERT_EQ(4U, tasks.size());
1304 EXPECT_EQ(kFooId, tasks[0].task_descriptor().app_id);
1305 EXPECT_EQ("Foo", tasks[0].task_title());
1306 EXPECT_EQ(Verb::VERB_ADD_TO, tasks[0].task_verb());
1307 EXPECT_EQ(kFooId, tasks[1].task_descriptor().app_id);
1308 EXPECT_EQ("Foo", tasks[1].task_title());
1309 EXPECT_EQ(Verb::VERB_OPEN_WITH, tasks[1].task_verb());
1310 EXPECT_EQ(kFooId, tasks[2].task_descriptor().app_id);
1311 EXPECT_EQ("Foo", tasks[2].task_title());
1312 EXPECT_EQ(Verb::VERB_PACK_WITH, tasks[2].task_verb());
1313 EXPECT_EQ(kFooId, tasks[3].task_descriptor().app_id);
1314 EXPECT_EQ("Foo", tasks[3].task_title());
1315 EXPECT_EQ(Verb::VERB_SHARE_WITH, tasks[3].task_verb());
1316
1317 // Find app with corresponding verbs for a "text/html" file.
1318 // Foo.app with ADD_TO and PACK_WITH should be found, but only the first
1319 // ADD_TO that is a good match will be taken into account, even though there
1320 // are 3 ADD_TO matches for "text/html".
1321 entries.clear();
1322 entries.emplace_back(
1323 util::GetMyFilesFolderForProfile(&test_profile_).AppendASCII("foo.html"),
1324 "text/html", false);
1325 tasks.clear();
1326 FindFileHandlerTasks(&test_profile_, entries, &tasks);
1327
1328 ASSERT_EQ(2U, tasks.size());
1329 EXPECT_EQ(kFooId, tasks[0].task_descriptor().app_id);
1330 EXPECT_EQ("Foo", tasks[0].task_title());
1331 EXPECT_EQ(Verb::VERB_ADD_TO, tasks[0].task_verb());
1332 EXPECT_EQ(kFooId, tasks[1].task_descriptor().app_id);
1333 EXPECT_EQ("Foo", tasks[1].task_title());
1334 EXPECT_EQ(Verb::VERB_PACK_WITH, tasks[1].task_verb());
1335
1336 // Find app with corresponding verbs for directories.
1337 // Foo.app with only PACK_WITH should be found.
1338 entries.clear();
1339 entries.emplace_back(
1340 util::GetMyFilesFolderForProfile(&test_profile_).AppendASCII("dir"), "",
1341 true);
1342 tasks.clear();
1343 FindFileHandlerTasks(&test_profile_, entries, &tasks);
1344
1345 ASSERT_EQ(1U, tasks.size());
1346 EXPECT_EQ(kFooId, tasks[0].task_descriptor().app_id);
1347 EXPECT_EQ("Foo", tasks[0].task_title());
1348 EXPECT_EQ(Verb::VERB_PACK_WITH, tasks[0].task_verb());
1349 }
1350
1351 // Test using the test extension system, which needs lots of setup.
1352 class FileManagerFileTasksCrostiniTest
1353 : public FileManagerFileTasksComplexTest {
1354 protected:
FileManagerFileTasksCrostiniTest()1355 FileManagerFileTasksCrostiniTest()
1356 : crostini_test_helper_(&test_profile_),
1357 crostini_folder_(util::GetCrostiniMountDirectory(&test_profile_)) {
1358 chromeos::DBusThreadManager::GetSetterForTesting()->SetConciergeClient(
1359 std::make_unique<chromeos::FakeConciergeClient>());
1360
1361 vm_tools::apps::App text_app =
1362 crostini::CrostiniTestHelper::BasicApp("text_app");
1363 *text_app.add_mime_types() = "text/plain";
1364 crostini_test_helper_.AddApp(text_app);
1365
1366 vm_tools::apps::App image_app =
1367 crostini::CrostiniTestHelper::BasicApp("image_app");
1368 *image_app.add_mime_types() = "image/gif";
1369 *image_app.add_mime_types() = "image/jpeg";
1370 *image_app.add_mime_types() = "image/jpg";
1371 *image_app.add_mime_types() = "image/png";
1372 crostini_test_helper_.AddApp(image_app);
1373
1374 vm_tools::apps::App gif_app =
1375 crostini::CrostiniTestHelper::BasicApp("gif_app");
1376 *gif_app.add_mime_types() = "image/gif";
1377 crostini_test_helper_.AddApp(gif_app);
1378
1379 vm_tools::apps::App alt_mime_app =
1380 crostini::CrostiniTestHelper::BasicApp("alt_mime_app");
1381 *alt_mime_app.add_mime_types() = "foo/x-bar";
1382 crostini_test_helper_.AddApp(alt_mime_app);
1383
1384 text_app_id_ = crostini::CrostiniTestHelper::GenerateAppId("text_app");
1385 image_app_id_ = crostini::CrostiniTestHelper::GenerateAppId("image_app");
1386 gif_app_id_ = crostini::CrostiniTestHelper::GenerateAppId("gif_app");
1387 alt_mime_app_id_ =
1388 crostini::CrostiniTestHelper::GenerateAppId("alt_mime_app");
1389
1390 // Setup the custom MIME type mapping.
1391 vm_tools::apps::MimeTypes mime_types_list;
1392 mime_types_list.set_vm_name(crostini::kCrostiniDefaultVmName);
1393 mime_types_list.set_container_name(crostini::kCrostiniDefaultContainerName);
1394 (*mime_types_list.mutable_mime_type_mappings())["foo"] = "foo/x-bar";
1395
1396 crostini::CrostiniMimeTypesServiceFactory::GetForProfile(&test_profile_)
1397 ->UpdateMimeTypes(mime_types_list);
1398 }
1399
SetUp()1400 void SetUp() override {
1401 storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
1402 util::GetDownloadsMountPointName(&test_profile_),
1403 storage::kFileSystemTypeNativeLocal, storage::FileSystemMountOption(),
1404 util::GetMyFilesFolderForProfile(&test_profile_));
1405 }
1406
TearDown()1407 void TearDown() override {
1408 storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(
1409 util::GetDownloadsMountPointName(&test_profile_));
1410 }
1411
PathToURL(const std::string & path)1412 GURL PathToURL(const std::string& path) {
1413 std::string virtual_path = net::EscapeUrlEncodedData(
1414 util::GetDownloadsMountPointName(&test_profile_) + "/" + path,
1415 /*use_plus=*/false);
1416 return GURL("filesystem:chrome-extension://id/external/" + virtual_path);
1417 }
1418
1419 crostini::CrostiniTestHelper crostini_test_helper_;
1420 base::FilePath crostini_folder_;
1421 std::string text_app_id_;
1422 std::string image_app_id_;
1423 std::string gif_app_id_;
1424 std::string alt_mime_app_id_;
1425 };
1426
TEST_F(FileManagerFileTasksCrostiniTest,BasicFiles)1427 TEST_F(FileManagerFileTasksCrostiniTest, BasicFiles) {
1428 std::vector<extensions::EntryInfo> entries{
1429 {crostini_folder_.Append("foo.txt"), "text/plain", false}};
1430 std::vector<GURL> file_urls{PathToURL("dir/foo.txt")};
1431
1432 std::vector<FullTaskDescriptor> tasks;
1433 FindAllTypesOfTasksSynchronousWrapper().Call(&test_profile_, entries,
1434 file_urls, &tasks);
1435 ASSERT_EQ(1U, tasks.size());
1436 EXPECT_EQ(text_app_id_, tasks[0].task_descriptor().app_id);
1437
1438 // Multiple text files
1439 entries.emplace_back(crostini_folder_.Append("bar.txt"), "text/plain", false);
1440 file_urls.emplace_back(PathToURL("dir/bar.txt"));
1441 FindAllTypesOfTasksSynchronousWrapper().Call(&test_profile_, entries,
1442 file_urls, &tasks);
1443 ASSERT_EQ(1U, tasks.size());
1444 EXPECT_EQ(text_app_id_, tasks[0].task_descriptor().app_id);
1445 }
1446
TEST_F(FileManagerFileTasksCrostiniTest,Directories)1447 TEST_F(FileManagerFileTasksCrostiniTest, Directories) {
1448 std::vector<extensions::EntryInfo> entries{
1449 {crostini_folder_.Append("dir"), "", true}};
1450 std::vector<GURL> file_urls{PathToURL("dir/dir")};
1451 std::vector<FullTaskDescriptor> tasks;
1452 FindAllTypesOfTasksSynchronousWrapper().Call(&test_profile_, entries,
1453 file_urls, &tasks);
1454 EXPECT_EQ(0U, tasks.size());
1455
1456 entries.emplace_back(crostini_folder_.Append("foo.txt"), "text/plain", false);
1457 file_urls.emplace_back(PathToURL("dir/foo.txt"));
1458 FindAllTypesOfTasksSynchronousWrapper().Call(&test_profile_, entries,
1459 file_urls, &tasks);
1460 EXPECT_EQ(0U, tasks.size());
1461 }
1462
TEST_F(FileManagerFileTasksCrostiniTest,MultipleMatches)1463 TEST_F(FileManagerFileTasksCrostiniTest, MultipleMatches) {
1464 std::vector<extensions::EntryInfo> entries{
1465 {crostini_folder_.Append("foo.gif"), "image/gif", false},
1466 {crostini_folder_.Append("bar.gif"), "image/gif", false}};
1467 std::vector<GURL> file_urls{PathToURL("dir/foo.gif"),
1468 PathToURL("dir/bar.gif")};
1469
1470 std::vector<FullTaskDescriptor> tasks;
1471 FindAllTypesOfTasksSynchronousWrapper().Call(&test_profile_, entries,
1472 file_urls, &tasks);
1473 // The returned values happen to be ordered alphabetically by app_id, so we
1474 // rely on this to keep the test simple.
1475 EXPECT_LT(gif_app_id_, image_app_id_);
1476 ASSERT_EQ(2U, tasks.size());
1477 EXPECT_EQ(gif_app_id_, tasks[0].task_descriptor().app_id);
1478 EXPECT_EQ(image_app_id_, tasks[1].task_descriptor().app_id);
1479 }
1480
TEST_F(FileManagerFileTasksCrostiniTest,MultipleTypes)1481 TEST_F(FileManagerFileTasksCrostiniTest, MultipleTypes) {
1482 std::vector<extensions::EntryInfo> entries{
1483 {crostini_folder_.Append("foo.gif"), "image/gif", false},
1484 {crostini_folder_.Append("bar.png"), "image/png", false}};
1485 std::vector<GURL> file_urls{PathToURL("dir/foo.gif"),
1486 PathToURL("dir/bar.png")};
1487
1488 std::vector<FullTaskDescriptor> tasks;
1489 FindAllTypesOfTasksSynchronousWrapper().Call(&test_profile_, entries,
1490 file_urls, &tasks);
1491 ASSERT_EQ(1U, tasks.size());
1492 EXPECT_EQ(image_app_id_, tasks[0].task_descriptor().app_id);
1493
1494 entries.emplace_back(crostini_folder_.Append("qux.mp4"), "video/mp4", false);
1495 file_urls.emplace_back(PathToURL("dir/qux.mp4"));
1496 FindAllTypesOfTasksSynchronousWrapper().Call(&test_profile_, entries,
1497 file_urls, &tasks);
1498 EXPECT_EQ(0U, tasks.size());
1499 }
1500
TEST_F(FileManagerFileTasksCrostiniTest,AlternateMimeTypes)1501 TEST_F(FileManagerFileTasksCrostiniTest, AlternateMimeTypes) {
1502 std::vector<extensions::EntryInfo> entries{
1503 {crostini_folder_.Append("bar1.foo"), "text/plain", false},
1504 {crostini_folder_.Append("bar2.foo"), "application/octet-stream", false}};
1505 std::vector<GURL> file_urls{PathToURL("dir/bar1.foo"),
1506 PathToURL("dir/bar2.foo")};
1507
1508 std::vector<FullTaskDescriptor> tasks;
1509 FindAllTypesOfTasksSynchronousWrapper().Call(&test_profile_, entries,
1510 file_urls, &tasks);
1511 ASSERT_EQ(1U, tasks.size());
1512 EXPECT_EQ(alt_mime_app_id_, tasks[0].task_descriptor().app_id);
1513 }
1514
1515 } // namespace file_tasks
1516 } // namespace file_manager.
1517