| // 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 <stdint.h> |
| |
| #include <set> |
| |
| #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_ui_data.h" |
| #include "chromeos/network/onc/onc_utils.h" |
| #include "components/device_event_log/device_event_log.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_t index = 0; index < static_cast<int32_t>(str.size()); ++index) { |
| uint32_t code_point_out; |
| bool is_unicode_char = base::ReadUnicodeCharacter( |
| str.c_str(), str.size(), &index, &code_point_out); |
| const uint32_t kFirstNonControlChar = 0x20; |
| if (is_unicode_char && (code_point_out >= kFirstNonControlChar)) { |
| base::WriteUnicodeCharacter(code_point_out, &result); |
| } else { |
| const uint32_t 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; |
| } |
| |
| } // 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 verbose_logging, |
| 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()) { |
| if (verbose_logging) |
| NET_LOG(DEBUG) << "GetSSIDFromProperties: No HexSSID set: " << name; |
| return std::string(); |
| } |
| |
| std::string ssid; |
| std::vector<uint8_t> raw_ssid_bytes; |
| if (base::HexStringToBytes(hex_ssid, &raw_ssid_bytes)) { |
| ssid = std::string(raw_ssid_bytes.begin(), raw_ssid_bytes.end()); |
| VLOG(2) << "GetSSIDFromProperties: " << name << " HexSsid=" << hex_ssid |
| << " SSID=" << ssid; |
| } else { |
| NET_LOG(ERROR) << "GetSSIDFromProperties: " << name |
| << " Error processing HexSsid: " << hex_ssid; |
| 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) { |
| if (verbose_logging) { |
| NET_LOG(DEBUG) << "GetSSIDFromProperties: " << name |
| << " Encoding=" << encoding << " SSID=" << ssid |
| << " UTF8 SSID=" << utf8_ssid; |
| } |
| } |
| return utf8_ssid; |
| } |
| |
| if (unknown_encoding) |
| *unknown_encoding = true; |
| if (verbose_logging) { |
| NET_LOG(DEBUG) << "GetSSIDFromProperties: " << name |
| << " Unrecognized Encoding=" << encoding; |
| } |
| 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: " << service_path |
| << " Validated name=" << validated_name << " name=" << name; |
| } |
| |
| std::string type; |
| properties.GetStringWithoutPathExpansion(shill::kTypeProperty, &type); |
| if (type.empty()) { |
| NET_LOG(ERROR) << "GetNameFromProperties: " << service_path << " No type."; |
| return validated_name; |
| } |
| if (!NetworkTypePattern::WiFi().MatchesType(type)) |
| return validated_name; |
| |
| bool unknown_ssid_encoding = false; |
| std::string ssid = GetSSIDFromProperties( |
| properties, true /* verbose_logging */, &unknown_ssid_encoding); |
| if (ssid.empty()) { |
| NET_LOG(ERROR) << "GetNameFromProperties: " << service_path |
| << " No SSID set"; |
| } |
| |
| // 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: " << service_path |
| << " SSID=" << ssid << " Validated name=" << validated_name; |
| } |
| 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; |
| } |
| |
| 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) { |
| success &= |
| CopyStringFromDictionary( |
| service_properties, shill::kSecurityClassProperty, dest); |
| success &= |
| CopyStringFromDictionary(service_properties, shill::kWifiHexSsid, dest); |
| success &= CopyStringFromDictionary( |
| service_properties, shill::kModeProperty, dest); |
| } else if (type == shill::kTypeCellular) { |
| success &= CopyStringFromDictionary( |
| service_properties, shill::kNetworkTechnologyProperty, 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 IsLoggableShillProperty(const std::string& key) { |
| static std::set<std::string>* s_skip_properties = nullptr; |
| if (!s_skip_properties) { |
| s_skip_properties = new std::set<std::string>; |
| s_skip_properties->insert(shill::kApnPasswordProperty); |
| s_skip_properties->insert(shill::kEapCaCertNssProperty); |
| s_skip_properties->insert(shill::kEapCaCertPemProperty); |
| s_skip_properties->insert(shill::kEapCaCertProperty); |
| s_skip_properties->insert(shill::kEapClientCertNssProperty); |
| s_skip_properties->insert(shill::kEapClientCertProperty); |
| s_skip_properties->insert(shill::kEapPasswordProperty); |
| s_skip_properties->insert(shill::kEapPinProperty); |
| s_skip_properties->insert(shill::kEapPrivateKeyPasswordProperty); |
| s_skip_properties->insert(shill::kEapPrivateKeyProperty); |
| s_skip_properties->insert(shill::kL2tpIpsecCaCertPemProperty); |
| s_skip_properties->insert(shill::kL2tpIpsecPasswordProperty); |
| s_skip_properties->insert(shill::kL2tpIpsecPinProperty); |
| s_skip_properties->insert(shill::kL2tpIpsecPskProperty); |
| s_skip_properties->insert(shill::kOpenVPNAuthUserPassProperty); |
| s_skip_properties->insert(shill::kOpenVPNCaCertNSSProperty); |
| s_skip_properties->insert(shill::kOpenVPNCaCertPemProperty); |
| s_skip_properties->insert(shill::kOpenVPNCaCertProperty); |
| s_skip_properties->insert(shill::kOpenVPNCertProperty); |
| s_skip_properties->insert(shill::kOpenVPNExtraCertPemProperty); |
| s_skip_properties->insert(shill::kOpenVPNOTPProperty); |
| s_skip_properties->insert(shill::kOpenVPNPasswordProperty); |
| s_skip_properties->insert(shill::kOpenVPNPinProperty); |
| s_skip_properties->insert(shill::kOpenVPNTLSAuthContentsProperty); |
| s_skip_properties->insert(shill::kPPPoEPasswordProperty); |
| s_skip_properties->insert(shill::kPassphraseProperty); |
| } |
| return s_skip_properties->count(key) == 0; |
| } |
| |
| 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 |