// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/net/firefox_proxy_settings.h"

#include "base/file_util.h"
#include "base/files/file_path.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_tokenizer.h"
#include "base/strings/string_util.h"
#include "base/values.h"
#include "chrome/common/importer/firefox_importer_utils.h"
#include "net/proxy/proxy_config.h"

namespace {

const char* const kNetworkProxyTypeKey = "network.proxy.type";
const char* const kHTTPProxyKey = "network.proxy.http";
const char* const kHTTPProxyPortKey = "network.proxy.http_port";
const char* const kSSLProxyKey = "network.proxy.ssl";
const char* const kSSLProxyPortKey = "network.proxy.ssl_port";
const char* const kFTPProxyKey = "network.proxy.ftp";
const char* const kFTPProxyPortKey = "network.proxy.ftp_port";
const char* const kGopherProxyKey = "network.proxy.gopher";
const char* const kGopherProxyPortKey = "network.proxy.gopher_port";
const char* const kSOCKSHostKey = "network.proxy.socks";
const char* const kSOCKSHostPortKey = "network.proxy.socks_port";
const char* const kSOCKSVersionKey = "network.proxy.socks_version";
const char* const kAutoconfigURL = "network.proxy.autoconfig_url";
const char* const kNoProxyListKey = "network.proxy.no_proxies_on";
const char* const kPrefFileName = "prefs.js";

FirefoxProxySettings::ProxyConfig IntToProxyConfig(int type) {
  switch (type) {
    case 1:
      return FirefoxProxySettings::MANUAL;
    case 2:
      return FirefoxProxySettings::AUTO_FROM_URL;
    case 4:
      return FirefoxProxySettings::AUTO_DETECT;
    case 5:
      return FirefoxProxySettings::SYSTEM;
    default:
      LOG(ERROR) << "Unknown Firefox proxy config type: " << type;
      return FirefoxProxySettings::NO_PROXY;
  }
}

FirefoxProxySettings::SOCKSVersion IntToSOCKSVersion(int type) {
  switch (type) {
    case 4:
      return FirefoxProxySettings::V4;
    case 5:
      return FirefoxProxySettings::V5;
    default:
      LOG(ERROR) << "Unknown Firefox proxy config type: " << type;
      return FirefoxProxySettings::UNKNONW;
  }
}

// Parses the prefs found in the file |pref_file| and puts the key/value pairs
// in |prefs|. Keys are strings, and values can be strings, booleans or
// integers.  Returns true if it succeeded, false otherwise (in which case
// |prefs| is not filled).
// Note: for strings, only valid UTF-8 string values are supported. If a
// key/pair is not valid UTF-8, it is ignored and will not appear in |prefs|.
bool ParsePrefFile(const base::FilePath& pref_file,
                   base::DictionaryValue* prefs) {
  // The string that is before a pref key.
  const std::string kUserPrefString = "user_pref(\"";
  std::string contents;
  if (!base::ReadFileToString(pref_file, &contents))
    return false;

  std::vector<std::string> lines;
  Tokenize(contents, "\n", &lines);

  for (std::vector<std::string>::const_iterator iter = lines.begin();
       iter != lines.end(); ++iter) {
    const std::string& line = *iter;
    size_t start_key = line.find(kUserPrefString);
    if (start_key == std::string::npos)
      continue;  // Could be a comment or a blank line.
    start_key += kUserPrefString.length();
    size_t stop_key = line.find('"', start_key);
    if (stop_key == std::string::npos) {
      LOG(ERROR) << "Invalid key found in Firefox pref file '" <<
          pref_file.value() << "' line is '" << line << "'.";
      continue;
    }
    std::string key = line.substr(start_key, stop_key - start_key);
    size_t start_value = line.find(',', stop_key + 1);
    if (start_value == std::string::npos) {
      LOG(ERROR) << "Invalid value found in Firefox pref file '" <<
          pref_file.value() << "' line is '" << line << "'.";
      continue;
    }
    size_t stop_value = line.find(");", start_value + 1);
    if (stop_value == std::string::npos) {
      LOG(ERROR) << "Invalid value found in Firefox pref file '" <<
          pref_file.value() << "' line is '" << line << "'.";
      continue;
    }
    std::string value = line.substr(start_value + 1,
                                    stop_value - start_value - 1);
    base::TrimWhitespace(value, base::TRIM_ALL, &value);
    // Value could be a boolean.
    bool is_value_true = LowerCaseEqualsASCII(value, "true");
    if (is_value_true || LowerCaseEqualsASCII(value, "false")) {
      prefs->SetBoolean(key, is_value_true);
      continue;
    }

    // Value could be a string.
    if (value.size() >= 2U &&
        value[0] == '"' && value[value.size() - 1] == '"') {
      value = value.substr(1, value.size() - 2);
      // ValueString only accept valid UTF-8.  Simply ignore that entry if it is
      // not UTF-8.
      if (base::IsStringUTF8(value))
        prefs->SetString(key, value);
      else
        VLOG(1) << "Non UTF8 value for key " << key << ", ignored.";
      continue;
    }

    // Or value could be an integer.
    int int_value = 0;
    if (base::StringToInt(value, &int_value)) {
      prefs->SetInteger(key, int_value);
      continue;
    }

    LOG(ERROR) << "Invalid value found in Firefox pref file '"
               << pref_file.value() << "' value is '" << value << "'.";
  }
  return true;
}

}  // namespace

FirefoxProxySettings::FirefoxProxySettings() {
  Reset();
}

FirefoxProxySettings::~FirefoxProxySettings() {
}

void FirefoxProxySettings::Reset() {
  config_type_ = NO_PROXY;
  http_proxy_.clear();
  http_proxy_port_ = 0;
  ssl_proxy_.clear();
  ssl_proxy_port_ = 0;
  ftp_proxy_.clear();
  ftp_proxy_port_ = 0;
  gopher_proxy_.clear();
  gopher_proxy_port_ = 0;
  socks_host_.clear();
  socks_port_ = 0;
  socks_version_ = UNKNONW;
  proxy_bypass_list_.clear();
  autoconfig_url_.clear();
}

// static
bool FirefoxProxySettings::GetSettings(FirefoxProxySettings* settings) {
  DCHECK(settings);
  settings->Reset();

  base::FilePath profile_path = GetFirefoxProfilePath();
  if (profile_path.empty())
    return false;
  base::FilePath pref_file = profile_path.AppendASCII(kPrefFileName);
  return GetSettingsFromFile(pref_file, settings);
}

bool FirefoxProxySettings::ToProxyConfig(net::ProxyConfig* config) {
  switch (config_type()) {
    case NO_PROXY:
      *config = net::ProxyConfig::CreateDirect();
      return true;
    case AUTO_DETECT:
      *config = net::ProxyConfig::CreateAutoDetect();
      return true;
    case AUTO_FROM_URL:
      *config = net::ProxyConfig::CreateFromCustomPacURL(
          GURL(autoconfig_url()));
      return true;
    case SYSTEM:
      // Can't convert this directly to a ProxyConfig.
      return false;
    case MANUAL:
      // Handled outside of the switch (since it is a lot of code.)
      break;
    default:
      NOTREACHED();
      return false;
  }

  // The rest of this funciton is for handling the MANUAL case.
  DCHECK_EQ(MANUAL, config_type());

  *config = net::ProxyConfig();
  config->proxy_rules().type =
      net::ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME;

  if (!http_proxy().empty()) {
    config->proxy_rules().proxies_for_http.SetSingleProxyServer(
        net::ProxyServer(
            net::ProxyServer::SCHEME_HTTP,
            net::HostPortPair(http_proxy(), http_proxy_port())));
  }

  if (!ftp_proxy().empty()) {
    config->proxy_rules().proxies_for_ftp.SetSingleProxyServer(
        net::ProxyServer(
            net::ProxyServer::SCHEME_HTTP,
            net::HostPortPair(ftp_proxy(), ftp_proxy_port())));
  }

  if (!ssl_proxy().empty()) {
    config->proxy_rules().proxies_for_https.SetSingleProxyServer(
        net::ProxyServer(
            net::ProxyServer::SCHEME_HTTP,
            net::HostPortPair(ssl_proxy(), ssl_proxy_port())));
  }

  if (!socks_host().empty()) {
    net::ProxyServer::Scheme proxy_scheme = V5 == socks_version() ?
        net::ProxyServer::SCHEME_SOCKS5 : net::ProxyServer::SCHEME_SOCKS4;

    config->proxy_rules().fallback_proxies.SetSingleProxyServer(
        net::ProxyServer(
            proxy_scheme,
            net::HostPortPair(socks_host(), socks_port())));
  }

  config->proxy_rules().bypass_rules.ParseFromStringUsingSuffixMatching(
      JoinString(proxy_bypass_list_, ';'));

  return true;
}

// static
bool FirefoxProxySettings::GetSettingsFromFile(const base::FilePath& pref_file,
                                               FirefoxProxySettings* settings) {
  base::DictionaryValue dictionary;
  if (!ParsePrefFile(pref_file, &dictionary))
    return false;

  int proxy_type = 0;
  if (!dictionary.GetInteger(kNetworkProxyTypeKey, &proxy_type))
    return true;  // No type means no proxy.

  settings->config_type_ = IntToProxyConfig(proxy_type);
  if (settings->config_type_ == AUTO_FROM_URL) {
    if (!dictionary.GetStringASCII(kAutoconfigURL,
                                   &(settings->autoconfig_url_))) {
      LOG(ERROR) << "Failed to retrieve Firefox proxy autoconfig URL";
    }
    return true;
  }

  if (settings->config_type_ == MANUAL) {
    if (!dictionary.GetStringASCII(kHTTPProxyKey, &(settings->http_proxy_)))
      LOG(ERROR) << "Failed to retrieve Firefox proxy HTTP host";
    if (!dictionary.GetInteger(kHTTPProxyPortKey,
                               &(settings->http_proxy_port_))) {
      LOG(ERROR) << "Failed to retrieve Firefox proxy HTTP port";
    }
    if (!dictionary.GetStringASCII(kSSLProxyKey, &(settings->ssl_proxy_)))
      LOG(ERROR) << "Failed to retrieve Firefox proxy SSL host";
    if (!dictionary.GetInteger(kSSLProxyPortKey, &(settings->ssl_proxy_port_)))
      LOG(ERROR) << "Failed to retrieve Firefox proxy SSL port";
    if (!dictionary.GetStringASCII(kFTPProxyKey, &(settings->ftp_proxy_)))
      LOG(ERROR) << "Failed to retrieve Firefox proxy FTP host";
    if (!dictionary.GetInteger(kFTPProxyPortKey, &(settings->ftp_proxy_port_)))
      LOG(ERROR) << "Failed to retrieve Firefox proxy SSL port";
    if (!dictionary.GetStringASCII(kGopherProxyKey, &(settings->gopher_proxy_)))
      LOG(ERROR) << "Failed to retrieve Firefox proxy gopher host";
    if (!dictionary.GetInteger(kGopherProxyPortKey,
                               &(settings->gopher_proxy_port_))) {
      LOG(ERROR) << "Failed to retrieve Firefox proxy gopher port";
    }
    if (!dictionary.GetStringASCII(kSOCKSHostKey, &(settings->socks_host_)))
      LOG(ERROR) << "Failed to retrieve Firefox SOCKS host";
    if (!dictionary.GetInteger(kSOCKSHostPortKey, &(settings->socks_port_)))
      LOG(ERROR) << "Failed to retrieve Firefox SOCKS port";
    int socks_version;
    if (dictionary.GetInteger(kSOCKSVersionKey, &socks_version))
      settings->socks_version_ = IntToSOCKSVersion(socks_version);

    std::string proxy_bypass;
    if (dictionary.GetStringASCII(kNoProxyListKey, &proxy_bypass) &&
        !proxy_bypass.empty()) {
      base::StringTokenizer string_tok(proxy_bypass, ",");
      while (string_tok.GetNext()) {
        std::string token = string_tok.token();
        base::TrimWhitespaceASCII(token, base::TRIM_ALL, &token);
        if (!token.empty())
          settings->proxy_bypass_list_.push_back(token);
      }
    }
  }
  return true;
}