// Copyright (c) 2012 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 "chromeos/network/onc/onc_utils.h"

#include "base/base64.h"
#include "base/json/json_reader.h"
#include "base/logging.h"
#include "base/metrics/histogram.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/values.h"
#include "chromeos/network/network_event_log.h"
#include "chromeos/network/onc/onc_mapper.h"
#include "chromeos/network/onc/onc_signature.h"
#include "chromeos/network/onc/onc_utils.h"
#include "chromeos/network/onc/onc_validator.h"
#include "components/device_event_log/device_event_log.h"
#include "components/proxy_config/proxy_config_dictionary.h"
#include "crypto/encryptor.h"
#include "crypto/hmac.h"
#include "crypto/symmetric_key.h"
#include "net/base/host_port_pair.h"
#include "net/cert/pem_tokenizer.h"
#include "net/cert/x509_certificate.h"
#include "net/proxy/proxy_bypass_rules.h"
#include "net/proxy/proxy_config.h"
#include "net/proxy/proxy_server.h"
#include "url/url_constants.h"

using namespace ::onc;

namespace chromeos {
namespace onc {

namespace {

const char kUnableToDecrypt[] = "Unable to decrypt encrypted ONC";
const char kUnableToDecode[] = "Unable to decode encrypted ONC";

}  // namespace

const char kEmptyUnencryptedConfiguration[] =
    "{\"Type\":\"UnencryptedConfiguration\",\"NetworkConfigurations\":[],"
    "\"Certificates\":[]}";

scoped_ptr<base::DictionaryValue> ReadDictionaryFromJson(
    const std::string& json) {
  std::string error;
  base::Value* root = base::JSONReader::DeprecatedReadAndReturnError(
      json, base::JSON_ALLOW_TRAILING_COMMAS, nullptr, &error);

  base::DictionaryValue* dict_ptr = nullptr;
  if (!root || !root->GetAsDictionary(&dict_ptr)) {
    NET_LOG(ERROR) << "Invalid JSON Dictionary: " << error;
    delete root;
  }

  return make_scoped_ptr(dict_ptr);
}

scoped_ptr<base::DictionaryValue> Decrypt(const std::string& passphrase,
                                          const base::DictionaryValue& root) {
  const int kKeySizeInBits = 256;
  const int kMaxIterationCount = 500000;
  std::string onc_type;
  std::string initial_vector;
  std::string salt;
  std::string cipher;
  std::string stretch_method;
  std::string hmac_method;
  std::string hmac;
  int iterations;
  std::string ciphertext;

  if (!root.GetString(encrypted::kCiphertext, &ciphertext) ||
      !root.GetString(encrypted::kCipher, &cipher) ||
      !root.GetString(encrypted::kHMAC, &hmac) ||
      !root.GetString(encrypted::kHMACMethod, &hmac_method) ||
      !root.GetString(encrypted::kIV, &initial_vector) ||
      !root.GetInteger(encrypted::kIterations, &iterations) ||
      !root.GetString(encrypted::kSalt, &salt) ||
      !root.GetString(encrypted::kStretch, &stretch_method) ||
      !root.GetString(toplevel_config::kType, &onc_type) ||
      onc_type != toplevel_config::kEncryptedConfiguration) {
    NET_LOG(ERROR) << "Encrypted ONC malformed.";
    return nullptr;
  }

  if (hmac_method != encrypted::kSHA1 ||
      cipher != encrypted::kAES256 ||
      stretch_method != encrypted::kPBKDF2) {
    NET_LOG(ERROR) << "Encrypted ONC unsupported encryption scheme.";
    return nullptr;
  }

  // Make sure iterations != 0, since that's not valid.
  if (iterations == 0) {
    NET_LOG(ERROR) << kUnableToDecrypt;
    return nullptr;
  }

  // Simply a sanity check to make sure we can't lock up the machine
  // for too long with a huge number (or a negative number).
  if (iterations < 0 || iterations > kMaxIterationCount) {
    NET_LOG(ERROR) << "Too many iterations in encrypted ONC";
    return nullptr;
  }

  if (!base::Base64Decode(salt, &salt)) {
    NET_LOG(ERROR) << kUnableToDecode;
    return nullptr;
  }

  scoped_ptr<crypto::SymmetricKey> key(
      crypto::SymmetricKey::DeriveKeyFromPassword(crypto::SymmetricKey::AES,
                                                  passphrase,
                                                  salt,
                                                  iterations,
                                                  kKeySizeInBits));

  if (!base::Base64Decode(initial_vector, &initial_vector)) {
    NET_LOG(ERROR) << kUnableToDecode;
    return nullptr;
  }
  if (!base::Base64Decode(ciphertext, &ciphertext)) {
    NET_LOG(ERROR) << kUnableToDecode;
    return nullptr;
  }
  if (!base::Base64Decode(hmac, &hmac)) {
    NET_LOG(ERROR) << kUnableToDecode;
    return nullptr;
  }

  crypto::HMAC hmac_verifier(crypto::HMAC::SHA1);
  if (!hmac_verifier.Init(key.get()) ||
      !hmac_verifier.Verify(ciphertext, hmac)) {
    NET_LOG(ERROR) << kUnableToDecrypt;
    return nullptr;
  }

  crypto::Encryptor decryptor;
  if (!decryptor.Init(key.get(), crypto::Encryptor::CBC, initial_vector))  {
    NET_LOG(ERROR) << kUnableToDecrypt;
    return nullptr;
  }

  std::string plaintext;
  if (!decryptor.Decrypt(ciphertext, &plaintext)) {
    NET_LOG(ERROR) << kUnableToDecrypt;
    return nullptr;
  }

  scoped_ptr<base::DictionaryValue> new_root =
      ReadDictionaryFromJson(plaintext);
  if (!new_root) {
    NET_LOG(ERROR) << "Property dictionary malformed.";
    return nullptr;
  }

  return new_root.Pass();
}

std::string GetSourceAsString(ONCSource source) {
  switch (source) {
    case ONC_SOURCE_UNKNOWN:
      return "unknown";
    case ONC_SOURCE_NONE:
      return "none";
    case ONC_SOURCE_DEVICE_POLICY:
      return "device policy";
    case ONC_SOURCE_USER_POLICY:
      return "user policy";
    case ONC_SOURCE_USER_IMPORT:
      return "user import";
  }
  NOTREACHED() << "unknown ONC source " << source;
  return "unknown";
}

void ExpandField(const std::string& fieldname,
                 const StringSubstitution& substitution,
                 base::DictionaryValue* onc_object) {
  std::string user_string;
  if (!onc_object->GetStringWithoutPathExpansion(fieldname, &user_string))
    return;

  std::string login_id;
  if (substitution.GetSubstitute(substitutes::kLoginIDField, &login_id)) {
    base::ReplaceSubstringsAfterOffset(&user_string, 0,
                                       substitutes::kLoginIDField,
                                       login_id);
  }

  std::string email;
  if (substitution.GetSubstitute(substitutes::kEmailField, &email)) {
    base::ReplaceSubstringsAfterOffset(&user_string, 0,
                                       substitutes::kEmailField,
                                       email);
  }

  onc_object->SetStringWithoutPathExpansion(fieldname, user_string);
}

void ExpandStringsInOncObject(
    const OncValueSignature& signature,
    const StringSubstitution& substitution,
    base::DictionaryValue* onc_object) {
  if (&signature == &kEAPSignature) {
    ExpandField(eap::kAnonymousIdentity, substitution, onc_object);
    ExpandField(eap::kIdentity, substitution, onc_object);
  } else if (&signature == &kL2TPSignature ||
             &signature == &kOpenVPNSignature) {
    ExpandField(vpn::kUsername, substitution, onc_object);
  }

  // Recurse into nested objects.
  for (base::DictionaryValue::Iterator it(*onc_object); !it.IsAtEnd();
       it.Advance()) {
    base::DictionaryValue* inner_object = nullptr;
    if (!onc_object->GetDictionaryWithoutPathExpansion(it.key(), &inner_object))
      continue;

    const OncFieldSignature* field_signature =
        GetFieldSignature(signature, it.key());
    if (!field_signature)
      continue;

    ExpandStringsInOncObject(*field_signature->value_signature,
                             substitution, inner_object);
  }
}

void ExpandStringsInNetworks(const StringSubstitution& substitution,
                             base::ListValue* network_configs) {
  for (base::Value* entry : *network_configs) {
    base::DictionaryValue* network = nullptr;
    entry->GetAsDictionary(&network);
    DCHECK(network);
    ExpandStringsInOncObject(
        kNetworkConfigurationSignature, substitution, network);
  }
}

void FillInHexSSIDFieldsInOncObject(const OncValueSignature& signature,
                                    base::DictionaryValue* onc_object) {
  if (&signature == &kWiFiSignature)
    FillInHexSSIDField(onc_object);

  // Recurse into nested objects.
  for (base::DictionaryValue::Iterator it(*onc_object); !it.IsAtEnd();
       it.Advance()) {
    base::DictionaryValue* inner_object = nullptr;
    if (!onc_object->GetDictionaryWithoutPathExpansion(it.key(), &inner_object))
      continue;

    const OncFieldSignature* field_signature =
        GetFieldSignature(signature, it.key());
    if (!field_signature)
      continue;

    FillInHexSSIDFieldsInOncObject(*field_signature->value_signature,
                                   inner_object);
  }
}

void FillInHexSSIDField(base::DictionaryValue* wifi_fields) {
  std::string ssid_string;
  if (wifi_fields->HasKey(::onc::wifi::kHexSSID) ||
      !wifi_fields->GetStringWithoutPathExpansion(::onc::wifi::kSSID,
                                                  &ssid_string)) {
    return;
  }
  if (ssid_string.empty()) {
    NET_LOG(ERROR) << "Found empty SSID field.";
    return;
  }
  wifi_fields->SetStringWithoutPathExpansion(
      ::onc::wifi::kHexSSID,
      base::HexEncode(ssid_string.c_str(), ssid_string.size()));
}

namespace {

class OncMaskValues : public Mapper {
 public:
  static scoped_ptr<base::DictionaryValue> Mask(
      const OncValueSignature& signature,
      const base::DictionaryValue& onc_object,
      const std::string& mask) {
    OncMaskValues masker(mask);
    bool unused_error;
    return masker.MapObject(signature, onc_object, &unused_error);
  }

 protected:
  explicit OncMaskValues(const std::string& mask)
      : mask_(mask) {
  }

  scoped_ptr<base::Value> MapField(const std::string& field_name,
                                   const OncValueSignature& object_signature,
                                   const base::Value& onc_value,
                                   bool* found_unknown_field,
                                   bool* error) override {
    if (FieldIsCredential(object_signature, field_name)) {
      return scoped_ptr<base::Value>(new base::StringValue(mask_));
    } else {
      return Mapper::MapField(field_name, object_signature, onc_value,
                              found_unknown_field, error);
    }
  }

  // Mask to insert in place of the sensitive values.
  std::string mask_;
};

}  // namespace

scoped_ptr<base::DictionaryValue> MaskCredentialsInOncObject(
    const OncValueSignature& signature,
    const base::DictionaryValue& onc_object,
    const std::string& mask) {
  return OncMaskValues::Mask(signature, onc_object, mask);
}

namespace {

std::string DecodePEM(const std::string& pem_encoded) {
  // The PEM block header used for DER certificates
  const char kCertificateHeader[] = "CERTIFICATE";

  // This is an older PEM marker for DER certificates.
  const char kX509CertificateHeader[] = "X509 CERTIFICATE";

  std::vector<std::string> pem_headers;
  pem_headers.push_back(kCertificateHeader);
  pem_headers.push_back(kX509CertificateHeader);

  net::PEMTokenizer pem_tokenizer(pem_encoded, pem_headers);
  std::string decoded;
  if (pem_tokenizer.GetNext()) {
    decoded = pem_tokenizer.data();
  } else {
    // If we failed to read the data as a PEM file, then try plain base64 decode
    // in case the PEM marker strings are missing. For this to work, there has
    // to be no white space, and it has to only contain the base64-encoded data.
    if (!base::Base64Decode(pem_encoded, &decoded)) {
      LOG(ERROR) << "Unable to base64 decode X509 data: " << pem_encoded;
      return std::string();
    }
  }
  return decoded;
}

CertPEMsByGUIDMap GetServerAndCACertsByGUID(
    const base::ListValue& certificates) {
  CertPEMsByGUIDMap certs_by_guid;
  for (const base::Value* entry : certificates) {
    const base::DictionaryValue* cert = nullptr;
    entry->GetAsDictionary(&cert);

    std::string guid;
    cert->GetStringWithoutPathExpansion(certificate::kGUID, &guid);
    std::string cert_type;
    cert->GetStringWithoutPathExpansion(certificate::kType, &cert_type);
    if (cert_type != certificate::kServer &&
        cert_type != certificate::kAuthority) {
      continue;
    }
    std::string x509_data;
    cert->GetStringWithoutPathExpansion(certificate::kX509, &x509_data);

    std::string der = DecodePEM(x509_data);
    std::string pem;
    if (der.empty() || !net::X509Certificate::GetPEMEncodedFromDER(der, &pem)) {
      LOG(ERROR) << "Certificate with GUID " << guid
                 << " is not in PEM encoding.";
      continue;
    }
    certs_by_guid[guid] = pem;
  }

  return certs_by_guid;
}

void FillInHexSSIDFieldsInNetworks(base::ListValue* network_configs) {
  for (base::Value* entry : *network_configs) {
    base::DictionaryValue* network = nullptr;
    entry->GetAsDictionary(&network);
    DCHECK(network);
    FillInHexSSIDFieldsInOncObject(kNetworkConfigurationSignature, network);
  }
}

}  // namespace

bool ParseAndValidateOncForImport(const std::string& onc_blob,
                                  ONCSource onc_source,
                                  const std::string& passphrase,
                                  base::ListValue* network_configs,
                                  base::DictionaryValue* global_network_config,
                                  base::ListValue* certificates) {
  network_configs->Clear();
  global_network_config->Clear();
  certificates->Clear();
  if (onc_blob.empty())
    return true;

  scoped_ptr<base::DictionaryValue> toplevel_onc =
      ReadDictionaryFromJson(onc_blob);
  if (!toplevel_onc) {
    LOG(ERROR) << "ONC loaded from " << GetSourceAsString(onc_source)
               << " is not a valid JSON dictionary.";
    return false;
  }

  // Check and see if this is an encrypted ONC file. If so, decrypt it.
  std::string onc_type;
  toplevel_onc->GetStringWithoutPathExpansion(toplevel_config::kType,
                                              &onc_type);
  if (onc_type == toplevel_config::kEncryptedConfiguration) {
    toplevel_onc = Decrypt(passphrase, *toplevel_onc);
    if (!toplevel_onc) {
      LOG(ERROR) << "Couldn't decrypt the ONC from "
                 << GetSourceAsString(onc_source);
      return false;
    }
  }

  bool from_policy = (onc_source == ONC_SOURCE_USER_POLICY ||
                      onc_source == ONC_SOURCE_DEVICE_POLICY);

  // Validate the ONC dictionary. We are liberal and ignore unknown field
  // names and ignore invalid field names in kRecommended arrays.
  Validator validator(false,  // Ignore unknown fields.
                      false,  // Ignore invalid recommended field names.
                      true,   // Fail on missing fields.
                      from_policy);
  validator.SetOncSource(onc_source);

  Validator::Result validation_result;
  toplevel_onc = validator.ValidateAndRepairObject(
      &kToplevelConfigurationSignature,
      *toplevel_onc,
      &validation_result);

  if (from_policy) {
    UMA_HISTOGRAM_BOOLEAN("Enterprise.ONC.PolicyValidation",
                          validation_result == Validator::VALID);
  }

  bool success = true;
  if (validation_result == Validator::VALID_WITH_WARNINGS) {
    LOG(WARNING) << "ONC from " << GetSourceAsString(onc_source)
                 << " produced warnings.";
    success = false;
  } else if (validation_result == Validator::INVALID || !toplevel_onc) {
    LOG(ERROR) << "ONC from " << GetSourceAsString(onc_source)
               << " is invalid and couldn't be repaired.";
    return false;
  }

  base::ListValue* validated_certs = nullptr;
  if (toplevel_onc->GetListWithoutPathExpansion(toplevel_config::kCertificates,
                                                &validated_certs)) {
    certificates->Swap(validated_certs);
  }

  base::ListValue* validated_networks = nullptr;
  if (toplevel_onc->GetListWithoutPathExpansion(
          toplevel_config::kNetworkConfigurations, &validated_networks)) {
    FillInHexSSIDFieldsInNetworks(validated_networks);

    CertPEMsByGUIDMap server_and_ca_certs =
        GetServerAndCACertsByGUID(*certificates);

    if (!ResolveServerCertRefsInNetworks(server_and_ca_certs,
                                         validated_networks)) {
      LOG(ERROR) << "Some certificate references in the ONC policy for source "
                 << GetSourceAsString(onc_source) << " could not be resolved.";
      success = false;
    }

    network_configs->Swap(validated_networks);
  }

  base::DictionaryValue* validated_global_config = nullptr;
  if (toplevel_onc->GetDictionaryWithoutPathExpansion(
          toplevel_config::kGlobalNetworkConfiguration,
          &validated_global_config)) {
    global_network_config->Swap(validated_global_config);
  }

  return success;
}

scoped_refptr<net::X509Certificate> DecodePEMCertificate(
    const std::string& pem_encoded) {
  std::string decoded = DecodePEM(pem_encoded);
  scoped_refptr<net::X509Certificate> cert =
      net::X509Certificate::CreateFromBytes(decoded.data(), decoded.size());
  LOG_IF(ERROR, !cert.get()) << "Couldn't create certificate from X509 data: "
                             << decoded;
  return cert;
}

namespace {

bool GUIDRefToPEMEncoding(const CertPEMsByGUIDMap& certs_by_guid,
                          const std::string& guid_ref,
                          std::string* pem_encoded) {
  CertPEMsByGUIDMap::const_iterator it = certs_by_guid.find(guid_ref);
  if (it == certs_by_guid.end()) {
    LOG(ERROR) << "Couldn't resolve certificate reference " << guid_ref;
    return false;
  }
  *pem_encoded = it->second;
  if (pem_encoded->empty()) {
    LOG(ERROR) << "Couldn't PEM-encode certificate with GUID " << guid_ref;
    return false;
  }
  return true;
}

bool ResolveSingleCertRef(const CertPEMsByGUIDMap& certs_by_guid,
                          const std::string& key_guid_ref,
                          const std::string& key_pem,
                          base::DictionaryValue* onc_object) {
  std::string guid_ref;
  if (!onc_object->GetStringWithoutPathExpansion(key_guid_ref, &guid_ref))
    return true;

  std::string pem_encoded;
  if (!GUIDRefToPEMEncoding(certs_by_guid, guid_ref, &pem_encoded))
    return false;

  onc_object->RemoveWithoutPathExpansion(key_guid_ref, nullptr);
  onc_object->SetStringWithoutPathExpansion(key_pem, pem_encoded);
  return true;
}

bool ResolveCertRefList(const CertPEMsByGUIDMap& certs_by_guid,
                        const std::string& key_guid_ref_list,
                        const std::string& key_pem_list,
                        base::DictionaryValue* onc_object) {
  const base::ListValue* guid_ref_list = nullptr;
  if (!onc_object->GetListWithoutPathExpansion(key_guid_ref_list,
                                               &guid_ref_list)) {
    return true;
  }

  scoped_ptr<base::ListValue> pem_list(new base::ListValue);
  for (const base::Value* entry : *guid_ref_list) {
    std::string guid_ref;
    entry->GetAsString(&guid_ref);

    std::string pem_encoded;
    if (!GUIDRefToPEMEncoding(certs_by_guid, guid_ref, &pem_encoded))
      return false;

    pem_list->AppendString(pem_encoded);
  }

  onc_object->RemoveWithoutPathExpansion(key_guid_ref_list, nullptr);
  onc_object->SetWithoutPathExpansion(key_pem_list, pem_list.release());
  return true;
}

bool ResolveSingleCertRefToList(const CertPEMsByGUIDMap& certs_by_guid,
                                const std::string& key_guid_ref,
                                const std::string& key_pem_list,
                                base::DictionaryValue* onc_object) {
  std::string guid_ref;
  if (!onc_object->GetStringWithoutPathExpansion(key_guid_ref, &guid_ref))
    return true;

  std::string pem_encoded;
  if (!GUIDRefToPEMEncoding(certs_by_guid, guid_ref, &pem_encoded))
    return false;

  scoped_ptr<base::ListValue> pem_list(new base::ListValue);
  pem_list->AppendString(pem_encoded);
  onc_object->RemoveWithoutPathExpansion(key_guid_ref, nullptr);
  onc_object->SetWithoutPathExpansion(key_pem_list, pem_list.release());
  return true;
}

// Resolves the reference list at |key_guid_refs| if present and otherwise the
// single reference at |key_guid_ref|. Returns whether the respective resolving
// was successful.
bool ResolveCertRefsOrRefToList(const CertPEMsByGUIDMap& certs_by_guid,
                                const std::string& key_guid_refs,
                                const std::string& key_guid_ref,
                                const std::string& key_pem_list,
                                base::DictionaryValue* onc_object) {
  if (onc_object->HasKey(key_guid_refs)) {
    if (onc_object->HasKey(key_guid_ref)) {
      LOG(ERROR) << "Found both " << key_guid_refs << " and " << key_guid_ref
                 << ". Ignoring and removing the latter.";
      onc_object->RemoveWithoutPathExpansion(key_guid_ref, nullptr);
    }
    return ResolveCertRefList(
        certs_by_guid, key_guid_refs, key_pem_list, onc_object);
  }

  // Only resolve |key_guid_ref| if |key_guid_refs| isn't present.
  return ResolveSingleCertRefToList(
      certs_by_guid, key_guid_ref, key_pem_list, onc_object);
}

bool ResolveServerCertRefsInObject(const CertPEMsByGUIDMap& certs_by_guid,
                                   const OncValueSignature& signature,
                                   base::DictionaryValue* onc_object) {
  if (&signature == &kCertificatePatternSignature) {
    if (!ResolveCertRefList(certs_by_guid,
                            client_cert::kIssuerCARef,
                            client_cert::kIssuerCAPEMs,
                            onc_object)) {
      return false;
    }
  } else if (&signature == &kEAPSignature) {
    if (!ResolveCertRefsOrRefToList(certs_by_guid,
                                    eap::kServerCARefs,
                                    eap::kServerCARef,
                                    eap::kServerCAPEMs,
                                    onc_object)) {
      return false;
    }
  } else if (&signature == &kIPsecSignature) {
    if (!ResolveCertRefsOrRefToList(certs_by_guid,
                                    ipsec::kServerCARefs,
                                    ipsec::kServerCARef,
                                    ipsec::kServerCAPEMs,
                                    onc_object)) {
      return false;
    }
  } else if (&signature == &kIPsecSignature ||
             &signature == &kOpenVPNSignature) {
    if (!ResolveSingleCertRef(certs_by_guid,
                              openvpn::kServerCertRef,
                              openvpn::kServerCertPEM,
                              onc_object) ||
        !ResolveCertRefsOrRefToList(certs_by_guid,
                                    openvpn::kServerCARefs,
                                    openvpn::kServerCARef,
                                    openvpn::kServerCAPEMs,
                                    onc_object)) {
      return false;
    }
  }

  // Recurse into nested objects.
  for (base::DictionaryValue::Iterator it(*onc_object); !it.IsAtEnd();
       it.Advance()) {
    base::DictionaryValue* inner_object = nullptr;
    if (!onc_object->GetDictionaryWithoutPathExpansion(it.key(), &inner_object))
      continue;

    const OncFieldSignature* field_signature =
        GetFieldSignature(signature, it.key());
    if (!field_signature)
      continue;

    if (!ResolveServerCertRefsInObject(certs_by_guid,
                                       *field_signature->value_signature,
                                       inner_object)) {
      return false;
    }
  }
  return true;
}

}  // namespace

bool ResolveServerCertRefsInNetworks(const CertPEMsByGUIDMap& certs_by_guid,
                                     base::ListValue* network_configs) {
  bool success = true;
  for (base::ListValue::iterator it = network_configs->begin();
       it != network_configs->end(); ) {
    base::DictionaryValue* network = nullptr;
    (*it)->GetAsDictionary(&network);
    if (!ResolveServerCertRefsInNetwork(certs_by_guid, network)) {
      std::string guid;
      network->GetStringWithoutPathExpansion(network_config::kGUID, &guid);
      // This might happen even with correct validation, if the referenced
      // certificate couldn't be imported.
      LOG(ERROR) << "Couldn't resolve some certificate reference of network "
                 << guid;
      it = network_configs->Erase(it, nullptr);
      success = false;
      continue;
    }
    ++it;
  }
  return success;
}

bool ResolveServerCertRefsInNetwork(const CertPEMsByGUIDMap& certs_by_guid,
                                    base::DictionaryValue* network_config) {
  return ResolveServerCertRefsInObject(certs_by_guid,
                                       kNetworkConfigurationSignature,
                                       network_config);
}

NetworkTypePattern NetworkTypePatternFromOncType(const std::string& type) {
  if (type == ::onc::network_type::kAllTypes)
    return NetworkTypePattern::Default();
  if (type == ::onc::network_type::kCellular)
    return NetworkTypePattern::Cellular();
  if (type == ::onc::network_type::kEthernet)
    return NetworkTypePattern::Ethernet();
  if (type == ::onc::network_type::kVPN)
    return NetworkTypePattern::VPN();
  if (type == ::onc::network_type::kWiFi)
    return NetworkTypePattern::WiFi();
  if (type == ::onc::network_type::kWimax)
    return NetworkTypePattern::Wimax();
  if (type == ::onc::network_type::kWireless)
    return NetworkTypePattern::Wireless();
  NOTREACHED() << "Unrecognized ONC type: " << type;
  return NetworkTypePattern::Default();
}

bool IsRecommendedValue(const base::DictionaryValue* onc,
                        const std::string& property_key) {
  std::string property_basename, recommended_property_key;
  size_t pos = property_key.find_last_of('.');
  if (pos != std::string::npos) {
    // 'WiFi.AutoConnect' -> 'AutoConnect', 'WiFi.Recommended'
    property_basename = property_key.substr(pos + 1);
    recommended_property_key =
        property_key.substr(0, pos + 1) + ::onc::kRecommended;
  } else {
    // 'Name' -> 'Name', 'Recommended'
    property_basename = property_key;
    recommended_property_key = ::onc::kRecommended;
  }

  const base::ListValue* recommended_keys = nullptr;
  return (onc->GetList(recommended_property_key, &recommended_keys) &&
          recommended_keys->Find(base::StringValue(property_basename)) !=
          recommended_keys->end());
}

namespace {

const char kDirectScheme[] = "direct";
const char kQuicScheme[] = "quic";
const char kSocksScheme[] = "socks";
const char kSocks4Scheme[] = "socks4";
const char kSocks5Scheme[] = "socks5";

net::ProxyServer ConvertOncProxyLocationToHostPort(
    net::ProxyServer::Scheme default_proxy_scheme,
    const base::DictionaryValue& onc_proxy_location) {
  std::string host;
  onc_proxy_location.GetStringWithoutPathExpansion(::onc::proxy::kHost, &host);
  // Parse |host| according to the format [<scheme>"://"]<server>[":"<port>].
  net::ProxyServer proxy_server =
      net::ProxyServer::FromURI(host, default_proxy_scheme);
  int port = 0;
  onc_proxy_location.GetIntegerWithoutPathExpansion(::onc::proxy::kPort, &port);

  // Replace the port parsed from |host| by the provided |port|.
  return net::ProxyServer(
      proxy_server.scheme(),
      net::HostPortPair(proxy_server.host_port_pair().host(),
                        static_cast<uint16>(port)));
}

void AppendProxyServerForScheme(const base::DictionaryValue& onc_manual,
                                const std::string& onc_scheme,
                                std::string* spec) {
  const base::DictionaryValue* onc_proxy_location = nullptr;
  if (!onc_manual.GetDictionaryWithoutPathExpansion(onc_scheme,
                                                    &onc_proxy_location)) {
    return;
  }

  net::ProxyServer::Scheme default_proxy_scheme = net::ProxyServer::SCHEME_HTTP;
  std::string url_scheme;
  if (onc_scheme == ::onc::proxy::kFtp) {
    url_scheme = url::kFtpScheme;
  } else if (onc_scheme == ::onc::proxy::kHttp) {
    url_scheme = url::kHttpScheme;
  } else if (onc_scheme == ::onc::proxy::kHttps) {
    url_scheme = url::kHttpsScheme;
  } else if (onc_scheme == ::onc::proxy::kSocks) {
    default_proxy_scheme = net::ProxyServer::SCHEME_SOCKS4;
    url_scheme = kSocksScheme;
  } else {
    NOTREACHED();
  }

  net::ProxyServer proxy_server = ConvertOncProxyLocationToHostPort(
      default_proxy_scheme, *onc_proxy_location);

  ProxyConfigDictionary::EncodeAndAppendProxyServer(url_scheme, proxy_server,
                                                    spec);
}

net::ProxyBypassRules ConvertOncExcludeDomainsToBypassRules(
    const base::ListValue& onc_exclude_domains) {
  net::ProxyBypassRules rules;
  for (base::ListValue::const_iterator it = onc_exclude_domains.begin();
       it != onc_exclude_domains.end(); ++it) {
    std::string rule;
    (*it)->GetAsString(&rule);
    rules.AddRuleFromString(rule);
  }
  return rules;
}

std::string SchemeToString(net::ProxyServer::Scheme scheme) {
  switch (scheme) {
    case net::ProxyServer::SCHEME_DIRECT:
      return kDirectScheme;
    case net::ProxyServer::SCHEME_HTTP:
      return url::kHttpScheme;
    case net::ProxyServer::SCHEME_SOCKS4:
      return kSocks4Scheme;
    case net::ProxyServer::SCHEME_SOCKS5:
      return kSocks5Scheme;
    case net::ProxyServer::SCHEME_HTTPS:
      return url::kHttpsScheme;
    case net::ProxyServer::SCHEME_QUIC:
      return kQuicScheme;
    case net::ProxyServer::SCHEME_INVALID:
      break;
  }
  NOTREACHED();
  return "";
}

void SetProxyForScheme(const net::ProxyConfig::ProxyRules& proxy_rules,
                       const std::string& scheme,
                       const std::string& onc_scheme,
                       base::DictionaryValue* dict) {
  const net::ProxyList* proxy_list = nullptr;
  if (proxy_rules.type == net::ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY) {
    proxy_list = &proxy_rules.single_proxies;
  } else if (proxy_rules.type ==
             net::ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME) {
    proxy_list = proxy_rules.MapUrlSchemeToProxyList(scheme);
  }
  if (!proxy_list || proxy_list->IsEmpty())
    return;
  const net::ProxyServer& server = proxy_list->Get();
  scoped_ptr<base::DictionaryValue> url_dict(new base::DictionaryValue);
  std::string host = server.host_port_pair().host();

  // For all proxy types except SOCKS, the default scheme of the proxy host is
  // HTTP.
  net::ProxyServer::Scheme default_scheme =
      (onc_scheme == ::onc::proxy::kSocks) ? net::ProxyServer::SCHEME_SOCKS4
                                           : net::ProxyServer::SCHEME_HTTP;
  // Only prefix the host with a non-default scheme.
  if (server.scheme() != default_scheme)
    host = SchemeToString(server.scheme()) + "://" + host;
  url_dict->SetStringWithoutPathExpansion(::onc::proxy::kHost, host);
  url_dict->SetIntegerWithoutPathExpansion(::onc::proxy::kPort,
                                           server.host_port_pair().port());
  dict->SetWithoutPathExpansion(onc_scheme, url_dict.release());
}

}  // namespace

scoped_ptr<base::DictionaryValue> ConvertOncProxySettingsToProxyConfig(
    const base::DictionaryValue& onc_proxy_settings) {
  std::string type;
  onc_proxy_settings.GetStringWithoutPathExpansion(::onc::proxy::kType, &type);
  scoped_ptr<base::DictionaryValue> proxy_dict;

  if (type == ::onc::proxy::kDirect) {
    proxy_dict.reset(ProxyConfigDictionary::CreateDirect());
  } else if (type == ::onc::proxy::kWPAD) {
    proxy_dict.reset(ProxyConfigDictionary::CreateAutoDetect());
  } else if (type == ::onc::proxy::kPAC) {
    std::string pac_url;
    onc_proxy_settings.GetStringWithoutPathExpansion(::onc::proxy::kPAC,
                                                     &pac_url);
    GURL url(pac_url);
    DCHECK(url.is_valid()) << "Invalid URL in ProxySettings.PAC";
    proxy_dict.reset(ProxyConfigDictionary::CreatePacScript(url.spec(), false));
  } else if (type == ::onc::proxy::kManual) {
    const base::DictionaryValue* manual_dict = nullptr;
    onc_proxy_settings.GetDictionaryWithoutPathExpansion(::onc::proxy::kManual,
                                                         &manual_dict);
    std::string manual_spec;
    AppendProxyServerForScheme(*manual_dict, ::onc::proxy::kFtp, &manual_spec);
    AppendProxyServerForScheme(*manual_dict, ::onc::proxy::kHttp, &manual_spec);
    AppendProxyServerForScheme(*manual_dict, ::onc::proxy::kSocks,
                               &manual_spec);
    AppendProxyServerForScheme(*manual_dict, ::onc::proxy::kHttps,
                               &manual_spec);

    const base::ListValue* exclude_domains = nullptr;
    net::ProxyBypassRules bypass_rules;
    if (onc_proxy_settings.GetListWithoutPathExpansion(
            ::onc::proxy::kExcludeDomains, &exclude_domains)) {
      bypass_rules.AssignFrom(
          ConvertOncExcludeDomainsToBypassRules(*exclude_domains));
    }
    proxy_dict.reset(ProxyConfigDictionary::CreateFixedServers(
        manual_spec, bypass_rules.ToString()));
  } else {
    NOTREACHED();
  }
  return proxy_dict.Pass();
}

scoped_ptr<base::DictionaryValue> ConvertProxyConfigToOncProxySettings(
    const base::DictionaryValue& proxy_config_value) {
  // Create a ProxyConfigDictionary from the DictionaryValue.
  scoped_ptr<ProxyConfigDictionary> proxy_config(
      new ProxyConfigDictionary(&proxy_config_value));

  // Create the result DictionaryValue and populate it.
  scoped_ptr<base::DictionaryValue> proxy_settings(new base::DictionaryValue);
  ProxyPrefs::ProxyMode mode;
  if (!proxy_config->GetMode(&mode))
    return nullptr;
  switch (mode) {
    case ProxyPrefs::MODE_DIRECT: {
      proxy_settings->SetStringWithoutPathExpansion(::onc::proxy::kType,
                                                    ::onc::proxy::kDirect);
      break;
    }
    case ProxyPrefs::MODE_AUTO_DETECT: {
      proxy_settings->SetStringWithoutPathExpansion(::onc::proxy::kType,
                                                    ::onc::proxy::kWPAD);
      break;
    }
    case ProxyPrefs::MODE_PAC_SCRIPT: {
      proxy_settings->SetStringWithoutPathExpansion(::onc::proxy::kType,
                                                    ::onc::proxy::kPAC);
      std::string pac_url;
      proxy_config->GetPacUrl(&pac_url);
      proxy_settings->SetStringWithoutPathExpansion(::onc::proxy::kPAC,
                                                    pac_url);
      break;
    }
    case ProxyPrefs::MODE_FIXED_SERVERS: {
      proxy_settings->SetString(::onc::proxy::kType, ::onc::proxy::kManual);
      scoped_ptr<base::DictionaryValue> manual(new base::DictionaryValue);
      std::string proxy_rules_string;
      if (proxy_config->GetProxyServer(&proxy_rules_string)) {
        net::ProxyConfig::ProxyRules proxy_rules;
        proxy_rules.ParseFromString(proxy_rules_string);
        SetProxyForScheme(proxy_rules, url::kFtpScheme, ::onc::proxy::kFtp,
                          manual.get());
        SetProxyForScheme(proxy_rules, url::kHttpScheme, ::onc::proxy::kHttp,
                          manual.get());
        SetProxyForScheme(proxy_rules, url::kHttpsScheme, ::onc::proxy::kHttps,
                          manual.get());
        SetProxyForScheme(proxy_rules, kSocksScheme, ::onc::proxy::kSocks,
                          manual.get());
      }
      proxy_settings->SetWithoutPathExpansion(::onc::proxy::kManual,
                                              manual.release());

      // Convert the 'bypass_list' string into dictionary entries.
      std::string bypass_rules_string;
      if (proxy_config->GetBypassList(&bypass_rules_string)) {
        net::ProxyBypassRules bypass_rules;
        bypass_rules.ParseFromString(bypass_rules_string);
        scoped_ptr<base::ListValue> exclude_domains(new base::ListValue);
        for (const net::ProxyBypassRules::Rule* rule : bypass_rules.rules())
          exclude_domains->AppendString(rule->ToString());
        if (!exclude_domains->empty()) {
          proxy_settings->SetWithoutPathExpansion(::onc::proxy::kExcludeDomains,
                                                  exclude_domains.release());
        }
      }
      break;
    }
    default: {
      LOG(ERROR) << "Unexpected proxy mode in Shill config: " << mode;
      return nullptr;
    }
  }
  return proxy_settings.Pass();
}

}  // namespace onc
}  // namespace chromeos