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