1 // Copyright (c) 2011 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/spellcheck_host_impl.h" 6 7 #include <set> 8 9 #include "base/file_util.h" 10 #include "base/logging.h" 11 #include "base/path_service.h" 12 #include "base/string_split.h" 13 #include "base/threading/thread_restrictions.h" 14 #include "base/utf_string_conversions.h" 15 #include "chrome/browser/spellcheck_host_observer.h" 16 #include "chrome/browser/spellchecker_platform_engine.h" 17 #include "chrome/common/chrome_constants.h" 18 #include "chrome/common/chrome_paths.h" 19 #include "chrome/common/spellcheck_common.h" 20 #include "content/common/notification_service.h" 21 #include "googleurl/src/gurl.h" 22 #include "net/url_request/url_request_context_getter.h" 23 #include "third_party/hunspell/google/bdict.h" 24 #include "ui/base/l10n/l10n_util.h" 25 #if defined(OS_MACOSX) 26 #include "base/metrics/histogram.h" 27 #endif 28 29 namespace { 30 31 FilePath GetFirstChoiceFilePath(const std::string& language) { 32 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 33 34 FilePath dict_dir; 35 PathService::Get(chrome::DIR_APP_DICTIONARIES, &dict_dir); 36 return SpellCheckCommon::GetVersionedFileName(language, dict_dir); 37 } 38 39 #if defined(OS_MACOSX) 40 // Collect metrics on how often Hunspell is used on OS X vs the native 41 // spellchecker. 42 void RecordSpellCheckStats(bool native_spellchecker_used, 43 const std::string& language) { 44 static std::set<std::string> languages_seen; 45 46 // Only count a language code once for each session.. 47 if (languages_seen.find(language) != languages_seen.end()) { 48 return; 49 } 50 languages_seen.insert(language); 51 52 enum { 53 SPELLCHECK_OSX_NATIVE_SPELLCHECKER_USED = 0, 54 SPELLCHECK_HUNSPELL_USED = 1 55 }; 56 57 bool engine_used = native_spellchecker_used ? 58 SPELLCHECK_OSX_NATIVE_SPELLCHECKER_USED : 59 SPELLCHECK_HUNSPELL_USED; 60 61 UMA_HISTOGRAM_COUNTS("SpellCheck.OSXEngineUsed", engine_used); 62 } 63 #endif 64 65 #if defined(OS_WIN) 66 FilePath GetFallbackFilePath(const FilePath& first_choice) { 67 FilePath dict_dir; 68 PathService::Get(chrome::DIR_USER_DATA, &dict_dir); 69 return dict_dir.Append(first_choice.BaseName()); 70 } 71 #endif 72 73 } // namespace 74 75 // Constructed on UI thread. 76 SpellCheckHostImpl::SpellCheckHostImpl( 77 SpellCheckHostObserver* observer, 78 const std::string& language, 79 net::URLRequestContextGetter* request_context_getter) 80 : observer_(observer), 81 language_(language), 82 file_(base::kInvalidPlatformFileValue), 83 tried_to_download_(false), 84 use_platform_spellchecker_(false), 85 request_context_getter_(request_context_getter) { 86 DCHECK(observer_); 87 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 88 89 FilePath personal_file_directory; 90 PathService::Get(chrome::DIR_USER_DATA, &personal_file_directory); 91 custom_dictionary_file_ = 92 personal_file_directory.Append(chrome::kCustomDictionaryFileName); 93 } 94 95 SpellCheckHostImpl::~SpellCheckHostImpl() { 96 if (file_ != base::kInvalidPlatformFileValue) 97 base::ClosePlatformFile(file_); 98 } 99 100 void SpellCheckHostImpl::Initialize() { 101 if (SpellCheckerPlatform::SpellCheckerAvailable() && 102 SpellCheckerPlatform::PlatformSupportsLanguage(language_)) { 103 #if defined(OS_MACOSX) 104 RecordSpellCheckStats(true, language_); 105 #endif 106 use_platform_spellchecker_ = true; 107 SpellCheckerPlatform::SetLanguage(language_); 108 MessageLoop::current()->PostTask(FROM_HERE, 109 NewRunnableMethod(this, 110 &SpellCheckHostImpl::InformObserverOfInitialization)); 111 return; 112 } 113 114 #if defined(OS_MACOSX) 115 RecordSpellCheckStats(false, language_); 116 #endif 117 118 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 119 NewRunnableMethod(this, 120 &SpellCheckHostImpl::InitializeDictionaryLocation)); 121 } 122 123 void SpellCheckHostImpl::UnsetObserver() { 124 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 125 126 observer_ = NULL; 127 request_context_getter_ = NULL; 128 fetcher_.reset(); 129 } 130 131 void SpellCheckHostImpl::AddWord(const std::string& word) { 132 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 133 134 custom_words_.push_back(word); 135 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 136 NewRunnableMethod(this, 137 &SpellCheckHostImpl::WriteWordToCustomDictionary, word)); 138 NotificationService::current()->Notify( 139 NotificationType::SPELLCHECK_WORD_ADDED, 140 Source<SpellCheckHost>(this), NotificationService::NoDetails()); 141 } 142 143 void SpellCheckHostImpl::InitializeDictionaryLocation() { 144 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 145 146 // Initialize the BDICT path. This initialization should be in the FILE thread 147 // because it checks if there is a "Dictionaries" directory and create it. 148 if (bdict_file_path_.empty()) 149 bdict_file_path_ = GetFirstChoiceFilePath(language_); 150 151 #if defined(OS_WIN) 152 // Check if the dictionary exists in the fallback location. If so, use it 153 // rather than downloading anew. 154 FilePath fallback = GetFallbackFilePath(bdict_file_path_); 155 if (!file_util::PathExists(bdict_file_path_) && 156 file_util::PathExists(fallback)) { 157 bdict_file_path_ = fallback; 158 } 159 #endif 160 161 InitializeInternal(); 162 } 163 164 void SpellCheckHostImpl::InitializeInternal() { 165 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 166 167 if (!observer_) 168 return; 169 170 file_ = base::CreatePlatformFile( 171 bdict_file_path_, 172 base::PLATFORM_FILE_READ | base::PLATFORM_FILE_OPEN, 173 NULL, NULL); 174 175 // File didn't exist. Download it. 176 if (file_ == base::kInvalidPlatformFileValue && !tried_to_download_ && 177 request_context_getter_) { 178 // We download from the ui thread because we need to know that 179 // |request_context_getter_| is still valid before initiating the download. 180 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 181 NewRunnableMethod(this, &SpellCheckHostImpl::DownloadDictionary)); 182 return; 183 } 184 185 request_context_getter_ = NULL; 186 187 if (file_ != base::kInvalidPlatformFileValue) { 188 // Load custom dictionary. 189 std::string contents; 190 file_util::ReadFileToString(custom_dictionary_file_, &contents); 191 std::vector<std::string> list_of_words; 192 base::SplitString(contents, '\n', &list_of_words); 193 for (size_t i = 0; i < list_of_words.size(); ++i) 194 custom_words_.push_back(list_of_words[i]); 195 } 196 197 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 198 NewRunnableMethod(this, 199 &SpellCheckHostImpl::InformObserverOfInitialization)); 200 } 201 202 void SpellCheckHostImpl::InitializeOnFileThread() { 203 DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::FILE)); 204 205 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 206 NewRunnableMethod(this, &SpellCheckHostImpl::Initialize)); 207 } 208 209 void SpellCheckHostImpl::InformObserverOfInitialization() { 210 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 211 212 if (observer_) 213 observer_->SpellCheckHostInitialized(); 214 } 215 216 void SpellCheckHostImpl::DownloadDictionary() { 217 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 218 219 if (!request_context_getter_) { 220 InitializeOnFileThread(); 221 return; 222 } 223 224 // Determine URL of file to download. 225 static const char kDownloadServerUrl[] = 226 "http://cache.pack.google.com/edgedl/chrome/dict/"; 227 std::string bdict_file = bdict_file_path_.BaseName().MaybeAsASCII(); 228 if (bdict_file.empty()) { 229 NOTREACHED(); 230 return; 231 } 232 GURL url = GURL(std::string(kDownloadServerUrl) + 233 StringToLowerASCII(bdict_file)); 234 fetcher_.reset(new URLFetcher(url, URLFetcher::GET, this)); 235 fetcher_->set_request_context(request_context_getter_); 236 tried_to_download_ = true; 237 fetcher_->Start(); 238 request_context_getter_ = NULL; 239 } 240 241 void SpellCheckHostImpl::WriteWordToCustomDictionary(const std::string& word) { 242 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 243 244 // Stored in UTF-8. 245 std::string word_to_add(word + "\n"); 246 FILE* f = file_util::OpenFile(custom_dictionary_file_, "a+"); 247 if (f) 248 fputs(word_to_add.c_str(), f); 249 file_util::CloseFile(f); 250 } 251 252 void SpellCheckHostImpl::OnURLFetchComplete(const URLFetcher* source, 253 const GURL& url, 254 const net::URLRequestStatus& status, 255 int response_code, 256 const ResponseCookies& cookies, 257 const std::string& data) { 258 DCHECK(source); 259 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 260 fetcher_.reset(); 261 262 if ((response_code / 100) != 2) { 263 // Initialize will not try to download the file a second time. 264 LOG(ERROR) << "Failure to download dictionary."; 265 InitializeOnFileThread(); 266 return; 267 } 268 269 // Basic sanity check on the dictionary. 270 // There's the small chance that we might see a 200 status code for a body 271 // that represents some form of failure. 272 if (data.size() < 4 || data[0] != 'B' || data[1] != 'D' || data[2] != 'i' || 273 data[3] != 'c') { 274 LOG(ERROR) << "Failure to download dictionary."; 275 InitializeOnFileThread(); 276 return; 277 } 278 279 data_ = data; 280 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 281 NewRunnableMethod(this, &SpellCheckHostImpl::SaveDictionaryData)); 282 } 283 284 void SpellCheckHostImpl::SaveDictionaryData() { 285 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 286 287 // To prevent corrupted dictionary data from causing a renderer crash, scan 288 // the dictionary data and verify it is sane before save it to a file. 289 if (!hunspell::BDict::Verify(data_.data(), data_.size())) { 290 LOG(ERROR) << "Failure to verify the downloaded dictionary."; 291 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 292 NewRunnableMethod(this, 293 &SpellCheckHostImpl::InformObserverOfInitialization)); 294 return; 295 } 296 297 size_t bytes_written = 298 file_util::WriteFile(bdict_file_path_, data_.data(), data_.length()); 299 if (bytes_written != data_.length()) { 300 bool success = false; 301 #if defined(OS_WIN) 302 bdict_file_path_ = GetFallbackFilePath(bdict_file_path_); 303 bytes_written = 304 file_util::WriteFile(GetFallbackFilePath(bdict_file_path_), 305 data_.data(), data_.length()); 306 if (bytes_written == data_.length()) 307 success = true; 308 #endif 309 data_.clear(); 310 311 if (!success) { 312 LOG(ERROR) << "Failure to save dictionary."; 313 file_util::Delete(bdict_file_path_, false); 314 // To avoid trying to load a partially saved dictionary, shortcut the 315 // Initialize() call. 316 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 317 NewRunnableMethod(this, 318 &SpellCheckHostImpl::InformObserverOfInitialization)); 319 return; 320 } 321 } 322 323 data_.clear(); 324 Initialize(); 325 } 326 327 const base::PlatformFile& SpellCheckHostImpl::GetDictionaryFile() const { 328 return file_; 329 } 330 331 const std::vector<std::string>& SpellCheckHostImpl::GetCustomWords() const { 332 return custom_words_; 333 } 334 335 const std::string& SpellCheckHostImpl::GetLastAddedFile() const { 336 return custom_words_.back(); 337 } 338 339 const std::string& SpellCheckHostImpl::GetLanguage() const { 340 return language_; 341 } 342 343 bool SpellCheckHostImpl::IsUsingPlatformChecker() const { 344 return use_platform_spellchecker_; 345 } 346