| 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());
 | 
| +}
 | 
| 
 |