blob: 81b8198c1bd460f9e69743b218113051e55dc06e [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"
21#include "chrome/browser/chromeos/extensions/file_manager/file_browser_handler.h"
22#include "chrome/browser/chromeos/extensions/file_manager/file_handler_util.h"
23#include "chrome/browser/chromeos/media/media_player.h"
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010024#include "chrome/browser/extensions/api/file_handlers/app_file_handler_util.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010025#include "chrome/browser/extensions/crx_installer.h"
26#include "chrome/browser/extensions/extension_install_prompt.h"
27#include "chrome/browser/extensions/extension_service.h"
28#include "chrome/browser/extensions/extension_system.h"
29#include "chrome/browser/google_apis/task_util.h"
30#include "chrome/browser/plugins/plugin_prefs.h"
31#include "chrome/browser/profiles/profile.h"
32#include "chrome/browser/profiles/profile_manager.h"
33#include "chrome/browser/ui/browser.h"
34#include "chrome/browser/ui/browser_finder.h"
35#include "chrome/browser/ui/browser_iterator.h"
36#include "chrome/browser/ui/browser_tabstrip.h"
37#include "chrome/browser/ui/browser_window.h"
38#include "chrome/browser/ui/extensions/application_launch.h"
39#include "chrome/browser/ui/host_desktop.h"
40#include "chrome/browser/ui/simple_message_box.h"
41#include "chrome/browser/ui/tabs/tab_strip_model.h"
42#include "chrome/common/chrome_paths.h"
43#include "chrome/common/chrome_switches.h"
44#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"
52#include "grit/generated_resources.h"
53#include "net/base/escape.h"
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010054#include "net/base/mime_util.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010055#include "net/base/net_util.h"
56#include "ui/base/l10n/l10n_util.h"
57#include "ui/gfx/screen.h"
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010058#include "webkit/browser/fileapi/file_system_context.h"
59#include "webkit/browser/fileapi/file_system_mount_point_provider.h"
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010060#include "webkit/browser/fileapi/file_system_operation_runner.h"
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010061#include "webkit/browser/fileapi/file_system_url.h"
62#include "webkit/common/fileapi/file_system_util.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010063#include "webkit/plugins/webplugininfo.h"
64
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
85namespace file_manager_util {
86namespace {
87
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010088const char kCRXExtension[] = ".crx";
89const char kPdfExtension[] = ".pdf";
90const char kSwfExtension[] = ".swf";
91// List of file extension we can open in tab.
92const char* kBrowserSupportedExtensions[] = {
93#if defined(GOOGLE_CHROME_BUILD)
94 ".pdf", ".swf",
95#endif
96 ".bmp", ".jpg", ".jpeg", ".png", ".webp", ".gif", ".txt", ".html", ".htm",
97 ".mhtml", ".mht", ".svg"
98};
99
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100100// List of all extensions we want to be shown in histogram that keep track of
101// files that were unsuccessfully tried to be opened.
102// The list has to be synced with histogram values.
103const char* kUMATrackingExtensions[] = {
104 "other", ".doc", ".docx", ".odt", ".rtf", ".pdf", ".ppt", ".pptx", ".odp",
105 ".xls", ".xlsx", ".ods", ".csv", ".odf", ".rar", ".asf", ".wma", ".wmv",
106 ".mov", ".mpg", ".log"
107};
108
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100109// Returns a file manager URL for the given |path|.
110GURL GetFileManagerUrl(const char* path) {
111 return GURL(std::string("chrome-extension://") + kFileBrowserDomain + path);
112}
113
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100114bool IsSupportedBrowserExtension(const char* file_extension) {
115 for (size_t i = 0; i < arraysize(kBrowserSupportedExtensions); i++) {
116 if (base::strcasecmp(file_extension, kBrowserSupportedExtensions[i]) == 0) {
117 return true;
118 }
119 }
120 return false;
121}
122
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100123bool IsCRXFile(const char* file_extension) {
124 return base::strcasecmp(file_extension, kCRXExtension) == 0;
125}
126
127bool IsPepperPluginEnabled(Profile* profile,
128 const base::FilePath& plugin_path) {
129 content::PepperPluginInfo* pepper_info =
130 PluginService::GetInstance()->GetRegisteredPpapiPluginInfo(plugin_path);
131 if (!pepper_info)
132 return false;
133
134 PluginPrefs* plugin_prefs = PluginPrefs::GetForProfile(profile);
135 if (!plugin_prefs)
136 return false;
137
138 return plugin_prefs->IsPluginEnabled(pepper_info->ToWebPluginInfo());
139}
140
141bool IsPdfPluginEnabled(Profile* profile) {
142 base::FilePath plugin_path;
143 PathService::Get(chrome::FILE_PDF_PLUGIN, &plugin_path);
144 return IsPepperPluginEnabled(profile, plugin_path);
145}
146
147bool IsFlashPluginEnabled(Profile* profile) {
148 base::FilePath plugin_path(
149 CommandLine::ForCurrentProcess()->GetSwitchValueNative(
150 switches::kPpapiFlashPath));
151 if (plugin_path.empty())
152 PathService::Get(chrome::FILE_PEPPER_FLASH_PLUGIN, &plugin_path);
153 return IsPepperPluginEnabled(profile, plugin_path);
154}
155
156// Returns index |ext| has in the |array|. If there is no |ext| in |array|, last
157// element's index is return (last element should have irrelevant value).
158int UMAExtensionIndex(const char *file_extension,
159 const char** array,
160 size_t array_size) {
161 for (size_t i = 0; i < array_size; i++) {
162 if (base::strcasecmp(file_extension, array[i]) == 0) {
163 return i;
164 }
165 }
166 return 0;
167}
168
169// Convert numeric dialog type to a string.
170std::string GetDialogTypeAsString(
171 ui::SelectFileDialog::Type dialog_type) {
172 std::string type_str;
173 switch (dialog_type) {
174 case ui::SelectFileDialog::SELECT_NONE:
175 type_str = "full-page";
176 break;
177
178 case ui::SelectFileDialog::SELECT_FOLDER:
179 type_str = "folder";
180 break;
181
182 case ui::SelectFileDialog::SELECT_SAVEAS_FILE:
183 type_str = "saveas-file";
184 break;
185
186 case ui::SelectFileDialog::SELECT_OPEN_FILE:
187 type_str = "open-file";
188 break;
189
190 case ui::SelectFileDialog::SELECT_OPEN_MULTI_FILE:
191 type_str = "open-multi-file";
192 break;
193
194 default:
195 NOTREACHED();
196 }
197
198 return type_str;
199}
200
201void OpenNewTab(Profile* profile, const GURL& url) {
202 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
203 Browser* browser = chrome::FindOrCreateTabbedBrowser(
204 profile ? profile : ProfileManager::GetDefaultProfileOrOffTheRecord(),
205 chrome::HOST_DESKTOP_TYPE_ASH);
206 chrome::AddSelectedTabWithURL(browser, url, content::PAGE_TRANSITION_LINK);
207 // If the current browser is not tabbed then the new tab will be created
208 // in a different browser. Make sure it is visible.
209 browser->window()->Show();
210}
211
212// Shows a warning message box saying that the file could not be opened.
213void ShowWarningMessageBox(Profile* profile, const base::FilePath& path) {
214 // TODO: if FindOrCreateTabbedBrowser creates a new browser the returned
215 // browser is leaked.
216 Browser* browser =
217 chrome::FindOrCreateTabbedBrowser(profile,
218 chrome::HOST_DESKTOP_TYPE_ASH);
219 chrome::ShowMessageBox(
220 browser->window()->GetNativeWindow(),
221 l10n_util::GetStringFUTF16(
222 IDS_FILE_BROWSER_ERROR_VIEWING_FILE_TITLE,
223 UTF8ToUTF16(path.BaseName().value())),
224 l10n_util::GetStringUTF16(IDS_FILE_BROWSER_ERROR_VIEWING_FILE),
225 chrome::MESSAGE_BOX_TYPE_WARNING);
226}
227
228void InstallCRX(Browser* browser, const base::FilePath& path) {
229 ExtensionService* service =
230 extensions::ExtensionSystem::Get(browser->profile())->extension_service();
231 CHECK(service);
232
233 scoped_refptr<extensions::CrxInstaller> installer(
234 extensions::CrxInstaller::Create(
235 service,
236 new ExtensionInstallPrompt(browser->profile(), NULL, NULL)));
237 installer->set_error_on_unsupported_requirements(true);
238 installer->set_is_gallery_install(false);
239 installer->set_allow_silent_install(false);
240 installer->InstallCrx(path);
241}
242
243// Called when a crx file on Drive was downloaded.
244void OnCRXDownloadCallback(Browser* browser,
245 drive::FileError error,
246 const base::FilePath& file,
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100247 scoped_ptr<drive::ResourceEntry> entry) {
248 if (error != drive::FILE_ERROR_OK)
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100249 return;
250 InstallCRX(browser, file);
251}
252
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100253// Grants file system access to the file browser.
254bool GrantFileSystemAccessToFileBrowser(Profile* profile) {
255 // File browser always runs in the site for its extension id, so that is the
256 // site for which file access permissions should be granted.
257 GURL site = extensions::ExtensionSystem::Get(profile)->extension_service()->
258 GetSiteForExtensionId(kFileBrowserDomain);
259 fileapi::ExternalFileSystemMountPointProvider* external_provider =
260 BrowserContext::GetStoragePartitionForSite(profile, site)->
261 GetFileSystemContext()->external_provider();
262 if (!external_provider)
263 return false;
264 external_provider->GrantFullAccessToExtension(GetFileBrowserUrl().host());
265 return true;
266}
267
268// Executes handler specifed with |extension_id| and |action_id| for |url|.
269void ExecuteHandler(Profile* profile,
270 std::string extension_id,
271 std::string action_id,
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100272 const GURL& url,
273 const std::string& task_type) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100274 // If File Browser has not been open yet then it did not request access
275 // to the file system. Do it now.
276 if (!GrantFileSystemAccessToFileBrowser(profile))
277 return;
278
279 GURL site = extensions::ExtensionSystem::Get(profile)->extension_service()->
280 GetSiteForExtensionId(kFileBrowserDomain);
281 fileapi::FileSystemContext* file_system_context =
282 BrowserContext::GetStoragePartitionForSite(profile, site)->
283 GetFileSystemContext();
284
285 // We are executing the task on behalf of File Browser extension.
286 const GURL source_url = GetFileBrowserUrl();
287 std::vector<FileSystemURL> urls;
288 urls.push_back(file_system_context->CrackURL(url));
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100289
290 file_handler_util::ExecuteFileTask(
291 profile,
292 source_url,
293 kFileBrowserDomain,
294 0, // no tab id
295 extension_id,
296 task_type,
297 action_id,
298 urls,
299 file_handler_util::FileTaskFinishedCallback());
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100300}
301
302void OpenFileBrowserImpl(const base::FilePath& path,
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100303 const std::string& action_id) {
304 content::RecordAction(UserMetricsAction("ShowFileBrowserFullTab"));
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100305 Profile* profile = ProfileManager::GetDefaultProfileOrOffTheRecord();
306
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100307 GURL url;
308 if (!ConvertFileToFileSystemUrl(profile, path, kFileBrowserDomain, &url))
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +0100309 return;
310
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100311 // Some values of |action_id| are not listed in the manifest and are used
312 // to parametrize the behavior when opening the Files app window.
313 ExecuteHandler(profile, kFileBrowserDomain, action_id, url,
314 file_handler_util::kTaskFile);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100315}
316
317Browser* GetBrowserForUrl(GURL target_url) {
318 for (chrome::BrowserIterator it; !it.done(); it.Next()) {
319 Browser* browser = *it;
320 TabStripModel* tab_strip = browser->tab_strip_model();
321 for (int idx = 0; idx < tab_strip->count(); idx++) {
322 content::WebContents* web_contents = tab_strip->GetWebContentsAt(idx);
323 const GURL& url = web_contents->GetURL();
324 if (url == target_url)
325 return browser;
326 }
327 }
328 return NULL;
329}
330
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100331bool ExecuteDefaultAppHandler(Profile* profile,
332 const base::FilePath& path,
333 const GURL& url,
334 const std::string& mime_type,
335 const std::string& default_task_id) {
336 ExtensionService* service = profile->GetExtensionService();
337 if (!service)
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100338 return false;
339
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100340 PathAndMimeTypeSet files;
341 files.insert(std::make_pair(path, mime_type));
342 const extensions::FileHandlerInfo* first_handler = NULL;
343 const extensions::Extension* extension_for_first_handler = NULL;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100344
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100345 // If we find the default handler, we execute it immediately, but otherwise,
346 // we remember the first handler, and if there was no default handler, simply
347 // execute the first one.
348 for (ExtensionSet::const_iterator iter = service->extensions()->begin();
349 iter != service->extensions()->end();
350 ++iter) {
351 const Extension* extension = *iter;
352
353 // We don't support using hosted apps to open files.
354 if (!extension->is_platform_app())
355 continue;
356
357 // We only support apps that specify "incognito: split" if in incognito
358 // mode.
359 if (profile->IsOffTheRecord() &&
360 !service->IsIncognitoEnabled(extension->id()))
361 continue;
362
363 typedef std::vector<const extensions::FileHandlerInfo*> FileHandlerList;
364 FileHandlerList file_handlers = FindFileHandlersForFiles(*extension, files);
365 for (FileHandlerList::iterator i = file_handlers.begin();
366 i != file_handlers.end(); ++i) {
367 const extensions::FileHandlerInfo* handler = *i;
368 std::string task_id = file_handler_util::MakeTaskID(extension->id(),
369 file_handler_util::kTaskApp, handler->id);
370 if (task_id == default_task_id) {
371 ExecuteHandler(profile, extension->id(), handler->id, url,
372 file_handler_util::kTaskApp);
373 return true;
374
375 } else if (!first_handler) {
376 first_handler = handler;
377 extension_for_first_handler = extension;
378 }
379 }
380 }
381 if (first_handler) {
382 ExecuteHandler(profile, extension_for_first_handler->id(),
383 first_handler->id, url, file_handler_util::kTaskApp);
384 return true;
385 }
386 return false;
387}
388
389bool ExecuteExtensionHandler(Profile* profile,
390 const base::FilePath& path,
391 const FileBrowserHandler& handler,
392 const GURL& url) {
393 std::string extension_id = handler.extension_id();
394 std::string action_id = handler.id();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100395 Browser* browser = chrome::FindLastActiveWithProfile(profile,
396 chrome::HOST_DESKTOP_TYPE_ASH);
397
398 // If there is no browsers for the profile, bail out. Return true so warning
399 // about file type not being supported is not displayed.
400 if (!browser)
401 return true;
402
403 if (extension_id == kFileBrowserDomain) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100404 if (action_id == kFileBrowserGalleryTaskId ||
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100405 action_id == kFileBrowserMountArchiveTaskId ||
406 action_id == kFileBrowserPlayTaskId ||
407 action_id == kFileBrowserWatchTaskId) {
408 ExecuteHandler(profile, extension_id, action_id, url,
409 file_handler_util::kTaskFile);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100410 return true;
411 }
412 return ExecuteBuiltinHandler(browser, path, action_id);
413 }
414
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100415 ExecuteHandler(profile, extension_id, action_id, url,
416 file_handler_util::kTaskFile);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100417 return true;
418}
419
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100420bool ExecuteDefaultHandler(Profile* profile, const base::FilePath& path) {
421 GURL url;
422 if (!ConvertFileToFileSystemUrl(profile, path, kFileBrowserDomain, &url))
423 return false;
424
425 std::string mime_type = GetMimeTypeForPath(path);
426 std::string default_task_id = file_handler_util::GetDefaultTaskIdFromPrefs(
427 profile, mime_type, path.Extension());
428 const FileBrowserHandler* handler;
429
430 // We choose the file handler from the following in decreasing priority or
431 // fail if none support the file type:
432 // 1. default extension
433 // 2. default app
434 // 3. a fallback handler (e.g. opening in the browser)
435 // 4. non-default app
436 // 5. non-default extension
437 // Note that there can be at most one of default extension and default app.
438 if (!file_handler_util::GetTaskForURLAndPath(profile, url, path, &handler)) {
439 return ExecuteDefaultAppHandler(
440 profile, path, url, mime_type, default_task_id);
441 }
442
443 std::string handler_task_id = file_handler_util::MakeTaskID(
444 handler->extension_id(), file_handler_util::kTaskFile, handler->id());
445 if (handler_task_id != default_task_id &&
446 !file_handler_util::IsFallbackTask(handler) &&
447 ExecuteDefaultAppHandler(
448 profile, path, url, mime_type, default_task_id)) {
449 return true;
450 }
451 return ExecuteExtensionHandler(profile, path, *handler, url);
452}
453
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100454// Reads the alternate URL from a GDoc file. When it fails, returns a file URL
455// for |file_path| as fallback.
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100456// Note that an alternate url is a URL to open a hosted document.
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100457GURL ReadUrlFromGDocOnBlockingPool(const base::FilePath& file_path) {
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100458 GURL url = drive::util::ReadUrlFromGDocFile(file_path);
459 if (url.is_empty())
460 url = net::FilePathToFileURL(file_path);
461 return url;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100462}
463
464// Used to implement ViewItem().
465void ContinueViewItem(Profile* profile,
466 const base::FilePath& path,
467 base::PlatformFileError error) {
468 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
469
470 if (error == base::PLATFORM_FILE_OK) {
471 // A directory exists at |path|. Open it with FileBrowser.
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100472 OpenFileBrowserImpl(path, "open");
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100473 } else {
474 if (!ExecuteDefaultHandler(profile, path))
475 ShowWarningMessageBox(profile, path);
476 }
477}
478
479// Used to implement CheckIfDirectoryExists().
480void CheckIfDirectoryExistsOnIOThread(
481 scoped_refptr<fileapi::FileSystemContext> file_system_context,
482 const GURL& url,
483 const fileapi::FileSystemOperation::StatusCallback& callback) {
484 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
485
486 fileapi::FileSystemURL file_system_url = file_system_context->CrackURL(url);
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100487 file_system_context->operation_runner()->DirectoryExists(
488 file_system_url, callback);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100489}
490
491// Checks if a directory exists at |url|.
492void CheckIfDirectoryExists(
493 scoped_refptr<fileapi::FileSystemContext> file_system_context,
494 const GURL& url,
495 const fileapi::FileSystemOperation::StatusCallback& callback) {
496 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
497
498 BrowserThread::PostTask(
499 BrowserThread::IO, FROM_HERE,
500 base::Bind(&CheckIfDirectoryExistsOnIOThread,
501 file_system_context,
502 url,
503 google_apis::CreateRelayCallback(callback)));
504}
505
506} // namespace
507
508GURL GetFileBrowserExtensionUrl() {
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100509 return GetFileManagerUrl("/");
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100510}
511
512GURL GetFileBrowserUrl() {
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100513 return GetFileManagerUrl("/main.html");
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100514}
515
516GURL GetMediaPlayerUrl() {
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100517 return GetFileManagerUrl("/mediaplayer.html");
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100518}
519
520GURL GetVideoPlayerUrl(const GURL& source_url) {
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100521 return GURL(GetFileManagerUrl("/video_player.html").spec() +
522 std::string("?") + source_url.spec());
523}
524
525GURL 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);
573 fileapi::ExternalFileSystemMountPointProvider* provider =
574 BrowserContext::GetStoragePartitionForSite(profile, site)->
575 GetFileSystemContext()->external_provider();
576 if (!provider)
577 return false;
578
579 // Find if this file path is managed by the external provider.
580 if (!provider->GetVirtualPath(full_file_path, virtual_path))
581 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
655 case ui::SelectFileDialog::SELECT_SAVEAS_FILE:
656 title = l10n_util::GetStringUTF16(
657 IDS_FILE_BROWSER_SELECT_SAVEAS_FILE_TITLE);
658 break;
659
660 case ui::SelectFileDialog::SELECT_OPEN_FILE:
661 title = l10n_util::GetStringUTF16(
662 IDS_FILE_BROWSER_SELECT_OPEN_FILE_TITLE);
663 break;
664
665 case ui::SelectFileDialog::SELECT_OPEN_MULTI_FILE:
666 title = l10n_util::GetStringUTF16(
667 IDS_FILE_BROWSER_SELECT_OPEN_MULTI_FILE_TITLE);
668 break;
669
670 default:
671 NOTREACHED();
672 }
673
674 return title;
675}
676
677void ViewRemovableDrive(const base::FilePath& path) {
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100678 OpenFileBrowserImpl(path, "auto-open");
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100679}
680
681void OpenNewWindow(Profile* profile, const GURL& url) {
682 ExtensionService* service = extensions::ExtensionSystem::Get(
683 profile ? profile : ProfileManager::GetDefaultProfileOrOffTheRecord())->
684 extension_service();
685 if (!service)
686 return;
687
688 const extensions::Extension* extension =
689 service->GetExtensionById(kFileBrowserDomain, false);
690 if (!extension)
691 return;
692
693 chrome::AppLaunchParams params(profile, extension,
694 extension_misc::LAUNCH_WINDOW,
695 NEW_FOREGROUND_TAB);
696 params.override_url = url;
697 chrome::OpenApplication(params);
698}
699
700void OpenActionChoiceDialog(const base::FilePath& path, bool advanced_mode) {
701 const int kDialogWidth = 394;
702 // TODO(dgozman): remove 50, which is a title height once popup window
703 // will have no title.
704 const int kDialogHeight = 316 + 50;
705
706 Profile* profile = ProfileManager::GetDefaultProfileOrOffTheRecord();
707
708 base::FilePath virtual_path;
709 if (!ConvertFileToRelativeFileSystemPath(profile, kFileBrowserDomain, path,
710 &virtual_path))
711 return;
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100712 GURL dialog_url = GetActionChoiceUrl(virtual_path, advanced_mode);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100713
714 const gfx::Size screen = ash::Shell::GetScreen()->GetPrimaryDisplay().size();
715 const gfx::Rect bounds((screen.width() - kDialogWidth) / 2,
716 (screen.height() - kDialogHeight) / 2,
717 kDialogWidth,
718 kDialogHeight);
719
720 Browser* browser = GetBrowserForUrl(dialog_url);
721
722 if (browser) {
723 browser->window()->Show();
724 return;
725 }
726
727 ExtensionService* service = extensions::ExtensionSystem::Get(
728 profile ? profile : ProfileManager::GetDefaultProfileOrOffTheRecord())->
729 extension_service();
730 if (!service)
731 return;
732
733 const extensions::Extension* extension =
734 service->GetExtensionById(kFileBrowserDomain, false);
735 if (!extension)
736 return;
737
738 chrome::AppLaunchParams params(profile, extension,
739 extension_misc::LAUNCH_WINDOW,
740 NEW_FOREGROUND_TAB);
741 params.override_url = dialog_url;
742 params.override_bounds = bounds;
743 chrome::OpenApplication(params);
744}
745
746void ViewItem(const base::FilePath& path) {
747 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
748
749 Profile* profile = ProfileManager::GetDefaultProfileOrOffTheRecord();
750 GURL url;
751 if (!ConvertFileToFileSystemUrl(profile, path, kFileBrowserDomain, &url) ||
752 !GrantFileSystemAccessToFileBrowser(profile)) {
753 ShowWarningMessageBox(profile, path);
754 return;
755 }
756
757 GURL site = extensions::ExtensionSystem::Get(profile)->extension_service()->
758 GetSiteForExtensionId(kFileBrowserDomain);
759 scoped_refptr<fileapi::FileSystemContext> file_system_context =
760 BrowserContext::GetStoragePartitionForSite(profile, site)->
761 GetFileSystemContext();
762
763 CheckIfDirectoryExists(file_system_context, url,
764 base::Bind(&ContinueViewItem, profile, path));
765}
766
767void ShowFileInFolder(const base::FilePath& path) {
768 // This action changes the selection so we do not reuse existing tabs.
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100769 OpenFileBrowserImpl(path, "select");
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100770}
771
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100772bool ExecuteBuiltinHandler(Browser* browser, const base::FilePath& path,
773 const std::string& internal_task_id) {
774 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
775
776 Profile* profile = browser->profile();
777 std::string file_extension = path.Extension();
778 // For things supported natively by the browser, we should open it
779 // in a tab.
780 if (IsSupportedBrowserExtension(file_extension.data()) ||
781 ShouldBeOpenedWithPlugin(profile, file_extension.data())) {
782 GURL page_url = net::FilePathToFileURL(path);
783 // Override drive resource to point to internal handler instead of file URL.
784 if (drive::util::IsUnderDriveMountPoint(path)) {
785 page_url = drive::util::FilePathToDriveURL(
786 drive::util::ExtractDrivePath(path));
787 }
788 OpenNewTab(profile, page_url);
789 return true;
790 }
791
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100792 if (drive::util::HasGDocFileExtension(path)) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100793 if (drive::util::IsUnderDriveMountPoint(path)) {
794 // The file is on Google Docs. Open with drive URL.
795 GURL url = drive::util::FilePathToDriveURL(
796 drive::util::ExtractDrivePath(path));
797 OpenNewTab(profile, url);
798 } else {
799 // The file is local (downloaded from an attachment or otherwise copied).
800 // Parse the file to extract the Docs url and open this url.
801 base::PostTaskAndReplyWithResult(
802 BrowserThread::GetBlockingPool(),
803 FROM_HERE,
804 base::Bind(&ReadUrlFromGDocOnBlockingPool, path),
805 base::Bind(&OpenNewTab, static_cast<Profile*>(NULL)));
806 }
807 return true;
808 }
809
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100810 if (IsCRXFile(file_extension.data())) {
811 if (drive::util::IsUnderDriveMountPoint(path)) {
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100812 drive::DriveIntegrationService* integration_service =
813 drive::DriveIntegrationServiceFactory::GetForProfile(profile);
814 if (!integration_service)
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100815 return false;
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100816 integration_service->file_system()->GetFileByPath(
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100817 drive::util::ExtractDrivePath(path),
818 base::Bind(&OnCRXDownloadCallback, browser));
819 } else {
820 InstallCRX(browser, path);
821 }
822 return true;
823 }
824
825 // Unknown file type. Record UMA and show an error message.
826 size_t extension_index = UMAExtensionIndex(file_extension.data(),
827 kUMATrackingExtensions,
828 arraysize(kUMATrackingExtensions));
829 UMA_HISTOGRAM_ENUMERATION("FileBrowser.OpeningFileType",
830 extension_index,
831 arraysize(kUMATrackingExtensions) - 1);
832 return false;
833}
834
835// If a bundled plugin is enabled, we should open pdf/swf files in a tab.
836bool ShouldBeOpenedWithPlugin(Profile* profile, const char* file_extension) {
837 if (LowerCaseEqualsASCII(file_extension, kPdfExtension))
838 return IsPdfPluginEnabled(profile);
839 if (LowerCaseEqualsASCII(file_extension, kSwfExtension))
840 return IsFlashPluginEnabled(profile);
841 return false;
842}
843
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100844std::string GetMimeTypeForPath(const base::FilePath& file_path) {
845 const base::FilePath::StringType file_extension =
846 StringToLowerASCII(file_path.Extension());
847
848 // TODO(thorogood): Rearchitect this call so it can run on the File thread;
849 // GetMimeTypeFromFile requires this on Linux. Right now, we use
850 // Chrome-level knowledge only.
851 std::string mime_type;
852 if (file_extension.empty() ||
853 !net::GetWellKnownMimeTypeFromExtension(file_extension.substr(1),
854 &mime_type)) {
855 // If the file doesn't have an extension or its mime-type cannot be
856 // determined, then indicate that it has the empty mime-type. This will
857 // only be matched if the Web Intents accepts "*" or "*/*".
858 return "";
859 } else {
860 return mime_type;
861 }
862}
863
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100864} // namespace file_manager_util