// 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_misc.h"
#include <stddef.h>
#include <set>
#include <utility>
#include <vector>
#include "ash/components/arc/arc_prefs.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/public/cpp/multi_user_window_manager.h"
#include "ash/public/cpp/new_window_delegate.h"
#include "ash/webui/settings/public/constants/routes_util.h"
#include "base/command_line.h"
#include "base/files/file.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/memory/scoped_refptr.h"
#include "base/strings/strcat.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ash/crostini/crostini_export_import.h"
#include "chrome/browser/ash/crostini/crostini_features.h"
#include "chrome/browser/ash/crostini/crostini_package_service.h"
#include "chrome/browser/ash/drive/drive_integration_service.h"
#include "chrome/browser/ash/drive/file_system_util.h"
#include "chrome/browser/ash/extensions/file_manager/private_api_util.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/path_util.h"
#include "chrome/browser/ash/file_manager/trash_common_util.h"
#include "chrome/browser/ash/file_manager/url_util.h"
#include "chrome/browser/ash/file_manager/volume_manager.h"
#include "chrome/browser/ash/file_system_provider/mount_path_util.h"
#include "chrome/browser/ash/file_system_provider/service.h"
#include "chrome/browser/ash/fileapi/recent_file.h"
#include "chrome/browser/ash/fileapi/recent_model.h"
#include "chrome/browser/ash/fileapi/recent_model_factory.h"
#include "chrome/browser/ash/guest_os/guest_os_share_path.h"
#include "chrome/browser/ash/guest_os/public/guest_os_service.h"
#include "chrome/browser/ash/policy/skyvault/policy_utils.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/upload_office_to_cloud/upload_office_to_cloud.h"
#include "chrome/browser/devtools/devtools_window.h"
#include "chrome/browser/download/download_dir_util.h"
#include "chrome/browser/extensions/devtools_util.h"
#include "chrome/browser/feedback/show_feedback_page.h"
#include "chrome/browser/lifetime/application_lifetime.h"
#include "chrome/browser/net/system_network_context_manager.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/profiles/profiles_state.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/ui/ash/multi_user/multi_user_util.h"
#include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_helper.h"
#include "chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/chrome_pages.h"
#include "chrome/browser/ui/settings_window_manager_chromeos.h"
#include "chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_dialog.h"
#include "chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_util.h"
#include "chrome/common/extensions/api/file_manager_private.h"
#include "chrome/common/extensions/api/file_manager_private_internal.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/pref_names.h"
#include "chromeos/ash/components/drivefs/drivefs_pinning_manager.h"
#include "chromeos/ash/components/settings/timezone_settings.h"
#include "components/account_id/account_id.h"
#include "components/drive/drive_pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/user_manager/user_manager.h"
#include "components/zoom/page_zoom.h"
#include "content/public/browser/network_service_instance.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/page_zoom.h"
#include "extensions/browser/api/file_handlers/mime_util.h"
#include "extensions/browser/app_window/app_window.h"
#include "extensions/browser/app_window/app_window_registry.h"
#include "storage/common/file_system/file_system_types.h"
#include "storage/common/file_system/file_system_util.h"
#include "ui/base/webui/web_ui_util.h"
#include "ui/display/screen.h"
#include "ui/shell_dialogs/select_file_dialog.h"
#include "url/gurl.h"
namespace extensions {
namespace {
namespace fsp = ash::file_system_provider;
namespace fmp = api::file_manager_private;
namespace fmpi = api::file_manager_private_internal;
using fmp::ProfileInfo;
using std::optional;
// Thresholds for mountCrostini() API.
constexpr base::TimeDelta kMountCrostiniSlowOperationThreshold =
base::Seconds(10);
constexpr base::TimeDelta kMountCrostiniVerySlowOperationThreshold =
base::Seconds(30);
// Obtains the current app window.
AppWindow* GetCurrentAppWindow(ExtensionFunction* function) {
content::WebContents* const contents = function->GetSenderWebContents();
return contents ? AppWindowRegistry::Get(function->browser_context())
->GetAppWindowForWebContents(contents)
: nullptr;
}
std::vector<ProfileInfo> GetLoggedInProfileInfoList() {
DCHECK(user_manager::UserManager::IsInitialized());
const std::vector<Profile*>& profiles =
g_browser_process->profile_manager()->GetLoadedProfiles();
std::set<Profile*> original_profiles;
std::vector<ProfileInfo> result_profiles;
for (Profile* profile : profiles) {
// Filter the profile.
profile = profile->GetOriginalProfile();
if (original_profiles.count(profile)) {
continue;
}
original_profiles.insert(profile);
const user_manager::User* const user =
ash::ProfileHelper::Get()->GetUserByProfile(profile);
if (!user || !user->is_logged_in()) {
continue;
}
// Make a ProfileInfo.
ProfileInfo profile_info;
profile_info.profile_id =
multi_user_util::GetAccountIdFromProfile(profile).GetUserEmail();
profile_info.display_name = base::UTF16ToUTF8(user->GetDisplayName());
// TODO(hirono): Remove the property from the profile_info.
profile_info.is_current_profile = true;
result_profiles.push_back(std::move(profile_info));
}
return result_profiles;
}
// Converts a list of file system urls (as strings) to a pair of a provided file
// system object and a list of unique paths on the file system. In case of an
// error, false is returned and the error message set.
bool ConvertURLsToProvidedInfo(
const scoped_refptr<storage::FileSystemContext>& file_system_context,
const std::vector<std::string>& urls,
fsp::ProvidedFileSystemInterface** file_system,
std::vector<base::FilePath>* paths,
std::string* error) {
DCHECK(file_system);
DCHECK(error);
if (urls.empty()) {
*error = "At least one file must be specified.";
return false;
}
*file_system = nullptr;
for (const auto& url : urls) {
storage::FileSystemURL file_system_url(
file_system_context->CrackURLInFirstPartyContext(GURL(url)));
// Convert fusebox URL to its backing (FSP) file system provider URL.
if (file_system_url.type() == storage::kFileSystemTypeFuseBox) {
std::string fsp_url(url);
base::ReplaceFirstSubstringAfterOffset(&fsp_url, 0, "/external/fusebox",
"/external/");
file_system_url =
file_system_context->CrackURLInFirstPartyContext(GURL(fsp_url));
}
fsp::util::FileSystemURLParser parser(file_system_url);
if (!parser.Parse()) {
*error = "Related provided file system not found.";
return false;
}
if (*file_system != nullptr) {
if (*file_system != parser.file_system()) {
*error = "All entries must be on the same file system.";
return false;
}
} else {
*file_system = parser.file_system();
}
paths->push_back(parser.file_path());
}
// Erase duplicates.
std::sort(paths->begin(), paths->end());
paths->erase(std::unique(paths->begin(), paths->end()), paths->end());
return true;
}
bool IsAllowedSource(storage::FileSystemType type,
fmp::SourceRestriction restriction) {
switch (restriction) {
case fmp::SourceRestriction::kNone:
NOTREACHED_IN_MIGRATION();
return false;
case fmp::SourceRestriction::kAnySource:
return true;
case fmp::SourceRestriction::kNativeSource:
return type == storage::kFileSystemTypeLocal;
}
}
std::string Redact(const std::string& s) {
return LOG_IS_ON(INFO) ? base::StrCat({"'", s, "'"}) : "(redacted)";
}
std::string Redact(const base::FilePath& path) {
return Redact(path.value());
}
// Gets the default location/volume that the user should use, usually MyFiles,
// based on `path`. When SkyVault is enabled the admin might choose between
// Google Drive and OneDrive. If SkyVault is misconfigured, e.g. local files are
// disabled but the download policy isn't set correctly defaults to MyFiles.
api::file_manager_private::DefaultLocation GetDefaultLocation(
const std::string& pref) {
if (policy::local_user_files::LocalUserFilesAllowed()) {
// If local files are allowed, always default to MyFiles.
return api::file_manager_private::DefaultLocation::kMyFiles;
}
if (pref == download_dir_util::kLocationGoogleDrive) {
return api::file_manager_private::DefaultLocation::kGoogleDrive;
}
if (pref == download_dir_util::kLocationOneDrive) {
return api::file_manager_private::DefaultLocation::kOnedrive;
}
// SkyVault is misconfigured - local files are disabled but no cloud location
// is enforced as the download location.
LOG(ERROR) << "SkyVault is misconfigured: Invalid cloud pref: " << pref
<< " defaulting to MyFiles.";
return api::file_manager_private::DefaultLocation::kMyFiles;
}
// Converts the value of LocalUserFilesMigrationDestination policy to
// api::file_manager_private::CloudProvider.
api::file_manager_private::CloudProvider GetSkyVaultMigrationDestination() {
auto cloud_provider = policy::local_user_files::GetMigrationDestination();
switch (cloud_provider) {
case policy::local_user_files::CloudProvider::kNotSpecified:
return api::file_manager_private::CloudProvider::kNotSpecified;
case policy::local_user_files::CloudProvider::kGoogleDrive:
return api::file_manager_private::CloudProvider::kGoogleDrive;
case policy::local_user_files::CloudProvider::kOneDrive:
return api::file_manager_private::CloudProvider::kOnedrive;
}
}
} // namespace
ExtensionFunction::ResponseAction
FileManagerPrivateGetPreferencesFunction::Run() {
fmp::Preferences result;
Profile* const profile = Profile::FromBrowserContext(browser_context());
DCHECK(profile);
const PrefService* const prefs = profile->GetPrefs();
DCHECK(prefs);
drive::DriveIntegrationService* const service =
drive::DriveIntegrationServiceFactory::FindForProfile(profile);
result.drive_enabled = drive::util::IsDriveEnabledForProfile(profile) &&
service && !service->mount_failed();
result.drive_sync_enabled_on_metered_network =
!prefs->GetBoolean(drive::prefs::kDisableDriveOverCellular);
result.drive_fs_bulk_pinning_available =
drive::util::IsDriveFsBulkPinningAvailable(profile);
result.drive_fs_bulk_pinning_enabled =
prefs->GetBoolean(drive::prefs::kDriveFsBulkPinningEnabled);
result.search_suggest_enabled =
prefs->GetBoolean(prefs::kSearchSuggestEnabled);
result.use24hour_clock = prefs->GetBoolean(prefs::kUse24HourClock);
result.timezone = base::UTF16ToUTF8(
ash::system::TimezoneSettings::GetInstance()->GetCurrentTimezoneID());
result.arc_enabled = prefs->GetBoolean(arc::prefs::kArcEnabled);
result.arc_removable_media_access_enabled =
prefs->GetBoolean(arc::prefs::kArcHasAccessToRemovableMedia);
result.trash_enabled = file_manager::trash::IsTrashEnabledForProfile(profile);
std::vector<std::string> folder_shortcuts;
const auto& value_list = prefs->GetList(ash::prefs::kFilesAppFolderShortcuts);
for (const base::Value& value : value_list) {
folder_shortcuts.push_back(value.is_string() ? value.GetString() : "");
}
result.folder_shortcuts = folder_shortcuts;
result.office_file_moved_one_drive =
prefs->GetTime(prefs::kOfficeFileMovedToOneDrive)
.InMillisecondsFSinceUnixEpoch();
result.office_file_moved_google_drive =
prefs->GetTime(prefs::kOfficeFileMovedToGoogleDrive)
.InMillisecondsFSinceUnixEpoch();
result.local_user_files_allowed =
policy::local_user_files::LocalUserFilesAllowed();
result.default_location =
GetDefaultLocation(prefs->GetString(prefs::kFilesAppDefaultLocation));
result.sky_vault_migration_destination = GetSkyVaultMigrationDestination();
return RespondNow(WithArguments(result.ToValue()));
}
ExtensionFunction::ResponseAction
FileManagerPrivateSetPreferencesFunction::Run() {
using fmp::SetPreferences::Params;
const optional<Params> params = Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(params);
const fmp::PreferencesChange& change = params->change_info;
Profile* const profile = Profile::FromBrowserContext(browser_context());
PrefService* const service = profile->GetPrefs();
if (change.drive_sync_enabled_on_metered_network.has_value()) {
service->SetBoolean(drive::prefs::kDisableDriveOverCellular,
!change.drive_sync_enabled_on_metered_network.value());
}
if (drive::util::IsDriveFsBulkPinningAvailable(profile) &&
change.drive_fs_bulk_pinning_enabled.has_value()) {
service->SetBoolean(drive::prefs::kDriveFsBulkPinningEnabled,
change.drive_fs_bulk_pinning_enabled.value());
drivefs::pinning::RecordBulkPinningEnabledSource(
drivefs::pinning::BulkPinningEnabledSource::kBanner);
}
if (change.arc_enabled.has_value()) {
service->SetBoolean(arc::prefs::kArcEnabled, change.arc_enabled.value());
}
if (change.arc_removable_media_access_enabled.has_value()) {
service->SetBoolean(arc::prefs::kArcHasAccessToRemovableMedia,
change.arc_removable_media_access_enabled.value());
}
if (change.folder_shortcuts.has_value()) {
base::Value::List folder_shortcuts;
for (const std::string& shortcut : change.folder_shortcuts.value()) {
folder_shortcuts.Append(shortcut);
}
service->SetList(ash::prefs::kFilesAppFolderShortcuts,
std::move(folder_shortcuts));
}
return RespondNow(NoArguments());
}
ExtensionFunction::ResponseAction FileManagerPrivateZoomFunction::Run() {
using fmp::Zoom::Params;
const optional<Params> params = Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(params);
content::PageZoom zoom_type;
switch (params->operation) {
case fmp::ZoomOperationType::kIn:
zoom_type = content::PAGE_ZOOM_IN;
break;
case fmp::ZoomOperationType::kOut:
zoom_type = content::PAGE_ZOOM_OUT;
break;
case fmp::ZoomOperationType::kReset:
zoom_type = content::PAGE_ZOOM_RESET;
break;
default:
NOTREACHED_IN_MIGRATION();
return RespondNow(Error(kUnknownErrorDoNotUse));
}
zoom::PageZoom::Zoom(GetSenderWebContents(), zoom_type);
return RespondNow(NoArguments());
}
ExtensionFunction::ResponseAction FileManagerPrivateGetProfilesFunction::Run() {
fmp::ProfilesResponse response;
response.profiles = GetLoggedInProfileInfoList();
// Obtains the display profile ID.
AppWindow* const app_window = GetCurrentAppWindow(this);
ash::MultiUserWindowManager* const window_manager =
MultiUserWindowManagerHelper::GetWindowManager();
const AccountId current_profile_id = multi_user_util::GetAccountIdFromProfile(
Profile::FromBrowserContext(browser_context()));
const AccountId display_profile_id =
window_manager && app_window ? window_manager->GetUserPresentingWindow(
app_window->GetNativeWindow())
: EmptyAccountId();
response.current_profile_id = current_profile_id.GetUserEmail();
response.displayed_profile_id = display_profile_id.is_valid()
? display_profile_id.GetUserEmail()
: current_profile_id.GetUserEmail();
return RespondNow(WithArguments(response.ToValue()));
}
ExtensionFunction::ResponseAction
FileManagerPrivateOpenInspectorFunction::Run() {
using fmp::OpenInspector::Params;
const optional<Params> params = Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(params);
switch (params->type) {
case fmp::InspectionType::kNormal:
// Open inspector for foreground page.
DevToolsWindow::OpenDevToolsWindow(GetSenderWebContents(),
DevToolsOpenedByAction::kUnknown);
break;
case fmp::InspectionType::kConsole:
// Open inspector for foreground page and bring focus to the console.
DevToolsWindow::OpenDevToolsWindow(
GetSenderWebContents(), DevToolsToggleAction::ShowConsolePanel(),
DevToolsOpenedByAction::kUnknown);
break;
case fmp::InspectionType::kElement:
// Open inspector for foreground page in inspect element mode.
DevToolsWindow::OpenDevToolsWindow(GetSenderWebContents(),
DevToolsToggleAction::Inspect(),
DevToolsOpenedByAction::kUnknown);
break;
case fmp::InspectionType::kBackground:
// Open inspector for background page if extension pointer is not null.
// Files app SWA is not an extension and thus has no associated background
// page.
if (extension()) {
devtools_util::InspectBackgroundPage(
extension(), Profile::FromBrowserContext(browser_context()),
DevToolsOpenedByAction::kUnknown);
} else {
return RespondNow(
Error(base::StringPrintf("Inspection type(%d) not supported.",
static_cast<int>(params->type))));
}
break;
default:
NOTREACHED_IN_MIGRATION();
return RespondNow(Error(
base::StringPrintf("Unexpected inspection type(%d) is specified.",
static_cast<int>(params->type))));
}
return RespondNow(NoArguments());
}
ExtensionFunction::ResponseAction
FileManagerPrivateOpenSettingsSubpageFunction::Run() {
using fmp::OpenSettingsSubpage::Params;
const optional<Params> params = Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(params);
Profile* profile = ProfileManager::GetActiveUserProfile();
if (chromeos::settings::IsOSSettingsSubPage(params->sub_page)) {
chrome::SettingsWindowManager::GetInstance()->ShowOSSettings(
profile, params->sub_page);
} else {
chrome::ShowSettingsSubPageForProfile(profile, params->sub_page);
}
return RespondNow(NoArguments());
}
FileManagerPrivateGetMimeTypeFunction::FileManagerPrivateGetMimeTypeFunction() =
default;
FileManagerPrivateGetMimeTypeFunction::
~FileManagerPrivateGetMimeTypeFunction() = default;
ExtensionFunction::ResponseAction FileManagerPrivateGetMimeTypeFunction::Run() {
using fmp::GetMimeType::Params;
const optional<Params> params = Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(params);
// Convert file url to local path.
Profile* const profile = Profile::FromBrowserContext(browser_context());
const scoped_refptr<storage::FileSystemContext> file_system_context =
file_manager::util::GetFileSystemContextForRenderFrameHost(
profile, render_frame_host());
storage::FileSystemURL file_system_url(
file_system_context->CrackURLInFirstPartyContext(GURL(params->url)));
app_file_handler_util::GetMimeTypeForLocalPath(
profile, file_system_url.path(),
base::BindOnce(&FileManagerPrivateGetMimeTypeFunction::OnGetMimeType,
this));
return RespondLater();
}
void FileManagerPrivateGetMimeTypeFunction::OnGetMimeType(
const std::string& mimeType) {
Respond(WithArguments(mimeType));
}
FileManagerPrivateGetProvidersFunction::
FileManagerPrivateGetProvidersFunction() = default;
ExtensionFunction::ResponseAction
FileManagerPrivateGetProvidersFunction::Run() {
using fsp::Capabilities;
using fsp::IconSet;
using fsp::ProviderId;
using fsp::ProviderInterface;
using fsp::Service;
const Service* const service = Service::Get(browser_context());
using fmp::Provider;
std::vector<Provider> result;
for (const auto& pair : service->GetProviders()) {
const ProviderInterface* const provider = pair.second.get();
const ProviderId provider_id = provider->GetId();
Provider result_item;
result_item.provider_id = provider->GetId().ToString();
const IconSet& icon_set = provider->GetIconSet();
file_manager::util::FillIconSet(&result_item.icon_set, icon_set);
result_item.name = provider->GetName();
const Capabilities capabilities = provider->GetCapabilities();
result_item.configurable = capabilities.configurable;
result_item.watchable = capabilities.watchable;
result_item.multiple_mounts = capabilities.multiple_mounts;
switch (capabilities.source) {
case SOURCE_FILE:
result_item.source = fmp::ProviderSource::kFile;
break;
case SOURCE_DEVICE:
result_item.source = fmp::ProviderSource::kDevice;
break;
case SOURCE_NETWORK:
result_item.source = fmp::ProviderSource::kNetwork;
break;
}
result.push_back(std::move(result_item));
}
return RespondNow(ArgumentList(fmp::GetProviders::Results::Create(result)));
}
FileManagerPrivateAddProvidedFileSystemFunction::
FileManagerPrivateAddProvidedFileSystemFunction() = default;
ExtensionFunction::ResponseAction
FileManagerPrivateAddProvidedFileSystemFunction::Run() {
using fmp::AddProvidedFileSystem::Params;
using fsp::ProviderId;
using fsp::Service;
const optional<Params> params = Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(params);
Profile* profile = Profile::FromBrowserContext(browser_context());
Service* const service = Service::Get(browser_context());
ProviderId provider_id = ProviderId::FromString(params->provider_id);
auto file_systems = service->GetProvidedFileSystemInfoList(provider_id);
bool first_file_system = file_systems.empty();
// Show Connect To OneDrive dialog only when mounting ODFS for the first time.
// There will already a ODFS mount if the user is requesting a new mount to
// replace the unauthenticated one.
if (ash::cloud_upload::
IsMicrosoftOfficeOneDriveIntegrationAllowedAndOdfsInstalled(
profile) &&
params->provider_id == extension_misc::kODFSExtensionId &&
first_file_system) {
// Get Files App window, if it exists.
Browser* browser =
FindSystemWebAppBrowser(profile, ash::SystemWebAppType::FILE_MANAGER);
gfx::NativeWindow modal_parent =
browser ? browser->window()->GetNativeWindow() : nullptr;
// This will call into service->RequestMount() if necessary. This is 'fire
// and forget' as Files app doesn't do anything if this succeeds or fails.
bool started = ash::cloud_upload::ShowConnectOneDriveDialog(modal_parent);
return RespondNow(started ? NoArguments()
: Error("Failed to request a new mount."));
}
if (!service->RequestMount(provider_id, base::DoNothing())) {
return RespondNow(Error("Failed to request a new mount."));
}
return RespondNow(NoArguments());
}
FileManagerPrivateConfigureVolumeFunction::
FileManagerPrivateConfigureVolumeFunction() = default;
ExtensionFunction::ResponseAction
FileManagerPrivateConfigureVolumeFunction::Run() {
using fmp::ConfigureVolume::Params;
const optional<Params> params = Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(params);
using file_manager::Volume;
using file_manager::VolumeManager;
VolumeManager* const volume_manager =
VolumeManager::Get(Profile::FromBrowserContext(browser_context()));
DCHECK(volume_manager);
std::string volume_id = params->volume_id;
volume_manager->ConvertFuseBoxFSPVolumeIdToFSPIfNeeded(&volume_id);
const base::WeakPtr<Volume> volume =
volume_manager->FindVolumeById(volume_id);
if (!volume) {
return RespondNow(
Error("ConfigureVolume: volume with ID * not found.", volume_id));
}
if (!volume->configurable()) {
return RespondNow(Error("Volume not configurable."));
}
switch (volume->type()) {
case file_manager::VOLUME_TYPE_PROVIDED: {
using fsp::Service;
Service* const service = Service::Get(browser_context());
DCHECK(service);
using fsp::ProvidedFileSystemInterface;
ProvidedFileSystemInterface* const file_system =
service->GetProvidedFileSystem(volume->provider_id(),
volume->file_system_id());
if (file_system) {
file_system->Configure(base::BindOnce(
&FileManagerPrivateConfigureVolumeFunction::OnCompleted, this));
}
break;
}
default:
NOTIMPLEMENTED();
}
return RespondLater();
}
void FileManagerPrivateConfigureVolumeFunction::OnCompleted(
base::File::Error result) {
if (result != base::File::FILE_OK) {
Respond(Error("Failed to complete configuration."));
return;
}
Respond(NoArguments());
}
FileManagerPrivateMountCrostiniFunction::
FileManagerPrivateMountCrostiniFunction() {
// Mounting crostini shares may require the crostini VM to be started.
SetWarningThresholds(kMountCrostiniSlowOperationThreshold,
kMountCrostiniVerySlowOperationThreshold);
}
FileManagerPrivateMountCrostiniFunction::
~FileManagerPrivateMountCrostiniFunction() = default;
ExtensionFunction::ResponseAction
FileManagerPrivateMountCrostiniFunction::Run() {
// Use OriginalProfile since using crostini in incognito such as saving
// files into Linux files should still work.
Profile* profile =
Profile::FromBrowserContext(browser_context())->GetOriginalProfile();
DCHECK(crostini::CrostiniFeatures::Get()->IsEnabled(profile));
crostini::CrostiniManager::GetForProfile(profile)->RestartCrostini(
crostini::DefaultContainerId(),
base::BindOnce(&FileManagerPrivateMountCrostiniFunction::RestartCallback,
this));
return RespondLater();
}
void FileManagerPrivateMountCrostiniFunction::RestartCallback(
crostini::CrostiniResult result) {
if (result != crostini::CrostiniResult::SUCCESS) {
Respond(Error(base::StringPrintf("Error mounting crostini container: %d",
static_cast<int>(result))));
return;
}
// Use OriginalProfile since using crostini in incognito such as saving
// files into Linux files should still work.
Profile* profile =
Profile::FromBrowserContext(browser_context())->GetOriginalProfile();
DCHECK(crostini::CrostiniFeatures::Get()->IsEnabled(profile));
crostini::CrostiniManager::GetForProfile(profile)->MountCrostiniFiles(
crostini::DefaultContainerId(),
base::BindOnce(&FileManagerPrivateMountCrostiniFunction::MountCallback,
this),
false);
}
void FileManagerPrivateMountCrostiniFunction::MountCallback(
crostini::CrostiniResult result) {
if (result != crostini::CrostiniResult::SUCCESS) {
Respond(Error(base::StringPrintf("Error mounting crostini container: %d",
static_cast<int>(result))));
return;
}
Respond(NoArguments());
}
FileManagerPrivateInternalImportCrostiniImageFunction::
FileManagerPrivateInternalImportCrostiniImageFunction() = default;
FileManagerPrivateInternalImportCrostiniImageFunction::
~FileManagerPrivateInternalImportCrostiniImageFunction() = default;
ExtensionFunction::ResponseAction
FileManagerPrivateInternalImportCrostiniImageFunction::Run() {
using fmpi::ImportCrostiniImage::Params;
const auto 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());
base::FilePath path =
file_system_context->CrackURLInFirstPartyContext(GURL(params->url))
.path();
crostini::CrostiniExportImport::GetForProfile(profile)->ImportContainer(
crostini::DefaultContainerId(), path,
base::BindOnce(
[](base::FilePath path, crostini::CrostiniResult result) {
if (result != crostini::CrostiniResult::SUCCESS) {
LOG(ERROR) << "Error importing crostini image " << Redact(path)
<< ": " << (int)result;
}
},
path));
return RespondNow(NoArguments());
}
ExtensionFunction::ResponseAction
FileManagerPrivateInternalSharePathsWithCrostiniFunction::Run() {
using fmpi::SharePathsWithCrostini::Params;
const 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());
std::vector<base::FilePath> paths;
for (const auto& url : params->urls) {
storage::FileSystemURL cracked =
file_system_context->CrackURLInFirstPartyContext(GURL(url));
paths.emplace_back(cracked.path());
}
auto vm_info =
guest_os::GuestOsSessionTracker::GetForProfile(profile)->GetVmInfo(
params->vm_name);
auto* share_service = guest_os::GuestOsSharePath::GetForProfile(profile);
share_service->RegisterPersistedPaths(params->vm_name, paths);
if (vm_info) {
// The share service will mount persistent shares at VM boot, but if the VM
// is already running we need to trigger the first mount ourselves.
share_service->SharePaths(
params->vm_name, vm_info->seneschal_server_handle(), std::move(paths),
base::BindOnce(
&FileManagerPrivateInternalSharePathsWithCrostiniFunction::
SharePathsCallback,
this));
return RespondLater();
} else {
return RespondNow(NoArguments());
}
}
void FileManagerPrivateInternalSharePathsWithCrostiniFunction::
SharePathsCallback(bool success, const std::string& failure_reason) {
Respond(success ? NoArguments() : Error(failure_reason));
}
ExtensionFunction::ResponseAction
FileManagerPrivateInternalUnsharePathWithCrostiniFunction::Run() {
using fmpi::UnsharePathWithCrostini::Params;
const 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());
storage::FileSystemURL cracked =
file_system_context->CrackURLInFirstPartyContext(GURL(params->url));
guest_os::GuestOsSharePath::GetForProfile(profile)->UnsharePath(
params->vm_name, cracked.path(), /*unpersist=*/true,
base::BindOnce(
&FileManagerPrivateInternalUnsharePathWithCrostiniFunction::
UnsharePathCallback,
this));
return RespondLater();
}
void FileManagerPrivateInternalUnsharePathWithCrostiniFunction::
UnsharePathCallback(bool success, const std::string& failure_reason) {
Respond(success ? NoArguments() : Error(failure_reason));
}
ExtensionFunction::ResponseAction
FileManagerPrivateInternalGetCrostiniSharedPathsFunction::Run() {
using fmpi::GetCrostiniSharedPaths::Params;
const optional<Params> params = Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(params);
// Use OriginalProfile since using crostini in incognito such as saving
// files into Linux files should still work.
Profile* profile =
Profile::FromBrowserContext(browser_context())->GetOriginalProfile();
auto* guest_os_share_path =
guest_os::GuestOsSharePath::GetForProfile(profile);
bool first_for_session =
params->observe_first_for_session &&
guest_os_share_path->GetAndSetFirstForSession(params->vm_name);
auto shared_paths =
guest_os_share_path->GetPersistedSharedPaths(params->vm_name);
base::Value::List entries;
fmpi::CrostiniSharedPathResponse response;
response.first_for_session = first_for_session;
for (const base::FilePath& path : shared_paths) {
std::string mount_name;
std::string file_system_name;
std::string full_path;
if (!file_manager::util::ExtractMountNameFileSystemNameFullPath(
path, &mount_name, &file_system_name, &full_path)) {
LOG(ERROR) << "Error extracting mount name and path from "
<< Redact(path);
continue;
}
auto& entry = response.entries.emplace_back();
entry.file_system_root =
storage::GetExternalFileSystemRootURIString(source_url(), mount_name);
entry.file_system_name = file_system_name;
entry.file_full_path = full_path;
// All shared paths should be directories. Even if this is not true,
// it is fine for foreground/js/crostini.js class to think so. We
// verify that the paths are in fact valid directories before calling
// seneschal/9p in GuestOsSharePath::CallSeneschalSharePath().
entry.file_is_directory = true;
}
return RespondNow(WithArguments(response.ToValue()));
}
ExtensionFunction::ResponseAction
FileManagerPrivateInternalGetLinuxPackageInfoFunction::Run() {
using fmpi::GetLinuxPackageInfo::Params;
const 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());
crostini::CrostiniPackageService::GetForProfile(profile)->GetLinuxPackageInfo(
crostini::DefaultContainerId(),
file_system_context->CrackURLInFirstPartyContext(GURL(params->url)),
base::BindOnce(&FileManagerPrivateInternalGetLinuxPackageInfoFunction::
OnGetLinuxPackageInfo,
this));
return RespondLater();
}
void FileManagerPrivateInternalGetLinuxPackageInfoFunction::
OnGetLinuxPackageInfo(
const crostini::LinuxPackageInfo& linux_package_info) {
fmp::LinuxPackageInfo result;
if (!linux_package_info.success) {
Respond(Error(linux_package_info.failure_reason));
return;
}
result.name = linux_package_info.name;
result.version = linux_package_info.version;
result.summary = linux_package_info.summary;
result.description = linux_package_info.description;
Respond(ArgumentList(fmpi::GetLinuxPackageInfo::Results::Create(result)));
}
ExtensionFunction::ResponseAction
FileManagerPrivateInternalInstallLinuxPackageFunction::Run() {
using fmpi::InstallLinuxPackage::Params;
const 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());
crostini::CrostiniPackageService::GetForProfile(profile)
->QueueInstallLinuxPackage(
crostini::DefaultContainerId(),
file_system_context->CrackURLInFirstPartyContext(GURL(params->url)),
base::BindOnce(
&FileManagerPrivateInternalInstallLinuxPackageFunction::
OnInstallLinuxPackage,
this));
return RespondLater();
}
void FileManagerPrivateInternalInstallLinuxPackageFunction::
OnInstallLinuxPackage(crostini::CrostiniResult result) {
fmp::InstallLinuxPackageStatus response;
switch (result) {
case crostini::CrostiniResult::SUCCESS:
response = fmp::InstallLinuxPackageStatus::kStarted;
break;
case crostini::CrostiniResult::INSTALL_LINUX_PACKAGE_FAILED:
response = fmp::InstallLinuxPackageStatus::kFailed;
break;
case crostini::CrostiniResult::BLOCKING_OPERATION_ALREADY_ACTIVE:
response = fmp::InstallLinuxPackageStatus::kInstallAlreadyActive;
break;
default:
NOTREACHED_IN_MIGRATION();
}
Respond(ArgumentList(fmpi::InstallLinuxPackage::Results::Create(response)));
}
FileManagerPrivateInternalGetCustomActionsFunction::
FileManagerPrivateInternalGetCustomActionsFunction() = default;
ExtensionFunction::ResponseAction
FileManagerPrivateInternalGetCustomActionsFunction::Run() {
using fmpi::GetCustomActions::Params;
const optional<Params> params = Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(params);
const scoped_refptr<storage::FileSystemContext> file_system_context =
file_manager::util::GetFileSystemContextForRenderFrameHost(
Profile::FromBrowserContext(browser_context()), render_frame_host());
std::vector<base::FilePath> paths;
fsp::ProvidedFileSystemInterface* file_system = nullptr;
std::string error;
if (!ConvertURLsToProvidedInfo(file_system_context, params->urls,
&file_system, &paths, &error)) {
return RespondNow(Error(error));
}
DCHECK(file_system);
file_system->GetActions(
paths,
base::BindOnce(
&FileManagerPrivateInternalGetCustomActionsFunction::OnCompleted,
this));
return RespondLater();
}
void FileManagerPrivateInternalGetCustomActionsFunction::OnCompleted(
const fsp::Actions& actions,
base::File::Error result) {
if (result != base::File::FILE_OK) {
Respond(Error("Failed to fetch actions."));
return;
}
using api::file_system_provider::Action;
std::vector<Action> items;
for (const auto& action : actions) {
Action item;
item.id = action.id;
item.title = action.title;
items.push_back(std::move(item));
}
Respond(ArgumentList(fmpi::GetCustomActions::Results::Create(items)));
}
FileManagerPrivateInternalExecuteCustomActionFunction::
FileManagerPrivateInternalExecuteCustomActionFunction() = default;
ExtensionFunction::ResponseAction
FileManagerPrivateInternalExecuteCustomActionFunction::Run() {
using fmpi::ExecuteCustomAction::Params;
const optional<Params> params = Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(params);
const scoped_refptr<storage::FileSystemContext> file_system_context =
file_manager::util::GetFileSystemContextForRenderFrameHost(
Profile::FromBrowserContext(browser_context()), render_frame_host());
std::vector<base::FilePath> paths;
fsp::ProvidedFileSystemInterface* file_system = nullptr;
std::string error;
if (!ConvertURLsToProvidedInfo(file_system_context, params->urls,
&file_system, &paths, &error)) {
return RespondNow(Error(error));
}
DCHECK(file_system);
file_system->ExecuteAction(
paths, params->action_id,
base::BindOnce(
&FileManagerPrivateInternalExecuteCustomActionFunction::OnCompleted,
this));
return RespondLater();
}
void FileManagerPrivateInternalExecuteCustomActionFunction::OnCompleted(
base::File::Error result) {
if (result != base::File::FILE_OK) {
Respond(Error("Failed to execute the action."));
return;
}
Respond(NoArguments());
}
FileManagerPrivateInternalGetRecentFilesFunction::
FileManagerPrivateInternalGetRecentFilesFunction() = default;
ExtensionFunction::ResponseAction
FileManagerPrivateInternalGetRecentFilesFunction::Run() {
using fmpi::GetRecentFiles::Params;
const optional<Params> params = Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(params);
Profile* const profile = Profile::FromBrowserContext(browser_context());
const scoped_refptr<storage::FileSystemContext> file_system_context =
file_manager::util::GetFileSystemContextForRenderFrameHost(
profile, render_frame_host());
ash::RecentModel* model = ash::RecentModelFactory::GetForProfile(profile);
if (!model) {
return RespondNow(Error("Failed to get recent model"));
}
ash::RecentModel::FileType file_type;
if (!file_manager::util::ToRecentSourceFileType(params->file_category,
&file_type)) {
return RespondNow(Error("Cannot convert category to file type"));
}
ash::RecentModelOptions options;
options.now_delta = base::Days(params->cutoff_days);
options.max_files = 1000u;
options.invalidate_cache = params->invalidate_cache;
options.file_type = file_type;
options.source_specs = {
{.volume_type = fmp::VolumeType::kCrostini},
{.volume_type = fmp::VolumeType::kMediaView},
{.volume_type = fmp::VolumeType::kDownloads},
{.volume_type = fmp::VolumeType::kDrive},
{.volume_type = fmp::VolumeType::kProvided},
};
// We set the maximum latency to be 3s due to File System Provider volumes.
// As these types of volumes may be located in the cloud, they may be slow.
// 3s is based on "User Preference and Search Engine Latency" paper, which
// stated that "[...] once latency exceeds 3 seconds for the slower engine,
// users are 1.5 times as likely to choose the faster engine."
options.scan_timeout = base::Milliseconds(3000);
model->GetRecentFiles(
file_system_context.get(), source_url(), params->query, options,
base::BindOnce(
&FileManagerPrivateInternalGetRecentFilesFunction::OnGetRecentFiles,
this, params->restriction));
return RespondLater();
}
void FileManagerPrivateInternalGetRecentFilesFunction::OnGetRecentFiles(
fmp::SourceRestriction restriction,
const std::vector<ash::RecentFile>& files) {
Profile* profile = Profile::FromBrowserContext(browser_context());
file_manager::util::FileDefinitionList file_definition_list;
for (const auto& file : files) {
// Filter out files from non-allowed sources.
// We do this filtering here rather than in RecentModel so that the set of
// files returned with some restriction is a subset of what would be
// returned without restriction. Anyway, the maximum number of files
// returned from RecentModel is large enough.
if (!IsAllowedSource(file.url().type(), restriction)) {
continue;
}
file_manager::util::FileDefinition file_definition;
// Recent file system only lists regular files, not directories.
file_definition.is_directory = false;
if (file_manager::util::ConvertAbsoluteFilePathToRelativeFileSystemPath(
profile, source_url(), file.url().path(),
&file_definition.virtual_path)) {
file_definition_list.emplace_back(std::move(file_definition));
}
}
// During this conversion a GET_METADATA_FIELD_IS_DIRECTORY will be triggered
// in file_system_context.cc DidOpenFileSystemForResolveURL() which will
// might not use file_definition.is_directory in some scenarios, which will
// make entry_definition's is_directory become true, so in the callback we
// need to filter out is_directory=true.
file_manager::util::ConvertFileDefinitionListToEntryDefinitionList(
file_manager::util::GetFileSystemContextForSourceURL(profile,
source_url()),
url::Origin::Create(source_url().DeprecatedGetOriginAsURL()),
file_definition_list, // Safe, since copied internally.
base::BindOnce(&FileManagerPrivateInternalGetRecentFilesFunction::
OnConvertFileDefinitionListToEntryDefinitionList,
this));
}
void FileManagerPrivateInternalGetRecentFilesFunction::
OnConvertFileDefinitionListToEntryDefinitionList(
std::unique_ptr<file_manager::util::EntryDefinitionList>
entry_definition_list) {
DCHECK(entry_definition_list);
// Remove all directories entries.
std::erase_if(*entry_definition_list,
[](const file_manager::util::EntryDefinition& e) {
return e.is_directory;
});
Respond(
WithArguments(file_manager::util::ConvertEntryDefinitionListToListValue(
*entry_definition_list)));
}
ExtensionFunction::ResponseAction
FileManagerPrivateIsTabletModeEnabledFunction::Run() {
return RespondNow(
WithArguments(display::Screen::GetScreen()->InTabletMode()));
}
ExtensionFunction::ResponseAction FileManagerPrivateOpenURLFunction::Run() {
using fmp::OpenURL::Params;
const optional<Params> params = Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(params);
const GURL url(params->url);
if (!ash::NewWindowDelegate::GetPrimary()) {
return RespondNow(
Error("Could not get NewWindowDelegate's primary browser"));
}
ash::NewWindowDelegate::GetPrimary()->OpenUrl(
url, ash::NewWindowDelegate::OpenUrlFrom::kUserInteraction,
ash::NewWindowDelegate::Disposition::kNewForegroundTab);
return RespondNow(NoArguments());
}
ExtensionFunction::ResponseAction FileManagerPrivateOpenWindowFunction::Run() {
using fmp::OpenWindow::Params;
const optional<Params> params = Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(params);
const GURL destination_folder(params->params.current_directory_url
? (*params->params.current_directory_url)
: "");
const GURL selection_url(
params->params.selection_url ? (*params->params.selection_url) : "");
ui::SelectFileDialog::FileTypeInfo file_type_info;
file_type_info.allowed_paths =
ui::SelectFileDialog::FileTypeInfo::ANY_PATH_OR_URL;
GURL files_swa_url =
::file_manager::util::GetFileManagerMainPageUrlWithParams(
ui::SelectFileDialog::SELECT_NONE, /*title=*/{}, destination_folder,
selection_url,
/*target_name=*/{}, &file_type_info,
/*file_type_index=*/0,
/*search_query=*/{},
/*show_android_picker_apps=*/false,
/*volume_filter=*/{});
ash::SystemAppLaunchParams launch_params;
launch_params.url = files_swa_url;
Profile* profile = Profile::FromBrowserContext(browser_context());
ash::LaunchSystemWebAppAsync(profile, ash::SystemWebAppType::FILE_MANAGER,
launch_params);
return RespondNow(WithArguments(true));
}
ExtensionFunction::ResponseAction
FileManagerPrivateSendFeedbackFunction::Run() {
GURL url;
if (GetSenderWebContents()) {
url = GetSenderWebContents()->GetVisibleURL();
}
Profile* profile = Profile::FromBrowserContext(browser_context());
chrome::ShowFeedbackPage(url, profile, feedback::kFeedbackSourceFilesApp,
/*description_template=*/std::string(),
/*description_placeholder_text=*/std::string(),
/*category_tag=*/"chromeos-files-app",
/*extra_diagnostics=*/std::string());
return RespondNow(NoArguments());
}
ExtensionFunction::ResponseAction
FileManagerPrivateGetDeviceConnectionStateFunction::Run() {
fmp::DeviceConnectionState result =
content::GetNetworkConnectionTracker()->IsOffline()
? fmp::DeviceConnectionState::kOffline
: fmp::DeviceConnectionState::kOnline;
return RespondNow(
ArgumentList(fmp::GetDeviceConnectionState::Results::Create(result)));
}
} // namespace extensions