Home | History | Annotate | Download | only in browser
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/shell_integration_linux.h"
      6 
      7 #include <fcntl.h>
      8 #include <glib.h>
      9 #include <stdlib.h>
     10 #include <sys/stat.h>
     11 #include <sys/types.h>
     12 #include <unistd.h>
     13 
     14 #include <string>
     15 #include <vector>
     16 
     17 #include "base/base_paths.h"
     18 #include "base/command_line.h"
     19 #include "base/environment.h"
     20 #include "base/file_util.h"
     21 #include "base/files/file_enumerator.h"
     22 #include "base/files/file_path.h"
     23 #include "base/files/scoped_temp_dir.h"
     24 #include "base/i18n/file_util_icu.h"
     25 #include "base/memory/ref_counted_memory.h"
     26 #include "base/memory/scoped_ptr.h"
     27 #include "base/message_loop/message_loop.h"
     28 #include "base/path_service.h"
     29 #include "base/posix/eintr_wrapper.h"
     30 #include "base/process/kill.h"
     31 #include "base/process/launch.h"
     32 #include "base/strings/string_number_conversions.h"
     33 #include "base/strings/string_tokenizer.h"
     34 #include "base/strings/string_util.h"
     35 #include "base/strings/utf_string_conversions.h"
     36 #include "base/threading/thread.h"
     37 #include "base/threading/thread_restrictions.h"
     38 #include "build/build_config.h"
     39 #include "chrome/browser/web_applications/web_app.h"
     40 #include "chrome/common/chrome_constants.h"
     41 #include "content/public/browser/browser_thread.h"
     42 #include "ui/gfx/image/image_family.h"
     43 #include "url/gurl.h"
     44 
     45 using content::BrowserThread;
     46 
     47 namespace {
     48 
     49 // Helper to launch xdg scripts. We don't want them to ask any questions on the
     50 // terminal etc. The function returns true if the utility launches and exits
     51 // cleanly, in which case |exit_code| returns the utility's exit code.
     52 bool LaunchXdgUtility(const std::vector<std::string>& argv, int* exit_code) {
     53   // xdg-settings internally runs xdg-mime, which uses mv to move newly-created
     54   // files on top of originals after making changes to them. In the event that
     55   // the original files are owned by another user (e.g. root, which can happen
     56   // if they are updated within sudo), mv will prompt the user to confirm if
     57   // standard input is a terminal (otherwise it just does it). So make sure it's
     58   // not, to avoid locking everything up waiting for mv.
     59   *exit_code = EXIT_FAILURE;
     60   int devnull = open("/dev/null", O_RDONLY);
     61   if (devnull < 0)
     62     return false;
     63   base::FileHandleMappingVector no_stdin;
     64   no_stdin.push_back(std::make_pair(devnull, STDIN_FILENO));
     65 
     66   base::ProcessHandle handle;
     67   base::LaunchOptions options;
     68   options.fds_to_remap = &no_stdin;
     69   if (!base::LaunchProcess(argv, options, &handle)) {
     70     close(devnull);
     71     return false;
     72   }
     73   close(devnull);
     74 
     75   return base::WaitForExitCode(handle, exit_code);
     76 }
     77 
     78 std::string CreateShortcutIcon(
     79     const ShellIntegration::ShortcutInfo& shortcut_info,
     80     const base::FilePath& shortcut_filename) {
     81   if (shortcut_info.favicon.empty())
     82     return std::string();
     83 
     84   // TODO(phajdan.jr): Report errors from this function, possibly as infobars.
     85   base::ScopedTempDir temp_dir;
     86   if (!temp_dir.CreateUniqueTempDir())
     87     return std::string();
     88 
     89   base::FilePath temp_file_path = temp_dir.path().Append(
     90       shortcut_filename.ReplaceExtension("png"));
     91   std::string icon_name = temp_file_path.BaseName().RemoveExtension().value();
     92 
     93   for (gfx::ImageFamily::const_iterator it = shortcut_info.favicon.begin();
     94        it != shortcut_info.favicon.end(); ++it) {
     95     int width = it->Width();
     96     scoped_refptr<base::RefCountedMemory> png_data = it->As1xPNGBytes();
     97     if (png_data->size() == 0) {
     98       // If the bitmap could not be encoded to PNG format, skip it.
     99       LOG(WARNING) << "Could not encode icon " << icon_name << ".png at size "
    100                    << width << ".";
    101       continue;
    102     }
    103     int bytes_written = file_util::WriteFile(temp_file_path,
    104         reinterpret_cast<const char*>(png_data->front()), png_data->size());
    105 
    106     if (bytes_written != static_cast<int>(png_data->size()))
    107       return std::string();
    108 
    109     std::vector<std::string> argv;
    110     argv.push_back("xdg-icon-resource");
    111     argv.push_back("install");
    112 
    113     // Always install in user mode, even if someone runs the browser as root
    114     // (people do that).
    115     argv.push_back("--mode");
    116     argv.push_back("user");
    117 
    118     argv.push_back("--size");
    119     argv.push_back(base::IntToString(width));
    120 
    121     argv.push_back(temp_file_path.value());
    122     argv.push_back(icon_name);
    123     int exit_code;
    124     if (!LaunchXdgUtility(argv, &exit_code) || exit_code) {
    125       LOG(WARNING) << "Could not install icon " << icon_name << ".png at size "
    126                    << width << ".";
    127     }
    128   }
    129   return icon_name;
    130 }
    131 
    132 bool CreateShortcutOnDesktop(const base::FilePath& shortcut_filename,
    133                              const std::string& contents) {
    134   // Make sure that we will later call openat in a secure way.
    135   DCHECK_EQ(shortcut_filename.BaseName().value(), shortcut_filename.value());
    136 
    137   base::FilePath desktop_path;
    138   if (!PathService::Get(base::DIR_USER_DESKTOP, &desktop_path))
    139     return false;
    140 
    141   int desktop_fd = open(desktop_path.value().c_str(), O_RDONLY | O_DIRECTORY);
    142   if (desktop_fd < 0)
    143     return false;
    144 
    145   int fd = openat(desktop_fd, shortcut_filename.value().c_str(),
    146                   O_CREAT | O_EXCL | O_WRONLY,
    147                   S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
    148   if (fd < 0) {
    149     if (HANDLE_EINTR(close(desktop_fd)) < 0)
    150       PLOG(ERROR) << "close";
    151     return false;
    152   }
    153 
    154   ssize_t bytes_written = file_util::WriteFileDescriptor(fd, contents.data(),
    155                                                          contents.length());
    156   if (HANDLE_EINTR(close(fd)) < 0)
    157     PLOG(ERROR) << "close";
    158 
    159   if (bytes_written != static_cast<ssize_t>(contents.length())) {
    160     // Delete the file. No shortuct is better than corrupted one. Use unlinkat
    161     // to make sure we're deleting the file in the directory we think we are.
    162     // Even if an attacker manager to put something other at
    163     // |shortcut_filename| we'll just undo his action.
    164     unlinkat(desktop_fd, shortcut_filename.value().c_str(), 0);
    165   }
    166 
    167   if (HANDLE_EINTR(close(desktop_fd)) < 0)
    168     PLOG(ERROR) << "close";
    169 
    170   return true;
    171 }
    172 
    173 void DeleteShortcutOnDesktop(const base::FilePath& shortcut_filename) {
    174   base::FilePath desktop_path;
    175   if (PathService::Get(base::DIR_USER_DESKTOP, &desktop_path))
    176     base::DeleteFile(desktop_path.Append(shortcut_filename), false);
    177 }
    178 
    179 // Creates a shortcut with |shortcut_filename| and |contents| in the system
    180 // applications menu. If |directory_filename| is non-empty, creates a sub-menu
    181 // with |directory_filename| and |directory_contents|, and stores the shortcut
    182 // under the sub-menu.
    183 bool CreateShortcutInApplicationsMenu(const base::FilePath& shortcut_filename,
    184                                       const std::string& contents,
    185                                       const base::FilePath& directory_filename,
    186                                       const std::string& directory_contents) {
    187   base::ScopedTempDir temp_dir;
    188   if (!temp_dir.CreateUniqueTempDir())
    189     return false;
    190 
    191   base::FilePath temp_directory_path;
    192   if (!directory_filename.empty()) {
    193     temp_directory_path = temp_dir.path().Append(directory_filename);
    194 
    195     int bytes_written = file_util::WriteFile(temp_directory_path,
    196                                              directory_contents.data(),
    197                                              directory_contents.length());
    198 
    199     if (bytes_written != static_cast<int>(directory_contents.length()))
    200       return false;
    201   }
    202 
    203   base::FilePath temp_file_path = temp_dir.path().Append(shortcut_filename);
    204 
    205   int bytes_written = file_util::WriteFile(temp_file_path, contents.data(),
    206                                            contents.length());
    207 
    208   if (bytes_written != static_cast<int>(contents.length()))
    209     return false;
    210 
    211   std::vector<std::string> argv;
    212   argv.push_back("xdg-desktop-menu");
    213   argv.push_back("install");
    214 
    215   // Always install in user mode, even if someone runs the browser as root
    216   // (people do that).
    217   argv.push_back("--mode");
    218   argv.push_back("user");
    219 
    220   // If provided, install the shortcut file inside the given directory.
    221   if (!directory_filename.empty())
    222     argv.push_back(temp_directory_path.value());
    223   argv.push_back(temp_file_path.value());
    224   int exit_code;
    225   LaunchXdgUtility(argv, &exit_code);
    226   return exit_code == 0;
    227 }
    228 
    229 void DeleteShortcutInApplicationsMenu(
    230     const base::FilePath& shortcut_filename,
    231     const base::FilePath& directory_filename) {
    232   std::vector<std::string> argv;
    233   argv.push_back("xdg-desktop-menu");
    234   argv.push_back("uninstall");
    235 
    236   // Uninstall in user mode, to match the install.
    237   argv.push_back("--mode");
    238   argv.push_back("user");
    239 
    240   // The file does not need to exist anywhere - xdg-desktop-menu will uninstall
    241   // items from the menu with a matching name.
    242   // If |directory_filename| is supplied, this will also remove the item from
    243   // the directory, and remove the directory if it is empty.
    244   if (!directory_filename.empty())
    245     argv.push_back(directory_filename.value());
    246   argv.push_back(shortcut_filename.value());
    247   int exit_code;
    248   LaunchXdgUtility(argv, &exit_code);
    249 }
    250 
    251 // Quote a string such that it appears as one verbatim argument for the Exec
    252 // key in a desktop file.
    253 std::string QuoteArgForDesktopFileExec(const std::string& arg) {
    254   // http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html
    255 
    256   // Quoting is only necessary if the argument has a reserved character.
    257   if (arg.find_first_of(" \t\n\"'\\><~|&;$*?#()`") == std::string::npos)
    258     return arg;  // No quoting necessary.
    259 
    260   std::string quoted = "\"";
    261   for (size_t i = 0; i < arg.size(); ++i) {
    262     // Note that the set of backslashed characters is smaller than the
    263     // set of reserved characters.
    264     switch (arg[i]) {
    265       case '"':
    266       case '`':
    267       case '$':
    268       case '\\':
    269         quoted += '\\';
    270         break;
    271     }
    272     quoted += arg[i];
    273   }
    274   quoted += '"';
    275 
    276   return quoted;
    277 }
    278 
    279 const char kDesktopEntry[] = "Desktop Entry";
    280 
    281 const char kXdgOpenShebang[] = "#!/usr/bin/env xdg-open";
    282 
    283 const char kXdgSettings[] = "xdg-settings";
    284 const char kXdgSettingsDefaultBrowser[] = "default-web-browser";
    285 const char kXdgSettingsDefaultSchemeHandler[] = "default-url-scheme-handler";
    286 
    287 const char kDirectoryFilename[] = "chrome-apps.directory";
    288 
    289 }  // namespace
    290 
    291 namespace {
    292 
    293 // Utility function to get the path to the version of a script shipped with
    294 // Chrome. |script| gives the name of the script. |chrome_version| returns the
    295 // path to the Chrome version of the script, and the return value of the
    296 // function is true if the function is successful and the Chrome version is
    297 // not the script found on the PATH.
    298 bool GetChromeVersionOfScript(const std::string& script,
    299                                std::string* chrome_version) {
    300   // Get the path to the Chrome version.
    301   base::FilePath chrome_dir;
    302   if (!PathService::Get(base::DIR_EXE, &chrome_dir))
    303     return false;
    304 
    305   base::FilePath chrome_version_path = chrome_dir.Append(script);
    306   *chrome_version = chrome_version_path.value();
    307 
    308   // Check if this is different to the one on path.
    309   std::vector<std::string> argv;
    310   argv.push_back("which");
    311   argv.push_back(script);
    312   std::string path_version;
    313   if (base::GetAppOutput(CommandLine(argv), &path_version)) {
    314     // Remove trailing newline
    315     path_version.erase(path_version.length() - 1, 1);
    316     base::FilePath path_version_path(path_version);
    317     return (chrome_version_path != path_version_path);
    318   }
    319   return false;
    320 }
    321 
    322 // Value returned by xdg-settings if it can't understand our request.
    323 const int EXIT_XDG_SETTINGS_SYNTAX_ERROR = 1;
    324 
    325 // We delegate the difficulty of setting the default browser and default url
    326 // scheme handler in Linux desktop environments to an xdg utility, xdg-settings.
    327 
    328 // When calling this script we first try to use the script on PATH. If that
    329 // fails we then try to use the script that we have included. This gives
    330 // scripts on the system priority over ours, as distribution vendors may have
    331 // tweaked the script, but still allows our copy to be used if the script on the
    332 // system fails, as the system copy may be missing capabilities of the Chrome
    333 // copy.
    334 
    335 // If |protocol| is empty this function sets Chrome as the default browser,
    336 // otherwise it sets Chrome as the default handler application for |protocol|.
    337 bool SetDefaultWebClient(const std::string& protocol) {
    338 #if defined(OS_CHROMEOS)
    339   return true;
    340 #else
    341   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    342 
    343   scoped_ptr<base::Environment> env(base::Environment::Create());
    344 
    345   std::vector<std::string> argv;
    346   argv.push_back(kXdgSettings);
    347   argv.push_back("set");
    348   if (protocol.empty()) {
    349     argv.push_back(kXdgSettingsDefaultBrowser);
    350   } else {
    351     argv.push_back(kXdgSettingsDefaultSchemeHandler);
    352     argv.push_back(protocol);
    353   }
    354   argv.push_back(ShellIntegrationLinux::GetDesktopName(env.get()));
    355 
    356   int exit_code;
    357   bool ran_ok = LaunchXdgUtility(argv, &exit_code);
    358   if (ran_ok && exit_code == EXIT_XDG_SETTINGS_SYNTAX_ERROR) {
    359     if (GetChromeVersionOfScript(kXdgSettings, &argv[0])) {
    360       ran_ok = LaunchXdgUtility(argv, &exit_code);
    361     }
    362   }
    363 
    364   return ran_ok && exit_code == EXIT_SUCCESS;
    365 #endif
    366 }
    367 
    368 // If |protocol| is empty this function checks if Chrome is the default browser,
    369 // otherwise it checks if Chrome is the default handler application for
    370 // |protocol|.
    371 ShellIntegration::DefaultWebClientState GetIsDefaultWebClient(
    372     const std::string& protocol) {
    373 #if defined(OS_CHROMEOS)
    374   return ShellIntegration::IS_DEFAULT;
    375 #else
    376   base::ThreadRestrictions::AssertIOAllowed();
    377 
    378   scoped_ptr<base::Environment> env(base::Environment::Create());
    379 
    380   std::vector<std::string> argv;
    381   argv.push_back(kXdgSettings);
    382   argv.push_back("check");
    383   if (protocol.empty()) {
    384     argv.push_back(kXdgSettingsDefaultBrowser);
    385   } else {
    386     argv.push_back(kXdgSettingsDefaultSchemeHandler);
    387     argv.push_back(protocol);
    388   }
    389   argv.push_back(ShellIntegrationLinux::GetDesktopName(env.get()));
    390 
    391   std::string reply;
    392   int success_code;
    393   bool ran_ok = base::GetAppOutputWithExitCode(CommandLine(argv), &reply,
    394                                                &success_code);
    395   if (ran_ok && success_code == EXIT_XDG_SETTINGS_SYNTAX_ERROR) {
    396     if (GetChromeVersionOfScript(kXdgSettings, &argv[0])) {
    397       ran_ok = base::GetAppOutputWithExitCode(CommandLine(argv), &reply,
    398                                               &success_code);
    399     }
    400   }
    401 
    402   if (!ran_ok || success_code != EXIT_SUCCESS) {
    403     // xdg-settings failed: we can't determine or set the default browser.
    404     return ShellIntegration::UNKNOWN_DEFAULT;
    405   }
    406 
    407   // Allow any reply that starts with "yes".
    408   return (reply.find("yes") == 0) ? ShellIntegration::IS_DEFAULT :
    409                                     ShellIntegration::NOT_DEFAULT;
    410 #endif
    411 }
    412 
    413 // Get the value of NoDisplay from the [Desktop Entry] section of a .desktop
    414 // file, given in |shortcut_contents|. If the key is not found, returns false.
    415 bool GetNoDisplayFromDesktopFile(const std::string& shortcut_contents) {
    416   // An empty file causes a crash with glib <= 2.32, so special case here.
    417   if (shortcut_contents.empty())
    418     return false;
    419 
    420   GKeyFile* key_file = g_key_file_new();
    421   GError* err = NULL;
    422   if (!g_key_file_load_from_data(key_file, shortcut_contents.c_str(),
    423                                  shortcut_contents.size(), G_KEY_FILE_NONE,
    424                                  &err)) {
    425     LOG(WARNING) << "Unable to read desktop file template: " << err->message;
    426     g_error_free(err);
    427     g_key_file_free(key_file);
    428     return false;
    429   }
    430 
    431   bool nodisplay = false;
    432   char* nodisplay_c_string = g_key_file_get_string(key_file, kDesktopEntry,
    433                                                    "NoDisplay", &err);
    434   if (nodisplay_c_string) {
    435     if (!g_strcmp0(nodisplay_c_string, "true"))
    436       nodisplay = true;
    437     g_free(nodisplay_c_string);
    438   }
    439 
    440   g_key_file_free(key_file);
    441   return nodisplay;
    442 }
    443 
    444 // Gets the path to the Chrome executable or wrapper script.
    445 // Returns an empty path if the executable path could not be found.
    446 base::FilePath GetChromeExePath() {
    447   // Try to get the name of the wrapper script that launched Chrome.
    448   scoped_ptr<base::Environment> environment(base::Environment::Create());
    449   std::string wrapper_script;
    450   if (environment->GetVar("CHROME_WRAPPER", &wrapper_script)) {
    451     return base::FilePath(wrapper_script);
    452   }
    453 
    454   // Just return the name of the executable path for Chrome.
    455   base::FilePath chrome_exe_path;
    456   PathService::Get(base::FILE_EXE, &chrome_exe_path);
    457   return chrome_exe_path;
    458 }
    459 
    460 } // namespace
    461 
    462 // static
    463 ShellIntegration::DefaultWebClientSetPermission
    464     ShellIntegration::CanSetAsDefaultBrowser() {
    465   return SET_DEFAULT_UNATTENDED;
    466 }
    467 
    468 // static
    469 bool ShellIntegration::SetAsDefaultBrowser() {
    470   return SetDefaultWebClient(std::string());
    471 }
    472 
    473 // static
    474 bool ShellIntegration::SetAsDefaultProtocolClient(const std::string& protocol) {
    475   return SetDefaultWebClient(protocol);
    476 }
    477 
    478 // static
    479 ShellIntegration::DefaultWebClientState ShellIntegration::GetDefaultBrowser() {
    480   return GetIsDefaultWebClient(std::string());
    481 }
    482 
    483 // static
    484 std::string ShellIntegration::GetApplicationForProtocol(const GURL& url) {
    485   return std::string("xdg-open");
    486 }
    487 
    488 // static
    489 ShellIntegration::DefaultWebClientState
    490 ShellIntegration::IsDefaultProtocolClient(const std::string& protocol) {
    491   return GetIsDefaultWebClient(protocol);
    492 }
    493 
    494 // static
    495 bool ShellIntegration::IsFirefoxDefaultBrowser() {
    496   std::vector<std::string> argv;
    497   argv.push_back(kXdgSettings);
    498   argv.push_back("get");
    499   argv.push_back(kXdgSettingsDefaultBrowser);
    500 
    501   std::string browser;
    502   // We don't care about the return value here.
    503   base::GetAppOutput(CommandLine(argv), &browser);
    504   return browser.find("irefox") != std::string::npos;
    505 }
    506 
    507 namespace ShellIntegrationLinux {
    508 
    509 bool GetDataWriteLocation(base::Environment* env, base::FilePath* search_path) {
    510   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    511 
    512   std::string xdg_data_home;
    513   std::string home;
    514   if (env->GetVar("XDG_DATA_HOME", &xdg_data_home) && !xdg_data_home.empty()) {
    515     *search_path = base::FilePath(xdg_data_home);
    516     return true;
    517   } else if (env->GetVar("HOME", &home) && !home.empty()) {
    518     *search_path = base::FilePath(home).Append(".local").Append("share");
    519     return true;
    520   }
    521   return false;
    522 }
    523 
    524 std::vector<base::FilePath> GetDataSearchLocations(base::Environment* env) {
    525   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    526 
    527   std::vector<base::FilePath> search_paths;
    528 
    529   base::FilePath write_location;
    530   if (GetDataWriteLocation(env, &write_location))
    531     search_paths.push_back(write_location);
    532 
    533   std::string xdg_data_dirs;
    534   if (env->GetVar("XDG_DATA_DIRS", &xdg_data_dirs) && !xdg_data_dirs.empty()) {
    535     base::StringTokenizer tokenizer(xdg_data_dirs, ":");
    536     while (tokenizer.GetNext()) {
    537       base::FilePath data_dir(tokenizer.token());
    538       search_paths.push_back(data_dir);
    539     }
    540   } else {
    541     search_paths.push_back(base::FilePath("/usr/local/share"));
    542     search_paths.push_back(base::FilePath("/usr/share"));
    543   }
    544 
    545   return search_paths;
    546 }
    547 
    548 std::string GetDesktopName(base::Environment* env) {
    549 #if defined(GOOGLE_CHROME_BUILD)
    550   return "google-chrome.desktop";
    551 #else  // CHROMIUM_BUILD
    552   // Allow $CHROME_DESKTOP to override the built-in value, so that development
    553   // versions can set themselves as the default without interfering with
    554   // non-official, packaged versions using the built-in value.
    555   std::string name;
    556   if (env->GetVar("CHROME_DESKTOP", &name) && !name.empty())
    557     return name;
    558   return "chromium-browser.desktop";
    559 #endif
    560 }
    561 
    562 std::string GetIconName() {
    563 #if defined(GOOGLE_CHROME_BUILD)
    564   return "google-chrome";
    565 #else  // CHROMIUM_BUILD
    566   return "chromium-browser";
    567 #endif
    568 }
    569 
    570 ShellIntegration::ShortcutLocations GetExistingShortcutLocations(
    571     base::Environment* env,
    572     const base::FilePath& profile_path,
    573     const std::string& extension_id) {
    574   base::FilePath desktop_path;
    575   // If Get returns false, just leave desktop_path empty.
    576   PathService::Get(base::DIR_USER_DESKTOP, &desktop_path);
    577   return GetExistingShortcutLocations(env, profile_path, extension_id,
    578                                              desktop_path);
    579 }
    580 
    581 ShellIntegration::ShortcutLocations GetExistingShortcutLocations(
    582     base::Environment* env,
    583     const base::FilePath& profile_path,
    584     const std::string& extension_id,
    585     const base::FilePath& desktop_path) {
    586   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    587 
    588   base::FilePath shortcut_filename = GetExtensionShortcutFilename(
    589       profile_path, extension_id);
    590   DCHECK(!shortcut_filename.empty());
    591   ShellIntegration::ShortcutLocations locations;
    592 
    593   // Determine whether there is a shortcut on desktop.
    594   if (!desktop_path.empty()) {
    595     locations.on_desktop =
    596         base::PathExists(desktop_path.Append(shortcut_filename));
    597   }
    598 
    599   // Determine whether there is a shortcut in the applications directory.
    600   std::string shortcut_contents;
    601   if (GetExistingShortcutContents(env, shortcut_filename, &shortcut_contents)) {
    602     // Whether this counts as "hidden" or "in_applications_menu" depends on
    603     // whether it contains NoDisplay=true.
    604     if (GetNoDisplayFromDesktopFile(shortcut_contents))
    605       locations.hidden = true;
    606     else
    607       locations.in_applications_menu = true;
    608   }
    609 
    610   return locations;
    611 }
    612 
    613 bool GetExistingShortcutContents(base::Environment* env,
    614                                  const base::FilePath& desktop_filename,
    615                                  std::string* output) {
    616   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    617 
    618   std::vector<base::FilePath> search_paths = GetDataSearchLocations(env);
    619 
    620   for (std::vector<base::FilePath>::const_iterator i = search_paths.begin();
    621        i != search_paths.end(); ++i) {
    622     base::FilePath path = i->Append("applications").Append(desktop_filename);
    623     VLOG(1) << "Looking for desktop file in " << path.value();
    624     if (base::PathExists(path)) {
    625       VLOG(1) << "Found desktop file at " << path.value();
    626       return file_util::ReadFileToString(path, output);
    627     }
    628   }
    629 
    630   return false;
    631 }
    632 
    633 base::FilePath GetWebShortcutFilename(const GURL& url) {
    634   // Use a prefix, because xdg-desktop-menu requires it.
    635   std::string filename =
    636       std::string(chrome::kBrowserProcessExecutableName) + "-" + url.spec();
    637   file_util::ReplaceIllegalCharactersInPath(&filename, '_');
    638 
    639   base::FilePath desktop_path;
    640   if (!PathService::Get(base::DIR_USER_DESKTOP, &desktop_path))
    641     return base::FilePath();
    642 
    643   base::FilePath filepath = desktop_path.Append(filename);
    644   base::FilePath alternative_filepath(filepath.value() + ".desktop");
    645   for (size_t i = 1; i < 100; ++i) {
    646     if (base::PathExists(base::FilePath(alternative_filepath))) {
    647       alternative_filepath = base::FilePath(
    648           filepath.value() + "_" + base::IntToString(i) + ".desktop");
    649     } else {
    650       return base::FilePath(alternative_filepath).BaseName();
    651     }
    652   }
    653 
    654   return base::FilePath();
    655 }
    656 
    657 base::FilePath GetExtensionShortcutFilename(const base::FilePath& profile_path,
    658                                             const std::string& extension_id) {
    659   DCHECK(!extension_id.empty());
    660 
    661   // Use a prefix, because xdg-desktop-menu requires it.
    662   std::string filename(chrome::kBrowserProcessExecutableName);
    663   filename.append("-")
    664       .append(extension_id)
    665       .append("-")
    666       .append(profile_path.BaseName().value());
    667   file_util::ReplaceIllegalCharactersInPath(&filename, '_');
    668   // Spaces in filenames break xdg-desktop-menu
    669   // (see https://bugs.freedesktop.org/show_bug.cgi?id=66605).
    670   ReplaceChars(filename, " ", "_", &filename);
    671   return base::FilePath(filename.append(".desktop"));
    672 }
    673 
    674 std::vector<base::FilePath> GetExistingProfileShortcutFilenames(
    675     const base::FilePath& profile_path,
    676     const base::FilePath& directory) {
    677   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    678   // Use a prefix, because xdg-desktop-menu requires it.
    679   std::string prefix(chrome::kBrowserProcessExecutableName);
    680   prefix.append("-");
    681   std::string suffix("-");
    682   suffix.append(profile_path.BaseName().value());
    683   file_util::ReplaceIllegalCharactersInPath(&suffix, '_');
    684   // Spaces in filenames break xdg-desktop-menu
    685   // (see https://bugs.freedesktop.org/show_bug.cgi?id=66605).
    686   ReplaceChars(suffix, " ", "_", &suffix);
    687   std::string glob = prefix + "*" + suffix + ".desktop";
    688 
    689   base::FileEnumerator files(directory, false, base::FileEnumerator::FILES,
    690                              glob);
    691   base::FilePath shortcut_file = files.Next();
    692   std::vector<base::FilePath> shortcut_paths;
    693   while (!shortcut_file.empty()) {
    694     shortcut_paths.push_back(shortcut_file.BaseName());
    695     shortcut_file = files.Next();
    696   }
    697   return shortcut_paths;
    698 }
    699 
    700 std::string GetDesktopFileContents(
    701     const base::FilePath& chrome_exe_path,
    702     const std::string& app_name,
    703     const GURL& url,
    704     const std::string& extension_id,
    705     const base::FilePath& extension_path,
    706     const string16& title,
    707     const std::string& icon_name,
    708     const base::FilePath& profile_path,
    709     bool no_display) {
    710   // Although not required by the spec, Nautilus on Ubuntu Karmic creates its
    711   // launchers with an xdg-open shebang. Follow that convention.
    712   std::string output_buffer = std::string(kXdgOpenShebang) + "\n";
    713 
    714   // See http://standards.freedesktop.org/desktop-entry-spec/latest/
    715   GKeyFile* key_file = g_key_file_new();
    716 
    717   // Set keys with fixed values.
    718   g_key_file_set_string(key_file, kDesktopEntry, "Version", "1.0");
    719   g_key_file_set_string(key_file, kDesktopEntry, "Terminal", "false");
    720   g_key_file_set_string(key_file, kDesktopEntry, "Type", "Application");
    721 
    722   // Set the "Name" key.
    723   std::string final_title = UTF16ToUTF8(title);
    724   // Make sure no endline characters can slip in and possibly introduce
    725   // additional lines (like Exec, which makes it a security risk). Also
    726   // use the URL as a default when the title is empty.
    727   if (final_title.empty() ||
    728       final_title.find("\n") != std::string::npos ||
    729       final_title.find("\r") != std::string::npos) {
    730     final_title = url.spec();
    731   }
    732   g_key_file_set_string(key_file, kDesktopEntry, "Name", final_title.c_str());
    733 
    734   // Set the "Exec" key.
    735   std::string final_path = chrome_exe_path.value();
    736   CommandLine cmd_line(CommandLine::NO_PROGRAM);
    737   cmd_line = ShellIntegration::CommandLineArgsForLauncher(
    738       url, extension_id, profile_path);
    739   const CommandLine::SwitchMap& switch_map = cmd_line.GetSwitches();
    740   for (CommandLine::SwitchMap::const_iterator i = switch_map.begin();
    741        i != switch_map.end(); ++i) {
    742     if (i->second.empty()) {
    743       final_path += " --" + i->first;
    744     } else {
    745       final_path += " " + QuoteArgForDesktopFileExec("--" + i->first +
    746                                                      "=" + i->second);
    747     }
    748   }
    749 
    750   g_key_file_set_string(key_file, kDesktopEntry, "Exec", final_path.c_str());
    751 
    752   // Set the "Icon" key.
    753   if (!icon_name.empty()) {
    754     g_key_file_set_string(key_file, kDesktopEntry, "Icon", icon_name.c_str());
    755   } else {
    756     g_key_file_set_string(key_file, kDesktopEntry, "Icon",
    757                           GetIconName().c_str());
    758   }
    759 
    760   // Set the "NoDisplay" key.
    761   if (no_display)
    762     g_key_file_set_string(key_file, kDesktopEntry, "NoDisplay", "true");
    763 
    764 #if defined(TOOLKIT_GTK)
    765   std::string wmclass = web_app::GetWMClassFromAppName(app_name);
    766   g_key_file_set_string(key_file, kDesktopEntry, "StartupWMClass",
    767                         wmclass.c_str());
    768 #endif
    769 
    770   gsize length = 0;
    771   gchar* data_dump = g_key_file_to_data(key_file, &length, NULL);
    772   if (data_dump) {
    773     // If strlen(data_dump[0]) == 0, this check will fail.
    774     if (data_dump[0] == '\n') {
    775       // Older versions of glib produce a leading newline. If this is the case,
    776       // remove it to avoid double-newline after the shebang.
    777       output_buffer += (data_dump + 1);
    778     } else {
    779       output_buffer += data_dump;
    780     }
    781     g_free(data_dump);
    782   }
    783 
    784   g_key_file_free(key_file);
    785   return output_buffer;
    786 }
    787 
    788 std::string GetDirectoryFileContents(const string16& title,
    789                                      const std::string& icon_name) {
    790   // See http://standards.freedesktop.org/desktop-entry-spec/latest/
    791   GKeyFile* key_file = g_key_file_new();
    792 
    793   g_key_file_set_string(key_file, kDesktopEntry, "Version", "1.0");
    794   g_key_file_set_string(key_file, kDesktopEntry, "Type", "Directory");
    795   std::string final_title = UTF16ToUTF8(title);
    796   g_key_file_set_string(key_file, kDesktopEntry, "Name", final_title.c_str());
    797   if (!icon_name.empty()) {
    798     g_key_file_set_string(key_file, kDesktopEntry, "Icon", icon_name.c_str());
    799   } else {
    800     g_key_file_set_string(key_file, kDesktopEntry, "Icon",
    801                           GetIconName().c_str());
    802   }
    803 
    804   gsize length = 0;
    805   gchar* data_dump = g_key_file_to_data(key_file, &length, NULL);
    806   std::string output_buffer;
    807   if (data_dump) {
    808     // If strlen(data_dump[0]) == 0, this check will fail.
    809     if (data_dump[0] == '\n') {
    810       // Older versions of glib produce a leading newline. If this is the case,
    811       // remove it to avoid double-newline after the shebang.
    812       output_buffer += (data_dump + 1);
    813     } else {
    814       output_buffer += data_dump;
    815     }
    816     g_free(data_dump);
    817   }
    818 
    819   g_key_file_free(key_file);
    820   return output_buffer;
    821 }
    822 
    823 bool CreateDesktopShortcut(
    824     const ShellIntegration::ShortcutInfo& shortcut_info,
    825     const ShellIntegration::ShortcutLocations& creation_locations) {
    826   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    827 
    828   base::FilePath shortcut_filename;
    829   if (!shortcut_info.extension_id.empty()) {
    830     shortcut_filename = GetExtensionShortcutFilename(
    831         shortcut_info.profile_path, shortcut_info.extension_id);
    832     // For extensions we do not want duplicate shortcuts. So, delete any that
    833     // already exist and replace them.
    834     if (creation_locations.on_desktop)
    835       DeleteShortcutOnDesktop(shortcut_filename);
    836     if (creation_locations.in_applications_menu || creation_locations.hidden)
    837       DeleteShortcutInApplicationsMenu(shortcut_filename, base::FilePath());
    838   } else {
    839     shortcut_filename = GetWebShortcutFilename(shortcut_info.url);
    840   }
    841   if (shortcut_filename.empty())
    842     return false;
    843 
    844   std::string icon_name = CreateShortcutIcon(shortcut_info, shortcut_filename);
    845 
    846   std::string app_name =
    847       web_app::GenerateApplicationNameFromInfo(shortcut_info);
    848 
    849   bool success = true;
    850 
    851   base::FilePath chrome_exe_path = GetChromeExePath();
    852   if (chrome_exe_path.empty()) {
    853     LOG(WARNING) << "Could not get executable path.";
    854     return false;
    855   }
    856 
    857   if (creation_locations.on_desktop) {
    858     std::string contents = ShellIntegrationLinux::GetDesktopFileContents(
    859         chrome_exe_path,
    860         app_name,
    861         shortcut_info.url,
    862         shortcut_info.extension_id,
    863         shortcut_info.extension_path,
    864         shortcut_info.title,
    865         icon_name,
    866         shortcut_info.profile_path,
    867         false);
    868     success = CreateShortcutOnDesktop(shortcut_filename, contents);
    869   }
    870 
    871   // The 'in_applications_menu' and 'hidden' locations are actually the same
    872   // place ('applications').
    873   if (creation_locations.in_applications_menu || creation_locations.hidden) {
    874     base::FilePath directory_filename;
    875     std::string directory_contents;
    876     if (!creation_locations.applications_menu_subdir.empty()) {
    877       directory_filename = base::FilePath(kDirectoryFilename);
    878       directory_contents = ShellIntegrationLinux::GetDirectoryFileContents(
    879           creation_locations.applications_menu_subdir, "");
    880     }
    881     // Set NoDisplay=true if hidden but not in_applications_menu. This will hide
    882     // the application from user-facing menus.
    883     std::string contents = ShellIntegrationLinux::GetDesktopFileContents(
    884         chrome_exe_path,
    885         app_name,
    886         shortcut_info.url,
    887         shortcut_info.extension_id,
    888         shortcut_info.extension_path,
    889         shortcut_info.title,
    890         icon_name,
    891         shortcut_info.profile_path,
    892         !creation_locations.in_applications_menu);
    893     success = CreateShortcutInApplicationsMenu(
    894         shortcut_filename, contents, directory_filename, directory_contents) &&
    895         success;
    896   }
    897 
    898   return success;
    899 }
    900 
    901 void DeleteDesktopShortcuts(const base::FilePath& profile_path,
    902                             const std::string& extension_id) {
    903   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    904 
    905   base::FilePath shortcut_filename = GetExtensionShortcutFilename(
    906       profile_path, extension_id);
    907   DCHECK(!shortcut_filename.empty());
    908 
    909   DeleteShortcutOnDesktop(shortcut_filename);
    910   // Delete shortcuts from |kDirectoryFilename|.
    911   // Note that it is possible that shortcuts were not created in the Chrome Apps
    912   // directory (depending on the value of |applications_menu_subdir| when they
    913   // were created). It doesn't matter: this will still delete the shortcut even
    914   // if it isn't in the directory.
    915   DeleteShortcutInApplicationsMenu(shortcut_filename,
    916                                    base::FilePath(kDirectoryFilename));
    917 }
    918 
    919 void DeleteAllDesktopShortcuts(const base::FilePath& profile_path) {
    920   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    921 
    922   scoped_ptr<base::Environment> env(base::Environment::Create());
    923 
    924   // Delete shortcuts from Desktop.
    925   base::FilePath desktop_path;
    926   if (PathService::Get(base::DIR_USER_DESKTOP, &desktop_path)) {
    927     std::vector<base::FilePath> shortcut_filenames_desktop =
    928         GetExistingProfileShortcutFilenames(profile_path, desktop_path);
    929     for (std::vector<base::FilePath>::const_iterator it =
    930          shortcut_filenames_desktop.begin();
    931          it != shortcut_filenames_desktop.end(); ++it) {
    932       DeleteShortcutOnDesktop(*it);
    933     }
    934   }
    935 
    936   // Delete shortcuts from |kDirectoryFilename|.
    937   base::FilePath applications_menu;
    938   if (GetDataWriteLocation(env.get(), &applications_menu)) {
    939     applications_menu = applications_menu.AppendASCII("applications");
    940     std::vector<base::FilePath> shortcut_filenames_app_menu =
    941         GetExistingProfileShortcutFilenames(profile_path, applications_menu);
    942     for (std::vector<base::FilePath>::const_iterator it =
    943          shortcut_filenames_app_menu.begin();
    944          it != shortcut_filenames_app_menu.end(); ++it) {
    945       DeleteShortcutInApplicationsMenu(*it,
    946                                        base::FilePath(kDirectoryFilename));
    947     }
    948   }
    949 }
    950 
    951 }  // namespace ShellIntegrationLinux
    952