// Copyright (c) 2011 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/chromeos/options/wifi_config_model.h"

#include <algorithm>

#include "base/utf_string_conversions.h"
#include "chrome/browser/browser_process.h"  // g_browser_process
#include "chrome/common/net/x509_certificate_model.h"
#include "net/base/cert_database.h"
#include "net/base/x509_certificate.h"
#include "ui/base/l10n/l10n_util_collator.h"  // CompareString16WithCollator
#include "unicode/coll.h"  // icu::Collator

namespace chromeos {

namespace {

typedef scoped_refptr<net::X509Certificate> X509CertificateRefPtr;

// Root CA certificates that are built into Chrome use this token name.
const char* const kRootCertificateTokenName = "Builtin Object Token";

// Returns a user-visible name for a given certificate.
string16 GetCertDisplayString(const net::X509Certificate* cert) {
  DCHECK(cert);
  std::string name_or_nick =
      x509_certificate_model::GetCertNameOrNickname(cert->os_cert_handle());
  return UTF8ToUTF16(name_or_nick);
}

// Comparison functor for locale-sensitive sorting of certificates by name.
class CertNameComparator {
 public:
  explicit CertNameComparator(icu::Collator* collator)
      : collator_(collator) {
  }

  bool operator()(const X509CertificateRefPtr& lhs,
                  const X509CertificateRefPtr& rhs) const {
    string16 lhs_name = GetCertDisplayString(lhs);
    string16 rhs_name = GetCertDisplayString(rhs);
    if (collator_ == NULL)
      return lhs_name < rhs_name;
    return l10n_util::CompareString16WithCollator(
        collator_, lhs_name, rhs_name) == UCOL_LESS;
  }

 private:
  icu::Collator* collator_;
};

}  // namespace

WifiConfigModel::WifiConfigModel() {
}

WifiConfigModel::~WifiConfigModel() {
}

void WifiConfigModel::UpdateCertificates() {
  // CertDatabase and its wrappers do not have random access to certificates,
  // so build filtered lists once.
  net::CertificateList cert_list;
  cert_db_.ListCerts(&cert_list);
  for (net::CertificateList::const_iterator it = cert_list.begin();
       it != cert_list.end();
       ++it) {
    net::X509Certificate* cert = it->get();
    net::X509Certificate::OSCertHandle cert_handle = cert->os_cert_handle();
    net::CertType type = x509_certificate_model::GetType(cert_handle);
    switch (type) {
      case net::USER_CERT:
        user_certs_.push_back(*it);
        break;
      case net::CA_CERT: {
        // Exclude root CA certificates that are built into Chrome.
        std::string token_name =
            x509_certificate_model::GetTokenName(cert_handle);
        if (token_name != kRootCertificateTokenName)
          server_ca_certs_.push_back(*it);
        break;
      }
      default:
        // We only care about those two types.
        break;
    }
  }

  // Perform locale-sensitive sorting by certificate name.
  scoped_ptr<icu::Collator> collator;
  UErrorCode error = U_ZERO_ERROR;
  collator.reset(
      icu::Collator::createInstance(
          icu::Locale(g_browser_process->GetApplicationLocale().c_str()),
          error));
  if (U_FAILURE(error))
    collator.reset(NULL);
  CertNameComparator cert_name_comparator(collator.get());
  std::sort(user_certs_.begin(), user_certs_.end(), cert_name_comparator);
  std::sort(server_ca_certs_.begin(), server_ca_certs_.end(),
            cert_name_comparator);
}

int WifiConfigModel::GetUserCertCount() const {
  return static_cast<int>(user_certs_.size());
}

string16 WifiConfigModel::GetUserCertName(int cert_index) const {
  DCHECK(cert_index >= 0);
  DCHECK(cert_index < static_cast<int>(user_certs_.size()));
  net::X509Certificate* cert = user_certs_[cert_index].get();
  return GetCertDisplayString(cert);
}

std::string WifiConfigModel::GetUserCertPkcs11Id(int cert_index) const {
  DCHECK(cert_index >= 0);
  DCHECK(cert_index < static_cast<int>(user_certs_.size()));
  net::X509Certificate* cert = user_certs_[cert_index].get();
  net::X509Certificate::OSCertHandle cert_handle = cert->os_cert_handle();
  return x509_certificate_model::GetPkcs11Id(cert_handle);
}

int WifiConfigModel::GetUserCertIndex(const std::string& pkcs11_id) const {
  // The list of user certs is small, so just test each one.
  for (int index = 0; index < static_cast<int>(user_certs_.size()); ++index) {
    net::X509Certificate* cert = user_certs_[index].get();
    net::X509Certificate::OSCertHandle cert_handle = cert->os_cert_handle();
    std::string id = x509_certificate_model::GetPkcs11Id(cert_handle);
    if (id == pkcs11_id)
      return index;
  }
  // Not found.
  return -1;
}

int WifiConfigModel::GetServerCaCertCount() const {
  return static_cast<int>(server_ca_certs_.size());
}

string16 WifiConfigModel::GetServerCaCertName(int cert_index) const {
  DCHECK(cert_index >= 0);
  DCHECK(cert_index < static_cast<int>(server_ca_certs_.size()));
  net::X509Certificate* cert = server_ca_certs_[cert_index].get();
  return GetCertDisplayString(cert);
}

std::string WifiConfigModel::GetServerCaCertNssNickname(int cert_index) const {
  DCHECK(cert_index >= 0);
  DCHECK(cert_index < static_cast<int>(server_ca_certs_.size()));
  net::X509Certificate* cert = server_ca_certs_[cert_index].get();
  net::X509Certificate::OSCertHandle cert_handle = cert->os_cert_handle();
  return x509_certificate_model::GetNickname(cert_handle);
}

int WifiConfigModel::GetServerCaCertIndex(
    const std::string& nss_nickname) const {
  // List of server certs is small, so just test each one.
  for (int i = 0; i < static_cast<int>(server_ca_certs_.size()); ++i) {
    net::X509Certificate* cert = server_ca_certs_[i].get();
    net::X509Certificate::OSCertHandle cert_handle = cert->os_cert_handle();
    std::string nickname = x509_certificate_model::GetNickname(cert_handle);
    if (nickname == nss_nickname)
      return i;
  }
  // Not found.
  return -1;
}

}  // namespace chromeos