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;
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() !=
142 base::kInvalidPlatformFileValue || IsUsingPlatformChecker();
145 base::PlatformFile SpellcheckHunspellDictionary::GetDictionaryFile() const {
146 return dictionary_file_.file.GetPlatformFile();
149 const std::string& SpellcheckHunspellDictionary::GetLanguage() const {
153 bool SpellcheckHunspellDictionary::IsUsingPlatformChecker() const {
154 return use_platform_spellchecker_;
157 void SpellcheckHunspellDictionary::AddObserver(Observer* observer) {
158 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
159 observers_.AddObserver(observer);
162 void SpellcheckHunspellDictionary::RemoveObserver(Observer* observer) {
163 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
164 observers_.RemoveObserver(observer);
167 bool SpellcheckHunspellDictionary::IsDownloadInProgress() {
168 return download_status_ == DOWNLOAD_IN_PROGRESS;
171 bool SpellcheckHunspellDictionary::IsDownloadFailure() {
172 return download_status_ == DOWNLOAD_FAILED;
175 void SpellcheckHunspellDictionary::OnURLFetchComplete(
176 const net::URLFetcher* source) {
178 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
179 scoped_ptr<net::URLFetcher> fetcher_destructor(fetcher_.release());
181 if ((source->GetResponseCode() / 100) != 2) {
182 // Initialize will not try to download the file a second time.
183 InformListenersOfDownloadFailure();
187 // Basic sanity check on the dictionary. There's a small chance of 200 status
188 // code for a body that represents some form of failure.
189 scoped_ptr<std::string> data(new std::string);
190 source->GetResponseAsString(data.get());
191 if (data->size() < 4 || data->compare(0, 4, "BDic") != 0) {
192 InformListenersOfDownloadFailure();
196 // To prevent corrupted dictionary data from causing a renderer crash, scan
197 // the dictionary data and verify it is sane before save it to a file.
198 // TODO(rlp): Adding metrics to RecordDictionaryCorruptionStats
199 if (!hunspell::BDict::Verify(data->data(), data->size())) {
200 // Let PostTaskAndReply caller send to InformListenersOfInitialization
201 // through SaveDictionaryDataComplete().
202 SaveDictionaryDataComplete(false);
206 BrowserThread::PostTaskAndReplyWithResult<bool>(
209 base::Bind(&SaveDictionaryData,
211 dictionary_file_.path),
212 base::Bind(&SpellcheckHunspellDictionary::SaveDictionaryDataComplete,
213 weak_ptr_factory_.GetWeakPtr()));
216 GURL SpellcheckHunspellDictionary::GetDictionaryURL() {
217 static const char kDownloadServerUrl[] =
218 "http://cache.pack.google.com/edgedl/chrome/dict/";
219 std::string bdict_file = dictionary_file_.path.BaseName().MaybeAsASCII();
221 DCHECK(!bdict_file.empty());
223 return GURL(std::string(kDownloadServerUrl) +
224 StringToLowerASCII(bdict_file));
227 void SpellcheckHunspellDictionary::DownloadDictionary(GURL url) {
228 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
229 DCHECK(request_context_getter_);
231 download_status_ = DOWNLOAD_IN_PROGRESS;
232 FOR_EACH_OBSERVER(Observer, observers_, OnHunspellDictionaryDownloadBegin());
234 fetcher_.reset(net::URLFetcher::Create(url, net::URLFetcher::GET, this));
235 fetcher_->SetRequestContext(request_context_getter_);
236 fetcher_->SetLoadFlags(
237 net::LOAD_DO_NOT_SEND_COOKIES | net::LOAD_DO_NOT_SAVE_COOKIES);
239 // Attempt downloading the dictionary only once.
240 request_context_getter_ = NULL;
243 // The default_dictionary_file can either come from the standard list of
244 // hunspell dictionaries (determined in InitializeDictionaryLocation), or it
245 // can be passed in via an extension. In either case, the file is checked for
246 // existence so that it's not re-downloaded.
247 // For systemwide installations on Windows, the default directory may not
248 // have permissions for download. In that case, the alternate directory for
249 // download is chrome::DIR_USER_DATA.
250 SpellcheckHunspellDictionary::DictionaryFile
251 SpellcheckHunspellDictionary::OpenDictionaryFile(const base::FilePath& path) {
252 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
253 DictionaryFile dictionary;
256 // Check if the dictionary exists in the fallback location. If so, use it
257 // rather than downloading anew.
258 base::FilePath user_dir;
259 PathService::Get(chrome::DIR_USER_DATA, &user_dir);
260 base::FilePath fallback = user_dir.Append(path.BaseName());
261 if (!base::PathExists(path) && base::PathExists(fallback))
262 dictionary.path = fallback;
264 dictionary.path = path;
266 dictionary.path = path;
269 // Read the dictionary file and scan its data to check for corruption. The
270 // scoping closes the memory-mapped file before it is opened or deleted.
273 base::MemoryMappedFile map;
275 base::PathExists(dictionary.path) &&
276 map.Initialize(dictionary.path) &&
277 hunspell::BDict::Verify(reinterpret_cast<const char*>(map.data()),
280 if (bdict_is_valid) {
281 dictionary.file.Initialize(dictionary.path,
282 base::File::FLAG_READ | base::File::FLAG_OPEN);
284 base::DeleteFile(dictionary.path, false);
287 return dictionary.Pass();
290 // The default place where the spellcheck dictionary resides is
291 // chrome::DIR_APP_DICTIONARIES.
292 SpellcheckHunspellDictionary::DictionaryFile
293 SpellcheckHunspellDictionary::InitializeDictionaryLocation(
294 const std::string& language) {
295 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
297 // Initialize the BDICT path. Initialization should be in the FILE thread
298 // because it checks if there is a "Dictionaries" directory and create it.
299 base::FilePath dict_dir;
300 PathService::Get(chrome::DIR_APP_DICTIONARIES, &dict_dir);
301 base::FilePath dict_path =
302 chrome::spellcheck_common::GetVersionedFileName(language, dict_dir);
304 return OpenDictionaryFile(dict_path);
307 void SpellcheckHunspellDictionary::InitializeDictionaryLocationComplete(
308 DictionaryFile file) {
309 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
310 dictionary_file_ = file.Pass();
312 if (!dictionary_file_.file.IsValid()) {
314 // Notify browser tests that this dictionary is corrupted. Skip downloading
315 // the dictionary in browser tests.
316 // TODO(rouslan): Remove this test-only case.
317 if (spellcheck_service_->SignalStatusEvent(
318 SpellcheckService::BDICT_CORRUPTED)) {
319 request_context_getter_ = NULL;
322 if (request_context_getter_) {
323 // Download from the UI thread to check that |request_context_getter_| is
325 DownloadDictionary(GetDictionaryURL());
330 InformListenersOfInitialization();
333 void SpellcheckHunspellDictionary::SaveDictionaryDataComplete(
334 bool dictionary_saved) {
335 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
337 if (dictionary_saved) {
338 download_status_ = DOWNLOAD_NONE;
339 FOR_EACH_OBSERVER(Observer,
341 OnHunspellDictionaryDownloadSuccess());
344 InformListenersOfDownloadFailure();
345 InformListenersOfInitialization();
349 void SpellcheckHunspellDictionary::InformListenersOfInitialization() {
350 FOR_EACH_OBSERVER(Observer, observers_, OnHunspellDictionaryInitialized());
353 void SpellcheckHunspellDictionary::InformListenersOfDownloadFailure() {
354 download_status_ = DOWNLOAD_FAILED;
355 FOR_EACH_OBSERVER(Observer,
357 OnHunspellDictionaryDownloadFailure());