blob: f1b6a996fb6625514726d04f6e17ef8589b6f41c [file] [log] [blame]
// 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/json/json_parser.h"
#include "base/json/json_writer.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/post_task.h"
#include "base/task_runner_util.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);
if (file.IsValid()) {
int64_t len = file.GetLength();
if (len >= static_cast<int64_t>(crypto::kSHA256Length) &&
len <= static_cast<int64_t>(kMaxPpdSizeBytes) +
static_cast<int64_t>(crypto::kSHA256Length)) {
std::unique_ptr<char[]> buf(new char[len]);
if (file.ReadAtCurrentPos(buf.get(), len) == len) {
base::StringPiece contents(buf.get(), len - crypto::kSHA256Length);
base::StringPiece checksum(buf.get() + len - crypto::kSHA256Length,
crypto::kSHA256Length);
if (crypto::SHA256HashString(contents) == checksum) {
base::File::Info info;
if (file.GetInfo(&info)) {
result.success = true;
result.age = base::Time::Now() - info.last_modified;
contents.CopyToString(&result.contents);
}
} else {
LOG(ERROR) << "Bad checksum for cache key " << key;
}
}
}
}
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, false)) {
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)) {}
// 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_;
DISALLOW_COPY_AND_ASSIGN(PpdCacheImpl);
};
} // namespace
// static
scoped_refptr<PpdCache> PpdCache::Create(const base::FilePath& cache_base_dir) {
return scoped_refptr<PpdCache>(
new PpdCacheImpl(cache_base_dir,
base::CreateSequencedTaskRunnerWithTraits(
{base::TaskPriority::USER_VISIBLE, base::MayBlock(),
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}),
base::CreateSequencedTaskRunnerWithTraits(
{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