blob: b5dd989a5f00da3d9e3133af445b0279f272b8b7 [file] [log] [blame]
// Copyright 2018 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/file_system_access/native_file_system_file_writer_impl.h"
#include <limits>
#include <memory>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/files/scoped_temp_dir.h"
#include "base/guid.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/bind.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/task_environment.h"
#include "content/browser/file_system_access/fixed_native_file_system_permission_grant.h"
#include "content/browser/file_system_access/mock_native_file_system_permission_context.h"
#include "content/public/test/browser_task_environment.h"
#include "mojo/public/cpp/system/data_pipe_producer.h"
#include "mojo/public/cpp/system/string_data_source.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "net/base/test_completion_callback.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/file_system/file_stream_reader.h"
#include "storage/browser/test/async_file_test_helper.h"
#include "storage/browser/test/test_file_system_backend.h"
#include "storage/browser/test/test_file_system_context.h"
#include "testing/gtest/include/gtest/gtest.h"
using blink::mojom::FileSystemAccessStatus;
using storage::FileSystemURL;
using storage::IsolatedContext;
using testing::_;
using testing::AllOf;
using testing::Eq;
using testing::Field;
namespace content {
namespace {
class MockQuarantine : public quarantine::mojom::Quarantine {
public:
MockQuarantine() = default;
void QuarantineFile(const base::FilePath& full_path,
const GURL& source_url,
const GURL& referrer_url,
const std::string& client_guid,
QuarantineFileCallback callback) override {
paths.push_back(full_path);
std::move(callback).Run(quarantine::mojom::QuarantineFileResult::OK);
}
std::vector<base::FilePath> paths;
};
// File System Backend that can notify whenever a FileSystemOperation is
// created. This lets tests simulate race conditions between file operations and
// other work.
class TestFileSystemBackend : public storage::TestFileSystemBackend {
public:
TestFileSystemBackend(base::SequencedTaskRunner* task_runner,
const base::FilePath& base_path)
: storage::TestFileSystemBackend(task_runner, base_path) {}
storage::FileSystemOperation* CreateFileSystemOperation(
const storage::FileSystemURL& url,
storage::FileSystemContext* context,
base::File::Error* error_code) const override {
if (operation_created_callback_)
std::move(operation_created_callback_).Run(url);
return storage::TestFileSystemBackend::CreateFileSystemOperation(
url, context, error_code);
}
void SetOperationCreatedCallback(
base::OnceCallback<void(const storage::FileSystemURL&)> callback) {
operation_created_callback_ = std::move(callback);
}
private:
mutable base::OnceCallback<void(const storage::FileSystemURL&)>
operation_created_callback_;
};
} // namespace
std::string GetHexEncodedString(const std::string& input) {
return base::HexEncode(base::as_bytes(base::make_span(input)));
}
class NativeFileSystemFileWriterImplTest : public testing::Test {
public:
NativeFileSystemFileWriterImplTest()
: task_environment_(base::test::TaskEnvironment::MainThreadType::IO) {}
virtual NativeFileSystemPermissionContext* permission_context() {
return nullptr;
}
void SetUp() override {
ASSERT_TRUE(dir_.CreateUniqueTempDir());
std::vector<std::unique_ptr<storage::FileSystemBackend>>
additional_providers;
additional_providers.push_back(std::make_unique<TestFileSystemBackend>(
base::ThreadTaskRunnerHandle::Get().get(), dir_.GetPath()));
test_file_system_backend_ =
static_cast<TestFileSystemBackend*>(additional_providers[0].get());
file_system_context_ =
storage::CreateFileSystemContextWithAdditionalProvidersForTesting(
base::ThreadTaskRunnerHandle::Get().get(),
base::ThreadTaskRunnerHandle::Get().get(),
/*quota_manager_proxy=*/nullptr, std::move(additional_providers),
dir_.GetPath());
auto* isolated_context = IsolatedContext::GetInstance();
std::string base_name;
IsolatedContext::ScopedFSHandle fs =
isolated_context->RegisterFileSystemForPath(
storage::kFileSystemTypeNativeLocal, std::string(), dir_.GetPath(),
&base_name);
base::FilePath root_path =
isolated_context->CreateVirtualRootPath(fs.id()).AppendASCII(base_name);
test_file_url_ = file_system_context_->CreateCrackedFileSystemURL(
kTestOrigin, storage::kFileSystemTypeIsolated,
root_path.AppendASCII("test"));
test_swap_url_ = file_system_context_->CreateCrackedFileSystemURL(
kTestOrigin, storage::kFileSystemTypeIsolated,
root_path.AppendASCII("test.crswap"));
ASSERT_EQ(base::File::FILE_OK,
storage::AsyncFileTestHelper::CreateFile(
file_system_context_.get(), test_file_url_));
ASSERT_EQ(base::File::FILE_OK,
storage::AsyncFileTestHelper::CreateFile(
file_system_context_.get(), test_swap_url_));
chrome_blob_context_ = base::MakeRefCounted<ChromeBlobStorageContext>();
chrome_blob_context_->InitializeOnIOThread(base::FilePath(),
base::FilePath(), nullptr);
blob_context_ = chrome_blob_context_->context();
manager_ = base::MakeRefCounted<NativeFileSystemManagerImpl>(
file_system_context_, chrome_blob_context_,
/*permission_context=*/permission_context(),
/*off_the_record=*/false);
quarantine_callback_ = base::BindLambdaForTesting(
[&](mojo::PendingReceiver<quarantine::mojom::Quarantine> receiver) {
quarantine_receivers_.Add(&quarantine_, std::move(receiver));
});
handle_ = manager_->CreateFileWriter(
NativeFileSystemManagerImpl::BindingContext(kTestOrigin, kTestURL,
kFrameId),
test_file_url_, test_swap_url_,
NativeFileSystemManagerImpl::SharedHandleState(
permission_grant_, permission_grant_, std::move(fs)),
remote_.InitWithNewPipeAndPassReceiver(),
/*has_transient_user_activation=*/false,
/*auto_close=*/false, quarantine_callback_);
}
void TearDown() override {
manager_.reset();
task_environment_.RunUntilIdle();
EXPECT_TRUE(dir_.Delete());
}
mojo::PendingRemote<blink::mojom::Blob> CreateBlob(
const std::string& contents) {
auto builder =
std::make_unique<storage::BlobDataBuilder>(base::GenerateGUID());
builder->AppendData(contents);
auto handle = blob_context_->AddFinishedBlob(std::move(builder));
mojo::PendingRemote<blink::mojom::Blob> result;
storage::BlobImpl::Create(std::move(handle),
result.InitWithNewPipeAndPassReceiver());
return result;
}
mojo::ScopedDataPipeConsumerHandle CreateStream(const std::string& contents) {
// Test with a relatively low capacity pipe to make sure it isn't all
// written/read in one go.
mojo::ScopedDataPipeProducerHandle producer_handle;
mojo::ScopedDataPipeConsumerHandle consumer_handle;
MojoCreateDataPipeOptions options;
options.struct_size = sizeof(MojoCreateDataPipeOptions);
options.flags = MOJO_CREATE_DATA_PIPE_FLAG_NONE;
options.element_num_bytes = 1;
options.capacity_num_bytes = 16;
mojo::CreateDataPipe(&options, &producer_handle, &consumer_handle);
CHECK(producer_handle.is_valid());
auto producer =
std::make_unique<mojo::DataPipeProducer>(std::move(producer_handle));
auto* producer_raw = producer.get();
producer_raw->Write(
std::make_unique<mojo::StringDataSource>(
contents, mojo::StringDataSource::AsyncWritingMode::
STRING_MAY_BE_INVALIDATED_BEFORE_COMPLETION),
base::BindOnce(
base::DoNothing::Once<std::unique_ptr<mojo::DataPipeProducer>,
MojoResult>(),
std::move(producer)));
return consumer_handle;
}
std::string ReadFile(const FileSystemURL& url) {
std::unique_ptr<storage::FileStreamReader> reader =
file_system_context_->CreateFileStreamReader(
url, 0, std::numeric_limits<int64_t>::max(), base::Time());
std::string result;
while (true) {
auto buf = base::MakeRefCounted<net::IOBufferWithSize>(4096);
net::TestCompletionCallback callback;
int rv = reader->Read(buf.get(), buf->size(), callback.callback());
if (rv == net::ERR_IO_PENDING)
rv = callback.WaitForResult();
EXPECT_GE(rv, 0);
if (rv < 0)
return "(read failure)";
if (rv == 0)
return result;
result.append(buf->data(), rv);
}
}
FileSystemAccessStatus WriteBlobSync(
uint64_t position,
mojo::PendingRemote<blink::mojom::Blob> blob,
uint64_t* bytes_written_out) {
base::RunLoop loop;
FileSystemAccessStatus result_out;
handle_->Write(position, std::move(blob),
base::BindLambdaForTesting(
[&](blink::mojom::FileSystemAccessErrorPtr result,
uint64_t bytes_written) {
result_out = result->status;
*bytes_written_out = bytes_written;
loop.Quit();
}));
loop.Run();
return result_out;
}
FileSystemAccessStatus WriteStreamSync(
uint64_t position,
mojo::ScopedDataPipeConsumerHandle data_pipe,
uint64_t* bytes_written_out) {
base::RunLoop loop;
FileSystemAccessStatus result_out;
handle_->WriteStream(position, std::move(data_pipe),
base::BindLambdaForTesting(
[&](blink::mojom::FileSystemAccessErrorPtr result,
uint64_t bytes_written) {
result_out = result->status;
*bytes_written_out = bytes_written;
loop.Quit();
}));
loop.Run();
return result_out;
}
FileSystemAccessStatus TruncateSync(uint64_t length) {
base::RunLoop loop;
FileSystemAccessStatus result_out;
handle_->Truncate(length,
base::BindLambdaForTesting(
[&](blink::mojom::FileSystemAccessErrorPtr result) {
result_out = result->status;
loop.Quit();
}));
loop.Run();
return result_out;
}
FileSystemAccessStatus CloseSync() {
base::RunLoop loop;
FileSystemAccessStatus result_out;
handle_->Close(base::BindLambdaForTesting(
[&](blink::mojom::FileSystemAccessErrorPtr result) {
result_out = result->status;
loop.Quit();
}));
loop.Run();
return result_out;
}
FileSystemAccessStatus AbortSync() {
base::RunLoop loop;
FileSystemAccessStatus result_out;
handle_->Abort(base::BindLambdaForTesting(
[&](blink::mojom::FileSystemAccessErrorPtr result) {
result_out = result->status;
loop.Quit();
}));
loop.Run();
return result_out;
}
virtual bool WriteUsingBlobs() { return true; }
FileSystemAccessStatus WriteSync(uint64_t position,
const std::string& contents,
uint64_t* bytes_written_out) {
if (WriteUsingBlobs())
return WriteBlobSync(position, CreateBlob(contents), bytes_written_out);
return WriteStreamSync(position, CreateStream(contents), bytes_written_out);
}
protected:
const GURL kTestURL = GURL("https://example.com/test");
const url::Origin kTestOrigin = url::Origin::Create(kTestURL);
const int kProcessId = 1;
const int kFrameRoutingId = 2;
const GlobalFrameRoutingId kFrameId{kProcessId, kFrameRoutingId};
BrowserTaskEnvironment task_environment_;
base::ScopedTempDir dir_;
scoped_refptr<storage::FileSystemContext> file_system_context_;
TestFileSystemBackend* test_file_system_backend_;
scoped_refptr<ChromeBlobStorageContext> chrome_blob_context_;
storage::BlobStorageContext* blob_context_;
scoped_refptr<NativeFileSystemManagerImpl> manager_;
FileSystemURL test_file_url_;
FileSystemURL test_swap_url_;
MockQuarantine quarantine_;
mojo::ReceiverSet<quarantine::mojom::Quarantine> quarantine_receivers_;
download::QuarantineConnectionCallback quarantine_callback_;
scoped_refptr<FixedNativeFileSystemPermissionGrant> permission_grant_ =
base::MakeRefCounted<FixedNativeFileSystemPermissionGrant>(
FixedNativeFileSystemPermissionGrant::PermissionStatus::GRANTED,
base::FilePath());
mojo::PendingRemote<blink::mojom::FileSystemAccessFileWriter> remote_;
base::WeakPtr<NativeFileSystemFileWriterImpl> handle_;
};
class NativeFileSystemFileWriterImplWriteTest
: public NativeFileSystemFileWriterImplTest,
public testing::WithParamInterface<bool> {
public:
bool WriteUsingBlobs() override { return GetParam(); }
};
INSTANTIATE_TEST_SUITE_P(NativeFileSystemFileWriterImplTest,
NativeFileSystemFileWriterImplWriteTest,
::testing::Bool());
TEST_F(NativeFileSystemFileWriterImplTest, WriteInvalidBlob) {
// This test primarily verifies behavior of the browser process in the
// presence of a compromised renderer process. The situation this tests for
// normally can't occur. As such it doesn't really matter what status the
// write operation returns, the important part is that nothing crashes.
mojo::PendingRemote<blink::mojom::Blob> blob;
ignore_result(blob.InitWithNewPipeAndPassReceiver());
uint64_t bytes_written;
FileSystemAccessStatus result =
WriteBlobSync(0, std::move(blob), &bytes_written);
EXPECT_EQ(bytes_written, 0u);
result = CloseSync();
EXPECT_EQ(result, FileSystemAccessStatus::kOk);
EXPECT_EQ("", ReadFile(test_file_url_));
}
TEST_F(NativeFileSystemFileWriterImplTest, HashSimpleOK) {
uint64_t bytes_written;
FileSystemAccessStatus result = WriteSync(0, "abc", &bytes_written);
EXPECT_EQ(result, FileSystemAccessStatus::kOk);
EXPECT_EQ(bytes_written, 3u);
base::RunLoop loop;
handle_->ComputeHashForSwapFileForTesting(base::BindLambdaForTesting(
[&](base::File::Error result, const std::string& hash_value,
int64_t size) {
EXPECT_EQ(base::File::FILE_OK, result);
EXPECT_EQ(
"BA7816BF8F01CFEA414140DE5DAE2223B00361A396177A9CB410FF61F20015AD",
GetHexEncodedString(hash_value));
EXPECT_EQ(3, size);
loop.Quit();
}));
loop.Run();
}
TEST_F(NativeFileSystemFileWriterImplTest, HashEmptyOK) {
base::RunLoop loop;
handle_->ComputeHashForSwapFileForTesting(base::BindLambdaForTesting(
[&](base::File::Error result, const std::string& hash_value,
int64_t size) {
EXPECT_EQ(base::File::FILE_OK, result);
EXPECT_EQ(
"E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855",
GetHexEncodedString(hash_value));
EXPECT_EQ(0, size);
loop.Quit();
}));
loop.Run();
}
TEST_F(NativeFileSystemFileWriterImplTest, HashNonExistingFileFails) {
ASSERT_EQ(base::File::FILE_OK, storage::AsyncFileTestHelper::Remove(
file_system_context_.get(),
handle_->swap_url(), /*recursive=*/false));
base::RunLoop loop;
handle_->ComputeHashForSwapFileForTesting(base::BindLambdaForTesting(
[&](base::File::Error result, const std::string& hash_value,
int64_t size) {
EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND, result);
loop.Quit();
}));
loop.Run();
}
TEST_F(NativeFileSystemFileWriterImplTest, HashLargerFileOK) {
size_t target_size = 9 * 1024u;
std::string file_data(target_size, '0');
uint64_t bytes_written;
FileSystemAccessStatus result = WriteSync(0, file_data, &bytes_written);
EXPECT_EQ(result, FileSystemAccessStatus::kOk);
EXPECT_EQ(bytes_written, target_size);
base::RunLoop loop;
handle_->ComputeHashForSwapFileForTesting(base::BindLambdaForTesting(
[&](base::File::Error result, const std::string& hash_value,
int64_t size) {
EXPECT_EQ(base::File::FILE_OK, result);
EXPECT_EQ(
"34A82D28CB1E0BA92CADC4BE8497DC9EEA9AC4F63B9C445A9E52D298990AC491",
GetHexEncodedString(hash_value));
EXPECT_EQ(int64_t{target_size}, size);
loop.Quit();
}));
loop.Run();
}
TEST_P(NativeFileSystemFileWriterImplWriteTest, WriteValidEmptyString) {
uint64_t bytes_written;
FileSystemAccessStatus result = WriteSync(0, "", &bytes_written);
EXPECT_EQ(result, FileSystemAccessStatus::kOk);
EXPECT_EQ(bytes_written, 0u);
result = CloseSync();
EXPECT_EQ(result, FileSystemAccessStatus::kOk);
EXPECT_TRUE(base::Contains(quarantine_.paths, test_file_url_.path()));
EXPECT_EQ("", ReadFile(test_file_url_));
}
TEST_P(NativeFileSystemFileWriterImplWriteTest, WriteValidNonEmpty) {
std::string test_data("abcdefghijklmnopqrstuvwxyz");
uint64_t bytes_written;
FileSystemAccessStatus result = WriteSync(0, test_data, &bytes_written);
EXPECT_EQ(result, FileSystemAccessStatus::kOk);
EXPECT_EQ(bytes_written, test_data.size());
result = CloseSync();
EXPECT_EQ(result, FileSystemAccessStatus::kOk);
EXPECT_TRUE(base::Contains(quarantine_.paths, test_file_url_.path()));
EXPECT_EQ(test_data, ReadFile(test_file_url_));
}
TEST_P(NativeFileSystemFileWriterImplWriteTest, WriteWithOffsetInFile) {
uint64_t bytes_written;
FileSystemAccessStatus result;
result = WriteSync(0, "1234567890", &bytes_written);
EXPECT_EQ(result, FileSystemAccessStatus::kOk);
EXPECT_EQ(bytes_written, 10u);
result = WriteSync(4, "abc", &bytes_written);
EXPECT_EQ(result, FileSystemAccessStatus::kOk);
EXPECT_EQ(bytes_written, 3u);
result = CloseSync();
EXPECT_EQ(result, FileSystemAccessStatus::kOk);
EXPECT_TRUE(base::Contains(quarantine_.paths, test_file_url_.path()));
EXPECT_EQ("1234abc890", ReadFile(test_file_url_));
}
TEST_P(NativeFileSystemFileWriterImplWriteTest, WriteWithOffsetPastFile) {
uint64_t bytes_written;
FileSystemAccessStatus result = WriteSync(4, "abc", &bytes_written);
EXPECT_EQ(result, FileSystemAccessStatus::kFileError);
EXPECT_EQ(bytes_written, 0u);
result = CloseSync();
EXPECT_EQ(result, FileSystemAccessStatus::kOk);
EXPECT_TRUE(base::Contains(quarantine_.paths, test_file_url_.path()));
using std::string_literals::operator""s;
EXPECT_EQ(""s, ReadFile(test_file_url_));
}
TEST_F(NativeFileSystemFileWriterImplTest, TruncateShrink) {
uint64_t bytes_written;
FileSystemAccessStatus result;
result = WriteSync(0, "1234567890", &bytes_written);
EXPECT_EQ(result, FileSystemAccessStatus::kOk);
EXPECT_EQ(bytes_written, 10u);
result = TruncateSync(5);
EXPECT_EQ(result, FileSystemAccessStatus::kOk);
result = CloseSync();
EXPECT_EQ(result, FileSystemAccessStatus::kOk);
EXPECT_EQ("12345", ReadFile(test_file_url_));
}
TEST_F(NativeFileSystemFileWriterImplTest, TruncateGrow) {
uint64_t bytes_written;
FileSystemAccessStatus result;
result = WriteSync(0, "abc", &bytes_written);
EXPECT_EQ(result, FileSystemAccessStatus::kOk);
EXPECT_EQ(bytes_written, 3u);
result = TruncateSync(5);
EXPECT_EQ(result, FileSystemAccessStatus::kOk);
result = CloseSync();
EXPECT_EQ(result, FileSystemAccessStatus::kOk);
EXPECT_EQ(std::string("abc\0\0", 5), ReadFile(test_file_url_));
}
TEST_F(NativeFileSystemFileWriterImplTest, CloseAfterCloseNotOK) {
uint64_t bytes_written;
FileSystemAccessStatus result = WriteSync(0, "abc", &bytes_written);
EXPECT_EQ(result, FileSystemAccessStatus::kOk);
EXPECT_EQ(bytes_written, 3u);
result = CloseSync();
EXPECT_EQ(result, FileSystemAccessStatus::kOk);
result = CloseSync();
EXPECT_EQ(result, FileSystemAccessStatus::kInvalidState);
}
TEST_F(NativeFileSystemFileWriterImplTest, TruncateAfterCloseNotOK) {
uint64_t bytes_written;
FileSystemAccessStatus result = WriteSync(0, "abc", &bytes_written);
EXPECT_EQ(result, FileSystemAccessStatus::kOk);
EXPECT_EQ(bytes_written, 3u);
result = CloseSync();
EXPECT_EQ(result, FileSystemAccessStatus::kOk);
result = TruncateSync(0);
EXPECT_EQ(result, FileSystemAccessStatus::kInvalidState);
}
TEST_P(NativeFileSystemFileWriterImplWriteTest, WriteAfterCloseNotOK) {
uint64_t bytes_written;
FileSystemAccessStatus result = WriteSync(0, "abc", &bytes_written);
EXPECT_EQ(result, FileSystemAccessStatus::kOk);
EXPECT_EQ(bytes_written, 3u);
result = CloseSync();
EXPECT_EQ(result, FileSystemAccessStatus::kOk);
result = WriteSync(0, "bcd", &bytes_written);
EXPECT_EQ(result, FileSystemAccessStatus::kInvalidState);
}
TEST_F(NativeFileSystemFileWriterImplTest, AbortAfterCloseNotOK) {
uint64_t bytes_written;
FileSystemAccessStatus result = WriteSync(0, "abc", &bytes_written);
EXPECT_EQ(result, FileSystemAccessStatus::kOk);
EXPECT_EQ(bytes_written, 3u);
result = CloseSync();
EXPECT_EQ(result, FileSystemAccessStatus::kOk);
result = AbortSync();
EXPECT_EQ(result, FileSystemAccessStatus::kInvalidState);
}
TEST_F(NativeFileSystemFileWriterImplTest, AbortOK) {
uint64_t bytes_written;
FileSystemAccessStatus result = WriteSync(0, "abc", &bytes_written);
EXPECT_EQ(result, FileSystemAccessStatus::kOk);
EXPECT_EQ(bytes_written, 3u);
result = AbortSync();
EXPECT_EQ(result, FileSystemAccessStatus::kOk);
EXPECT_EQ("", ReadFile(test_file_url_));
}
TEST_F(NativeFileSystemFileWriterImplTest, TruncateAfterAbortNotOK) {
uint64_t bytes_written;
FileSystemAccessStatus result = WriteSync(0, "abc", &bytes_written);
EXPECT_EQ(result, FileSystemAccessStatus::kOk);
EXPECT_EQ(bytes_written, 3u);
result = AbortSync();
EXPECT_EQ(result, FileSystemAccessStatus::kOk);
result = TruncateSync(0);
EXPECT_EQ(result, FileSystemAccessStatus::kInvalidState);
}
TEST_P(NativeFileSystemFileWriterImplWriteTest, WriteAfterAbortNotOK) {
uint64_t bytes_written;
FileSystemAccessStatus result = WriteSync(0, "abc", &bytes_written);
EXPECT_EQ(result, FileSystemAccessStatus::kOk);
EXPECT_EQ(bytes_written, 3u);
result = AbortSync();
EXPECT_EQ(result, FileSystemAccessStatus::kOk);
result = WriteSync(0, "bcd", &bytes_written);
EXPECT_EQ(result, FileSystemAccessStatus::kInvalidState);
}
TEST_F(NativeFileSystemFileWriterImplTest, CloseAfterAbortNotOK) {
uint64_t bytes_written;
FileSystemAccessStatus result = WriteSync(0, "abc", &bytes_written);
EXPECT_EQ(result, FileSystemAccessStatus::kOk);
EXPECT_EQ(bytes_written, 3u);
result = AbortSync();
EXPECT_EQ(result, FileSystemAccessStatus::kOk);
result = CloseSync();
EXPECT_EQ(result, FileSystemAccessStatus::kInvalidState);
}
// TODO(mek): More tests, particularly for error conditions.
class NativeFileSystemFileWriterAfterWriteChecksTest
: public NativeFileSystemFileWriterImplTest {
public:
NativeFileSystemPermissionContext* permission_context() override {
return &permission_context_;
}
protected:
testing::StrictMock<MockNativeFileSystemPermissionContext>
permission_context_;
};
TEST_F(NativeFileSystemFileWriterAfterWriteChecksTest, Allow) {
uint64_t bytes_written;
FileSystemAccessStatus result = WriteSync(0, "abc", &bytes_written);
EXPECT_EQ(result, FileSystemAccessStatus::kOk);
EXPECT_EQ(bytes_written, 3u);
std::string expected_hash;
ASSERT_TRUE(base::HexStringToString(
"BA7816BF8F01CFEA414140DE5DAE2223B00361A396177A9CB410FF61F20015AD",
&expected_hash));
EXPECT_CALL(
permission_context_,
PerformAfterWriteChecks_(
AllOf(
Field(&NativeFileSystemWriteItem::target_file_path,
Eq(test_file_url_.path())),
Field(&NativeFileSystemWriteItem::full_path,
Eq(test_swap_url_.path())),
Field(&NativeFileSystemWriteItem::sha256_hash, Eq(expected_hash)),
Field(&NativeFileSystemWriteItem::size, Eq(3)),
Field(&NativeFileSystemWriteItem::frame_url, Eq(kTestURL)),
Field(&NativeFileSystemWriteItem::has_user_gesture, Eq(false))),
kFrameId, _))
.WillOnce(base::test::RunOnceCallback<2>(
NativeFileSystemPermissionContext::AfterWriteCheckResult::kAllow));
result = CloseSync();
EXPECT_EQ(result, FileSystemAccessStatus::kOk);
task_environment_.RunUntilIdle();
EXPECT_FALSE(storage::AsyncFileTestHelper::FileExists(
file_system_context_.get(), test_swap_url_,
storage::AsyncFileTestHelper::kDontCheckSize));
EXPECT_TRUE(storage::AsyncFileTestHelper::FileExists(
file_system_context_.get(), test_file_url_, 3));
}
TEST_F(NativeFileSystemFileWriterAfterWriteChecksTest, Block) {
uint64_t bytes_written;
FileSystemAccessStatus result = WriteSync(0, "abc", &bytes_written);
EXPECT_EQ(result, FileSystemAccessStatus::kOk);
EXPECT_EQ(bytes_written, 3u);
EXPECT_CALL(permission_context_, PerformAfterWriteChecks_(_, kFrameId, _))
.WillOnce(base::test::RunOnceCallback<2>(
NativeFileSystemPermissionContext::AfterWriteCheckResult::kBlock));
result = CloseSync();
EXPECT_EQ(result, FileSystemAccessStatus::kOperationAborted);
task_environment_.RunUntilIdle();
EXPECT_FALSE(storage::AsyncFileTestHelper::FileExists(
file_system_context_.get(), test_swap_url_,
storage::AsyncFileTestHelper::kDontCheckSize));
EXPECT_TRUE(storage::AsyncFileTestHelper::FileExists(
file_system_context_.get(), test_file_url_, 0));
}
TEST_F(NativeFileSystemFileWriterAfterWriteChecksTest,
HandleCloseDuringCheckOK) {
uint64_t bytes_written;
FileSystemAccessStatus result = WriteSync(0, "abc", &bytes_written);
EXPECT_EQ(result, FileSystemAccessStatus::kOk);
EXPECT_EQ(bytes_written, 3u);
using SBCallback = base::OnceCallback<void(
NativeFileSystemPermissionContext::AfterWriteCheckResult)>;
SBCallback sb_callback;
base::RunLoop loop;
EXPECT_CALL(permission_context_, PerformAfterWriteChecks_)
.WillOnce(testing::Invoke([&](NativeFileSystemWriteItem* item,
GlobalFrameRoutingId frame_id,
SBCallback& callback) {
sb_callback = std::move(callback);
loop.Quit();
}));
handle_->Close(base::DoNothing());
loop.Run();
remote_.reset();
// Destructor should not have deleted swap file with an active safe browsing
// check pending.
task_environment_.RunUntilIdle();
EXPECT_TRUE(storage::AsyncFileTestHelper::FileExists(
file_system_context_.get(), test_swap_url_,
storage::AsyncFileTestHelper::kDontCheckSize));
std::move(sb_callback)
.Run(NativeFileSystemPermissionContext::AfterWriteCheckResult::kAllow);
// Swap file should now be deleted, target file should be written out.
task_environment_.RunUntilIdle();
EXPECT_FALSE(storage::AsyncFileTestHelper::FileExists(
file_system_context_.get(), test_swap_url_,
storage::AsyncFileTestHelper::kDontCheckSize));
EXPECT_TRUE(storage::AsyncFileTestHelper::FileExists(
file_system_context_.get(), test_file_url_, 3));
}
TEST_F(NativeFileSystemFileWriterAfterWriteChecksTest,
HandleCloseDuringCheckNotOK) {
uint64_t bytes_written;
FileSystemAccessStatus result = WriteSync(0, "abc", &bytes_written);
EXPECT_EQ(result, FileSystemAccessStatus::kOk);
EXPECT_EQ(bytes_written, 3u);
using SBCallback = base::OnceCallback<void(
NativeFileSystemPermissionContext::AfterWriteCheckResult)>;
SBCallback sb_callback;
base::RunLoop loop;
EXPECT_CALL(permission_context_, PerformAfterWriteChecks_)
.WillOnce(testing::Invoke([&](NativeFileSystemWriteItem* item,
GlobalFrameRoutingId frame_id,
SBCallback& callback) {
sb_callback = std::move(callback);
loop.Quit();
}));
handle_->Close(base::DoNothing());
loop.Run();
remote_.reset();
// Destructor should not have deleted swap file with an active safe browsing
// check pending.
task_environment_.RunUntilIdle();
EXPECT_TRUE(storage::AsyncFileTestHelper::FileExists(
file_system_context_.get(), test_swap_url_,
storage::AsyncFileTestHelper::kDontCheckSize));
std::move(sb_callback)
.Run(NativeFileSystemPermissionContext::AfterWriteCheckResult::kBlock);
// Swap file should now be deleted, target file should be unmodified.
task_environment_.RunUntilIdle();
EXPECT_FALSE(storage::AsyncFileTestHelper::FileExists(
file_system_context_.get(), test_swap_url_,
storage::AsyncFileTestHelper::kDontCheckSize));
EXPECT_TRUE(storage::AsyncFileTestHelper::FileExists(
file_system_context_.get(), test_file_url_, 0));
}
TEST_F(NativeFileSystemFileWriterAfterWriteChecksTest,
DestructDuringMoveQuarantines) {
// This test uses kFileSystemTypeTest to be able to intercept file system
// operations. As such, recreate urls and handle_.
test_file_url_ = file_system_context_->CreateCrackedFileSystemURL(
kTestOrigin, storage::kFileSystemTypeTest,
base::FilePath::FromUTF8Unsafe("test2"));
test_swap_url_ = file_system_context_->CreateCrackedFileSystemURL(
kTestOrigin, storage::kFileSystemTypeTest,
base::FilePath::FromUTF8Unsafe("test2.crswap"));
ASSERT_EQ(base::File::FILE_OK,
storage::AsyncFileTestHelper::CreateFile(file_system_context_.get(),
test_file_url_));
ASSERT_EQ(base::File::FILE_OK,
storage::AsyncFileTestHelper::CreateFile(file_system_context_.get(),
test_swap_url_));
mojo::PendingRemote<blink::mojom::FileSystemAccessFileWriter> remote;
handle_ =
manager_->CreateFileWriter(NativeFileSystemManagerImpl::BindingContext(
kTestOrigin, kTestURL, kFrameId),
test_file_url_, test_swap_url_,
NativeFileSystemManagerImpl::SharedHandleState(
permission_grant_, permission_grant_, {}),
remote.InitWithNewPipeAndPassReceiver(),
/*has_transient_user_activation=*/false,
/*auto_close=*/false, quarantine_callback_);
uint64_t bytes_written;
FileSystemAccessStatus result = WriteSync(0, "foo", &bytes_written);
EXPECT_EQ(result, FileSystemAccessStatus::kOk);
EXPECT_EQ(bytes_written, 3u);
using SBCallback = base::OnceCallback<void(
NativeFileSystemPermissionContext::AfterWriteCheckResult)>;
SBCallback sb_callback;
base::RunLoop sb_loop;
EXPECT_CALL(permission_context_, PerformAfterWriteChecks_)
.WillOnce(testing::Invoke([&](NativeFileSystemWriteItem* item,
GlobalFrameRoutingId frame_id,
SBCallback& callback) {
sb_callback = std::move(callback);
sb_loop.Quit();
}));
handle_->Close(base::DoNothing());
sb_loop.Run();
std::move(sb_callback)
.Run(NativeFileSystemPermissionContext::AfterWriteCheckResult::kAllow);
base::RunLoop move_loop;
test_file_system_backend_->SetOperationCreatedCallback(
base::BindLambdaForTesting([&](const storage::FileSystemURL& url) {
EXPECT_EQ(url, test_swap_url_);
move_loop.Quit();
}));
move_loop.Run();
// About to start the move operation. Now destroy the writer. The
// move will still complete, but make sure that quarantine was also
// applied to the resulting file.
remote_.reset();
task_environment_.RunUntilIdle();
// Swap file should have been deleted since writer was closed.
ASSERT_FALSE(storage::AsyncFileTestHelper::FileExists(
file_system_context_.get(), test_swap_url_,
storage::AsyncFileTestHelper::kDontCheckSize));
// And destination file should have been created, since writer was
// destroyed after move was started.
ASSERT_TRUE(storage::AsyncFileTestHelper::FileExists(
file_system_context_.get(), test_file_url_, 3));
// Destination file should also have been quarantined.
EXPECT_TRUE(base::Contains(quarantine_.paths, test_file_url_.path()));
}
} // namespace content