#include "chromeos/printing/printer_config_cache.h"
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "base/containers/queue.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h"
#include "base/strings/strcat.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/clock.h"
#include "base/time/time.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "url/gurl.h"
namespace chromeos {
namespace {
constexpr char kServingRoot[] =
"https://printerconfigurations.googleusercontent.com/"
"chromeos_printing/";
constexpr char kLocalhostRoot[] = "http://localhost:7002/";
std::string PrependServingRoot(const std::string& name,
bool use_localhost_as_root) {
if (use_localhost_as_root) {
return base::StrCat({kLocalhostRoot, name});
}
return base::StrCat({kServingRoot, name});
}
std::unique_ptr<network::ResourceRequest> FormRequest(
const std::string& path,
bool use_localhost_as_root) {
GURL full_url(PrependServingRoot(path, use_localhost_as_root));
if (!full_url.is_valid()) {
return nullptr;
}
auto request = std::make_unique<network::ResourceRequest>();
request->url = full_url;
request->load_flags = net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE;
request->credentials_mode = network::mojom::CredentialsMode::kOmit;
return request;
}
}
PrinterConfigCache::FetchResult PrinterConfigCache::FetchResult::Failure(
const std::string& key) {
return PrinterConfigCache::FetchResult{false, key, std::string(),
base::Time()};
}
PrinterConfigCache::FetchResult PrinterConfigCache::FetchResult::Success(
const std::string& key,
const std::string& contents,
base::Time time_of_fetch) {
return PrinterConfigCache::FetchResult{true, key, contents, time_of_fetch};
}
class PrinterConfigCacheImpl : public PrinterConfigCache {
public:
explicit PrinterConfigCacheImpl(
const base::Clock* clock,
base::RepeatingCallback<network::mojom::URLLoaderFactory*()>
loader_factory_dispenser,
bool use_localhost_as_root)
: clock_(clock),
loader_factory_dispenser_(std::move(loader_factory_dispenser)),
use_localhost_as_root_(use_localhost_as_root),
weak_factory_(this) {}
~PrinterConfigCacheImpl() override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void Fetch(const std::string& key,
base::TimeDelta expiration,
FetchCallback cb) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const auto& finding = cache_.find(key);
if (finding != cache_.end()) {
const Entry& entry = finding->second;
if (entry.time_of_fetch + expiration > clock_->Now()) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(cb), FetchResult::Success(
key, entry.contents,
entry.time_of_fetch)));
return;
}
}
auto context = std::make_unique<FetchContext>(key, std::move(cb));
fetch_queue_.push(std::move(context));
TryToStartNetworkedFetch();
}
void Drop(const std::string& key) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
cache_.erase(key);
}
private:
struct FetchContext {
const std::string key;
PrinterConfigCache::FetchCallback cb;
FetchContext(const std::string& arg_key,
PrinterConfigCache::FetchCallback arg_cb)
: key(arg_key), cb(std::move(arg_cb)) {}
~FetchContext() = default;
};
struct Entry {
std::string contents;
base::Time time_of_fetch;
Entry(const std::string& arg_contents, base::Time time)
: contents(arg_contents), time_of_fetch(time) {}
~Entry() = default;
};
void TryToStartNetworkedFetch() {
if (fetcher_ || fetch_queue_.empty()) {
return;
}
std::unique_ptr<FetchContext> context = std::move(fetch_queue_.front());
fetch_queue_.pop();
auto request = FormRequest(context->key, use_localhost_as_root_);
net::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation("printer_config_fetch", R"(
semantics {
sender: "Printer Configuration"
description:
"This component sends requests to the Chrome OS Printing "
"serving root during printer configuration. This can return "
"two pieces of information, depending on the request: "
"PostScript Printer Description (PPD) files for a specified "
"printer, and PPD file metadata to help locate the desired PPD "
"file."
trigger: "On printer setup in ChromeOS."
data: "Printer names (comprising of make and/or model)."
user_data: {
type: OTHER
}
destination: GOOGLE_OWNED_SERVICE
internal: {
contacts: {
email: "bmgordon@google.com"
}
}
last_reviewed: "2023-01-18"
}
policy {
cookies_allowed: NO
setting:
"Admins must disable access to both enterprise and "
"non-enterprise printers. Enterprise printers should be left "
"empty under 'Devices > Chrome > Printers'. Non-enterprise "
"printers can be disabled under 'Devices > Chrome > Settings > "
"Printer management' by setting to: 'Do not allow users to add "
"new printers'."
chrome_policy {
UserPrintersAllowed {
UserPrintersAllowed: false
}
PrintersBulkConfiguration: {
PrintersBulkConfiguration: ""
}
}
chrome_device_policy {
# DevicePrinters
device_printers: {
external_policy: ""
}
}
})");
fetcher_ = network::SimpleURLLoader::Create(std::move(request),
traffic_annotation);
fetcher_->DownloadToString(
loader_factory_dispenser_.Run(),
base::BindOnce(&PrinterConfigCacheImpl::FinishNetworkedFetch,
weak_factory_.GetWeakPtr(), std::move(context)),
network::SimpleURLLoader::kMaxBoundedStringDownloadSize);
}
void FinishNetworkedFetch(std::unique_ptr<FetchContext> context,
std::optional<std::string> contents) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (fetcher_->NetError() == net::Error::OK) {
CHECK(contents);
const Entry newly_inserted =
Entry(std::move(contents).value(), clock_->Now());
cache_.insert_or_assign(context->key, newly_inserted);
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(context->cb),
FetchResult::Success(
context->key, newly_inserted.contents,
newly_inserted.time_of_fetch)));
} else {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(context->cb),
FetchResult::Failure(context->key)));
}
fetcher_.reset();
TryToStartNetworkedFetch();
}
base::flat_map<std::string, Entry> cache_;
base::queue<std::unique_ptr<FetchContext>> fetch_queue_;
raw_ptr<const base::Clock> clock_;
base::RepeatingCallback<network::mojom::URLLoaderFactory*()>
loader_factory_dispenser_;
std::unique_ptr<network::SimpleURLLoader> fetcher_;
const bool use_localhost_as_root_;
SEQUENCE_CHECKER(sequence_checker_);
base::WeakPtrFactory<PrinterConfigCacheImpl> weak_factory_;
};
std::unique_ptr<PrinterConfigCache> PrinterConfigCache::Create(
const base::Clock* clock,
base::RepeatingCallback<network::mojom::URLLoaderFactory*()>
loader_factory_dispenser,
bool use_localhost_as_root) {
return std::make_unique<PrinterConfigCacheImpl>(
clock, std::move(loader_factory_dispenser), use_localhost_as_root);
}
}