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