blob: 22c8e80fbedc44f9968067b50db2bad5f0eb3ab6 [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_provider.h"
#include <algorithm>
#include <memory>
#include <set>
#include <unordered_map>
#include <utility>
#include <vector>
#include "base/base64.h"
#include "base/bind_helpers.h"
#include "base/containers/circular_deque.h"
#include "base/files/file.h"
#include "base/files/file_util.h"
#include "base/json/json_parser.h"
#include "base/optional.h"
#include "base/sequenced_task_runner.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_tokenizer.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.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/ppd_cache.h"
#include "chromeos/printing/ppd_line_reader.h"
#include "chromeos/printing/printing_constants.h"
#include "net/base/filename_util.h"
#include "net/base/load_flags.h"
#include "net/http/http_status_code.h"
#include "net/url_request/url_fetcher.h"
#include "net/url_request/url_fetcher_delegate.h"
#include "net/url_request/url_request_context_getter.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 {
// Holds a metadata_v2 reverse-index response
struct ReverseIndexJSON {
// Canonical name of printer
std::string effective_make_and_model;
// Name of printer manufacturer
std::string manufacturer;
// Name of printer model
std::string model;
// Restrictions for this manufacturer
PpdProvider::Restrictions restrictions;
};
// Holds a metadata_v2 manufacturers response
struct ManufacturersJSON {
// Name of printer manufacturer
std::string name;
// Key for lookup of this manutacturer's printers (JSON file)
std::string reference;
// Restrictions for this manufacturer
PpdProvider::Restrictions restrictions;
};
// Holds a metadata_v2 printers response
struct PrintersJSON {
// Name of printer
std::string name;
// Canonical name of printer
std::string effective_make_and_model;
// Restrictions for this printer
PpdProvider::Restrictions restrictions;
};
// Holds a metadata_v2 ppd-index response
struct PpdIndexJSON {
// Canonical name of printer
std::string effective_make_and_model;
// Ppd filename
std::string ppd_filename;
};
// A queued request to download printer information for a manufacturer.
struct PrinterResolutionQueueEntry {
// Localized manufacturer name
std::string manufacturer;
// URL we are going to pull from.
GURL url;
// User callback on completion.
PpdProvider::ResolvePrintersCallback cb;
};
// A queued request to download reverse index information for a make and model
struct ReverseIndexQueueEntry {
// Canonical Printer Name
std::string effective_make_and_model;
// URL we are going to pull from.
GURL url;
// User callback on completion.
PpdProvider::ReverseLookupCallback cb;
};
// Holds manufacturer to printers relation
struct ManufacturerMetadata {
// Key used to look up the printer list on the server. This is initially
// populated.
std::string reference;
// Map from localized printer name to canonical-make-and-model string for
// the given printer. Populated on demand.
std::unique_ptr<std::unordered_map<std::string, PrintersJSON>> printers;
};
// Carried information for an inflight PPD resolution.
struct PpdResolutionQueueEntry {
// Original reference being resolved.
Printer::PpdReference reference;
// If non-empty, the contents from the cache for this resolution.
std::string cached_contents;
// Callback to be invoked on completion.
PpdProvider::ResolvePpdCallback callback;
};
// Extract cupsFilter/cupsFilter2 filter names from a line from a ppd.
// cupsFilter2 lines look like this:
//
// *cupsFilter2: "application/vnd.cups-raster application/vnd.foo 100
// rastertofoo"
//
// cupsFilter lines look like this:
//
// *cupsFilter: "application/vnd.cups-raster 100 rastertofoo"
//
// |field_name| is the starting token we look for (*cupsFilter: or
// *cupsFilter2:).
//
// |num_value_tokens| is the number of tokens we expect to find in the
// value string. The filter is always the last of these.
//
// Return the name of the filter, if one is found.
//
// This would be simpler with re2, but re2 is not an allowed dependency in
// this part of the tree.
base::Optional<std::string> ExtractCupsFilter(const std::string& line,
const std::string& field_name,
int num_value_tokens) {
std::string delims(" \n\t\r\"");
base::StringTokenizer line_tok(line, delims);
if (!line_tok.GetNext()) {
return {};
}
if (line_tok.token_piece() != field_name) {
return {};
}
// Skip to the last of the value tokens.
for (int i = 0; i < num_value_tokens; ++i) {
if (!line_tok.GetNext()) {
return {};
}
}
if (line_tok.token_piece() != "") {
return line_tok.token_piece().as_string();
}
return {};
}
// Extract the used cups filters from a ppd.
//
// Note that CUPS (post 1.5) discards all cupsFilter lines if *any*
// cupsFilter2 lines exist.
//
std::vector<std::string> ExtractFiltersFromPpd(
const std::string& ppd_contents) {
std::string line;
base::Optional<std::string> tmp;
auto ppd_reader = PpdLineReader::Create(ppd_contents, 255);
std::vector<std::string> cups_filters;
std::vector<std::string> cups_filter2s;
while (ppd_reader->NextLine(&line)) {
tmp = ExtractCupsFilter(line, "*cupsFilter:", 3);
if (tmp.has_value()) {
cups_filters.push_back(tmp.value());
}
tmp = ExtractCupsFilter(line, "*cupsFilter2:", 4);
if (tmp.has_value()) {
cups_filter2s.push_back(tmp.value());
}
}
if (!cups_filter2s.empty()) {
return cups_filter2s;
}
return cups_filters;
}
// Returns false if there are obvious errors in the reference that will prevent
// resolution.
bool PpdReferenceIsWellFormed(const Printer::PpdReference& reference) {
int filled_fields = 0;
if (!reference.user_supplied_ppd_url.empty()) {
++filled_fields;
GURL tmp_url(reference.user_supplied_ppd_url);
if (!tmp_url.is_valid() || !tmp_url.SchemeIs("file")) {
LOG(ERROR) << "Invalid url for a user-supplied ppd: "
<< reference.user_supplied_ppd_url
<< " (must be a file:// URL)";
return false;
}
}
if (!reference.effective_make_and_model.empty()) {
++filled_fields;
}
// All effective-make-and-model strings should be lowercased, since v2.
// Since make-and-model strings could include non-Latin chars, only checking
// that it excludes all upper-case chars A-Z.
if (!std::all_of(reference.effective_make_and_model.begin(),
reference.effective_make_and_model.end(),
[](char c) -> bool { return !base::IsAsciiUpper(c); })) {
return false;
}
// Should have exactly one non-empty field.
return filled_fields == 1;
}
// Fetch the file pointed at by |url| and store it in |file_contents|.
// Returns true if the fetch was successful.
bool FetchFile(const GURL& url, std::string* file_contents) {
CHECK(url.is_valid());
CHECK(url.SchemeIs("file"));
base::ScopedBlockingCall scoped_blocking_call(base::BlockingType::MAY_BLOCK);
// Here we are un-escaping the file path represented by the url. If we don't
// transform the url into a valid file path then the file may fail to be
// opened by the system later.
base::FilePath path;
if (!net::FileURLToFilePath(url, &path)) {
LOG(ERROR) << "Not a valid file URL.";
return false;
}
return base::ReadFileToString(path, file_contents);
}
// Constructs and returns a printers' restrictions parsed from |dict|.
PpdProvider::Restrictions ComputeRestrictions(const base::Value& dict) {
DCHECK(dict.is_dict());
PpdProvider::Restrictions restrictions;
const base::Value* min_milestone =
dict.FindKeyOfType({"min_milestone"}, base::Value::Type::DOUBLE);
const base::Value* max_milestone =
dict.FindKeyOfType({"max_milestone"}, base::Value::Type::DOUBLE);
if (min_milestone) {
restrictions.min_milestone =
base::Version(base::NumberToString(min_milestone->GetDouble()));
}
if (max_milestone) {
restrictions.max_milestone =
base::Version(base::NumberToString(max_milestone->GetDouble()));
}
return restrictions;
}
// Returns true if this |printer| is restricted from the
// |current_version|.
bool IsPrinterRestricted(const PrintersJSON& printer,
const base::Version& current_version) {
const PpdProvider::Restrictions& restrictions = printer.restrictions;
if (restrictions.min_milestone != base::Version("0.0") &&
restrictions.min_milestone > current_version) {
return true;
}
if (restrictions.max_milestone != base::Version("0.0") &&
restrictions.max_milestone < current_version) {
return true;
}
return false;
}
// Modifies |printers| by removing any restricted printers excluded from the
// current |version|, as judged by IsPrinterPermitted.
void FilterRestrictedPpdReferences(const base::Version& version,
std::vector<PrintersJSON>* printers) {
base::EraseIf(*printers, [&version](const PrintersJSON& printer) {
return IsPrinterRestricted(printer, version);
});
}
class PpdProviderImpl : public PpdProvider {
public:
// What kind of thing is the fetcher currently fetching? We use this to
// determine what to do when the fetcher returns a result.
enum FetcherTarget {
FT_LOCALES, // Locales metadata.
FT_MANUFACTURERS, // List of manufacturers metadata.
FT_PRINTERS, // List of printers from a manufacturer.
FT_PPD_INDEX, // Master ppd index.
FT_PPD, // A Ppd file.
FT_REVERSE_INDEX, // List of sharded printers from a manufacturer
FT_USB_DEVICES // USB device id to canonical name map.
};
PpdProviderImpl(const std::string& browser_locale,
network::mojom::URLLoaderFactory* loader_factory,
scoped_refptr<PpdCache> ppd_cache,
const base::Version& current_version,
const PpdProvider::Options& options)
: browser_locale_(browser_locale),
loader_factory_(loader_factory),
ppd_cache_(ppd_cache),
disk_task_runner_(base::CreateSequencedTaskRunnerWithTraits(
{base::TaskPriority::USER_VISIBLE, base::MayBlock(),
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})),
version_(current_version),
options_(options),
weak_factory_(this) {}
// Resolving manufacturers requires a couple of steps, because of
// localization. First we have to figure out what locale to use, which
// involves grabbing a list of available locales from the server. Once we
// have decided on a locale, we go out and fetch the manufacturers map in that
// localization.
//
// This means when a request comes in, we either queue it and start background
// fetches if necessary, or we satisfy it immediately from memory.
void ResolveManufacturers(ResolveManufacturersCallback cb) override {
CHECK(base::SequencedTaskRunnerHandle::IsSet())
<< "ResolveManufacturers must be called from a SequencedTaskRunner"
"context";
if (cached_metadata_.get() != nullptr) {
// We already have this in memory.
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(cb), PpdProvider::SUCCESS,
GetManufacturerList()));
return;
}
manufacturers_resolution_queue_.push_back(std::move(cb));
MaybeStartFetch();
}
// If there are any queued ppd reference resolutions, attempt to make progress
// on them. Returns true if a fetch was started, false if no fetch was
// started.
bool MaybeStartNextPpdReferenceResolution() {
while (!ppd_reference_resolution_queue_.empty()) {
auto& next = ppd_reference_resolution_queue_.front();
// Have we successfully resolved next yet?
bool resolved_next = false;
if (!next.first.make_and_model.empty()) {
// Check the index for each make-and-model guess.
for (const std::string& make_and_model : next.first.make_and_model) {
// Check if we need to load its ppd_index
int ppd_index_shard = IndexShard(make_and_model);
if (!base::ContainsKey(cached_ppd_idxs_, ppd_index_shard)) {
StartFetch(GetPpdIndexURL(ppd_index_shard), FT_PPD_INDEX);
return true;
}
if (base::ContainsKey(cached_ppd_idxs_[ppd_index_shard],
make_and_model)) {
// Found a hit, satisfy this resolution.
Printer::PpdReference ret;
ret.effective_make_and_model = make_and_model;
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(next.second),
PpdProvider::SUCCESS, ret));
ppd_reference_resolution_queue_.pop_front();
resolved_next = true;
break;
}
}
}
if (!resolved_next) {
// If we get to this point, either we don't have any make and model
// guesses for the front entry, or they all missed. Try USB ids
// instead. This entry will be completed when the usb fetch
// returns.
if (next.first.usb_vendor_id && next.first.usb_product_id) {
StartFetch(GetUsbURL(next.first.usb_vendor_id), FT_USB_DEVICES);
return true;
}
// We don't have anything else left to try. NOT_FOUND it is.
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(next.second), PpdProvider::NOT_FOUND,
Printer::PpdReference()));
ppd_reference_resolution_queue_.pop_front();
}
}
// Didn't start any fetches.
return false;
}
// If there is work outstanding that requires a URL fetch to complete, start
// going on it.
void MaybeStartFetch() {
if (fetch_inflight_) {
// We'll call this again when the outstanding fetch completes.
return;
}
if (MaybeStartNextPpdReferenceResolution()) {
return;
}
if (!manufacturers_resolution_queue_.empty() ||
!reverse_index_resolution_queue_.empty()) {
if (locale_.empty()) {
// Don't have a locale yet, figure that out first.
StartFetch(GetLocalesURL(), FT_LOCALES);
} else {
// Get manufacturers based on the locale we have.
if (!manufacturers_resolution_queue_.empty()) {
StartFetch(GetManufacturersURL(locale_), FT_MANUFACTURERS);
} else if (!reverse_index_resolution_queue_.empty()) {
// Update the url with the locale before fetching
ReverseIndexQueueEntry& entry =
reverse_index_resolution_queue_.front();
entry.url = GetReverseIndexURL(entry.effective_make_and_model);
StartFetch(entry.url, FT_REVERSE_INDEX);
}
}
return;
}
if (!printers_resolution_queue_.empty()) {
StartFetch(printers_resolution_queue_.front().url, FT_PRINTERS);
return;
}
while (!ppd_resolution_queue_.empty()) {
auto& next = ppd_resolution_queue_.front();
if (!next.reference.user_supplied_ppd_url.empty()) {
DCHECK(next.reference.effective_make_and_model.empty());
GURL url(next.reference.user_supplied_ppd_url);
DCHECK(url.is_valid());
StartFetch(url, FT_PPD);
return;
}
DCHECK(!next.reference.effective_make_and_model.empty());
int ppd_index_shard = IndexShard(next.reference.effective_make_and_model);
if (!base::ContainsKey(cached_ppd_idxs_, ppd_index_shard)) {
// Have to have the ppd index before we can resolve by ppd server
// key.
StartFetch(GetPpdIndexURL(ppd_index_shard), FT_PPD_INDEX);
return;
}
// Get the URL from the ppd index and start the fetch.
auto& cached_ppd_index = cached_ppd_idxs_[ppd_index_shard];
auto it = cached_ppd_index.find(next.reference.effective_make_and_model);
if (it != cached_ppd_index.end()) {
StartFetch(GetPpdURL(it->second), FT_PPD);
return;
}
// This ppd reference isn't in the index. That's not good. Fail
// out the current resolution and go try to start the next
// thing if there is one.
LOG(ERROR) << "PPD " << next.reference.effective_make_and_model
<< " not found in server index";
FinishPpdResolution(std::move(next.callback), {},
PpdProvider::INTERNAL_ERROR);
ppd_resolution_queue_.pop_front();
}
}
void ResolvePrinters(const std::string& manufacturer,
ResolvePrintersCallback cb) override {
std::unordered_map<std::string, ManufacturerMetadata>::iterator it;
if (cached_metadata_.get() == nullptr ||
(it = cached_metadata_->find(manufacturer)) ==
cached_metadata_->end()) {
// User error.
LOG(ERROR) << "Can't resolve printers for unknown manufacturer "
<< manufacturer;
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(cb), PpdProvider::INTERNAL_ERROR,
ResolvedPrintersList()));
return;
}
if (it->second.printers.get() != nullptr) {
// Satisfy from the cache.
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(cb), PpdProvider::SUCCESS,
GetManufacturerPrinterList(it->second)));
} else {
// We haven't resolved this manufacturer yet.
PrinterResolutionQueueEntry entry;
entry.manufacturer = manufacturer;
entry.url = GetPrintersURL(it->second.reference);
entry.cb = std::move(cb);
printers_resolution_queue_.push_back(std::move(entry));
MaybeStartFetch();
}
}
void ResolvePpdReference(const PrinterSearchData& search_data,
ResolvePpdReferenceCallback cb) override {
// In v2 metadata, we work with lowercased effective_make_and_models.
PrinterSearchData lowercase_search_data(search_data);
for (auto& make_and_model : lowercase_search_data.make_and_model) {
make_and_model = base::ToLowerASCII(make_and_model);
}
ppd_reference_resolution_queue_.push_back(
{lowercase_search_data, std::move(cb)});
MaybeStartFetch();
}
void ResolvePpd(const Printer::PpdReference& reference,
ResolvePpdCallback cb) override {
// In v2 metadata, we work with lowercased effective_make_and_models.
Printer::PpdReference lowercase_reference(reference);
lowercase_reference.effective_make_and_model =
base::ToLowerASCII(lowercase_reference.effective_make_and_model);
// Do a sanity check here, so we can assume |reference| is well-formed in
// the rest of this class.
if (!PpdReferenceIsWellFormed(lowercase_reference)) {
FinishPpdResolution(std::move(cb), {}, PpdProvider::INTERNAL_ERROR);
return;
}
// First step, check the cache. If the cache lookup fails, we'll (try to)
// consult the server.
ppd_cache_->Find(PpdReferenceToCacheKey(lowercase_reference),
base::BindOnce(&PpdProviderImpl::ResolvePpdCacheLookupDone,
weak_factory_.GetWeakPtr(),
lowercase_reference, std::move(cb)));
}
void ReverseLookup(const std::string& effective_make_and_model,
ReverseLookupCallback cb) override {
if (effective_make_and_model.empty()) {
LOG(WARNING) << "Cannot resolve an empty make and model";
PostReverseLookupFailure(PpdProvider::NOT_FOUND, std::move(cb));
return;
}
// In v2 metadata, we work with lowercased effective_make_and_models.
std::string lowercase_effective_make_and_model =
base::ToLowerASCII(effective_make_and_model);
ReverseIndexQueueEntry entry;
entry.effective_make_and_model = lowercase_effective_make_and_model;
entry.url = GetReverseIndexURL(lowercase_effective_make_and_model);
entry.cb = std::move(cb);
reverse_index_resolution_queue_.push_back(std::move(entry));
MaybeStartFetch();
}
// Common handler that gets called whenever a fetch completes. Note this
// is used both for |fetcher_| fetches (i.e. http[s]) and file-based fetches;
// |source| may be null in the latter case.
void OnURLFetchComplete(std::unique_ptr<std::string> body) {
response_body_ = std::move(body);
switch (fetcher_target_) {
case FT_LOCALES:
OnLocalesFetchComplete();
break;
case FT_MANUFACTURERS:
OnManufacturersFetchComplete();
break;
case FT_PRINTERS:
OnPrintersFetchComplete();
break;
case FT_PPD_INDEX:
OnPpdIndexFetchComplete(fetcher_->GetFinalURL());
break;
case FT_PPD:
OnPpdFetchComplete();
break;
case FT_REVERSE_INDEX:
OnReverseIndexComplete();
break;
case FT_USB_DEVICES:
OnUsbFetchComplete();
break;
default:
LOG(DFATAL) << "Unknown fetch source";
}
fetch_inflight_ = false;
MaybeStartFetch();
}
private:
// Return the URL used to look up the supported locales list.
GURL GetLocalesURL() {
return GURL(options_.ppd_server_root + "/metadata_v2/locales.json");
}
GURL GetUsbURL(int vendor_id) {
DCHECK_GT(vendor_id, 0);
DCHECK_LE(vendor_id, 0xffff);
return GURL(base::StringPrintf("%s/metadata_v2/usb-%04x.json",
options_.ppd_server_root.c_str(),
vendor_id));
}
// Return the URL used to get the |ppd_index_shard| index.
GURL GetPpdIndexURL(int ppd_index_shard) {
return GURL(base::StringPrintf("%s/metadata_v2/index-%02d.json",
options_.ppd_server_root.c_str(),
ppd_index_shard));
}
// Return the ppd index shard number from its |url|.
int GetShardFromUrl(const GURL& url) {
auto url_str = url.spec();
if (url_str.empty()) {
return -1;
}
// Strip shard number from 2 digits following 'index'
int idx_pos = url_str.find_first_of("0123456789", url_str.find("index-"));
return std::stoi(url_str.substr(idx_pos, 2));
}
// Return the URL to get a localized manufacturers map.
GURL GetManufacturersURL(const std::string& locale) {
return GURL(base::StringPrintf("%s/metadata_v2/manufacturers-%s.json",
options_.ppd_server_root.c_str(),
locale.c_str()));
}
// Return the URL used to get a list of printers from the manufacturer |ref|.
GURL GetPrintersURL(const std::string& ref) {
return GURL(base::StringPrintf(
"%s/metadata_v2/%s", options_.ppd_server_root.c_str(), ref.c_str()));
}
// Return the URL used to get a ppd with the given filename.
GURL GetPpdURL(const std::string& filename) {
return GURL(base::StringPrintf(
"%s/ppds/%s", options_.ppd_server_root.c_str(), filename.c_str()));
}
// Return the URL to get a localized, shared manufacturers map.
GURL GetReverseIndexURL(const std::string& effective_make_and_model) {
return GURL(base::StringPrintf("%s/metadata_v2/reverse_index-%s-%02d.json",
options_.ppd_server_root.c_str(),
locale_.c_str(),
IndexShard(effective_make_and_model)));
}
// Create and return a fetcher that has the usual (for this class) flags set
// and calls back to OnURLFetchComplete in this class when it finishes.
void StartFetch(const GURL& url, FetcherTarget target) {
DCHECK(!fetch_inflight_);
DCHECK_EQ(fetcher_.get(), nullptr);
fetcher_target_ = target;
fetch_inflight_ = true;
if (url.SchemeIs("http") || url.SchemeIs("https")) {
auto resource_request = std::make_unique<network::ResourceRequest>();
resource_request->url = url;
resource_request->load_flags =
net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE |
net::LOAD_DO_NOT_SAVE_COOKIES | net::LOAD_DO_NOT_SEND_COOKIES |
net::LOAD_DO_NOT_SEND_AUTH_DATA;
// TODO(luum): confirm correct traffic annotation
fetcher_ = network::SimpleURLLoader::Create(std::move(resource_request),
MISSING_TRAFFIC_ANNOTATION);
// TODO(luum): consider using unbounded size
fetcher_->DownloadToString(
loader_factory_,
base::BindOnce(&PpdProviderImpl::OnURLFetchComplete, this),
network::SimpleURLLoader::kMaxBoundedStringDownloadSize);
} else if (url.SchemeIs("file")) {
auto file_contents = std::make_unique<std::string>();
std::string* content_ptr = file_contents.get();
base::PostTaskAndReplyWithResult(
disk_task_runner_.get(), FROM_HERE,
base::BindOnce(&FetchFile, url, content_ptr),
base::BindOnce(&PpdProviderImpl::OnFileFetchComplete, this,
std::move(file_contents)));
}
}
// Handle the result of a file fetch.
void OnFileFetchComplete(std::unique_ptr<std::string> file_contents,
bool success) {
file_fetch_success_ = success;
file_fetch_contents_ = success ? *file_contents : "";
OnURLFetchComplete(nullptr);
}
// Tidy up loose ends on an outstanding PPD resolution.
//
// If |ppd_contents| is non-empty, the request is resolved successfully
// using those contents. Otherwise |error_code| and an empty ppd
// is given to |cb|.
void FinishPpdResolution(ResolvePpdCallback cb,
const std::string& ppd_contents,
PpdProvider::CallbackResultCode error_code) {
if (!ppd_contents.empty()) {
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(cb), PpdProvider::SUCCESS, ppd_contents,
ExtractFiltersFromPpd(ppd_contents)));
} else {
DCHECK_NE(error_code, PpdProvider::SUCCESS);
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(cb), error_code, std::string(),
std::vector<std::string>()));
}
}
// Callback when the cache lookup for a ppd request finishes. If we hit in
// the cache, satisfy the resolution, otherwise kick it over to the fetcher
// queue to be grabbed from a server.
void ResolvePpdCacheLookupDone(const Printer::PpdReference& reference,
ResolvePpdCallback cb,
const PpdCache::FindResult& result) {
// If all of the following are true, we use the cache result now:
//
// * It was a cache hit
// * The reference is not from a user-supplied ppd file
// * The cached data was fresh.
//
// In all other cases, we go through the full resolution flow (passing along
// the cached data, if we got any), even if we got something from the cache.
if (result.success && reference.user_supplied_ppd_url.empty() &&
result.age < options_.cache_staleness_age) {
DCHECK(!result.contents.empty());
FinishPpdResolution(std::move(cb), result.contents, PpdProvider::SUCCESS);
} else {
// Save the cache result (if any), and queue up for resolution.
ppd_resolution_queue_.push_back(
{reference, result.contents, std::move(cb)});
MaybeStartFetch();
}
}
// Handler for the completion of the locales fetch. This response should be a
// list of strings, each of which is a locale in which we can answer queries
// on the server. The server (should) guarantee that we get 'en' as an
// absolute minimum.
//
// Combine this information with the browser locale to figure out the best
// locale to use, and then start a fetch of the manufacturers map in that
// locale.
void OnLocalesFetchComplete() {
std::string contents;
if (ValidateAndGetResponseAsString(&contents) != PpdProvider::SUCCESS) {
FailQueuedMetadataResolutions(PpdProvider::SERVER_ERROR);
return;
}
auto top_list = base::ListValue::From(base::JSONReader::Read(contents));
if (top_list.get() == nullptr) {
// We got something malformed back.
FailQueuedMetadataResolutions(PpdProvider::INTERNAL_ERROR);
return;
}
// This should just be a simple list of locale strings.
std::vector<std::string> available_locales;
bool found_en = false;
for (const base::Value& entry : *top_list) {
std::string tmp;
// Locales should have at *least* a two-character country code. 100 is an
// arbitrary upper bound for length to protect against extreme bogosity.
if (!entry.GetAsString(&tmp) || tmp.size() < 2 || tmp.size() > 100) {
FailQueuedMetadataResolutions(PpdProvider::INTERNAL_ERROR);
return;
}
if (tmp == "en") {
found_en = true;
}
available_locales.push_back(tmp);
}
if (available_locales.empty() || !found_en) {
// We have no locales, or we didn't get an english locale (which is our
// ultimate fallback)
FailQueuedMetadataResolutions(PpdProvider::INTERNAL_ERROR);
return;
}
// Everything checks out, set the locale, head back to fetch dispatch
// to start the manufacturer fetch.
locale_ = GetBestLocale(available_locales);
}
// Called when the |fetcher_| is expected have the results of a
// manufacturer map (which maps localized manufacturer names to keys for
// looking up printers from that manufacturer). Use this information to
// populate manufacturer_map_, and resolve all queued ResolveManufacturers()
// calls.
void OnManufacturersFetchComplete() {
DCHECK_EQ(nullptr, cached_metadata_.get());
std::vector<ManufacturersJSON> contents;
PpdProvider::CallbackResultCode code =
ValidateAndParseManufacturersJSON(&contents);
if (code != PpdProvider::SUCCESS) {
LOG(ERROR) << "Failed manufacturer parsing";
FailQueuedMetadataResolutions(code);
return;
}
cached_metadata_ = std::make_unique<
std::unordered_map<std::string, ManufacturerMetadata>>();
for (const auto& entry : contents) {
(*cached_metadata_)[entry.name].reference = entry.reference;
}
std::vector<std::string> manufacturer_list = GetManufacturerList();
// Complete any queued manufacturer resolutions.
for (auto& cb : manufacturers_resolution_queue_) {
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(cb), PpdProvider::SUCCESS,
manufacturer_list));
}
manufacturers_resolution_queue_.clear();
}
// The outstanding fetch associated with the front of
// |printers_resolution_queue_| finished, use the response to satisfy that
// ResolvePrinters() call.
void OnPrintersFetchComplete() {
CHECK(cached_metadata_.get() != nullptr);
DCHECK(!printers_resolution_queue_.empty());
std::vector<PrintersJSON> contents;
PpdProvider::CallbackResultCode code =
ValidateAndParsePrintersJSON(&contents);
if (code != PpdProvider::SUCCESS) {
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(printers_resolution_queue_.front().cb), code,
ResolvedPrintersList()));
} else {
// This should be a list of lists of 2-element strings, where the first
// element is the localized name of the printer and the second element
// is the canonical name of the printer.
const std::string& manufacturer =
printers_resolution_queue_.front().manufacturer;
auto it = cached_metadata_->find(manufacturer);
// If we kicked off a resolution, the entry better have already been
// in the map.
CHECK(it != cached_metadata_->end());
// Create the printer map in the cache, and populate it.
auto& manufacturer_metadata = it->second;
CHECK(manufacturer_metadata.printers.get() == nullptr);
manufacturer_metadata.printers =
std::make_unique<std::unordered_map<std::string, PrintersJSON>>();
for (const auto& entry : contents) {
manufacturer_metadata.printers->insert({entry.name, entry});
}
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(printers_resolution_queue_.front().cb),
PpdProvider::SUCCESS,
GetManufacturerPrinterList(manufacturer_metadata)));
}
printers_resolution_queue_.pop_front();
}
// Called when |fetcher_| should have just received an index mapping
// ppd server keys to ppd filenames. Use this to populate
// |cached_ppd_idxs_|.
void OnPpdIndexFetchComplete(GURL url) {
std::vector<PpdIndexJSON> contents;
PpdProvider::CallbackResultCode code =
ValidateAndParsePpdIndexJSON(&contents);
if (code != PpdProvider::SUCCESS) {
FailQueuedServerPpdResolutions(code);
} else {
int ppd_index_shard = GetShardFromUrl(url);
if (ppd_index_shard < 0) {
FailQueuedServerPpdResolutions(PpdProvider::INTERNAL_ERROR);
return;
}
auto& cached_ppd_index = cached_ppd_idxs_[ppd_index_shard];
for (const auto& entry : contents) {
cached_ppd_index.insert(
{entry.effective_make_and_model, entry.ppd_filename});
}
}
}
// This is called when |fetcher_| should have just downloaded a ppd. If we
// downloaded something successfully, use it to satisfy the front of the ppd
// resolution queue, otherwise fail out that resolution.
void OnPpdFetchComplete() {
DCHECK(!ppd_resolution_queue_.empty());
std::string contents;
auto& entry = ppd_resolution_queue_.front();
if ((ValidateAndGetResponseAsString(&contents) != PpdProvider::SUCCESS)) {
FinishPpdResolution(std::move(entry.callback), entry.cached_contents,
PpdProvider::SERVER_ERROR);
} else if (contents.size() > kMaxPpdSizeBytes) {
FinishPpdResolution(std::move(entry.callback), entry.cached_contents,
PpdProvider::PPD_TOO_LARGE);
} else if (contents.empty()) {
FinishPpdResolution(std::move(entry.callback), entry.cached_contents,
PpdProvider::INTERNAL_ERROR);
} else {
// Success. Cache it and return it to the user.
ppd_cache_->Store(
PpdReferenceToCacheKey(ppd_resolution_queue_.front().reference),
contents);
FinishPpdResolution(std::move(entry.callback), contents,
PpdProvider::SUCCESS);
}
ppd_resolution_queue_.pop_front();
}
// This is called when |fetch_| should have just downloaded a reverse index
// file. If we downloaded something successfully, used the downloaded results
// to satisfy the callback in the first item of the reverse index resolution
// queue.
void OnReverseIndexComplete() {
DCHECK(!reverse_index_resolution_queue_.empty());
std::vector<ReverseIndexJSON> contents;
PpdProvider::CallbackResultCode code =
ValidateAndParseReverseIndexJSON(&contents);
auto& entry = reverse_index_resolution_queue_.front();
if (code != PpdProvider::SUCCESS) {
LOG(ERROR) << "Request Failed or failed reverse index parsing";
PostReverseLookupFailure(code, std::move(entry.cb));
} else {
auto found = std::find_if(contents.begin(), contents.end(),
[&entry](const ReverseIndexJSON& rij) -> bool {
return rij.effective_make_and_model ==
entry.effective_make_and_model;
});
if (found != contents.end()) {
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(entry.cb), PpdProvider::SUCCESS,
found->manufacturer, found->model));
} else {
LOG(ERROR) << "Failed to lookup printer in retrieved data response";
PostReverseLookupFailure(PpdProvider::NOT_FOUND, std::move(entry.cb));
}
}
reverse_index_resolution_queue_.pop_front();
}
// Called when |fetcher_| should have just downloaded a usb device map
// for the vendor at the head of the |ppd_reference_resolution_queue_|.
void OnUsbFetchComplete() {
DCHECK(!ppd_reference_resolution_queue_.empty());
std::string contents;
std::string buffer;
PpdProvider::CallbackResultCode result =
ValidateAndGetResponseAsString(&buffer);
int desired_device_id =
ppd_reference_resolution_queue_.front().first.usb_product_id;
if (result == PpdProvider::SUCCESS) {
// Parse the JSON response. This should be a list of the form
// [
// [0x3141, "some canonical name"],
// [0x5926, "some othercanonical name"]
// ]
// So we scan through the response looking for our desired device id.
auto top_list = base::ListValue::From(base::JSONReader::Read(buffer));
if (top_list.get() == nullptr) {
// We got something malformed back.
LOG(ERROR) << "Malformed top list";
result = PpdProvider::INTERNAL_ERROR;
} else {
// We'll set result to SUCCESS if we do find the device.
result = PpdProvider::NOT_FOUND;
for (const auto& entry : *top_list) {
int device_id;
const base::ListValue* sub_list;
// Each entry should be a size-2 list with an integer and a string.
if (!entry.GetAsList(&sub_list) || sub_list->GetSize() != 2 ||
!sub_list->GetInteger(0, &device_id) ||
!sub_list->GetString(1, &contents) || device_id < 0 ||
device_id > 0xffff) {
// Malformed data.
LOG(ERROR) << "Malformed line in usb device list";
result = PpdProvider::INTERNAL_ERROR;
break;
}
if (device_id == desired_device_id) {
// Found it.
result = PpdProvider::SUCCESS;
break;
}
}
}
}
Printer::PpdReference ret;
if (result == PpdProvider::SUCCESS) {
ret.effective_make_and_model = contents;
}
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(
std::move(ppd_reference_resolution_queue_.front().second), result,
ret));
ppd_reference_resolution_queue_.pop_front();
}
// Something went wrong during metadata fetches. Fail all queued metadata
// resolutions with the given error code.
void FailQueuedMetadataResolutions(PpdProvider::CallbackResultCode code) {
for (auto& cb : manufacturers_resolution_queue_) {
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(cb), code, std::vector<std::string>()));
}
manufacturers_resolution_queue_.clear();
}
// Give up on all server-based ppd and ppd reference resolutions inflight,
// because we failed to grab the necessary index data from the server.
//
// User-based ppd resolutions, which depend on local files, are left in the
// queue.
//
// Other entries use cached data, if they found some, or failed outright.
void FailQueuedServerPpdResolutions(PpdProvider::CallbackResultCode code) {
base::circular_deque<PpdResolutionQueueEntry> filtered_queue;
for (auto& entry : ppd_resolution_queue_) {
if (!entry.reference.user_supplied_ppd_url.empty()) {
filtered_queue.emplace_back(std::move(entry));
} else {
FinishPpdResolution(std::move(entry.callback), entry.cached_contents,
code);
}
}
ppd_resolution_queue_ = std::move(filtered_queue);
// Everything in the PpdReference queue also depends on server information,
// so should also be failed.
auto task_runner = base::SequencedTaskRunnerHandle::Get();
for (auto& cb : ppd_reference_resolution_queue_) {
task_runner->PostTask(
FROM_HERE,
base::BindOnce(std::move(cb.second), code, Printer::PpdReference()));
}
ppd_reference_resolution_queue_.clear();
}
// Given a list of possible locale strings (e.g. 'en-GB'), determine which of
// them we should use to best serve results for the browser locale (which was
// given to us at construction time).
std::string GetBestLocale(const std::vector<std::string>& available_locales) {
// First look for an exact match. If we find one, just use that.
for (const std::string& available : available_locales) {
if (available == browser_locale_) {
return available;
}
}
// Next, look for an available locale that is a parent of browser_locale_.
// Return the most specific one. For example, if we want 'en-GB-foo' and we
// don't have an exact match, but we do have 'en-GB' and 'en', we will
// return 'en-GB' -- the most specific match which is a parent of the
// requested locale.
size_t best_len = 0;
size_t best_idx = -1;
for (size_t i = 0; i < available_locales.size(); ++i) {
const std::string& available = available_locales[i];
if (base::StringPiece(browser_locale_).starts_with(available + "-") &&
available.size() > best_len) {
best_len = available.size();
best_idx = i;
}
}
if (best_idx != static_cast<size_t>(-1)) {
return available_locales[best_idx];
}
// Last chance for a match, look for the locale that matches the *most*
// pieces of locale_, with ties broken by being least specific. So for
// example, if we have 'es-GB', 'es-GB-foo' but no 'es' available, and we're
// requesting something for 'es', we'll get back 'es-GB' -- the least
// specific thing that matches some of the locale.
std::vector<base::StringPiece> browser_locale_pieces =
base::SplitStringPiece(browser_locale_, "-", base::KEEP_WHITESPACE,
base::SPLIT_WANT_ALL);
size_t best_match_size = 0;
size_t best_match_specificity;
best_idx = -1;
for (size_t i = 0; i < available_locales.size(); ++i) {
const std::string& available = available_locales[i];
std::vector<base::StringPiece> available_pieces = base::SplitStringPiece(
available, "-", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
size_t match_size = 0;
for (; match_size < available_pieces.size() &&
match_size < browser_locale_pieces.size();
++match_size) {
if (available_pieces[match_size] != browser_locale_pieces[match_size]) {
break;
}
}
if (match_size > 0 &&
(best_idx == static_cast<size_t>(-1) ||
match_size > best_match_size ||
(match_size == best_match_size &&
available_pieces.size() < best_match_specificity))) {
best_idx = i;
best_match_size = match_size;
best_match_specificity = available_pieces.size();
}
}
if (best_idx != static_cast<size_t>(-1)) {
return available_locales[best_idx];
}
// Everything else failed. Throw up our hands and default to english.
return "en";
}
// Get the results of a fetch. This is a little tricky, because a fetch
// may have been done by |fetcher_|, or it may have been a file access, in
// which case we want to look at |file_fetch_contents_|. We distinguish
// between the cases based on whether or not |fetcher_| is null.
//
// We return NOT_FOUND for 404 or file not found, SERVER_ERROR for other
// errors, SUCCESS if everything was good.
CallbackResultCode ValidateAndGetResponseAsString(std::string* contents) {
CallbackResultCode ret;
if (fetcher_.get() != nullptr) {
if (response_body_.get() == nullptr) {
ret = PpdProvider::SERVER_ERROR;
} else if (fetcher_->NetError() != 0) {
// TODO(luum): confirm netError != 0 behavior === 404 not found
ret = PpdProvider::SERVER_ERROR;
} else {
*contents = std::move(*response_body_);
ret = PpdProvider::SUCCESS;
}
fetcher_.reset();
} else {
// It's a file load.
if (file_fetch_success_) {
*contents = file_fetch_contents_;
} else {
contents->clear();
}
// A failure to load a file is always considered a NOT FOUND error (even
// if the underlying causes is lack of access or similar, this seems to be
// the best match for intent.
ret = file_fetch_success_ ? PpdProvider::SUCCESS : PpdProvider::NOT_FOUND;
file_fetch_contents_.clear();
}
return ret;
}
// Ensures that the fetched JSON is in the expected format, that is leading
// with exactly |num_strings| and followed by an optional dictionary. Returns
// PpdProvider::SUCCESS and saves JSON list in |top_list| if format is valid,
// returns PpdProvider::INTERNAL_ERROR otherwise.
PpdProvider::CallbackResultCode ParseAndValidateJSONFormat(
base::Value::ListStorage* top_list,
size_t num_strings) {
std::string buffer;
auto fetch_result = ValidateAndGetResponseAsString(&buffer);
if (fetch_result != PpdProvider::SUCCESS) {
return fetch_result;
}
auto ret_list = base::ListValue::From(base::JSONReader::Read(buffer));
if (ret_list == nullptr) {
return PpdProvider::INTERNAL_ERROR;
}
*top_list = std::move(ret_list->GetList());
for (const auto& entry : *top_list) {
if (!entry.is_list()) {
return PpdProvider::INTERNAL_ERROR;
}
// entry must start with |num_strings| strings
const base::Value::ListStorage& list = entry.GetList();
if (list.size() < num_strings) {
return PpdProvider::INTERNAL_ERROR;
}
for (size_t i = 0; i < num_strings; ++i) {
if (!list[i].is_string()) {
return PpdProvider::INTERNAL_ERROR;
}
}
// entry may optionally have a last arg that must be a dict
if (list.size() > num_strings && !list[num_strings].is_dict()) {
return PpdProvider::INTERNAL_ERROR;
}
// entry may not have more than |num_strings| strings and one dict
if (list.size() > num_strings + 1) {
return PpdProvider::INTERNAL_ERROR;
}
}
return PpdProvider::SUCCESS;
}
// Attempts to parse a ReverseIndexJSON reply to |fetcher| into the passed
// contents. Returns PpdProvider::SUCCESS on valid JSON formatting and filled
// |contents|, clears |contents| otherwise.
PpdProvider::CallbackResultCode ValidateAndParseReverseIndexJSON(
std::vector<ReverseIndexJSON>* contents) {
DCHECK(contents != nullptr);
contents->clear();
base::Value::ListStorage top_list;
auto ret = ParseAndValidateJSONFormat(&top_list, 3);
if (ret != PpdProvider::SUCCESS) {
LOG(ERROR) << "Failed to parse ReverseIndex metadata";
return ret;
}
// Fetched data should be in the form {[effective_make_and_model],
// [manufacturer], [model], [dictionary of metadata]}
for (const auto& entry : top_list) {
const base::Value::ListStorage& list = entry.GetList();
ReverseIndexJSON rij_entry;
rij_entry.effective_make_and_model = list[0].GetString();
rij_entry.manufacturer = list[1].GetString();
rij_entry.model = list[2].GetString();
// Populate restrictions, if available
if (list.size() > 3) {
rij_entry.restrictions = ComputeRestrictions(list[3]);
}
contents->push_back(rij_entry);
}
return PpdProvider::SUCCESS;
}
// Attempts to parse a ManufacturersJSON reply to |fetcher| into the passed
// contents. Returns PpdProvider::SUCCESS on valid JSON formatting and filled
// |contents|, clears |contents| otherwise.
PpdProvider::CallbackResultCode ValidateAndParseManufacturersJSON(
std::vector<ManufacturersJSON>* contents) {
DCHECK(contents != NULL);
contents->clear();
base::Value::ListStorage top_list;
auto ret = ParseAndValidateJSONFormat(&top_list, 2);
if (ret != PpdProvider::SUCCESS) {
LOG(ERROR) << "Failed to process Manufacturers metadata";
return ret;
}
// Fetched data should be in form [[name], [canonical name],
// {restrictions}]
for (const auto& entry : top_list) {
const base::Value::ListStorage& list = entry.GetList();
ManufacturersJSON mj_entry;
mj_entry.name = list[0].GetString();
mj_entry.reference = list[1].GetString();
// Populate restrictions, if available
if (list.size() > 2) {
mj_entry.restrictions = ComputeRestrictions(list[2]);
}
contents->push_back(mj_entry);
}
return PpdProvider::SUCCESS;
}
// Attempts to parse a PrintersJSON reply to |fetcher| into the passed
// contents. Returns PpdProvider::SUCCESS on valid JSON formatting and filled
// |contents|, clears |contents| otherwise.
PpdProvider::CallbackResultCode ValidateAndParsePrintersJSON(
std::vector<PrintersJSON>* contents) {
DCHECK(contents != NULL);
contents->clear();
base::Value::ListStorage top_list;
auto ret = ParseAndValidateJSONFormat(&top_list, 2);
if (ret != PpdProvider::SUCCESS) {
LOG(ERROR) << "Failed to parse Printers metadata";
return ret;
}
// Fetched data should be in form [[name], [canonical name],
// {restrictions}]
for (const auto& entry : top_list) {
const base::Value::ListStorage& list = entry.GetList();
PrintersJSON pj_entry;
pj_entry.name = list[0].GetString();
pj_entry.effective_make_and_model = list[1].GetString();
// Populate restrictions, if available
if (list.size() > 2) {
pj_entry.restrictions = ComputeRestrictions(list[2]);
}
contents->push_back(pj_entry);
}
return PpdProvider::SUCCESS;
}
// Attempts to parse a PpdIndexJSON reply to |fetcher| into the passed
// contents. Returns PpdProvider::SUCCESS on valid JSON formatting and filled
// |contents|, clears |contents| otherwise.
PpdProvider::CallbackResultCode ValidateAndParsePpdIndexJSON(
std::vector<PpdIndexJSON>* contents) {
DCHECK(contents != nullptr);
contents->clear();
base::Value::ListStorage top_list;
auto ret = ParseAndValidateJSONFormat(&top_list, 2);
if (ret != PpdProvider::SUCCESS) {
LOG(ERROR) << "Failed to parse PpdIndex metadata";
return ret;
}
// Fetched data should be in the form {[effective_make_and_model],
// [manufacturer], [model], [dictionary of metadata]}
for (const auto& entry : top_list) {
const base::Value::ListStorage& list = entry.GetList();
PpdIndexJSON pij_entry;
pij_entry.effective_make_and_model = list[0].GetString();
pij_entry.ppd_filename = list[1].GetString();
contents->push_back(pij_entry);
}
return PpdProvider::SUCCESS;
}
// Create the list of manufacturers from |cached_metadata_|. Requires that
// the manufacturer list has already been resolved.
std::vector<std::string> GetManufacturerList() const {
CHECK(cached_metadata_.get() != nullptr);
std::vector<std::string> ret;
ret.reserve(cached_metadata_->size());
for (const auto& entry : *cached_metadata_) {
ret.push_back(entry.first);
}
// TODO(justincarlson) -- this should be a localization-aware sort.
sort(ret.begin(), ret.end());
return ret;
}
// Get the list of printers from a given manufacturer from |cached_metadata_|.
// Requires that we have already resolved this from the server.
ResolvedPrintersList GetManufacturerPrinterList(
const ManufacturerMetadata& meta) const {
CHECK(meta.printers.get() != nullptr);
std::vector<PrintersJSON> printers;
printers.reserve(meta.printers->size());
for (const auto& entry : *meta.printers) {
printers.push_back(entry.second);
}
// TODO(justincarlson) -- this should be a localization-aware sort.
sort(printers.begin(), printers.end(),
[](const PrintersJSON& a, const PrintersJSON& b) -> bool {
return a.name < b.name;
});
FilterRestrictedPpdReferences(version_, &printers);
ResolvedPrintersList ret;
ret.reserve(printers.size());
for (const auto& printer : printers) {
Printer::PpdReference ppd_ref;
ppd_ref.effective_make_and_model = printer.effective_make_and_model;
ret.push_back({printer.name, ppd_ref});
}
return ret;
}
void PostReverseLookupFailure(CallbackResultCode result,
ReverseLookupCallback cb) {
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(cb), result, std::string(), std::string()));
}
// The hash function to calculate the hash of canonical identifiers to the
// name of the ppd file for that printer.
int IndexShard(std::string effective_make_and_model) {
unsigned int hash = 5381;
int kNumIndexShards = 20;
for (char c : effective_make_and_model) {
hash = hash * 33 + c;
}
return hash % kNumIndexShards;
}
// Map from (localized) manufacturer name to metadata for that manufacturer.
// This is populated lazily. If we don't yet have a manufacturer list, the
// top pointer will be null. When we create the top level map, then each
// value will only contain a reference which can be used to resolve the
// printer list from that manufacturer. On demand, we use these references to
// resolve the actual printer lists.
std::unique_ptr<std::unordered_map<std::string, ManufacturerMetadata>>
cached_metadata_;
// Cached contents of the server indexs, which maps first a shard number to
// the corresponding index map of PpdReference::effective_make_and_models to a
// urls for the corresponding ppds. Starts as an empty map and filled lazily
// as we need to fill in more indexs.
std::unordered_map<int, std::unordered_map<std::string, std::string>>
cached_ppd_idxs_;
// Queued ResolveManufacturers() calls. We will simultaneously resolve
// all queued requests, so no need for a deque here.
std::vector<ResolveManufacturersCallback> manufacturers_resolution_queue_;
// Queued ResolvePrinters() calls.
base::circular_deque<PrinterResolutionQueueEntry> printers_resolution_queue_;
// Queued ResolvePpd() requests.
base::circular_deque<PpdResolutionQueueEntry> ppd_resolution_queue_;
// Queued ResolvePpdReference() requests.
base::circular_deque<
std::pair<PrinterSearchData, ResolvePpdReferenceCallback>>
ppd_reference_resolution_queue_;
// Queued ReverseIndex() calls.
base::circular_deque<ReverseIndexQueueEntry> reverse_index_resolution_queue_;
// Locale we're using for grabbing stuff from the server. Empty if we haven't
// determined it yet.
std::string locale_;
// If the fetcher is active, what's it fetching?
FetcherTarget fetcher_target_;
// Fetcher used for all network fetches. This is explicitly reset() when
// a fetch has been processed.
std::unique_ptr<network::SimpleURLLoader> fetcher_;
bool fetch_inflight_ = false;
std::unique_ptr<std::string> response_body_;
// Locale of the browser, as returned by
// BrowserContext::GetApplicationLocale();
const std::string browser_locale_;
network::mojom::URLLoaderFactory* loader_factory_;
// For file:// fetches, a staging buffer and result flag for loading the file.
std::string file_fetch_contents_;
bool file_fetch_success_;
// Cache of ppd files.
scoped_refptr<PpdCache> ppd_cache_;
// Where to run disk operations.
scoped_refptr<base::SequencedTaskRunner> disk_task_runner_;
// Current version used to filter restricted ppds
base::Version version_;
// Construction-time options, immutable.
const PpdProvider::Options options_;
base::WeakPtrFactory<PpdProviderImpl> weak_factory_;
protected:
~PpdProviderImpl() override = default;
};
} // namespace
// static
std::string PpdProvider::PpdReferenceToCacheKey(
const Printer::PpdReference& reference) {
DCHECK(PpdReferenceIsWellFormed(reference));
// The key prefixes here are arbitrary, but ensure we can't have an (unhashed)
// collision between keys generated from different PpdReference fields.
if (!reference.effective_make_and_model.empty()) {
return std::string("em:") + reference.effective_make_and_model;
} else {
return std::string("up:") + reference.user_supplied_ppd_url;
}
}
PpdProvider::PrinterSearchData::PrinterSearchData() = default;
PpdProvider::PrinterSearchData::PrinterSearchData(
const PrinterSearchData& other) = default;
PpdProvider::PrinterSearchData::~PrinterSearchData() = default;
// static
scoped_refptr<PpdProvider> PpdProvider::Create(
const std::string& browser_locale,
network::mojom::URLLoaderFactory* loader_factory,
scoped_refptr<PpdCache> ppd_cache,
const base::Version& current_version,
const PpdProvider::Options& options) {
return scoped_refptr<PpdProvider>(new PpdProviderImpl(
browser_locale, loader_factory, ppd_cache, current_version, options));
}
} // namespace chromeos