| Index: chromeos/printing/ppd_provider.cc
|
| diff --git a/chromeos/printing/ppd_provider.cc b/chromeos/printing/ppd_provider.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..ecd74653c1e2f8b470bcc1ccd4c7b2478dcee972
|
| --- /dev/null
|
| +++ b/chromeos/printing/ppd_provider.cc
|
| @@ -0,0 +1,237 @@
|
| +// 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 <utility>
|
| +
|
| +#include "base/files/file_util.h"
|
| +#include "base/json/json_parser.h"
|
| +#include "base/memory/ptr_util.h"
|
| +#include "base/sequence_checker.h"
|
| +#include "base/strings/string_number_conversions.h"
|
| +#include "base/strings/string_util.h"
|
| +#include "base/strings/stringprintf.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 "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 "url/gurl.h"
|
| +
|
| +using base::FilePath;
|
| +using net::URLFetcher;
|
| +using std::string;
|
| +using std::unique_ptr;
|
| +
|
| +namespace chromeos {
|
| +namespace printing {
|
| +namespace {
|
| +
|
| +// Expected fields from the quirks server.
|
| +const char kJSONPPDKey[] = "compressedPpd";
|
| +const char kJSONLastUpdatedKey[] = "lastUpdatedTime";
|
| +
|
| +class PpdProviderImpl;
|
| +
|
| +// URLFetcherDelegate that just forwards the complete callback back to
|
| +// the PpdProvider that owns the delegate.
|
| +class ForwardingURLFetcherDelegate : public net::URLFetcherDelegate {
|
| + public:
|
| + explicit ForwardingURLFetcherDelegate(PpdProviderImpl* owner)
|
| + : owner_(owner) {}
|
| + ~ForwardingURLFetcherDelegate() override {}
|
| +
|
| + // URLFetcherDelegate API method. Defined below since we need the
|
| + // PpdProviderImpl definition first.
|
| + void OnURLFetchComplete(const URLFetcher* source) override;
|
| +
|
| + private:
|
| + // owner of this delegate.
|
| + PpdProviderImpl* const owner_;
|
| +};
|
| +
|
| +class PpdProviderImpl : public PpdProvider {
|
| + public:
|
| + PpdProviderImpl(
|
| + const string& api_key,
|
| + scoped_refptr<net::URLRequestContextGetter> url_context_getter,
|
| + unique_ptr<PpdCache> cache,
|
| + const PpdProvider::Options& options)
|
| + : api_key_(api_key),
|
| + forwarding_delegate_(this),
|
| + url_context_getter_(url_context_getter),
|
| + cache_(std::move(cache)),
|
| + options_(options) {
|
| + CHECK_GT(options_.max_ppd_contents_size_, 0U);
|
| + }
|
| + ~PpdProviderImpl() override {}
|
| +
|
| + void Resolve(const Printer::PpdReference& ppd_reference,
|
| + PpdProvider::ResolveCallback cb) override {
|
| + CHECK(sequence_checker_.CalledOnValidSequence());
|
| + CHECK(base::SequencedTaskRunnerHandle::IsSet())
|
| + << "Resolve must be called from a SequencedTaskRunner context";
|
| +
|
| + CHECK(fetcher_ == nullptr)
|
| + << "Can't have concurrent PpdProvider Resolve calls";
|
| +
|
| + base::Optional<FilePath> tmp = cache_->Find(ppd_reference);
|
| + if (tmp) {
|
| + // Cache hit. Schedule the callback now and return.
|
| + base::SequencedTaskRunnerHandle::Get()->PostTask(
|
| + FROM_HERE, base::Bind(cb, PpdProvider::SUCCESS, tmp.value()));
|
| + return;
|
| + }
|
| +
|
| + // We don't have a way to automatically resolve user-supplied PPDs yet. So
|
| + // if we have one specified, and it's not cached, we fail out rather than
|
| + // fall back to quirks-server based resolution. The reasoning here is that
|
| + // if the user has specified a PPD when a quirks-server one exists, it
|
| + // probably means the quirks server one doesn't work for some reason, so we
|
| + // shouldn't silently use it.
|
| + if (!ppd_reference.user_supplied_ppd_url.empty()) {
|
| + base::SequencedTaskRunnerHandle::Get()->PostTask(
|
| + FROM_HERE, base::Bind(cb, PpdProvider::NOT_FOUND, base::FilePath()));
|
| + return;
|
| + }
|
| +
|
| + active_reference_ = ppd_reference;
|
| + done_callback_ = cb;
|
| +
|
| + fetcher_ = URLFetcher::Create(GetQuirksServerLookupURL(ppd_reference),
|
| + URLFetcher::GET, &forwarding_delegate_);
|
| + fetcher_->SetRequestContext(url_context_getter_.get());
|
| + fetcher_->SetLoadFlags(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);
|
| + fetcher_->Start();
|
| + };
|
| +
|
| + void AbortResolve() override {
|
| + // UrlFetcher guarantees that when the object has been destroyed, no further
|
| + // callbacks will occur.
|
| + fetcher_.reset();
|
| + }
|
| +
|
| + bool CachePpd(const Printer::PpdReference& ppd_reference,
|
| + const base::FilePath& ppd_path) override {
|
| + string buf;
|
| + if (!base::ReadFileToStringWithMaxSize(ppd_path, &buf,
|
| + options_.max_ppd_contents_size_)) {
|
| + return false;
|
| + }
|
| + return static_cast<bool>(cache_->Store(ppd_reference, buf));
|
| + }
|
| +
|
| + // Called on the same thread as Resolve() when the fetcher completes its
|
| + // fetch.
|
| + void OnURLFetchComplete() {
|
| + CHECK(sequence_checker_.CalledOnValidSequence());
|
| + // Scope the allocated |fetcher_| into this function so we clean it up when
|
| + // we're done here instead of leaving it around until the next Resolve call.
|
| + auto fetcher = std::move(fetcher_);
|
| + string contents;
|
| + if ((fetcher->GetStatus().status() != net::URLRequestStatus::SUCCESS) ||
|
| + (fetcher->GetResponseCode() != net::HTTP_OK) ||
|
| + !fetcher->GetResponseAsString(&contents)) {
|
| + // Something went wrong with the fetch.
|
| + done_callback_.Run(PpdProvider::SERVER_ERROR, FilePath());
|
| + return;
|
| + }
|
| +
|
| + auto dict = base::DictionaryValue::From(base::JSONReader::Read(contents));
|
| + if (dict == nullptr) {
|
| + done_callback_.Run(PpdProvider::SERVER_ERROR, FilePath());
|
| + return;
|
| + }
|
| + string ppd_contents;
|
| + string last_updated_time_string;
|
| + uint64_t last_updated_time;
|
| + if (!(dict->GetString(kJSONPPDKey, &ppd_contents) &&
|
| + dict->GetString(kJSONLastUpdatedKey, &last_updated_time_string) &&
|
| + base::StringToUint64(last_updated_time_string, &last_updated_time))) {
|
| + // Malformed response. TODO(justincarlson) - LOG something here?
|
| + done_callback_.Run(PpdProvider::SERVER_ERROR, FilePath());
|
| + return;
|
| + }
|
| +
|
| + if (ppd_contents.size() > options_.max_ppd_contents_size_) {
|
| + // PPD is too big.
|
| + //
|
| + // Note -- if we ever add shared-ppd-sourcing, e.g. we may serve a ppd to
|
| + // a user that's not from an explicitly trusted source, we should also
|
| + // check *uncompressed* size here to head off zip-bombs (e.g. let's
|
| + // compress 1GBs of zeros into a 900kb file and see what cups does when it
|
| + // tries to expand that...)
|
| + done_callback_.Run(PpdProvider::SERVER_ERROR, FilePath());
|
| + return;
|
| + }
|
| +
|
| + auto ppd_file = cache_->Store(active_reference_, ppd_contents);
|
| + if (!ppd_file) {
|
| + // Failed to store.
|
| + done_callback_.Run(PpdProvider::INTERNAL_ERROR, FilePath());
|
| + return;
|
| + }
|
| + done_callback_.Run(PpdProvider::SUCCESS, ppd_file.value());
|
| + }
|
| +
|
| + private:
|
| + // Generate a url to look up a manufacturer/model from the quirks server
|
| + GURL GetQuirksServerLookupURL(
|
| + const Printer::PpdReference& ppd_reference) const {
|
| + return GURL(base::StringPrintf(
|
| + "https://%s/v2/printer/manufacturers/%s/models/%s?key=%s",
|
| + options_.quirks_server.c_str(),
|
| + ppd_reference.effective_manufacturer.c_str(),
|
| + ppd_reference.effective_model.c_str(), api_key_.c_str()));
|
| + }
|
| +
|
| + // API key for accessing quirks server.
|
| + const string api_key_;
|
| +
|
| + // Reference we're currently trying to resolve.
|
| + Printer::PpdReference active_reference_;
|
| +
|
| + ForwardingURLFetcherDelegate forwarding_delegate_;
|
| + scoped_refptr<net::URLRequestContextGetter> url_context_getter_;
|
| + unique_ptr<PpdCache> cache_;
|
| +
|
| + PpdProvider::ResolveCallback done_callback_;
|
| +
|
| + // Check that Resolve() and its callback are sequenced appropriately.
|
| + base::SequenceChecker sequence_checker_;
|
| +
|
| + // Fetcher for the current resolve call, if any.
|
| + unique_ptr<URLFetcher> fetcher_;
|
| +
|
| + // Construction-time options, immutable.
|
| + const PpdProvider::Options options_;
|
| +};
|
| +
|
| +void ForwardingURLFetcherDelegate::OnURLFetchComplete(
|
| + const URLFetcher* source) {
|
| + owner_->OnURLFetchComplete();
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +// static
|
| +unique_ptr<PpdProvider> PpdProvider::Create(
|
| + const string& api_key,
|
| + scoped_refptr<net::URLRequestContextGetter> url_context_getter,
|
| + unique_ptr<PpdCache> cache,
|
| + const PpdProvider::Options& options) {
|
| + return base::MakeUnique<PpdProviderImpl>(api_key, url_context_getter,
|
| + std::move(cache), options);
|
| +}
|
| +
|
| +} // namespace printing
|
| +} // namespace chromeos
|
|
|