- add sources.
[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 platform file.
30 void CloseDictionary(base::PlatformFile file) {
31   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
32   base::ClosePlatformFile(file);
33 }
34
35 }  // namespace
36
37 // Dictionary file information to be passed between the FILE and UI threads.
38 struct DictionaryFile {
39   DictionaryFile() : descriptor(base::kInvalidPlatformFileValue) {
40   }
41
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;
49     }
50   }
51
52   // The desired location of the dictionary file, whether or not it exists.
53   base::FilePath path;
54
55   // The file descriptor/handle for the dictionary file.
56   base::PlatformFile descriptor;
57
58   DISALLOW_COPY_AND_ASSIGN(DictionaryFile);
59 };
60
61 namespace {
62
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));
76
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
86
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;
90   {
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());
96   }
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);
105   }
106
107   return file.Pass();
108 }
109
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);
118
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);
125
126   return OpenDictionaryFile(file.Pass());
127 }
128
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));
134
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
149
150     if (!success) {
151       base::DeleteFile(path, false);
152       return false;
153     }
154   }
155
156   return true;
157 }
158
159 }  // namespace
160
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) {
172 }
173
174 SpellcheckHunspellDictionary::~SpellcheckHunspellDictionary() {
175 }
176
177 void SpellcheckHunspellDictionary::Load() {
178   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
179
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;
190   }
191 #endif  // OS_MACOSX
192
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()));
200 }
201
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());
207 }
208
209 bool SpellcheckHunspellDictionary::IsReady() const {
210   return GetDictionaryFile() !=
211       base::kInvalidPlatformFileValue || IsUsingPlatformChecker();
212 }
213
214 const base::PlatformFile&
215 SpellcheckHunspellDictionary::GetDictionaryFile() const {
216   return dictionary_file_->descriptor;
217 }
218
219 const std::string& SpellcheckHunspellDictionary::GetLanguage() const {
220   return language_;
221 }
222
223 bool SpellcheckHunspellDictionary::IsUsingPlatformChecker() const {
224   return use_platform_spellchecker_;
225 }
226
227 void SpellcheckHunspellDictionary::AddObserver(Observer* observer) {
228   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
229   observers_.AddObserver(observer);
230 }
231
232 void SpellcheckHunspellDictionary::RemoveObserver(Observer* observer) {
233   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
234   observers_.RemoveObserver(observer);
235 }
236
237 bool SpellcheckHunspellDictionary::IsDownloadInProgress() {
238   return download_status_ == DOWNLOAD_IN_PROGRESS;
239 }
240
241 bool SpellcheckHunspellDictionary::IsDownloadFailure() {
242   return download_status_ == DOWNLOAD_FAILED;
243 }
244
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());
250
251   if ((source->GetResponseCode() / 100) != 2) {
252     // Initialize will not try to download the file a second time.
253     InformListenersOfDownloadFailure();
254     return;
255   }
256
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;
264   }
265
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;
274   }
275
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()));
284 }
285
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();
290
291   DCHECK(!bdict_file.empty());
292
293   return GURL(std::string(kDownloadServerUrl) +
294               StringToLowerASCII(bdict_file));
295 }
296
297 void SpellcheckHunspellDictionary::DownloadDictionary(GURL url) {
298   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
299   DCHECK(request_context_getter_);
300
301   download_status_ = DOWNLOAD_IN_PROGRESS;
302   FOR_EACH_OBSERVER(Observer, observers_, OnHunspellDictionaryDownloadBegin());
303
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;
311 }
312
313 void SpellcheckHunspellDictionary::InitializeDictionaryLocationComplete(
314     scoped_ptr<DictionaryFile> file) {
315   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
316   dictionary_file_ = file.Pass();
317
318   if (dictionary_file_->descriptor == base::kInvalidPlatformFileValue) {
319
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;
326     }
327
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;
333     }
334   }
335
336   InformListenersOfInitialization();
337 }
338
339 void SpellcheckHunspellDictionary::SaveDictionaryDataComplete(
340     bool dictionary_saved) {
341   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
342
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();
352   }
353 }
354
355 void SpellcheckHunspellDictionary::InformListenersOfInitialization() {
356   FOR_EACH_OBSERVER(Observer, observers_, OnHunspellDictionaryInitialized());
357 }
358
359 void SpellcheckHunspellDictionary::InformListenersOfDownloadFailure() {
360   download_status_ = DOWNLOAD_FAILED;
361   FOR_EACH_OBSERVER(Observer,
362                     observers_,
363                     OnHunspellDictionaryDownloadFailure());
364 }