Index: chrome/browser/spellchecker/spellcheck_custom_dictionary.cc |
diff --git a/chrome/browser/spellchecker/spellcheck_custom_dictionary.cc b/chrome/browser/spellchecker/spellcheck_custom_dictionary.cc |
index 4510332e59e3032ee7b35bf26f640ff6f3f2885c..588e3675c2fa3d8e22620f578cd497abef322e16 100644 |
--- a/chrome/browser/spellchecker/spellcheck_custom_dictionary.cc |
+++ b/chrome/browser/spellchecker/spellcheck_custom_dictionary.cc |
@@ -7,6 +7,8 @@ |
#include <functional> |
#include "base/file_util.h" |
+#include "base/files/important_file_writer.h" |
+#include "base/md5.h" |
#include "base/string_split.h" |
#include "chrome/browser/profiles/profile.h" |
#include "chrome/common/chrome_constants.h" |
@@ -17,6 +19,41 @@ |
using content::BrowserThread; |
using chrome::spellcheck_common::WordList; |
+namespace { |
+ |
+const FilePath::CharType BACKUP_EXTENSION[] = FILE_PATH_LITERAL("backup"); |
+const char CHECKSUM_PREFIX[] = "checksum_v1 = "; |
+ |
+// Loads the lines from the file at |file_path| into the |lines| container. If |
+// the file has a valid checksum, then returns |true|. If the file has an |
+// invalid checksum, then returns |false| and clears |lines|. |
+bool LoadFile(FilePath file_path, std::vector<std::string>* lines) { |
+ lines->clear(); |
+ std::string contents; |
+ file_util::ReadFileToString(file_path, &contents); |
+ size_t pos = contents.rfind(CHECKSUM_PREFIX); |
+ if (pos != std::string::npos) { |
+ std::string checksum = contents.substr(pos + strlen(CHECKSUM_PREFIX)); |
+ contents = contents.substr(0, pos); |
+ if (checksum != base::MD5String(contents)) |
+ return false; |
+ } |
+ TrimWhitespaceASCII(contents, TRIM_ALL, &contents); |
+ base::SplitString(contents, '\n', lines); |
+ return true; |
+} |
+ |
+bool IsValidWord(const std::string& word) { |
+ return IsStringUTF8(word) && word.length() <= 128 && word.length() > 0 && |
+ std::string::npos == word.find_first_of(kWhitespaceASCII); |
+} |
+ |
+bool IsInvalidWord(const std::string& word) { |
+ return !IsValidWord(word); |
+} |
+ |
+} // namespace |
+ |
SpellcheckCustomDictionary::SpellcheckCustomDictionary(Profile* profile) |
: SpellcheckDictionary(profile), |
custom_dictionary_path_(), |
@@ -49,35 +86,21 @@ void SpellcheckCustomDictionary::LoadDictionaryIntoCustomWordList( |
WordList* custom_words) { |
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
- std::string contents; |
- file_util::ReadFileToString(custom_dictionary_path_, &contents); |
- if (contents.empty()) { |
- custom_words->clear(); |
+ LoadDictionaryFileReliably(custom_words); |
+ if (custom_words->empty()) |
return; |
- } |
- |
- base::SplitString(contents, '\n', custom_words); |
- // Erase duplicates. |
+ // Clean up the dictionary file contents by removing duplicates and invalid |
+ // words. |
std::sort(custom_words->begin(), custom_words->end()); |
custom_words->erase(std::unique(custom_words->begin(), custom_words->end()), |
custom_words->end()); |
+ custom_words->erase(std::remove_if(custom_words->begin(), |
+ custom_words->end(), |
+ IsInvalidWord), |
+ custom_words->end()); |
- // Clear out empty words. |
- custom_words->erase(remove_if(custom_words->begin(), custom_words->end(), |
- mem_fun_ref(&std::string::empty)), custom_words->end()); |
- |
- // Write out the clean file. |
- std::stringstream ss; |
- for (WordList::iterator it = custom_words->begin(); |
- it != custom_words->end(); |
- ++it) { |
- ss << *it << '\n'; |
- } |
- contents = ss.str(); |
- file_util::WriteFile(custom_dictionary_path_, |
- contents.c_str(), |
- contents.length()); |
+ SaveDictionaryFileReliably(*custom_words); |
} |
void SpellcheckCustomDictionary::SetCustomWordList(WordList* custom_words) { |
@@ -87,13 +110,13 @@ void SpellcheckCustomDictionary::SetCustomWordList(WordList* custom_words) { |
if (custom_words) |
std::swap(words_, *custom_words); |
- std::vector<Observer*>::iterator it; |
- for (it = observers_.begin(); it != observers_.end(); ++it) |
- (*it)->OnCustomDictionaryLoaded(); |
+ FOR_EACH_OBSERVER(Observer, observers_, OnCustomDictionaryLoaded()); |
} |
bool SpellcheckCustomDictionary::AddWord(const std::string& word) { |
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ if (!IsValidWord(word)) |
+ return false; |
if (!CustomWordAddedLocally(word)) |
return false; |
@@ -108,9 +131,7 @@ bool SpellcheckCustomDictionary::AddWord(const std::string& word) { |
i.GetCurrentValue()->Send(new SpellCheckMsg_WordAdded(word)); |
} |
- std::vector<Observer*>::iterator it; |
- for (it = observers_.begin(); it != observers_.end(); ++it) |
- (*it)->OnCustomDictionaryWordAdded(word); |
+ FOR_EACH_OBSERVER(Observer, observers_, OnCustomDictionaryWordAdded(word)); |
return true; |
} |
@@ -118,6 +139,7 @@ bool SpellcheckCustomDictionary::AddWord(const std::string& word) { |
bool SpellcheckCustomDictionary::CustomWordAddedLocally( |
const std::string& word) { |
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ DCHECK(IsValidWord(word)); |
WordList::iterator it = std::find(words_.begin(), words_.end(), word); |
if (it == words_.end()) { |
@@ -131,22 +153,18 @@ bool SpellcheckCustomDictionary::CustomWordAddedLocally( |
void SpellcheckCustomDictionary::WriteWordToCustomDictionary( |
const std::string& word) { |
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
+ DCHECK(IsValidWord(word)); |
- // Stored in UTF-8. |
- DCHECK(IsStringUTF8(word)); |
- |
- std::string word_to_add(word + "\n"); |
- if (!file_util::PathExists(custom_dictionary_path_)) { |
- file_util::WriteFile(custom_dictionary_path_, word_to_add.c_str(), |
- word_to_add.length()); |
- } else { |
- file_util::AppendToFile(custom_dictionary_path_, word_to_add.c_str(), |
- word_to_add.length()); |
- } |
+ WordList custom_words; |
+ LoadDictionaryFileReliably(&custom_words); |
+ custom_words.push_back(word); |
+ SaveDictionaryFileReliably(custom_words); |
} |
bool SpellcheckCustomDictionary::RemoveWord(const std::string& word) { |
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ if (!IsValidWord(word)) |
+ return false; |
if (!CustomWordRemovedLocally(word)) |
return false; |
@@ -161,9 +179,7 @@ bool SpellcheckCustomDictionary::RemoveWord(const std::string& word) { |
i.GetCurrentValue()->Send(new SpellCheckMsg_WordRemoved(word)); |
} |
- std::vector<Observer*>::iterator it; |
- for (it = observers_.begin(); it != observers_.end(); ++it) |
- (*it)->OnCustomDictionaryWordRemoved(word); |
+ FOR_EACH_OBSERVER(Observer, observers_, OnCustomDictionaryWordRemoved(word)); |
return true; |
} |
@@ -171,6 +187,7 @@ bool SpellcheckCustomDictionary::RemoveWord(const std::string& word) { |
bool SpellcheckCustomDictionary::CustomWordRemovedLocally( |
const std::string& word) { |
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ DCHECK(IsValidWord(word)); |
WordList::iterator it = std::find(words_.begin(), words_.end(), word); |
if (it != words_.end()) { |
@@ -183,40 +200,32 @@ bool SpellcheckCustomDictionary::CustomWordRemovedLocally( |
void SpellcheckCustomDictionary::EraseWordFromCustomDictionary( |
const std::string& word) { |
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
- DCHECK(IsStringUTF8(word)); |
+ DCHECK(IsValidWord(word)); |
WordList custom_words; |
- LoadDictionaryIntoCustomWordList(&custom_words); |
- |
- const char empty[] = {'\0'}; |
- const char separator[] = {'\n', '\0'}; |
- file_util::WriteFile(custom_dictionary_path_, empty, 0); |
- for (WordList::iterator it = custom_words.begin(); |
- it != custom_words.end(); |
- ++it) { |
- std::string word_to_add = *it; |
- if (word.compare(word_to_add) != 0) { |
- file_util::AppendToFile(custom_dictionary_path_, word_to_add.c_str(), |
- word_to_add.length()); |
- file_util::AppendToFile(custom_dictionary_path_, separator, 1); |
- } |
- } |
+ LoadDictionaryFileReliably(&custom_words); |
+ if (custom_words.empty()) |
+ return; |
+ |
+ WordList::iterator it = std::find(custom_words.begin(), |
+ custom_words.end(), |
+ word); |
+ if (it != custom_words.end()) |
+ custom_words.erase(it); |
+ |
+ SaveDictionaryFileReliably(custom_words); |
} |
void SpellcheckCustomDictionary::AddObserver(Observer* observer) { |
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
- observers_.push_back(observer); |
+ observers_.AddObserver(observer); |
} |
void SpellcheckCustomDictionary::RemoveObserver(Observer* observer) { |
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
- std::vector<Observer*>::iterator it = std::find(observers_.begin(), |
- observers_.end(), |
- observer); |
- if (it != observers_.end()) |
- observers_.erase(it); |
+ observers_.RemoveObserver(observer); |
} |
WordList* SpellcheckCustomDictionary::LoadDictionary() { |
@@ -234,3 +243,43 @@ void SpellcheckCustomDictionary::SetCustomWordListAndDelete( |
SetCustomWordList(custom_words); |
delete custom_words; |
} |
+ |
+void SpellcheckCustomDictionary::LoadDictionaryFileReliably( |
+ WordList* custom_words) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
+ |
+ // Load the contents and verify the checksum. |
+ if (LoadFile(custom_dictionary_path_, custom_words)) |
+ return; |
+ |
+ // Checksum is not valid. See if there's a backup. |
+ FilePath backup = custom_dictionary_path_.AddExtension(BACKUP_EXTENSION); |
+ if (!file_util::PathExists(backup)) |
+ return; |
+ |
+ // Load the backup and verify its checksum. |
+ if (!LoadFile(backup, custom_words)) |
+ return; |
+ |
+ // Backup checksum is valid. Restore the backup. |
+ file_util::CopyFile(backup, custom_dictionary_path_); |
+} |
+ |
+void SpellcheckCustomDictionary::SaveDictionaryFileReliably( |
+ const WordList& custom_words) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
+ |
+ std::stringstream content; |
+ for (WordList::const_iterator it = custom_words.begin(); |
+ it != custom_words.end(); |
+ ++it) { |
+ content << *it << '\n'; |
+ } |
+ std::string checksum = base::MD5String(content.str()); |
+ content << CHECKSUM_PREFIX << checksum; |
+ |
+ file_util::CopyFile(custom_dictionary_path_, |
+ custom_dictionary_path_.AddExtension(BACKUP_EXTENSION)); |
+ base::ImportantFileWriter::WriteFileAtomically(custom_dictionary_path_, |
+ content.str()); |
+} |