1// Copyright (c) 2012 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/network_state.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 "chromeos/network/network_event_log.h"
15#include "chromeos/network/network_profile_handler.h"
16#include "chromeos/network/network_util.h"
17#include "chromeos/network/onc/onc_utils.h"
18#include "third_party/cros_system_api/dbus/service_constants.h"
19
20namespace {
21
22const char kErrorUnknown[] = "Unknown";
23
24bool ConvertListValueToStringVector(const base::ListValue& string_list,
25                                    std::vector<std::string>* result) {
26  for (size_t i = 0; i < string_list.GetSize(); ++i) {
27    std::string str;
28    if (!string_list.GetString(i, &str))
29      return false;
30    result->push_back(str);
31  }
32  return true;
33}
34
35// Replace non UTF8 characters in |str| with a replacement character.
36std::string ValidateUTF8(const std::string& str) {
37  std::string result;
38  for (int32 index = 0; index < static_cast<int32>(str.size()); ++index) {
39    uint32 code_point_out;
40    bool is_unicode_char = base::ReadUnicodeCharacter(str.c_str(), str.size(),
41                                                      &index, &code_point_out);
42    const uint32 kFirstNonControlChar = 0x20;
43    if (is_unicode_char && (code_point_out >= kFirstNonControlChar)) {
44      base::WriteUnicodeCharacter(code_point_out, &result);
45    } else {
46      const uint32 kReplacementChar = 0xFFFD;
47      // Puts kReplacementChar if character is a control character [0,0x20)
48      // or is not readable UTF8.
49      base::WriteUnicodeCharacter(kReplacementChar, &result);
50    }
51  }
52  return result;
53}
54
55bool IsCaCertNssSet(const base::DictionaryValue& properties) {
56  std::string ca_cert_nss;
57  if (properties.GetStringWithoutPathExpansion(flimflam::kEapCaCertNssProperty,
58                                               &ca_cert_nss) &&
59      !ca_cert_nss.empty()) {
60    return true;
61  }
62
63  const base::DictionaryValue* provider = NULL;
64  properties.GetDictionaryWithoutPathExpansion(flimflam::kProviderProperty,
65                                               &provider);
66  if (!provider)
67    return false;
68  if (provider->GetStringWithoutPathExpansion(
69          flimflam::kL2tpIpsecCaCertNssProperty, &ca_cert_nss) &&
70      !ca_cert_nss.empty()) {
71    return true;
72  }
73  if (provider->GetStringWithoutPathExpansion(
74          flimflam::kOpenVPNCaCertNSSProperty, &ca_cert_nss) &&
75      !ca_cert_nss.empty()) {
76    return true;
77  }
78
79  return false;
80}
81
82}  // namespace
83
84namespace chromeos {
85
86NetworkState::NetworkState(const std::string& path)
87    : ManagedState(MANAGED_TYPE_NETWORK, path),
88      auto_connect_(false),
89      favorite_(false),
90      priority_(0),
91      prefix_length_(0),
92      signal_strength_(0),
93      connectable_(false),
94      activate_over_non_cellular_networks_(false),
95      cellular_out_of_credits_(false),
96      has_ca_cert_nss_(false) {
97}
98
99NetworkState::~NetworkState() {
100}
101
102bool NetworkState::PropertyChanged(const std::string& key,
103                                   const base::Value& value) {
104  // Keep care that these properties are the same as in |GetProperties|.
105  if (ManagedStatePropertyChanged(key, value))
106    return true;
107  if (key == flimflam::kSignalStrengthProperty) {
108    return GetIntegerValue(key, value, &signal_strength_);
109  } else if (key == flimflam::kStateProperty) {
110    return GetStringValue(key, value, &connection_state_);
111  } else if (key == flimflam::kConnectableProperty) {
112    return GetBooleanValue(key, value, &connectable_);
113  } else if (key == flimflam::kErrorProperty) {
114    if (!GetStringValue(key, value, &error_))
115      return false;
116    // Shill uses "Unknown" to indicate an unset error state.
117    if (error_ == kErrorUnknown)
118      error_.clear();
119    return true;
120  } else if (key == shill::kErrorDetailsProperty) {
121    return GetStringValue(key, value, &error_details_);
122  } else if (key == IPConfigProperty(flimflam::kAddressProperty)) {
123    return GetStringValue(key, value, &ip_address_);
124  } else if (key == IPConfigProperty(flimflam::kGatewayProperty)) {
125    return GetStringValue(key, value, &gateway_);
126  } else if (key == IPConfigProperty(flimflam::kNameServersProperty)) {
127    const base::ListValue* dns_servers;
128    if (!value.GetAsList(&dns_servers))
129      return false;
130    dns_servers_.clear();
131    ConvertListValueToStringVector(*dns_servers, &dns_servers_);
132    return true;
133  } else if (key == IPConfigProperty(flimflam::kPrefixlenProperty)) {
134    return GetIntegerValue(key, value, &prefix_length_);
135  } else if (key == IPConfigProperty(
136      shill::kWebProxyAutoDiscoveryUrlProperty)) {
137    std::string url_string;
138    if (!GetStringValue(key, value, &url_string))
139      return false;
140    if (url_string.empty()) {
141      web_proxy_auto_discovery_url_ = GURL();
142    } else {
143      GURL gurl(url_string);
144      if (!gurl.is_valid()) {
145        web_proxy_auto_discovery_url_ = gurl;
146      } else {
147        NET_LOG_ERROR("Invalid WebProxyAutoDiscoveryUrl: " + url_string,
148                      path());
149        web_proxy_auto_discovery_url_ = GURL();
150      }
151    }
152    return true;
153  } else if (key == flimflam::kActivationStateProperty) {
154    return GetStringValue(key, value, &activation_state_);
155  } else if (key == flimflam::kRoamingStateProperty) {
156    return GetStringValue(key, value, &roaming_);
157  } else if (key == flimflam::kSecurityProperty) {
158    return GetStringValue(key, value, &security_);
159  } else if (key == flimflam::kAutoConnectProperty) {
160    return GetBooleanValue(key, value, &auto_connect_);
161  } else if (key == flimflam::kFavoriteProperty) {
162    return GetBooleanValue(key, value, &favorite_);
163  } else if (key == flimflam::kPriorityProperty) {
164    return GetIntegerValue(key, value, &priority_);
165  } else if (key == flimflam::kProxyConfigProperty) {
166    std::string proxy_config_str;
167    if (!value.GetAsString(&proxy_config_str)) {
168      NET_LOG_ERROR("Failed to parse " + key, path());
169      return false;
170    }
171
172    proxy_config_.Clear();
173    if (proxy_config_str.empty())
174      return true;
175
176    scoped_ptr<base::DictionaryValue> proxy_config_dict(
177        onc::ReadDictionaryFromJson(proxy_config_str));
178    if (proxy_config_dict) {
179      // Warning: The DictionaryValue returned from
180      // ReadDictionaryFromJson/JSONParser is an optimized derived class that
181      // doesn't allow releasing ownership of nested values. A Swap in the wrong
182      // order leads to memory access errors.
183      proxy_config_.MergeDictionary(proxy_config_dict.get());
184    } else {
185      NET_LOG_ERROR("Failed to parse " + key, path());
186    }
187    return true;
188  } else if (key == flimflam::kUIDataProperty) {
189    if (!GetUIDataFromValue(value, &ui_data_)) {
190      NET_LOG_ERROR("Failed to parse " + key, path());
191      return false;
192    }
193    return true;
194  } else if (key == flimflam::kNetworkTechnologyProperty) {
195    return GetStringValue(key, value, &network_technology_);
196  } else if (key == flimflam::kDeviceProperty) {
197    return GetStringValue(key, value, &device_path_);
198  } else if (key == flimflam::kGuidProperty) {
199    return GetStringValue(key, value, &guid_);
200  } else if (key == flimflam::kProfileProperty) {
201    return GetStringValue(key, value, &profile_path_);
202  } else if (key == shill::kActivateOverNonCellularNetworkProperty) {
203    return GetBooleanValue(key, value, &activate_over_non_cellular_networks_);
204  } else if (key == shill::kOutOfCreditsProperty) {
205    return GetBooleanValue(key, value, &cellular_out_of_credits_);
206  } else if (key == flimflam::kUsageURLProperty) {
207    return GetStringValue(key, value, &usage_url_);
208  } else if (key == flimflam::kPaymentPortalProperty) {
209    const DictionaryValue* dict;
210    if (!value.GetAsDictionary(&dict))
211      return false;
212    if (!dict->GetStringWithoutPathExpansion(
213            flimflam::kPaymentPortalURL, &payment_url_) ||
214        !dict->GetStringWithoutPathExpansion(
215            flimflam::kPaymentPortalMethod, &post_method_) ||
216        !dict->GetStringWithoutPathExpansion(
217            flimflam::kPaymentPortalPostData, &post_data_)) {
218      return false;
219    }
220    return true;
221  }
222  return false;
223}
224
225bool NetworkState::InitialPropertiesReceived(
226    const base::DictionaryValue& properties) {
227  NET_LOG_DEBUG("InitialPropertiesReceived", path());
228  bool changed = UpdateName(properties);
229  bool had_ca_cert_nss = has_ca_cert_nss_;
230  has_ca_cert_nss_ = IsCaCertNssSet(properties);
231  changed |= had_ca_cert_nss != has_ca_cert_nss_;
232  return changed;
233}
234
235void NetworkState::GetProperties(base::DictionaryValue* dictionary) const {
236  // Keep care that these properties are the same as in |PropertyChanged|.
237  dictionary->SetStringWithoutPathExpansion(flimflam::kNameProperty, name());
238  dictionary->SetStringWithoutPathExpansion(flimflam::kTypeProperty, type());
239  dictionary->SetIntegerWithoutPathExpansion(flimflam::kSignalStrengthProperty,
240                                             signal_strength_);
241  dictionary->SetStringWithoutPathExpansion(flimflam::kStateProperty,
242                                            connection_state_);
243  dictionary->SetBooleanWithoutPathExpansion(flimflam::kConnectableProperty,
244                                             connectable_);
245
246  dictionary->SetStringWithoutPathExpansion(flimflam::kErrorProperty,
247                                            error_);
248  dictionary->SetStringWithoutPathExpansion(shill::kErrorDetailsProperty,
249                                            error_details_);
250
251  // IPConfig properties
252  base::DictionaryValue* ipconfig_properties = new base::DictionaryValue;
253  ipconfig_properties->SetStringWithoutPathExpansion(flimflam::kAddressProperty,
254                                                     ip_address_);
255  ipconfig_properties->SetStringWithoutPathExpansion(flimflam::kGatewayProperty,
256                                                     gateway_);
257  base::ListValue* name_servers = new base::ListValue;
258  name_servers->AppendStrings(dns_servers_);
259  ipconfig_properties->SetWithoutPathExpansion(flimflam::kNameServersProperty,
260                                               name_servers);
261  ipconfig_properties->SetIntegerWithoutPathExpansion(
262      flimflam::kPrefixlenProperty, prefix_length_);
263  ipconfig_properties->SetStringWithoutPathExpansion(
264      shill::kWebProxyAutoDiscoveryUrlProperty,
265      web_proxy_auto_discovery_url_.spec());
266  dictionary->SetWithoutPathExpansion(shill::kIPConfigProperty,
267                                      ipconfig_properties);
268
269  dictionary->SetStringWithoutPathExpansion(flimflam::kActivationStateProperty,
270                                            activation_state_);
271  dictionary->SetStringWithoutPathExpansion(flimflam::kRoamingStateProperty,
272                                            roaming_);
273  dictionary->SetStringWithoutPathExpansion(flimflam::kSecurityProperty,
274                                            security_);
275  dictionary->SetBooleanWithoutPathExpansion(flimflam::kAutoConnectProperty,
276                                             auto_connect_);
277  dictionary->SetBooleanWithoutPathExpansion(flimflam::kFavoriteProperty,
278                                             favorite_);
279  dictionary->SetIntegerWithoutPathExpansion(flimflam::kPriorityProperty,
280                                             priority_);
281  // Proxy config and ONC source are intentionally omitted: These properties are
282  // placed in NetworkState to transition ProxyConfigServiceImpl from
283  // NetworkLibrary to the new network stack. The networking extension API
284  // shouldn't depend on this member. Once ManagedNetworkConfigurationHandler
285  // is used instead of NetworkLibrary, we can remove them again.
286  dictionary->SetStringWithoutPathExpansion(
287      flimflam::kNetworkTechnologyProperty,
288      network_technology_);
289  dictionary->SetStringWithoutPathExpansion(flimflam::kDeviceProperty,
290                                            device_path_);
291  dictionary->SetStringWithoutPathExpansion(flimflam::kGuidProperty, guid_);
292  dictionary->SetStringWithoutPathExpansion(flimflam::kProfileProperty,
293                                            profile_path_);
294  dictionary->SetBooleanWithoutPathExpansion(
295      shill::kActivateOverNonCellularNetworkProperty,
296      activate_over_non_cellular_networks_);
297  dictionary->SetBooleanWithoutPathExpansion(shill::kOutOfCreditsProperty,
298                                             cellular_out_of_credits_);
299  base::DictionaryValue* payment_portal_properties = new DictionaryValue;
300  payment_portal_properties->SetStringWithoutPathExpansion(
301      flimflam::kPaymentPortalURL,
302      payment_url_);
303  payment_portal_properties->SetStringWithoutPathExpansion(
304      flimflam::kPaymentPortalMethod,
305      post_method_);
306  payment_portal_properties->SetStringWithoutPathExpansion(
307      flimflam::kPaymentPortalPostData,
308      post_data_);
309  dictionary->SetWithoutPathExpansion(flimflam::kPaymentPortalProperty,
310                                      payment_portal_properties);
311}
312
313bool NetworkState::IsConnectedState() const {
314  return StateIsConnected(connection_state_);
315}
316
317bool NetworkState::IsConnectingState() const {
318  return StateIsConnecting(connection_state_);
319}
320
321bool NetworkState::IsManaged() const {
322  return ui_data_.onc_source() == onc::ONC_SOURCE_DEVICE_POLICY ||
323         ui_data_.onc_source() == onc::ONC_SOURCE_USER_POLICY;
324}
325
326bool NetworkState::IsPrivate() const {
327  return !profile_path_.empty() &&
328      profile_path_ != NetworkProfileHandler::kSharedProfilePath;
329}
330
331std::string NetworkState::GetDnsServersAsString() const {
332  std::string result;
333  for (size_t i = 0; i < dns_servers_.size(); ++i) {
334    if (i != 0)
335      result += ",";
336    result += dns_servers_[i];
337  }
338  return result;
339}
340
341std::string NetworkState::GetNetmask() const {
342  return network_util::PrefixLengthToNetmask(prefix_length_);
343}
344
345bool NetworkState::UpdateName(const base::DictionaryValue& properties) {
346  std::string updated_name = GetNameFromProperties(path(), properties);
347  if (updated_name != name()) {
348    set_name(updated_name);
349    return true;
350  }
351  return false;
352}
353
354// static
355std::string NetworkState::GetNameFromProperties(
356    const std::string& service_path,
357    const base::DictionaryValue& properties) {
358  std::string name;
359  properties.GetStringWithoutPathExpansion(flimflam::kNameProperty, &name);
360
361  std::string hex_ssid;
362  properties.GetStringWithoutPathExpansion(flimflam::kWifiHexSsid, &hex_ssid);
363
364  if (hex_ssid.empty()) {
365    // Validate name for UTF8.
366    std::string valid_ssid = ValidateUTF8(name);
367    if (valid_ssid != name) {
368      NET_LOG_DEBUG("GetNameFromProperties", base::StringPrintf(
369          "%s: UTF8: %s", service_path.c_str(), valid_ssid.c_str()));
370    }
371    return valid_ssid;
372  }
373
374  std::string ssid;
375  std::vector<uint8> raw_ssid_bytes;
376  if (base::HexStringToBytes(hex_ssid, &raw_ssid_bytes)) {
377    ssid = std::string(raw_ssid_bytes.begin(), raw_ssid_bytes.end());
378    NET_LOG_DEBUG("GetNameFromProperties", base::StringPrintf(
379        "%s: %s, SSID: %s", service_path.c_str(),
380        hex_ssid.c_str(), ssid.c_str()));
381  } else {
382    NET_LOG_ERROR("GetNameFromProperties",
383                  base::StringPrintf("%s: Error processing: %s",
384                                     service_path.c_str(), hex_ssid.c_str()));
385    return name;
386  }
387
388  if (IsStringUTF8(ssid)) {
389    if (ssid != name) {
390      NET_LOG_DEBUG("GetNameFromProperties", base::StringPrintf(
391          "%s: UTF8: %s", service_path.c_str(), ssid.c_str()));
392    }
393    return ssid;
394  }
395
396  // Detect encoding and convert to UTF-8.
397  std::string country_code;
398  properties.GetStringWithoutPathExpansion(
399      flimflam::kCountryProperty, &country_code);
400  std::string encoding;
401  if (!base::DetectEncoding(ssid, &encoding)) {
402    // TODO(stevenjb): This is currently experimental. If we find a case where
403    // base::DetectEncoding() fails, we need to figure out whether we can use
404    // country_code with ConvertToUtf8(). crbug.com/233267.
405    encoding = country_code;
406  }
407  if (!encoding.empty()) {
408    std::string utf8_ssid;
409    if (base::ConvertToUtf8AndNormalize(ssid, encoding, &utf8_ssid)) {
410      if (utf8_ssid != name) {
411        NET_LOG_DEBUG("GetNameFromProperties", base::StringPrintf(
412            "%s: Encoding=%s: %s", service_path.c_str(),
413            encoding.c_str(), utf8_ssid.c_str()));
414      }
415      return utf8_ssid;
416    }
417  }
418
419  // Unrecognized encoding. Only use raw bytes if name_ is empty.
420  NET_LOG_DEBUG("GetNameFromProperties", base::StringPrintf(
421      "%s: Unrecognized Encoding=%s: %s", service_path.c_str(),
422      encoding.c_str(), ssid.c_str()));
423  if (name.empty() && !ssid.empty())
424    return ssid;
425  return name;
426}
427
428// static
429bool NetworkState::StateIsConnected(const std::string& connection_state) {
430  return (connection_state == flimflam::kStateReady ||
431          connection_state == flimflam::kStateOnline ||
432          connection_state == flimflam::kStatePortal);
433}
434
435// static
436bool NetworkState::StateIsConnecting(const std::string& connection_state) {
437  return (connection_state == flimflam::kStateAssociation ||
438          connection_state == flimflam::kStateConfiguration ||
439          connection_state == flimflam::kStateCarrier);
440}
441
442// static
443std::string NetworkState::IPConfigProperty(const char* key) {
444  return base::StringPrintf("%s.%s", shill::kIPConfigProperty, key);
445}
446
447// static
448bool NetworkState::GetUIDataFromValue(const base::Value& ui_data_value,
449                                      NetworkUIData* out) {
450  std::string ui_data_str;
451  if (!ui_data_value.GetAsString(&ui_data_str))
452    return false;
453  if (ui_data_str.empty()) {
454    *out = NetworkUIData();
455    return true;
456  }
457  scoped_ptr<base::DictionaryValue> ui_data_dict(
458      chromeos::onc::ReadDictionaryFromJson(ui_data_str));
459  if (!ui_data_dict)
460    return false;
461  *out = NetworkUIData(*ui_data_dict);
462  return true;
463}
464
465}  // namespace chromeos
466