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/files/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().IsValid() || IsUsingPlatformChecker();
142}
143
144const base::File& SpellcheckHunspellDictionary::GetDictionaryFile() const {
145  return dictionary_file_.file;
146}
147
148const std::string& SpellcheckHunspellDictionary::GetLanguage() const {
149  return language_;
150}
151
152bool SpellcheckHunspellDictionary::IsUsingPlatformChecker() const {
153  return use_platform_spellchecker_;
154}
155
156void SpellcheckHunspellDictionary::AddObserver(Observer* observer) {
157  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
158  observers_.AddObserver(observer);
159}
160
161void SpellcheckHunspellDictionary::RemoveObserver(Observer* observer) {
162  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
163  observers_.RemoveObserver(observer);
164}
165
166bool SpellcheckHunspellDictionary::IsDownloadInProgress() {
167  return download_status_ == DOWNLOAD_IN_PROGRESS;
168}
169
170bool SpellcheckHunspellDictionary::IsDownloadFailure() {
171  return download_status_ == DOWNLOAD_FAILED;
172}
173
174void SpellcheckHunspellDictionary::OnURLFetchComplete(
175    const net::URLFetcher* source) {
176  DCHECK(source);
177  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
178  scoped_ptr<net::URLFetcher> fetcher_destructor(fetcher_.release());
179
180  if ((source->GetResponseCode() / 100) != 2) {
181    // Initialize will not try to download the file a second time.
182    InformListenersOfDownloadFailure();
183    return;
184  }
185
186  // Basic sanity check on the dictionary. There's a small chance of 200 status
187  // code for a body that represents some form of failure.
188  scoped_ptr<std::string> data(new std::string);
189  source->GetResponseAsString(data.get());
190  if (data->size() < 4 || data->compare(0, 4, "BDic") != 0) {
191    InformListenersOfDownloadFailure();
192    return;
193  }
194
195  // To prevent corrupted dictionary data from causing a renderer crash, scan
196  // the dictionary data and verify it is sane before save it to a file.
197  // TODO(rlp): Adding metrics to RecordDictionaryCorruptionStats
198  if (!hunspell::BDict::Verify(data->data(), data->size())) {
199    // Let PostTaskAndReply caller send to InformListenersOfInitialization
200    // through SaveDictionaryDataComplete().
201    SaveDictionaryDataComplete(false);
202    return;
203  }
204
205  BrowserThread::PostTaskAndReplyWithResult<bool>(
206      BrowserThread::FILE,
207      FROM_HERE,
208      base::Bind(&SaveDictionaryData,
209                 base::Passed(&data),
210                 dictionary_file_.path),
211      base::Bind(&SpellcheckHunspellDictionary::SaveDictionaryDataComplete,
212                 weak_ptr_factory_.GetWeakPtr()));
213}
214
215GURL SpellcheckHunspellDictionary::GetDictionaryURL() {
216  static const char kDownloadServerUrl[] =
217      "http://cache.pack.google.com/edgedl/chrome/dict/";
218  std::string bdict_file = dictionary_file_.path.BaseName().MaybeAsASCII();
219
220  DCHECK(!bdict_file.empty());
221
222  return GURL(std::string(kDownloadServerUrl) +
223              base::StringToLowerASCII(bdict_file));
224}
225
226void SpellcheckHunspellDictionary::DownloadDictionary(GURL url) {
227  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
228  DCHECK(request_context_getter_);
229
230  download_status_ = DOWNLOAD_IN_PROGRESS;
231  FOR_EACH_OBSERVER(Observer, observers_, OnHunspellDictionaryDownloadBegin());
232
233  fetcher_.reset(net::URLFetcher::Create(url, net::URLFetcher::GET, this));
234  fetcher_->SetRequestContext(request_context_getter_);
235  fetcher_->SetLoadFlags(
236      net::LOAD_DO_NOT_SEND_COOKIES | net::LOAD_DO_NOT_SAVE_COOKIES);
237  fetcher_->Start();
238  // Attempt downloading the dictionary only once.
239  request_context_getter_ = NULL;
240}
241
242// The default_dictionary_file can either come from the standard list of
243// hunspell dictionaries (determined in InitializeDictionaryLocation), or it
244// can be passed in via an extension. In either case, the file is checked for
245// existence so that it's not re-downloaded.
246// For systemwide installations on Windows, the default directory may not
247// have permissions for download. In that case, the alternate directory for
248// download is chrome::DIR_USER_DATA.
249SpellcheckHunspellDictionary::DictionaryFile
250SpellcheckHunspellDictionary::OpenDictionaryFile(const base::FilePath& path) {
251  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
252  DictionaryFile dictionary;
253
254#if defined(OS_WIN)
255  // Check if the dictionary exists in the fallback location. If so, use it
256  // rather than downloading anew.
257  base::FilePath user_dir;
258  PathService::Get(chrome::DIR_USER_DATA, &user_dir);
259  base::FilePath fallback = user_dir.Append(path.BaseName());
260  if (!base::PathExists(path) && base::PathExists(fallback))
261    dictionary.path = fallback;
262  else
263    dictionary.path = path;
264#else
265  dictionary.path = path;
266#endif
267
268  // Read the dictionary file and scan its data to check for corruption. The
269  // scoping closes the memory-mapped file before it is opened or deleted.
270  bool bdict_is_valid;
271  {
272    base::MemoryMappedFile map;
273    bdict_is_valid =
274        base::PathExists(dictionary.path) &&
275        map.Initialize(dictionary.path) &&
276        hunspell::BDict::Verify(reinterpret_cast<const char*>(map.data()),
277                                map.length());
278  }
279  if (bdict_is_valid) {
280    dictionary.file.Initialize(dictionary.path,
281                               base::File::FLAG_READ | base::File::FLAG_OPEN);
282  } else {
283    base::DeleteFile(dictionary.path, false);
284  }
285
286  return dictionary.Pass();
287}
288
289// The default place where the spellcheck dictionary resides is
290// chrome::DIR_APP_DICTIONARIES.
291SpellcheckHunspellDictionary::DictionaryFile
292SpellcheckHunspellDictionary::InitializeDictionaryLocation(
293    const std::string& language) {
294  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
295
296  // Initialize the BDICT path. Initialization should be in the FILE thread
297  // because it checks if there is a "Dictionaries" directory and create it.
298  base::FilePath dict_dir;
299  PathService::Get(chrome::DIR_APP_DICTIONARIES, &dict_dir);
300  base::FilePath dict_path =
301      chrome::spellcheck_common::GetVersionedFileName(language, dict_dir);
302
303  return OpenDictionaryFile(dict_path);
304}
305
306void SpellcheckHunspellDictionary::InitializeDictionaryLocationComplete(
307    DictionaryFile file) {
308  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
309  dictionary_file_ = file.Pass();
310
311  if (!dictionary_file_.file.IsValid()) {
312
313    // Notify browser tests that this dictionary is corrupted. Skip downloading
314    // the dictionary in browser tests.
315    // TODO(rouslan): Remove this test-only case.
316    if (spellcheck_service_->SignalStatusEvent(
317          SpellcheckService::BDICT_CORRUPTED)) {
318      request_context_getter_ = NULL;
319    }
320
321    if (request_context_getter_) {
322      // Download from the UI thread to check that |request_context_getter_| is
323      // still valid.
324      DownloadDictionary(GetDictionaryURL());
325      return;
326    }
327  }
328
329  InformListenersOfInitialization();
330}
331
332void SpellcheckHunspellDictionary::SaveDictionaryDataComplete(
333    bool dictionary_saved) {
334  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
335
336  if (dictionary_saved) {
337    download_status_ = DOWNLOAD_NONE;
338    FOR_EACH_OBSERVER(Observer,
339                      observers_,
340                      OnHunspellDictionaryDownloadSuccess());
341    Load();
342  } else {
343    InformListenersOfDownloadFailure();
344    InformListenersOfInitialization();
345  }
346}
347
348void SpellcheckHunspellDictionary::InformListenersOfInitialization() {
349  FOR_EACH_OBSERVER(Observer, observers_, OnHunspellDictionaryInitialized());
350}
351
352void SpellcheckHunspellDictionary::InformListenersOfDownloadFailure() {
353  download_status_ = DOWNLOAD_FAILED;
354  FOR_EACH_OBSERVER(Observer,
355                    observers_,
356                    OnHunspellDictionaryDownloadFailure());
357}
358