Torne (Richard Coles) | a36e592 | 2013-08-05 13:57:33 +0100 | [diff] [blame] | 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/extensions/file_manager/file_tasks.h" |
| 6 | |
Ben Murdoch | bb1529c | 2013-08-08 10:24:53 +0100 | [diff] [blame^] | 7 | #include "apps/launcher.h" |
Torne (Richard Coles) | a36e592 | 2013-08-05 13:57:33 +0100 | [diff] [blame] | 8 | #include "base/bind.h" |
| 9 | #include "base/file_util.h" |
| 10 | #include "base/i18n/case_conversion.h" |
| 11 | #include "base/strings/string_util.h" |
| 12 | #include "base/strings/stringprintf.h" |
| 13 | #include "base/strings/utf_string_conversions.h" |
| 14 | #include "chrome/browser/chromeos/drive/file_system_util.h" |
| 15 | #include "chrome/browser/chromeos/drive/file_task_executor.h" |
| 16 | #include "chrome/browser/chromeos/extensions/file_manager/file_manager_util.h" |
| 17 | #include "chrome/browser/chromeos/fileapi/file_system_backend.h" |
| 18 | #include "chrome/browser/extensions/event_router.h" |
| 19 | #include "chrome/browser/extensions/extension_host.h" |
| 20 | #include "chrome/browser/extensions/extension_service.h" |
| 21 | #include "chrome/browser/extensions/extension_system.h" |
| 22 | #include "chrome/browser/extensions/extension_tab_util.h" |
| 23 | #include "chrome/browser/extensions/lazy_background_task_queue.h" |
Torne (Richard Coles) | a36e592 | 2013-08-05 13:57:33 +0100 | [diff] [blame] | 24 | #include "chrome/browser/prefs/scoped_user_pref_update.h" |
| 25 | #include "chrome/browser/profiles/profile.h" |
| 26 | #include "chrome/browser/ui/browser.h" |
| 27 | #include "chrome/browser/ui/browser_tabstrip.h" |
| 28 | #include "chrome/browser/ui/host_desktop.h" |
| 29 | #include "chrome/common/extensions/api/file_browser_handlers/file_browser_handler.h" |
| 30 | #include "chrome/common/extensions/background_info.h" |
| 31 | #include "chrome/common/pref_names.h" |
| 32 | #include "content/public/browser/browser_thread.h" |
| 33 | #include "content/public/browser/child_process_security_policy.h" |
| 34 | #include "content/public/browser/render_process_host.h" |
| 35 | #include "content/public/browser/site_instance.h" |
| 36 | #include "content/public/browser/storage_partition.h" |
| 37 | #include "content/public/browser/web_contents.h" |
| 38 | #include "net/base/escape.h" |
| 39 | #include "webkit/browser/fileapi/file_system_context.h" |
| 40 | #include "webkit/browser/fileapi/file_system_url.h" |
| 41 | #include "webkit/browser/fileapi/isolated_context.h" |
| 42 | #include "webkit/common/fileapi/file_system_util.h" |
| 43 | |
| 44 | using content::BrowserContext; |
| 45 | using content::BrowserThread; |
| 46 | using content::ChildProcessSecurityPolicy; |
| 47 | using content::SiteInstance; |
| 48 | using content::WebContents; |
| 49 | using extensions::Extension; |
| 50 | using fileapi::FileSystemURL; |
| 51 | |
| 52 | namespace file_manager { |
| 53 | namespace file_tasks { |
| 54 | |
| 55 | const char kTaskFile[] = "file"; |
| 56 | const char kTaskDrive[] = "drive"; |
| 57 | const char kTaskApp[] = "app"; |
| 58 | |
| 59 | namespace { |
| 60 | |
| 61 | // Legacy Drive task extension prefix, used by CrackTaskID. |
| 62 | const char kDriveTaskExtensionPrefix[] = "drive-app:"; |
| 63 | const size_t kDriveTaskExtensionPrefixLength = |
| 64 | arraysize(kDriveTaskExtensionPrefix) - 1; |
| 65 | |
| 66 | // Returns process id of the process the extension is running in. |
| 67 | int ExtractProcessFromExtensionId(Profile* profile, |
| 68 | const std::string& extension_id) { |
| 69 | GURL extension_url = |
| 70 | Extension::GetBaseURLFromExtensionId(extension_id); |
| 71 | ExtensionProcessManager* manager = |
| 72 | extensions::ExtensionSystem::Get(profile)->process_manager(); |
| 73 | |
| 74 | SiteInstance* site_instance = manager->GetSiteInstanceForURL(extension_url); |
| 75 | if (!site_instance || !site_instance->HasProcess()) |
| 76 | return -1; |
| 77 | content::RenderProcessHost* process = site_instance->GetProcess(); |
| 78 | |
| 79 | return process->GetID(); |
| 80 | } |
| 81 | |
| 82 | const FileBrowserHandler* FindFileBrowserHandler(const Extension* extension, |
| 83 | const std::string& action_id) { |
| 84 | FileBrowserHandler::List* handler_list = |
| 85 | FileBrowserHandler::GetHandlers(extension); |
| 86 | for (FileBrowserHandler::List::const_iterator action_iter = |
| 87 | handler_list->begin(); |
| 88 | action_iter != handler_list->end(); |
| 89 | ++action_iter) { |
| 90 | if (action_iter->get()->id() == action_id) |
| 91 | return action_iter->get(); |
| 92 | } |
| 93 | return NULL; |
| 94 | } |
| 95 | |
| 96 | std::string EscapedUtf8ToLower(const std::string& str) { |
| 97 | string16 utf16 = UTF8ToUTF16( |
| 98 | net::UnescapeURLComponent(str, net::UnescapeRule::NORMAL)); |
| 99 | return net::EscapeUrlEncodedData( |
| 100 | UTF16ToUTF8(base::i18n::ToLower(utf16)), |
| 101 | false /* do not replace space with plus */); |
| 102 | } |
| 103 | |
| 104 | bool GetFileBrowserHandlers(Profile* profile, |
| 105 | const GURL& selected_file_url, |
| 106 | FileBrowserHandlerList* results) { |
| 107 | ExtensionService* service = |
| 108 | extensions::ExtensionSystem::Get(profile)->extension_service(); |
| 109 | if (!service) |
| 110 | return false; // In unit-tests, we may not have an ExtensionService. |
| 111 | |
| 112 | // We need case-insensitive matching, and pattern in the handler is already |
| 113 | // in lower case. |
| 114 | const GURL lowercase_url(EscapedUtf8ToLower(selected_file_url.spec())); |
| 115 | |
| 116 | for (ExtensionSet::const_iterator iter = service->extensions()->begin(); |
| 117 | iter != service->extensions()->end(); |
| 118 | ++iter) { |
| 119 | const Extension* extension = iter->get(); |
| 120 | if (profile->IsOffTheRecord() && |
| 121 | !service->IsIncognitoEnabled(extension->id())) |
| 122 | continue; |
| 123 | |
| 124 | FileBrowserHandler::List* handler_list = |
| 125 | FileBrowserHandler::GetHandlers(extension); |
| 126 | if (!handler_list) |
| 127 | continue; |
| 128 | for (FileBrowserHandler::List::const_iterator action_iter = |
| 129 | handler_list->begin(); |
| 130 | action_iter != handler_list->end(); |
| 131 | ++action_iter) { |
| 132 | const FileBrowserHandler* action = action_iter->get(); |
| 133 | if (!action->MatchesURL(lowercase_url)) |
| 134 | continue; |
| 135 | |
| 136 | results->push_back(action_iter->get()); |
| 137 | } |
| 138 | } |
| 139 | return true; |
| 140 | } |
| 141 | |
| 142 | fileapi::FileSystemContext* GetFileSystemContextForExtension( |
| 143 | Profile* profile, |
| 144 | const std::string& extension_id) { |
| 145 | GURL site = extensions::ExtensionSystem::Get(profile)-> |
| 146 | extension_service()->GetSiteForExtensionId(extension_id); |
| 147 | return BrowserContext::GetStoragePartitionForSite(profile, site)-> |
| 148 | GetFileSystemContext(); |
| 149 | } |
| 150 | |
| 151 | // Checks if the file browser extension has permissions for the files in its |
| 152 | // file system context. |
| 153 | bool FileBrowserHasAccessPermissionForFiles( |
| 154 | Profile* profile, |
| 155 | const GURL& source_url, |
| 156 | const std::string& file_browser_id, |
| 157 | const std::vector<FileSystemURL>& files) { |
| 158 | fileapi::ExternalFileSystemBackend* backend = |
| 159 | GetFileSystemContextForExtension(profile, file_browser_id)-> |
| 160 | external_backend(); |
| 161 | if (!backend) |
| 162 | return false; |
| 163 | |
| 164 | for (size_t i = 0; i < files.size(); ++i) { |
| 165 | // Make sure this url really being used by the right caller extension. |
| 166 | if (source_url.GetOrigin() != files[i].origin()) |
| 167 | return false; |
| 168 | |
| 169 | if (!chromeos::FileSystemBackend::CanHandleURL(files[i]) || |
| 170 | !backend->IsAccessAllowed(files[i])) { |
| 171 | return false; |
| 172 | } |
| 173 | } |
| 174 | |
| 175 | return true; |
| 176 | } |
| 177 | |
| 178 | } // namespace |
| 179 | |
| 180 | bool IsFallbackTask(const FileBrowserHandler* task) { |
| 181 | return (task->extension_id() == kFileBrowserDomain || |
| 182 | task->extension_id() == |
| 183 | extension_misc::kQuickOfficeComponentExtensionId || |
| 184 | task->extension_id() == extension_misc::kQuickOfficeDevExtensionId || |
| 185 | task->extension_id() == extension_misc::kQuickOfficeExtensionId); |
| 186 | } |
| 187 | |
| 188 | void UpdateDefaultTask(Profile* profile, |
| 189 | const std::string& task_id, |
| 190 | const std::set<std::string>& suffixes, |
| 191 | const std::set<std::string>& mime_types) { |
| 192 | if (!profile || !profile->GetPrefs()) |
| 193 | return; |
| 194 | |
| 195 | if (!mime_types.empty()) { |
| 196 | DictionaryPrefUpdate mime_type_pref(profile->GetPrefs(), |
| 197 | prefs::kDefaultTasksByMimeType); |
| 198 | for (std::set<std::string>::const_iterator iter = mime_types.begin(); |
| 199 | iter != mime_types.end(); ++iter) { |
| 200 | base::StringValue* value = new base::StringValue(task_id); |
| 201 | mime_type_pref->SetWithoutPathExpansion(*iter, value); |
| 202 | } |
| 203 | } |
| 204 | |
| 205 | if (!suffixes.empty()) { |
| 206 | DictionaryPrefUpdate mime_type_pref(profile->GetPrefs(), |
| 207 | prefs::kDefaultTasksBySuffix); |
| 208 | for (std::set<std::string>::const_iterator iter = suffixes.begin(); |
| 209 | iter != suffixes.end(); ++iter) { |
| 210 | base::StringValue* value = new base::StringValue(task_id); |
| 211 | // Suffixes are case insensitive. |
| 212 | std::string lower_suffix = StringToLowerASCII(*iter); |
| 213 | mime_type_pref->SetWithoutPathExpansion(lower_suffix, value); |
| 214 | } |
| 215 | } |
| 216 | } |
| 217 | |
| 218 | std::string GetDefaultTaskIdFromPrefs(Profile* profile, |
| 219 | const std::string& mime_type, |
| 220 | const std::string& suffix) { |
| 221 | VLOG(1) << "Looking for default for MIME type: " << mime_type |
| 222 | << " and suffix: " << suffix; |
| 223 | std::string task_id; |
| 224 | if (!mime_type.empty()) { |
| 225 | const DictionaryValue* mime_task_prefs = |
| 226 | profile->GetPrefs()->GetDictionary(prefs::kDefaultTasksByMimeType); |
| 227 | DCHECK(mime_task_prefs); |
| 228 | LOG_IF(ERROR, !mime_task_prefs) << "Unable to open MIME type prefs"; |
| 229 | if (mime_task_prefs && |
| 230 | mime_task_prefs->GetStringWithoutPathExpansion(mime_type, &task_id)) { |
| 231 | VLOG(1) << "Found MIME default handler: " << task_id; |
| 232 | return task_id; |
| 233 | } |
| 234 | } |
| 235 | |
| 236 | const DictionaryValue* suffix_task_prefs = |
| 237 | profile->GetPrefs()->GetDictionary(prefs::kDefaultTasksBySuffix); |
| 238 | DCHECK(suffix_task_prefs); |
| 239 | LOG_IF(ERROR, !suffix_task_prefs) << "Unable to open suffix prefs"; |
| 240 | std::string lower_suffix = StringToLowerASCII(suffix); |
| 241 | if (suffix_task_prefs) |
| 242 | suffix_task_prefs->GetStringWithoutPathExpansion(lower_suffix, &task_id); |
| 243 | VLOG_IF(1, !task_id.empty()) << "Found suffix default handler: " << task_id; |
| 244 | return task_id; |
| 245 | } |
| 246 | |
| 247 | std::string MakeTaskID(const std::string& extension_id, |
| 248 | const std::string& task_type, |
| 249 | const std::string& action_id) { |
| 250 | DCHECK(task_type == kTaskFile || |
| 251 | task_type == kTaskDrive || |
| 252 | task_type == kTaskApp); |
| 253 | return base::StringPrintf("%s|%s|%s", |
| 254 | extension_id.c_str(), |
| 255 | task_type.c_str(), |
| 256 | action_id.c_str()); |
| 257 | } |
| 258 | |
| 259 | bool CrackTaskID(const std::string& task_id, |
| 260 | std::string* extension_id, |
| 261 | std::string* task_type, |
| 262 | std::string* action_id) { |
| 263 | std::vector<std::string> result; |
| 264 | int count = Tokenize(task_id, std::string("|"), &result); |
| 265 | |
| 266 | // Parse historic task_id parameters that only contain two parts. Drive tasks |
| 267 | // are identified by a prefix "drive-app:" on the extension ID. |
| 268 | if (count == 2) { |
| 269 | if (StartsWithASCII(result[0], kDriveTaskExtensionPrefix, true)) { |
| 270 | if (task_type) |
| 271 | *task_type = kTaskDrive; |
| 272 | |
| 273 | if (extension_id) |
| 274 | *extension_id = result[0].substr(kDriveTaskExtensionPrefixLength); |
| 275 | } else { |
| 276 | if (task_type) |
| 277 | *task_type = kTaskFile; |
| 278 | |
| 279 | if (extension_id) |
| 280 | *extension_id = result[0]; |
| 281 | } |
| 282 | |
| 283 | if (action_id) |
| 284 | *action_id = result[1]; |
| 285 | |
| 286 | return true; |
| 287 | } |
| 288 | |
| 289 | if (count != 3) |
| 290 | return false; |
| 291 | |
| 292 | if (extension_id) |
| 293 | *extension_id = result[0]; |
| 294 | |
| 295 | if (task_type) { |
| 296 | *task_type = result[1]; |
| 297 | DCHECK(*task_type == kTaskFile || |
| 298 | *task_type == kTaskDrive || |
| 299 | *task_type == kTaskApp); |
| 300 | } |
| 301 | |
| 302 | if (action_id) |
| 303 | *action_id = result[2]; |
| 304 | |
| 305 | return true; |
| 306 | } |
| 307 | |
| 308 | // Find a specific handler in the handler list. |
| 309 | FileBrowserHandlerList::iterator FindHandler( |
| 310 | FileBrowserHandlerList* handler_list, |
| 311 | const std::string& extension_id, |
| 312 | const std::string& id) { |
| 313 | FileBrowserHandlerList::iterator iter = handler_list->begin(); |
| 314 | while (iter != handler_list->end() && |
| 315 | !((*iter)->extension_id() == extension_id && |
| 316 | (*iter)->id() == id)) { |
| 317 | ++iter; |
| 318 | } |
| 319 | return iter; |
| 320 | } |
| 321 | |
| 322 | // Given the list of selected files, returns array of file action tasks |
| 323 | // that are shared between them. |
| 324 | void FindDefaultTasks(Profile* profile, |
| 325 | const std::vector<base::FilePath>& files_list, |
| 326 | const FileBrowserHandlerList& common_tasks, |
| 327 | FileBrowserHandlerList* default_tasks) { |
| 328 | DCHECK(default_tasks); |
| 329 | default_tasks->clear(); |
| 330 | |
| 331 | std::set<std::string> default_ids; |
| 332 | for (std::vector<base::FilePath>::const_iterator it = files_list.begin(); |
| 333 | it != files_list.end(); ++it) { |
| 334 | std::string task_id = file_tasks::GetDefaultTaskIdFromPrefs( |
| 335 | profile, "", it->Extension()); |
| 336 | if (!task_id.empty()) |
| 337 | default_ids.insert(task_id); |
| 338 | } |
| 339 | |
| 340 | const FileBrowserHandler* fallback_task = NULL; |
| 341 | // Convert the default task IDs collected above to one of the handler pointers |
| 342 | // from common_tasks. |
| 343 | for (FileBrowserHandlerList::const_iterator task_iter = common_tasks.begin(); |
| 344 | task_iter != common_tasks.end(); ++task_iter) { |
| 345 | std::string task_id = MakeTaskID((*task_iter)->extension_id(), kTaskFile, |
| 346 | (*task_iter)->id()); |
| 347 | std::set<std::string>::iterator default_iter = default_ids.find(task_id); |
| 348 | if (default_iter != default_ids.end()) { |
| 349 | default_tasks->push_back(*task_iter); |
| 350 | continue; |
| 351 | } |
| 352 | |
| 353 | // Remember the first fallback task. |
| 354 | if (!fallback_task && IsFallbackTask(*task_iter)) |
| 355 | fallback_task = *task_iter; |
| 356 | } |
| 357 | |
| 358 | // If there are no default tasks found, use fallback as default. |
| 359 | if (fallback_task && default_tasks->empty()) |
| 360 | default_tasks->push_back(fallback_task); |
| 361 | } |
| 362 | |
| 363 | // Given the list of selected files, returns array of context menu tasks |
| 364 | // that are shared |
| 365 | bool FindCommonTasks(Profile* profile, |
| 366 | const std::vector<GURL>& files_list, |
| 367 | FileBrowserHandlerList* common_tasks) { |
| 368 | DCHECK(common_tasks); |
| 369 | common_tasks->clear(); |
| 370 | |
| 371 | FileBrowserHandlerList common_task_list; |
| 372 | std::set<std::string> default_task_ids; |
| 373 | for (std::vector<GURL>::const_iterator it = files_list.begin(); |
| 374 | it != files_list.end(); ++it) { |
| 375 | FileBrowserHandlerList file_actions; |
| 376 | if (!GetFileBrowserHandlers(profile, *it, &file_actions)) |
| 377 | return false; |
| 378 | // If there is nothing to do for one file, the intersection of tasks for all |
| 379 | // files will be empty at the end, and so will the default tasks. |
| 380 | if (file_actions.empty()) |
| 381 | return true; |
| 382 | |
| 383 | // For the very first file, just copy all the elements. |
| 384 | if (it == files_list.begin()) { |
| 385 | common_task_list = file_actions; |
| 386 | } else { |
| 387 | // For all additional files, find intersection between the accumulated and |
| 388 | // file specific set. |
| 389 | FileBrowserHandlerList intersection; |
| 390 | std::set_intersection(common_task_list.begin(), common_task_list.end(), |
| 391 | file_actions.begin(), file_actions.end(), |
| 392 | std::back_inserter(intersection)); |
| 393 | common_task_list = intersection; |
| 394 | if (common_task_list.empty()) |
| 395 | return true; |
| 396 | } |
| 397 | } |
| 398 | |
| 399 | FileBrowserHandlerList::iterator watch_iter = FindHandler( |
| 400 | &common_task_list, kFileBrowserDomain, kFileBrowserWatchTaskId); |
| 401 | FileBrowserHandlerList::iterator gallery_iter = FindHandler( |
| 402 | &common_task_list, kFileBrowserDomain, kFileBrowserGalleryTaskId); |
| 403 | if (watch_iter != common_task_list.end() && |
| 404 | gallery_iter != common_task_list.end()) { |
| 405 | // Both "watch" and "gallery" actions are applicable which means that the |
| 406 | // selection is all videos. Showing them both is confusing, so we only keep |
| 407 | // the one that makes more sense ("watch" for single selection, "gallery" |
| 408 | // for multiple selection). |
| 409 | if (files_list.size() == 1) |
| 410 | common_task_list.erase(gallery_iter); |
| 411 | else |
| 412 | common_task_list.erase(watch_iter); |
| 413 | } |
| 414 | |
| 415 | common_tasks->swap(common_task_list); |
| 416 | return true; |
| 417 | } |
| 418 | |
| 419 | bool GetTaskForURLAndPath(Profile* profile, |
| 420 | const GURL& url, |
| 421 | const base::FilePath& file_path, |
| 422 | const FileBrowserHandler** handler) { |
| 423 | std::vector<GURL> file_urls; |
| 424 | file_urls.push_back(url); |
| 425 | |
| 426 | FileBrowserHandlerList default_tasks; |
| 427 | FileBrowserHandlerList common_tasks; |
| 428 | if (!FindCommonTasks(profile, file_urls, &common_tasks)) |
| 429 | return false; |
| 430 | |
| 431 | if (common_tasks.empty()) |
| 432 | return false; |
| 433 | |
| 434 | std::vector<base::FilePath> file_paths; |
| 435 | file_paths.push_back(file_path); |
| 436 | |
| 437 | FindDefaultTasks(profile, file_paths, common_tasks, &default_tasks); |
| 438 | |
| 439 | // If there's none, or more than one, then we don't have a canonical default. |
| 440 | if (!default_tasks.empty()) { |
| 441 | // There should not be multiple default tasks for a single URL. |
| 442 | DCHECK_EQ(1u, default_tasks.size()); |
| 443 | |
| 444 | *handler = *default_tasks.begin(); |
| 445 | return true; |
| 446 | } |
| 447 | |
| 448 | // If there are no default tasks, use first task in the list (file manager |
| 449 | // does the same in this situation). |
| 450 | // TODO(tbarzic): This is so not optimal behaviour. |
| 451 | *handler = *common_tasks.begin(); |
| 452 | return true; |
| 453 | } |
| 454 | |
| 455 | // ExtensionTaskExecutor executes tasks with kTaskFile type. |
| 456 | class ExtensionTaskExecutor { |
| 457 | public: |
| 458 | ExtensionTaskExecutor(Profile* profile, |
| 459 | const Extension* extension, |
| 460 | int32 tab_id, |
| 461 | const std::string& action_id); |
| 462 | |
| 463 | // Executes the task for each file. |done| will be run with the result. |
| 464 | void Execute(const std::vector<FileSystemURL>& file_urls, |
| 465 | const FileTaskFinishedCallback& done); |
| 466 | |
| 467 | private: |
| 468 | // This object is responsible to delete itself. |
| 469 | virtual ~ExtensionTaskExecutor(); |
| 470 | |
| 471 | struct FileDefinition { |
| 472 | FileDefinition(); |
| 473 | ~FileDefinition(); |
| 474 | |
| 475 | base::FilePath virtual_path; |
| 476 | base::FilePath absolute_path; |
| 477 | bool is_directory; |
| 478 | }; |
| 479 | |
| 480 | typedef std::vector<FileDefinition> FileDefinitionList; |
| 481 | |
| 482 | // Checks legitimacy of file url and grants file RO access permissions from |
| 483 | // handler (target) extension and its renderer process. |
| 484 | static FileDefinitionList SetupFileAccessPermissions( |
| 485 | scoped_refptr<fileapi::FileSystemContext> file_system_context_handler, |
| 486 | const scoped_refptr<const Extension>& handler_extension, |
| 487 | const std::vector<FileSystemURL>& file_urls); |
| 488 | |
| 489 | void DidOpenFileSystem(const std::vector<FileSystemURL>& file_urls, |
| 490 | base::PlatformFileError result, |
| 491 | const std::string& file_system_name, |
| 492 | const GURL& file_system_root); |
| 493 | |
| 494 | void ExecuteDoneOnUIThread(bool success); |
| 495 | void ExecuteFileActionsOnUIThread(const std::string& file_system_name, |
| 496 | const GURL& file_system_root, |
| 497 | const FileDefinitionList& file_list); |
| 498 | void SetupPermissionsAndDispatchEvent(const std::string& file_system_name, |
| 499 | const GURL& file_system_root, |
| 500 | const FileDefinitionList& file_list, |
| 501 | int handler_pid_in, |
| 502 | extensions::ExtensionHost* host); |
| 503 | |
| 504 | // Registers file permissions from |handler_host_permissions_| with |
| 505 | // ChildProcessSecurityPolicy for process with id |handler_pid|. |
| 506 | void SetupHandlerHostFileAccessPermissions( |
| 507 | const FileDefinitionList& file_list, |
| 508 | const Extension* extension, |
| 509 | int handler_pid); |
| 510 | |
| 511 | Profile* profile_; |
| 512 | scoped_refptr<const Extension> extension_; |
| 513 | int32 tab_id_; |
| 514 | const std::string action_id_; |
| 515 | FileTaskFinishedCallback done_; |
| 516 | base::WeakPtrFactory<ExtensionTaskExecutor> weak_ptr_factory_; |
| 517 | |
| 518 | DISALLOW_COPY_AND_ASSIGN(ExtensionTaskExecutor); |
| 519 | }; |
| 520 | |
| 521 | bool ExecuteFileTask(Profile* profile, |
| 522 | const GURL& source_url, |
| 523 | const std::string& file_browser_id, |
| 524 | int32 tab_id, |
| 525 | const std::string& extension_id, |
| 526 | const std::string& task_type, |
| 527 | const std::string& action_id, |
| 528 | const std::vector<FileSystemURL>& file_urls, |
| 529 | const FileTaskFinishedCallback& done) { |
| 530 | if (!FileBrowserHasAccessPermissionForFiles(profile, source_url, |
| 531 | file_browser_id, file_urls)) |
| 532 | return false; |
| 533 | |
| 534 | // drive::FileTaskExecutor is responsible to handle drive tasks. |
| 535 | if (task_type == kTaskDrive) { |
| 536 | DCHECK_EQ("open-with", action_id); |
| 537 | drive::FileTaskExecutor* executor = |
| 538 | new drive::FileTaskExecutor(profile, extension_id); |
| 539 | executor->Execute(file_urls, done); |
| 540 | return true; |
| 541 | } |
| 542 | |
| 543 | // Get the extension. |
| 544 | ExtensionService* service = |
| 545 | extensions::ExtensionSystem::Get(profile)->extension_service(); |
| 546 | const Extension* extension = service ? |
| 547 | service->GetExtensionById(extension_id, false) : NULL; |
| 548 | if (!extension) |
| 549 | return false; |
| 550 | |
| 551 | // Execute the task. |
| 552 | if (task_type == kTaskFile) { |
| 553 | // Forbid calling undeclared handlers. |
| 554 | if (!FindFileBrowserHandler(extension, action_id)) |
| 555 | return false; |
| 556 | |
| 557 | (new ExtensionTaskExecutor( |
| 558 | profile, extension, tab_id, action_id))->Execute(file_urls, done); |
| 559 | return true; |
| 560 | } else if (task_type == kTaskApp) { |
| 561 | for (size_t i = 0; i != file_urls.size(); ++i) { |
Ben Murdoch | bb1529c | 2013-08-08 10:24:53 +0100 | [diff] [blame^] | 562 | apps::LaunchPlatformAppWithFileHandler( |
Torne (Richard Coles) | a36e592 | 2013-08-05 13:57:33 +0100 | [diff] [blame] | 563 | profile, extension, action_id, file_urls[i].path()); |
| 564 | } |
| 565 | |
| 566 | if (!done.is_null()) |
| 567 | done.Run(true); |
| 568 | return true; |
| 569 | } |
| 570 | NOTREACHED(); |
| 571 | return false; |
| 572 | } |
| 573 | |
| 574 | ExtensionTaskExecutor::FileDefinition::FileDefinition() : is_directory(false) { |
| 575 | } |
| 576 | |
| 577 | ExtensionTaskExecutor::FileDefinition::~FileDefinition() { |
| 578 | } |
| 579 | |
| 580 | // static |
| 581 | ExtensionTaskExecutor::FileDefinitionList |
| 582 | ExtensionTaskExecutor::SetupFileAccessPermissions( |
| 583 | scoped_refptr<fileapi::FileSystemContext> file_system_context_handler, |
| 584 | const scoped_refptr<const Extension>& handler_extension, |
| 585 | const std::vector<FileSystemURL>& file_urls) { |
| 586 | DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 587 | DCHECK(handler_extension.get()); |
| 588 | |
| 589 | fileapi::ExternalFileSystemBackend* backend = |
| 590 | file_system_context_handler->external_backend(); |
| 591 | |
| 592 | FileDefinitionList file_list; |
| 593 | for (size_t i = 0; i < file_urls.size(); ++i) { |
| 594 | const FileSystemURL& url = file_urls[i]; |
| 595 | |
| 596 | // Check if this file system entry exists first. |
| 597 | base::PlatformFileInfo file_info; |
| 598 | |
| 599 | base::FilePath local_path = url.path(); |
| 600 | base::FilePath virtual_path = url.virtual_path(); |
| 601 | |
| 602 | bool is_drive_file = url.type() == fileapi::kFileSystemTypeDrive; |
| 603 | DCHECK(!is_drive_file || drive::util::IsUnderDriveMountPoint(local_path)); |
| 604 | |
| 605 | // If the file is under drive mount point, there is no actual file to be |
| 606 | // found on the url.path(). |
| 607 | if (!is_drive_file) { |
| 608 | if (!base::PathExists(local_path) || |
| 609 | file_util::IsLink(local_path) || |
| 610 | !file_util::GetFileInfo(local_path, &file_info)) { |
| 611 | continue; |
| 612 | } |
| 613 | } |
| 614 | |
| 615 | // Grant access to this particular file to target extension. This will |
| 616 | // ensure that the target extension can access only this FS entry and |
| 617 | // prevent from traversing FS hierarchy upward. |
| 618 | backend->GrantFileAccessToExtension( |
| 619 | handler_extension->id(), virtual_path); |
| 620 | |
| 621 | // Output values. |
| 622 | FileDefinition file; |
| 623 | file.virtual_path = virtual_path; |
| 624 | file.is_directory = file_info.is_directory; |
| 625 | file.absolute_path = local_path; |
| 626 | file_list.push_back(file); |
| 627 | } |
| 628 | return file_list; |
| 629 | } |
| 630 | |
| 631 | ExtensionTaskExecutor::ExtensionTaskExecutor( |
| 632 | Profile* profile, |
| 633 | const Extension* extension, |
| 634 | int32 tab_id, |
| 635 | const std::string& action_id) |
| 636 | : profile_(profile), |
| 637 | extension_(extension), |
| 638 | tab_id_(tab_id), |
| 639 | action_id_(action_id), |
| 640 | weak_ptr_factory_(this) { |
| 641 | } |
| 642 | |
| 643 | ExtensionTaskExecutor::~ExtensionTaskExecutor() {} |
| 644 | |
| 645 | void ExtensionTaskExecutor::Execute(const std::vector<FileSystemURL>& file_urls, |
| 646 | const FileTaskFinishedCallback& done) { |
| 647 | done_ = done; |
| 648 | |
| 649 | // Get file system context for the extension to which onExecute event will be |
| 650 | // sent. The file access permissions will be granted to the extension in the |
| 651 | // file system context for the files in |file_urls|. |
| 652 | GetFileSystemContextForExtension(profile_, extension_->id())->OpenFileSystem( |
| 653 | Extension::GetBaseURLFromExtensionId(extension_->id()).GetOrigin(), |
| 654 | fileapi::kFileSystemTypeExternal, |
| 655 | fileapi::OPEN_FILE_SYSTEM_FAIL_IF_NONEXISTENT, |
| 656 | base::Bind(&ExtensionTaskExecutor::DidOpenFileSystem, |
| 657 | weak_ptr_factory_.GetWeakPtr(), |
| 658 | file_urls)); |
| 659 | } |
| 660 | |
| 661 | void ExtensionTaskExecutor::DidOpenFileSystem( |
| 662 | const std::vector<FileSystemURL>& file_urls, |
| 663 | base::PlatformFileError result, |
| 664 | const std::string& file_system_name, |
| 665 | const GURL& file_system_root) { |
| 666 | if (result != base::PLATFORM_FILE_OK) { |
| 667 | ExecuteDoneOnUIThread(false); |
| 668 | return; |
| 669 | } |
| 670 | |
| 671 | scoped_refptr<fileapi::FileSystemContext> file_system_context( |
| 672 | GetFileSystemContextForExtension(profile_, extension_->id())); |
| 673 | BrowserThread::PostTaskAndReplyWithResult( |
| 674 | BrowserThread::FILE, |
| 675 | FROM_HERE, |
| 676 | base::Bind(&SetupFileAccessPermissions, |
| 677 | file_system_context, |
| 678 | extension_, |
| 679 | file_urls), |
| 680 | base::Bind(&ExtensionTaskExecutor::ExecuteFileActionsOnUIThread, |
| 681 | weak_ptr_factory_.GetWeakPtr(), |
| 682 | file_system_name, |
| 683 | file_system_root)); |
| 684 | } |
| 685 | |
| 686 | void ExtensionTaskExecutor::ExecuteDoneOnUIThread(bool success) { |
| 687 | DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 688 | if (!done_.is_null()) |
| 689 | done_.Run(success); |
| 690 | delete this; |
| 691 | } |
| 692 | |
| 693 | void ExtensionTaskExecutor::ExecuteFileActionsOnUIThread( |
| 694 | const std::string& file_system_name, |
| 695 | const GURL& file_system_root, |
| 696 | const FileDefinitionList& file_list) { |
| 697 | DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 698 | |
| 699 | if (file_list.empty()) { |
| 700 | ExecuteDoneOnUIThread(false); |
| 701 | return; |
| 702 | } |
| 703 | |
| 704 | int handler_pid = ExtractProcessFromExtensionId(profile_, extension_->id()); |
| 705 | if (handler_pid <= 0 && |
| 706 | !extensions::BackgroundInfo::HasLazyBackgroundPage(extension_.get())) { |
| 707 | ExecuteDoneOnUIThread(false); |
| 708 | return; |
| 709 | } |
| 710 | |
| 711 | if (handler_pid > 0) { |
| 712 | SetupPermissionsAndDispatchEvent(file_system_name, file_system_root, |
| 713 | file_list, handler_pid, NULL); |
| 714 | } else { |
| 715 | // We have to wake the handler background page before we proceed. |
| 716 | extensions::LazyBackgroundTaskQueue* queue = |
| 717 | extensions::ExtensionSystem::Get(profile_)-> |
| 718 | lazy_background_task_queue(); |
| 719 | if (!queue->ShouldEnqueueTask(profile_, extension_.get())) { |
| 720 | ExecuteDoneOnUIThread(false); |
| 721 | return; |
| 722 | } |
| 723 | queue->AddPendingTask( |
| 724 | profile_, extension_->id(), |
| 725 | base::Bind(&ExtensionTaskExecutor::SetupPermissionsAndDispatchEvent, |
| 726 | weak_ptr_factory_.GetWeakPtr(), |
| 727 | file_system_name, |
| 728 | file_system_root, |
| 729 | file_list, |
| 730 | handler_pid)); |
| 731 | } |
| 732 | } |
| 733 | |
| 734 | void ExtensionTaskExecutor::SetupPermissionsAndDispatchEvent( |
| 735 | const std::string& file_system_name, |
| 736 | const GURL& file_system_root, |
| 737 | const FileDefinitionList& file_list, |
| 738 | int handler_pid_in, |
| 739 | extensions::ExtensionHost* host) { |
| 740 | int handler_pid = host ? host->render_process_host()->GetID() : |
| 741 | handler_pid_in; |
| 742 | |
| 743 | if (handler_pid <= 0) { |
| 744 | ExecuteDoneOnUIThread(false); |
| 745 | return; |
| 746 | } |
| 747 | |
| 748 | extensions::EventRouter* event_router = |
| 749 | extensions::ExtensionSystem::Get(profile_)->event_router(); |
| 750 | if (!event_router) { |
| 751 | ExecuteDoneOnUIThread(false); |
| 752 | return; |
| 753 | } |
| 754 | |
| 755 | SetupHandlerHostFileAccessPermissions( |
| 756 | file_list, extension_.get(), handler_pid); |
| 757 | |
| 758 | scoped_ptr<ListValue> event_args(new ListValue()); |
| 759 | event_args->Append(new base::StringValue(action_id_)); |
| 760 | DictionaryValue* details = new DictionaryValue(); |
| 761 | event_args->Append(details); |
| 762 | // Get file definitions. These will be replaced with Entry instances by |
| 763 | // dispatchEvent() method from event_binding.js. |
| 764 | ListValue* files_urls = new ListValue(); |
| 765 | details->Set("entries", files_urls); |
| 766 | for (FileDefinitionList::const_iterator iter = file_list.begin(); |
| 767 | iter != file_list.end(); |
| 768 | ++iter) { |
| 769 | DictionaryValue* file_def = new DictionaryValue(); |
| 770 | files_urls->Append(file_def); |
| 771 | file_def->SetString("fileSystemName", file_system_name); |
| 772 | file_def->SetString("fileSystemRoot", file_system_root.spec()); |
| 773 | base::FilePath root(FILE_PATH_LITERAL("/")); |
| 774 | base::FilePath full_path = root.Append(iter->virtual_path); |
| 775 | file_def->SetString("fileFullPath", full_path.value()); |
| 776 | file_def->SetBoolean("fileIsDirectory", iter->is_directory); |
| 777 | } |
| 778 | |
| 779 | details->SetInteger("tab_id", tab_id_); |
| 780 | |
| 781 | scoped_ptr<extensions::Event> event(new extensions::Event( |
| 782 | "fileBrowserHandler.onExecute", event_args.Pass())); |
| 783 | event->restrict_to_profile = profile_; |
| 784 | event_router->DispatchEventToExtension(extension_->id(), event.Pass()); |
| 785 | |
| 786 | ExecuteDoneOnUIThread(true); |
| 787 | } |
| 788 | |
| 789 | void ExtensionTaskExecutor::SetupHandlerHostFileAccessPermissions( |
| 790 | const FileDefinitionList& file_list, |
| 791 | const Extension* extension, |
| 792 | int handler_pid) { |
| 793 | const FileBrowserHandler* action = FindFileBrowserHandler(extension_, |
| 794 | action_id_); |
| 795 | for (FileDefinitionList::const_iterator iter = file_list.begin(); |
| 796 | iter != file_list.end(); |
| 797 | ++iter) { |
| 798 | if (!action) |
| 799 | continue; |
| 800 | if (action->CanRead()) { |
| 801 | content::ChildProcessSecurityPolicy::GetInstance()->GrantReadFile( |
| 802 | handler_pid, iter->absolute_path); |
| 803 | } |
| 804 | if (action->CanWrite()) { |
| 805 | content::ChildProcessSecurityPolicy::GetInstance()-> |
| 806 | GrantCreateReadWriteFile(handler_pid, iter->absolute_path); |
| 807 | } |
| 808 | } |
| 809 | } |
| 810 | |
| 811 | } // namespace file_tasks |
| 812 | } // namespace file_manager |