blob: d8f7d7bbe0aa9857d75eee0ee70184c72ca95ca2 [file] [log] [blame]
[email protected]a82af392012-02-24 04:40:201// Copyright (c) 2012 The Chromium Authors. All rights reserved.
[email protected]ef525cc2009-07-10 17:08:162// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
[email protected]8806d3b2012-04-13 06:46:345#include "chrome/browser/shell_integration_linux.h"
[email protected]ef525cc2009-07-10 17:08:166
[email protected]6584f0b12009-07-20 23:38:147#include <fcntl.h>
[email protected]0a96c3f2011-05-11 22:10:208#include <glib.h>
[email protected]ef525cc2009-07-10 17:08:169#include <stdlib.h>
[email protected]6584f0b12009-07-20 23:38:1410#include <sys/stat.h>
11#include <sys/types.h>
12#include <unistd.h>
[email protected]ef525cc2009-07-10 17:08:1613
[email protected]42896802009-08-28 23:39:4414#include <string>
[email protected]ef525cc2009-07-10 17:08:1615#include <vector>
16
[email protected]6a83c4242011-07-07 06:06:4117#include "base/base_paths.h"
[email protected]42896802009-08-28 23:39:4418#include "base/command_line.h"
[email protected]76b90d312010-08-03 03:00:5019#include "base/environment.h"
[email protected]b96aa932009-08-12 21:34:4920#include "base/file_util.h"
[email protected]57999812013-02-24 05:40:5221#include "base/files/file_path.h"
[email protected]ea1a3f62012-11-16 20:34:2322#include "base/files/scoped_temp_dir.h"
[email protected]d0767cb542009-10-08 17:38:3023#include "base/i18n/file_util_icu.h"
[email protected]b96aa932009-08-12 21:34:4924#include "base/message_loop.h"
25#include "base/path_service.h"
[email protected]2025d002012-11-14 20:54:3526#include "base/posix/eintr_wrapper.h"
[email protected]ef525cc2009-07-10 17:08:1627#include "base/process_util.h"
[email protected]3ea1b182013-02-08 22:38:4128#include "base/strings/string_number_conversions.h"
[email protected]f4ebe772013-02-02 00:21:3929#include "base/strings/string_tokenizer.h"
[email protected]34b99632011-01-01 01:01:0630#include "base/threading/thread.h"
[email protected]89886652012-12-11 18:09:0731#include "base/threading/thread_restrictions.h"
[email protected]1cb92b82010-03-08 23:12:1532#include "base/utf_string_conversions.h"
[email protected]b03f53cd2011-04-06 18:18:4333#include "build/build_config.h"
[email protected]a0b60cfd2011-04-06 18:02:4134#include "chrome/browser/web_applications/web_app.h"
[email protected]42896802009-08-28 23:39:4435#include "chrome/common/chrome_constants.h"
[email protected]c38831a12011-10-28 12:44:4936#include "content/public/browser/browser_thread.h"
[email protected]b96aa932009-08-12 21:34:4937#include "googleurl/src/gurl.h"
[email protected]08397d52011-02-05 01:53:3838#include "ui/gfx/codec/png_codec.h"
[email protected]c67d0342d2013-02-12 06:34:3739#include "ui/gfx/image/image_skia.h"
40#include "ui/gfx/image/image_skia_rep.h"
[email protected]ef525cc2009-07-10 17:08:1641
[email protected]631bb742011-11-02 11:29:3942using content::BrowserThread;
43
[email protected]b96aa932009-08-12 21:34:4944namespace {
45
[email protected]42896802009-08-28 23:39:4446// Helper to launch xdg scripts. We don't want them to ask any questions on the
[email protected]6a83c4242011-07-07 06:06:4147// 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) {
[email protected]42896802009-08-28 23:39:4450 // 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.
[email protected]6a83c4242011-07-07 06:06:4156 *exit_code = EXIT_FAILURE;
[email protected]42896802009-08-28 23:39:4457 int devnull = open("/dev/null", O_RDONLY);
58 if (devnull < 0)
59 return false;
[email protected]a82af392012-02-24 04:40:2060 base::FileHandleMappingVector no_stdin;
[email protected]42896802009-08-28 23:39:4461 no_stdin.push_back(std::make_pair(devnull, STDIN_FILENO));
62
63 base::ProcessHandle handle;
[email protected]b5ce736e2011-07-13 18:51:4764 base::LaunchOptions options;
[email protected]b5ce736e2011-07-13 18:51:4765 options.fds_to_remap = &no_stdin;
[email protected]e5992182011-07-15 16:47:0266 if (!base::LaunchProcess(argv, options, &handle)) {
[email protected]42896802009-08-28 23:39:4467 close(devnull);
68 return false;
69 }
70 close(devnull);
71
[email protected]6a83c4242011-07-07 06:06:4172 return base::WaitForExitCode(handle, exit_code);
[email protected]42896802009-08-28 23:39:4473}
74
[email protected]620942e2010-02-16 10:12:1275std::string CreateShortcutIcon(
76 const ShellIntegration::ShortcutInfo& shortcut_info,
[email protected]650b2d52013-02-10 03:41:4577 const base::FilePath& shortcut_filename) {
[email protected]bdd6eec2012-03-03 19:58:0678 if (shortcut_info.favicon.IsEmpty())
[email protected]620942e2010-02-16 10:12:1279 return std::string();
80
81 // TODO(phajdan.jr): Report errors from this function, possibly as infobars.
[email protected]ea1a3f62012-11-16 20:34:2382 base::ScopedTempDir temp_dir;
[email protected]620942e2010-02-16 10:12:1283 if (!temp_dir.CreateUniqueTempDir())
84 return std::string();
85
[email protected]650b2d52013-02-10 03:41:4586 base::FilePath temp_file_path = temp_dir.path().Append(
[email protected]620942e2010-02-16 10:12:1287 shortcut_filename.ReplaceExtension("png"));
[email protected]620942e2010-02-16 10:12:1288 std::string icon_name = temp_file_path.BaseName().RemoveExtension().value();
[email protected]c67d0342d2013-02-12 06:34:3789
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 }
[email protected]620942e2010-02-16 10:12:12128 return icon_name;
129}
130
[email protected]650b2d52013-02-10 03:41:45131bool CreateShortcutOnDesktop(const base::FilePath& shortcut_filename,
[email protected]620942e2010-02-16 10:12:12132 const std::string& contents) {
[email protected]620942e2010-02-16 10:12:12133 // Make sure that we will later call openat in a secure way.
134 DCHECK_EQ(shortcut_filename.BaseName().value(), shortcut_filename.value());
135
[email protected]650b2d52013-02-10 03:41:45136 base::FilePath desktop_path;
[email protected]dea1d7d2012-09-20 16:24:52137 if (!PathService::Get(base::DIR_USER_DESKTOP, &desktop_path))
[email protected]8806d3b2012-04-13 06:46:34138 return false;
[email protected]620942e2010-02-16 10:12:12139
140 int desktop_fd = open(desktop_path.value().c_str(), O_RDONLY | O_DIRECTORY);
141 if (desktop_fd < 0)
[email protected]8806d3b2012-04-13 06:46:34142 return false;
[email protected]620942e2010-02-16 10:12:12143
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) {
[email protected]19cb9292010-04-16 23:00:15148 if (HANDLE_EINTR(close(desktop_fd)) < 0)
149 PLOG(ERROR) << "close";
[email protected]8806d3b2012-04-13 06:46:34150 return false;
[email protected]620942e2010-02-16 10:12:12151 }
152
153 ssize_t bytes_written = file_util::WriteFileDescriptor(fd, contents.data(),
154 contents.length());
[email protected]19cb9292010-04-16 23:00:15155 if (HANDLE_EINTR(close(fd)) < 0)
156 PLOG(ERROR) << "close";
[email protected]620942e2010-02-16 10:12:12157
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
[email protected]19cb9292010-04-16 23:00:15166 if (HANDLE_EINTR(close(desktop_fd)) < 0)
167 PLOG(ERROR) << "close";
[email protected]8806d3b2012-04-13 06:46:34168
169 return true;
[email protected]620942e2010-02-16 10:12:12170}
171
[email protected]650b2d52013-02-10 03:41:45172void DeleteShortcutOnDesktop(const base::FilePath& shortcut_filename) {
173 base::FilePath desktop_path;
[email protected]dea1d7d2012-09-20 16:24:52174 if (PathService::Get(base::DIR_USER_DESKTOP, &desktop_path))
[email protected]0b7df36d2012-07-11 09:50:47175 file_util::Delete(desktop_path.Append(shortcut_filename), false);
176}
177
[email protected]650b2d52013-02-10 03:41:45178bool CreateShortcutInApplicationsMenu(const base::FilePath& shortcut_filename,
[email protected]620942e2010-02-16 10:12:12179 const std::string& contents) {
[email protected]ea1a3f62012-11-16 20:34:23180 base::ScopedTempDir temp_dir;
[email protected]620942e2010-02-16 10:12:12181 if (!temp_dir.CreateUniqueTempDir())
[email protected]8806d3b2012-04-13 06:46:34182 return false;
[email protected]620942e2010-02-16 10:12:12183
[email protected]650b2d52013-02-10 03:41:45184 base::FilePath temp_file_path = temp_dir.path().Append(shortcut_filename);
[email protected]620942e2010-02-16 10:12:12185
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()))
[email protected]8806d3b2012-04-13 06:46:34190 return false;
[email protected]620942e2010-02-16 10:12:12191
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());
[email protected]6a83c4242011-07-07 06:06:41202 int exit_code;
203 LaunchXdgUtility(argv, &exit_code);
[email protected]8806d3b2012-04-13 06:46:34204 return exit_code == 0;
[email protected]620942e2010-02-16 10:12:12205}
206
[email protected]650b2d52013-02-10 03:41:45207void DeleteShortcutInApplicationsMenu(const base::FilePath& shortcut_filename) {
[email protected]0b7df36d2012-07-11 09:50:47208 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
[email protected]b10392932011-03-08 21:28:14223// 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
[email protected]0a96c3f2011-05-11 22:10:20251// 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};
[email protected]b10392932011-03-08 21:28:14261
[email protected]4f0806a72011-09-21 03:08:45262const char kDesktopEntry[] = "Desktop Entry";
[email protected]0a96c3f2011-05-11 22:10:20263
[email protected]4f0806a72011-09-21 03:08:45264const 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";
[email protected]b10392932011-03-08 21:28:14269
[email protected]07753f0f2013-02-05 07:52:59270// Regex to match a localized key name such as "Name[en_AU]".
271const char kLocalizedKeyRegex[] = "^[A-Za-z0-9\\-]+\\[[^\\]]*\\]$";
272
[email protected]620942e2010-02-16 10:12:12273} // namespace
274
[email protected]6a83c4242011-07-07 06:06:41275namespace {
[email protected]620942e2010-02-16 10:12:12276
[email protected]6a83c4242011-07-07 06:06:41277// 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.
[email protected]650b2d52013-02-10 03:41:45285 base::FilePath chrome_dir;
[email protected]6a83c4242011-07-07 06:06:41286 if (!PathService::Get(base::DIR_EXE, &chrome_dir))
287 return false;
288
[email protected]650b2d52013-02-10 03:41:45289 base::FilePath chrome_version_path = chrome_dir.Append(script);
[email protected]6a83c4242011-07-07 06:06:41290 *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);
[email protected]650b2d52013-02-10 03:41:45300 base::FilePath path_version_path(path_version);
[email protected]6a83c4242011-07-07 06:06:41301 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) {
[email protected]cc50544002012-05-01 18:56:47322#if defined(OS_CHROMEOS)
323 return true;
324#else
[email protected]6a83c4242011-07-07 06:06:41325 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
326
[email protected]76b90d312010-08-03 03:00:50327 scoped_ptr<base::Environment> env(base::Environment::Create());
[email protected]af71d642010-03-12 10:29:04328
[email protected]620942e2010-02-16 10:12:12329 std::vector<std::string> argv;
[email protected]4f0806a72011-09-21 03:08:45330 argv.push_back(kXdgSettings);
[email protected]620942e2010-02-16 10:12:12331 argv.push_back("set");
[email protected]6a83c4242011-07-07 06:06:41332 if (protocol.empty()) {
[email protected]4f0806a72011-09-21 03:08:45333 argv.push_back(kXdgSettingsDefaultBrowser);
[email protected]6a83c4242011-07-07 06:06:41334 } else {
[email protected]4f0806a72011-09-21 03:08:45335 argv.push_back(kXdgSettingsDefaultSchemeHandler);
[email protected]6a83c4242011-07-07 06:06:41336 argv.push_back(protocol);
337 }
[email protected]98566d7a2012-04-17 00:28:56338 argv.push_back(ShellIntegrationLinux::GetDesktopName(env.get()));
[email protected]6a83c4242011-07-07 06:06:41339
340 int exit_code;
341 bool ran_ok = LaunchXdgUtility(argv, &exit_code);
342 if (ran_ok && exit_code == EXIT_XDG_SETTINGS_SYNTAX_ERROR) {
[email protected]4f0806a72011-09-21 03:08:45343 if (GetChromeVersionOfScript(kXdgSettings, &argv[0])) {
[email protected]6a83c4242011-07-07 06:06:41344 ran_ok = LaunchXdgUtility(argv, &exit_code);
345 }
346 }
347
348 return ran_ok && exit_code == EXIT_SUCCESS;
[email protected]cc50544002012-05-01 18:56:47349#endif
[email protected]620942e2010-02-16 10:12:12350}
351
[email protected]6a83c4242011-07-07 06:06:41352// 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) {
[email protected]cc50544002012-05-01 18:56:47357#if defined(OS_CHROMEOS)
[email protected]89886652012-12-11 18:09:07358 return ShellIntegration::IS_DEFAULT;
[email protected]cc50544002012-05-01 18:56:47359#else
[email protected]89886652012-12-11 18:09:07360 base::ThreadRestrictions::AssertIOAllowed();
[email protected]8fcec3c72010-06-03 00:17:22361
[email protected]76b90d312010-08-03 03:00:50362 scoped_ptr<base::Environment> env(base::Environment::Create());
[email protected]af71d642010-03-12 10:29:04363
[email protected]620942e2010-02-16 10:12:12364 std::vector<std::string> argv;
[email protected]4f0806a72011-09-21 03:08:45365 argv.push_back(kXdgSettings);
[email protected]620942e2010-02-16 10:12:12366 argv.push_back("check");
[email protected]6a83c4242011-07-07 06:06:41367 if (protocol.empty()) {
[email protected]4f0806a72011-09-21 03:08:45368 argv.push_back(kXdgSettingsDefaultBrowser);
[email protected]6a83c4242011-07-07 06:06:41369 } else {
[email protected]4f0806a72011-09-21 03:08:45370 argv.push_back(kXdgSettingsDefaultSchemeHandler);
[email protected]6a83c4242011-07-07 06:06:41371 argv.push_back(protocol);
372 }
[email protected]98566d7a2012-04-17 00:28:56373 argv.push_back(ShellIntegrationLinux::GetDesktopName(env.get()));
[email protected]620942e2010-02-16 10:12:12374
375 std::string reply;
[email protected]6a83c4242011-07-07 06:06:41376 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) {
[email protected]4f0806a72011-09-21 03:08:45380 if (GetChromeVersionOfScript(kXdgSettings, &argv[0])) {
[email protected]6a83c4242011-07-07 06:06:41381 ran_ok = base::GetAppOutputWithExitCode(CommandLine(argv), &reply,
382 &success_code);
383 }
384 }
385
386 if (!ran_ok || success_code != EXIT_SUCCESS) {
[email protected]620942e2010-02-16 10:12:12387 // xdg-settings failed: we can't determine or set the default browser.
[email protected]89886652012-12-11 18:09:07388 return ShellIntegration::UNKNOWN_DEFAULT;
[email protected]620942e2010-02-16 10:12:12389 }
390
391 // Allow any reply that starts with "yes".
[email protected]89886652012-12-11 18:09:07392 return (reply.find("yes") == 0) ? ShellIntegration::IS_DEFAULT :
393 ShellIntegration::NOT_DEFAULT;
[email protected]cc50544002012-05-01 18:56:47394#endif
[email protected]6a83c4242011-07-07 06:06:41395}
396
397} // namespace
398
399// static
[email protected]bd046bd42012-06-08 05:07:32400ShellIntegration::DefaultWebClientSetPermission
401 ShellIntegration::CanSetAsDefaultBrowser() {
402 return SET_DEFAULT_UNATTENDED;
[email protected]a01481b2011-07-15 04:30:02403}
404
405// static
[email protected]6a83c4242011-07-07 06:06:41406bool ShellIntegration::SetAsDefaultBrowser() {
407 return SetDefaultWebClient("");
408}
409
410// static
411bool ShellIntegration::SetAsDefaultProtocolClient(const std::string& protocol) {
412 return SetDefaultWebClient(protocol);
413}
414
415// static
[email protected]89886652012-12-11 18:09:07416ShellIntegration::DefaultWebClientState ShellIntegration::GetDefaultBrowser() {
[email protected]6a83c4242011-07-07 06:06:41417 return GetIsDefaultWebClient("");
[email protected]4468a5b2011-05-26 07:48:02418}
419
420// static
[email protected]42dc9402013-01-30 07:54:20421std::string ShellIntegration::GetApplicationForProtocol(const GURL& url) {
422 return std::string("xdg-open");
423}
424
425// static
[email protected]4468a5b2011-05-26 07:48:02426ShellIntegration::DefaultWebClientState
427ShellIntegration::IsDefaultProtocolClient(const std::string& protocol) {
[email protected]6a83c4242011-07-07 06:06:41428 return GetIsDefaultWebClient(protocol);
[email protected]620942e2010-02-16 10:12:12429}
430
431// static
432bool ShellIntegration::IsFirefoxDefaultBrowser() {
433 std::vector<std::string> argv;
[email protected]4f0806a72011-09-21 03:08:45434 argv.push_back(kXdgSettings);
[email protected]620942e2010-02-16 10:12:12435 argv.push_back("get");
[email protected]4f0806a72011-09-21 03:08:45436 argv.push_back(kXdgSettingsDefaultBrowser);
[email protected]620942e2010-02-16 10:12:12437
438 std::string browser;
439 // We don't care about the return value here.
440 base::GetAppOutput(CommandLine(argv), &browser);
441 return browser.find("irefox") != std::string::npos;
442}
443
[email protected]98566d7a2012-04-17 00:28:56444namespace ShellIntegrationLinux {
445
446std::string GetDesktopName(base::Environment* env) {
447#if defined(GOOGLE_CHROME_BUILD)
448 return "google-chrome.desktop";
449#else // CHROMIUM_BUILD
450 // Allow $CHROME_DESKTOP to override the built-in value, so that development
451 // versions can set themselves as the default without interfering with
452 // non-official, packaged versions using the built-in value.
453 std::string name;
454 if (env->GetVar("CHROME_DESKTOP", &name) && !name.empty())
455 return name;
456 return "chromium-browser.desktop";
457#endif
458}
459
460bool GetDesktopShortcutTemplate(base::Environment* env,
461 std::string* output) {
[email protected]0c7d74f2010-10-11 11:55:26462 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
[email protected]620942e2010-02-16 10:12:12463
[email protected]650b2d52013-02-10 03:41:45464 std::vector<base::FilePath> search_paths;
[email protected]b96aa932009-08-12 21:34:49465
[email protected]58bf9252013-03-06 04:12:36466 // Search paths as specified in the XDG Base Directory Specification.
467 // http://standards.freedesktop.org/basedir-spec/latest/
[email protected]af71d642010-03-12 10:29:04468 std::string xdg_data_home;
[email protected]58bf9252013-03-06 04:12:36469 std::string home;
[email protected]3ba7e082010-08-07 02:57:59470 if (env->GetVar("XDG_DATA_HOME", &xdg_data_home) &&
[email protected]af71d642010-03-12 10:29:04471 !xdg_data_home.empty()) {
[email protected]650b2d52013-02-10 03:41:45472 search_paths.push_back(base::FilePath(xdg_data_home));
[email protected]58bf9252013-03-06 04:12:36473 } else if (env->GetVar("HOME", &home) && !home.empty()) {
474 search_paths.push_back(base::FilePath(home).Append(".local").Append(
475 "share"));
[email protected]af71d642010-03-12 10:29:04476 }
[email protected]b96aa932009-08-12 21:34:49477
[email protected]af71d642010-03-12 10:29:04478 std::string xdg_data_dirs;
[email protected]3ba7e082010-08-07 02:57:59479 if (env->GetVar("XDG_DATA_DIRS", &xdg_data_dirs) &&
[email protected]af71d642010-03-12 10:29:04480 !xdg_data_dirs.empty()) {
[email protected]f4ebe772013-02-02 00:21:39481 base::StringTokenizer tokenizer(xdg_data_dirs, ":");
[email protected]b96aa932009-08-12 21:34:49482 while (tokenizer.GetNext()) {
[email protected]650b2d52013-02-10 03:41:45483 base::FilePath data_dir(tokenizer.token());
[email protected]fd928962009-09-18 17:55:55484 search_paths.push_back(data_dir);
[email protected]b96aa932009-08-12 21:34:49485 }
[email protected]58bf9252013-03-06 04:12:36486 } else {
487 search_paths.push_back(base::FilePath("/usr/local/share"));
488 search_paths.push_back(base::FilePath("/usr/share"));
[email protected]b96aa932009-08-12 21:34:49489 }
490
[email protected]76b90d312010-08-03 03:00:50491 std::string template_filename(GetDesktopName(env));
[email protected]650b2d52013-02-10 03:41:45492 for (std::vector<base::FilePath>::const_iterator i = search_paths.begin();
[email protected]b96aa932009-08-12 21:34:49493 i != search_paths.end(); ++i) {
[email protected]58bf9252013-03-06 04:12:36494 base::FilePath path = i->Append("applications").Append(template_filename);
[email protected]8e96e502010-10-21 20:57:12495 VLOG(1) << "Looking for desktop file template in " << path.value();
[email protected]620942e2010-02-16 10:12:12496 if (file_util::PathExists(path)) {
[email protected]8e96e502010-10-21 20:57:12497 VLOG(1) << "Found desktop file template at " << path.value();
[email protected]b96aa932009-08-12 21:34:49498 return file_util::ReadFileToString(path, output);
[email protected]620942e2010-02-16 10:12:12499 }
[email protected]b96aa932009-08-12 21:34:49500 }
501
[email protected]620942e2010-02-16 10:12:12502 LOG(ERROR) << "Could not find desktop file template.";
[email protected]b96aa932009-08-12 21:34:49503 return false;
504}
505
[email protected]650b2d52013-02-10 03:41:45506base::FilePath GetWebShortcutFilename(const GURL& url) {
[email protected]42896802009-08-28 23:39:44507 // Use a prefix, because xdg-desktop-menu requires it.
[email protected]de2943352009-10-22 23:06:12508 std::string filename =
[email protected]4f260d02010-12-23 18:35:42509 std::string(chrome::kBrowserProcessExecutableName) + "-" + url.spec();
[email protected]de2943352009-10-22 23:06:12510 file_util::ReplaceIllegalCharactersInPath(&filename, '_');
[email protected]b96aa932009-08-12 21:34:49511
[email protected]650b2d52013-02-10 03:41:45512 base::FilePath desktop_path;
[email protected]dea1d7d2012-09-20 16:24:52513 if (!PathService::Get(base::DIR_USER_DESKTOP, &desktop_path))
[email protected]650b2d52013-02-10 03:41:45514 return base::FilePath();
[email protected]fcc23e842009-10-01 03:19:10515
[email protected]650b2d52013-02-10 03:41:45516 base::FilePath filepath = desktop_path.Append(filename);
517 base::FilePath alternative_filepath(filepath.value() + ".desktop");
[email protected]fcc23e842009-10-01 03:19:10518 for (size_t i = 1; i < 100; ++i) {
[email protected]650b2d52013-02-10 03:41:45519 if (file_util::PathExists(base::FilePath(alternative_filepath))) {
520 alternative_filepath = base::FilePath(
[email protected]528c56d2010-07-30 19:28:44521 filepath.value() + "_" + base::IntToString(i) + ".desktop");
[email protected]fcc23e842009-10-01 03:19:10522 } else {
[email protected]650b2d52013-02-10 03:41:45523 return base::FilePath(alternative_filepath).BaseName();
[email protected]fcc23e842009-10-01 03:19:10524 }
525 }
526
[email protected]650b2d52013-02-10 03:41:45527 return base::FilePath();
[email protected]b96aa932009-08-12 21:34:49528}
529
[email protected]650b2d52013-02-10 03:41:45530base::FilePath GetExtensionShortcutFilename(const base::FilePath& profile_path,
531 const std::string& extension_id) {
[email protected]0b7df36d2012-07-11 09:50:47532 DCHECK(!extension_id.empty());
533
534 // Use a prefix, because xdg-desktop-menu requires it.
535 std::string filename(chrome::kBrowserProcessExecutableName);
536 filename.append("-")
537 .append(extension_id)
538 .append("-")
539 .append(profile_path.BaseName().value());
540 file_util::ReplaceIllegalCharactersInPath(&filename, '_');
[email protected]650b2d52013-02-10 03:41:45541 return base::FilePath(filename.append(".desktop"));
[email protected]0b7df36d2012-07-11 09:50:47542}
543
[email protected]98566d7a2012-04-17 00:28:56544std::string GetDesktopFileContents(
[email protected]a0b60cfd2011-04-06 18:02:41545 const std::string& template_contents,
546 const std::string& app_name,
547 const GURL& url,
548 const std::string& extension_id,
[email protected]650b2d52013-02-10 03:41:45549 const base::FilePath& extension_path,
[email protected]a0b60cfd2011-04-06 18:02:41550 const string16& title,
[email protected]5951c852012-06-20 00:12:53551 const std::string& icon_name,
[email protected]650b2d52013-02-10 03:41:45552 const base::FilePath& profile_path) {
[email protected]b9eb4e52013-02-05 00:01:49553 // Although not required by the spec, Nautilus on Ubuntu Karmic creates its
554 // launchers with an xdg-open shebang. Follow that convention.
555 std::string output_buffer = std::string(kXdgOpenShebang) + "\n";
[email protected]0a96c3f2011-05-11 22:10:20556 if (template_contents.empty())
[email protected]b9eb4e52013-02-05 00:01:49557 return output_buffer;
[email protected]0a96c3f2011-05-11 22:10:20558
[email protected]b96aa932009-08-12 21:34:49559 // See http://standards.freedesktop.org/desktop-entry-spec/latest/
[email protected]0a96c3f2011-05-11 22:10:20560 // http://developer.gnome.org/glib/unstable/glib-Key-value-file-parser.html
561 GKeyFile* key_file = g_key_file_new();
562 GError* err = NULL;
563 // Loading the data will strip translations and comments from the desktop
564 // file (which we want to do!)
565 if (!g_key_file_load_from_data(
566 key_file,
567 template_contents.c_str(),
568 template_contents.size(),
569 G_KEY_FILE_NONE,
570 &err)) {
571 NOTREACHED() << "Unable to read desktop file template:" << err->message;
572 g_error_free(err);
[email protected]b9eb4e52013-02-05 00:01:49573 return output_buffer;
[email protected]0a96c3f2011-05-11 22:10:20574 }
575
576 // Remove all sections except for the Desktop Entry
577 gsize length = 0;
578 gchar** groups = g_key_file_get_groups(key_file, &length);
579 for (gsize i = 0; i < length; ++i) {
580 if (strcmp(groups[i], kDesktopEntry) != 0) {
581 g_key_file_remove_group(key_file, groups[i], NULL);
[email protected]b96aa932009-08-12 21:34:49582 }
583 }
[email protected]0a96c3f2011-05-11 22:10:20584 g_strfreev(groups);
585
586 // Remove keys that we won't need.
587 for (const char** current_key = kDesktopKeysToDelete; *current_key;
588 ++current_key) {
589 g_key_file_remove_key(key_file, kDesktopEntry, *current_key, NULL);
590 }
[email protected]07753f0f2013-02-05 07:52:59591 // Remove all localized keys.
592 GRegex* localized_key_regex = g_regex_new(kLocalizedKeyRegex,
593 static_cast<GRegexCompileFlags>(0),
594 static_cast<GRegexMatchFlags>(0),
595 NULL);
596 gchar** keys = g_key_file_get_keys(key_file, kDesktopEntry, NULL, NULL);
597 for (gchar** keys_ptr = keys; *keys_ptr; ++keys_ptr) {
598 if (g_regex_match(localized_key_regex, *keys_ptr,
599 static_cast<GRegexMatchFlags>(0), NULL)) {
600 g_key_file_remove_key(key_file, kDesktopEntry, *keys_ptr, NULL);
601 }
602 }
603 g_strfreev(keys);
604 g_regex_unref(localized_key_regex);
[email protected]0a96c3f2011-05-11 22:10:20605
606 // Set the "Name" key.
607 std::string final_title = UTF16ToUTF8(title);
608 // Make sure no endline characters can slip in and possibly introduce
609 // additional lines (like Exec, which makes it a security risk). Also
610 // use the URL as a default when the title is empty.
611 if (final_title.empty() ||
612 final_title.find("\n") != std::string::npos ||
613 final_title.find("\r") != std::string::npos) {
614 final_title = url.spec();
615 }
616 g_key_file_set_string(key_file, kDesktopEntry, "Name", final_title.c_str());
617
618 // Set the "Exec" key.
619 char* exec_c_string = g_key_file_get_string(key_file, kDesktopEntry, "Exec",
620 NULL);
621 if (exec_c_string) {
622 std::string exec_string(exec_c_string);
623 g_free(exec_c_string);
[email protected]f4ebe772013-02-02 00:21:39624 base::StringTokenizer exec_tokenizer(exec_string, " ");
[email protected]0a96c3f2011-05-11 22:10:20625
626 std::string final_path;
627 while (exec_tokenizer.GetNext() && exec_tokenizer.token() != "%U") {
628 if (!final_path.empty())
629 final_path += " ";
630 final_path += exec_tokenizer.token();
631 }
[email protected]8806d3b2012-04-13 06:46:34632 CommandLine cmd_line(CommandLine::NO_PROGRAM);
[email protected]5c93a0c12012-05-02 19:45:24633 cmd_line = ShellIntegration::CommandLineArgsForLauncher(
[email protected]dd5f67e92012-09-06 04:19:22634 url, extension_id, profile_path);
[email protected]0a96c3f2011-05-11 22:10:20635 const CommandLine::SwitchMap& switch_map = cmd_line.GetSwitches();
636 for (CommandLine::SwitchMap::const_iterator i = switch_map.begin();
637 i != switch_map.end(); ++i) {
638 if (i->second.empty()) {
639 final_path += " --" + i->first;
640 } else {
641 final_path += " " + QuoteArgForDesktopFileExec("--" + i->first +
642 "=" + i->second);
643 }
644 }
645
646 g_key_file_set_string(key_file, kDesktopEntry, "Exec", final_path.c_str());
647 }
648
649 // Set the "Icon" key.
650 if (!icon_name.empty())
651 g_key_file_set_string(key_file, kDesktopEntry, "Icon", icon_name.c_str());
[email protected]a0b60cfd2011-04-06 18:02:41652
[email protected]a13283cc2012-04-05 00:21:22653#if defined(TOOLKIT_GTK)
[email protected]a0b60cfd2011-04-06 18:02:41654 std::string wmclass = web_app::GetWMClassFromAppName(app_name);
[email protected]0a96c3f2011-05-11 22:10:20655 g_key_file_set_string(key_file, kDesktopEntry, "StartupWMClass",
656 wmclass.c_str());
[email protected]87c914a2011-04-06 18:15:00657#endif
[email protected]a0b60cfd2011-04-06 18:02:41658
[email protected]0a96c3f2011-05-11 22:10:20659 length = 0;
660 gchar* data_dump = g_key_file_to_data(key_file, &length, NULL);
661 if (data_dump) {
[email protected]b9eb4e52013-02-05 00:01:49662 // If strlen(data_dump[0]) == 0, this check will fail.
663 if (data_dump[0] == '\n') {
664 // Older versions of glib produce a leading newline. If this is the case,
665 // remove it to avoid double-newline after the shebang.
666 output_buffer += (data_dump + 1);
667 } else {
668 output_buffer += data_dump;
669 }
[email protected]0a96c3f2011-05-11 22:10:20670 g_free(data_dump);
671 }
672
673 g_key_file_free(key_file);
[email protected]b96aa932009-08-12 21:34:49674 return output_buffer;
675}
676
[email protected]98566d7a2012-04-17 00:28:56677bool CreateDesktopShortcut(
678 const ShellIntegration::ShortcutInfo& shortcut_info,
[email protected]b5ff7ab2013-03-01 07:48:53679 const ShellIntegration::ShortcutLocations& creation_locations,
[email protected]8806d3b2012-04-13 06:46:34680 const std::string& shortcut_template) {
[email protected]0c7d74f2010-10-11 11:55:26681 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
[email protected]620942e2010-02-16 10:12:12682
[email protected]650b2d52013-02-10 03:41:45683 base::FilePath shortcut_filename;
[email protected]0b7df36d2012-07-11 09:50:47684 if (!shortcut_info.extension_id.empty()) {
685 shortcut_filename = GetExtensionShortcutFilename(
686 shortcut_info.profile_path, shortcut_info.extension_id);
687 // For extensions we do not want duplicate shortcuts. So, delete any that
688 // already exist and replace them.
[email protected]b5ff7ab2013-03-01 07:48:53689 if (creation_locations.on_desktop)
[email protected]0b7df36d2012-07-11 09:50:47690 DeleteShortcutOnDesktop(shortcut_filename);
[email protected]b5ff7ab2013-03-01 07:48:53691 if (creation_locations.in_applications_menu)
[email protected]0b7df36d2012-07-11 09:50:47692 DeleteShortcutInApplicationsMenu(shortcut_filename);
693 } else {
694 shortcut_filename = GetWebShortcutFilename(shortcut_info.url);
695 }
[email protected]620942e2010-02-16 10:12:12696 if (shortcut_filename.empty())
[email protected]8806d3b2012-04-13 06:46:34697 return false;
[email protected]620942e2010-02-16 10:12:12698
[email protected]5951c852012-06-20 00:12:53699 std::string icon_name = CreateShortcutIcon(shortcut_info, shortcut_filename);
[email protected]620942e2010-02-16 10:12:12700
[email protected]a0b60cfd2011-04-06 18:02:41701 std::string app_name =
702 web_app::GenerateApplicationNameFromInfo(shortcut_info);
[email protected]98566d7a2012-04-17 00:28:56703 std::string contents = ShellIntegrationLinux::GetDesktopFileContents(
[email protected]a0b60cfd2011-04-06 18:02:41704 shortcut_template,
705 app_name,
706 shortcut_info.url,
707 shortcut_info.extension_id,
[email protected]8806d3b2012-04-13 06:46:34708 shortcut_info.extension_path,
[email protected]a0b60cfd2011-04-06 18:02:41709 shortcut_info.title,
[email protected]5951c852012-06-20 00:12:53710 icon_name,
711 shortcut_info.profile_path);
[email protected]620942e2010-02-16 10:12:12712
[email protected]8806d3b2012-04-13 06:46:34713 bool success = true;
714
[email protected]b5ff7ab2013-03-01 07:48:53715 if (creation_locations.on_desktop)
[email protected]8806d3b2012-04-13 06:46:34716 success = CreateShortcutOnDesktop(shortcut_filename, contents);
[email protected]620942e2010-02-16 10:12:12717
[email protected]b5ff7ab2013-03-01 07:48:53718 if (creation_locations.in_applications_menu)
[email protected]8806d3b2012-04-13 06:46:34719 success = CreateShortcutInApplicationsMenu(shortcut_filename, contents) &&
720 success;
721
722 return success;
[email protected]b96aa932009-08-12 21:34:49723}
[email protected]8806d3b2012-04-13 06:46:34724
[email protected]650b2d52013-02-10 03:41:45725void DeleteDesktopShortcuts(const base::FilePath& profile_path,
[email protected]0b7df36d2012-07-11 09:50:47726 const std::string& extension_id) {
727 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
728
[email protected]650b2d52013-02-10 03:41:45729 base::FilePath shortcut_filename = GetExtensionShortcutFilename(
[email protected]0b7df36d2012-07-11 09:50:47730 profile_path, extension_id);
731 DCHECK(!shortcut_filename.empty());
732
733 DeleteShortcutOnDesktop(shortcut_filename);
734 DeleteShortcutInApplicationsMenu(shortcut_filename);
735}
736
[email protected]8806d3b2012-04-13 06:46:34737} // namespace ShellIntegrationLinux