Home | History | Annotate | Download | only in file_manager
      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                      const TaskDescriptor& task,
    270                      const std::vector<FileSystemURL>& file_urls,
    271                      const FileTaskFinishedCallback& done) {
    272   if (!FileBrowserHasAccessPermissionForFiles(profile, source_url,
    273                                               app_id, file_urls))
    274     return false;
    275 
    276   // drive::FileTaskExecutor is responsible to handle drive tasks.
    277   if (task.task_type == TASK_TYPE_DRIVE_APP) {
    278     DCHECK_EQ(kDriveAppActionID, task.action_id);
    279     drive::FileTaskExecutor* executor =
    280         new drive::FileTaskExecutor(profile, task.app_id);
    281     executor->Execute(file_urls, done);
    282     return true;
    283   }
    284 
    285   // Get the extension.
    286   ExtensionService* service =
    287       extensions::ExtensionSystem::Get(profile)->extension_service();
    288   const Extension* extension = service ?
    289       service->GetExtensionById(task.app_id, false) : NULL;
    290   if (!extension)
    291     return false;
    292 
    293   // Execute the task.
    294   if (task.task_type == TASK_TYPE_FILE_BROWSER_HANDLER) {
    295     return file_browser_handlers::ExecuteFileBrowserHandler(
    296         profile,
    297         extension,
    298         task.action_id,
    299         file_urls,
    300         done);
    301   } else if (task.task_type == TASK_TYPE_FILE_HANDLER) {
    302     for (size_t i = 0; i != file_urls.size(); ++i) {
    303       apps::LaunchPlatformAppWithFileHandler(
    304           profile, extension, task.action_id, file_urls[i].path());
    305     }
    306 
    307     if (!done.is_null())
    308       done.Run(true);
    309     return true;
    310   }
    311   NOTREACHED();
    312   return false;
    313 }
    314 
    315 void FindDriveAppTasks(
    316     const drive::DriveAppRegistry& drive_app_registry,
    317     const PathAndMimeTypeSet& path_mime_set,
    318     std::vector<FullTaskDescriptor>* result_list) {
    319   DCHECK(result_list);
    320 
    321   bool is_first = true;
    322   typedef std::map<std::string, drive::DriveAppInfo> DriveAppInfoMap;
    323   DriveAppInfoMap drive_app_map;
    324 
    325   for (PathAndMimeTypeSet::const_iterator it = path_mime_set.begin();
    326        it != path_mime_set.end(); ++it) {
    327     const base::FilePath& file_path = it->first;
    328     const std::string& mime_type = it->second;
    329     // Return immediately if a file not on Drive is found, as Drive app tasks
    330     // work only if all files are on Drive.
    331     if (!drive::util::IsUnderDriveMountPoint(file_path))
    332       return;
    333 
    334     ScopedVector<drive::DriveAppInfo> app_info_list;
    335     drive_app_registry.GetAppsForFile(file_path.Extension(),
    336                                       mime_type,
    337                                       &app_info_list);
    338 
    339     if (is_first) {
    340       // For the first file, we store all the info.
    341       for (size_t j = 0; j < app_info_list.size(); ++j) {
    342         const drive::DriveAppInfo& app_info = *app_info_list[j];
    343         drive_app_map[app_info.app_id] = app_info;
    344       }
    345     } else {
    346       // For remaining files, take the intersection with the current
    347       // result, based on the app id.
    348       std::set<std::string> app_id_set;
    349       for (size_t j = 0; j < app_info_list.size(); ++j)
    350         app_id_set.insert(app_info_list[j]->app_id);
    351       for (DriveAppInfoMap::iterator iter = drive_app_map.begin();
    352            iter != drive_app_map.end();) {
    353         if (app_id_set.count(iter->first) == 0) {
    354           drive_app_map.erase(iter++);
    355         } else {
    356           ++iter;
    357         }
    358       }
    359     }
    360 
    361     is_first = false;
    362   }
    363 
    364   for (DriveAppInfoMap::const_iterator iter = drive_app_map.begin();
    365        iter != drive_app_map.end(); ++iter) {
    366     const drive::DriveAppInfo& app_info = iter->second;
    367     TaskDescriptor descriptor(app_info.app_id,
    368                               TASK_TYPE_DRIVE_APP,
    369                               kDriveAppActionID);
    370     GURL icon_url = drive::util::FindPreferredIcon(
    371         app_info.app_icons,
    372         drive::util::kPreferredIconSize);
    373     result_list->push_back(
    374         FullTaskDescriptor(descriptor,
    375                            app_info.app_name,
    376                            icon_url,
    377                            false /* is_default */));
    378   }
    379 }
    380 
    381 void FindFileHandlerTasks(
    382     Profile* profile,
    383     const PathAndMimeTypeSet& path_mime_set,
    384     std::vector<FullTaskDescriptor>* result_list) {
    385   DCHECK(!path_mime_set.empty());
    386   DCHECK(result_list);
    387 
    388   ExtensionService* service = profile->GetExtensionService();
    389   if (!service)
    390     return;
    391 
    392   for (ExtensionSet::const_iterator iter = service->extensions()->begin();
    393        iter != service->extensions()->end();
    394        ++iter) {
    395     const Extension* extension = iter->get();
    396 
    397     // We don't support using hosted apps to open files.
    398     if (!extension->is_platform_app())
    399       continue;
    400 
    401     if (profile->IsOffTheRecord() &&
    402         !extension_util::IsIncognitoEnabled(extension->id(), service))
    403       continue;
    404 
    405     typedef std::vector<const extensions::FileHandlerInfo*> FileHandlerList;
    406     FileHandlerList file_handlers =
    407         FindFileHandlersForFiles(*extension, path_mime_set);
    408     if (file_handlers.empty())
    409       continue;
    410 
    411     for (FileHandlerList::iterator i = file_handlers.begin();
    412          i != file_handlers.end(); ++i) {
    413       std::string task_id = file_tasks::MakeTaskID(
    414           extension->id(), file_tasks::TASK_TYPE_FILE_HANDLER, (*i)->id);
    415 
    416       GURL best_icon = extensions::ExtensionIconSource::GetIconURL(
    417           extension,
    418           drive::util::kPreferredIconSize,
    419           ExtensionIconSet::MATCH_BIGGER,
    420           false,  // grayscale
    421           NULL);  // exists
    422 
    423       result_list->push_back(FullTaskDescriptor(
    424           TaskDescriptor(extension->id(),
    425                          file_tasks::TASK_TYPE_FILE_HANDLER,
    426                          (*i)->id),
    427           (*i)->title,
    428           best_icon,
    429           false /* is_default */));
    430     }
    431   }
    432 }
    433 
    434 void FindFileBrowserHandlerTasks(
    435     Profile* profile,
    436     const std::vector<GURL>& file_urls,
    437     std::vector<FullTaskDescriptor>* result_list) {
    438   DCHECK(!file_urls.empty());
    439   DCHECK(result_list);
    440 
    441   file_browser_handlers::FileBrowserHandlerList common_tasks =
    442       file_browser_handlers::FindFileBrowserHandlers(profile, file_urls);
    443   if (common_tasks.empty())
    444     return;
    445 
    446   ExtensionService* service =
    447       extensions::ExtensionSystem::Get(profile)->extension_service();
    448   for (file_browser_handlers::FileBrowserHandlerList::const_iterator iter =
    449            common_tasks.begin();
    450        iter != common_tasks.end();
    451        ++iter) {
    452     const FileBrowserHandler* handler = *iter;
    453     const std::string extension_id = handler->extension_id();
    454     const Extension* extension = service->GetExtensionById(extension_id, false);
    455     DCHECK(extension);
    456 
    457     // TODO(zelidrag): Figure out how to expose icon URL that task defined in
    458     // manifest instead of the default extension icon.
    459     const GURL icon_url = extensions::ExtensionIconSource::GetIconURL(
    460         extension,
    461         extension_misc::EXTENSION_ICON_BITTY,
    462         ExtensionIconSet::MATCH_BIGGER,
    463         false,  // grayscale
    464         NULL);  // exists
    465 
    466     result_list->push_back(FullTaskDescriptor(
    467         TaskDescriptor(extension_id,
    468                        file_tasks::TASK_TYPE_FILE_BROWSER_HANDLER,
    469                        handler->id()),
    470         handler->title(),
    471         icon_url,
    472         false /* is_default */));
    473   }
    474 }
    475 
    476 void FindAllTypesOfTasks(
    477     Profile* profile,
    478     const drive::DriveAppRegistry* drive_app_registry,
    479     const PathAndMimeTypeSet& path_mime_set,
    480     const std::vector<GURL>& file_urls,
    481     std::vector<FullTaskDescriptor>* result_list) {
    482   DCHECK(profile);
    483   DCHECK(result_list);
    484 
    485   // Find Drive app tasks, if the drive app registry is present.
    486   if (drive_app_registry)
    487     FindDriveAppTasks(*drive_app_registry, path_mime_set, result_list);
    488 
    489   // Find and append file handler tasks. We know there aren't duplicates
    490   // because Drive apps and platform apps are entirely different kinds of
    491   // tasks.
    492   FindFileHandlerTasks(profile, path_mime_set, result_list);
    493 
    494   // Find and append file browser handler tasks. We know there aren't
    495   // duplicates because "file_browser_handlers" and "file_handlers" shouldn't
    496   // be used in the same manifest.json.
    497   FindFileBrowserHandlerTasks(profile, file_urls, result_list);
    498 
    499   // Google documents can only be handled by internal handlers.
    500   if (ContainsGoogleDocument(path_mime_set))
    501     KeepOnlyFileManagerInternalTasks(result_list);
    502 
    503   ChooseAndSetDefaultTask(*profile->GetPrefs(), path_mime_set, result_list);
    504 }
    505 
    506 void ChooseAndSetDefaultTask(const PrefService& pref_service,
    507                              const PathAndMimeTypeSet& path_mime_set,
    508                              std::vector<FullTaskDescriptor>* tasks) {
    509   // Collect the task IDs of default tasks from the preferences into a set.
    510   std::set<std::string> default_task_ids;
    511   for (PathAndMimeTypeSet::const_iterator it = path_mime_set.begin();
    512        it != path_mime_set.end(); ++it) {
    513     const base::FilePath& file_path = it->first;
    514     const std::string& mime_type = it->second;
    515     std::string task_id = file_tasks::GetDefaultTaskIdFromPrefs(
    516         pref_service, mime_type, file_path.Extension());
    517     default_task_ids.insert(task_id);
    518   }
    519 
    520   // Go through all the tasks from the beginning and see if there is any
    521   // default task. If found, pick and set it as default and return.
    522   for (size_t i = 0; i < tasks->size(); ++i) {
    523     FullTaskDescriptor* task = &tasks->at(i);
    524     DCHECK(!task->is_default());
    525     const std::string task_id = TaskDescriptorToId(task->task_descriptor());
    526     if (ContainsKey(default_task_ids, task_id)) {
    527       task->set_is_default(true);
    528       return;
    529     }
    530   }
    531 
    532   // No default tasks found. If there is any fallback file browser handler,
    533   // make it as default task, so it's selected by default.
    534   for (size_t i = 0; i < tasks->size(); ++i) {
    535     FullTaskDescriptor* task = &tasks->at(i);
    536     DCHECK(!task->is_default());
    537     if (file_browser_handlers::IsFallbackFileBrowserHandler(
    538             task->task_descriptor())) {
    539       task->set_is_default(true);
    540       return;
    541     }
    542   }
    543 }
    544 
    545 }  // namespace file_tasks
    546 }  // namespace file_manager
    547