普通文本  |  376行  |  13.97 KB

// 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 "chromeos/network/shill_property_util.h"

#include "base/i18n/icu_encoding_detection.h"
#include "base/i18n/icu_string_conversions.h"
#include "base/json/json_writer.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversion_utils.h"
#include "base/values.h"
#include "chromeos/network/network_event_log.h"
#include "chromeos/network/network_ui_data.h"
#include "chromeos/network/onc/onc_utils.h"
#include "third_party/cros_system_api/dbus/service_constants.h"

namespace chromeos {

namespace shill_property_util {

namespace {

// Replace non UTF8 characters in |str| with a replacement character.
std::string ValidateUTF8(const std::string& str) {
  std::string result;
  for (int32 index = 0; index < static_cast<int32>(str.size()); ++index) {
    uint32 code_point_out;
    bool is_unicode_char = base::ReadUnicodeCharacter(
        str.c_str(), str.size(), &index, &code_point_out);
    const uint32 kFirstNonControlChar = 0x20;
    if (is_unicode_char && (code_point_out >= kFirstNonControlChar)) {
      base::WriteUnicodeCharacter(code_point_out, &result);
    } else {
      const uint32 kReplacementChar = 0xFFFD;
      // Puts kReplacementChar if character is a control character [0,0x20)
      // or is not readable UTF8.
      base::WriteUnicodeCharacter(kReplacementChar, &result);
    }
  }
  return result;
}

// If existent and non-empty, copies the string at |key| from |source| to
// |dest|. Returns true if the string was copied.
bool CopyStringFromDictionary(const base::DictionaryValue& source,
                              const std::string& key,
                              base::DictionaryValue* dest) {
  std::string string_value;
  if (!source.GetStringWithoutPathExpansion(key, &string_value) ||
      string_value.empty()) {
    return false;
  }
  dest->SetStringWithoutPathExpansion(key, string_value);
  return true;
}

// This is the same normalization that Shill applies to security types for the
// sake of comparing/identifying WiFi networks. See Shill's
// WiFiService::GetSecurityClass.
std::string GetSecurityClass(const std::string& security) {
  if (security == shill::kSecurityRsn || security == shill::kSecurityWpa)
    return shill::kSecurityPsk;
  else
    return security;
}

}  // namespace

void SetSSID(const std::string ssid, base::DictionaryValue* properties) {
  std::string hex_ssid = base::HexEncode(ssid.c_str(), ssid.size());
  properties->SetStringWithoutPathExpansion(shill::kWifiHexSsid, hex_ssid);
}

std::string GetSSIDFromProperties(const base::DictionaryValue& properties,
                                  bool* unknown_encoding) {
  if (unknown_encoding)
    *unknown_encoding = false;

  // Get name for debugging.
  std::string name;
  properties.GetStringWithoutPathExpansion(shill::kNameProperty, &name);

  std::string hex_ssid;
  properties.GetStringWithoutPathExpansion(shill::kWifiHexSsid, &hex_ssid);

  if (hex_ssid.empty()) {
    NET_LOG_DEBUG("GetSSIDFromProperties: No HexSSID set.", name);
    return std::string();
  }

  std::string ssid;
  std::vector<uint8> raw_ssid_bytes;
  if (base::HexStringToBytes(hex_ssid, &raw_ssid_bytes)) {
    ssid = std::string(raw_ssid_bytes.begin(), raw_ssid_bytes.end());
    NET_LOG_DEBUG(
        "GetSSIDFromProperties: " +
            base::StringPrintf("%s, SSID: %s", hex_ssid.c_str(), ssid.c_str()),
        name);
  } else {
    NET_LOG_ERROR(
        "GetSSIDFromProperties: " +
            base::StringPrintf("Error processing: %s", hex_ssid.c_str()),
        name);
    return std::string();
  }

  if (base::IsStringUTF8(ssid))
    return ssid;

  // Detect encoding and convert to UTF-8.
  std::string encoding;
  if (!base::DetectEncoding(ssid, &encoding)) {
    // TODO(stevenjb): This is currently experimental. If we find a case where
    // base::DetectEncoding() fails, we need to figure out whether we can use
    // country_code with ConvertToUtf8(). crbug.com/233267.
    properties.GetStringWithoutPathExpansion(shill::kCountryProperty,
                                             &encoding);
  }
  std::string utf8_ssid;
  if (!encoding.empty() &&
      base::ConvertToUtf8AndNormalize(ssid, encoding, &utf8_ssid)) {
    if (utf8_ssid != ssid) {
      NET_LOG_DEBUG(
          "GetSSIDFromProperties",
          base::StringPrintf(
              "Encoding=%s: %s", encoding.c_str(), utf8_ssid.c_str()));
    }
    return utf8_ssid;
  }

  if (unknown_encoding)
    *unknown_encoding = true;
  NET_LOG_DEBUG(
      "GetSSIDFromProperties: " +
          base::StringPrintf("Unrecognized Encoding=%s", encoding.c_str()),
      name);
  return ssid;
}

std::string GetNetworkIdFromProperties(
    const base::DictionaryValue& properties) {
  if (properties.empty())
    return "EmptyProperties";
  std::string result;
  if (properties.GetStringWithoutPathExpansion(shill::kGuidProperty, &result))
    return result;
  if (properties.GetStringWithoutPathExpansion(shill::kSSIDProperty, &result))
    return result;
  if (properties.GetStringWithoutPathExpansion(shill::kNameProperty, &result))
    return result;
  std::string type = "UnknownType";
  properties.GetStringWithoutPathExpansion(shill::kTypeProperty, &type);
  return "Unidentified " + type;
}

std::string GetNameFromProperties(const std::string& service_path,
                                  const base::DictionaryValue& properties) {
  std::string name;
  properties.GetStringWithoutPathExpansion(shill::kNameProperty, &name);

  std::string validated_name = ValidateUTF8(name);
  if (validated_name != name) {
    NET_LOG_DEBUG("GetNameFromProperties",
                  base::StringPrintf("Validated name %s: UTF8: %s",
                                     service_path.c_str(),
                                     validated_name.c_str()));
  }

  std::string type;
  properties.GetStringWithoutPathExpansion(shill::kTypeProperty, &type);
  if (type.empty()) {
    NET_LOG_ERROR("GetNameFromProperties: No type", service_path);
    return validated_name;
  }
  if (!NetworkTypePattern::WiFi().MatchesType(type))
    return validated_name;

  bool unknown_ssid_encoding = false;
  std::string ssid = GetSSIDFromProperties(properties, &unknown_ssid_encoding);
  if (ssid.empty())
    NET_LOG_ERROR("GetNameFromProperties", "No SSID set: " + service_path);

  // Use |validated_name| if |ssid| is empty.
  // And if the encoding of the SSID is unknown, use |ssid|, which contains raw
  // bytes in that case, only if |validated_name| is empty.
  if (ssid.empty() || (unknown_ssid_encoding && !validated_name.empty()))
    return validated_name;

  if (ssid != validated_name) {
    NET_LOG_DEBUG("GetNameFromProperties",
                  base::StringPrintf("%s: SSID: %s, Name: %s",
                                     service_path.c_str(),
                                     ssid.c_str(),
                                     validated_name.c_str()));
  }
  return ssid;
}

scoped_ptr<NetworkUIData> GetUIDataFromValue(const base::Value& ui_data_value) {
  std::string ui_data_str;
  if (!ui_data_value.GetAsString(&ui_data_str))
    return scoped_ptr<NetworkUIData>();
  if (ui_data_str.empty())
    return make_scoped_ptr(new NetworkUIData());
  scoped_ptr<base::DictionaryValue> ui_data_dict(
      chromeos::onc::ReadDictionaryFromJson(ui_data_str));
  if (!ui_data_dict)
    return scoped_ptr<NetworkUIData>();
  return make_scoped_ptr(new NetworkUIData(*ui_data_dict));
}

scoped_ptr<NetworkUIData> GetUIDataFromProperties(
    const base::DictionaryValue& shill_dictionary) {
  const base::Value* ui_data_value = NULL;
  shill_dictionary.GetWithoutPathExpansion(shill::kUIDataProperty,
                                           &ui_data_value);
  if (!ui_data_value) {
    VLOG(2) << "Dictionary has no UIData entry.";
    return scoped_ptr<NetworkUIData>();
  }
  scoped_ptr<NetworkUIData> ui_data = GetUIDataFromValue(*ui_data_value);
  if (!ui_data)
    LOG(ERROR) << "UIData is not a valid JSON dictionary.";
  return ui_data.Pass();
}

void SetUIData(const NetworkUIData& ui_data,
               base::DictionaryValue* shill_dictionary) {
  base::DictionaryValue ui_data_dict;
  ui_data.FillDictionary(&ui_data_dict);
  std::string ui_data_blob;
  base::JSONWriter::Write(&ui_data_dict, &ui_data_blob);
  shill_dictionary->SetStringWithoutPathExpansion(shill::kUIDataProperty,
                                                  ui_data_blob);
}

bool CopyIdentifyingProperties(const base::DictionaryValue& service_properties,
                               const bool properties_read_from_shill,
                               base::DictionaryValue* dest) {
  bool success = true;

  // GUID is optional.
  CopyStringFromDictionary(service_properties, shill::kGuidProperty, dest);

  std::string type;
  service_properties.GetStringWithoutPathExpansion(shill::kTypeProperty, &type);
  success &= !type.empty();
  dest->SetStringWithoutPathExpansion(shill::kTypeProperty, type);
  if (type == shill::kTypeWifi) {
    std::string security;
    service_properties.GetStringWithoutPathExpansion(shill::kSecurityProperty,
                                                     &security);
    if (security.empty()) {
      success = false;
    } else {
      dest->SetStringWithoutPathExpansion(shill::kSecurityProperty,
                                          GetSecurityClass(security));
    }
    success &=
        CopyStringFromDictionary(service_properties, shill::kWifiHexSsid, dest);
    success &= CopyStringFromDictionary(
        service_properties, shill::kModeProperty, dest);
  } else if (type == shill::kTypeVPN) {
    success &= CopyStringFromDictionary(
        service_properties, shill::kNameProperty, dest);

    // VPN Provider values are read from the "Provider" dictionary, but written
    // with the keys "Provider.Type" and "Provider.Host".
    // TODO(pneubeck): Simplify this once http://crbug.com/381135 is fixed.
    std::string vpn_provider_type;
    std::string vpn_provider_host;
    if (properties_read_from_shill) {
      const base::DictionaryValue* provider_properties = NULL;
      if (!service_properties.GetDictionaryWithoutPathExpansion(
              shill::kProviderProperty, &provider_properties)) {
        NET_LOG_ERROR("Missing VPN provider dict",
                      GetNetworkIdFromProperties(service_properties));
      }
      provider_properties->GetStringWithoutPathExpansion(shill::kTypeProperty,
                                                         &vpn_provider_type);
      provider_properties->GetStringWithoutPathExpansion(shill::kHostProperty,
                                                         &vpn_provider_host);
    } else {
      service_properties.GetStringWithoutPathExpansion(
          shill::kProviderTypeProperty, &vpn_provider_type);
      service_properties.GetStringWithoutPathExpansion(
          shill::kProviderHostProperty, &vpn_provider_host);
    }
    success &= !vpn_provider_type.empty();
    dest->SetStringWithoutPathExpansion(shill::kProviderTypeProperty,
                                        vpn_provider_type);

    success &= !vpn_provider_host.empty();
    dest->SetStringWithoutPathExpansion(shill::kProviderHostProperty,
                                        vpn_provider_host);
  } else if (type == shill::kTypeEthernet || type == shill::kTypeEthernetEap) {
    // Ethernet and EthernetEAP don't have any additional identifying
    // properties.
  } else {
    NOTREACHED() << "Unsupported network type " << type;
    success = false;
  }
  if (!success) {
    NET_LOG_ERROR("Missing required properties",
                  GetNetworkIdFromProperties(service_properties));
  }
  return success;
}

bool DoIdentifyingPropertiesMatch(const base::DictionaryValue& new_properties,
                                  const base::DictionaryValue& old_properties) {
  base::DictionaryValue new_identifying;
  if (!CopyIdentifyingProperties(
          new_properties,
          false /* properties were not read from Shill */,
          &new_identifying)) {
    return false;
  }
  base::DictionaryValue old_identifying;
  if (!CopyIdentifyingProperties(old_properties,
                                 true /* properties were read from Shill */,
                                 &old_identifying)) {
    return false;
  }

  return new_identifying.Equals(&old_identifying);
}

bool IsPassphraseKey(const std::string& key) {
  return key == shill::kEapPrivateKeyPasswordProperty ||
      key == shill::kEapPasswordProperty ||
      key == shill::kL2tpIpsecPasswordProperty ||
      key == shill::kOpenVPNPasswordProperty ||
      key == shill::kOpenVPNAuthUserPassProperty ||
      key == shill::kOpenVPNTLSAuthContentsProperty ||
      key == shill::kPassphraseProperty ||
      key == shill::kOpenVPNOTPProperty ||
      key == shill::kEapPrivateKeyProperty ||
      key == shill::kEapPinProperty ||
      key == shill::kApnPasswordProperty;
}

bool GetHomeProviderFromProperty(const base::Value& value,
                                 std::string* home_provider_id) {
  const base::DictionaryValue* dict = NULL;
  if (!value.GetAsDictionary(&dict))
    return false;
  std::string home_provider_country;
  std::string home_provider_name;
  dict->GetStringWithoutPathExpansion(shill::kOperatorCountryKey,
                                      &home_provider_country);
  dict->GetStringWithoutPathExpansion(shill::kOperatorNameKey,
                                      &home_provider_name);
  // Set home_provider_id
  if (!home_provider_name.empty() && !home_provider_country.empty()) {
    *home_provider_id = base::StringPrintf(
        "%s (%s)", home_provider_name.c_str(), home_provider_country.c_str());
  } else {
    if (!dict->GetStringWithoutPathExpansion(shill::kOperatorCodeKey,
                                             home_provider_id)) {
      return false;
    }
    LOG(WARNING)
        << "Provider name and country not defined, using code instead: "
        << *home_provider_id;
  }
  return true;
}

}  // namespace shill_property_util

}  // namespace chromeos