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.
ValidateUTF8(const std::string & str)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.
CopyStringFromDictionary(const base::DictionaryValue & source,const std::string & key,base::DictionaryValue * dest)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.
GetSecurityClass(const std::string & security)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
SetSSID(const std::string ssid,base::DictionaryValue * properties)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
GetSSIDFromProperties(const base::DictionaryValue & properties,bool * unknown_encoding)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
GetNetworkIdFromProperties(const base::DictionaryValue & properties)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
GetNameFromProperties(const std::string & service_path,const base::DictionaryValue & properties)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
GetUIDataFromValue(const base::Value & ui_data_value)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
GetUIDataFromProperties(const base::DictionaryValue & shill_dictionary)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
SetUIData(const NetworkUIData & ui_data,base::DictionaryValue * shill_dictionary)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
CopyIdentifyingProperties(const base::DictionaryValue & service_properties,const bool properties_read_from_shill,base::DictionaryValue * dest)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
DoIdentifyingPropertiesMatch(const base::DictionaryValue & new_properties,const base::DictionaryValue & old_properties)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
IsPassphraseKey(const std::string & key)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
GetHomeProviderFromProperty(const base::Value & value,std::string * home_provider_id)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