Skip to content

Commit a45a02b

Browse files
committed
allow Uuencode using backticks instead of spaces to represent zeros
1 parent a90b990 commit a45a02b

File tree

9 files changed

+126
-62
lines changed

9 files changed

+126
-62
lines changed

Doc/library/binascii.rst

Lines changed: 5 additions & 2 deletions
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 backticks instead of spaces.
48+
49+
.. versionchanged:: 3.7
50+
Added the *backtick* parameter.
4851

4952

5053
.. function:: a2b_base64(string)

Doc/library/uu.rst

Lines changed: 6 additions & 2 deletions
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+
backticks 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/whatsnew/3.7.rst

Lines changed: 14 additions & 0 deletions
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 backticks
103+
instead of spaces. (Contributed by Xiang Zhang in :issue:`30103`.)
104+
98105
distutils
99106
---------
100107

@@ -153,6 +160,13 @@ urllib.parse
153160
adding `~` to the set of characters that is never quoted by default.
154161
(Contributed by Christian Theune and Ratnadeep Debnath in :issue:`16285`.)
155162

163+
uu
164+
--
165+
166+
Function :func:`~uu.encode` now accepts an optional *backtick*
167+
keyword argument. When it's true, zeros are represented by backticks
168+
instead of spaces. (Contributed by Xiang Zhang in :issue:`30103`.)
169+
156170

157171
Optimizations
158172
=============

Lib/test/test_binascii.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -112,29 +112,40 @@ 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+
self.assertRaises(TypeError, binascii.b2a_uu, b'', b'`\n', True)
148+
138149
def test_crc_hqx(self):
139150
crc = binascii.crc_hqx(self.type2test(b"Test the CRC-32 of"), 0)
140151
crc = binascii.crc_hqx(self.type2test(b" this string."), crc)

Lib/test/test_uu.py

Lines changed: 44 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -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,25 @@ 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))
6271

6372
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)
73+
for backtick in True, False:
74+
inp = io.BytesIO(encodedtextwrapped(0o666, "t1", backtick=backtick))
75+
out = io.BytesIO()
76+
uu.decode(inp, out)
77+
self.assertEqual(out.getvalue(), plaintext)
78+
inp = io.BytesIO(
79+
b"UUencoded files may contain many lines,\n" +
80+
b"even some that have 'begin' in them.\n" +
81+
encodedtextwrapped(0o666, "t1", backtick=backtick)
82+
)
83+
out = io.BytesIO()
84+
uu.decode(inp, out)
85+
self.assertEqual(out.getvalue(), plaintext)
7686

7787
def test_truncatedinput(self):
7888
inp = io.BytesIO(b"begin 644 t1\n" + encodedtext)
@@ -94,25 +104,33 @@ def test_missingbegin(self):
94104

95105
def test_garbage_padding(self):
96106
# Issue #22406
97-
encodedtext = (
107+
encodedtext1 = (
98108
b"begin 644 file\n"
99109
# length 1; bits 001100 111111 111111 111111
100110
b"\x21\x2C\x5F\x5F\x5F\n"
101111
b"\x20\n"
102112
b"end\n"
103113
)
114+
encodedtext2 = (
115+
b"begin 644 file\n"
116+
# length 1; bits 001100 111111 111111 111111
117+
b"\x21\x2C\x5F\x5F\x5F\n"
118+
b"\x60\n"
119+
b"end\n"
120+
)
104121
plaintext = b"\x33" # 00110011
105122

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)
123+
for encodedtext in encodedtext1, encodedtext2:
124+
with self.subTest("uu.decode()"):
125+
inp = io.BytesIO(encodedtext)
126+
out = io.BytesIO()
127+
uu.decode(inp, out, quiet=True)
128+
self.assertEqual(out.getvalue(), plaintext)
111129

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

117135
class UUStdIOTest(unittest.TestCase):
118136

@@ -251,10 +269,7 @@ def test_decodetwice(self):
251269
self._kill(f)
252270

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

259274
if __name__=="__main__":
260275
test_main()

Lib/uu.py

Lines changed: 8 additions & 5 deletions
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

Lines changed: 3 additions & 0 deletions
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 backtick
321+
as zero instead of space.
322+
320323
- bpo-30101: Add support for curses.A_ITALIC.
321324

322325
- bpo-29822: inspect.isabstract() now works during __init_subclass__. Patch

Modules/binascii.c

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -334,14 +334,15 @@ binascii_a2b_uu_impl(PyObject *module, Py_buffer *data)
334334
binascii.b2a_uu
335335
336336
data: Py_buffer
337-
/
337+
*
338+
backtick: bool(accept={int}) = False
338339
339340
Uuencode line of data.
340341
[clinic start generated code]*/
341342

342343
static PyObject *
343-
binascii_b2a_uu_impl(PyObject *module, Py_buffer *data)
344-
/*[clinic end generated code: output=0070670e52e4aa6b input=00fdf458ce8b465b]*/
344+
binascii_b2a_uu_impl(PyObject *module, Py_buffer *data, int backtick)
345+
/*[clinic end generated code: output=b1b99de62d9bbeb8 input=141f61b6ceb56af6]*/
345346
{
346347
unsigned char *ascii_data;
347348
const unsigned char *bin_data;
@@ -367,7 +368,10 @@ binascii_b2a_uu_impl(PyObject *module, Py_buffer *data)
367368
return NULL;
368369

369370
/* Store the length */
370-
*ascii_data++ = ' ' + (bin_len & 077);
371+
if (backtick)
372+
*ascii_data++ = bin_len ? ' ' + (bin_len & 077) : '`';
373+
else
374+
*ascii_data++ = ' ' + (bin_len & 077);
371375

372376
for( ; bin_len > 0 || leftbits != 0 ; bin_len--, bin_data++ ) {
373377
/* Shift the data (or padding) into our buffer */
@@ -381,7 +385,10 @@ binascii_b2a_uu_impl(PyObject *module, Py_buffer *data)
381385
while ( leftbits >= 6 ) {
382386
this_ch = (leftchar >> (leftbits-6)) & 0x3f;
383387
leftbits -= 6;
384-
*ascii_data++ = this_ch + ' ';
388+
if (backtick)
389+
*ascii_data++ = this_ch ? this_ch + ' ' : '`';
390+
else
391+
*ascii_data++ = this_ch + ' ';
385392
}
386393
}
387394
*ascii_data++ = '\n'; /* Append a courtesy newline */

Modules/clinic/binascii.c.h

Lines changed: 11 additions & 7 deletions
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[] = {"data", "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=4a418f883ccc79fe input=a9049054013a1b77]*/
565+
/*[clinic end generated code: output=25820051c57501c7 input=a9049054013a1b77]*/

0 commit comments

Comments
 (0)