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

Side by Side Diff: chrome/browser/spellchecker/spellcheck_custom_dictionary.cc

Issue 11414282: Improve reliability of custom spelling dictionary file. (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: Address comments Created 8 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 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 "chrome/browser/spellchecker/spellcheck_custom_dictionary.h" 5 #include "chrome/browser/spellchecker/spellcheck_custom_dictionary.h"
6 6
7 #include <functional> 7 #include <functional>
8 8
9 #include "base/file_util.h" 9 #include "base/file_util.h"
10 #include "base/files/important_file_writer.h"
11 #include "base/md5.h"
10 #include "base/string_split.h" 12 #include "base/string_split.h"
11 #include "chrome/browser/profiles/profile.h" 13 #include "chrome/browser/profiles/profile.h"
12 #include "chrome/common/chrome_constants.h" 14 #include "chrome/common/chrome_constants.h"
13 #include "chrome/common/spellcheck_messages.h" 15 #include "chrome/common/spellcheck_messages.h"
14 #include "content/public/browser/browser_thread.h" 16 #include "content/public/browser/browser_thread.h"
15 #include "content/public/browser/render_process_host.h" 17 #include "content/public/browser/render_process_host.h"
16 18
17 using content::BrowserThread; 19 using content::BrowserThread;
18 using chrome::spellcheck_common::WordList; 20 using chrome::spellcheck_common::WordList;
19 21
22 namespace {
23
24 const FilePath::CharType BACKUP_EXTENSION[] = FILE_PATH_LITERAL("backup");
25 const char CHECKSUM_PREFIX[] = "checksum_v1 = ";
26
27 // Loads the lines from the file at |file_path| into the |lines| container. If
28 // the file has a valid checksum, then returns |true|. If the file has an
29 // invalid checksum, then returns |false| and clears |lines|.
30 bool LoadFile(FilePath file_path, std::vector<std::string>* lines) {
31 lines->clear();
32 std::string contents;
33 file_util::ReadFileToString(file_path, &contents);
34 size_t pos = contents.rfind(CHECKSUM_PREFIX);
35 if (pos != std::string::npos) {
36 std::string checksum = contents.substr(pos + strlen(CHECKSUM_PREFIX));
37 contents = contents.substr(0, pos);
38 if (checksum != base::MD5String(contents))
39 return false;
40 }
41 TrimWhitespaceASCII(contents, TRIM_ALL, &contents);
42 base::SplitString(contents, '\n', lines);
43 return true;
44 }
45
46 bool IsValidWord(const std::string& word) {
47 return IsStringUTF8(word) && word.length() <= 128 && word.length() > 0 &&
48 std::string::npos == word.find_first_of(kWhitespaceASCII);
49 }
50
51 bool IsInvalidWord(const std::string& word) {
52 return !IsValidWord(word);
53 }
54
55 } // namespace
56
20 SpellcheckCustomDictionary::SpellcheckCustomDictionary(Profile* profile) 57 SpellcheckCustomDictionary::SpellcheckCustomDictionary(Profile* profile)
21 : SpellcheckDictionary(profile), 58 : SpellcheckDictionary(profile),
22 custom_dictionary_path_(), 59 custom_dictionary_path_(),
23 weak_ptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) { 60 weak_ptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) {
24 DCHECK(profile); 61 DCHECK(profile);
25 custom_dictionary_path_ = 62 custom_dictionary_path_ =
26 profile_->GetPath().Append(chrome::kCustomDictionaryFileName); 63 profile_->GetPath().Append(chrome::kCustomDictionaryFileName);
27 } 64 }
28 65
29 SpellcheckCustomDictionary::~SpellcheckCustomDictionary() { 66 SpellcheckCustomDictionary::~SpellcheckCustomDictionary() {
(...skipping 12 matching lines...) Expand all
42 } 79 }
43 80
44 const WordList& SpellcheckCustomDictionary::GetWords() const { 81 const WordList& SpellcheckCustomDictionary::GetWords() const {
45 return words_; 82 return words_;
46 } 83 }
47 84
48 void SpellcheckCustomDictionary::LoadDictionaryIntoCustomWordList( 85 void SpellcheckCustomDictionary::LoadDictionaryIntoCustomWordList(
49 WordList* custom_words) { 86 WordList* custom_words) {
50 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 87 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
51 88
52 std::string contents; 89 LoadDictionaryFileReliably(custom_words);
53 file_util::ReadFileToString(custom_dictionary_path_, &contents); 90 if (custom_words->empty())
54 if (contents.empty()) {
55 custom_words->clear();
56 return; 91 return;
57 }
58 92
59 base::SplitString(contents, '\n', custom_words); 93 // Clean up the dictionary file contents by removing duplicates and invalid
60 94 // words.
61 // Erase duplicates.
62 std::sort(custom_words->begin(), custom_words->end()); 95 std::sort(custom_words->begin(), custom_words->end());
63 custom_words->erase(std::unique(custom_words->begin(), custom_words->end()), 96 custom_words->erase(std::unique(custom_words->begin(), custom_words->end()),
64 custom_words->end()); 97 custom_words->end());
98 custom_words->erase(std::remove_if(custom_words->begin(),
99 custom_words->end(),
100 IsInvalidWord),
101 custom_words->end());
65 102
66 // Clear out empty words. 103 SaveDictionaryFileReliably(*custom_words);
67 custom_words->erase(remove_if(custom_words->begin(), custom_words->end(),
68 mem_fun_ref(&std::string::empty)), custom_words->end());
69
70 // Write out the clean file.
71 std::stringstream ss;
72 for (WordList::iterator it = custom_words->begin();
73 it != custom_words->end();
74 ++it) {
75 ss << *it << '\n';
76 }
77 contents = ss.str();
78 file_util::WriteFile(custom_dictionary_path_,
79 contents.c_str(),
80 contents.length());
81 } 104 }
82 105
83 void SpellcheckCustomDictionary::SetCustomWordList(WordList* custom_words) { 106 void SpellcheckCustomDictionary::SetCustomWordList(WordList* custom_words) {
84 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 107 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
85 108
86 words_.clear(); 109 words_.clear();
87 if (custom_words) 110 if (custom_words)
88 std::swap(words_, *custom_words); 111 std::swap(words_, *custom_words);
89 112
90 std::vector<Observer*>::iterator it; 113 FOR_EACH_OBSERVER(Observer, observers_, OnCustomDictionaryLoaded());
91 for (it = observers_.begin(); it != observers_.end(); ++it)
92 (*it)->OnCustomDictionaryLoaded();
93 } 114 }
94 115
95 bool SpellcheckCustomDictionary::AddWord(const std::string& word) { 116 bool SpellcheckCustomDictionary::AddWord(const std::string& word) {
96 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 117 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
118 if (!IsValidWord(word))
119 return false;
97 120
98 if (!CustomWordAddedLocally(word)) 121 if (!CustomWordAddedLocally(word))
99 return false; 122 return false;
100 123
101 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 124 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
102 base::Bind(&SpellcheckCustomDictionary::WriteWordToCustomDictionary, 125 base::Bind(&SpellcheckCustomDictionary::WriteWordToCustomDictionary,
103 base::Unretained(this), word)); 126 base::Unretained(this), word));
104 127
105 for (content::RenderProcessHost::iterator i( 128 for (content::RenderProcessHost::iterator i(
106 content::RenderProcessHost::AllHostsIterator()); 129 content::RenderProcessHost::AllHostsIterator());
107 !i.IsAtEnd(); i.Advance()) { 130 !i.IsAtEnd(); i.Advance()) {
108 i.GetCurrentValue()->Send(new SpellCheckMsg_WordAdded(word)); 131 i.GetCurrentValue()->Send(new SpellCheckMsg_WordAdded(word));
109 } 132 }
110 133
111 std::vector<Observer*>::iterator it; 134 FOR_EACH_OBSERVER(Observer, observers_, OnCustomDictionaryWordAdded(word));
112 for (it = observers_.begin(); it != observers_.end(); ++it)
113 (*it)->OnCustomDictionaryWordAdded(word);
114 135
115 return true; 136 return true;
116 } 137 }
117 138
118 bool SpellcheckCustomDictionary::CustomWordAddedLocally( 139 bool SpellcheckCustomDictionary::CustomWordAddedLocally(
119 const std::string& word) { 140 const std::string& word) {
120 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 141 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
142 DCHECK(IsValidWord(word));
121 143
122 WordList::iterator it = std::find(words_.begin(), words_.end(), word); 144 WordList::iterator it = std::find(words_.begin(), words_.end(), word);
123 if (it == words_.end()) { 145 if (it == words_.end()) {
124 words_.push_back(word); 146 words_.push_back(word);
125 return true; 147 return true;
126 } 148 }
127 return false; 149 return false;
128 // TODO(rlp): record metrics on custom word size 150 // TODO(rlp): record metrics on custom word size
129 } 151 }
130 152
131 void SpellcheckCustomDictionary::WriteWordToCustomDictionary( 153 void SpellcheckCustomDictionary::WriteWordToCustomDictionary(
132 const std::string& word) { 154 const std::string& word) {
133 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 155 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
156 DCHECK(IsValidWord(word));
134 157
135 // Stored in UTF-8. 158 WordList custom_words;
136 DCHECK(IsStringUTF8(word)); 159 LoadDictionaryFileReliably(&custom_words);
137 160 custom_words.push_back(word);
138 std::string word_to_add(word + "\n"); 161 SaveDictionaryFileReliably(custom_words);
139 if (!file_util::PathExists(custom_dictionary_path_)) {
140 file_util::WriteFile(custom_dictionary_path_, word_to_add.c_str(),
141 word_to_add.length());
142 } else {
143 file_util::AppendToFile(custom_dictionary_path_, word_to_add.c_str(),
144 word_to_add.length());
145 }
146 } 162 }
147 163
148 bool SpellcheckCustomDictionary::RemoveWord(const std::string& word) { 164 bool SpellcheckCustomDictionary::RemoveWord(const std::string& word) {
149 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 165 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
166 if (!IsValidWord(word))
167 return false;
150 168
151 if (!CustomWordRemovedLocally(word)) 169 if (!CustomWordRemovedLocally(word))
152 return false; 170 return false;
153 171
154 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 172 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
155 base::Bind(&SpellcheckCustomDictionary::EraseWordFromCustomDictionary, 173 base::Bind(&SpellcheckCustomDictionary::EraseWordFromCustomDictionary,
156 base::Unretained(this), word)); 174 base::Unretained(this), word));
157 175
158 for (content::RenderProcessHost::iterator i( 176 for (content::RenderProcessHost::iterator i(
159 content::RenderProcessHost::AllHostsIterator()); 177 content::RenderProcessHost::AllHostsIterator());
160 !i.IsAtEnd(); i.Advance()) { 178 !i.IsAtEnd(); i.Advance()) {
161 i.GetCurrentValue()->Send(new SpellCheckMsg_WordRemoved(word)); 179 i.GetCurrentValue()->Send(new SpellCheckMsg_WordRemoved(word));
162 } 180 }
163 181
164 std::vector<Observer*>::iterator it; 182 FOR_EACH_OBSERVER(Observer, observers_, OnCustomDictionaryWordRemoved(word));
165 for (it = observers_.begin(); it != observers_.end(); ++it)
166 (*it)->OnCustomDictionaryWordRemoved(word);
167 183
168 return true; 184 return true;
169 } 185 }
170 186
171 bool SpellcheckCustomDictionary::CustomWordRemovedLocally( 187 bool SpellcheckCustomDictionary::CustomWordRemovedLocally(
172 const std::string& word) { 188 const std::string& word) {
173 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 189 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
190 DCHECK(IsValidWord(word));
174 191
175 WordList::iterator it = std::find(words_.begin(), words_.end(), word); 192 WordList::iterator it = std::find(words_.begin(), words_.end(), word);
176 if (it != words_.end()) { 193 if (it != words_.end()) {
177 words_.erase(it); 194 words_.erase(it);
178 return true; 195 return true;
179 } 196 }
180 return false; 197 return false;
181 } 198 }
182 199
183 void SpellcheckCustomDictionary::EraseWordFromCustomDictionary( 200 void SpellcheckCustomDictionary::EraseWordFromCustomDictionary(
184 const std::string& word) { 201 const std::string& word) {
185 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 202 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
186 DCHECK(IsStringUTF8(word)); 203 DCHECK(IsValidWord(word));
187 204
188 WordList custom_words; 205 WordList custom_words;
189 LoadDictionaryIntoCustomWordList(&custom_words); 206 LoadDictionaryFileReliably(&custom_words);
207 if (custom_words.empty())
208 return;
190 209
191 const char empty[] = {'\0'}; 210 WordList::iterator it = std::find(custom_words.begin(),
192 const char separator[] = {'\n', '\0'}; 211 custom_words.end(),
193 file_util::WriteFile(custom_dictionary_path_, empty, 0); 212 word);
194 for (WordList::iterator it = custom_words.begin(); 213 if (it != custom_words.end())
195 it != custom_words.end(); 214 custom_words.erase(it);
196 ++it) { 215
197 std::string word_to_add = *it; 216 SaveDictionaryFileReliably(custom_words);
198 if (word.compare(word_to_add) != 0) {
199 file_util::AppendToFile(custom_dictionary_path_, word_to_add.c_str(),
200 word_to_add.length());
201 file_util::AppendToFile(custom_dictionary_path_, separator, 1);
202 }
203 }
204 } 217 }
205 218
206 void SpellcheckCustomDictionary::AddObserver(Observer* observer) { 219 void SpellcheckCustomDictionary::AddObserver(Observer* observer) {
207 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 220 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
208 221
209 observers_.push_back(observer); 222 observers_.AddObserver(observer);
210 } 223 }
211 224
212 void SpellcheckCustomDictionary::RemoveObserver(Observer* observer) { 225 void SpellcheckCustomDictionary::RemoveObserver(Observer* observer) {
213 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 226 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
214 227
215 std::vector<Observer*>::iterator it = std::find(observers_.begin(), 228 observers_.RemoveObserver(observer);
216 observers_.end(),
217 observer);
218 if (it != observers_.end())
219 observers_.erase(it);
220 } 229 }
221 230
222 WordList* SpellcheckCustomDictionary::LoadDictionary() { 231 WordList* SpellcheckCustomDictionary::LoadDictionary() {
223 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 232 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
224 233
225 WordList* custom_words = new WordList; 234 WordList* custom_words = new WordList;
226 LoadDictionaryIntoCustomWordList(custom_words); 235 LoadDictionaryIntoCustomWordList(custom_words);
227 return custom_words; 236 return custom_words;
228 } 237 }
229 238
230 void SpellcheckCustomDictionary::SetCustomWordListAndDelete( 239 void SpellcheckCustomDictionary::SetCustomWordListAndDelete(
231 WordList* custom_words) { 240 WordList* custom_words) {
232 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 241 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
233 242
234 SetCustomWordList(custom_words); 243 SetCustomWordList(custom_words);
235 delete custom_words; 244 delete custom_words;
236 } 245 }
246
247 void SpellcheckCustomDictionary::LoadDictionaryFileReliably(
248 WordList* custom_words) {
249 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
250
251 // Load the contents and verify the checksum.
252 if (LoadFile(custom_dictionary_path_, custom_words))
253 return;
254
255 // Checksum is not valid. See if there's a backup.
256 FilePath backup = custom_dictionary_path_.AddExtension(BACKUP_EXTENSION);
257 if (!file_util::PathExists(backup))
258 return;
259
260 // Load the backup and verify its checksum.
261 if (!LoadFile(backup, custom_words))
262 return;
263
264 // Backup checksum is valid. Restore the backup.
265 file_util::CopyFile(backup, custom_dictionary_path_);
266 }
267
268 void SpellcheckCustomDictionary::SaveDictionaryFileReliably(
269 const WordList& custom_words) {
270 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
271
272 std::stringstream content;
273 for (WordList::const_iterator it = custom_words.begin();
274 it != custom_words.end();
275 ++it) {
276 content << *it << '\n';
277 }
278 std::string checksum = base::MD5String(content.str());
279 content << CHECKSUM_PREFIX << checksum;
280
281 file_util::CopyFile(custom_dictionary_path_,
282 custom_dictionary_path_.AddExtension(BACKUP_EXTENSION));
283 base::ImportantFileWriter::WriteFileAtomically(custom_dictionary_path_,
284 content.str());
285 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698