blob: e38c55b3b153c47c297d8279ad6477214dc1526c [file] [log] [blame]
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001// 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#include "chrome/browser/chromeos/extensions/file_manager/file_manager_util.h"
5
6#include "ash/shell.h"
7#include "base/bind.h"
8#include "base/command_line.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01009#include "base/json/json_writer.h"
10#include "base/logging.h"
11#include "base/metrics/histogram.h"
12#include "base/path_service.h"
Torne (Richard Coles)5e3f23d2013-06-11 16:24:11 +010013#include "base/strings/string_util.h"
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010014#include "base/strings/utf_string_conversions.h"
15#include "base/threading/sequenced_worker_pool.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010016#include "base/values.h"
17#include "chrome/browser/chromeos/drive/drive.pb.h"
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010018#include "chrome/browser/chromeos/drive/drive_integration_service.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010019#include "chrome/browser/chromeos/drive/file_system.h"
20#include "chrome/browser/chromeos/drive/file_system_util.h"
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +010021#include "chrome/browser/chromeos/extensions/file_manager/file_tasks.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010022#include "chrome/browser/chromeos/media/media_player.h"
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010023#include "chrome/browser/extensions/api/file_handlers/app_file_handler_util.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010024#include "chrome/browser/extensions/crx_installer.h"
25#include "chrome/browser/extensions/extension_install_prompt.h"
26#include "chrome/browser/extensions/extension_service.h"
27#include "chrome/browser/extensions/extension_system.h"
28#include "chrome/browser/google_apis/task_util.h"
29#include "chrome/browser/plugins/plugin_prefs.h"
30#include "chrome/browser/profiles/profile.h"
31#include "chrome/browser/profiles/profile_manager.h"
32#include "chrome/browser/ui/browser.h"
33#include "chrome/browser/ui/browser_finder.h"
34#include "chrome/browser/ui/browser_iterator.h"
35#include "chrome/browser/ui/browser_tabstrip.h"
36#include "chrome/browser/ui/browser_window.h"
37#include "chrome/browser/ui/extensions/application_launch.h"
38#include "chrome/browser/ui/host_desktop.h"
39#include "chrome/browser/ui/simple_message_box.h"
40#include "chrome/browser/ui/tabs/tab_strip_model.h"
41#include "chrome/common/chrome_paths.h"
42#include "chrome/common/chrome_switches.h"
Ben Murdoch7dbb3d52013-07-17 14:55:54 +010043#include "chrome/common/extensions/api/file_browser_handlers/file_browser_handler.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010044#include "chrome/common/url_constants.h"
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +010045#include "chromeos/chromeos_switches.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010046#include "content/public/browser/browser_thread.h"
47#include "content/public/browser/plugin_service.h"
48#include "content/public/browser/storage_partition.h"
49#include "content/public/browser/user_metrics.h"
50#include "content/public/browser/web_contents.h"
51#include "content/public/common/pepper_plugin_info.h"
Ben Murdochca12bfa2013-07-23 11:17:05 +010052#include "content/public/common/webplugininfo.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010053#include "grit/generated_resources.h"
54#include "net/base/escape.h"
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010055#include "net/base/mime_util.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010056#include "net/base/net_util.h"
57#include "ui/base/l10n/l10n_util.h"
58#include "ui/gfx/screen.h"
Ben Murdoch7dbb3d52013-07-17 14:55:54 +010059#include "webkit/browser/fileapi/file_system_backend.h"
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010060#include "webkit/browser/fileapi/file_system_context.h"
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010061#include "webkit/browser/fileapi/file_system_operation_runner.h"
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010062#include "webkit/browser/fileapi/file_system_url.h"
63#include "webkit/common/fileapi/file_system_util.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010064
65using base::DictionaryValue;
66using base::ListValue;
67using content::BrowserContext;
68using content::BrowserThread;
69using content::PluginService;
70using content::UserMetricsAction;
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010071using extensions::app_file_handler_util::FindFileHandlersForFiles;
72using extensions::app_file_handler_util::PathAndMimeTypeSet;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010073using extensions::Extension;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010074using fileapi::FileSystemURL;
75
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010076const char kFileBrowserDomain[] = "hhaomjibdihmijegdhdafkllkbggdgoj";
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010077
78const char kFileBrowserGalleryTaskId[] = "gallery";
79const char kFileBrowserMountArchiveTaskId[] = "mount-archive";
80const char kFileBrowserWatchTaskId[] = "watch";
81const char kFileBrowserPlayTaskId[] = "play";
82
83const char kVideoPlayerAppName[] = "videoplayer";
84
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +010085namespace file_manager {
86namespace util {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010087namespace {
88
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010089const char kCRXExtension[] = ".crx";
90const char kPdfExtension[] = ".pdf";
91const char kSwfExtension[] = ".swf";
92// List of file extension we can open in tab.
93const char* kBrowserSupportedExtensions[] = {
94#if defined(GOOGLE_CHROME_BUILD)
95 ".pdf", ".swf",
96#endif
97 ".bmp", ".jpg", ".jpeg", ".png", ".webp", ".gif", ".txt", ".html", ".htm",
98 ".mhtml", ".mht", ".svg"
99};
100
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100101// List of all extensions we want to be shown in histogram that keep track of
102// files that were unsuccessfully tried to be opened.
103// The list has to be synced with histogram values.
104const char* kUMATrackingExtensions[] = {
105 "other", ".doc", ".docx", ".odt", ".rtf", ".pdf", ".ppt", ".pptx", ".odp",
106 ".xls", ".xlsx", ".ods", ".csv", ".odf", ".rar", ".asf", ".wma", ".wmv",
107 ".mov", ".mpg", ".log"
108};
109
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100110// Returns a file manager URL for the given |path|.
111GURL GetFileManagerUrl(const char* path) {
112 return GURL(std::string("chrome-extension://") + kFileBrowserDomain + path);
113}
114
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100115bool IsSupportedBrowserExtension(const char* file_extension) {
116 for (size_t i = 0; i < arraysize(kBrowserSupportedExtensions); i++) {
117 if (base::strcasecmp(file_extension, kBrowserSupportedExtensions[i]) == 0) {
118 return true;
119 }
120 }
121 return false;
122}
123
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100124bool IsCRXFile(const char* file_extension) {
125 return base::strcasecmp(file_extension, kCRXExtension) == 0;
126}
127
128bool IsPepperPluginEnabled(Profile* profile,
129 const base::FilePath& plugin_path) {
130 content::PepperPluginInfo* pepper_info =
131 PluginService::GetInstance()->GetRegisteredPpapiPluginInfo(plugin_path);
132 if (!pepper_info)
133 return false;
134
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100135 scoped_refptr<PluginPrefs> plugin_prefs = PluginPrefs::GetForProfile(profile);
136 if (!plugin_prefs.get())
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100137 return false;
138
139 return plugin_prefs->IsPluginEnabled(pepper_info->ToWebPluginInfo());
140}
141
142bool IsPdfPluginEnabled(Profile* profile) {
143 base::FilePath plugin_path;
144 PathService::Get(chrome::FILE_PDF_PLUGIN, &plugin_path);
145 return IsPepperPluginEnabled(profile, plugin_path);
146}
147
148bool IsFlashPluginEnabled(Profile* profile) {
149 base::FilePath plugin_path(
150 CommandLine::ForCurrentProcess()->GetSwitchValueNative(
151 switches::kPpapiFlashPath));
152 if (plugin_path.empty())
153 PathService::Get(chrome::FILE_PEPPER_FLASH_PLUGIN, &plugin_path);
154 return IsPepperPluginEnabled(profile, plugin_path);
155}
156
157// Returns index |ext| has in the |array|. If there is no |ext| in |array|, last
158// element's index is return (last element should have irrelevant value).
159int UMAExtensionIndex(const char *file_extension,
160 const char** array,
161 size_t array_size) {
162 for (size_t i = 0; i < array_size; i++) {
163 if (base::strcasecmp(file_extension, array[i]) == 0) {
164 return i;
165 }
166 }
167 return 0;
168}
169
170// Convert numeric dialog type to a string.
171std::string GetDialogTypeAsString(
172 ui::SelectFileDialog::Type dialog_type) {
173 std::string type_str;
174 switch (dialog_type) {
175 case ui::SelectFileDialog::SELECT_NONE:
176 type_str = "full-page";
177 break;
178
179 case ui::SelectFileDialog::SELECT_FOLDER:
180 type_str = "folder";
181 break;
182
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100183 case ui::SelectFileDialog::SELECT_UPLOAD_FOLDER:
184 type_str = "upload-folder";
185 break;
186
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100187 case ui::SelectFileDialog::SELECT_SAVEAS_FILE:
188 type_str = "saveas-file";
189 break;
190
191 case ui::SelectFileDialog::SELECT_OPEN_FILE:
192 type_str = "open-file";
193 break;
194
195 case ui::SelectFileDialog::SELECT_OPEN_MULTI_FILE:
196 type_str = "open-multi-file";
197 break;
198
199 default:
200 NOTREACHED();
201 }
202
203 return type_str;
204}
205
206void OpenNewTab(Profile* profile, const GURL& url) {
207 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
208 Browser* browser = chrome::FindOrCreateTabbedBrowser(
209 profile ? profile : ProfileManager::GetDefaultProfileOrOffTheRecord(),
210 chrome::HOST_DESKTOP_TYPE_ASH);
211 chrome::AddSelectedTabWithURL(browser, url, content::PAGE_TRANSITION_LINK);
212 // If the current browser is not tabbed then the new tab will be created
213 // in a different browser. Make sure it is visible.
214 browser->window()->Show();
215}
216
217// Shows a warning message box saying that the file could not be opened.
218void ShowWarningMessageBox(Profile* profile, const base::FilePath& path) {
219 // TODO: if FindOrCreateTabbedBrowser creates a new browser the returned
220 // browser is leaked.
221 Browser* browser =
222 chrome::FindOrCreateTabbedBrowser(profile,
223 chrome::HOST_DESKTOP_TYPE_ASH);
224 chrome::ShowMessageBox(
225 browser->window()->GetNativeWindow(),
226 l10n_util::GetStringFUTF16(
227 IDS_FILE_BROWSER_ERROR_VIEWING_FILE_TITLE,
228 UTF8ToUTF16(path.BaseName().value())),
229 l10n_util::GetStringUTF16(IDS_FILE_BROWSER_ERROR_VIEWING_FILE),
230 chrome::MESSAGE_BOX_TYPE_WARNING);
231}
232
233void InstallCRX(Browser* browser, const base::FilePath& path) {
234 ExtensionService* service =
235 extensions::ExtensionSystem::Get(browser->profile())->extension_service();
236 CHECK(service);
237
238 scoped_refptr<extensions::CrxInstaller> installer(
239 extensions::CrxInstaller::Create(
240 service,
241 new ExtensionInstallPrompt(browser->profile(), NULL, NULL)));
242 installer->set_error_on_unsupported_requirements(true);
243 installer->set_is_gallery_install(false);
244 installer->set_allow_silent_install(false);
245 installer->InstallCrx(path);
246}
247
248// Called when a crx file on Drive was downloaded.
249void OnCRXDownloadCallback(Browser* browser,
250 drive::FileError error,
251 const base::FilePath& file,
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100252 scoped_ptr<drive::ResourceEntry> entry) {
253 if (error != drive::FILE_ERROR_OK)
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100254 return;
255 InstallCRX(browser, file);
256}
257
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100258// Grants file system access to the file browser.
259bool GrantFileSystemAccessToFileBrowser(Profile* profile) {
260 // File browser always runs in the site for its extension id, so that is the
261 // site for which file access permissions should be granted.
262 GURL site = extensions::ExtensionSystem::Get(profile)->extension_service()->
263 GetSiteForExtensionId(kFileBrowserDomain);
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100264 fileapi::ExternalFileSystemBackend* backend =
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100265 BrowserContext::GetStoragePartitionForSite(profile, site)->
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100266 GetFileSystemContext()->external_backend();
267 if (!backend)
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100268 return false;
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100269 backend->GrantFullAccessToExtension(GetFileBrowserUrl().host());
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100270 return true;
271}
272
Ben Murdochbb1529c2013-08-08 10:24:53 +0100273// Executes handler specified with |extension_id| and |action_id| for |url|.
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100274void ExecuteHandler(Profile* profile,
275 std::string extension_id,
276 std::string action_id,
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100277 const GURL& url,
278 const std::string& task_type) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100279 // If File Browser has not been open yet then it did not request access
280 // to the file system. Do it now.
281 if (!GrantFileSystemAccessToFileBrowser(profile))
282 return;
283
284 GURL site = extensions::ExtensionSystem::Get(profile)->extension_service()->
285 GetSiteForExtensionId(kFileBrowserDomain);
286 fileapi::FileSystemContext* file_system_context =
287 BrowserContext::GetStoragePartitionForSite(profile, site)->
288 GetFileSystemContext();
289
290 // We are executing the task on behalf of File Browser extension.
291 const GURL source_url = GetFileBrowserUrl();
292 std::vector<FileSystemURL> urls;
293 urls.push_back(file_system_context->CrackURL(url));
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100294
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100295 file_tasks::ExecuteFileTask(
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100296 profile,
297 source_url,
298 kFileBrowserDomain,
299 0, // no tab id
300 extension_id,
301 task_type,
302 action_id,
303 urls,
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100304 file_tasks::FileTaskFinishedCallback());
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100305}
306
307void OpenFileBrowserImpl(const base::FilePath& path,
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100308 const std::string& action_id) {
309 content::RecordAction(UserMetricsAction("ShowFileBrowserFullTab"));
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100310 Profile* profile = ProfileManager::GetDefaultProfileOrOffTheRecord();
311
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100312 GURL url;
313 if (!ConvertFileToFileSystemUrl(profile, path, kFileBrowserDomain, &url))
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +0100314 return;
315
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100316 // Some values of |action_id| are not listed in the manifest and are used
Ben Murdochbb1529c2013-08-08 10:24:53 +0100317 // to parameterize the behavior when opening the Files app window.
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100318 ExecuteHandler(profile, kFileBrowserDomain, action_id, url,
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100319 file_tasks::kTaskFile);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100320}
321
322Browser* GetBrowserForUrl(GURL target_url) {
323 for (chrome::BrowserIterator it; !it.done(); it.Next()) {
324 Browser* browser = *it;
325 TabStripModel* tab_strip = browser->tab_strip_model();
326 for (int idx = 0; idx < tab_strip->count(); idx++) {
327 content::WebContents* web_contents = tab_strip->GetWebContentsAt(idx);
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100328 const GURL& url = web_contents->GetLastCommittedURL();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100329 if (url == target_url)
330 return browser;
331 }
332 }
333 return NULL;
334}
335
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100336bool ExecuteDefaultAppHandler(Profile* profile,
337 const base::FilePath& path,
338 const GURL& url,
339 const std::string& mime_type,
340 const std::string& default_task_id) {
341 ExtensionService* service = profile->GetExtensionService();
342 if (!service)
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100343 return false;
344
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100345 PathAndMimeTypeSet files;
346 files.insert(std::make_pair(path, mime_type));
347 const extensions::FileHandlerInfo* first_handler = NULL;
348 const extensions::Extension* extension_for_first_handler = NULL;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100349
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100350 // If we find the default handler, we execute it immediately, but otherwise,
351 // we remember the first handler, and if there was no default handler, simply
352 // execute the first one.
353 for (ExtensionSet::const_iterator iter = service->extensions()->begin();
354 iter != service->extensions()->end();
355 ++iter) {
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100356 const Extension* extension = iter->get();
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100357
358 // We don't support using hosted apps to open files.
359 if (!extension->is_platform_app())
360 continue;
361
362 // We only support apps that specify "incognito: split" if in incognito
363 // mode.
364 if (profile->IsOffTheRecord() &&
365 !service->IsIncognitoEnabled(extension->id()))
366 continue;
367
368 typedef std::vector<const extensions::FileHandlerInfo*> FileHandlerList;
369 FileHandlerList file_handlers = FindFileHandlersForFiles(*extension, files);
370 for (FileHandlerList::iterator i = file_handlers.begin();
371 i != file_handlers.end(); ++i) {
372 const extensions::FileHandlerInfo* handler = *i;
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100373 std::string task_id = file_tasks::MakeTaskID(extension->id(),
374 file_tasks::kTaskApp, handler->id);
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100375 if (task_id == default_task_id) {
376 ExecuteHandler(profile, extension->id(), handler->id, url,
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100377 file_tasks::kTaskApp);
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100378 return true;
379
380 } else if (!first_handler) {
381 first_handler = handler;
382 extension_for_first_handler = extension;
383 }
384 }
385 }
386 if (first_handler) {
387 ExecuteHandler(profile, extension_for_first_handler->id(),
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100388 first_handler->id, url, file_tasks::kTaskApp);
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100389 return true;
390 }
391 return false;
392}
393
394bool ExecuteExtensionHandler(Profile* profile,
395 const base::FilePath& path,
396 const FileBrowserHandler& handler,
397 const GURL& url) {
398 std::string extension_id = handler.extension_id();
399 std::string action_id = handler.id();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100400 Browser* browser = chrome::FindLastActiveWithProfile(profile,
401 chrome::HOST_DESKTOP_TYPE_ASH);
402
403 // If there is no browsers for the profile, bail out. Return true so warning
404 // about file type not being supported is not displayed.
405 if (!browser)
406 return true;
407
408 if (extension_id == kFileBrowserDomain) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100409 if (action_id == kFileBrowserGalleryTaskId ||
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100410 action_id == kFileBrowserMountArchiveTaskId ||
411 action_id == kFileBrowserPlayTaskId ||
412 action_id == kFileBrowserWatchTaskId) {
413 ExecuteHandler(profile, extension_id, action_id, url,
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100414 file_tasks::kTaskFile);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100415 return true;
416 }
Ben Murdochbb1529c2013-08-08 10:24:53 +0100417 return ExecuteBuiltinHandler(browser, path);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100418 }
419
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100420 ExecuteHandler(profile, extension_id, action_id, url,
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100421 file_tasks::kTaskFile);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100422 return true;
423}
424
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100425bool ExecuteDefaultHandler(Profile* profile, const base::FilePath& path) {
426 GURL url;
427 if (!ConvertFileToFileSystemUrl(profile, path, kFileBrowserDomain, &url))
428 return false;
429
430 std::string mime_type = GetMimeTypeForPath(path);
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100431 std::string default_task_id = file_tasks::GetDefaultTaskIdFromPrefs(
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100432 profile, mime_type, path.Extension());
433 const FileBrowserHandler* handler;
434
435 // We choose the file handler from the following in decreasing priority or
436 // fail if none support the file type:
437 // 1. default extension
438 // 2. default app
439 // 3. a fallback handler (e.g. opening in the browser)
440 // 4. non-default app
441 // 5. non-default extension
442 // Note that there can be at most one of default extension and default app.
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100443 if (!file_tasks::GetTaskForURLAndPath(profile, url, path, &handler)) {
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100444 return ExecuteDefaultAppHandler(
445 profile, path, url, mime_type, default_task_id);
446 }
447
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100448 std::string handler_task_id = file_tasks::MakeTaskID(
449 handler->extension_id(), file_tasks::kTaskFile, handler->id());
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100450 if (handler_task_id != default_task_id &&
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100451 !file_tasks::IsFallbackTask(handler) &&
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100452 ExecuteDefaultAppHandler(
453 profile, path, url, mime_type, default_task_id)) {
454 return true;
455 }
456 return ExecuteExtensionHandler(profile, path, *handler, url);
457}
458
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100459// Reads the alternate URL from a GDoc file. When it fails, returns a file URL
460// for |file_path| as fallback.
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100461// Note that an alternate url is a URL to open a hosted document.
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100462GURL ReadUrlFromGDocOnBlockingPool(const base::FilePath& file_path) {
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100463 GURL url = drive::util::ReadUrlFromGDocFile(file_path);
464 if (url.is_empty())
465 url = net::FilePathToFileURL(file_path);
466 return url;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100467}
468
469// Used to implement ViewItem().
470void ContinueViewItem(Profile* profile,
471 const base::FilePath& path,
472 base::PlatformFileError error) {
473 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
474
475 if (error == base::PLATFORM_FILE_OK) {
476 // A directory exists at |path|. Open it with FileBrowser.
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100477 OpenFileBrowserImpl(path, "open");
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100478 } else {
479 if (!ExecuteDefaultHandler(profile, path))
480 ShowWarningMessageBox(profile, path);
481 }
482}
483
484// Used to implement CheckIfDirectoryExists().
485void CheckIfDirectoryExistsOnIOThread(
486 scoped_refptr<fileapi::FileSystemContext> file_system_context,
487 const GURL& url,
Ben Murdochca12bfa2013-07-23 11:17:05 +0100488 const fileapi::FileSystemOperationRunner::StatusCallback& callback) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100489 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
490
491 fileapi::FileSystemURL file_system_url = file_system_context->CrackURL(url);
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100492 file_system_context->operation_runner()->DirectoryExists(
493 file_system_url, callback);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100494}
495
496// Checks if a directory exists at |url|.
497void CheckIfDirectoryExists(
498 scoped_refptr<fileapi::FileSystemContext> file_system_context,
499 const GURL& url,
Ben Murdochca12bfa2013-07-23 11:17:05 +0100500 const fileapi::FileSystemOperationRunner::StatusCallback& callback) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100501 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
502
503 BrowserThread::PostTask(
504 BrowserThread::IO, FROM_HERE,
505 base::Bind(&CheckIfDirectoryExistsOnIOThread,
506 file_system_context,
507 url,
508 google_apis::CreateRelayCallback(callback)));
509}
510
511} // namespace
512
513GURL GetFileBrowserExtensionUrl() {
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100514 return GetFileManagerUrl("/");
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100515}
516
517GURL GetFileBrowserUrl() {
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100518 return GetFileManagerUrl("/main.html");
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100519}
520
521GURL GetMediaPlayerUrl() {
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100522 return GetFileManagerUrl("/mediaplayer.html");
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100523}
524
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100525GURL GetActionChoiceUrl(const base::FilePath& virtual_path,
526 bool advanced_mode) {
527 std::string url = GetFileManagerUrl("/action_choice.html").spec();
528 if (advanced_mode)
529 url += "?advanced-mode";
530 url += "#/" + net::EscapeUrlEncodedData(virtual_path.value(),
531 false); // Space to %20 instead of +.
532 return GURL(url);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100533}
534
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100535GURL ConvertRelativePathToFileSystemUrl(const base::FilePath& relative_path,
536 const std::string& extension_id) {
537 GURL base_url = fileapi::GetFileSystemRootURI(
538 Extension::GetBaseURLFromExtensionId(extension_id),
539 fileapi::kFileSystemTypeExternal);
540 return GURL(base_url.spec() +
541 net::EscapeUrlEncodedData(relative_path.AsUTF8Unsafe(),
542 false)); // Space to %20 instead of +.
543}
544
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100545bool ConvertFileToFileSystemUrl(Profile* profile,
546 const base::FilePath& full_file_path,
547 const std::string& extension_id,
548 GURL* url) {
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100549 base::FilePath relative_path;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100550 if (!ConvertFileToRelativeFileSystemPath(profile, extension_id,
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100551 full_file_path, &relative_path)) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100552 return false;
553 }
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100554 *url = ConvertRelativePathToFileSystemUrl(relative_path, extension_id);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100555 return true;
556}
557
558bool ConvertFileToRelativeFileSystemPath(
559 Profile* profile,
560 const std::string& extension_id,
561 const base::FilePath& full_file_path,
562 base::FilePath* virtual_path) {
563 ExtensionService* service =
564 extensions::ExtensionSystem::Get(profile)->extension_service();
565 // May be NULL during unit_tests.
566 if (!service)
567 return false;
568
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100569 // File browser APIs are meant to be used only from extension context, so the
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100570 // extension's site is the one in whose file system context the virtual path
571 // should be found.
572 GURL site = service->GetSiteForExtensionId(extension_id);
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100573 fileapi::ExternalFileSystemBackend* backend =
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100574 BrowserContext::GetStoragePartitionForSite(profile, site)->
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100575 GetFileSystemContext()->external_backend();
576 if (!backend)
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100577 return false;
578
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100579 // Find if this file path is managed by the external backend.
580 if (!backend->GetVirtualPath(full_file_path, virtual_path))
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100581 return false;
582
583 return true;
584}
585
586GURL GetFileBrowserUrlWithParams(
587 ui::SelectFileDialog::Type type,
588 const string16& title,
589 const base::FilePath& default_virtual_path,
590 const ui::SelectFileDialog::FileTypeInfo* file_types,
591 int file_type_index,
592 const base::FilePath::StringType& default_extension) {
593 DictionaryValue arg_value;
594 arg_value.SetString("type", GetDialogTypeAsString(type));
595 arg_value.SetString("title", title);
596 arg_value.SetString("defaultPath", default_virtual_path.value());
597 arg_value.SetString("defaultExtension", default_extension);
598
599 if (file_types) {
600 ListValue* types_list = new ListValue();
601 for (size_t i = 0; i < file_types->extensions.size(); ++i) {
602 ListValue* extensions_list = new ListValue();
603 for (size_t j = 0; j < file_types->extensions[i].size(); ++j) {
604 extensions_list->Append(
605 new base::StringValue(file_types->extensions[i][j]));
606 }
607
608 DictionaryValue* dict = new DictionaryValue();
609 dict->Set("extensions", extensions_list);
610
611 if (i < file_types->extension_description_overrides.size()) {
612 string16 desc = file_types->extension_description_overrides[i];
613 dict->SetString("description", desc);
614 }
615
616 // file_type_index is 1-based. 0 means no selection at all.
617 dict->SetBoolean("selected",
618 (static_cast<size_t>(file_type_index) == (i + 1)));
619
620 types_list->Set(i, dict);
621 }
622 arg_value.Set("typeList", types_list);
623
624 arg_value.SetBoolean("includeAllFiles", file_types->include_all_files);
625 }
626
627 // If the caller cannot handle Drive path, the file chooser dialog need to
628 // return resolved local native paths to the selected files.
629 arg_value.SetBoolean("shouldReturnLocalPath",
630 !file_types || !file_types->support_drive);
631
632 std::string json_args;
633 base::JSONWriter::Write(&arg_value, &json_args);
634
635 // kChromeUIFileManagerURL could not be used since query parameters are not
636 // supported for it.
637 std::string url = GetFileBrowserUrl().spec() + '?' +
638 net::EscapeUrlEncodedData(json_args,
639 false); // Space to %20 instead of +.
640 return GURL(url);
641}
642
643string16 GetTitleFromType(ui::SelectFileDialog::Type dialog_type) {
644 string16 title;
645 switch (dialog_type) {
646 case ui::SelectFileDialog::SELECT_NONE:
647 // Full page file manager doesn't need a title.
648 break;
649
650 case ui::SelectFileDialog::SELECT_FOLDER:
651 title = l10n_util::GetStringUTF16(
652 IDS_FILE_BROWSER_SELECT_FOLDER_TITLE);
653 break;
654
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100655 case ui::SelectFileDialog::SELECT_UPLOAD_FOLDER:
656 title = l10n_util::GetStringUTF16(
657 IDS_FILE_BROWSER_SELECT_UPLOAD_FOLDER_TITLE);
658 break;
659
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100660 case ui::SelectFileDialog::SELECT_SAVEAS_FILE:
661 title = l10n_util::GetStringUTF16(
662 IDS_FILE_BROWSER_SELECT_SAVEAS_FILE_TITLE);
663 break;
664
665 case ui::SelectFileDialog::SELECT_OPEN_FILE:
666 title = l10n_util::GetStringUTF16(
667 IDS_FILE_BROWSER_SELECT_OPEN_FILE_TITLE);
668 break;
669
670 case ui::SelectFileDialog::SELECT_OPEN_MULTI_FILE:
671 title = l10n_util::GetStringUTF16(
672 IDS_FILE_BROWSER_SELECT_OPEN_MULTI_FILE_TITLE);
673 break;
674
675 default:
676 NOTREACHED();
677 }
678
679 return title;
680}
681
682void ViewRemovableDrive(const base::FilePath& path) {
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100683 OpenFileBrowserImpl(path, "auto-open");
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100684}
685
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100686void OpenActionChoiceDialog(const base::FilePath& path, bool advanced_mode) {
687 const int kDialogWidth = 394;
688 // TODO(dgozman): remove 50, which is a title height once popup window
689 // will have no title.
690 const int kDialogHeight = 316 + 50;
691
692 Profile* profile = ProfileManager::GetDefaultProfileOrOffTheRecord();
693
694 base::FilePath virtual_path;
695 if (!ConvertFileToRelativeFileSystemPath(profile, kFileBrowserDomain, path,
696 &virtual_path))
697 return;
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100698 GURL dialog_url = GetActionChoiceUrl(virtual_path, advanced_mode);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100699
700 const gfx::Size screen = ash::Shell::GetScreen()->GetPrimaryDisplay().size();
701 const gfx::Rect bounds((screen.width() - kDialogWidth) / 2,
702 (screen.height() - kDialogHeight) / 2,
703 kDialogWidth,
704 kDialogHeight);
705
706 Browser* browser = GetBrowserForUrl(dialog_url);
707
708 if (browser) {
709 browser->window()->Show();
710 return;
711 }
712
713 ExtensionService* service = extensions::ExtensionSystem::Get(
714 profile ? profile : ProfileManager::GetDefaultProfileOrOffTheRecord())->
715 extension_service();
716 if (!service)
717 return;
718
719 const extensions::Extension* extension =
720 service->GetExtensionById(kFileBrowserDomain, false);
721 if (!extension)
722 return;
723
724 chrome::AppLaunchParams params(profile, extension,
725 extension_misc::LAUNCH_WINDOW,
726 NEW_FOREGROUND_TAB);
727 params.override_url = dialog_url;
728 params.override_bounds = bounds;
729 chrome::OpenApplication(params);
730}
731
732void ViewItem(const base::FilePath& path) {
733 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
734
735 Profile* profile = ProfileManager::GetDefaultProfileOrOffTheRecord();
736 GURL url;
737 if (!ConvertFileToFileSystemUrl(profile, path, kFileBrowserDomain, &url) ||
738 !GrantFileSystemAccessToFileBrowser(profile)) {
739 ShowWarningMessageBox(profile, path);
740 return;
741 }
742
743 GURL site = extensions::ExtensionSystem::Get(profile)->extension_service()->
744 GetSiteForExtensionId(kFileBrowserDomain);
745 scoped_refptr<fileapi::FileSystemContext> file_system_context =
746 BrowserContext::GetStoragePartitionForSite(profile, site)->
747 GetFileSystemContext();
748
749 CheckIfDirectoryExists(file_system_context, url,
750 base::Bind(&ContinueViewItem, profile, path));
751}
752
753void ShowFileInFolder(const base::FilePath& path) {
754 // This action changes the selection so we do not reuse existing tabs.
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100755 OpenFileBrowserImpl(path, "select");
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100756}
757
Ben Murdochbb1529c2013-08-08 10:24:53 +0100758bool ExecuteBuiltinHandler(Browser* browser, const base::FilePath& path) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100759 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
760
761 Profile* profile = browser->profile();
762 std::string file_extension = path.Extension();
763 // For things supported natively by the browser, we should open it
764 // in a tab.
765 if (IsSupportedBrowserExtension(file_extension.data()) ||
766 ShouldBeOpenedWithPlugin(profile, file_extension.data())) {
767 GURL page_url = net::FilePathToFileURL(path);
768 // Override drive resource to point to internal handler instead of file URL.
769 if (drive::util::IsUnderDriveMountPoint(path)) {
770 page_url = drive::util::FilePathToDriveURL(
771 drive::util::ExtractDrivePath(path));
772 }
773 OpenNewTab(profile, page_url);
774 return true;
775 }
776
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100777 if (drive::util::HasGDocFileExtension(path)) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100778 if (drive::util::IsUnderDriveMountPoint(path)) {
779 // The file is on Google Docs. Open with drive URL.
780 GURL url = drive::util::FilePathToDriveURL(
781 drive::util::ExtractDrivePath(path));
782 OpenNewTab(profile, url);
783 } else {
784 // The file is local (downloaded from an attachment or otherwise copied).
785 // Parse the file to extract the Docs url and open this url.
786 base::PostTaskAndReplyWithResult(
787 BrowserThread::GetBlockingPool(),
788 FROM_HERE,
789 base::Bind(&ReadUrlFromGDocOnBlockingPool, path),
790 base::Bind(&OpenNewTab, static_cast<Profile*>(NULL)));
791 }
792 return true;
793 }
794
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100795 if (IsCRXFile(file_extension.data())) {
796 if (drive::util::IsUnderDriveMountPoint(path)) {
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100797 drive::DriveIntegrationService* integration_service =
798 drive::DriveIntegrationServiceFactory::GetForProfile(profile);
799 if (!integration_service)
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100800 return false;
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100801 integration_service->file_system()->GetFileByPath(
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100802 drive::util::ExtractDrivePath(path),
803 base::Bind(&OnCRXDownloadCallback, browser));
804 } else {
805 InstallCRX(browser, path);
806 }
807 return true;
808 }
809
810 // Unknown file type. Record UMA and show an error message.
811 size_t extension_index = UMAExtensionIndex(file_extension.data(),
812 kUMATrackingExtensions,
813 arraysize(kUMATrackingExtensions));
814 UMA_HISTOGRAM_ENUMERATION("FileBrowser.OpeningFileType",
815 extension_index,
816 arraysize(kUMATrackingExtensions) - 1);
817 return false;
818}
819
820// If a bundled plugin is enabled, we should open pdf/swf files in a tab.
821bool ShouldBeOpenedWithPlugin(Profile* profile, const char* file_extension) {
822 if (LowerCaseEqualsASCII(file_extension, kPdfExtension))
823 return IsPdfPluginEnabled(profile);
824 if (LowerCaseEqualsASCII(file_extension, kSwfExtension))
825 return IsFlashPluginEnabled(profile);
826 return false;
827}
828
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100829std::string GetMimeTypeForPath(const base::FilePath& file_path) {
830 const base::FilePath::StringType file_extension =
831 StringToLowerASCII(file_path.Extension());
832
833 // TODO(thorogood): Rearchitect this call so it can run on the File thread;
834 // GetMimeTypeFromFile requires this on Linux. Right now, we use
835 // Chrome-level knowledge only.
836 std::string mime_type;
837 if (file_extension.empty() ||
838 !net::GetWellKnownMimeTypeFromExtension(file_extension.substr(1),
839 &mime_type)) {
840 // If the file doesn't have an extension or its mime-type cannot be
841 // determined, then indicate that it has the empty mime-type. This will
842 // only be matched if the Web Intents accepts "*" or "*/*".
843 return "";
844 } else {
845 return mime_type;
846 }
847}
848
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100849} // namespace util
850} // namespace file_manager