1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546 |
- // 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 <stddef.h>
- #include <stdint.h>
- #include <map>
- #include <memory>
- #include <string>
- #include <utility>
- #include <vector>
- #include "base/bind.h"
- #include "base/callback_helpers.h"
- #include "base/containers/contains.h"
- #include "base/containers/queue.h"
- #include "base/containers/span.h"
- #include "base/files/file_util.h"
- #include "base/files/scoped_temp_dir.h"
- #include "base/location.h"
- #include "base/memory/raw_ptr.h"
- #include "base/memory/scoped_refptr.h"
- #include "base/ranges/algorithm.h"
- #include "base/run_loop.h"
- #include "base/task/single_thread_task_runner.h"
- #include "base/test/task_environment.h"
- #include "base/threading/thread.h"
- #include "base/threading/thread_task_runner_handle.h"
- #include "base/time/time.h"
- #include "components/services/filesystem/public/mojom/types.mojom.h"
- #include "storage/browser/file_system/copy_or_move_file_validator.h"
- #include "storage/browser/file_system/copy_or_move_hook_delegate.h"
- #include "storage/browser/file_system/copy_or_move_operation_delegate.h"
- #include "storage/browser/file_system/file_stream_reader.h"
- #include "storage/browser/file_system/file_stream_writer.h"
- #include "storage/browser/file_system/file_system_backend.h"
- #include "storage/browser/file_system/file_system_context.h"
- #include "storage/browser/file_system/file_system_operation.h"
- #include "storage/browser/file_system/file_system_url.h"
- #include "storage/browser/file_system/file_system_util.h"
- #include "storage/browser/quota/quota_manager.h"
- #include "storage/browser/test/async_file_test_helper.h"
- #include "storage/browser/test/file_system_test_file_set.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_backend.h"
- #include "storage/browser/test/test_file_system_context.h"
- #include "storage/common/file_system/file_system_mount_option.h"
- #include "storage/common/file_system/file_system_types.h"
- #include "storage/common/file_system/file_system_util.h"
- #include "testing/gmock/include/gmock/gmock.h"
- #include "testing/gtest/include/gtest/gtest.h"
- #include "third_party/blink/public/common/storage_key/storage_key.h"
- #include "url/gurl.h"
- #include "url/origin.h"
- namespace storage {
- namespace {
- using FileEntryList = FileSystemOperation::FileEntryList;
- constexpr int64_t kDefaultFileSize = 10;
- void ExpectOk(const GURL& origin_url,
- const std::string& name,
- base::File::Error error) {
- ASSERT_EQ(base::File::FILE_OK, error);
- }
- class TestValidatorFactory : public CopyOrMoveFileValidatorFactory {
- public:
- // A factory that creates validators that accept everything or nothing.
- TestValidatorFactory() = default;
- ~TestValidatorFactory() override = default;
- CopyOrMoveFileValidator* CreateCopyOrMoveFileValidator(
- const FileSystemURL& /*src_url*/,
- const base::FilePath& /*platform_path*/) override {
- // Move arg management to TestValidator?
- return new TestValidator(true, true, std::string("2"));
- }
- private:
- class TestValidator : public CopyOrMoveFileValidator {
- public:
- explicit TestValidator(bool pre_copy_valid,
- bool post_copy_valid,
- const std::string& reject_string)
- : result_(pre_copy_valid ? base::File::FILE_OK
- : base::File::FILE_ERROR_SECURITY),
- write_result_(post_copy_valid ? base::File::FILE_OK
- : base::File::FILE_ERROR_SECURITY),
- reject_string_(reject_string) {}
- TestValidator(const TestValidator&) = delete;
- TestValidator& operator=(const TestValidator&) = delete;
- ~TestValidator() override = default;
- void StartPreWriteValidation(ResultCallback result_callback) override {
- // Post the result since a real validator must do work asynchronously.
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::BindOnce(std::move(result_callback), result_));
- }
- void StartPostWriteValidation(const base::FilePath& dest_platform_path,
- ResultCallback result_callback) override {
- base::File::Error result = write_result_;
- std::string unsafe = dest_platform_path.BaseName().AsUTF8Unsafe();
- if (unsafe.find(reject_string_) != std::string::npos) {
- result = base::File::FILE_ERROR_SECURITY;
- }
- // Post the result since a real validator must do work asynchronously.
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::BindOnce(std::move(result_callback), result));
- }
- private:
- base::File::Error result_;
- base::File::Error write_result_;
- std::string reject_string_;
- };
- };
- class CopyOrMoveRecordAndSecurityDelegate : public CopyOrMoveHookDelegate {
- public:
- // Records method invocations.
- struct ProgressRecord {
- enum class Type {
- kBeginFile = 0,
- kBeginDirectory,
- kProgress,
- kEndCopy,
- kEndMove,
- kEndRemoveSource,
- kError,
- } type;
- FileSystemURL source_url;
- FileSystemURL dest_url;
- int64_t size;
- base::File::Error error;
- };
- using StatusCallback = FileSystemOperation::StatusCallback;
- // Required callback to check whether a file/directory should be blocked for
- // transfer. The callback returns true if the transfer should be blocked.
- using ShouldBlockCallback =
- base::RepeatingCallback<bool(const FileSystemURL& source_url)>;
- explicit CopyOrMoveRecordAndSecurityDelegate(
- std::vector<ProgressRecord>& records,
- const ShouldBlockCallback& should_block_callback)
- : records_(records), should_block_callback_(should_block_callback) {}
- ~CopyOrMoveRecordAndSecurityDelegate() override = default;
- void OnBeginProcessFile(const FileSystemURL& source_url,
- const FileSystemURL& destination_url,
- StatusCallback callback) override {
- AddRecord(ProgressRecord::Type::kBeginFile, source_url, destination_url, 0,
- base::File::FILE_OK);
- if (should_block_callback_.Run(source_url)) {
- std::move(callback).Run(base::File::FILE_ERROR_SECURITY);
- } else {
- std::move(callback).Run(base::File::FILE_OK);
- }
- }
- void OnBeginProcessDirectory(const FileSystemURL& source_url,
- const FileSystemURL& destination_url,
- StatusCallback callback) override {
- AddRecord(ProgressRecord::Type::kBeginDirectory, source_url,
- destination_url, 0, base::File::FILE_OK);
- if (should_block_callback_.Run(source_url)) {
- std::move(callback).Run(base::File::FILE_ERROR_SECURITY);
- } else {
- std::move(callback).Run(base::File::FILE_OK);
- }
- }
- void OnProgress(const FileSystemURL& source_url,
- const FileSystemURL& destination_url,
- int64_t size) override {
- AddRecord(ProgressRecord::Type::kProgress, source_url, destination_url,
- size, base::File::FILE_OK);
- }
- void OnError(const FileSystemURL& source_url,
- const FileSystemURL& destination_url,
- base::File::Error error) override {
- AddRecord(ProgressRecord::Type::kError, source_url, destination_url, 0,
- error);
- }
- void OnEndCopy(const FileSystemURL& source_url,
- const FileSystemURL& destination_url) override {
- AddRecord(ProgressRecord::Type::kEndCopy, source_url, destination_url, 0,
- base::File::FILE_OK);
- }
- void OnEndMove(const FileSystemURL& source_url,
- const FileSystemURL& destination_url) override {
- AddRecord(ProgressRecord::Type::kEndMove, source_url, destination_url, 0,
- base::File::FILE_OK);
- }
- void OnEndRemoveSource(const FileSystemURL& source_url) override {
- AddRecord(ProgressRecord::Type::kEndRemoveSource, source_url,
- FileSystemURL(), 0, base::File::FILE_OK);
- }
- private:
- void AddRecord(ProgressRecord::Type type,
- const FileSystemURL& source_url,
- const FileSystemURL& dest_url,
- int64_t size,
- base::File::Error error) {
- ProgressRecord record;
- record.type = type;
- record.source_url = source_url;
- record.dest_url = dest_url;
- record.size = size;
- record.error = error;
- records_.push_back(record);
- }
- // Raw ptr safe here, because the records will be destructed at end of test,
- // i.e., after the CopyOrMove operation has finished.
- std::vector<ProgressRecord>& records_;
- ShouldBlockCallback should_block_callback_;
- };
- void RecordFileProgressCallback(std::vector<int64_t>* records,
- int64_t progress) {
- records->push_back(progress);
- }
- void AssignAndQuit(base::RunLoop* run_loop,
- base::File::Error* result_out,
- base::File::Error result) {
- *result_out = result;
- run_loop->Quit();
- }
- class ScopedThreadStopper {
- public:
- explicit ScopedThreadStopper(base::Thread* thread) : thread_(thread) {}
- ScopedThreadStopper(const ScopedThreadStopper&) = delete;
- ScopedThreadStopper& operator=(const ScopedThreadStopper&) = delete;
- ~ScopedThreadStopper() {
- if (thread_) {
- // Give another chance for deleted streams to perform Close.
- base::RunLoop run_loop;
- thread_->task_runner()->PostTaskAndReply(FROM_HERE, base::DoNothing(),
- run_loop.QuitClosure());
- run_loop.Run();
- thread_->Stop();
- }
- }
- bool is_valid() const { return thread_; }
- private:
- raw_ptr<base::Thread> thread_;
- };
- class CopyOrMoveOperationTestHelper {
- public:
- CopyOrMoveOperationTestHelper(const std::string& origin,
- FileSystemType src_type,
- FileSystemType dest_type)
- : origin_(url::Origin::Create(GURL(origin))),
- src_type_(src_type),
- dest_type_(dest_type),
- special_storage_policy_(
- base::MakeRefCounted<MockSpecialStoragePolicy>()),
- task_environment_(base::test::TaskEnvironment::MainThreadType::IO) {}
- CopyOrMoveOperationTestHelper(const CopyOrMoveOperationTestHelper&) = delete;
- CopyOrMoveOperationTestHelper& operator=(
- const CopyOrMoveOperationTestHelper&) = delete;
- ~CopyOrMoveOperationTestHelper() {
- file_system_context_ = nullptr;
- quota_manager_ = nullptr;
- quota_manager_proxy_ = nullptr;
- task_environment_.RunUntilIdle();
- }
- void SetUp() { SetUp(true, true); }
- void SetUpNoValidator() { SetUp(true, false); }
- void SetUp(bool require_copy_or_move_validator,
- bool init_copy_or_move_validator) {
- ASSERT_TRUE(base_.CreateUniqueTempDir());
- base::FilePath base_dir = base_.GetPath();
- quota_manager_ = base::MakeRefCounted<MockQuotaManager>(
- false /* is_incognito */, base_dir, base::ThreadTaskRunnerHandle::Get(),
- special_storage_policy_);
- quota_manager_proxy_ = base::MakeRefCounted<MockQuotaManagerProxy>(
- quota_manager_.get(), base::ThreadTaskRunnerHandle::Get());
- file_system_context_ =
- CreateFileSystemContextForTesting(quota_manager_proxy_, base_dir);
- // Prepare the origin's root directory.
- FileSystemBackend* backend =
- file_system_context_->GetFileSystemBackend(src_type_);
- backend->ResolveURL(
- FileSystemURL::CreateForTest(blink::StorageKey(url::Origin(origin_)),
- src_type_, base::FilePath()),
- OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, base::BindOnce(&ExpectOk));
- backend = file_system_context_->GetFileSystemBackend(dest_type_);
- if (dest_type_ == kFileSystemTypeTest) {
- TestFileSystemBackend* test_backend =
- static_cast<TestFileSystemBackend*>(backend);
- auto factory = std::make_unique<TestValidatorFactory>();
- test_backend->set_require_copy_or_move_validator(
- require_copy_or_move_validator);
- if (init_copy_or_move_validator)
- test_backend->InitializeCopyOrMoveFileValidatorFactory(
- std::move(factory));
- }
- backend->ResolveURL(
- FileSystemURL::CreateForTest(blink::StorageKey(url::Origin(origin_)),
- dest_type_, base::FilePath()),
- OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, base::BindOnce(&ExpectOk));
- task_environment_.RunUntilIdle();
- // Grant relatively big quota initially.
- quota_manager_->SetQuota(blink::StorageKey(origin_),
- FileSystemTypeToQuotaStorageType(src_type_),
- 1024 * 1024);
- quota_manager_->SetQuota(blink::StorageKey(origin_),
- FileSystemTypeToQuotaStorageType(dest_type_),
- 1024 * 1024);
- }
- int64_t GetSourceUsage() {
- int64_t usage = 0;
- GetUsageAndQuota(src_type_, &usage, nullptr);
- return usage;
- }
- int64_t GetDestUsage() {
- int64_t usage = 0;
- GetUsageAndQuota(dest_type_, &usage, nullptr);
- return usage;
- }
- FileSystemURL SourceURL(const std::string& path) {
- return file_system_context_->CreateCrackedFileSystemURL(
- blink::StorageKey(origin_), src_type_,
- base::FilePath::FromUTF8Unsafe(path));
- }
- FileSystemURL DestURL(const std::string& path) {
- return file_system_context_->CreateCrackedFileSystemURL(
- blink::StorageKey(origin_), dest_type_,
- base::FilePath::FromUTF8Unsafe(path));
- }
- base::File::Error Copy(const FileSystemURL& src, const FileSystemURL& dest) {
- return AsyncFileTestHelper::Copy(file_system_context_.get(), src, dest);
- }
- base::File::Error CopyWithHookDelegate(
- const FileSystemURL& src,
- const FileSystemURL& dest,
- FileSystemOperation::ErrorBehavior error_behavior,
- std::unique_ptr<storage::CopyOrMoveHookDelegate>
- copy_or_move_hook_delegate) {
- return AsyncFileTestHelper::CopyWithHookDelegate(
- file_system_context_.get(), src, dest, error_behavior,
- std::move(copy_or_move_hook_delegate));
- }
- base::File::Error CopyBlockAll(const FileSystemURL& src,
- const FileSystemURL& dest) {
- CopyOrMoveRecordAndSecurityDelegate::ShouldBlockCallback blocking_callback =
- base::BindRepeating(
- [](const FileSystemURL& source_url) { return true; });
- std::vector<CopyOrMoveRecordAndSecurityDelegate::ProgressRecord> records;
- return CopyWithHookDelegate(
- src, dest, FileSystemOperation::ERROR_BEHAVIOR_ABORT,
- std::make_unique<CopyOrMoveRecordAndSecurityDelegate>(
- records, blocking_callback));
- }
- // Determines whether a specific `src` should be blocked.
- // It uses the relative path of `src` from `root_src` and compares this path
- // with the records within `kRegularFileSystemTestCases`. It blocks the `src`
- // if the corresponding test case's block_action is `BLOCKED`.
- // Additionally, it ensures that this function is not called for test cases
- // with block_action `PARENT_BLOCKED`.
- static bool ShouldBlockSource(const FileSystemURL& root_src,
- const FileSystemURL& src) {
- base::FilePath src_relative_path;
- if (root_src == src) {
- // Don't block root, because we only care about the directories/folders
- // within the root.
- return false;
- }
- root_src.virtual_path().AppendRelativePath(src.virtual_path(),
- &src_relative_path);
- src_relative_path = src_relative_path.NormalizePathSeparators();
- const auto records = base::make_span(kRegularFileSystemTestCases,
- kRegularFileSystemTestCaseSize);
- auto record_it = base::ranges::find(
- records, src_relative_path, [](const FileSystemTestCaseRecord& record) {
- return base::FilePath(record.path).NormalizePathSeparators();
- });
- EXPECT_NE(record_it, records.end());
- EXPECT_NE(record_it->block_action, TestBlockAction::PARENT_BLOCKED);
- return record_it->block_action == TestBlockAction::BLOCKED;
- }
- // Copy the `root` directory, but block some files that are expected to be
- // blocked based on `kRegularFileSystemTestCases`.
- base::File::Error CopyBlockSome(
- const FileSystemURL& src,
- const FileSystemURL& dest,
- std::vector<CopyOrMoveRecordAndSecurityDelegate::ProgressRecord>&
- records) {
- return CopyWithHookDelegate(
- src, dest, FileSystemOperation::ERROR_BEHAVIOR_SKIP,
- std::make_unique<CopyOrMoveRecordAndSecurityDelegate>(
- records,
- base::BindRepeating(
- &CopyOrMoveOperationTestHelper::ShouldBlockSource, src)));
- }
- base::File::Error Move(const FileSystemURL& src, const FileSystemURL& dest) {
- return AsyncFileTestHelper::Move(file_system_context_.get(), src, dest);
- }
- base::File::Error MoveWithHookDelegate(
- const FileSystemURL& src,
- const FileSystemURL& dest,
- FileSystemOperation::ErrorBehavior error_behavior,
- std::unique_ptr<storage::CopyOrMoveHookDelegate>
- copy_or_move_hook_delegate) {
- return AsyncFileTestHelper::MoveWithHookDelegate(
- file_system_context_.get(), src, dest, error_behavior,
- std::move(copy_or_move_hook_delegate));
- }
- base::File::Error MoveBlockAll(const FileSystemURL& src,
- const FileSystemURL& dest) {
- CopyOrMoveRecordAndSecurityDelegate::ShouldBlockCallback blocking_callback =
- base::BindRepeating(
- [](const FileSystemURL& source_url) { return true; });
- std::vector<CopyOrMoveRecordAndSecurityDelegate::ProgressRecord> records;
- return MoveWithHookDelegate(
- src, dest, FileSystemOperation::ERROR_BEHAVIOR_ABORT,
- std::make_unique<CopyOrMoveRecordAndSecurityDelegate>(
- records, blocking_callback));
- }
- // Move the `root` directory, but block some files that are expected to be
- // blocked based on `kRegularFileSystemTestCases`.
- base::File::Error MoveBlockSome(
- const FileSystemURL& src,
- const FileSystemURL& dest,
- std::vector<CopyOrMoveRecordAndSecurityDelegate::ProgressRecord>&
- records) {
- return MoveWithHookDelegate(
- src, dest, FileSystemOperation::ERROR_BEHAVIOR_SKIP,
- std::make_unique<CopyOrMoveRecordAndSecurityDelegate>(
- records,
- base::BindRepeating(
- &CopyOrMoveOperationTestHelper::ShouldBlockSource, src)));
- }
- base::File::Error SetUpTestCaseFiles(
- const FileSystemURL& root,
- const FileSystemTestCaseRecord* const test_cases,
- size_t test_case_size) {
- base::File::Error result = base::File::FILE_ERROR_FAILED;
- for (size_t i = 0; i < test_case_size; ++i) {
- const FileSystemTestCaseRecord& test_case = test_cases[i];
- FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL(
- root.storage_key(), root.mount_type(),
- root.virtual_path().Append(test_case.path));
- if (test_case.is_directory)
- result = CreateDirectory(url);
- else
- result = CreateFile(url, test_case.data_file_size);
- EXPECT_EQ(base::File::FILE_OK, result) << url.DebugString();
- if (result != base::File::FILE_OK)
- return result;
- }
- return result;
- }
- enum class VerifyDirectoryState {
- // Verifies all files.
- // Used if no blocking is specified and for the source directory after a
- // move.
- ALL_FILES_EXIST,
- // Verifies that only the blocked files and their ancestors exist.
- // Used to check the source for a move.
- ONLY_BLOCKED_FILES_AND_PARENTS,
- // Verifies that only the allowed files exist.
- // Used to check the destination.
- ONLY_ALLOWED_FILES,
- };
- void VerifyTestCaseFiles(const FileSystemURL& root,
- const FileSystemTestCaseRecord* const test_cases,
- size_t test_case_size,
- VerifyDirectoryState check_state) {
- std::map<base::FilePath, const FileSystemTestCaseRecord*> test_case_map;
- for (size_t i = 0; i < test_case_size; ++i) {
- test_case_map[base::FilePath(test_cases[i].path)
- .NormalizePathSeparators()] = &test_cases[i];
- }
- base::queue<FileSystemURL> directories;
- FileEntryList entries;
- directories.push(root);
- while (!directories.empty()) {
- FileSystemURL dir = directories.front();
- directories.pop();
- ASSERT_EQ(base::File::FILE_OK, ReadDirectory(dir, &entries));
- for (const filesystem::mojom::DirectoryEntry& entry : entries) {
- FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL(
- dir.storage_key(), dir.mount_type(),
- dir.virtual_path().Append(entry.name));
- base::FilePath relative;
- root.virtual_path().AppendRelativePath(url.virtual_path(), &relative);
- relative = relative.NormalizePathSeparators();
- ASSERT_TRUE(base::Contains(test_case_map, relative));
- if (entry.type == filesystem::mojom::FsFileType::DIRECTORY) {
- EXPECT_TRUE(test_case_map[relative]->is_directory);
- directories.push(url);
- } else {
- EXPECT_FALSE(test_case_map[relative]->is_directory);
- EXPECT_TRUE(FileExists(url, test_case_map[relative]->data_file_size));
- }
- if (check_state ==
- VerifyDirectoryState::ONLY_BLOCKED_FILES_AND_PARENTS) {
- // We check for remaining blocked files, i.e., all files that are
- // actually blocked (BLOCK, PARENT_BLOCKED) and their parent
- // directories (CHILD_BLOCKED).
- EXPECT_THAT(test_case_map[relative]->block_action,
- ::testing::AnyOf(TestBlockAction::CHILD_BLOCKED,
- TestBlockAction::BLOCKED,
- TestBlockAction::PARENT_BLOCKED))
- << "path: " << relative.LossyDisplayName() << "\nblock_action: "
- << static_cast<int>(test_case_map[relative]->block_action);
- } else if (check_state == VerifyDirectoryState::ONLY_ALLOWED_FILES) {
- // We check for allowed files, i.e., all files that were transferred
- // and their parent directories (CHILD_BLOCKED, ALLOW).
- EXPECT_THAT(test_case_map[relative]->block_action,
- ::testing::AnyOf(TestBlockAction::ALLOWED,
- TestBlockAction::CHILD_BLOCKED))
- << "path: " << relative.LossyDisplayName() << "\nblock_action: "
- << static_cast<int>(test_case_map[relative]->block_action);
- }
- test_case_map.erase(relative);
- }
- }
- if (check_state == VerifyDirectoryState::ALL_FILES_EXIST) {
- EXPECT_TRUE(test_case_map.empty());
- for (const auto& path_record_pair : test_case_map) {
- LOG(ERROR) << "Extra entry: "
- << path_record_pair.first.LossyDisplayName();
- }
- } else {
- for (const auto& path_record_pair : test_case_map) {
- auto* record = path_record_pair.second;
- if (check_state ==
- VerifyDirectoryState::ONLY_BLOCKED_FILES_AND_PARENTS) {
- EXPECT_THAT(record->block_action,
- ::testing::AnyOf(TestBlockAction::ALLOWED))
- << "path: " << path_record_pair.first.LossyDisplayName()
- << "\nblock_action: " << static_cast<int>(record->block_action);
- } else if (check_state == VerifyDirectoryState::ONLY_ALLOWED_FILES) {
- EXPECT_THAT(record->block_action,
- ::testing::AnyOf(TestBlockAction::BLOCKED,
- TestBlockAction::PARENT_BLOCKED))
- << "path: " << path_record_pair.first.LossyDisplayName()
- << "\nblock_action: " << static_cast<int>(record->block_action);
- }
- }
- }
- }
- base::File::Error ReadDirectory(const FileSystemURL& url,
- FileEntryList* entries) {
- return AsyncFileTestHelper::ReadDirectory(file_system_context_.get(), url,
- entries);
- }
- base::File::Error CreateDirectory(const FileSystemURL& url) {
- return AsyncFileTestHelper::CreateDirectory(file_system_context_.get(),
- url);
- }
- base::File::Error CreateFile(const FileSystemURL& url, size_t size) {
- base::File::Error result =
- AsyncFileTestHelper::CreateFile(file_system_context_.get(), url);
- if (result != base::File::FILE_OK)
- return result;
- return AsyncFileTestHelper::TruncateFile(file_system_context_.get(), url,
- size);
- }
- bool FileExists(const FileSystemURL& url, int64_t expected_size) {
- return AsyncFileTestHelper::FileExists(file_system_context_.get(), url,
- expected_size);
- }
- bool DirectoryExists(const FileSystemURL& url) {
- return AsyncFileTestHelper::DirectoryExists(file_system_context_.get(),
- url);
- }
- private:
- void GetUsageAndQuota(FileSystemType type, int64_t* usage, int64_t* quota) {
- blink::mojom::QuotaStatusCode status =
- AsyncFileTestHelper::GetUsageAndQuota(quota_manager_->proxy(),
- blink::StorageKey(origin_), type,
- usage, quota);
- ASSERT_EQ(blink::mojom::QuotaStatusCode::kOk, status);
- }
- private:
- const url::Origin origin_;
- const FileSystemType src_type_;
- const FileSystemType dest_type_;
- scoped_refptr<MockSpecialStoragePolicy> special_storage_policy_;
- base::ScopedTempDir base_;
- base::test::TaskEnvironment task_environment_;
- scoped_refptr<FileSystemContext> file_system_context_;
- scoped_refptr<MockQuotaManagerProxy> quota_manager_proxy_;
- scoped_refptr<MockQuotaManager> quota_manager_;
- };
- } // namespace
- class LocalFileSystemCopyOrMoveOperationTest
- : public ::testing::TestWithParam<std::tuple</*is_move*/ bool,
- /*is_local*/ bool,
- /*blocking_enabled*/ bool>> {
- protected:
- bool IsMove() { return std::get<0>(GetParam()); }
- bool IsLocal() { return std::get<1>(GetParam()); }
- bool BlockingEnabled() { return std::get<2>(GetParam()); }
- };
- INSTANTIATE_TEST_SUITE_P(
- All,
- LocalFileSystemCopyOrMoveOperationTest,
- ::testing::Combine(::testing::Bool(), ::testing::Bool(), ::testing::Bool()),
- [](const ::testing::TestParamInfo<
- LocalFileSystemCopyOrMoveOperationTest::ParamType>& info) {
- std::string name;
- name += std::get<0>(info.param) ? "Move" : "Copy";
- name += std::get<1>(info.param) ? "Local" : "";
- name += std::get<2>(info.param) ? "WithBlocking" : "";
- return name;
- });
- TEST_P(LocalFileSystemCopyOrMoveOperationTest, SingleFile) {
- CopyOrMoveOperationTestHelper helper(
- "http://foo", kFileSystemTypeTemporary,
- IsLocal() ? kFileSystemTypeTemporary : kFileSystemTypePersistent);
- helper.SetUp();
- FileSystemURL src = helper.SourceURL("a");
- FileSystemURL dest = helper.DestURL("b");
- int64_t src_initial_usage = helper.GetSourceUsage();
- // Set up a source file.
- ASSERT_EQ(base::File::FILE_OK, helper.CreateFile(src, 10));
- int64_t src_increase = helper.GetSourceUsage() - src_initial_usage;
- if (IsMove()) {
- // Move it.
- if (BlockingEnabled()) {
- ASSERT_EQ(base::File::FILE_ERROR_SECURITY, helper.MoveBlockAll(src, dest))
- << "For single file moves, the last error should be a security "
- "error!";
- } else {
- ASSERT_EQ(base::File::FILE_OK, helper.Move(src, dest));
- }
- } else {
- // Copy it.
- if (BlockingEnabled()) {
- ASSERT_EQ(base::File::FILE_ERROR_SECURITY, helper.CopyBlockAll(src, dest))
- << "For single file copies, the last error should be a security "
- "error!";
- } else {
- ASSERT_EQ(base::File::FILE_OK, helper.Copy(src, dest));
- }
- }
- bool src_should_exist = !IsMove() || BlockingEnabled();
- bool dest_should_exist = !BlockingEnabled();
- // Verify.
- ASSERT_EQ(src_should_exist, helper.FileExists(src, 10));
- ASSERT_EQ(dest_should_exist, helper.FileExists(dest, 10));
- int64_t src_new_usage = helper.GetSourceUsage();
- if (IsMove() || BlockingEnabled()) {
- EXPECT_EQ(src_new_usage, src_initial_usage + src_increase);
- } else {
- EXPECT_EQ(src_new_usage, src_initial_usage + 2 * src_increase);
- }
- }
- TEST_P(LocalFileSystemCopyOrMoveOperationTest, EmptyDirectory) {
- CopyOrMoveOperationTestHelper helper(
- "http://foo", kFileSystemTypeTemporary,
- IsLocal() ? kFileSystemTypeTemporary : kFileSystemTypePersistent);
- helper.SetUp();
- FileSystemURL src = helper.SourceURL("a");
- FileSystemURL dest = helper.DestURL("b");
- int64_t src_initial_usage = helper.GetSourceUsage();
- // Set up a source directory.
- ASSERT_EQ(base::File::FILE_OK, helper.CreateDirectory(src));
- int64_t src_increase = helper.GetSourceUsage() - src_initial_usage;
- if (IsMove()) {
- // Move it.
- if (BlockingEnabled()) {
- ASSERT_EQ(base::File::FILE_ERROR_SECURITY, helper.MoveBlockAll(src, dest))
- << "For single directory moves, the last error should be a security "
- "error!";
- } else {
- ASSERT_EQ(base::File::FILE_OK, helper.Move(src, dest));
- }
- } else {
- // Copy it.
- if (BlockingEnabled()) {
- ASSERT_EQ(base::File::FILE_ERROR_SECURITY, helper.CopyBlockAll(src, dest))
- << "For single directory copies, the last error should be a security "
- "error!";
- } else {
- ASSERT_EQ(base::File::FILE_OK, helper.Copy(src, dest));
- }
- }
- bool src_should_exist = !IsMove() || BlockingEnabled();
- bool dest_should_exist = !BlockingEnabled();
- // Verify.
- ASSERT_EQ(src_should_exist, helper.DirectoryExists(src));
- ASSERT_EQ(dest_should_exist, helper.DirectoryExists(dest));
- int64_t src_new_usage = helper.GetSourceUsage();
- if (IsMove() || BlockingEnabled()) {
- EXPECT_EQ(src_new_usage, src_initial_usage + src_increase);
- } else {
- EXPECT_EQ(src_new_usage, src_initial_usage + 2 * src_increase);
- }
- }
- TEST_P(LocalFileSystemCopyOrMoveOperationTest, FilesAndDirectories) {
- CopyOrMoveOperationTestHelper helper(
- "http://foo", kFileSystemTypeTemporary,
- IsLocal() ? kFileSystemTypeTemporary : kFileSystemTypePersistent);
- helper.SetUp();
- FileSystemURL src = helper.SourceURL("a");
- FileSystemURL dest = helper.DestURL("b");
- int64_t src_initial_usage = helper.GetSourceUsage();
- // Set up a source directory.
- ASSERT_EQ(base::File::FILE_OK, helper.CreateDirectory(src));
- ASSERT_EQ(base::File::FILE_OK,
- helper.SetUpTestCaseFiles(src, kRegularFileSystemTestCases,
- kRegularFileSystemTestCaseSize));
- int64_t src_increase = helper.GetSourceUsage() - src_initial_usage;
- if (IsMove()) {
- // Move it.
- if (BlockingEnabled()) {
- // Records are not checked in this test.
- std::vector<CopyOrMoveRecordAndSecurityDelegate::ProgressRecord> records;
- ASSERT_EQ(base::File::FILE_ERROR_NOT_EMPTY,
- helper.MoveBlockSome(src, dest, records))
- << "For multi-directory moves, the last error should be "
- "FILE_ERROR_NOT_EMPTY because the src directory could not be "
- "removed!";
- } else {
- ASSERT_EQ(base::File::FILE_OK, helper.Move(src, dest));
- }
- } else {
- // Copy it.
- if (BlockingEnabled()) {
- // For copies, only forbidden file/directory access errors are reported.
- // Records are not checked in this test.
- std::vector<CopyOrMoveRecordAndSecurityDelegate::ProgressRecord> records;
- ASSERT_EQ(base::File::FILE_ERROR_SECURITY,
- helper.CopyBlockSome(src, dest, records))
- << "For multi-directory copies, the last error should be "
- "FILE_ERROR_SECURITY!";
- } else {
- ASSERT_EQ(base::File::FILE_OK, helper.Copy(src, dest));
- }
- }
- bool src_should_exist = !IsMove() || BlockingEnabled();
- // Verify.
- ASSERT_EQ(src_should_exist, helper.DirectoryExists(src));
- // For recursive transfers, the destination directory is always created
- // (unless the root source directory is blocked, which is not blocked here)!
- ASSERT_TRUE(helper.DirectoryExists(dest));
- if (!IsMove()) {
- // For copies, all files should be there!
- helper.VerifyTestCaseFiles(
- src, kRegularFileSystemTestCases, kRegularFileSystemTestCaseSize,
- CopyOrMoveOperationTestHelper::VerifyDirectoryState::ALL_FILES_EXIST);
- } else {
- if (BlockingEnabled()) {
- // For moves, only blocked files should remain.
- helper.VerifyTestCaseFiles(
- src, kRegularFileSystemTestCases, kRegularFileSystemTestCaseSize,
- CopyOrMoveOperationTestHelper::VerifyDirectoryState::
- ONLY_BLOCKED_FILES_AND_PARENTS);
- }
- }
- // Destination checking is independent of whether the operation is a copy or
- // move.
- if (BlockingEnabled()) {
- helper.VerifyTestCaseFiles(dest, kRegularFileSystemTestCases,
- kRegularFileSystemTestCaseSize,
- CopyOrMoveOperationTestHelper::
- VerifyDirectoryState::ONLY_ALLOWED_FILES);
- } else {
- helper.VerifyTestCaseFiles(
- dest, kRegularFileSystemTestCases, kRegularFileSystemTestCaseSize,
- CopyOrMoveOperationTestHelper::VerifyDirectoryState::ALL_FILES_EXIST);
- }
- // For local operations we can only check the size if there is no blocking
- // involved.
- if (!BlockingEnabled()) {
- int64_t src_new_usage = helper.GetSourceUsage();
- if (IsMove()) {
- ASSERT_EQ(src_initial_usage + src_increase, src_new_usage);
- } else {
- // Copies duplicate used size on common file system.
- ASSERT_EQ(src_initial_usage + 2 * src_increase, src_new_usage);
- }
- }
- }
- TEST(LocalFileSystemCopyOrMoveOperationTest,
- MoveDirectoryFailPostWriteValidation) {
- CopyOrMoveOperationTestHelper helper("http://foo", kFileSystemTypeTemporary,
- kFileSystemTypeTest);
- helper.SetUp();
- FileSystemURL src = helper.SourceURL("a");
- FileSystemURL dest = helper.DestURL("b");
- // Set up a source directory.
- ASSERT_EQ(base::File::FILE_OK, helper.CreateDirectory(src));
- ASSERT_EQ(base::File::FILE_OK,
- helper.SetUpTestCaseFiles(src, kRegularFileSystemTestCases,
- kRegularFileSystemTestCaseSize));
- // Move it.
- helper.Move(src, dest);
- // Verify.
- ASSERT_TRUE(helper.DirectoryExists(src));
- ASSERT_TRUE(helper.DirectoryExists(dest));
- // In the move operation, [file 0, file 2, file 3] are processed as LIFO.
- // After file 3 is processed, file 2 is rejected by the validator and the
- // operation fails. That is, only file 3 should be in dest.
- FileSystemTestCaseRecord kMoveDirResultCases[] = {
- {false, FILE_PATH_LITERAL("file 3"), 0},
- };
- helper.VerifyTestCaseFiles(
- dest, kMoveDirResultCases, std::size(kMoveDirResultCases),
- CopyOrMoveOperationTestHelper::VerifyDirectoryState::ALL_FILES_EXIST);
- }
- TEST(LocalFileSystemCopyOrMoveOperationTest, CopySingleFileNoValidator) {
- CopyOrMoveOperationTestHelper helper("http://foo", kFileSystemTypeTemporary,
- kFileSystemTypeTest);
- helper.SetUpNoValidator();
- FileSystemURL src = helper.SourceURL("a");
- FileSystemURL dest = helper.DestURL("b");
- // Set up a source file.
- ASSERT_EQ(base::File::FILE_OK, helper.CreateFile(src, 10));
- // The copy attempt should fail with a security error -- getting
- // the factory returns a security error, and the copy operation must
- // respect that.
- ASSERT_EQ(base::File::FILE_ERROR_SECURITY, helper.Copy(src, dest));
- }
- TEST_P(LocalFileSystemCopyOrMoveOperationTest, FilesAndDirectoriesProgress) {
- CopyOrMoveOperationTestHelper helper(
- "http://foo", kFileSystemTypeTemporary,
- IsLocal() ? kFileSystemTypeTemporary : kFileSystemTypePersistent);
- helper.SetUp();
- FileSystemURL src = helper.SourceURL("a");
- FileSystemURL dest = helper.DestURL("b");
- // Set up a source directory.
- ASSERT_EQ(base::File::FILE_OK, helper.CreateDirectory(src));
- ASSERT_EQ(base::File::FILE_OK,
- helper.SetUpTestCaseFiles(src, kRegularFileSystemTestCases,
- kRegularFileSystemTestCaseSize));
- CopyOrMoveRecordAndSecurityDelegate::ShouldBlockCallback allow_all_callback =
- base::BindRepeating(
- [](const FileSystemURL& source_url) { return false; });
- std::vector<CopyOrMoveRecordAndSecurityDelegate::ProgressRecord> records;
- if (IsMove()) {
- // Move it.
- if (BlockingEnabled()) {
- ASSERT_EQ(base::File::FILE_ERROR_NOT_EMPTY,
- helper.MoveBlockSome(src, dest, records))
- << "For multi-directory moves, the last error should be "
- "FILE_ERROR_NOT_EMPTY because the src directory could not be "
- "removed!";
- } else {
- ASSERT_EQ(base::File::FILE_OK,
- helper.MoveWithHookDelegate(
- src, dest, FileSystemOperation::ERROR_BEHAVIOR_ABORT,
- std::make_unique<CopyOrMoveRecordAndSecurityDelegate>(
- records, allow_all_callback)));
- }
- } else {
- // Copy it.
- if (BlockingEnabled()) {
- // For copies, only forbidden file/directory access errors are reported.
- ASSERT_EQ(base::File::FILE_ERROR_SECURITY,
- helper.CopyBlockSome(src, dest, records))
- << "For multi-directory copies, the last error should be "
- "FILE_ERROR_SECURITY!";
- } else {
- ASSERT_EQ(base::File::FILE_OK,
- helper.CopyWithHookDelegate(
- src, dest, FileSystemOperation::ERROR_BEHAVIOR_ABORT,
- std::make_unique<CopyOrMoveRecordAndSecurityDelegate>(
- records, allow_all_callback)));
- }
- }
- // Verify that for `src` kBeginFile is called.
- // This behavior is expected, because for the src entry, ProcessFile is
- // always called independent of whether it is a directory or not. Note: This
- // might change if the behavior of RecursiveOperationDelegate is changed.
- EXPECT_EQ(
- CopyOrMoveRecordAndSecurityDelegate::ProgressRecord::Type::kBeginFile,
- records[0].type);
- EXPECT_EQ(dest, records[0].dest_url);
- // Verify progress records.
- for (size_t i = 0; i < kRegularFileSystemTestCaseSize; ++i) {
- const FileSystemTestCaseRecord& test_case = kRegularFileSystemTestCases[i];
- FileSystemURL src_url = helper.SourceURL(
- std::string("a/") + base::FilePath(test_case.path).AsUTF8Unsafe());
- FileSystemURL dest_url = helper.DestURL(
- std::string("b/") + base::FilePath(test_case.path).AsUTF8Unsafe());
- // Find the first and last progress record.
- size_t begin_index = records.size();
- size_t end_index = records.size();
- for (size_t j = 0; j < records.size(); ++j) {
- if (records[j].source_url == src_url) {
- if (begin_index == records.size())
- begin_index = j;
- end_index = j;
- }
- }
- if (BlockingEnabled() &&
- test_case.block_action == TestBlockAction::PARENT_BLOCKED) {
- // There shouldn't be any record, if the parent is blocked.
- ASSERT_EQ(begin_index, records.size());
- continue;
- }
- // The record should be found.
- ASSERT_NE(begin_index, records.size());
- ASSERT_NE(end_index, records.size());
- ASSERT_NE(begin_index, end_index);
- if (test_case.is_directory) {
- // A directory copy/move starts with kBegin and kEndCopy.
- EXPECT_EQ(CopyOrMoveRecordAndSecurityDelegate::ProgressRecord::Type::
- kBeginDirectory,
- records[begin_index].type);
- EXPECT_EQ(dest_url, records[begin_index].dest_url);
- if (BlockingEnabled() &&
- test_case.block_action == TestBlockAction::BLOCKED) {
- EXPECT_EQ(
- CopyOrMoveRecordAndSecurityDelegate::ProgressRecord::Type::kError,
- records[begin_index + 1].type);
- EXPECT_EQ(base::File::FILE_ERROR_SECURITY,
- records[begin_index + 1].error);
- EXPECT_EQ(dest_url, records[begin_index + 1].dest_url);
- EXPECT_EQ(begin_index + 1, end_index);
- continue;
- }
- EXPECT_EQ(
- CopyOrMoveRecordAndSecurityDelegate::ProgressRecord::Type::kEndCopy,
- records[begin_index + 1].type);
- EXPECT_EQ(dest_url, records[begin_index + 1].dest_url);
- if (IsMove()) {
- if (BlockingEnabled() &&
- test_case.block_action == TestBlockAction::CHILD_BLOCKED) {
- EXPECT_EQ(
- CopyOrMoveRecordAndSecurityDelegate::ProgressRecord::Type::kError,
- records[end_index].type);
- EXPECT_FALSE(records[end_index].dest_url.is_valid());
- EXPECT_EQ(base::File::FILE_ERROR_NOT_EMPTY, records[end_index].error);
- EXPECT_NE(records[end_index - 1].source_url, src_url);
- continue;
- }
- // A directory move ends with kEndRemoveSource, after the contents of
- // the directory have been copied.
- EXPECT_EQ(CopyOrMoveRecordAndSecurityDelegate::ProgressRecord::Type::
- kEndRemoveSource,
- records[end_index].type);
- EXPECT_FALSE(records[end_index].dest_url.is_valid());
- } else {
- // For directory copy, the progress shouldn't be interlaced.
- EXPECT_EQ(begin_index + 1, end_index);
- }
- } else {
- // A file copy/move starts with kBeginFile.
- EXPECT_EQ(
- CopyOrMoveRecordAndSecurityDelegate::ProgressRecord::Type::kBeginFile,
- records[begin_index].type);
- EXPECT_EQ(dest_url, records[begin_index].dest_url);
- if (BlockingEnabled() &&
- test_case.block_action == TestBlockAction::BLOCKED) {
- // If a file is blocked, the second and last record for that file should
- // be an error.
- EXPECT_EQ(
- CopyOrMoveRecordAndSecurityDelegate::ProgressRecord::Type::kError,
- records[begin_index + 1].type);
- EXPECT_EQ(base::File::FILE_ERROR_SECURITY,
- records[begin_index + 1].error);
- EXPECT_EQ(dest_url, records[begin_index + 1].dest_url);
- EXPECT_EQ(begin_index + 1, end_index);
- continue;
- }
- // PROGRESS event's size should be ascending order.
- int64_t current_size = 0;
- size_t end_progress_index = end_index;
- if (IsMove() && !IsLocal()) {
- end_progress_index = end_index - 1;
- }
- if (IsMove() && IsLocal()) {
- EXPECT_EQ(begin_index, end_progress_index - 1)
- << "No progress should be reported for local moves!";
- } else {
- EXPECT_LT(begin_index, end_progress_index - 1)
- << "There should be some progress reported.";
- }
- for (size_t j = begin_index + 1; j < end_progress_index; ++j) {
- if (records[j].source_url == src_url) {
- EXPECT_EQ(CopyOrMoveRecordAndSecurityDelegate::ProgressRecord::Type::
- kProgress,
- records[j].type);
- EXPECT_EQ(dest_url, records[j].dest_url);
- EXPECT_GE(records[j].size, current_size);
- current_size = records[j].size;
- }
- }
- if (IsMove() && IsLocal()) {
- // For local moves, we expect kEndMove at the end.
- EXPECT_EQ(
- CopyOrMoveRecordAndSecurityDelegate::ProgressRecord::Type::kEndMove,
- records[end_index].type);
- EXPECT_EQ(dest_url, records[end_index].dest_url);
- } else {
- // A file move ends with kEndCopy and kEndRemoveSource, a copy with just
- // kEndCopy.
- size_t end_copy_index = IsMove() ? end_index - 1 : end_index;
- EXPECT_EQ(
- CopyOrMoveRecordAndSecurityDelegate::ProgressRecord::Type::kEndCopy,
- records[end_copy_index].type);
- EXPECT_EQ(dest_url, records[end_copy_index].dest_url);
- if (IsMove()) {
- EXPECT_EQ(CopyOrMoveRecordAndSecurityDelegate::ProgressRecord::Type::
- kEndRemoveSource,
- records[end_index].type);
- EXPECT_FALSE(records[end_index].dest_url.is_valid());
- }
- }
- }
- }
- }
- TEST(LocalFileSystemCopyOrMoveOperationTest, StreamCopyHelper) {
- base::ScopedTempDir temp_dir;
- ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
- base::FilePath source_path = temp_dir.GetPath().AppendASCII("source");
- base::FilePath dest_path = temp_dir.GetPath().AppendASCII("dest");
- const char kTestData[] = "abcdefghijklmnopqrstuvwxyz0123456789";
- base::WriteFile(source_path, kTestData);
- base::test::SingleThreadTaskEnvironment task_environment(
- base::test::SingleThreadTaskEnvironment::MainThreadType::IO);
- base::Thread file_thread("file_thread");
- ASSERT_TRUE(file_thread.Start());
- ScopedThreadStopper thread_stopper(&file_thread);
- ASSERT_TRUE(thread_stopper.is_valid());
- scoped_refptr<base::SingleThreadTaskRunner> task_runner =
- file_thread.task_runner();
- std::unique_ptr<FileStreamReader> reader =
- FileStreamReader::CreateForLocalFile(task_runner.get(), source_path, 0,
- base::Time());
- std::unique_ptr<FileStreamWriter> writer =
- FileStreamWriter::CreateForLocalFile(task_runner.get(), dest_path, 0,
- FileStreamWriter::CREATE_NEW_FILE);
- std::vector<int64_t> progress;
- CopyOrMoveOperationDelegate::StreamCopyHelper helper(
- std::move(reader), std::move(writer), FlushPolicy::NO_FLUSH_ON_COMPLETION,
- 10, // buffer size
- base::BindRepeating(&RecordFileProgressCallback,
- base::Unretained(&progress)),
- base::TimeDelta()); // For testing, we need all the progress.
- base::File::Error error = base::File::FILE_ERROR_FAILED;
- base::RunLoop run_loop;
- helper.Run(base::BindOnce(&AssignAndQuit, &run_loop, &error));
- run_loop.Run();
- EXPECT_EQ(base::File::FILE_OK, error);
- ASSERT_EQ(5U, progress.size());
- EXPECT_EQ(0, progress[0]);
- EXPECT_EQ(10, progress[1]);
- EXPECT_EQ(20, progress[2]);
- EXPECT_EQ(30, progress[3]);
- EXPECT_EQ(36, progress[4]);
- std::string content;
- ASSERT_TRUE(base::ReadFileToString(dest_path, &content));
- EXPECT_EQ(kTestData, content);
- }
- TEST(LocalFileSystemCopyOrMoveOperationTest, StreamCopyHelperWithFlush) {
- // Testing the same configuration as StreamCopyHelper, but with |need_flush|
- // parameter set to true. Since it is hard to test that the flush is indeed
- // taking place, this test just only verifies that the file is correctly
- // written with or without the flag.
- base::ScopedTempDir temp_dir;
- ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
- base::FilePath source_path = temp_dir.GetPath().AppendASCII("source");
- base::FilePath dest_path = temp_dir.GetPath().AppendASCII("dest");
- const char kTestData[] = "abcdefghijklmnopqrstuvwxyz0123456789";
- base::WriteFile(source_path, kTestData);
- base::test::SingleThreadTaskEnvironment task_environment(
- base::test::SingleThreadTaskEnvironment::MainThreadType::IO);
- base::Thread file_thread("file_thread");
- ASSERT_TRUE(file_thread.Start());
- ScopedThreadStopper thread_stopper(&file_thread);
- ASSERT_TRUE(thread_stopper.is_valid());
- scoped_refptr<base::SingleThreadTaskRunner> task_runner =
- file_thread.task_runner();
- std::unique_ptr<FileStreamReader> reader =
- FileStreamReader::CreateForLocalFile(task_runner.get(), source_path, 0,
- base::Time());
- std::unique_ptr<FileStreamWriter> writer =
- FileStreamWriter::CreateForLocalFile(task_runner.get(), dest_path, 0,
- FileStreamWriter::CREATE_NEW_FILE);
- std::vector<int64_t> progress;
- CopyOrMoveOperationDelegate::StreamCopyHelper helper(
- std::move(reader), std::move(writer), FlushPolicy::NO_FLUSH_ON_COMPLETION,
- 10, // buffer size
- base::BindRepeating(&RecordFileProgressCallback,
- base::Unretained(&progress)),
- base::TimeDelta()); // For testing, we need all the progress.
- base::File::Error error = base::File::FILE_ERROR_FAILED;
- base::RunLoop run_loop;
- helper.Run(base::BindOnce(&AssignAndQuit, &run_loop, &error));
- run_loop.Run();
- EXPECT_EQ(base::File::FILE_OK, error);
- ASSERT_EQ(5U, progress.size());
- EXPECT_EQ(0, progress[0]);
- EXPECT_EQ(10, progress[1]);
- EXPECT_EQ(20, progress[2]);
- EXPECT_EQ(30, progress[3]);
- EXPECT_EQ(36, progress[4]);
- std::string content;
- ASSERT_TRUE(base::ReadFileToString(dest_path, &content));
- EXPECT_EQ(kTestData, content);
- }
- TEST(LocalFileSystemCopyOrMoveOperationTest, StreamCopyHelper_Cancel) {
- base::ScopedTempDir temp_dir;
- ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
- base::FilePath source_path = temp_dir.GetPath().AppendASCII("source");
- base::FilePath dest_path = temp_dir.GetPath().AppendASCII("dest");
- const char kTestData[] = "abcdefghijklmnopqrstuvwxyz0123456789";
- base::WriteFile(source_path, kTestData);
- base::test::SingleThreadTaskEnvironment task_environment(
- base::test::SingleThreadTaskEnvironment::MainThreadType::IO);
- base::Thread file_thread("file_thread");
- ASSERT_TRUE(file_thread.Start());
- ScopedThreadStopper thread_stopper(&file_thread);
- ASSERT_TRUE(thread_stopper.is_valid());
- scoped_refptr<base::SingleThreadTaskRunner> task_runner =
- file_thread.task_runner();
- std::unique_ptr<FileStreamReader> reader =
- FileStreamReader::CreateForLocalFile(task_runner.get(), source_path, 0,
- base::Time());
- std::unique_ptr<FileStreamWriter> writer =
- FileStreamWriter::CreateForLocalFile(task_runner.get(), dest_path, 0,
- FileStreamWriter::CREATE_NEW_FILE);
- std::vector<int64_t> progress;
- CopyOrMoveOperationDelegate::StreamCopyHelper helper(
- std::move(reader), std::move(writer), FlushPolicy::NO_FLUSH_ON_COMPLETION,
- 10, // buffer size
- base::BindRepeating(&RecordFileProgressCallback,
- base::Unretained(&progress)),
- base::TimeDelta()); // For testing, we need all the progress.
- // Call Cancel() later.
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE,
- base::BindOnce(&CopyOrMoveOperationDelegate::StreamCopyHelper::Cancel,
- base::Unretained(&helper)));
- base::File::Error error = base::File::FILE_ERROR_FAILED;
- base::RunLoop run_loop;
- helper.Run(base::BindOnce(&AssignAndQuit, &run_loop, &error));
- run_loop.Run();
- EXPECT_EQ(base::File::FILE_ERROR_ABORT, error);
- }
- class CopyOrMoveOperationDelegateTestHelper {
- public:
- CopyOrMoveOperationDelegateTestHelper(
- const std::string& origin,
- FileSystemType src_type,
- FileSystemType dest_type,
- FileSystemOperation::CopyOrMoveOptionSet options)
- : origin_(url::Origin::Create(GURL(origin))),
- src_type_(src_type),
- dest_type_(dest_type),
- options_(options),
- task_environment_(base::test::TaskEnvironment::MainThreadType::IO) {}
- CopyOrMoveOperationDelegateTestHelper(
- const CopyOrMoveOperationDelegateTestHelper&) = delete;
- CopyOrMoveOperationDelegateTestHelper& operator=(
- const CopyOrMoveOperationDelegateTestHelper&) = delete;
- ~CopyOrMoveOperationDelegateTestHelper() {
- file_system_context_ = nullptr;
- task_environment_.RunUntilIdle();
- }
- void SetUp() {
- ASSERT_TRUE(base_.CreateUniqueTempDir());
- base::FilePath base_dir = base_.GetPath();
- quota_manager_ = base::MakeRefCounted<storage::MockQuotaManager>(
- /*is_incognito=*/false, base_dir, base::ThreadTaskRunnerHandle::Get(),
- base::MakeRefCounted<storage::MockSpecialStoragePolicy>());
- quota_manager_proxy_ = base::MakeRefCounted<storage::MockQuotaManagerProxy>(
- quota_manager_.get(), base::ThreadTaskRunnerHandle::Get());
- // Prepare file system.
- file_system_context_ = storage::CreateFileSystemContextForTesting(
- quota_manager_proxy_.get(), base_dir);
- // Prepare the origin's root directory.
- FileSystemBackend* backend =
- file_system_context_->GetFileSystemBackend(src_type_);
- backend->ResolveURL(
- FileSystemURL::CreateForTest(blink::StorageKey(url::Origin(origin_)),
- src_type_, base::FilePath()),
- OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, base::BindOnce(&ExpectOk));
- backend = file_system_context_->GetFileSystemBackend(dest_type_);
- backend->ResolveURL(
- FileSystemURL::CreateForTest(blink::StorageKey(url::Origin(origin_)),
- dest_type_, base::FilePath()),
- OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, base::BindOnce(&ExpectOk));
- task_environment_.RunUntilIdle();
- }
- FileSystemURL GenerateSourceUrlFromPath(const std::string& path) {
- return file_system_context_->CreateCrackedFileSystemURL(
- blink::StorageKey(origin_), src_type_,
- base::FilePath::FromUTF8Unsafe(path));
- }
- FileSystemURL GenerateDestinationUrlFromPath(const std::string& path) {
- return file_system_context_->CreateCrackedFileSystemURL(
- blink::StorageKey(origin_), dest_type_,
- base::FilePath::FromUTF8Unsafe(path));
- }
- base::File::Error CreateFile(const FileSystemURL& url, size_t size) {
- base::File::Error result =
- AsyncFileTestHelper::CreateFile(file_system_context_.get(), url);
- if (result != base::File::FILE_OK)
- return result;
- return AsyncFileTestHelper::TruncateFile(file_system_context_.get(), url,
- size);
- }
- base::File::Error CreateDirectory(const FileSystemURL& url) {
- return AsyncFileTestHelper::CreateDirectory(file_system_context_.get(),
- url);
- }
- bool FileExists(const FileSystemURL& url, int64_t expected_size) {
- return AsyncFileTestHelper::FileExists(file_system_context_.get(), url,
- expected_size);
- }
- bool DirectoryExists(const FileSystemURL& url) {
- return AsyncFileTestHelper::DirectoryExists(file_system_context_.get(),
- url);
- }
- // Force Copy or Move error when a given URL is encountered.
- void SetErrorUrl(const FileSystemURL& url) { error_url_ = url; }
- base::File::Error Copy(const FileSystemURL& src, const FileSystemURL& dest) {
- base::RunLoop run_loop;
- base::File::Error result = base::File::FILE_ERROR_FAILED;
- CopyOrMoveOperationDelegate copy_or_move_operation_delegate(
- file_system_context_.get(), src, dest,
- CopyOrMoveOperationDelegate::OPERATION_COPY, options_,
- FileSystemOperation::ERROR_BEHAVIOR_ABORT,
- std::make_unique<storage::CopyOrMoveHookDelegate>(),
- base::BindOnce(&AssignAndQuit, &run_loop, base::Unretained(&result)));
- if (error_url_.is_valid()) {
- copy_or_move_operation_delegate.SetErrorUrlForTest(error_url_);
- }
- copy_or_move_operation_delegate.RunRecursively();
- run_loop.Run();
- return result;
- }
- base::File::Error Move(const FileSystemURL& src, const FileSystemURL& dest) {
- base::RunLoop run_loop;
- base::File::Error result = base::File::FILE_ERROR_FAILED;
- CopyOrMoveOperationDelegate copy_or_move_operation_delegate(
- file_system_context_.get(), src, dest,
- CopyOrMoveOperationDelegate::OPERATION_MOVE, options_,
- FileSystemOperation::ERROR_BEHAVIOR_ABORT,
- std::make_unique<storage::CopyOrMoveHookDelegate>(),
- base::BindOnce(&AssignAndQuit, &run_loop, base::Unretained(&result)));
- if (error_url_.is_valid()) {
- copy_or_move_operation_delegate.SetErrorUrlForTest(error_url_);
- }
- copy_or_move_operation_delegate.RunRecursively();
- run_loop.Run();
- return result;
- }
- private:
- base::ScopedTempDir base_;
- const url::Origin origin_;
- const FileSystemType src_type_;
- const FileSystemType dest_type_;
- FileSystemOperation::CopyOrMoveOptionSet options_;
- FileSystemURL error_url_;
- base::test::TaskEnvironment task_environment_;
- scoped_refptr<storage::MockQuotaManager> quota_manager_;
- scoped_refptr<storage::MockQuotaManagerProxy> quota_manager_proxy_;
- scoped_refptr<FileSystemContext> file_system_context_;
- };
- TEST(CopyOrMoveOperationDelegateTest, StopRecursionOnCopyError) {
- FileSystemOperation::CopyOrMoveOptionSet options;
- CopyOrMoveOperationDelegateTestHelper helper(
- "http://foo", kFileSystemTypePersistent, kFileSystemTypePersistent,
- options);
- helper.SetUp();
- FileSystemURL src = helper.GenerateSourceUrlFromPath("a");
- FileSystemURL src_file_1 = helper.GenerateSourceUrlFromPath("a/file 1");
- FileSystemURL src_file_2 = helper.GenerateSourceUrlFromPath("a/file 2");
- FileSystemURL dest = helper.GenerateDestinationUrlFromPath("b");
- FileSystemURL dest_file_1 = helper.GenerateDestinationUrlFromPath("b/file 1");
- FileSystemURL dest_file_2 = helper.GenerateDestinationUrlFromPath("b/file 2");
- // Set up source files.
- ASSERT_EQ(base::File::FILE_OK, helper.CreateDirectory(src));
- ASSERT_EQ(base::File::FILE_OK,
- helper.CreateFile(src_file_1, kDefaultFileSize));
- ASSERT_EQ(base::File::FILE_OK,
- helper.CreateFile(src_file_2, kDefaultFileSize));
- // [file 1, file 2] are processed as a LIFO. An error is returned after
- // copying file 2.
- helper.SetErrorUrl(src_file_2);
- ASSERT_EQ(base::File::FILE_ERROR_FAILED, helper.Copy(src, dest));
- EXPECT_TRUE(helper.DirectoryExists(src));
- EXPECT_TRUE(helper.DirectoryExists(dest));
- // Check: file 2 is copied, even though the copy results in an error.
- EXPECT_TRUE(helper.FileExists(src_file_2, kDefaultFileSize));
- EXPECT_TRUE(
- helper.FileExists(dest_file_2, AsyncFileTestHelper::kDontCheckSize));
- // Check: the recursion has been interrupted after the error, so file 1
- // hasn't been copied.
- EXPECT_TRUE(helper.FileExists(src_file_1, kDefaultFileSize));
- EXPECT_FALSE(
- helper.FileExists(dest_file_1, AsyncFileTestHelper::kDontCheckSize));
- }
- TEST(CopyOrMoveOperationDelegateTest, RemoveDestFileOnCopyError) {
- FileSystemOperation::CopyOrMoveOptionSet options(
- storage::FileSystemOperation::CopyOrMoveOption::
- kRemovePartiallyCopiedFilesOnError);
- CopyOrMoveOperationDelegateTestHelper helper(
- "http://foo", kFileSystemTypePersistent, kFileSystemTypePersistent,
- options);
- helper.SetUp();
- FileSystemURL src = helper.GenerateSourceUrlFromPath("a");
- FileSystemURL src_file_1 = helper.GenerateSourceUrlFromPath("a/file 1");
- FileSystemURL src_file_2 = helper.GenerateSourceUrlFromPath("a/file 2");
- FileSystemURL dest = helper.GenerateDestinationUrlFromPath("b");
- FileSystemURL dest_file_1 = helper.GenerateDestinationUrlFromPath("b/file 1");
- FileSystemURL dest_file_2 = helper.GenerateDestinationUrlFromPath("b/file 2");
- // Set up source files.
- ASSERT_EQ(base::File::FILE_OK, helper.CreateDirectory(src));
- ASSERT_EQ(base::File::FILE_OK,
- helper.CreateFile(src_file_1, kDefaultFileSize));
- ASSERT_EQ(base::File::FILE_OK,
- helper.CreateFile(src_file_2, kDefaultFileSize));
- // [file 1, file 2] are processed as a LIFO. An error is returned after
- // copying file 1.
- helper.SetErrorUrl(src_file_1);
- ASSERT_EQ(base::File::FILE_ERROR_FAILED, helper.Copy(src, dest));
- EXPECT_TRUE(helper.DirectoryExists(src));
- EXPECT_TRUE(helper.DirectoryExists(dest));
- // Check: file 2 is properly copied.
- EXPECT_TRUE(helper.FileExists(src_file_2, kDefaultFileSize));
- EXPECT_TRUE(helper.FileExists(dest_file_2, kDefaultFileSize));
- // Check: file 1 has been removed on error after being copied.
- EXPECT_TRUE(helper.FileExists(src_file_1, kDefaultFileSize));
- EXPECT_FALSE(
- helper.FileExists(dest_file_1, AsyncFileTestHelper::kDontCheckSize));
- }
- TEST(CopyOrMoveOperationDelegateTest,
- RemoveDestFileOnCrossFilesystemMoveError) {
- FileSystemOperation::CopyOrMoveOptionSet options(
- storage::FileSystemOperation::CopyOrMoveOption::
- kRemovePartiallyCopiedFilesOnError);
- // Removing destination files on Move errors applies only to cross-filesystem
- // moves.
- CopyOrMoveOperationDelegateTestHelper helper(
- "http://foo", kFileSystemTypeTemporary, kFileSystemTypePersistent,
- options);
- helper.SetUp();
- FileSystemURL src = helper.GenerateSourceUrlFromPath("a");
- FileSystemURL src_file_1 = helper.GenerateSourceUrlFromPath("a/file 1");
- FileSystemURL src_file_2 = helper.GenerateSourceUrlFromPath("a/file 2");
- FileSystemURL dest = helper.GenerateDestinationUrlFromPath("b");
- FileSystemURL dest_file_1 = helper.GenerateDestinationUrlFromPath("b/file 1");
- FileSystemURL dest_file_2 = helper.GenerateDestinationUrlFromPath("b/file 2");
- // Set up source files.
- ASSERT_EQ(base::File::FILE_OK, helper.CreateDirectory(src));
- ASSERT_EQ(base::File::FILE_OK,
- helper.CreateFile(src_file_1, kDefaultFileSize));
- ASSERT_EQ(base::File::FILE_OK,
- helper.CreateFile(src_file_2, kDefaultFileSize));
- // [file 1, file 2] are processed as a LIFO. An error is returned after
- // copying file 1.
- helper.SetErrorUrl(src_file_1);
- ASSERT_EQ(base::File::FILE_ERROR_FAILED, helper.Move(src, dest));
- EXPECT_TRUE(helper.DirectoryExists(src));
- EXPECT_TRUE(helper.DirectoryExists(dest));
- // Check: file 2 is moved.
- EXPECT_FALSE(
- helper.FileExists(src_file_2, AsyncFileTestHelper::kDontCheckSize));
- EXPECT_TRUE(helper.FileExists(dest_file_2, kDefaultFileSize));
- // Check: destination file 1 has been removed on error, and its source still
- // exists.
- EXPECT_TRUE(helper.FileExists(src_file_1, kDefaultFileSize));
- EXPECT_FALSE(
- helper.FileExists(dest_file_1, AsyncFileTestHelper::kDontCheckSize));
- }
- } // namespace storage
|