From 92b12556112148e760dffe4dc151010daf7050b3 Mon Sep 17 00:00:00 2001
From: Anish Mangal <anish@activitycentral.com>
Date: Tue, 24 Apr 2012 09:20:00 +0530
Subject: [PATCH sugar] Don't treat SSID as UTF-8 character sequence (fixes
 SL#2023)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Sugar Labs Foundation

This is a backport of 7f8ba95a66780828531eba0494e004757bf45c71 on
dextrose.

IEEE 802.11 [2] defines the SSID as a sequence of octets (i.e. bytes), but
Sugar treated it as UTF-8 character data. While in most cases the SSID is
actually some human-readable string, there's neither a guarantee for that nor
does any (de-facto or de-jure) standard specify the encoding to use. As a
result, we'll encounter SSIDs in a large variety of encodings and will also
need to cope with arbitrary byte strings. Any assumption of a single (or in
fact any) character encoding is incorrect.

The D-Bus API of NetworkManager 0.9 [3] passes SSIDs as uninterpreted byte
strings (D-Bus signature "ay"). Before SSIDs can be displayed on screen, some
kind of interpretation must happen.

NetworkManager has a rather elaborate heuristic that takes the user locale into
account. In the future (i.e. when the NetworkManager client code in Sugar has
been ported to gobject-introspection) we may use nm_utils_ssid_to_utf8() [4],
but for now we're doing the following to allow the user to use non-UTF-8 APs at
all:

1. If the SSID is a valid character string consisting only of printable characters in one of the following encodings (tried in the given order), decode it accordingly: UTF-8, ISO-8859-1, Windows-1251.

2. Return a hex dump of the SSID.

The first rule should cover the majority of current Sugar users and hopefully all AP vendors will switch to UTF-8 eventually. In the meantime, the second rule allows users to distinguish between several APs with SSIDs in unknown encodings (or even using arbitrary byte sequences that don't _have_ a character representation).

Tested:
- filtering on ASCII and non-ASCII parts of the name of and connecting to:
- an unsecured AP with a UTF-8 SSID ("äöüß€sugartest", HostAP)
- an unsecured AP with an ISO-8859-1 SSID ("äöüßsugartest", HostAP)
- an unsecured AP with a non-character SSID (0d:06:f0:0d, HostAP)
- a WEP-secured AP with a UTF-8 name ("äöüß€sugartest2", HostAP)
- a WEP-secured AP with an ISO-8859-1 name ("äöüßsugartest2", HostAP)
- a WEP-secured AP with a non-character SSID (0d:06:f0:0d, HostAP)

In each case the name was displayed correctly in
a) the palette of the AP icon in the Neighbourhood,
b) the palette of the wireless network Frame device and
c) the title of the WLAN credentials (WEP/WPA passphrase) dialog (for the WEP cases).

[1] https://bugs.sugarlabs.org/ticket/2023
[2] http://standards.ieee.org/getieee802/download/802.11-2007.pdf
[3] http://projects.gnome.org/NetworkManager/developers/api/09/spec.html
[4] http://projects.gnome.org/NetworkManager/developers/libnm-util/09/libnm-util-nm-utils.html#nm-utils-ssid-to-utf8

Signed-off-by: Sascha Silbe <silbe@activitycentral.com>
Signed-off-by: Anish Mangal <anish@activitycentral.com>
---
 extensions/deviceicon/network.py   |   19 ++++++++------
 src/jarabe/desktop/keydialog.py    |    5 ++-
 src/jarabe/desktop/meshbox.py      |    4 +-
 src/jarabe/desktop/networkviews.py |   32 ++++++++++++------------
 src/jarabe/model/adhoc.py          |    6 ++--
 src/jarabe/model/network.py        |   48 +++++++++++++++++++++++++++++++++--
 6 files changed, 80 insertions(+), 34 deletions(-)

diff --git a/extensions/deviceicon/network.py b/extensions/deviceicon/network.py
index 08442cd..4627d26 100644
--- a/extensions/deviceicon/network.py
+++ b/extensions/deviceicon/network.py
@@ -439,7 +439,8 @@ class WirelessDeviceView(ToolButton):
         self._device = device
         self._device_props = None
         self._flags = 0
-        self._name = ''
+        self._ssid = ''
+        self._display_name = ''
         self._mode = network.NM_802_11_MODE_UNKNOWN
         self._strength = 0
         self._frequency = 0
@@ -459,7 +460,7 @@ class WirelessDeviceView(ToolButton):
         self._icon.show()
 
         self.set_palette_invoker(FrameWidgetInvoker(self))
-        self._palette = WirelessPalette(self._name)
+        self._palette = WirelessPalette(self._display_name)
         self._palette.connect('deactivate-connection',
                               self.__deactivate_connection_cb)
         self.set_palette(self._palette)
@@ -536,7 +537,8 @@ class WirelessDeviceView(ToolButton):
             self._mode = properties['Mode']
             self._color = None
         if 'Ssid' in properties:
-            self._name = properties['Ssid']
+            self._ssid = properties['Ssid']
+            self._display_name = network.ssid_to_display_name(self._ssid)
             self._color = None
         if 'Strength' in properties:
             self._strength = properties['Strength']
@@ -547,11 +549,11 @@ class WirelessDeviceView(ToolButton):
 
         if self._color == None:
             if self._mode == network.NM_802_11_MODE_ADHOC and \
-                    network.is_sugar_adhoc_network(self._name):
+                    network.is_sugar_adhoc_network(self._ssid):
                 self._color = profile.get_color()
             else:
                 sha_hash = hashlib.sha1()
-                data = self._name + hex(self._flags)
+                data = self._ssid + hex(self._flags)
                 sha_hash.update(data)
                 digest = hash(sha_hash.digest())
                 index = digest % len(xocolor.colors)
@@ -573,7 +575,8 @@ class WirelessDeviceView(ToolButton):
         else:
             self._icon.props.badge_name = None
 
-        self._palette.props.primary_text = glib.markup_escape_text(self._name)
+        label = glib.markup_escape_text(self._display_name)
+        self._palette.props.primary_text = label
 
         self._update_state()
         self._update_color()
@@ -585,7 +588,7 @@ class WirelessDeviceView(ToolButton):
             state = network.DEVICE_STATE_UNKNOWN
 
         if self._mode == network.NM_802_11_MODE_ADHOC and \
-                network.is_sugar_adhoc_network(self._name):
+                network.is_sugar_adhoc_network(self._ssid):
             channel = network.frequency_to_channel(self._frequency)
             if state == network.DEVICE_STATE_ACTIVATED:
                 self._icon.props.icon_name = 'network-adhoc-%s-connected' \
@@ -626,7 +629,7 @@ class WirelessDeviceView(ToolButton):
 
     def __deactivate_connection_cb(self, palette, data=None):
         if self._mode == network.NM_802_11_MODE_INFRA:
-            connection = network.find_connection_by_ssid(self._name)
+            connection = network.find_connection_by_ssid(self._ssid)
             if connection:
                 connection.disable_autoconnect()
 
diff --git a/src/jarabe/desktop/keydialog.py b/src/jarabe/desktop/keydialog.py
index d8c8cf9..62db06f 100644
--- a/src/jarabe/desktop/keydialog.py
+++ b/src/jarabe/desktop/keydialog.py
@@ -99,8 +99,9 @@ class KeyDialog(gtk.Dialog):
 
         self.set_has_separator(False)
 
-        label = gtk.Label("A wireless encryption key is required for\n" \
-                          " the wireless network '%s'." % self._ssid)
+        display_name = network.ssid_to_display_name(ssid)
+        label = gtk.Label(_("A wireless encryption key is required for\n"
+                            " the wireless network '%s'.") % (display_name, ))
         self.vbox.pack_start(label)
 
         self.add_buttons(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
diff --git a/src/jarabe/desktop/meshbox.py b/src/jarabe/desktop/meshbox.py
index 6d5bb48..c111e7e 100644
--- a/src/jarabe/desktop/meshbox.py
+++ b/src/jarabe/desktop/meshbox.py
@@ -554,13 +554,13 @@ class MeshBox(gtk.VBox):
         # if we have mesh hardware, ignore OLPC mesh networks that appear as
         # normal wifi networks
         if len(self._mesh) > 0 and ap.mode == network.NM_802_11_MODE_ADHOC \
-                and ap.name == 'olpc-mesh':
+                and ap.ssid == 'olpc-mesh':
             logging.debug('ignoring OLPC mesh IBSS')
             ap.disconnect()
             return
 
         if self._adhoc_manager is not None and \
-                network.is_sugar_adhoc_network(ap.name) and \
+                network.is_sugar_adhoc_network(ap.ssid) and \
                 ap.mode == network.NM_802_11_MODE_ADHOC:
             if old_hash_value is None:
                 # new Ad-hoc network finished initializing
diff --git a/src/jarabe/desktop/networkviews.py b/src/jarabe/desktop/networkviews.py
index d36aeb3..7a2fc17 100644
--- a/src/jarabe/desktop/networkviews.py
+++ b/src/jarabe/desktop/networkviews.py
@@ -256,7 +256,8 @@ class WirelessNetworkView(CanvasPulsingIcon):
         self._disconnect_item = None
         self._connect_item = None
         self._filtered = False
-        self._name = initial_ap.name
+        self._ssid = initial_ap.ssid
+        self._display_name = network.ssid_to_display_name(self._ssid)
         self._mode = initial_ap.mode
         self._strength = initial_ap.strength
         self._flags = initial_ap.flags
@@ -267,11 +268,11 @@ class WirelessNetworkView(CanvasPulsingIcon):
         self._color = None
 
         if self._mode == network.NM_802_11_MODE_ADHOC and \
-                network.is_sugar_adhoc_network(self._name):
+                network.is_sugar_adhoc_network(self._ssid):
             self._color = profile.get_color()
         else:
             sha_hash = hashlib.sha1()
-            data = self._name + hex(self._flags)
+            data = self._ssid + hex(self._flags)
             sha_hash.update(data)
             digest = hash(sha_hash.digest())
             index = digest % len(xocolor.colors)
@@ -317,8 +318,8 @@ class WirelessNetworkView(CanvasPulsingIcon):
                                   icon_size=style.STANDARD_ICON_SIZE,
                                   badge_name=self.props.badge_name)
 
-        p = palette.Palette(primary_text=glib.markup_escape_text(self._name),
-                            icon=self._palette_icon)
+        label = glib.markup_escape_text(self._display_name)
+        p = palette.Palette(primary_text=label, icon=self._palette_icon)
 
         self._connect_item = MenuItem(_('Connect'), 'dialog-ok')
         self._connect_item.connect('activate', self.__connect_activate_cb)
@@ -376,7 +377,7 @@ class WirelessNetworkView(CanvasPulsingIcon):
 
     def _update_icon(self):
         if self._mode == network.NM_802_11_MODE_ADHOC and \
-                network.is_sugar_adhoc_network(self._name):
+                network.is_sugar_adhoc_network(self._ssid):
             channel = max([1] + [ap.channel for ap in
                                  self._access_points.values()])
             if self._device_state == network.DEVICE_STATE_ACTIVATED and \
@@ -402,7 +403,7 @@ class WirelessNetworkView(CanvasPulsingIcon):
 
     def _update_badge(self):
         if self._mode != network.NM_802_11_MODE_ADHOC:
-            if network.find_connection_by_ssid(self._name) is not None:
+            if network.find_connection_by_ssid(self._ssid) is not None:
                 self.props.badge_name = 'emblem-favorite'
                 self._palette_icon.props.badge_name = 'emblem-favorite'
             elif self._flags == network.NM_802_11_AP_FLAGS_PRIVACY:
@@ -431,7 +432,7 @@ class WirelessNetworkView(CanvasPulsingIcon):
             self._palette.props.secondary_text = _('Connecting...')
             self.props.pulsing = True
         elif state == network.DEVICE_STATE_ACTIVATED:
-            connection = network.find_connection_by_ssid(self._name)
+            connection = network.find_connection_by_ssid(self._ssid)
             if connection is not None:
                 if self._mode == network.NM_802_11_MODE_INFRA:
                     connection.set_connected()
@@ -457,7 +458,7 @@ class WirelessNetworkView(CanvasPulsingIcon):
 
     def _disconnect_activate_cb(self, item):
         if self._mode == network.NM_802_11_MODE_INFRA:
-            connection = network.find_connection_by_ssid(self._name)
+            connection = network.find_connection_by_ssid(self._ssid)
             if connection:
                 connection.disable_autoconnect()
 
@@ -594,22 +595,22 @@ class WirelessNetworkView(CanvasPulsingIcon):
         self._connect()
 
     def _connect(self):
-        connection = network.find_connection_by_ssid(self._name)
+        connection = network.find_connection_by_ssid(self._ssid)
         if connection is None:
             settings = Settings()
             self._settings = settings
-            settings.connection.id = 'Auto ' + self._name
+            settings.connection.id = 'Auto ' + self._ssid
             uuid = settings.connection.uuid = unique_id()
             self._uuid = uuid
             settings.connection.type = '802-11-wireless'
-            settings.wireless.ssid = self._name
+            settings.wireless.ssid = self._ssid
 
             if self._mode == network.NM_802_11_MODE_INFRA:
                 settings.wireless.mode = 'infrastructure'
             elif self._mode == network.NM_802_11_MODE_ADHOC:
                 settings.wireless.mode = 'adhoc'
                 settings.wireless.band = 'bg'
-                if network.is_sugar_adhoc_network(self._name):
+                if network.is_sugar_adhoc_network(self._ssid):
                     settings.ip4_config = IP4Config()
                     settings.ip4_config.method = 'link-local'
 
@@ -641,12 +642,12 @@ class WirelessNetworkView(CanvasPulsingIcon):
         logging.error('Failed to activate connection: %s', err)
 
     def set_filter(self, query):
-        self._filtered = self._name.lower().find(query) == -1
+        self._filtered = self._display_name.lower().find(query) == -1
         self._update_icon()
         self._update_color()
 
     def create_keydialog(self, settings, response):
-        keydialog.create(self._name, self._flags, self._wpa_flags,
+        keydialog.create(self._ssid, self._flags, self._wpa_flags,
                          self._rsn_flags, self._device_caps, settings,
                          response)
 
@@ -855,7 +856,6 @@ class OlpcMeshView(CanvasPulsingIcon):
         self._disconnect_item = None
         self._connect_item = None
         self._filtered = False
-        self._name = ''
         self._device_state = None
         self._active = False
         device = mesh_mgr.mesh_device
diff --git a/src/jarabe/model/adhoc.py b/src/jarabe/model/adhoc.py
index 647bd8e..ab540fa 100644
--- a/src/jarabe/model/adhoc.py
+++ b/src/jarabe/model/adhoc.py
@@ -249,13 +249,13 @@ class AdHocManager(gobject.GObject):
         access_point -- Access Point
 
         """
-        if access_point.name.endswith(' 1'):
+        if access_point.ssid.endswith(' 1'):
             self._networks[self._CHANNEL_1] = access_point
             self.emit('members-changed', self._CHANNEL_1, True)
-        elif access_point.name.endswith(' 6'):
+        elif access_point.ssid.endswith(' 6'):
             self._networks[self._CHANNEL_6] = access_point
             self.emit('members-changed', self._CHANNEL_6, True)
-        elif access_point.name.endswith('11'):
+        elif access_point.ssid.endswith('11'):
             self._networks[self._CHANNEL_11] = access_point
             self.emit('members-changed', self._CHANNEL_11, True)
 
diff --git a/src/jarabe/model/network.py b/src/jarabe/model/network.py
index 236c2ba..0ff6e7d 100644
--- a/src/jarabe/model/network.py
+++ b/src/jarabe/model/network.py
@@ -725,7 +725,7 @@ class AccessPoint(gobject.GObject):
         self._initialized = False
         self._bus = dbus.SystemBus()
 
-        self.name = ''
+        self.ssid = ''
         self.strength = 0
         self.flags = 0
         self.wpa_flags = 0
@@ -779,7 +779,7 @@ class AccessPoint(gobject.GObject):
         else:
             fl |= 1 << 6
 
-        hashstr = str(fl) + '@' + self.name
+        hashstr = str(fl) + '@' + self.ssid
         return hash(hashstr)
 
     def _update_properties(self, properties):
@@ -789,7 +789,7 @@ class AccessPoint(gobject.GObject):
             old_hash = None
 
         if 'Ssid' in properties:
-            self.name = properties['Ssid']
+            self.ssid = properties['Ssid']
         if 'Strength' in properties:
             self.strength = properties['Strength']
         if 'Flags' in properties:
@@ -1026,3 +1026,45 @@ def disconnect_access_points(ap_paths):
             dev_obj = bus.get_object(NM_SERVICE, dev_path)
             dev = dbus.Interface(dev_obj, NM_DEVICE_IFACE)
             dev.Disconnect()
+
+def _is_non_printable(char):
+    """
+    Return True if char is a non-printable unicode character, False otherwise
+    """
+    return (char < u' ') or (u'~' < char < u'\xA0') or (char == u'\xAD')
+
+
+def ssid_to_display_name(ssid):
+    """Convert an SSID into a unicode string for recognising Access Points
+
+    Return a unicode string that's useful for recognising and
+    distinguishing between Access Points (APs).
+
+    IEEE 802.11 defines SSIDs as arbitrary byte sequences. As random
+    bytes are not very user-friendly, most APs use some human-readable
+    character string as SSID. However, because there's no standard
+    specifying what encoding to use, AP vendors chose various
+    different encodings. Since there's also no indication of what
+    encoding was used for a particular SSID, the best we can do for
+    turning an SSID into a displayable string is to try a couple of
+    encodings based on some heuristic.
+
+    We're currently using the following heuristic:
+
+    1. If the SSID is a valid character string consisting only of
+       printable characters in one of the following encodings (tried in
+       the given order), decode it accordingly:
+       UTF-8, ISO-8859-1, Windows-1251.
+    2. Return a hex dump of the SSID.
+    """
+    for encoding in ['utf-8', 'iso-8859-1', 'windows-1251']:
+        try:
+            display_name = unicode(ssid, encoding)
+        except UnicodeDecodeError:
+            continue
+
+        if not [True for char in display_name if _is_non_printable(char)]:
+            # Only printable characters
+            return display_name
+
+    return ':'.join(['%02x' % (ord(byte), ) for byte in ssid])
-- 
1.7.4.4

