| // Copyright (c) 2012 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/command_line.h" | 
 | #include "base/files/file_util.h" | 
 | #include "base/process/kill.h" | 
 | #include "base/process/launch.h" | 
 | #include "base/strings/string_util.h" | 
 | #include "base/strings/utf_string_conversions.h" | 
 | #include "base/task/post_task.h" | 
 | #include "base/version.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 { | 
 |  | 
 | const char kNautilusKey[] = "nautilus.desktop"; | 
 | const char kNautilusKeyExtended[] = "nautilus-folder-handler.desktop"; | 
 | const char kNautilusCmd[] = "nautilus"; | 
 | const char kSupportedNautilusVersion[] = "3.0.2"; | 
 |  | 
 | void RunCommand(const std::string& command, | 
 |                 const base::FilePath& working_directory, | 
 |                 const std::string& arg) { | 
 |   std::vector<std::string> argv; | 
 |   argv.push_back(command); | 
 |   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 | 
 |   // it that we definitely don't have a terminal available and that it should | 
 |   // bring up a new terminal if necessary.  See "man mailcap". | 
 |   options.environ["MM_NOTTTY"] = "1"; | 
 |  | 
 |   // In Google Chrome, we do not let GNOME's bug-buddy intercept our crashes. | 
 |   // However, we do not want this environment variable to propagate to external | 
 |   // applications. See http://crbug.com/24120 | 
 |   char* disable_gnome_bug_buddy = getenv("GNOME_DISABLE_CRASH_DIALOG"); | 
 |   if (disable_gnome_bug_buddy && | 
 |       disable_gnome_bug_buddy == std::string("SET_BY_GOOGLE_CHROME")) | 
 |     options.environ["GNOME_DISABLE_CRASH_DIALOG"] = std::string(); | 
 |  | 
 |   base::Process process = base::LaunchProcess(argv, options); | 
 |   if (process.IsValid()) | 
 |     base::EnsureProcessGetsReaped(std::move(process)); | 
 | } | 
 |  | 
 | void XDGOpen(const base::FilePath& working_directory, const std::string& path) { | 
 |   RunCommand("xdg-open", working_directory, path); | 
 | } | 
 |  | 
 | void XDGEmail(const std::string& email) { | 
 |   RunCommand("xdg-email", base::FilePath(), email); | 
 | } | 
 |  | 
 | void ShowFileInNautilus(const base::FilePath& working_directory, | 
 |                         const std::string& path) { | 
 |   RunCommand(kNautilusCmd, working_directory, path); | 
 | } | 
 |  | 
 | std::string GetNautilusVersion() { | 
 |   std::string output; | 
 |   std::string found_version; | 
 |  | 
 |   base::CommandLine nautilus_cl((base::FilePath(kNautilusCmd))); | 
 |   nautilus_cl.AppendArg("--version"); | 
 |  | 
 |   if (base::GetAppOutputAndError(nautilus_cl, &output)) { | 
 |     // It is assumed that "nautilus --version" returns something like | 
 |     // "GNOME nautilus 3.14.2". First, find the position of the first char of | 
 |     // "nautilus " and skip the whole string to get the position of | 
 |     // version in the |output| string. | 
 |     size_t nautilus_position = output.find("nautilus "); | 
 |     size_t version_position = nautilus_position + strlen("nautilus "); | 
 |     if (nautilus_position != std::string::npos) { | 
 |       found_version = output.substr(version_position); | 
 |       base::TrimWhitespaceASCII(found_version, | 
 |                                 base::TRIM_TRAILING, | 
 |                                 &found_version); | 
 |     } | 
 |   } | 
 |   return found_version; | 
 | } | 
 |  | 
 | bool CheckNautilusIsDefault() { | 
 |   std::string file_browser; | 
 |  | 
 |   base::CommandLine xdg_mime(base::FilePath("xdg-mime")); | 
 |   xdg_mime.AppendArg("query"); | 
 |   xdg_mime.AppendArg("default"); | 
 |   xdg_mime.AppendArg("inode/directory"); | 
 |  | 
 |   bool success = base::GetAppOutputAndError(xdg_mime, &file_browser); | 
 |   base::TrimWhitespaceASCII(file_browser, | 
 |                             base::TRIM_TRAILING, | 
 |                             &file_browser); | 
 |  | 
 |   if (!success || | 
 |       (file_browser != kNautilusKey && file_browser != kNautilusKeyExtended)) | 
 |     return false; | 
 |  | 
 |   const base::Version supported_version(kSupportedNautilusVersion); | 
 |   DCHECK(supported_version.IsValid()); | 
 |   const base::Version current_version(GetNautilusVersion()); | 
 |   return current_version.IsValid() && current_version >= supported_version; | 
 | } | 
 |  | 
 | void ShowItem(Profile* profile, | 
 |               const base::FilePath& full_path, | 
 |               bool use_nautilus_file_browser) { | 
 |   if (use_nautilus_file_browser) { | 
 |     OpenItem(profile, full_path, SHOW_ITEM_IN_FOLDER, OpenOperationCallback()); | 
 |   } else { | 
 |     // TODO(estade): It would be nice to be able to select the file in other | 
 |     // file managers, but that probably requires extending xdg-open. | 
 |     // For now just show the folder for non-Nautilus users. | 
 |     OpenItem(profile, full_path.DirName(), OPEN_FOLDER, | 
 |              OpenOperationCallback()); | 
 |   } | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | 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; | 
 |     case SHOW_ITEM_IN_FOLDER: | 
 |       ShowFileInNautilus(path.DirName(), path.value()); | 
 |       break; | 
 |   } | 
 | } | 
 |  | 
 | }  // namespace internal | 
 |  | 
 | void ShowItemInFolder(Profile* profile, const base::FilePath& full_path) { | 
 |   DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |   base::PostTaskWithTraitsAndReplyWithResult( | 
 |       FROM_HERE, | 
 |       {base::WithBaseSyncPrimitives(), base::MayBlock(), | 
 |        base::TaskPriority::USER_BLOCKING}, | 
 |       base::BindOnce(&CheckNautilusIsDefault), | 
 |       base::BindOnce(&ShowItem, profile, full_path)); | 
 | } | 
 |  | 
 | void OpenExternal(Profile* profile, const GURL& url) { | 
 |   DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |   if (url.SchemeIs("mailto")) | 
 |     XDGEmail(url.spec()); | 
 |   else | 
 |     XDGOpen(base::FilePath(), url.spec()); | 
 | } | 
 |  | 
 | }  // namespace platform_util |