• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
GetFirstChoiceFilePath(const std::string & language)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.
RecordSpellCheckStats(bool native_spellchecker_used,const std::string & language)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)
GetFallbackFilePath(const FilePath & first_choice)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.
SpellCheckHostImpl(SpellCheckHostObserver * observer,const std::string & language,net::URLRequestContextGetter * request_context_getter)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 
~SpellCheckHostImpl()95 SpellCheckHostImpl::~SpellCheckHostImpl() {
96   if (file_ != base::kInvalidPlatformFileValue)
97     base::ClosePlatformFile(file_);
98 }
99 
Initialize()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 
UnsetObserver()123 void SpellCheckHostImpl::UnsetObserver() {
124   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
125 
126   observer_ = NULL;
127   request_context_getter_ = NULL;
128   fetcher_.reset();
129 }
130 
AddWord(const std::string & word)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 
InitializeDictionaryLocation()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 
InitializeInternal()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 
InitializeOnFileThread()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 
InformObserverOfInitialization()209 void SpellCheckHostImpl::InformObserverOfInitialization() {
210   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
211 
212   if (observer_)
213     observer_->SpellCheckHostInitialized();
214 }
215 
DownloadDictionary()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 
WriteWordToCustomDictionary(const std::string & word)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 
OnURLFetchComplete(const URLFetcher * source,const GURL & url,const net::URLRequestStatus & status,int response_code,const ResponseCookies & cookies,const std::string & data)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 
SaveDictionaryData()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 
GetDictionaryFile() const327 const base::PlatformFile& SpellCheckHostImpl::GetDictionaryFile() const {
328   return file_;
329 }
330 
GetCustomWords() const331 const std::vector<std::string>& SpellCheckHostImpl::GetCustomWords() const {
332   return custom_words_;
333 }
334 
GetLastAddedFile() const335 const std::string& SpellCheckHostImpl::GetLastAddedFile() const {
336   return custom_words_.back();
337 }
338 
GetLanguage() const339 const std::string& SpellCheckHostImpl::GetLanguage() const {
340   return language_;
341 }
342 
IsUsingPlatformChecker() const343 bool SpellCheckHostImpl::IsUsingPlatformChecker() const {
344   return use_platform_spellchecker_;
345 }
346