| // Copyright 2016 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 "chromeos/printing/ppd_cache.h" |
| |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/files/file_util.h" |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/synchronization/lock.h" |
| #include "base/task/task_runner_util.h" |
| #include "base/task/thread_pool.h" |
| #include "base/threading/scoped_blocking_call.h" |
| #include "base/threading/sequenced_task_runner_handle.h" |
| #include "base/time/time.h" |
| #include "base/values.h" |
| #include "chromeos/printing/printing_constants.h" |
| #include "crypto/sha2.h" |
| #include "net/base/io_buffer.h" |
| #include "net/filter/gzip_header.h" |
| |
| namespace chromeos { |
| namespace { |
| |
| // Return the (full) path to the file we expect to find the given key at. |
| base::FilePath FilePathForKey(const base::FilePath& base_dir, |
| const std::string& key) { |
| std::string hashed_key = crypto::SHA256HashString(key); |
| return base_dir.Append(base::HexEncode(hashed_key.data(), hashed_key.size())); |
| } |
| |
| // If the cache doesn't already exist, create it. |
| void MaybeCreateCache(const base::FilePath& base_dir) { |
| if (!base::PathExists(base_dir)) { |
| base::CreateDirectory(base_dir); |
| } |
| } |
| |
| // Find implementation, blocks on file access. Must be run on a thread that |
| // allows I/O. |
| PpdCache::FindResult FindImpl(const base::FilePath& cache_dir, |
| const std::string& key) { |
| base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, |
| base::BlockingType::MAY_BLOCK); |
| |
| PpdCache::FindResult result; |
| result.success = false; |
| if (!base::PathExists(cache_dir)) { |
| // If the cache dir was missing, we'll miss anyway. |
| return result; |
| } |
| |
| base::File file(FilePathForKey(cache_dir, key), |
| base::File::FLAG_OPEN | base::File::FLAG_READ); |
| |
| base::File::Info info; |
| if (!file.IsValid() || !file.GetInfo(&info)) |
| return result; |
| |
| if (info.size < static_cast<int64_t>(crypto::kSHA256Length) || |
| info.size > static_cast<int64_t>(kMaxPpdSizeBytes) + |
| static_cast<int64_t>(crypto::kSHA256Length)) { |
| return result; |
| } |
| |
| std::vector<char> buf(info.size); |
| if (file.ReadAtCurrentPos(buf.data(), info.size) != info.size) |
| return result; |
| |
| base::StringPiece contents(buf.data(), info.size - crypto::kSHA256Length); |
| base::StringPiece checksum(buf.data() + info.size - crypto::kSHA256Length, |
| crypto::kSHA256Length); |
| if (crypto::SHA256HashString(contents) != checksum) { |
| LOG(ERROR) << "Bad checksum for cache key " << key; |
| return result; |
| } |
| |
| result.success = true; |
| result.age = base::Time::Now() - info.last_modified; |
| result.contents = std::string(contents); |
| return result; |
| } |
| |
| // Store implementation, blocks on file access. Must be run on a thread that |
| // allows I/O. If |age| is non-zero, explicitly set the age of the resulting |
| // file to be |age| before Now. |
| void StoreImpl(const base::FilePath& cache_dir, |
| const std::string& key, |
| const std::string& contents, |
| base::TimeDelta age) { |
| base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, |
| base::BlockingType::MAY_BLOCK); |
| MaybeCreateCache(cache_dir); |
| if (contents.size() > kMaxPpdSizeBytes) { |
| LOG(ERROR) << "Ignoring attempt to cache large object"; |
| } else { |
| auto path = FilePathForKey(cache_dir, key); |
| base::File file(path, |
| base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); |
| std::string checksum = crypto::SHA256HashString(contents); |
| if (!file.IsValid() || |
| file.WriteAtCurrentPos(contents.data(), contents.size()) != |
| static_cast<int>(contents.size()) || |
| file.WriteAtCurrentPos(checksum.data(), checksum.size()) != |
| static_cast<int>(checksum.size())) { |
| LOG(ERROR) << "Failed to create ppd cache file"; |
| file.Close(); |
| if (!base::DeleteFile(path)) { |
| LOG(ERROR) << "Failed to cleanup failed creation."; |
| } |
| } else { |
| // Successfully wrote the file, adjust the age if requested. |
| if (!age.is_zero()) { |
| base::Time mod_time = base::Time::Now() - age; |
| file.SetTimes(mod_time, mod_time); |
| } |
| } |
| } |
| } |
| |
| // Implementation of the PpdCache that uses two separate task runners for Store |
| // and Fetch since the two operations have different priorities. Note that the |
| // two operations are not sequenced so there should be no expectation that a |
| // call to Find will return a file that was previously Stored until the Store |
| // callback is run. |
| class PpdCacheImpl : public PpdCache { |
| public: |
| explicit PpdCacheImpl( |
| const base::FilePath& cache_base_dir, |
| scoped_refptr<base::SequencedTaskRunner> fetch_task_runner, |
| scoped_refptr<base::SequencedTaskRunner> store_task_runner) |
| : cache_base_dir_(cache_base_dir), |
| fetch_task_runner_(std::move(fetch_task_runner)), |
| store_task_runner_(std::move(store_task_runner)) {} |
| |
| PpdCacheImpl(const PpdCacheImpl&) = delete; |
| PpdCacheImpl& operator=(const PpdCacheImpl&) = delete; |
| |
| // Public API functions. |
| void Find(const std::string& key, FindCallback cb) override { |
| base::PostTaskAndReplyWithResult( |
| fetch_task_runner_.get(), FROM_HERE, |
| base::BindOnce(&FindImpl, cache_base_dir_, key), std::move(cb)); |
| } |
| |
| // Store the given contents at the given key. If cb is non-null, it will |
| // be invoked on completion. |
| void Store(const std::string& key, const std::string& contents) override { |
| store_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&StoreImpl, cache_base_dir_, key, contents, |
| base::TimeDelta())); |
| } |
| |
| void StoreForTesting(const std::string& key, |
| const std::string& contents, |
| base::TimeDelta age) override { |
| store_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&StoreImpl, cache_base_dir_, key, contents, age)); |
| } |
| |
| private: |
| ~PpdCacheImpl() override = default; |
| |
| base::FilePath cache_base_dir_; |
| scoped_refptr<base::SequencedTaskRunner> fetch_task_runner_; |
| scoped_refptr<base::SequencedTaskRunner> store_task_runner_; |
| }; |
| |
| } // namespace |
| |
| // static |
| scoped_refptr<PpdCache> PpdCache::Create(const base::FilePath& cache_base_dir) { |
| return scoped_refptr<PpdCache>( |
| new PpdCacheImpl(cache_base_dir, |
| base::ThreadPool::CreateSequencedTaskRunner( |
| {base::TaskPriority::USER_VISIBLE, base::MayBlock(), |
| base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}), |
| base::ThreadPool::CreateSequencedTaskRunner( |
| {base::TaskPriority::BEST_EFFORT, base::MayBlock(), |
| base::TaskShutdownBehavior::BLOCK_SHUTDOWN}))); |
| } |
| |
| scoped_refptr<PpdCache> PpdCache::CreateForTesting( |
| const base::FilePath& cache_base_dir, |
| scoped_refptr<base::SequencedTaskRunner> io_task_runner) { |
| return scoped_refptr<PpdCache>( |
| new PpdCacheImpl(cache_base_dir, io_task_runner, io_task_runner)); |
| } |
| |
| } // namespace chromeos |