Home | History | Annotate | Download | only in spellchecker
      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
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/spellchecker/spellcheck_hunspell_dictionary.h"
      6 
      7 #include "base/file_util.h"
      8 #include "base/files/memory_mapped_file.h"
      9 #include "base/logging.h"
     10 #include "base/message_loop/message_loop.h"
     11 #include "base/path_service.h"
     12 #include "chrome/browser/spellchecker/spellcheck_platform_mac.h"
     13 #include "chrome/browser/spellchecker/spellcheck_service.h"
     14 #include "chrome/common/chrome_paths.h"
     15 #include "chrome/common/spellcheck_common.h"
     16 #include "chrome/common/spellcheck_messages.h"
     17 #include "content/public/browser/browser_thread.h"
     18 #include "content/public/browser/render_process_host.h"
     19 #include "net/base/load_flags.h"
     20 #include "net/url_request/url_fetcher.h"
     21 #include "net/url_request/url_request_context_getter.h"
     22 #include "third_party/hunspell/google/bdict.h"
     23 #include "url/gurl.h"
     24 
     25 using content::BrowserThread;
     26 
     27 namespace {
     28 
     29 // Close the file.
     30 void CloseDictionary(base::File file) {
     31   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
     32   file.Close();
     33 }
     34 
     35 // Saves |data| to file at |path|. Returns true on successful save, otherwise
     36 // returns false.
     37 bool SaveDictionaryData(scoped_ptr<std::string> data,
     38                         const base::FilePath& path) {
     39   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
     40 
     41   size_t bytes_written =
     42       base::WriteFile(path, data->data(), data->length());
     43   if (bytes_written != data->length()) {
     44     bool success = false;
     45 #if defined(OS_WIN)
     46     base::FilePath dict_dir;
     47     PathService::Get(chrome::DIR_USER_DATA, &dict_dir);
     48     base::FilePath fallback_file_path =
     49         dict_dir.Append(path.BaseName());
     50     bytes_written =
     51         base::WriteFile(fallback_file_path, data->data(), data->length());
     52     if (bytes_written == data->length())
     53       success = true;
     54 #endif
     55 
     56     if (!success) {
     57       base::DeleteFile(path, false);
     58       return false;
     59     }
     60   }
     61 
     62   return true;
     63 }
     64 
     65 }  // namespace
     66 
     67 SpellcheckHunspellDictionary::DictionaryFile::DictionaryFile() {
     68  }
     69 
     70  SpellcheckHunspellDictionary::DictionaryFile::~DictionaryFile() {
     71   if (file.IsValid()) {
     72     BrowserThread::PostTask(
     73         BrowserThread::FILE,
     74         FROM_HERE,
     75         base::Bind(&CloseDictionary, Passed(&file)));
     76   }
     77 }
     78 
     79 SpellcheckHunspellDictionary::DictionaryFile::DictionaryFile(RValue other)
     80     : path(other.object->path),
     81       file(other.object->file.Pass()) {
     82 }
     83 
     84 SpellcheckHunspellDictionary::DictionaryFile&
     85 SpellcheckHunspellDictionary::DictionaryFile::operator=(RValue other) {
     86   if (this != other.object) {
     87     path = other.object->path;
     88     file = other.object->file.Pass();
     89   }
     90   return *this;
     91 }
     92 
     93 SpellcheckHunspellDictionary::SpellcheckHunspellDictionary(
     94     const std::string& language,
     95     net::URLRequestContextGetter* request_context_getter,
     96     SpellcheckService* spellcheck_service)
     97     : language_(language),
     98       use_platform_spellchecker_(false),
     99       request_context_getter_(request_context_getter),
    100       spellcheck_service_(spellcheck_service),
    101       download_status_(DOWNLOAD_NONE),
    102       weak_ptr_factory_(this) {
    103 }
    104 
    105 SpellcheckHunspellDictionary::~SpellcheckHunspellDictionary() {
    106 }
    107 
    108 void SpellcheckHunspellDictionary::Load() {
    109   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    110 
    111 #if defined(OS_MACOSX)
    112   if (spellcheck_mac::SpellCheckerAvailable() &&
    113       spellcheck_mac::PlatformSupportsLanguage(language_)) {
    114     use_platform_spellchecker_ = true;
    115     spellcheck_mac::SetLanguage(language_);
    116     base::MessageLoop::current()->PostTask(FROM_HERE,
    117         base::Bind(
    118             &SpellcheckHunspellDictionary::InformListenersOfInitialization,
    119             weak_ptr_factory_.GetWeakPtr()));
    120     return;
    121   }
    122 #endif  // OS_MACOSX
    123 
    124   BrowserThread::PostTaskAndReplyWithResult(
    125       BrowserThread::FILE,
    126       FROM_HERE,
    127       base::Bind(&InitializeDictionaryLocation, language_),
    128       base::Bind(
    129           &SpellcheckHunspellDictionary::InitializeDictionaryLocationComplete,
    130           weak_ptr_factory_.GetWeakPtr()));
    131 }
    132 
    133 void SpellcheckHunspellDictionary::RetryDownloadDictionary(
    134       net::URLRequestContextGetter* request_context_getter) {
    135   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    136   request_context_getter_ = request_context_getter;
    137   DownloadDictionary(GetDictionaryURL());
    138 }
    139 
    140 bool SpellcheckHunspellDictionary::IsReady() const {
    141   return GetDictionaryFile().IsValid() || IsUsingPlatformChecker();
    142 }
    143 
    144 const base::File& SpellcheckHunspellDictionary::GetDictionaryFile() const {
    145   return dictionary_file_.file;
    146 }
    147 
    148 const std::string& SpellcheckHunspellDictionary::GetLanguage() const {
    149   return language_;
    150 }
    151 
    152 bool SpellcheckHunspellDictionary::IsUsingPlatformChecker() const {
    153   return use_platform_spellchecker_;
    154 }
    155 
    156 void SpellcheckHunspellDictionary::AddObserver(Observer* observer) {
    157   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    158   observers_.AddObserver(observer);
    159 }
    160 
    161 void SpellcheckHunspellDictionary::RemoveObserver(Observer* observer) {
    162   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    163   observers_.RemoveObserver(observer);
    164 }
    165 
    166 bool SpellcheckHunspellDictionary::IsDownloadInProgress() {
    167   return download_status_ == DOWNLOAD_IN_PROGRESS;
    168 }
    169 
    170 bool SpellcheckHunspellDictionary::IsDownloadFailure() {
    171   return download_status_ == DOWNLOAD_FAILED;
    172 }
    173 
    174 void SpellcheckHunspellDictionary::OnURLFetchComplete(
    175     const net::URLFetcher* source) {
    176   DCHECK(source);
    177   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    178   scoped_ptr<net::URLFetcher> fetcher_destructor(fetcher_.release());
    179 
    180   if ((source->GetResponseCode() / 100) != 2) {
    181     // Initialize will not try to download the file a second time.
    182     InformListenersOfDownloadFailure();
    183     return;
    184   }
    185 
    186   // Basic sanity check on the dictionary. There's a small chance of 200 status
    187   // code for a body that represents some form of failure.
    188   scoped_ptr<std::string> data(new std::string);
    189   source->GetResponseAsString(data.get());
    190   if (data->size() < 4 || data->compare(0, 4, "BDic") != 0) {
    191     InformListenersOfDownloadFailure();
    192     return;
    193   }
    194 
    195   // To prevent corrupted dictionary data from causing a renderer crash, scan
    196   // the dictionary data and verify it is sane before save it to a file.
    197   // TODO(rlp): Adding metrics to RecordDictionaryCorruptionStats
    198   if (!hunspell::BDict::Verify(data->data(), data->size())) {
    199     // Let PostTaskAndReply caller send to InformListenersOfInitialization
    200     // through SaveDictionaryDataComplete().
    201     SaveDictionaryDataComplete(false);
    202     return;
    203   }
    204 
    205   BrowserThread::PostTaskAndReplyWithResult<bool>(
    206       BrowserThread::FILE,
    207       FROM_HERE,
    208       base::Bind(&SaveDictionaryData,
    209                  base::Passed(&data),
    210                  dictionary_file_.path),
    211       base::Bind(&SpellcheckHunspellDictionary::SaveDictionaryDataComplete,
    212                  weak_ptr_factory_.GetWeakPtr()));
    213 }
    214 
    215 GURL SpellcheckHunspellDictionary::GetDictionaryURL() {
    216   static const char kDownloadServerUrl[] =
    217       "http://cache.pack.google.com/edgedl/chrome/dict/";
    218   std::string bdict_file = dictionary_file_.path.BaseName().MaybeAsASCII();
    219 
    220   DCHECK(!bdict_file.empty());
    221 
    222   return GURL(std::string(kDownloadServerUrl) +
    223               StringToLowerASCII(bdict_file));
    224 }
    225 
    226 void SpellcheckHunspellDictionary::DownloadDictionary(GURL url) {
    227   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    228   DCHECK(request_context_getter_);
    229 
    230   download_status_ = DOWNLOAD_IN_PROGRESS;
    231   FOR_EACH_OBSERVER(Observer, observers_, OnHunspellDictionaryDownloadBegin());
    232 
    233   fetcher_.reset(net::URLFetcher::Create(url, net::URLFetcher::GET, this));
    234   fetcher_->SetRequestContext(request_context_getter_);
    235   fetcher_->SetLoadFlags(
    236       net::LOAD_DO_NOT_SEND_COOKIES | net::LOAD_DO_NOT_SAVE_COOKIES);
    237   fetcher_->Start();
    238   // Attempt downloading the dictionary only once.
    239   request_context_getter_ = NULL;
    240 }
    241 
    242 // The default_dictionary_file can either come from the standard list of
    243 // hunspell dictionaries (determined in InitializeDictionaryLocation), or it
    244 // can be passed in via an extension. In either case, the file is checked for
    245 // existence so that it's not re-downloaded.
    246 // For systemwide installations on Windows, the default directory may not
    247 // have permissions for download. In that case, the alternate directory for
    248 // download is chrome::DIR_USER_DATA.
    249 SpellcheckHunspellDictionary::DictionaryFile
    250 SpellcheckHunspellDictionary::OpenDictionaryFile(const base::FilePath& path) {
    251   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    252   DictionaryFile dictionary;
    253 
    254 #if defined(OS_WIN)
    255   // Check if the dictionary exists in the fallback location. If so, use it
    256   // rather than downloading anew.
    257   base::FilePath user_dir;
    258   PathService::Get(chrome::DIR_USER_DATA, &user_dir);
    259   base::FilePath fallback = user_dir.Append(path.BaseName());
    260   if (!base::PathExists(path) && base::PathExists(fallback))
    261     dictionary.path = fallback;
    262   else
    263     dictionary.path = path;
    264 #else
    265   dictionary.path = path;
    266 #endif
    267 
    268   // Read the dictionary file and scan its data to check for corruption. The
    269   // scoping closes the memory-mapped file before it is opened or deleted.
    270   bool bdict_is_valid;
    271   {
    272     base::MemoryMappedFile map;
    273     bdict_is_valid =
    274         base::PathExists(dictionary.path) &&
    275         map.Initialize(dictionary.path) &&
    276         hunspell::BDict::Verify(reinterpret_cast<const char*>(map.data()),
    277                                 map.length());
    278   }
    279   if (bdict_is_valid) {
    280     dictionary.file.Initialize(dictionary.path,
    281                                base::File::FLAG_READ | base::File::FLAG_OPEN);
    282   } else {
    283     base::DeleteFile(dictionary.path, false);
    284   }
    285 
    286   return dictionary.Pass();
    287 }
    288 
    289 // The default place where the spellcheck dictionary resides is
    290 // chrome::DIR_APP_DICTIONARIES.
    291 SpellcheckHunspellDictionary::DictionaryFile
    292 SpellcheckHunspellDictionary::InitializeDictionaryLocation(
    293     const std::string& language) {
    294   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    295 
    296   // Initialize the BDICT path. Initialization should be in the FILE thread
    297   // because it checks if there is a "Dictionaries" directory and create it.
    298   base::FilePath dict_dir;
    299   PathService::Get(chrome::DIR_APP_DICTIONARIES, &dict_dir);
    300   base::FilePath dict_path =
    301       chrome::spellcheck_common::GetVersionedFileName(language, dict_dir);
    302 
    303   return OpenDictionaryFile(dict_path);
    304 }
    305 
    306 void SpellcheckHunspellDictionary::InitializeDictionaryLocationComplete(
    307     DictionaryFile file) {
    308   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    309   dictionary_file_ = file.Pass();
    310 
    311   if (!dictionary_file_.file.IsValid()) {
    312 
    313     // Notify browser tests that this dictionary is corrupted. Skip downloading
    314     // the dictionary in browser tests.
    315     // TODO(rouslan): Remove this test-only case.
    316     if (spellcheck_service_->SignalStatusEvent(
    317           SpellcheckService::BDICT_CORRUPTED)) {
    318       request_context_getter_ = NULL;
    319     }
    320 
    321     if (request_context_getter_) {
    322       // Download from the UI thread to check that |request_context_getter_| is
    323       // still valid.
    324       DownloadDictionary(GetDictionaryURL());
    325       return;
    326     }
    327   }
    328 
    329   InformListenersOfInitialization();
    330 }
    331 
    332 void SpellcheckHunspellDictionary::SaveDictionaryDataComplete(
    333     bool dictionary_saved) {
    334   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    335 
    336   if (dictionary_saved) {
    337     download_status_ = DOWNLOAD_NONE;
    338     FOR_EACH_OBSERVER(Observer,
    339                       observers_,
    340                       OnHunspellDictionaryDownloadSuccess());
    341     Load();
    342   } else {
    343     InformListenersOfDownloadFailure();
    344     InformListenersOfInitialization();
    345   }
    346 }
    347 
    348 void SpellcheckHunspellDictionary::InformListenersOfInitialization() {
    349   FOR_EACH_OBSERVER(Observer, observers_, OnHunspellDictionaryInitialized());
    350 }
    351 
    352 void SpellcheckHunspellDictionary::InformListenersOfDownloadFailure() {
    353   download_status_ = DOWNLOAD_FAILED;
    354   FOR_EACH_OBSERVER(Observer,
    355                     observers_,
    356                     OnHunspellDictionaryDownloadFailure());
    357 }
    358