shill_property_util.cc revision f8ee788a64d60abd8f2d742a5fdedde054ecd910
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
20namespace chromeos {
21
22namespace shill_property_util {
23
24namespace {
25
26// Replace non UTF8 characters in |str| with a replacement character.
27std::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.
48bool 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.
63std::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
72void 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
77std::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
143std::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
159std::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
202scoped_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
215scoped_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
230void 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
240bool 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
313bool 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
332bool 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
346bool 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