spellcheck_hunspell_dictionary.cc revision c5cede9ae108bb15f6b7a8aea21c7e1fefa2834c
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
25using content::BrowserThread;
26
27namespace {
28
29// Close the file.
30void 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.
37bool 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
67SpellcheckHunspellDictionary::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
79SpellcheckHunspellDictionary::DictionaryFile::DictionaryFile(RValue other)
80    : path(other.object->path),
81      file(other.object->file.Pass()) {
82}
83
84SpellcheckHunspellDictionary::DictionaryFile&
85SpellcheckHunspellDictionary::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
93SpellcheckHunspellDictionary::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
105SpellcheckHunspellDictionary::~SpellcheckHunspellDictionary() {
106}
107
108void 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
133void 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
140bool SpellcheckHunspellDictionary::IsReady() const {
141  return GetDictionaryFile() !=
142      base::kInvalidPlatformFileValue || IsUsingPlatformChecker();
143}
144
145base::PlatformFile SpellcheckHunspellDictionary::GetDictionaryFile() const {
146  return dictionary_file_.file.GetPlatformFile();
147}
148
149const std::string& SpellcheckHunspellDictionary::GetLanguage() const {
150  return language_;
151}
152
153bool SpellcheckHunspellDictionary::IsUsingPlatformChecker() const {
154  return use_platform_spellchecker_;
155}
156
157void SpellcheckHunspellDictionary::AddObserver(Observer* observer) {
158  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
159  observers_.AddObserver(observer);
160}
161
162void SpellcheckHunspellDictionary::RemoveObserver(Observer* observer) {
163  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
164  observers_.RemoveObserver(observer);
165}
166
167bool SpellcheckHunspellDictionary::IsDownloadInProgress() {
168  return download_status_ == DOWNLOAD_IN_PROGRESS;
169}
170
171bool SpellcheckHunspellDictionary::IsDownloadFailure() {
172  return download_status_ == DOWNLOAD_FAILED;
173}
174
175void 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
216GURL 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
227void 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.
250SpellcheckHunspellDictionary::DictionaryFile
251SpellcheckHunspellDictionary::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.
292SpellcheckHunspellDictionary::DictionaryFile
293SpellcheckHunspellDictionary::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
307void 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
333void 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
349void SpellcheckHunspellDictionary::InformListenersOfInitialization() {
350  FOR_EACH_OBSERVER(Observer, observers_, OnHunspellDictionaryInitialized());
351}
352
353void SpellcheckHunspellDictionary::InformListenersOfDownloadFailure() {
354  download_status_ = DOWNLOAD_FAILED;
355  FOR_EACH_OBSERVER(Observer,
356                    observers_,
357                    OnHunspellDictionaryDownloadFailure());
358}
359