tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

mozPersonalDictionary.cpp (11998B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #include "mozPersonalDictionary.h"
      7 
      8 #include <utility>
      9 
     10 #include "mozilla/Try.h"
     11 #include "nsAppDirectoryServiceDefs.h"
     12 #include "nsCRT.h"
     13 #include "nsIFile.h"
     14 #include "nsIInputStream.h"
     15 #include "nsIObserverService.h"
     16 #include "nsIOutputStream.h"
     17 #include "nsIRunnable.h"
     18 #include "nsISafeOutputStream.h"
     19 #include "nsIUnicharInputStream.h"
     20 #include "nsIWeakReference.h"
     21 #include "nsNetCID.h"
     22 #include "nsNetUtil.h"
     23 #include "nsProxyRelease.h"
     24 #include "nsReadableUtils.h"
     25 #include "nsStringEnumerator.h"
     26 #include "nsTArray.h"
     27 #include "nsThreadUtils.h"
     28 #include "nsUnicharInputStream.h"
     29 #include "prio.h"
     30 
     31 #define MOZ_PERSONAL_DICT_NAME u"persdict.dat"
     32 
     33 /**
     34 * This is the most braindead implementation of a personal dictionary possible.
     35 * There is not much complexity needed, though.  It could be made much faster,
     36 * and probably should, but I don't see much need for more in terms of
     37 * interface.
     38 *
     39 * Allowing personal words to be associated with only certain dictionaries
     40 * maybe.
     41 *
     42 * TODO:
     43 * Implement the suggestion record.
     44 */
     45 
     46 NS_IMPL_ADDREF(mozPersonalDictionary)
     47 NS_IMPL_RELEASE(mozPersonalDictionary)
     48 
     49 NS_INTERFACE_MAP_BEGIN(mozPersonalDictionary)
     50  NS_INTERFACE_MAP_ENTRY(mozIPersonalDictionary)
     51  NS_INTERFACE_MAP_ENTRY(nsIObserver)
     52  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
     53  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozIPersonalDictionary)
     54 NS_INTERFACE_MAP_END
     55 
     56 class mozPersonalDictionaryLoader final : public mozilla::Runnable {
     57 public:
     58  explicit mozPersonalDictionaryLoader(mozPersonalDictionary* dict)
     59      : mozilla::Runnable("mozPersonalDictionaryLoader"), mDict(dict) {}
     60 
     61  NS_IMETHOD Run() override {
     62    mDict->SyncLoad();
     63 
     64    // Release the dictionary on the main thread
     65    NS_ReleaseOnMainThread("mozPersonalDictionaryLoader::mDict",
     66                           mDict.forget().downcast<mozIPersonalDictionary>());
     67 
     68    return NS_OK;
     69  }
     70 
     71 private:
     72  RefPtr<mozPersonalDictionary> mDict;
     73 };
     74 
     75 class mozPersonalDictionarySave final : public mozilla::Runnable {
     76 public:
     77  explicit mozPersonalDictionarySave(mozPersonalDictionary* aDict,
     78                                     nsCOMPtr<nsIFile> aFile,
     79                                     nsTArray<nsString>&& aDictWords)
     80      : mozilla::Runnable("mozPersonalDictionarySave"),
     81        mDictWords(std::move(aDictWords)),
     82        mFile(aFile),
     83        mDict(aDict) {}
     84 
     85  NS_IMETHOD Run() override {
     86    nsresult res;
     87 
     88    MOZ_ASSERT(!NS_IsMainThread());
     89 
     90    {
     91      mozilla::MonitorAutoLock mon(mDict->mMonitorSave);
     92 
     93      nsCOMPtr<nsIOutputStream> outStream;
     94      MOZ_TRY(NS_NewSafeLocalFileOutputStream(
     95          getter_AddRefs(outStream), mFile,
     96          PR_CREATE_FILE | PR_WRONLY | PR_TRUNCATE, 0664));
     97 
     98      // Get a buffered output stream 4096 bytes big, to optimize writes.
     99      nsCOMPtr<nsIOutputStream> bufferedOutputStream;
    100      res = NS_NewBufferedOutputStream(getter_AddRefs(bufferedOutputStream),
    101                                       outStream.forget(), 4096);
    102      if (NS_FAILED(res)) {
    103        return res;
    104      }
    105 
    106      uint32_t bytesWritten;
    107      nsAutoCString utf8Key;
    108      for (uint32_t i = 0; i < mDictWords.Length(); ++i) {
    109        CopyUTF16toUTF8(mDictWords[i], utf8Key);
    110 
    111        bufferedOutputStream->Write(utf8Key.get(), utf8Key.Length(),
    112                                    &bytesWritten);
    113        bufferedOutputStream->Write("\n", 1, &bytesWritten);
    114      }
    115      nsCOMPtr<nsISafeOutputStream> safeStream =
    116          do_QueryInterface(bufferedOutputStream);
    117      NS_ASSERTION(safeStream, "expected a safe output stream!");
    118      if (safeStream) {
    119        res = safeStream->Finish();
    120        if (NS_FAILED(res)) {
    121          NS_WARNING(
    122              "failed to save personal dictionary file! possible data loss");
    123        }
    124      }
    125 
    126      // Save is done, reset the state variable and notify those who are
    127      // waiting.
    128      mDict->mSavePending = false;
    129      mon.Notify();
    130 
    131      // Leaving the block where 'mon' was declared will call the destructor
    132      // and unlock.
    133    }
    134 
    135    // Release the dictionary on the main thread.
    136    NS_ReleaseOnMainThread("mozPersonalDictionarySave::mDict",
    137                           mDict.forget().downcast<mozIPersonalDictionary>());
    138 
    139    return NS_OK;
    140  }
    141 
    142 private:
    143  nsTArray<nsString> mDictWords;
    144  nsCOMPtr<nsIFile> mFile;
    145  RefPtr<mozPersonalDictionary> mDict;
    146 };
    147 
    148 mozPersonalDictionary::mozPersonalDictionary()
    149    : mIsLoaded(false),
    150      mSavePending(false),
    151      mMonitor("mozPersonalDictionary::mMonitor"),
    152      mMonitorSave("mozPersonalDictionary::mMonitorSave") {}
    153 
    154 mozPersonalDictionary::~mozPersonalDictionary() {}
    155 
    156 nsresult mozPersonalDictionary::Init() {
    157  nsCOMPtr<nsIObserverService> svc =
    158      do_GetService("@mozilla.org/observer-service;1");
    159 
    160  NS_ENSURE_STATE(svc);
    161  // we want to reload the dictionary if the profile switches
    162  nsresult rv = svc->AddObserver(this, "profile-do-change", true);
    163  if (NS_WARN_IF(NS_FAILED(rv))) {
    164    return rv;
    165  }
    166 
    167  rv = svc->AddObserver(this, "profile-before-change", true);
    168  if (NS_WARN_IF(NS_FAILED(rv))) {
    169    return rv;
    170  }
    171 
    172  Load();
    173 
    174  return NS_OK;
    175 }
    176 
    177 void mozPersonalDictionary::WaitForLoad() {
    178  // If the dictionary is already loaded, we return straight away.
    179  if (mIsLoaded) {
    180    return;
    181  }
    182 
    183  // If the dictionary hasn't been loaded, we try to lock the same monitor
    184  // that the thread uses that does the load. This way the main thread will
    185  // be suspended until the monitor becomes available.
    186  mozilla::MonitorAutoLock mon(mMonitor);
    187 
    188  // The monitor has become available. This can have two reasons:
    189  // 1: The thread that does the load has finished.
    190  // 2: The thread that does the load hasn't even started.
    191  //    In this case we need to wait.
    192  if (!mIsLoaded) {
    193    mon.Wait();
    194  }
    195 }
    196 
    197 nsresult mozPersonalDictionary::LoadInternal() {
    198  nsresult rv;
    199  mozilla::MonitorAutoLock mon(mMonitor);
    200 
    201  if (mIsLoaded) {
    202    return NS_OK;
    203  }
    204 
    205  rv =
    206      NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(mFile));
    207  if (NS_WARN_IF(NS_FAILED(rv))) {
    208    return rv;
    209  }
    210 
    211  if (!mFile) {
    212    return NS_ERROR_FAILURE;
    213  }
    214 
    215  rv = mFile->Append(nsLiteralString(MOZ_PERSONAL_DICT_NAME));
    216  if (NS_WARN_IF(NS_FAILED(rv))) {
    217    return rv;
    218  }
    219 
    220  nsCOMPtr<nsIEventTarget> target =
    221      do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
    222  if (NS_WARN_IF(NS_FAILED(rv))) {
    223    return rv;
    224  }
    225 
    226  nsCOMPtr<nsIRunnable> runnable = new mozPersonalDictionaryLoader(this);
    227  rv = target->Dispatch(runnable, NS_DISPATCH_NORMAL);
    228  if (NS_WARN_IF(NS_FAILED(rv))) {
    229    return rv;
    230  }
    231 
    232  return NS_OK;
    233 }
    234 
    235 NS_IMETHODIMP mozPersonalDictionary::Load() {
    236  nsresult rv = LoadInternal();
    237 
    238  if (NS_FAILED(rv)) {
    239    mIsLoaded = true;
    240  }
    241 
    242  return rv;
    243 }
    244 
    245 void mozPersonalDictionary::SyncLoad() {
    246  MOZ_ASSERT(!NS_IsMainThread());
    247 
    248  mozilla::MonitorAutoLock mon(mMonitor);
    249 
    250  if (mIsLoaded) {
    251    return;
    252  }
    253 
    254  SyncLoadInternal();
    255  mIsLoaded = true;
    256  mon.Notify();
    257 }
    258 
    259 void mozPersonalDictionary::SyncLoadInternal() {
    260  MOZ_ASSERT(!NS_IsMainThread());
    261 
    262  // FIXME Deinst  -- get dictionary name from prefs;
    263  nsresult rv;
    264  bool dictExists;
    265 
    266  rv = mFile->Exists(&dictExists);
    267  if (NS_FAILED(rv)) {
    268    return;
    269  }
    270 
    271  if (!dictExists) {
    272    // Nothing is really wrong...
    273    return;
    274  }
    275 
    276  nsCOMPtr<nsIInputStream> inStream;
    277  rv = NS_NewLocalFileInputStream(getter_AddRefs(inStream), mFile);
    278  if (NS_FAILED(rv)) {
    279    return;
    280  }
    281 
    282  nsCOMPtr<nsIUnicharInputStream> convStream;
    283  rv = NS_NewUnicharInputStream(inStream, getter_AddRefs(convStream));
    284  if (NS_FAILED(rv)) {
    285    return;
    286  }
    287 
    288  // we're rereading to get rid of the old data  -- we shouldn't have any,
    289  // but...
    290  mDictionaryTable.Clear();
    291 
    292  char16_t c;
    293  uint32_t nRead;
    294  bool done = false;
    295  do {  // read each line of text into the string array.
    296    if ((NS_OK != convStream->Read(&c, 1, &nRead)) || (nRead != 1)) break;
    297    while (!done && ((c == '\n') || (c == '\r'))) {
    298      if ((NS_OK != convStream->Read(&c, 1, &nRead)) || (nRead != 1))
    299        done = true;
    300    }
    301    if (!done) {
    302      nsAutoString word;
    303      while ((c != '\n') && (c != '\r') && !done) {
    304        word.Append(c);
    305        if ((NS_OK != convStream->Read(&c, 1, &nRead)) || (nRead != 1))
    306          done = true;
    307      }
    308      mDictionaryTable.Insert(word);
    309    }
    310  } while (!done);
    311 }
    312 
    313 void mozPersonalDictionary::WaitForSave() {
    314  // If no save is pending, we return straight away.
    315  if (!mSavePending) {
    316    return;
    317  }
    318 
    319  // If a save is pending, we try to lock the same monitor that the thread uses
    320  // that does the save. This way the main thread will be suspended until the
    321  // monitor becomes available.
    322  mozilla::MonitorAutoLock mon(mMonitorSave);
    323 
    324  // The monitor has become available. This can have two reasons:
    325  // 1: The thread that does the save has finished.
    326  // 2: The thread that does the save hasn't even started.
    327  //    In this case we need to wait.
    328  if (mSavePending) {
    329    mon.Wait();
    330  }
    331 }
    332 
    333 NS_IMETHODIMP mozPersonalDictionary::Save() {
    334  nsCOMPtr<nsIFile> theFile;
    335  nsresult res;
    336 
    337  WaitForSave();
    338 
    339  mSavePending = true;
    340 
    341  // FIXME Deinst  -- get dictionary name from prefs;
    342  res = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
    343                               getter_AddRefs(theFile));
    344  if (NS_FAILED(res)) return res;
    345  if (!theFile) return NS_ERROR_FAILURE;
    346  res = theFile->Append(nsLiteralString(MOZ_PERSONAL_DICT_NAME));
    347  if (NS_FAILED(res)) return res;
    348 
    349  nsCOMPtr<nsIEventTarget> target =
    350      do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &res);
    351  if (NS_WARN_IF(NS_FAILED(res))) {
    352    return res;
    353  }
    354 
    355  nsCOMPtr<nsIRunnable> runnable = new mozPersonalDictionarySave(
    356      this, theFile, mozilla::ToTArray<nsTArray<nsString>>(mDictionaryTable));
    357  res = target->Dispatch(runnable, NS_DISPATCH_NORMAL);
    358  if (NS_WARN_IF(NS_FAILED(res))) {
    359    return res;
    360  }
    361  return res;
    362 }
    363 
    364 NS_IMETHODIMP mozPersonalDictionary::GetWordList(nsIStringEnumerator** aWords) {
    365  NS_ENSURE_ARG_POINTER(aWords);
    366  *aWords = nullptr;
    367 
    368  WaitForLoad();
    369 
    370  nsTArray<nsString>* array = new nsTArray<nsString>(
    371      mozilla::ToTArray<nsTArray<nsString>>(mDictionaryTable));
    372 
    373  array->Sort();
    374 
    375  return NS_NewAdoptingStringEnumerator(aWords, array);
    376 }
    377 
    378 NS_IMETHODIMP
    379 mozPersonalDictionary::Check(const nsAString& aWord, bool* aResult) {
    380  NS_ENSURE_ARG_POINTER(aResult);
    381 
    382  WaitForLoad();
    383 
    384  *aResult = (mDictionaryTable.Contains(aWord) || mIgnoreTable.Contains(aWord));
    385  return NS_OK;
    386 }
    387 
    388 NS_IMETHODIMP
    389 mozPersonalDictionary::AddWord(const nsAString& aWord) {
    390  nsresult res;
    391  WaitForLoad();
    392 
    393  mDictionaryTable.Insert(aWord);
    394  res = Save();
    395  return res;
    396 }
    397 
    398 NS_IMETHODIMP
    399 mozPersonalDictionary::RemoveWord(const nsAString& aWord) {
    400  nsresult res;
    401  WaitForLoad();
    402 
    403  mDictionaryTable.Remove(aWord);
    404  res = Save();
    405  return res;
    406 }
    407 
    408 NS_IMETHODIMP
    409 mozPersonalDictionary::IgnoreWord(const nsAString& aWord) {
    410  // avoid adding duplicate words to the ignore list
    411  mIgnoreTable.EnsureInserted(aWord);
    412  return NS_OK;
    413 }
    414 
    415 NS_IMETHODIMP mozPersonalDictionary::EndSession() {
    416  WaitForLoad();
    417 
    418  WaitForSave();
    419  mIgnoreTable.Clear();
    420  return NS_OK;
    421 }
    422 
    423 NS_IMETHODIMP mozPersonalDictionary::Observe(nsISupports* aSubject,
    424                                             const char* aTopic,
    425                                             const char16_t* aData) {
    426  if (!nsCRT::strcmp(aTopic, "profile-do-change")) {
    427    // The observer is registered in Init() which calls Load and in turn
    428    // LoadInternal(); i.e. Observe() can't be called before Load().
    429    WaitForLoad();
    430    mIsLoaded = false;
    431    Load();  // load automatically clears out the existing dictionary table
    432  } else if (!nsCRT::strcmp(aTopic, "profile-before-change")) {
    433    WaitForSave();
    434  }
    435 
    436  return NS_OK;
    437 }