| OLD | NEW |
| 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 <algorithm> |
| 8 #include <deque> |
| 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" |
| 15 #include "base/files/file.h" |
| 12 #include "base/files/file_util.h" | 16 #include "base/files/file_util.h" |
| 13 #include "base/json/json_parser.h" | 17 #include "base/json/json_parser.h" |
| 14 #include "base/memory/ptr_util.h" | 18 #include "base/memory/ptr_util.h" |
| 15 #include "base/strings/string_number_conversions.h" | 19 #include "base/strings/string_number_conversions.h" |
| 20 #include "base/strings/string_split.h" |
| 16 #include "base/strings/string_util.h" | 21 #include "base/strings/string_util.h" |
| 17 #include "base/strings/stringprintf.h" | 22 #include "base/strings/stringprintf.h" |
| 18 #include "base/synchronization/lock.h" | 23 #include "base/synchronization/lock.h" |
| 19 #include "base/task_runner_util.h" | 24 #include "base/task_runner_util.h" |
| 20 #include "base/threading/sequenced_task_runner_handle.h" | 25 #include "base/threading/sequenced_task_runner_handle.h" |
| 26 #include "base/threading/thread_restrictions.h" |
| 21 #include "base/time/time.h" | 27 #include "base/time/time.h" |
| 22 #include "base/values.h" | 28 #include "base/values.h" |
| 23 #include "chromeos/printing/ppd_cache.h" | 29 #include "chromeos/printing/ppd_cache.h" |
| 30 #include "chromeos/printing/printing_constants.h" |
| 24 #include "net/base/load_flags.h" | 31 #include "net/base/load_flags.h" |
| 25 #include "net/http/http_status_code.h" | 32 #include "net/http/http_status_code.h" |
| 26 #include "net/url_request/url_fetcher.h" | 33 #include "net/url_request/url_fetcher.h" |
| 27 #include "net/url_request/url_fetcher_delegate.h" | 34 #include "net/url_request/url_fetcher_delegate.h" |
| 28 #include "net/url_request/url_request_context_getter.h" | 35 #include "net/url_request/url_request_context_getter.h" |
| 29 #include "url/gurl.h" | 36 #include "url/gurl.h" |
| 30 | 37 |
| 31 namespace chromeos { | 38 namespace chromeos { |
| 32 namespace printing { | 39 namespace printing { |
| 33 namespace { | 40 namespace { |
| 34 | 41 |
| 35 // Expected fields from the quirks server. | 42 // Returns false if there are obvious errors in the reference that will prevent |
| 36 const char kJSONPPDKey[] = "compressedPpd"; | 43 // resolution. |
| 37 const char kJSONLastUpdatedKey[] = "lastUpdatedTime"; | 44 bool PpdReferenceIsWellFormed(const Printer::PpdReference& reference) { |
| 38 const char kJSONTopListKey[] = "manufacturers"; | 45 int filled_fields = 0; |
| 39 const char kJSONManufacturer[] = "manufacturer"; | 46 if (!reference.user_supplied_ppd_url.empty()) { |
| 40 const char kJSONModelList[] = "models"; | 47 ++filled_fields; |
| 41 | 48 GURL tmp_url(reference.user_supplied_ppd_url); |
| 42 class PpdProviderImpl; | 49 if (!tmp_url.is_valid() || !tmp_url.SchemeIs("file")) { |
| 43 | 50 LOG(ERROR) << "Invalid url for a user-supplied ppd: " |
| 44 // PpdProvider handles two types of URL fetches, and so uses these delegates | 51 << reference.user_supplied_ppd_url |
| 45 // to route OnURLFetchComplete to the appropriate handler. | 52 << " (must be a file:// URL)"; |
| 46 class ResolveURLFetcherDelegate : public net::URLFetcherDelegate { | 53 return false; |
| 54 } |
| 55 } |
| 56 if (!reference.effective_make_and_model.empty()) { |
| 57 ++filled_fields; |
| 58 } |
| 59 // Should have exactly one non-empty field. |
| 60 return filled_fields == 1; |
| 61 } |
| 62 |
| 63 std::string PpdReferenceToCacheKey(const Printer::PpdReference& reference) { |
| 64 DCHECK(PpdReferenceIsWellFormed(reference)); |
| 65 // The key prefixes here are arbitrary, but ensure we can't have an (unhashed) |
| 66 // collision between keys generated from different PpdReference fields. |
| 67 if (!reference.effective_make_and_model.empty()) { |
| 68 return std::string("em:") + reference.effective_make_and_model; |
| 69 } else { |
| 70 return std::string("up:") + reference.user_supplied_ppd_url; |
| 71 } |
| 72 } |
| 73 |
| 74 struct ManufacturerMetadata { |
| 75 // Key used to look up the printer list on the server. This is initially |
| 76 // populated. |
| 77 std::string reference; |
| 78 |
| 79 // Map from localized printer name to canonical-make-and-model string for |
| 80 // the given printer. Populated on demand. |
| 81 std::unique_ptr<std::unordered_map<std::string, std::string>> printers; |
| 82 }; |
| 83 |
| 84 // A queued request to download printer information for a manufacturer. |
| 85 struct PrinterResolutionQueueEntry { |
| 86 // Localized manufacturer name |
| 87 std::string manufacturer; |
| 88 |
| 89 // URL we are going to pull from. |
| 90 GURL url; |
| 91 |
| 92 // User callback on completion. |
| 93 PpdProvider::ResolvePrintersCallback cb; |
| 94 }; |
| 95 |
| 96 class PpdProviderImpl : public PpdProvider, public net::URLFetcherDelegate { |
| 47 public: | 97 public: |
| 48 explicit ResolveURLFetcherDelegate(PpdProviderImpl* parent) | 98 // What kind of thing is the fetcher currently fetching? We use this to |
| 49 : parent_(parent) {} | 99 // determine what to do when the fetcher returns a result. |
| 50 | 100 enum FetcherTarget { |
| 51 void OnURLFetchComplete(const net::URLFetcher* source) override; | 101 FT_LOCALES, // Locales metadata. |
| 52 | 102 FT_MANUFACTURERS, // List of manufacturers metadata. |
| 53 // Link back to parent. Not owned. | 103 FT_PRINTERS, // List of printers from a manufacturer. |
| 54 PpdProviderImpl* const parent_; | 104 FT_PPD_INDEX, // Master ppd index. |
| 55 }; | 105 FT_PPD // A Ppd file. |
| 56 | 106 }; |
| 57 class QueryAvailableURLFetcherDelegate : public net::URLFetcherDelegate { | 107 |
| 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( | 108 PpdProviderImpl( |
| 92 const std::string& api_key, | 109 const std::string& browser_locale, |
| 93 scoped_refptr<net::URLRequestContextGetter> url_context_getter, | 110 scoped_refptr<net::URLRequestContextGetter> url_context_getter, |
| 94 scoped_refptr<base::SequencedTaskRunner> io_task_runner, | 111 scoped_refptr<PpdCache> ppd_cache, |
| 95 std::unique_ptr<PpdCache> cache, | 112 scoped_refptr<base::SequencedTaskRunner> disk_task_runner, |
| 96 const PpdProvider::Options& options) | 113 const PpdProvider::Options& options) |
| 97 : api_key_(api_key), | 114 : browser_locale_(browser_locale), |
| 98 url_context_getter_(url_context_getter), | 115 url_context_getter_(url_context_getter), |
| 99 io_task_runner_(io_task_runner), | 116 ppd_cache_(ppd_cache), |
| 100 cache_(std::move(cache)), | 117 disk_task_runner_(disk_task_runner), |
| 101 options_(options), | 118 options_(options), |
| 102 resolve_delegate_(this), | 119 weak_factory_(this) {} |
| 103 query_available_delegate_(this), | 120 |
| 104 weak_factory_(this) { | 121 // Resolving manufacturers requires a couple of steps, because of |
| 105 CHECK_GT(options_.max_ppd_contents_size_, 0U); | 122 // localization. First we have to figure out what locale to use, which |
| 106 } | 123 // involves grabbing a list of available locales from the server. Once we |
| 107 ~PpdProviderImpl() override {} | 124 // have decided on a locale, we go out and fetch the manufacturers map in that |
| 108 | 125 // localization. |
| 109 void Resolve(const Printer::PpdReference& ppd_reference, | 126 // |
| 110 const PpdProvider::ResolveCallback& cb) override { | 127 // This means when a request comes in, we either queue it and start background |
| 111 CHECK(!cb.is_null()); | 128 // fetches if necessary, or we satisfy it immediately from memory. |
| 129 void ResolveManufacturers(const ResolveManufacturersCallback& cb) override { |
| 112 CHECK(base::SequencedTaskRunnerHandle::IsSet()) | 130 CHECK(base::SequencedTaskRunnerHandle::IsSet()) |
| 113 << "Resolve must be called from a SequencedTaskRunner context"; | 131 << "ResolveManufacturers must be called from a SequencedTaskRunner" |
| 114 auto cache_result = base::MakeUnique<base::Optional<base::FilePath>>(); | 132 "context"; |
| 115 auto* raw_cache_result_ptr = cache_result.get(); | 133 if (cached_metadata_.get() != nullptr) { |
| 116 bool post_result = io_task_runner_->PostTaskAndReply( | 134 // We already have this in memory. |
| 117 FROM_HERE, base::Bind(&PpdProviderImpl::ResolveDoCacheLookup, | 135 base::SequencedTaskRunnerHandle::Get()->PostTask( |
| 118 weak_factory_.GetWeakPtr(), ppd_reference, | 136 FROM_HERE, |
| 119 raw_cache_result_ptr), | 137 base::Bind(cb, PpdProvider::SUCCESS, GetManufacturerList())); |
| 120 base::Bind(&PpdProviderImpl::ResolveCacheLookupDone, | 138 return; |
| 121 weak_factory_.GetWeakPtr(), ppd_reference, cb, | 139 } |
| 122 base::Passed(&cache_result))); | 140 manufacturers_resolution_queue_.push_back(cb); |
| 123 DCHECK(post_result); | 141 MaybeStartFetch(); |
| 124 } | 142 } |
| 125 | 143 |
| 126 void QueryAvailable(const QueryAvailableCallback& cb) override { | 144 // If there is work outstanding that requires a URL fetch to complete, start |
| 127 CHECK(!cb.is_null()); | 145 // going on it. |
| 128 CHECK(base::SequencedTaskRunnerHandle::IsSet()) | 146 void MaybeStartFetch() { |
| 129 << "QueryAvailable() must be called from a SequencedTaskRunner context"; | 147 if (fetch_inflight_) { |
| 130 auto cache_result = | 148 // We'll call this again when the outstanding fetch completes. |
| 131 base::MakeUnique<base::Optional<PpdProvider::AvailablePrintersMap>>(); | 149 return; |
| 132 auto* raw_cache_result_ptr = cache_result.get(); | 150 } |
| 133 bool post_result = io_task_runner_->PostTaskAndReply( | 151 |
| 134 FROM_HERE, base::Bind(&PpdProviderImpl::QueryAvailableDoCacheLookup, | 152 if (!manufacturers_resolution_queue_.empty()) { |
| 135 weak_factory_.GetWeakPtr(), raw_cache_result_ptr), | 153 if (locale_.empty()) { |
| 136 base::Bind(&PpdProviderImpl::QueryAvailableCacheLookupDone, | 154 // Don't have a locale yet, figure that out first. |
| 137 weak_factory_.GetWeakPtr(), cb, | 155 StartFetch(GetLocalesURL(), FT_LOCALES); |
| 138 base::Passed(&cache_result))); | 156 } else { |
| 139 DCHECK(post_result); | 157 // Get manufacturers based on the locale we have. |
| 140 } | 158 StartFetch(GetManufacturersURL(locale_), FT_MANUFACTURERS); |
| 141 | 159 } |
| 142 bool CachePpd(const Printer::PpdReference& ppd_reference, | 160 return; |
| 143 const base::FilePath& ppd_path) override { | 161 } |
| 144 std::string buf; | 162 if (!printers_resolution_queue_.empty()) { |
| 145 if (!base::ReadFileToStringWithMaxSize(ppd_path, &buf, | 163 StartFetch(printers_resolution_queue_.front().url, FT_PRINTERS); |
| 146 options_.max_ppd_contents_size_)) { | 164 return; |
| 165 } |
| 166 while (!ppd_resolution_queue_.empty()) { |
| 167 const auto& next = ppd_resolution_queue_.front(); |
| 168 if (!next.first.user_supplied_ppd_url.empty()) { |
| 169 DCHECK(next.first.effective_make_and_model.empty()); |
| 170 GURL url(next.first.user_supplied_ppd_url); |
| 171 DCHECK(url.is_valid()); |
| 172 StartFetch(url, FT_PPD); |
| 173 return; |
| 174 } |
| 175 DCHECK(!next.first.effective_make_and_model.empty()); |
| 176 if (cached_ppd_index_.get() == nullptr) { |
| 177 // Have to have the ppd index before we can resolve by ppd server |
| 178 // key. |
| 179 StartFetch(GetPpdIndexURL(), FT_PPD_INDEX); |
| 180 return; |
| 181 } |
| 182 // Get the URL from the ppd index and start the fetch. |
| 183 auto it = cached_ppd_index_->find(next.first.effective_make_and_model); |
| 184 if (it != cached_ppd_index_->end()) { |
| 185 StartFetch(GetPpdURL(it->second), FT_PPD); |
| 186 return; |
| 187 } |
| 188 // This ppd reference isn't in the index. That's not good. Fail |
| 189 // out the current resolution and go try to start the next |
| 190 // thing if there is one. |
| 191 LOG(ERROR) << "PPD " << next.first.effective_make_and_model |
| 192 << " not found in server index"; |
| 193 base::SequencedTaskRunnerHandle::Get()->PostTask( |
| 194 FROM_HERE, |
| 195 base::Bind(next.second, PpdProvider::INTERNAL_ERROR, std::string())); |
| 196 ppd_resolution_queue_.pop_front(); |
| 197 } |
| 198 } |
| 199 |
| 200 void ResolvePrinters(const std::string& manufacturer, |
| 201 const ResolvePrintersCallback& cb) override { |
| 202 std::unordered_map<std::string, ManufacturerMetadata>::iterator it; |
| 203 if (cached_metadata_.get() == nullptr || |
| 204 (it = cached_metadata_->find(manufacturer)) == |
| 205 cached_metadata_->end()) { |
| 206 // User error. |
| 207 LOG(ERROR) << "Can't resolve printers for unknown manufacturer " |
| 208 << manufacturer; |
| 209 base::SequencedTaskRunnerHandle::Get()->PostTask( |
| 210 FROM_HERE, base::Bind(cb, PpdProvider::INTERNAL_ERROR, |
| 211 std::vector<std::string>())); |
| 212 return; |
| 213 } |
| 214 if (it->second.printers.get() != nullptr) { |
| 215 // Satisfy from the cache. |
| 216 base::SequencedTaskRunnerHandle::Get()->PostTask( |
| 217 FROM_HERE, base::Bind(cb, PpdProvider::SUCCESS, |
| 218 GetManufacturerPrinterList(it->second))); |
| 219 } else { |
| 220 // We haven't resolved this manufacturer yet. |
| 221 PrinterResolutionQueueEntry entry; |
| 222 entry.manufacturer = manufacturer; |
| 223 entry.url = GetPrintersURL(it->second.reference); |
| 224 entry.cb = cb; |
| 225 printers_resolution_queue_.push_back(entry); |
| 226 MaybeStartFetch(); |
| 227 } |
| 228 } |
| 229 |
| 230 bool GetPpdReference(const std::string& manufacturer, |
| 231 const std::string& printer, |
| 232 Printer::PpdReference* reference) const override { |
| 233 std::unordered_map<std::string, ManufacturerMetadata>::iterator top_it; |
| 234 if (cached_metadata_.get() == nullptr) { |
| 147 return false; | 235 return false; |
| 148 } | 236 } |
| 149 return static_cast<bool>(cache_->Store(ppd_reference, buf)); | 237 auto it = cached_metadata_->find(manufacturer); |
| 150 } | 238 if (it == cached_metadata_->end() || it->second.printers.get() == nullptr) { |
| 151 | 239 return false; |
| 152 // Called on the same thread as Resolve() when the fetcher completes its | 240 } |
| 153 // fetch. | 241 const auto& printers_map = *it->second.printers; |
| 154 void OnResolveFetchComplete(const net::URLFetcher* source) { | 242 auto it2 = printers_map.find(printer); |
| 155 std::unique_ptr<ResolveFetchData> fetch_data = GetResolveFetchData(source); | 243 if (it2 == printers_map.end()) { |
| 156 std::string ppd_contents; | 244 return false; |
| 157 uint64_t last_updated_time; | 245 } |
| 158 if (!ExtractResolveResponseData(source, fetch_data.get(), &ppd_contents, | 246 *reference = Printer::PpdReference(); |
| 159 &last_updated_time)) { | 247 reference->effective_make_and_model = it2->second; |
| 160 fetch_data->done_callback.Run(PpdProvider::SERVER_ERROR, | 248 return true; |
| 161 base::FilePath()); | 249 } |
| 162 return; | 250 |
| 163 } | 251 void ResolvePpd(const Printer::PpdReference& reference, |
| 164 | 252 const ResolvePpdCallback& cb) override { |
| 165 auto ppd_file = cache_->Store(fetch_data->ppd_reference, ppd_contents); | 253 // Do a sanity check here, so we can assumed |reference| is well-formed in |
| 166 if (!ppd_file) { | 254 // the rest of this class. |
| 167 // Failed to store. | 255 if (!PpdReferenceIsWellFormed(reference)) { |
| 168 fetch_data->done_callback.Run(PpdProvider::INTERNAL_ERROR, | 256 base::SequencedTaskRunnerHandle::Get()->PostTask( |
| 169 base::FilePath()); | 257 FROM_HERE, base::Bind(cb, PpdProvider::INTERNAL_ERROR, "")); |
| 170 return; | 258 return; |
| 171 } | 259 } |
| 172 fetch_data->done_callback.Run(PpdProvider::SUCCESS, ppd_file.value()); | 260 // First step, check the cache. If the cache lookup fails, we'll (try to) |
| 173 } | 261 // consult the server. |
| 174 | 262 ppd_cache_->Find(PpdReferenceToCacheKey(reference), |
| 175 // Called on the same thread as QueryAvailable() when the cache lookup is | 263 base::Bind(&PpdProviderImpl::ResolvePpdCacheLookupDone, |
| 176 // done. If the cache satisfied the request, finish the Query, otherwise | 264 weak_factory_.GetWeakPtr(), reference, cb)); |
| 177 // start a URL request to satisfy the Query. This runs on the same thread as | 265 } |
| 178 // QueryAvailable() was invoked on. | 266 |
| 179 void QueryAvailableCacheLookupDone( | 267 // Our only sources of long running ops are cache fetches and network fetches. |
| 180 const PpdProvider::QueryAvailableCallback& done_callback, | 268 bool Idle() const override { return ppd_cache_->Idle() && !fetch_inflight_; } |
| 181 std::unique_ptr<base::Optional<PpdProvider::AvailablePrintersMap>> | 269 |
| 182 cache_result) { | 270 // Common handler that gets called whenever a fetch completes. Note this |
| 183 if (*cache_result) { | 271 // is used both for |fetcher_| fetches (i.e. http[s]) and file-based fetches; |
| 184 done_callback.Run(PpdProvider::SUCCESS, cache_result->value()); | 272 // |source| may be null in the latter case. |
| 185 return; | 273 void OnURLFetchComplete(const net::URLFetcher* source) override { |
| 186 } | 274 switch (fetcher_target_) { |
| 187 | 275 case FT_LOCALES: |
| 188 auto fetch_data = base::MakeUnique<QueryAvailableFetchData>(); | 276 OnLocalesFetchComplete(); |
| 189 fetch_data->done_callback = done_callback; | 277 break; |
| 190 | 278 case FT_MANUFACTURERS: |
| 191 fetch_data->fetcher = net::URLFetcher::Create(GetQuirksServerPpdListURL(), | 279 OnManufacturersFetchComplete(); |
| 192 net::URLFetcher::GET, | 280 break; |
| 193 &query_available_delegate_); | 281 case FT_PRINTERS: |
| 194 fetch_data->fetcher->SetRequestContext(url_context_getter_.get()); | 282 OnPrintersFetchComplete(); |
| 195 fetch_data->fetcher->SetLoadFlags( | 283 break; |
| 196 net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE | | 284 case FT_PPD_INDEX: |
| 197 net::LOAD_DO_NOT_SAVE_COOKIES | net::LOAD_DO_NOT_SEND_COOKIES | | 285 OnPpdIndexFetchComplete(); |
| 198 net::LOAD_DO_NOT_SEND_AUTH_DATA); | 286 break; |
| 199 auto* fetcher = fetch_data->fetcher.get(); | 287 case FT_PPD: |
| 200 StoreQueryAvailableFetchData(std::move(fetch_data)); | 288 OnPpdFetchComplete(); |
| 201 fetcher->Start(); | 289 break; |
| 202 } | 290 default: |
| 203 | 291 LOG(DFATAL) << "Unknown fetch source"; |
| 204 void OnQueryAvailableFetchComplete(const net::URLFetcher* source) { | 292 } |
| 205 std::unique_ptr<QueryAvailableFetchData> fetch_data = | 293 fetch_inflight_ = false; |
| 206 GetQueryAvailableFetchData(source); | 294 MaybeStartFetch(); |
| 207 | 295 } |
| 296 |
| 297 private: |
| 298 // Return the URL used to look up the supported locales list. |
| 299 GURL GetLocalesURL() { |
| 300 return GURL(options_.ppd_server_root + "/metadata/locales.json"); |
| 301 } |
| 302 |
| 303 // Return the URL used to get the index of ppd server key -> ppd. |
| 304 GURL GetPpdIndexURL() { |
| 305 return GURL(options_.ppd_server_root + "/metadata/index.json"); |
| 306 } |
| 307 |
| 308 // Return the URL to get a localized manufacturers map. |
| 309 GURL GetManufacturersURL(const std::string& locale) { |
| 310 return GURL(base::StringPrintf("%s/metadata/manufacturers-%s.json", |
| 311 options_.ppd_server_root.c_str(), |
| 312 locale.c_str())); |
| 313 } |
| 314 |
| 315 // Return the URL used to get a list of printers from the manufacturer |ref|. |
| 316 GURL GetPrintersURL(const std::string& ref) { |
| 317 return GURL(base::StringPrintf( |
| 318 "%s/metadata/%s", options_.ppd_server_root.c_str(), ref.c_str())); |
| 319 } |
| 320 |
| 321 // Return the URL used to get a ppd with the given filename. |
| 322 GURL GetPpdURL(const std::string& filename) { |
| 323 return GURL(base::StringPrintf( |
| 324 "%s/ppds/%s", options_.ppd_server_root.c_str(), filename.c_str())); |
| 325 } |
| 326 |
| 327 // Create and return a fetcher that has the usual (for this class) flags set |
| 328 // and calls back to OnURLFetchComplete in this class when it finishes. |
| 329 void StartFetch(const GURL& url, FetcherTarget target) { |
| 330 DCHECK(!fetch_inflight_); |
| 331 DCHECK_EQ(fetcher_.get(), nullptr); |
| 332 fetcher_target_ = target; |
| 333 fetch_inflight_ = true; |
| 334 |
| 335 if (url.SchemeIs("http") || url.SchemeIs("https")) { |
| 336 fetcher_ = net::URLFetcher::Create(url, net::URLFetcher::GET, this); |
| 337 fetcher_->SetRequestContext(url_context_getter_.get()); |
| 338 fetcher_->SetLoadFlags(net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE | |
| 339 net::LOAD_DO_NOT_SAVE_COOKIES | |
| 340 net::LOAD_DO_NOT_SEND_COOKIES | |
| 341 net::LOAD_DO_NOT_SEND_AUTH_DATA); |
| 342 fetcher_->Start(); |
| 343 } else if (url.SchemeIs("file")) { |
| 344 disk_task_runner_->PostTaskAndReply( |
| 345 FROM_HERE, base::Bind(&PpdProviderImpl::FetchFile, this, url), |
| 346 base::Bind(&PpdProviderImpl::OnURLFetchComplete, this, nullptr)); |
| 347 } |
| 348 } |
| 349 |
| 350 // Fetch the file pointed at by url and store it in |file_fetch_contents_|. |
| 351 // Use |file_fetch_success_| to indicate success or failure. |
| 352 void FetchFile(const GURL& url) { |
| 353 CHECK(url.is_valid()); |
| 354 CHECK(url.SchemeIs("file")); |
| 355 base::ThreadRestrictions::AssertIOAllowed(); |
| 356 file_fetch_success_ = base::ReadFileToString(base::FilePath(url.path()), |
| 357 &file_fetch_contents_); |
| 358 } |
| 359 |
| 360 // Callback when the cache lookup for a ppd request finishes. If we hit in |
| 361 // the cache, satisfy the resolution, otherwise kick it over to the fetcher |
| 362 // queue to be grabbed from a server. |
| 363 void ResolvePpdCacheLookupDone(const Printer::PpdReference& reference, |
| 364 const ResolvePpdCallback& cb, |
| 365 const PpdCache::FindResult& result) { |
| 366 if (result.success) { |
| 367 // Cache hit. |
| 368 base::SequencedTaskRunnerHandle::Get()->PostTask( |
| 369 FROM_HERE, base::Bind(cb, PpdProvider::SUCCESS, result.contents)); |
| 370 } else { |
| 371 // Cache miss. Queue it to be satisfied by the fetcher queue. |
| 372 ppd_resolution_queue_.push_back({reference, cb}); |
| 373 MaybeStartFetch(); |
| 374 } |
| 375 } |
| 376 |
| 377 // Handler for the completion of the locales fetch. This response should be a |
| 378 // list of strings, each of which is a locale in which we can answer queries |
| 379 // on the server. The server (should) guarantee that we get 'en' as an |
| 380 // absolute minimum. |
| 381 // |
| 382 // Combine this information with the browser locale to figure out the best |
| 383 // locale to use, and then start a fetch of the manufacturers map in that |
| 384 // locale. |
| 385 void OnLocalesFetchComplete() { |
| 208 std::string contents; | 386 std::string contents; |
| 209 if (!ValidateAndGetResponseAsString(*source, &contents)) { | 387 if (!ValidateAndGetResponseAsString(&contents)) { |
| 210 // Something went wrong with the fetch. | 388 FailQueuedMetadataResolutions(PpdProvider::SERVER_ERROR); |
| 211 fetch_data->done_callback.Run(PpdProvider::SERVER_ERROR, | 389 return; |
| 212 AvailablePrintersMap()); | 390 } |
| 213 return; | 391 auto top_list = base::ListValue::From(base::JSONReader::Read(contents)); |
| 214 } | 392 |
| 215 | 393 if (top_list.get() == nullptr) { |
| 216 // The server gives us JSON in the form of a list of (manufacturer, list of | 394 // We got something malformed back. |
| 217 // models) tuples. | 395 FailQueuedMetadataResolutions(PpdProvider::INTERNAL_ERROR); |
| 218 auto top_dict = | 396 return; |
| 219 base::DictionaryValue::From(base::JSONReader::Read(contents)); | 397 } |
| 220 const base::ListValue* top_list; | 398 |
| 221 if (top_dict == nullptr || !top_dict->GetList(kJSONTopListKey, &top_list)) { | 399 // This should just be a simple list of locale strings. |
| 222 LOG(ERROR) << "Malformed response from quirks server"; | 400 std::vector<std::string> available_locales; |
| 223 fetch_data->done_callback.Run(PpdProvider::SERVER_ERROR, | 401 bool found_en = false; |
| 224 AvailablePrintersMap()); | |
| 225 return; | |
| 226 } | |
| 227 | |
| 228 auto result = base::MakeUnique<PpdProvider::AvailablePrintersMap>(); | |
| 229 for (const std::unique_ptr<base::Value>& entry : *top_list) { | 402 for (const std::unique_ptr<base::Value>& entry : *top_list) { |
| 230 base::DictionaryValue* dict; | 403 std::string tmp; |
| 231 std::string manufacturer; | 404 // Locales should have at *least* a two-character country code. 100 is an |
| 232 std::string model; | 405 // arbitrary upper bound for length to protect against extreme bogosity. |
| 233 base::ListValue* model_list; | 406 if (!entry->GetAsString(&tmp) || tmp.size() < 2 || tmp.size() > 100) { |
| 234 if (!entry->GetAsDictionary(&dict) || | 407 FailQueuedMetadataResolutions(PpdProvider::INTERNAL_ERROR); |
| 235 !dict->GetString(kJSONManufacturer, &manufacturer) || | 408 return; |
| 236 !dict->GetList(kJSONModelList, &model_list)) { | 409 } |
| 237 LOG(ERROR) << "Unexpected contents in quirks server printer list."; | 410 if (tmp == "en") { |
| 238 // Just skip this entry instead of aborting the whole thing. | 411 found_en = true; |
| 239 continue; | 412 } |
| 240 } | 413 available_locales.push_back(tmp); |
| 241 | 414 } |
| 242 std::vector<std::string>& dest = (*result)[manufacturer]; | 415 if (available_locales.empty() || !found_en) { |
| 243 for (const std::unique_ptr<base::Value>& model_value : *model_list) { | 416 // We have no locales, or we didn't get an english locale (which is our |
| 244 if (model_value->GetAsString(&model)) { | 417 // ultimate fallback) |
| 245 dest.push_back(model); | 418 FailQueuedMetadataResolutions(PpdProvider::INTERNAL_ERROR); |
| 246 } else { | 419 return; |
| 247 LOG(ERROR) << "Skipping unknown model for manufacturer " | 420 } |
| 248 << manufacturer; | 421 // Everything checks out, set the locale, head back to fetch dispatch |
| 422 // to start the manufacturer fetch. |
| 423 locale_ = GetBestLocale(available_locales); |
| 424 } |
| 425 |
| 426 // Called when the |fetcher_| is expected have the results of a |
| 427 // manufacturer map (which maps localized manufacturer names to keys for |
| 428 // looking up printers from that manufacturer). Use this information to |
| 429 // populate manufacturer_map_, and resolve all queued ResolveManufacturers() |
| 430 // calls. |
| 431 void OnManufacturersFetchComplete() { |
| 432 DCHECK_EQ(nullptr, cached_metadata_.get()); |
| 433 std::vector<std::pair<std::string, std::string>> contents; |
| 434 PpdProvider::CallbackResultCode code = |
| 435 ValidateAndParseJSONResponse(&contents); |
| 436 if (code != PpdProvider::SUCCESS) { |
| 437 LOG(ERROR) << "Failed manufacturer parsing"; |
| 438 FailQueuedMetadataResolutions(code); |
| 439 return; |
| 440 } |
| 441 cached_metadata_ = base::MakeUnique< |
| 442 std::unordered_map<std::string, ManufacturerMetadata>>(); |
| 443 |
| 444 for (const auto& entry : contents) { |
| 445 ManufacturerMetadata value; |
| 446 value.reference = entry.second; |
| 447 (*cached_metadata_)[entry.first].reference = entry.second; |
| 448 } |
| 449 |
| 450 std::vector<std::string> manufacturer_list = GetManufacturerList(); |
| 451 // Complete any queued manufacturer resolutions. |
| 452 for (const auto& cb : manufacturers_resolution_queue_) { |
| 453 base::SequencedTaskRunnerHandle::Get()->PostTask( |
| 454 FROM_HERE, base::Bind(cb, PpdProvider::SUCCESS, manufacturer_list)); |
| 455 } |
| 456 manufacturers_resolution_queue_.clear(); |
| 457 } |
| 458 |
| 459 // The outstanding fetch associated with the front of |
| 460 // |printers_resolution_queue_| finished, use the response to satisfy that |
| 461 // ResolvePrinters() call. |
| 462 void OnPrintersFetchComplete() { |
| 463 CHECK(cached_metadata_.get() != nullptr); |
| 464 DCHECK(!printers_resolution_queue_.empty()); |
| 465 std::vector<std::pair<std::string, std::string>> contents; |
| 466 PpdProvider::CallbackResultCode code = |
| 467 ValidateAndParseJSONResponse(&contents); |
| 468 if (code != PpdProvider::SUCCESS) { |
| 469 base::SequencedTaskRunnerHandle::Get()->PostTask( |
| 470 FROM_HERE, base::Bind(printers_resolution_queue_.front().cb, code, |
| 471 std::vector<std::string>())); |
| 472 } else { |
| 473 // This should be a list of lists of 2-element strings, where the first |
| 474 // element is the localized name of the printer and the second element |
| 475 // is the canonical name of the printer. |
| 476 const std::string& manufacturer = |
| 477 printers_resolution_queue_.front().manufacturer; |
| 478 auto it = cached_metadata_->find(manufacturer); |
| 479 |
| 480 // If we kicked off a resolution, the entry better have already been |
| 481 // in the map. |
| 482 CHECK(it != cached_metadata_->end()); |
| 483 |
| 484 // Create the printer map in the cache, and populate it. |
| 485 auto& manufacturer_metadata = it->second; |
| 486 CHECK(manufacturer_metadata.printers.get() == nullptr); |
| 487 manufacturer_metadata.printers = |
| 488 base::MakeUnique<std::unordered_map<std::string, std::string>>(); |
| 489 |
| 490 for (const auto& entry : contents) { |
| 491 manufacturer_metadata.printers->insert({entry.first, entry.second}); |
| 492 } |
| 493 base::SequencedTaskRunnerHandle::Get()->PostTask( |
| 494 FROM_HERE, |
| 495 base::Bind(printers_resolution_queue_.front().cb, |
| 496 PpdProvider::SUCCESS, |
| 497 GetManufacturerPrinterList(manufacturer_metadata))); |
| 498 } |
| 499 printers_resolution_queue_.pop_front(); |
| 500 } |
| 501 |
| 502 // Called when |fetcher_| should have just received the index mapping |
| 503 // ppd server keys to ppd filenames. Use this to populate |
| 504 // |cached_ppd_index_|. |
| 505 void OnPpdIndexFetchComplete() { |
| 506 std::vector<std::pair<std::string, std::string>> contents; |
| 507 PpdProvider::CallbackResultCode code = |
| 508 ValidateAndParseJSONResponse(&contents); |
| 509 if (code != PpdProvider::SUCCESS) { |
| 510 FailQueuedServerPpdResolutions(code); |
| 511 } else { |
| 512 cached_ppd_index_ = |
| 513 base::MakeUnique<std::unordered_map<std::string, std::string>>(); |
| 514 // This should be a list of lists of 2-element strings, where the first |
| 515 // element is the |effective_make_and_model| of the printer and the second |
| 516 // element is the filename of the ppd in the ppds/ directory on the |
| 517 // server. |
| 518 for (const auto& entry : contents) { |
| 519 cached_ppd_index_->insert(entry); |
| 520 } |
| 521 } |
| 522 } |
| 523 |
| 524 // This is called when |fetcher_| should have just downloaded a ppd. If we |
| 525 // downloaded something successfully, use it to satisfy the front of the ppd |
| 526 // resolution queue, otherwise fail out that resolution. |
| 527 void OnPpdFetchComplete() { |
| 528 DCHECK(!ppd_resolution_queue_.empty()); |
| 529 std::string contents; |
| 530 if (!ValidateAndGetResponseAsString(&contents) || |
| 531 contents.size() > kMaxPpdSizeBytes) { |
| 532 base::SequencedTaskRunnerHandle::Get()->PostTask( |
| 533 FROM_HERE, base::Bind(ppd_resolution_queue_.front().second, |
| 534 PpdProvider::SERVER_ERROR, std::string())); |
| 535 } else { |
| 536 ppd_cache_->Store( |
| 537 PpdReferenceToCacheKey(ppd_resolution_queue_.front().first), contents, |
| 538 base::Callback<void()>()); |
| 539 base::SequencedTaskRunnerHandle::Get()->PostTask( |
| 540 FROM_HERE, base::Bind(ppd_resolution_queue_.front().second, |
| 541 PpdProvider::SUCCESS, contents)); |
| 542 } |
| 543 ppd_resolution_queue_.pop_front(); |
| 544 } |
| 545 |
| 546 // Something went wrong during metadata fetches. Fail all queued metadata |
| 547 // resolutions with the given error code. |
| 548 void FailQueuedMetadataResolutions(PpdProvider::CallbackResultCode code) { |
| 549 for (const auto& cb : manufacturers_resolution_queue_) { |
| 550 base::SequencedTaskRunnerHandle::Get()->PostTask( |
| 551 FROM_HERE, base::Bind(cb, code, std::vector<std::string>())); |
| 552 } |
| 553 manufacturers_resolution_queue_.clear(); |
| 554 } |
| 555 |
| 556 // Fail all server-based ppd resolutions inflight, because we failed to grab |
| 557 // the necessary index data from the server. Note we leave any user-based ppd |
| 558 // resolutions intact, as they don't depend on the data we're missing. |
| 559 void FailQueuedServerPpdResolutions(PpdProvider::CallbackResultCode code) { |
| 560 std::deque<std::pair<Printer::PpdReference, ResolvePpdCallback>> |
| 561 filtered_queue; |
| 562 |
| 563 for (const auto& entry : ppd_resolution_queue_) { |
| 564 if (!entry.first.user_supplied_ppd_url.empty()) { |
| 565 filtered_queue.push_back(entry); |
| 566 } else { |
| 567 base::SequencedTaskRunnerHandle::Get()->PostTask( |
| 568 FROM_HERE, base::Bind(entry.second, code, std::string())); |
| 569 } |
| 570 } |
| 571 ppd_resolution_queue_ = std::move(filtered_queue); |
| 572 } |
| 573 |
| 574 // Given a list of possible locale strings (e.g. 'en-GB'), determine which of |
| 575 // them we should use to best serve results for the browser locale (which was |
| 576 // given to us at construction time). |
| 577 std::string GetBestLocale(const std::vector<std::string>& available_locales) { |
| 578 // First look for an exact match. If we find one, just use that. |
| 579 for (const std::string& available : available_locales) { |
| 580 if (available == browser_locale_) { |
| 581 return available; |
| 582 } |
| 583 } |
| 584 |
| 585 // Next, look for an available locale that is a parent of browser_locale_. |
| 586 // Return the most specific one. For example, if we want 'en-GB-foo' and we |
| 587 // don't have an exact match, but we do have 'en-GB' and 'en', we will |
| 588 // return 'en-GB' -- the most specific match which is a parent of the |
| 589 // requested locale. |
| 590 size_t best_len = 0; |
| 591 size_t best_idx = -1; |
| 592 for (size_t i = 0; i < available_locales.size(); ++i) { |
| 593 const std::string& available = available_locales[i]; |
| 594 if (base::StringPiece(browser_locale_).starts_with(available + "-") && |
| 595 available.size() > best_len) { |
| 596 best_len = available.size(); |
| 597 best_idx = i; |
| 598 } |
| 599 } |
| 600 if (best_idx != static_cast<size_t>(-1)) { |
| 601 return available_locales[best_idx]; |
| 602 } |
| 603 |
| 604 // Last chance for a match, look for the locale that matches the *most* |
| 605 // pieces of locale_, with ties broken by being least specific. So for |
| 606 // example, if we have 'es-GB', 'es-GB-foo' but no 'es' available, and we're |
| 607 // requesting something for 'es', we'll get back 'es-GB' -- the least |
| 608 // specific thing that matches some of the locale. |
| 609 std::vector<base::StringPiece> browser_locale_pieces = |
| 610 base::SplitStringPiece(browser_locale_, "-", base::KEEP_WHITESPACE, |
| 611 base::SPLIT_WANT_ALL); |
| 612 size_t best_match_size = 0; |
| 613 size_t best_match_specificity; |
| 614 best_idx = -1; |
| 615 for (size_t i = 0; i < available_locales.size(); ++i) { |
| 616 const std::string& available = available_locales[i]; |
| 617 std::vector<base::StringPiece> available_pieces = base::SplitStringPiece( |
| 618 available, "-", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL); |
| 619 size_t match_size = 0; |
| 620 for (; match_size < available_pieces.size() && |
| 621 match_size < browser_locale_pieces.size(); |
| 622 ++match_size) { |
| 623 if (available_pieces[match_size] != browser_locale_pieces[match_size]) { |
| 624 break; |
| 249 } | 625 } |
| 250 } | 626 } |
| 251 } | 627 if (match_size > 0 && |
| 252 fetch_data->done_callback.Run(PpdProvider::SUCCESS, *result); | 628 (best_idx == static_cast<size_t>(-1) || |
| 253 if (!result->empty()) { | 629 match_size > best_match_size || |
| 254 cache_->StoreAvailablePrinters(std::move(result)); | 630 (match_size == best_match_size && |
| 631 available_pieces.size() < best_match_specificity))) { |
| 632 best_idx = i; |
| 633 best_match_size = match_size; |
| 634 best_match_specificity = available_pieces.size(); |
| 635 } |
| 636 } |
| 637 if (best_idx != static_cast<size_t>(-1)) { |
| 638 return available_locales[best_idx]; |
| 639 } |
| 640 |
| 641 // Everything else failed. Throw up our hands and default to english. |
| 642 return "en"; |
| 643 } |
| 644 |
| 645 // Get the results of a fetch. This is a little tricky, because a fetch |
| 646 // may have been done by |fetcher_|, or it may have been a file access, in |
| 647 // which case we want to look at |file_fetch_contents_|. We distinguish |
| 648 // between the cases based on whether or not |fetcher_| is null. |
| 649 bool ValidateAndGetResponseAsString(std::string* contents) { |
| 650 bool ret; |
| 651 if (fetcher_.get() != nullptr) { |
| 652 ret = |
| 653 ((fetcher_->GetStatus().status() == net::URLRequestStatus::SUCCESS) && |
| 654 (fetcher_->GetResponseCode() == net::HTTP_OK) && |
| 655 fetcher_->GetResponseAsString(contents)); |
| 656 fetcher_.reset(); |
| 255 } else { | 657 } else { |
| 256 // An empty map means something is probably wrong; if we cache this map, | 658 // It's a file load. |
| 257 // we'll have an empty map until the cache expires. So complain and | 659 if (file_fetch_success_) { |
| 258 // refuse to cache. | 660 *contents = file_fetch_contents_; |
| 259 LOG(ERROR) << "Available printers map is unexpectedly empty. Refusing " | 661 } else { |
| 260 "to cache this."; | 662 contents->clear(); |
| 261 } | 663 } |
| 262 } | 664 ret = file_fetch_success_; |
| 263 | 665 file_fetch_contents_.clear(); |
| 264 private: | 666 } |
| 265 void StoreResolveFetchData(std::unique_ptr<ResolveFetchData> fetch_data) { | |
| 266 auto raw_ptr = fetch_data->fetcher.get(); | |
| 267 base::AutoLock lock(resolve_fetches_lock_); | |
| 268 resolve_fetches_[raw_ptr] = std::move(fetch_data); | |
| 269 } | |
| 270 | |
| 271 void StoreQueryAvailableFetchData( | |
| 272 std::unique_ptr<QueryAvailableFetchData> fetch_data) { | |
| 273 auto raw_ptr = fetch_data->fetcher.get(); | |
| 274 base::AutoLock lock(query_available_fetches_lock_); | |
| 275 query_available_fetches_[raw_ptr] = std::move(fetch_data); | |
| 276 } | |
| 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; | 667 return ret; |
| 327 } | 668 } |
| 328 | 669 |
| 329 // Return the QueryAvailableFetchData associated with |source|. | 670 // Many of our metadata fetches happens to be in the form of a JSON |
| 330 std::unique_ptr<QueryAvailableFetchData> GetQueryAvailableFetchData( | 671 // list-of-lists-of-2-strings. So this just attempts to parse a JSON reply to |
| 331 const net::URLFetcher* source) { | 672 // |fetcher| into the passed contents vector. A return code of SUCCESS means |
| 332 base::AutoLock lock(query_available_fetches_lock_); | 673 // the JSON was formatted as expected and we've parsed it into |contents|. On |
| 333 auto it = query_available_fetches_.find(source); | 674 // error the contents of |contents| cleared. |
| 334 CHECK(it != query_available_fetches_.end()) << "Fetch data not found!"; | 675 PpdProvider::CallbackResultCode ValidateAndParseJSONResponse( |
| 335 auto fetch_data = std::move(it->second); | 676 std::vector<std::pair<std::string, std::string>>* contents) { |
| 336 query_available_fetches_.erase(it); | 677 contents->clear(); |
| 337 return fetch_data; | 678 std::string buffer; |
| 338 } | 679 if (!ValidateAndGetResponseAsString(&buffer)) { |
| 339 | 680 return PpdProvider::SERVER_ERROR; |
| 340 // Trivial wrappers around PpdCache::Find() and | 681 } |
| 341 // PpdCache::FindAvailablePrinters(). We need these wrappers both because we | 682 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 | 683 |
| 343 // *this*, and because weak_ptr's preclude return values in posted tasks, so | 684 if (top_list.get() == nullptr) { |
| 344 // we have to use a parameter to return state. | 685 // We got something malformed back. |
| 345 void ResolveDoCacheLookup( | 686 return PpdProvider::INTERNAL_ERROR; |
| 346 const Printer::PpdReference& reference, | 687 } |
| 347 base::Optional<base::FilePath>* cache_result) const { | 688 for (const auto& entry : *top_list) { |
| 348 *cache_result = cache_->Find(reference); | 689 base::ListValue* sub_list; |
| 349 } | 690 contents->push_back({}); |
| 350 | 691 if (!entry->GetAsList(&sub_list) || sub_list->GetSize() != 2 || |
| 351 void QueryAvailableDoCacheLookup( | 692 !sub_list->GetString(0, &contents->back().first) || |
| 352 base::Optional<PpdProvider::AvailablePrintersMap>* cache_result) const { | 693 !sub_list->GetString(1, &contents->back().second)) { |
| 353 *cache_result = cache_->FindAvailablePrinters(); | 694 contents->clear(); |
| 354 } | 695 return PpdProvider::INTERNAL_ERROR; |
| 355 | 696 } |
| 356 // Callback that happens when the Resolve() cache lookup completes. If the | 697 } |
| 357 // cache satisfied the request, finish the Resolve, otherwise start a URL | 698 return PpdProvider::SUCCESS; |
| 358 // request to satisfy the request. This runs on the same thread as Resolve() | 699 } |
| 359 // was invoked on. | 700 |
| 360 void ResolveCacheLookupDone( | 701 // Create the list of manufacturers from |cached_metadata_|. Requires that |
| 361 const Printer::PpdReference& ppd_reference, | 702 // the manufacturer list has already been resolved. |
| 362 const PpdProvider::ResolveCallback& done_callback, | 703 std::vector<std::string> GetManufacturerList() const { |
| 363 std::unique_ptr<base::Optional<base::FilePath>> cache_result) { | 704 CHECK(cached_metadata_.get() != nullptr); |
| 364 if (*cache_result) { | 705 std::vector<std::string> ret; |
| 365 // Cache hit. Schedule the callback now and return. | 706 ret.reserve(cached_metadata_->size()); |
| 366 done_callback.Run(PpdProvider::SUCCESS, cache_result->value()); | 707 for (const auto& entry : *cached_metadata_) { |
| 367 return; | 708 ret.push_back(entry.first); |
| 368 } | 709 } |
| 369 | 710 // TODO(justincarlson) -- this should be a localization-aware sort. |
| 370 // We don't have a way to automatically resolve user-supplied PPDs yet. So | 711 sort(ret.begin(), ret.end()); |
| 371 // if we have one specified, and it's not cached, we fail out rather than | 712 return ret; |
| 372 // fall back to quirks-server based resolution. The reasoning here is that | 713 } |
| 373 // if the user has specified a PPD when a quirks-server one exists, it | 714 |
| 374 // probably means the quirks server one doesn't work for some reason, so we | 715 // Get the list of printers from a given manufacturer from |cached_metadata_|. |
| 375 // shouldn't silently use it. | 716 // Requires that we have already resolved this from the server. |
| 376 if (!ppd_reference.user_supplied_ppd_url.empty()) { | 717 std::vector<std::string> GetManufacturerPrinterList( |
| 377 done_callback.Run(PpdProvider::NOT_FOUND, base::FilePath()); | 718 const ManufacturerMetadata& meta) const { |
| 378 return; | 719 CHECK(meta.printers.get() != nullptr); |
| 379 } | 720 std::vector<std::string> ret; |
| 380 | 721 ret.reserve(meta.printers->size()); |
| 381 auto fetch_data = base::MakeUnique<ResolveFetchData>(); | 722 for (const auto& entry : *meta.printers) { |
| 382 fetch_data->ppd_reference = ppd_reference; | 723 ret.push_back(entry.first); |
| 383 fetch_data->done_callback = done_callback; | 724 } |
| 384 fetch_data->fetcher = | 725 // TODO(justincarlson) -- this should be a localization-aware sort. |
| 385 net::URLFetcher::Create(GetQuirksServerPpdLookupURL(ppd_reference), | 726 sort(ret.begin(), ret.end()); |
| 386 net::URLFetcher::GET, &resolve_delegate_); | 727 return ret; |
| 387 | 728 } |
| 388 fetch_data->fetcher->SetRequestContext(url_context_getter_.get()); | 729 |
| 389 fetch_data->fetcher->SetLoadFlags( | 730 // Map from (localized) manufacturer name to metadata for that manufacturer. |
| 390 net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE | | 731 // This is populated lazily. If we don't yet have a manufacturer list, the |
| 391 net::LOAD_DO_NOT_SAVE_COOKIES | net::LOAD_DO_NOT_SEND_COOKIES | | 732 // top pointer will be null. When we create the top level map, then each |
| 392 net::LOAD_DO_NOT_SEND_AUTH_DATA); | 733 // value will only contain a reference which can be used to resolve the |
| 393 auto* fetcher = fetch_data->fetcher.get(); | 734 // printer list from that manufacturer. On demand, we use these references to |
| 394 StoreResolveFetchData(std::move(fetch_data)); | 735 // resolve the actual printer lists. |
| 395 fetcher->Start(); | 736 std::unique_ptr<std::unordered_map<std::string, ManufacturerMetadata>> |
| 396 } | 737 cached_metadata_; |
| 397 | 738 |
| 398 // Generate a url to look up a manufacturer/model from the quirks server | 739 // Cached contents of the server index, which maps a |
| 399 GURL GetQuirksServerPpdLookupURL( | 740 // PpdReference::effective_make_and_model to a url for the corresponding |
| 400 const Printer::PpdReference& ppd_reference) const { | 741 // ppd. |
| 401 return GURL(base::StringPrintf( | 742 // Null until we have fetched the index. |
| 402 "https://%s/v2/printer/manufacturers/%s/models/%s?key=%s", | 743 std::unique_ptr<std::unordered_map<std::string, std::string>> |
| 403 options_.quirks_server.c_str(), | 744 cached_ppd_index_; |
| 404 ppd_reference.effective_manufacturer.c_str(), | 745 |
| 405 ppd_reference.effective_model.c_str(), api_key_.c_str())); | 746 // Queued ResolveManufacturers() calls. We will simultaneously resolve |
| 406 } | 747 // all queued requests, so no need for a deque here. |
| 407 | 748 std::vector<ResolveManufacturersCallback> manufacturers_resolution_queue_; |
| 408 // Generate a url to ask for the full supported printer list from the quirks | 749 |
| 409 // server. | 750 // Queued ResolvePrinters() calls. |
| 410 GURL GetQuirksServerPpdListURL() const { | 751 std::deque<PrinterResolutionQueueEntry> printers_resolution_queue_; |
| 411 return GURL(base::StringPrintf("https://%s/v2/printer/list?key=%s", | 752 |
| 412 options_.quirks_server.c_str(), | 753 // Queued ResolvePpd() requests. |
| 413 api_key_.c_str())); | 754 std::deque<std::pair<Printer::PpdReference, ResolvePpdCallback>> |
| 414 } | 755 ppd_resolution_queue_; |
| 415 | 756 |
| 416 // If |fetcher| succeeded in its fetch, get the response in |response| and | 757 // Locale we're using for grabbing stuff from the server. Empty if we haven't |
| 417 // return true, otherwise return false. | 758 // determined it yet. |
| 418 bool ValidateAndGetResponseAsString(const net::URLFetcher& fetcher, | 759 std::string locale_; |
| 419 std::string* contents) { | 760 |
| 420 return (fetcher.GetStatus().status() == net::URLRequestStatus::SUCCESS) && | 761 // If the fetcher is active, what's it fetching? |
| 421 (fetcher.GetResponseCode() == net::HTTP_OK) && | 762 FetcherTarget fetcher_target_; |
| 422 fetcher.GetResponseAsString(contents); | 763 |
| 423 } | 764 // Fetcher used for all network fetches. This is explicitly reset() when |
| 424 | 765 // a fetch has been processed. |
| 425 // API key for accessing quirks server. | 766 std::unique_ptr<net::URLFetcher> fetcher_; |
| 426 const std::string api_key_; | 767 bool fetch_inflight_ = false; |
| 768 |
| 769 // Locale of the browser, as returned by |
| 770 // BrowserContext::GetApplicationLocale(); |
| 771 const std::string browser_locale_; |
| 427 | 772 |
| 428 scoped_refptr<net::URLRequestContextGetter> url_context_getter_; | 773 scoped_refptr<net::URLRequestContextGetter> url_context_getter_; |
| 429 scoped_refptr<base::SequencedTaskRunner> io_task_runner_; | 774 |
| 430 std::unique_ptr<PpdCache> cache_; | 775 // For file:// fetches, a staging buffer and result flag for loading the file. |
| 776 std::string file_fetch_contents_; |
| 777 bool file_fetch_success_; |
| 778 |
| 779 // Cache of ppd files. |
| 780 scoped_refptr<PpdCache> ppd_cache_; |
| 781 |
| 782 // Where to run disk operations. |
| 783 scoped_refptr<base::SequencedTaskRunner> disk_task_runner_; |
| 431 | 784 |
| 432 // Construction-time options, immutable. | 785 // Construction-time options, immutable. |
| 433 const PpdProvider::Options options_; | 786 const PpdProvider::Options options_; |
| 434 | 787 |
| 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_; | 788 base::WeakPtrFactory<PpdProviderImpl> weak_factory_; |
| 789 |
| 790 protected: |
| 791 ~PpdProviderImpl() override {} |
| 450 }; | 792 }; |
| 451 | 793 |
| 452 void ResolveURLFetcherDelegate::OnURLFetchComplete( | 794 } // namespace |
| 453 const net::URLFetcher* source) { | 795 |
| 454 parent_->OnResolveFetchComplete(source); | 796 // static |
| 797 scoped_refptr<PpdProvider> PpdProvider::Create( |
| 798 const std::string& browser_locale, |
| 799 scoped_refptr<net::URLRequestContextGetter> url_context_getter, |
| 800 scoped_refptr<PpdCache> ppd_cache, |
| 801 scoped_refptr<base::SequencedTaskRunner> disk_task_runner, |
| 802 const PpdProvider::Options& options) { |
| 803 return scoped_refptr<PpdProvider>( |
| 804 new PpdProviderImpl(browser_locale, url_context_getter, ppd_cache, |
| 805 disk_task_runner, options)); |
| 455 } | 806 } |
| 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 | 807 } // namespace printing |
| 476 } // namespace chromeos | 808 } // namespace chromeos |
| OLD | NEW |