• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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