chromium/chrome/browser/extensions/file_handlers/web_file_handlers_permission_handler.cc

// Copyright 2023 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/extensions/file_handlers/web_file_handlers_permission_handler.h"

#include <optional>

#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/extensions/extensions_dialogs.h"
#include "chrome/browser/web_applications/web_app_utils.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/pref_types.h"
#include "extensions/common/extension.h"
#include "extensions/common/features/feature.h"
#include "extensions/common/features/feature_provider.h"
#include "extensions/common/manifest_handlers/web_file_handlers_info.h"

namespace extensions {

namespace {

constexpr PrefMap kPrefShouldOpen{"web_file_handlers_should_open",
                                  PrefType::kBool,
                                  PrefScope::kExtensionSpecific};

// Get extension prefs for profile.
ExtensionPrefs* GetExtensionPrefs(Profile* profile) {
  return ExtensionPrefs::Get(profile);
}

// Get dictionary of extension prefs.
std::optional<bool> GetExtensionPrefsAsBoolean(
    ExtensionPrefs* extension_prefs,
    const ExtensionId& extension_id) {
  bool out_value = false;
  if (extension_prefs->ReadPrefAsBoolean(extension_id, kPrefShouldOpen,
                                         &out_value)) {
    return out_value;
  }
  return std::nullopt;
}

// Get extension pref result.
std::optional<bool> GetExtensionPrefsAsBoolean(const Extension& extension,
                                               Profile* profile) {
  return GetExtensionPrefsAsBoolean(GetExtensionPrefs(profile), extension.id());
}

}  // namespace

// Open file using Web File Handlers. Maybe show a file launch dialog first.
WebFileHandlersPermissionHandler::WebFileHandlersPermissionHandler(
    Profile* profile)
    : profile_(profile) {}

WebFileHandlersPermissionHandler::~WebFileHandlersPermissionHandler() = default;

void WebFileHandlersPermissionHandler::CallbackAfterDialog(
    const ExtensionId& extension_id,
    const std::vector<std::u16string>& file_types,
    CallbackType launch_callback,
    bool should_open,
    bool should_remember) {
  // Maybe remember the decision to open the file.
  if (should_remember) {
    auto* extension_prefs = GetExtensionPrefs(profile_);
    extension_prefs->SetBooleanPref(extension_id, kPrefShouldOpen, should_open);
  }

  // Maybe open the file.
  std::move(launch_callback).Run(should_open);
}

void WebFileHandlersPermissionHandler::Confirm(
    const Extension& extension,
    const std::vector<base::SafeBaseName>& base_names,
    CallbackType launch_callback) {
  CHECK(!base_names.empty());

  // Default installed extensions can skip the file launch dialog.
  // TODO(crbug.com/40269541): Remove the allowlist check after development.
  // Also, for development and manual testing purposes, the manifest_features
  // allowlist can also bypass the dialog.
  if (WebFileHandlers::CanBypassPermissionDialog(extension)) {
    std::move(launch_callback).Run(/*should_open=*/true);
    return;
  }

  auto apps_file_handlers = GetAppsFileHandlers(extension);
  const std::vector<std::u16string> file_types =
      web_app::TransformFileExtensionsForDisplay(
          apps::GetFileExtensionsFromFileHandlers(apps_file_handlers));

  // Get saved preferences that represents previous file opening decisions.
  const auto current_prefs = GetExtensionPrefsAsBoolean(extension, profile_);
  if (current_prefs.has_value()) {
    std::move(launch_callback).Run(current_prefs.value());
    return;
  }

  // Maybe open the file. Maybe remember the decision to open the file.
  auto callback_after_dialog =
      base::BindOnce(&WebFileHandlersPermissionHandler::CallbackAfterDialog,
                     weak_factory_.GetWeakPtr(), extension.id(), file_types,
                     std::move(launch_callback));

  // Present a contextual file launch dialog.
  file_handlers::ShowWebFileHandlersFileLaunchDialog(
      std::move(base_names), file_types, std::move(callback_after_dialog));
}

// static
const apps::FileHandlers WebFileHandlersPermissionHandler::GetAppsFileHandlers(
    const Extension& extension) {
  apps::FileHandlers web_file_handlers;
  auto* file_handlers = WebFileHandlers::GetFileHandlers(extension);

  if (!file_handlers) {
    return web_file_handlers;
  }

  for (const auto& web_file_handler : *file_handlers) {
    apps::FileHandler file_handler;
    file_handler.action = GURL(web_file_handler.file_handler.action);
    file_handler.display_name =
        base::UTF8ToUTF16(web_file_handler.file_handler.name);

    // Compute `accept`, which contains all mime types and file extensions.
    apps::FileHandler::Accept accept;
    for (const auto [mime_type, file_extension_list] :
         web_file_handler.file_handler.accept.additional_properties) {
      apps::FileHandler::AcceptEntry accept_entry;
      accept_entry.mime_type = mime_type;
      base::flat_set<std::string> file_extensions;
      for (const auto& file_extension : file_extension_list.GetList()) {
        file_extensions.insert(file_extension.GetString());
      }
      accept_entry.file_extensions = file_extensions;
      accept.emplace_back(accept_entry);
    }
    file_handler.accept = accept;

    // The default launch type is single client.
    file_handler.launch_type =
        web_file_handler.launch_type ==
                WebFileHandler::LaunchType::kSingleClient
            ? apps::FileHandler::LaunchType::kSingleClient
            : apps::FileHandler::LaunchType::kMultipleClients;

    web_file_handlers.emplace_back(file_handler);
  }

  return web_file_handlers;
}

}  // namespace extensions