/* -*- 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 "mozilla/Try.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(nsACString& 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(nsACString& aResult) {
  return !ReadLine(aResult).isErr();
}

/* static */
uint32_t mozHunspellCallbacks::sCurrentFreshId = 0;
/* static */
mozilla::StaticRWLock mozHunspellCallbacks::sFileMgrMapLock;
/* static */
MOZ_RUNINIT std::map<uint32_t, std::unique_ptr<mozHunspellFileMgrHost>>
    mozHunspellCallbacks::sFileMgrMap;
/* static */
MOZ_RUNINIT 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);
  nsAutoCString 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.Length() + 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.get(), 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;
  });
}
