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/files/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 base::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