Skip to content

Commit

Permalink
[FilesTrash] Add parseTrashInfoFiles fileManagerPrivate API
Browse files Browse the repository at this point in the history
The current method to parse .trashinfo files is to directly read the
files via FileEntry.file() which presents a problem as the files are
accessible by users. There has since been a sandboxed parsing service
setup that opens and reads these files securely. This adds a FMP method
that leverages that service to open .trashinfo files, parse their
restore path and deletion date and return it back to the caller.

Any errors encountered along the way on individual trashinfo files are
skipped and not returned, this requires the .trashinfo filename to be
sent back as a way to match it to the entries that were passed to the
API. This exposes the `trash_info_path` on the `ParsedTrashInfoData`
struct. In the process this tripped the complex constructor error on
compilation [1] and thus had to also add a constructor / destructor in
the process.

Furthermore this doesn't actually wire up the API, this will be done in
a follow up CL and all the existing browser tests will be used.

[1] https://www.chromium.org/developers/coding-style/chromium-style-checker-errors/#constructordestructor-errors

Bug: b:238946031
Change-Id: I3361f53c6b3ba2fdd40404888e8eac562496b3b1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3827456
Reviewed-by: Luciano Pacheco <lucmult@chromium.org>
Commit-Queue: Ben Reich <benreich@chromium.org>
Reviewed-by: Ben Wells <benwells@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1034965}
  • Loading branch information
ben-reich authored and Chromium LUCI CQ committed Aug 15, 2022
1 parent 0beff9e commit 3bd34df
Show file tree
Hide file tree
Showing 10 changed files with 247 additions and 10 deletions.
26 changes: 17 additions & 9 deletions chrome/browser/ash/file_manager/trash_info_validator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ void RunCallbackWithError(base::File::Error error,

} // namespace

ParsedTrashInfoData::ParsedTrashInfoData() = default;
ParsedTrashInfoData::~ParsedTrashInfoData() = default;

ParsedTrashInfoData::ParsedTrashInfoData(ParsedTrashInfoData&& other) = default;
ParsedTrashInfoData& ParsedTrashInfoData::operator=(
ParsedTrashInfoData&& other) = default;

TrashInfoValidator::TrashInfoValidator(Profile* profile,
const base::FilePath& base_path) {
enabled_trash_locations_ =
Expand Down Expand Up @@ -100,14 +107,15 @@ void TrashInfoValidator::OnTrashedFileExists(
auto complete_callback = base::BindPostTask(
base::SequencedTaskRunnerHandle::Get(),
base::BindOnce(&TrashInfoValidator::OnTrashInfoParsed,
weak_ptr_factory_.GetWeakPtr(), mount_point_path,
trashed_file_location, std::move(callback)));
weak_ptr_factory_.GetWeakPtr(), trash_info_path,
mount_point_path, trashed_file_location,
std::move(callback)));

parser_->ParseTrashInfoFile(std::move(trash_info_path),
std::move(complete_callback));
parser_->ParseTrashInfoFile(trash_info_path, std::move(complete_callback));
}

void TrashInfoValidator::OnTrashInfoParsed(
const base::FilePath& trash_info_path,
const base::FilePath& mount_point_path,
const base::FilePath& trashed_file_location,
ValidateAndParseTrashInfoCallback callback,
Expand All @@ -133,11 +141,11 @@ void TrashInfoValidator::OnTrashInfoParsed(
base::StringPiece(restore_path.value()).substr(1);
base::FilePath absolute_restore_path = mount_point_path.Append(relative_path);

ParsedTrashInfoData parsed_data = {
.trashed_file_path = std::move(trashed_file_location),
.absolute_restore_path = std::move(absolute_restore_path),
.deletion_date = std::move(deletion_date),
};
ParsedTrashInfoData parsed_data;
parsed_data.trash_info_path = std::move(trash_info_path);
parsed_data.trashed_file_path = std::move(trashed_file_location);
parsed_data.absolute_restore_path = std::move(absolute_restore_path);
parsed_data.deletion_date = std::move(deletion_date);

std::move(callback).Run(
base::FileErrorOr<ParsedTrashInfoData>(std::move(parsed_data)));
Expand Down
13 changes: 12 additions & 1 deletion chrome/browser/ash/file_manager/trash_info_validator.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ namespace file_manager::trash {
// On a successful parse of .trashinfo files, returns the restoration path,
// deletion date and actual location of the trashed file.
struct ParsedTrashInfoData {
ParsedTrashInfoData();
~ParsedTrashInfoData();

ParsedTrashInfoData(ParsedTrashInfoData&& other);
ParsedTrashInfoData& operator=(ParsedTrashInfoData&& other);

// The on-disk location of the .trashinfo file, e.g.
// .Trash/info/foo.txt.trashinfo.
base::FilePath trash_info_path;

// The actual on-disk location of the trashed file, e.g. .Trash/files/foo.txt.
base::FilePath trashed_file_path;

Expand Down Expand Up @@ -87,7 +97,8 @@ class TrashInfoValidator {
bool exists);

// Invoked when the TrashService has finished parsing the .trashinfo file.
void OnTrashInfoParsed(const base::FilePath& mount_point_path,
void OnTrashInfoParsed(const base::FilePath& trash_info_path,
const base::FilePath& mount_point_path,
const base::FilePath& trashed_file_location,
ValidateAndParseTrashInfoCallback callback,
base::File::Error status,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "ash/components/disks/disk.h"
#include "ash/components/disks/disk_mount_manager.h"
#include "ash/constants/ash_features.h"
#include "base/barrier_callback.h"
#include "base/bind.h"
#include "base/files/file.h"
#include "base/files/file_enumerator.h"
Expand Down Expand Up @@ -1868,4 +1869,119 @@ FileManagerPrivateCancelIOTaskFunction::Run() {
return RespondNow(NoArguments());
}

FileManagerPrivateInternalParseTrashInfoFilesFunction::
FileManagerPrivateInternalParseTrashInfoFilesFunction() = default;

FileManagerPrivateInternalParseTrashInfoFilesFunction::
~FileManagerPrivateInternalParseTrashInfoFilesFunction() = default;

ExtensionFunction::ResponseAction
FileManagerPrivateInternalParseTrashInfoFilesFunction::Run() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);

using extensions::api::file_manager_private_internal::ParseTrashInfoFiles::
Params;
const std::unique_ptr<Params> params(Params::Create(args()));
EXTENSION_FUNCTION_VALIDATE(params);

auto* const profile = Profile::FromBrowserContext(browser_context());
file_system_context_ =
file_manager::util::GetFileSystemContextForRenderFrameHost(
profile, render_frame_host());

std::vector<base::FilePath> trash_info_paths;
for (const std::string& url : params->urls) {
storage::FileSystemURL cracked_url =
file_system_context_->CrackURLInFirstPartyContext(GURL(url));
if (!cracked_url.is_valid()) {
return RespondNow(Error("Invalid source url."));
}
trash_info_paths.push_back(cracked_url.path());
}

validator_ = std::make_unique<file_manager::trash::TrashInfoValidator>(
profile, /*base_path=*/base::FilePath());

auto barrier_callback = base::BarrierCallback<
base::FileErrorOr<file_manager::trash::ParsedTrashInfoData>>(
trash_info_paths.size(),
base::BindOnce(&FileManagerPrivateInternalParseTrashInfoFilesFunction::
OnTrashInfoFilesParsed,
this));

for (const base::FilePath& path : trash_info_paths) {
validator_->ValidateAndParseTrashInfo(std::move(path), barrier_callback);
}

return RespondLater();
}

void FileManagerPrivateInternalParseTrashInfoFilesFunction::
OnTrashInfoFilesParsed(
std::vector<base::FileErrorOr<file_manager::trash::ParsedTrashInfoData>>
parsed_data) {
file_manager::util::FileDefinitionList file_definition_list;
std::vector<file_manager::trash::ParsedTrashInfoData> valid_data;
url::Origin origin = render_frame_host()->GetLastCommittedOrigin();

for (auto& trash_info_data : parsed_data) {
if (trash_info_data.is_error()) {
LOG(ERROR) << "Failed parsing trashinfo file: "
<< trash_info_data.error();
continue;
}

file_manager::util::FileDefinition file_definition;
if (!file_manager::util::ConvertAbsoluteFilePathToRelativeFileSystemPath(
Profile::FromBrowserContext(browser_context()), origin.GetURL(),
trash_info_data.value().absolute_restore_path,
&file_definition.virtual_path)) {
LOG(ERROR) << "Failed to convert absolute path to relative path";
continue;
}

file_definition_list.push_back(std::move(file_definition));
valid_data.push_back(std::move(trash_info_data.value()));
}

file_manager::util::ConvertFileDefinitionListToEntryDefinitionList(
file_system_context_, origin, std::move(file_definition_list),
base::BindOnce(&FileManagerPrivateInternalParseTrashInfoFilesFunction::
OnConvertFileDefinitionListToEntryDefinitionList,
this, std::move(valid_data)));
}

void FileManagerPrivateInternalParseTrashInfoFilesFunction::
OnConvertFileDefinitionListToEntryDefinitionList(
std::vector<file_manager::trash::ParsedTrashInfoData> parsed_data,
std::unique_ptr<file_manager::util::EntryDefinitionList>
entry_definition_list) {
DCHECK_EQ(parsed_data.size(), entry_definition_list->size());
std::vector<api::file_manager_private_internal::ParsedTrashInfoFile> results;

for (int i = 0; i < parsed_data.size(); ++i) {
const auto& [trash_info_path, trashed_file_path, absolute_restore_path,
deletion_date] = parsed_data[i];
api::file_manager_private_internal::ParsedTrashInfoFile info;

info.restore_entry.file_system_name =
entry_definition_list->at(i).file_system_name;
info.restore_entry.file_system_root =
entry_definition_list->at(i).file_system_root_url;
info.restore_entry.file_full_path =
base::FilePath("/")
.Append(entry_definition_list->at(i).full_path)
.value();
info.restore_entry.file_is_directory =
entry_definition_list->at(i).is_directory;
info.trash_info_file_name = trash_info_path.BaseName().value();
info.deletion_date = deletion_date.ToJsTimeIgnoringNull();

results.push_back(std::move(info));
}

Respond(ArgumentList(extensions::api::file_manager_private_internal::
ParseTrashInfoFiles::Results::Create(results)));
}

} // namespace extensions
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "ash/components/drivefs/mojom/drivefs.mojom-forward.h"
#include "base/callback_forward.h"
#include "base/memory/weak_ptr.h"
#include "chrome/browser/ash/file_manager/trash_info_validator.h"
#include "chrome/browser/ash/policy/dlp/dlp_files_controller.h"
#include "chrome/browser/chromeos/extensions/file_manager/logged_extension_function.h"
#include "components/drive/file_errors.h"
Expand Down Expand Up @@ -539,6 +540,43 @@ class FileManagerPrivateCancelIOTaskFunction : public LoggedExtensionFunction {
ResponseAction Run() override;
};

class FileManagerPrivateInternalParseTrashInfoFilesFunction
: public LoggedExtensionFunction {
public:
FileManagerPrivateInternalParseTrashInfoFilesFunction();

DECLARE_EXTENSION_FUNCTION("fileManagerPrivateInternal.parseTrashInfoFiles",
FILEMANAGERPRIVATEINTERNAL_PARSETRASHINFOFILES)

protected:
~FileManagerPrivateInternalParseTrashInfoFilesFunction() override;

// ExtensionFunction overrides
ResponseAction Run() override;

private:
// Invoked after calling the `ValidateAndParseTrashInfoFile` with all the data
// retrieved from the .trashinfo files. If any are error'd out they are logged
// and ultimately discarded.
void OnTrashInfoFilesParsed(
std::vector<base::FileErrorOr<file_manager::trash::ParsedTrashInfoData>>
parsed_data);

// After converting the restorePath (converted to Entry to ensure we can
// perform a getMetadata on it to verify existence) zip the 2 `std::vector`'s
// together to return back to the UI.
void OnConvertFileDefinitionListToEntryDefinitionList(
std::vector<file_manager::trash::ParsedTrashInfoData> parsed_data,
std::unique_ptr<file_manager::util::EntryDefinitionList>
entry_definition_list);

scoped_refptr<storage::FileSystemContext> file_system_context_;

// The TrashInfoValidator that maintains a connection to the TrashService
// which performs the parsing.
std::unique_ptr<file_manager::trash::TrashInfoValidator> validator_ = nullptr;
};

} // namespace extensions

#endif // CHROME_BROWSER_CHROMEOS_EXTENSIONS_FILE_MANAGER_PRIVATE_API_FILE_SYSTEM_H_
20 changes: 20 additions & 0 deletions chrome/common/extensions/api/file_manager_private.idl
Original file line number Diff line number Diff line change
Expand Up @@ -1071,6 +1071,19 @@ dictionary MountableGuest {
VmType vmType;
};

dictionary ParsedTrashInfoFile {
// The entry that the trashed file should be restored to. This does not exist
// but is used to identify whether the parent location still exists and
// identify the file name to restore to.
[instanceOf=Entry] object restoreEntry;

// The file name for the .trashinfo.
DOMString trashInfoFileName;

// The date the file was originally deleted.
double deletionDate;
};

// Callback that does not take arguments.
callback SimpleCallback = void();

Expand Down Expand Up @@ -1242,6 +1255,8 @@ callback HoldingSpaceStateCallback = void(HoldingSpaceState state);
// |guests| List of Guest OSs which have available mounts.
callback ListMountableGuestsCallback = void(MountableGuest[] guest);

callback ParseTrashInfoFilesCallback = void(ParsedTrashInfoFile[] files);

interface Functions {
// Logout the current user for navigating to the re-authentication screen for
// the Google account.
Expand Down Expand Up @@ -1789,6 +1804,11 @@ interface Functions {

// Opens the dialog to manage the currently syncing folders.
static void openManageSyncSettings();

// Validates and parses the supplied `entries` as .trashinfo files.
[nocompile]
static void parseTrashInfoFiles([instanceOf=Entry] object[] entries,
ParseTrashInfoFilesCallback callback);
};

// Events supported by fileManagerPrivate API. These events are broadcasted.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ namespace fileManagerPrivateInternal {
DOMString? destinationFolderUrl;
DOMString? password;
};
dictionary ParsedTrashInfoFile {
EntryDescription restoreEntry;
DOMString trashInfoFileName;
double deletionDate;
};

callback SimpleCallback = void();
callback ResolveIsolatedEntriesCallback = void(EntryDescription[] entries);
Expand Down Expand Up @@ -53,6 +58,7 @@ namespace fileManagerPrivateInternal {
callback GetThumbnailCallback = void(DOMString ThumbnailDataUrl);
callback BooleanCallback = void(boolean result);
callback GetVolumeRootCallback = void(EntryDescription rootDir);
callback ParseTrashInfoFilesCallback = void(ParsedTrashInfoFile[] files);

interface Functions {
static void resolveIsolatedEntries(DOMString[] urls,
Expand Down Expand Up @@ -155,5 +161,7 @@ namespace fileManagerPrivateInternal {
DOMString[] urls,
IOTaskParams params,
optional IOTaskIdCallback callback);
static void parseTrashInfoFiles(DOMString[] urls,
ParseTrashInfoFilesCallback callback);
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,22 @@ apiBridge.registerCustomHook(function(bindingsAPI) {
}
fileManagerPrivateInternal.startIOTask(type, urls, newParams, callback);
});

apiFunctions.setHandleRequest(
'parseTrashInfoFiles', function(entries, callback) {
const urls = entries.map(entry => getEntryURL(entry));
fileManagerPrivateInternal.parseTrashInfoFiles(
urls, function(entryDescriptions) {
// Convert the restoreEntry to a DirectoryEntry and the deletion
// date to a JS Date.
callback(entryDescriptions.map(description => {
description.restoreEntry =
GetExternalFileEntry(description.restoreEntry);
description.deletionDate = new Date(description.deletionDate);
return description;
}));
});
});
});

bindingUtil.registerEventArgumentMassager(
Expand Down
1 change: 1 addition & 0 deletions extensions/browser/extension_function_histogram_value.h
Original file line number Diff line number Diff line change
Expand Up @@ -1760,6 +1760,7 @@ enum HistogramValue {
PASSWORDSPRIVATE_REQUESTCREDENTIALDETAILS = 1697,
DEVELOPERPRIVATE_GETMATCHINGEXTENSIONSFORSITE = 1698,
ACCESSIBILITY_PRIVATE_GETDLCCONTENTS = 1699,
FILEMANAGERPRIVATEINTERNAL_PARSETRASHINFOFILES = 1700,
// Last entry: Add new entries above, then run:
// tools/metrics/histograms/update_extension_histograms.py
ENUM_BOUNDARY
Expand Down
18 changes: 18 additions & 0 deletions third_party/closure_compiler/externs/file_manager_private.js
Original file line number Diff line number Diff line change
Expand Up @@ -771,6 +771,15 @@ chrome.fileManagerPrivate.DlpLevel = {
*/
chrome.fileManagerPrivate.DlpRestrictionDetails;

/**
* @typedef {{
* restoreEntry: !Entry,
* trashInfoFileName: string,
* deletionDate: Date,
* }}
*/
chrome.fileManagerPrivate.ParsedTrashInfoFile;

/**
* Logout the current user for navigating to the re-authentication screen for
* the Google account.
Expand Down Expand Up @@ -1531,6 +1540,15 @@ chrome.fileManagerPrivate.openManageSyncSettings = function() {};
*/
chrome.fileManagerPrivate.getFrameColor = function(callback) {};

/**
* Parses the supplied .trashinfo files and returns the successfully parsed
* data.
* @param {!Array<!Entry>} entries
* @param {function(!Array<!chrome.fileManagerPrivate.ParsedTrashInfoFile>):
* void} callback
*/
chrome.fileManagerPrivate.parseTrashInfoFiles = function(entries, callback) {};

/** @type {!ChromeEvent} */
chrome.fileManagerPrivate.onMountCompleted;

Expand Down
1 change: 1 addition & 0 deletions tools/metrics/histograms/enums.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35338,6 +35338,7 @@ Called by update_extension_histograms.py.-->
<int value="1697" label="PASSWORDSPRIVATE_REQUESTCREDENTIALDETAILS"/>
<int value="1698" label="DEVELOPERPRIVATE_GETMATCHINGEXTENSIONSFORSITE"/>
<int value="1699" label="ACCESSIBILITY_PRIVATE_GETDLCCONTENTS"/>
<int value="1700" label="FILEMANAGERPRIVATEINTERNAL_PARSETRASHINFOFILES"/>
</enum>

<enum name="ExtensionIconState">
Expand Down

0 comments on commit 3bd34df

Please sign in to comment.