Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / spellchecker / spellcheck_hunspell_dictionary.cc
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.
4
5 #include "chrome/browser/spellchecker/spellcheck_hunspell_dictionary.h"
6
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"
24
25 using content::BrowserThread;
26
27 namespace {
28
29 // Close the file.
30 void CloseDictionary(base::File file) {
31   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
32   file.Close();
33 }
34
35 // Saves |data| to file at |path|. Returns true on successful save, otherwise
36 // returns false.
37 bool SaveDictionaryData(scoped_ptr<std::string> data,
38                         const base::FilePath& path) {
39   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
40
41   size_t bytes_written =
42       base::WriteFile(path, data->data(), data->length());
43   if (bytes_written != data->length()) {
44     bool success = false;
45 #if defined(OS_WIN)
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());
50     bytes_written =
51         base::WriteFile(fallback_file_path, data->data(), data->length());
52     if (bytes_written == data->length())
53       success = true;
54 #endif
55
56     if (!success) {
57       base::DeleteFile(path, false);
58       return false;
59     }
60   }
61
62   return true;
63 }
64
65 }  // namespace
66
67 SpellcheckHunspellDictionary::DictionaryFile::DictionaryFile() {
68  }
69
70  SpellcheckHunspellDictionary::DictionaryFile::~DictionaryFile() {
71   if (file.IsValid()) {
72     BrowserThread::PostTask(
73         BrowserThread::FILE,
74         FROM_HERE,
75         base::Bind(&CloseDictionary, Passed(&file)));
76   }
77 }
78
79 SpellcheckHunspellDictionary::DictionaryFile::DictionaryFile(RValue other)
80     : path(other.object->path),
81       file(other.object->file.Pass()) {
82 }
83
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();
89   }
90   return *this;
91 }
92
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) {
103 }
104
105 SpellcheckHunspellDictionary::~SpellcheckHunspellDictionary() {
106 }
107
108 void SpellcheckHunspellDictionary::Load() {
109   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
110
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,
117         base::Bind(
118             &SpellcheckHunspellDictionary::InformListenersOfInitialization,
119             weak_ptr_factory_.GetWeakPtr()));
120     return;
121   }
122 #endif  // OS_MACOSX
123
124   BrowserThread::PostTaskAndReplyWithResult(
125       BrowserThread::FILE,
126       FROM_HERE,
127       base::Bind(&InitializeDictionaryLocation, language_),
128       base::Bind(
129           &SpellcheckHunspellDictionary::InitializeDictionaryLocationComplete,
130           weak_ptr_factory_.GetWeakPtr()));
131 }
132
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());
138 }
139
140 bool SpellcheckHunspellDictionary::IsReady() const {
141   return GetDictionaryFile() !=
142       base::kInvalidPlatformFileValue || IsUsingPlatformChecker();
143 }
144
145 base::PlatformFile SpellcheckHunspellDictionary::GetDictionaryFile() const {
146   return dictionary_file_.file.GetPlatformFile();
147 }
148
149 const std::string& SpellcheckHunspellDictionary::GetLanguage() const {
150   return language_;
151 }
152
153 bool SpellcheckHunspellDictionary::IsUsingPlatformChecker() const {
154   return use_platform_spellchecker_;
155 }
156
157 void SpellcheckHunspellDictionary::AddObserver(Observer* observer) {
158   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
159   observers_.AddObserver(observer);
160 }
161
162 void SpellcheckHunspellDictionary::RemoveObserver(Observer* observer) {
163   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
164   observers_.RemoveObserver(observer);
165 }
166
167 bool SpellcheckHunspellDictionary::IsDownloadInProgress() {
168   return download_status_ == DOWNLOAD_IN_PROGRESS;
169 }
170
171 bool SpellcheckHunspellDictionary::IsDownloadFailure() {
172   return download_status_ == DOWNLOAD_FAILED;
173 }
174
175 void SpellcheckHunspellDictionary::OnURLFetchComplete(
176     const net::URLFetcher* source) {
177   DCHECK(source);
178   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
179   scoped_ptr<net::URLFetcher> fetcher_destructor(fetcher_.release());
180
181   if ((source->GetResponseCode() / 100) != 2) {
182     // Initialize will not try to download the file a second time.
183     InformListenersOfDownloadFailure();
184     return;
185   }
186
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();
193     return;
194   }
195
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);
203     return;
204   }
205
206   BrowserThread::PostTaskAndReplyWithResult<bool>(
207       BrowserThread::FILE,
208       FROM_HERE,
209       base::Bind(&SaveDictionaryData,
210                  base::Passed(&data),
211                  dictionary_file_.path),
212       base::Bind(&SpellcheckHunspellDictionary::SaveDictionaryDataComplete,
213                  weak_ptr_factory_.GetWeakPtr()));
214 }
215
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();
220
221   DCHECK(!bdict_file.empty());
222
223   return GURL(std::string(kDownloadServerUrl) +
224               StringToLowerASCII(bdict_file));
225 }
226
227 void SpellcheckHunspellDictionary::DownloadDictionary(GURL url) {
228   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
229   DCHECK(request_context_getter_);
230
231   download_status_ = DOWNLOAD_IN_PROGRESS;
232   FOR_EACH_OBSERVER(Observer, observers_, OnHunspellDictionaryDownloadBegin());
233
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);
238   fetcher_->Start();
239   // Attempt downloading the dictionary only once.
240   request_context_getter_ = NULL;
241 }
242
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;
254
255 #if defined(OS_WIN)
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;
263   else
264     dictionary.path = path;
265 #else
266   dictionary.path = path;
267 #endif
268
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.
271   bool bdict_is_valid;
272   {
273     base::MemoryMappedFile map;
274     bdict_is_valid =
275         base::PathExists(dictionary.path) &&
276         map.Initialize(dictionary.path) &&
277         hunspell::BDict::Verify(reinterpret_cast<const char*>(map.data()),
278                                 map.length());
279   }
280   if (bdict_is_valid) {
281     dictionary.file.Initialize(dictionary.path,
282                                base::File::FLAG_READ | base::File::FLAG_OPEN);
283   } else {
284     base::DeleteFile(dictionary.path, false);
285   }
286
287   return dictionary.Pass();
288 }
289
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));
296
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);
303
304   return OpenDictionaryFile(dict_path);
305 }
306
307 void SpellcheckHunspellDictionary::InitializeDictionaryLocationComplete(
308     DictionaryFile file) {
309   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
310   dictionary_file_ = file.Pass();
311
312   if (!dictionary_file_.file.IsValid()) {
313
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;
320     }
321
322     if (request_context_getter_) {
323       // Download from the UI thread to check that |request_context_getter_| is
324       // still valid.
325       DownloadDictionary(GetDictionaryURL());
326       return;
327     }
328   }
329
330   InformListenersOfInitialization();
331 }
332
333 void SpellcheckHunspellDictionary::SaveDictionaryDataComplete(
334     bool dictionary_saved) {
335   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
336
337   if (dictionary_saved) {
338     download_status_ = DOWNLOAD_NONE;
339     FOR_EACH_OBSERVER(Observer,
340                       observers_,
341                       OnHunspellDictionaryDownloadSuccess());
342     Load();
343   } else {
344     InformListenersOfDownloadFailure();
345     InformListenersOfInitialization();
346   }
347 }
348
349 void SpellcheckHunspellDictionary::InformListenersOfInitialization() {
350   FOR_EACH_OBSERVER(Observer, observers_, OnHunspellDictionaryInitialized());
351 }
352
353 void SpellcheckHunspellDictionary::InformListenersOfDownloadFailure() {
354   download_status_ = DOWNLOAD_FAILED;
355   FOR_EACH_OBSERVER(Observer,
356                     observers_,
357                     OnHunspellDictionaryDownloadFailure());
358 }