[Sugar-devel] [PATCH sugar-0.94] Don't treat SSID as UTF-8 character sequence (fixes SL#2023)

Sascha Silbe silbe at activitycentral.com
Tue Apr 10 14:39:18 EDT 2012


This is a backport of 7f8ba95a66780828531eba0494e004757bf45c71.

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).

This backport was not tested in any way.

[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 at activitycentral.com>
---
 extensions/deviceicon/network.py   |   17 +++++++------
 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        |   49 +++++++++++++++++++++++++++++++++---
 6 files changed, 80 insertions(+), 33 deletions(-)

diff --git a/extensions/deviceicon/network.py b/extensions/deviceicon/network.py
index 789ea13..713a741 100644
--- a/extensions/deviceicon/network.py
+++ b/extensions/deviceicon/network.py
@@ -385,7 +385,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
@@ -405,7 +406,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)
@@ -482,7 +483,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']
@@ -493,11 +495,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)
@@ -519,7 +521,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()
@@ -531,7 +534,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) == False:
+                network.is_sugar_adhoc_network(self._ssid) == False:
             if state == network.DEVICE_STATE_ACTIVATED:
                 icon_name = '%s-connected' % 'network-wireless'
             else:
diff --git a/src/jarabe/desktop/keydialog.py b/src/jarabe/desktop/keydialog.py
index c72f498..cfe56f7 100644
--- a/src/jarabe/desktop/keydialog.py
+++ b/src/jarabe/desktop/keydialog.py
@@ -90,8 +90,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 677452d..66e34b8 100644
--- a/src/jarabe/desktop/networkviews.py
+++ b/src/jarabe/desktop/networkviews.py
@@ -69,7 +69,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
@@ -80,11 +81,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)
@@ -130,8 +131,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)
@@ -189,7 +190,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 \
@@ -215,7 +216,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:
@@ -244,7 +245,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()
@@ -270,7 +271,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()
 
@@ -347,13 +348,13 @@ 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()
-            settings.connection.id = 'Auto ' + self._name
+            settings.connection.id = 'Auto ' + self._display_name
             uuid = settings.connection.uuid = unique_id()
             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'
@@ -387,12 +388,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)
 
@@ -433,7 +434,7 @@ class WirelessNetworkView(CanvasPulsingIcon):
 
     def is_olpc_mesh(self):
         return self._mode == network.NM_802_11_MODE_ADHOC \
-            and self.name == 'olpc-mesh'
+            and self._ssid == 'olpc-mesh'
 
     def remove_all_aps(self):
         for ap in self._access_points.values():
@@ -601,7 +602,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 f265ae4..63e1ff0 100644
--- a/src/jarabe/model/network.py
+++ b/src/jarabe/model/network.py
@@ -714,7 +714,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
@@ -768,7 +768,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):
@@ -778,7 +778,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:
@@ -1005,3 +1005,46 @@ 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.9.5



More information about the Sugar-devel mailing list