// Copyright 2013 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/ash/extensions/file_manager/private_api_tasks.h"
#include <stddef.h>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "ash/webui/system_apps/public/system_web_app_type.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "chrome/browser/ash/drive/file_system_util.h"
#include "chrome/browser/ash/file_manager/app_service_file_tasks.h"
#include "chrome/browser/ash/file_manager/file_tasks.h"
#include "chrome/browser/ash/file_manager/fileapi_util.h"
#include "chrome/browser/ash/file_manager/filesystem_api_util.h"
#include "chrome/browser/ash/fileapi/file_system_backend.h"
#include "chrome/browser/extensions/chrome_extension_function_details.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/common/extensions/api/file_manager_private.h"
#include "chrome/common/extensions/api/file_manager_private_internal.h"
#include "extensions/browser/api/file_handlers/directory_util.h"
#include "extensions/browser/api/file_handlers/mime_util.h"
#include "extensions/browser/entry_info.h"
#include "storage/browser/file_system/file_system_context.h"
#include "storage/browser/file_system/file_system_url.h"
namespace extensions {
namespace {
// Error messages.
constexpr char kInvalidTaskType[] = "Invalid task type: ";
constexpr char kInvalidFileUrl[] = "Invalid file URL";
// Make a set of unique filename suffixes out of the list of file URLs.
std::set<std::string> GetUniqueSuffixes(
const std::vector<std::string>& file_urls,
const storage::FileSystemContext* context) {
std::set<std::string> suffixes;
for (const auto& file_url : file_urls) {
const storage::FileSystemURL url =
context->CrackURLInFirstPartyContext(GURL{file_url});
if (!url.is_valid() || url.path().empty()) {
return {};
}
// We'll skip empty suffixes.
if (!url.path().Extension().empty()) {
suffixes.insert(url.path().Extension());
}
}
return suffixes;
}
// Make a set of unique MIME types out of the list of MIME types.
std::set<std::string> GetUniqueMimeTypes(
const std::vector<std::string>& mime_type_list) {
std::set<std::string> mime_types;
for (const auto& mime_type : mime_type_list) {
// We'll skip empty MIME types and existing MIME types.
if (!mime_type.empty()) {
mime_types.insert(mime_type);
}
}
return mime_types;
}
namespace api_fmp = extensions::api::file_manager_private;
namespace api_fmp_internal = extensions::api::file_manager_private_internal;
std::optional<api_fmp::PolicyDefaultHandlerStatus>
RemapPolicyDefaultHandlerStatus(
const std::optional<file_manager::file_tasks::PolicyDefaultHandlerStatus>&
status) {
if (!status) {
return {};
}
switch (*status) {
case file_manager::file_tasks::PolicyDefaultHandlerStatus::
kDefaultHandlerAssignedByPolicy:
return api_fmp::PolicyDefaultHandlerStatus::
kDefaultHandlerAssignedByPolicy;
case file_manager::file_tasks::PolicyDefaultHandlerStatus::
kIncorrectAssignment:
return api_fmp::PolicyDefaultHandlerStatus::kIncorrectAssignment;
}
}
} // namespace
FileManagerPrivateInternalExecuteTaskFunction::
FileManagerPrivateInternalExecuteTaskFunction() = default;
ExtensionFunction::ResponseAction
FileManagerPrivateInternalExecuteTaskFunction::Run() {
using api_fmp_internal::ExecuteTask::Params;
using api_fmp_internal::ExecuteTask::Results::Create;
const std::optional<Params> params = Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(params);
file_manager::file_tasks::TaskType task_type =
file_manager::file_tasks::StringToTaskType(params->descriptor.task_type);
if (task_type == file_manager::file_tasks::TASK_TYPE_UNKNOWN) {
return RespondNow(Error(kInvalidTaskType + params->descriptor.task_type));
}
file_manager::file_tasks::TaskDescriptor task(
params->descriptor.app_id, task_type, params->descriptor.action_id);
if (params->urls.empty()) {
return RespondNow(ArgumentList(Create(api_fmp::TaskResult::kEmpty)));
}
Profile* const profile = Profile::FromBrowserContext(browser_context());
const scoped_refptr<storage::FileSystemContext> file_system_context =
file_manager::util::GetFileSystemContextForRenderFrameHost(
profile, render_frame_host());
std::vector<storage::FileSystemURL> urls;
for (const auto& url_param : params->urls) {
const storage::FileSystemURL url =
file_system_context->CrackURLInFirstPartyContext(GURL{url_param});
if (!ash::FileSystemBackend::CanHandleURL(url)) {
return RespondNow(Error(kInvalidFileUrl));
}
urls.push_back(url);
}
const bool result = file_manager::file_tasks::ExecuteFileTask(
profile, task, urls,
base::BindOnce(
&FileManagerPrivateInternalExecuteTaskFunction::OnTaskExecuted,
this));
if (!result) {
return RespondNow(Error("ExecuteFileTask failed"));
}
return RespondLater();
}
void FileManagerPrivateInternalExecuteTaskFunction::OnTaskExecuted(
api_fmp::TaskResult result,
std::string failure_reason) {
auto result_list = api_fmp_internal::ExecuteTask::Results::Create(result);
if (result == api_fmp::TaskResult::kFailed) {
Respond(Error("Task result failed: " + failure_reason));
} else {
Respond(ArgumentList(std::move(result_list)));
}
}
FileManagerPrivateInternalGetFileTasksFunction::
FileManagerPrivateInternalGetFileTasksFunction() = default;
FileManagerPrivateInternalGetFileTasksFunction::
~FileManagerPrivateInternalGetFileTasksFunction() = default;
ExtensionFunction::ResponseAction
FileManagerPrivateInternalGetFileTasksFunction::Run() {
using api_fmp_internal::GetFileTasks::Params;
const std::optional<Params> params = Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(params);
if (params->urls.empty()) {
return RespondNow(Error("No URLs provided"));
}
if (params->dlp_source_urls.size() != params->urls.size()) {
return RespondNow(Error("Mismatching URLs and DLP source URLs provided"));
}
Profile* const profile = Profile::FromBrowserContext(browser_context());
const scoped_refptr<storage::FileSystemContext> file_system_context =
file_manager::util::GetFileSystemContextForRenderFrameHost(
profile, render_frame_host());
// Collect all the URLs, convert them to GURLs, and crack all the urls into
// file paths.
for (const auto& url_param : params->urls) {
const GURL url{url_param};
storage::FileSystemURL file_system_url(
file_system_context->CrackURLInFirstPartyContext(url));
if (!ash::FileSystemBackend::CanHandleURL(file_system_url)) {
continue;
}
urls_.push_back(url);
local_paths_.push_back(file_system_url.path());
}
dlp_source_urls_ = std::move(params->dlp_source_urls);
mime_type_collector_ =
std::make_unique<app_file_handler_util::MimeTypeCollector>(profile);
mime_type_collector_->CollectForLocalPaths(
local_paths_,
base::BindOnce(
&FileManagerPrivateInternalGetFileTasksFunction::OnMimeTypesCollected,
this));
return RespondLater();
}
void FileManagerPrivateInternalGetFileTasksFunction::OnMimeTypesCollected(
std::unique_ptr<std::vector<std::string>> mime_types) {
is_directory_collector_ =
std::make_unique<app_file_handler_util::IsDirectoryCollector>(
Profile::FromBrowserContext(browser_context()));
is_directory_collector_->CollectForEntriesPaths(
local_paths_,
base::BindOnce(&FileManagerPrivateInternalGetFileTasksFunction::
OnAreDirectoriesAndMimeTypesCollected,
this, std::move(mime_types)));
}
void FileManagerPrivateInternalGetFileTasksFunction::
OnAreDirectoriesAndMimeTypesCollected(
std::unique_ptr<std::vector<std::string>> mime_types,
std::unique_ptr<std::set<base::FilePath>> directory_paths) {
std::vector<EntryInfo> entries;
for (size_t i = 0; i < local_paths_.size(); ++i) {
entries.emplace_back(
local_paths_[i], (*mime_types)[i],
directory_paths->find(local_paths_[i]) != directory_paths->end());
}
file_manager::file_tasks::FindAllTypesOfTasks(
Profile::FromBrowserContext(browser_context()), entries, urls_,
dlp_source_urls_,
base::BindOnce(
&FileManagerPrivateInternalGetFileTasksFunction::OnFileTasksListed,
this));
}
void FileManagerPrivateInternalGetFileTasksFunction::OnFileTasksListed(
std::unique_ptr<file_manager::file_tasks::ResultingTasks> resulting_tasks) {
// Convert the tasks into JSON compatible objects.
std::vector<api_fmp::FileTask> results;
for (const auto& task : resulting_tasks->tasks) {
api_fmp::FileTask converted;
converted.descriptor.app_id = task.task_descriptor.app_id;
converted.descriptor.task_type =
TaskTypeToString(task.task_descriptor.task_type);
converted.descriptor.action_id = task.task_descriptor.action_id;
if (!task.icon_url.is_empty()) {
converted.icon_url = task.icon_url.spec();
}
converted.title = task.task_title;
converted.is_default = task.is_default;
converted.is_generic_file_handler = task.is_generic_file_handler;
converted.is_dlp_blocked = task.is_dlp_blocked;
results.push_back(std::move(converted));
}
api_fmp::ResultingTasks api_resulting_tasks;
api_resulting_tasks.tasks = std::move(results);
if (auto status = RemapPolicyDefaultHandlerStatus(
resulting_tasks->policy_default_handler_status)) {
api_resulting_tasks.policy_default_handler_status = *status;
}
Respond(ArgumentList(api_fmp_internal::GetFileTasks::Results::Create(
std::move(api_resulting_tasks))));
}
ExtensionFunction::ResponseAction
FileManagerPrivateInternalSetDefaultTaskFunction::Run() {
using api_fmp_internal::SetDefaultTask::Params;
const std::optional<Params> params = Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(params);
Profile* profile = Profile::FromBrowserContext(browser_context());
const scoped_refptr<storage::FileSystemContext> file_system_context =
file_manager::util::GetFileSystemContextForRenderFrameHost(
profile, render_frame_host());
const std::set<std::string> suffixes =
GetUniqueSuffixes(params->urls, file_system_context.get());
const std::set<std::string> mime_types =
GetUniqueMimeTypes(params->mime_types);
// If there weren't any mime_types, and all the suffixes were blank,
// then we "succeed", but don't actually associate with anything.
// Otherwise, any time we set the default on a file with no extension
// on the local drive, we'd fail.
// TODO(gspencer): Fix file manager so that it never tries to set default in
// cases where extensionless local files are part of the selection.
if (suffixes.empty() && mime_types.empty()) {
return RespondNow(WithArguments(true));
}
file_manager::file_tasks::TaskType task_type =
file_manager::file_tasks::StringToTaskType(params->descriptor.task_type);
if (task_type == file_manager::file_tasks::TASK_TYPE_UNKNOWN) {
return RespondNow(Error(kInvalidTaskType + params->descriptor.task_type));
}
file_manager::file_tasks::TaskDescriptor descriptor(
params->descriptor.app_id, task_type, params->descriptor.action_id);
file_manager::file_tasks::UpdateDefaultTask(profile, descriptor, suffixes,
mime_types);
return RespondNow(NoArguments());
}
} // namespace extensions