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