shell_integration_linux.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
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/eintr_wrapper.h"
20#include "base/environment.h"
21#include "base/file_path.h"
22#include "base/file_util.h"
23#include "base/i18n/file_util_icu.h"
24#include "base/message_loop.h"
25#include "base/path_service.h"
26#include "base/process_util.h"
27#include "base/scoped_temp_dir.h"
28#include "base/string_number_conversions.h"
29#include "base/string_tokenizer.h"
30#include "base/threading/thread.h"
31#include "base/utf_string_conversions.h"
32#include "build/build_config.h"
33#include "chrome/browser/web_applications/web_app.h"
34#include "chrome/common/chrome_constants.h"
35#include "content/public/browser/browser_thread.h"
36#include "googleurl/src/gurl.h"
37#include "ui/gfx/codec/png_codec.h"
38
39using content::BrowserThread;
40
41namespace {
42
43// Helper to launch xdg scripts. We don't want them to ask any questions on the
44// terminal etc. The function returns true if the utility launches and exits
45// cleanly, in which case |exit_code| returns the utility's exit code.
46bool LaunchXdgUtility(const std::vector<std::string>& argv, int* exit_code) {
47  // xdg-settings internally runs xdg-mime, which uses mv to move newly-created
48  // files on top of originals after making changes to them. In the event that
49  // the original files are owned by another user (e.g. root, which can happen
50  // if they are updated within sudo), mv will prompt the user to confirm if
51  // standard input is a terminal (otherwise it just does it). So make sure it's
52  // not, to avoid locking everything up waiting for mv.
53  *exit_code = EXIT_FAILURE;
54  int devnull = open("/dev/null", O_RDONLY);
55  if (devnull < 0)
56    return false;
57  base::FileHandleMappingVector no_stdin;
58  no_stdin.push_back(std::make_pair(devnull, STDIN_FILENO));
59
60  base::ProcessHandle handle;
61  base::LaunchOptions options;
62  options.fds_to_remap = &no_stdin;
63  if (!base::LaunchProcess(argv, options, &handle)) {
64    close(devnull);
65    return false;
66  }
67  close(devnull);
68
69  return base::WaitForExitCode(handle, exit_code);
70}
71
72std::string CreateShortcutIcon(
73    const ShellIntegration::ShortcutInfo& shortcut_info,
74    const FilePath& shortcut_filename) {
75  if (shortcut_info.favicon.IsEmpty())
76    return std::string();
77
78  // TODO(phajdan.jr): Report errors from this function, possibly as infobars.
79  ScopedTempDir temp_dir;
80  if (!temp_dir.CreateUniqueTempDir())
81    return std::string();
82
83  FilePath temp_file_path = temp_dir.path().Append(
84      shortcut_filename.ReplaceExtension("png"));
85
86  std::vector<unsigned char> png_data;
87  const SkBitmap* bitmap = shortcut_info.favicon.ToSkBitmap();
88  gfx::PNGCodec::EncodeBGRASkBitmap(*bitmap, false, &png_data);
89  int bytes_written = file_util::WriteFile(temp_file_path,
90      reinterpret_cast<char*>(png_data.data()), png_data.size());
91
92  if (bytes_written != static_cast<int>(png_data.size()))
93    return std::string();
94
95  std::vector<std::string> argv;
96  argv.push_back("xdg-icon-resource");
97  argv.push_back("install");
98
99  // Always install in user mode, even if someone runs the browser as root
100  // (people do that).
101  argv.push_back("--mode");
102  argv.push_back("user");
103
104  argv.push_back("--size");
105  argv.push_back(base::IntToString(bitmap->width()));
106
107  argv.push_back(temp_file_path.value());
108  std::string icon_name = temp_file_path.BaseName().RemoveExtension().value();
109  argv.push_back(icon_name);
110  int exit_code;
111  LaunchXdgUtility(argv, &exit_code);
112  return icon_name;
113}
114
115bool CreateShortcutOnDesktop(const FilePath& shortcut_filename,
116                             const std::string& contents) {
117  // Make sure that we will later call openat in a secure way.
118  DCHECK_EQ(shortcut_filename.BaseName().value(), shortcut_filename.value());
119
120  FilePath desktop_path;
121  if (!PathService::Get(base::DIR_USER_DESKTOP, &desktop_path))
122    return false;
123
124  int desktop_fd = open(desktop_path.value().c_str(), O_RDONLY | O_DIRECTORY);
125  if (desktop_fd < 0)
126    return false;
127
128  int fd = openat(desktop_fd, shortcut_filename.value().c_str(),
129                  O_CREAT | O_EXCL | O_WRONLY,
130                  S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
131  if (fd < 0) {
132    if (HANDLE_EINTR(close(desktop_fd)) < 0)
133      PLOG(ERROR) << "close";
134    return false;
135  }
136
137  ssize_t bytes_written = file_util::WriteFileDescriptor(fd, contents.data(),
138                                                         contents.length());
139  if (HANDLE_EINTR(close(fd)) < 0)
140    PLOG(ERROR) << "close";
141
142  if (bytes_written != static_cast<ssize_t>(contents.length())) {
143    // Delete the file. No shortuct is better than corrupted one. Use unlinkat
144    // to make sure we're deleting the file in the directory we think we are.
145    // Even if an attacker manager to put something other at
146    // |shortcut_filename| we'll just undo his action.
147    unlinkat(desktop_fd, shortcut_filename.value().c_str(), 0);
148  }
149
150  if (HANDLE_EINTR(close(desktop_fd)) < 0)
151    PLOG(ERROR) << "close";
152
153  return true;
154}
155
156void DeleteShortcutOnDesktop(const FilePath& shortcut_filename) {
157  FilePath desktop_path;
158  if (PathService::Get(base::DIR_USER_DESKTOP, &desktop_path))
159    file_util::Delete(desktop_path.Append(shortcut_filename), false);
160}
161
162bool CreateShortcutInApplicationsMenu(const FilePath& shortcut_filename,
163                                      const std::string& contents) {
164  ScopedTempDir temp_dir;
165  if (!temp_dir.CreateUniqueTempDir())
166    return false;
167
168  FilePath temp_file_path = temp_dir.path().Append(shortcut_filename);
169
170  int bytes_written = file_util::WriteFile(temp_file_path, contents.data(),
171                                           contents.length());
172
173  if (bytes_written != static_cast<int>(contents.length()))
174    return false;
175
176  std::vector<std::string> argv;
177  argv.push_back("xdg-desktop-menu");
178  argv.push_back("install");
179
180  // Always install in user mode, even if someone runs the browser as root
181  // (people do that).
182  argv.push_back("--mode");
183  argv.push_back("user");
184
185  argv.push_back(temp_file_path.value());
186  int exit_code;
187  LaunchXdgUtility(argv, &exit_code);
188  return exit_code == 0;
189}
190
191void DeleteShortcutInApplicationsMenu(const FilePath& shortcut_filename) {
192  std::vector<std::string> argv;
193  argv.push_back("xdg-desktop-menu");
194  argv.push_back("uninstall");
195
196  // Uninstall in user mode, to match the install.
197  argv.push_back("--mode");
198  argv.push_back("user");
199
200  // The file does not need to exist anywhere - xdg-desktop-menu will uninstall
201  // items from the menu with a matching name.
202  argv.push_back(shortcut_filename.value());
203  int exit_code;
204  LaunchXdgUtility(argv, &exit_code);
205}
206
207// Quote a string such that it appears as one verbatim argument for the Exec
208// key in a desktop file.
209std::string QuoteArgForDesktopFileExec(const std::string& arg) {
210  // http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html
211
212  // Quoting is only necessary if the argument has a reserved character.
213  if (arg.find_first_of(" \t\n\"'\\><~|&;$*?#()`") == std::string::npos)
214    return arg;  // No quoting necessary.
215
216  std::string quoted = "\"";
217  for (size_t i = 0; i < arg.size(); ++i) {
218    // Note that the set of backslashed characters is smaller than the
219    // set of reserved characters.
220    switch (arg[i]) {
221      case '"':
222      case '`':
223      case '$':
224      case '\\':
225        quoted += '\\';
226        break;
227    }
228    quoted += arg[i];
229  }
230  quoted += '"';
231
232  return quoted;
233}
234
235// Remove keys from the [Desktop Entry] that would be wrong if copied verbatim
236// into the new .desktop file.
237const char* kDesktopKeysToDelete[] = {
238  "GenericName",
239  "Comment",
240  "MimeType",
241  "X-Ayatana-Desktop-Shortcuts",
242  "StartupWMClass",
243  NULL
244};
245
246const char kDesktopEntry[] = "Desktop Entry";
247
248const char kXdgOpenShebang[] = "#!/usr/bin/env xdg-open";
249
250const char kXdgSettings[] = "xdg-settings";
251const char kXdgSettingsDefaultBrowser[] = "default-web-browser";
252const char kXdgSettingsDefaultSchemeHandler[] = "default-url-scheme-handler";
253
254}  // namespace
255
256namespace {
257
258// Utility function to get the path to the version of a script shipped with
259// Chrome. |script| gives the name of the script. |chrome_version| returns the
260// path to the Chrome version of the script, and the return value of the
261// function is true if the function is successful and the Chrome version is
262// not the script found on the PATH.
263bool GetChromeVersionOfScript(const std::string& script,
264                               std::string* chrome_version) {
265  // Get the path to the Chrome version.
266  FilePath chrome_dir;
267  if (!PathService::Get(base::DIR_EXE, &chrome_dir))
268    return false;
269
270  FilePath chrome_version_path = chrome_dir.Append(script);
271  *chrome_version = chrome_version_path.value();
272
273  // Check if this is different to the one on path.
274  std::vector<std::string> argv;
275  argv.push_back("which");
276  argv.push_back(script);
277  std::string path_version;
278  if (base::GetAppOutput(CommandLine(argv), &path_version)) {
279    // Remove trailing newline
280    path_version.erase(path_version.length() - 1, 1);
281    FilePath path_version_path(path_version);
282    return (chrome_version_path != path_version_path);
283  }
284  return false;
285}
286
287// Value returned by xdg-settings if it can't understand our request.
288const int EXIT_XDG_SETTINGS_SYNTAX_ERROR = 1;
289
290// We delegate the difficulty of setting the default browser and default url
291// scheme handler in Linux desktop environments to an xdg utility, xdg-settings.
292
293// When calling this script we first try to use the script on PATH. If that
294// fails we then try to use the script that we have included. This gives
295// scripts on the system priority over ours, as distribution vendors may have
296// tweaked the script, but still allows our copy to be used if the script on the
297// system fails, as the system copy may be missing capabilities of the Chrome
298// copy.
299
300// If |protocol| is empty this function sets Chrome as the default browser,
301// otherwise it sets Chrome as the default handler application for |protocol|.
302bool SetDefaultWebClient(const std::string& protocol) {
303#if defined(OS_CHROMEOS)
304  return true;
305#else
306  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
307
308  scoped_ptr<base::Environment> env(base::Environment::Create());
309
310  std::vector<std::string> argv;
311  argv.push_back(kXdgSettings);
312  argv.push_back("set");
313  if (protocol.empty()) {
314    argv.push_back(kXdgSettingsDefaultBrowser);
315  } else {
316    argv.push_back(kXdgSettingsDefaultSchemeHandler);
317    argv.push_back(protocol);
318  }
319  argv.push_back(ShellIntegrationLinux::GetDesktopName(env.get()));
320
321  int exit_code;
322  bool ran_ok = LaunchXdgUtility(argv, &exit_code);
323  if (ran_ok && exit_code == EXIT_XDG_SETTINGS_SYNTAX_ERROR) {
324    if (GetChromeVersionOfScript(kXdgSettings, &argv[0])) {
325      ran_ok = LaunchXdgUtility(argv, &exit_code);
326    }
327  }
328
329  return ran_ok && exit_code == EXIT_SUCCESS;
330#endif
331}
332
333// If |protocol| is empty this function checks if Chrome is the default browser,
334// otherwise it checks if Chrome is the default handler application for
335// |protocol|.
336ShellIntegration::DefaultWebClientState GetIsDefaultWebClient(
337    const std::string& protocol) {
338#if defined(OS_CHROMEOS)
339  return ShellIntegration::IS_DEFAULT_WEB_CLIENT;
340#else
341  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
342
343  scoped_ptr<base::Environment> env(base::Environment::Create());
344
345  std::vector<std::string> argv;
346  argv.push_back(kXdgSettings);
347  argv.push_back("check");
348  if (protocol.empty()) {
349    argv.push_back(kXdgSettingsDefaultBrowser);
350  } else {
351    argv.push_back(kXdgSettingsDefaultSchemeHandler);
352    argv.push_back(protocol);
353  }
354  argv.push_back(ShellIntegrationLinux::GetDesktopName(env.get()));
355
356  std::string reply;
357  int success_code;
358  bool ran_ok = base::GetAppOutputWithExitCode(CommandLine(argv), &reply,
359                                               &success_code);
360  if (ran_ok && success_code == EXIT_XDG_SETTINGS_SYNTAX_ERROR) {
361    if (GetChromeVersionOfScript(kXdgSettings, &argv[0])) {
362      ran_ok = base::GetAppOutputWithExitCode(CommandLine(argv), &reply,
363                                              &success_code);
364    }
365  }
366
367  if (!ran_ok || success_code != EXIT_SUCCESS) {
368    // xdg-settings failed: we can't determine or set the default browser.
369    return ShellIntegration::UNKNOWN_DEFAULT_WEB_CLIENT;
370  }
371
372  // Allow any reply that starts with "yes".
373  return (reply.find("yes") == 0) ? ShellIntegration::IS_DEFAULT_WEB_CLIENT :
374                                    ShellIntegration::NOT_DEFAULT_WEB_CLIENT;
375#endif
376}
377
378} // namespace
379
380// static
381ShellIntegration::DefaultWebClientSetPermission
382    ShellIntegration::CanSetAsDefaultBrowser() {
383  return SET_DEFAULT_UNATTENDED;
384}
385
386// static
387bool ShellIntegration::SetAsDefaultBrowser() {
388  return SetDefaultWebClient("");
389}
390
391// static
392bool ShellIntegration::SetAsDefaultProtocolClient(const std::string& protocol) {
393  return SetDefaultWebClient(protocol);
394}
395
396// static
397ShellIntegration::DefaultWebClientState ShellIntegration::IsDefaultBrowser() {
398  return GetIsDefaultWebClient("");
399}
400
401// static
402ShellIntegration::DefaultWebClientState
403ShellIntegration::IsDefaultProtocolClient(const std::string& protocol) {
404  return GetIsDefaultWebClient(protocol);
405}
406
407// static
408bool ShellIntegration::IsFirefoxDefaultBrowser() {
409  std::vector<std::string> argv;
410  argv.push_back(kXdgSettings);
411  argv.push_back("get");
412  argv.push_back(kXdgSettingsDefaultBrowser);
413
414  std::string browser;
415  // We don't care about the return value here.
416  base::GetAppOutput(CommandLine(argv), &browser);
417  return browser.find("irefox") != std::string::npos;
418}
419
420namespace ShellIntegrationLinux {
421
422std::string GetDesktopName(base::Environment* env) {
423#if defined(GOOGLE_CHROME_BUILD)
424  return "google-chrome.desktop";
425#else  // CHROMIUM_BUILD
426  // Allow $CHROME_DESKTOP to override the built-in value, so that development
427  // versions can set themselves as the default without interfering with
428  // non-official, packaged versions using the built-in value.
429  std::string name;
430  if (env->GetVar("CHROME_DESKTOP", &name) && !name.empty())
431    return name;
432  return "chromium-browser.desktop";
433#endif
434}
435
436bool GetDesktopShortcutTemplate(base::Environment* env,
437                                std::string* output) {
438  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
439
440  std::vector<FilePath> search_paths;
441
442  std::string xdg_data_home;
443  if (env->GetVar("XDG_DATA_HOME", &xdg_data_home) &&
444      !xdg_data_home.empty()) {
445    search_paths.push_back(FilePath(xdg_data_home));
446  }
447
448  std::string xdg_data_dirs;
449  if (env->GetVar("XDG_DATA_DIRS", &xdg_data_dirs) &&
450      !xdg_data_dirs.empty()) {
451    StringTokenizer tokenizer(xdg_data_dirs, ":");
452    while (tokenizer.GetNext()) {
453      FilePath data_dir(tokenizer.token());
454      search_paths.push_back(data_dir);
455      search_paths.push_back(data_dir.Append("applications"));
456    }
457  }
458
459  // Add some fallback paths for systems which don't have XDG_DATA_DIRS or have
460  // it incomplete.
461  search_paths.push_back(FilePath("/usr/share/applications"));
462  search_paths.push_back(FilePath("/usr/local/share/applications"));
463
464  std::string template_filename(GetDesktopName(env));
465  for (std::vector<FilePath>::const_iterator i = search_paths.begin();
466       i != search_paths.end(); ++i) {
467    FilePath path = i->Append(template_filename);
468    VLOG(1) << "Looking for desktop file template in " << path.value();
469    if (file_util::PathExists(path)) {
470      VLOG(1) << "Found desktop file template at " << path.value();
471      return file_util::ReadFileToString(path, output);
472    }
473  }
474
475  LOG(ERROR) << "Could not find desktop file template.";
476  return false;
477}
478
479FilePath GetWebShortcutFilename(const GURL& url) {
480  // Use a prefix, because xdg-desktop-menu requires it.
481  std::string filename =
482      std::string(chrome::kBrowserProcessExecutableName) + "-" + url.spec();
483  file_util::ReplaceIllegalCharactersInPath(&filename, '_');
484
485  FilePath desktop_path;
486  if (!PathService::Get(base::DIR_USER_DESKTOP, &desktop_path))
487    return FilePath();
488
489  FilePath filepath = desktop_path.Append(filename);
490  FilePath alternative_filepath(filepath.value() + ".desktop");
491  for (size_t i = 1; i < 100; ++i) {
492    if (file_util::PathExists(FilePath(alternative_filepath))) {
493      alternative_filepath = FilePath(
494          filepath.value() + "_" + base::IntToString(i) + ".desktop");
495    } else {
496      return FilePath(alternative_filepath).BaseName();
497    }
498  }
499
500  return FilePath();
501}
502
503FilePath GetExtensionShortcutFilename(const FilePath& profile_path,
504                                      const std::string& extension_id) {
505  DCHECK(!extension_id.empty());
506
507  // Use a prefix, because xdg-desktop-menu requires it.
508  std::string filename(chrome::kBrowserProcessExecutableName);
509  filename.append("-")
510      .append(extension_id)
511      .append("-")
512      .append(profile_path.BaseName().value());
513  file_util::ReplaceIllegalCharactersInPath(&filename, '_');
514  return FilePath(filename.append(".desktop"));
515}
516
517std::string GetDesktopFileContents(
518    const std::string& template_contents,
519    const std::string& app_name,
520    const GURL& url,
521    const std::string& extension_id,
522    const FilePath& extension_path,
523    const string16& title,
524    const std::string& icon_name,
525    const FilePath& profile_path) {
526  if (template_contents.empty())
527    return std::string(kXdgOpenShebang) + "\n";
528
529  // See http://standards.freedesktop.org/desktop-entry-spec/latest/
530  // http://developer.gnome.org/glib/unstable/glib-Key-value-file-parser.html
531  GKeyFile* key_file = g_key_file_new();
532  GError* err = NULL;
533  // Loading the data will strip translations and comments from the desktop
534  // file (which we want to do!)
535  if (!g_key_file_load_from_data(
536          key_file,
537          template_contents.c_str(),
538          template_contents.size(),
539          G_KEY_FILE_NONE,
540          &err)) {
541    NOTREACHED() << "Unable to read desktop file template:" << err->message;
542    g_error_free(err);
543    return std::string(kXdgOpenShebang) + "\n";
544  }
545
546  // Remove all sections except for the Desktop Entry
547  gsize length = 0;
548  gchar** groups = g_key_file_get_groups(key_file, &length);
549  for (gsize i = 0; i < length; ++i) {
550    if (strcmp(groups[i], kDesktopEntry) != 0) {
551      g_key_file_remove_group(key_file, groups[i], NULL);
552    }
553  }
554  g_strfreev(groups);
555
556  // Remove keys that we won't need.
557  for (const char** current_key = kDesktopKeysToDelete; *current_key;
558       ++current_key) {
559    g_key_file_remove_key(key_file, kDesktopEntry, *current_key, NULL);
560  }
561
562  // Set the "Name" key.
563  std::string final_title = UTF16ToUTF8(title);
564  // Make sure no endline characters can slip in and possibly introduce
565  // additional lines (like Exec, which makes it a security risk). Also
566  // use the URL as a default when the title is empty.
567  if (final_title.empty() ||
568      final_title.find("\n") != std::string::npos ||
569      final_title.find("\r") != std::string::npos) {
570    final_title = url.spec();
571  }
572  g_key_file_set_string(key_file, kDesktopEntry, "Name", final_title.c_str());
573
574  // Set the "Exec" key.
575  char* exec_c_string = g_key_file_get_string(key_file, kDesktopEntry, "Exec",
576                                              NULL);
577  if (exec_c_string) {
578    std::string exec_string(exec_c_string);
579    g_free(exec_c_string);
580    StringTokenizer exec_tokenizer(exec_string, " ");
581
582    std::string final_path;
583    while (exec_tokenizer.GetNext() && exec_tokenizer.token() != "%U") {
584      if (!final_path.empty())
585        final_path += " ";
586      final_path += exec_tokenizer.token();
587    }
588    CommandLine cmd_line(CommandLine::NO_PROGRAM);
589    cmd_line = ShellIntegration::CommandLineArgsForLauncher(
590        url, extension_id, profile_path);
591    const CommandLine::SwitchMap& switch_map = cmd_line.GetSwitches();
592    for (CommandLine::SwitchMap::const_iterator i = switch_map.begin();
593         i != switch_map.end(); ++i) {
594      if (i->second.empty()) {
595        final_path += " --" + i->first;
596      } else {
597        final_path += " " + QuoteArgForDesktopFileExec("--" + i->first +
598                                                       "=" + i->second);
599      }
600    }
601
602    g_key_file_set_string(key_file, kDesktopEntry, "Exec", final_path.c_str());
603  }
604
605  // Set the "Icon" key.
606  if (!icon_name.empty())
607    g_key_file_set_string(key_file, kDesktopEntry, "Icon", icon_name.c_str());
608
609#if defined(TOOLKIT_GTK)
610  std::string wmclass = web_app::GetWMClassFromAppName(app_name);
611  g_key_file_set_string(key_file, kDesktopEntry, "StartupWMClass",
612                        wmclass.c_str());
613#endif
614
615  // Although not required by the spec, Nautilus on Ubuntu Karmic creates its
616  // launchers with an xdg-open shebang. Follow that convention.
617  std::string output_buffer = kXdgOpenShebang;
618  length = 0;
619  gchar* data_dump = g_key_file_to_data(key_file, &length, NULL);
620  if (data_dump) {
621    output_buffer += data_dump;
622    g_free(data_dump);
623  }
624
625  g_key_file_free(key_file);
626  return output_buffer;
627}
628
629bool CreateDesktopShortcut(
630    const ShellIntegration::ShortcutInfo& shortcut_info,
631    const std::string& shortcut_template) {
632  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
633
634  FilePath shortcut_filename;
635  if (!shortcut_info.extension_id.empty()) {
636    shortcut_filename = GetExtensionShortcutFilename(
637        shortcut_info.profile_path, shortcut_info.extension_id);
638    // For extensions we do not want duplicate shortcuts. So, delete any that
639    // already exist and replace them.
640    if (shortcut_info.create_on_desktop)
641      DeleteShortcutOnDesktop(shortcut_filename);
642    if (shortcut_info.create_in_applications_menu)
643      DeleteShortcutInApplicationsMenu(shortcut_filename);
644  } else {
645    shortcut_filename = GetWebShortcutFilename(shortcut_info.url);
646  }
647  if (shortcut_filename.empty())
648    return false;
649
650  std::string icon_name = CreateShortcutIcon(shortcut_info, shortcut_filename);
651
652  std::string app_name =
653      web_app::GenerateApplicationNameFromInfo(shortcut_info);
654  std::string contents = ShellIntegrationLinux::GetDesktopFileContents(
655      shortcut_template,
656      app_name,
657      shortcut_info.url,
658      shortcut_info.extension_id,
659      shortcut_info.extension_path,
660      shortcut_info.title,
661      icon_name,
662      shortcut_info.profile_path);
663
664  bool success = true;
665
666  if (shortcut_info.create_on_desktop)
667    success = CreateShortcutOnDesktop(shortcut_filename, contents);
668
669  if (shortcut_info.create_in_applications_menu)
670    success = CreateShortcutInApplicationsMenu(shortcut_filename, contents) &&
671              success;
672
673  return success;
674}
675
676void DeleteDesktopShortcuts(const FilePath& profile_path,
677                            const std::string& extension_id) {
678  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
679
680  FilePath shortcut_filename = GetExtensionShortcutFilename(
681      profile_path, extension_id);
682  DCHECK(!shortcut_filename.empty());
683
684  DeleteShortcutOnDesktop(shortcut_filename);
685  DeleteShortcutInApplicationsMenu(shortcut_filename);
686}
687
688}  // namespace ShellIntegrationLinux
689