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