blob: d16cb3b4fad71ad3b6212c00f3a33cd3e3a007fe [file] [log] [blame]
// Copyright 2019 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 "content/browser/native_file_system/native_file_system_directory_handle_impl.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "content/browser/native_file_system/native_file_system_error.h"
#include "content/browser/native_file_system/native_file_system_transfer_token_impl.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "net/base/escape.h"
#include "storage/browser/fileapi/file_system_context.h"
#include "storage/browser/fileapi/file_system_operation_runner.h"
#include "storage/common/fileapi/file_system_util.h"
#include "third_party/blink/public/mojom/native_file_system/native_file_system_error.mojom.h"
#include "third_party/blink/public/mojom/native_file_system/native_file_system_file_handle.mojom.h"
#include "third_party/blink/public/mojom/native_file_system/native_file_system_transfer_token.mojom.h"
using blink::mojom::NativeFileSystemEntry;
using blink::mojom::NativeFileSystemEntryPtr;
using blink::mojom::NativeFileSystemHandle;
using blink::mojom::NativeFileSystemStatus;
using blink::mojom::NativeFileSystemTransferToken;
namespace content {
namespace {
// Returns true when |name| contains a path separator like "/".
bool ContainsPathSeparator(const std::string& name) {
const base::FilePath filepath_name = storage::StringToFilePath(name);
const size_t separator_position =
filepath_name.value().find_first_of(base::FilePath::kSeparators);
return separator_position != base::FilePath::StringType::npos;
}
// Returns true when |name| is "." or "..".
bool IsCurrentOrParentDirectory(const std::string& name) {
const base::FilePath filepath_name = storage::StringToFilePath(name);
return filepath_name.value() == base::FilePath::kCurrentDirectory ||
filepath_name.value() == base::FilePath::kParentDirectory;
}
} // namespace
struct NativeFileSystemDirectoryHandleImpl::ReadDirectoryState {
GetEntriesCallback callback;
std::vector<NativeFileSystemEntryPtr> entries;
};
NativeFileSystemDirectoryHandleImpl::NativeFileSystemDirectoryHandleImpl(
NativeFileSystemManagerImpl* manager,
const BindingContext& context,
const storage::FileSystemURL& url,
const SharedHandleState& handle_state)
: NativeFileSystemHandleBase(manager,
context,
url,
handle_state,
/*is_directory=*/true) {}
NativeFileSystemDirectoryHandleImpl::~NativeFileSystemDirectoryHandleImpl() =
default;
void NativeFileSystemDirectoryHandleImpl::GetPermissionStatus(
bool writable,
GetPermissionStatusCallback callback) {
DoGetPermissionStatus(writable, std::move(callback));
}
void NativeFileSystemDirectoryHandleImpl::RequestPermission(
bool writable,
RequestPermissionCallback callback) {
DoRequestPermission(writable, std::move(callback));
}
void NativeFileSystemDirectoryHandleImpl::GetFile(const std::string& basename,
bool create,
GetFileCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
storage::FileSystemURL child_url;
blink::mojom::NativeFileSystemErrorPtr get_child_url_result =
GetChildURL(basename, &child_url);
if (get_child_url_result->status != NativeFileSystemStatus::kOk) {
std::move(callback).Run(std::move(get_child_url_result),
mojo::NullRemote());
return;
}
if (GetReadPermissionStatus() != PermissionStatus::GRANTED) {
std::move(callback).Run(native_file_system_error::FromStatus(
NativeFileSystemStatus::kPermissionDenied),
mojo::NullRemote());
return;
}
if (create) {
// If |create| is true, write permission is required unconditionally, i.e.
// even if the file already exists. This is intentional, and matches the
// behavior that is specified in the spec.
RunWithWritePermission(
base::BindOnce(
&NativeFileSystemDirectoryHandleImpl::GetFileWithWritePermission,
weak_factory_.GetWeakPtr(), child_url),
base::BindOnce([](GetFileCallback callback) {
std::move(callback).Run(
native_file_system_error::FromStatus(
NativeFileSystemStatus::kPermissionDenied),
mojo::NullRemote());
}),
std::move(callback));
} else {
operation_runner()->FileExists(
child_url,
base::BindOnce(&NativeFileSystemDirectoryHandleImpl::DidGetFile,
weak_factory_.GetWeakPtr(), child_url,
std::move(callback)));
}
}
void NativeFileSystemDirectoryHandleImpl::GetDirectory(
const std::string& basename,
bool create,
GetDirectoryCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
storage::FileSystemURL child_url;
blink::mojom::NativeFileSystemErrorPtr get_child_url_result =
GetChildURL(basename, &child_url);
if (get_child_url_result->status != NativeFileSystemStatus::kOk) {
std::move(callback).Run(std::move(get_child_url_result),
mojo::NullRemote());
return;
}
if (GetReadPermissionStatus() != PermissionStatus::GRANTED) {
std::move(callback).Run(native_file_system_error::FromStatus(
NativeFileSystemStatus::kPermissionDenied),
mojo::NullRemote());
return;
}
if (create) {
// If |create| is true, write permission is required unconditionally, i.e.
// even if the file already exists. This is intentional, and matches the
// behavior that is specified in the spec.
RunWithWritePermission(
base::BindOnce(&NativeFileSystemDirectoryHandleImpl::
GetDirectoryWithWritePermission,
weak_factory_.GetWeakPtr(), child_url),
base::BindOnce([](GetDirectoryCallback callback) {
std::move(callback).Run(
native_file_system_error::FromStatus(
NativeFileSystemStatus::kPermissionDenied),
mojo::NullRemote());
}),
std::move(callback));
} else {
operation_runner()->DirectoryExists(
child_url,
base::BindOnce(&NativeFileSystemDirectoryHandleImpl::DidGetDirectory,
weak_factory_.GetWeakPtr(), child_url,
std::move(callback)));
}
}
void NativeFileSystemDirectoryHandleImpl::GetEntries(
GetEntriesCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
operation_runner()->ReadDirectory(
url(), base::BindRepeating(
&NativeFileSystemDirectoryHandleImpl::DidReadDirectory,
weak_factory_.GetWeakPtr(),
base::Owned(new ReadDirectoryState{std::move(callback)})));
}
void NativeFileSystemDirectoryHandleImpl::RemoveEntry(
const std::string& basename,
bool recurse,
RemoveEntryCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
storage::FileSystemURL child_url;
blink::mojom::NativeFileSystemErrorPtr get_child_url_result =
GetChildURL(basename, &child_url);
if (get_child_url_result->status != NativeFileSystemStatus::kOk) {
std::move(callback).Run(std::move(get_child_url_result));
return;
}
RunWithWritePermission(
base::BindOnce(&NativeFileSystemDirectoryHandleImpl::RemoveEntryImpl,
weak_factory_.GetWeakPtr(), child_url, recurse),
base::BindOnce([](RemoveEntryCallback callback) {
std::move(callback).Run(native_file_system_error::FromStatus(
NativeFileSystemStatus::kPermissionDenied));
}),
std::move(callback));
}
void NativeFileSystemDirectoryHandleImpl::Transfer(
mojo::PendingReceiver<NativeFileSystemTransferToken> token) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
manager()->CreateTransferToken(*this, std::move(token));
}
void NativeFileSystemDirectoryHandleImpl::GetFileWithWritePermission(
const storage::FileSystemURL& child_url,
GetFileCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK_EQ(GetWritePermissionStatus(),
blink::mojom::PermissionStatus::GRANTED);
operation_runner()->CreateFile(
child_url, /*exclusive=*/false,
base::BindOnce(&NativeFileSystemDirectoryHandleImpl::DidGetFile,
weak_factory_.GetWeakPtr(), child_url,
std::move(callback)));
}
void NativeFileSystemDirectoryHandleImpl::DidGetFile(
const storage::FileSystemURL& url,
GetFileCallback callback,
base::File::Error result) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (result != base::File::FILE_OK) {
std::move(callback).Run(native_file_system_error::FromFileError(result),
mojo::NullRemote());
return;
}
std::move(callback).Run(
native_file_system_error::Ok(),
manager()->CreateFileHandle(context(), url, handle_state()));
}
void NativeFileSystemDirectoryHandleImpl::GetDirectoryWithWritePermission(
const storage::FileSystemURL& child_url,
GetDirectoryCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK_EQ(GetWritePermissionStatus(),
blink::mojom::PermissionStatus::GRANTED);
operation_runner()->CreateDirectory(
child_url, /*exclusive=*/false, /*recursive=*/false,
base::BindOnce(&NativeFileSystemDirectoryHandleImpl::DidGetDirectory,
weak_factory_.GetWeakPtr(), child_url,
std::move(callback)));
}
void NativeFileSystemDirectoryHandleImpl::DidGetDirectory(
const storage::FileSystemURL& url,
GetDirectoryCallback callback,
base::File::Error result) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (result != base::File::FILE_OK) {
std::move(callback).Run(native_file_system_error::FromFileError(result),
mojo::NullRemote());
return;
}
std::move(callback).Run(
native_file_system_error::Ok(),
manager()->CreateDirectoryHandle(context(), url, handle_state()));
}
void NativeFileSystemDirectoryHandleImpl::DidReadDirectory(
ReadDirectoryState* state,
base::File::Error result,
std::vector<filesystem::mojom::DirectoryEntry> file_list,
bool has_more) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (result != base::File::FILE_OK) {
DCHECK(!has_more);
std::move(state->callback)
.Run(native_file_system_error::FromFileError(result), {});
return;
}
for (const auto& entry : file_list) {
std::string basename = storage::FilePathToString(entry.name);
storage::FileSystemURL child_url;
blink::mojom::NativeFileSystemErrorPtr get_child_url_result =
GetChildURL(basename, &child_url);
// All entries must exist in this directory as a direct child with a valid
// |basename|.
CHECK_EQ(get_child_url_result->status, NativeFileSystemStatus::kOk);
state->entries.push_back(
CreateEntry(basename, child_url,
entry.type == filesystem::mojom::FsFileType::DIRECTORY));
}
// TODO(mek): Change API so we can stream back entries as they come in, rather
// than waiting till we have retrieved them all.
if (!has_more) {
std::move(state->callback)
.Run(native_file_system_error::Ok(), std::move(state->entries));
}
}
void NativeFileSystemDirectoryHandleImpl::RemoveEntryImpl(
const storage::FileSystemURL& url,
bool recurse,
RemoveEntryCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK_EQ(GetWritePermissionStatus(),
blink::mojom::PermissionStatus::GRANTED);
operation_runner()->Remove(
url, recurse,
base::BindOnce(
[](RemoveEntryCallback callback, base::File::Error result) {
std::move(callback).Run(
native_file_system_error::FromFileError(result));
},
std::move(callback)));
}
blink::mojom::NativeFileSystemErrorPtr
NativeFileSystemDirectoryHandleImpl::GetChildURL(
const std::string& basename,
storage::FileSystemURL* result) {
// TODO(mek): Rather than doing URL serialization and parsing we should just
// have a way to get a child FileSystemURL directly from its parent.
if (basename.empty()) {
return native_file_system_error::FromStatus(
NativeFileSystemStatus::kInvalidArgument,
"Name can't be an empty string.");
}
if (ContainsPathSeparator(basename) || IsCurrentOrParentDirectory(basename)) {
// |basename| must refer to a entry that exists in this directory as a
// direct child.
return native_file_system_error::FromStatus(
NativeFileSystemStatus::kInvalidArgument,
"Name contains invalid characters.");
}
std::string escaped_name =
net::EscapeQueryParamValue(basename, /*use_plus=*/false);
GURL parent_url = url().ToGURL();
std::string path = base::StrCat({parent_url.path(), "/", escaped_name});
GURL::Replacements replacements;
replacements.SetPathStr(path);
GURL child_url = parent_url.ReplaceComponents(replacements);
*result = file_system_context()->CrackURL(child_url);
return native_file_system_error::Ok();
}
NativeFileSystemEntryPtr NativeFileSystemDirectoryHandleImpl::CreateEntry(
const std::string& basename,
const storage::FileSystemURL& url,
bool is_directory) {
if (is_directory) {
return NativeFileSystemEntry::New(
NativeFileSystemHandle::NewDirectory(
manager()->CreateDirectoryHandle(context(), url, handle_state())),
basename);
}
return NativeFileSystemEntry::New(
NativeFileSystemHandle::NewFile(
manager()->CreateFileHandle(context(), url, handle_state())),
basename);
}
base::WeakPtr<NativeFileSystemHandleBase>
NativeFileSystemDirectoryHandleImpl::AsWeakPtr() {
return weak_factory_.GetWeakPtr();
}
} // namespace content