chromium/chrome/browser/web_applications/os_integration/web_app_file_handler_registration_win.cc

// Copyright 2019 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/web_applications/os_integration/web_app_file_handler_registration.h"

#include <iterator>
#include <set>
#include <string>
#include <string_view>

#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/callback_helpers.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/thread_pool.h"
#include "chrome/browser/web_applications/os_integration/web_app_handler_registration_utils_win.h"
#include "chrome/browser/web_applications/web_app_constants.h"
#include "chrome/installer/util/shell_util.h"
#include "components/services/app_service/public/cpp/file_handler.h"

namespace web_app {

bool ShouldRegisterFileHandlersWithOs() {
  return true;
}

bool FileHandlingIconsSupportedByOs() {
  // TODO(crbug.com/40185571): implement and flip this to true.
  return false;
}

void RegisterFileHandlersWithOsTask(const webapps::AppId& app_id,
                                    const std::wstring& app_name,
                                    const base::FilePath& profile_path,
                                    const apps::FileHandlers& file_handlers,
                                    const std::wstring& app_name_extension) {
  const base::FilePath web_app_path =
      GetOsIntegrationResourcesDirectoryForApp(profile_path, app_id, GURL());
  std::optional<base::FilePath> app_specific_launcher_path =
      CreateAppLauncherFile(app_name, app_name_extension, web_app_path);
  if (!app_specific_launcher_path.has_value())
    return;

  const base::CommandLine app_specific_launcher_command = GetAppLauncherCommand(
      app_id, app_specific_launcher_path.value(), profile_path);

  std::wstring user_visible_app_name(app_name);
  user_visible_app_name.append(app_name_extension);

  const base::FilePath icon_path =
      internals::GetIconFilePath(web_app_path, base::AsString16(app_name));

  // Although this ProgId won't be used to launch web apps with file handlers,
  // it makes it easy to tell if the web app is installed in a profile, and is
  // consistent with the way web apps that handle protocols are registered.
  ShellUtil::AddApplicationClass(GetProgIdForApp(profile_path, app_id),
                                 app_specific_launcher_command,
                                 user_visible_app_name, app_name, icon_path);

  // Iterate over the file handlers and add file associations for each
  // of them, using the `display_name` in the file handler, not the ProgId for
  // the app, so that we can have separate display names in Windows Explorer and
  // separate icons for the file handler in the Open With context menu.
  std::vector<std::wstring> file_handler_progids;
  bool result = true;
  for (const auto& file_handler : file_handlers) {
    std::set<std::string> file_extensions =
        apps::GetFileExtensionsFromFileHandler(file_handler);
    std::set<std::wstring> file_extensions_wide;
    for (const auto& file_extension : file_extensions) {
      // The file extensions in apps::FileHandler include a '.' prefix, which
      // must be removed.
      file_extensions_wide.insert(base::UTF8ToWide(file_extension.substr(1)));
    }

    file_handler_progids.push_back(
        GetProgIdForAppFileHandler(profile_path, app_id, file_extensions));
    result &= ShellUtil::AddFileAssociations(
        file_handler_progids.back(), app_specific_launcher_command,
        user_visible_app_name,
        base::AsWString(std::u16string_view(file_handler.display_name)),
        icon_path, file_extensions_wide);
  }
  if (!result)
    RecordRegistration(RegistrationResult::kFailToAddFileAssociation);
  else
    RecordRegistration(RegistrationResult::kSuccess);
  // Store the app file handler ProgIds in the registry so that we can
  // unregister the app file handler ProgIds at uninstall time. At uninstall
  // time, all we have is the `app_id`.
  ShellUtil::RegisterFileHandlerProgIdsForAppId(
      GetProgIdForApp(profile_path, app_id), file_handler_progids);
}

void RegisterFileHandlersWithOs(const webapps::AppId& app_id,
                                const std::string& app_name,
                                const base::FilePath& profile_path,
                                const apps::FileHandlers& file_handlers,
                                ResultCallback callback) {
  DCHECK(!file_handlers.empty());

  const std::wstring app_name_extension =
      GetAppNameExtensionForNextInstall(app_id, profile_path);

  base::ThreadPool::PostTaskAndReply(
      FROM_HERE,
      {base::MayBlock(), base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
      base::BindOnce(&RegisterFileHandlersWithOsTask, app_id,
                     base::UTF8ToWide(app_name), profile_path, file_handlers,
                     app_name_extension),
      base::BindOnce(&CheckAndUpdateExternalInstallations, profile_path, app_id,
                     std::move(callback)));
}

void DeleteAppLauncher(const base::FilePath& launcher_path) {
  // Need to delete the app launcher file, since extension uninstall by default
  // doesn't remove the web application directory.
  base::DeleteFile(launcher_path);
}

void UnregisterFileHandlersWithOs(const webapps::AppId& app_id,
                                  const base::FilePath& profile_path,
                                  ResultCallback callback) {
  // The app-specific-launcher file name must be calculated before cleaning up
  // the registry, since the app-specific-launcher path is retrieved from the
  // registry.
  const std::wstring prog_id = GetProgIdForApp(profile_path, app_id);
  const base::FilePath app_specific_launcher_path =
      ShellUtil::GetApplicationPathForProgId(prog_id);
  // This needs to be done synchronously. If it's done via a task, protocol
  // unregistration will delete HKCU\Classes\<progid> before the task runs.
  // Information in that key is needed to unregister file associations.
  ShellUtil::DeleteFileAssociations(prog_id);

  base::ThreadPool::PostTaskAndReply(
      FROM_HERE,
      {base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
      base::BindOnce(&DeleteAppLauncher, app_specific_launcher_path),
      base::BindOnce(&CheckAndUpdateExternalInstallations, profile_path, app_id,
                     std::move(callback)));
}

}  // namespace web_app