Skip to content

Commit 13f1f42

Browse files
authored
bpo-30103: Allow Uuencode in Python using backtick as zero instead of space (python#1326)
1 parent 0360a9d commit 13f1f42

File tree

10 files changed

+137
-67
lines changed

10 files changed

+137
-67
lines changed

Doc/library/binascii.rst

+6-3
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,14 @@ The :mod:`binascii` module defines the following functions:
4040
data may be followed by whitespace.
4141

4242

43-
.. function:: b2a_uu(data)
43+
.. function:: b2a_uu(data, *, backtick=False)
4444

4545
Convert binary data to a line of ASCII characters, the return value is the
4646
converted line, including a newline char. The length of *data* should be at most
47-
45.
47+
45. If *backtick* is true, zeros are represented by ``'`'`` instead of spaces.
48+
49+
.. versionchanged:: 3.7
50+
Added the *backtick* parameter.
4851

4952

5053
.. function:: a2b_base64(string)
@@ -53,7 +56,7 @@ The :mod:`binascii` module defines the following functions:
5356
than one line may be passed at a time.
5457

5558

56-
.. function:: b2a_base64(data, \*, newline=True)
59+
.. function:: b2a_base64(data, *, newline=True)
5760

5861
Convert binary data to a line of ASCII characters in base64 coding. The return
5962
value is the converted line, including a newline char if *newline* is

Doc/library/uu.rst

+6-2
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,16 @@ This code was contributed by Lance Ellinghouse, and modified by Jack Jansen.
2828
The :mod:`uu` module defines the following functions:
2929

3030

31-
.. function:: encode(in_file, out_file, name=None, mode=None)
31+
.. function:: encode(in_file, out_file, name=None, mode=None, *, backtick=False)
3232

3333
Uuencode file *in_file* into file *out_file*. The uuencoded file will have
3434
the header specifying *name* and *mode* as the defaults for the results of
3535
decoding the file. The default defaults are taken from *in_file*, or ``'-'``
36-
and ``0o666`` respectively.
36+
and ``0o666`` respectively. If *backtick* is true, zeros are represented by
37+
``'`'`` instead of spaces.
38+
39+
.. versionchanged:: 3.7
40+
Added the *backtick* parameter.
3741

3842

3943
.. function:: decode(in_file, out_file=None, mode=None, quiet=False)

Doc/tools/susp-ignored.csv

+4
Original file line numberDiff line numberDiff line change
@@ -328,3 +328,7 @@ whatsnew/3.5,,:exception,ERROR:root:exception
328328
whatsnew/changelog,,:version,import sys; I = version[:version.index(' ')]
329329
whatsnew/changelog,,`,"for readability (was ""`"")."
330330
whatsnew/changelog,,:end,str[start:end]
331+
library/binascii,,`,'`'
332+
library/uu,,`,'`'
333+
whatsnew/3.7,,`,'`'
334+
whatsnew/changelog,,`,'`'

Doc/whatsnew/3.7.rst

+14
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,13 @@ New Modules
9595
Improved Modules
9696
================
9797

98+
binascii
99+
--------
100+
101+
The :func:`~binascii.b2a_uu` function now accepts an optional *backtick*
102+
keyword argument. When it's true, zeros are represented by ``'`'``
103+
instead of spaces. (Contributed by Xiang Zhang in :issue:`30103`.)
104+
98105
contextlib
99106
----------
100107

@@ -159,6 +166,13 @@ urllib.parse
159166
adding `~` to the set of characters that is never quoted by default.
160167
(Contributed by Christian Theune and Ratnadeep Debnath in :issue:`16285`.)
161168

169+
uu
170+
--
171+
172+
Function :func:`~uu.encode` now accepts an optional *backtick*
173+
keyword argument. When it's true, zeros are represented by ``'`'``
174+
instead of spaces. (Contributed by Xiang Zhang in :issue:`30103`.)
175+
162176

163177
Optimizations
164178
=============

Lib/test/test_binascii.py

+24-12
Original file line numberDiff line numberDiff line change
@@ -112,29 +112,41 @@ def addnoise(line):
112112

113113
def test_uu(self):
114114
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)
115+
for backtick in (True, False):
116+
lines = []
117+
for i in range(0, len(self.data), MAX_UU):
118+
b = self.type2test(self.rawdata[i:i+MAX_UU])
119+
a = binascii.b2a_uu(b, backtick=backtick)
120+
lines.append(a)
121+
res = bytes()
122+
for line in lines:
123+
a = self.type2test(line)
124+
b = binascii.a2b_uu(a)
125+
res += b
126+
self.assertEqual(res, self.rawdata)
126127

127128
self.assertEqual(binascii.a2b_uu(b"\x7f"), b"\x00"*31)
128129
self.assertEqual(binascii.a2b_uu(b"\x80"), b"\x00"*32)
129130
self.assertEqual(binascii.a2b_uu(b"\xff"), b"\x00"*31)
130131
self.assertRaises(binascii.Error, binascii.a2b_uu, b"\xff\x00")
131132
self.assertRaises(binascii.Error, binascii.a2b_uu, b"!!!!")
132-
133133
self.assertRaises(binascii.Error, binascii.b2a_uu, 46*b"!")
134134

135135
# Issue #7701 (crash on a pydebug build)
136136
self.assertEqual(binascii.b2a_uu(b'x'), b'!> \n')
137137

138+
self.assertEqual(binascii.b2a_uu(b''), b' \n')
139+
self.assertEqual(binascii.b2a_uu(b'', backtick=True), b'`\n')
140+
self.assertEqual(binascii.a2b_uu(b' \n'), b'')
141+
self.assertEqual(binascii.a2b_uu(b'`\n'), b'')
142+
self.assertEqual(binascii.b2a_uu(b'\x00Cat'), b'$ $-A= \n')
143+
self.assertEqual(binascii.b2a_uu(b'\x00Cat', backtick=True),
144+
b'$`$-A=```\n')
145+
self.assertEqual(binascii.a2b_uu(b'$`$-A=```\n'),
146+
binascii.a2b_uu(b'$ $-A= \n'))
147+
with self.assertRaises(TypeError):
148+
binascii.b2a_uu(b"", True)
149+
138150
def test_crc_hqx(self):
139151
crc = binascii.crc_hqx(self.type2test(b"Test the CRC-32 of"), 0)
140152
crc = binascii.crc_hqx(self.type2test(b" this string."), crc)

Lib/test/test_uu.py

+49-34
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@
1010
import uu
1111
import io
1212

13-
plaintext = b"The smooth-scaled python crept over the sleeping dog\n"
13+
plaintext = b"The symbols on top of your keyboard are !@#$%^&*()_+|~\n"
1414

1515
encodedtext = b"""\
16-
M5&AE('-M;V]T:\"US8V%L960@<'ET:&]N(&-R97!T(&]V97(@=&AE('-L965P
17-
(:6YG(&1O9PH """
16+
M5&AE('-Y;6)O;',@;VX@=&]P(&]F('EO=7(@:V5Y8F]A<F0@87)E("% (R0E
17+
*7B8J*"E?*WQ^"@ """
1818

1919
# Stolen from io.py
2020
class FakeIO(io.TextIOWrapper):
@@ -44,9 +44,14 @@ def getvalue(self):
4444
return self.buffer.getvalue().decode(self._encoding, self._errors)
4545

4646

47-
def encodedtextwrapped(mode, filename):
48-
return (bytes("begin %03o %s\n" % (mode, filename), "ascii") +
49-
encodedtext + b"\n \nend\n")
47+
def encodedtextwrapped(mode, filename, backtick=False):
48+
if backtick:
49+
res = (bytes("begin %03o %s\n" % (mode, filename), "ascii") +
50+
encodedtext.replace(b' ', b'`') + b"\n`\nend\n")
51+
else:
52+
res = (bytes("begin %03o %s\n" % (mode, filename), "ascii") +
53+
encodedtext + b"\n \nend\n")
54+
return res
5055

5156
class UUTest(unittest.TestCase):
5257

@@ -59,20 +64,27 @@ def test_encode(self):
5964
out = io.BytesIO()
6065
uu.encode(inp, out, "t1", 0o644)
6166
self.assertEqual(out.getvalue(), encodedtextwrapped(0o644, "t1"))
67+
inp = io.BytesIO(plaintext)
68+
out = io.BytesIO()
69+
uu.encode(inp, out, "t1", backtick=True)
70+
self.assertEqual(out.getvalue(), encodedtextwrapped(0o666, "t1", True))
71+
with self.assertRaises(TypeError):
72+
uu.encode(inp, out, "t1", 0o644, True)
6273

6374
def test_decode(self):
64-
inp = io.BytesIO(encodedtextwrapped(0o666, "t1"))
65-
out = io.BytesIO()
66-
uu.decode(inp, out)
67-
self.assertEqual(out.getvalue(), plaintext)
68-
inp = io.BytesIO(
69-
b"UUencoded files may contain many lines,\n" +
70-
b"even some that have 'begin' in them.\n" +
71-
encodedtextwrapped(0o666, "t1")
72-
)
73-
out = io.BytesIO()
74-
uu.decode(inp, out)
75-
self.assertEqual(out.getvalue(), plaintext)
75+
for backtick in True, False:
76+
inp = io.BytesIO(encodedtextwrapped(0o666, "t1", backtick=backtick))
77+
out = io.BytesIO()
78+
uu.decode(inp, out)
79+
self.assertEqual(out.getvalue(), plaintext)
80+
inp = io.BytesIO(
81+
b"UUencoded files may contain many lines,\n" +
82+
b"even some that have 'begin' in them.\n" +
83+
encodedtextwrapped(0o666, "t1", backtick=backtick)
84+
)
85+
out = io.BytesIO()
86+
uu.decode(inp, out)
87+
self.assertEqual(out.getvalue(), plaintext)
7688

7789
def test_truncatedinput(self):
7890
inp = io.BytesIO(b"begin 644 t1\n" + encodedtext)
@@ -94,25 +106,33 @@ def test_missingbegin(self):
94106

95107
def test_garbage_padding(self):
96108
# Issue #22406
97-
encodedtext = (
109+
encodedtext1 = (
98110
b"begin 644 file\n"
99111
# length 1; bits 001100 111111 111111 111111
100112
b"\x21\x2C\x5F\x5F\x5F\n"
101113
b"\x20\n"
102114
b"end\n"
103115
)
116+
encodedtext2 = (
117+
b"begin 644 file\n"
118+
# length 1; bits 001100 111111 111111 111111
119+
b"\x21\x2C\x5F\x5F\x5F\n"
120+
b"\x60\n"
121+
b"end\n"
122+
)
104123
plaintext = b"\x33" # 00110011
105124

106-
with self.subTest("uu.decode()"):
107-
inp = io.BytesIO(encodedtext)
108-
out = io.BytesIO()
109-
uu.decode(inp, out, quiet=True)
110-
self.assertEqual(out.getvalue(), plaintext)
125+
for encodedtext in encodedtext1, encodedtext2:
126+
with self.subTest("uu.decode()"):
127+
inp = io.BytesIO(encodedtext)
128+
out = io.BytesIO()
129+
uu.decode(inp, out, quiet=True)
130+
self.assertEqual(out.getvalue(), plaintext)
111131

112-
with self.subTest("uu_codec"):
113-
import codecs
114-
decoded = codecs.decode(encodedtext, "uu_codec")
115-
self.assertEqual(decoded, plaintext)
132+
with self.subTest("uu_codec"):
133+
import codecs
134+
decoded = codecs.decode(encodedtext, "uu_codec")
135+
self.assertEqual(decoded, plaintext)
116136

117137
class UUStdIOTest(unittest.TestCase):
118138

@@ -250,11 +270,6 @@ def test_decodetwice(self):
250270
finally:
251271
self._kill(f)
252272

253-
def test_main():
254-
support.run_unittest(UUTest,
255-
UUStdIOTest,
256-
UUFileTest,
257-
)
258273

259274
if __name__=="__main__":
260-
test_main()
275+
unittest.main()

Lib/uu.py

+8-5
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@
2626

2727
"""Implementation of the UUencode and UUdecode functions.
2828
29-
encode(in_file, out_file [,name, mode])
30-
decode(in_file [, out_file, mode])
29+
encode(in_file, out_file [,name, mode], *, backtick=False)
30+
decode(in_file [, out_file, mode, quiet])
3131
"""
3232

3333
import binascii
@@ -39,7 +39,7 @@
3939
class Error(Exception):
4040
pass
4141

42-
def encode(in_file, out_file, name=None, mode=None):
42+
def encode(in_file, out_file, name=None, mode=None, *, backtick=False):
4343
"""Uuencode file"""
4444
#
4545
# If in_file is a pathname open it and change defaults
@@ -79,9 +79,12 @@ def encode(in_file, out_file, name=None, mode=None):
7979
out_file.write(('begin %o %s\n' % ((mode & 0o777), name)).encode("ascii"))
8080
data = in_file.read(45)
8181
while len(data) > 0:
82-
out_file.write(binascii.b2a_uu(data))
82+
out_file.write(binascii.b2a_uu(data, backtick=backtick))
8383
data = in_file.read(45)
84-
out_file.write(b' \nend\n')
84+
if backtick:
85+
out_file.write(b'`\nend\n')
86+
else:
87+
out_file.write(b' \nend\n')
8588
finally:
8689
for f in opened_files:
8790
f.close()

Misc/NEWS

+3
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,9 @@ Extension Modules
317317
Library
318318
-------
319319

320+
- bpo-30103: binascii.b2a_uu() and uu.encode() now support using ``'`'``
321+
as zero instead of space.
322+
320323
- bpo-28556: Various updates to typing module: add typing.NoReturn type, use
321324
WrapperDescriptorType, minor bug-fixes. Original PRs by
322325
Jim Fasarakis-Hilliard and Ivan Levkivskyi.

Modules/binascii.c

+12-4
Original file line numberDiff line numberDiff line change
@@ -335,13 +335,15 @@ binascii.b2a_uu
335335
336336
data: Py_buffer
337337
/
338+
*
339+
backtick: bool(accept={int}) = False
338340
339341
Uuencode line of data.
340342
[clinic start generated code]*/
341343

342344
static PyObject *
343-
binascii_b2a_uu_impl(PyObject *module, Py_buffer *data)
344-
/*[clinic end generated code: output=0070670e52e4aa6b input=00fdf458ce8b465b]*/
345+
binascii_b2a_uu_impl(PyObject *module, Py_buffer *data, int backtick)
346+
/*[clinic end generated code: output=b1b99de62d9bbeb8 input=b26bc8d32b6ed2f6]*/
345347
{
346348
unsigned char *ascii_data;
347349
const unsigned char *bin_data;
@@ -367,7 +369,10 @@ binascii_b2a_uu_impl(PyObject *module, Py_buffer *data)
367369
return NULL;
368370

369371
/* Store the length */
370-
*ascii_data++ = ' ' + (bin_len & 077);
372+
if (backtick && !bin_len)
373+
*ascii_data++ = '`';
374+
else
375+
*ascii_data++ = ' ' + bin_len;
371376

372377
for( ; bin_len > 0 || leftbits != 0 ; bin_len--, bin_data++ ) {
373378
/* Shift the data (or padding) into our buffer */
@@ -381,7 +386,10 @@ binascii_b2a_uu_impl(PyObject *module, Py_buffer *data)
381386
while ( leftbits >= 6 ) {
382387
this_ch = (leftchar >> (leftbits-6)) & 0x3f;
383388
leftbits -= 6;
384-
*ascii_data++ = this_ch + ' ';
389+
if (backtick && !this_ch)
390+
*ascii_data++ = '`';
391+
else
392+
*ascii_data++ = this_ch + ' ';
385393
}
386394
}
387395
*ascii_data++ = '\n'; /* Append a courtesy newline */

Modules/clinic/binascii.c.h

+11-7
Original file line numberDiff line numberDiff line change
@@ -34,27 +34,31 @@ binascii_a2b_uu(PyObject *module, PyObject *arg)
3434
}
3535

3636
PyDoc_STRVAR(binascii_b2a_uu__doc__,
37-
"b2a_uu($module, data, /)\n"
37+
"b2a_uu($module, data, /, *, backtick=False)\n"
3838
"--\n"
3939
"\n"
4040
"Uuencode line of data.");
4141

4242
#define BINASCII_B2A_UU_METHODDEF \
43-
{"b2a_uu", (PyCFunction)binascii_b2a_uu, METH_O, binascii_b2a_uu__doc__},
43+
{"b2a_uu", (PyCFunction)binascii_b2a_uu, METH_FASTCALL, binascii_b2a_uu__doc__},
4444

4545
static PyObject *
46-
binascii_b2a_uu_impl(PyObject *module, Py_buffer *data);
46+
binascii_b2a_uu_impl(PyObject *module, Py_buffer *data, int backtick);
4747

4848
static PyObject *
49-
binascii_b2a_uu(PyObject *module, PyObject *arg)
49+
binascii_b2a_uu(PyObject *module, PyObject **args, Py_ssize_t nargs, PyObject *kwnames)
5050
{
5151
PyObject *return_value = NULL;
52+
static const char * const _keywords[] = {"", "backtick", NULL};
53+
static _PyArg_Parser _parser = {"y*|$i:b2a_uu", _keywords, 0};
5254
Py_buffer data = {NULL, NULL};
55+
int backtick = 0;
5356

54-
if (!PyArg_Parse(arg, "y*:b2a_uu", &data)) {
57+
if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser,
58+
&data, &backtick)) {
5559
goto exit;
5660
}
57-
return_value = binascii_b2a_uu_impl(module, &data);
61+
return_value = binascii_b2a_uu_impl(module, &data, backtick);
5862

5963
exit:
6064
/* Cleanup for data */
@@ -558,4 +562,4 @@ binascii_b2a_qp(PyObject *module, PyObject **args, Py_ssize_t nargs, PyObject *k
558562

559563
return return_value;
560564
}
561-
/*[clinic end generated code: output=35821bce7e0e4714 input=a9049054013a1b77]*/
565+
/*[clinic end generated code: output=9db57e86dbe7b2fa input=a9049054013a1b77]*/

0 commit comments

Comments
 (0)