/* 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 mozilla_netwerk_base_proxy_config_h
#define mozilla_netwerk_base_proxy_config_h

#include <map>

#include "nsCRT.h"
#include "nsString.h"
#include "nsTArray.h"

// NOTE: This file is inspired by Chromium's code.
// https://source.chromium.org/chromium/chromium/src/+/main:net/proxy_resolution/proxy_config.h.

namespace mozilla {
namespace net {

// ProxyServer stores the {type, host, port} of a proxy server.
// ProxyServer is immutable.
class ProxyServer final {
 public:
  enum class ProxyType {
    DIRECT = 0,
    HTTP,
    HTTPS,
    SOCKS,
    SOCKS4,
    SOCKS5,
    FTP,
    // DEFAULT is a special type used on windows only.
    DEFAULT
  };

  ProxyServer() = default;

  ProxyServer(ProxyType aType, const nsACString& aHost, int32_t aPort)
      : mType(aType), mHost(aHost), mPort(aPort) {}

  const nsCString& Host() const { return mHost; }

  int32_t Port() const { return mPort; }

  ProxyType Type() const { return mType; }

  void ToHostAndPortStr(nsACString& aOutput) {
    aOutput.Truncate();
    if (mType == ProxyType::DIRECT) {
      return;
    }

    aOutput.Assign(mHost);
    if (mPort != -1) {
      aOutput.Append(':');
      aOutput.AppendInt(mPort);
    }
  }

  bool operator==(const ProxyServer& aOther) const {
    return mType == aOther.mType && mHost == aOther.mHost &&
           mPort == aOther.mPort;
  }

  bool operator!=(const ProxyServer& aOther) const {
    return !(*this == aOther);
  }

 private:
  ProxyType mType{ProxyType::DIRECT};
  nsCString mHost;
  int32_t mPort{-1};
};

// This class includes the information about proxy configuration.
// It contains enabled proxy servers, exception list, and the url of PAC
// script.
class ProxyConfig {
 public:
  struct ProxyRules {
    ProxyRules() = default;
    ~ProxyRules() = default;

    std::map<ProxyServer::ProxyType, ProxyServer> mProxyServers;
  };

  struct ProxyBypassRules {
    ProxyBypassRules() = default;
    ~ProxyBypassRules() = default;

    CopyableTArray<nsCString> mExceptions;
  };

  ProxyConfig() = default;
  ProxyConfig(const ProxyConfig& config);
  ~ProxyConfig() = default;

  ProxyRules& Rules() { return mRules; }

  const ProxyRules& Rules() const { return mRules; }

  ProxyBypassRules& ByPassRules() { return mBypassRules; }

  const ProxyBypassRules& ByPassRules() const { return mBypassRules; }

  void SetPACUrl(const nsACString& aUrl) { mPACUrl = aUrl; }

  const nsCString& PACUrl() const { return mPACUrl; }

  static ProxyServer::ProxyType ToProxyType(const char* aType) {
    if (!aType) {
      return ProxyServer::ProxyType::DIRECT;
    }

    if (nsCRT::strcasecmp(aType, "http") == 0) {
      return ProxyServer::ProxyType::HTTP;
    }
    if (nsCRT::strcasecmp(aType, "https") == 0) {
      return ProxyServer::ProxyType::HTTPS;
    }
    if (nsCRT::strcasecmp(aType, "socks") == 0) {
      return ProxyServer::ProxyType::SOCKS;
    }
    if (nsCRT::strcasecmp(aType, "socks4") == 0) {
      return ProxyServer::ProxyType::SOCKS4;
    }
    if (nsCRT::strcasecmp(aType, "socks5") == 0) {
      return ProxyServer::ProxyType::SOCKS5;
    }
    if (nsCRT::strcasecmp(aType, "ftp") == 0) {
      return ProxyServer::ProxyType::FTP;
    }

    return ProxyServer::ProxyType::DIRECT;
  }

  static void SetProxyResult(const char* aType, const nsACString& aHostPort,
                             nsACString& aResult) {
    aResult.AssignASCII(aType);
    aResult.Append(' ');
    aResult.Append(aHostPort);
  }

  static void SetProxyResultDirect(nsACString& aResult) {
    // For whatever reason, a proxy is not to be used.
    aResult.AssignLiteral("DIRECT");
  }

  static void ProxyStrToResult(const nsACString& aSpecificProxy,
                               const nsACString& aDefaultProxy,
                               const nsACString& aSocksProxy,
                               nsACString& aResult) {
    if (!aSpecificProxy.IsEmpty()) {
      SetProxyResult("PROXY", aSpecificProxy,
                     aResult);  // Protocol-specific proxy.
    } else if (!aDefaultProxy.IsEmpty()) {
      SetProxyResult("PROXY", aDefaultProxy, aResult);  // Default proxy.
    } else if (!aSocksProxy.IsEmpty()) {
      SetProxyResult("SOCKS", aSocksProxy, aResult);  // SOCKS proxy.
    } else {
      SetProxyResultDirect(aResult);  // Direct connection.
    }
  }

  void GetProxyString(const nsACString& aScheme, nsACString& aResult) {
    SetProxyResultDirect(aResult);

    nsAutoCString specificProxy;
    nsAutoCString defaultProxy;
    nsAutoCString socksProxy;
    nsAutoCString prefix;
    ToLowerCase(aScheme, prefix);
    ProxyServer::ProxyType type = ProxyConfig::ToProxyType(prefix.get());
    for (auto& [key, value] : mRules.mProxyServers) {
      // Break the loop if we found a specific proxy.
      if (key == type) {
        value.ToHostAndPortStr(specificProxy);
        break;
      } else if (key == ProxyServer::ProxyType::DEFAULT) {
        value.ToHostAndPortStr(defaultProxy);
      } else if (key == ProxyServer::ProxyType::SOCKS) {
        value.ToHostAndPortStr(socksProxy);
      }
    }

    ProxyStrToResult(specificProxy, defaultProxy, socksProxy, aResult);
  }

 private:
  nsCString mPACUrl;
  ProxyRules mRules;
  ProxyBypassRules mBypassRules;
};

}  // namespace net
}  // namespace mozilla

#endif  // mozilla_netwerk_base_proxy_config_h
