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