OLD | NEW |
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 Loading... |
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 } |
OLD | NEW |