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

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

Issue 2613683004: Completely rewrite the PpdProvider/PpdCache to use the SCS backend. Along the way, clean it up a l… (Closed)
Patch Set: Address michealpg@ comments Created 3 years, 10 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
« no previous file with comments | « chromeos/printing/ppd_cache.h ('k') | chromeos/printing/ppd_cache_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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_cache.h" 5 #include "chromeos/printing/ppd_cache.h"
6 6
7 #include <utility> 7 #include <utility>
8 #include <vector> 8 #include <vector>
9 9
10 #include "base/bind.h"
10 #include "base/files/file_util.h" 11 #include "base/files/file_util.h"
11 #include "base/json/json_parser.h" 12 #include "base/json/json_parser.h"
12 #include "base/json/json_writer.h" 13 #include "base/json/json_writer.h"
13 #include "base/memory/ptr_util.h" 14 #include "base/memory/ptr_util.h"
14 #include "base/strings/string_number_conversions.h" 15 #include "base/strings/string_number_conversions.h"
15 #include "base/strings/string_util.h" 16 #include "base/strings/string_util.h"
16 #include "base/synchronization/lock.h" 17 #include "base/synchronization/lock.h"
18 #include "base/threading/sequenced_task_runner_handle.h"
17 #include "base/threading/thread_restrictions.h" 19 #include "base/threading/thread_restrictions.h"
18 #include "base/time/time.h" 20 #include "base/time/time.h"
19 #include "base/values.h" 21 #include "base/values.h"
22 #include "chromeos/printing/printing_constants.h"
20 #include "crypto/sha2.h" 23 #include "crypto/sha2.h"
21 #include "net/base/io_buffer.h" 24 #include "net/base/io_buffer.h"
22 #include "net/filter/gzip_header.h" 25 #include "net/filter/gzip_header.h"
23 26
24 namespace chromeos { 27 namespace chromeos {
25 namespace printing { 28 namespace printing {
26 namespace { 29 namespace {
27 30
28 // Name of the file we use to cache the list of available printer drivers from
29 // QuirksServer. This file resides in the cache directory.
30 const char kAvailablePrintersFilename[] = "all_printers.json";
31
32 // Return true if it looks like contents is already gzipped, false otherwise.
33 bool IsGZipped(const std::string& contents) {
34 const char* ignored;
35 net::GZipHeader header;
36 return header.ReadMore(contents.data(), contents.size(), &ignored) ==
37 net::GZipHeader::COMPLETE_HEADER;
38 }
39
40 class PpdCacheImpl : public PpdCache { 31 class PpdCacheImpl : public PpdCache {
41 public: 32 public:
42 PpdCacheImpl(const base::FilePath& cache_base_dir, 33 PpdCacheImpl(const base::FilePath& cache_base_dir,
43 const PpdCache::Options& options) 34 const scoped_refptr<base::SequencedTaskRunner>& disk_task_runner)
44 : cache_base_dir_(cache_base_dir), 35 : cache_base_dir_(cache_base_dir), disk_task_runner_(disk_task_runner) {}
45 available_printers_file_( 36
46 cache_base_dir.Append(kAvailablePrintersFilename)), 37 // Public API functions.
47 options_(options) {} 38 void Find(const std::string& key, const FindCallback& cb) override {
39 // Ensure the cache lives until the op is over.
40 AddRef();
41 ++inflight_ops_;
42 disk_task_runner_->PostTask(
43 FROM_HERE, base::Bind(&PpdCacheImpl::FindImpl, this, key,
44 base::SequencedTaskRunnerHandle::Get(), cb));
45 }
46
47 // Store the given contents at the given key. If cb is non-null, it will
48 // be invoked on completion.
49 void Store(const std::string& key,
50 const std::string& contents,
51 const base::Callback<void()>& cb) override {
52 AddRef();
53 ++inflight_ops_;
54 disk_task_runner_->PostTask(
55 FROM_HERE, base::Bind(&PpdCacheImpl::StoreImpl, this, key, contents,
56 base::SequencedTaskRunnerHandle::Get(), cb));
57 }
58
59 bool Idle() const override { return inflight_ops_ == 0; }
60
61 private:
48 ~PpdCacheImpl() override {} 62 ~PpdCacheImpl() override {}
49 63
50 // Public API functions. 64 // If the cache doesn't already exist, create it.
51 base::Optional<base::FilePath> Find( 65 void MaybeCreateCache() {
52 const Printer::PpdReference& reference) const override { 66 if (!base::PathExists(cache_base_dir_)) {
53 base::AutoLock l(lock_); 67 base::CreateDirectory(cache_base_dir_);
54 base::ThreadRestrictions::AssertIOAllowed();
55 base::Optional<base::FilePath> ret;
56
57 // We can't know here if we have a gzipped or un-gzipped version, so just
58 // look for both.
59 base::FilePath contents_path_base = GetCachePathBase(reference);
60 for (const std::string& extension : {".ppd", ".ppd.gz"}) {
61 base::FilePath contents_path = contents_path_base.AddExtension(extension);
62 if (base::PathExists(contents_path)) {
63 ret = contents_path;
64 break;
65 }
66 } 68 }
67 return ret;
68 } 69 }
69 70
70 base::Optional<base::FilePath> Store( 71 // Find implementation, runs on the i/o thread.
71 const Printer::PpdReference& reference, 72 void FindImpl(const std::string& key,
72 const std::string& ppd_contents) override { 73 const scoped_refptr<base::SequencedTaskRunner>& callback_runner,
74 const FindCallback& cb) {
73 base::ThreadRestrictions::AssertIOAllowed(); 75 base::ThreadRestrictions::AssertIOAllowed();
74 base::AutoLock l(lock_); 76 FindResult result;
75 if (!EnsureCacheDirectoryExists()) { 77 result.success = false;
76 return base::nullopt; 78 MaybeCreateCache();
77 } 79 base::File file(FilePathForKey(key),
78 base::Optional<base::FilePath> ret; 80 base::File::FLAG_OPEN | base::File::FLAG_READ);
79 base::FilePath contents_path =
80 GetCachePathBase(reference).AddExtension(".ppd");
81 if (IsGZipped(ppd_contents)) {
82 contents_path = contents_path.AddExtension(".gz");
83 }
84 if (base::WriteFile(contents_path, ppd_contents.data(),
85 ppd_contents.size()) ==
86 static_cast<int>(ppd_contents.size())) {
87 ret = contents_path;
88 } else {
89 LOG(ERROR) << "Failed to write " << contents_path.LossyDisplayName();
90 // Try to clean up the file, as it may have partial contents. Note that
91 // DeleteFile(nonexistant file) should return true, so failure here means
92 // something is exceptionally hosed.
93 if (!base::DeleteFile(contents_path, false)) {
94 LOG(ERROR) << "Failed to cleanup partially-written file "
95 << contents_path.LossyDisplayName();
96 return ret;
97 }
98 }
99 return ret;
100 }
101 81
102 base::Optional<PpdProvider::AvailablePrintersMap> FindAvailablePrinters() 82 if (file.IsValid()) {
103 override { 83 int64_t len = file.GetLength();
104 base::ThreadRestrictions::AssertIOAllowed(); 84 if (len >= static_cast<int64_t>(crypto::kSHA256Length) &&
105 base::AutoLock l(lock_); 85 len <= static_cast<int64_t>(kMaxPpdSizeBytes) +
106 if (available_printers_ != nullptr && 86 static_cast<int64_t>(crypto::kSHA256Length)) {
107 base::Time::Now() - available_printers_timestamp_ < 87 std::unique_ptr<char[]> buf(new char[len]);
108 options_.max_available_list_staleness) { 88 if (file.ReadAtCurrentPos(buf.get(), len) == len) {
109 // Satisfy from memory cache. 89 base::StringPiece contents(buf.get(), len - crypto::kSHA256Length);
110 return *available_printers_; 90 base::StringPiece checksum(buf.get() + len - crypto::kSHA256Length,
111 } 91 crypto::kSHA256Length);
112 std::string buf; 92 if (crypto::SHA256HashString(contents) == checksum) {
113 if (!MaybeReadAvailablePrintersCache(&buf)) { 93 base::File::Info info;
114 // Disk cache miss. 94 if (file.GetInfo(&info)) {
115 return base::nullopt; 95 result.success = true;
116 } 96 result.age = base::Time::Now() - info.last_modified;
117 auto dict = base::DictionaryValue::From(base::JSONReader::Read(buf)); 97 contents.CopyToString(&result.contents);
118 if (dict == nullptr) { 98 }
119 LOG(ERROR) << "Failed to deserialize available printers cache"; 99 } else {
120 return base::nullopt; 100 LOG(ERROR) << "Bad checksum for cache key " << key;
121 } 101 }
122 // Note if we got here, we've already set available_printers_timestamp_ to
123 // the mtime of the file we read from.
124 available_printers_ = base::MakeUnique<PpdProvider::AvailablePrintersMap>();
125 const base::ListValue* models;
126 std::string model;
127 for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd();
128 it.Advance()) {
129 auto& out = (*available_printers_)[it.key()];
130 if (!it.value().GetAsList(&models)) {
131 LOG(ERROR) << "Skipping malformed printer make: " << it.key();
132 continue;
133 }
134 for (const auto& model_value : *models) {
135 if (model_value->GetAsString(&model)) {
136 out.push_back(model);
137 } else {
138 LOG(ERROR) << "Skipping malformed printer model in: " << it.key()
139 << ". Expected a string, found a "
140 << base::Value::GetTypeName(model_value->GetType());
141 } 102 }
142 } 103 }
143 } 104 }
144 return *available_printers_; 105 callback_runner->PostTask(FROM_HERE,
106 base::Bind(&PpdCacheImpl::ReplyAndRelease, this,
107 base::Bind(cb, result)));
145 } 108 }
146 109
147 // Note we throw up our hands and fail (gracefully) to store if we encounter 110 // If |cb| is non-null, invoke it on this thread, then decrement the refcount.
148 // non-unicode things in the strings of |available_printers|. Since these 111 void ReplyAndRelease(const base::Callback<void()>& cb) {
149 // strings come from a source we control, being less paranoid about these 112 if (!cb.is_null()) {
150 // values seems reasonable. 113 cb.Run();
151 void StoreAvailablePrinters(std::unique_ptr<PpdProvider::AvailablePrintersMap>
152 available_printers) override {
153 base::ThreadRestrictions::AssertIOAllowed();
154 base::AutoLock l(lock_);
155 if (!EnsureCacheDirectoryExists()) {
156 return;
157 } 114 }
158 available_printers_ = std::move(available_printers); 115 --inflight_ops_;
159 available_printers_timestamp_ = base::Time::Now(); 116 Release();
160 // Convert the map to Values, in preparation for jsonification. 117 // Object may be destroyed here, so no further access to object data
161 base::DictionaryValue top_level; 118 // allowed.
162 for (const auto& entry : *available_printers_) {
163 auto printers = base::MakeUnique<base::ListValue>();
164 printers->AppendStrings(entry.second);
165 top_level.Set(entry.first, std::move(printers));
166 }
167 std::string contents;
168 if (!base::JSONWriter::Write(top_level, &contents)) {
169 LOG(ERROR) << "Failed to generate JSON";
170 return;
171 }
172 if (contents.size() > options_.max_available_list_cached_size) {
173 LOG(ERROR) << "Serialized available printers list too large (size is "
174 << contents.size() << " bytes)";
175 return;
176 }
177 if (base::WriteFile(available_printers_file_, contents.data(),
178 contents.size()) != static_cast<int>(contents.size())) {
179 LOG(ERROR) << "Failed to write available printers cache to "
180 << available_printers_file_.MaybeAsASCII();
181 }
182 } 119 }
183 120
184 private: 121 // Store implementation, runs on the i/o thread.
185 // Create the cache directory if it doesn't already exist. Returns true 122 void StoreImpl(
186 // on success. 123 const std::string& key,
187 bool EnsureCacheDirectoryExists() { 124 const std::string& contents,
188 if (base::PathExists(cache_base_dir_) || 125 const scoped_refptr<base::SequencedTaskRunner>& callback_runner,
189 base::CreateDirectory(cache_base_dir_)) { 126 const base::Callback<void()> cb) {
190 return true; 127 base::ThreadRestrictions::AssertIOAllowed();
128 MaybeCreateCache();
129 if (contents.size() > kMaxPpdSizeBytes) {
130 LOG(ERROR) << "Ignoring attempt to cache large object";
131 } else {
132 auto path = FilePathForKey(key);
133 base::File file(path,
134 base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
135 std::string checksum = crypto::SHA256HashString(contents);
136 if (!file.IsValid() ||
137 file.WriteAtCurrentPos(contents.data(), contents.size()) !=
138 static_cast<int>(contents.size()) ||
139 file.WriteAtCurrentPos(checksum.data(), checksum.size()) !=
140 static_cast<int>(checksum.size())) {
141 LOG(ERROR) << "Failed to create ppd cache file";
142 file.Close();
143 if (!base::DeleteFile(path, false)) {
144 LOG(ERROR) << "Failed to cleanup failed creation.";
145 }
146 }
191 } 147 }
192 LOG(ERROR) << "Failed to create ppd cache directory " 148 callback_runner->PostTask(
193 << cache_base_dir_.MaybeAsASCII(); 149 FROM_HERE, base::Bind(&PpdCacheImpl::ReplyAndRelease, this, cb));
194 return false;
195 } 150 }
196 151
197 // Get the file path at which we expect to find a PPD if it's cached. 152 // Return the (full) path to the file we expect to find the given key at.
198 // 153 base::FilePath FilePathForKey(const std::string& key) {
199 // This is, ultimately, just a hash function. It's extremely infrequently 154 std::string hashed_key = crypto::SHA256HashString(key);
200 // used (called once when trying to look up information on a printer or store 155 return cache_base_dir_.Append(
201 // a PPD), and should be stable, as changing the function will make previously 156 base::HexEncode(hashed_key.data(), hashed_key.size()));
202 // cached entries unfindable, causing resolve logic to be reinvoked
203 // unnecessarily.
204 //
205 // There's also a faint possibility that a bad actor might try to do something
206 // nefarious by intentionally causing a cache collision that makes the wrong
207 // PPD be used for a printer. There's no obvious attack vector, but
208 // there's also no real cost to being paranoid here, so we use SHA-256 as the
209 // underlying hash function, and inject fixed field prefixes to prevent
210 // field-substitution spoofing. This also buys us hash function stability at
211 // the same time.
212 //
213 // Also, care should be taken to preserve the existing hash values if new
214 // fields are added to PpdReference -- that is, if a new field F is added
215 // to PpdReference, a PpdReference with a default F value should hash to
216 // the same thing as a PpdReference that predates the addition of F to the
217 // structure.
218 //
219 // Note this function expects that the caller will append ".ppd", or ".ppd.gz"
220 // to the output as needed.
221 base::FilePath GetCachePathBase(const Printer::PpdReference& ref) const {
222 std::vector<std::string> pieces;
223 if (!ref.user_supplied_ppd_url.empty()) {
224 pieces.push_back("user_supplied_ppd_url:");
225 pieces.push_back(ref.user_supplied_ppd_url);
226 } else if (!ref.effective_manufacturer.empty() &&
227 !ref.effective_model.empty()) {
228 pieces.push_back("manufacturer:");
229 pieces.push_back(ref.effective_manufacturer);
230 pieces.push_back("model:");
231 pieces.push_back(ref.effective_model);
232 } else {
233 NOTREACHED() << "PpdCache hashing empty PpdReference";
234 }
235 // The separator here is not needed, but makes debug output more readable.
236 std::string full_key = base::JoinString(pieces, "|");
237 std::string hashed_key = crypto::SHA256HashString(full_key);
238 std::string ascii_hash =
239 base::HexEncode(hashed_key.data(), hashed_key.size());
240 VLOG(3) << "PPD Cache key is " << full_key << " which hashes to "
241 << ascii_hash;
242
243 return cache_base_dir_.Append(ascii_hash);
244 } 157 }
245 158
246 // Try to read the available printers cache. Returns true on success. On 159 int inflight_ops_ = 0;
247 // success, |buf| will contain the contents of the file, otherwise it will be
248 // cleared.
249 bool MaybeReadAvailablePrintersCache(std::string* buf) {
250 buf->clear();
251 160
252 base::File cache_file(available_printers_file_, 161 base::FilePath cache_base_dir_;
253 base::File::FLAG_OPEN | base::File::FLAG_READ); 162 scoped_refptr<base::SequencedTaskRunner> disk_task_runner_;
254 base::File::Info info;
255 if (cache_file.IsValid() && cache_file.GetInfo(&info) &&
256 (base::Time::Now() - info.last_modified <=
257 options_.max_available_list_staleness)) {
258 // We have a file that's recent enough to use.
259 if (!base::ReadFileToStringWithMaxSize(
260 available_printers_file_, buf,
261 options_.max_available_list_cached_size)) {
262 LOG(ERROR) << "Failed to read printer cache from "
263 << available_printers_file_.MaybeAsASCII();
264 buf->clear();
265 return false;
266 }
267 available_printers_timestamp_ = info.last_modified;
268 return true;
269 }
270 // Either we don't have an openable file, or it's too old.
271 //
272 // If we have an invalid file and it's not valid for reasons other than
273 // NOT_FOUND, that's unexpected and worth logging. Otherwise this is
274 // a normal cache miss.
275 if (!cache_file.IsValid() &&
276 cache_file.error_details() != base::File::FILE_ERROR_NOT_FOUND) {
277 LOG(ERROR) << "Unexpected result when attempting to open printer cache: "
278 << base::File::ErrorToString(cache_file.error_details());
279 }
280 return false;
281 }
282
283 // In-memory copy of the available printers map, null if we don't have an
284 // in-memory copy yet. Filled in the first time the map is fetched from
285 // disk or stored.
286 std::unique_ptr<PpdProvider::AvailablePrintersMap> available_printers_;
287 // Timestamp for the in-memory copy of the cache. (The on-disk version uses
288 // the file mtime).
289 base::Time available_printers_timestamp_;
290
291 const base::FilePath cache_base_dir_;
292 const base::FilePath available_printers_file_;
293 const PpdCache::Options options_;
294
295 mutable base::Lock lock_;
296 163
297 DISALLOW_COPY_AND_ASSIGN(PpdCacheImpl); 164 DISALLOW_COPY_AND_ASSIGN(PpdCacheImpl);
298 }; 165 };
299 166
300 } // namespace 167 } // namespace
301 168
302 // static 169 // static
303 std::unique_ptr<PpdCache> PpdCache::Create(const base::FilePath& cache_base_dir, 170 scoped_refptr<PpdCache> PpdCache::Create(
304 const PpdCache::Options& options) { 171 const base::FilePath& cache_base_dir,
305 return base::MakeUnique<PpdCacheImpl>(cache_base_dir, options); 172 scoped_refptr<base::SequencedTaskRunner> disk_task_runner) {
173 return scoped_refptr<PpdCache>(
174 new PpdCacheImpl(cache_base_dir, disk_task_runner));
306 } 175 }
307 176
308 } // namespace printing 177 } // namespace printing
309 } // namespace chromeos 178 } // namespace chromeos
OLDNEW
« no previous file with comments | « chromeos/printing/ppd_cache.h ('k') | chromeos/printing/ppd_cache_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698