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