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 <stdint.h>
8
9 #include <memory>
10 #include <set>
11
12 #include "base/i18n/encoding_detection.h"
13 #include "base/i18n/icu_string_conversions.h"
14 #include "base/notreached.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/stringprintf.h"
18 #include "base/strings/utf_string_conversion_utils.h"
19 #include "base/values.h"
20 #include "chromeos/network/network_event_log.h"
21 #include "chromeos/network/network_ui_data.h"
22 #include "chromeos/network/onc/onc_utils.h"
23 #include "third_party/cros_system_api/dbus/service_constants.h"
24
25 namespace chromeos {
26
27 namespace shill_property_util {
28
29 namespace {
30
31 // Replace non UTF8 characters in |str| with a replacement character.
ValidateUTF8(const std::string & str)32 std::string ValidateUTF8(const std::string& str) {
33 std::string result;
34 for (int32_t index = 0; index < static_cast<int32_t>(str.size()); ++index) {
35 uint32_t code_point_out;
36 bool is_unicode_char = base::ReadUnicodeCharacter(
37 str.c_str(), str.size(), &index, &code_point_out);
38 const uint32_t kFirstNonControlChar = 0x20;
39 if (is_unicode_char && (code_point_out >= kFirstNonControlChar)) {
40 base::WriteUnicodeCharacter(code_point_out, &result);
41 } else {
42 const uint32_t kReplacementChar = 0xFFFD;
43 // Puts kReplacementChar if character is a control character [0,0x20)
44 // or is not readable UTF8.
45 base::WriteUnicodeCharacter(kReplacementChar, &result);
46 }
47 }
48 return result;
49 }
50
51 // If existent and non-empty, copies the string at |key| from |source| to
52 // |dest|. Returns true if the string was copied.
CopyStringFromDictionary(const base::DictionaryValue & source,const std::string & key,base::DictionaryValue * dest)53 bool CopyStringFromDictionary(const base::DictionaryValue& source,
54 const std::string& key,
55 base::DictionaryValue* dest) {
56 std::string string_value;
57 if (!source.GetStringWithoutPathExpansion(key, &string_value) ||
58 string_value.empty()) {
59 return false;
60 }
61 dest->SetKey(key, base::Value(string_value));
62 return true;
63 }
64
GetStringFromDictionary(const base::Value * dict,const char * key)65 std::string GetStringFromDictionary(const base::Value* dict, const char* key) {
66 const base::Value* v = dict ? dict->FindKey(key) : nullptr;
67 return v ? v->GetString() : std::string();
68 }
69
70 } // namespace
71
SetSSID(const std::string & ssid,base::Value * properties)72 void SetSSID(const std::string& ssid, base::Value* properties) {
73 std::string hex_ssid = base::HexEncode(ssid.c_str(), ssid.size());
74 properties->SetKey(shill::kWifiHexSsid, base::Value(hex_ssid));
75 }
76
GetSSIDFromProperties(const base::Value & properties,bool verbose_logging,bool * unknown_encoding)77 std::string GetSSIDFromProperties(const base::Value& properties,
78 bool verbose_logging,
79 bool* unknown_encoding) {
80 if (unknown_encoding)
81 *unknown_encoding = false;
82
83 // Get name for debugging.
84 std::string name = GetStringFromDictionary(&properties, shill::kNameProperty);
85 std::string hex_ssid =
86 GetStringFromDictionary(&properties, shill::kWifiHexSsid);
87
88 if (hex_ssid.empty()) {
89 if (verbose_logging)
90 NET_LOG(DEBUG) << "GetSSIDFromProperties: No HexSSID set: " << name;
91 return std::string();
92 }
93
94 std::string ssid;
95 std::string ssid_bytes;
96 if (base::HexStringToString(hex_ssid, &ssid_bytes)) {
97 ssid = ssid_bytes;
98 VLOG(2) << "GetSSIDFromProperties: " << name << " HexSsid=" << hex_ssid
99 << " SSID=" << ssid;
100 } else {
101 NET_LOG(ERROR) << "GetSSIDFromProperties: " << name
102 << " Error processing HexSsid: " << hex_ssid;
103 return std::string();
104 }
105
106 if (base::IsStringUTF8(ssid))
107 return ssid;
108
109 // Detect encoding and convert to UTF-8.
110 std::string encoding;
111 if (!base::DetectEncoding(ssid, &encoding)) {
112 // TODO(stevenjb): This is currently experimental. If we find a case where
113 // base::DetectEncoding() fails, we need to figure out whether we can use
114 // country_code with ConvertToUtf8(). crbug.com/233267.
115 encoding = GetStringFromDictionary(&properties, shill::kCountryProperty);
116 }
117 std::string utf8_ssid;
118 if (!encoding.empty() &&
119 base::ConvertToUtf8AndNormalize(ssid, encoding, &utf8_ssid)) {
120 if (utf8_ssid != ssid) {
121 if (verbose_logging) {
122 NET_LOG(DEBUG) << "GetSSIDFromProperties: " << name
123 << " Encoding=" << encoding << " SSID=" << ssid
124 << " UTF8 SSID=" << utf8_ssid;
125 }
126 }
127 return utf8_ssid;
128 }
129
130 if (unknown_encoding)
131 *unknown_encoding = true;
132 if (verbose_logging) {
133 NET_LOG(DEBUG) << "GetSSIDFromProperties: " << name
134 << " Unrecognized Encoding=" << encoding;
135 }
136 return ssid;
137 }
138
GetNetworkIdFromProperties(const base::Value & properties)139 std::string GetNetworkIdFromProperties(const base::Value& properties) {
140 if (properties.DictEmpty())
141 return "EmptyProperties";
142 std::string guid = GetStringFromDictionary(&properties, shill::kGuidProperty);
143 if (!guid.empty())
144 return NetworkGuidId(guid);
145 std::string type = GetStringFromDictionary(&properties, shill::kTypeProperty);
146 if (!type.empty()) {
147 std::string security =
148 GetStringFromDictionary(&properties, shill::kSecurityClassProperty);
149 if (!security.empty())
150 return type + "_" + security + "_unconfigured";
151 }
152 return "<Unconfigured Network>";
153 }
154
GetNameFromProperties(const std::string & service_path,const base::Value & properties)155 std::string GetNameFromProperties(const std::string& service_path,
156 const base::Value& properties) {
157 std::string name = GetStringFromDictionary(&properties, shill::kNameProperty);
158 std::string validated_name = ValidateUTF8(name);
159 if (validated_name != name) {
160 NET_LOG(DEBUG) << "GetNameFromProperties: " << service_path
161 << " Validated name=" << validated_name << " name=" << name;
162 }
163
164 std::string type = GetStringFromDictionary(&properties, shill::kTypeProperty);
165 if (type.empty()) {
166 NET_LOG(ERROR) << "GetNameFromProperties: " << service_path << " No type.";
167 return validated_name;
168 }
169 if (!NetworkTypePattern::WiFi().MatchesType(type))
170 return validated_name;
171
172 bool unknown_ssid_encoding = false;
173 std::string ssid = GetSSIDFromProperties(
174 properties, true /* verbose_logging */, &unknown_ssid_encoding);
175 if (ssid.empty()) {
176 NET_LOG(ERROR) << "GetNameFromProperties: " << service_path
177 << " No SSID set";
178 }
179
180 // Use |validated_name| if |ssid| is empty.
181 // And if the encoding of the SSID is unknown, use |ssid|, which contains raw
182 // bytes in that case, only if |validated_name| is empty.
183 if (ssid.empty() || (unknown_ssid_encoding && !validated_name.empty()))
184 return validated_name;
185
186 if (ssid != validated_name) {
187 NET_LOG(DEBUG) << "GetNameFromProperties: " << service_path
188 << " SSID=" << ssid << " Validated name=" << validated_name;
189 }
190 return ssid;
191 }
192
GetUIDataFromValue(const base::Value & ui_data_value)193 std::unique_ptr<NetworkUIData> GetUIDataFromValue(
194 const base::Value& ui_data_value) {
195 std::string ui_data_str;
196 if (!ui_data_value.GetAsString(&ui_data_str))
197 return std::unique_ptr<NetworkUIData>();
198 if (ui_data_str.empty())
199 return std::make_unique<NetworkUIData>();
200 base::Value ui_data_dict = chromeos::onc::ReadDictionaryFromJson(ui_data_str);
201 if (!ui_data_dict.is_dict())
202 return std::unique_ptr<NetworkUIData>();
203 return std::make_unique<NetworkUIData>(ui_data_dict);
204 }
205
GetUIDataFromProperties(const base::DictionaryValue & shill_dictionary)206 std::unique_ptr<NetworkUIData> GetUIDataFromProperties(
207 const base::DictionaryValue& shill_dictionary) {
208 const base::Value* ui_data_value = NULL;
209 shill_dictionary.GetWithoutPathExpansion(shill::kUIDataProperty,
210 &ui_data_value);
211 if (!ui_data_value) {
212 VLOG(2) << "Dictionary has no UIData entry.";
213 return std::unique_ptr<NetworkUIData>();
214 }
215 std::unique_ptr<NetworkUIData> ui_data = GetUIDataFromValue(*ui_data_value);
216 if (!ui_data)
217 LOG(ERROR) << "UIData is not a valid JSON dictionary.";
218 return ui_data;
219 }
220
SetUIData(const NetworkUIData & ui_data,base::DictionaryValue * shill_dictionary)221 void SetUIData(const NetworkUIData& ui_data,
222 base::DictionaryValue* shill_dictionary) {
223 shill_dictionary->SetKey(shill::kUIDataProperty,
224 base::Value(ui_data.GetAsJson()));
225 }
226
CopyIdentifyingProperties(const base::DictionaryValue & service_properties,const bool properties_read_from_shill,base::DictionaryValue * dest)227 bool CopyIdentifyingProperties(const base::DictionaryValue& service_properties,
228 const bool properties_read_from_shill,
229 base::DictionaryValue* dest) {
230 bool success = true;
231
232 // GUID is optional.
233 CopyStringFromDictionary(service_properties, shill::kGuidProperty, dest);
234
235 std::string type;
236 service_properties.GetStringWithoutPathExpansion(shill::kTypeProperty, &type);
237 success &= !type.empty();
238 dest->SetKey(shill::kTypeProperty, base::Value(type));
239 if (type == shill::kTypeWifi) {
240 success &=
241 CopyStringFromDictionary(
242 service_properties, shill::kSecurityClassProperty, dest);
243 success &=
244 CopyStringFromDictionary(service_properties, shill::kWifiHexSsid, dest);
245 success &= CopyStringFromDictionary(
246 service_properties, shill::kModeProperty, dest);
247 } else if (type == shill::kTypeCellular) {
248 success &= CopyStringFromDictionary(
249 service_properties, shill::kNetworkTechnologyProperty, dest);
250 } else if (type == shill::kTypeVPN) {
251 success &= CopyStringFromDictionary(
252 service_properties, shill::kNameProperty, dest);
253
254 // VPN Provider values are read from the "Provider" dictionary, but written
255 // with the keys "Provider.Type" and "Provider.Host".
256 // TODO(pneubeck): Simplify this once http://crbug.com/381135 is fixed.
257 std::string vpn_provider_type;
258 std::string vpn_provider_host;
259 if (properties_read_from_shill) {
260 const base::DictionaryValue* provider_properties = NULL;
261 if (!service_properties.GetDictionaryWithoutPathExpansion(
262 shill::kProviderProperty, &provider_properties)) {
263 NET_LOG(ERROR) << "Missing VPN provider dict: "
264 << GetNetworkIdFromProperties(service_properties);
265 }
266 provider_properties->GetStringWithoutPathExpansion(shill::kTypeProperty,
267 &vpn_provider_type);
268 provider_properties->GetStringWithoutPathExpansion(shill::kHostProperty,
269 &vpn_provider_host);
270 } else {
271 service_properties.GetStringWithoutPathExpansion(
272 shill::kProviderTypeProperty, &vpn_provider_type);
273 service_properties.GetStringWithoutPathExpansion(
274 shill::kProviderHostProperty, &vpn_provider_host);
275 }
276 success &= !vpn_provider_type.empty();
277 dest->SetKey(shill::kProviderTypeProperty, base::Value(vpn_provider_type));
278
279 success &= !vpn_provider_host.empty();
280 dest->SetKey(shill::kProviderHostProperty, base::Value(vpn_provider_host));
281 } else if (type == shill::kTypeEthernet || type == shill::kTypeEthernetEap) {
282 // Ethernet and EthernetEAP don't have any additional identifying
283 // properties.
284 } else {
285 NET_LOG(ERROR) << "Unsupported network type " << type;
286 success = false;
287 }
288 if (!success) {
289 NET_LOG(ERROR) << "Missing required properties: "
290 << GetNetworkIdFromProperties(service_properties);
291 }
292 return success;
293 }
294
DoIdentifyingPropertiesMatch(const base::DictionaryValue & new_properties,const base::DictionaryValue & old_properties)295 bool DoIdentifyingPropertiesMatch(const base::DictionaryValue& new_properties,
296 const base::DictionaryValue& old_properties) {
297 base::DictionaryValue new_identifying;
298 if (!CopyIdentifyingProperties(
299 new_properties,
300 false /* properties were not read from Shill */,
301 &new_identifying)) {
302 return false;
303 }
304 base::DictionaryValue old_identifying;
305 if (!CopyIdentifyingProperties(old_properties,
306 true /* properties were read from Shill */,
307 &old_identifying)) {
308 return false;
309 }
310
311 return new_identifying.Equals(&old_identifying);
312 }
313
IsLoggableShillProperty(const std::string & key)314 bool IsLoggableShillProperty(const std::string& key) {
315 static std::set<std::string>* s_skip_properties = nullptr;
316 if (!s_skip_properties) {
317 s_skip_properties = new std::set<std::string>;
318 s_skip_properties->insert(shill::kApnPasswordProperty);
319 s_skip_properties->insert(shill::kEapCaCertPemProperty);
320 s_skip_properties->insert(shill::kEapCaCertProperty);
321 s_skip_properties->insert(shill::kEapPasswordProperty);
322 s_skip_properties->insert(shill::kEapPinProperty);
323 s_skip_properties->insert(shill::kL2tpIpsecCaCertPemProperty);
324 s_skip_properties->insert(shill::kL2tpIpsecPasswordProperty);
325 s_skip_properties->insert(shill::kL2tpIpsecPinProperty);
326 s_skip_properties->insert(shill::kL2tpIpsecPskProperty);
327 s_skip_properties->insert(shill::kOpenVPNAuthUserPassProperty);
328 s_skip_properties->insert(shill::kOpenVPNCaCertPemProperty);
329 s_skip_properties->insert(shill::kOpenVPNExtraCertPemProperty);
330 s_skip_properties->insert(shill::kOpenVPNOTPProperty);
331 s_skip_properties->insert(shill::kOpenVPNPasswordProperty);
332 s_skip_properties->insert(shill::kOpenVPNPinProperty);
333 s_skip_properties->insert(shill::kOpenVPNTLSAuthContentsProperty);
334 s_skip_properties->insert(shill::kPPPoEPasswordProperty);
335 s_skip_properties->insert(shill::kPassphraseProperty);
336 }
337 return s_skip_properties->count(key) == 0;
338 }
339
340 } // namespace shill_property_util
341
342 } // namespace chromeos
343