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/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
;
29 // Close the platform file.
30 void CloseDictionary(base::PlatformFile file
) {
31 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
32 base::ClosePlatformFile(file
);
37 // Dictionary file information to be passed between the FILE and UI threads.
38 struct DictionaryFile
{
39 DictionaryFile() : descriptor(base::kInvalidPlatformFileValue
) {
43 if (descriptor
!= base::kInvalidPlatformFileValue
) {
44 BrowserThread::PostTask(
47 base::Bind(&CloseDictionary
, descriptor
));
48 descriptor
= base::kInvalidPlatformFileValue
;
52 // The desired location of the dictionary file, whether or not it exists.
55 // The file descriptor/handle for the dictionary file.
56 base::PlatformFile descriptor
;
58 DISALLOW_COPY_AND_ASSIGN(DictionaryFile
);
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));
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
;
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.
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()),
98 file
->descriptor
= base::CreatePlatformFile(
100 base::PLATFORM_FILE_READ
| base::PLATFORM_FILE_OPEN
,
104 base::DeleteFile(file
->path
, false);
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
);
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(
126 return OpenDictionaryFile(file
.Pass());
129 // Saves |data| to file at |path|. Returns true on successful save, otherwise
131 bool SaveDictionaryData(scoped_ptr
<std::string
> data
,
132 const base::FilePath
& path
) {
133 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
135 size_t bytes_written
=
136 file_util::WriteFile(path
, data
->data(), data
->length());
137 if (bytes_written
!= data
->length()) {
138 bool success
= false;
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());
145 file_util::WriteFile(fallback_file_path
, data
->data(), data
->length());
146 if (bytes_written
== data
->length())
151 base::DeleteFile(path
, false);
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) {
174 SpellcheckHunspellDictionary::~SpellcheckHunspellDictionary() {
177 void SpellcheckHunspellDictionary::Load() {
178 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
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
,
187 &SpellcheckHunspellDictionary::InformListenersOfInitialization
,
188 weak_ptr_factory_
.GetWeakPtr()));
193 BrowserThread::PostTaskAndReplyWithResult(
196 base::Bind(&InitializeDictionaryLocation
, language_
),
198 &SpellcheckHunspellDictionary::InitializeDictionaryLocationComplete
,
199 weak_ptr_factory_
.GetWeakPtr()));
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());
209 bool SpellcheckHunspellDictionary::IsReady() const {
210 return GetDictionaryFile() !=
211 base::kInvalidPlatformFileValue
|| IsUsingPlatformChecker();
214 const base::PlatformFile
&
215 SpellcheckHunspellDictionary::GetDictionaryFile() const {
216 return dictionary_file_
->descriptor
;
219 const std::string
& SpellcheckHunspellDictionary::GetLanguage() const {
223 bool SpellcheckHunspellDictionary::IsUsingPlatformChecker() const {
224 return use_platform_spellchecker_
;
227 void SpellcheckHunspellDictionary::AddObserver(Observer
* observer
) {
228 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
229 observers_
.AddObserver(observer
);
232 void SpellcheckHunspellDictionary::RemoveObserver(Observer
* observer
) {
233 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
234 observers_
.RemoveObserver(observer
);
237 bool SpellcheckHunspellDictionary::IsDownloadInProgress() {
238 return download_status_
== DOWNLOAD_IN_PROGRESS
;
241 bool SpellcheckHunspellDictionary::IsDownloadFailure() {
242 return download_status_
== DOWNLOAD_FAILED
;
245 void SpellcheckHunspellDictionary::OnURLFetchComplete(
246 const net::URLFetcher
* source
) {
248 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
249 scoped_ptr
<net::URLFetcher
> fetcher_destructor(fetcher_
.release());
251 if ((source
->GetResponseCode() / 100) != 2) {
252 // Initialize will not try to download the file a second time.
253 InformListenersOfDownloadFailure();
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();
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);
276 BrowserThread::PostTaskAndReplyWithResult
<bool>(
279 base::Bind(&SaveDictionaryData
,
281 dictionary_file_
->path
),
282 base::Bind(&SpellcheckHunspellDictionary::SaveDictionaryDataComplete
,
283 weak_ptr_factory_
.GetWeakPtr()));
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();
291 DCHECK(!bdict_file
.empty());
293 return GURL(std::string(kDownloadServerUrl
) +
294 StringToLowerASCII(bdict_file
));
297 void SpellcheckHunspellDictionary::DownloadDictionary(GURL url
) {
298 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
299 DCHECK(request_context_getter_
);
301 download_status_
= DOWNLOAD_IN_PROGRESS
;
302 FOR_EACH_OBSERVER(Observer
, observers_
, OnHunspellDictionaryDownloadBegin());
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
);
309 // Attempt downloading the dictionary only once.
310 request_context_getter_
= NULL
;
313 void SpellcheckHunspellDictionary::InitializeDictionaryLocationComplete(
314 scoped_ptr
<DictionaryFile
> file
) {
315 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
316 dictionary_file_
= file
.Pass();
318 if (dictionary_file_
->descriptor
== base::kInvalidPlatformFileValue
) {
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
;
328 if (request_context_getter_
) {
329 // Download from the UI thread to check that |request_context_getter_| is
331 DownloadDictionary(GetDictionaryURL());
336 InformListenersOfInitialization();
339 void SpellcheckHunspellDictionary::SaveDictionaryDataComplete(
340 bool dictionary_saved
) {
341 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
343 if (dictionary_saved
) {
344 download_status_
= DOWNLOAD_NONE
;
345 FOR_EACH_OBSERVER(Observer
,
347 OnHunspellDictionaryDownloadSuccess());
350 InformListenersOfDownloadFailure();
351 InformListenersOfInitialization();
355 void SpellcheckHunspellDictionary::InformListenersOfInitialization() {
356 FOR_EACH_OBSERVER(Observer
, observers_
, OnHunspellDictionaryInitialized());
359 void SpellcheckHunspellDictionary::InformListenersOfDownloadFailure() {
360 download_status_
= DOWNLOAD_FAILED
;
361 FOR_EACH_OBSERVER(Observer
,
363 OnHunspellDictionaryDownloadFailure());