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