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  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
148std::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
164std::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
207scoped_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
220scoped_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
235void 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
245bool 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
318bool 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
337bool 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
351bool 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