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