[M120][Tizen][Onscreen] Fix build errors for TV profile
[platform/framework/web/chromium-efl.git] / chrome / browser / shell_integration_linux.cc
1 // Copyright 2012 The Chromium Authors
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 <stddef.h>
9 #include <stdlib.h>
10 #include <sys/stat.h>
11 #include <sys/types.h>
12 #include <unistd.h>
13
14 #include <memory>
15 #include <sstream>
16 #include <string>
17 #include <utility>
18 #include <vector>
19
20 #include "base/base_paths.h"
21 #include "base/command_line.h"
22 #include "base/environment.h"
23 #include "base/files/file_enumerator.h"
24 #include "base/files/file_path.h"
25 #include "base/files/file_util.h"
26 #include "base/i18n/file_util_icu.h"
27 #include "base/logging.h"
28 #include "base/memory/ref_counted_memory.h"
29 #include "base/nix/xdg_util.h"
30 #include "base/path_service.h"
31 #include "base/posix/eintr_wrapper.h"
32 #include "base/process/kill.h"
33 #include "base/process/launch.h"
34 #include "base/strings/string_number_conversions.h"
35 #include "base/strings/string_tokenizer.h"
36 #include "base/strings/string_util.h"
37 #include "base/strings/stringprintf.h"
38 #include "base/strings/utf_string_conversions.h"
39 #include "base/threading/scoped_blocking_call.h"
40 #include "base/threading/thread.h"
41 #include "base/threading/thread_restrictions.h"
42 #include "build/branding_buildflags.h"
43 #include "build/build_config.h"
44 #include "build/chromeos_buildflags.h"
45 #include "chrome/browser/shell_integration.h"
46 #include "chrome/browser/web_applications/os_integration/web_app_shortcut.h"
47 #include "chrome/browser/web_applications/web_app_helpers.h"
48 #include "chrome/common/buildflags.h"
49 #include "chrome/common/channel_info.h"
50 #include "chrome/common/chrome_constants.h"
51 #include "chrome/common/chrome_switches.h"
52 #include "chrome/grit/chrome_unscaled_resources.h"
53 #include "components/version_info/version_info.h"
54 #include "third_party/libxml/chromium/xml_writer.h"
55 #include "third_party/re2/src/re2/re2.h"
56 #include "ui/base/resource/resource_bundle.h"
57 #include "ui/gfx/image/image_family.h"
58 #include "ui/ozone/public/ozone_platform.h"
59 #include "ui/ozone/public/platform_utils.h"
60 #include "url/gurl.h"
61
62 #if defined(USE_GLIB)
63 #include <glib.h>
64 #endif
65
66 namespace shell_integration_linux {
67
68 const char kXdgSettings[] = "xdg-settings";
69 const char kXdgSettingsDefaultBrowser[] = "default-web-browser";
70 const char kXdgSettingsDefaultSchemeHandler[] = "default-url-scheme-handler";
71
72 // Utility function to get the path to the version of a script shipped with
73 // Chrome. |script| gives the name of the script. |chrome_version| returns the
74 // path to the Chrome version of the script, and the return value of the
75 // function is true if the function is successful and the Chrome version is
76 // not the script found on the PATH.
77 bool GetChromeVersionOfScript(const std::string& script,
78                               std::string* chrome_version) {
79   // Get the path to the Chrome version.
80   base::FilePath chrome_dir;
81   if (!base::PathService::Get(base::DIR_EXE, &chrome_dir))
82     return false;
83
84   base::FilePath chrome_version_path = chrome_dir.Append(script);
85   *chrome_version = chrome_version_path.value();
86
87   // Check if this is different to the one on path.
88   std::vector<std::string> argv;
89   argv.push_back("which");
90   argv.push_back(script);
91   std::string path_version;
92   if (base::GetAppOutput(base::CommandLine(argv), &path_version)) {
93     // Remove trailing newline
94     path_version.erase(path_version.length() - 1, 1);
95     base::FilePath path_version_path(path_version);
96     return (chrome_version_path != path_version_path);
97   }
98   return false;
99 }
100
101 // Value returned by xdg-settings if it can't understand our request.
102 const int EXIT_XDG_SETTINGS_SYNTAX_ERROR = 1;
103
104 // We delegate the difficulty of setting the default browser and default url
105 // scheme handler in Linux desktop environments to an xdg utility, xdg-settings.
106
107 // When calling this script we first try to use the script on PATH. If that
108 // fails we then try to use the script that we have included. This gives
109 // scripts on the system priority over ours, as distribution vendors may have
110 // tweaked the script, but still allows our copy to be used if the script on the
111 // system fails, as the system copy may be missing capabilities of the Chrome
112 // copy.
113
114 // If |scheme| is empty this function sets Chrome as the default browser,
115 // otherwise it sets Chrome as the default handler application for |scheme|.
116 bool SetDefaultWebClient(const std::string& scheme) {
117 #if BUILDFLAG(IS_CHROMEOS_ASH)
118   return true;
119 #else
120   std::unique_ptr<base::Environment> env(base::Environment::Create());
121
122   std::vector<std::string> argv;
123   argv.push_back(kXdgSettings);
124   argv.push_back("set");
125   if (scheme.empty()) {
126     argv.push_back(kXdgSettingsDefaultBrowser);
127   } else {
128     argv.push_back(kXdgSettingsDefaultSchemeHandler);
129     argv.push_back(scheme);
130   }
131   argv.push_back(chrome::GetDesktopName(env.get()));
132
133   int exit_code;
134   bool ran_ok = LaunchXdgUtility(argv, &exit_code);
135   if (ran_ok && exit_code == EXIT_XDG_SETTINGS_SYNTAX_ERROR) {
136     if (GetChromeVersionOfScript(kXdgSettings, &argv[0])) {
137       ran_ok = LaunchXdgUtility(argv, &exit_code);
138     }
139   }
140
141   return ran_ok && exit_code == EXIT_SUCCESS;
142 #endif
143 }
144
145 // If |scheme| is empty this function checks if Chrome is the default browser,
146 // otherwise it checks if Chrome is the default handler application for
147 // |scheme|.
148 shell_integration::DefaultWebClientState GetIsDefaultWebClient(
149     const std::string& scheme) {
150 #if BUILDFLAG(IS_CHROMEOS_ASH)
151   return shell_integration::UNKNOWN_DEFAULT;
152 #else
153   base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
154                                                 base::BlockingType::MAY_BLOCK);
155
156   std::unique_ptr<base::Environment> env(base::Environment::Create());
157
158   std::vector<std::string> argv;
159   argv.push_back(kXdgSettings);
160   argv.push_back("check");
161   if (scheme.empty()) {
162     argv.push_back(kXdgSettingsDefaultBrowser);
163   } else {
164     argv.push_back(kXdgSettingsDefaultSchemeHandler);
165     argv.push_back(scheme);
166   }
167   argv.push_back(chrome::GetDesktopName(env.get()));
168
169   std::string reply;
170   int success_code;
171   bool ran_ok = base::GetAppOutputWithExitCode(base::CommandLine(argv), &reply,
172                                                &success_code);
173   if (ran_ok && success_code == EXIT_XDG_SETTINGS_SYNTAX_ERROR) {
174     if (GetChromeVersionOfScript(kXdgSettings, &argv[0])) {
175       ran_ok = base::GetAppOutputWithExitCode(base::CommandLine(argv), &reply,
176                                               &success_code);
177     }
178   }
179
180   if (!ran_ok || success_code != EXIT_SUCCESS) {
181     // xdg-settings failed: we can't determine or set the default browser.
182     return shell_integration::UNKNOWN_DEFAULT;
183   }
184
185   // Allow any reply that starts with "yes".
186   return base::StartsWith(reply, "yes", base::CompareCase::SENSITIVE)
187              ? shell_integration::IS_DEFAULT
188              : shell_integration::NOT_DEFAULT;
189 #endif
190 }
191
192 // https://wiki.gnome.org/Projects/GnomeShell/ApplicationBased
193 // The WM_CLASS property should be set to the same as the *.desktop file without
194 // the .desktop extension.  We cannot simply use argv[0] in this case, because
195 // on the stable channel, the executable name is google-chrome-stable, but the
196 // desktop file is google-chrome.desktop.
197 std::string GetDesktopBaseName(const std::string& desktop_file_name) {
198   static const char kDesktopExtension[] = ".desktop";
199   if (base::EndsWith(desktop_file_name, kDesktopExtension,
200                      base::CompareCase::SENSITIVE)) {
201     return desktop_file_name.substr(
202         0, desktop_file_name.length() - strlen(kDesktopExtension));
203   }
204   return desktop_file_name;
205 }
206
207 namespace {
208
209 #if defined(USE_GLIB)
210 // Quote a string such that it appears as one verbatim argument for the Exec
211 // key in a desktop file.
212 std::string QuoteArgForDesktopFileExec(const std::string& arg) {
213   // http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html
214
215   // Quoting is only necessary if the argument has a reserved character.
216   if (arg.find_first_of(" \t\n\"'\\><~|&;$*?#()`") == std::string::npos)
217     return arg;  // No quoting necessary.
218
219   std::string quoted = "\"";
220   for (size_t i = 0; i < arg.size(); ++i) {
221     // Note that the set of backslashed characters is smaller than the
222     // set of reserved characters.
223     switch (arg[i]) {
224       case '"':
225       case '`':
226       case '$':
227       case '\\':
228         quoted += '\\';
229         break;
230     }
231     quoted += arg[i];
232   }
233   quoted += '"';
234
235   return quoted;
236 }
237
238 // Quote a command line so it is suitable for use as the Exec key in a desktop
239 // file. Note: This should be used instead of GetCommandLineString, which does
240 // not properly quote the string; this function is designed for the Exec key.
241 std::string QuoteCommandLineForDesktopFileExec(
242     const base::CommandLine& command_line) {
243   // http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html
244
245   std::string quoted_path;
246   const base::CommandLine::StringVector& argv = command_line.argv();
247   for (auto i = argv.begin(); i != argv.end(); ++i) {
248     if (i != argv.begin())
249       quoted_path += " ";
250     quoted_path += QuoteArgForDesktopFileExec(*i);
251   }
252
253   return quoted_path;
254 }
255 #endif
256
257 #if defined(USE_GLIB)
258 const char kDesktopEntry[] = "Desktop Entry";
259 const char kXdgOpenShebang[] = "#!/usr/bin/env xdg-open";
260
261 void SetActionsForDesktopApplication(
262     const base::CommandLine& command_line,
263     GKeyFile* key_file,
264     std::set<web_app::DesktopActionInfo> action_info) {
265   if (action_info.empty())
266     return;
267
268   std::vector<std::string> action_ids;
269   for (const auto& info : action_info) {
270     action_ids.push_back(info.id);
271   }
272
273   std::string joined_action_ids = base::JoinString(action_ids, ";");
274   g_key_file_set_string(key_file, kDesktopEntry, "Actions",
275                         joined_action_ids.c_str());
276
277   for (const auto& info : action_info) {
278     std::string section_title = "Desktop Action " + info.id;
279     g_key_file_set_string(key_file, section_title.c_str(), "Name",
280                           info.name.c_str());
281
282     std::string launch_url_str = info.exec_launch_url.spec();
283     // Escape % as %%.
284     RE2::GlobalReplace(&launch_url_str, "%", "%%");
285     base::CommandLine current_cmd(command_line);
286     current_cmd.AppendSwitchASCII(switches::kAppLaunchUrlForShortcutsMenuItem,
287                                   launch_url_str);
288
289     g_key_file_set_string(
290         key_file, section_title.c_str(), "Exec",
291         QuoteCommandLineForDesktopFileExec(current_cmd).c_str());
292   }
293 }
294 #endif
295
296 base::FilePath GetDesktopFileForDefaultSchemeHandler(base::Environment* env,
297                                                      const GURL& url) {
298   base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
299                                                 base::BlockingType::MAY_BLOCK);
300
301   std::vector<std::string> argv;
302   argv.push_back(shell_integration_linux::kXdgSettings);
303   argv.push_back("get");
304   argv.push_back(shell_integration_linux::kXdgSettingsDefaultSchemeHandler);
305   argv.push_back(url.scheme());
306   argv.push_back(chrome::GetDesktopName(env));
307
308   std::string desktop_file_name;
309   if (base::GetAppOutput(base::CommandLine(argv), &desktop_file_name) &&
310       desktop_file_name.find(".desktop") != std::string::npos) {
311     // Remove trailing newline
312     desktop_file_name.erase(desktop_file_name.length() - 1, 1);
313     return base::FilePath(desktop_file_name);
314   }
315
316   return base::FilePath();
317 }
318
319 std::string GetDesktopEntryStringValueFromFromDesktopFile(
320     const std::string& key,
321     const std::string& shortcut_contents) {
322   std::string key_value;
323 #if defined(USE_GLIB)
324   // An empty file causes a crash with glib <= 2.32, so special case here.
325   if (shortcut_contents.empty())
326     return key_value;
327
328   GKeyFile* key_file = g_key_file_new();
329   GError* err = nullptr;
330   if (!g_key_file_load_from_data(key_file, shortcut_contents.c_str(),
331                                  shortcut_contents.size(), G_KEY_FILE_NONE,
332                                  &err)) {
333     LOG(WARNING) << "Unable to read desktop file template: " << err->message;
334     g_error_free(err);
335     g_key_file_free(key_file);
336     return key_value;
337   }
338
339   char* key_c_string =
340       g_key_file_get_string(key_file, kDesktopEntry, key.c_str(), &err);
341   if (key_c_string) {
342     key_value = key_c_string;
343     g_free(key_c_string);
344   } else {
345     g_error_free(err);
346   }
347
348   g_key_file_free(key_file);
349 #else
350   NOTIMPLEMENTED();
351 #endif
352
353   return key_value;
354 }
355
356 }  // namespace
357
358 // Allows LaunchXdgUtility to join a process.
359 // thread_restrictions.h assumes it to be in shell_integration_linux namespace.
360 class [[maybe_unused, nodiscard]] LaunchXdgUtilityScopedAllowBaseSyncPrimitives
361     : public base::ScopedAllowBaseSyncPrimitives{};
362
363 bool LaunchXdgUtility(const std::vector<std::string>& argv, int* exit_code) {
364   // xdg-settings internally runs xdg-mime, which uses mv to move newly-created
365   // files on top of originals after making changes to them. In the event that
366   // the original files are owned by another user (e.g. root, which can happen
367   // if they are updated within sudo), mv will prompt the user to confirm if
368   // standard input is a terminal (otherwise it just does it). So make sure it's
369   // not, to avoid locking everything up waiting for mv.
370   *exit_code = EXIT_FAILURE;
371   int devnull = open("/dev/null", O_RDONLY);
372   if (devnull < 0)
373     return false;
374
375   base::LaunchOptions options;
376   options.fds_to_remap.push_back(std::make_pair(devnull, STDIN_FILENO));
377   base::Process process = base::LaunchProcess(argv, options);
378   close(devnull);
379   if (!process.IsValid())
380     return false;
381   LaunchXdgUtilityScopedAllowBaseSyncPrimitives allow_base_sync_primitives;
382   return process.WaitForExit(exit_code);
383 }
384
385 std::string GetWMClassFromAppName(std::string app_name) {
386   base::i18n::ReplaceIllegalCharactersInPath(&app_name, '_');
387   base::TrimString(app_name, "_", &app_name);
388   return app_name;
389 }
390
391 std::string GetXdgAppIdForWebApp(std::string app_name,
392                                  const base::FilePath& profile_path) {
393   if (base::StartsWith(app_name, web_app::kCrxAppPrefix))
394     app_name = app_name.substr(strlen(web_app::kCrxAppPrefix));
395   return GetDesktopBaseName(
396       web_app::GetAppShortcutFilename(profile_path, app_name).AsUTF8Unsafe());
397 }
398
399 base::FilePath GetDataWriteLocation(base::Environment* env) {
400   return base::nix::GetXDGDirectory(env, "XDG_DATA_HOME", ".local/share");
401 }
402
403 std::vector<base::FilePath> GetDataSearchLocations(base::Environment* env) {
404   base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
405                                                 base::BlockingType::MAY_BLOCK);
406
407   std::vector<base::FilePath> search_paths;
408   base::FilePath write_location = GetDataWriteLocation(env);
409   search_paths.push_back(write_location);
410
411   std::string xdg_data_dirs;
412   if (env->GetVar("XDG_DATA_DIRS", &xdg_data_dirs) && !xdg_data_dirs.empty()) {
413     base::StringTokenizer tokenizer(xdg_data_dirs, ":");
414     while (tokenizer.GetNext()) {
415       search_paths.emplace_back(tokenizer.token_piece());
416     }
417   } else {
418     search_paths.push_back(base::FilePath("/usr/local/share"));
419     search_paths.push_back(base::FilePath("/usr/share"));
420   }
421
422   return search_paths;
423 }
424
425 namespace internal {
426
427 std::string GetDesktopEntryStringValueFromFromDesktopFileForTest(
428     const std::string& key,
429     const std::string& shortcut_contents) {
430   return shell_integration_linux::GetDesktopEntryStringValueFromFromDesktopFile(
431       key, shortcut_contents);
432 }
433
434 // Get the value of NoDisplay from the [Desktop Entry] section of a .desktop
435 // file, given in |shortcut_contents|. If the key is not found, returns false.
436 bool GetNoDisplayFromDesktopFile(const std::string& shortcut_contents) {
437   std::string nodisplay_value =
438       shell_integration_linux::GetDesktopEntryStringValueFromFromDesktopFile(
439           "NoDisplay", shortcut_contents);
440   return nodisplay_value == "true";
441 }
442
443 // Gets the path to the Chrome executable or wrapper script.
444 // Returns an empty path if the executable path could not be found, which should
445 // never happen.
446 base::FilePath GetChromeExePath() {
447   // Try to get the name of the wrapper script that launched Chrome.
448   std::unique_ptr<base::Environment> environment(base::Environment::Create());
449   std::string wrapper_script;
450   if (environment->GetVar("CHROME_WRAPPER", &wrapper_script))
451     return base::FilePath(wrapper_script);
452
453   // Just return the name of the executable path for Chrome.
454   base::FilePath chrome_exe_path;
455   base::PathService::Get(base::FILE_EXE, &chrome_exe_path);
456   return chrome_exe_path;
457 }
458
459 std::string GetProgramClassName(const base::CommandLine& command_line,
460                                 const std::string& desktop_file_name) {
461   std::string class_name = GetDesktopBaseName(desktop_file_name);
462   std::string user_data_dir =
463       command_line.GetSwitchValueNative(switches::kUserDataDir);
464   // If the user launches with e.g. --user-data-dir=/tmp/my-user-data, set the
465   // class name to "Chrome (/tmp/my-user-data)".  The class name will show up in
466   // the alt-tab list in gnome-shell if you're running a binary that doesn't
467   // have a matching .desktop file.
468   return user_data_dir.empty()
469              ? class_name
470              : class_name + " (" + user_data_dir + ")";
471 }
472
473 std::string GetProgramClassClass(const base::CommandLine& command_line,
474                                  const std::string& desktop_file_name) {
475   if (command_line.HasSwitch(switches::kWmClass))
476     return command_line.GetSwitchValueASCII(switches::kWmClass);
477   std::string desktop_base_name = GetDesktopBaseName(desktop_file_name);
478   if (auto* platform_utils =
479           ui::OzonePlatform::GetInstance()->GetPlatformUtils()) {
480     return platform_utils->GetWmWindowClass(desktop_base_name);
481   }
482   if (!desktop_base_name.empty()) {
483     // Capitalize the first character like gtk does.
484     desktop_base_name[0] = base::ToUpperASCII(desktop_base_name[0]);
485   }
486   return desktop_base_name;
487 }
488
489 }  // namespace internal
490
491 std::string GetProgramClassName() {
492   std::unique_ptr<base::Environment> env(base::Environment::Create());
493   return internal::GetProgramClassName(*base::CommandLine::ForCurrentProcess(),
494                                        chrome::GetDesktopName(env.get()));
495 }
496
497 std::string GetProgramClassClass() {
498   std::unique_ptr<base::Environment> env(base::Environment::Create());
499   return internal::GetProgramClassClass(*base::CommandLine::ForCurrentProcess(),
500                                         chrome::GetDesktopName(env.get()));
501 }
502
503 std::string GetIconName() {
504 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
505   return "google-chrome";
506 #else  // BUILDFLAG(CHROMIUM_BRANDING)
507   return "chromium-browser";
508 #endif
509 }
510
511 bool GetExistingShortcutContents(base::Environment* env,
512                                  const base::FilePath& desktop_filename,
513                                  std::string* output) {
514   base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
515                                                 base::BlockingType::MAY_BLOCK);
516
517   std::vector<base::FilePath> search_paths = GetDataSearchLocations(env);
518
519   for (std::vector<base::FilePath>::const_iterator i = search_paths.begin();
520        i != search_paths.end(); ++i) {
521     base::FilePath path = i->Append("applications").Append(desktop_filename);
522     VLOG(1) << "Looking for desktop file in " << path.value();
523     if (base::PathExists(path)) {
524       VLOG(1) << "Found desktop file at " << path.value();
525       return base::ReadFileToString(path, output);
526     }
527   }
528
529   return false;
530 }
531
532 base::FilePath GetWebShortcutFilename(const GURL& url) {
533   // Use a prefix, because xdg-desktop-menu requires it.
534   std::string filename =
535       std::string(chrome::kBrowserProcessExecutableName) + "-" + url.spec();
536   base::i18n::ReplaceIllegalCharactersInPath(&filename, '_');
537
538   base::FilePath desktop_path;
539   if (!base::PathService::Get(base::DIR_USER_DESKTOP, &desktop_path))
540     return base::FilePath();
541
542   base::FilePath filepath = desktop_path.Append(filename);
543   base::FilePath alternative_filepath(filepath.value() + ".desktop");
544   for (size_t i = 1; i < 100; ++i) {
545     if (base::PathExists(base::FilePath(alternative_filepath))) {
546       alternative_filepath = base::FilePath(
547           filepath.value() + "_" + base::NumberToString(i) + ".desktop");
548     } else {
549       return base::FilePath(alternative_filepath).BaseName();
550     }
551   }
552
553   return base::FilePath();
554 }
555
556 std::vector<base::FilePath> GetExistingProfileShortcutFilenames(
557     const base::FilePath& profile_path,
558     const base::FilePath& directory) {
559   base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
560                                                 base::BlockingType::MAY_BLOCK);
561
562   // Use a prefix, because xdg-desktop-menu requires it.
563   std::string prefix(chrome::kBrowserProcessExecutableName);
564   prefix.append("-");
565   std::string suffix("-");
566   suffix.append(profile_path.BaseName().value());
567   base::i18n::ReplaceIllegalCharactersInPath(&suffix, '_');
568   // Spaces in filenames break xdg-desktop-menu
569   // (see https://bugs.freedesktop.org/show_bug.cgi?id=66605).
570   base::ReplaceChars(suffix, " ", "_", &suffix);
571   std::string glob = prefix + "*" + suffix + ".desktop";
572
573   base::FileEnumerator files(directory, false, base::FileEnumerator::FILES,
574                              glob);
575   base::FilePath shortcut_file = files.Next();
576   std::vector<base::FilePath> shortcut_paths;
577   while (!shortcut_file.empty()) {
578     shortcut_paths.push_back(shortcut_file.BaseName());
579     shortcut_file = files.Next();
580   }
581   return shortcut_paths;
582 }
583
584 std::string GetDesktopFileContents(
585     const base::FilePath& chrome_exe_path,
586     const std::string& app_name,
587     const GURL& url,
588     const std::string& extension_id,
589     const std::u16string& title,
590     const std::string& icon_name,
591     const base::FilePath& profile_path,
592     const std::string& categories,
593     const std::string& mime_type,
594     bool no_display,
595     const std::string& run_on_os_login_mode,
596     std::set<web_app::DesktopActionInfo> action_info) {
597   base::CommandLine cmd_line = shell_integration::CommandLineArgsForLauncher(
598       url, extension_id, profile_path, run_on_os_login_mode);
599   cmd_line.SetProgram(chrome_exe_path);
600   return GetDesktopFileContentsForCommand(cmd_line, app_name, url, title,
601                                           icon_name, categories, mime_type,
602                                           no_display, std::move(action_info));
603 }
604
605 std::string GetDesktopFileContentsForCommand(
606     const base::CommandLine& command_line,
607     const std::string& app_name,
608     const GURL& url,
609     const std::u16string& title,
610     const std::string& icon_name,
611     const std::string& categories,
612     const std::string& mime_type,
613     bool no_display,
614     std::set<web_app::DesktopActionInfo> action_info) {
615 #if defined(USE_GLIB)
616   // Although not required by the spec, Nautilus on Ubuntu Karmic creates its
617   // launchers with an xdg-open shebang. Follow that convention.
618   std::string output_buffer = std::string(kXdgOpenShebang) + "\n";
619
620   // See http://standards.freedesktop.org/desktop-entry-spec/latest/
621   GKeyFile* key_file = g_key_file_new();
622
623   // Set keys with fixed values.
624   g_key_file_set_string(key_file, kDesktopEntry, "Version", "1.0");
625   g_key_file_set_string(key_file, kDesktopEntry, "Terminal", "false");
626   g_key_file_set_string(key_file, kDesktopEntry, "Type", "Application");
627
628   // Set the "Name" key.
629   std::string final_title = base::UTF16ToUTF8(title);
630   // Make sure no endline characters can slip in and possibly introduce
631   // additional lines (like Exec, which makes it a security risk). Also
632   // use the URL as a default when the title is empty.
633   if (final_title.empty() ||
634       final_title.find("\n") != std::string::npos ||
635       final_title.find("\r") != std::string::npos) {
636     final_title = url.spec();
637   }
638   g_key_file_set_string(key_file, kDesktopEntry, "Name", final_title.c_str());
639
640   base::CommandLine modified_command_line(command_line);
641
642   // Set the "MimeType" key.
643   if (!mime_type.empty() && mime_type.find("\n") == std::string::npos &&
644       mime_type.find("\r") == std::string::npos) {
645     g_key_file_set_string(key_file, kDesktopEntry, "MimeType",
646                           mime_type.c_str());
647
648     // Some Linux Desktop Environments don't show file handlers unless they
649     // specify where to place file arguments.
650     // Note: We only include this parameter if the application is actually able
651     // to handle files, to prevent it showing up in the list of all applications
652     // which can handle files.
653     modified_command_line.AppendArg("%U");
654   }
655
656   // Set the "Exec" key.
657   std::string final_path =
658       QuoteCommandLineForDesktopFileExec(modified_command_line);
659   g_key_file_set_string(key_file, kDesktopEntry, "Exec", final_path.c_str());
660
661   // Set the "Icon" key.
662   if (!icon_name.empty()) {
663     g_key_file_set_string(key_file, kDesktopEntry, "Icon", icon_name.c_str());
664   } else {
665     g_key_file_set_string(key_file, kDesktopEntry, "Icon",
666                           GetIconName().c_str());
667   }
668
669   // Set the "Categories" key.
670   if (!categories.empty()) {
671     g_key_file_set_string(
672         key_file, kDesktopEntry, "Categories", categories.c_str());
673   }
674
675   // Set the "NoDisplay" key.
676   if (no_display)
677     g_key_file_set_string(key_file, kDesktopEntry, "NoDisplay", "true");
678
679   std::string wmclass = GetWMClassFromAppName(app_name);
680   g_key_file_set_string(key_file, kDesktopEntry, "StartupWMClass",
681                         wmclass.c_str());
682
683   SetActionsForDesktopApplication(command_line, key_file,
684                                   std::move(action_info));
685
686   gsize length = 0;
687   gchar* data_dump = g_key_file_to_data(key_file, &length, NULL);
688   if (data_dump) {
689     // If strlen(data_dump[0]) == 0, this check will fail.
690     if (data_dump[0] == '\n') {
691       // Older versions of glib produce a leading newline. If this is the case,
692       // remove it to avoid double-newline after the shebang.
693       output_buffer += (data_dump + 1);
694     } else {
695       output_buffer += data_dump;
696     }
697     g_free(data_dump);
698   }
699
700   g_key_file_free(key_file);
701   return output_buffer;
702 #else
703   NOTIMPLEMENTED();
704   return std::string();
705 #endif
706 }
707
708 std::string GetDirectoryFileContents(const std::u16string& title,
709                                      const std::string& icon_name) {
710 #if defined(USE_GLIB)
711   // See http://standards.freedesktop.org/desktop-entry-spec/latest/
712   GKeyFile* key_file = g_key_file_new();
713
714   g_key_file_set_string(key_file, kDesktopEntry, "Version", "1.0");
715   g_key_file_set_string(key_file, kDesktopEntry, "Type", "Directory");
716   std::string final_title = base::UTF16ToUTF8(title);
717   g_key_file_set_string(key_file, kDesktopEntry, "Name", final_title.c_str());
718   if (!icon_name.empty()) {
719     g_key_file_set_string(key_file, kDesktopEntry, "Icon", icon_name.c_str());
720   } else {
721     g_key_file_set_string(key_file, kDesktopEntry, "Icon",
722                           GetIconName().c_str());
723   }
724
725   gsize length = 0;
726   gchar* data_dump = g_key_file_to_data(key_file, &length, NULL);
727   std::string output_buffer;
728   if (data_dump) {
729     // If strlen(data_dump[0]) == 0, this check will fail.
730     if (data_dump[0] == '\n') {
731       // Older versions of glib produce a leading newline. If this is the case,
732       // remove it to avoid double-newline after the shebang.
733       output_buffer += (data_dump + 1);
734     } else {
735       output_buffer += data_dump;
736     }
737     g_free(data_dump);
738   }
739
740   g_key_file_free(key_file);
741   return output_buffer;
742 #else
743   NOTIMPLEMENTED();
744   return std::string();
745 #endif
746 }
747
748 base::FilePath GetMimeTypesRegistrationFilename(
749     const base::FilePath& profile_path,
750     const webapps::AppId& app_id) {
751   DCHECK(!profile_path.empty() && !app_id.empty());
752
753   // Use a prefix to clearly group files created by Chrome.
754   std::string filename = base::StringPrintf(
755       "%s-%s-%s%s", chrome::kBrowserProcessExecutableName, app_id.c_str(),
756       profile_path.BaseName().value().c_str(), ".xml");
757
758   // Replace illegal characters and spaces in |filename|.
759   base::i18n::ReplaceIllegalCharactersInPath(&filename, '_');
760   base::ReplaceChars(filename, " ", "_", &filename);
761
762   return base::FilePath(filename);
763 }
764
765 std::string GetMimeTypesRegistrationFileContents(
766     const apps::FileHandlers& file_handlers) {
767   XmlWriter writer;
768
769   writer.StartWriting();
770   writer.StartElement("mime-info");
771   writer.AddAttribute("xmlns",
772                       "http://www.freedesktop.org/standards/shared-mime-info");
773
774   for (const auto& file_handler : file_handlers) {
775     for (const auto& accept_entry : file_handler.accept) {
776       writer.StartElement("mime-type");
777       writer.AddAttribute("type", accept_entry.mime_type);
778
779       if (!file_handler.display_name.empty()) {
780         writer.WriteElement("comment",
781                             base::UTF16ToUTF8(file_handler.display_name));
782       }
783       for (const auto& file_extension : accept_entry.file_extensions) {
784         writer.StartElement("glob");
785         writer.AddAttribute("pattern", "*" + file_extension);
786         writer.EndElement();  // "glob"
787       }
788       writer.EndElement();  // "mime-type"
789     }
790   }
791
792   writer.EndElement();  // "mime-info"
793   writer.StopWriting();
794   return writer.GetWrittenString();
795 }
796
797 }  // namespace shell_integration_linux
798
799 namespace shell_integration {
800
801 bool SetAsDefaultBrowser() {
802   return shell_integration_linux::SetDefaultWebClient(std::string());
803 }
804
805 bool SetAsDefaultClientForScheme(const std::string& scheme) {
806   return shell_integration_linux::SetDefaultWebClient(scheme);
807 }
808
809 std::u16string GetApplicationNameForScheme(const GURL& url) {
810   std::unique_ptr<base::Environment> env(base::Environment::Create());
811
812   std::string desktop_file_contents;
813   std::string application_name;
814   base::FilePath desktop_filepath =
815       shell_integration_linux::GetDesktopFileForDefaultSchemeHandler(env.get(),
816                                                                      url);
817   if (shell_integration_linux::GetExistingShortcutContents(
818           env.get(), desktop_filepath, &desktop_file_contents)) {
819     application_name =
820         shell_integration_linux::GetDesktopEntryStringValueFromFromDesktopFile(
821             "Name", desktop_file_contents);
822   }
823
824   return application_name.empty() ? u"xdg-open"
825                                   : base::ASCIIToUTF16(application_name);
826 }
827
828 DefaultWebClientState GetDefaultBrowser() {
829   return shell_integration_linux::GetIsDefaultWebClient(std::string());
830 }
831
832 bool IsFirefoxDefaultBrowser() {
833   std::vector<std::string> argv;
834   argv.push_back(shell_integration_linux::kXdgSettings);
835   argv.push_back("get");
836   argv.push_back(shell_integration_linux::kXdgSettingsDefaultBrowser);
837
838   std::string browser;
839   // We don't care about the return value here.
840   base::GetAppOutput(base::CommandLine(argv), &browser);
841   return browser.find("irefox") != std::string::npos;
842 }
843
844 DefaultWebClientState IsDefaultClientForScheme(const std::string& scheme) {
845   return shell_integration_linux::GetIsDefaultWebClient(scheme);
846 }
847
848 namespace internal {
849
850 DefaultWebClientSetPermission GetPlatformSpecificDefaultWebClientSetPermission(
851     WebClientSetMethod method) {
852   return SET_DEFAULT_UNATTENDED;
853 }
854
855 }  // namespace internal
856
857 }  // namespace shell_integration