#include "content/browser/file_system_access/file_system_access_file_handle_impl.h"
#include <limits>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/memory/scoped_refptr.h"
#include "base/strings/stringprintf.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/gmock_expected_support.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "build/build_config.h"
#include "content/browser/file_system_access/features.h"
#include "content/browser/file_system_access/fixed_file_system_access_permission_grant.h"
#include "content/browser/file_system_access/mock_file_system_access_permission_grant.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_web_contents_factory.h"
#include "content/test/test_web_contents.h"
#include "file_system_access_directory_handle_impl.h"
#include "mock_file_system_access_permission_context.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_storage_context.h"
#include "storage/browser/file_system/file_stream_reader.h"
#include "storage/browser/quota/quota_manager_proxy.h"
#include "storage/browser/test/async_file_test_helper.h"
#include "storage/browser/test/mock_quota_manager.h"
#include "storage/browser/test/mock_quota_manager_proxy.h"
#include "storage/browser/test/mock_special_storage_policy.h"
#include "storage/browser/test/test_file_system_context.h"
#include "storage/common/file_system/file_system_types.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features_generated.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "third_party/blink/public/mojom/permissions/permission_status.mojom-shared.h"
#include "url/gurl.h"
#if BUILDFLAG(IS_ANDROID)
#include "base/android/path_utils.h"
#include "base/strings/escape.h"
#include "base/test/android/content_uri_test_utils.h"
#endif
namespace content {
PermissionStatus;
FileSystemURL;
class FileSystemAccessFileHandleImplTest : public testing::Test { … };
class FileSystemAccessAccessHandleTest
: public FileSystemAccessFileHandleImplTest { … };
class FileSystemAccessAccessHandleIncognitoTest
: public FileSystemAccessAccessHandleTest { … };
#if BUILDFLAG(IS_ANDROID)
class FileSystemAccessAccessHandleContentUriTest
: public FileSystemAccessAccessHandleTest {
void SetUp() override {
SetupHelper(storage::kFileSystemTypeLocal, false,
true);
}
};
#endif
TEST_F(FileSystemAccessFileHandleImplTest, CreateFileWriterOverLimitNotOK) { … }
TEST_F(FileSystemAccessFileHandleImplTest, CreateFileWriterSiloedMode) { … }
TEST_F(FileSystemAccessFileHandleImplTest, CreateFileWriterExclusiveMode) { … }
TEST_F(FileSystemAccessFileHandleImplTest,
CreateFileWriterWithExistingSwapFile) { … }
#if BUILDFLAG(IS_ANDROID)
TEST_F(FileSystemAccessAccessHandleContentUriTest, CreateFileWriter) {
base::test::TestFuture<
blink::mojom::FileSystemAccessErrorPtr,
mojo::PendingRemote<blink::mojom::FileSystemAccessFileWriter>>
future;
handle_->CreateFileWriter(
false,
false,
blink::mojom::FileSystemAccessWritableFileStreamLockMode::kSiloed,
future.GetCallback());
blink::mojom::FileSystemAccessErrorPtr result;
mojo::PendingRemote<blink::mojom::FileSystemAccessFileWriter> writer_remote;
std::tie(result, writer_remote) = future.Take();
EXPECT_EQ(result->status, blink::mojom::FileSystemAccessStatus::kOk);
EXPECT_TRUE(writer_remote.is_valid());
base::FilePath cache_dir;
EXPECT_TRUE(base::android::GetCacheDirectory(&cache_dir));
auto escaped = base::EscapeAllExceptUnreserved(test_file_url_.path().value());
base::FilePath swap = cache_dir.Append("FileSystemAPISwap")
.Append(escaped)
.AddExtension(".crswap");
EXPECT_TRUE(base::PathExists(swap));
}
#endif
TEST_F(FileSystemAccessFileHandleImplTest, Remove_NoWriteAccess) { … }
TEST_F(FileSystemAccessFileHandleImplTest, Remove_HasWriteAccess) { … }
TEST_F(FileSystemAccessAccessHandleTest, OpenAccessHandle) { … }
TEST_F(FileSystemAccessAccessHandleIncognitoTest, OpenAccessHandle) { … }
TEST_F(FileSystemAccessAccessHandleTest, OpenAccessHandleLockModes_Readwrite) { … }
TEST_F(FileSystemAccessAccessHandleTest, OpenAccessHandleLockModes_ReadOnly) { … }
TEST_F(FileSystemAccessAccessHandleTest,
OpenAccessHandleLockModes_ReadwriteUnsafe) { … }
TEST_F(FileSystemAccessFileHandleImplTest, Rename_NoWriteAccess) { … }
TEST_F(FileSystemAccessFileHandleImplTest, Rename_HasWriteAccess) { … }
TEST_F(FileSystemAccessFileHandleImplTest, Move_NoWriteAccess) { … }
TEST_F(FileSystemAccessFileHandleImplTest, Move_NoDestWriteAccess) { … }
TEST_F(FileSystemAccessFileHandleImplTest, Move_HasDestWriteAccess) { … }
#if BUILDFLAG(IS_MAC)
class FileSystemAccessFileHandleSwapFileCloningTest
: public FileSystemAccessFileHandleImplTest {
public:
enum class CloneFileResult {
kDidNotAttempt,
kAttemptedAndAborted,
kAttemptedAndCompletedUnexpectedly,
kAttemptedAndCompletedAsExpected
};
void SetUp() override {
SetupHelper(storage::kFileSystemTypeLocal, false,
false);
}
CloneFileResult GetCloneFileResult(
const std::unique_ptr<FileSystemAccessFileHandleImpl>& handle) {
auto maybe_clone_result = handle->get_swap_file_clone_result_for_testing();
if (!maybe_clone_result.has_value()) {
return CloneFileResult::kDidNotAttempt;
}
if (maybe_clone_result.value() == base::File::Error::FILE_ERROR_ABORT) {
return CloneFileResult::kAttemptedAndAborted;
}
if (maybe_clone_result.value() == base::File::Error::FILE_ERROR_EXISTS) {
return CloneFileResult::kAttemptedAndCompletedUnexpectedly;
}
return CloneFileResult::kAttemptedAndCompletedAsExpected;
}
};
TEST_F(FileSystemAccessFileHandleSwapFileCloningTest, BasicClone) {
base::test::TestFuture<
blink::mojom::FileSystemAccessErrorPtr,
mojo::PendingRemote<blink::mojom::FileSystemAccessFileWriter>>
future;
handle_->CreateFileWriter(
true,
false,
blink::mojom::FileSystemAccessWritableFileStreamLockMode::kSiloed,
future.GetCallback());
blink::mojom::FileSystemAccessErrorPtr result;
mojo::PendingRemote<blink::mojom::FileSystemAccessFileWriter> writer_remote;
std::tie(result, writer_remote) = future.Take();
EXPECT_EQ(result->status, blink::mojom::FileSystemAccessStatus::kOk);
EXPECT_TRUE(writer_remote.is_valid());
EXPECT_EQ(GetCloneFileResult(handle_),
CloneFileResult::kAttemptedAndCompletedAsExpected);
}
TEST_F(FileSystemAccessFileHandleSwapFileCloningTest,
IgnoringExistingDataDoesNotClone) {
base::test::TestFuture<
blink::mojom::FileSystemAccessErrorPtr,
mojo::PendingRemote<blink::mojom::FileSystemAccessFileWriter>>
future;
handle_->CreateFileWriter(
false,
false,
blink::mojom::FileSystemAccessWritableFileStreamLockMode::kSiloed,
future.GetCallback());
blink::mojom::FileSystemAccessErrorPtr result;
mojo::PendingRemote<blink::mojom::FileSystemAccessFileWriter> writer_remote;
std::tie(result, writer_remote) = future.Take();
EXPECT_EQ(result->status, blink::mojom::FileSystemAccessStatus::kOk);
EXPECT_TRUE(writer_remote.is_valid());
EXPECT_EQ(GetCloneFileResult(handle_), CloneFileResult::kDidNotAttempt);
}
TEST_F(FileSystemAccessFileHandleSwapFileCloningTest, HandleExistingSwapFile) {
const FileSystemURL swap_url =
file_system_context_->CreateCrackedFileSystemURL(
test_src_storage_key_, storage::kFileSystemTypeLocal,
dir_.GetPath().AppendASCII("test.crswap"));
ASSERT_EQ(base::File::FILE_OK, storage::AsyncFileTestHelper::CreateFile(
file_system_context_.get(), swap_url));
base::test::TestFuture<
blink::mojom::FileSystemAccessErrorPtr,
mojo::PendingRemote<blink::mojom::FileSystemAccessFileWriter>>
future;
handle_->CreateFileWriter(
true,
false,
blink::mojom::FileSystemAccessWritableFileStreamLockMode::kSiloed,
future.GetCallback());
blink::mojom::FileSystemAccessErrorPtr result;
mojo::PendingRemote<blink::mojom::FileSystemAccessFileWriter> writer_remote;
std::tie(result, writer_remote) = future.Take();
EXPECT_EQ(result->status, blink::mojom::FileSystemAccessStatus::kOk);
EXPECT_TRUE(writer_remote.is_valid());
EXPECT_EQ(GetCloneFileResult(handle_),
CloneFileResult::kAttemptedAndCompletedAsExpected);
}
TEST_F(FileSystemAccessFileHandleSwapFileCloningTest, HandleCloneFailure) {
handle_->set_swap_file_cloning_will_fail_for_testing();
base::test::TestFuture<
blink::mojom::FileSystemAccessErrorPtr,
mojo::PendingRemote<blink::mojom::FileSystemAccessFileWriter>>
future;
handle_->CreateFileWriter(
true,
false,
blink::mojom::FileSystemAccessWritableFileStreamLockMode::kSiloed,
future.GetCallback());
blink::mojom::FileSystemAccessErrorPtr result;
mojo::PendingRemote<blink::mojom::FileSystemAccessFileWriter> writer_remote;
std::tie(result, writer_remote) = future.Take();
EXPECT_EQ(result->status, blink::mojom::FileSystemAccessStatus::kOk);
EXPECT_TRUE(writer_remote.is_valid());
EXPECT_EQ(GetCloneFileResult(handle_), CloneFileResult::kAttemptedAndAborted);
}
#endif
class FileSystemAccessFileHandleImplMovePermissionsTest
: public FileSystemAccessFileHandleImplTest,
public testing::WithParamInterface<std::tuple<bool, bool>> { … };
TEST_P(FileSystemAccessFileHandleImplMovePermissionsTest,
Rename_HasTargetWriteAccess) { … }
TEST_P(FileSystemAccessFileHandleImplMovePermissionsTest,
Rename_AskTargetWriteAccess) { … }
TEST_P(FileSystemAccessFileHandleImplMovePermissionsTest, Rename_SameFile) { … }
TEST_P(FileSystemAccessFileHandleImplMovePermissionsTest, Move) { … }
TEST_P(FileSystemAccessFileHandleImplMovePermissionsTest,
Move_NoTargetWriteAccessFails) { … }
TEST_P(FileSystemAccessFileHandleImplMovePermissionsTest, Move_SameFile) { … }
INSTANTIATE_TEST_SUITE_P(…);
}