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_browser_handlers.h"
6
7 #include "base/bind.h"
8 #include "base/file_util.h"
9 #include "base/i18n/case_conversion.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "chrome/browser/chromeos/drive/file_system_util.h"
12 #include "chrome/browser/chromeos/file_manager/app_id.h"
13 #include "chrome/browser/chromeos/file_manager/fileapi_util.h"
14 #include "chrome/browser/chromeos/file_manager/open_with_browser.h"
15 #include "chrome/browser/chromeos/fileapi/file_system_backend.h"
16 #include "chrome/browser/extensions/extension_service.h"
17 #include "chrome/browser/extensions/extension_util.h"
18 #include "chrome/browser/profiles/profile.h"
19 #include "chrome/browser/ui/browser_finder.h"
20 #include "chrome/common/extensions/api/file_browser_handlers/file_browser_handler.h"
21 #include "chrome/common/extensions/api/file_browser_private.h"
22 #include "chrome/common/extensions/extension_constants.h"
23 #include "content/public/browser/browser_thread.h"
24 #include "content/public/browser/child_process_security_policy.h"
25 #include "content/public/browser/render_process_host.h"
26 #include "content/public/browser/site_instance.h"
27 #include "content/public/browser/web_contents.h"
28 #include "extensions/browser/event_router.h"
29 #include "extensions/browser/extension_host.h"
30 #include "extensions/browser/extension_system.h"
31 #include "extensions/browser/lazy_background_task_queue.h"
32 #include "extensions/common/extension_set.h"
33 #include "extensions/common/manifest_handlers/background_info.h"
34 #include "net/base/escape.h"
35 #include "webkit/browser/fileapi/file_system_context.h"
36 #include "webkit/browser/fileapi/file_system_url.h"
37 #include "webkit/common/fileapi/file_system_info.h"
38 #include "webkit/common/fileapi/file_system_util.h"
39
40 using content::BrowserThread;
41 using content::ChildProcessSecurityPolicy;
42 using content::SiteInstance;
43 using content::WebContents;
44 using extensions::Extension;
45 using fileapi::FileSystemURL;
46 using file_manager::util::EntryDefinition;
47 using file_manager::util::EntryDefinitionList;
48 using file_manager::util::FileDefinition;
49 using file_manager::util::FileDefinitionList;
50
51 namespace file_manager {
52 namespace file_browser_handlers {
53 namespace {
54
55 // Returns process id of the process the extension is running in.
ExtractProcessFromExtensionId(Profile * profile,const std::string & extension_id)56 int ExtractProcessFromExtensionId(Profile* profile,
57 const std::string& extension_id) {
58 GURL extension_url =
59 Extension::GetBaseURLFromExtensionId(extension_id);
60 extensions::ProcessManager* manager =
61 extensions::ExtensionSystem::Get(profile)->process_manager();
62
63 SiteInstance* site_instance = manager->GetSiteInstanceForURL(extension_url);
64 if (!site_instance || !site_instance->HasProcess())
65 return -1;
66 content::RenderProcessHost* process = site_instance->GetProcess();
67
68 return process->GetID();
69 }
70
71 // Finds a file browser handler that matches |action_id|. Returns NULL if not
72 // found.
FindFileBrowserHandlerForActionId(const Extension * extension,const std::string & action_id)73 const FileBrowserHandler* FindFileBrowserHandlerForActionId(
74 const Extension* extension,
75 const std::string& action_id) {
76 FileBrowserHandler::List* handler_list =
77 FileBrowserHandler::GetHandlers(extension);
78 for (FileBrowserHandler::List::const_iterator handler_iter =
79 handler_list->begin();
80 handler_iter != handler_list->end();
81 ++handler_iter) {
82 if (handler_iter->get()->id() == action_id)
83 return handler_iter->get();
84 }
85 return NULL;
86 }
87
EscapedUtf8ToLower(const std::string & str)88 std::string EscapedUtf8ToLower(const std::string& str) {
89 base::string16 utf16 = base::UTF8ToUTF16(
90 net::UnescapeURLComponent(str, net::UnescapeRule::NORMAL));
91 return net::EscapeUrlEncodedData(
92 base::UTF16ToUTF8(base::i18n::ToLower(utf16)),
93 false /* do not replace space with plus */);
94 }
95
96 // Finds file browser handlers that can handle the |selected_file_url|.
FindFileBrowserHandlersForURL(Profile * profile,const GURL & selected_file_url)97 FileBrowserHandlerList FindFileBrowserHandlersForURL(
98 Profile* profile,
99 const GURL& selected_file_url) {
100 ExtensionService* service =
101 extensions::ExtensionSystem::Get(profile)->extension_service();
102 // In unit-tests, we may not have an ExtensionService.
103 if (!service)
104 return FileBrowserHandlerList();
105
106 // We need case-insensitive matching, and pattern in the handler is already
107 // in lower case.
108 const GURL lowercase_url(EscapedUtf8ToLower(selected_file_url.spec()));
109
110 FileBrowserHandlerList results;
111 for (extensions::ExtensionSet::const_iterator iter =
112 service->extensions()->begin();
113 iter != service->extensions()->end(); ++iter) {
114 const Extension* extension = iter->get();
115 if (profile->IsOffTheRecord() &&
116 !extensions::util::IsIncognitoEnabled(extension->id(), profile))
117 continue;
118
119 FileBrowserHandler::List* handler_list =
120 FileBrowserHandler::GetHandlers(extension);
121 if (!handler_list)
122 continue;
123 for (FileBrowserHandler::List::const_iterator handler_iter =
124 handler_list->begin();
125 handler_iter != handler_list->end();
126 ++handler_iter) {
127 const FileBrowserHandler* handler = handler_iter->get();
128 if (!handler->MatchesURL(lowercase_url))
129 continue;
130
131 results.push_back(handler_iter->get());
132 }
133 }
134 return results;
135 }
136
137 // This class is used to execute a file browser handler task. Here's how this
138 // works:
139 //
140 // 1) Open the "external" file system
141 // 2) Set up permissions for the target files on the external file system.
142 // 3) Raise onExecute event with the action ID and entries of the target
143 // files. The event will launch the file browser handler if not active.
144 // 4) In the file browser handler, onExecute event is handled and executes the
145 // task in JavaScript.
146 //
147 // That said, the class itself does not execute a task. The task will be
148 // executed in JavaScript.
149 class FileBrowserHandlerExecutor {
150 public:
151 FileBrowserHandlerExecutor(Profile* profile,
152 const Extension* extension,
153 const std::string& action_id);
154
155 // Executes the task for each file. |done| will be run with the result.
156 void Execute(const std::vector<FileSystemURL>& file_urls,
157 const file_tasks::FileTaskFinishedCallback& done);
158
159 private:
160 // This object is responsible to delete itself.
161 virtual ~FileBrowserHandlerExecutor();
162
163 // Checks legitimacy of file url and grants file RO access permissions from
164 // handler (target) extension and its renderer process.
165 static scoped_ptr<FileDefinitionList> SetupFileAccessPermissions(
166 scoped_refptr<fileapi::FileSystemContext> file_system_context_handler,
167 const scoped_refptr<const Extension>& handler_extension,
168 const std::vector<FileSystemURL>& file_urls);
169
170 void ExecuteDoneOnUIThread(bool success);
171 void ExecuteAfterSetupFileAccess(scoped_ptr<FileDefinitionList> file_list);
172 void ExecuteFileActionsOnUIThread(
173 scoped_ptr<FileDefinitionList> file_definition_list,
174 scoped_ptr<EntryDefinitionList> entry_definition_list);
175 void SetupPermissionsAndDispatchEvent(
176 scoped_ptr<FileDefinitionList> file_definition_list,
177 scoped_ptr<EntryDefinitionList> entry_definition_list,
178 int handler_pid_in,
179 extensions::ExtensionHost* host);
180
181 // Registers file permissions from |handler_host_permissions_| with
182 // ChildProcessSecurityPolicy for process with id |handler_pid|.
183 void SetupHandlerHostFileAccessPermissions(
184 FileDefinitionList* file_definition_list,
185 const Extension* extension,
186 int handler_pid);
187
188 Profile* profile_;
189 scoped_refptr<const Extension> extension_;
190 const std::string action_id_;
191 file_tasks::FileTaskFinishedCallback done_;
192 base::WeakPtrFactory<FileBrowserHandlerExecutor> weak_ptr_factory_;
193
194 DISALLOW_COPY_AND_ASSIGN(FileBrowserHandlerExecutor);
195 };
196
197 // static
198 scoped_ptr<FileDefinitionList>
SetupFileAccessPermissions(scoped_refptr<fileapi::FileSystemContext> file_system_context_handler,const scoped_refptr<const Extension> & handler_extension,const std::vector<FileSystemURL> & file_urls)199 FileBrowserHandlerExecutor::SetupFileAccessPermissions(
200 scoped_refptr<fileapi::FileSystemContext> file_system_context_handler,
201 const scoped_refptr<const Extension>& handler_extension,
202 const std::vector<FileSystemURL>& file_urls) {
203 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
204 DCHECK(handler_extension.get());
205
206 fileapi::ExternalFileSystemBackend* backend =
207 file_system_context_handler->external_backend();
208
209 scoped_ptr<FileDefinitionList> file_definition_list(new FileDefinitionList);
210 for (size_t i = 0; i < file_urls.size(); ++i) {
211 const FileSystemURL& url = file_urls[i];
212
213 // Check if this file system entry exists first.
214 base::File::Info file_info;
215
216 base::FilePath local_path = url.path();
217 base::FilePath virtual_path = url.virtual_path();
218
219 const bool is_drive_file = url.type() == fileapi::kFileSystemTypeDrive;
220 DCHECK(!is_drive_file || drive::util::IsUnderDriveMountPoint(local_path));
221
222 const bool is_native_file =
223 url.type() == fileapi::kFileSystemTypeNativeLocal ||
224 url.type() == fileapi::kFileSystemTypeRestrictedNativeLocal;
225
226 // If the file is from a physical volume, actual file must be found.
227 if (is_native_file) {
228 if (!base::PathExists(local_path) ||
229 base::IsLink(local_path) ||
230 !base::GetFileInfo(local_path, &file_info)) {
231 continue;
232 }
233 }
234
235 // Grant access to this particular file to target extension. This will
236 // ensure that the target extension can access only this FS entry and
237 // prevent from traversing FS hierarchy upward.
238 backend->GrantFileAccessToExtension(handler_extension->id(), virtual_path);
239
240 // Output values.
241 FileDefinition file_definition;
242 file_definition.virtual_path = virtual_path;
243 file_definition.is_directory = file_info.is_directory;
244 file_definition.absolute_path = local_path;
245 file_definition_list->push_back(file_definition);
246 }
247
248 return file_definition_list.Pass();
249 }
250
FileBrowserHandlerExecutor(Profile * profile,const Extension * extension,const std::string & action_id)251 FileBrowserHandlerExecutor::FileBrowserHandlerExecutor(
252 Profile* profile,
253 const Extension* extension,
254 const std::string& action_id)
255 : profile_(profile),
256 extension_(extension),
257 action_id_(action_id),
258 weak_ptr_factory_(this) {
259 }
260
~FileBrowserHandlerExecutor()261 FileBrowserHandlerExecutor::~FileBrowserHandlerExecutor() {}
262
Execute(const std::vector<FileSystemURL> & file_urls,const file_tasks::FileTaskFinishedCallback & done)263 void FileBrowserHandlerExecutor::Execute(
264 const std::vector<FileSystemURL>& file_urls,
265 const file_tasks::FileTaskFinishedCallback& done) {
266 done_ = done;
267
268 // Get file system context for the extension to which onExecute event will be
269 // sent. The file access permissions will be granted to the extension in the
270 // file system context for the files in |file_urls|.
271 scoped_refptr<fileapi::FileSystemContext> file_system_context(
272 util::GetFileSystemContextForExtensionId(
273 profile_, extension_->id()));
274
275 BrowserThread::PostTaskAndReplyWithResult(
276 BrowserThread::FILE,
277 FROM_HERE,
278 base::Bind(&SetupFileAccessPermissions,
279 file_system_context,
280 extension_,
281 file_urls),
282 base::Bind(&FileBrowserHandlerExecutor::ExecuteAfterSetupFileAccess,
283 weak_ptr_factory_.GetWeakPtr()));
284 }
285
ExecuteAfterSetupFileAccess(scoped_ptr<FileDefinitionList> file_definition_list)286 void FileBrowserHandlerExecutor::ExecuteAfterSetupFileAccess(
287 scoped_ptr<FileDefinitionList> file_definition_list) {
288 // Outlives the conversion process, since bound to the callback.
289 const FileDefinitionList& file_definition_list_ref =
290 *file_definition_list.get();
291 file_manager::util::ConvertFileDefinitionListToEntryDefinitionList(
292 profile_,
293 extension_->id(),
294 file_definition_list_ref,
295 base::Bind(&FileBrowserHandlerExecutor::ExecuteFileActionsOnUIThread,
296 weak_ptr_factory_.GetWeakPtr(),
297 base::Passed(&file_definition_list)));
298 }
299
ExecuteDoneOnUIThread(bool success)300 void FileBrowserHandlerExecutor::ExecuteDoneOnUIThread(bool success) {
301 DCHECK_CURRENTLY_ON(BrowserThread::UI);
302 if (!done_.is_null())
303 done_.Run(
304 success
305 ? extensions::api::file_browser_private::TASK_RESULT_MESSAGE_SENT
306 : extensions::api::file_browser_private::TASK_RESULT_FAILED);
307 delete this;
308 }
309
ExecuteFileActionsOnUIThread(scoped_ptr<FileDefinitionList> file_definition_list,scoped_ptr<EntryDefinitionList> entry_definition_list)310 void FileBrowserHandlerExecutor::ExecuteFileActionsOnUIThread(
311 scoped_ptr<FileDefinitionList> file_definition_list,
312 scoped_ptr<EntryDefinitionList> entry_definition_list) {
313 DCHECK_CURRENTLY_ON(BrowserThread::UI);
314
315 if (file_definition_list->empty() || entry_definition_list->empty()) {
316 ExecuteDoneOnUIThread(false);
317 return;
318 }
319
320 int handler_pid = ExtractProcessFromExtensionId(profile_, extension_->id());
321 if (handler_pid <= 0 &&
322 !extensions::BackgroundInfo::HasLazyBackgroundPage(extension_.get())) {
323 ExecuteDoneOnUIThread(false);
324 return;
325 }
326
327 if (handler_pid > 0) {
328 SetupPermissionsAndDispatchEvent(file_definition_list.Pass(),
329 entry_definition_list.Pass(),
330 handler_pid,
331 NULL);
332 } else {
333 // We have to wake the handler background page before we proceed.
334 extensions::LazyBackgroundTaskQueue* queue =
335 extensions::ExtensionSystem::Get(profile_)->
336 lazy_background_task_queue();
337 if (!queue->ShouldEnqueueTask(profile_, extension_.get())) {
338 ExecuteDoneOnUIThread(false);
339 return;
340 }
341 queue->AddPendingTask(
342 profile_,
343 extension_->id(),
344 base::Bind(
345 &FileBrowserHandlerExecutor::SetupPermissionsAndDispatchEvent,
346 weak_ptr_factory_.GetWeakPtr(),
347 base::Passed(file_definition_list.Pass()),
348 base::Passed(entry_definition_list.Pass()),
349 handler_pid));
350 }
351 }
352
SetupPermissionsAndDispatchEvent(scoped_ptr<FileDefinitionList> file_definition_list,scoped_ptr<EntryDefinitionList> entry_definition_list,int handler_pid_in,extensions::ExtensionHost * host)353 void FileBrowserHandlerExecutor::SetupPermissionsAndDispatchEvent(
354 scoped_ptr<FileDefinitionList> file_definition_list,
355 scoped_ptr<EntryDefinitionList> entry_definition_list,
356 int handler_pid_in,
357 extensions::ExtensionHost* host) {
358 int handler_pid = host ? host->render_process_host()->GetID() :
359 handler_pid_in;
360
361 if (handler_pid <= 0) {
362 ExecuteDoneOnUIThread(false);
363 return;
364 }
365
366 extensions::EventRouter* router = extensions::EventRouter::Get(profile_);
367 if (!router) {
368 ExecuteDoneOnUIThread(false);
369 return;
370 }
371
372 SetupHandlerHostFileAccessPermissions(
373 file_definition_list.get(), extension_.get(), handler_pid);
374
375 scoped_ptr<base::ListValue> event_args(new base::ListValue());
376 event_args->Append(new base::StringValue(action_id_));
377 base::DictionaryValue* details = new base::DictionaryValue();
378 event_args->Append(details);
379 // Get file definitions. These will be replaced with Entry instances by
380 // dispatchEvent() method from event_binding.js.
381 base::ListValue* file_entries = new base::ListValue();
382 details->Set("entries", file_entries);
383
384 for (EntryDefinitionList::const_iterator iter =
385 entry_definition_list->begin();
386 iter != entry_definition_list->end();
387 ++iter) {
388 base::DictionaryValue* file_def = new base::DictionaryValue();
389 file_entries->Append(file_def);
390 file_def->SetString("fileSystemName", iter->file_system_name);
391 file_def->SetString("fileSystemRoot", iter->file_system_root_url);
392 file_def->SetString("fileFullPath",
393 "/" + iter->full_path.AsUTF8Unsafe());
394 file_def->SetBoolean("fileIsDirectory", iter->is_directory);
395 }
396
397 scoped_ptr<extensions::Event> event(new extensions::Event(
398 "fileBrowserHandler.onExecute", event_args.Pass()));
399 event->restrict_to_browser_context = profile_;
400 router->DispatchEventToExtension(extension_->id(), event.Pass());
401
402 ExecuteDoneOnUIThread(true);
403 }
404
SetupHandlerHostFileAccessPermissions(FileDefinitionList * file_definition_list,const Extension * extension,int handler_pid)405 void FileBrowserHandlerExecutor::SetupHandlerHostFileAccessPermissions(
406 FileDefinitionList* file_definition_list,
407 const Extension* extension,
408 int handler_pid) {
409 const FileBrowserHandler* action = FindFileBrowserHandlerForActionId(
410 extension_, action_id_);
411 for (FileDefinitionList::const_iterator iter = file_definition_list->begin();
412 iter != file_definition_list->end();
413 ++iter) {
414 if (!action)
415 continue;
416 if (action->CanRead()) {
417 content::ChildProcessSecurityPolicy::GetInstance()->GrantReadFile(
418 handler_pid, iter->absolute_path);
419 }
420 if (action->CanWrite()) {
421 content::ChildProcessSecurityPolicy::GetInstance()->
422 GrantCreateReadWriteFile(handler_pid, iter->absolute_path);
423 }
424 }
425 }
426
427 // Returns true if |extension_id| and |action_id| indicate that the file
428 // currently being handled should be opened with the browser. This function
429 // is used to handle certain action IDs of the file manager.
ShouldBeOpenedWithBrowser(const std::string & extension_id,const std::string & action_id)430 bool ShouldBeOpenedWithBrowser(const std::string& extension_id,
431 const std::string& action_id) {
432
433 return (extension_id == kFileManagerAppId &&
434 (action_id == "view-pdf" ||
435 action_id == "view-swf" ||
436 action_id == "view-in-browser" ||
437 action_id == "open-hosted-generic" ||
438 action_id == "open-hosted-gdoc" ||
439 action_id == "open-hosted-gsheet" ||
440 action_id == "open-hosted-gslides"));
441 }
442
443 // Opens the files specified by |file_urls| with the browser for |profile|.
444 // Returns true on success. It's a failure if no files are opened.
OpenFilesWithBrowser(Profile * profile,const std::vector<FileSystemURL> & file_urls)445 bool OpenFilesWithBrowser(Profile* profile,
446 const std::vector<FileSystemURL>& file_urls) {
447 int num_opened = 0;
448 for (size_t i = 0; i < file_urls.size(); ++i) {
449 const FileSystemURL& file_url = file_urls[i];
450 if (chromeos::FileSystemBackend::CanHandleURL(file_url)) {
451 const base::FilePath& file_path = file_url.path();
452 num_opened += util::OpenFileWithBrowser(profile, file_path);
453 }
454 }
455 return num_opened > 0;
456 }
457
458 } // namespace
459
ExecuteFileBrowserHandler(Profile * profile,const Extension * extension,const std::string & action_id,const std::vector<FileSystemURL> & file_urls,const file_tasks::FileTaskFinishedCallback & done)460 bool ExecuteFileBrowserHandler(
461 Profile* profile,
462 const Extension* extension,
463 const std::string& action_id,
464 const std::vector<FileSystemURL>& file_urls,
465 const file_tasks::FileTaskFinishedCallback& done) {
466 // Forbid calling undeclared handlers.
467 if (!FindFileBrowserHandlerForActionId(extension, action_id))
468 return false;
469
470 // Some action IDs of the file manager's file browser handlers require the
471 // files to be directly opened with the browser.
472 if (ShouldBeOpenedWithBrowser(extension->id(), action_id)) {
473 const bool result = OpenFilesWithBrowser(profile, file_urls);
474 if (result && !done.is_null())
475 done.Run(extensions::api::file_browser_private::TASK_RESULT_OPENED);
476 return result;
477 }
478
479 // The executor object will be self deleted on completion.
480 (new FileBrowserHandlerExecutor(
481 profile, extension, action_id))->Execute(file_urls, done);
482 return true;
483 }
484
IsFallbackFileBrowserHandler(const file_tasks::TaskDescriptor & task)485 bool IsFallbackFileBrowserHandler(const file_tasks::TaskDescriptor& task) {
486 return ((task.task_type == file_tasks::TASK_TYPE_FILE_BROWSER_HANDLER ||
487 task.task_type == file_tasks::TASK_TYPE_FILE_HANDLER) &&
488 (task.app_id == kFileManagerAppId ||
489 task.app_id == kVideoPlayerAppId ||
490 task.app_id == kGalleryAppId ||
491 task.app_id == extension_misc::kQuickOfficeComponentExtensionId ||
492 task.app_id == extension_misc::kQuickOfficeInternalExtensionId ||
493 task.app_id == extension_misc::kQuickOfficeExtensionId));
494 }
495
FindFileBrowserHandlers(Profile * profile,const std::vector<GURL> & file_list)496 FileBrowserHandlerList FindFileBrowserHandlers(
497 Profile* profile,
498 const std::vector<GURL>& file_list) {
499 FileBrowserHandlerList common_handlers;
500 for (std::vector<GURL>::const_iterator it = file_list.begin();
501 it != file_list.end(); ++it) {
502 FileBrowserHandlerList handlers =
503 FindFileBrowserHandlersForURL(profile, *it);
504 // If there is nothing to do for one file, the intersection of handlers
505 // for all files will be empty at the end, so no need to check further.
506 if (handlers.empty())
507 return FileBrowserHandlerList();
508
509 // For the very first file, just copy all the elements.
510 if (it == file_list.begin()) {
511 common_handlers = handlers;
512 } else {
513 // For all additional files, find intersection between the accumulated and
514 // file specific set.
515 FileBrowserHandlerList intersection;
516 std::set<const FileBrowserHandler*> common_handler_set(
517 common_handlers.begin(), common_handlers.end());
518
519 for (FileBrowserHandlerList::const_iterator itr = handlers.begin();
520 itr != handlers.end(); ++itr) {
521 if (ContainsKey(common_handler_set, *itr))
522 intersection.push_back(*itr);
523 }
524
525 std::swap(common_handlers, intersection);
526 if (common_handlers.empty())
527 return FileBrowserHandlerList();
528 }
529 }
530
531 return common_handlers;
532 }
533
534 } // namespace file_browser_handlers
535 } // namespace file_manager
536