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 "base/base64.h" | 5 #include "base/base64.h" |
6 #include "base/bind.h" | 6 #include "base/bind.h" |
7 #include "base/files/file_util.h" | |
8 #include "base/metrics/histogram.h" | |
9 #include "base/metrics/sparse_histogram.h" | |
7 #include "base/strings/stringprintf.h" | 10 #include "base/strings/stringprintf.h" |
8 #include "components/safe_browsing_db/v4_store.h" | 11 #include "components/safe_browsing_db/v4_store.h" |
12 #include "components/safe_browsing_db/v4_store.pb.h" | |
9 | 13 |
10 namespace safe_browsing { | 14 namespace safe_browsing { |
11 | 15 |
16 namespace { | |
17 // NOTE(vakh): kFileMagic should not be a byte-wise palindrome, so | |
18 // that byte-order changes force corruption. | |
Scott Hess - ex-Googler
2016/06/22 22:58:38
Protobuf would handle byte-ordering issues, so thi
vakh (use Gerrit instead)
2016/06/23 23:09:31
Removed the comment.
| |
19 const uint32_t kFileMagic = 0x600D71FE; | |
20 | |
21 const uint32_t kFileVersion = 9; | |
22 | |
23 void RecordStoreReadResult(StoreReadResult result) { | |
24 UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.V4StoreReadResult", result, | |
25 STORE_READ_RESULT_MAX); | |
26 } | |
27 | |
28 void RecordStoreWriteResult(StoreWriteResult result) { | |
29 UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.V4StoreWriteResult", result, | |
30 STORE_WRITE_RESULT_MAX); | |
31 } | |
32 | |
33 // Returns the name of the temporary file used to buffer data for | |
34 // |filename|. Exported for unit tests. | |
35 const base::FilePath TemporaryFileForFilename(const base::FilePath& filename) { | |
36 return base::FilePath(filename.value() + FILE_PATH_LITERAL("_new")); | |
37 } | |
38 | |
39 } // namespace | |
40 | |
12 std::ostream& operator<<(std::ostream& os, const V4Store& store) { | 41 std::ostream& operator<<(std::ostream& os, const V4Store& store) { |
13 os << store.DebugString(); | 42 os << store.DebugString(); |
14 return os; | 43 return os; |
15 } | 44 } |
16 | 45 |
17 V4Store* V4StoreFactory::CreateV4Store( | 46 V4Store* V4StoreFactory::CreateV4Store( |
18 const scoped_refptr<base::SequencedTaskRunner>& task_runner, | 47 const scoped_refptr<base::SequencedTaskRunner>& task_runner, |
19 const base::FilePath& store_path) { | 48 const base::FilePath& store_path) { |
20 return new V4Store(task_runner, store_path); | 49 V4Store* new_store = new V4Store(task_runner, store_path); |
50 new_store->Initialize(); | |
Scott Hess - ex-Googler
2016/06/22 22:58:39
If this is only called from the factory, it might
vakh (use Gerrit instead)
2016/06/23 23:09:32
FakeV4StoreFactory, derived from V4StoreFactory, a
Scott Hess - ex-Googler
2016/06/24 04:14:11
Acknowledged. But it kind of feels like your inhe
| |
51 return new_store; | |
52 } | |
53 | |
54 void V4Store::Initialize() { | |
55 // If a state already exists, don't re-initilize. | |
vakh (use Gerrit instead)
2016/06/22 07:28:04
Nit: remove this comment.
Scott Hess - ex-Googler
2016/06/22 22:58:38
I'm fine with a comment, but the comment makes it
vakh (use Gerrit instead)
2016/06/24 18:03:50
Acknowledged.
| |
56 DCHECK(state_.empty()); | |
57 | |
58 StoreReadResult store_read_result = ReadFromDisk(); | |
59 RecordStoreReadResult(store_read_result); | |
21 } | 60 } |
22 | 61 |
23 V4Store::V4Store(const scoped_refptr<base::SequencedTaskRunner>& task_runner, | 62 V4Store::V4Store(const scoped_refptr<base::SequencedTaskRunner>& task_runner, |
24 const base::FilePath& store_path) | 63 const base::FilePath& store_path) |
25 : store_path_(store_path), task_runner_(task_runner) {} | 64 : store_path_(store_path), task_runner_(task_runner) {} |
26 | 65 |
27 V4Store::~V4Store() {} | 66 V4Store::~V4Store() {} |
28 | 67 |
29 std::string V4Store::DebugString() const { | 68 std::string V4Store::DebugString() const { |
30 std::string state_base64; | 69 std::string state_base64; |
31 base::Base64Encode(state_, &state_base64); | 70 base::Base64Encode(state_, &state_base64); |
32 | 71 |
33 std::string value; | 72 std::string value; |
34 return base::SStringPrintf(&value, "path: %s; state: %s", | 73 return base::SStringPrintf(&value, "{path: %s; state: %s}", |
35 store_path_.value().c_str(), state_base64.c_str()); | 74 store_path_.value().c_str(), state_base64.c_str()); |
Scott Hess - ex-Googler
2016/06/22 22:58:38
Thanks!
This can be base::StringPrintf() without
vakh (use Gerrit instead)
2016/06/23 23:09:32
Done.
| |
36 } | 75 } |
37 | 76 |
38 bool V4Store::Reset() { | 77 bool V4Store::Reset() { |
39 // TODO(vakh): Implement skeleton. | 78 // TODO(vakh): Implement skeleton. |
40 state_ = ""; | 79 state_ = ""; |
41 return true; | 80 return true; |
42 } | 81 } |
43 | 82 |
44 void V4Store::ApplyUpdate( | 83 void V4Store::ApplyUpdate( |
45 const ListUpdateResponse& response, | 84 const ListUpdateResponse& response, |
46 const scoped_refptr<base::SingleThreadTaskRunner>& callback_task_runner, | 85 const scoped_refptr<base::SingleThreadTaskRunner>& callback_task_runner, |
47 UpdatedStoreReadyCallback callback) { | 86 UpdatedStoreReadyCallback callback) { |
48 std::unique_ptr<V4Store> new_store( | 87 std::unique_ptr<V4Store> new_store( |
49 new V4Store(this->task_runner_, this->store_path_)); | 88 new V4Store(this->task_runner_, this->store_path_)); |
50 | 89 |
51 // TODO(vakh): The new store currently only picks up the new state. Do more. | 90 // TODO(vakh): The new store currently only picks up the new state. Do more. |
52 new_store->state_ = response.new_client_state(); | 91 new_store->state_ = response.new_client_state(); |
53 | 92 |
93 // TODO(vakh): Merge the old store and the new update in new_store. | |
94 // Then, create a ListUpdateResponse containing RICE encoded hash-prefixes and | |
95 // response_type as FULL_UPDATE, and write that to disk. | |
96 StoreWriteResult result = new_store->WriteToDisk(response); | |
97 RecordStoreWriteResult(result); | |
98 | |
54 // new_store is done updating, pass it to the callback. | 99 // new_store is done updating, pass it to the callback. |
55 callback_task_runner->PostTask( | 100 callback_task_runner->PostTask( |
56 FROM_HERE, base::Bind(callback, base::Passed(&new_store))); | 101 FROM_HERE, base::Bind(callback, base::Passed(&new_store))); |
57 } | 102 } |
58 | 103 |
104 StoreReadResult V4Store::ReadFromDisk() { | |
105 DCHECK(task_runner_->RunsTasksOnCurrentThread()); | |
106 | |
107 std::string contents; | |
108 bool read_success = base::ReadFileToString(store_path_, &contents); | |
109 V4StoreFileFormat file_format; | |
Scott Hess - ex-Googler
2016/06/22 22:58:39
Push file_format down to where it's used.
vakh (use Gerrit instead)
2016/06/23 23:09:31
Done.
| |
110 if (!read_success) { | |
111 return FILE_UNREADABLE_FAILURE; | |
112 } | |
113 | |
114 if (contents.empty()) { | |
115 return FILE_EMPTY_FAILURE; | |
116 } | |
117 | |
118 if (!file_format.ParseFromString(contents)) { | |
119 return PROTO_PARSING_FAILURE; | |
120 } | |
121 | |
122 if (!file_format.has_magic_number() || | |
123 file_format.magic_number() != kFileMagic) { | |
124 DVLOG(1) << "Unexpected magic number found in file: " | |
125 << file_format.magic_number(); | |
Scott Hess - ex-Googler
2016/06/22 22:58:38
What does this DVLOG do for !has_magic_number()?
vakh (use Gerrit instead)
2016/06/23 23:09:31
You probably meant something else, but if you aski
Scott Hess - ex-Googler
2016/06/24 04:14:11
Mostly I meant that this case is both !has_magic_n
vakh (use Gerrit instead)
2016/06/24 18:03:50
Done.
| |
126 return UNEXPECTED_MAGIC_NUMBER_FAILURE; | |
127 } | |
128 | |
129 UMA_HISTOGRAM_SPARSE_SLOWLY("SafeBrowsing.V4StoreVersionRead", | |
130 file_format.version_number()); | |
Scott Hess - ex-Googler
2016/06/22 22:58:38
Should probably check if you have a version number
vakh (use Gerrit instead)
2016/06/23 23:09:31
Yes, it returns 0 if it isn't set.
We never expect
| |
131 if (!file_format.has_version_number() || | |
132 file_format.version_number() != kFileVersion) { | |
133 DVLOG(1) << "File version incompatible: " << file_format.version_number() | |
134 << "; expected: " << kFileVersion; | |
Scott Hess - ex-Googler
2016/06/22 22:58:38
Maybe this should be a DCHECK, I can't think of an
vakh (use Gerrit instead)
2016/06/23 23:09:31
DCHECKs make it hard to test conditions. I feel te
Scott Hess - ex-Googler
2016/06/24 04:14:11
Acknowledged.
| |
135 return FILE_VERSION_INCOMPATIBLE_FAILURE; | |
136 } | |
137 | |
138 if (!file_format.has_list_update_response()) { | |
139 return HASH_PREFIX_INFO_MISSING_FAILURE; | |
140 } | |
141 | |
142 ListUpdateResponse list_update_response = file_format.list_update_response(); | |
143 state_ = list_update_response.new_client_state(); | |
144 // TODO(vakh): Do more with what's read from the disk. | |
145 | |
146 return READ_SUCCESS; | |
147 } | |
148 | |
149 StoreWriteResult V4Store::WriteToDisk( | |
150 const ListUpdateResponse& response) const { | |
151 // Do not write partial updates to the disk. | |
152 // After merging the updates, the ListUpdateResponse passed to this method | |
153 // should be a FULL_UPDATE. | |
154 if (!response.has_response_type() || | |
155 response.response_type() != ListUpdateResponse::FULL_UPDATE) { | |
156 DVLOG(1) << "response.has_response_type(): " | |
157 << response.has_response_type(); | |
158 DVLOG(1) << "response.response_type(): " << response.response_type(); | |
Scott Hess - ex-Googler
2016/06/22 22:58:39
I think just going with a DCHECK would be fair for
vakh (use Gerrit instead)
2016/06/23 23:09:31
DCHECK vs tests, as above.
| |
159 return INVALID_RESPONSE_TYPE_FAILURE; | |
160 } | |
161 | |
162 // Attempt writing to a temporary file first and at the end, swap the files. | |
163 const base::FilePath new_filename = TemporaryFileForFilename(store_path_); | |
164 | |
165 V4StoreFileFormat file_format; | |
166 file_format.set_magic_number(kFileMagic); | |
167 file_format.set_version_number(kFileVersion); | |
168 ListUpdateResponse* response_to_write = | |
169 file_format.mutable_list_update_response(); | |
170 *response_to_write = response; | |
Scott Hess - ex-Googler
2016/06/22 22:58:38
Is there no set_list_update_response() method?
vakh (use Gerrit instead)
2016/06/23 23:09:31
No, it is a repeated field so no set_X only add_X
Scott Hess - ex-Googler
2016/06/24 04:14:11
Acknowledged.
| |
171 std::string file_format_string; | |
172 file_format.SerializeToString(&file_format_string); | |
173 size_t written = base::WriteFile(new_filename, file_format_string.c_str(), | |
174 file_format_string.size()); | |
Scott Hess - ex-Googler
2016/06/22 22:58:38
file_format_string.c_str() has to nul-terminate.
vakh (use Gerrit instead)
2016/06/23 23:09:32
I didn't know about data(), but it looks like it's
Scott Hess - ex-Googler
2016/06/24 04:14:11
Sigh. This changed between C++98 and C++11.
I th
vakh (use Gerrit instead)
2016/06/24 18:03:50
Done.
| |
175 if (file_format_string.size() != written) { | |
176 DVLOG(1) << "file_format_string.size(): " << file_format_string.size(); | |
177 DVLOG(1) << "written: " << written; | |
Scott Hess - ex-Googler
2016/06/22 22:58:39
DCHECK_EQ() would seem reasonable, this should nev
vakh (use Gerrit instead)
2016/06/23 23:09:31
Done.
| |
178 return UNEXPECTED_BYTES_WRITTEN_FAILURE; | |
179 } | |
180 | |
181 if (!base::Move(new_filename, store_path_)) { | |
182 DVLOG(1) << "store_path_: " << store_path_.value(); | |
183 DVLOG(1) << "new_filename: " << new_filename.value(); | |
Scott Hess - ex-Googler
2016/06/22 22:58:38
new_filename can be derived (or store_path_ can be
vakh (use Gerrit instead)
2016/06/23 23:09:31
Done.
| |
184 return UNABLE_TO_RENAME_FAILURE; | |
185 } | |
186 | |
187 return WRITE_SUCCESS; | |
188 } | |
189 | |
59 } // namespace safe_browsing | 190 } // namespace safe_browsing |
OLD | NEW |