Roll src/third_party/WebKit eac3800:0237a66 (svn 202606:202607)
[chromium-blink-merge.git] / chrome / browser / spellchecker / spellcheck_hunspell_dictionary.cc
blob8ce3037417a41495500ebf94dc73402c0c069cdc
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 "components/data_use_measurement/core/data_use_user_data.h"
16 #include "content/public/browser/browser_thread.h"
17 #include "content/public/browser/render_process_host.h"
18 #include "net/base/load_flags.h"
19 #include "net/url_request/url_fetcher.h"
20 #include "net/url_request/url_request_context_getter.h"
21 #include "url/gurl.h"
23 #if !defined(OS_ANDROID)
24 #include "base/files/memory_mapped_file.h"
25 #include "third_party/hunspell/google/bdict.h"
26 #endif
28 using content::BrowserThread;
30 namespace {
32 // Close the file.
33 void CloseDictionary(base::File file) {
34 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
35 file.Close();
38 // Saves |data| to file at |path|. Returns true on successful save, otherwise
39 // returns false.
40 bool SaveDictionaryData(scoped_ptr<std::string> data,
41 const base::FilePath& path) {
42 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
44 size_t bytes_written =
45 base::WriteFile(path, data->data(), data->length());
46 if (bytes_written != data->length()) {
47 bool success = false;
48 #if defined(OS_WIN)
49 base::FilePath dict_dir;
50 PathService::Get(chrome::DIR_USER_DATA, &dict_dir);
51 base::FilePath fallback_file_path =
52 dict_dir.Append(path.BaseName());
53 bytes_written =
54 base::WriteFile(fallback_file_path, data->data(), data->length());
55 if (bytes_written == data->length())
56 success = true;
57 #endif
59 if (!success) {
60 base::DeleteFile(path, false);
61 return false;
65 return true;
68 } // namespace
70 SpellcheckHunspellDictionary::DictionaryFile::DictionaryFile() {
73 SpellcheckHunspellDictionary::DictionaryFile::~DictionaryFile() {
74 if (file.IsValid()) {
75 BrowserThread::PostTask(
76 BrowserThread::FILE,
77 FROM_HERE,
78 base::Bind(&CloseDictionary, Passed(&file)));
82 SpellcheckHunspellDictionary::DictionaryFile::DictionaryFile(RValue other)
83 : path(other.object->path),
84 file(other.object->file.Pass()) {
87 SpellcheckHunspellDictionary::DictionaryFile&
88 SpellcheckHunspellDictionary::DictionaryFile::operator=(RValue other) {
89 if (this != other.object) {
90 path = other.object->path;
91 file = other.object->file.Pass();
93 return *this;
96 SpellcheckHunspellDictionary::SpellcheckHunspellDictionary(
97 const std::string& language,
98 net::URLRequestContextGetter* request_context_getter,
99 SpellcheckService* spellcheck_service)
100 : language_(language),
101 use_browser_spellchecker_(false),
102 request_context_getter_(request_context_getter),
103 spellcheck_service_(spellcheck_service),
104 download_status_(DOWNLOAD_NONE),
105 weak_ptr_factory_(this) {
108 SpellcheckHunspellDictionary::~SpellcheckHunspellDictionary() {
111 void SpellcheckHunspellDictionary::Load() {
112 DCHECK_CURRENTLY_ON(BrowserThread::UI);
114 #if defined(USE_BROWSER_SPELLCHECKER)
115 if (spellcheck_platform::SpellCheckerAvailable() &&
116 spellcheck_platform::PlatformSupportsLanguage(language_)) {
117 use_browser_spellchecker_ = true;
118 spellcheck_platform::SetLanguage(language_);
119 base::MessageLoop::current()->PostTask(FROM_HERE,
120 base::Bind(
121 &SpellcheckHunspellDictionary::InformListenersOfInitialization,
122 weak_ptr_factory_.GetWeakPtr()));
123 return;
125 #endif // USE_BROWSER_SPELLCHECKER
127 // Mac falls back on hunspell if its platform spellchecker isn't available.
128 // However, Android does not support hunspell.
129 #if !defined(OS_ANDROID)
130 BrowserThread::PostTaskAndReplyWithResult(
131 BrowserThread::FILE,
132 FROM_HERE,
133 base::Bind(&InitializeDictionaryLocation, language_),
134 base::Bind(
135 &SpellcheckHunspellDictionary::InitializeDictionaryLocationComplete,
136 weak_ptr_factory_.GetWeakPtr()));
137 #endif // !OS_ANDROID
140 void SpellcheckHunspellDictionary::RetryDownloadDictionary(
141 net::URLRequestContextGetter* request_context_getter) {
142 DCHECK_CURRENTLY_ON(BrowserThread::UI);
143 if (dictionary_file_.file.IsValid()) {
144 NOTREACHED();
145 return;
147 request_context_getter_ = request_context_getter;
148 DownloadDictionary(GetDictionaryURL());
151 bool SpellcheckHunspellDictionary::IsReady() const {
152 return GetDictionaryFile().IsValid() || IsUsingPlatformChecker();
155 const base::File& SpellcheckHunspellDictionary::GetDictionaryFile() const {
156 return dictionary_file_.file;
159 const std::string& SpellcheckHunspellDictionary::GetLanguage() const {
160 return language_;
163 bool SpellcheckHunspellDictionary::IsUsingPlatformChecker() const {
164 return use_browser_spellchecker_;
167 void SpellcheckHunspellDictionary::AddObserver(Observer* observer) {
168 DCHECK_CURRENTLY_ON(BrowserThread::UI);
169 observers_.AddObserver(observer);
172 void SpellcheckHunspellDictionary::RemoveObserver(Observer* observer) {
173 DCHECK_CURRENTLY_ON(BrowserThread::UI);
174 observers_.RemoveObserver(observer);
177 bool SpellcheckHunspellDictionary::IsDownloadInProgress() {
178 return download_status_ == DOWNLOAD_IN_PROGRESS;
181 bool SpellcheckHunspellDictionary::IsDownloadFailure() {
182 return download_status_ == DOWNLOAD_FAILED;
185 void SpellcheckHunspellDictionary::OnURLFetchComplete(
186 const net::URLFetcher* source) {
187 DCHECK(source);
188 DCHECK_CURRENTLY_ON(BrowserThread::UI);
189 scoped_ptr<net::URLFetcher> fetcher_destructor(fetcher_.release());
191 if ((source->GetResponseCode() / 100) != 2) {
192 // Initialize will not try to download the file a second time.
193 InformListenersOfDownloadFailure();
194 return;
197 // Basic sanity check on the dictionary. There's a small chance of 200 status
198 // code for a body that represents some form of failure.
199 scoped_ptr<std::string> data(new std::string);
200 source->GetResponseAsString(data.get());
201 if (data->size() < 4 || data->compare(0, 4, "BDic") != 0) {
202 InformListenersOfDownloadFailure();
203 return;
206 #if !defined(OS_ANDROID)
207 // To prevent corrupted dictionary data from causing a renderer crash, scan
208 // the dictionary data and verify it is sane before save it to a file.
209 // TODO(rlp): Adding metrics to RecordDictionaryCorruptionStats
210 if (!hunspell::BDict::Verify(data->data(), data->size())) {
211 // Let PostTaskAndReply caller send to InformListenersOfInitialization
212 // through SaveDictionaryDataComplete().
213 SaveDictionaryDataComplete(false);
214 return;
216 #endif
218 BrowserThread::PostTaskAndReplyWithResult<bool>(
219 BrowserThread::FILE,
220 FROM_HERE,
221 base::Bind(&SaveDictionaryData,
222 base::Passed(&data),
223 dictionary_file_.path),
224 base::Bind(&SpellcheckHunspellDictionary::SaveDictionaryDataComplete,
225 weak_ptr_factory_.GetWeakPtr()));
228 GURL SpellcheckHunspellDictionary::GetDictionaryURL() {
229 static const char kDownloadServerUrl[] =
230 "https://redirector.gvt1.com/edgedl/chrome/dict/";
231 std::string bdict_file = dictionary_file_.path.BaseName().MaybeAsASCII();
233 DCHECK(!bdict_file.empty());
235 return GURL(std::string(kDownloadServerUrl) +
236 base::ToLowerASCII(bdict_file));
239 void SpellcheckHunspellDictionary::DownloadDictionary(GURL url) {
240 DCHECK_CURRENTLY_ON(BrowserThread::UI);
241 DCHECK(request_context_getter_);
243 download_status_ = DOWNLOAD_IN_PROGRESS;
244 FOR_EACH_OBSERVER(Observer, observers_,
245 OnHunspellDictionaryDownloadBegin(language_));
247 fetcher_ = net::URLFetcher::Create(url, net::URLFetcher::GET, this);
248 data_use_measurement::DataUseUserData::AttachToFetcher(
249 fetcher_.get(), data_use_measurement::DataUseUserData::SPELL_CHECKER);
250 fetcher_->SetRequestContext(request_context_getter_);
251 fetcher_->SetLoadFlags(
252 net::LOAD_DO_NOT_SEND_COOKIES | net::LOAD_DO_NOT_SAVE_COOKIES);
253 fetcher_->Start();
254 // Attempt downloading the dictionary only once.
255 request_context_getter_ = NULL;
258 // The default_dictionary_file can either come from the standard list of
259 // hunspell dictionaries (determined in InitializeDictionaryLocation), or it
260 // can be passed in via an extension. In either case, the file is checked for
261 // existence so that it's not re-downloaded.
262 // For systemwide installations on Windows, the default directory may not
263 // have permissions for download. In that case, the alternate directory for
264 // download is chrome::DIR_USER_DATA.
265 SpellcheckHunspellDictionary::DictionaryFile
266 SpellcheckHunspellDictionary::OpenDictionaryFile(const base::FilePath& path) {
267 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
268 DictionaryFile dictionary;
270 #if defined(OS_WIN)
271 // Check if the dictionary exists in the fallback location. If so, use it
272 // rather than downloading anew.
273 base::FilePath user_dir;
274 PathService::Get(chrome::DIR_USER_DATA, &user_dir);
275 base::FilePath fallback = user_dir.Append(path.BaseName());
276 if (!base::PathExists(path) && base::PathExists(fallback))
277 dictionary.path = fallback;
278 else
279 dictionary.path = path;
280 #else
281 dictionary.path = path;
282 #endif
284 // Read the dictionary file and scan its data to check for corruption. The
285 // scoping closes the memory-mapped file before it is opened or deleted.
286 bool bdict_is_valid = false;
288 #if !defined(OS_ANDROID)
290 base::MemoryMappedFile map;
291 bdict_is_valid =
292 base::PathExists(dictionary.path) &&
293 map.Initialize(dictionary.path) &&
294 hunspell::BDict::Verify(reinterpret_cast<const char*>(map.data()),
295 map.length());
297 #endif
299 if (bdict_is_valid) {
300 dictionary.file.Initialize(dictionary.path,
301 base::File::FLAG_READ | base::File::FLAG_OPEN);
302 } else {
303 base::DeleteFile(dictionary.path, false);
306 return dictionary.Pass();
309 // The default place where the spellcheck dictionary resides is
310 // chrome::DIR_APP_DICTIONARIES.
311 SpellcheckHunspellDictionary::DictionaryFile
312 SpellcheckHunspellDictionary::InitializeDictionaryLocation(
313 const std::string& language) {
314 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
316 // Initialize the BDICT path. Initialization should be in the FILE thread
317 // because it checks if there is a "Dictionaries" directory and create it.
318 base::FilePath dict_dir;
319 PathService::Get(chrome::DIR_APP_DICTIONARIES, &dict_dir);
320 base::FilePath dict_path =
321 chrome::spellcheck_common::GetVersionedFileName(language, dict_dir);
323 return OpenDictionaryFile(dict_path);
326 void SpellcheckHunspellDictionary::InitializeDictionaryLocationComplete(
327 DictionaryFile file) {
328 DCHECK_CURRENTLY_ON(BrowserThread::UI);
329 dictionary_file_ = file.Pass();
331 if (!dictionary_file_.file.IsValid()) {
332 // Notify browser tests that this dictionary is corrupted. Skip downloading
333 // the dictionary in browser tests.
334 // TODO(rouslan): Remove this test-only case.
335 if (spellcheck_service_->SignalStatusEvent(
336 SpellcheckService::BDICT_CORRUPTED)) {
337 request_context_getter_ = NULL;
340 if (request_context_getter_) {
341 // Download from the UI thread to check that |request_context_getter_| is
342 // still valid.
343 DownloadDictionary(GetDictionaryURL());
344 return;
348 InformListenersOfInitialization();
351 void SpellcheckHunspellDictionary::SaveDictionaryDataComplete(
352 bool dictionary_saved) {
353 DCHECK_CURRENTLY_ON(BrowserThread::UI);
355 if (dictionary_saved) {
356 download_status_ = DOWNLOAD_NONE;
357 FOR_EACH_OBSERVER(Observer,
358 observers_,
359 OnHunspellDictionaryDownloadSuccess(language_));
360 Load();
361 } else {
362 InformListenersOfDownloadFailure();
363 InformListenersOfInitialization();
367 void SpellcheckHunspellDictionary::InformListenersOfInitialization() {
368 FOR_EACH_OBSERVER(Observer, observers_,
369 OnHunspellDictionaryInitialized(language_));
372 void SpellcheckHunspellDictionary::InformListenersOfDownloadFailure() {
373 download_status_ = DOWNLOAD_FAILED;
374 FOR_EACH_OBSERVER(Observer,
375 observers_,
376 OnHunspellDictionaryDownloadFailure(language_));