// 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