// Copyright 2022 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/system_web_apps/apps/files_internals_ui_delegate.h"
#include "base/barrier_callback.h"
#include "base/files/file_enumerator.h"
#include "base/strings/escape.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/browser/ash/extensions/file_manager/event_router.h"
#include "chrome/browser/ash/extensions/file_manager/event_router_factory.h"
#include "chrome/browser/ash/file_manager/file_manager_pref_names.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/office_file_tasks.h"
#include "chrome/browser/ash/file_manager/path_util.h"
#include "chrome/browser/ash/fusebox/fusebox_server.h"
#include "chrome/browser/chromeos/upload_office_to_cloud/upload_office_to_cloud.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.h"
#include "chrome/common/pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.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/external_mount_points.h"
ChromeFilesInternalsUIDelegate::ChromeFilesInternalsUIDelegate(
content::WebUI* web_ui)
: web_ui_(web_ui) {}
ChromeFilesInternalsUIDelegate::~ChromeFilesInternalsUIDelegate() = default;
void ChromeFilesInternalsUIDelegate::GetDebugJSON(
base::OnceCallback<void(const base::Value&)> callback) const {
using JSONKeyValuePair =
ash::FilesInternalsDebugJSONProvider::JSONKeyValuePair;
struct NamedProvider {
std::string_view key;
ash::FilesInternalsDebugJSONProvider::FunctionPointerType function_ptr;
raw_ptr<ash::FilesInternalsDebugJSONProvider> object_ptr;
};
const NamedProvider kNamedProviders[] = {
{
"execute_file_task",
&file_manager::file_tasks::GetDebugJSONForKeyForExecuteFileTask,
nullptr,
},
{
"external_mount_points",
&storage::ExternalMountPoints::GetDebugJSONForKey,
nullptr,
},
{
"fusebox",
nullptr,
fusebox::Server::GetInstance(),
},
};
base::RepeatingCallback<void(JSONKeyValuePair)> barrier_callback =
base::BarrierCallback<JSONKeyValuePair>(
std::size(kNamedProviders),
base::BindOnce(
[](base::OnceCallback<void(const base::Value&)> callback,
std::vector<JSONKeyValuePair> key_value_pairs) {
base::Value::Dict dict;
for (auto& kvp : key_value_pairs) {
dict.Set(kvp.first, std::move(kvp.second));
}
std::move(callback).Run(base::Value(std::move(dict)));
},
std::move(callback)));
for (auto& np : kNamedProviders) {
if (np.function_ptr) {
(*np.function_ptr)(np.key, barrier_callback);
} else if (np.object_ptr) {
np.object_ptr->GetDebugJSONForKey(np.key, barrier_callback);
} else {
barrier_callback.Run(std::make_pair(np.key, base::Value()));
}
}
}
void ChromeFilesInternalsUIDelegate::GetDownloadsFSURLs(
base::OnceCallback<void(const std::string_view)> callback) const {
Profile* profile = Profile::FromWebUI(web_ui_);
base::FilePath downloads_folder =
file_manager::util::GetDownloadsFolderForProfile(profile);
static constexpr auto enumerate_on_worker_thread =
[](base::FilePath downloads_folder) -> std::vector<base::FilePath> {
base::ScopedBlockingCall scoped_blocking_call(
FROM_HERE, base::BlockingType::MAY_BLOCK);
std::vector<base::FilePath> files;
base::FileEnumerator e(downloads_folder, /*recursive=*/false,
base::FileEnumerator::FILES);
for (base::FilePath path = e.Next(); !path.empty(); path = e.Next()) {
files.push_back(path);
}
std::sort(files.begin(), files.end(),
[](const base::FilePath& l, const base::FilePath& r) {
return l.BaseName() < r.BaseName();
});
return files;
};
static constexpr auto reply_on_ui_thread =
[](Profile* profile,
base::OnceCallback<void(const std::string_view)> callback,
std::vector<base::FilePath> files) {
std::string out;
out.reserve(4096 * (1 + files.size()));
out +=
"<html><body><p>Files (not directories; non-recursive) in the "
"Downloads folder:</p><ul>\n\n";
GURL gurl;
for (const auto& file : files) {
if (file_manager::util::ConvertAbsoluteFilePathToFileSystemUrl(
profile, file, file_manager::util::GetFileManagerURL(),
&gurl)) {
out += "<li>";
out += base::EscapeForHTML(gurl.spec());
out += "\n";
}
}
out += "</ul></body></html>\n";
std::move(callback).Run(out);
};
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock()},
base::BindOnce(enumerate_on_worker_thread, downloads_folder),
base::BindOnce(reply_on_ui_thread, profile, std::move(callback)));
}
void ChromeFilesInternalsUIDelegate::GetFileTasks(
const std::string& file_system_url,
base::OnceCallback<void(const std::string_view)> callback) const {
Profile* profile = Profile::FromWebUI(web_ui_);
storage::FileSystemURL fs_url =
file_manager::util::GetFileManagerFileSystemContext(profile)
->CrackURLInFirstPartyContext(GURL(file_system_url));
if (!fs_url.is_valid()) {
std::move(callback).Run("GetFileTasks: invalid FileSystemURL");
return;
}
static constexpr auto on_find_all_types_of_tasks =
[](storage::FileSystemURL fs_url,
base::OnceCallback<void(const std::string_view)> callback,
std::unique_ptr<file_manager::file_tasks::ResultingTasks>
resulting_tasks) {
std::string out;
out.reserve(4096 * (1 + resulting_tasks->tasks.size()));
out += "<html><body>\n";
out +=
"<p>See also <a href=\"chrome://app-service-internals\">App "
"Service Internals</a> and <a "
"href=\"chrome://extensions-internals/\">Extensions "
"Internals</a>.</p>\n<p>FileSystemURL: <b>";
out += base::EscapeForHTML(fs_url.ToGURL().spec());
out +=
"</b></p>\n<p>Note that some Task icons and titles are separately "
"<a "
"href=\"https://source.chromium.org/chromium/chromium/src/+/"
"main:ui/file_manager/file_manager/foreground/js/"
"file_tasks.ts;l=913;drc="
"9c9022199a6b2e7411a3cafdc347efeb6f229785\">overriden in the Files "
"app frontend</a>.</p>\n";
for (const auto& task : resulting_tasks->tasks) {
out += "<hr><table><tr><td><img width=64px height=64px src=\"";
out += base::EscapeForHTML(task.icon_url.spec());
out += "\"></td><td>";
out += "<ul><li>TaskTitle: <b>";
out += base::EscapeForHTML(task.task_title);
out += "</b>";
if (task.is_default) {
out += " (default)";
}
out += "<li>ActionID: ";
out += base::EscapeForHTML(task.task_descriptor.action_id);
out += "<li>AppID: ";
out += base::EscapeForHTML(task.task_descriptor.app_id);
out += "<li>TaskType: ";
out += TaskTypeToString(task.task_descriptor.task_type);
out += "</ul></td></tr></table>\n";
}
out += "</body></html>\n";
std::move(callback).Run(out);
};
static constexpr auto on_get_is_directory =
[](Profile* profile, storage::FileSystemURL fs_url,
base::FilePath local_path, std::string mime_type,
base::OnceCallback<void(const std::string_view)> callback,
bool is_directory) {
std::vector<extensions::EntryInfo> entries;
entries.emplace_back(local_path, mime_type, is_directory);
std::vector<GURL> file_urls;
file_urls.push_back(fs_url.ToGURL());
std::vector<std::string> dlp_source_urls;
dlp_source_urls.emplace_back("");
file_manager::file_tasks::FindAllTypesOfTasks(
profile, entries, file_urls, dlp_source_urls,
base::BindOnce(on_find_all_types_of_tasks, fs_url,
std::move(callback)));
};
static constexpr auto on_get_mime_type =
[](Profile* profile, storage::FileSystemURL fs_url,
base::FilePath local_path,
base::OnceCallback<void(const std::string_view)> callback,
const std::string& mime_type) {
extensions::app_file_handler_util::GetIsDirectoryForLocalPath(
profile, local_path,
base::BindOnce(on_get_is_directory, profile, fs_url, local_path,
mime_type, std::move(callback)));
};
base::FilePath local_path = fs_url.path();
extensions::app_file_handler_util::GetMimeTypeForLocalPath(
profile, local_path,
base::BindOnce(on_get_mime_type, profile, fs_url, local_path,
std::move(callback)));
}
bool ChromeFilesInternalsUIDelegate::GetSmbfsEnableVerboseLogging() const {
Profile* profile = Profile::FromWebUI(web_ui_);
return profile && profile->GetPrefs()->GetBoolean(
file_manager::prefs::kSmbfsEnableVerboseLogging);
}
void ChromeFilesInternalsUIDelegate::SetSmbfsEnableVerboseLogging(
bool enabled) {
Profile* profile = Profile::FromWebUI(web_ui_);
if (profile) {
profile->GetPrefs()->SetBoolean(
file_manager::prefs::kSmbfsEnableVerboseLogging, enabled);
}
}
std::string ChromeFilesInternalsUIDelegate::GetOfficeFileHandlers() const {
Profile* profile = Profile::FromWebUI(web_ui_);
const base::Value::Dict& extension_task_prefs =
profile->GetPrefs()->GetDict(prefs::kDefaultTasksBySuffix);
base::Value::Dict filtered_prefs;
for (const std::string& extension :
file_manager::file_tasks::WordGroupExtensions()) {
if (extension_task_prefs.contains(extension)) {
filtered_prefs.Set(extension,
*extension_task_prefs.FindString(extension));
}
}
for (const std::string& extension :
file_manager::file_tasks::ExcelGroupExtensions()) {
if (extension_task_prefs.contains(extension)) {
filtered_prefs.Set(extension,
*extension_task_prefs.FindString(extension));
}
}
for (const std::string& extension :
file_manager::file_tasks::PowerPointGroupExtensions()) {
if (extension_task_prefs.contains(extension)) {
filtered_prefs.Set(extension,
*extension_task_prefs.FindString(extension));
}
}
return filtered_prefs.DebugString();
}
void ChromeFilesInternalsUIDelegate::ClearOfficeFileHandlers() {
Profile* profile = Profile::FromWebUI(web_ui_);
if (!profile) {
return;
}
// Do not allow cleaning office handlers when automated Clippy flows are in
// place.
if (chromeos::cloud_upload::IsGoogleWorkspaceCloudUploadAutomated(profile) ||
chromeos::cloud_upload::IsMicrosoftOfficeCloudUploadAutomated(profile)) {
return;
}
ScopedDictPrefUpdate mime_type_pref(profile->GetPrefs(),
prefs::kDefaultTasksByMimeType);
for (const std::string& mime_type :
file_manager::file_tasks::WordGroupMimeTypes()) {
mime_type_pref->Remove(mime_type);
}
for (const std::string& mime_type :
file_manager::file_tasks::ExcelGroupMimeTypes()) {
mime_type_pref->Remove(mime_type);
}
for (const std::string& mime_type :
file_manager::file_tasks::PowerPointGroupMimeTypes()) {
mime_type_pref->Remove(mime_type);
}
ScopedDictPrefUpdate extension_pref(profile->GetPrefs(),
prefs::kDefaultTasksBySuffix);
for (const std::string& extension :
file_manager::file_tasks::WordGroupExtensions()) {
extension_pref->Remove(extension);
}
for (const std::string& extension :
file_manager::file_tasks::ExcelGroupExtensions()) {
extension_pref->Remove(extension);
}
for (const std::string& extension :
file_manager::file_tasks::PowerPointGroupExtensions()) {
extension_pref->Remove(extension);
}
// Also update the preferences to signal that the move confirmation dialog
// has never been shown.
file_manager::file_tasks::SetOfficeMoveConfirmationShownForDrive(profile,
false);
file_manager::file_tasks::SetOfficeMoveConfirmationShownForOneDrive(profile,
false);
file_manager::file_tasks::SetOfficeMoveConfirmationShownForLocalToDrive(
profile, false);
file_manager::file_tasks::SetOfficeMoveConfirmationShownForLocalToOneDrive(
profile, false);
file_manager::file_tasks::SetOfficeMoveConfirmationShownForCloudToDrive(
profile, false);
file_manager::file_tasks::SetOfficeMoveConfirmationShownForCloudToOneDrive(
profile, false);
}
bool ChromeFilesInternalsUIDelegate::GetMoveConfirmationShownForDrive() const {
Profile* profile = Profile::FromWebUI(web_ui_);
return profile &&
file_manager::file_tasks::GetOfficeMoveConfirmationShownForDrive(
profile);
}
bool ChromeFilesInternalsUIDelegate::GetMoveConfirmationShownForOneDrive()
const {
Profile* profile = Profile::FromWebUI(web_ui_);
return profile &&
file_manager::file_tasks::GetOfficeMoveConfirmationShownForOneDrive(
profile);
}
bool ChromeFilesInternalsUIDelegate::GetMoveConfirmationShownForLocalToDrive()
const {
Profile* profile = Profile::FromWebUI(web_ui_);
return profile && file_manager::file_tasks::
GetOfficeMoveConfirmationShownForLocalToDrive(profile);
}
bool ChromeFilesInternalsUIDelegate::
GetMoveConfirmationShownForLocalToOneDrive() const {
Profile* profile = Profile::FromWebUI(web_ui_);
return profile &&
file_manager::file_tasks::
GetOfficeMoveConfirmationShownForLocalToOneDrive(profile);
}
bool ChromeFilesInternalsUIDelegate::GetMoveConfirmationShownForCloudToDrive()
const {
Profile* profile = Profile::FromWebUI(web_ui_);
return profile && file_manager::file_tasks::
GetOfficeMoveConfirmationShownForCloudToDrive(profile);
}
bool ChromeFilesInternalsUIDelegate::
GetMoveConfirmationShownForCloudToOneDrive() const {
Profile* profile = Profile::FromWebUI(web_ui_);
return profile &&
file_manager::file_tasks::
GetOfficeMoveConfirmationShownForCloudToOneDrive(profile);
}
bool ChromeFilesInternalsUIDelegate::GetAlwaysMoveOfficeFilesToDrive() const {
Profile* profile = Profile::FromWebUI(web_ui_);
return profile &&
file_manager::file_tasks::GetAlwaysMoveOfficeFilesToDrive(profile);
}
void ChromeFilesInternalsUIDelegate::SetAlwaysMoveOfficeFilesToDrive(
bool always_move) {
Profile* profile = Profile::FromWebUI(web_ui_);
if (profile) {
file_manager::file_tasks::SetAlwaysMoveOfficeFilesToDrive(profile,
always_move);
// Also clear up the timestamp for when files are moved to the Cloud.
file_manager::file_tasks::SetOfficeFileMovedToGoogleDrive(profile,
base::Time());
// Spawn the Files app Window so it clears up its localStorage.
auto url = ::file_manager::util::GetFileManagerURL().Resolve("");
::ash::SystemAppLaunchParams params;
params.url = url;
::ash::LaunchSystemWebAppAsync(profile, ash::SystemWebAppType::FILE_MANAGER,
params);
}
}
bool ChromeFilesInternalsUIDelegate::GetAlwaysMoveOfficeFilesToOneDrive()
const {
Profile* profile = Profile::FromWebUI(web_ui_);
return profile &&
file_manager::file_tasks::GetAlwaysMoveOfficeFilesToOneDrive(profile);
}
void ChromeFilesInternalsUIDelegate::SetAlwaysMoveOfficeFilesToOneDrive(
bool always_move) {
Profile* profile = Profile::FromWebUI(web_ui_);
if (profile) {
file_manager::file_tasks::SetAlwaysMoveOfficeFilesToOneDrive(profile,
always_move);
// Also clear up the timestamp for when files are moved to the Cloud.
file_manager::file_tasks::SetOfficeFileMovedToOneDrive(profile,
base::Time());
// Spawn the Files app Window so it clears up its localStorage.
auto url = ::file_manager::util::GetFileManagerURL().Resolve("");
::ash::SystemAppLaunchParams params;
params.url = url;
::ash::LaunchSystemWebAppAsync(profile, ash::SystemWebAppType::FILE_MANAGER,
params);
}
}