• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""Test the binascii C module."""
2
3import unittest
4import binascii
5import array
6import re
7from test.support import warnings_helper
8
9
10# Note: "*_hex" functions are aliases for "(un)hexlify"
11b2a_functions = ['b2a_base64', 'b2a_hex', 'b2a_hqx', 'b2a_qp', 'b2a_uu',
12                 'hexlify', 'rlecode_hqx']
13a2b_functions = ['a2b_base64', 'a2b_hex', 'a2b_hqx', 'a2b_qp', 'a2b_uu',
14                 'unhexlify', 'rledecode_hqx']
15all_functions = a2b_functions + b2a_functions + ['crc32', 'crc_hqx']
16
17
18class BinASCIITest(unittest.TestCase):
19
20    type2test = bytes
21    # Create binary test data
22    rawdata = b"The quick brown fox jumps over the lazy dog.\r\n"
23    # Be slow so we don't depend on other modules
24    rawdata += bytes(range(256))
25    rawdata += b"\r\nHello world.\n"
26
27    def setUp(self):
28        self.data = self.type2test(self.rawdata)
29
30    def test_exceptions(self):
31        # Check module exceptions
32        self.assertTrue(issubclass(binascii.Error, Exception))
33        self.assertTrue(issubclass(binascii.Incomplete, Exception))
34
35    def test_functions(self):
36        # Check presence of all functions
37        for name in all_functions:
38            self.assertTrue(hasattr(getattr(binascii, name), '__call__'))
39            self.assertRaises(TypeError, getattr(binascii, name))
40
41    @warnings_helper.ignore_warnings(category=DeprecationWarning)
42    def test_returned_value(self):
43        # Limit to the minimum of all limits (b2a_uu)
44        MAX_ALL = 45
45        raw = self.rawdata[:MAX_ALL]
46        for fa, fb in zip(a2b_functions, b2a_functions):
47            a2b = getattr(binascii, fa)
48            b2a = getattr(binascii, fb)
49            try:
50                a = b2a(self.type2test(raw))
51                res = a2b(self.type2test(a))
52            except Exception as err:
53                self.fail("{}/{} conversion raises {!r}".format(fb, fa, err))
54            if fb == 'b2a_hqx':
55                # b2a_hqx returns a tuple
56                res, _ = res
57            self.assertEqual(res, raw, "{}/{} conversion: "
58                             "{!r} != {!r}".format(fb, fa, res, raw))
59            self.assertIsInstance(res, bytes)
60            self.assertIsInstance(a, bytes)
61            self.assertLess(max(a), 128)
62        self.assertIsInstance(binascii.crc_hqx(raw, 0), int)
63        self.assertIsInstance(binascii.crc32(raw), int)
64
65    def test_base64valid(self):
66        # Test base64 with valid data
67        MAX_BASE64 = 57
68        lines = []
69        for i in range(0, len(self.rawdata), MAX_BASE64):
70            b = self.type2test(self.rawdata[i:i+MAX_BASE64])
71            a = binascii.b2a_base64(b)
72            lines.append(a)
73        res = bytes()
74        for line in lines:
75            a = self.type2test(line)
76            b = binascii.a2b_base64(a)
77            res += b
78        self.assertEqual(res, self.rawdata)
79
80    def test_base64invalid(self):
81        # Test base64 with random invalid characters sprinkled throughout
82        # (This requires a new version of binascii.)
83        MAX_BASE64 = 57
84        lines = []
85        for i in range(0, len(self.data), MAX_BASE64):
86            b = self.type2test(self.rawdata[i:i+MAX_BASE64])
87            a = binascii.b2a_base64(b)
88            lines.append(a)
89
90        fillers = bytearray()
91        valid = b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/"
92        for i in range(256):
93            if i not in valid:
94                fillers.append(i)
95        def addnoise(line):
96            noise = fillers
97            ratio = len(line) // len(noise)
98            res = bytearray()
99            while line and noise:
100                if len(line) // len(noise) > ratio:
101                    c, line = line[0], line[1:]
102                else:
103                    c, noise = noise[0], noise[1:]
104                res.append(c)
105            return res + noise + line
106        res = bytearray()
107        for line in map(addnoise, lines):
108            a = self.type2test(line)
109            b = binascii.a2b_base64(a)
110            res += b
111        self.assertEqual(res, self.rawdata)
112
113        # Test base64 with just invalid characters, which should return
114        # empty strings. TBD: shouldn't it raise an exception instead ?
115        self.assertEqual(binascii.a2b_base64(self.type2test(fillers)), b'')
116
117    def test_base64errors(self):
118        # Test base64 with invalid padding
119        def assertIncorrectPadding(data):
120            with self.assertRaisesRegex(binascii.Error, r'(?i)Incorrect padding'):
121                binascii.a2b_base64(self.type2test(data))
122
123        assertIncorrectPadding(b'ab')
124        assertIncorrectPadding(b'ab=')
125        assertIncorrectPadding(b'abc')
126        assertIncorrectPadding(b'abcdef')
127        assertIncorrectPadding(b'abcdef=')
128        assertIncorrectPadding(b'abcdefg')
129        assertIncorrectPadding(b'a=b=')
130        assertIncorrectPadding(b'a\nb=')
131
132        # Test base64 with invalid number of valid characters (1 mod 4)
133        def assertInvalidLength(data):
134            n_data_chars = len(re.sub(br'[^A-Za-z0-9/+]', br'', data))
135            expected_errmsg_re = \
136                r'(?i)Invalid.+number of data characters.+' + str(n_data_chars)
137            with self.assertRaisesRegex(binascii.Error, expected_errmsg_re):
138                binascii.a2b_base64(self.type2test(data))
139
140        assertInvalidLength(b'a')
141        assertInvalidLength(b'a=')
142        assertInvalidLength(b'a==')
143        assertInvalidLength(b'a===')
144        assertInvalidLength(b'a' * 5)
145        assertInvalidLength(b'a' * (4 * 87 + 1))
146        assertInvalidLength(b'A\tB\nC ??DE')  # only 5 valid characters
147
148    def test_uu(self):
149        MAX_UU = 45
150        for backtick in (True, False):
151            lines = []
152            for i in range(0, len(self.data), MAX_UU):
153                b = self.type2test(self.rawdata[i:i+MAX_UU])
154                a = binascii.b2a_uu(b, backtick=backtick)
155                lines.append(a)
156            res = bytes()
157            for line in lines:
158                a = self.type2test(line)
159                b = binascii.a2b_uu(a)
160                res += b
161            self.assertEqual(res, self.rawdata)
162
163        self.assertEqual(binascii.a2b_uu(b"\x7f"), b"\x00"*31)
164        self.assertEqual(binascii.a2b_uu(b"\x80"), b"\x00"*32)
165        self.assertEqual(binascii.a2b_uu(b"\xff"), b"\x00"*31)
166        self.assertRaises(binascii.Error, binascii.a2b_uu, b"\xff\x00")
167        self.assertRaises(binascii.Error, binascii.a2b_uu, b"!!!!")
168        self.assertRaises(binascii.Error, binascii.b2a_uu, 46*b"!")
169
170        # Issue #7701 (crash on a pydebug build)
171        self.assertEqual(binascii.b2a_uu(b'x'), b'!>   \n')
172
173        self.assertEqual(binascii.b2a_uu(b''), b' \n')
174        self.assertEqual(binascii.b2a_uu(b'', backtick=True), b'`\n')
175        self.assertEqual(binascii.a2b_uu(b' \n'), b'')
176        self.assertEqual(binascii.a2b_uu(b'`\n'), b'')
177        self.assertEqual(binascii.b2a_uu(b'\x00Cat'), b'$ $-A=   \n')
178        self.assertEqual(binascii.b2a_uu(b'\x00Cat', backtick=True),
179                         b'$`$-A=```\n')
180        self.assertEqual(binascii.a2b_uu(b'$`$-A=```\n'),
181                         binascii.a2b_uu(b'$ $-A=   \n'))
182        with self.assertRaises(TypeError):
183            binascii.b2a_uu(b"", True)
184
185    @warnings_helper.ignore_warnings(category=DeprecationWarning)
186    def test_crc_hqx(self):
187        crc = binascii.crc_hqx(self.type2test(b"Test the CRC-32 of"), 0)
188        crc = binascii.crc_hqx(self.type2test(b" this string."), crc)
189        self.assertEqual(crc, 14290)
190
191        self.assertRaises(TypeError, binascii.crc_hqx)
192        self.assertRaises(TypeError, binascii.crc_hqx, self.type2test(b''))
193
194        for crc in 0, 1, 0x1234, 0x12345, 0x12345678, -1:
195            self.assertEqual(binascii.crc_hqx(self.type2test(b''), crc),
196                             crc & 0xffff)
197
198    def test_crc32(self):
199        crc = binascii.crc32(self.type2test(b"Test the CRC-32 of"))
200        crc = binascii.crc32(self.type2test(b" this string."), crc)
201        self.assertEqual(crc, 1571220330)
202
203        self.assertRaises(TypeError, binascii.crc32)
204
205    @warnings_helper.ignore_warnings(category=DeprecationWarning)
206    def test_hqx(self):
207        # Perform binhex4 style RLE-compression
208        # Then calculate the hexbin4 binary-to-ASCII translation
209        rle = binascii.rlecode_hqx(self.data)
210        a = binascii.b2a_hqx(self.type2test(rle))
211
212        b, _ = binascii.a2b_hqx(self.type2test(a))
213        res = binascii.rledecode_hqx(b)
214        self.assertEqual(res, self.rawdata)
215
216    @warnings_helper.ignore_warnings(category=DeprecationWarning)
217    def test_rle(self):
218        # test repetition with a repetition longer than the limit of 255
219        data = (b'a' * 100 + b'b' + b'c' * 300)
220
221        encoded = binascii.rlecode_hqx(data)
222        self.assertEqual(encoded,
223                         (b'a\x90d'      # 'a' * 100
224                          b'b'           # 'b'
225                          b'c\x90\xff'   # 'c' * 255
226                          b'c\x90-'))    # 'c' * 45
227
228        decoded = binascii.rledecode_hqx(encoded)
229        self.assertEqual(decoded, data)
230
231    def test_hex(self):
232        # test hexlification
233        s = b'{s\005\000\000\000worldi\002\000\000\000s\005\000\000\000helloi\001\000\000\0000'
234        t = binascii.b2a_hex(self.type2test(s))
235        u = binascii.a2b_hex(self.type2test(t))
236        self.assertEqual(s, u)
237        self.assertRaises(binascii.Error, binascii.a2b_hex, t[:-1])
238        self.assertRaises(binascii.Error, binascii.a2b_hex, t[:-1] + b'q')
239        self.assertRaises(binascii.Error, binascii.a2b_hex, bytes([255, 255]))
240        self.assertRaises(binascii.Error, binascii.a2b_hex, b'0G')
241        self.assertRaises(binascii.Error, binascii.a2b_hex, b'0g')
242        self.assertRaises(binascii.Error, binascii.a2b_hex, b'G0')
243        self.assertRaises(binascii.Error, binascii.a2b_hex, b'g0')
244
245        # Confirm that b2a_hex == hexlify and a2b_hex == unhexlify
246        self.assertEqual(binascii.hexlify(self.type2test(s)), t)
247        self.assertEqual(binascii.unhexlify(self.type2test(t)), u)
248
249    def test_hex_separator(self):
250        """Test that hexlify and b2a_hex are binary versions of bytes.hex."""
251        # Logic of separators is tested in test_bytes.py.  This checks that
252        # arg parsing works and exercises the direct to bytes object code
253        # path within pystrhex.c.
254        s = b'{s\005\000\000\000worldi\002\000\000\000s\005\000\000\000helloi\001\000\000\0000'
255        self.assertEqual(binascii.hexlify(self.type2test(s)), s.hex().encode('ascii'))
256        expected8 = s.hex('.', 8).encode('ascii')
257        self.assertEqual(binascii.hexlify(self.type2test(s), '.', 8), expected8)
258        expected1 = s.hex(':').encode('ascii')
259        self.assertEqual(binascii.b2a_hex(self.type2test(s), ':'), expected1)
260
261    def test_qp(self):
262        type2test = self.type2test
263        a2b_qp = binascii.a2b_qp
264        b2a_qp = binascii.b2a_qp
265
266        a2b_qp(data=b"", header=False)  # Keyword arguments allowed
267
268        # A test for SF bug 534347 (segfaults without the proper fix)
269        try:
270            a2b_qp(b"", **{1:1})
271        except TypeError:
272            pass
273        else:
274            self.fail("binascii.a2b_qp(**{1:1}) didn't raise TypeError")
275
276        self.assertEqual(a2b_qp(type2test(b"=")), b"")
277        self.assertEqual(a2b_qp(type2test(b"= ")), b"= ")
278        self.assertEqual(a2b_qp(type2test(b"==")), b"=")
279        self.assertEqual(a2b_qp(type2test(b"=\nAB")), b"AB")
280        self.assertEqual(a2b_qp(type2test(b"=\r\nAB")), b"AB")
281        self.assertEqual(a2b_qp(type2test(b"=\rAB")), b"")  # ?
282        self.assertEqual(a2b_qp(type2test(b"=\rAB\nCD")), b"CD")  # ?
283        self.assertEqual(a2b_qp(type2test(b"=AB")), b"\xab")
284        self.assertEqual(a2b_qp(type2test(b"=ab")), b"\xab")
285        self.assertEqual(a2b_qp(type2test(b"=AX")), b"=AX")
286        self.assertEqual(a2b_qp(type2test(b"=XA")), b"=XA")
287        self.assertEqual(a2b_qp(type2test(b"=AB")[:-1]), b"=A")
288
289        self.assertEqual(a2b_qp(type2test(b'_')), b'_')
290        self.assertEqual(a2b_qp(type2test(b'_'), header=True), b' ')
291
292        self.assertRaises(TypeError, b2a_qp, foo="bar")
293        self.assertEqual(a2b_qp(type2test(b"=00\r\n=00")), b"\x00\r\n\x00")
294        self.assertEqual(b2a_qp(type2test(b"\xff\r\n\xff\n\xff")),
295                         b"=FF\r\n=FF\r\n=FF")
296        self.assertEqual(b2a_qp(type2test(b"0"*75+b"\xff\r\n\xff\r\n\xff")),
297                         b"0"*75+b"=\r\n=FF\r\n=FF\r\n=FF")
298
299        self.assertEqual(b2a_qp(type2test(b'\x7f')), b'=7F')
300        self.assertEqual(b2a_qp(type2test(b'=')), b'=3D')
301
302        self.assertEqual(b2a_qp(type2test(b'_')), b'_')
303        self.assertEqual(b2a_qp(type2test(b'_'), header=True), b'=5F')
304        self.assertEqual(b2a_qp(type2test(b'x y'), header=True), b'x_y')
305        self.assertEqual(b2a_qp(type2test(b'x '), header=True), b'x=20')
306        self.assertEqual(b2a_qp(type2test(b'x y'), header=True, quotetabs=True),
307                         b'x=20y')
308        self.assertEqual(b2a_qp(type2test(b'x\ty'), header=True), b'x\ty')
309
310        self.assertEqual(b2a_qp(type2test(b' ')), b'=20')
311        self.assertEqual(b2a_qp(type2test(b'\t')), b'=09')
312        self.assertEqual(b2a_qp(type2test(b' x')), b' x')
313        self.assertEqual(b2a_qp(type2test(b'\tx')), b'\tx')
314        self.assertEqual(b2a_qp(type2test(b' x')[:-1]), b'=20')
315        self.assertEqual(b2a_qp(type2test(b'\tx')[:-1]), b'=09')
316        self.assertEqual(b2a_qp(type2test(b'\0')), b'=00')
317
318        self.assertEqual(b2a_qp(type2test(b'\0\n')), b'=00\n')
319        self.assertEqual(b2a_qp(type2test(b'\0\n'), quotetabs=True), b'=00\n')
320
321        self.assertEqual(b2a_qp(type2test(b'x y\tz')), b'x y\tz')
322        self.assertEqual(b2a_qp(type2test(b'x y\tz'), quotetabs=True),
323                         b'x=20y=09z')
324        self.assertEqual(b2a_qp(type2test(b'x y\tz'), istext=False),
325                         b'x y\tz')
326        self.assertEqual(b2a_qp(type2test(b'x \ny\t\n')),
327                         b'x=20\ny=09\n')
328        self.assertEqual(b2a_qp(type2test(b'x \ny\t\n'), quotetabs=True),
329                         b'x=20\ny=09\n')
330        self.assertEqual(b2a_qp(type2test(b'x \ny\t\n'), istext=False),
331                         b'x =0Ay\t=0A')
332        self.assertEqual(b2a_qp(type2test(b'x \ry\t\r')),
333                         b'x \ry\t\r')
334        self.assertEqual(b2a_qp(type2test(b'x \ry\t\r'), quotetabs=True),
335                         b'x=20\ry=09\r')
336        self.assertEqual(b2a_qp(type2test(b'x \ry\t\r'), istext=False),
337                         b'x =0Dy\t=0D')
338        self.assertEqual(b2a_qp(type2test(b'x \r\ny\t\r\n')),
339                         b'x=20\r\ny=09\r\n')
340        self.assertEqual(b2a_qp(type2test(b'x \r\ny\t\r\n'), quotetabs=True),
341                         b'x=20\r\ny=09\r\n')
342        self.assertEqual(b2a_qp(type2test(b'x \r\ny\t\r\n'), istext=False),
343                         b'x =0D=0Ay\t=0D=0A')
344
345        self.assertEqual(b2a_qp(type2test(b'x \r\n')[:-1]), b'x \r')
346        self.assertEqual(b2a_qp(type2test(b'x\t\r\n')[:-1]), b'x\t\r')
347        self.assertEqual(b2a_qp(type2test(b'x \r\n')[:-1], quotetabs=True),
348                         b'x=20\r')
349        self.assertEqual(b2a_qp(type2test(b'x\t\r\n')[:-1], quotetabs=True),
350                         b'x=09\r')
351        self.assertEqual(b2a_qp(type2test(b'x \r\n')[:-1], istext=False),
352                         b'x =0D')
353        self.assertEqual(b2a_qp(type2test(b'x\t\r\n')[:-1], istext=False),
354                         b'x\t=0D')
355
356        self.assertEqual(b2a_qp(type2test(b'.')), b'=2E')
357        self.assertEqual(b2a_qp(type2test(b'.\n')), b'=2E\n')
358        self.assertEqual(b2a_qp(type2test(b'.\r')), b'=2E\r')
359        self.assertEqual(b2a_qp(type2test(b'.\0')), b'=2E=00')
360        self.assertEqual(b2a_qp(type2test(b'a.\n')), b'a.\n')
361        self.assertEqual(b2a_qp(type2test(b'.a')[:-1]), b'=2E')
362
363    @warnings_helper.ignore_warnings(category=DeprecationWarning)
364    def test_empty_string(self):
365        # A test for SF bug #1022953.  Make sure SystemError is not raised.
366        empty = self.type2test(b'')
367        for func in all_functions:
368            if func == 'crc_hqx':
369                # crc_hqx needs 2 arguments
370                binascii.crc_hqx(empty, 0)
371                continue
372            f = getattr(binascii, func)
373            try:
374                f(empty)
375            except Exception as err:
376                self.fail("{}({!r}) raises {!r}".format(func, empty, err))
377
378    def test_unicode_b2a(self):
379        # Unicode strings are not accepted by b2a_* functions.
380        for func in set(all_functions) - set(a2b_functions) | {'rledecode_hqx'}:
381            try:
382                self.assertRaises(TypeError, getattr(binascii, func), "test")
383            except Exception as err:
384                self.fail('{}("test") raises {!r}'.format(func, err))
385        # crc_hqx needs 2 arguments
386        self.assertRaises(TypeError, binascii.crc_hqx, "test", 0)
387
388    @warnings_helper.ignore_warnings(category=DeprecationWarning)
389    def test_unicode_a2b(self):
390        # Unicode strings are accepted by a2b_* functions.
391        MAX_ALL = 45
392        raw = self.rawdata[:MAX_ALL]
393        for fa, fb in zip(a2b_functions, b2a_functions):
394            if fa == 'rledecode_hqx':
395                # Takes non-ASCII data
396                continue
397            a2b = getattr(binascii, fa)
398            b2a = getattr(binascii, fb)
399            try:
400                a = b2a(self.type2test(raw))
401                binary_res = a2b(a)
402                a = a.decode('ascii')
403                res = a2b(a)
404            except Exception as err:
405                self.fail("{}/{} conversion raises {!r}".format(fb, fa, err))
406            if fb == 'b2a_hqx':
407                # b2a_hqx returns a tuple
408                res, _ = res
409                binary_res, _ = binary_res
410            self.assertEqual(res, raw, "{}/{} conversion: "
411                             "{!r} != {!r}".format(fb, fa, res, raw))
412            self.assertEqual(res, binary_res)
413            self.assertIsInstance(res, bytes)
414            # non-ASCII string
415            self.assertRaises(ValueError, a2b, "\x80")
416
417    def test_b2a_base64_newline(self):
418        # Issue #25357: test newline parameter
419        b = self.type2test(b'hello')
420        self.assertEqual(binascii.b2a_base64(b),
421                         b'aGVsbG8=\n')
422        self.assertEqual(binascii.b2a_base64(b, newline=True),
423                         b'aGVsbG8=\n')
424        self.assertEqual(binascii.b2a_base64(b, newline=False),
425                         b'aGVsbG8=')
426
427    def test_deprecated_warnings(self):
428        with self.assertWarns(DeprecationWarning):
429            self.assertEqual(binascii.b2a_hqx(b'abc'), b'B@*M')
430        with self.assertWarns(DeprecationWarning):
431            self.assertEqual(binascii.a2b_hqx(b'B@*M'), (b'abc', 0))
432
433        with self.assertWarns(DeprecationWarning):
434            self.assertEqual(binascii.rlecode_hqx(b'a' * 10), b'a\x90\n')
435
436        with self.assertWarns(DeprecationWarning):
437            self.assertEqual(binascii.rledecode_hqx(b'a\x90\n'), b'a' * 10)
438
439
440class ArrayBinASCIITest(BinASCIITest):
441    def type2test(self, s):
442        return array.array('B', list(s))
443
444
445class BytearrayBinASCIITest(BinASCIITest):
446    type2test = bytearray
447
448
449class MemoryviewBinASCIITest(BinASCIITest):
450    type2test = memoryview
451
452
453if __name__ == "__main__":
454    unittest.main()
455