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

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

Issue 2343983004: Add PPDProvider barebones implementation and associated cache skeleton. (Closed)
Patch Set: Initial PPDProvider/PPDCache implementation. Also, add associated unittests. This doesn't plumb th… Created 4 years, 2 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
(Empty)
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
3 // found in the LICENSE file.
4
5 #include <vector>
6
7 #include "base/files/file_util.h"
8 #include "base/memory/ptr_util.h"
9 #include "base/path_service.h"
10 #include "base/strings/string_util.h"
11 #include "chromeos/printing/ppd_cache.h"
12 #include "crypto/sha2.h"
13 #include "net/base/io_buffer.h"
14 #include "net/filter/filter.h"
15 #include "net/filter/gzip_header.h"
16
17 using base::File;
18 using base::FilePath;
19 using base::Optional;
20 using net::Filter;
21 using std::string;
22 using std::unique_ptr;
23 using std::vector;
24
25 namespace chromeos {
26 namespace printing {
27 namespace {
28
29 // Return the ASCII character representing the given nibble, in hex.
30 char HexifyNibble(int nibble) {
skau 2016/10/14 23:51:49 Is this equivalent to HexEncode? https://cs.chromi
Carlson 2016/10/17 16:43:20 I spent seriously like 15 minutes looking for that
31 DCHECK(nibble >= 0 && nibble <= 0xf);
32 if (nibble <= 9) {
33 return '0' + nibble;
34 }
35 return 'a' + (nibble - 10);
36 }
37
38 // Return a ascii-hex version of the data in data, interpreted as a list of
39 // bytes. Leading zeros are not removed. For example, a 3-byte string
40 // containing the bytes {0x0a, 0x3f, 0xff} would be converted to the 6-byte
41 // string "0a3fff"
42 string Hexify(const string& data) {
43 string ret;
44 // Each nibble gets a char, so 2 output bytes per input raw byte.
45 ret.resize(data.size() * 2);
46 int out_idx = 0;
47 for (char c : data) {
48 ret[out_idx++] = HexifyNibble((c >> 4) & 0xf);
49 ret[out_idx++] = HexifyNibble(c & 0xf);
50 }
51 DCHECK_EQ(static_cast<size_t>(out_idx), ret.size());
52 return ret;
53 }
54
55 // Return true if it looks like contents is already gzipped, false otherwise.
56 bool IsGZipped(const string& contents) {
57 const char* ignored;
58 net::GZipHeader header;
59 return header.ReadMore(contents.data(), contents.size(), &ignored) ==
60 net::GZipHeader::COMPLETE_HEADER;
61 }
62
63 class PpdCacheImpl : public PpdCache {
64 public:
65 explicit PpdCacheImpl(const FilePath& cache_base_dir,
66 const PpdCache::Options& options)
67 : cache_base_dir_(cache_base_dir) {}
68 ~PpdCacheImpl() override {}
69
70 // Public API functions.
71 Optional<FilePath> Find(
72 const Printer::PpdReference& reference) const override {
73 Optional<FilePath> ret;
74
75 // We can't know here if we have a gzipped or un-gzipped version, so just
76 // look for both.
77 FilePath contents_path_base = GetCachePathBase(reference);
78 for (const string& extension : {".ppd", ".ppd.gz"}) {
79 FilePath contents_path = contents_path_base.AddExtension(extension);
80 if (base::PathExists(contents_path)) {
81 ret = contents_path;
82 break;
83 }
84 }
85 return ret;
86 }
87
88 Optional<FilePath> Store(const Printer::PpdReference& reference,
89 const string& ppd_contents) override {
90 Optional<FilePath> ret;
91 FilePath contents_path;
92 contents_path = GetCachePathBase(reference).AddExtension(".ppd");
93 if (IsGZipped(ppd_contents)) {
94 contents_path = contents_path.AddExtension(".gz");
95 }
96 if (base::WriteFile(contents_path, ppd_contents.data(),
97 ppd_contents.size()) ==
98 static_cast<int>(ppd_contents.size())) {
99 ret = contents_path;
100 } else {
101 LOG(ERROR) << "Failed to write " << contents_path.LossyDisplayName();
102 // Try to clean up the file, as it may have partial contents. Note
103 // that DeleteFile(nonexistant file) should return true, so failure here
104 // means something is exceptionally hosed.
105 if (!base::DeleteFile(contents_path, false)) {
106 LOG(ERROR) << "Failed to cleanup partially-written file "
107 << contents_path.LossyDisplayName();
108 return ret;
109 }
110 }
111 return ret;
112 }
113
114 private:
115 // Get the file path at which we expect to find a PPD if it's cached.
116 //
117 // This is, ultimately, just a hash function. It's extremely infrequently
118 // used (called once when trying to look up information on a printer or store
119 // a PPD), and should be stable, as changing the function will make previously
120 // cached entries unfindable, causing resolve logic to be reinvoked
121 // unnecessarily.
122 //
123 // There's also a faint possibility that a bad actor might try to do something
124 // nefarious by intentionally causing a cache collision that makes the wrong
125 // PPD be used for a printer. There's no obvious attack vector, but
126 // there's also no real cost to being paranoid here, so we use SHA-256 as the
127 // underlying hash function, and inject fixed field prefixes to prevent
128 // field-substitution spoofing. This also buys us hash function stability at
129 // the same time.
130 //
131 // Also, care should be taken to preserve the existing hash values if new
132 // fields are added to PpdReference -- that is, if a new field F is added
133 // to PpdReference, a PpdReference with a default F value should hash to
134 // the same thing as a PpdReference that predates the addition of F to the
135 // structure.
136 //
137 // Note this function expects that the caller will append ".ppd", or ".ppd.gz"
138 // to the output as needed.
139 FilePath GetCachePathBase(const Printer::PpdReference& ref) const {
skau 2016/10/14 23:51:49 Is it desirable that { "user_supplied_ppd_url": "f
Carlson 2016/10/17 16:43:20 I *think* so, but could be convinced otherwise. M
skau 2016/10/17 17:16:26 Please change it. While we will need to make sure
Carlson 2016/10/18 19:05:00 Done.
140 vector<string> pieces;
141 if (!ref.user_supplied_ppd_url.empty()) {
142 pieces.push_back("user_supplied_ppd_url:");
143 pieces.push_back(ref.user_supplied_ppd_url);
144 }
145 if (!ref.effective_manufacturer.empty()) {
146 pieces.push_back("manufacturer:");
147 pieces.push_back(ref.effective_manufacturer);
148 }
149 if (!ref.effective_model.empty()) {
150 pieces.push_back("model:");
151 pieces.push_back(ref.effective_model);
152 }
153 // The separator here is not needed, but makes debug output more readable.
154 string full_key = base::JoinString(pieces, "|");
155 string ascii_hash = Hexify(crypto::SHA256HashString(full_key));
156 VLOG(3) << "PPD Cache key is " << full_key << " which hashes to "
157 << ascii_hash;
158
159 return cache_base_dir_.Append(ascii_hash);
160 }
161
162 const FilePath cache_base_dir_;
163 };
Lei Zhang 2016/10/17 17:54:06 Add DISALLOW_COPY_AND_ASSIGN(...);
Carlson 2016/10/18 19:05:01 Done.
164
165 } // namespace
166
167 // static
168 unique_ptr<PpdCache> PpdCache::Create(const FilePath& cache_base_dir,
169 const PpdCache::Options& options) {
170 return ::base::MakeUnique<PpdCacheImpl>(cache_base_dir, options);
171 }
172
173 } // namespace printing
174 } // namespace chromeos
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698