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 bool verbose_logging = false; 80 if (unknown_encoding) { 81 *unknown_encoding = false; 82 verbose_logging = true; 83 } 84 85 // Get name for debugging. 86 std::string name; 87 properties.GetStringWithoutPathExpansion(shill::kNameProperty, &name); 88 89 std::string hex_ssid; 90 properties.GetStringWithoutPathExpansion(shill::kWifiHexSsid, &hex_ssid); 91 92 if (hex_ssid.empty()) { 93 if (verbose_logging) 94 NET_LOG_DEBUG("GetSSIDFromProperties: No HexSSID set.", name); 95 return std::string(); 96 } 97 98 std::string ssid; 99 std::vector<uint8> raw_ssid_bytes; 100 if (base::HexStringToBytes(hex_ssid, &raw_ssid_bytes)) { 101 ssid = std::string(raw_ssid_bytes.begin(), raw_ssid_bytes.end()); 102 if (verbose_logging) { 103 NET_LOG_DEBUG(base::StringPrintf("GetSSIDFromProperties: %s, SSID: %s", 104 hex_ssid.c_str(), ssid.c_str()), name); 105 } 106 } else { 107 NET_LOG_ERROR( 108 base::StringPrintf("GetSSIDFromProperties: Error processing: %s", 109 hex_ssid.c_str()), name); 110 return std::string(); 111 } 112 113 if (base::IsStringUTF8(ssid)) 114 return ssid; 115 116 // Detect encoding and convert to UTF-8. 117 std::string encoding; 118 if (!base::DetectEncoding(ssid, &encoding)) { 119 // TODO(stevenjb): This is currently experimental. If we find a case where 120 // base::DetectEncoding() fails, we need to figure out whether we can use 121 // country_code with ConvertToUtf8(). crbug.com/233267. 122 properties.GetStringWithoutPathExpansion(shill::kCountryProperty, 123 &encoding); 124 } 125 std::string utf8_ssid; 126 if (!encoding.empty() && 127 base::ConvertToUtf8AndNormalize(ssid, encoding, &utf8_ssid)) { 128 if (utf8_ssid != ssid) { 129 if (verbose_logging) { 130 NET_LOG_DEBUG( 131 base::StringPrintf("GetSSIDFromProperties: Encoding=%s: %s", 132 encoding.c_str(), utf8_ssid.c_str()), name); 133 } 134 } 135 return utf8_ssid; 136 } 137 138 if (unknown_encoding) 139 *unknown_encoding = true; 140 if (verbose_logging) { 141 NET_LOG_DEBUG( 142 base::StringPrintf("GetSSIDFromProperties: Unrecognized Encoding=%s", 143 encoding.c_str()), name); 144 } 145 return ssid; 146 } 147 148 std::string GetNetworkIdFromProperties( 149 const base::DictionaryValue& properties) { 150 if (properties.empty()) 151 return "EmptyProperties"; 152 std::string result; 153 if (properties.GetStringWithoutPathExpansion(shill::kGuidProperty, &result)) 154 return result; 155 if (properties.GetStringWithoutPathExpansion(shill::kSSIDProperty, &result)) 156 return result; 157 if (properties.GetStringWithoutPathExpansion(shill::kNameProperty, &result)) 158 return result; 159 std::string type = "UnknownType"; 160 properties.GetStringWithoutPathExpansion(shill::kTypeProperty, &type); 161 return "Unidentified " + type; 162 } 163 164 std::string GetNameFromProperties(const std::string& service_path, 165 const base::DictionaryValue& properties) { 166 std::string name; 167 properties.GetStringWithoutPathExpansion(shill::kNameProperty, &name); 168 169 std::string validated_name = ValidateUTF8(name); 170 if (validated_name != name) { 171 NET_LOG_DEBUG("GetNameFromProperties", 172 base::StringPrintf("Validated name %s: UTF8: %s", 173 service_path.c_str(), 174 validated_name.c_str())); 175 } 176 177 std::string type; 178 properties.GetStringWithoutPathExpansion(shill::kTypeProperty, &type); 179 if (type.empty()) { 180 NET_LOG_ERROR("GetNameFromProperties: No type", service_path); 181 return validated_name; 182 } 183 if (!NetworkTypePattern::WiFi().MatchesType(type)) 184 return validated_name; 185 186 bool unknown_ssid_encoding = false; 187 std::string ssid = GetSSIDFromProperties(properties, &unknown_ssid_encoding); 188 if (ssid.empty()) 189 NET_LOG_ERROR("GetNameFromProperties", "No SSID set: " + service_path); 190 191 // Use |validated_name| if |ssid| is empty. 192 // And if the encoding of the SSID is unknown, use |ssid|, which contains raw 193 // bytes in that case, only if |validated_name| is empty. 194 if (ssid.empty() || (unknown_ssid_encoding && !validated_name.empty())) 195 return validated_name; 196 197 if (ssid != validated_name) { 198 NET_LOG_DEBUG("GetNameFromProperties", 199 base::StringPrintf("%s: SSID: %s, Name: %s", 200 service_path.c_str(), 201 ssid.c_str(), 202 validated_name.c_str())); 203 } 204 return ssid; 205 } 206 207 scoped_ptr<NetworkUIData> GetUIDataFromValue(const base::Value& ui_data_value) { 208 std::string ui_data_str; 209 if (!ui_data_value.GetAsString(&ui_data_str)) 210 return scoped_ptr<NetworkUIData>(); 211 if (ui_data_str.empty()) 212 return make_scoped_ptr(new NetworkUIData()); 213 scoped_ptr<base::DictionaryValue> ui_data_dict( 214 chromeos::onc::ReadDictionaryFromJson(ui_data_str)); 215 if (!ui_data_dict) 216 return scoped_ptr<NetworkUIData>(); 217 return make_scoped_ptr(new NetworkUIData(*ui_data_dict)); 218 } 219 220 scoped_ptr<NetworkUIData> GetUIDataFromProperties( 221 const base::DictionaryValue& shill_dictionary) { 222 const base::Value* ui_data_value = NULL; 223 shill_dictionary.GetWithoutPathExpansion(shill::kUIDataProperty, 224 &ui_data_value); 225 if (!ui_data_value) { 226 VLOG(2) << "Dictionary has no UIData entry."; 227 return scoped_ptr<NetworkUIData>(); 228 } 229 scoped_ptr<NetworkUIData> ui_data = GetUIDataFromValue(*ui_data_value); 230 if (!ui_data) 231 LOG(ERROR) << "UIData is not a valid JSON dictionary."; 232 return ui_data.Pass(); 233 } 234 235 void SetUIData(const NetworkUIData& ui_data, 236 base::DictionaryValue* shill_dictionary) { 237 base::DictionaryValue ui_data_dict; 238 ui_data.FillDictionary(&ui_data_dict); 239 std::string ui_data_blob; 240 base::JSONWriter::Write(&ui_data_dict, &ui_data_blob); 241 shill_dictionary->SetStringWithoutPathExpansion(shill::kUIDataProperty, 242 ui_data_blob); 243 } 244 245 bool CopyIdentifyingProperties(const base::DictionaryValue& service_properties, 246 const bool properties_read_from_shill, 247 base::DictionaryValue* dest) { 248 bool success = true; 249 250 // GUID is optional. 251 CopyStringFromDictionary(service_properties, shill::kGuidProperty, dest); 252 253 std::string type; 254 service_properties.GetStringWithoutPathExpansion(shill::kTypeProperty, &type); 255 success &= !type.empty(); 256 dest->SetStringWithoutPathExpansion(shill::kTypeProperty, type); 257 if (type == shill::kTypeWifi) { 258 std::string security; 259 service_properties.GetStringWithoutPathExpansion(shill::kSecurityProperty, 260 &security); 261 if (security.empty()) { 262 success = false; 263 } else { 264 dest->SetStringWithoutPathExpansion(shill::kSecurityProperty, 265 GetSecurityClass(security)); 266 } 267 success &= 268 CopyStringFromDictionary(service_properties, shill::kWifiHexSsid, dest); 269 success &= CopyStringFromDictionary( 270 service_properties, shill::kModeProperty, dest); 271 } else if (type == shill::kTypeVPN) { 272 success &= CopyStringFromDictionary( 273 service_properties, shill::kNameProperty, dest); 274 275 // VPN Provider values are read from the "Provider" dictionary, but written 276 // with the keys "Provider.Type" and "Provider.Host". 277 // TODO(pneubeck): Simplify this once http://crbug.com/381135 is fixed. 278 std::string vpn_provider_type; 279 std::string vpn_provider_host; 280 if (properties_read_from_shill) { 281 const base::DictionaryValue* provider_properties = NULL; 282 if (!service_properties.GetDictionaryWithoutPathExpansion( 283 shill::kProviderProperty, &provider_properties)) { 284 NET_LOG_ERROR("Missing VPN provider dict", 285 GetNetworkIdFromProperties(service_properties)); 286 } 287 provider_properties->GetStringWithoutPathExpansion(shill::kTypeProperty, 288 &vpn_provider_type); 289 provider_properties->GetStringWithoutPathExpansion(shill::kHostProperty, 290 &vpn_provider_host); 291 } else { 292 service_properties.GetStringWithoutPathExpansion( 293 shill::kProviderTypeProperty, &vpn_provider_type); 294 service_properties.GetStringWithoutPathExpansion( 295 shill::kProviderHostProperty, &vpn_provider_host); 296 } 297 success &= !vpn_provider_type.empty(); 298 dest->SetStringWithoutPathExpansion(shill::kProviderTypeProperty, 299 vpn_provider_type); 300 301 success &= !vpn_provider_host.empty(); 302 dest->SetStringWithoutPathExpansion(shill::kProviderHostProperty, 303 vpn_provider_host); 304 } else if (type == shill::kTypeEthernet || type == shill::kTypeEthernetEap) { 305 // Ethernet and EthernetEAP don't have any additional identifying 306 // properties. 307 } else { 308 NOTREACHED() << "Unsupported network type " << type; 309 success = false; 310 } 311 if (!success) { 312 NET_LOG_ERROR("Missing required properties", 313 GetNetworkIdFromProperties(service_properties)); 314 } 315 return success; 316 } 317 318 bool DoIdentifyingPropertiesMatch(const base::DictionaryValue& new_properties, 319 const base::DictionaryValue& old_properties) { 320 base::DictionaryValue new_identifying; 321 if (!CopyIdentifyingProperties( 322 new_properties, 323 false /* properties were not read from Shill */, 324 &new_identifying)) { 325 return false; 326 } 327 base::DictionaryValue old_identifying; 328 if (!CopyIdentifyingProperties(old_properties, 329 true /* properties were read from Shill */, 330 &old_identifying)) { 331 return false; 332 } 333 334 return new_identifying.Equals(&old_identifying); 335 } 336 337 bool IsPassphraseKey(const std::string& key) { 338 return key == shill::kEapPrivateKeyPasswordProperty || 339 key == shill::kEapPasswordProperty || 340 key == shill::kL2tpIpsecPasswordProperty || 341 key == shill::kOpenVPNPasswordProperty || 342 key == shill::kOpenVPNAuthUserPassProperty || 343 key == shill::kOpenVPNTLSAuthContentsProperty || 344 key == shill::kPassphraseProperty || 345 key == shill::kOpenVPNOTPProperty || 346 key == shill::kEapPrivateKeyProperty || 347 key == shill::kEapPinProperty || 348 key == shill::kApnPasswordProperty; 349 } 350 351 bool GetHomeProviderFromProperty(const base::Value& value, 352 std::string* home_provider_id) { 353 const base::DictionaryValue* dict = NULL; 354 if (!value.GetAsDictionary(&dict)) 355 return false; 356 std::string home_provider_country; 357 std::string home_provider_name; 358 dict->GetStringWithoutPathExpansion(shill::kOperatorCountryKey, 359 &home_provider_country); 360 dict->GetStringWithoutPathExpansion(shill::kOperatorNameKey, 361 &home_provider_name); 362 // Set home_provider_id 363 if (!home_provider_name.empty() && !home_provider_country.empty()) { 364 *home_provider_id = base::StringPrintf( 365 "%s (%s)", home_provider_name.c_str(), home_provider_country.c_str()); 366 } else { 367 if (!dict->GetStringWithoutPathExpansion(shill::kOperatorCodeKey, 368 home_provider_id)) { 369 return false; 370 } 371 LOG(WARNING) 372 << "Provider name and country not defined, using code instead: " 373 << *home_provider_id; 374 } 375 return true; 376 } 377 378 } // namespace shill_property_util 379 380 } // namespace chromeos 381