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