- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / chromeos / file_manager / file_tasks.cc
1 // Copyright (c) 2012 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 "apps/launcher.h"
8 #include "base/bind.h"
9 #include "base/prefs/pref_service.h"
10 #include "base/strings/stringprintf.h"
11 #include "chrome/browser/chromeos/drive/drive_app_registry.h"
12 #include "chrome/browser/chromeos/drive/file_system_util.h"
13 #include "chrome/browser/chromeos/drive/file_task_executor.h"
14 #include "chrome/browser/chromeos/file_manager/app_id.h"
15 #include "chrome/browser/chromeos/file_manager/file_browser_handlers.h"
16 #include "chrome/browser/chromeos/file_manager/fileapi_util.h"
17 #include "chrome/browser/chromeos/file_manager/open_util.h"
18 #include "chrome/browser/chromeos/fileapi/file_system_backend.h"
19 #include "chrome/browser/extensions/extension_host.h"
20 #include "chrome/browser/extensions/extension_service.h"
21 #include "chrome/browser/extensions/extension_service.h"
22 #include "chrome/browser/extensions/extension_system.h"
23 #include "chrome/browser/extensions/extension_system.h"
24 #include "chrome/browser/extensions/extension_tab_util.h"
25 #include "chrome/browser/extensions/extension_util.h"
26 #include "chrome/browser/profiles/profile.h"
27 #include "chrome/browser/ui/webui/extensions/extension_icon_source.h"
28 #include "chrome/common/extensions/api/file_browser_handlers/file_browser_handler.h"
29 #include "chrome/common/pref_names.h"
30 #include "webkit/browser/fileapi/file_system_context.h"
31 #include "webkit/browser/fileapi/file_system_url.h"
32
33 using extensions::Extension;
34 using extensions::app_file_handler_util::FindFileHandlersForFiles;
35 using fileapi::FileSystemURL;
36
37 namespace file_manager {
38 namespace file_tasks {
39
40 namespace {
41
42 // The values "file" and "app" are confusing, but cannot be changed easily as
43 // these are used in default task IDs stored in preferences.
44 //
45 // TODO(satorux): We should rename them to "file_browser_handler" and
46 // "file_handler" respectively when switching from preferences to
47 // chrome.storage crbug.com/267359
48 const char kFileBrowserHandlerTaskType[] = "file";
49 const char kFileHandlerTaskType[] = "app";
50 const char kDriveAppTaskType[] = "drive";
51
52 // Drive apps always use the action ID.
53 const char kDriveAppActionID[] = "open-with";
54
55 // Converts a TaskType to a string.
56 std::string TaskTypeToString(TaskType task_type) {
57   switch (task_type) {
58     case TASK_TYPE_FILE_BROWSER_HANDLER:
59       return kFileBrowserHandlerTaskType;
60     case TASK_TYPE_FILE_HANDLER:
61       return kFileHandlerTaskType;
62     case TASK_TYPE_DRIVE_APP:
63       return kDriveAppTaskType;
64     case TASK_TYPE_UNKNOWN:
65       break;
66   }
67   NOTREACHED();
68   return "";
69 }
70
71 // Converts a string to a TaskType. Returns TASK_TYPE_UNKNOWN on error.
72 TaskType StringToTaskType(const std::string& str) {
73   if (str == kFileBrowserHandlerTaskType)
74     return TASK_TYPE_FILE_BROWSER_HANDLER;
75   if (str == kFileHandlerTaskType)
76     return TASK_TYPE_FILE_HANDLER;
77   if (str == kDriveAppTaskType)
78     return TASK_TYPE_DRIVE_APP;
79   return TASK_TYPE_UNKNOWN;
80 }
81
82 // Legacy Drive task extension prefix, used by CrackTaskID.
83 const char kDriveTaskExtensionPrefix[] = "drive-app:";
84 const size_t kDriveTaskExtensionPrefixLength =
85     arraysize(kDriveTaskExtensionPrefix) - 1;
86
87 // Checks if the file browser extension has permissions for the files in its
88 // file system context.
89 bool FileBrowserHasAccessPermissionForFiles(
90     Profile* profile,
91     const GURL& source_url,
92     const std::string& file_browser_id,
93     const std::vector<FileSystemURL>& files) {
94   fileapi::ExternalFileSystemBackend* backend =
95       util::GetFileSystemContextForExtensionId(
96           profile, file_browser_id)->external_backend();
97   if (!backend)
98     return false;
99
100   for (size_t i = 0; i < files.size(); ++i) {
101     // Make sure this url really being used by the right caller extension.
102     if (source_url.GetOrigin() != files[i].origin())
103       return false;
104
105     if (!chromeos::FileSystemBackend::CanHandleURL(files[i]) ||
106         !backend->IsAccessAllowed(files[i])) {
107       return false;
108     }
109   }
110
111   return true;
112 }
113
114 // Returns true if path_mime_set contains a Google document.
115 bool ContainsGoogleDocument(const PathAndMimeTypeSet& path_mime_set) {
116   for (PathAndMimeTypeSet::const_iterator iter = path_mime_set.begin();
117        iter != path_mime_set.end(); ++iter) {
118     if (google_apis::ResourceEntry::ClassifyEntryKindByFileExtension(
119             iter->first) &
120         google_apis::ResourceEntry::KIND_OF_GOOGLE_DOCUMENT) {
121       return true;
122     }
123   }
124   return false;
125 }
126
127 // Leaves tasks handled by the file manger itself as is and removes all others.
128 void KeepOnlyFileManagerInternalTasks(std::vector<FullTaskDescriptor>* tasks) {
129   std::vector<FullTaskDescriptor> filtered;
130   for (size_t i = 0; i < tasks->size(); ++i) {
131     if ((*tasks)[i].task_descriptor().app_id == kFileManagerAppId)
132       filtered.push_back((*tasks)[i]);
133   }
134   tasks->swap(filtered);
135 }
136
137 }  // namespace
138
139 FullTaskDescriptor::FullTaskDescriptor(
140     const TaskDescriptor& task_descriptor,
141     const std::string& task_title,
142     const GURL& icon_url,
143     bool is_default)
144     : task_descriptor_(task_descriptor),
145       task_title_(task_title),
146       icon_url_(icon_url),
147       is_default_(is_default){
148 }
149
150 void UpdateDefaultTask(PrefService* pref_service,
151                        const std::string& task_id,
152                        const std::set<std::string>& suffixes,
153                        const std::set<std::string>& mime_types) {
154   if (!pref_service)
155     return;
156
157   if (!mime_types.empty()) {
158     DictionaryPrefUpdate mime_type_pref(pref_service,
159                                         prefs::kDefaultTasksByMimeType);
160     for (std::set<std::string>::const_iterator iter = mime_types.begin();
161         iter != mime_types.end(); ++iter) {
162       base::StringValue* value = new base::StringValue(task_id);
163       mime_type_pref->SetWithoutPathExpansion(*iter, value);
164     }
165   }
166
167   if (!suffixes.empty()) {
168     DictionaryPrefUpdate mime_type_pref(pref_service,
169                                         prefs::kDefaultTasksBySuffix);
170     for (std::set<std::string>::const_iterator iter = suffixes.begin();
171         iter != suffixes.end(); ++iter) {
172       base::StringValue* value = new base::StringValue(task_id);
173       // Suffixes are case insensitive.
174       std::string lower_suffix = StringToLowerASCII(*iter);
175       mime_type_pref->SetWithoutPathExpansion(lower_suffix, value);
176     }
177   }
178 }
179
180 std::string GetDefaultTaskIdFromPrefs(const PrefService& pref_service,
181                                       const std::string& mime_type,
182                                       const std::string& suffix) {
183   VLOG(1) << "Looking for default for MIME type: " << mime_type
184       << " and suffix: " << suffix;
185   std::string task_id;
186   if (!mime_type.empty()) {
187     const DictionaryValue* mime_task_prefs =
188         pref_service.GetDictionary(prefs::kDefaultTasksByMimeType);
189     DCHECK(mime_task_prefs);
190     LOG_IF(ERROR, !mime_task_prefs) << "Unable to open MIME type prefs";
191     if (mime_task_prefs &&
192         mime_task_prefs->GetStringWithoutPathExpansion(mime_type, &task_id)) {
193       VLOG(1) << "Found MIME default handler: " << task_id;
194       return task_id;
195     }
196   }
197
198   const DictionaryValue* suffix_task_prefs =
199       pref_service.GetDictionary(prefs::kDefaultTasksBySuffix);
200   DCHECK(suffix_task_prefs);
201   LOG_IF(ERROR, !suffix_task_prefs) << "Unable to open suffix prefs";
202   std::string lower_suffix = StringToLowerASCII(suffix);
203   if (suffix_task_prefs)
204     suffix_task_prefs->GetStringWithoutPathExpansion(lower_suffix, &task_id);
205   VLOG_IF(1, !task_id.empty()) << "Found suffix default handler: " << task_id;
206   return task_id;
207 }
208
209 std::string MakeTaskID(const std::string& app_id,
210                        TaskType task_type,
211                        const std::string& action_id) {
212   return base::StringPrintf("%s|%s|%s",
213                             app_id.c_str(),
214                             TaskTypeToString(task_type).c_str(),
215                             action_id.c_str());
216 }
217
218 std::string MakeDriveAppTaskId(const std::string& app_id) {
219   return MakeTaskID(app_id, TASK_TYPE_DRIVE_APP, kDriveAppActionID);
220 }
221
222 std::string TaskDescriptorToId(const TaskDescriptor& task_descriptor) {
223   return MakeTaskID(task_descriptor.app_id,
224                     task_descriptor.task_type,
225                     task_descriptor.action_id);
226 }
227
228 bool ParseTaskID(const std::string& task_id, TaskDescriptor* task) {
229   DCHECK(task);
230
231   std::vector<std::string> result;
232   int count = Tokenize(task_id, std::string("|"), &result);
233
234   // Parse a legacy task ID that only contain two parts. Drive tasks are
235   // identified by a prefix "drive-app:" on the extension ID. The legacy task
236   // IDs can be stored in preferences.
237   // TODO(satorux): We should get rid of this code: crbug.com/267359.
238   if (count == 2) {
239     if (StartsWithASCII(result[0], kDriveTaskExtensionPrefix, true)) {
240       task->task_type = TASK_TYPE_DRIVE_APP;
241       task->app_id = result[0].substr(kDriveTaskExtensionPrefixLength);
242     } else {
243       task->task_type = TASK_TYPE_FILE_BROWSER_HANDLER;
244       task->app_id = result[0];
245     }
246
247     task->action_id = result[1];
248
249     return true;
250   }
251
252   if (count != 3)
253     return false;
254
255   TaskType task_type = StringToTaskType(result[1]);
256   if (task_type == TASK_TYPE_UNKNOWN)
257     return false;
258
259   task->app_id = result[0];
260   task->task_type = task_type;
261   task->action_id = result[2];
262
263   return true;
264 }
265
266 bool ExecuteFileTask(Profile* profile,
267                      const GURL& source_url,
268                      const std::string& app_id,
269                      int32 tab_id,
270                      const TaskDescriptor& task,
271                      const std::vector<FileSystemURL>& file_urls,
272                      const FileTaskFinishedCallback& done) {
273   if (!FileBrowserHasAccessPermissionForFiles(profile, source_url,
274                                               app_id, file_urls))
275     return false;
276
277   // drive::FileTaskExecutor is responsible to handle drive tasks.
278   if (task.task_type == TASK_TYPE_DRIVE_APP) {
279     DCHECK_EQ(kDriveAppActionID, task.action_id);
280     drive::FileTaskExecutor* executor =
281         new drive::FileTaskExecutor(profile, task.app_id);
282     executor->Execute(file_urls, done);
283     return true;
284   }
285
286   // Get the extension.
287   ExtensionService* service =
288       extensions::ExtensionSystem::Get(profile)->extension_service();
289   const Extension* extension = service ?
290       service->GetExtensionById(task.app_id, false) : NULL;
291   if (!extension)
292     return false;
293
294   // Execute the task.
295   if (task.task_type == TASK_TYPE_FILE_BROWSER_HANDLER) {
296     return file_browser_handlers::ExecuteFileBrowserHandler(
297         profile,
298         extension,
299         tab_id,
300         task.action_id,
301         file_urls,
302         done);
303   } else if (task.task_type == TASK_TYPE_FILE_HANDLER) {
304     for (size_t i = 0; i != file_urls.size(); ++i) {
305       apps::LaunchPlatformAppWithFileHandler(
306           profile, extension, task.action_id, file_urls[i].path());
307     }
308
309     if (!done.is_null())
310       done.Run(true);
311     return true;
312   }
313   NOTREACHED();
314   return false;
315 }
316
317 void FindDriveAppTasks(
318     const drive::DriveAppRegistry& drive_app_registry,
319     const PathAndMimeTypeSet& path_mime_set,
320     std::vector<FullTaskDescriptor>* result_list) {
321   DCHECK(result_list);
322
323   bool is_first = true;
324   typedef std::map<std::string, drive::DriveAppInfo> DriveAppInfoMap;
325   DriveAppInfoMap drive_app_map;
326
327   for (PathAndMimeTypeSet::const_iterator it = path_mime_set.begin();
328        it != path_mime_set.end(); ++it) {
329     const base::FilePath& file_path = it->first;
330     const std::string& mime_type = it->second;
331     // Return immediately if a file not on Drive is found, as Drive app tasks
332     // work only if all files are on Drive.
333     if (!drive::util::IsUnderDriveMountPoint(file_path))
334       return;
335
336     ScopedVector<drive::DriveAppInfo> app_info_list;
337     drive_app_registry.GetAppsForFile(file_path.Extension(),
338                                       mime_type,
339                                       &app_info_list);
340
341     if (is_first) {
342       // For the first file, we store all the info.
343       for (size_t j = 0; j < app_info_list.size(); ++j) {
344         const drive::DriveAppInfo& app_info = *app_info_list[j];
345         drive_app_map[app_info.app_id] = app_info;
346       }
347     } else {
348       // For remaining files, take the intersection with the current
349       // result, based on the app id.
350       std::set<std::string> app_id_set;
351       for (size_t j = 0; j < app_info_list.size(); ++j)
352         app_id_set.insert(app_info_list[j]->app_id);
353       for (DriveAppInfoMap::iterator iter = drive_app_map.begin();
354            iter != drive_app_map.end();) {
355         if (app_id_set.count(iter->first) == 0) {
356           drive_app_map.erase(iter++);
357         } else {
358           ++iter;
359         }
360       }
361     }
362
363     is_first = false;
364   }
365
366   for (DriveAppInfoMap::const_iterator iter = drive_app_map.begin();
367        iter != drive_app_map.end(); ++iter) {
368     const drive::DriveAppInfo& app_info = iter->second;
369     TaskDescriptor descriptor(app_info.app_id,
370                               TASK_TYPE_DRIVE_APP,
371                               kDriveAppActionID);
372     GURL icon_url = drive::util::FindPreferredIcon(
373         app_info.app_icons,
374         drive::util::kPreferredIconSize);
375     result_list->push_back(
376         FullTaskDescriptor(descriptor,
377                            app_info.app_name,
378                            icon_url,
379                            false /* is_default */));
380   }
381 }
382
383 void FindFileHandlerTasks(
384     Profile* profile,
385     const PathAndMimeTypeSet& path_mime_set,
386     std::vector<FullTaskDescriptor>* result_list) {
387   DCHECK(!path_mime_set.empty());
388   DCHECK(result_list);
389
390   ExtensionService* service = profile->GetExtensionService();
391   if (!service)
392     return;
393
394   for (ExtensionSet::const_iterator iter = service->extensions()->begin();
395        iter != service->extensions()->end();
396        ++iter) {
397     const Extension* extension = iter->get();
398
399     // We don't support using hosted apps to open files.
400     if (!extension->is_platform_app())
401       continue;
402
403     if (profile->IsOffTheRecord() &&
404         !extension_util::IsIncognitoEnabled(extension->id(), service))
405       continue;
406
407     typedef std::vector<const extensions::FileHandlerInfo*> FileHandlerList;
408     FileHandlerList file_handlers =
409         FindFileHandlersForFiles(*extension, path_mime_set);
410     if (file_handlers.empty())
411       continue;
412
413     for (FileHandlerList::iterator i = file_handlers.begin();
414          i != file_handlers.end(); ++i) {
415       std::string task_id = file_tasks::MakeTaskID(
416           extension->id(), file_tasks::TASK_TYPE_FILE_HANDLER, (*i)->id);
417
418       GURL best_icon = extensions::ExtensionIconSource::GetIconURL(
419           extension,
420           drive::util::kPreferredIconSize,
421           ExtensionIconSet::MATCH_BIGGER,
422           false,  // grayscale
423           NULL);  // exists
424
425       result_list->push_back(FullTaskDescriptor(
426           TaskDescriptor(extension->id(),
427                          file_tasks::TASK_TYPE_FILE_HANDLER,
428                          (*i)->id),
429           (*i)->title,
430           best_icon,
431           false /* is_default */));
432     }
433   }
434 }
435
436 void FindFileBrowserHandlerTasks(
437     Profile* profile,
438     const std::vector<GURL>& file_urls,
439     std::vector<FullTaskDescriptor>* result_list) {
440   DCHECK(!file_urls.empty());
441   DCHECK(result_list);
442
443   file_browser_handlers::FileBrowserHandlerList common_tasks =
444       file_browser_handlers::FindFileBrowserHandlers(profile, file_urls);
445   if (common_tasks.empty())
446     return;
447
448   ExtensionService* service =
449       extensions::ExtensionSystem::Get(profile)->extension_service();
450   for (file_browser_handlers::FileBrowserHandlerList::const_iterator iter =
451            common_tasks.begin();
452        iter != common_tasks.end();
453        ++iter) {
454     const FileBrowserHandler* handler = *iter;
455     const std::string extension_id = handler->extension_id();
456     const Extension* extension = service->GetExtensionById(extension_id, false);
457     DCHECK(extension);
458
459     // TODO(zelidrag): Figure out how to expose icon URL that task defined in
460     // manifest instead of the default extension icon.
461     const GURL icon_url = extensions::ExtensionIconSource::GetIconURL(
462         extension,
463         extension_misc::EXTENSION_ICON_BITTY,
464         ExtensionIconSet::MATCH_BIGGER,
465         false,  // grayscale
466         NULL);  // exists
467
468     result_list->push_back(FullTaskDescriptor(
469         TaskDescriptor(extension_id,
470                        file_tasks::TASK_TYPE_FILE_BROWSER_HANDLER,
471                        handler->id()),
472         handler->title(),
473         icon_url,
474         false /* is_default */));
475   }
476 }
477
478 void FindAllTypesOfTasks(
479     Profile* profile,
480     const drive::DriveAppRegistry* drive_app_registry,
481     const PathAndMimeTypeSet& path_mime_set,
482     const std::vector<GURL>& file_urls,
483     std::vector<FullTaskDescriptor>* result_list) {
484   DCHECK(profile);
485   DCHECK(result_list);
486
487   // Find Drive app tasks, if the drive app registry is present.
488   if (drive_app_registry)
489     FindDriveAppTasks(*drive_app_registry, path_mime_set, result_list);
490
491   // Find and append file handler tasks. We know there aren't duplicates
492   // because Drive apps and platform apps are entirely different kinds of
493   // tasks.
494   FindFileHandlerTasks(profile, path_mime_set, result_list);
495
496   // Find and append file browser handler tasks. We know there aren't
497   // duplicates because "file_browser_handlers" and "file_handlers" shouldn't
498   // be used in the same manifest.json.
499   FindFileBrowserHandlerTasks(profile, file_urls, result_list);
500
501   // Google documents can only be handled by internal handlers.
502   if (ContainsGoogleDocument(path_mime_set))
503     KeepOnlyFileManagerInternalTasks(result_list);
504
505   ChooseAndSetDefaultTask(*profile->GetPrefs(), path_mime_set, result_list);
506 }
507
508 void ChooseAndSetDefaultTask(const PrefService& pref_service,
509                              const PathAndMimeTypeSet& path_mime_set,
510                              std::vector<FullTaskDescriptor>* tasks) {
511   // Collect the task IDs of default tasks from the preferences into a set.
512   std::set<std::string> default_task_ids;
513   for (PathAndMimeTypeSet::const_iterator it = path_mime_set.begin();
514        it != path_mime_set.end(); ++it) {
515     const base::FilePath& file_path = it->first;
516     const std::string& mime_type = it->second;
517     std::string task_id = file_tasks::GetDefaultTaskIdFromPrefs(
518         pref_service, mime_type, file_path.Extension());
519     default_task_ids.insert(task_id);
520   }
521
522   // Go through all the tasks from the beginning and see if there is any
523   // default task. If found, pick and set it as default and return.
524   for (size_t i = 0; i < tasks->size(); ++i) {
525     FullTaskDescriptor* task = &tasks->at(i);
526     DCHECK(!task->is_default());
527     const std::string task_id = TaskDescriptorToId(task->task_descriptor());
528     if (ContainsKey(default_task_ids, task_id)) {
529       task->set_is_default(true);
530       return;
531     }
532   }
533
534   // No default tasks found. If there is any fallback file browser handler,
535   // make it as default task, so it's selected by default.
536   for (size_t i = 0; i < tasks->size(); ++i) {
537     FullTaskDescriptor* task = &tasks->at(i);
538     DCHECK(!task->is_default());
539     if (file_browser_handlers::IsFallbackFileBrowserHandler(
540             task->task_descriptor())) {
541       task->set_is_default(true);
542       return;
543     }
544   }
545 }
546
547 }  // namespace file_tasks
548 }  // namespace file_manager