Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / spellchecker / spellcheck_hunspell_dictionary.cc
blob6cc0fa2be5602545a7d3dbdc7bd3d44edd4c964f
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"
23 #include "url/gurl.h"
25 using content::BrowserThread;
27 namespace {
29 // Close the platform file.
30 void CloseDictionary(base::PlatformFile file) {
31 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
32 base::ClosePlatformFile(file);
35 } // namespace
37 // Dictionary file information to be passed between the FILE and UI threads.
38 struct DictionaryFile {
39 DictionaryFile() : descriptor(base::kInvalidPlatformFileValue) {
42 ~DictionaryFile() {
43 if (descriptor != base::kInvalidPlatformFileValue) {
44 BrowserThread::PostTask(
45 BrowserThread::FILE,
46 FROM_HERE,
47 base::Bind(&CloseDictionary, descriptor));
48 descriptor = base::kInvalidPlatformFileValue;
52 // The desired location of the dictionary file, whether or not it exists.
53 base::FilePath path;
55 // The file descriptor/handle for the dictionary file.
56 base::PlatformFile descriptor;
58 DISALLOW_COPY_AND_ASSIGN(DictionaryFile);
61 namespace {
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));
77 #if defined(OS_WIN)
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;
85 #endif
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.
89 bool bdict_is_valid;
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()),
95 map.length());
97 if (bdict_is_valid) {
98 file->descriptor = base::CreatePlatformFile(
99 file->path,
100 base::PLATFORM_FILE_READ | base::PLATFORM_FILE_OPEN,
101 NULL,
102 NULL);
103 } else {
104 base::DeleteFile(file->path, false);
107 return file.Pass();
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(
124 language, dict_dir);
126 return OpenDictionaryFile(file.Pass());
129 // Saves |data| to file at |path|. Returns true on successful save, otherwise
130 // returns false.
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;
139 #if defined(OS_WIN)
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());
144 bytes_written =
145 file_util::WriteFile(fallback_file_path, data->data(), data->length());
146 if (bytes_written == data->length())
147 success = true;
148 #endif
150 if (!success) {
151 base::DeleteFile(path, false);
152 return false;
156 return true;
159 } // namespace
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,
186 base::Bind(
187 &SpellcheckHunspellDictionary::InformListenersOfInitialization,
188 weak_ptr_factory_.GetWeakPtr()));
189 return;
191 #endif // OS_MACOSX
193 BrowserThread::PostTaskAndReplyWithResult(
194 BrowserThread::FILE,
195 FROM_HERE,
196 base::Bind(&InitializeDictionaryLocation, language_),
197 base::Bind(
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 {
220 return language_;
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) {
247 DCHECK(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();
254 return;
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();
263 return;
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);
273 return;
276 BrowserThread::PostTaskAndReplyWithResult<bool>(
277 BrowserThread::FILE,
278 FROM_HERE,
279 base::Bind(&SaveDictionaryData,
280 base::Passed(&data),
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);
308 fetcher_->Start();
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
330 // still valid.
331 DownloadDictionary(GetDictionaryURL());
332 return;
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,
346 observers_,
347 OnHunspellDictionaryDownloadSuccess());
348 Load();
349 } else {
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,
362 observers_,
363 OnHunspellDictionaryDownloadFailure());