diff options
Diffstat (limited to 'chromium/components/policy/core/common/cloud/resource_cache.cc')
-rw-r--r-- | chromium/components/policy/core/common/cloud/resource_cache.cc | 315 |
1 files changed, 315 insertions, 0 deletions
diff --git a/chromium/components/policy/core/common/cloud/resource_cache.cc b/chromium/components/policy/core/common/cloud/resource_cache.cc new file mode 100644 index 00000000000..8fa8abad785 --- /dev/null +++ b/chromium/components/policy/core/common/cloud/resource_cache.cc @@ -0,0 +1,315 @@ +// Copyright (c) 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 "components/policy/core/common/cloud/resource_cache.h" + +#include "base/base64url.h" +#include "base/bind.h" +#include "base/callback.h" +#include "base/files/file_enumerator.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/logging.h" +#include "base/numerics/safe_conversions.h" +#include "base/task/sequenced_task_runner.h" + +namespace policy { + +namespace { + +// Decodes all elements of |input| from base64url format and stores the decoded +// elements in |output|. +bool Base64UrlEncode(const std::set<std::string>& input, + std::set<std::string>* output) { + output->clear(); + for (const auto& plain : input) { + if (plain.empty()) { + NOTREACHED(); + output->clear(); + return false; + } + + std::string encoded; + base::Base64UrlEncode(plain, base::Base64UrlEncodePolicy::INCLUDE_PADDING, + &encoded); + + output->insert(encoded); + } + return true; +} + +} // namespace + +ResourceCache::ResourceCache( + const base::FilePath& cache_dir, + scoped_refptr<base::SequencedTaskRunner> task_runner, + absl::optional<int64_t> max_cache_size) + : cache_dir_(cache_dir), + task_runner_(task_runner), + max_cache_size_(max_cache_size) { + // Safe to post this without a WeakPtr because this class must be destructed + // on the same thread. + if (max_cache_size_.has_value()) { + task_runner_->PostTask(FROM_HERE, + base::BindOnce(&ResourceCache::InitCurrentCacheSize, + base::Unretained(this))); + } +} + +ResourceCache::~ResourceCache() { + // No RunsTasksInCurrentSequence() check to avoid unit tests failures. + // In unit tests the browser process instance is deleted only after test ends + // and test task scheduler is shutted down. Therefore we need to delete some + // components of BrowserPolicyConnector (ResourceCache and + // CloudExternalDataManagerBase::Backend) manually when task runner doesn't + // accept new tasks (DeleteSoon in this case). This leads to the situation + // when this destructor is called not on |task_runner|. +} + +base::FilePath ResourceCache::Store(const std::string& key, + const std::string& subkey, + const std::string& data) { + DCHECK(task_runner_->RunsTasksInCurrentSequence()); + base::FilePath subkey_path; + if (!VerifyKeyPathAndGetSubkeyPath(key, true, subkey, &subkey_path)) + return base::FilePath(); + int64_t size = base::checked_cast<int64_t>(data.size()); + if (max_cache_size_.has_value() && + current_cache_size_ - GetCacheDirectoryOrFileSize(subkey_path) + size > + max_cache_size_.value()) { + LOG(ERROR) << "Data (" << key << ", " << subkey << ") with size " << size + << " bytes doesn't fit in cache, left size: " + << max_cache_size_.value() - current_cache_size_ << " bytes"; + return base::FilePath(); + } + if (!WriteCacheFile(subkey_path, data)) + return base::FilePath(); + return subkey_path; +} + +base::FilePath ResourceCache::Load(const std::string& key, + const std::string& subkey, + std::string* data) { + DCHECK(task_runner_->RunsTasksInCurrentSequence()); + base::FilePath subkey_path; + // Only read from |subkey_path| if it is not a symlink. + if (!VerifyKeyPathAndGetSubkeyPath(key, false, subkey, &subkey_path) || + base::IsLink(subkey_path)) { + return base::FilePath(); + } + data->clear(); + if (!base::ReadFileToString(subkey_path, data)) + return base::FilePath(); + return subkey_path; +} + +void ResourceCache::LoadAllSubkeys( + const std::string& key, + std::map<std::string, std::string>* contents) { + DCHECK(task_runner_->RunsTasksInCurrentSequence()); + contents->clear(); + base::FilePath key_path; + if (!VerifyKeyPath(key, false, &key_path)) + return; + + base::FileEnumerator enumerator(key_path, false, base::FileEnumerator::FILES); + for (base::FilePath path = enumerator.Next(); !path.empty(); + path = enumerator.Next()) { + const std::string encoded_subkey = path.BaseName().MaybeAsASCII(); + std::string subkey; + std::string data; + // Only read from |subkey_path| if it is not a symlink and its name is + // a base64-encoded string. + if (!base::IsLink(path) && + base::Base64UrlDecode(encoded_subkey, + base::Base64UrlDecodePolicy::REQUIRE_PADDING, + &subkey) && + !subkey.empty() && base::ReadFileToString(path, &data)) { + (*contents)[subkey].swap(data); + } + } +} + +void ResourceCache::Delete(const std::string& key, const std::string& subkey) { + DCHECK(task_runner_->RunsTasksInCurrentSequence()); + base::FilePath subkey_path; + if (VerifyKeyPathAndGetSubkeyPath(key, false, subkey, &subkey_path)) + DeleteCacheFile(subkey_path, false); + base::FilePath key_path; + // DeleteCacheFile() does nothing if the directory given to it is not empty. + // Hence, the call below deletes the directory representing |key| if its last + // subkey was just removed and does nothing otherwise. + if (VerifyKeyPath(key, false, &key_path)) + DeleteCacheFile(key_path, false); +} + +void ResourceCache::Clear(const std::string& key) { + DCHECK(task_runner_->RunsTasksInCurrentSequence()); + base::FilePath key_path; + if (VerifyKeyPath(key, false, &key_path)) + DeleteCacheFile(key_path, true); +} + +void ResourceCache::FilterSubkeys(const std::string& key, + const SubkeyFilter& test) { + DCHECK(task_runner_->RunsTasksInCurrentSequence()); + + base::FilePath key_path; + if (!VerifyKeyPath(key, false, &key_path)) + return; + + base::FileEnumerator enumerator(key_path, false, base::FileEnumerator::FILES); + for (base::FilePath subkey_path = enumerator.Next(); + !subkey_path.empty(); subkey_path = enumerator.Next()) { + std::string subkey; + // Delete files with invalid names, and files whose subkey doesn't pass the + // filter. + if (!base::Base64UrlDecode(subkey_path.BaseName().MaybeAsASCII(), + base::Base64UrlDecodePolicy::REQUIRE_PADDING, + &subkey) || + subkey.empty() || test.Run(subkey)) { + DeleteCacheFile(subkey_path, true); + } + } + + // Delete() does nothing if the directory given to it is not empty. Hence, the + // call below deletes the directory representing |key| if all of its subkeys + // were just removed and does nothing otherwise. + DeleteCacheFile(key_path, false); +} + +void ResourceCache::PurgeOtherKeys(const std::set<std::string>& keys_to_keep) { + DCHECK(task_runner_->RunsTasksInCurrentSequence()); + std::set<std::string> encoded_keys_to_keep; + if (!Base64UrlEncode(keys_to_keep, &encoded_keys_to_keep)) + return; + + base::FileEnumerator enumerator( + cache_dir_, false, base::FileEnumerator::DIRECTORIES); + for (base::FilePath path = enumerator.Next(); !path.empty(); + path = enumerator.Next()) { + const std::string name(path.BaseName().MaybeAsASCII()); + if (encoded_keys_to_keep.find(name) == encoded_keys_to_keep.end()) + DeleteCacheFile(path, true); + } +} + +void ResourceCache::PurgeOtherSubkeys( + const std::string& key, + const std::set<std::string>& subkeys_to_keep) { + DCHECK(task_runner_->RunsTasksInCurrentSequence()); + base::FilePath key_path; + if (!VerifyKeyPath(key, false, &key_path)) + return; + + std::set<std::string> encoded_subkeys_to_keep; + if (!Base64UrlEncode(subkeys_to_keep, &encoded_subkeys_to_keep)) + return; + + base::FileEnumerator enumerator(key_path, false, base::FileEnumerator::FILES); + for (base::FilePath path = enumerator.Next(); !path.empty(); + path = enumerator.Next()) { + const std::string name(path.BaseName().MaybeAsASCII()); + if (encoded_subkeys_to_keep.find(name) == encoded_subkeys_to_keep.end()) + DeleteCacheFile(path, false); + } + // Delete() does nothing if the directory given to it is not empty. Hence, the + // call below deletes the directory representing |key| if all of its subkeys + // were just removed and does nothing otherwise. + DeleteCacheFile(key_path, false); +} + +bool ResourceCache::VerifyKeyPath(const std::string& key, + bool allow_create, + base::FilePath* path) { + if (key.empty()) { + NOTREACHED(); + return false; + } + + std::string encoded; + base::Base64UrlEncode(key, base::Base64UrlEncodePolicy::INCLUDE_PADDING, + &encoded); + + *path = cache_dir_.AppendASCII(encoded); + return allow_create ? base::CreateDirectory(*path) : + base::DirectoryExists(*path); +} + +bool ResourceCache::VerifyKeyPathAndGetSubkeyPath(const std::string& key, + bool allow_create_key, + const std::string& subkey, + base::FilePath* path) { + if (subkey.empty()) { + NOTREACHED(); + return false; + } + + base::FilePath key_path; + if (!VerifyKeyPath(key, allow_create_key, &key_path)) + return false; + + std::string encoded; + base::Base64UrlEncode(subkey, base::Base64UrlEncodePolicy::INCLUDE_PADDING, + &encoded); + + *path = key_path.AppendASCII(encoded); + return true; +} + +void ResourceCache::InitCurrentCacheSize() { + DCHECK(task_runner_->RunsTasksInCurrentSequence()); + current_cache_size_ = GetCacheDirectoryOrFileSize(cache_dir_); +} + +bool ResourceCache::WriteCacheFile(const base::FilePath& path, + const std::string& data) { + DCHECK(task_runner_->RunsTasksInCurrentSequence()); + DCHECK(cache_dir_.IsParent(path)); + bool success = DeleteCacheFile(path, false); + int size = base::checked_cast<int>(data.size()); + int bytes_written = base::WriteFile(path, data.data(), size); + if (max_cache_size_.has_value()) + current_cache_size_ += bytes_written; + return success && bytes_written == size; +} + +bool ResourceCache::DeleteCacheFile(const base::FilePath& path, + bool recursive) { + DCHECK(task_runner_->RunsTasksInCurrentSequence()); + DCHECK(cache_dir_.IsParent(path)); + int64_t size = GetCacheDirectoryOrFileSize(path); + bool success; + if (recursive) + success = base::DeletePathRecursively(path); + else + success = base::DeleteFile(path); + if (success && max_cache_size_.has_value()) + current_cache_size_ -= size; + return success; +} + +int64_t ResourceCache::GetCacheDirectoryOrFileSize( + const base::FilePath& path) const { + DCHECK(path == cache_dir_ || cache_dir_.IsParent(path)); + if (base::IsLink(path)) { + DLOG(WARNING) << "Symlink " << path.LossyDisplayName() + << " detected in cache directory"; + return 0; + } + int64_t path_size = 0; + if (base::DirectoryExists(path)) { + int types = base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES; + base::FileEnumerator enumerator(path, /* recursive */ false, types); + for (base::FilePath child_path = enumerator.Next(); !child_path.empty(); + child_path = enumerator.Next()) { + path_size += GetCacheDirectoryOrFileSize(child_path); + } + } else if (!base::GetFileSize(path, &path_size)) { + path_size = 0; + } + return path_size; +} + +} // namespace policy |