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