Switch global error menu icon to vectorized MD asset
[chromium-blink-merge.git] / chrome / browser / spellchecker / spellcheck_hunspell_dictionary.cc
blob5463f407bb852f446939f7046da8341dba15d2c8
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/logging.h"
9 #include "base/message_loop/message_loop.h"
10 #include "base/path_service.h"
11 #include "chrome/browser/spellchecker/spellcheck_platform.h"
12 #include "chrome/browser/spellchecker/spellcheck_service.h"
13 #include "chrome/common/chrome_paths.h"
14 #include "chrome/common/spellcheck_common.h"
15 #include "content/public/browser/browser_thread.h"
16 #include "content/public/browser/render_process_host.h"
17 #include "net/base/load_flags.h"
18 #include "net/url_request/url_fetcher.h"
19 #include "net/url_request/url_request_context_getter.h"
20 #include "url/gurl.h"
22 #if !defined(OS_ANDROID)
23 #include "base/files/memory_mapped_file.h"
24 #include "third_party/hunspell/google/bdict.h"
25 #endif
27 using content::BrowserThread;
29 namespace {
31 // Close the file.
32 void CloseDictionary(base::File file) {
33 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
34 file.Close();
37 // Saves |data| to file at |path|. Returns true on successful save, otherwise
38 // returns false.
39 bool SaveDictionaryData(scoped_ptr<std::string> data,
40 const base::FilePath& path) {
41 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
43 size_t bytes_written =
44 base::WriteFile(path, data->data(), data->length());
45 if (bytes_written != data->length()) {
46 bool success = false;
47 #if defined(OS_WIN)
48 base::FilePath dict_dir;
49 PathService::Get(chrome::DIR_USER_DATA, &dict_dir);
50 base::FilePath fallback_file_path =
51 dict_dir.Append(path.BaseName());
52 bytes_written =
53 base::WriteFile(fallback_file_path, data->data(), data->length());
54 if (bytes_written == data->length())
55 success = true;
56 #endif
58 if (!success) {
59 base::DeleteFile(path, false);
60 return false;
64 return true;
67 } // namespace
69 SpellcheckHunspellDictionary::DictionaryFile::DictionaryFile() {
72 SpellcheckHunspellDictionary::DictionaryFile::~DictionaryFile() {
73 if (file.IsValid()) {
74 BrowserThread::PostTask(
75 BrowserThread::FILE,
76 FROM_HERE,
77 base::Bind(&CloseDictionary, Passed(&file)));
81 SpellcheckHunspellDictionary::DictionaryFile::DictionaryFile(RValue other)
82 : path(other.object->path),
83 file(other.object->file.Pass()) {
86 SpellcheckHunspellDictionary::DictionaryFile&
87 SpellcheckHunspellDictionary::DictionaryFile::operator=(RValue other) {
88 if (this != other.object) {
89 path = other.object->path;
90 file = other.object->file.Pass();
92 return *this;
95 SpellcheckHunspellDictionary::SpellcheckHunspellDictionary(
96 const std::string& language,
97 net::URLRequestContextGetter* request_context_getter,
98 SpellcheckService* spellcheck_service)
99 : language_(language),
100 use_browser_spellchecker_(false),
101 request_context_getter_(request_context_getter),
102 spellcheck_service_(spellcheck_service),
103 download_status_(DOWNLOAD_NONE),
104 weak_ptr_factory_(this) {
107 SpellcheckHunspellDictionary::~SpellcheckHunspellDictionary() {
110 void SpellcheckHunspellDictionary::Load() {
111 DCHECK_CURRENTLY_ON(BrowserThread::UI);
113 #if defined(USE_BROWSER_SPELLCHECKER)
114 if (spellcheck_platform::SpellCheckerAvailable() &&
115 spellcheck_platform::PlatformSupportsLanguage(language_)) {
116 use_browser_spellchecker_ = true;
117 spellcheck_platform::SetLanguage(language_);
118 base::MessageLoop::current()->PostTask(FROM_HERE,
119 base::Bind(
120 &SpellcheckHunspellDictionary::InformListenersOfInitialization,
121 weak_ptr_factory_.GetWeakPtr()));
122 return;
124 #endif // USE_BROWSER_SPELLCHECKER
126 // Mac falls back on hunspell if its platform spellchecker isn't available.
127 // However, Android does not support hunspell.
128 #if !defined(OS_ANDROID)
129 BrowserThread::PostTaskAndReplyWithResult(
130 BrowserThread::FILE,
131 FROM_HERE,
132 base::Bind(&InitializeDictionaryLocation, language_),
133 base::Bind(
134 &SpellcheckHunspellDictionary::InitializeDictionaryLocationComplete,
135 weak_ptr_factory_.GetWeakPtr()));
136 #endif // !OS_ANDROID
139 void SpellcheckHunspellDictionary::RetryDownloadDictionary(
140 net::URLRequestContextGetter* request_context_getter) {
141 DCHECK_CURRENTLY_ON(BrowserThread::UI);
142 request_context_getter_ = request_context_getter;
143 DownloadDictionary(GetDictionaryURL());
146 bool SpellcheckHunspellDictionary::IsReady() const {
147 return GetDictionaryFile().IsValid() || IsUsingPlatformChecker();
150 const base::File& SpellcheckHunspellDictionary::GetDictionaryFile() const {
151 return dictionary_file_.file;
154 const std::string& SpellcheckHunspellDictionary::GetLanguage() const {
155 return language_;
158 bool SpellcheckHunspellDictionary::IsUsingPlatformChecker() const {
159 return use_browser_spellchecker_;
162 void SpellcheckHunspellDictionary::AddObserver(Observer* observer) {
163 DCHECK_CURRENTLY_ON(BrowserThread::UI);
164 observers_.AddObserver(observer);
167 void SpellcheckHunspellDictionary::RemoveObserver(Observer* observer) {
168 DCHECK_CURRENTLY_ON(BrowserThread::UI);
169 observers_.RemoveObserver(observer);
172 bool SpellcheckHunspellDictionary::IsDownloadInProgress() {
173 return download_status_ == DOWNLOAD_IN_PROGRESS;
176 bool SpellcheckHunspellDictionary::IsDownloadFailure() {
177 return download_status_ == DOWNLOAD_FAILED;
180 void SpellcheckHunspellDictionary::OnURLFetchComplete(
181 const net::URLFetcher* source) {
182 DCHECK(source);
183 DCHECK_CURRENTLY_ON(BrowserThread::UI);
184 scoped_ptr<net::URLFetcher> fetcher_destructor(fetcher_.release());
186 if ((source->GetResponseCode() / 100) != 2) {
187 // Initialize will not try to download the file a second time.
188 InformListenersOfDownloadFailure();
189 return;
192 // Basic sanity check on the dictionary. There's a small chance of 200 status
193 // code for a body that represents some form of failure.
194 scoped_ptr<std::string> data(new std::string);
195 source->GetResponseAsString(data.get());
196 if (data->size() < 4 || data->compare(0, 4, "BDic") != 0) {
197 InformListenersOfDownloadFailure();
198 return;
201 #if !defined(OS_ANDROID)
202 // To prevent corrupted dictionary data from causing a renderer crash, scan
203 // the dictionary data and verify it is sane before save it to a file.
204 // TODO(rlp): Adding metrics to RecordDictionaryCorruptionStats
205 if (!hunspell::BDict::Verify(data->data(), data->size())) {
206 // Let PostTaskAndReply caller send to InformListenersOfInitialization
207 // through SaveDictionaryDataComplete().
208 SaveDictionaryDataComplete(false);
209 return;
211 #endif
213 BrowserThread::PostTaskAndReplyWithResult<bool>(
214 BrowserThread::FILE,
215 FROM_HERE,
216 base::Bind(&SaveDictionaryData,
217 base::Passed(&data),
218 dictionary_file_.path),
219 base::Bind(&SpellcheckHunspellDictionary::SaveDictionaryDataComplete,
220 weak_ptr_factory_.GetWeakPtr()));
223 GURL SpellcheckHunspellDictionary::GetDictionaryURL() {
224 static const char kDownloadServerUrl[] =
225 "https://redirector.gvt1.com/edgedl/chrome/dict/";
226 std::string bdict_file = dictionary_file_.path.BaseName().MaybeAsASCII();
228 DCHECK(!bdict_file.empty());
230 return GURL(std::string(kDownloadServerUrl) +
231 base::ToLowerASCII(bdict_file));
234 void SpellcheckHunspellDictionary::DownloadDictionary(GURL url) {
235 DCHECK_CURRENTLY_ON(BrowserThread::UI);
236 DCHECK(request_context_getter_);
238 download_status_ = DOWNLOAD_IN_PROGRESS;
239 FOR_EACH_OBSERVER(Observer, observers_, OnHunspellDictionaryDownloadBegin());
241 fetcher_ = net::URLFetcher::Create(url, net::URLFetcher::GET, this);
242 fetcher_->SetRequestContext(request_context_getter_);
243 fetcher_->SetLoadFlags(
244 net::LOAD_DO_NOT_SEND_COOKIES | net::LOAD_DO_NOT_SAVE_COOKIES);
245 fetcher_->Start();
246 // Attempt downloading the dictionary only once.
247 request_context_getter_ = NULL;
250 // The default_dictionary_file can either come from the standard list of
251 // hunspell dictionaries (determined in InitializeDictionaryLocation), or it
252 // can be passed in via an extension. In either case, the file is checked for
253 // existence so that it's not re-downloaded.
254 // For systemwide installations on Windows, the default directory may not
255 // have permissions for download. In that case, the alternate directory for
256 // download is chrome::DIR_USER_DATA.
257 SpellcheckHunspellDictionary::DictionaryFile
258 SpellcheckHunspellDictionary::OpenDictionaryFile(const base::FilePath& path) {
259 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
260 DictionaryFile dictionary;
262 #if defined(OS_WIN)
263 // Check if the dictionary exists in the fallback location. If so, use it
264 // rather than downloading anew.
265 base::FilePath user_dir;
266 PathService::Get(chrome::DIR_USER_DATA, &user_dir);
267 base::FilePath fallback = user_dir.Append(path.BaseName());
268 if (!base::PathExists(path) && base::PathExists(fallback))
269 dictionary.path = fallback;
270 else
271 dictionary.path = path;
272 #else
273 dictionary.path = path;
274 #endif
276 // Read the dictionary file and scan its data to check for corruption. The
277 // scoping closes the memory-mapped file before it is opened or deleted.
278 bool bdict_is_valid = false;
280 #if !defined(OS_ANDROID)
282 base::MemoryMappedFile map;
283 bdict_is_valid =
284 base::PathExists(dictionary.path) &&
285 map.Initialize(dictionary.path) &&
286 hunspell::BDict::Verify(reinterpret_cast<const char*>(map.data()),
287 map.length());
289 #endif
291 if (bdict_is_valid) {
292 dictionary.file.Initialize(dictionary.path,
293 base::File::FLAG_READ | base::File::FLAG_OPEN);
294 } else {
295 base::DeleteFile(dictionary.path, false);
298 return dictionary.Pass();
301 // The default place where the spellcheck dictionary resides is
302 // chrome::DIR_APP_DICTIONARIES.
303 SpellcheckHunspellDictionary::DictionaryFile
304 SpellcheckHunspellDictionary::InitializeDictionaryLocation(
305 const std::string& language) {
306 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
308 // Initialize the BDICT path. Initialization should be in the FILE thread
309 // because it checks if there is a "Dictionaries" directory and create it.
310 base::FilePath dict_dir;
311 PathService::Get(chrome::DIR_APP_DICTIONARIES, &dict_dir);
312 base::FilePath dict_path =
313 chrome::spellcheck_common::GetVersionedFileName(language, dict_dir);
315 return OpenDictionaryFile(dict_path);
318 void SpellcheckHunspellDictionary::InitializeDictionaryLocationComplete(
319 DictionaryFile file) {
320 DCHECK_CURRENTLY_ON(BrowserThread::UI);
321 dictionary_file_ = file.Pass();
323 if (!dictionary_file_.file.IsValid()) {
324 // Notify browser tests that this dictionary is corrupted. Skip downloading
325 // the dictionary in browser tests.
326 // TODO(rouslan): Remove this test-only case.
327 if (spellcheck_service_->SignalStatusEvent(
328 SpellcheckService::BDICT_CORRUPTED)) {
329 request_context_getter_ = NULL;
332 if (request_context_getter_) {
333 // Download from the UI thread to check that |request_context_getter_| is
334 // still valid.
335 DownloadDictionary(GetDictionaryURL());
336 return;
340 InformListenersOfInitialization();
343 void SpellcheckHunspellDictionary::SaveDictionaryDataComplete(
344 bool dictionary_saved) {
345 DCHECK_CURRENTLY_ON(BrowserThread::UI);
347 if (dictionary_saved) {
348 download_status_ = DOWNLOAD_NONE;
349 FOR_EACH_OBSERVER(Observer,
350 observers_,
351 OnHunspellDictionaryDownloadSuccess());
352 Load();
353 } else {
354 InformListenersOfDownloadFailure();
355 InformListenersOfInitialization();
359 void SpellcheckHunspellDictionary::InformListenersOfInitialization() {
360 FOR_EACH_OBSERVER(Observer, observers_, OnHunspellDictionaryInitialized());
363 void SpellcheckHunspellDictionary::InformListenersOfDownloadFailure() {
364 download_status_ = DOWNLOAD_FAILED;
365 FOR_EACH_OBSERVER(Observer,
366 observers_,
367 OnHunspellDictionaryDownloadFailure());