// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/shell_integration_linux.h"

#include <fcntl.h>
#include <stddef.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include <memory>
#include <optional>
#include <sstream>
#include <string>
#include <utility>
#include <vector>

#include "base/base_paths.h"
#include "base/command_line.h"
#include "base/environment.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/safe_base_name.h"
#include "base/i18n/file_util_icu.h"
#include "base/logging.h"
#include "base/nix/xdg_util.h"
#include "base/notimplemented.h"
#include "base/path_service.h"
#include "base/process/launch.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/threading/thread_restrictions.h"
#include "build/branding_buildflags.h"
#include "build/build_config.h"
#include "chrome/browser/shell_integration.h"
#include "chrome/browser/web_applications/os_integration/web_app_shortcut.h"
#include "chrome/browser/web_applications/web_app_helpers.h"
#include "chrome/common/buildflags.h"
#include "chrome/common/channel_info.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/grit/branded_strings.h"
#include "components/version_info/version_info.h"
#include "third_party/libxml/chromium/xml_writer.h"
#include "third_party/re2/src/re2/re2.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/image/image_family.h"
#include "ui/ozone/public/ozone_platform.h"
#include "ui/ozone/public/platform_utils.h"
#include "url/gurl.h"

#if defined(USE_GLIB)
#include <glib.h>
#endif

namespace shell_integration_linux {

namespace {

const char kXdgSettings[] = "xdg-settings";
const char kXdgSettingsDefaultBrowser[] = "default-web-browser";
const char kXdgSettingsDefaultSchemeHandler[] = "default-url-scheme-handler";
const char kWebAppDesktopIdPrefixEnvVar[] = "CHROME_WEB_APP_DESKTOP_ID_PREFIX";

#if defined(USE_GLIB)
struct GKeyFileDataDeleter {
  void operator()(gchar* data) { g_free(data); }
};

// Returns the contents of `key_file`. Assumes `key_file` is non-null.
std::string GetFileContents(GKeyFile* key_file) {
  gsize length = 0;
  std::unique_ptr<gchar, GKeyFileDataDeleter> data_dump(
      g_key_file_to_data(key_file, &length, nullptr));
  if (!data_dump) {
    return "";
  }

  std::string_view data_view(data_dump.get(), length);
  if (data_view.starts_with('\n')) {
    // Older versions of glib produce a leading newline. If this is the case,
    // remove it to avoid double-newline after the shebang.
    data_view.remove_prefix(1);
  }
  return std::string(data_view);
}
#endif

// We delegate the difficulty of setting the default browser and default url
// scheme handler in Linux desktop environments to an xdg utility, xdg-settings.

// If |scheme| is empty this function sets Chrome as the default browser,
// otherwise it sets Chrome as the default handler application for |scheme|.
bool SetDefaultWebClient(const std::string& scheme) {
#if BUILDFLAG(IS_CHROMEOS)
  return true;
#else
  std::unique_ptr<base::Environment> env(base::Environment::Create());

  std::vector<std::string> argv;
  argv.push_back(kXdgSettings);
  argv.push_back("set");
  if (scheme.empty()) {
    argv.push_back(kXdgSettingsDefaultBrowser);
  } else {
    argv.push_back(kXdgSettingsDefaultSchemeHandler);
    argv.push_back(scheme);
  }
  argv.push_back(chrome::GetDesktopName(env.get()));

  int exit_code;
  bool ran_ok = LaunchXdgUtility(argv, &exit_code);

  return ran_ok && exit_code == EXIT_SUCCESS;
#endif
}

// If |scheme| is empty this function checks if Chrome is the default browser,
// otherwise it checks if Chrome is the default handler application for
// |scheme|.
shell_integration::DefaultWebClientState GetIsDefaultWebClient(
    const std::string& scheme) {
#if BUILDFLAG(IS_CHROMEOS)
  return shell_integration::UNKNOWN_DEFAULT;
#else
  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
                                                base::BlockingType::MAY_BLOCK);

  std::unique_ptr<base::Environment> env(base::Environment::Create());

  std::vector<std::string> argv;
  argv.push_back(kXdgSettings);
  argv.push_back("check");
  if (scheme.empty()) {
    argv.push_back(kXdgSettingsDefaultBrowser);
  } else {
    argv.push_back(kXdgSettingsDefaultSchemeHandler);
    argv.push_back(scheme);
  }
  argv.push_back(chrome::GetDesktopName(env.get()));

  std::string reply;
  int success_code;
  bool ran_ok = base::GetAppOutputWithExitCode(base::CommandLine(argv), &reply,
                                               &success_code);

  if (!ran_ok || success_code != EXIT_SUCCESS) {
    // xdg-settings failed: we can't determine or set the default browser.
    return shell_integration::UNKNOWN_DEFAULT;
  }

  // Allow any reply that starts with "yes".
  return base::StartsWith(reply, "yes", base::CompareCase::SENSITIVE)
             ? shell_integration::IS_DEFAULT
             : shell_integration::NOT_DEFAULT;
#endif
}

// https://wiki.gnome.org/Projects/GnomeShell/ApplicationBased
// The WM_CLASS property should be set to the same as the *.desktop file without
// the .desktop extension.  We cannot simply use argv[0] in this case, because
// on the stable channel, the executable name is google-chrome-stable, but the
// desktop file is google-chrome.desktop.
std::string GetDesktopBaseName(const std::string& desktop_file_name) {
  static const char kDesktopExtension[] = ".desktop";
  auto remainder = base::RemoveSuffix(desktop_file_name, kDesktopExtension);
  return remainder ? std::string(*remainder) : desktop_file_name;
}

#if defined(USE_GLIB)
// Quote a string such that it appears as one verbatim argument for the Exec
// key in a desktop file.
std::string QuoteArgForDesktopFileExec(const std::string& arg) {
  // http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html

  // Literal % characters must be escaped as %%.
  std::string arg_copy = arg;
  RE2::GlobalReplace(&arg_copy, "%", "%%");

  // Quoting is only necessary if the argument has a reserved character.
  if (arg_copy.find_first_of(" \t\n\"'\\><~|&;$*?#()`") == std::string::npos) {
    return arg_copy;  // No quoting necessary.
  }

  std::string quoted = "\"";
  for (size_t i = 0; i < arg_copy.size(); ++i) {
    // Note that the set of backslashed characters is smaller than the
    // set of reserved characters.
    switch (arg_copy[i]) {
      case '"':
      case '`':
      case '$':
      case '\\':
        quoted += '\\';
        break;
    }
    quoted += arg_copy[i];
  }
  quoted += '"';

  return quoted;
}

// Quote a command line so it is suitable for use as the Exec key in a desktop
// file. Note: This should be used instead of GetCommandLineString, which does
// not properly quote the string; this function is designed for the Exec key.
std::string QuoteCommandLineForDesktopFileExec(
    const base::CommandLine& command_line) {
  // http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html

  std::string quoted_path;
  const base::CommandLine::StringVector& argv = command_line.argv();
  for (auto i = argv.begin(); i != argv.end(); ++i) {
    if (i != argv.begin())
      quoted_path += " ";
    quoted_path += QuoteArgForDesktopFileExec(*i);
  }

  return quoted_path;
}
#endif

#if defined(USE_GLIB)
const char kDesktopEntry[] = "Desktop Entry";

void SetActionsForDesktopApplication(
    const base::CommandLine& command_line,
    GKeyFile* key_file,
    std::set<web_app::DesktopActionInfo> action_info) {
  if (action_info.empty())
    return;

  std::vector<std::string> action_ids;
  for (const auto& info : action_info) {
    action_ids.push_back(info.id);
  }

  std::string joined_action_ids = base::JoinString(action_ids, ";");
  g_key_file_set_string(key_file, kDesktopEntry, "Actions",
                        joined_action_ids.c_str());

  for (const auto& info : action_info) {
    std::string section_title = "Desktop Action " + info.id;
    g_key_file_set_string(key_file, section_title.c_str(), "Name",
                          info.name.c_str());

    std::string launch_url_str = info.exec_launch_url.spec();
    base::CommandLine current_cmd(command_line);
    current_cmd.AppendSwitchASCII(switches::kAppLaunchUrlForShortcutsMenuItem,
                                  launch_url_str);

    g_key_file_set_string(
        key_file, section_title.c_str(), "Exec",
        QuoteCommandLineForDesktopFileExec(current_cmd).c_str());
  }
}
#endif

base::FilePath GetDesktopFileForDefaultSchemeHandler(base::Environment* env,
                                                     const GURL& url) {
  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
                                                base::BlockingType::MAY_BLOCK);

  std::vector<std::string> argv;
  argv.push_back(shell_integration_linux::kXdgSettings);
  argv.push_back("get");
  argv.push_back(shell_integration_linux::kXdgSettingsDefaultSchemeHandler);
  argv.push_back(url.GetScheme());
  argv.push_back(chrome::GetDesktopName(env));

  std::string desktop_file_name;
  if (base::GetAppOutput(base::CommandLine(argv), &desktop_file_name) &&
      desktop_file_name.find(".desktop") != std::string::npos) {
    // Remove trailing newline
    desktop_file_name.erase(desktop_file_name.length() - 1, 1);
    return base::FilePath(desktop_file_name);
  }

  return base::FilePath();
}

std::string GetDesktopEntryStringValueFromFromDesktopFile(
    const std::string& key,
    const std::string& shortcut_contents) {
  std::string key_value;
#if defined(USE_GLIB)
  // An empty file causes a crash with glib <= 2.32, so special case here.
  if (shortcut_contents.empty())
    return key_value;

  GKeyFile* key_file = g_key_file_new();
  GError* err = nullptr;
  if (!g_key_file_load_from_data(key_file, shortcut_contents.c_str(),
                                 shortcut_contents.size(), G_KEY_FILE_NONE,
                                 &err)) {
    LOG(WARNING) << "Unable to read desktop file template: " << err->message;
    g_error_free(err);
    g_key_file_free(key_file);
    return key_value;
  }

  char* key_c_string =
      g_key_file_get_string(key_file, kDesktopEntry, key.c_str(), &err);
  if (key_c_string) {
    key_value = key_c_string;
    g_free(key_c_string);
  } else {
    g_error_free(err);
  }

  g_key_file_free(key_file);
#else
  NOTIMPLEMENTED();
#endif

  return key_value;
}
}  // namespace

std::string GetDirectLaunchMimeTypeHandler() {
  std::string scheme = shell_integration::GetDirectLaunchUrlScheme();
  if (scheme.empty()) {
    return "";
  }
  return base::StrCat({"x-scheme-handler/", scheme, ";"});
}


// Allows LaunchXdgUtility to join a process.
// thread_restrictions.h assumes it to be in shell_integration_linux namespace.
class [[maybe_unused, nodiscard]] LaunchXdgUtilityScopedAllowBaseSyncPrimitives
    : public base::ScopedAllowBaseSyncPrimitives{};

bool LaunchXdgUtility(const std::vector<std::string>& argv, int* exit_code) {
  // xdg-settings internally runs xdg-mime, which uses mv to move newly-created
  // files on top of originals after making changes to them. In the event that
  // the original files are owned by another user (e.g. root, which can happen
  // if they are updated within sudo), mv will prompt the user to confirm if
  // standard input is a terminal (otherwise it just does it). So make sure it's
  // not, to avoid locking everything up waiting for mv.
  *exit_code = EXIT_FAILURE;
  int devnull = open("/dev/null", O_RDONLY);
  if (devnull < 0)
    return false;

  base::LaunchOptions options;
  options.fds_to_remap.push_back(std::make_pair(devnull, STDIN_FILENO));
  base::Process process = base::LaunchProcess(argv, options);
  close(devnull);
  if (!process.IsValid())
    return false;
  LaunchXdgUtilityScopedAllowBaseSyncPrimitives allow_base_sync_primitives;
  return process.WaitForExit(exit_code);
}

std::string GetWMClassFromAppName(std::string app_name) {
  base::i18n::ReplaceIllegalCharactersInPath(&app_name, '_');
  base::TrimString(app_name, "_", &app_name);
  return app_name;
}

std::string GetXdgAppIdForWebApp(std::string app_name,
                                 const base::FilePath& profile_path) {
  auto remainder = base::RemovePrefix(app_name, web_app::kCrxAppPrefix);
  if (remainder) {
    app_name = std::string(*remainder);
  }
  std::string app_id = GetDesktopBaseName(
      web_app::GetAppDesktopShortcutFilename(profile_path, app_name)
          .AsUTF8Unsafe());

  std::unique_ptr<base::Environment> env = base::Environment::Create();
  std::optional<std::string> desktop_id_prefix =
      env->GetVar(kWebAppDesktopIdPrefixEnvVar);
  if (desktop_id_prefix.has_value()) {
    app_id.insert(0, desktop_id_prefix.value());
  }

  return app_id;
}

namespace internal {

std::string GetDesktopEntryStringValueFromFromDesktopFileForTest(
    const std::string& key,
    const std::string& shortcut_contents) {
  return shell_integration_linux::GetDesktopEntryStringValueFromFromDesktopFile(
      key, shortcut_contents);
}

// Get the value of NoDisplay from the [Desktop Entry] section of a .desktop
// file, given in |shortcut_contents|. If the key is not found, returns false.
bool GetNoDisplayFromDesktopFile(const std::string& shortcut_contents) {
  std::string nodisplay_value =
      shell_integration_linux::GetDesktopEntryStringValueFromFromDesktopFile(
          "NoDisplay", shortcut_contents);
  return nodisplay_value == "true";
}

// Gets the path to the Chrome executable or wrapper script.
// Returns an empty path if the executable path could not be found, which should
// never happen.
base::FilePath GetChromeExePath() {
  // Try to get the name of the wrapper script that launched Chrome.
  std::unique_ptr<base::Environment> environment(base::Environment::Create());
  std::optional<std::string> wrapper_script =
      environment->GetVar("CHROME_WRAPPER");
  if (wrapper_script.has_value()) {
    return base::FilePath(wrapper_script.value());
  }

  // Just return the name of the executable path for Chrome.
  base::FilePath chrome_exe_path;
  base::PathService::Get(base::FILE_EXE, &chrome_exe_path);
  return chrome_exe_path;
}

std::string GetProgramClassName(const base::CommandLine& command_line,
                                const std::string& desktop_file_name) {
  std::string class_name = GetDesktopBaseName(desktop_file_name);
  std::string user_data_dir =
      command_line.GetSwitchValueNative(switches::kUserDataDir);
  // If the user launches with e.g. --user-data-dir=/tmp/my-user-data, set the
  // class name to "Chrome (/tmp/my-user-data)".  The class name will show up in
  // the alt-tab list in gnome-shell if you're running a binary that doesn't
  // have a matching .desktop file.
  return user_data_dir.empty()
             ? class_name
             : class_name + " (" + user_data_dir + ")";
}

std::string GetProgramClassClass(const base::CommandLine& command_line,
                                 const std::string& desktop_file_name) {
  if (command_line.HasSwitch(switches::kWmClass))
    return command_line.GetSwitchValueASCII(switches::kWmClass);
  std::string desktop_base_name = GetDesktopBaseName(desktop_file_name);
  if (auto* platform_utils =
          ui::OzonePlatform::GetInstance()->GetPlatformUtils()) {
    return platform_utils->GetWmWindowClass(desktop_base_name);
  }
  if (!desktop_base_name.empty()) {
    // Capitalize the first character like gtk does.
    desktop_base_name[0] = base::ToUpperASCII(desktop_base_name[0]);
  }
  return desktop_base_name;
}

}  // namespace internal

std::string GetProgramClassName() {
  std::unique_ptr<base::Environment> env(base::Environment::Create());
  return internal::GetProgramClassName(*base::CommandLine::ForCurrentProcess(),
                                       chrome::GetDesktopName(env.get()));
}

std::string GetProgramClassClass() {
  std::unique_ptr<base::Environment> env(base::Environment::Create());
  return internal::GetProgramClassClass(*base::CommandLine::ForCurrentProcess(),
                                        chrome::GetDesktopName(env.get()));
}

std::string GetIconName() {
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
  return "google-chrome";
#else  // BUILDFLAG(CHROMIUM_BRANDING)
  return "chromium-browser";
#endif
}

bool GetExistingShortcutContents(base::Environment* env,
                                 const base::FilePath& desktop_filename,
                                 std::string* output) {
  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
                                                base::BlockingType::MAY_BLOCK);

  std::vector<base::FilePath> search_paths =
      base::nix::GetXDGDataSearchLocations(env);

  for (std::vector<base::FilePath>::const_iterator i = search_paths.begin();
       i != search_paths.end(); ++i) {
    base::FilePath path = i->Append("applications").Append(desktop_filename);
    VLOG(1) << "Looking for desktop file in " << path.value();
    if (base::PathExists(path)) {
      VLOG(1) << "Found desktop file at " << path.value();
      return base::ReadFileToString(path, output);
    }
  }

  return false;
}

std::optional<base::SafeBaseName> GetUniqueWebShortcutFilename(
    const std::string& name) {
  // Use a prefix, because xdg-desktop-menu requires it.
  std::string filename =
      base::StrCat({chrome::kBrowserProcessExecutableName, "-", name});
  base::i18n::ReplaceIllegalCharactersInPath(&filename, '_');
  base::ReplaceChars(filename, " \n\r", "_", &filename);
  std::optional<base::SafeBaseName> opt_base_name_no_extension =
      base::SafeBaseName::Create(filename);
  // There should never be any path separators, as
  // `ReplaceIllegalCharactersInPath` removes them.
  CHECK(opt_base_name_no_extension);
  base::FilePath base_name_no_extension = opt_base_name_no_extension->path();

  base::FilePath desktop_path;
  if (!base::PathService::Get(base::DIR_USER_DESKTOP, &desktop_path)) {
    return std::nullopt;
  }

  // Using DCHECK because this is a slightly expensive check. This should be
  // guaranteed by `ReplaceIllegalCharactersInPath`.
  DCHECK(base::i18n::IsFilenameLegal(base_name_no_extension.AsUTF16Unsafe()));

  base::FilePath filepath =
      desktop_path.Append(base_name_no_extension.AddExtension(".desktop"));
  return base::SafeBaseName::Create(
      base::GetUniquePathWithSuffixFormat(filepath, "_%d"));
}

std::vector<base::FilePath> GetExistingProfileShortcutFilenames(
    const base::FilePath& profile_path,
    const base::FilePath& directory) {
  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
                                                base::BlockingType::MAY_BLOCK);

  // Use a prefix, because xdg-desktop-menu requires it.
  std::string prefix(chrome::kBrowserProcessExecutableName);
  prefix.append("-");
  std::string suffix("-");
  suffix.append(profile_path.BaseName().value());
  base::i18n::ReplaceIllegalCharactersInPath(&suffix, '_');
  // Spaces in filenames break xdg-desktop-menu
  // (see https://bugs.freedesktop.org/show_bug.cgi?id=66605).
  base::ReplaceChars(suffix, " ", "_", &suffix);
  std::string glob = prefix + "*" + suffix + ".desktop";

  base::FileEnumerator files(directory, false, base::FileEnumerator::FILES,
                             glob);
  base::FilePath shortcut_file = files.Next();
  std::vector<base::FilePath> shortcut_paths;
  while (!shortcut_file.empty()) {
    shortcut_paths.push_back(shortcut_file.BaseName());
    shortcut_file = files.Next();
  }
  return shortcut_paths;
}

std::string GetDesktopFileContents(
    const base::FilePath& chrome_exe_path,
    const std::string& app_name,
    const GURL& url,
    const std::string& extension_id,
    const std::u16string& title,
    const std::string& icon_name,
    const base::FilePath& profile_path,
    const std::string& categories,
    const std::string& mime_type,
    bool no_display,
    const std::string& run_on_os_login_mode,
    std::set<web_app::DesktopActionInfo> action_info) {
  base::CommandLine cmd_line = shell_integration::CommandLineArgsForLauncher(
      url, extension_id, profile_path, run_on_os_login_mode);
  cmd_line.SetProgram(chrome_exe_path);
  return GetDesktopFileContentsForCommand(
      cmd_line, app_name, url, title, icon_name, categories, mime_type,
      no_display, GetDirectLaunchMimeTypeHandler(), std::move(action_info));
}

std::string GetDesktopFileContentsForCommand(
    const base::CommandLine& command_line,
    const std::string& app_name,
    const GURL& url,
    const std::u16string& title,
    const std::string& icon_name,
    const std::string& categories,
    const std::string& mime_type,
    bool no_display,
    std::string_view extra_mime_types,
    std::set<web_app::DesktopActionInfo> action_info) {
#if defined(USE_GLIB)
  // See http://standards.freedesktop.org/desktop-entry-spec/latest/
  GKeyFile* key_file = g_key_file_new();

  // Set keys with fixed values.
  g_key_file_set_string(key_file, kDesktopEntry, "Version", "1.0");
  g_key_file_set_string(key_file, kDesktopEntry, "Terminal", "false");
  g_key_file_set_string(key_file, kDesktopEntry, "Type", "Application");

  // Set the "Name" key.
  std::string final_title = base::UTF16ToUTF8(title);
  // Make sure no endline characters can slip in and possibly introduce
  // additional lines (like Exec, which makes it a security risk). Also
  // use the URL as a default when the title is empty.
  if (final_title.empty() ||
      final_title.find("\n") != std::string::npos ||
      final_title.find("\r") != std::string::npos) {
    final_title = url.spec();
  }
  g_key_file_set_string(key_file, kDesktopEntry, "Name", final_title.c_str());

  base::CommandLine modified_command_line(command_line);

  // Set the "MimeType" key.
  bool include_U = false;
  if (!mime_type.empty() && mime_type.find("\n") == std::string::npos &&
      mime_type.find("\r") == std::string::npos) {
    std::string full_mime_type = mime_type;
    if (!extra_mime_types.empty()) {
      if (!full_mime_type.empty() && full_mime_type.back() != ';') {
        full_mime_type += ';';
      }
      base::StrAppend(&full_mime_type, {extra_mime_types});
    }
    g_key_file_set_string(key_file, kDesktopEntry, "MimeType",
                          full_mime_type.c_str());

    // Some Linux Desktop Environments don't show file handlers unless they
    // specify where to place file arguments.
    // Note: We only include this parameter if the application is actually able
    // to handle files, to prevent it showing up in the list of all applications
    // which can handle files.
    include_U = true;
  }

  // Set the "Exec" key.
  std::string final_path =
      QuoteCommandLineForDesktopFileExec(modified_command_line);
  if (include_U) {
    final_path += " %U";
  }
  g_key_file_set_string(key_file, kDesktopEntry, "Exec", final_path.c_str());

  // Set the "Icon" key.
  if (!icon_name.empty()) {
    g_key_file_set_string(key_file, kDesktopEntry, "Icon", icon_name.c_str());
  } else {
    g_key_file_set_string(key_file, kDesktopEntry, "Icon",
                          GetIconName().c_str());
  }

  // Set the "Categories" key.
  if (!categories.empty()) {
    g_key_file_set_string(
        key_file, kDesktopEntry, "Categories", categories.c_str());
  }

  // Set the "NoDisplay" key.
  if (no_display)
    g_key_file_set_string(key_file, kDesktopEntry, "NoDisplay", "true");

  std::string wmclass = GetWMClassFromAppName(app_name);
  g_key_file_set_string(key_file, kDesktopEntry, "StartupWMClass",
                        wmclass.c_str());

  SetActionsForDesktopApplication(command_line, key_file,
                                  std::move(action_info));

  std::string output_buffer = GetFileContents(key_file);

  base::ReplaceSubstringsAfterOffset(&output_buffer, 0, "@@URI_SCHEME@@",
                                     shell_integration::GetDirectLaunchUrlScheme());

  g_key_file_free(key_file);
  return output_buffer;
#else
  NOTIMPLEMENTED();
  return std::string();
#endif
}

std::string GetDesktopFileContentsForUrlShortcut(
    const std::string& title,
    const GURL& url,
    const base::FilePath& icon_path,
    const base::FilePath& profile_path) {
  CHECK(url.is_valid());
  CHECK(!profile_path.empty());
  CHECK(!icon_path.empty());
#if defined(USE_GLIB)
  base::FilePath chrome_exe_path =
      shell_integration_linux::internal::GetChromeExePath();
  base::CommandLine command_line =
      shell_integration::CommandLineArgsForUrlShortcut(chrome_exe_path,
                                                       profile_path, url);

  // See http://standards.freedesktop.org/desktop-entry-spec/latest/
  GKeyFile* key_file = g_key_file_new();

  // Set keys with fixed values.
  g_key_file_set_string(key_file, kDesktopEntry, "Version", "1.0");
  g_key_file_set_string(key_file, kDesktopEntry, "Type", "Application");

  std::string final_title;
  // Make sure no endline characters can slip in and possibly introduce
  // additional lines (like Exec, which makes it a security risk).
  base::ReplaceChars(title, "\n\r", "", &final_title);
  if (final_title.empty()) {
    final_title = url.spec();
  }
  g_key_file_set_string(key_file, kDesktopEntry, "Name", final_title.c_str());

  std::string final_path = QuoteCommandLineForDesktopFileExec(command_line);
  g_key_file_set_string(key_file, kDesktopEntry, "Exec", final_path.c_str());

  // Set the "Icon" key.
  g_key_file_set_string(key_file, kDesktopEntry, "Icon",
                        icon_path.value().c_str());

  g_key_file_set_string(key_file, kDesktopEntry, "URL", url.spec().c_str());

  std::string comment = l10n_util::GetStringFUTF8(
      IDS_DESKTOP_SHORTCUT_COMMENT, base::UTF8ToUTF16(url.spec()));
  g_key_file_set_string(key_file, kDesktopEntry, "Comment", comment.c_str());

  std::string output_buffer = GetFileContents(key_file);

  g_key_file_free(key_file);
  return output_buffer;
#else
  NOTREACHED();
#endif
}

std::string GetDirectoryFileContents(const std::u16string& title,
                                     const std::string& icon_name) {
#if defined(USE_GLIB)
  // See http://standards.freedesktop.org/desktop-entry-spec/latest/
  GKeyFile* key_file = g_key_file_new();

  g_key_file_set_string(key_file, kDesktopEntry, "Version", "1.0");
  g_key_file_set_string(key_file, kDesktopEntry, "Type", "Directory");
  std::string final_title = base::UTF16ToUTF8(title);
  g_key_file_set_string(key_file, kDesktopEntry, "Name", final_title.c_str());
  if (!icon_name.empty()) {
    g_key_file_set_string(key_file, kDesktopEntry, "Icon", icon_name.c_str());
  } else {
    g_key_file_set_string(key_file, kDesktopEntry, "Icon",
                          GetIconName().c_str());
  }

  std::string output_buffer = GetFileContents(key_file);

  g_key_file_free(key_file);
  return output_buffer;
#else
  NOTIMPLEMENTED();
  return std::string();
#endif
}

base::FilePath GetMimeTypesRegistrationFilename(
    const base::FilePath& profile_path,
    const webapps::AppId& app_id) {
  DCHECK(!profile_path.empty() && !app_id.empty());

  // Use a prefix to clearly group files created by Chrome.
  std::string filename = base::StringPrintf(
      "%s-%s-%s%s", chrome::kBrowserProcessExecutableName, app_id.c_str(),
      profile_path.BaseName().value().c_str(), ".xml");

  // Replace illegal characters and spaces in |filename|.
  base::i18n::ReplaceIllegalCharactersInPath(&filename, '_');
  base::ReplaceChars(filename, " ", "_", &filename);

  return base::FilePath(filename);
}

std::string GetMimeTypesRegistrationFileContents(
    const apps::FileHandlers& file_handlers) {
  XmlWriter writer;

  writer.StartWriting();
  writer.StartElement("mime-info");
  writer.AddAttribute("xmlns",
                      "http://www.freedesktop.org/standards/shared-mime-info");

  for (const auto& file_handler : file_handlers) {
    for (const auto& accept_entry : file_handler.accept) {
      writer.StartElement("mime-type");
      writer.AddAttribute("type", accept_entry.mime_type);

      if (!file_handler.display_name.empty()) {
        writer.WriteElement("comment",
                            base::UTF16ToUTF8(file_handler.display_name));
      }
      for (const auto& file_extension : accept_entry.file_extensions) {
        writer.StartElement("glob");
        writer.AddAttribute("pattern", "*" + file_extension);
        writer.EndElement();  // "glob"
      }
      writer.EndElement();  // "mime-type"
    }
  }

  writer.EndElement();  // "mime-info"
  writer.StopWriting();
  return writer.GetWrittenString();
}

}  // namespace shell_integration_linux

namespace shell_integration {

bool SetAsDefaultBrowser() {
  return shell_integration_linux::SetDefaultWebClient(std::string());
}

bool SetAsDefaultClientForScheme(const std::string& scheme) {
  return shell_integration_linux::SetDefaultWebClient(scheme);
}

std::u16string GetApplicationNameForScheme(const GURL& url) {
  std::unique_ptr<base::Environment> env(base::Environment::Create());

  std::string desktop_file_contents;
  std::string application_name;
  base::FilePath desktop_filepath =
      shell_integration_linux::GetDesktopFileForDefaultSchemeHandler(env.get(),
                                                                     url);
  if (shell_integration_linux::GetExistingShortcutContents(
          env.get(), desktop_filepath, &desktop_file_contents)) {
    application_name =
        shell_integration_linux::GetDesktopEntryStringValueFromFromDesktopFile(
            "Name", desktop_file_contents);
  }

  return application_name.empty() ? u"xdg-open"
                                  : base::UTF8ToUTF16(application_name);
}

DefaultWebClientState GetDefaultBrowser() {
  return shell_integration_linux::GetIsDefaultWebClient(std::string());
}

bool IsFirefoxDefaultBrowser() {
  std::vector<std::string> argv;
  argv.push_back(shell_integration_linux::kXdgSettings);
  argv.push_back("get");
  argv.push_back(shell_integration_linux::kXdgSettingsDefaultBrowser);

  std::string browser;
  // We don't care about the return value here.
  base::GetAppOutput(base::CommandLine(argv), &browser);
  return browser.find("irefox") != std::string::npos;
}

DefaultWebClientState IsDefaultClientForScheme(const std::string& scheme) {
  return shell_integration_linux::GetIsDefaultWebClient(scheme);
}

std::string GetDirectLaunchUrlScheme() {
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
  // On Linux, channel determination is static and tied to the installed
  // package, unlike macOS and Windows which support "floating" channels.
  // For security reasons, the custom URI scheme handler is only registered
  // for Google Chrome in stable and extended stable channels. For other
  // channels (beta, dev, canary), an empty string is returned to omit the
  // handler from the .desktop file.
  switch (chrome::GetChannel()) {
    case version_info::Channel::CANARY:
    case version_info::Channel::DEV:
    case version_info::Channel::BETA:
      return "";
    case version_info::Channel::STABLE:
    case version_info::Channel::UNKNOWN:
      return "google-chrome";
  }
#else
  return "chromium";
#endif
}

namespace internal {

DefaultWebClientSetPermission GetPlatformSpecificDefaultWebClientSetPermission(
    WebClientSetMethod method) {
  return SET_DEFAULT_UNATTENDED;
}

}  // namespace internal

}  // namespace shell_integration
