Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(55)

Side by Side Diff: chromeos/printing/ppd_provider.cc

Issue 2613683004: Completely rewrite the PpdProvider/PpdCache to use the SCS backend. Along the way, clean it up a l… (Closed)
Patch Set: Created 3 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2016 The Chromium Authors. All rights reserved. 1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chromeos/printing/ppd_provider.h" 5 #include "chromeos/printing/ppd_provider.h"
6 6
7 #include <deque>
8 #include <functional>
skau 2017/01/05 20:38:46 Do you need this header? Most of the included fea
Carlson 2017/01/26 21:59:36 Not sure what this got put in for. Removed.
7 #include <unordered_map> 9 #include <unordered_map>
8 #include <utility> 10 #include <utility>
11 #include <vector>
9 12
10 #include "base/base64.h" 13 #include "base/base64.h"
11 #include "base/bind_helpers.h" 14 #include "base/bind_helpers.h"
12 #include "base/files/file_util.h" 15 #include "base/files/file_util.h"
13 #include "base/json/json_parser.h" 16 #include "base/json/json_parser.h"
14 #include "base/memory/ptr_util.h" 17 #include "base/memory/ptr_util.h"
15 #include "base/strings/string_number_conversions.h" 18 #include "base/strings/string_number_conversions.h"
19 #include "base/strings/string_split.h"
16 #include "base/strings/string_util.h" 20 #include "base/strings/string_util.h"
17 #include "base/strings/stringprintf.h" 21 #include "base/strings/stringprintf.h"
18 #include "base/synchronization/lock.h" 22 #include "base/synchronization/lock.h"
19 #include "base/task_runner_util.h" 23 #include "base/task_runner_util.h"
20 #include "base/threading/sequenced_task_runner_handle.h" 24 #include "base/threading/sequenced_task_runner_handle.h"
21 #include "base/time/time.h" 25 #include "base/time/time.h"
22 #include "base/values.h" 26 #include "base/values.h"
23 #include "chromeos/printing/ppd_cache.h" 27 #include "chromeos/printing/ppd_cache.h"
24 #include "net/base/load_flags.h" 28 #include "net/base/load_flags.h"
25 #include "net/http/http_status_code.h" 29 #include "net/http/http_status_code.h"
26 #include "net/url_request/url_fetcher.h" 30 #include "net/url_request/url_fetcher.h"
27 #include "net/url_request/url_fetcher_delegate.h" 31 #include "net/url_request/url_fetcher_delegate.h"
28 #include "net/url_request/url_request_context_getter.h" 32 #include "net/url_request/url_request_context_getter.h"
29 #include "url/gurl.h" 33 #include "url/gurl.h"
30 34
31 namespace chromeos { 35 namespace chromeos {
32 namespace printing { 36 namespace printing {
33 namespace { 37 namespace {
34 38
35 // Expected fields from the quirks server. 39 // Returns false if there are obvious errors in the reference that will prevent
36 const char kJSONPPDKey[] = "compressedPpd"; 40 // resolution.
37 const char kJSONLastUpdatedKey[] = "lastUpdatedTime"; 41 bool PpdReferenceIsWellFormed(const Printer::PpdReference& reference) {
38 const char kJSONTopListKey[] = "manufacturers"; 42 int filled_fields = 0;
39 const char kJSONManufacturer[] = "manufacturer"; 43 if (!reference.user_supplied_ppd_url.empty()) {
40 const char kJSONModelList[] = "models"; 44 ++filled_fields;
41 45 if (!GURL(reference.user_supplied_ppd_url).is_valid()) {
42 class PpdProviderImpl; 46 return false;
43 47 }
44 // PpdProvider handles two types of URL fetches, and so uses these delegates 48 }
45 // to route OnURLFetchComplete to the appropriate handler. 49 if (!reference.effective_model.empty()) {
46 class ResolveURLFetcherDelegate : public net::URLFetcherDelegate { 50 ++filled_fields;
51 }
52 // Should have exactly one non-empty field.
53 return filled_fields == 1;
54 }
55
56 std::string PpdReferenceToCacheKey(const Printer::PpdReference& reference) {
57 DCHECK(PpdReferenceIsWellFormed(reference));
58 // The key prefixes here are arbitrary, but ensure we can't have an (unhashed)
59 // collision between keys generated from different PpdReference fields.
60 if (!reference.effective_model.empty()) {
61 return std::string("em:") + reference.effective_model;
62 } else {
63 return std::string("up:") + reference.user_supplied_ppd_url;
64 }
65 }
66
67 class PpdProviderImpl : public PpdProvider, public net::URLFetcherDelegate {
47 public: 68 public:
48 explicit ResolveURLFetcherDelegate(PpdProviderImpl* parent) 69 // What kind of thing is the fetcher currently fetching? We use this to
49 : parent_(parent) {} 70 // determine what to do when the fetcher returns a result.
50 71 enum FetcherTarget {
51 void OnURLFetchComplete(const net::URLFetcher* source) override; 72 FT_LOCALES, // Locales metadata.
52 73 FT_MANUFACTURERS, // List of manufacturers metadata.
53 // Link back to parent. Not owned. 74 FT_PRINTERS, // List of printers from a manufacturer.
54 PpdProviderImpl* const parent_; 75 FT_PPD_INDEX, // Master ppd index.
55 }; 76 FT_PPD // A Ppd file.
56 77 };
57 class QueryAvailableURLFetcherDelegate : public net::URLFetcherDelegate { 78
58 public:
59 explicit QueryAvailableURLFetcherDelegate(PpdProviderImpl* parent)
60 : parent_(parent) {}
61
62 void OnURLFetchComplete(const net::URLFetcher* source) override;
63
64 // Link back to parent. Not owned.
65 PpdProviderImpl* const parent_;
66 };
67
68 // Data involved in an active Resolve() URL fetch.
69 struct ResolveFetchData {
70 // The fetcher doing the fetch.
71 std::unique_ptr<net::URLFetcher> fetcher;
72
73 // The reference being resolved.
74 Printer::PpdReference ppd_reference;
75
76 // Callback to invoke on completion.
77 PpdProvider::ResolveCallback done_callback;
78 };
79
80 // Data involved in an active QueryAvailable() URL fetch.
81 struct QueryAvailableFetchData {
82 // The fetcher doing the fetch.
83 std::unique_ptr<net::URLFetcher> fetcher;
84
85 // Callback to invoke on completion.
86 PpdProvider::QueryAvailableCallback done_callback;
87 };
88
89 class PpdProviderImpl : public PpdProvider {
90 public:
91 PpdProviderImpl( 79 PpdProviderImpl(
92 const std::string& api_key, 80 const std::string& browser_locale,
93 scoped_refptr<net::URLRequestContextGetter> url_context_getter, 81 scoped_refptr<net::URLRequestContextGetter> url_context_getter,
94 scoped_refptr<base::SequencedTaskRunner> io_task_runner, 82 scoped_refptr<PpdCache> ppd_cache,
95 std::unique_ptr<PpdCache> cache,
96 const PpdProvider::Options& options) 83 const PpdProvider::Options& options)
97 : api_key_(api_key), 84 : browser_locale_(browser_locale),
98 url_context_getter_(url_context_getter), 85 url_context_getter_(url_context_getter),
99 io_task_runner_(io_task_runner), 86 ppd_cache_(ppd_cache),
100 cache_(std::move(cache)),
101 options_(options), 87 options_(options),
102 resolve_delegate_(this),
103 query_available_delegate_(this),
104 weak_factory_(this) { 88 weak_factory_(this) {
105 CHECK_GT(options_.max_ppd_contents_size_, 0U); 89 CHECK_GT(options_.max_ppd_contents_size_, 0U);
106 } 90 }
107 ~PpdProviderImpl() override {} 91
108 92 // Resolving manufacturers requires a couple of steps, because of
109 void Resolve(const Printer::PpdReference& ppd_reference, 93 // localization. First we have to figure out what locale to use, which
110 const PpdProvider::ResolveCallback& cb) override { 94 // involves grabbing a list of available locales from the server. Once we
111 CHECK(!cb.is_null()); 95 // have decided on a locale, we go out and fetch the manufacturers map in that
96 // localization.
97 //
98 // This means when a request comes in, we either queue it and start background
99 // fetches if necessary, or we satisfy it immediately from memory.
100 void ResolveManufacturers(const ResolveManufacturersCallback& cb) override {
112 CHECK(base::SequencedTaskRunnerHandle::IsSet()) 101 CHECK(base::SequencedTaskRunnerHandle::IsSet())
113 << "Resolve must be called from a SequencedTaskRunner context"; 102 << "ResolveManufacturers must be called from a SequencedTaskRunner"
114 auto cache_result = base::MakeUnique<base::Optional<base::FilePath>>(); 103 "context";
115 auto* raw_cache_result_ptr = cache_result.get(); 104 if (manufacturer_map_.get() == nullptr) {
116 bool post_result = io_task_runner_->PostTaskAndReply( 105 manufacturers_resolution_queue_.push_back(cb);
117 FROM_HERE, base::Bind(&PpdProviderImpl::ResolveDoCacheLookup, 106 MaybeStartFetch();
skau 2017/01/05 20:38:46 Can you make this an early return? Unless your in
Carlson 2017/01/26 21:59:36 Done.
118 weak_factory_.GetWeakPtr(), ppd_reference, 107 } else {
119 raw_cache_result_ptr), 108 // We already have this in memory.
120 base::Bind(&PpdProviderImpl::ResolveCacheLookupDone, 109 base::SequencedTaskRunnerHandle::Get()->PostTask(
121 weak_factory_.GetWeakPtr(), ppd_reference, cb, 110 FROM_HERE, base::Bind(cb, PpdProvider::SUCCESS, *manufacturer_map_));
122 base::Passed(&cache_result))); 111 }
123 DCHECK(post_result); 112 }
124 } 113
125 114 // If there is work outstanding that requires a URL fetch to complete, start
126 void QueryAvailable(const QueryAvailableCallback& cb) override { 115 // going on it.
127 CHECK(!cb.is_null()); 116 void MaybeStartFetch() {
128 CHECK(base::SequencedTaskRunnerHandle::IsSet()) 117 if (fetcher_.get() != nullptr) {
129 << "QueryAvailable() must be called from a SequencedTaskRunner context"; 118 // Something is already in flight. We'll call this again when that
130 auto cache_result = 119 // completes.
131 base::MakeUnique<base::Optional<PpdProvider::AvailablePrintersMap>>();
132 auto* raw_cache_result_ptr = cache_result.get();
133 bool post_result = io_task_runner_->PostTaskAndReply(
134 FROM_HERE, base::Bind(&PpdProviderImpl::QueryAvailableDoCacheLookup,
135 weak_factory_.GetWeakPtr(), raw_cache_result_ptr),
136 base::Bind(&PpdProviderImpl::QueryAvailableCacheLookupDone,
137 weak_factory_.GetWeakPtr(), cb,
138 base::Passed(&cache_result)));
139 DCHECK(post_result);
140 }
141
142 bool CachePpd(const Printer::PpdReference& ppd_reference,
143 const base::FilePath& ppd_path) override {
144 std::string buf;
145 if (!base::ReadFileToStringWithMaxSize(ppd_path, &buf,
146 options_.max_ppd_contents_size_)) {
147 return false;
148 }
149 return static_cast<bool>(cache_->Store(ppd_reference, buf));
150 }
151
152 // Called on the same thread as Resolve() when the fetcher completes its
153 // fetch.
154 void OnResolveFetchComplete(const net::URLFetcher* source) {
155 std::unique_ptr<ResolveFetchData> fetch_data = GetResolveFetchData(source);
156 std::string ppd_contents;
157 uint64_t last_updated_time;
158 if (!ExtractResolveResponseData(source, fetch_data.get(), &ppd_contents,
159 &last_updated_time)) {
160 fetch_data->done_callback.Run(PpdProvider::SERVER_ERROR,
161 base::FilePath());
162 return; 120 return;
163 } 121 }
skau 2017/01/05 20:38:46 nit: add a new line
Carlson 2017/01/26 21:59:36 Done.
164 122 bool redo_from_start;
skau 2017/01/05 20:38:46 Can you break instead of using this flag? It'll r
Carlson 2017/01/26 21:59:36 Restructured a bit to reduce the loop complexity.
165 auto ppd_file = cache_->Store(fetch_data->ppd_reference, ppd_contents); 123 do {
166 if (!ppd_file) { 124 redo_from_start = false;
167 // Failed to store. 125 if (!manufacturers_resolution_queue_.empty()) {
168 fetch_data->done_callback.Run(PpdProvider::INTERNAL_ERROR, 126 StartFetch(GetLocalesURL(), FT_LOCALES);
169 base::FilePath()); 127 } else if (!printers_resolution_queue_.empty()) {
128 StartFetch(GetPrintersURL(printers_resolution_queue_.front().first),
129 FT_PRINTERS);
130 } else if (!ppd_resolution_queue_.empty()) {
131 const auto& next = ppd_resolution_queue_.front();
132 if (!next.first.user_supplied_ppd_url.empty()) {
133 DCHECK(next.first.effective_model.empty());
134 GURL url(next.first.user_supplied_ppd_url);
135 DCHECK(url.is_valid());
136 StartFetch(url, FT_PPD);
137 } else {
138 DCHECK(!next.first.effective_model.empty());
139 if (cached_ppd_index_.get() == nullptr) {
140 // Have to have the ppd index before we can resolve by effective
141 // model
142 StartFetch(GetPpdIndexURL(), FT_PPD_INDEX);
143 } else {
144 // Get the URL from the ppd index and start the fetch.
145 auto it = cached_ppd_index_->find(next.first.effective_model);
146 if (it != cached_ppd_index_->end()) {
147 StartFetch(GetPpdURL(it->second), FT_PPD);
148 } else {
149 // This ppd reference isn't in the index. That's not good. Fail
150 // out the current resolution and go try to start the next
151 // thing if there is one.
152 LOG(ERROR) << "PPD " << next.first.effective_model
153 << " not found in server index";
154 base::SequencedTaskRunnerHandle::Get()->PostTask(
155 FROM_HERE,
156 base::Bind(next.second, PpdProvider::INTERNAL_ERROR,
157 std::string()));
158 ppd_resolution_queue_.pop_front();
159 redo_from_start = true;
160 }
161 }
162 }
163 }
164 } while (redo_from_start);
165 }
166
167 void ResolvePrinters(const ManufacturerReference& manufacturer,
168 const ResolvePrintersCallback& cb) override {
169 auto it = cached_printers_.find(manufacturer.key);
170 if (it != cached_printers_.end()) {
171 // Satisfy from the cache.
172 base::SequencedTaskRunnerHandle::Get()->PostTask(
173 FROM_HERE, base::Bind(cb, PpdProvider::SUCCESS, it->second));
174 } else {
175 // We haven't resolved this manufacturer yet.
176 printers_resolution_queue_.push_back({manufacturer, cb});
177 MaybeStartFetch();
178 }
179 }
180
181 void ResolvePpd(const Printer::PpdReference& reference,
182 const ResolvePpdCallback& cb) override {
183 // Do a sanity check here, so we can assumed |reference| is well-formed in
184 // the rest of this class.
185 if (!PpdReferenceIsWellFormed(reference)) {
186 base::SequencedTaskRunnerHandle::Get()->PostTask(
187 FROM_HERE, base::Bind(cb, PpdProvider::INTERNAL_ERROR, ""));
170 return; 188 return;
171 } 189 }
172 fetch_data->done_callback.Run(PpdProvider::SUCCESS, ppd_file.value()); 190 // First step, check the cache. If the cache lookup fails, we'll (try to)
173 } 191 // consult the server.
174 192 ppd_cache_->Find(PpdReferenceToCacheKey(reference),
175 // Called on the same thread as QueryAvailable() when the cache lookup is 193 base::Bind(&PpdProviderImpl::ResolvePpdCacheLookupDone,
176 // done. If the cache satisfied the request, finish the Query, otherwise 194 weak_factory_.GetWeakPtr(), reference, cb));
177 // start a URL request to satisfy the Query. This runs on the same thread as 195 }
178 // QueryAvailable() was invoked on. 196
179 void QueryAvailableCacheLookupDone( 197 void CachePpd(const Printer::PpdReference& ppd_reference,
180 const PpdProvider::QueryAvailableCallback& done_callback, 198 const std::string& ppd_contents) override {
181 std::unique_ptr<base::Optional<PpdProvider::AvailablePrintersMap>> 199 ppd_cache_->Store(PpdReferenceToCacheKey(ppd_reference), ppd_contents,
182 cache_result) { 200 base::Callback<void()>());
183 if (*cache_result) { 201 };
184 done_callback.Run(PpdProvider::SUCCESS, cache_result->value()); 202
203 // Our only sources of long running ops are cache fetches and network fetches.
204 bool Idle() const override {
205 return ppd_cache_->Idle() && (fetcher_.get() == nullptr);
206 }
207
208 // Common handler that gets called whenever a fetch completes.
209 void OnURLFetchComplete(const net::URLFetcher* source) override {
210 switch (fetcher_target_) {
211 case FT_LOCALES:
212 OnLocalesFetchComplete();
213 break;
214 case FT_MANUFACTURERS:
215 OnManufacturersFetchComplete();
216 break;
217 case FT_PRINTERS:
218 OnPrintersFetchComplete();
219 break;
220 case FT_PPD_INDEX:
221 OnPpdIndexFetchComplete();
222 break;
223 case FT_PPD:
224 OnPpdFetchComplete();
225 break;
226 default:
227 LOG(DFATAL) << "Unknown fetch source";
228 };
229 MaybeStartFetch();
230 }
231
232 private:
233 // Return the URL used to look up the supported locales list.
234 GURL GetLocalesURL() {
235 return GURL(options_.ppd_server_root + "/metadata/locales.json");
236 }
237
238 // Return the URL used to get the index of effective model -> ppd.
239 GURL GetPpdIndexURL() {
240 return GURL(options_.ppd_server_root + "/metadata/index.json");
241 }
242
243 // Return the URL to get a localized manufacturers map.
244 GURL GetManufacturersURL(const std::string& locale) {
245 return GURL(base::StringPrintf("%s/metadata/manufacturers-%s.json",
246 options_.ppd_server_root.c_str(),
247 locale.c_str()));
248 }
249
250 // Return the URL used to get a list of printers from the manufacturer |ref|.
251 GURL GetPrintersURL(const ManufacturerReference& ref) {
252 return GURL(base::StringPrintf(
253 "%s/metadata/%s", options_.ppd_server_root.c_str(), ref.key.c_str()));
254 }
255
256 // Return the URL used to get a ppd with the given filename.
257 GURL GetPpdURL(const std::string& filename) {
258 return GURL(base::StringPrintf(
259 "%s/ppds/%s", options_.ppd_server_root.c_str(), filename.c_str()));
260 }
261
262 // Create and return a fetcher that has the usual (for this class) flags set
263 // and calls back to OnURLFetchComplete in this class when it finishes.
264 void StartFetch(const GURL& url, FetcherTarget target) {
265 DCHECK_EQ(nullptr, fetcher_.get());
266 fetcher_target_ = target;
267
268 fetcher_ = net::URLFetcher::Create(url, net::URLFetcher::GET, this);
269 fetcher_->SetRequestContext(url_context_getter_.get());
270 fetcher_->SetLoadFlags(net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE |
271 net::LOAD_DO_NOT_SAVE_COOKIES |
272 net::LOAD_DO_NOT_SEND_COOKIES |
273 net::LOAD_DO_NOT_SEND_AUTH_DATA);
274 fetcher_->Start();
275 }
276
277 // Callback when the cache lookup for a ppd request finishes. If we hit in
278 // the cache, satisfy the resolution, otherwise kick it over to the fetcher
279 // queue to be grabbed from a server.
280 void ResolvePpdCacheLookupDone(const Printer::PpdReference& reference,
281 const ResolvePpdCallback& cb,
282 const PpdCache::FindResult& result) {
283 if (result.success) {
284 // Cache hit.
285 base::SequencedTaskRunnerHandle::Get()->PostTask(
286 FROM_HERE, base::Bind(cb, PpdProvider::SUCCESS, result.contents));
287 } else {
288 // Cache miss. Queue it to be satisfied by the fetcher queue.
289 ppd_resolution_queue_.push_back({reference, cb});
290 MaybeStartFetch();
291 }
292 }
293
294 // Handler for the completion of the locales fetch. This response should be a
295 // list of strings, each of which is a locale in which we can answer queries
296 // on the server. The server (should) guarantee that we get 'en' as an
297 // absolute minimum.
298 //
299 // Combine this information with the browser locale to figure out the best
300 // locale to use, and then start a fetch of the manufacturers map in that
301 // locale.
302 void OnLocalesFetchComplete() {
303 std::string contents;
304 if (!ValidateAndGetResponseAsString(&contents)) {
305 FailQueuedMetadataResolutions(PpdProvider::SERVER_ERROR);
185 return; 306 return;
186 } 307 }
187 308 auto top_list = base::ListValue::From(base::JSONReader::Read(contents));
188 auto fetch_data = base::MakeUnique<QueryAvailableFetchData>(); 309
189 fetch_data->done_callback = done_callback; 310 if (top_list.get() == nullptr) {
190 311 // We got something malformed back.
191 fetch_data->fetcher = net::URLFetcher::Create(GetQuirksServerPpdListURL(), 312 FailQueuedMetadataResolutions(PpdProvider::INTERNAL_ERROR);
192 net::URLFetcher::GET, 313 return;
193 &query_available_delegate_); 314 }
194 fetch_data->fetcher->SetRequestContext(url_context_getter_.get()); 315
195 fetch_data->fetcher->SetLoadFlags( 316 // This should just be a simple list of locale strings.
196 net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE | 317 std::vector<std::string> available_locales;
197 net::LOAD_DO_NOT_SAVE_COOKIES | net::LOAD_DO_NOT_SEND_COOKIES | 318 bool found_en = false;
198 net::LOAD_DO_NOT_SEND_AUTH_DATA); 319 for (const std::unique_ptr<base::Value>& entry : *top_list) {
199 auto* fetcher = fetch_data->fetcher.get(); 320 std::string tmp;
200 StoreQueryAvailableFetchData(std::move(fetch_data)); 321 // Locales should have at *least* a two-character country code. 100 is an
201 fetcher->Start(); 322 // arbitrary upper bound for length to protect against extreme bogosity.
202 } 323 if (!entry->GetAsString(&tmp) || tmp.size() < 2 || tmp.size() > 100) {
203 324 FailQueuedMetadataResolutions(PpdProvider::INTERNAL_ERROR);
204 void OnQueryAvailableFetchComplete(const net::URLFetcher* source) { 325 return;
205 std::unique_ptr<QueryAvailableFetchData> fetch_data = 326 }
206 GetQueryAvailableFetchData(source); 327 if (tmp == "en") {
207 328 found_en = true;
329 }
330 available_locales.push_back(tmp);
331 }
332 if (available_locales.empty() || !found_en) {
333 // We have no locales, or we didn't get an english locale (which is our
334 // ultimate fallback)
335 FailQueuedMetadataResolutions(PpdProvider::INTERNAL_ERROR);
336 return;
337 }
338 // Everything checks out, kick off the manufacturers list fetch in the
339 // appropriate locale.
340 std::string locale_to_use = GetBestLocale(available_locales);
341 StartFetch(GetManufacturersURL(locale_to_use), FT_MANUFACTURERS);
342 }
343
344 // Called when the |fetcher_| is expected have the results of a
345 // manufacturer map (which maps localized manufacturer names to keys for
346 // looking up printers from that manufacturer). Use this information to
347 // populate manufacturer_map_, and resolve all queued ResolveManufacturers()
348 // calls.
349 void OnManufacturersFetchComplete() {
350 DCHECK_EQ(nullptr, manufacturer_map_.get());
351 std::vector<std::pair<std::string, std::string>> contents;
352 auto code = ValidateAndParseJSONResponse(&contents);
skau 2017/01/05 20:38:46 Is this the result code?
Carlson 2017/01/26 21:59:36 Removed some autos to make this clearer.
353 if (code != PpdProvider::SUCCESS) {
354 FailQueuedMetadataResolutions(code);
355 return;
356 }
357 manufacturer_map_ = base::MakeUnique<ManufacturerMap>();
358
359 for (const auto& entry : contents) {
360 ManufacturerReference ref;
361 ref.key = entry.second;
362 manufacturer_map_->insert({entry.first, ref});
363 }
364
365 // Complete any queued manufacturer resolutions.
366 for (const auto& cb : manufacturers_resolution_queue_) {
367 base::SequencedTaskRunnerHandle::Get()->PostTask(
368 FROM_HERE, base::Bind(cb, PpdProvider::SUCCESS, *manufacturer_map_));
369 }
370 manufacturers_resolution_queue_.clear();
371 }
372
373 // The outstanding fetch associated with the front of
374 // |printers_resolution_queue_| finished, use the response to satisfy that
375 // ResolvePrinters() call.
376 void OnPrintersFetchComplete() {
377 DCHECK(!printers_resolution_queue_.empty());
378 std::vector<std::pair<std::string, std::string>> contents;
379 auto code = ValidateAndParseJSONResponse(&contents);
380 if (code != PpdProvider::SUCCESS) {
381 base::SequencedTaskRunnerHandle::Get()->PostTask(
382 FROM_HERE, base::Bind(printers_resolution_queue_.front().second, code,
383 PrinterMap()));
384 } else {
385 // This should be a list of lists of 2-element strings, where the first
386 // element is the localized name of the printer and the second element
387 // is the canonical name of the printer.
388
389 // Create the printer map in the cache, and populate it.
390 auto& printer_map =
391 cached_printers_[printers_resolution_queue_.front().first.key];
392 for (const auto& entry : contents) {
393 Printer::PpdReference reference;
394 reference.effective_model = entry.second;
395 printer_map.insert({entry.first, reference});
396 }
397 base::SequencedTaskRunnerHandle::Get()->PostTask(
398 FROM_HERE, base::Bind(printers_resolution_queue_.front().second,
399 PpdProvider::SUCCESS, printer_map));
400 }
401 printers_resolution_queue_.pop_front();
402 }
403
404 // Called when |fetcher_| should have just received the index mapping
405 // effective model name to ppd filenames. Use this to populate
406 // |cached_ppd_index_|.
407 void OnPpdIndexFetchComplete() {
408 std::vector<std::pair<std::string, std::string>> contents;
409 auto code = ValidateAndParseJSONResponse(&contents);
410 if (code != PpdProvider::SUCCESS) {
411 FailQueuedServerPpdResolutions(code);
412 } else {
413 cached_ppd_index_ =
414 base::MakeUnique<std::unordered_map<std::string, std::string>>();
415 // This should be a list of lists of 2-element strings, where the first
416 // element is the effective model name of the printer and the second
417 // element is the filename of the ppd.
418 for (const auto& entry : contents) {
419 cached_ppd_index_->insert(entry);
420 }
421 }
422 }
423
424 // This is called when |fetcher_| should have just downloaded a ppd. If we
425 // downloaded something successfully, use it to satisfy the front of the ppd
426 // resolution queue, otherwise fail out that resolution.
427 void OnPpdFetchComplete() {
428 DCHECK(!ppd_resolution_queue_.empty());
208 std::string contents; 429 std::string contents;
209 if (!ValidateAndGetResponseAsString(*source, &contents)) { 430 if (!ValidateAndGetResponseAsString(&contents) ||
210 // Something went wrong with the fetch. 431 contents.size() > options_.max_ppd_contents_size_) {
211 fetch_data->done_callback.Run(PpdProvider::SERVER_ERROR, 432 base::SequencedTaskRunnerHandle::Get()->PostTask(
212 AvailablePrintersMap()); 433 FROM_HERE, base::Bind(ppd_resolution_queue_.front().second,
213 return; 434 PpdProvider::SERVER_ERROR, std::string()));
214 } 435 } else {
215 436 ppd_cache_->Store(
216 // The server gives us JSON in the form of a list of (manufacturer, list of 437 PpdReferenceToCacheKey(ppd_resolution_queue_.front().first), contents,
217 // models) tuples. 438 base::Callback<void()>());
218 auto top_dict = 439 base::SequencedTaskRunnerHandle::Get()->PostTask(
219 base::DictionaryValue::From(base::JSONReader::Read(contents)); 440 FROM_HERE, base::Bind(ppd_resolution_queue_.front().second,
220 const base::ListValue* top_list; 441 PpdProvider::SUCCESS, contents));
221 if (top_dict == nullptr || !top_dict->GetList(kJSONTopListKey, &top_list)) { 442 }
222 LOG(ERROR) << "Malformed response from quirks server"; 443 ppd_resolution_queue_.pop_front();
223 fetch_data->done_callback.Run(PpdProvider::SERVER_ERROR, 444 }
224 AvailablePrintersMap()); 445
225 return; 446 // Something went wrong during metadata fetches. Fail all queued metadata
226 } 447 // resolutions with the given error code.
227 448 void FailQueuedMetadataResolutions(PpdProvider::CallbackResultCode code) {
228 auto result = base::MakeUnique<PpdProvider::AvailablePrintersMap>(); 449 for (const auto& cb : manufacturers_resolution_queue_) {
229 for (const std::unique_ptr<base::Value>& entry : *top_list) { 450 base::SequencedTaskRunnerHandle::Get()->PostTask(
230 base::DictionaryValue* dict; 451 FROM_HERE, base::Bind(cb, code, ManufacturerMap()));
231 std::string manufacturer; 452 }
232 std::string model; 453 manufacturers_resolution_queue_.clear();
233 base::ListValue* model_list; 454 }
234 if (!entry->GetAsDictionary(&dict) || 455
235 !dict->GetString(kJSONManufacturer, &manufacturer) || 456 // Fail all server-based ppd resolutions inflight, because we failed to grab
236 !dict->GetList(kJSONModelList, &model_list)) { 457 // the necessary index data from the server. to the ppd server. Note we
skau 2017/01/05 20:38:46 'to the ppd server'?
Carlson 2017/01/26 21:59:36 Sentence fragment. Good device. More later. (re
237 LOG(ERROR) << "Unexpected contents in quirks server printer list."; 458 // leave any user-based ppd resolutions intact, as they don't depend on the
238 // Just skip this entry instead of aborting the whole thing. 459 // data we're missing.
239 continue; 460 void FailQueuedServerPpdResolutions(PpdProvider::CallbackResultCode code) {
240 } 461 std::deque<std::pair<Printer::PpdReference, ResolvePpdCallback>>
241 462 filtered_queue;
242 std::vector<std::string>& dest = (*result)[manufacturer]; 463
243 for (const std::unique_ptr<base::Value>& model_value : *model_list) { 464 for (const auto& entry : ppd_resolution_queue_) {
244 if (model_value->GetAsString(&model)) { 465 if (!entry.first.user_supplied_ppd_url.empty()) {
245 dest.push_back(model); 466 filtered_queue.push_back(entry);
246 } else { 467 } else {
247 LOG(ERROR) << "Skipping unknown model for manufacturer " 468 base::SequencedTaskRunnerHandle::Get()->PostTask(
248 << manufacturer; 469 FROM_HERE, base::Bind(entry.second, code, std::string()));
470 }
471 }
472 ppd_resolution_queue_ = std::move(filtered_queue);
473 }
474
475 // Given a list of possible locale strings (e.g. 'en-GB'), determine which of
476 // them we should use to best serve results for the browser locale (which was
477 // given to us at construction time).
478 std::string GetBestLocale(const std::vector<std::string>& available_locales) {
479 // First look for an exact match. If we find one, just use that.
480 for (const std::string& available : available_locales) {
481 if (available == browser_locale_) {
482 return available;
483 }
484 }
485
486 // Next, look for an available locale that is a parent of browser_locale_.
487 // Return the most specific one. For example, if we want 'en-GB-foo' and we
488 // don't have an exact match, but we do have 'en-GB' and 'en', we will
489 // return 'en-GB' -- the most specific match which is a parent of the
490 // requested locale.
491 size_t best_len = 0;
492 size_t best_idx = -1;
493 for (size_t i = 0; i < available_locales.size(); ++i) {
494 const std::string& available = available_locales[i];
495 if (base::StringPiece(browser_locale_).starts_with(available + "-") &&
496 available.size() > best_len) {
497 best_len = available.size();
498 best_idx = i;
499 }
500 }
501 if (best_idx != static_cast<size_t>(-1)) {
502 return available_locales[best_idx];
503 }
504
505 // Last chance for a match, look for the locale that matches the *most*
506 // pieces of locale_, with ties broken by being least specific. So for
507 // example, if we have 'es-GB', 'es-GB-foo' but no 'es' available, and we're
508 // requesting something for 'es', we'll get back 'es-GB' -- the least
509 // specific thing that matches some of the locale.
510 std::vector<base::StringPiece> browser_locale_pieces =
511 base::SplitStringPiece(browser_locale_, "-", base::KEEP_WHITESPACE,
512 base::SPLIT_WANT_ALL);
513 size_t best_match_size = 0;
514 size_t best_match_specificity;
515 best_idx = -1;
516 for (size_t i = 0; i < available_locales.size(); ++i) {
517 const std::string& available = available_locales[i];
518 std::vector<base::StringPiece> available_pieces = base::SplitStringPiece(
519 available, "-", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
520 size_t match_size = 0;
521 for (; match_size < available_pieces.size() &&
522 match_size < browser_locale_pieces.size();
523 ++match_size) {
524 if (available_pieces[match_size] != browser_locale_pieces[match_size]) {
525 break;
249 } 526 }
250 } 527 }
251 } 528 if (match_size > 0 &&
252 fetch_data->done_callback.Run(PpdProvider::SUCCESS, *result); 529 (best_idx == static_cast<size_t>(-1) ||
253 if (!result->empty()) { 530 match_size > best_match_size ||
254 cache_->StoreAvailablePrinters(std::move(result)); 531 (match_size == best_match_size &&
255 } else { 532 available_pieces.size() < best_match_specificity))) {
256 // An empty map means something is probably wrong; if we cache this map, 533 best_idx = i;
257 // we'll have an empty map until the cache expires. So complain and 534 best_match_size = match_size;
258 // refuse to cache. 535 best_match_specificity = available_pieces.size();
259 LOG(ERROR) << "Available printers map is unexpectedly empty. Refusing " 536 }
260 "to cache this."; 537 }
261 } 538 if (best_idx != static_cast<size_t>(-1)) {
262 } 539 return available_locales[best_idx];
263 540 }
264 private: 541
265 void StoreResolveFetchData(std::unique_ptr<ResolveFetchData> fetch_data) { 542 // Everything else failed. Throw up our hands and default to english.
266 auto raw_ptr = fetch_data->fetcher.get(); 543 return "en";
267 base::AutoLock lock(resolve_fetches_lock_); 544 }
268 resolve_fetches_[raw_ptr] = std::move(fetch_data); 545
269 } 546 // If |fetcher| succeeded in its fetch, get the response in |response| and
270 547 // return true, otherwise return false. In all cases, resets |fetcher_|.
271 void StoreQueryAvailableFetchData( 548 bool ValidateAndGetResponseAsString(std::string* contents) {
272 std::unique_ptr<QueryAvailableFetchData> fetch_data) { 549 bool ret =
273 auto raw_ptr = fetch_data->fetcher.get(); 550 ((fetcher_->GetStatus().status() == net::URLRequestStatus::SUCCESS) &&
274 base::AutoLock lock(query_available_fetches_lock_); 551 (fetcher_->GetResponseCode() == net::HTTP_OK) &&
275 query_available_fetches_[raw_ptr] = std::move(fetch_data); 552 fetcher_->GetResponseAsString(contents));
276 } 553 fetcher_.reset();
277
278 // Extract the result of a resolve url fetch into |ppd_contents| and
279 // |last_updated time|. Returns true on success.
280 bool ExtractResolveResponseData(const net::URLFetcher* source,
281 const ResolveFetchData* fetch,
282 std::string* ppd_contents,
283 uint64_t* last_updated_time) {
284 std::string contents;
285 if (!ValidateAndGetResponseAsString(*source, &contents)) {
286 LOG(WARNING) << "Response not validated";
287 return false;
288 }
289
290 auto dict = base::DictionaryValue::From(base::JSONReader::Read(contents));
291 if (dict == nullptr) {
292 LOG(WARNING) << "Response not a dictionary";
293 return false;
294 }
295 std::string last_updated_time_string;
296 if (!dict->GetString(kJSONPPDKey, ppd_contents) ||
297 !dict->GetString(kJSONLastUpdatedKey, &last_updated_time_string) ||
298 !base::StringToUint64(last_updated_time_string, last_updated_time)) {
299 // Malformed response. TODO(justincarlson) - LOG something here?
300 return false;
301 }
302
303 if (ppd_contents->size() > options_.max_ppd_contents_size_) {
304 LOG(WARNING) << "PPD too large";
305 // PPD is too big.
306 //
307 // Note -- if we ever add shared-ppd-sourcing, e.g. we may serve a ppd to
308 // a user that's not from an explicitly trusted source, we should also
309 // check *uncompressed* size here to head off zip-bombs (e.g. let's
310 // compress 1GBs of zeros into a 900kb file and see what cups does when it
311 // tries to expand that...)
312 ppd_contents->clear();
313 return false;
314 }
315 return base::Base64Decode(*ppd_contents, ppd_contents);
316 }
317
318 // Return the ResolveFetchData associated with |source|.
319 std::unique_ptr<ResolveFetchData> GetResolveFetchData(
320 const net::URLFetcher* source) {
321 base::AutoLock l(resolve_fetches_lock_);
322 auto it = resolve_fetches_.find(source);
323 CHECK(it != resolve_fetches_.end());
324 auto ret = std::move(it->second);
325 resolve_fetches_.erase(it);
326 return ret; 554 return ret;
327 } 555 }
328 556
329 // Return the QueryAvailableFetchData associated with |source|. 557 // Many of our metadata fetches happens to be in the form of a JSON
330 std::unique_ptr<QueryAvailableFetchData> GetQueryAvailableFetchData( 558 // list-of-lists-of-2-strings. So this just attempts to parse a JSON reply to
331 const net::URLFetcher* source) { 559 // |fetcher| into the passed contents vector. A return code of SUCCESS means
332 base::AutoLock lock(query_available_fetches_lock_); 560 // the JSON was formatted as expected and we've parsed it into |contents|. On
333 auto it = query_available_fetches_.find(source); 561 // error the contents of |contents| cleared.
334 CHECK(it != query_available_fetches_.end()) << "Fetch data not found!"; 562 PpdProvider::CallbackResultCode ValidateAndParseJSONResponse(
335 auto fetch_data = std::move(it->second); 563 std::vector<std::pair<std::string, std::string>>* contents) {
336 query_available_fetches_.erase(it); 564 contents->clear();
337 return fetch_data; 565 std::string buffer;
338 } 566 if (!ValidateAndGetResponseAsString(&buffer)) {
339 567 return PpdProvider::SERVER_ERROR;
340 // Trivial wrappers around PpdCache::Find() and 568 }
341 // PpdCache::FindAvailablePrinters(). We need these wrappers both because we 569 auto top_list = base::ListValue::From(base::JSONReader::Read(buffer));
342 // use weak_ptrs to manage lifetime, and so we need to bind callbacks to 570
343 // *this*, and because weak_ptr's preclude return values in posted tasks, so 571 if (top_list.get() == nullptr) {
344 // we have to use a parameter to return state. 572 // We got something malformed back.
345 void ResolveDoCacheLookup( 573 return PpdProvider::INTERNAL_ERROR;
346 const Printer::PpdReference& reference, 574 }
347 base::Optional<base::FilePath>* cache_result) const { 575 for (const auto& entry : *top_list) {
348 *cache_result = cache_->Find(reference); 576 base::ListValue* sub_list;
349 } 577 contents->push_back({});
350 578 if (!entry->GetAsList(&sub_list) || sub_list->GetSize() != 2 ||
351 void QueryAvailableDoCacheLookup( 579 !sub_list->GetString(0, &contents->back().first) ||
352 base::Optional<PpdProvider::AvailablePrintersMap>* cache_result) const { 580 !sub_list->GetString(1, &contents->back().second)) {
353 *cache_result = cache_->FindAvailablePrinters(); 581 contents->clear();
354 } 582 return PpdProvider::INTERNAL_ERROR;
355 583 }
356 // Callback that happens when the Resolve() cache lookup completes. If the 584 }
357 // cache satisfied the request, finish the Resolve, otherwise start a URL 585 return PpdProvider::SUCCESS;
358 // request to satisfy the request. This runs on the same thread as Resolve() 586 }
359 // was invoked on. 587
360 void ResolveCacheLookupDone( 588 // Map from (localized) manufacturers to ManufacturerReferences used to get
361 const Printer::PpdReference& ppd_reference, 589 // printer lists. null until populated.
362 const PpdProvider::ResolveCallback& done_callback, 590 std::unique_ptr<ManufacturerMap> manufacturer_map_;
363 std::unique_ptr<base::Optional<base::FilePath>> cache_result) { 591
364 if (*cache_result) { 592 // Map from manufacturer reference to PrinterMap for that manufacturer,
365 // Cache hit. Schedule the callback now and return. 593 // populated lazily, on demand.
366 done_callback.Run(PpdProvider::SUCCESS, cache_result->value()); 594 std::unordered_map<std::string, PrinterMap> cached_printers_;
367 return; 595
368 } 596 // Cached contents of the server index, which maps
369 597 // PpdReference::effective_models to urls for the corresponding ppd. Null
370 // We don't have a way to automatically resolve user-supplied PPDs yet. So 598 // until we have fetched the index.
371 // if we have one specified, and it's not cached, we fail out rather than 599 std::unique_ptr<std::unordered_map<std::string, std::string>>
372 // fall back to quirks-server based resolution. The reasoning here is that 600 cached_ppd_index_;
373 // if the user has specified a PPD when a quirks-server one exists, it 601
374 // probably means the quirks server one doesn't work for some reason, so we 602 // Queued ResolveManufacturers() calls. We will simultaneously resolve
375 // shouldn't silently use it. 603 // all queued requests, so no need for a deque here.
376 if (!ppd_reference.user_supplied_ppd_url.empty()) { 604 std::vector<ResolveManufacturersCallback> manufacturers_resolution_queue_;
377 done_callback.Run(PpdProvider::NOT_FOUND, base::FilePath()); 605
378 return; 606 // Queued ResolvePrinters() calls.
379 } 607 std::deque<std::pair<ManufacturerReference, ResolvePrintersCallback>>
380 608 printers_resolution_queue_;
381 auto fetch_data = base::MakeUnique<ResolveFetchData>(); 609
382 fetch_data->ppd_reference = ppd_reference; 610 // Queued ResolvePpd() requests.
383 fetch_data->done_callback = done_callback; 611 std::deque<std::pair<Printer::PpdReference, ResolvePpdCallback>>
384 fetch_data->fetcher = 612 ppd_resolution_queue_;
385 net::URLFetcher::Create(GetQuirksServerPpdLookupURL(ppd_reference), 613
386 net::URLFetcher::GET, &resolve_delegate_); 614 // If the fetcher is active, what's it fetching?
387 615 FetcherTarget fetcher_target_;
388 fetch_data->fetcher->SetRequestContext(url_context_getter_.get()); 616
389 fetch_data->fetcher->SetLoadFlags( 617 // Fetcher used for all network fetches. This is explicitly reset() when
390 net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE | 618 // a fetch has been processed.
391 net::LOAD_DO_NOT_SAVE_COOKIES | net::LOAD_DO_NOT_SEND_COOKIES | 619 std::unique_ptr<net::URLFetcher> fetcher_;
392 net::LOAD_DO_NOT_SEND_AUTH_DATA); 620
393 auto* fetcher = fetch_data->fetcher.get(); 621 // Locale of the browser, as returned by
394 StoreResolveFetchData(std::move(fetch_data)); 622 // BrowserContext::GetApplicationLocale();
395 fetcher->Start(); 623 const std::string browser_locale_;
396 }
397
398 // Generate a url to look up a manufacturer/model from the quirks server
399 GURL GetQuirksServerPpdLookupURL(
400 const Printer::PpdReference& ppd_reference) const {
401 return GURL(base::StringPrintf(
402 "https://%s/v2/printer/manufacturers/%s/models/%s?key=%s",
403 options_.quirks_server.c_str(),
404 ppd_reference.effective_manufacturer.c_str(),
405 ppd_reference.effective_model.c_str(), api_key_.c_str()));
406 }
407
408 // Generate a url to ask for the full supported printer list from the quirks
409 // server.
410 GURL GetQuirksServerPpdListURL() const {
411 return GURL(base::StringPrintf("https://%s/v2/printer/list?key=%s",
412 options_.quirks_server.c_str(),
413 api_key_.c_str()));
414 }
415
416 // If |fetcher| succeeded in its fetch, get the response in |response| and
417 // return true, otherwise return false.
418 bool ValidateAndGetResponseAsString(const net::URLFetcher& fetcher,
419 std::string* contents) {
420 return (fetcher.GetStatus().status() == net::URLRequestStatus::SUCCESS) &&
421 (fetcher.GetResponseCode() == net::HTTP_OK) &&
422 fetcher.GetResponseAsString(contents);
423 }
424
425 // API key for accessing quirks server.
426 const std::string api_key_;
427 624
428 scoped_refptr<net::URLRequestContextGetter> url_context_getter_; 625 scoped_refptr<net::URLRequestContextGetter> url_context_getter_;
429 scoped_refptr<base::SequencedTaskRunner> io_task_runner_; 626
430 std::unique_ptr<PpdCache> cache_; 627 // Cache of ppd files.
628 scoped_refptr<PpdCache> ppd_cache_;
431 629
432 // Construction-time options, immutable. 630 // Construction-time options, immutable.
433 const PpdProvider::Options options_; 631 const PpdProvider::Options options_;
434 632
435 ResolveURLFetcherDelegate resolve_delegate_;
436 QueryAvailableURLFetcherDelegate query_available_delegate_;
437
438 // Active resolve fetches and associated lock.
439 std::unordered_map<const net::URLFetcher*, std::unique_ptr<ResolveFetchData>>
440 resolve_fetches_;
441 base::Lock resolve_fetches_lock_;
442
443 // Active QueryAvailable() fetches and associated lock.
444 std::unordered_map<const net::URLFetcher*,
445 std::unique_ptr<QueryAvailableFetchData>>
446 query_available_fetches_;
447 base::Lock query_available_fetches_lock_;
448
449 base::WeakPtrFactory<PpdProviderImpl> weak_factory_; 633 base::WeakPtrFactory<PpdProviderImpl> weak_factory_;
634
635 protected:
636 ~PpdProviderImpl() override {}
450 }; 637 };
451 638
452 void ResolveURLFetcherDelegate::OnURLFetchComplete( 639 } // namespace
453 const net::URLFetcher* source) { 640
454 parent_->OnResolveFetchComplete(source); 641 // static
642 scoped_refptr<PpdProvider> PpdProvider::Create(
643 const std::string& browser_locale,
644 scoped_refptr<net::URLRequestContextGetter> url_context_getter,
645 scoped_refptr<PpdCache> ppd_cache,
646 const PpdProvider::Options& options) {
647 return scoped_refptr<PpdProvider>(new PpdProviderImpl(
648 browser_locale, url_context_getter, ppd_cache, options));
455 } 649 }
456
457 void QueryAvailableURLFetcherDelegate::OnURLFetchComplete(
458 const net::URLFetcher* source) {
459 parent_->OnQueryAvailableFetchComplete(source);
460 }
461
462 } // namespace
463
464 // static
465 std::unique_ptr<PpdProvider> PpdProvider::Create(
466 const std::string& api_key,
467 scoped_refptr<net::URLRequestContextGetter> url_context_getter,
468 scoped_refptr<base::SequencedTaskRunner> io_task_runner,
469 std::unique_ptr<PpdCache> cache,
470 const PpdProvider::Options& options) {
471 return base::MakeUnique<PpdProviderImpl>(
472 api_key, url_context_getter, io_task_runner, std::move(cache), options);
473 }
474
475 } // namespace printing 650 } // namespace printing
476 } // namespace chromeos 651 } // namespace chromeos
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698