1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
|
https://bugs.gentoo.org/show_bug.cgi?id=250075
http://bugs.python.org/issue1813
--- Lib/decimal.py
+++ Lib/decimal.py
@@ -153,6 +153,13 @@
ROUND_HALF_DOWN = 'ROUND_HALF_DOWN'
ROUND_05UP = 'ROUND_05UP'
+import string
+
+def ascii_upper(s):
+ trans_table = string.maketrans(string.ascii_lowercase, string.ascii_uppercase)
+ return s.translate(trans_table)
+
+
# Errors
class DecimalException(ArithmeticError):
@@ -3645,7 +3652,7 @@
if name.startswith('_round_')]
for name in rounding_functions:
# name is like _round_half_even, goes to the global ROUND_HALF_EVEN value.
- globalname = name[1:].upper()
+ globalname = ascii_upper(name[1:])
val = globals()[globalname]
Decimal._pick_rounding_function[val] = name
--- Lib/email/__init__.py
+++ Lib/email/__init__.py
@@ -109,15 +109,19 @@
'Text',
]
+import string
+lower_map = string.maketrans(string.ascii_uppercase, string.ascii_lowercase)
+
+
for _name in _LOWERNAMES:
- importer = LazyImporter(_name.lower())
+ importer = LazyImporter(_name.translate(lower_map))
sys.modules['email.' + _name] = importer
setattr(sys.modules['email'], _name, importer)
import email.mime
for _name in _MIMENAMES:
- importer = LazyImporter('mime.' + _name.lower())
+ importer = LazyImporter('mime.' + _name.translate(lower_map))
sys.modules['email.MIME' + _name] = importer
setattr(sys.modules['email'], 'MIME' + _name, importer)
setattr(sys.modules['email.mime'], _name, importer)
--- Lib/locale.py
+++ Lib/locale.py
@@ -313,6 +313,14 @@
# overridden below)
_setlocale = setlocale
+# Avoid relying on the locale-dependent .lower() method
+# (see bug #1813).
+_ascii_lower_map = ''.join(
+ chr(x + 32 if x >= ord('A') and x <= ord('Z') else x)
+ for x in range(256)
+)
+
+
def normalize(localename):
""" Returns a normalized locale code for the given locale
@@ -330,7 +338,7 @@
"""
# Normalize the locale name and extract the encoding
- fullname = localename.lower()
+ fullname = localename.encode('ascii').translate(_ascii_lower_map)
if ':' in fullname:
# ':' is sometimes used as encoding delimiter.
fullname = fullname.replace(':', '.')
--- Lib/test/test_codecs.py
+++ Lib/test/test_codecs.py
@@ -1,5 +1,6 @@
from test import test_support
import unittest
+import locale
import codecs
import sys, StringIO, _testcapi
@@ -1133,6 +1134,16 @@
self.assertRaises(LookupError, codecs.lookup, "__spam__")
self.assertRaises(LookupError, codecs.lookup, " ")
+ def test_lookup_with_locale(self):
+ # Bug #1813: when normalizing codec name, lowercasing must be locale
+ # agnostic, otherwise the looked up codec name might end up wrong.
+ try:
+ locale.setlocale(locale.LC_CTYPE, 'tr')
+ except locale.Error:
+ # SKIPped test
+ return
+ codecs.lookup('ISO8859_1')
+
def test_getencoder(self):
self.assertRaises(TypeError, codecs.getencoder)
self.assertRaises(LookupError, codecs.getencoder, "__spam__")
--- Python/codecs.c
+++ Python/codecs.c
@@ -45,6 +45,12 @@
return -1;
}
+/* isupper() forced into the ASCII locale */
+#define ascii_isupper(x) (((x) >= 0x41) && ((x) <= 0x5A))
+/* tolower() forced into the ASCII locale */
+#define ascii_tolower(x) (ascii_isupper(x) ? ((x) + 0x20) : (x))
+
+
/* Convert a string to a normalized Python string: all characters are
converted to lower case, spaces are replaced with underscores. */
@@ -70,7 +76,7 @@
if (ch == ' ')
ch = '-';
else
- ch = tolower(Py_CHARMASK(ch));
+ ch = ascii_tolower(Py_CHARMASK(ch));
p[i] = ch;
}
return v;
|