spellcheck_hunspell_dictionary.cc revision c5cede9ae108bb15f6b7a8aea21c7e1fefa2834c
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 25using content::BrowserThread; 26 27namespace { 28 29// Close the file. 30void 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. 37bool 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 67SpellcheckHunspellDictionary::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 79SpellcheckHunspellDictionary::DictionaryFile::DictionaryFile(RValue other) 80 : path(other.object->path), 81 file(other.object->file.Pass()) { 82} 83 84SpellcheckHunspellDictionary::DictionaryFile& 85SpellcheckHunspellDictionary::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 93SpellcheckHunspellDictionary::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 105SpellcheckHunspellDictionary::~SpellcheckHunspellDictionary() { 106} 107 108void 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 133void 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 140bool SpellcheckHunspellDictionary::IsReady() const { 141 return GetDictionaryFile() != 142 base::kInvalidPlatformFileValue || IsUsingPlatformChecker(); 143} 144 145base::PlatformFile SpellcheckHunspellDictionary::GetDictionaryFile() const { 146 return dictionary_file_.file.GetPlatformFile(); 147} 148 149const std::string& SpellcheckHunspellDictionary::GetLanguage() const { 150 return language_; 151} 152 153bool SpellcheckHunspellDictionary::IsUsingPlatformChecker() const { 154 return use_platform_spellchecker_; 155} 156 157void SpellcheckHunspellDictionary::AddObserver(Observer* observer) { 158 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 159 observers_.AddObserver(observer); 160} 161 162void SpellcheckHunspellDictionary::RemoveObserver(Observer* observer) { 163 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 164 observers_.RemoveObserver(observer); 165} 166 167bool SpellcheckHunspellDictionary::IsDownloadInProgress() { 168 return download_status_ == DOWNLOAD_IN_PROGRESS; 169} 170 171bool SpellcheckHunspellDictionary::IsDownloadFailure() { 172 return download_status_ == DOWNLOAD_FAILED; 173} 174 175void SpellcheckHunspellDictionary::OnURLFetchComplete( 176 const net::URLFetcher* source) { 177 DCHECK(source); 178 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 179 scoped_ptr<net::URLFetcher> fetcher_destructor(fetcher_.release()); 180 181 if ((source->GetResponseCode() / 100) != 2) { 182 // Initialize will not try to download the file a second time. 183 InformListenersOfDownloadFailure(); 184 return; 185 } 186 187 // Basic sanity check on the dictionary. There's a small chance of 200 status 188 // code for a body that represents some form of failure. 189 scoped_ptr<std::string> data(new std::string); 190 source->GetResponseAsString(data.get()); 191 if (data->size() < 4 || data->compare(0, 4, "BDic") != 0) { 192 InformListenersOfDownloadFailure(); 193 return; 194 } 195 196 // To prevent corrupted dictionary data from causing a renderer crash, scan 197 // the dictionary data and verify it is sane before save it to a file. 198 // TODO(rlp): Adding metrics to RecordDictionaryCorruptionStats 199 if (!hunspell::BDict::Verify(data->data(), data->size())) { 200 // Let PostTaskAndReply caller send to InformListenersOfInitialization 201 // through SaveDictionaryDataComplete(). 202 SaveDictionaryDataComplete(false); 203 return; 204 } 205 206 BrowserThread::PostTaskAndReplyWithResult<bool>( 207 BrowserThread::FILE, 208 FROM_HERE, 209 base::Bind(&SaveDictionaryData, 210 base::Passed(&data), 211 dictionary_file_.path), 212 base::Bind(&SpellcheckHunspellDictionary::SaveDictionaryDataComplete, 213 weak_ptr_factory_.GetWeakPtr())); 214} 215 216GURL SpellcheckHunspellDictionary::GetDictionaryURL() { 217 static const char kDownloadServerUrl[] = 218 "http://cache.pack.google.com/edgedl/chrome/dict/"; 219 std::string bdict_file = dictionary_file_.path.BaseName().MaybeAsASCII(); 220 221 DCHECK(!bdict_file.empty()); 222 223 return GURL(std::string(kDownloadServerUrl) + 224 StringToLowerASCII(bdict_file)); 225} 226 227void SpellcheckHunspellDictionary::DownloadDictionary(GURL url) { 228 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 229 DCHECK(request_context_getter_); 230 231 download_status_ = DOWNLOAD_IN_PROGRESS; 232 FOR_EACH_OBSERVER(Observer, observers_, OnHunspellDictionaryDownloadBegin()); 233 234 fetcher_.reset(net::URLFetcher::Create(url, net::URLFetcher::GET, this)); 235 fetcher_->SetRequestContext(request_context_getter_); 236 fetcher_->SetLoadFlags( 237 net::LOAD_DO_NOT_SEND_COOKIES | net::LOAD_DO_NOT_SAVE_COOKIES); 238 fetcher_->Start(); 239 // Attempt downloading the dictionary only once. 240 request_context_getter_ = NULL; 241} 242 243// The default_dictionary_file can either come from the standard list of 244// hunspell dictionaries (determined in InitializeDictionaryLocation), or it 245// can be passed in via an extension. In either case, the file is checked for 246// existence so that it's not re-downloaded. 247// For systemwide installations on Windows, the default directory may not 248// have permissions for download. In that case, the alternate directory for 249// download is chrome::DIR_USER_DATA. 250SpellcheckHunspellDictionary::DictionaryFile 251SpellcheckHunspellDictionary::OpenDictionaryFile(const base::FilePath& path) { 252 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 253 DictionaryFile dictionary; 254 255#if defined(OS_WIN) 256 // Check if the dictionary exists in the fallback location. If so, use it 257 // rather than downloading anew. 258 base::FilePath user_dir; 259 PathService::Get(chrome::DIR_USER_DATA, &user_dir); 260 base::FilePath fallback = user_dir.Append(path.BaseName()); 261 if (!base::PathExists(path) && base::PathExists(fallback)) 262 dictionary.path = fallback; 263 else 264 dictionary.path = path; 265#else 266 dictionary.path = path; 267#endif 268 269 // Read the dictionary file and scan its data to check for corruption. The 270 // scoping closes the memory-mapped file before it is opened or deleted. 271 bool bdict_is_valid; 272 { 273 base::MemoryMappedFile map; 274 bdict_is_valid = 275 base::PathExists(dictionary.path) && 276 map.Initialize(dictionary.path) && 277 hunspell::BDict::Verify(reinterpret_cast<const char*>(map.data()), 278 map.length()); 279 } 280 if (bdict_is_valid) { 281 dictionary.file.Initialize(dictionary.path, 282 base::File::FLAG_READ | base::File::FLAG_OPEN); 283 } else { 284 base::DeleteFile(dictionary.path, false); 285 } 286 287 return dictionary.Pass(); 288} 289 290// The default place where the spellcheck dictionary resides is 291// chrome::DIR_APP_DICTIONARIES. 292SpellcheckHunspellDictionary::DictionaryFile 293SpellcheckHunspellDictionary::InitializeDictionaryLocation( 294 const std::string& language) { 295 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 296 297 // Initialize the BDICT path. Initialization should be in the FILE thread 298 // because it checks if there is a "Dictionaries" directory and create it. 299 base::FilePath dict_dir; 300 PathService::Get(chrome::DIR_APP_DICTIONARIES, &dict_dir); 301 base::FilePath dict_path = 302 chrome::spellcheck_common::GetVersionedFileName(language, dict_dir); 303 304 return OpenDictionaryFile(dict_path); 305} 306 307void SpellcheckHunspellDictionary::InitializeDictionaryLocationComplete( 308 DictionaryFile file) { 309 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 310 dictionary_file_ = file.Pass(); 311 312 if (!dictionary_file_.file.IsValid()) { 313 314 // Notify browser tests that this dictionary is corrupted. Skip downloading 315 // the dictionary in browser tests. 316 // TODO(rouslan): Remove this test-only case. 317 if (spellcheck_service_->SignalStatusEvent( 318 SpellcheckService::BDICT_CORRUPTED)) { 319 request_context_getter_ = NULL; 320 } 321 322 if (request_context_getter_) { 323 // Download from the UI thread to check that |request_context_getter_| is 324 // still valid. 325 DownloadDictionary(GetDictionaryURL()); 326 return; 327 } 328 } 329 330 InformListenersOfInitialization(); 331} 332 333void SpellcheckHunspellDictionary::SaveDictionaryDataComplete( 334 bool dictionary_saved) { 335 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 336 337 if (dictionary_saved) { 338 download_status_ = DOWNLOAD_NONE; 339 FOR_EACH_OBSERVER(Observer, 340 observers_, 341 OnHunspellDictionaryDownloadSuccess()); 342 Load(); 343 } else { 344 InformListenersOfDownloadFailure(); 345 InformListenersOfInitialization(); 346 } 347} 348 349void SpellcheckHunspellDictionary::InformListenersOfInitialization() { 350 FOR_EACH_OBSERVER(Observer, observers_, OnHunspellDictionaryInitialized()); 351} 352 353void SpellcheckHunspellDictionary::InformListenersOfDownloadFailure() { 354 download_status_ = DOWNLOAD_FAILED; 355 FOR_EACH_OBSERVER(Observer, 356 observers_, 357 OnHunspellDictionaryDownloadFailure()); 358} 359