blob: 62f96f0dab6a53718992a3a2d894e3d9830e6cb0 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/file_manager/fileapi_util.h"
#include <stddef.h>
#include <utility>
#include "base/bind.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/macros.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/post_task.h"
#include "chrome/browser/chromeos/file_manager/app_id.h"
#include "chrome/browser/chromeos/file_manager/filesystem_api_util.h"
#include "chrome/browser/profiles/profile.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/site_instance.h"
#include "content/public/browser/storage_partition.h"
#include "extensions/browser/extension_util.h"
#include "extensions/common/extension.h"
#include "google_apis/drive/task_util.h"
#include "net/base/escape.h"
#include "storage/browser/file_system/file_system_context.h"
#include "storage/browser/file_system/isolated_context.h"
#include "storage/browser/file_system/open_file_system_mode.h"
#include "storage/common/file_system/file_system_util.h"
#include "third_party/blink/public/mojom/choosers/file_chooser.mojom.h"
#include "ui/shell_dialogs/selected_file_info.h"
#include "url/gurl.h"
#include "url/origin.h"
using content::BrowserThread;
namespace file_manager {
namespace util {
using blink::mojom::FileChooserFileInfo;
using blink::mojom::FileSystemFileInfo;
using blink::mojom::NativeFileInfo;
namespace {
GURL ConvertRelativeFilePathToFileSystemUrl(const base::FilePath& relative_path,
const std::string& extension_id) {
GURL base_url = storage::GetFileSystemRootURI(
extensions::Extension::GetBaseURLFromExtensionId(extension_id),
storage::kFileSystemTypeExternal);
return GURL(base_url.spec() +
net::EscapeUrlEncodedData(relative_path.AsUTF8Unsafe(),
false)); // Space to %20 instead of +.
}
// Creates an ErrorDefinition with an error set to |error|.
EntryDefinition CreateEntryDefinitionWithError(base::File::Error error) {
EntryDefinition result;
result.error = error;
return result;
}
// Helper class for performing conversions from file definitions to entry
// definitions. It is possible to do it without a class, but the code would be
// crazy and super tricky.
//
// This class copies the input |file_definition_list|,
// so there is no need to worry about validity of passed |file_definition_list|
// reference. Also, it automatically deletes itself after converting finished,
// or if shutdown is invoked during ResolveURL(). Must be called on UI thread.
class FileDefinitionListConverter {
public:
FileDefinitionListConverter(Profile* profile,
const std::string& extension_id,
const FileDefinitionList& file_definition_list,
EntryDefinitionListCallback callback);
~FileDefinitionListConverter() = default;
private:
// Converts the element under the iterator to an entry. First, converts
// the virtual path to an URL, and calls OnResolvedURL(). In case of error
// calls OnIteratorConverted with an error entry definition.
void ConvertNextIterator(
std::unique_ptr<FileDefinitionListConverter> self_deleter,
FileDefinitionList::const_iterator iterator);
// Creates an entry definition from the URL as well as the file definition.
// Then, calls OnIteratorConverted with the created entry definition.
void OnResolvedURL(std::unique_ptr<FileDefinitionListConverter> self_deleter,
FileDefinitionList::const_iterator iterator,
base::File::Error error,
const storage::FileSystemInfo& info,
const base::FilePath& file_path,
storage::FileSystemContext::ResolvedEntryType type);
// Called when the iterator is converted. Adds the |entry_definition| to
// |results_| and calls ConvertNextIterator() for the next element.
void OnIteratorConverted(
std::unique_ptr<FileDefinitionListConverter> self_deleter,
FileDefinitionList::const_iterator iterator,
const EntryDefinition& entry_definition);
scoped_refptr<storage::FileSystemContext> file_system_context_;
const std::string extension_id_;
const FileDefinitionList file_definition_list_;
EntryDefinitionListCallback callback_;
std::unique_ptr<EntryDefinitionList> result_;
};
FileDefinitionListConverter::FileDefinitionListConverter(
Profile* profile,
const std::string& extension_id,
const FileDefinitionList& file_definition_list,
EntryDefinitionListCallback callback)
: extension_id_(extension_id),
file_definition_list_(file_definition_list),
callback_(std::move(callback)),
result_(new EntryDefinitionList) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
file_system_context_ =
GetFileSystemContextForExtensionId(profile, extension_id_);
// Deletes the converter, once the scoped pointer gets out of scope. It is
// either, if the conversion is finished, or ResolveURL() is terminated, and
// the callback not called because of shutdown.
std::unique_ptr<FileDefinitionListConverter> self_deleter(this);
ConvertNextIterator(std::move(self_deleter), file_definition_list_.begin());
}
void FileDefinitionListConverter::ConvertNextIterator(
std::unique_ptr<FileDefinitionListConverter> self_deleter,
FileDefinitionList::const_iterator iterator) {
if (iterator == file_definition_list_.end()) {
// The converter object will be destroyed since |self_deleter| gets out of
// scope.
std::move(callback_).Run(std::move(result_));
return;
}
if (!file_system_context_.get()) {
OnIteratorConverted(std::move(self_deleter), iterator,
CreateEntryDefinitionWithError(
base::File::FILE_ERROR_INVALID_OPERATION));
return;
}
storage::FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL(
url::Origin::Create(
extensions::Extension::GetBaseURLFromExtensionId(extension_id_)),
storage::kFileSystemTypeExternal, iterator->virtual_path);
if (!url.is_valid()) {
OnIteratorConverted(
std::move(self_deleter), iterator,
CreateEntryDefinitionWithError(base::File::FILE_ERROR_NOT_FOUND));
return;
}
// The converter object will be deleted if the callback is not called because
// of shutdown during ResolveURL().
file_system_context_->ResolveURL(
url, base::BindOnce(&FileDefinitionListConverter::OnResolvedURL,
base::Unretained(this), std::move(self_deleter),
iterator));
}
void FileDefinitionListConverter::OnResolvedURL(
std::unique_ptr<FileDefinitionListConverter> self_deleter,
FileDefinitionList::const_iterator iterator,
base::File::Error error,
const storage::FileSystemInfo& info,
const base::FilePath& file_path,
storage::FileSystemContext::ResolvedEntryType type) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (error != base::File::FILE_OK) {
OnIteratorConverted(std::move(self_deleter), iterator,
CreateEntryDefinitionWithError(error));
return;
}
EntryDefinition entry_definition;
entry_definition.file_system_root_url = info.root_url.spec();
entry_definition.file_system_name = info.name;
switch (type) {
case storage::FileSystemContext::RESOLVED_ENTRY_FILE:
entry_definition.is_directory = false;
break;
case storage::FileSystemContext::RESOLVED_ENTRY_DIRECTORY:
entry_definition.is_directory = true;
break;
case storage::FileSystemContext::RESOLVED_ENTRY_NOT_FOUND:
entry_definition.is_directory = iterator->is_directory;
break;
}
entry_definition.error = base::File::FILE_OK;
// Construct a target Entry.fullPath value from the virtual path and the
// root URL. Eg. Downloads/A/b.txt -> A/b.txt.
storage::FileSystemURL fs_url = file_system_context_->CrackURL(info.root_url);
if (!fs_url.is_valid()) {
OnIteratorConverted(
std::move(self_deleter), iterator,
CreateEntryDefinitionWithError(base::File::FILE_ERROR_NOT_FOUND));
return;
}
const base::FilePath root_virtual_path = fs_url.virtual_path();
DCHECK(root_virtual_path == iterator->virtual_path ||
root_virtual_path.IsParent(iterator->virtual_path));
base::FilePath full_path;
root_virtual_path.AppendRelativePath(iterator->virtual_path, &full_path);
entry_definition.full_path = full_path;
OnIteratorConverted(std::move(self_deleter), iterator, entry_definition);
}
void FileDefinitionListConverter::OnIteratorConverted(
std::unique_ptr<FileDefinitionListConverter> self_deleter,
FileDefinitionList::const_iterator iterator,
const EntryDefinition& entry_definition) {
result_->push_back(entry_definition);
ConvertNextIterator(std::move(self_deleter), ++iterator);
}
// Helper function to return the converted definition entry directly, without
// the redundant container.
void OnConvertFileDefinitionDone(
EntryDefinitionCallback callback,
std::unique_ptr<EntryDefinitionList> entry_definition_list) {
DCHECK_EQ(1u, entry_definition_list->size());
std::move(callback).Run(entry_definition_list->at(0));
}
// Checks if the |file_path| points non-native location or not.
bool IsUnderNonNativeLocalPath(const storage::FileSystemContext& context,
const base::FilePath& file_path) {
base::FilePath virtual_path;
if (!context.external_backend()->GetVirtualPath(file_path, &virtual_path))
return false;
const storage::FileSystemURL url = context.CreateCrackedFileSystemURL(
url::Origin(), storage::kFileSystemTypeExternal, virtual_path);
if (!url.is_valid())
return false;
return IsNonNativeFileSystemType(url.type());
}
// Helper class to convert SelectedFileInfoList into ChooserFileInfoList.
class ConvertSelectedFileInfoListToFileChooserFileInfoListImpl {
public:
// The scoped pointer to control lifetime of the instance itself. The pointer
// is passed to callback functions and binds the lifetime of the instance to
// the callback's lifetime.
typedef std::unique_ptr<
ConvertSelectedFileInfoListToFileChooserFileInfoListImpl>
Lifetime;
ConvertSelectedFileInfoListToFileChooserFileInfoListImpl(
storage::FileSystemContext* context,
const GURL& origin,
const SelectedFileInfoList& selected_info_list,
FileChooserFileInfoListCallback callback)
: context_(context),
callback_(std::move(callback)) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
Lifetime lifetime(this);
bool need_fill_metadata = false;
for (size_t i = 0; i < selected_info_list.size(); ++i) {
// Native file.
if (!IsUnderNonNativeLocalPath(*context,
selected_info_list[i].file_path)) {
chooser_info_list_.push_back(
FileChooserFileInfo::NewNativeFile(NativeFileInfo::New(
selected_info_list[i].file_path,
base::UTF8ToUTF16(selected_info_list[i].display_name))));
continue;
}
// Non-native file, but it has a native snapshot file.
if (!selected_info_list[i].local_path.empty()) {
chooser_info_list_.push_back(
FileChooserFileInfo::NewNativeFile(NativeFileInfo::New(
selected_info_list[i].local_path,
base::UTF8ToUTF16(selected_info_list[i].display_name))));
continue;
}
// Non-native file without a snapshot file.
base::FilePath virtual_path;
if (!context->external_backend()->GetVirtualPath(
selected_info_list[i].file_path, &virtual_path)) {
NotifyError(std::move(lifetime));
return;
}
FileSystemURLAndHandle isolated_file_system_url_and_handle =
CreateIsolatedURLFromVirtualPath(*context_, origin, virtual_path);
const GURL url = isolated_file_system_url_and_handle.url.ToGURL();
if (!url.is_valid()) {
NotifyError(std::move(lifetime));
return;
}
// Increase ref count of file system to keep it alive after |file_system|
// goes out of scope. Our destructor will eventually revoke the file
// system.
storage::IsolatedContext::GetInstance()->AddReference(
isolated_file_system_url_and_handle.handle.id());
auto fs_info = FileSystemFileInfo::New();
fs_info->url = url;
chooser_info_list_.push_back(
FileChooserFileInfo::NewFileSystem(std::move(fs_info)));
need_fill_metadata = true;
}
// If the list includes at least one non-native file (wihtout a snapshot
// file), move to IO thread to obtian metadata for the non-native file.
if (need_fill_metadata) {
base::PostTask(
FROM_HERE, {BrowserThread::IO},
base::BindOnce(
&ConvertSelectedFileInfoListToFileChooserFileInfoListImpl::
FillMetadataOnIOThread,
base::Unretained(this), std::move(lifetime),
chooser_info_list_.begin()));
return;
}
NotifyComplete(std::move(lifetime));
}
~ConvertSelectedFileInfoListToFileChooserFileInfoListImpl() {
for (const auto& info : chooser_info_list_) {
if (info && info->is_file_system()) {
storage::IsolatedContext::GetInstance()->RevokeFileSystem(
context_->CrackURL(info->get_file_system()->url)
.mount_filesystem_id());
}
}
}
private:
// Obtains metadata for the non-native file |it|.
void FillMetadataOnIOThread(Lifetime lifetime,
const FileChooserFileInfoList::iterator& it) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (it == chooser_info_list_.end()) {
base::PostTask(
FROM_HERE, {BrowserThread::UI},
base::BindOnce(
&ConvertSelectedFileInfoListToFileChooserFileInfoListImpl::
NotifyComplete,
base::Unretained(this), std::move(lifetime)));
return;
}
if ((*it)->is_native_file()) {
FillMetadataOnIOThread(std::move(lifetime), it + 1);
return;
}
context_->operation_runner()->GetMetadata(
context_->CrackURL((*it)->get_file_system()->url),
storage::FileSystemOperation::GET_METADATA_FIELD_IS_DIRECTORY |
storage::FileSystemOperation::GET_METADATA_FIELD_SIZE |
storage::FileSystemOperation::GET_METADATA_FIELD_LAST_MODIFIED,
base::BindOnce(
&ConvertSelectedFileInfoListToFileChooserFileInfoListImpl::
OnGotMetadataOnIOThread,
base::Unretained(this), std::move(lifetime), it));
}
// Callback invoked after GetMetadata.
void OnGotMetadataOnIOThread(Lifetime lifetime,
const FileChooserFileInfoList::iterator& it,
base::File::Error result,
const base::File::Info& file_info) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (result != base::File::FILE_OK) {
base::PostTask(
FROM_HERE, {BrowserThread::UI},
base::BindOnce(
&ConvertSelectedFileInfoListToFileChooserFileInfoListImpl::
NotifyError,
base::Unretained(this), std::move(lifetime)));
return;
}
(*it)->get_file_system()->length = file_info.size;
(*it)->get_file_system()->modification_time = file_info.last_modified;
DCHECK(!file_info.is_directory);
FillMetadataOnIOThread(std::move(lifetime), it + 1);
}
// Returns a result to the |callback_|.
void NotifyComplete(Lifetime /* lifetime */) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Move the list content so that the file systems are not revoked at the
// destructor.
std::move(callback_).Run(std::move(chooser_info_list_));
}
// Returns an empty list to the |callback_|.
void NotifyError(Lifetime /* lifetime */) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
std::move(callback_).Run(FileChooserFileInfoList());
}
scoped_refptr<storage::FileSystemContext> context_;
FileChooserFileInfoList chooser_info_list_;
FileChooserFileInfoListCallback callback_;
DISALLOW_COPY_AND_ASSIGN(
ConvertSelectedFileInfoListToFileChooserFileInfoListImpl);
};
void CheckIfDirectoryExistsOnIoThread(
scoped_refptr<storage::FileSystemContext> file_system_context,
const storage::FileSystemURL& internal_url,
storage::FileSystemOperationRunner::StatusCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
file_system_context->operation_runner()->DirectoryExists(internal_url,
std::move(callback));
}
void GetMetadataForPathOnIoThread(
scoped_refptr<storage::FileSystemContext> file_system_context,
const storage::FileSystemURL& internal_url,
int fields,
storage::FileSystemOperationRunner::GetMetadataCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
file_system_context->operation_runner()->GetMetadata(internal_url, fields,
std::move(callback));
}
} // namespace
EntryDefinition::EntryDefinition() = default;
EntryDefinition::EntryDefinition(const EntryDefinition& other) = default;
EntryDefinition::~EntryDefinition() = default;
storage::FileSystemContext* GetFileSystemContextForExtensionId(
Profile* profile,
const std::string& extension_id) {
return extensions::util::GetStoragePartitionForExtensionId(extension_id,
profile)
->GetFileSystemContext();
}
storage::FileSystemContext* GetFileSystemContextForRenderFrameHost(
Profile* profile,
content::RenderFrameHost* render_frame_host) {
return render_frame_host->GetStoragePartition()->GetFileSystemContext();
}
bool ConvertAbsoluteFilePathToFileSystemUrl(Profile* profile,
const base::FilePath& absolute_path,
const std::string& extension_id,
GURL* url) {
base::FilePath relative_path;
if (!ConvertAbsoluteFilePathToRelativeFileSystemPath(profile,
extension_id,
absolute_path,
&relative_path)) {
return false;
}
*url = ConvertRelativeFilePathToFileSystemUrl(relative_path, extension_id);
return true;
}
bool ConvertAbsoluteFilePathToRelativeFileSystemPath(
Profile* profile,
const std::string& extension_id,
const base::FilePath& absolute_path,
base::FilePath* virtual_path) {
storage::ExternalFileSystemBackend* backend =
GetFileSystemContextForExtensionId(profile, extension_id)
->external_backend();
if (!backend)
return false;
// Find if this file path is managed by the external backend.
if (!backend->GetVirtualPath(absolute_path, virtual_path))
return false;
return true;
}
void ConvertFileDefinitionListToEntryDefinitionList(
Profile* profile,
const std::string& extension_id,
const FileDefinitionList& file_definition_list,
EntryDefinitionListCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// The converter object destroys itself.
new FileDefinitionListConverter(profile, extension_id, file_definition_list,
std::move(callback));
}
void ConvertFileDefinitionToEntryDefinition(
Profile* profile,
const std::string& extension_id,
const FileDefinition& file_definition,
EntryDefinitionCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
FileDefinitionList file_definition_list;
file_definition_list.push_back(file_definition);
ConvertFileDefinitionListToEntryDefinitionList(
profile, extension_id, file_definition_list,
base::BindOnce(&OnConvertFileDefinitionDone, std::move(callback)));
}
void ConvertSelectedFileInfoListToFileChooserFileInfoList(
storage::FileSystemContext* context,
const GURL& origin,
const SelectedFileInfoList& selected_info_list,
FileChooserFileInfoListCallback callback) {
// The object deletes itself.
new ConvertSelectedFileInfoListToFileChooserFileInfoListImpl(
context, origin, selected_info_list, std::move(callback));
}
std::unique_ptr<base::DictionaryValue> ConvertEntryDefinitionToValue(
const EntryDefinition& entry_definition) {
auto entry = std::make_unique<base::DictionaryValue>();
entry->SetString("fileSystemName", entry_definition.file_system_name);
entry->SetString("fileSystemRoot", entry_definition.file_system_root_url);
entry->SetString(
"fileFullPath",
base::FilePath("/").Append(entry_definition.full_path).AsUTF8Unsafe());
entry->SetBoolean("fileIsDirectory", entry_definition.is_directory);
return entry;
}
std::unique_ptr<base::ListValue> ConvertEntryDefinitionListToListValue(
const EntryDefinitionList& entry_definition_list) {
auto entries = std::make_unique<base::ListValue>();
for (auto it = entry_definition_list.begin();
it != entry_definition_list.end(); ++it) {
entries->Append(ConvertEntryDefinitionToValue(*it));
}
return entries;
}
void CheckIfDirectoryExists(
scoped_refptr<storage::FileSystemContext> file_system_context,
const base::FilePath& directory_path,
storage::FileSystemOperationRunner::StatusCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
storage::ExternalFileSystemBackend* const backend =
file_system_context->external_backend();
DCHECK(backend);
const storage::FileSystemURL internal_url =
backend->CreateInternalURL(file_system_context.get(), directory_path);
base::PostTask(
FROM_HERE, {BrowserThread::IO},
base::BindOnce(&CheckIfDirectoryExistsOnIoThread, file_system_context,
internal_url,
google_apis::CreateRelayCallback(std::move(callback))));
}
void GetMetadataForPath(
scoped_refptr<storage::FileSystemContext> file_system_context,
const base::FilePath& entry_path,
int fields,
storage::FileSystemOperationRunner::GetMetadataCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
storage::ExternalFileSystemBackend* const backend =
file_system_context->external_backend();
DCHECK(backend);
const storage::FileSystemURL internal_url =
backend->CreateInternalURL(file_system_context.get(), entry_path);
base::PostTask(
FROM_HERE, {BrowserThread::IO},
base::BindOnce(&GetMetadataForPathOnIoThread, file_system_context,
internal_url, fields,
google_apis::CreateRelayCallback(std::move(callback))));
}
FileSystemURLAndHandle CreateIsolatedURLFromVirtualPath(
const storage::FileSystemContext& context,
const GURL& origin,
const base::FilePath& virtual_path) {
const storage::FileSystemURL original_url =
context.CreateCrackedFileSystemURL(url::Origin::Create(origin),
storage::kFileSystemTypeExternal,
virtual_path);
std::string register_name;
storage::IsolatedContext::ScopedFSHandle file_system =
storage::IsolatedContext::GetInstance()->RegisterFileSystemForPath(
original_url.type(), original_url.filesystem_id(),
original_url.path(), &register_name);
storage::FileSystemURL isolated_url = context.CreateCrackedFileSystemURL(
url::Origin::Create(origin), storage::kFileSystemTypeIsolated,
base::FilePath(file_system.id()).Append(register_name));
return {isolated_url, file_system};
}
} // namespace util
} // namespace file_manager