blob: 24b5a877a926309c02df6db1117856633dfa01bb [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_file_handle_impl.h"
#include "base/guid.h"
#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "content/browser/native_file_system/native_file_system_error.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "net/base/mime_util.h"
#include "storage/browser/blob/blob_data_builder.h"
#include "storage/browser/blob/blob_impl.h"
#include "storage/browser/blob/blob_storage_context.h"
#include "storage/browser/fileapi/file_system_operation_runner.h"
#include "third_party/blink/public/mojom/blob/blob.mojom.h"
#include "third_party/blink/public/mojom/blob/serialized_blob.mojom.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_transfer_token.mojom.h"
using blink::mojom::NativeFileSystemStatus;
using storage::BlobDataHandle;
using storage::BlobImpl;
using storage::FileSystemOperation;
using storage::FileSystemOperationRunner;
using storage::IsolatedContext;
namespace content {
NativeFileSystemFileHandleImpl::NativeFileSystemFileHandleImpl(
NativeFileSystemManagerImpl* manager,
const BindingContext& context,
const storage::FileSystemURL& url,
const SharedHandleState& handle_state)
: NativeFileSystemHandleBase(manager,
context,
url,
handle_state,
/*is_directory=*/false) {}
NativeFileSystemFileHandleImpl::~NativeFileSystemFileHandleImpl() = default;
void NativeFileSystemFileHandleImpl::GetPermissionStatus(
bool writable,
GetPermissionStatusCallback callback) {
DoGetPermissionStatus(writable, std::move(callback));
}
void NativeFileSystemFileHandleImpl::RequestPermission(
bool writable,
RequestPermissionCallback callback) {
DoRequestPermission(writable, std::move(callback));
}
void NativeFileSystemFileHandleImpl::AsBlob(AsBlobCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (GetReadPermissionStatus() != PermissionStatus::GRANTED) {
std::move(callback).Run(native_file_system_error::FromStatus(
NativeFileSystemStatus::kPermissionDenied),
nullptr);
return;
}
// TODO(mek): Check backend::SupportsStreaming and create snapshot file if
// streaming is not supported.
DoFileSystemOperation(
FROM_HERE, &FileSystemOperationRunner::GetMetadata,
base::BindOnce(&NativeFileSystemFileHandleImpl::DidGetMetaDataForBlob,
weak_factory_.GetWeakPtr(), std::move(callback)),
url(),
FileSystemOperation::GET_METADATA_FIELD_IS_DIRECTORY |
FileSystemOperation::GET_METADATA_FIELD_SIZE |
FileSystemOperation::GET_METADATA_FIELD_LAST_MODIFIED);
}
void NativeFileSystemFileHandleImpl::CreateFileWriter(
bool keep_existing_data,
CreateFileWriterCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
RunWithWritePermission(
base::BindOnce(&NativeFileSystemFileHandleImpl::CreateFileWriterImpl,
weak_factory_.GetWeakPtr(), keep_existing_data),
base::BindOnce([](CreateFileWriterCallback callback) {
std::move(callback).Run(native_file_system_error::FromStatus(
NativeFileSystemStatus::kPermissionDenied),
mojo::NullRemote());
}),
std::move(callback));
}
void NativeFileSystemFileHandleImpl::Transfer(
mojo::PendingReceiver<blink::mojom::NativeFileSystemTransferToken> token) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
manager()->CreateTransferToken(*this, std::move(token));
}
void NativeFileSystemFileHandleImpl::DidGetMetaDataForBlob(
AsBlobCallback callback,
base::File::Error result,
const base::File::Info& info) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (result != base::File::FILE_OK) {
std::move(callback).Run(native_file_system_error::FromFileError(result),
nullptr);
return;
}
std::string uuid = base::GenerateGUID();
auto blob_builder = std::make_unique<storage::BlobDataBuilder>(uuid);
// Only append if the file has data.
if (info.size > 0) {
// Use AppendFileSystemFile here, since we're streaming the file directly
// from the file system backend, and the file thus might not actually be
// backed by a file on disk.
blob_builder->AppendFileSystemFile(url().ToGURL(), 0, info.size,
info.last_modified,
file_system_context());
}
base::FilePath::StringType extension = url().path().Extension();
if (!extension.empty()) {
std::string mime_type;
// TODO(https://crbug.com/962306): Using GetMimeTypeFromExtension and
// including platform defined mime type mappings might be nice/make sense,
// however that method can potentially block and thus can't be called from
// the IO thread.
if (net::GetWellKnownMimeTypeFromExtension(extension.substr(1), &mime_type))
blob_builder->set_content_type(mime_type);
}
std::unique_ptr<BlobDataHandle> blob_handle =
blob_context()->AddFinishedBlob(std::move(blob_builder));
if (blob_handle->IsBroken()) {
std::move(callback).Run(
native_file_system_error::FromStatus(
NativeFileSystemStatus::kOperationFailed, "Failed to create blob."),
nullptr);
return;
}
std::string content_type = blob_handle->content_type();
mojo::PendingRemote<blink::mojom::Blob> blob_remote;
BlobImpl::Create(std::move(blob_handle),
blob_remote.InitWithNewPipeAndPassReceiver());
std::move(callback).Run(
native_file_system_error::Ok(),
blink::mojom::SerializedBlob::New(uuid, content_type, info.size,
std::move(blob_remote)));
}
void NativeFileSystemFileHandleImpl::CreateFileWriterImpl(
bool keep_existing_data,
CreateFileWriterCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK_EQ(GetWritePermissionStatus(),
blink::mojom::PermissionStatus::GRANTED);
// We first attempt to create the swap file, even if we might do a subsequent
// operation to copy a file to the same path if keep_existing_data is set.
// This file creation has to be `exclusive`, meaning, it will fail if a file
// already exists. Using the filesystem for synchronization, a successful
// creation of the file ensures that this File Writer creation request owns
// the file and eliminates possible race conditions.
CreateSwapFile(
/*count=*/0, keep_existing_data, std::move(callback));
}
void NativeFileSystemFileHandleImpl::CreateSwapFile(
int count,
bool keep_existing_data,
CreateFileWriterCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(count >= 0);
DCHECK(max_swap_files_ >= 0);
if (GetWritePermissionStatus() != blink::mojom::PermissionStatus::GRANTED) {
std::move(callback).Run(native_file_system_error::FromStatus(
NativeFileSystemStatus::kPermissionDenied),
mojo::NullRemote());
return;
}
auto swap_path =
base::FilePath(url().virtual_path()).AddExtensionASCII(".crswap");
auto swap_file_system = file_system();
if (count >= max_swap_files_) {
DLOG(ERROR) << "Error Creating Swap File, count: " << count
<< " exceeds max unique files of: " << max_swap_files_
<< " base path: " << swap_path;
std::move(callback).Run(native_file_system_error::FromStatus(
NativeFileSystemStatus::kOperationFailed,
"Failed to create swap file."),
mojo::NullRemote());
return;
}
if (count > 0) {
swap_path =
swap_path.InsertBeforeExtensionASCII(base::StringPrintf(".%d", count));
}
// First attempt to just create the swap file in the same file system as this
// file.
storage::FileSystemURL swap_url =
manager()->context()->CreateCrackedFileSystemURL(
url().origin().GetURL(), url().mount_type(), swap_path);
// If that failed, it means this file was part of an isolated file system,
// and specifically, a single file isolated file system. In that case we'll
// need to register a new isolated file system for the swap file, and use that
// for the writer.
if (!swap_url.is_valid()) {
DCHECK_EQ(url().mount_type(), storage::kFileSystemTypeIsolated);
swap_path = base::FilePath(url().path()).AddExtensionASCII(".crswap");
if (count > 0) {
swap_path = swap_path.InsertBeforeExtensionASCII(
base::StringPrintf(".%d", count));
}
auto handle =
manager()->CreateFileSystemURLFromPath(context().origin, swap_path);
swap_url = std::move(handle.url);
swap_file_system = std::move(handle.file_system);
}
DoFileSystemOperation(
FROM_HERE, &FileSystemOperationRunner::CreateFile,
base::BindOnce(&NativeFileSystemFileHandleImpl::DidCreateSwapFile,
weak_factory_.GetWeakPtr(), count, swap_url,
swap_file_system, keep_existing_data, std::move(callback)),
swap_url,
/*exclusive=*/true);
}
void NativeFileSystemFileHandleImpl::DidCreateSwapFile(
int count,
const storage::FileSystemURL& swap_url,
storage::IsolatedContext::ScopedFSHandle swap_file_system,
bool keep_existing_data,
CreateFileWriterCallback callback,
base::File::Error result) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (result == base::File::FILE_ERROR_EXISTS) {
// Creation attempt failed. We need to find an unused filename.
CreateSwapFile(count + 1, keep_existing_data, std::move(callback));
return;
}
if (result != base::File::FILE_OK) {
DLOG(ERROR) << "Error Creating Swap File, status: "
<< base::File::ErrorToString(result)
<< " path: " << swap_url.path();
std::move(callback).Run(native_file_system_error::FromFileError(
result, "Error creating swap file."),
mojo::NullRemote());
return;
}
if (!keep_existing_data) {
std::move(callback).Run(
native_file_system_error::Ok(),
manager()->CreateFileWriter(
context(), url(), swap_url,
NativeFileSystemManagerImpl::SharedHandleState(
handle_state().read_grant, handle_state().write_grant,
swap_file_system)));
return;
}
DoFileSystemOperation(
FROM_HERE, &FileSystemOperationRunner::Copy,
base::BindOnce(&NativeFileSystemFileHandleImpl::DidCopySwapFile,
weak_factory_.GetWeakPtr(), swap_url, swap_file_system,
std::move(callback)),
url(), swap_url,
storage::FileSystemOperation::OPTION_PRESERVE_LAST_MODIFIED,
storage::FileSystemOperation::ERROR_BEHAVIOR_ABORT,
storage::FileSystemOperation::CopyProgressCallback());
}
void NativeFileSystemFileHandleImpl::DidCopySwapFile(
const storage::FileSystemURL& swap_url,
storage::IsolatedContext::ScopedFSHandle swap_file_system,
CreateFileWriterCallback callback,
base::File::Error result) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (result != base::File::FILE_OK) {
DLOG(ERROR) << "Error Creating Swap File, status: "
<< base::File::ErrorToString(result)
<< " path: " << swap_url.path();
std::move(callback).Run(native_file_system_error::FromFileError(
result, "Error copying to swap file."),
mojo::NullRemote());
return;
}
std::move(callback).Run(
native_file_system_error::Ok(),
manager()->CreateFileWriter(
context(), url(), swap_url,
NativeFileSystemManagerImpl::SharedHandleState(
handle_state().read_grant, handle_state().write_grant,
swap_file_system)));
}
base::WeakPtr<NativeFileSystemHandleBase>
NativeFileSystemFileHandleImpl::AsWeakPtr() {
return weak_factory_.GetWeakPtr();
}
} // namespace content