spellcheck_hunspell_dictionary.cc revision 7dbb3d5cf0c15f500944d211057644d6a2f37371
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.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 platform file.
30void 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.
38struct 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
61namespace {
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.
73scoped_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.
114scoped_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.
131bool 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
161SpellcheckHunspellDictionary::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      weak_ptr_factory_(this),
169      spellcheck_service_(spellcheck_service),
170      download_status_(DOWNLOAD_NONE),
171      dictionary_file_(new DictionaryFile) {
172}
173
174SpellcheckHunspellDictionary::~SpellcheckHunspellDictionary() {
175}
176
177void 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
202void 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
209bool SpellcheckHunspellDictionary::IsReady() const {
210  return GetDictionaryFile() !=
211      base::kInvalidPlatformFileValue || IsUsingPlatformChecker();
212}
213
214const base::PlatformFile&
215SpellcheckHunspellDictionary::GetDictionaryFile() const {
216  return dictionary_file_->descriptor;
217}
218
219const std::string& SpellcheckHunspellDictionary::GetLanguage() const {
220  return language_;
221}
222
223bool SpellcheckHunspellDictionary::IsUsingPlatformChecker() const {
224  return use_platform_spellchecker_;
225}
226
227void SpellcheckHunspellDictionary::AddObserver(Observer* observer) {
228  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
229  observers_.AddObserver(observer);
230}
231
232void SpellcheckHunspellDictionary::RemoveObserver(Observer* observer) {
233  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
234  observers_.RemoveObserver(observer);
235}
236
237bool SpellcheckHunspellDictionary::IsDownloadInProgress() {
238  return download_status_ == DOWNLOAD_IN_PROGRESS;
239}
240
241bool SpellcheckHunspellDictionary::IsDownloadFailure() {
242  return download_status_ == DOWNLOAD_FAILED;
243}
244
245void 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
286GURL 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
297void 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
313void 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
339void 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
355void SpellcheckHunspellDictionary::InformListenersOfInitialization() {
356  FOR_EACH_OBSERVER(Observer, observers_, OnHunspellDictionaryInitialized());
357}
358
359void SpellcheckHunspellDictionary::InformListenersOfDownloadFailure() {
360  download_status_ = DOWNLOAD_FAILED;
361  FOR_EACH_OBSERVER(Observer,
362                    observers_,
363                    OnHunspellDictionaryDownloadFailure());
364}
365