1 // Copyright 2013 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chromeos/network/shill_property_util.h" 6 7 #include "base/i18n/icu_encoding_detection.h" 8 #include "base/i18n/icu_string_conversions.h" 9 #include "base/json/json_writer.h" 10 #include "base/strings/string_number_conversions.h" 11 #include "base/strings/string_util.h" 12 #include "base/strings/stringprintf.h" 13 #include "base/strings/utf_string_conversion_utils.h" 14 #include "base/values.h" 15 #include "chromeos/network/network_event_log.h" 16 #include "chromeos/network/network_ui_data.h" 17 #include "chromeos/network/onc/onc_utils.h" 18 #include "third_party/cros_system_api/dbus/service_constants.h" 19 20 namespace chromeos { 21 22 namespace shill_property_util { 23 24 namespace { 25 26 // Replace non UTF8 characters in |str| with a replacement character. 27 std::string ValidateUTF8(const std::string& str) { 28 std::string result; 29 for (int32 index = 0; index < static_cast<int32>(str.size()); ++index) { 30 uint32 code_point_out; 31 bool is_unicode_char = base::ReadUnicodeCharacter( 32 str.c_str(), str.size(), &index, &code_point_out); 33 const uint32 kFirstNonControlChar = 0x20; 34 if (is_unicode_char && (code_point_out >= kFirstNonControlChar)) { 35 base::WriteUnicodeCharacter(code_point_out, &result); 36 } else { 37 const uint32 kReplacementChar = 0xFFFD; 38 // Puts kReplacementChar if character is a control character [0,0x20) 39 // or is not readable UTF8. 40 base::WriteUnicodeCharacter(kReplacementChar, &result); 41 } 42 } 43 return result; 44 } 45 46 // If existent and non-empty, copies the string at |key| from |source| to 47 // |dest|. Returns true if the string was copied. 48 bool CopyStringFromDictionary(const base::DictionaryValue& source, 49 const std::string& key, 50 base::DictionaryValue* dest) { 51 std::string string_value; 52 if (!source.GetStringWithoutPathExpansion(key, &string_value) || 53 string_value.empty()) { 54 return false; 55 } 56 dest->SetStringWithoutPathExpansion(key, string_value); 57 return true; 58 } 59 60 // This is the same normalization that Shill applies to security types for the 61 // sake of comparing/identifying WiFi networks. See Shill's 62 // WiFiService::GetSecurityClass. 63 std::string GetSecurityClass(const std::string& security) { 64 if (security == shill::kSecurityRsn || security == shill::kSecurityWpa) 65 return shill::kSecurityPsk; 66 else 67 return security; 68 } 69 70 } // namespace 71 72 void SetSSID(const std::string ssid, base::DictionaryValue* properties) { 73 std::string hex_ssid = base::HexEncode(ssid.c_str(), ssid.size()); 74 properties->SetStringWithoutPathExpansion(shill::kWifiHexSsid, hex_ssid); 75 } 76 77 std::string GetSSIDFromProperties(const base::DictionaryValue& properties, 78 bool* unknown_encoding) { 79 if (unknown_encoding) 80 *unknown_encoding = false; 81 82 // Get name for debugging. 83 std::string name; 84 properties.GetStringWithoutPathExpansion(shill::kNameProperty, &name); 85 86 std::string hex_ssid; 87 properties.GetStringWithoutPathExpansion(shill::kWifiHexSsid, &hex_ssid); 88 89 if (hex_ssid.empty()) { 90 NET_LOG_DEBUG("GetSSIDFromProperties: No HexSSID set.", name); 91 return std::string(); 92 } 93 94 std::string ssid; 95 std::vector<uint8> raw_ssid_bytes; 96 if (base::HexStringToBytes(hex_ssid, &raw_ssid_bytes)) { 97 ssid = std::string(raw_ssid_bytes.begin(), raw_ssid_bytes.end()); 98 NET_LOG_DEBUG( 99 "GetSSIDFromProperties: " + 100 base::StringPrintf("%s, SSID: %s", hex_ssid.c_str(), ssid.c_str()), 101 name); 102 } else { 103 NET_LOG_ERROR( 104 "GetSSIDFromProperties: " + 105 base::StringPrintf("Error processing: %s", hex_ssid.c_str()), 106 name); 107 return std::string(); 108 } 109 110 if (base::IsStringUTF8(ssid)) 111 return ssid; 112 113 // Detect encoding and convert to UTF-8. 114 std::string encoding; 115 if (!base::DetectEncoding(ssid, &encoding)) { 116 // TODO(stevenjb): This is currently experimental. If we find a case where 117 // base::DetectEncoding() fails, we need to figure out whether we can use 118 // country_code with ConvertToUtf8(). crbug.com/233267. 119 properties.GetStringWithoutPathExpansion(shill::kCountryProperty, 120 &encoding); 121 } 122 std::string utf8_ssid; 123 if (!encoding.empty() && 124 base::ConvertToUtf8AndNormalize(ssid, encoding, &utf8_ssid)) { 125 if (utf8_ssid != ssid) { 126 NET_LOG_DEBUG( 127 "GetSSIDFromProperties", 128 base::StringPrintf( 129 "Encoding=%s: %s", encoding.c_str(), utf8_ssid.c_str())); 130 } 131 return utf8_ssid; 132 } 133 134 if (unknown_encoding) 135 *unknown_encoding = true; 136 NET_LOG_DEBUG( 137 "GetSSIDFromProperties: " + 138 base::StringPrintf("Unrecognized Encoding=%s", encoding.c_str()), 139 name); 140 return ssid; 141 } 142 143 std::string GetNetworkIdFromProperties( 144 const base::DictionaryValue& properties) { 145 if (properties.empty()) 146 return "EmptyProperties"; 147 std::string result; 148 if (properties.GetStringWithoutPathExpansion(shill::kGuidProperty, &result)) 149 return result; 150 if (properties.GetStringWithoutPathExpansion(shill::kSSIDProperty, &result)) 151 return result; 152 if (properties.GetStringWithoutPathExpansion(shill::kNameProperty, &result)) 153 return result; 154 std::string type = "UnknownType"; 155 properties.GetStringWithoutPathExpansion(shill::kTypeProperty, &type); 156 return "Unidentified " + type; 157 } 158 159 std::string GetNameFromProperties(const std::string& service_path, 160 const base::DictionaryValue& properties) { 161 std::string name; 162 properties.GetStringWithoutPathExpansion(shill::kNameProperty, &name); 163 164 std::string validated_name = ValidateUTF8(name); 165 if (validated_name != name) { 166 NET_LOG_DEBUG("GetNameFromProperties", 167 base::StringPrintf("Validated name %s: UTF8: %s", 168 service_path.c_str(), 169 validated_name.c_str())); 170 } 171 172 std::string type; 173 properties.GetStringWithoutPathExpansion(shill::kTypeProperty, &type); 174 if (type.empty()) { 175 NET_LOG_ERROR("GetNameFromProperties: No type", service_path); 176 return validated_name; 177 } 178 if (!NetworkTypePattern::WiFi().MatchesType(type)) 179 return validated_name; 180 181 bool unknown_ssid_encoding = false; 182 std::string ssid = GetSSIDFromProperties(properties, &unknown_ssid_encoding); 183 if (ssid.empty()) 184 NET_LOG_ERROR("GetNameFromProperties", "No SSID set: " + service_path); 185 186 // Use |validated_name| if |ssid| is empty. 187 // And if the encoding of the SSID is unknown, use |ssid|, which contains raw 188 // bytes in that case, only if |validated_name| is empty. 189 if (ssid.empty() || (unknown_ssid_encoding && !validated_name.empty())) 190 return validated_name; 191 192 if (ssid != validated_name) { 193 NET_LOG_DEBUG("GetNameFromProperties", 194 base::StringPrintf("%s: SSID: %s, Name: %s", 195 service_path.c_str(), 196 ssid.c_str(), 197 validated_name.c_str())); 198 } 199 return ssid; 200 } 201 202 scoped_ptr<NetworkUIData> GetUIDataFromValue(const base::Value& ui_data_value) { 203 std::string ui_data_str; 204 if (!ui_data_value.GetAsString(&ui_data_str)) 205 return scoped_ptr<NetworkUIData>(); 206 if (ui_data_str.empty()) 207 return make_scoped_ptr(new NetworkUIData()); 208 scoped_ptr<base::DictionaryValue> ui_data_dict( 209 chromeos::onc::ReadDictionaryFromJson(ui_data_str)); 210 if (!ui_data_dict) 211 return scoped_ptr<NetworkUIData>(); 212 return make_scoped_ptr(new NetworkUIData(*ui_data_dict)); 213 } 214 215 scoped_ptr<NetworkUIData> GetUIDataFromProperties( 216 const base::DictionaryValue& shill_dictionary) { 217 const base::Value* ui_data_value = NULL; 218 shill_dictionary.GetWithoutPathExpansion(shill::kUIDataProperty, 219 &ui_data_value); 220 if (!ui_data_value) { 221 VLOG(2) << "Dictionary has no UIData entry."; 222 return scoped_ptr<NetworkUIData>(); 223 } 224 scoped_ptr<NetworkUIData> ui_data = GetUIDataFromValue(*ui_data_value); 225 if (!ui_data) 226 LOG(ERROR) << "UIData is not a valid JSON dictionary."; 227 return ui_data.Pass(); 228 } 229 230 void SetUIData(const NetworkUIData& ui_data, 231 base::DictionaryValue* shill_dictionary) { 232 base::DictionaryValue ui_data_dict; 233 ui_data.FillDictionary(&ui_data_dict); 234 std::string ui_data_blob; 235 base::JSONWriter::Write(&ui_data_dict, &ui_data_blob); 236 shill_dictionary->SetStringWithoutPathExpansion(shill::kUIDataProperty, 237 ui_data_blob); 238 } 239 240 bool CopyIdentifyingProperties(const base::DictionaryValue& service_properties, 241 const bool properties_read_from_shill, 242 base::DictionaryValue* dest) { 243 bool success = true; 244 245 // GUID is optional. 246 CopyStringFromDictionary(service_properties, shill::kGuidProperty, dest); 247 248 std::string type; 249 service_properties.GetStringWithoutPathExpansion(shill::kTypeProperty, &type); 250 success &= !type.empty(); 251 dest->SetStringWithoutPathExpansion(shill::kTypeProperty, type); 252 if (type == shill::kTypeWifi) { 253 std::string security; 254 service_properties.GetStringWithoutPathExpansion(shill::kSecurityProperty, 255 &security); 256 if (security.empty()) { 257 success = false; 258 } else { 259 dest->SetStringWithoutPathExpansion(shill::kSecurityProperty, 260 GetSecurityClass(security)); 261 } 262 success &= 263 CopyStringFromDictionary(service_properties, shill::kWifiHexSsid, dest); 264 success &= CopyStringFromDictionary( 265 service_properties, shill::kModeProperty, dest); 266 } else if (type == shill::kTypeVPN) { 267 success &= CopyStringFromDictionary( 268 service_properties, shill::kNameProperty, dest); 269 270 // VPN Provider values are read from the "Provider" dictionary, but written 271 // with the keys "Provider.Type" and "Provider.Host". 272 // TODO(pneubeck): Simplify this once http://crbug.com/381135 is fixed. 273 std::string vpn_provider_type; 274 std::string vpn_provider_host; 275 if (properties_read_from_shill) { 276 const base::DictionaryValue* provider_properties = NULL; 277 if (!service_properties.GetDictionaryWithoutPathExpansion( 278 shill::kProviderProperty, &provider_properties)) { 279 NET_LOG_ERROR("Missing VPN provider dict", 280 GetNetworkIdFromProperties(service_properties)); 281 } 282 provider_properties->GetStringWithoutPathExpansion(shill::kTypeProperty, 283 &vpn_provider_type); 284 provider_properties->GetStringWithoutPathExpansion(shill::kHostProperty, 285 &vpn_provider_host); 286 } else { 287 service_properties.GetStringWithoutPathExpansion( 288 shill::kProviderTypeProperty, &vpn_provider_type); 289 service_properties.GetStringWithoutPathExpansion( 290 shill::kProviderHostProperty, &vpn_provider_host); 291 } 292 success &= !vpn_provider_type.empty(); 293 dest->SetStringWithoutPathExpansion(shill::kProviderTypeProperty, 294 vpn_provider_type); 295 296 success &= !vpn_provider_host.empty(); 297 dest->SetStringWithoutPathExpansion(shill::kProviderHostProperty, 298 vpn_provider_host); 299 } else if (type == shill::kTypeEthernet || type == shill::kTypeEthernetEap) { 300 // Ethernet and EthernetEAP don't have any additional identifying 301 // properties. 302 } else { 303 NOTREACHED() << "Unsupported network type " << type; 304 success = false; 305 } 306 if (!success) { 307 NET_LOG_ERROR("Missing required properties", 308 GetNetworkIdFromProperties(service_properties)); 309 } 310 return success; 311 } 312 313 bool DoIdentifyingPropertiesMatch(const base::DictionaryValue& new_properties, 314 const base::DictionaryValue& old_properties) { 315 base::DictionaryValue new_identifying; 316 if (!CopyIdentifyingProperties( 317 new_properties, 318 false /* properties were not read from Shill */, 319 &new_identifying)) { 320 return false; 321 } 322 base::DictionaryValue old_identifying; 323 if (!CopyIdentifyingProperties(old_properties, 324 true /* properties were read from Shill */, 325 &old_identifying)) { 326 return false; 327 } 328 329 return new_identifying.Equals(&old_identifying); 330 } 331 332 bool IsPassphraseKey(const std::string& key) { 333 return key == shill::kEapPrivateKeyPasswordProperty || 334 key == shill::kEapPasswordProperty || 335 key == shill::kL2tpIpsecPasswordProperty || 336 key == shill::kOpenVPNPasswordProperty || 337 key == shill::kOpenVPNAuthUserPassProperty || 338 key == shill::kOpenVPNTLSAuthContentsProperty || 339 key == shill::kPassphraseProperty || 340 key == shill::kOpenVPNOTPProperty || 341 key == shill::kEapPrivateKeyProperty || 342 key == shill::kEapPinProperty || 343 key == shill::kApnPasswordProperty; 344 } 345 346 bool GetHomeProviderFromProperty(const base::Value& value, 347 std::string* home_provider_id) { 348 const base::DictionaryValue* dict = NULL; 349 if (!value.GetAsDictionary(&dict)) 350 return false; 351 std::string home_provider_country; 352 std::string home_provider_name; 353 dict->GetStringWithoutPathExpansion(shill::kOperatorCountryKey, 354 &home_provider_country); 355 dict->GetStringWithoutPathExpansion(shill::kOperatorNameKey, 356 &home_provider_name); 357 // Set home_provider_id 358 if (!home_provider_name.empty() && !home_provider_country.empty()) { 359 *home_provider_id = base::StringPrintf( 360 "%s (%s)", home_provider_name.c_str(), home_provider_country.c_str()); 361 } else { 362 if (!dict->GetStringWithoutPathExpansion(shill::kOperatorCodeKey, 363 home_provider_id)) { 364 return false; 365 } 366 LOG(WARNING) 367 << "Provider name and country not defined, using code instead: " 368 << *home_provider_id; 369 } 370 return true; 371 } 372 373 } // namespace shill_property_util 374 375 } // namespace chromeos 376