[PATCH] Add ASCII-only upper and lower string functions.

David Shea dshea at redhat.com
Tue Aug 13 15:59:09 UTC 2013


The behavior of the upper() and lower() string methods differs based on
the current locale, which can cause issues when attempting the compare
to result to an untranslated string constant. Added two functions to
perform ASCII-only conversions and applied them where appropriate.
---
 pyanaconda/iutil.py                 | 37 +++++++++++++++++++++++++++++++++++++
 pyanaconda/network.py               | 14 +++++++-------
 pyanaconda/packaging/livepayload.py |  4 ++--
 pyanaconda/queue.py                 |  5 +++--
 pyanaconda/simpleconfig.py          | 20 +++++---------------
 pyanaconda/ui/gui/spokes/custom.py  | 18 ++++++++++--------
 6 files changed, 64 insertions(+), 34 deletions(-)

diff --git a/pyanaconda/iutil.py b/pyanaconda/iutil.py
index 37fa1f8..5ddb703 100644
--- a/pyanaconda/iutil.py
+++ b/pyanaconda/iutil.py
@@ -28,6 +28,8 @@ import errno
 import subprocess
 import re
 import unicodedata
+import string
+import types
 from threading import Thread
 from Queue import Queue, Empty
 
@@ -723,3 +725,38 @@ def is_unsupported_hw():
     if status:
         log.debug("Installing on Unsupported Hardware")
     return status
+
+# Define translations between ASCII uppercase and lowercase for
+# locale-independent string conversions. The tables are 256-byte string used
+# with string.translate. If string.translate is used with a unicode string,
+# even if the string contains only 7-bit characters, string.translate will
+# raise a UnicodeDecodeError.
+_ASCIIupper_table = string.maketrans(string.ascii_lowercase, 
+        string.ascii_uppercase)
+_ASCIIlower_table = string.maketrans(string.ascii_uppercase,
+        string.ascii_lowercase)
+
+def _toASCII(s):
+    """Convert a unicode string to ASCII"""
+    if type(s) == types.UnicodeType:
+        # Decompose the string using the NFK decomposition, which in addition
+        # to the canonical decomposition replaces characters based on
+        # compatibility equivalence (e.g., ROMAN NUMERAL ONE has its own code
+        # point but it's really just a capital I), so that we can keep as much
+        # of the ASCII part of the string as possible.
+        s = unicodedata.normalize('NKFD', s).encode('ascii', 'ignore')
+    return s
+
+def upperASCII(s):
+    """Convert a string to uppercase using only ASCII character definitions.
+
+    The returned string will contain only ASCII characters. This function is
+    locale-independent."""
+    return string.translate(_toASCII(s), _ASCIIupper_table)
+
+def lowerASCII(s):
+    """Convert a string to lowercase using only ASCII character definitions.
+
+    The returned string will contain only ASCII characters. This function is
+    locale-independent."""
+    return string.translate(_toASCII(s), _ASCIIlower_table)
diff --git a/pyanaconda/network.py b/pyanaconda/network.py
index 8b2a73e..33ab04b 100644
--- a/pyanaconda/network.py
+++ b/pyanaconda/network.py
@@ -374,7 +374,7 @@ def dracutBootArguments(ifcfg, storage_ipaddr, hostname=None):
                 netargs.add("ip=%s::%s:%s:%s:%s:none" % (ipaddr, gateway,
                            ifcfg.get('PREFIX'), hostname, devname))
         else:
-            if ifcfg.get('bootproto').lower() == 'dhcp':
+            if iutil.lowerASCII(ifcfg.get('bootproto')) == 'dhcp':
                 netargs.add("ip=%s:dhcp" % devname)
             else:
                 if ifcfg.get('GATEWAY'):
@@ -431,7 +431,7 @@ def kickstartNetworkData(ifcfg=None, hostname=None):
     if not ifcfg.get('BOOTPROTO'):
         kwargs["noipv4"] = True
     else:
-        if ifcfg.get('BOOTPROTO').lower() == 'dhcp':
+        if iutil.lowerASCII(ifcfg.get('BOOTPROTO')) == 'dhcp':
             kwargs["bootProto"] = "dhcp"
             if ifcfg.get('DHCPCLASS'):
                 kwargs["dhcpclass"] = ifcfg.get('DHCPCLASS')
@@ -477,7 +477,7 @@ def kickstartNetworkData(ifcfg=None, hostname=None):
     # ipv4 and ipv6
     dnsline = ''
     for key in ifcfg.info.keys():
-        if key.upper().startswith('DNS'):
+        if iutil.upperASCII(key).startswith('DNS'):
             if dnsline == '':
                 dnsline = ifcfg.get(key)
             else:
@@ -494,7 +494,7 @@ def kickstartNetworkData(ifcfg=None, hostname=None):
     # hostname
     if ifcfg.get("DHCP_HOSTNAME"):
         kwargs["hostname"] = ifcfg.get("DHCP_HOSTNAME")
-    elif ifcfg.get("BOOTPROTO").lower != "dhcp":
+    elif iutil.lowerASCII(ifcfg.get("BOOTPROTO")) != "dhcp":
         if (hostname and
             hostname != DEFAULT_HOSTNAME):
             kwargs["hostname"] = hostname
@@ -811,9 +811,9 @@ def get_device_name(devspec):
         log.info("unspecified network --device in kickstart, using %s (%s)" %
                  (devname, msg))
     else:
-        if devspec.lower() == "ibft":
+        if iutil.lowerASCII(devspec) == "ibft":
             devname = ""
-        if devspec.lower() == "link":
+        if iutil.lowerASCII(devspec) == "link":
             for dev in sorted(devices):
                 try:
                     link_up = nm.nm_device_carrier(dev)
@@ -825,7 +825,7 @@ def get_device_name(devspec):
                     break
             else:
                 log.error("Kickstart: No network device with link found")
-        elif devspec.lower() == "bootif":
+        elif iutil.lowerASCII(devspec) == "bootif":
             if "BOOTIF" in flags.cmdline:
                 # MAC address like 01-aa-bb-cc-dd-ee-ff
                 devname = flags.cmdline["BOOTIF"][3:]
diff --git a/pyanaconda/packaging/livepayload.py b/pyanaconda/packaging/livepayload.py
index d412385..c52cfca 100644
--- a/pyanaconda/packaging/livepayload.py
+++ b/pyanaconda/packaging/livepayload.py
@@ -35,7 +35,7 @@ from time import sleep
 from threading import Lock
 from urlgrabber.grabber import URLGrabber
 from urlgrabber.grabber import URLGrabError
-from pyanaconda.iutil import ProxyString, ProxyStringError
+from pyanaconda.iutil import ProxyString, ProxyStringError, lowerASCII
 import urllib
 import hashlib
 import glob
@@ -297,7 +297,7 @@ class LiveImageKSPayload(LiveImagePayload):
             filesum = sha256.hexdigest()
             log.debug("sha256 of %s is %s" % (self.data.method.url, filesum))
 
-            if self.data.method.checksum.lower() != filesum:
+            if lowerASCII(self.data.method.checksum) != filesum:
                 log.error("%s does not match checksum." % self.data.method.checksum)
                 exn = PayloadInstallError("Checksum of image does not match")
                 if errorHandler.cb(exn) == ERROR_RAISE:
diff --git a/pyanaconda/queue.py b/pyanaconda/queue.py
index bdf69ee..22ea5de 100644
--- a/pyanaconda/queue.py
+++ b/pyanaconda/queue.py
@@ -19,6 +19,7 @@
 # Author(s): Chris Lumens <clumens at redhat.com>
 
 import Queue
+from iutil import lowerASCII, upperASCII
 
 class QueueFactory(object):
     """Constructs a new object wrapping a Queue.Queue, complete with constants
@@ -65,12 +66,12 @@ class QueueFactory(object):
             raise AttributeError("%s queue already has a message named %s" % (self.name, name))
 
         # Add a constant.
-        const_name = self.name.upper() + "_CODE_" + name.upper()
+        const_name = upperASCII(self.name) + "_CODE_" + upperASCII(name)
         setattr(self, const_name, self.__counter)
         self.__counter += 1
 
         # Add a convenience method for putting things into the queue.
-        method_name = "send_" + name.lower()
+        method_name = "send_" + lowerASCII(name)
         method = self._makeMethod(getattr(self, const_name), method_name, argc)
         setattr(self, method_name, method)
 
diff --git a/pyanaconda/simpleconfig.py b/pyanaconda/simpleconfig.py
index c5ac0b8..45c54e3 100644
--- a/pyanaconda/simpleconfig.py
+++ b/pyanaconda/simpleconfig.py
@@ -21,17 +21,7 @@ import string
 import shlex
 from pipes import _safechars
 import tempfile
-
-# use our own ASCII only uppercase function to avoid locale issues
-# not going to be fast but not important
-def uppercase_ASCII_string(s):
-    newstr = ""
-    for c in s:
-        if c in string.lowercase:
-            newstr += chr(ord(c)-32)
-        else:
-            newstr += c
-    return newstr
+from iutil import upperASCII
 
 def unquote(s):
     return ' '.join(shlex.split(s))
@@ -104,15 +94,15 @@ class SimpleConfigFile(object):
 
     def set(self, *args):
         for key, value in args:
-            self.info[uppercase_ASCII_string(key)] = value
+            self.info[upperASCII(key)] = value
 
     def unset(self, *keys):
-        for key in (uppercase_ASCII_string(k) for k in keys):
+        for key in (upperASCII(k) for k in keys):
             if key in self.info:
                 del self.info[key]
 
     def get(self, key):
-        return self.info.get(uppercase_ASCII_string(key), "")
+        return self.info.get(upperASCII(key), "")
 
     def _parseline(self, line):
         """ parse a line into a key, value pair
@@ -128,7 +118,7 @@ class SimpleConfigFile(object):
         if self.read_unquote:
             val = unquote(val)
         if key != '' and eq == '=':
-            return (uppercase_ASCII_string(key), val)
+            return (upperASCII(key), val)
         else:
             return (None, None)
 
diff --git a/pyanaconda/ui/gui/spokes/custom.py b/pyanaconda/ui/gui/spokes/custom.py
index 936c85b..6636868 100644
--- a/pyanaconda/ui/gui/spokes/custom.py
+++ b/pyanaconda/ui/gui/spokes/custom.py
@@ -37,6 +37,7 @@ from pyanaconda.i18n import _, N_, P_
 from pyanaconda.product import productName, productVersion
 from pyanaconda.threads import AnacondaThread, threadMgr
 from pyanaconda.constants import THREAD_EXECUTE_STORAGE, THREAD_STORAGE, THREAD_CUSTOM_STORAGE_INIT
+from pyanaconda.iutil import lowerASCII
 
 from blivet import devicefactory
 from blivet.formats import device_formats
@@ -205,7 +206,7 @@ def validate_mountpoint(mountpoint, used_mountpoints, strict=True):
     elif mountpoint.startswith("/dev") or mountpoint.startswith("/proc") or \
          mountpoint.startswith("/sys"):
         valid = MOUNTPOINT_INVALID
-    elif (mountpoint.lower() not in fake_mountpoints and
+    elif (lowerASCII(mountpoint) not in fake_mountpoints and
           ((len(mountpoint) > 1 and mountpoint.endswith("/")) or
            not mountpoint.startswith("/") or
            " " in mountpoint or
@@ -556,11 +557,12 @@ class ContainerDialog(GUIObject):
             return
 
         selected_level_string = store[itr][0]   # eg: "RAID1 (Redundancy)"
-        level = selected_level_string.split()[0].lower()    # -> "raid1"
-        if level == "none" or level == _("None").lower():
+        level = selected_level_string.split()[0]    # -> "raid1"
+        levellower = level.lower()
+        if lowerASCII(level) == "none" or levellower == _("None").lower():
             level = None
 
-        return level
+        return levellower
 
     def _populate_raid(self):
         """ Set up the raid-specific portion of the device details. """
@@ -1566,11 +1568,11 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
             return
 
         selected_level_string = store[itr][0]   # eg: "RAID1 (Redundancy)"
-        level = selected_level_string.split()[0].lower()    # -> "raid1"
-        if level == "none":
+        level = selected_level_string.split()[0]    # -> "raid1"
+        if lowerASCII(level) == "none":
             level = None
 
-        return level
+        return level.lower()
 
     def _populate_raid(self, raid_level):
         """ Set up the raid-specific portion of the device details. """
@@ -1941,7 +1943,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
         # we're doing nothing here to ensure that bootable requests end up on
         # the boot disk, but the weight from platform should take care of this
 
-        if mountpoint.lower() in ("swap", "biosboot", "prepboot"):
+        if lowerASCII(mountpoint) in ("swap", "biosboot", "prepboot"):
             mountpoint = None
 
         device_type_from_autopart = {AUTOPART_TYPE_LVM: DEVICE_TYPE_LVM,
-- 
1.8.3.1



More information about the anaconda-patches mailing list