summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorasanka <asanka@chromium.org>2015-03-06 21:33:41 -0800
committerCommit bot <commit-bot@chromium.org>2015-03-07 05:34:23 +0000
commit655d1118025f1b7d2b0cff9fb465da3a50ad15e9 (patch)
treeaeee7b953bc9520854fa5e2ce0dcc6fc9d464c47
parent8084e61dc6ef3b33928b6feb5ef615c9728abf88 (diff)
downloadchromium_src-655d1118025f1b7d2b0cff9fb465da3a50ad15e9.zip
chromium_src-655d1118025f1b7d2b0cff9fb465da3a50ad15e9.tar.gz
chromium_src-655d1118025f1b7d2b0cff9fb465da3a50ad15e9.tar.bz2
Be explicit about target type in platform_util::OpenItem()
OpenItem() now takes an OpenItemType parameter that should specify expected type of the object to be opened. It verifies the type of the object before invoking platform specific logic for opening the item. Code that assumed that the target of OpenItem() was always a folder should now no longer unintentionally open or execute the file at the target location when this assumption was found to not be correct. In addition to the checks performed by OpenItem, the platform specific logic used to open folders fail if the target type is not a directory. BUG=387037 Review URL: https://codereview.chromium.org/352393002 Cr-Commit-Position: refs/heads/master@{#319555}
-rw-r--r--chrome/browser/chromeos/file_manager/fileapi_util.cc33
-rw-r--r--chrome/browser/chromeos/file_manager/fileapi_util.h6
-rw-r--r--chrome/browser/chromeos/file_manager/open_util.cc139
-rw-r--r--chrome/browser/chromeos/file_manager/open_util.h34
-rw-r--r--chrome/browser/download/chrome_download_manager_delegate.cc3
-rw-r--r--chrome/browser/extensions/api/downloads/downloads_api.cc4
-rw-r--r--chrome/browser/media_galleries/media_galleries_scan_result_controller.cc4
-rw-r--r--chrome/browser/platform_util.cc65
-rw-r--r--chrome/browser/platform_util.h51
-rw-r--r--chrome/browser/platform_util_android.cc5
-rw-r--r--chrome/browser/platform_util_chromeos.cc77
-rw-r--r--chrome/browser/platform_util_internal.h29
-rw-r--r--chrome/browser/platform_util_linux.cc60
-rw-r--r--chrome/browser/platform_util_mac.mm42
-rw-r--r--chrome/browser/platform_util_unittest.cc300
-rw-r--r--chrome/browser/platform_util_win.cc68
-rw-r--r--chrome/browser/ui/ash/chrome_screenshot_grabber.cc7
-rw-r--r--chrome/browser/ui/webui/downloads_dom_handler.cc3
-rw-r--r--chrome/chrome_browser.gypi2
-rw-r--r--chrome/chrome_tests_unit.gypi1
-rw-r--r--chrome/common/chrome_utility_messages.h7
-rw-r--r--chrome/utility/shell_handler_win.cc14
-rw-r--r--chrome/utility/shell_handler_win.h3
-rw-r--r--ui/base/win/shell.cc76
-rw-r--r--ui/base/win/shell.h27
25 files changed, 856 insertions, 204 deletions
diff --git a/chrome/browser/chromeos/file_manager/fileapi_util.cc b/chrome/browser/chromeos/file_manager/fileapi_util.cc
index 15550c5..e7a1a4b 100644
--- a/chrome/browser/chromeos/file_manager/fileapi_util.cc
+++ b/chrome/browser/chromeos/file_manager/fileapi_util.cc
@@ -229,6 +229,18 @@ void CheckIfDirectoryExistsOnIOThread(
file_system_url, callback);
}
+// Used by GetMetadataForPath
+void GetMetadataOnIOThread(
+ scoped_refptr<storage::FileSystemContext> file_system_context,
+ const GURL& url,
+ const storage::FileSystemOperationRunner::GetMetadataCallback& callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ const storage::FileSystemURL file_system_url =
+ file_system_context->CrackURL(url);
+ file_system_context->operation_runner()->GetMetadata(file_system_url,
+ callback);
+}
+
// Checks if the |file_path| points non-native location or not.
bool IsUnderNonNativeLocalPath(const storage::FileSystemContext& context,
const base::FilePath& file_path) {
@@ -557,9 +569,24 @@ void CheckIfDirectoryExists(
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
- base::Bind(&CheckIfDirectoryExistsOnIOThread,
- file_system_context,
- url,
+ base::Bind(&CheckIfDirectoryExistsOnIOThread, file_system_context, url,
+ google_apis::CreateRelayCallback(callback)));
+}
+
+void GetMetadataForPath(
+ scoped_refptr<storage::FileSystemContext> file_system_context,
+ const GURL& url,
+ const storage::FileSystemOperationRunner::GetMetadataCallback& callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ storage::ExternalFileSystemBackend* const backend =
+ file_system_context->external_backend();
+ DCHECK(backend);
+ backend->GrantFullAccessToExtension(kFileManagerAppId);
+
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&GetMetadataOnIOThread, file_system_context, url,
google_apis::CreateRelayCallback(callback)));
}
diff --git a/chrome/browser/chromeos/file_manager/fileapi_util.h b/chrome/browser/chromeos/file_manager/fileapi_util.h
index 3756c7b..a6ae85e 100644
--- a/chrome/browser/chromeos/file_manager/fileapi_util.h
+++ b/chrome/browser/chromeos/file_manager/fileapi_util.h
@@ -151,6 +151,12 @@ void CheckIfDirectoryExists(
const GURL& url,
const storage::FileSystemOperationRunner::StatusCallback& callback);
+// Get metadata for object at |url|.
+void GetMetadataForPath(
+ scoped_refptr<storage::FileSystemContext> file_system_context,
+ const GURL& url,
+ const storage::FileSystemOperationRunner::GetMetadataCallback& callback);
+
// Obtains isolated file system URL from |virtual_path| pointing a file in the
// external file system.
storage::FileSystemURL CreateIsolatedURLFromVirtualPath(
diff --git a/chrome/browser/chromeos/file_manager/open_util.cc b/chrome/browser/chromeos/file_manager/open_util.cc
index 9cbc3a0..b21834e 100644
--- a/chrome/browser/chromeos/file_manager/open_util.cc
+++ b/chrome/browser/chromeos/file_manager/open_util.cc
@@ -15,18 +15,12 @@
#include "chrome/browser/chromeos/file_manager/path_util.h"
#include "chrome/browser/chromeos/file_manager/url_util.h"
#include "chrome/browser/extensions/api/file_handlers/mime_util.h"
-#include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/browser_finder.h"
-#include "chrome/browser/ui/browser_window.h"
-#include "chrome/browser/ui/simple_message_box.h"
-#include "chrome/grit/generated_resources.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/user_metrics.h"
#include "storage/browser/fileapi/file_system_backend.h"
#include "storage/browser/fileapi/file_system_context.h"
#include "storage/browser/fileapi/file_system_operation_runner.h"
#include "storage/browser/fileapi/file_system_url.h"
-#include "ui/base/l10n/l10n_util.h"
using content::BrowserThread;
using storage::FileSystemURL;
@@ -35,25 +29,14 @@ namespace file_manager {
namespace util {
namespace {
-// Shows a warning message box saying that the file could not be opened.
-void ShowWarningMessageBox(Profile* profile,
- const base::FilePath& file_path,
- int message_id) {
- Browser* browser = chrome::FindTabbedBrowser(
- profile, false, chrome::HOST_DESKTOP_TYPE_ASH);
- chrome::ShowMessageBox(
- browser ? browser->window()->GetNativeWindow() : NULL,
- l10n_util::GetStringFUTF16(
- IDS_FILE_BROWSER_ERROR_VIEWING_FILE_TITLE,
- base::UTF8ToUTF16(file_path.BaseName().AsUTF8Unsafe())),
- l10n_util::GetStringUTF16(message_id),
- chrome::MESSAGE_BOX_TYPE_WARNING);
-}
+bool shell_operations_allowed = true;
// Executes the |task| for the file specified by |url|.
void ExecuteFileTaskForUrl(Profile* profile,
const file_tasks::TaskDescriptor& task,
const GURL& url) {
+ if (!shell_operations_allowed)
+ return;
storage::FileSystemContext* file_system_context =
GetFileSystemContextForExtensionId(profile, kFileManagerAppId);
@@ -75,6 +58,8 @@ void OpenFileManagerWithInternalActionId(Profile* profile,
const GURL& url,
const std::string& action_id) {
DCHECK(action_id == "open" || action_id == "select");
+ if (!shell_operations_allowed)
+ return;
content::RecordAction(base::UserMetricsAction("ShowFileBrowserFullTab"));
file_tasks::TaskDescriptor task(kFileManagerAppId,
@@ -87,7 +72,7 @@ void OpenFileManagerWithInternalActionId(Profile* profile,
void OpenFileWithMimeType(Profile* profile,
const base::FilePath& path,
const GURL& url,
- const base::Callback<void(bool)>& callback,
+ const platform_util::OpenOperationCallback& callback,
const std::string& mime_type) {
extensions::app_file_handler_util::PathAndMimeTypeSet path_mime_set;
path_mime_set.insert(std::make_pair(path, mime_type));
@@ -115,100 +100,96 @@ void OpenFileWithMimeType(Profile* profile,
}
if (chosen_task != nullptr) {
- ExecuteFileTaskForUrl(profile, chosen_task->task_descriptor(), url);
- callback.Run(true);
+ if (shell_operations_allowed)
+ ExecuteFileTaskForUrl(profile, chosen_task->task_descriptor(), url);
+ callback.Run(platform_util::OPEN_SUCCEEDED);
} else {
- callback.Run(false);
+ callback.Run(platform_util::OPEN_FAILED_NO_HANLDER_FOR_FILE_TYPE);
}
}
// Opens the file specified by |url| by finding and executing a file task for
-// the file. In case of success, calls |callback| with true. Otherwise the
-// returned value is false.
+// the file. Calls |callback| with the result.
void OpenFile(Profile* profile,
const base::FilePath& path,
const GURL& url,
- const base::Callback<void(bool)>& callback) {
+ const platform_util::OpenOperationCallback& callback) {
extensions::app_file_handler_util::GetMimeTypeForLocalPath(
- profile,
- path,
+ profile, path,
base::Bind(&OpenFileWithMimeType, profile, path, url, callback));
}
-// Called when execution of ContinueOpenItem() is completed.
-void OnContinueOpenItemCompleted(Profile* profile,
- const base::FilePath& file_path,
- bool result) {
- if (!result) {
- int message;
- if (file_path.Extension() == FILE_PATH_LITERAL(".dmg"))
- message = IDS_FILE_BROWSER_ERROR_VIEWING_FILE_FOR_DMG;
- else if (file_path.Extension() == FILE_PATH_LITERAL(".exe") ||
- file_path.Extension() == FILE_PATH_LITERAL(".msi"))
- message = IDS_FILE_BROWSER_ERROR_VIEWING_FILE_FOR_EXECUTABLE;
- else
- message = IDS_FILE_BROWSER_ERROR_VIEWING_FILE;
- ShowWarningMessageBox(profile, file_path, message);
- }
-}
-
-// Used to implement OpenItem().
-void ContinueOpenItem(Profile* profile,
- const base::FilePath& file_path,
- const GURL& url,
- base::File::Error error) {
+void OpenItemWithMetadata(Profile* profile,
+ const base::FilePath& file_path,
+ const GURL& url,
+ platform_util::OpenItemType expected_type,
+ const platform_util::OpenOperationCallback& callback,
+ base::File::Error error,
+ const base::File::Info& file_info) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ if (error != base::File::FILE_OK) {
+ callback.Run(error == base::File::FILE_ERROR_NOT_FOUND
+ ? platform_util::OPEN_FAILED_PATH_NOT_FOUND
+ : platform_util::OPEN_FAILED_FILE_ERROR);
+ return;
+ }
- if (error == base::File::FILE_OK) {
- // A directory exists at |url|. Open it with the file manager.
+ // Note that there exists a TOCTOU race between the time the metadata for
+ // |file_path| was determined and when it is opened based on the metadata.
+ if (expected_type == platform_util::OPEN_FOLDER && file_info.is_directory) {
OpenFileManagerWithInternalActionId(profile, url, "open");
- } else {
- // |url| should be a file. Open it.
- OpenFile(profile,
- file_path,
- url,
- base::Bind(&OnContinueOpenItemCompleted, profile, file_path));
+ callback.Run(platform_util::OPEN_SUCCEEDED);
+ return;
}
-}
-// Converts the |path| passed from external callers to the filesystem URL
-// that the file manager can correctly handle.
-//
-// When conversion fails, it shows a warning dialog UI and returns false.
-bool ConvertPath(Profile* profile, const base::FilePath& path, GURL* url) {
- if (!ConvertAbsoluteFilePathToFileSystemUrl(
- profile, path, kFileManagerAppId, url)) {
- ShowWarningMessageBox(profile, path,
- IDS_FILE_BROWSER_ERROR_UNRESOLVABLE_FILE);
- return false;
+ if (expected_type == platform_util::OPEN_FILE && !file_info.is_directory) {
+ OpenFile(profile, file_path, url, callback);
+ return;
}
- return true;
+
+ callback.Run(platform_util::OPEN_FAILED_INVALID_TYPE);
}
} // namespace
-void OpenItem(Profile* profile, const base::FilePath& file_path) {
+void OpenItem(Profile* profile,
+ const base::FilePath& file_path,
+ platform_util::OpenItemType expected_type,
+ const platform_util::OpenOperationCallback& callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
GURL url;
- if (!ConvertPath(profile, file_path, &url))
+ if (!ConvertAbsoluteFilePathToFileSystemUrl(profile, file_path,
+ kFileManagerAppId, &url)) {
+ callback.Run(platform_util::OPEN_FAILED_PATH_NOT_FOUND);
return;
+ }
- CheckIfDirectoryExists(
- GetFileSystemContextForExtensionId(profile, kFileManagerAppId),
- url,
- base::Bind(&ContinueOpenItem, profile, file_path, url));
+ GetMetadataForPath(
+ GetFileSystemContextForExtensionId(profile, kFileManagerAppId), url,
+ base::Bind(&OpenItemWithMetadata, profile, file_path, url, expected_type,
+ callback));
}
-void ShowItemInFolder(Profile* profile, const base::FilePath& file_path) {
+void ShowItemInFolder(Profile* profile,
+ const base::FilePath& file_path,
+ const platform_util::OpenOperationCallback& callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
GURL url;
- if (!ConvertPath(profile, file_path, &url))
+ if (!ConvertAbsoluteFilePathToFileSystemUrl(profile, file_path,
+ kFileManagerAppId, &url)) {
+ callback.Run(platform_util::OPEN_FAILED_PATH_NOT_FOUND);
return;
+ }
// This action changes the selection so we do not reuse existing tabs.
OpenFileManagerWithInternalActionId(profile, url, "select");
+ callback.Run(platform_util::OPEN_SUCCEEDED);
+}
+
+void DisableShellOperationsForTesting() {
+ shell_operations_allowed = false;
}
} // namespace util
diff --git a/chrome/browser/chromeos/file_manager/open_util.h b/chrome/browser/chromeos/file_manager/open_util.h
index 898cd8b..668827a 100644
--- a/chrome/browser/chromeos/file_manager/open_util.h
+++ b/chrome/browser/chromeos/file_manager/open_util.h
@@ -8,6 +8,9 @@
#ifndef CHROME_BROWSER_CHROMEOS_FILE_MANAGER_OPEN_UTIL_H_
#define CHROME_BROWSER_CHROMEOS_FILE_MANAGER_OPEN_UTIL_H_
+#include "base/callback_forward.h"
+#include "chrome/browser/platform_util.h"
+
class Profile;
namespace base {
@@ -17,16 +20,33 @@ class FilePath;
namespace file_manager {
namespace util {
-// Opens an item (file or directory). If the target is a directory, the
-// directory will be opened in the file manager. If the target is a file, the
-// file will be opened using a file handler, a file browser handler, or the
-// browser (open in a tab). The default handler has precedence over other
-// handlers, if defined for the type of the target file.
-void OpenItem(Profile* profile, const base::FilePath& file_path);
+// If |item_type| is OPEN_FILE: Opens an item using a file handler, a file
+// browser handler, or the browser (open in a tab). The default handler has
+// precedence over other handlers, if defined for the type of the target file.
+//
+// If |item_type| is OPEN_FOLDER: Open the directory at |file_path| using the
+// file browser.
+//
+// It is an error for |file_path| to not match the type implied by |item_type|.
+// This error will be reported to |callback|.
+//
+// If |callback| is null, shows an error message to the user indicating the
+// error if the operation is unsuccessful. No error messages will be displayed
+// if |callback| is non-null.
+void OpenItem(Profile* profile,
+ const base::FilePath& file_path,
+ platform_util::OpenItemType item_type,
+ const platform_util::OpenOperationCallback& callback);
// Opens the file manager for the folder containing the item specified by
// |file_path|, with the item selected.
-void ShowItemInFolder(Profile* profile, const base::FilePath& file_path);
+void ShowItemInFolder(Profile* profile,
+ const base::FilePath& file_path,
+ const platform_util::OpenOperationCallback& callback);
+
+// Change the behavior of the above functions to do everything except launch any
+// extensions including a file browser.
+void DisableShellOperationsForTesting();
} // namespace util
} // namespace file_manager
diff --git a/chrome/browser/download/chrome_download_manager_delegate.cc b/chrome/browser/download/chrome_download_manager_delegate.cc
index 52a151b..ad2b94b 100644
--- a/chrome/browser/download/chrome_download_manager_delegate.cc
+++ b/chrome/browser/download/chrome_download_manager_delegate.cc
@@ -433,7 +433,8 @@ void ChromeDownloadManagerDelegate::OpenDownloadUsingPlatformHandler(
base::FilePath platform_path(
GetPlatformDownloadPath(profile_, download, PLATFORM_TARGET_PATH));
DCHECK(!platform_path.empty());
- platform_util::OpenItem(profile_, platform_path);
+ platform_util::OpenItem(profile_, platform_path, platform_util::OPEN_FILE,
+ platform_util::OpenOperationCallback());
}
void ChromeDownloadManagerDelegate::OpenDownload(DownloadItem* download) {
diff --git a/chrome/browser/extensions/api/downloads/downloads_api.cc b/chrome/browser/extensions/api/downloads/downloads_api.cc
index ae841dc..268043e 100644
--- a/chrome/browser/extensions/api/downloads/downloads_api.cc
+++ b/chrome/browser/extensions/api/downloads/downloads_api.cc
@@ -1331,8 +1331,8 @@ bool DownloadsShowDefaultFolderFunction::RunAsync() {
DownloadManager* incognito_manager = NULL;
GetManagers(GetProfile(), include_incognito(), &manager, &incognito_manager);
platform_util::OpenItem(
- GetProfile(),
- DownloadPrefs::FromDownloadManager(manager)->DownloadPath());
+ GetProfile(), DownloadPrefs::FromDownloadManager(manager)->DownloadPath(),
+ platform_util::OPEN_FOLDER, platform_util::OpenOperationCallback());
RecordApiFunctions(DOWNLOADS_FUNCTION_SHOW_DEFAULT_FOLDER);
return true;
}
diff --git a/chrome/browser/media_galleries/media_galleries_scan_result_controller.cc b/chrome/browser/media_galleries/media_galleries_scan_result_controller.cc
index 3b39a44..1b791b2 100644
--- a/chrome/browser/media_galleries/media_galleries_scan_result_controller.cc
+++ b/chrome/browser/media_galleries/media_galleries_scan_result_controller.cc
@@ -186,7 +186,9 @@ void MediaGalleriesScanResultController::DidClickOpenFolderViewer(
NOTREACHED();
return;
}
- platform_util::OpenItem(GetProfile(), entry->second.pref_info.AbsolutePath());
+ platform_util::OpenItem(GetProfile(), entry->second.pref_info.AbsolutePath(),
+ platform_util::OPEN_FOLDER,
+ platform_util::OpenOperationCallback());
}
void MediaGalleriesScanResultController::DidForgetEntry(
diff --git a/chrome/browser/platform_util.cc b/chrome/browser/platform_util.cc
new file mode 100644
index 0000000..5cd2e45
--- /dev/null
+++ b/chrome/browser/platform_util.cc
@@ -0,0 +1,65 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/platform_util.h"
+
+#include "base/files/file.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "chrome/browser/platform_util_internal.h"
+#include "content/public/browser/browser_thread.h"
+
+using content::BrowserThread;
+
+namespace platform_util {
+
+namespace {
+
+bool shell_operations_allowed = true;
+
+void VerifyAndOpenItemOnBlockingThread(const base::FilePath& path,
+ OpenItemType type,
+ const OpenOperationCallback& callback) {
+ base::File target_item(path, base::File::FLAG_OPEN | base::File::FLAG_READ);
+ if (!base::PathExists(path)) {
+ if (!callback.is_null())
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(callback, OPEN_FAILED_PATH_NOT_FOUND));
+ return;
+ }
+ if (base::DirectoryExists(path) != (type == OPEN_FOLDER)) {
+ if (!callback.is_null())
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(callback, OPEN_FAILED_INVALID_TYPE));
+ return;
+ }
+
+ if (shell_operations_allowed)
+ internal::PlatformOpenVerifiedItem(path, type);
+ if (!callback.is_null())
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(callback, OPEN_SUCCEEDED));
+}
+
+} // namespace
+
+namespace internal {
+
+void DisableShellOperationsForTesting() {
+ shell_operations_allowed = false;
+}
+
+} // namespace internal
+
+void OpenItem(Profile* profile,
+ const base::FilePath& full_path,
+ OpenItemType item_type,
+ const OpenOperationCallback& callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ BrowserThread::PostBlockingPoolTask(
+ FROM_HERE, base::Bind(&VerifyAndOpenItemOnBlockingThread, full_path,
+ item_type, callback));
+}
+
+} // namespace platform_util
diff --git a/chrome/browser/platform_util.h b/chrome/browser/platform_util.h
index 3e93469..822e89d 100644
--- a/chrome/browser/platform_util.h
+++ b/chrome/browser/platform_util.h
@@ -7,6 +7,7 @@
#include <string>
+#include "base/callback_forward.h"
#include "base/strings/string16.h"
#include "ui/gfx/native_widget_types.h"
@@ -19,16 +20,50 @@ class FilePath;
namespace platform_util {
-// Show the given file in a file manager. If possible, select the file.
-// The |profile| is used to determine the running profile of file manager app
-// in Chrome OS only. Not used in other platforms.
-// Must be called from the UI thread.
+// Result of calling OpenFile() or OpenFolder() passed into OpenOperationResult.
+enum OpenOperationResult {
+ OPEN_SUCCEEDED,
+ OPEN_FAILED_PATH_NOT_FOUND, // Specified path does not exist.
+ OPEN_FAILED_INVALID_TYPE, // Type of object found at path did not match what
+ // was expected. I.e. OpenFile was called on a
+ // folder or OpenFolder called on a file.
+ OPEN_FAILED_NO_HANLDER_FOR_FILE_TYPE, // There was no file handler capable of
+ // opening file. Only returned on
+ // ChromeOS.
+ OPEN_FAILED_FILE_ERROR, // Open operation failed due to some other file
+ // error.
+};
+
+// Type of item that is the target of the OpenItem() call.
+enum OpenItemType { OPEN_FILE, OPEN_FOLDER };
+
+// Callback used with OpenFile and OpenFolder.
+typedef base::Callback<void(OpenOperationResult)> OpenOperationCallback;
+
+// Opens the item specified by |full_path|, which is expected to be the type
+// indicated by |item_type| in the desktop's default manner.
+// |callback| will be invoked on the UI thread with the result of the open
+// operation.
+//
+// It is an error if the object at |full_path| does not match the intended type
+// specified in |item_type|. This error will be reported to |callback|.
+//
+// Note: On all platforms, the user may be shown additional UI if there is no
+// suitable handler for |full_path|. On Chrome OS, all errors will result in
+// visible error messages iff |callback| is not specified.
+// Must be called on the UI thread.
+void OpenItem(Profile* profile,
+ const base::FilePath& full_path,
+ OpenItemType item_type,
+ const OpenOperationCallback& callback);
+
+// Opens the folder containing the item specified by |full_path| in the
+// desktop's default manner. If possible, the item will be selected. The
+// |profile| is used to determine the running profile of file manager app in
+// Chrome OS only. |profile| is not used in platforms other than Chrome OS. Must
+// be called on the UI thread.
void ShowItemInFolder(Profile* profile, const base::FilePath& full_path);
-// Open the given file in the desktop's default manner.
-// Must be called from the UI thread.
-void OpenItem(Profile* profile, const base::FilePath& full_path);
-
// Open the given external protocol URL in the desktop's default manner.
// (For example, mailto: URLs in the default mail user agent.)
// Must be called from the UI thread.
diff --git a/chrome/browser/platform_util_android.cc b/chrome/browser/platform_util_android.cc
index b74b86d..8a14b58 100644
--- a/chrome/browser/platform_util_android.cc
+++ b/chrome/browser/platform_util_android.cc
@@ -14,7 +14,10 @@ void ShowItemInFolder(Profile* profile, const base::FilePath& full_path) {
NOTIMPLEMENTED();
}
-void OpenItem(Profile* profile, const base::FilePath& full_path) {
+void OpenItem(Profile* profile,
+ const base::FilePath& full_path,
+ OpenItemType item_type,
+ const OpenOperationCallback& callback) {
NOTIMPLEMENTED();
}
diff --git a/chrome/browser/platform_util_chromeos.cc b/chrome/browser/platform_util_chromeos.cc
index aad0b73..8d463bf 100644
--- a/chrome/browser/platform_util_chromeos.cc
+++ b/chrome/browser/platform_util_chromeos.cc
@@ -4,34 +4,99 @@
#include "chrome/browser/platform_util.h"
+#include "base/bind.h"
+#include "base/files/file_path.h"
#include "chrome/browser/chromeos/file_manager/open_util.h"
+#include "chrome/browser/platform_util_internal.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_navigator.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/simple_message_box.h"
+#include "chrome/grit/generated_resources.h"
#include "content/public/browser/browser_thread.h"
+#include "ui/base/l10n/l10n_util.h"
#include "url/gurl.h"
using content::BrowserThread;
+namespace platform_util {
+
namespace {
const char kGmailComposeUrl[] =
"https://mail.google.com/mail/?extsrc=mailto&url=";
+void ShowWarningOnOpenOperationResult(Profile* profile,
+ const base::FilePath& path,
+ OpenOperationResult result) {
+ int message_id = IDS_FILE_BROWSER_ERROR_VIEWING_FILE;
+ switch (result) {
+ case OPEN_SUCCEEDED:
+ return;
+
+ case OPEN_FAILED_PATH_NOT_FOUND:
+ message_id = IDS_FILE_BROWSER_ERROR_UNRESOLVABLE_FILE;
+ break;
+
+ case OPEN_FAILED_INVALID_TYPE:
+ return;
+
+ case OPEN_FAILED_NO_HANLDER_FOR_FILE_TYPE:
+ if (path.MatchesExtension(FILE_PATH_LITERAL(".dmg")))
+ message_id = IDS_FILE_BROWSER_ERROR_VIEWING_FILE_FOR_DMG;
+ else if (path.MatchesExtension(FILE_PATH_LITERAL(".exe")) ||
+ path.MatchesExtension(FILE_PATH_LITERAL(".msi")))
+ message_id = IDS_FILE_BROWSER_ERROR_VIEWING_FILE_FOR_EXECUTABLE;
+ else
+ message_id = IDS_FILE_BROWSER_ERROR_VIEWING_FILE;
+ break;
+
+ case OPEN_FAILED_FILE_ERROR:
+ message_id = IDS_FILE_BROWSER_ERROR_VIEWING_FILE;
+ break;
+ }
+
+ Browser* browser =
+ chrome::FindTabbedBrowser(profile, false, chrome::HOST_DESKTOP_TYPE_ASH);
+ chrome::ShowMessageBox(
+ browser ? browser->window()->GetNativeWindow() : nullptr,
+ l10n_util::GetStringFUTF16(IDS_FILE_BROWSER_ERROR_VIEWING_FILE_TITLE,
+ path.BaseName().AsUTF16Unsafe()),
+ l10n_util::GetStringUTF16(message_id), chrome::MESSAGE_BOX_TYPE_WARNING);
+}
+
} // namespace
-namespace platform_util {
+namespace internal {
+
+void DisableShellOperationsForTesting() {
+ file_manager::util::DisableShellOperationsForTesting();
+}
+
+} // namespace internal
void ShowItemInFolder(Profile* profile, const base::FilePath& full_path) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
- file_manager::util::ShowItemInFolder(profile, full_path);
+ file_manager::util::ShowItemInFolder(
+ profile, full_path,
+ base::Bind(&ShowWarningOnOpenOperationResult, profile, full_path));
}
-void OpenItem(Profile* profile, const base::FilePath& full_path) {
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
- file_manager::util::OpenItem(profile, full_path);
+void OpenItem(Profile* profile,
+ const base::FilePath& full_path,
+ OpenItemType item_type,
+ const OpenOperationCallback& callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ file_manager::util::OpenItem(
+ profile, full_path, item_type,
+ callback.is_null()
+ ? base::Bind(&ShowWarningOnOpenOperationResult, profile, full_path)
+ : callback);
}
void OpenExternal(Profile* profile, const GURL& url) {
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
// This code should be obsolete since we have default handlers in ChromeOS
// which should handle this. However - there are two things which make it
diff --git a/chrome/browser/platform_util_internal.h b/chrome/browser/platform_util_internal.h
new file mode 100644
index 0000000..0b93b7d
--- /dev/null
+++ b/chrome/browser/platform_util_internal.h
@@ -0,0 +1,29 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_PLATFORM_UTIL_INTERNAL_H_
+#define CHROME_BROWSER_PLATFORM_UTIL_INTERNAL_H_
+
+#include "chrome/browser/platform_util.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace platform_util {
+namespace internal {
+
+// Called by platform_util.cc on desktop platforms to invoke platform specific
+// logic to open |path| using a suitable handler. |path| has been verified to be
+// of type |type|.
+// Always called on the blocking pool.
+void PlatformOpenVerifiedItem(const base::FilePath& path, OpenItemType type);
+
+// Prevent shell or external applications from being invoked during testing.
+void DisableShellOperationsForTesting();
+
+} // namespace internal
+} // namespace platform_util
+
+#endif // CHROME_BROWSER_PLATFORM_UTIL_INTERNAL_H_
diff --git a/chrome/browser/platform_util_linux.cc b/chrome/browser/platform_util_linux.cc
index c94f8fe..9938451 100644
--- a/chrome/browser/platform_util_linux.cc
+++ b/chrome/browser/platform_util_linux.cc
@@ -9,19 +9,25 @@
#include "base/process/kill.h"
#include "base/process/launch.h"
#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/platform_util_internal.h"
#include "content/public/browser/browser_thread.h"
#include "url/gurl.h"
using content::BrowserThread;
+namespace platform_util {
+
namespace {
-void XDGUtil(const std::string& util, const std::string& arg) {
+void XDGUtil(const std::string& util,
+ const base::FilePath& working_directory,
+ const std::string& arg) {
std::vector<std::string> argv;
argv.push_back(util);
argv.push_back(arg);
base::LaunchOptions options;
+ options.current_directory = working_directory;
options.allow_new_privs = true;
// xdg-open can fall back on mailcap which eventually might plumb through
// to a command that needs a terminal. Set the environment variable telling
@@ -42,39 +48,43 @@ void XDGUtil(const std::string& util, const std::string& arg) {
base::EnsureProcessGetsReaped(process.Pid());
}
-void XDGOpen(const std::string& path) {
- XDGUtil("xdg-open", path);
+void XDGOpen(const base::FilePath& working_directory, const std::string& path) {
+ XDGUtil("xdg-open", working_directory, path);
}
void XDGEmail(const std::string& email) {
- XDGUtil("xdg-email", email);
-}
-
-// TODO(estade): It would be nice to be able to select the file in the file
-// manager, but that probably requires extending xdg-open. For now just
-// show the folder.
-void ShowItemInFolderOnFileThread(const base::FilePath& full_path) {
- base::FilePath dir = full_path.DirName();
- if (!base::DirectoryExists(dir))
- return;
-
- XDGOpen(dir.value());
+ XDGUtil("xdg-email", base::FilePath(), email);
}
} // namespace
-namespace platform_util {
-
-void ShowItemInFolder(Profile* profile, const base::FilePath& full_path) {
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
- BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
- base::Bind(&ShowItemInFolderOnFileThread, full_path));
+namespace internal {
+
+void PlatformOpenVerifiedItem(const base::FilePath& path, OpenItemType type) {
+ switch (type) {
+ case OPEN_FILE:
+ XDGOpen(path.DirName(), path.value());
+ break;
+ case OPEN_FOLDER:
+ // The utility process checks the working directory prior to the
+ // invocation of xdg-open by changing the current directory into it. This
+ // operation only succeeds if |path| is a directory. Opening "." from
+ // there ensures that the target of the operation is a directory. Note
+ // that there remains a TOCTOU race where the directory could be unlinked
+ // between the time the utility process changes into the directory and the
+ // time the application invoked by xdg-open inspects the path by name.
+ XDGOpen(path, ".");
+ break;
+ }
}
+} // namespace internal
-void OpenItem(Profile* profile, const base::FilePath& full_path) {
+void ShowItemInFolder(Profile* profile, const base::FilePath& full_path) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
- BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
- base::Bind(&XDGOpen, full_path.value()));
+ // TODO(estade): It would be nice to be able to select the file in the file
+ // manager, but that probably requires extending xdg-open. For now just show
+ // the folder.
+ OpenItem(profile, full_path.DirName(), OPEN_FOLDER, OpenOperationCallback());
}
void OpenExternal(Profile* profile, const GURL& url) {
@@ -82,7 +92,7 @@ void OpenExternal(Profile* profile, const GURL& url) {
if (url.SchemeIs("mailto"))
XDGEmail(url.spec());
else
- XDGOpen(url.spec());
+ XDGOpen(base::FilePath(), url.spec());
}
} // namespace platform_util
diff --git a/chrome/browser/platform_util_mac.mm b/chrome/browser/platform_util_mac.mm
index 15622f1..8ce07be 100644
--- a/chrome/browser/platform_util_mac.mm
+++ b/chrome/browser/platform_util_mac.mm
@@ -8,6 +8,8 @@
#import <Cocoa/Cocoa.h>
#include <CoreServices/CoreServices.h>
+#include "base/bind.h"
+#include "base/files/file_util.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/mac/mac_logging.h"
@@ -15,6 +17,8 @@
#import "base/mac/sdk_forward_declarations.h"
#include "base/mac/scoped_aedesc.h"
#include "base/strings/sys_string_conversions.h"
+#include "chrome/browser/platform_util_internal.h"
+#include "content/public/browser/browser_thread.h"
#include "url/gurl.h"
namespace platform_util {
@@ -27,7 +31,7 @@ void ShowItemInFolder(Profile* profile, const base::FilePath& full_path) {
LOG(WARNING) << "NSWorkspace failed to select file " << full_path.value();
}
-void OpenItem(Profile* profile, const base::FilePath& full_path) {
+void OpenFileOnMainThread(const base::FilePath& full_path) {
DCHECK([NSThread isMainThread]);
NSString* path_string = base::SysUTF8ToNSString(full_path.value());
if (!path_string)
@@ -64,7 +68,7 @@ void OpenItem(Profile* profile, const base::FilePath& full_path) {
sizeof(finderCreatorCode), // dataSize
address.OutPointer()); // result
if (status != noErr) {
- OSSTATUS_LOG(WARNING, status) << "Could not create OpenItem() AE target";
+ OSSTATUS_LOG(WARNING, status) << "Could not create OpenFile() AE target";
return;
}
@@ -77,7 +81,7 @@ void OpenItem(Profile* profile, const base::FilePath& full_path) {
kAnyTransactionID, // transactionID
theEvent.OutPointer()); // result
if (status != noErr) {
- OSSTATUS_LOG(WARNING, status) << "Could not create OpenItem() AE event";
+ OSSTATUS_LOG(WARNING, status) << "Could not create OpenFile() AE event";
return;
}
@@ -88,7 +92,7 @@ void OpenItem(Profile* profile, const base::FilePath& full_path) {
false, // isRecord
fileList.OutPointer()); // resultList
if (status != noErr) {
- OSSTATUS_LOG(WARNING, status) << "Could not create OpenItem() AE file list";
+ OSSTATUS_LOG(WARNING, status) << "Could not create OpenFile() AE file list";
return;
}
@@ -104,11 +108,11 @@ void OpenItem(Profile* profile, const base::FilePath& full_path) {
sizeof(pathRef)); // dataSize
if (status != noErr) {
OSSTATUS_LOG(WARNING, status)
- << "Could not add file path to AE list in OpenItem()";
+ << "Could not add file path to AE list in OpenFile()";
return;
}
} else {
- LOG(WARNING) << "Could not get FSRef for path URL in OpenItem()";
+ LOG(WARNING) << "Could not get FSRef for path URL in OpenFile()";
return;
}
@@ -118,7 +122,7 @@ void OpenItem(Profile* profile, const base::FilePath& full_path) {
fileList); // theAEDesc
if (status != noErr) {
OSSTATUS_LOG(WARNING, status)
- << "Could not put the AE file list the path in OpenItem()";
+ << "Could not put the AE file list the path in OpenFile()";
return;
}
@@ -133,10 +137,32 @@ void OpenItem(Profile* profile, const base::FilePath& full_path) {
NULL); // filterProc
if (status != noErr) {
OSSTATUS_LOG(WARNING, status)
- << "Could not send AE to Finder in OpenItem()";
+ << "Could not send AE to Finder in OpenFile()";
}
}
+namespace internal {
+
+void PlatformOpenVerifiedItem(const base::FilePath& path, OpenItemType type) {
+ switch (type) {
+ case OPEN_FILE:
+ content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
+ base::Bind(&OpenFileOnMainThread, path));
+ return;
+ case OPEN_FOLDER:
+ NSString* path_string = base::SysUTF8ToNSString(path.value());
+ if (!path_string)
+ return;
+ // Note that there exists a TOCTOU race between the time that |path| was
+ // verified as being a directory and when NSWorkspace invokes Finder (or
+ // alternative) to open |path_string|.
+ [[NSWorkspace sharedWorkspace] openFile:path_string];
+ return;
+ }
+}
+
+} // namespace internal
+
void OpenExternal(Profile* profile, const GURL& url) {
DCHECK([NSThread isMainThread]);
NSString* url_string = base::SysUTF8ToNSString(url.spec());
diff --git a/chrome/browser/platform_util_unittest.cc b/chrome/browser/platform_util_unittest.cc
new file mode 100644
index 0000000..16a5edc
--- /dev/null
+++ b/chrome/browser/platform_util_unittest.cc
@@ -0,0 +1,300 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/platform_util.h"
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/run_loop.h"
+#include "chrome/browser/platform_util_internal.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_CHROMEOS)
+#include "base/json/json_string_value_serializer.h"
+#include "base/values.h"
+#include "chrome/browser/chrome_content_browser_client.h"
+#include "chrome/browser/chromeos/file_manager/app_id.h"
+#include "chrome/browser/chromeos/fileapi/file_system_backend.h"
+#include "chrome/browser/extensions/extension_special_storage_policy.h"
+#include "chrome/test/base/browser_with_test_window_test.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/mock_special_storage_policy.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/common/extension.h"
+#include "storage/browser/fileapi/external_mount_points.h"
+#include "storage/common/fileapi/file_system_types.h"
+#else
+#include "content/public/test/test_browser_thread_bundle.h"
+#endif
+
+namespace platform_util {
+
+namespace {
+
+#if defined(OS_CHROMEOS)
+
+// ChromeContentBrowserClient subclass that sets up a custom file system backend
+// that allows the test to grant file access to the file manager extension ID
+// without having to install the extension.
+class PlatformUtilTestContentBrowserClient
+ : public chrome::ChromeContentBrowserClient {
+ public:
+ void GetAdditionalFileSystemBackends(
+ content::BrowserContext* browser_context,
+ const base::FilePath& storage_partition_path,
+ ScopedVector<storage::FileSystemBackend>* additional_backends) override {
+ storage::ExternalMountPoints* external_mount_points =
+ content::BrowserContext::GetMountPoints(browser_context);
+ scoped_refptr<content::MockSpecialStoragePolicy> special_storage_policy =
+ new content::MockSpecialStoragePolicy();
+
+ // New FileSystemBackend that uses our MockSpecialStoragePolicy.
+ chromeos::FileSystemBackend* backend = new chromeos::FileSystemBackend(
+ nullptr, nullptr, nullptr, special_storage_policy,
+ external_mount_points,
+ storage::ExternalMountPoints::GetSystemInstance());
+ additional_backends->push_back(backend);
+
+ // The only change we need to make is to add kFileManagerAppId as a file
+ // handler.
+ special_storage_policy->AddFileHandler(file_manager::kFileManagerAppId);
+ }
+};
+
+// Base test fixture class to be used on Chrome OS.
+class PlatformUtilTestBase : public BrowserWithTestWindowTest {
+ protected:
+ void SetUpPlatformFixture(const base::FilePath& test_directory) {
+ content_browser_client_.reset(new PlatformUtilTestContentBrowserClient());
+ old_content_browser_client_ =
+ content::SetBrowserClientForTesting(content_browser_client_.get());
+
+ // The test_directory needs to be mounted for it to be accessible.
+ content::BrowserContext::GetMountPoints(GetProfile())
+ ->RegisterFileSystem("test", storage::kFileSystemTypeNativeLocal,
+ storage::FileSystemMountOption(), test_directory);
+
+ // To test opening a file, we are going to register a mock extension that
+ // handles .txt files. The extension doesn't actually need to exist due to
+ // the DisableShellOperationsForTesting() call which prevents the extension
+ // from being invoked.
+ std::string error;
+ int error_code = 0;
+
+ std::string json_manifest =
+ "{"
+ " \"manifest_version\": 2,"
+ " \"name\": \"Test extension\","
+ " \"version\": \"0\","
+ " \"app\": { \"background\": { \"scripts\": [\"main.js\"] }},"
+ " \"file_handlers\": {"
+ " \"text\": {"
+ " \"extensions\": [ \"txt\" ],"
+ " \"title\": \"Text\""
+ " }"
+ " }"
+ "}";
+ JSONStringValueDeserializer json_string_deserializer(json_manifest);
+ scoped_ptr<base::Value> manifest(
+ json_string_deserializer.Deserialize(&error_code, &error));
+ base::DictionaryValue* manifest_dictionary;
+
+ manifest->GetAsDictionary(&manifest_dictionary);
+ ASSERT_TRUE(manifest_dictionary);
+
+ scoped_refptr<extensions::Extension> extension =
+ extensions::Extension::Create(
+ test_directory.AppendASCII("invalid-extension"),
+ extensions::Manifest::INVALID_LOCATION, *manifest_dictionary,
+ extensions::Extension::NO_FLAGS, &error);
+ ASSERT_TRUE(error.empty()) << error;
+ extensions::ExtensionRegistry::Get(GetProfile())->AddEnabled(extension);
+ }
+
+ void TearDown() override {
+ content::ContentBrowserClient* content_browser_client =
+ content::SetBrowserClientForTesting(old_content_browser_client_);
+ old_content_browser_client_ = nullptr;
+ DCHECK_EQ(static_cast<content::ContentBrowserClient*>(
+ content_browser_client_.get()),
+ content_browser_client)
+ << "ContentBrowserClient changed during test.";
+ BrowserWithTestWindowTest::TearDown();
+ }
+
+ private:
+ scoped_ptr<content::ContentBrowserClient> content_browser_client_;
+ content::ContentBrowserClient* old_content_browser_client_ = nullptr;
+};
+
+#else
+
+// Test fixture used by all desktop platforms other than Chrome OS.
+class PlatformUtilTestBase : public testing::Test {
+ protected:
+ Profile* GetProfile() { return nullptr; }
+ void SetUpPlatformFixture(const base::FilePath&) {}
+
+ private:
+ content::TestBrowserThreadBundle thread_bundle_;
+};
+
+#endif
+
+class PlatformUtilTest : public PlatformUtilTestBase {
+ public:
+ void SetUp() override {
+ ASSERT_NO_FATAL_FAILURE(PlatformUtilTestBase::SetUp());
+
+ static const char kTestFileData[] = "Cow says moo!";
+ const int kTestFileDataLength = arraysize(kTestFileData) - 1;
+
+ // This prevents platfrom_util from invoking any shell or external APIs
+ // during tests. Doing so may result in external applications being launched
+ // and intefering with tests.
+ internal::DisableShellOperationsForTesting();
+
+ ASSERT_TRUE(directory_.CreateUniqueTempDir());
+
+ // A valid file.
+ existing_file_ = directory_.path().AppendASCII("test_file.txt");
+ ASSERT_EQ(
+ kTestFileDataLength,
+ base::WriteFile(existing_file_, kTestFileData, kTestFileDataLength));
+
+ // A valid folder.
+ existing_folder_ = directory_.path().AppendASCII("test_folder");
+ ASSERT_TRUE(base::CreateDirectory(existing_folder_));
+
+ // A non-existent path.
+ nowhere_ = directory_.path().AppendASCII("nowhere");
+
+ SetUpPlatformFixture(directory_.path());
+ }
+
+ OpenOperationResult CallOpenItem(const base::FilePath& path,
+ OpenItemType item_type) {
+ base::RunLoop run_loop;
+ OpenOperationResult result = OPEN_SUCCEEDED;
+ OpenOperationCallback callback =
+ base::Bind(&OnOpenOperationDone, run_loop.QuitClosure(), &result);
+ OpenItem(GetProfile(), path, item_type, callback);
+ run_loop.Run();
+ return result;
+ }
+
+ base::FilePath existing_file_;
+ base::FilePath existing_folder_;
+ base::FilePath nowhere_;
+
+ protected:
+ base::ScopedTempDir directory_;
+
+ private:
+ scoped_ptr<base::RunLoop> run_loop_;
+
+ static void OnOpenOperationDone(const base::Closure& closure,
+ OpenOperationResult* store_result,
+ OpenOperationResult result) {
+ *store_result = result;
+ closure.Run();
+ }
+};
+
+} // namespace
+
+TEST_F(PlatformUtilTest, OpenFile) {
+ EXPECT_EQ(OPEN_SUCCEEDED, CallOpenItem(existing_file_, OPEN_FILE));
+ EXPECT_EQ(OPEN_FAILED_INVALID_TYPE,
+ CallOpenItem(existing_folder_, OPEN_FILE));
+ EXPECT_EQ(OPEN_FAILED_PATH_NOT_FOUND, CallOpenItem(nowhere_, OPEN_FILE));
+}
+
+TEST_F(PlatformUtilTest, OpenFolder) {
+ EXPECT_EQ(OPEN_SUCCEEDED, CallOpenItem(existing_folder_, OPEN_FOLDER));
+ EXPECT_EQ(OPEN_FAILED_INVALID_TYPE,
+ CallOpenItem(existing_file_, OPEN_FOLDER));
+ EXPECT_EQ(OPEN_FAILED_PATH_NOT_FOUND, CallOpenItem(nowhere_, OPEN_FOLDER));
+}
+
+#if defined(OS_POSIX)
+// Symbolic links are currently only supported on Posix. Windows technically
+// supports it as well, but not on Windows XP.
+class PlatformUtilPosixTest : public PlatformUtilTest {
+ public:
+ void SetUp() override {
+ ASSERT_NO_FATAL_FAILURE(PlatformUtilTest::SetUp());
+
+ symlink_to_file_ = directory_.path().AppendASCII("l_file.txt");
+ ASSERT_TRUE(base::CreateSymbolicLink(existing_file_, symlink_to_file_));
+ symlink_to_folder_ = directory_.path().AppendASCII("l_folder");
+ ASSERT_TRUE(base::CreateSymbolicLink(existing_folder_, symlink_to_folder_));
+ symlink_to_nowhere_ = directory_.path().AppendASCII("l_nowhere");
+ ASSERT_TRUE(base::CreateSymbolicLink(nowhere_, symlink_to_nowhere_));
+ }
+
+ protected:
+ base::FilePath symlink_to_file_;
+ base::FilePath symlink_to_folder_;
+ base::FilePath symlink_to_nowhere_;
+};
+#endif // OS_POSIX
+
+#if defined(OS_CHROMEOS)
+// ChromeOS doesn't follow symbolic links in sandboxed filesystems. So all the
+// symbolic link tests should return PATH_NOT_FOUND.
+
+TEST_F(PlatformUtilPosixTest, OpenFileWithPosixSymlinksChromeOS) {
+ EXPECT_EQ(OPEN_FAILED_PATH_NOT_FOUND,
+ CallOpenItem(symlink_to_file_, OPEN_FILE));
+ EXPECT_EQ(OPEN_FAILED_PATH_NOT_FOUND,
+ CallOpenItem(symlink_to_folder_, OPEN_FILE));
+ EXPECT_EQ(OPEN_FAILED_PATH_NOT_FOUND,
+ CallOpenItem(symlink_to_nowhere_, OPEN_FILE));
+}
+
+TEST_F(PlatformUtilPosixTest, OpenFolderWithPosixSymlinksChromeOS) {
+ EXPECT_EQ(OPEN_FAILED_PATH_NOT_FOUND,
+ CallOpenItem(symlink_to_folder_, OPEN_FOLDER));
+ EXPECT_EQ(OPEN_FAILED_PATH_NOT_FOUND,
+ CallOpenItem(symlink_to_file_, OPEN_FOLDER));
+ EXPECT_EQ(OPEN_FAILED_PATH_NOT_FOUND,
+ CallOpenItem(symlink_to_nowhere_, OPEN_FOLDER));
+}
+
+TEST_F(PlatformUtilTest, OpenFileWithUnhandledFileType) {
+ base::FilePath unhandled_file =
+ directory_.path().AppendASCII("myfile.filetype");
+ ASSERT_EQ(3, base::WriteFile(unhandled_file, "cat", 3));
+ EXPECT_EQ(OPEN_FAILED_NO_HANLDER_FOR_FILE_TYPE,
+ CallOpenItem(unhandled_file, OPEN_FILE));
+}
+#endif // OS_CHROMEOS
+
+#if defined(OS_POSIX) && !defined(OS_CHROMEOS)
+// On all other Posix platforms, the symbolic link tests should work as
+// expected.
+
+TEST_F(PlatformUtilPosixTest, OpenFileWithPosixSymlinks) {
+ EXPECT_EQ(OPEN_SUCCEEDED, CallOpenItem(symlink_to_file_, OPEN_FILE));
+ EXPECT_EQ(OPEN_FAILED_INVALID_TYPE,
+ CallOpenItem(symlink_to_folder_, OPEN_FILE));
+ EXPECT_EQ(OPEN_FAILED_PATH_NOT_FOUND,
+ CallOpenItem(symlink_to_nowhere_, OPEN_FILE));
+}
+
+TEST_F(PlatformUtilPosixTest, OpenFolderWithPosixSymlinks) {
+ EXPECT_EQ(OPEN_SUCCEEDED, CallOpenItem(symlink_to_folder_, OPEN_FOLDER));
+ EXPECT_EQ(OPEN_FAILED_INVALID_TYPE,
+ CallOpenItem(symlink_to_file_, OPEN_FOLDER));
+ EXPECT_EQ(OPEN_FAILED_PATH_NOT_FOUND,
+ CallOpenItem(symlink_to_nowhere_, OPEN_FOLDER));
+}
+#endif // OS_POSIX && !OS_CHROMEOS
+
+} // namespace platform_util
diff --git a/chrome/browser/platform_util_win.cc b/chrome/browser/platform_util_win.cc
index 65aee57..a905b2f 100644
--- a/chrome/browser/platform_util_win.cc
+++ b/chrome/browser/platform_util_win.cc
@@ -12,6 +12,7 @@
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/files/file_path.h"
+#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/metrics/field_trial.h"
#include "base/strings/string_util.h"
@@ -21,6 +22,7 @@
#include "base/win/scoped_comptr.h"
#include "base/win/windows_version.h"
#include "chrome/browser/lifetime/application_lifetime.h"
+#include "chrome/browser/platform_util_internal.h"
#include "chrome/browser/ui/host_desktop.h"
#include "chrome/common/chrome_utility_messages.h"
#include "content/public/browser/browser_thread.h"
@@ -32,8 +34,12 @@
using content::BrowserThread;
+namespace platform_util {
+
namespace {
+// TODO(asanka): Move this to ui/base/win/shell.{h,cc} and invoke it from the
+// utility process.
void ShowItemInFolderOnFileThread(const base::FilePath& full_path) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
base::FilePath dir = full_path.DirName().AsEndingWithSeparator();
@@ -102,10 +108,9 @@ void ShowItemInFolderOnFileThread(const base::FilePath& full_path) {
if (hr == ERROR_FILE_NOT_FOUND) {
ShellExecute(NULL, L"open", dir.value().c_str(), NULL, NULL, SW_SHOW);
} else {
- LOG(WARNING) << " " << __FUNCTION__
- << "(): Can't open full_path = \""
+ LOG(WARNING) << " " << __FUNCTION__ << "(): Can't open full_path = \""
<< full_path.value() << "\""
- << " hr = " << logging::SystemErrorCodeToString(hr);
+ << " hr = " << logging::SystemErrorCodeToString(hr);
}
}
}
@@ -159,47 +164,64 @@ void OpenExternalOnFileThread(const GURL& url) {
}
}
-void OpenItemViaShellInUtilityProcess(const base::FilePath& full_path) {
+void OpenItemViaShellInUtilityProcess(const base::FilePath& full_path,
+ OpenItemType type) {
base::WeakPtr<content::UtilityProcessHost> utility_process_host(
content::UtilityProcessHost::Create(NULL, NULL)->AsWeakPtr());
utility_process_host->DisableSandbox();
- utility_process_host->Send(new ChromeUtilityMsg_OpenItemViaShell(full_path));
-}
-
-} // namespace
-
-namespace platform_util {
+ switch (type) {
+ case OPEN_FILE:
+ utility_process_host->Send(
+ new ChromeUtilityMsg_OpenFileViaShell(full_path));
+ return;
-void ShowItemInFolder(Profile* profile, const base::FilePath& full_path) {
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ case OPEN_FOLDER:
+ utility_process_host->Send(
+ new ChromeUtilityMsg_OpenFolderViaShell(full_path));
+ return;
+ }
+}
+void ActivateDesktopIfNecessary() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (chrome::GetActiveDesktop() == chrome::HOST_DESKTOP_TYPE_ASH)
chrome::ActivateDesktopHelper(chrome::ASH_KEEP_RUNNING);
+}
+
+} // namespace
+void ShowItemInFolder(Profile* profile, const base::FilePath& full_path) {
+ ActivateDesktopIfNecessary();
BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
base::Bind(&ShowItemInFolderOnFileThread, full_path));
}
-void OpenItem(Profile* profile, const base::FilePath& full_path) {
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+namespace internal {
- if (chrome::GetActiveDesktop() == chrome::HOST_DESKTOP_TYPE_ASH)
- chrome::ActivateDesktopHelper(chrome::ASH_KEEP_RUNNING);
+void PlatformOpenVerifiedItem(const base::FilePath& path, OpenItemType type) {
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(&ActivateDesktopIfNecessary));
if (base::FieldTrialList::FindFullName("IsolateShellOperations") ==
"Enabled") {
BrowserThread::PostTask(
- BrowserThread::IO,
- FROM_HERE,
- base::Bind(&OpenItemViaShellInUtilityProcess, full_path));
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&OpenItemViaShellInUtilityProcess, path, type));
} else {
- BrowserThread::PostTask(
- BrowserThread::FILE,
- FROM_HERE,
- base::Bind(base::IgnoreResult(&ui::win::OpenItemViaShell), full_path));
+ switch (type) {
+ case OPEN_FILE:
+ ui::win::OpenFileViaShell(path);
+ break;
+
+ case OPEN_FOLDER:
+ ui::win::OpenFolderViaShell(path);
+ break;
+ }
}
}
+} // namespace internal
+
void OpenExternal(Profile* profile, const GURL& url) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
diff --git a/chrome/browser/ui/ash/chrome_screenshot_grabber.cc b/chrome/browser/ui/ash/chrome_screenshot_grabber.cc
index 36b873a..9671d32 100644
--- a/chrome/browser/ui/ash/chrome_screenshot_grabber.cc
+++ b/chrome/browser/ui/ash/chrome_screenshot_grabber.cc
@@ -17,6 +17,7 @@
#include "chrome/browser/browser_process.h"
#include "chrome/browser/download/download_prefs.h"
#include "chrome/browser/notifications/notification_ui_manager.h"
+#include "chrome/browser/platform_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/common/pref_names.h"
@@ -114,11 +115,7 @@ class ScreenshotGrabberNotificationDelegate : public NotificationDelegate {
void Click() override {
if (!success_)
return;
-#if defined(OS_CHROMEOS)
- file_manager::util::ShowItemInFolder(profile_, screenshot_path_);
-#else
-// TODO(sschmitz): perhaps add similar action for Windows.
-#endif
+ platform_util::ShowItemInFolder(profile_, screenshot_path_);
}
void ButtonClick(int button_index) override {
DCHECK(success_ && button_index == 0);
diff --git a/chrome/browser/ui/webui/downloads_dom_handler.cc b/chrome/browser/ui/webui/downloads_dom_handler.cc
index 1a1dbc9..60bdcf5 100644
--- a/chrome/browser/ui/webui/downloads_dom_handler.cc
+++ b/chrome/browser/ui/webui/downloads_dom_handler.cc
@@ -522,7 +522,8 @@ void DownloadsDOMHandler::HandleOpenDownloadsFolder(
if (manager) {
platform_util::OpenItem(
Profile::FromBrowserContext(manager->GetBrowserContext()),
- DownloadPrefs::FromDownloadManager(manager)->DownloadPath());
+ DownloadPrefs::FromDownloadManager(manager)->DownloadPath(),
+ platform_util::OPEN_FOLDER, platform_util::OpenOperationCallback());
}
}
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index 0f1b79d..be4f0d3 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -521,6 +521,7 @@
'browser/platform_util.h',
'browser/platform_util_android.cc',
'browser/platform_util_chromeos.cc',
+ 'browser/platform_util_internal.h',
'browser/platform_util_mac.mm',
'browser/platform_util_win.cc',
'browser/precache/most_visited_urls_provider.cc',
@@ -777,6 +778,7 @@
],
# Everything but Android, iOS, and CrOS.
'chrome_browser_desktop_sources': [
+ 'browser/platform_util.cc',
'browser/profiles/avatar_menu_actions_desktop.cc',
'browser/profiles/avatar_menu_actions_desktop.h',
'browser/profiles/avatar_menu_desktop.cc',
diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi
index 360aa35..3977471 100644
--- a/chrome/chrome_tests_unit.gypi
+++ b/chrome/chrome_tests_unit.gypi
@@ -1434,6 +1434,7 @@
'browser/media_galleries/win/mtp_device_delegate_impl_win_unittest.cc',
'browser/media_galleries/win/mtp_device_object_enumerator_unittest.cc',
'browser/net/firefox_proxy_settings_unittest.cc',
+ 'browser/platform_util_unittest.cc',
'browser/power/process_power_collector_unittest.cc',
'browser/process_singleton_posix_unittest.cc',
'browser/profile_resetter/profile_resetter_unittest.cc',
diff --git a/chrome/common/chrome_utility_messages.h b/chrome/common/chrome_utility_messages.h
index 5f07459..1e50ef1 100644
--- a/chrome/common/chrome_utility_messages.h
+++ b/chrome/common/chrome_utility_messages.h
@@ -111,7 +111,12 @@ IPC_MESSAGE_CONTROL1(ChromeUtilityMsg_AnalyzeZipFileForDownloadProtection,
#endif
#if defined(OS_WIN)
-IPC_MESSAGE_CONTROL1(ChromeUtilityMsg_OpenItemViaShell,
+// Invokes ui::base::win::OpenFileViaShell from the utility process.
+IPC_MESSAGE_CONTROL1(ChromeUtilityMsg_OpenFileViaShell,
+ base::FilePath /* full_path */)
+
+// Invokes ui::base::win::OpenFolderViaShell from the utility process.
+IPC_MESSAGE_CONTROL1(ChromeUtilityMsg_OpenFolderViaShell,
base::FilePath /* full_path */)
// Instructs the utility process to invoke GetOpenFileName. |owner| is the
diff --git a/chrome/utility/shell_handler_win.cc b/chrome/utility/shell_handler_win.cc
index e7b3c09..96cce54 100644
--- a/chrome/utility/shell_handler_win.cc
+++ b/chrome/utility/shell_handler_win.cc
@@ -19,8 +19,10 @@ ShellHandler::~ShellHandler() {}
bool ShellHandler::OnMessageReceived(const IPC::Message& message) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(ShellHandler, message)
- IPC_MESSAGE_HANDLER(ChromeUtilityMsg_OpenItemViaShell,
- OnOpenItemViaShell)
+ IPC_MESSAGE_HANDLER(ChromeUtilityMsg_OpenFileViaShell,
+ OnOpenFileViaShell)
+ IPC_MESSAGE_HANDLER(ChromeUtilityMsg_OpenFolderViaShell,
+ OnOpenFolderViaShell)
IPC_MESSAGE_HANDLER(ChromeUtilityMsg_GetOpenFileName,
OnGetOpenFileName)
IPC_MESSAGE_HANDLER(ChromeUtilityMsg_GetSaveFileName,
@@ -30,8 +32,12 @@ bool ShellHandler::OnMessageReceived(const IPC::Message& message) {
return handled;
}
-void ShellHandler::OnOpenItemViaShell(const base::FilePath& full_path) {
- ui::win::OpenItemViaShell(full_path);
+void ShellHandler::OnOpenFileViaShell(const base::FilePath& full_path) {
+ ui::win::OpenFileViaShell(full_path);
+}
+
+void ShellHandler::OnOpenFolderViaShell(const base::FilePath& full_path) {
+ ui::win::OpenFolderViaShell(full_path);
}
void ShellHandler::OnGetOpenFileName(
diff --git a/chrome/utility/shell_handler_win.h b/chrome/utility/shell_handler_win.h
index fdbed1b..729f111 100644
--- a/chrome/utility/shell_handler_win.h
+++ b/chrome/utility/shell_handler_win.h
@@ -36,7 +36,8 @@ class ShellHandler : public UtilityMessageHandler {
virtual bool OnMessageReceived(const IPC::Message& message) override;
private:
- void OnOpenItemViaShell(const base::FilePath& full_path);
+ void OnOpenFileViaShell(const base::FilePath& full_path);
+ void OnOpenFolderViaShell(const base::FilePath& full_path);
void OnGetOpenFileName(
HWND owner,
diff --git a/ui/base/win/shell.cc b/ui/base/win/shell.cc
index 9e47f8c..2a5bd29 100644
--- a/ui/base/win/shell.cc
+++ b/ui/base/win/shell.cc
@@ -11,9 +11,11 @@
#include "base/command_line.h"
#include "base/debug/alias.h"
+#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/native_library.h"
#include "base/strings/string_util.h"
+#include "base/threading/thread_restrictions.h"
#include "base/win/metro.h"
#include "base/win/scoped_comptr.h"
#include "base/win/win_util.h"
@@ -25,15 +27,39 @@ namespace win {
namespace {
-// Show the Windows "Open With" dialog box to ask the user to pick an app to
-// open the file with.
-bool OpenItemWithExternalApp(const base::string16& full_path) {
- SHELLEXECUTEINFO sei = { sizeof(sei) };
- sei.fMask = SEE_MASK_FLAG_DDEWAIT;
+// Default ShellExecuteEx flags used with the "openas" verb.
+//
+// SEE_MASK_NOASYNC is specified so that ShellExecuteEx can be invoked from a
+// thread whose message loop may not wait around long enough for the
+// asynchronous tasks initiated by ShellExecuteEx to complete. Using this flag
+// causes ShellExecuteEx() to block until these tasks complete.
+const DWORD kDefaultOpenAsFlags = SEE_MASK_NOASYNC;
+
+// Default ShellExecuteEx flags used with the "explore", "open" or default verb.
+//
+// See kDefaultOpenFlags for description SEE_MASK_NOASYNC flag.
+// SEE_MASK_FLAG_NO_UI is used to suppress any error message boxes that might be
+// displayed if there is an error in opening the file. Failure in invoking the
+// "open" actions result in invocation of the "saveas" verb, making the error
+// dialog superfluous.
+const DWORD kDefaultOpenFlags = SEE_MASK_NOASYNC | SEE_MASK_FLAG_NO_UI;
+
+// Invokes ShellExecuteExW() with the given parameters.
+DWORD InvokeShellExecute(const base::string16 path,
+ const base::string16 working_directory,
+ const base::string16 args,
+ const base::string16 verb,
+ DWORD mask) {
+ base::ThreadRestrictions::AssertIOAllowed();
+ SHELLEXECUTEINFO sei = {sizeof(sei)};
+ sei.fMask = mask;
sei.nShow = SW_SHOWNORMAL;
- sei.lpVerb = L"openas";
- sei.lpFile = full_path.c_str();
- return (TRUE == ::ShellExecuteExW(&sei));
+ sei.lpVerb = (verb.empty() ? nullptr : verb.c_str());
+ sei.lpFile = path.c_str();
+ sei.lpDirectory =
+ (working_directory.empty() ? nullptr : working_directory.c_str());
+ sei.lpParameters = (args.empty() ? nullptr : args.c_str());
+ return ::ShellExecuteExW(&sei) ? ERROR_SUCCESS : ::GetLastError();
}
} // namespace
@@ -42,24 +68,32 @@ bool OpenAnyViaShell(const base::string16& full_path,
const base::string16& directory,
const base::string16& args,
DWORD mask) {
- SHELLEXECUTEINFO sei = { sizeof(sei) };
- sei.fMask = mask;
- sei.nShow = SW_SHOWNORMAL;
- sei.lpFile = full_path.c_str();
- sei.lpDirectory = directory.c_str();
- if (!args.empty())
- sei.lpParameters = args.c_str();
-
- if (::ShellExecuteExW(&sei))
+ DWORD open_result =
+ InvokeShellExecute(full_path, directory, args, base::string16(), mask);
+ if (open_result == ERROR_SUCCESS)
return true;
- if (::GetLastError() == ERROR_NO_ASSOCIATION)
- return OpenItemWithExternalApp(full_path);
+ // Show the Windows "Open With" dialog box to ask the user to pick an app to
+ // open the file with. Note that we are not forwarding |args| for the "openas"
+ // call since the target application is nolonger known at this point.
+ if (open_result == ERROR_NO_ASSOCIATION)
+ return InvokeShellExecute(full_path, directory, base::string16(), L"openas",
+ kDefaultOpenAsFlags) == ERROR_SUCCESS;
return false;
}
-bool OpenItemViaShell(const base::FilePath& full_path) {
+bool OpenFileViaShell(const base::FilePath& full_path) {
return OpenAnyViaShell(full_path.value(), full_path.DirName().value(),
- base::string16(), 0);
+ base::string16(), kDefaultOpenFlags);
+}
+
+bool OpenFolderViaShell(const base::FilePath& full_path) {
+ // The "explore" verb causes the folder at |full_path| to be displayed in a
+ // file browser. This will fail if |full_path| is not a directory. The
+ // resulting error does not cause UI due to the SEE_MASK_FLAG_NO_UI flag in
+ // kDefaultOpenFlags.
+ return InvokeShellExecute(full_path.value(), full_path.value(),
+ base::string16(), L"explore",
+ kDefaultOpenFlags) == ERROR_SUCCESS;
}
bool PreventWindowFromPinning(HWND hwnd) {
diff --git a/ui/base/win/shell.h b/ui/base/win/shell.h
index 3b64ce7..4031de6 100644
--- a/ui/base/win/shell.h
+++ b/ui/base/win/shell.h
@@ -17,16 +17,29 @@ class FilePath;
namespace ui {
namespace win {
-// Open or run a file via the Windows shell. In the event that there is no
-// default application registered for the file specified by 'full_path',
-// ask the user, via the Windows "Open With" dialog.
-// Returns 'true' on successful open, 'false' otherwise.
-UI_BASE_EXPORT bool OpenItemViaShell(const base::FilePath& full_path);
+// Open the folder at |full_path| via the Windows shell. Does nothing if
+// |full_path| is not a folder.
+//
+// Note: Must be called on a thread that allows blocking.
+UI_BASE_EXPORT bool OpenFolderViaShell(const base::FilePath& full_path);
+
+// Invokes the default verb on the file specified by |full_path| via the Windows
+// shell. Usually, the default verb is "open" unless specified otherwise for the
+// file type.
+//
+// In the event that there is no default application registered for the
+// specified file, asks the user via the Windows "Open With" dialog. Returns
+// |true| on success.
+//
+// Note: Must be called on a thread that allows blocking.
+UI_BASE_EXPORT bool OpenFileViaShell(const base::FilePath& full_path);
// Lower level function that allows opening of non-files like urls or GUIDs
// don't use it if one of the above will do. |mask| is a valid combination
-// of SEE_MASK_FLAG_XXX as stated in msdn. If there is no default application
-// registered for the item, it behaves the same as OpenItemViaShell.
+// of SEE_MASK_XXX as stated in MSDN. If there is no default application
+// registered for the item, it behaves the same as OpenFileViaShell.
+//
+// Note: Must be called on a thread that allows blocking.
UI_BASE_EXPORT bool OpenAnyViaShell(const base::string16& full_path,
const base::string16& directory,
const base::string16& args,