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.
5 #include "chrome/browser/spellchecker/spellcheck_hunspell_dictionary.h"
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"
25 using content::BrowserThread
;
30 void CloseDictionary(base::File file
) {
31 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
35 // Saves |data| to file at |path|. Returns true on successful save, otherwise
37 bool SaveDictionaryData(scoped_ptr
<std::string
> data
,
38 const base::FilePath
& path
) {
39 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
41 size_t bytes_written
=
42 base::WriteFile(path
, data
->data(), data
->length());
43 if (bytes_written
!= data
->length()) {
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());
51 base::WriteFile(fallback_file_path
, data
->data(), data
->length());
52 if (bytes_written
== data
->length())
57 base::DeleteFile(path
, false);
67 SpellcheckHunspellDictionary::DictionaryFile::DictionaryFile() {
70 SpellcheckHunspellDictionary::DictionaryFile::~DictionaryFile() {
72 BrowserThread::PostTask(
75 base::Bind(&CloseDictionary
, Passed(&file
)));
79 SpellcheckHunspellDictionary::DictionaryFile::DictionaryFile(RValue other
)
80 : path(other
.object
->path
),
81 file(other
.object
->file
.Pass()) {
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();
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) {
105 SpellcheckHunspellDictionary::~SpellcheckHunspellDictionary() {
108 void SpellcheckHunspellDictionary::Load() {
109 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
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
,
118 &SpellcheckHunspellDictionary::InformListenersOfInitialization
,
119 weak_ptr_factory_
.GetWeakPtr()));
124 BrowserThread::PostTaskAndReplyWithResult(
127 base::Bind(&InitializeDictionaryLocation
, language_
),
129 &SpellcheckHunspellDictionary::InitializeDictionaryLocationComplete
,
130 weak_ptr_factory_
.GetWeakPtr()));
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());
140 bool SpellcheckHunspellDictionary::IsReady() const {
141 return GetDictionaryFile().IsValid() || IsUsingPlatformChecker();
144 const base::File
& SpellcheckHunspellDictionary::GetDictionaryFile() const {
145 return dictionary_file_
.file
;
148 const std::string
& SpellcheckHunspellDictionary::GetLanguage() const {
152 bool SpellcheckHunspellDictionary::IsUsingPlatformChecker() const {
153 return use_platform_spellchecker_
;
156 void SpellcheckHunspellDictionary::AddObserver(Observer
* observer
) {
157 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
158 observers_
.AddObserver(observer
);
161 void SpellcheckHunspellDictionary::RemoveObserver(Observer
* observer
) {
162 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
163 observers_
.RemoveObserver(observer
);
166 bool SpellcheckHunspellDictionary::IsDownloadInProgress() {
167 return download_status_
== DOWNLOAD_IN_PROGRESS
;
170 bool SpellcheckHunspellDictionary::IsDownloadFailure() {
171 return download_status_
== DOWNLOAD_FAILED
;
174 void SpellcheckHunspellDictionary::OnURLFetchComplete(
175 const net::URLFetcher
* source
) {
177 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
178 scoped_ptr
<net::URLFetcher
> fetcher_destructor(fetcher_
.release());
180 if ((source
->GetResponseCode() / 100) != 2) {
181 // Initialize will not try to download the file a second time.
182 InformListenersOfDownloadFailure();
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();
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);
205 BrowserThread::PostTaskAndReplyWithResult
<bool>(
208 base::Bind(&SaveDictionaryData
,
210 dictionary_file_
.path
),
211 base::Bind(&SpellcheckHunspellDictionary::SaveDictionaryDataComplete
,
212 weak_ptr_factory_
.GetWeakPtr()));
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();
220 DCHECK(!bdict_file
.empty());
222 return GURL(std::string(kDownloadServerUrl
) +
223 base::StringToLowerASCII(bdict_file
));
226 void SpellcheckHunspellDictionary::DownloadDictionary(GURL url
) {
227 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
228 DCHECK(request_context_getter_
);
230 download_status_
= DOWNLOAD_IN_PROGRESS
;
231 FOR_EACH_OBSERVER(Observer
, observers_
, OnHunspellDictionaryDownloadBegin());
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
);
238 // Attempt downloading the dictionary only once.
239 request_context_getter_
= NULL
;
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
;
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
;
263 dictionary
.path
= path
;
265 dictionary
.path
= path
;
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.
272 base::MemoryMappedFile map
;
274 base::PathExists(dictionary
.path
) &&
275 map
.Initialize(dictionary
.path
) &&
276 hunspell::BDict::Verify(reinterpret_cast<const char*>(map
.data()),
279 if (bdict_is_valid
) {
280 dictionary
.file
.Initialize(dictionary
.path
,
281 base::File::FLAG_READ
| base::File::FLAG_OPEN
);
283 base::DeleteFile(dictionary
.path
, false);
286 return dictionary
.Pass();
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));
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
);
303 return OpenDictionaryFile(dict_path
);
306 void SpellcheckHunspellDictionary::InitializeDictionaryLocationComplete(
307 DictionaryFile file
) {
308 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
309 dictionary_file_
= file
.Pass();
311 if (!dictionary_file_
.file
.IsValid()) {
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
;
321 if (request_context_getter_
) {
322 // Download from the UI thread to check that |request_context_getter_| is
324 DownloadDictionary(GetDictionaryURL());
329 InformListenersOfInitialization();
332 void SpellcheckHunspellDictionary::SaveDictionaryDataComplete(
333 bool dictionary_saved
) {
334 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
336 if (dictionary_saved
) {
337 download_status_
= DOWNLOAD_NONE
;
338 FOR_EACH_OBSERVER(Observer
,
340 OnHunspellDictionaryDownloadSuccess());
343 InformListenersOfDownloadFailure();
344 InformListenersOfInitialization();
348 void SpellcheckHunspellDictionary::InformListenersOfInitialization() {
349 FOR_EACH_OBSERVER(Observer
, observers_
, OnHunspellDictionaryInitialized());
352 void SpellcheckHunspellDictionary::InformListenersOfDownloadFailure() {
353 download_status_
= DOWNLOAD_FAILED
;
354 FOR_EACH_OBSERVER(Observer
,
356 OnHunspellDictionaryDownloadFailure());