/*
 * This file is part of PowerDNS or dnsdist.
 * Copyright -- PowerDNS.COM B.V. and its contributors
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * In addition, for the avoidance of any doubt, permission is granted to
 * link this program with OpenSSL and to (re)distribute the binaries
 * produced as the result of such linking.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
#include "base64.hh"
#include "dnsdist-doh-common.hh"
#include "dnsdist-rules.hh"

#ifdef HAVE_DNS_OVER_HTTPS

HTTPHeaderRule::HTTPHeaderRule(const std::string& header, const std::string& regex) :
  d_header(toLower(header)), d_regex(regex), d_visual("http[" + header + "] ~ " + regex)
{
}

bool HTTPHeaderRule::matches(const DNSQuestion* dq) const
{
  if (!dq->ids.du) {
    return false;
  }

  const auto& headers = dq->ids.du->getHTTPHeaders();
  for (const auto& header : headers) {
    if (header.first == d_header) {
      return d_regex.match(header.second);
    }
  }
  return false;
}

string HTTPHeaderRule::toString() const
{
  return d_visual;
}

HTTPPathRule::HTTPPathRule(std::string path) :
  d_path(std::move(path))
{
}

bool HTTPPathRule::matches(const DNSQuestion* dq) const
{
  if (!dq->ids.du) {
    return false;
  }

  const auto path = dq->ids.du->getHTTPPath();
  return d_path == path;
}

string HTTPPathRule::toString() const
{
  return "url path == " + d_path;
}

HTTPPathRegexRule::HTTPPathRegexRule(const std::string& regex) :
  d_regex(regex), d_visual("http path ~ " + regex)
{
}

bool HTTPPathRegexRule::matches(const DNSQuestion* dq) const
{
  if (!dq->ids.du) {
    return false;
  }

  return d_regex.match(dq->ids.du->getHTTPPath());
}

string HTTPPathRegexRule::toString() const
{
  return d_visual;
}

void DOHFrontend::rotateTicketsKey(time_t now)
{
  return d_tlsContext.rotateTicketsKey(now);
}

void DOHFrontend::loadTicketsKeys(const std::string& keyFile)
{
  return d_tlsContext.loadTicketsKeys(keyFile);
}

void DOHFrontend::loadTicketsKey(const std::string& key)
{
  return d_tlsContext.loadTicketsKey(key);
}

void DOHFrontend::handleTicketsKeyRotation()
{
}

std::string DOHFrontend::getNextTicketsKeyRotation() const
{
  return d_tlsContext.getNextTicketsKeyRotation();
}

size_t DOHFrontend::getTicketsKeysCount()
{
  return d_tlsContext.getTicketsKeysCount();
}

void DOHFrontend::reloadCertificates()
{
  if (isHTTPS()) {
    d_tlsContext.setupTLS();
  }
}

void DOHFrontend::setup()
{
  if (isHTTPS()) {
    if (!d_tlsContext.setupTLS()) {
      throw std::runtime_error("Error setting up TLS context for DoH listener on '" + d_tlsContext.d_addr.toStringWithPort());
    }
  }
}

#endif /* HAVE_DNS_OVER_HTTPS */

namespace dnsdist::doh
{
std::optional<PacketBuffer> getPayloadFromPath(const std::string_view& path)
{
  std::optional<PacketBuffer> result{std::nullopt};

  try {
    if (path.size() <= 5) {
      return result;
    }

    auto pos = path.find("?dns=");
    if (pos == string::npos) {
      pos = path.find("&dns=");
    }

    if (pos == string::npos) {
      return result;
    }

    // need to base64url decode this
    string sdns;
    const size_t payloadSize = path.size() - pos - 5;
    size_t neededPadding = 0;
    switch (payloadSize % 4) {
    case 2:
      neededPadding = 2;
      break;
    case 3:
      neededPadding = 1;
      break;
    }
    sdns.reserve(payloadSize + neededPadding);
    sdns = path.substr(pos + 5);
    for (auto& entry : sdns) {
      switch (entry) {
      case '-':
        entry = '+';
        break;
      case '_':
        entry = '/';
        break;
      }
    }

    if (neededPadding != 0) {
      // re-add padding that may have been missing
      sdns.append(neededPadding, '=');
    }

    PacketBuffer decoded;
    /* rough estimate so we hopefully don't need a new allocation later */
    /* We reserve at few additional bytes to be able to add EDNS later */
    const size_t estimate = ((sdns.size() * 3) / 4);
    decoded.reserve(estimate);
    if (B64Decode(sdns, decoded) < 0) {
      return result;
    }

    result = std::move(decoded);
    return result;
  }
  catch (const std::exception& exp) {
    infolog("Exception while decoding base64 payload: %s", exp.what());
  }

  return result;
}
}
