diff options
Diffstat (limited to '')
22 files changed, 2327 insertions, 0 deletions
diff --git a/extensions/spellcheck/hunspell/glue/PRemoteSpellcheckEngine.ipdl b/extensions/spellcheck/hunspell/glue/PRemoteSpellcheckEngine.ipdl new file mode 100644 index 0000000000..c327d97e65 --- /dev/null +++ b/extensions/spellcheck/hunspell/glue/PRemoteSpellcheckEngine.ipdl @@ -0,0 +1,46 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include protocol PContent; + +include "mozilla/RemoteSpellCheckEngineParent.h"; +include "mozilla/RemoteSpellCheckEngineChild.h"; + +namespace mozilla { + +[ManualDealloc, ChildImpl="RemoteSpellcheckEngineChild", ParentImpl="RemoteSpellcheckEngineParent"] +sync protocol PRemoteSpellcheckEngine { + manager PContent; + +parent: + async __delete__(); + + async CheckAsync(nsString[] aWord) returns (bool[] aIsMisspelled); + + sync SetDictionary(nsCString aDictionary) returns (bool success); + + /* + * Set multiple current dictionaries from a list of dictionary names. + * + * @aDictionaries An array of dictionary names to use. If the array is empty, + * no dictionary will be used. + * @aSuccess true if setting the dictionaries succeeded, false otherwise. + */ + async SetDictionaries(nsCString[] aDictionaries) returns (bool success); + + async Suggest(nsString aWord, uint32_t aCount) returns (nsString[] aSuggestions); + + /* + * Set current dictionary from list of dictionary name. + * + * @aList A list of dictionary name. If a string into this list is + * empty string, dictionary selection is reset + * @aSuccess true if setting dictionary is successful + * @aDictionary Return current dictionary name that set by this method. + */ + async SetDictionaryFromList(nsCString[] aList) + returns (bool aSuccess, nsCString aDictionary); +}; + +} // namespace mozilla diff --git a/extensions/spellcheck/hunspell/glue/RLBoxHunspell.cpp b/extensions/spellcheck/hunspell/glue/RLBoxHunspell.cpp new file mode 100644 index 0000000000..b3a1990ceb --- /dev/null +++ b/extensions/spellcheck/hunspell/glue/RLBoxHunspell.cpp @@ -0,0 +1,256 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Assertions.h" +#ifdef MOZ_WASM_SANDBOXING_HUNSPELL +# include "mozilla/ipc/LibrarySandboxPreload.h" +#endif +#include "RLBoxHunspell.h" +#include "mozHunspellRLBoxGlue.h" +#include "mozHunspellRLBoxHost.h" +#include "nsThread.h" + +using namespace rlbox; +using namespace mozilla; + +// Helper function for allocating and copying std::string into sandbox +static tainted_hunspell<char*> allocStrInSandbox( + rlbox_sandbox_hunspell& aSandbox, const std::string& str) { + size_t size = str.size() + 1; + tainted_hunspell<char*> t_str = aSandbox.malloc_in_sandbox<char>(size); + if (t_str) { + rlbox::memcpy(aSandbox, t_str, str.c_str(), size); + } + return t_str; +} + +/* static */ +RLBoxHunspell* RLBoxHunspell::Create(const nsCString& affpath, + const nsCString& dpath) { + MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread()); + + mozilla::UniquePtr<rlbox_sandbox_hunspell> sandbox( + new rlbox_sandbox_hunspell()); + +#if defined(MOZ_WASM_SANDBOXING_HUNSPELL) && !defined(HAVE_64BIT_BUILD) + // By default, the rlbox sandbox size is smaller on 32-bit builds than the max + // 4GB. We may need to ask for a larger sandbox size for hunspell to + // spellcheck in some locales See Bug 1739669 for more details + + // We first get the size of the dictionary. This is actually the first read we + // try on dpath and it might fail for whatever filesystem reasons (invalid + // path, unaccessible, ...). + Result<int64_t, nsresult> dictSizeResult = + mozHunspellFileMgrHost::GetSize(dpath); + NS_ENSURE_TRUE(dictSizeResult.isOk(), nullptr); + + int64_t dictSize = dictSizeResult.unwrap(); + NS_ENSURE_TRUE(dictSize >= 0, nullptr); + + // Next, we compute the expected memory needed for hunspell spell checking. + // This will vary based on the size of the dictionary file, which varies by + // locale — so we size the sandbox by multiplying the file size by 4.8. This + // allows the 1.5MB en_US dictionary to fit in an 8MB sandbox. See bug 1739669 + // and bug 1739761 for the analysis behind this. + const uint64_t expectedMaxMemory = static_cast<uint64_t>(4.8 * dictSize); + + // Get a capacity of at least the expected size + const w2c_mem_capacity capacity = get_valid_wasm2c_memory_capacity( + expectedMaxMemory, true /* wasm's 32-bit memory */); + + bool success = + sandbox->create_sandbox(/* shouldAbortOnFailure = */ false, &capacity); +#elif defined(MOZ_WASM_SANDBOXING_HUNSPELL) + bool success = sandbox->create_sandbox(/* shouldAbortOnFailure = */ false); +#else + sandbox->create_sandbox(); + const bool success = true; +#endif + + NS_ENSURE_TRUE(success, nullptr); + + mozilla::UniquePtr<rlbox_sandbox_hunspell, RLBoxDeleter> sandbox_initialized( + sandbox.release()); + + // Add the aff and dict files to allow list + if (!affpath.IsEmpty()) { + mozHunspellCallbacks::AllowFile(affpath); + } + if (!dpath.IsEmpty()) { + mozHunspellCallbacks::AllowFile(dpath); + } + + // TODO Bug 1788857: Verify error handling in case of inaccessible file + return new RLBoxHunspell(std::move(sandbox_initialized), affpath, dpath); +} + +RLBoxHunspell::RLBoxHunspell( + mozilla::UniquePtr<rlbox_sandbox_hunspell, RLBoxDeleter> aSandbox, + const nsCString& affpath, const nsCString& dpath) + : mSandbox(std::move(aSandbox)), mHandle(nullptr) { + // Register callbacks + mCreateFilemgr = + mSandbox->register_callback(mozHunspellCallbacks::CreateFilemgr); + mGetLine = mSandbox->register_callback(mozHunspellCallbacks::GetLine); + mGetLineNum = mSandbox->register_callback(mozHunspellCallbacks::GetLineNum); + mDestructFilemgr = + mSandbox->register_callback(mozHunspellCallbacks::DestructFilemgr); + mHunspellToUpperCase = + mSandbox->register_callback(mozHunspellCallbacks::ToUpperCase); + mHunspellToLowerCase = + mSandbox->register_callback(mozHunspellCallbacks::ToLowerCase); + mHunspellGetCurrentCS = + mSandbox->register_callback(mozHunspellCallbacks::GetCurrentCS); + + mSandbox->invoke_sandbox_function(RegisterHunspellCallbacks, mCreateFilemgr, + mGetLine, mGetLineNum, mDestructFilemgr, + mHunspellToUpperCase, mHunspellToLowerCase, + mHunspellGetCurrentCS); + + // Copy the affpath and dpath into the sandbox + // These allocations should definitely succeed as these are first allocations + // inside the sandbox. + tainted_hunspell<char*> t_affpath = + allocStrInSandbox(*mSandbox, affpath.get()); + MOZ_RELEASE_ASSERT(t_affpath); + + tainted_hunspell<char*> t_dpath = allocStrInSandbox(*mSandbox, dpath.get()); + MOZ_RELEASE_ASSERT(t_dpath); + + // Create handle + mHandle = mSandbox->invoke_sandbox_function( + Hunspell_create, rlbox::sandbox_const_cast<const char*>(t_affpath), + rlbox::sandbox_const_cast<const char*>(t_dpath)); + MOZ_RELEASE_ASSERT(mHandle); + + mSandbox->free_in_sandbox(t_dpath); + mSandbox->free_in_sandbox(t_affpath); + + // Get dictionary encoding + tainted_hunspell<char*> t_enc = + mSandbox->invoke_sandbox_function(Hunspell_get_dic_encoding, mHandle); + t_enc.copy_and_verify_string([&](std::unique_ptr<char[]> enc) { + size_t len = std::strlen(enc.get()); + mDicEncoding = std::string(enc.get(), len); + }); +} + +RLBoxHunspell::~RLBoxHunspell() { + MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread()); + // Call hunspell's destroy which frees mHandle + mSandbox->invoke_sandbox_function(Hunspell_destroy, mHandle); + mHandle = nullptr; + + // Unregister callbacks + mDestructFilemgr.unregister(); + mGetLineNum.unregister(); + mGetLine.unregister(); + mCreateFilemgr.unregister(); + mHunspellToUpperCase.unregister(); + mHunspellToLowerCase.unregister(); + mHunspellGetCurrentCS.unregister(); + + // Clear any callback data and allow list + mozHunspellCallbacks::Clear(); +} + +// Invoking hunspell with words larger than a certain size will cause the +// Hunspell sandbox to run out of memory. So we pick an arbitrary limit of +// 200000 here to ensure this doesn't happen. +static const size_t gWordSizeLimit = 200000; + +int RLBoxHunspell::spell(const std::string& stdWord) { + MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread()); + + const int ok = 1; + + if (stdWord.length() >= gWordSizeLimit) { + // Fail gracefully assuming the word is spelt correctly + return ok; + } + + // Copy word into the sandbox + tainted_hunspell<char*> t_word = allocStrInSandbox(*mSandbox, stdWord); + if (!t_word) { + // Ran out of memory in the hunspell sandbox + // Fail gracefully assuming the word is spelt correctly + return ok; + } + + // Check word + int good = mSandbox + ->invoke_sandbox_function( + Hunspell_spell, mHandle, + rlbox::sandbox_const_cast<const char*>(t_word)) + .copy_and_verify([](int good) { return good; }); + mSandbox->free_in_sandbox(t_word); + return good; +} + +const std::string& RLBoxHunspell::get_dict_encoding() const { + return mDicEncoding; +} + +// This function fails gracefully - if we run out of memory in the hunspell +// sandbox, we return empty suggestion list +std::vector<std::string> RLBoxHunspell::suggest(const std::string& stdWord) { + MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread()); + + if (stdWord.length() >= gWordSizeLimit) { + return {}; + } + + // Copy word into the sandbox + tainted_hunspell<char*> t_word = allocStrInSandbox(*mSandbox, stdWord); + if (!t_word) { + return {}; + } + + // Allocate suggestion list in the sandbox + tainted_hunspell<char***> t_slst = mSandbox->malloc_in_sandbox<char**>(); + if (!t_slst) { + // Free the earlier allocation + mSandbox->free_in_sandbox(t_word); + return {}; + } + + *t_slst = nullptr; + + // Get suggestions + int nr = mSandbox + ->invoke_sandbox_function( + Hunspell_suggest, mHandle, t_slst, + rlbox::sandbox_const_cast<const char*>(t_word)) + .copy_and_verify([](int nr) { + MOZ_RELEASE_ASSERT(nr >= 0); + return nr; + }); + + tainted_hunspell<char**> t_slst_ref = *t_slst; + + std::vector<std::string> suggestions; + if (nr > 0 && t_slst_ref != nullptr) { + // Copy suggestions from sandbox + suggestions.reserve(nr); + + for (int i = 0; i < nr; i++) { + tainted_hunspell<char*> t_sug = t_slst_ref[i]; + + if (t_sug) { + t_sug.copy_and_verify_string( + [&](std::string sug) { suggestions.push_back(std::move(sug)); }); + // free the suggestion string allocated by the sandboxed hunspell + mSandbox->free_in_sandbox(t_sug); + } + } + + // free the suggestion list allocated by the sandboxed hunspell + mSandbox->free_in_sandbox(t_slst_ref); + } + + mSandbox->free_in_sandbox(t_word); + mSandbox->free_in_sandbox(t_slst); + return suggestions; +} diff --git a/extensions/spellcheck/hunspell/glue/RLBoxHunspell.h b/extensions/spellcheck/hunspell/glue/RLBoxHunspell.h new file mode 100644 index 0000000000..2e5a11d936 --- /dev/null +++ b/extensions/spellcheck/hunspell/glue/RLBoxHunspell.h @@ -0,0 +1,66 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef EXTENSIONS_SPELLCHECK_HUNSPELL_GLUE_RLBOXHUNSPELL_H_ +#define EXTENSIONS_SPELLCHECK_HUNSPELL_GLUE_RLBOXHUNSPELL_H_ + +#include "RLBoxHunspellTypes.h" + +// Load general firefox configuration of RLBox +#include "mozilla/rlbox/rlbox_config.h" + +#ifdef MOZ_WASM_SANDBOXING_HUNSPELL +// Include the generated header file so that we are able to resolve the symbols +// in the wasm binary +# include "rlbox.wasm.h" +# define RLBOX_USE_STATIC_CALLS() rlbox_wasm2c_sandbox_lookup_symbol +# include "mozilla/rlbox/rlbox_wasm2c_sandbox.hpp" +#else +# define RLBOX_USE_STATIC_CALLS() rlbox_noop_sandbox_lookup_symbol +# include "mozilla/rlbox/rlbox_noop_sandbox.hpp" +#endif + +#include "mozilla/rlbox/rlbox.hpp" + +#include <hunspell.h> +#include "mozHunspellRLBoxGlue.h" + +class RLBoxHunspell { + public: + static RLBoxHunspell* Create(const nsCString& affpath, + const nsCString& dpath); + + ~RLBoxHunspell(); + + int spell(const std::string& stdWord); + const std::string& get_dict_encoding() const; + + std::vector<std::string> suggest(const std::string& word); + + private: + struct RLBoxDeleter { + void operator()(rlbox_sandbox_hunspell* sandbox) { + sandbox->destroy_sandbox(); + delete sandbox; + } + }; + + RLBoxHunspell( + mozilla::UniquePtr<rlbox_sandbox_hunspell, RLBoxDeleter> aSandbox, + const nsCString& affpath, const nsCString& dpath); + + mozilla::UniquePtr<rlbox_sandbox_hunspell, RLBoxDeleter> mSandbox; + sandbox_callback_hunspell<hunspell_create_filemgr_t*> mCreateFilemgr; + sandbox_callback_hunspell<hunspell_get_line_t*> mGetLine; + sandbox_callback_hunspell<hunspell_get_line_num_t*> mGetLineNum; + sandbox_callback_hunspell<hunspell_destruct_filemgr_t*> mDestructFilemgr; + sandbox_callback_hunspell<hunspell_ToUpperCase_t*> mHunspellToUpperCase; + sandbox_callback_hunspell<hunspell_ToLowerCase_t*> mHunspellToLowerCase; + sandbox_callback_hunspell<hunspell_get_current_cs_t*> mHunspellGetCurrentCS; + tainted_hunspell<Hunhandle*> mHandle; + std::string mDicEncoding; +}; + +#endif diff --git a/extensions/spellcheck/hunspell/glue/RLBoxHunspellTypes.h b/extensions/spellcheck/hunspell/glue/RLBoxHunspellTypes.h new file mode 100644 index 0000000000..d05703ad0f --- /dev/null +++ b/extensions/spellcheck/hunspell/glue/RLBoxHunspellTypes.h @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef EXTENSIONS_SPELLCHECK_HUNSPELL_GLUE_RLBOXHUNSPELLTYPES_H_ +#define EXTENSIONS_SPELLCHECK_HUNSPELL_GLUE_RLBOXHUNSPELLTYPES_H_ + +#include <stddef.h> +#include "mozilla/rlbox/rlbox_types.hpp" +#include "hunspell_csutil.hxx" + +#ifdef MOZ_WASM_SANDBOXING_HUNSPELL +RLBOX_DEFINE_BASE_TYPES_FOR(hunspell, wasm2c) +#else +RLBOX_DEFINE_BASE_TYPES_FOR(hunspell, noop) +#endif + +#define sandbox_fields_reflection_hunspell_class_cs_info(f, g, ...) \ + f(unsigned char, ccase, FIELD_NORMAL, ##__VA_ARGS__) g() \ + f(unsigned char, clower, FIELD_NORMAL, ##__VA_ARGS__) g() \ + f(unsigned char, cupper, FIELD_NORMAL, ##__VA_ARGS__) g() + +#define sandbox_fields_reflection_hunspell_allClasses(f, ...) \ + f(cs_info, hunspell, ##__VA_ARGS__) + +#endif diff --git a/extensions/spellcheck/hunspell/glue/RemoteSpellCheckEngineChild.cpp b/extensions/spellcheck/hunspell/glue/RemoteSpellCheckEngineChild.cpp new file mode 100644 index 0000000000..8971694db3 --- /dev/null +++ b/extensions/spellcheck/hunspell/glue/RemoteSpellCheckEngineChild.cpp @@ -0,0 +1,84 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/UniquePtr.h" +#include "RemoteSpellCheckEngineChild.h" + +namespace mozilla { + +RemoteSpellcheckEngineChild::RemoteSpellcheckEngineChild( + mozSpellChecker* aOwner) + : mOwner(aOwner) {} + +RemoteSpellcheckEngineChild::~RemoteSpellcheckEngineChild() { + // null out the owner's SpellcheckEngineChild to prevent state corruption + // during shutdown + mOwner->DeleteRemoteEngine(); +} + +RefPtr<GenericPromise> RemoteSpellcheckEngineChild::SetCurrentDictionaries( + const nsTArray<nsCString>& aDictionaries) { + RefPtr<mozSpellChecker> spellChecker = mOwner; + + return SendSetDictionaries(aDictionaries) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [spellChecker, dictionaries = aDictionaries.Clone()](bool&& aParam) { + if (aParam) { + spellChecker->mCurrentDictionaries = dictionaries.Clone(); + return GenericPromise::CreateAndResolve(true, __func__); + } + spellChecker->mCurrentDictionaries.Clear(); + return GenericPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, + __func__); + }, + [spellChecker](ResponseRejectReason&& aReason) { + spellChecker->mCurrentDictionaries.Clear(); + return GenericPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, + __func__); + }); +} + +RefPtr<GenericPromise> +RemoteSpellcheckEngineChild::SetCurrentDictionaryFromList( + const nsTArray<nsCString>& aList) { + RefPtr<mozSpellChecker> spellChecker = mOwner; + + return SendSetDictionaryFromList(aList)->Then( + GetMainThreadSerialEventTarget(), __func__, + [spellChecker](std::tuple<bool, nsCString>&& aParam) { + if (!std::get<0>(aParam)) { + spellChecker->mCurrentDictionaries.Clear(); + return GenericPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, + __func__); + } + spellChecker->mCurrentDictionaries.Clear(); + spellChecker->mCurrentDictionaries.AppendElement( + std::move(std::get<1>(aParam))); + return GenericPromise::CreateAndResolve(true, __func__); + }, + [spellChecker](ResponseRejectReason&& aReason) { + spellChecker->mCurrentDictionaries.Clear(); + return GenericPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, + __func__); + }); +} + +RefPtr<CheckWordPromise> RemoteSpellcheckEngineChild::CheckWords( + const nsTArray<nsString>& aWords) { + RefPtr<mozSpellChecker> kungFuDeathGrip = mOwner; + + return SendCheckAsync(aWords)->Then( + GetMainThreadSerialEventTarget(), __func__, + [kungFuDeathGrip](nsTArray<bool>&& aIsMisspelled) { + return CheckWordPromise::CreateAndResolve(std::move(aIsMisspelled), + __func__); + }, + [kungFuDeathGrip](mozilla::ipc::ResponseRejectReason&& aReason) { + return CheckWordPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, + __func__); + }); +} + +} // namespace mozilla diff --git a/extensions/spellcheck/hunspell/glue/RemoteSpellCheckEngineChild.h b/extensions/spellcheck/hunspell/glue/RemoteSpellCheckEngineChild.h new file mode 100644 index 0000000000..06721d91ea --- /dev/null +++ b/extensions/spellcheck/hunspell/glue/RemoteSpellCheckEngineChild.h @@ -0,0 +1,36 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef RemoteSpellcheckEngineChild_h_ +#define RemoteSpellcheckEngineChild_h_ + +#include "mozilla/MozPromise.h" +#include "mozilla/PRemoteSpellcheckEngineChild.h" +#include "mozSpellChecker.h" + +class mozSpellChecker; + +namespace mozilla { + +class RemoteSpellcheckEngineChild + : public mozilla::PRemoteSpellcheckEngineChild { + public: + explicit RemoteSpellcheckEngineChild(mozSpellChecker* aOwner); + virtual ~RemoteSpellcheckEngineChild(); + + RefPtr<GenericPromise> SetCurrentDictionaries( + const nsTArray<nsCString>& aDictionaries); + + RefPtr<GenericPromise> SetCurrentDictionaryFromList( + const nsTArray<nsCString>& aList); + + RefPtr<CheckWordPromise> CheckWords(const nsTArray<nsString>& aWords); + + private: + mozSpellChecker* mOwner; +}; + +} // namespace mozilla + +#endif // RemoteSpellcheckEngineChild_h_ diff --git a/extensions/spellcheck/hunspell/glue/RemoteSpellCheckEngineParent.cpp b/extensions/spellcheck/hunspell/glue/RemoteSpellCheckEngineParent.cpp new file mode 100644 index 0000000000..e9f341d524 --- /dev/null +++ b/extensions/spellcheck/hunspell/glue/RemoteSpellCheckEngineParent.cpp @@ -0,0 +1,85 @@ +/* vim: set ts=2 sw=2 sts=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "RemoteSpellCheckEngineParent.h" +#include "mozilla/Unused.h" +#include "mozilla/mozSpellChecker.h" +#include "nsServiceManagerUtils.h" + +namespace mozilla { + +RemoteSpellcheckEngineParent::RemoteSpellcheckEngineParent() { + mSpellChecker = mozSpellChecker::Create(); +} + +RemoteSpellcheckEngineParent::~RemoteSpellcheckEngineParent() {} + +mozilla::ipc::IPCResult RemoteSpellcheckEngineParent::RecvSetDictionary( + const nsACString& aDictionary, bool* success) { + nsresult rv = mSpellChecker->SetCurrentDictionary(aDictionary); + *success = NS_SUCCEEDED(rv); + return IPC_OK(); +} + +mozilla::ipc::IPCResult RemoteSpellcheckEngineParent::RecvSetDictionaries( + const nsTArray<nsCString>& aDictionaries, + SetDictionariesResolver&& aResolve) { + mSpellChecker->SetCurrentDictionaries(aDictionaries) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [aResolve]() { aResolve(true); }, [aResolve]() { aResolve(false); }); + return IPC_OK(); +} + +mozilla::ipc::IPCResult RemoteSpellcheckEngineParent::RecvSetDictionaryFromList( + nsTArray<nsCString>&& aList, SetDictionaryFromListResolver&& aResolve) { + for (auto& dictionary : aList) { + nsresult rv = mSpellChecker->SetCurrentDictionary(dictionary); + if (NS_SUCCEEDED(rv)) { + aResolve(std::tuple<const bool&, const nsACString&>(true, dictionary)); + return IPC_OK(); + } + } + aResolve(std::tuple<const bool&, const nsACString&>(false, ""_ns)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult RemoteSpellcheckEngineParent::RecvCheckAsync( + nsTArray<nsString>&& aWords, CheckAsyncResolver&& aResolve) { + nsTArray<bool> misspells; + misspells.SetCapacity(aWords.Length()); + for (auto& word : aWords) { + bool misspelled; + nsresult rv = mSpellChecker->CheckWord(word, &misspelled, nullptr); + // If CheckWord failed, we can't tell whether the word is correctly spelled + if (NS_FAILED(rv)) { + misspelled = false; + } + misspells.AppendElement(misspelled); + } + aResolve(std::move(misspells)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult RemoteSpellcheckEngineParent::RecvSuggest( + const nsAString& aWord, uint32_t aCount, SuggestResolver&& aResolve) { + nsTArray<nsString> suggestions; + mSpellChecker->Suggest(aWord, aCount) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [aResolve](CopyableTArray<nsString> aSuggestions) { + aResolve(std::move(aSuggestions)); + }, + [aResolve](nsresult aError) { + // No suggestions due to error + nsTArray<nsString> suggestions; + aResolve(std::move(suggestions)); + }); + return IPC_OK(); +} + +void RemoteSpellcheckEngineParent::ActorDestroy(ActorDestroyReason aWhy) {} + +} // namespace mozilla diff --git a/extensions/spellcheck/hunspell/glue/RemoteSpellCheckEngineParent.h b/extensions/spellcheck/hunspell/glue/RemoteSpellCheckEngineParent.h new file mode 100644 index 0000000000..06f4a7e8f5 --- /dev/null +++ b/extensions/spellcheck/hunspell/glue/RemoteSpellCheckEngineParent.h @@ -0,0 +1,46 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef RemoteSpellcheckEngineParent_h_ +#define RemoteSpellcheckEngineParent_h_ + +#include "mozilla/PRemoteSpellcheckEngineParent.h" +#include "nsCOMPtr.h" +#include "nsTArray.h" + +class mozSpellChecker; + +namespace mozilla { + +class RemoteSpellcheckEngineParent : public PRemoteSpellcheckEngineParent { + public: + RemoteSpellcheckEngineParent(); + + virtual ~RemoteSpellcheckEngineParent(); + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + + virtual mozilla::ipc::IPCResult RecvSetDictionary( + const nsACString& aDictionary, bool* success); + + virtual mozilla::ipc::IPCResult RecvSetDictionaries( + const nsTArray<nsCString>& aDictionaries, + SetDictionariesResolver&& aResolve); + + virtual mozilla::ipc::IPCResult RecvSetDictionaryFromList( + nsTArray<nsCString>&& aList, SetDictionaryFromListResolver&& aResolve); + + virtual mozilla::ipc::IPCResult RecvCheckAsync(nsTArray<nsString>&& aWord, + CheckAsyncResolver&& aResolve); + + virtual mozilla::ipc::IPCResult RecvSuggest(const nsAString& aWord, + uint32_t aCount, + SuggestResolver&& aResolve); + + private: + RefPtr<mozSpellChecker> mSpellChecker; +}; + +} // namespace mozilla + +#endif diff --git a/extensions/spellcheck/hunspell/glue/common.mozbuild b/extensions/spellcheck/hunspell/glue/common.mozbuild new file mode 100644 index 0000000000..ded414075e --- /dev/null +++ b/extensions/spellcheck/hunspell/glue/common.mozbuild @@ -0,0 +1,9 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +@template +def HunspellIncludes(): + ForceInclude('hunspell_alloc_hooks.h', 'hunspell_fopen_hooks.h') diff --git a/extensions/spellcheck/hunspell/glue/hunspell_alloc_hooks.h b/extensions/spellcheck/hunspell/glue/hunspell_alloc_hooks.h new file mode 100644 index 0000000000..27a719d788 --- /dev/null +++ b/extensions/spellcheck/hunspell/glue/hunspell_alloc_hooks.h @@ -0,0 +1,59 @@ +/******* BEGIN LICENSE BLOCK ******* + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Initial Developers of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developers + * are Copyright (C) 2011 the Initial Developers. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + ******* END LICENSE BLOCK *******/ + +#ifndef alloc_hooks_h__ +#define alloc_hooks_h__ + +/** + * This file is force-included in hunspell code. Its purpose is to add memory + * reporting to hunspell without modifying its code, in order to ease future + * upgrades. + * + * Currently, the memory allocated using operator new/new[] is not being + * tracked, but that's OK, since almost all of the memory used by Hunspell is + * allocated using C memory allocation functions. + */ + +#include "mozHunspellAllocator.h" + +// Ensure that malloc is imported before we set our malloc-counting hooks below. +// Otherwise, if malloc is imported afterwards, its source will be trampled +// over by the "#define"s. +#include "mozmemory.h" + +#define malloc(size) HunspellAllocator::CountingMalloc(size) +#define calloc(count, size) HunspellAllocator::CountingCalloc(count, size) +#define free(ptr) HunspellAllocator::CountingFree(ptr) +#define realloc(ptr, size) HunspellAllocator::CountingRealloc(ptr, size) + +#endif diff --git a/extensions/spellcheck/hunspell/glue/hunspell_csutil.cxx b/extensions/spellcheck/hunspell/glue/hunspell_csutil.cxx new file mode 100644 index 0000000000..0f5565a12c --- /dev/null +++ b/extensions/spellcheck/hunspell/glue/hunspell_csutil.cxx @@ -0,0 +1,160 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * Copyright (C) 2002-2017 Németh László + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * Hunspell is based on MySpell which is Copyright (C) 2002 Kevin Hendricks. + * + * Contributor(s): David Einstein, Davide Prina, Giuseppe Modugno, + * Gianluca Turconi, Simon Brouwer, Noll János, Bíró Árpád, + * Goldman Eleonóra, Sarlós Tamás, Bencsáth Boldizsár, Halácsy Péter, + * Dvornik László, Gefferth András, Nagy Viktor, Varga Dániel, Chris Halls, + * Rene Engelhard, Bram Moolenaar, Dafydd Jones, Harri Pitkänen + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ +/* + * Copyright 2002 Kevin B. Hendricks, Stratford, Ontario, Canada + * And Contributors. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. All modifications to the source code must be clearly marked as + * such. Binary redistributions based on modified source code + * must be clearly marked as modified versions in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY KEVIN B. HENDRICKS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * KEVIN B. HENDRICKS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#include "hunspell_csutil.hxx" +#include "mozilla/Encoding.h" +#include "mozilla/Span.h" +#include "nsUnicharUtils.h" + +/* This is a copy of get_current_cs from the hunspell csutil.cxx file. + */ +struct cs_info* hunspell_get_current_cs(const std::string& es) { + struct cs_info* ccs = new cs_info[256]; + // Initialze the array with dummy data so that we wouldn't need + // to return null in case of failures. + for (int i = 0; i <= 0xff; ++i) { + ccs[i].ccase = false; + ccs[i].clower = i; + ccs[i].cupper = i; + } + + auto encoding = mozilla::Encoding::ForLabelNoReplacement(es); + if (!encoding) { + return ccs; + } + auto encoder = encoding->NewEncoder(); + auto decoder = encoding->NewDecoderWithoutBOMHandling(); + + for (unsigned int i = 0; i <= 0xff; ++i) { + bool success = false; + // We want to find the upper/lowercase equivalents of each byte + // in this 1-byte character encoding. Call our encoding/decoding + // APIs separately for each byte since they may reject some of the + // bytes, and we want to handle errors separately for each byte. + uint8_t lower, upper; + do { + if (i == 0) break; + uint8_t source = uint8_t(i); + char16_t uni[2]; + char16_t uniCased; + uint8_t destination[4]; + auto src1 = mozilla::Span(&source, 1); + auto dst1 = mozilla::Span(uni); + auto src2 = mozilla::Span(&uniCased, 1); + auto dst2 = mozilla::Span(destination); + + uint32_t result; + size_t read; + size_t written; + std::tie(result, read, written) = + decoder->DecodeToUTF16WithoutReplacement(src1, dst1, true); + if (result != mozilla::kInputEmpty || read != 1 || written != 1) { + break; + } + + uniCased = ToLowerCase(uni[0]); + std::tie(result, read, written) = + encoder->EncodeFromUTF16WithoutReplacement(src2, dst2, true); + if (result != mozilla::kInputEmpty || read != 1 || written != 1) { + break; + } + lower = destination[0]; + + uniCased = ToUpperCase(uni[0]); + std::tie(result, read, written) = + encoder->EncodeFromUTF16WithoutReplacement(src2, dst2, true); + if (result != mozilla::kInputEmpty || read != 1 || written != 1) { + break; + } + upper = destination[0]; + + success = true; + } while (0); + + encoding->NewEncoderInto(*encoder); + encoding->NewDecoderWithoutBOMHandlingInto(*decoder); + + if (success) { + ccs[i].cupper = upper; + ccs[i].clower = lower; + } else { + ccs[i].cupper = i; + ccs[i].clower = i; + } + + if (ccs[i].clower != (unsigned char)i) + ccs[i].ccase = true; + else + ccs[i].ccase = false; + } + + return ccs; +} diff --git a/extensions/spellcheck/hunspell/glue/hunspell_csutil.hxx b/extensions/spellcheck/hunspell/glue/hunspell_csutil.hxx new file mode 100644 index 0000000000..00dd89a964 --- /dev/null +++ b/extensions/spellcheck/hunspell/glue/hunspell_csutil.hxx @@ -0,0 +1,87 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * Copyright (C) 2002-2017 Németh László + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * Hunspell is based on MySpell which is Copyright (C) 2002 Kevin Hendricks. + * + * Contributor(s): David Einstein, Davide Prina, Giuseppe Modugno, + * Gianluca Turconi, Simon Brouwer, Noll János, Bíró Árpád, + * Goldman Eleonóra, Sarlós Tamás, Bencsáth Boldizsár, Halácsy Péter, + * Dvornik László, Gefferth András, Nagy Viktor, Varga Dániel, Chris Halls, + * Rene Engelhard, Bram Moolenaar, Dafydd Jones, Harri Pitkänen + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ +/* + * Copyright 2002 Kevin B. Hendricks, Stratford, Ontario, Canada + * And Contributors. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. All modifications to the source code must be clearly marked as + * such. Binary redistributions based on modified source code + * must be clearly marked as modified versions in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY KEVIN B. HENDRICKS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * KEVIN B. HENDRICKS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#ifndef EXTENSIONS_SPELLCHECK_HUNSPELL_GLUE_HUNSPELL_CSUTIL_H_ +#define EXTENSIONS_SPELLCHECK_HUNSPELL_GLUE_HUNSPELL_CSUTIL_H_ + +/* We need get_current_cs from hunspell's csutil to live outside the RLBox + * sandbox (since it relies on a Gecko encoding bits) and then expose it to the + * sandboxed hunspell. + */ + +struct cs_info { + unsigned char ccase; + unsigned char clower; + unsigned char cupper; +}; + +struct cs_info* hunspell_get_current_cs(const std::string& es); + +#endif diff --git a/extensions/spellcheck/hunspell/glue/hunspell_fopen_hooks.h b/extensions/spellcheck/hunspell/glue/hunspell_fopen_hooks.h new file mode 100644 index 0000000000..7388b1daa7 --- /dev/null +++ b/extensions/spellcheck/hunspell/glue/hunspell_fopen_hooks.h @@ -0,0 +1,81 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef fopen_hooks_h__ +#define fopen_hooks_h__ + +/** + * This file is force-included in hunspell code. Its purpose is to add + * readahead to fopen() calls in hunspell without modifying its code, in order + * to ease future upgrades. + */ + +#include "mozilla/FileUtils.h" +#include <stdio.h> +#include <string.h> + +#if defined(XP_WIN) +# include "nsNativeCharsetUtils.h" +# include "nsString.h" + +# include <fcntl.h> +# include <windows.h> +// Hunspell defines a function named near. Windef.h #defines near. +# undef near +// mozHunspell defines a function named RemoveDirectory. +# undef RemoveDirectory +#endif /* defined(XP_WIN) */ + +inline FILE* hunspell_fopen_readahead(const char* filename, const char* mode) { + if (!filename || !mode) { + return nullptr; + } + // Fall back to libc's fopen for modes not supported by ReadAheadFile + if (!strchr(mode, 'r') || strchr(mode, '+')) { + return fopen(filename, mode); + } + int fd = -1; +#if defined(XP_WIN) + // filename is obtained via the nsIFile::nativePath attribute, so + // it is using the Windows ANSI code page, NOT UTF-8! + nsAutoString utf16Filename; + nsresult rv = + NS_CopyNativeToUnicode(nsDependentCString(filename), utf16Filename); + if (NS_FAILED(rv)) { + return nullptr; + } + HANDLE handle = INVALID_HANDLE_VALUE; + mozilla::ReadAheadFile(utf16Filename.get(), 0, SIZE_MAX, &handle); + if (handle == INVALID_HANDLE_VALUE) { + return nullptr; + } + int flags = _O_RDONLY; + // MSVC CRT's _open_osfhandle only supports adding _O_TEXT, not _O_BINARY + if (strchr(mode, 't')) { + // Force translated mode + flags |= _O_TEXT; + } + // Import the Win32 fd into the CRT + fd = _open_osfhandle((intptr_t)handle, flags); + if (fd < 0) { + CloseHandle(handle); + return nullptr; + } +#else + mozilla::ReadAheadFile(filename, 0, SIZE_MAX, &fd); + if (fd < 0) { + return nullptr; + } +#endif /* defined(XP_WIN) */ + + FILE* file = fdopen(fd, mode); + if (!file) { + close(fd); + } + return file; +} + +#define fopen(filename, mode) hunspell_fopen_readahead(filename, mode) + +#endif /* fopen_hooks_h__ */ diff --git a/extensions/spellcheck/hunspell/glue/moz.build b/extensions/spellcheck/hunspell/glue/moz.build new file mode 100644 index 0000000000..7cde4156c7 --- /dev/null +++ b/extensions/spellcheck/hunspell/glue/moz.build @@ -0,0 +1,39 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +UNIFIED_SOURCES += [ + "hunspell_csutil.cxx", + "mozHunspell.cpp", + "mozHunspellRLBoxHost.cpp", + "RemoteSpellCheckEngineChild.cpp", + "RemoteSpellCheckEngineParent.cpp", + "RLBoxHunspell.cpp", +] + +DEFINES["HUNSPELL_STATIC"] = True + +FINAL_LIBRARY = "xul" + +LOCAL_INCLUDES += [ + "!/security/rlbox", + "../src", + "/dom/base", + "/extensions/spellcheck/src", +] + +include("/ipc/chromium/chromium-config.mozbuild") +include("common.mozbuild") + +HunspellIncludes() + +IPDL_SOURCES = [ + "PRemoteSpellcheckEngine.ipdl", +] + +EXPORTS.mozilla += [ + "RemoteSpellCheckEngineChild.h", + "RemoteSpellCheckEngineParent.h", +] diff --git a/extensions/spellcheck/hunspell/glue/mozHunspell.cpp b/extensions/spellcheck/hunspell/glue/mozHunspell.cpp new file mode 100644 index 0000000000..a69dcd6b05 --- /dev/null +++ b/extensions/spellcheck/hunspell/glue/mozHunspell.cpp @@ -0,0 +1,594 @@ +/******* BEGIN LICENSE BLOCK ******* + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Initial Developers of the Original Code are Kevin Hendricks (MySpell) + * and László Németh (Hunspell). Portions created by the Initial Developers + * are Copyright (C) 2002-2005 the Initial Developers. All Rights Reserved. + * + * Contributor(s): Kevin Hendricks (kevin.hendricks@sympatico.ca) + * David Einstein (deinst@world.std.com) + * Michiel van Leeuwen (mvl@exedo.nl) + * Caolan McNamara (cmc@openoffice.org) + * László Németh (nemethl@gyorsposta.hu) + * Davide Prina + * Giuseppe Modugno + * Gianluca Turconi + * Simon Brouwer + * Noll Janos + * Biro Arpad + * Goldman Eleonora + * Sarlos Tamas + * Bencsath Boldizsar + * Halacsy Peter + * Dvornik Laszlo + * Gefferth Andras + * Nagy Viktor + * Varga Daniel + * Chris Halls + * Rene Engelhard + * Bram Moolenaar + * Dafydd Jones + * Harri Pitkanen + * Andras Timar + * Tor Lillqvist + * Jesper Kristensen (mail@jesperkristensen.dk) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + ******* END LICENSE BLOCK *******/ + +#include "mozHunspell.h" +#include "nsReadableUtils.h" +#include "nsString.h" +#include "nsIObserverService.h" +#include "nsIDirectoryEnumerator.h" +#include "nsIFile.h" +#include "nsUnicharUtils.h" +#include "nsCRT.h" +#include "mozInlineSpellChecker.h" +#include "nsIPrefBranch.h" +#include "nsIPrefService.h" +#include "nsNetUtil.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/Components.h" +#include "mozilla/Services.h" + +#include <stdlib.h> +#include <tuple> + +using mozilla::dom::ContentParent; +using namespace mozilla; + +NS_IMPL_CYCLE_COLLECTING_ADDREF(mozHunspell) +NS_IMPL_CYCLE_COLLECTING_RELEASE(mozHunspell) + +NS_INTERFACE_MAP_BEGIN(mozHunspell) + NS_INTERFACE_MAP_ENTRY(mozISpellCheckingEngine) + NS_INTERFACE_MAP_ENTRY(nsIObserver) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) + NS_INTERFACE_MAP_ENTRY(nsIMemoryReporter) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozISpellCheckingEngine) + NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(mozHunspell) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTION_WEAK(mozHunspell, mPersonalDictionary) + +NS_IMPL_COMPONENT_FACTORY(mozHunspell) { + auto hunspell = MakeRefPtr<mozHunspell>(); + if (NS_SUCCEEDED(hunspell->Init())) { + return hunspell.forget().downcast<mozISpellCheckingEngine>(); + } + return nullptr; +} + +mozHunspell::mozHunspell() { +#ifdef DEBUG + // There must be only one instance of this class: it reports memory based on + // a single static count in HunspellAllocator. + static bool hasRun = false; + MOZ_ASSERT(!hasRun); + hasRun = true; +#endif +} + +nsresult mozHunspell::Init() { + LoadDictionaryList(false); + + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->AddObserver(this, "profile-do-change", true); + obs->AddObserver(this, "profile-after-change", true); + } + + mozilla::RegisterWeakMemoryReporter(this); + + return NS_OK; +} + +mozHunspell::~mozHunspell() { + mozilla::UnregisterWeakMemoryReporter(this); + + mPersonalDictionary = nullptr; + mHunspells.Clear(); +} + +NS_IMETHODIMP +mozHunspell::GetDictionaries(nsTArray<nsCString>& aDictionaries) { + MOZ_ASSERT(aDictionaries.IsEmpty()); + for (auto iter = mHunspells.ConstIter(); !iter.Done(); iter.Next()) { + if (iter.Data().mEnabled) { + aDictionaries.AppendElement(iter.Key()); + } + } + return NS_OK; +} + +/* Set the Dictionaries. + * This also Loads the dictionaries and initializes the converter using the + * dictionaries converter + */ +NS_IMETHODIMP +mozHunspell::SetDictionaries(const nsTArray<nsCString>& aDictionaries) { + if (aDictionaries.IsEmpty()) { + mHunspells.Clear(); + return NS_OK; + } + + // Disable any dictionaries we've already loaded that we're not + // going to use. + for (auto iter = mHunspells.Iter(); !iter.Done(); iter.Next()) { + if (!aDictionaries.Contains(iter.Key())) { + iter.Data().mEnabled = false; + } + } + + bool firstDictionary = true; + for (const auto& dictionary : aDictionaries) { + NS_ConvertUTF8toUTF16 dict(dictionary); + nsIURI* affFile = mDictionaries.GetWeak(dict); + if (!affFile) { + return NS_ERROR_FILE_NOT_FOUND; + } + + nsAutoCString affFileName; + nsresult rv = affFile->GetSpec(affFileName); + NS_ENSURE_SUCCESS(rv, rv); + + if (auto entry = mHunspells.Lookup(dictionary)) { + if (entry.Data().mAffixFileName == affFileName) { + entry.Data().mEnabled = true; + continue; + } + } + + DictionaryData dictionaryData; + dictionaryData.mAffixFileName = affFileName; + + // Load the first dictionary now, we'll load the others lazily during + // checking. + if (firstDictionary) { + rv = dictionaryData.LoadIfNecessary(); + NS_ENSURE_SUCCESS(rv, rv); + firstDictionary = false; + } + + mHunspells.InsertOrUpdate(dictionary, std::move(dictionaryData)); + } + + // If we have a large number of dictionaries loaded, try freeing any disabled + // dictionaries to limit memory use. + if (mHunspells.Count() > 10) { + mHunspells.RemoveIf([](const auto& iter) { return !iter.Data().mEnabled; }); + } + + return NS_OK; +} + +NS_IMETHODIMP mozHunspell::GetPersonalDictionary( + mozIPersonalDictionary** aPersonalDictionary) { + *aPersonalDictionary = mPersonalDictionary; + NS_IF_ADDREF(*aPersonalDictionary); + return NS_OK; +} + +NS_IMETHODIMP mozHunspell::SetPersonalDictionary( + mozIPersonalDictionary* aPersonalDictionary) { + mPersonalDictionary = aPersonalDictionary; + return NS_OK; +} + +NS_IMETHODIMP mozHunspell::GetDictionaryList( + nsTArray<nsCString>& aDictionaries) { + MOZ_ASSERT(aDictionaries.IsEmpty()); + for (const auto& key : mDictionaries.Keys()) { + aDictionaries.AppendElement(NS_ConvertUTF16toUTF8(key)); + } + + return NS_OK; +} + +void mozHunspell::LoadDictionaryList(bool aNotifyChildProcesses) { + mDictionaries.Clear(); + + nsresult rv; + + // find built in dictionaries, or dictionaries specified in + // spellchecker.dictionary_path in prefs + nsCOMPtr<nsIFile> dictDir; + + // check preferences first + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (prefs) { + nsAutoCString extDictPath; + rv = prefs->GetCharPref("spellchecker.dictionary_path", extDictPath); + if (NS_SUCCEEDED(rv)) { + // set the spellchecker.dictionary_path + rv = NS_NewNativeLocalFile(extDictPath, true, getter_AddRefs(dictDir)); + } + if (dictDir) { + LoadDictionariesFromDir(dictDir); + } + } + + // find dictionaries in DICPATH + char* dicEnv = PR_GetEnv("DICPATH"); + if (dicEnv) { + // do a two-pass dance so dictionaries are loaded right-to-left as + // preference + nsTArray<nsCOMPtr<nsIFile>> dirs; + nsAutoCString env(dicEnv); // assume dicEnv is UTF-8 + + char* currPath = nullptr; + char* nextPaths = env.BeginWriting(); + while ((currPath = NS_strtok(":", &nextPaths))) { + nsCOMPtr<nsIFile> dir; + rv = + NS_NewNativeLocalFile(nsCString(currPath), true, getter_AddRefs(dir)); + if (NS_SUCCEEDED(rv)) { + dirs.AppendElement(dir); + } + } + + // load them in reverse order so they override each other properly + for (int32_t i = dirs.Length() - 1; i >= 0; i--) { + LoadDictionariesFromDir(dirs[i]); + } + } + + // find dictionaries from restartless extensions + for (int32_t i = 0; i < mDynamicDirectories.Count(); i++) { + LoadDictionariesFromDir(mDynamicDirectories[i]); + } + + for (const auto& dictionaryEntry : mDynamicDictionaries) { + mDictionaries.InsertOrUpdate(dictionaryEntry.GetKey(), + dictionaryEntry.GetData()); + } + + DictionariesChanged(aNotifyChildProcesses); +} + +void mozHunspell::DictionariesChanged(bool aNotifyChildProcesses) { + // Now we have finished updating the list of dictionaries, update the current + // dictionary and any editors which may use it. + mozInlineSpellChecker::UpdateCanEnableInlineSpellChecking(); + + if (aNotifyChildProcesses) { + ContentParent::NotifyUpdatedDictionaries(); + } + + // Check if the current dictionaries are still available. + // If not, try to replace it with other dictionaries of the same language. + if (!mHunspells.IsEmpty()) { + nsTArray<nsCString> dictionaries; + for (auto iter = mHunspells.ConstIter(); !iter.Done(); iter.Next()) { + if (iter.Data().mEnabled) { + dictionaries.AppendElement(iter.Key()); + } + } + nsresult rv = SetDictionaries(dictionaries); + if (NS_SUCCEEDED(rv)) return; + } + + // If the current dictionaries are gone, and we don't have a good replacement, + // set no current dictionary. + if (!mHunspells.IsEmpty()) { + nsTArray<nsCString> empty; + SetDictionaries(empty); + } +} + +NS_IMETHODIMP +mozHunspell::LoadDictionariesFromDir(nsIFile* aDir) { + nsresult rv; + + bool check = false; + rv = aDir->Exists(&check); + if (NS_FAILED(rv) || !check) return NS_ERROR_UNEXPECTED; + + rv = aDir->IsDirectory(&check); + if (NS_FAILED(rv) || !check) return NS_ERROR_UNEXPECTED; + + nsCOMPtr<nsIDirectoryEnumerator> files; + rv = aDir->GetDirectoryEntries(getter_AddRefs(files)); + if (NS_FAILED(rv)) return NS_ERROR_UNEXPECTED; + + nsCOMPtr<nsIFile> file; + while (NS_SUCCEEDED(files->GetNextFile(getter_AddRefs(file))) && file) { + nsAutoString leafName; + file->GetLeafName(leafName); + if (!StringEndsWith(leafName, u".dic"_ns)) continue; + + nsAutoString dict(leafName); + dict.SetLength(dict.Length() - 4); // magic length of ".dic" + + // check for the presence of the .aff file + leafName = dict; + leafName.AppendLiteral(".aff"); + file->SetLeafName(leafName); + rv = file->Exists(&check); + if (NS_FAILED(rv) || !check) continue; + + // Replace '_' separator with '-' + dict.ReplaceChar('_', '-'); + + nsCOMPtr<nsIURI> uri; + rv = NS_NewFileURI(getter_AddRefs(uri), file); + NS_ENSURE_SUCCESS(rv, rv); + + mDictionaries.InsertOrUpdate(dict, uri); + } + + return NS_OK; +} + +nsresult mozHunspell::DictionaryData::ConvertCharset(const nsAString& aStr, + std::string& aDst) { + if (NS_WARN_IF(!mEncoder)) { + return NS_ERROR_NOT_INITIALIZED; + } + + auto src = Span(aStr.BeginReading(), aStr.Length()); + CheckedInt<size_t> needed = + mEncoder->MaxBufferLengthFromUTF16WithoutReplacement(src.Length()); + if (!needed.isValid()) { + return NS_ERROR_OUT_OF_MEMORY; + } + + aDst.resize(needed.value()); + + char* dstPtr = &aDst[0]; + auto dst = Span(reinterpret_cast<uint8_t*>(dstPtr), needed.value()); + + uint32_t result; + size_t written; + std::tie(result, std::ignore, written) = + mEncoder->EncodeFromUTF16WithoutReplacement(src, dst, true); + MOZ_ASSERT(result != kOutputFull); + if (result != kInputEmpty) { + return NS_ERROR_UENC_NOMAPPING; + } + aDst.resize(written); + mEncoder->Encoding()->NewEncoderInto(*mEncoder); + return NS_OK; +} + +nsresult mozHunspell::DictionaryData::LoadIfNecessary() { + if (mHunspell && mEncoder && mDecoder) { + return NS_OK; + } + + if (mLoadFailed) { + return NS_ERROR_FAILURE; + } + + nsCString dictFileName = mAffixFileName; + int32_t dotPos = dictFileName.RFindChar('.'); + if (dotPos == -1) { + mLoadFailed = true; + return NS_ERROR_FAILURE; + } + dictFileName.SetLength(dotPos); + dictFileName.AppendLiteral(".dic"); + + UniquePtr<RLBoxHunspell> hunspell( + RLBoxHunspell::Create(mAffixFileName, dictFileName)); + if (!hunspell) { + mLoadFailed = true; + // TODO Bug 1788857: Verify error propagation in case of inaccessible file + return NS_ERROR_OUT_OF_MEMORY; + } + mHunspell = std::move(hunspell); + auto encoding = + Encoding::ForLabelNoReplacement(mHunspell->get_dict_encoding()); + if (!encoding) { + mLoadFailed = true; + return NS_ERROR_UCONV_NOCONV; + } + mEncoder = encoding->NewEncoder(); + mDecoder = encoding->NewDecoderWithoutBOMHandling(); + return NS_OK; +} + +NS_IMETHODIMP +mozHunspell::CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) { + MOZ_COLLECT_REPORT("explicit/spell-check", KIND_HEAP, UNITS_BYTES, + HunspellAllocator::MemoryAllocated(), + "Memory used by the spell-checking engine."); + + return NS_OK; +} + +NS_IMETHODIMP +mozHunspell::Check(const nsAString& aWord, bool* aResult) { + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + if (NS_WARN_IF(mHunspells.IsEmpty())) { + return NS_ERROR_FAILURE; + } + + *aResult = true; + for (auto iter = mHunspells.Iter(); !iter.Done(); iter.Next()) { + if (!iter.Data().mEnabled) { + continue; + } + + nsresult rv = iter.Data().LoadIfNecessary(); + if (NS_FAILED(rv)) { + continue; + } + + std::string charsetWord; + rv = iter.Data().ConvertCharset(aWord, charsetWord); + if (NS_FAILED(rv)) { + continue; + } + + // Depending upon the encoding, we might end up with a string that begins + // with the null byte. Since the hunspell interface uses C-style strings, + // this appears like an empty string, and hunspell marks empty strings as + // spelled correctly. Skip these cases to allow another dictionary to have + // the chance to spellcheck them. + if (charsetWord.empty() || charsetWord[0] == 0) { + continue; + } + + *aResult = iter.Data().mHunspell->spell(charsetWord); + if (*aResult) { + break; + } + } + + if (!*aResult && mPersonalDictionary) { + return mPersonalDictionary->Check(aWord, aResult); + } + + return NS_OK; +} + +NS_IMETHODIMP +mozHunspell::Suggest(const nsAString& aWord, nsTArray<nsString>& aSuggestions) { + if (NS_WARN_IF(mHunspells.IsEmpty())) { + return NS_ERROR_FAILURE; + } + + MOZ_ASSERT(aSuggestions.IsEmpty()); + + for (auto iter = mHunspells.Iter(); !iter.Done(); iter.Next()) { + if (!iter.Data().mEnabled) { + continue; + } + + nsresult rv = iter.Data().LoadIfNecessary(); + if (NS_FAILED(rv)) { + continue; + } + + std::string charsetWord; + rv = iter.Data().ConvertCharset(aWord, charsetWord); + NS_ENSURE_SUCCESS(rv, rv); + + std::vector<std::string> suggestions = + iter.Data().mHunspell->suggest(charsetWord); + if (!suggestions.empty()) { + aSuggestions.SetCapacity(aSuggestions.Length() + suggestions.size()); + for (Span<const char> charSrc : suggestions) { + // Convert the suggestion to utf16 + auto src = AsBytes(charSrc); + nsresult rv = + iter.Data().mDecoder->Encoding()->DecodeWithoutBOMHandling( + src, *aSuggestions.AppendElement()); + NS_ENSURE_SUCCESS(rv, rv); + iter.Data().mDecoder->Encoding()->NewDecoderWithoutBOMHandlingInto( + *iter.Data().mDecoder); + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP +mozHunspell::Observe(nsISupports* aSubj, const char* aTopic, + const char16_t* aData) { + NS_ASSERTION(!strcmp(aTopic, "profile-do-change") || + !strcmp(aTopic, "profile-after-change"), + "Unexpected observer topic"); + + LoadDictionaryList(false); + + return NS_OK; +} + +NS_IMETHODIMP mozHunspell::AddDirectory(nsIFile* aDir) { + mDynamicDirectories.AppendObject(aDir); + LoadDictionaryList(true); + return NS_OK; +} + +NS_IMETHODIMP mozHunspell::RemoveDirectory(nsIFile* aDir) { + mDynamicDirectories.RemoveObject(aDir); + LoadDictionaryList(true); + +#ifdef MOZ_THUNDERBIRD + /* + * This notification is needed for Thunderbird. Thunderbird derives the + * dictionary from the document's "lang" attribute. If a dictionary is + * removed, we need to change the "lang" attribute. + */ + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->NotifyObservers(nullptr, SPELLCHECK_DICTIONARY_REMOVE_NOTIFICATION, + nullptr); + } +#endif + return NS_OK; +} + +NS_IMETHODIMP mozHunspell::AddDictionary(const nsAString& aLang, + nsIURI* aFile) { + NS_ENSURE_TRUE(aFile, NS_ERROR_INVALID_ARG); + + mDynamicDictionaries.InsertOrUpdate(aLang, aFile); + mDictionaries.InsertOrUpdate(aLang, aFile); + DictionariesChanged(true); + return NS_OK; +} + +NS_IMETHODIMP mozHunspell::RemoveDictionary(const nsAString& aLang, + nsIURI* aFile, bool* aRetVal) { + NS_ENSURE_TRUE(aFile, NS_ERROR_INVALID_ARG); + *aRetVal = false; + + nsCOMPtr<nsIURI> file = mDynamicDictionaries.Get(aLang); + bool equal; + if (file && NS_SUCCEEDED(file->Equals(aFile, &equal)) && equal) { + mDynamicDictionaries.Remove(aLang); + LoadDictionaryList(true); + *aRetVal = true; + } + return NS_OK; +} diff --git a/extensions/spellcheck/hunspell/glue/mozHunspell.h b/extensions/spellcheck/hunspell/glue/mozHunspell.h new file mode 100644 index 0000000000..89a38d9e49 --- /dev/null +++ b/extensions/spellcheck/hunspell/glue/mozHunspell.h @@ -0,0 +1,145 @@ +/******* BEGIN LICENSE BLOCK ******* + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Initial Developers of the Original Code are Kevin Hendricks (MySpell) + * and László Németh (Hunspell). Portions created by the Initial Developers + * are Copyright (C) 2002-2005 the Initial Developers. All Rights Reserved. + * + * Contributor(s): Kevin Hendricks (kevin.hendricks@sympatico.ca) + * David Einstein (deinst@world.std.com) + * Michiel van Leeuwen (mvl@exedo.nl) + * Caolan McNamara (cmc@openoffice.org) + * László Németh (nemethl@gyorsposta.hu) + * Davide Prina + * Giuseppe Modugno + * Gianluca Turconi + * Simon Brouwer + * Noll Janos + * Biro Arpad + * Goldman Eleonora + * Sarlos Tamas + * Bencsath Boldizsar + * Halacsy Peter + * Dvornik Laszlo + * Gefferth Andras + * Nagy Viktor + * Varga Daniel + * Chris Halls + * Rene Engelhard + * Bram Moolenaar + * Dafydd Jones + * Harri Pitkanen + * Andras Timar + * Tor Lillqvist + * Jesper Kristensen (mail@jesperkristensen.dk) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + ******* END LICENSE BLOCK *******/ + +#ifndef mozHunspell_h__ +#define mozHunspell_h__ + +#include "RLBoxHunspell.h" +#include "mozISpellCheckingEngine.h" +#include "mozIPersonalDictionary.h" +#include "nsString.h" +#include "nsCOMPtr.h" +#include "nsCOMArray.h" +#include "nsHashKeys.h" +#include "nsIMemoryReporter.h" +#include "nsIObserver.h" +#include "nsIURI.h" +#include "mozilla/Encoding.h" +#include "mozilla/UniquePtr.h" +#include "nsInterfaceHashtable.h" +#include "nsWeakReference.h" +#include "nsTHashMap.h" +#include "nsCycleCollectionParticipant.h" +#include "mozHunspellAllocator.h" + +#define MOZ_HUNSPELL_CONTRACTID "@mozilla.org/spellchecker/engine;1" +#define MOZ_HUNSPELL_CID \ + /* 56c778e4-1bee-45f3-a689-886692a97fe7 */ \ + { \ + 0x56c778e4, 0x1bee, 0x45f3, { \ + 0xa6, 0x89, 0x88, 0x66, 0x92, 0xa9, 0x7f, 0xe7 \ + } \ + } + +class mozHunspell final : public mozISpellCheckingEngine, + public nsIObserver, + public nsSupportsWeakReference, + public nsIMemoryReporter { + public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_MOZISPELLCHECKINGENGINE + NS_DECL_NSIOBSERVER + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(mozHunspell, mozISpellCheckingEngine) + + mozHunspell(); + + nsresult Init(); + + void LoadDictionaryList(bool aNotifyChildProcesses); + + NS_DECL_NSIMEMORYREPORTER + + protected: + virtual ~mozHunspell(); + + void DictionariesChanged(bool aNotifyChildProcesses); + + nsCOMPtr<mozIPersonalDictionary> mPersonalDictionary; + + // Hashtable matches dictionary name to .aff file + nsInterfaceHashtable<nsStringHashKey, nsIURI> mDictionaries; + + // dynamic dirs used to search for dictionaries + nsCOMArray<nsIFile> mDynamicDirectories; + nsInterfaceHashtable<nsStringHashKey, nsIURI> mDynamicDictionaries; + + struct DictionaryData { + // keep track of whether the dictionary is currently in use or not + bool mEnabled = true; + + // keep track of whether we've failed loading this dictionary before. + // if set, we don't try loading it again. + bool mLoadFailed = false; + + mozilla::UniquePtr<mozilla::Encoder> mEncoder; + mozilla::UniquePtr<mozilla::Decoder> mDecoder; + mozilla::UniquePtr<RLBoxHunspell> mHunspell; + nsCString mAffixFileName; + + // helper method for converting a word to the charset of the dictionary + nsresult ConvertCharset(const nsAString& aStr, std::string& aDst); + + // helper method to the load the dictionary if it is not already loaded + nsresult LoadIfNecessary(); + }; + + nsTHashMap<nsCStringHashKey, DictionaryData> mHunspells; +}; + +#endif diff --git a/extensions/spellcheck/hunspell/glue/mozHunspellAllocator.h b/extensions/spellcheck/hunspell/glue/mozHunspellAllocator.h new file mode 100644 index 0000000000..ddcec3e28c --- /dev/null +++ b/extensions/spellcheck/hunspell/glue/mozHunspellAllocator.h @@ -0,0 +1,15 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozHunspellAllocator_h__ +#define mozHunspellAllocator_h__ + +#include "mozilla/CountingAllocatorBase.h" + +class HunspellAllocator + : public mozilla::CountingAllocatorBase<HunspellAllocator> {}; + +#endif diff --git a/extensions/spellcheck/hunspell/glue/mozHunspellRLBoxGlue.h b/extensions/spellcheck/hunspell/glue/mozHunspellRLBoxGlue.h new file mode 100644 index 0000000000..d84a5ba739 --- /dev/null +++ b/extensions/spellcheck/hunspell/glue/mozHunspellRLBoxGlue.h @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozHunspellRLBoxGlue_h +#define mozHunspellRLBoxGlue_h + +#include <stdint.h> + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef uint32_t(hunspell_create_filemgr_t)(const char* aFilename); +typedef bool(hunspell_get_line_t)(uint32_t aFd, char** aLinePtr); +typedef int(hunspell_get_line_num_t)(uint32_t aFd); +typedef void(hunspell_destruct_filemgr_t)(uint32_t aFd); +typedef uint32_t(hunspell_ToUpperCase_t)(uint32_t aChar); +typedef uint32_t(hunspell_ToLowerCase_t)(uint32_t aChar); +typedef struct cs_info*(hunspell_get_current_cs_t)(const char* es); + +void RegisterHunspellCallbacks( + hunspell_create_filemgr_t* aHunspellCreateFilemgr, + hunspell_get_line_t* aHunspellGetLine, + hunspell_get_line_num_t* aHunspellGetLine_num, + hunspell_destruct_filemgr_t* aHunspellDestructFilemgr, + hunspell_ToUpperCase_t* aHunspellToUpperCase, + hunspell_ToLowerCase_t* aHunspellToLowerCase, + hunspell_get_current_cs_t* aHunspellGetCurrentCS); + +extern hunspell_create_filemgr_t* moz_glue_hunspell_create_filemgr; +extern hunspell_get_line_t* moz_glue_hunspell_get_line; +extern hunspell_get_line_num_t* moz_glue_hunspell_get_line_num; +extern hunspell_destruct_filemgr_t* moz_glue_hunspell_destruct_filemgr; +extern hunspell_ToUpperCase_t* moz_hunspell_ToUpperCase; +extern hunspell_ToLowerCase_t* moz_hunspell_ToLowerCase; +extern hunspell_get_current_cs_t* moz_hunspell_GetCurrentCS; + +#if defined(__cplusplus) +} +#endif + +#endif diff --git a/extensions/spellcheck/hunspell/glue/mozHunspellRLBoxHost.cpp b/extensions/spellcheck/hunspell/glue/mozHunspellRLBoxHost.cpp new file mode 100644 index 0000000000..cd80fb989b --- /dev/null +++ b/extensions/spellcheck/hunspell/glue/mozHunspellRLBoxHost.cpp @@ -0,0 +1,235 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <limits> + +#include "mozHunspellRLBoxHost.h" +#include "mozilla/DebugOnly.h" +#include "nsContentUtils.h" +#include "nsILoadInfo.h" +#include "nsNetUtil.h" +#include "nsUnicharUtils.h" + +#include "hunspell_csutil.hxx" + +using namespace mozilla; + +mozHunspellFileMgrHost::mozHunspellFileMgrHost(const nsCString& aFilename) { + nsCOMPtr<nsIChannel> channel; + DebugOnly<Result<Ok, nsresult>> result = Open(aFilename, channel, mStream); + NS_WARNING_ASSERTION(result.value.isOk(), "Failed to open Hunspell file"); +} + +/* static */ +Result<Ok, nsresult> mozHunspellFileMgrHost::Open( + const nsCString& aPath, nsCOMPtr<nsIChannel>& aChannel, + nsCOMPtr<nsIInputStream>& aStream) { + nsCOMPtr<nsIURI> uri; + MOZ_TRY(NS_NewURI(getter_AddRefs(uri), aPath)); + + MOZ_TRY(NS_NewChannel( + getter_AddRefs(aChannel), uri, nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT, + nsIContentPolicy::TYPE_OTHER)); + + MOZ_TRY(aChannel->Open(getter_AddRefs(aStream))); + return Ok(); +} + +Result<Ok, nsresult> mozHunspellFileMgrHost::ReadLine(nsCString& aLine) { + if (!mStream) { + return Err(NS_ERROR_NOT_INITIALIZED); + } + + bool ok; + MOZ_TRY(NS_ReadLine(mStream.get(), &mLineBuffer, aLine, &ok)); + if (!ok) { + mStream = nullptr; + } + + mLineNum++; + return Ok(); +} + +/* static */ +Result<int64_t, nsresult> mozHunspellFileMgrHost::GetSize( + const nsCString& aFilename) { + int64_t ret = -1; + + nsCOMPtr<nsIChannel> channel; + nsCOMPtr<nsIInputStream> stream; + MOZ_TRY(Open(aFilename, channel, stream)); + + channel->GetContentLength(&ret); + return ret; +} + +bool mozHunspellFileMgrHost::GetLine(std::string& aResult) { + nsAutoCString line; + auto res = ReadLine(line); + if (res.isErr()) { + return false; + } + + aResult.assign(line.BeginReading(), line.Length()); + return true; +} + +/* static */ +uint32_t mozHunspellCallbacks::sCurrentFreshId = 0; +/* static */ +mozilla::StaticRWLock mozHunspellCallbacks::sFileMgrMapLock; +/* static */ +std::map<uint32_t, std::unique_ptr<mozHunspellFileMgrHost>> + mozHunspellCallbacks::sFileMgrMap; +/* static */ +std::set<nsCString> mozHunspellCallbacks::sFileMgrAllowList; + +/* static */ +void mozHunspellCallbacks::AllowFile(const nsCString& aFilename) { + mozilla::StaticAutoWriteLock lock(sFileMgrMapLock); + sFileMgrAllowList.insert(aFilename); +} + +/* static */ +void mozHunspellCallbacks::Clear() { + mozilla::StaticAutoWriteLock lock(sFileMgrMapLock); + sCurrentFreshId = 0; + sFileMgrMap.clear(); + sFileMgrAllowList.clear(); +} + +/* static */ +tainted_hunspell<uint32_t> mozHunspellCallbacks::CreateFilemgr( + rlbox_sandbox_hunspell& aSandbox, + tainted_hunspell<const char*> t_aFilename) { + mozilla::StaticAutoWriteLock lock(sFileMgrMapLock); + + return t_aFilename.copy_and_verify_string( + [&](std::unique_ptr<char[]> aFilename) { + nsCString cFilename = nsDependentCString(aFilename.get()); + + // Ensure that the filename is in the allowlist + auto it = sFileMgrAllowList.find(cFilename); + MOZ_RELEASE_ASSERT(it != sFileMgrAllowList.end()); + + // Get new id + uint32_t freshId = GetFreshId(); + // Save mapping of id to file manager + sFileMgrMap[freshId] = std::unique_ptr<mozHunspellFileMgrHost>( + new mozHunspellFileMgrHost(cFilename)); + + return freshId; + }); +} + +/* static */ +uint32_t mozHunspellCallbacks::GetFreshId() { + // i is uint64_t to prevent overflow during loop increment which would cause + // an infinite loop + for (uint64_t i = sCurrentFreshId; i < std::numeric_limits<uint32_t>::max(); + i++) { + auto it = sFileMgrMap.find(i); + if (it == sFileMgrMap.end()) { + // set sCurrentFreshId to the next (possibly) available id + sCurrentFreshId = i + 1; + return static_cast<uint32_t>(i); + } + } + + MOZ_CRASH("Ran out of unique file ids for hunspell dictionaries"); +} + +/* static */ +mozHunspellFileMgrHost& mozHunspellCallbacks::GetMozHunspellFileMgrHost( + tainted_hunspell<uint32_t> t_aFd) { + mozilla::StaticAutoReadLock lock(sFileMgrMapLock); + uint32_t aFd = t_aFd.copy_and_verify([](uint32_t aFd) { return aFd; }); + auto iter = sFileMgrMap.find(aFd); + MOZ_RELEASE_ASSERT(iter != sFileMgrMap.end()); + return *(iter->second.get()); +} + +/* static */ +tainted_hunspell<bool> mozHunspellCallbacks::GetLine( + rlbox_sandbox_hunspell& aSandbox, tainted_hunspell<uint32_t> t_aFd, + tainted_hunspell<char**> t_aLinePtr) { + mozHunspellFileMgrHost& inst = + mozHunspellCallbacks::GetMozHunspellFileMgrHost(t_aFd); + std::string line; + bool ok = inst.GetLine(line); + // If the getline fails, return a null which is "graceful" failure + if (ok) { + // Copy the line into the sandbox. This memory is eventually freed by + // hunspell. + size_t size = line.size() + 1; + tainted_hunspell<char*> t_line = aSandbox.malloc_in_sandbox<char>(size); + + if (t_line == nullptr) { + // If malloc fails, we should go to "graceful" failure path + ok = false; + } else { + rlbox::memcpy(aSandbox, t_line, line.c_str(), size); + } + *t_aLinePtr = t_line; + } else { + *t_aLinePtr = nullptr; + } + return ok; +} + +/* static */ +tainted_hunspell<int> mozHunspellCallbacks::GetLineNum( + rlbox_sandbox_hunspell& aSandbox, tainted_hunspell<uint32_t> t_aFd) { + mozHunspellFileMgrHost& inst = + mozHunspellCallbacks::GetMozHunspellFileMgrHost(t_aFd); + int num = inst.GetLineNum(); + return num; +} + +/* static */ +void mozHunspellCallbacks::DestructFilemgr(rlbox_sandbox_hunspell& aSandbox, + tainted_hunspell<uint32_t> t_aFd) { + mozilla::StaticAutoWriteLock lock(sFileMgrMapLock); + uint32_t aFd = t_aFd.copy_and_verify([](uint32_t aFd) { return aFd; }); + + auto iter = sFileMgrMap.find(aFd); + if (iter != sFileMgrMap.end()) { + sFileMgrMap.erase(iter); + } +} + +// Callbacks for using Firefox's encoding instead of hunspell's + +/* static */ +tainted_hunspell<uint32_t> mozHunspellCallbacks::ToUpperCase( + rlbox_sandbox_hunspell& aSandbox, tainted_hunspell<uint32_t> t_aChar) { + uint32_t aChar = + t_aChar.copy_and_verify([](uint32_t aChar) { return aChar; }); + return ::ToUpperCase(aChar); +} + +/* static */ +tainted_hunspell<uint32_t> mozHunspellCallbacks::ToLowerCase( + rlbox_sandbox_hunspell& aSandbox, tainted_hunspell<uint32_t> t_aChar) { + uint32_t aChar = + t_aChar.copy_and_verify([](uint32_t aChar) { return aChar; }); + return ::ToLowerCase(aChar); +} + +/* static */ tainted_hunspell<struct cs_info*> +mozHunspellCallbacks::GetCurrentCS(rlbox_sandbox_hunspell& aSandbox, + tainted_hunspell<const char*> t_es) { + tainted_hunspell<struct cs_info*> t_ccs = + aSandbox.malloc_in_sandbox<struct cs_info>(256); + MOZ_RELEASE_ASSERT(t_ccs); + return t_es.copy_and_verify_string([&](std::unique_ptr<char[]> es) { + struct cs_info* ccs = hunspell_get_current_cs(es.get()); + rlbox::memcpy(aSandbox, t_ccs, ccs, sizeof(struct cs_info) * 256); + delete[] ccs; + return t_ccs; + }); +} diff --git a/extensions/spellcheck/hunspell/glue/mozHunspellRLBoxHost.h b/extensions/spellcheck/hunspell/glue/mozHunspellRLBoxHost.h new file mode 100644 index 0000000000..f92070eabf --- /dev/null +++ b/extensions/spellcheck/hunspell/glue/mozHunspellRLBoxHost.h @@ -0,0 +1,122 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozHunspellRLBoxHost_h +#define mozHunspellRLBoxHost_h + +#include <map> +#include <memory> +#include <mutex> +#include <set> +#include <stdint.h> +#include <string> + +#include "RLBoxHunspell.h" +#include "mozilla/Result.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/RWLock.h" +#include "nsIChannel.h" +#include "nsIInputStream.h" +#include "nsReadLine.h" + +namespace mozilla { + +class mozHunspellFileMgrHost final { + public: + /** + * aFilename must be a local file/jar URI for the file to load. + */ + explicit mozHunspellFileMgrHost(const nsCString& aFilename); + ~mozHunspellFileMgrHost() = default; + + bool GetLine(std::string& aResult); + int GetLineNum() const { return mLineNum; } + + static Result<int64_t, nsresult> GetSize(const nsCString& aFilename); + + private: + static mozilla::Result<mozilla::Ok, nsresult> Open( + const nsCString& aPath, nsCOMPtr<nsIChannel>& aChannel, + nsCOMPtr<nsIInputStream>& aStream); + + mozilla::Result<mozilla::Ok, nsresult> ReadLine(nsCString& aLine); + + int mLineNum = 0; + nsCOMPtr<nsIInputStream> mStream; + nsLineBuffer<char> mLineBuffer; +}; + +class mozHunspellCallbacks { + public: + // APIs invoked by the sandboxed hunspell file manager + static tainted_hunspell<uint32_t> CreateFilemgr( + rlbox_sandbox_hunspell& aSandbox, + tainted_hunspell<const char*> t_aFilename); + static tainted_hunspell<bool> GetLine(rlbox_sandbox_hunspell& aSandbox, + tainted_hunspell<uint32_t> t_aFd, + tainted_hunspell<char**> t_aLinePtr); + static tainted_hunspell<int> GetLineNum(rlbox_sandbox_hunspell& aSandbox, + tainted_hunspell<uint32_t> t_aFd); + static void DestructFilemgr(rlbox_sandbox_hunspell& aSandbox, + tainted_hunspell<uint32_t> t_aFd); + + // APIs necessary for hunspell UTF encoding + static tainted_hunspell<uint32_t> ToUpperCase( + rlbox_sandbox_hunspell& aSandbox, tainted_hunspell<uint32_t> t_aChar); + static tainted_hunspell<uint32_t> ToLowerCase( + rlbox_sandbox_hunspell& aSandbox, tainted_hunspell<uint32_t> t_aChar); + static tainted_hunspell<struct cs_info*> GetCurrentCS( + rlbox_sandbox_hunspell& aSandbox, tainted_hunspell<const char*> t_es); + + protected: + // API called by RLBox + + /** + * Add filename to allow list. + */ + static void AllowFile(const nsCString& aFilename); + friend RLBoxHunspell* RLBoxHunspell::Create(const nsCString& affpath, + const nsCString& dpath); + /** + * Clear allow list and map of hunspell file managers. + */ + static void Clear(); + friend RLBoxHunspell::~RLBoxHunspell(); + + private: + /** + * sFileMgrMap holds a map between unique uint32_t + * integers and mozHunspellFileMgrHost instances + */ + static std::map<uint32_t, std::unique_ptr<mozHunspellFileMgrHost>> + sFileMgrMap; + + /** + * sFileMgrAllowList contains the filenames of the dictionary files hunspell + * is allowed to open + */ + static std::set<nsCString> sFileMgrAllowList; + /** + * Reader-writer lock for the sFileMgrMap + */ + static mozilla::StaticRWLock sFileMgrMapLock; + /** + * Tracks the next possibly unused id for sFileMgrMap + */ + static uint32_t sCurrentFreshId; + /** + * Returns an unused id for sFileMgrMap + */ + static uint32_t GetFreshId(); + /** + * Returns the mozHunspellFileMgrHost for the given uint32_t id + */ + static mozHunspellFileMgrHost& GetMozHunspellFileMgrHost( + tainted_hunspell<uint32_t> t_aFd); +}; +} // namespace mozilla + +#endif diff --git a/extensions/spellcheck/hunspell/glue/mozHunspellRLBoxSandbox.cpp b/extensions/spellcheck/hunspell/glue/mozHunspellRLBoxSandbox.cpp new file mode 100644 index 0000000000..f913b4bc83 --- /dev/null +++ b/extensions/spellcheck/hunspell/glue/mozHunspellRLBoxSandbox.cpp @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozHunspellRLBoxSandbox.h" +#include "mozHunspellRLBoxGlue.h" + +FileMgr::FileMgr(const char* aFilename, const char* aKey) : mFd(0) { + // The key is not used in firefox + mFd = moz_glue_hunspell_create_filemgr(aFilename); +} + +bool FileMgr::getline(std::string& aResult) { + char* line = nullptr; + bool ok = moz_glue_hunspell_get_line(mFd, &line); + if (ok && line) { + aResult = line; + free(line); + } + return ok; +} + +int FileMgr::getlinenum() const { return moz_glue_hunspell_get_line_num(mFd); } + +FileMgr::~FileMgr() { moz_glue_hunspell_destruct_filemgr(mFd); } + +// Glue code to set global callbacks + +hunspell_create_filemgr_t* moz_glue_hunspell_create_filemgr = nullptr; +hunspell_get_line_t* moz_glue_hunspell_get_line = nullptr; +hunspell_get_line_num_t* moz_glue_hunspell_get_line_num = nullptr; +hunspell_destruct_filemgr_t* moz_glue_hunspell_destruct_filemgr = nullptr; +hunspell_ToUpperCase_t* moz_hunspell_ToUpperCase = nullptr; +hunspell_ToLowerCase_t* moz_hunspell_ToLowerCase = nullptr; +hunspell_get_current_cs_t* moz_hunspell_GetCurrentCS = nullptr; + +void RegisterHunspellCallbacks( + hunspell_create_filemgr_t* aHunspellCreateFilemgr, + hunspell_get_line_t* aHunspellGetLine, + hunspell_get_line_num_t* aHunspellGetLine_num, + hunspell_destruct_filemgr_t* aHunspellDestructFilemgr, + hunspell_ToUpperCase_t* aHunspellToUpperCase, + hunspell_ToLowerCase_t* aHunspellToLowerCase, + hunspell_get_current_cs_t* aHunspellGetCurrentCS) { + moz_glue_hunspell_create_filemgr = aHunspellCreateFilemgr; + moz_glue_hunspell_get_line = aHunspellGetLine; + moz_glue_hunspell_get_line_num = aHunspellGetLine_num; + moz_glue_hunspell_destruct_filemgr = aHunspellDestructFilemgr; + moz_hunspell_ToUpperCase = aHunspellToUpperCase; + moz_hunspell_ToLowerCase = aHunspellToLowerCase; + moz_hunspell_GetCurrentCS = aHunspellGetCurrentCS; +} diff --git a/extensions/spellcheck/hunspell/glue/mozHunspellRLBoxSandbox.h b/extensions/spellcheck/hunspell/glue/mozHunspellRLBoxSandbox.h new file mode 100644 index 0000000000..9850408807 --- /dev/null +++ b/extensions/spellcheck/hunspell/glue/mozHunspellRLBoxSandbox.h @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozHunspellRLBoxSandbox_h +#define mozHunspellRLBoxSandbox_h + +#include <string> +#include <stdint.h> + +// Note: This class name and lack of namespacing terrible, but are necessary +// for Hunspell compatibility. +class FileMgr final { + public: + /** + * aFilename must be a local file/jar URI for the file to load. + * + * aKey is the decription key for encrypted Hunzip files, and is + * unsupported. The argument is there solely for compatibility. + */ + explicit FileMgr(const char* aFilename, const char* aKey = nullptr); + ~FileMgr(); + + // Note: The nonstandard naming conventions of these methods are necessary for + // Hunspell compatibility. + bool getline(std::string& aLine); + int getlinenum() const; + + private: + // opaque file descriptor got from the host application + uint32_t mFd; +}; + +#endif // mozHunspellRLBoxSandbox_h |