bpo-30103: Allow Uuencode in Python using backtick as zero instead of space (#1326)

This commit is contained in:
Xiang Zhang 2017-05-03 11:16:21 +08:00 committed by GitHub
parent 0360a9d015
commit 13f1f423fa
10 changed files with 137 additions and 67 deletions

View File

@ -40,11 +40,14 @@ The :mod:`binascii` module defines the following functions:
data may be followed by whitespace. data may be followed by whitespace.
.. function:: b2a_uu(data) .. function:: b2a_uu(data, *, backtick=False)
Convert binary data to a line of ASCII characters, the return value is the Convert binary data to a line of ASCII characters, the return value is the
converted line, including a newline char. The length of *data* should be at most converted line, including a newline char. The length of *data* should be at most
45. 45. If *backtick* is true, zeros are represented by ``'`'`` instead of spaces.
.. versionchanged:: 3.7
Added the *backtick* parameter.
.. function:: a2b_base64(string) .. function:: a2b_base64(string)
@ -53,7 +56,7 @@ The :mod:`binascii` module defines the following functions:
than one line may be passed at a time. than one line may be passed at a time.
.. function:: b2a_base64(data, \*, newline=True) .. function:: b2a_base64(data, *, newline=True)
Convert binary data to a line of ASCII characters in base64 coding. The return Convert binary data to a line of ASCII characters in base64 coding. The return
value is the converted line, including a newline char if *newline* is value is the converted line, including a newline char if *newline* is

View File

@ -28,12 +28,16 @@ This code was contributed by Lance Ellinghouse, and modified by Jack Jansen.
The :mod:`uu` module defines the following functions: The :mod:`uu` module defines the following functions:
.. function:: encode(in_file, out_file, name=None, mode=None) .. function:: encode(in_file, out_file, name=None, mode=None, *, backtick=False)
Uuencode file *in_file* into file *out_file*. The uuencoded file will have Uuencode file *in_file* into file *out_file*. The uuencoded file will have
the header specifying *name* and *mode* as the defaults for the results of the header specifying *name* and *mode* as the defaults for the results of
decoding the file. The default defaults are taken from *in_file*, or ``'-'`` decoding the file. The default defaults are taken from *in_file*, or ``'-'``
and ``0o666`` respectively. and ``0o666`` respectively. If *backtick* is true, zeros are represented by
``'`'`` instead of spaces.
.. versionchanged:: 3.7
Added the *backtick* parameter.
.. function:: decode(in_file, out_file=None, mode=None, quiet=False) .. function:: decode(in_file, out_file=None, mode=None, quiet=False)

View File

@ -328,3 +328,7 @@ whatsnew/3.5,,:exception,ERROR:root:exception
whatsnew/changelog,,:version,import sys; I = version[:version.index(' ')] whatsnew/changelog,,:version,import sys; I = version[:version.index(' ')]
whatsnew/changelog,,`,"for readability (was ""`"")." whatsnew/changelog,,`,"for readability (was ""`"")."
whatsnew/changelog,,:end,str[start:end] whatsnew/changelog,,:end,str[start:end]
library/binascii,,`,'`'
library/uu,,`,'`'
whatsnew/3.7,,`,'`'
whatsnew/changelog,,`,'`'

1 c-api/arg :ref PyArg_ParseTuple(args, "O|O:ref", &object, &callback)
328 whatsnew/changelog :version import sys; I = version[:version.index(' ')]
329 whatsnew/changelog ` for readability (was "`").
330 whatsnew/changelog :end str[start:end]
331 library/binascii ` '`'
332 library/uu ` '`'
333 whatsnew/3.7 ` '`'
334 whatsnew/changelog ` '`'

View File

@ -95,6 +95,13 @@ New Modules
Improved Modules Improved Modules
================ ================
binascii
--------
The :func:`~binascii.b2a_uu` function now accepts an optional *backtick*
keyword argument. When it's true, zeros are represented by ``'`'``
instead of spaces. (Contributed by Xiang Zhang in :issue:`30103`.)
contextlib contextlib
---------- ----------
@ -159,6 +166,13 @@ urllib.parse
adding `~` to the set of characters that is never quoted by default. adding `~` to the set of characters that is never quoted by default.
(Contributed by Christian Theune and Ratnadeep Debnath in :issue:`16285`.) (Contributed by Christian Theune and Ratnadeep Debnath in :issue:`16285`.)
uu
--
Function :func:`~uu.encode` now accepts an optional *backtick*
keyword argument. When it's true, zeros are represented by ``'`'``
instead of spaces. (Contributed by Xiang Zhang in :issue:`30103`.)
Optimizations Optimizations
============= =============

View File

@ -112,10 +112,11 @@ class BinASCIITest(unittest.TestCase):
def test_uu(self): def test_uu(self):
MAX_UU = 45 MAX_UU = 45
for backtick in (True, False):
lines = [] lines = []
for i in range(0, len(self.data), MAX_UU): for i in range(0, len(self.data), MAX_UU):
b = self.type2test(self.rawdata[i:i+MAX_UU]) b = self.type2test(self.rawdata[i:i+MAX_UU])
a = binascii.b2a_uu(b) a = binascii.b2a_uu(b, backtick=backtick)
lines.append(a) lines.append(a)
res = bytes() res = bytes()
for line in lines: for line in lines:
@ -129,12 +130,23 @@ class BinASCIITest(unittest.TestCase):
self.assertEqual(binascii.a2b_uu(b"\xff"), b"\x00"*31) self.assertEqual(binascii.a2b_uu(b"\xff"), b"\x00"*31)
self.assertRaises(binascii.Error, binascii.a2b_uu, b"\xff\x00") self.assertRaises(binascii.Error, binascii.a2b_uu, b"\xff\x00")
self.assertRaises(binascii.Error, binascii.a2b_uu, b"!!!!") self.assertRaises(binascii.Error, binascii.a2b_uu, b"!!!!")
self.assertRaises(binascii.Error, binascii.b2a_uu, 46*b"!") self.assertRaises(binascii.Error, binascii.b2a_uu, 46*b"!")
# Issue #7701 (crash on a pydebug build) # Issue #7701 (crash on a pydebug build)
self.assertEqual(binascii.b2a_uu(b'x'), b'!> \n') self.assertEqual(binascii.b2a_uu(b'x'), b'!> \n')
self.assertEqual(binascii.b2a_uu(b''), b' \n')
self.assertEqual(binascii.b2a_uu(b'', backtick=True), b'`\n')
self.assertEqual(binascii.a2b_uu(b' \n'), b'')
self.assertEqual(binascii.a2b_uu(b'`\n'), b'')
self.assertEqual(binascii.b2a_uu(b'\x00Cat'), b'$ $-A= \n')
self.assertEqual(binascii.b2a_uu(b'\x00Cat', backtick=True),
b'$`$-A=```\n')
self.assertEqual(binascii.a2b_uu(b'$`$-A=```\n'),
binascii.a2b_uu(b'$ $-A= \n'))
with self.assertRaises(TypeError):
binascii.b2a_uu(b"", True)
def test_crc_hqx(self): def test_crc_hqx(self):
crc = binascii.crc_hqx(self.type2test(b"Test the CRC-32 of"), 0) crc = binascii.crc_hqx(self.type2test(b"Test the CRC-32 of"), 0)
crc = binascii.crc_hqx(self.type2test(b" this string."), crc) crc = binascii.crc_hqx(self.type2test(b" this string."), crc)

View File

@ -10,11 +10,11 @@ import sys, os
import uu import uu
import io import io
plaintext = b"The smooth-scaled python crept over the sleeping dog\n" plaintext = b"The symbols on top of your keyboard are !@#$%^&*()_+|~\n"
encodedtext = b"""\ encodedtext = b"""\
M5&AE('-M;V]T:\"US8V%L960@<'ET:&]N(&-R97!T(&]V97(@=&AE('-L965P M5&AE('-Y;6)O;',@;VX@=&]P(&]F('EO=7(@:V5Y8F]A<F0@87)E("% (R0E
(:6YG(&1O9PH """ *7B8J*"E?*WQ^"@ """
# Stolen from io.py # Stolen from io.py
class FakeIO(io.TextIOWrapper): class FakeIO(io.TextIOWrapper):
@ -44,9 +44,14 @@ class FakeIO(io.TextIOWrapper):
return self.buffer.getvalue().decode(self._encoding, self._errors) return self.buffer.getvalue().decode(self._encoding, self._errors)
def encodedtextwrapped(mode, filename): def encodedtextwrapped(mode, filename, backtick=False):
return (bytes("begin %03o %s\n" % (mode, filename), "ascii") + if backtick:
res = (bytes("begin %03o %s\n" % (mode, filename), "ascii") +
encodedtext.replace(b' ', b'`') + b"\n`\nend\n")
else:
res = (bytes("begin %03o %s\n" % (mode, filename), "ascii") +
encodedtext + b"\n \nend\n") encodedtext + b"\n \nend\n")
return res
class UUTest(unittest.TestCase): class UUTest(unittest.TestCase):
@ -59,16 +64,23 @@ class UUTest(unittest.TestCase):
out = io.BytesIO() out = io.BytesIO()
uu.encode(inp, out, "t1", 0o644) uu.encode(inp, out, "t1", 0o644)
self.assertEqual(out.getvalue(), encodedtextwrapped(0o644, "t1")) self.assertEqual(out.getvalue(), encodedtextwrapped(0o644, "t1"))
inp = io.BytesIO(plaintext)
out = io.BytesIO()
uu.encode(inp, out, "t1", backtick=True)
self.assertEqual(out.getvalue(), encodedtextwrapped(0o666, "t1", True))
with self.assertRaises(TypeError):
uu.encode(inp, out, "t1", 0o644, True)
def test_decode(self): def test_decode(self):
inp = io.BytesIO(encodedtextwrapped(0o666, "t1")) for backtick in True, False:
inp = io.BytesIO(encodedtextwrapped(0o666, "t1", backtick=backtick))
out = io.BytesIO() out = io.BytesIO()
uu.decode(inp, out) uu.decode(inp, out)
self.assertEqual(out.getvalue(), plaintext) self.assertEqual(out.getvalue(), plaintext)
inp = io.BytesIO( inp = io.BytesIO(
b"UUencoded files may contain many lines,\n" + b"UUencoded files may contain many lines,\n" +
b"even some that have 'begin' in them.\n" + b"even some that have 'begin' in them.\n" +
encodedtextwrapped(0o666, "t1") encodedtextwrapped(0o666, "t1", backtick=backtick)
) )
out = io.BytesIO() out = io.BytesIO()
uu.decode(inp, out) uu.decode(inp, out)
@ -94,15 +106,23 @@ class UUTest(unittest.TestCase):
def test_garbage_padding(self): def test_garbage_padding(self):
# Issue #22406 # Issue #22406
encodedtext = ( encodedtext1 = (
b"begin 644 file\n" b"begin 644 file\n"
# length 1; bits 001100 111111 111111 111111 # length 1; bits 001100 111111 111111 111111
b"\x21\x2C\x5F\x5F\x5F\n" b"\x21\x2C\x5F\x5F\x5F\n"
b"\x20\n" b"\x20\n"
b"end\n" b"end\n"
) )
encodedtext2 = (
b"begin 644 file\n"
# length 1; bits 001100 111111 111111 111111
b"\x21\x2C\x5F\x5F\x5F\n"
b"\x60\n"
b"end\n"
)
plaintext = b"\x33" # 00110011 plaintext = b"\x33" # 00110011
for encodedtext in encodedtext1, encodedtext2:
with self.subTest("uu.decode()"): with self.subTest("uu.decode()"):
inp = io.BytesIO(encodedtext) inp = io.BytesIO(encodedtext)
out = io.BytesIO() out = io.BytesIO()
@ -250,11 +270,6 @@ class UUFileTest(unittest.TestCase):
finally: finally:
self._kill(f) self._kill(f)
def test_main():
support.run_unittest(UUTest,
UUStdIOTest,
UUFileTest,
)
if __name__=="__main__": if __name__=="__main__":
test_main() unittest.main()

View File

@ -26,8 +26,8 @@
"""Implementation of the UUencode and UUdecode functions. """Implementation of the UUencode and UUdecode functions.
encode(in_file, out_file [,name, mode]) encode(in_file, out_file [,name, mode], *, backtick=False)
decode(in_file [, out_file, mode]) decode(in_file [, out_file, mode, quiet])
""" """
import binascii import binascii
@ -39,7 +39,7 @@ __all__ = ["Error", "encode", "decode"]
class Error(Exception): class Error(Exception):
pass pass
def encode(in_file, out_file, name=None, mode=None): def encode(in_file, out_file, name=None, mode=None, *, backtick=False):
"""Uuencode file""" """Uuencode file"""
# #
# If in_file is a pathname open it and change defaults # If in_file is a pathname open it and change defaults
@ -79,8 +79,11 @@ def encode(in_file, out_file, name=None, mode=None):
out_file.write(('begin %o %s\n' % ((mode & 0o777), name)).encode("ascii")) out_file.write(('begin %o %s\n' % ((mode & 0o777), name)).encode("ascii"))
data = in_file.read(45) data = in_file.read(45)
while len(data) > 0: while len(data) > 0:
out_file.write(binascii.b2a_uu(data)) out_file.write(binascii.b2a_uu(data, backtick=backtick))
data = in_file.read(45) data = in_file.read(45)
if backtick:
out_file.write(b'`\nend\n')
else:
out_file.write(b' \nend\n') out_file.write(b' \nend\n')
finally: finally:
for f in opened_files: for f in opened_files:

View File

@ -317,6 +317,9 @@ Extension Modules
Library Library
------- -------
- bpo-30103: binascii.b2a_uu() and uu.encode() now support using ``'`'``
as zero instead of space.
- bpo-28556: Various updates to typing module: add typing.NoReturn type, use - bpo-28556: Various updates to typing module: add typing.NoReturn type, use
WrapperDescriptorType, minor bug-fixes. Original PRs by WrapperDescriptorType, minor bug-fixes. Original PRs by
Jim Fasarakis-Hilliard and Ivan Levkivskyi. Jim Fasarakis-Hilliard and Ivan Levkivskyi.

View File

@ -335,13 +335,15 @@ binascii.b2a_uu
data: Py_buffer data: Py_buffer
/ /
*
backtick: bool(accept={int}) = False
Uuencode line of data. Uuencode line of data.
[clinic start generated code]*/ [clinic start generated code]*/
static PyObject * static PyObject *
binascii_b2a_uu_impl(PyObject *module, Py_buffer *data) binascii_b2a_uu_impl(PyObject *module, Py_buffer *data, int backtick)
/*[clinic end generated code: output=0070670e52e4aa6b input=00fdf458ce8b465b]*/ /*[clinic end generated code: output=b1b99de62d9bbeb8 input=b26bc8d32b6ed2f6]*/
{ {
unsigned char *ascii_data; unsigned char *ascii_data;
const unsigned char *bin_data; const unsigned char *bin_data;
@ -367,7 +369,10 @@ binascii_b2a_uu_impl(PyObject *module, Py_buffer *data)
return NULL; return NULL;
/* Store the length */ /* Store the length */
*ascii_data++ = ' ' + (bin_len & 077); if (backtick && !bin_len)
*ascii_data++ = '`';
else
*ascii_data++ = ' ' + bin_len;
for( ; bin_len > 0 || leftbits != 0 ; bin_len--, bin_data++ ) { for( ; bin_len > 0 || leftbits != 0 ; bin_len--, bin_data++ ) {
/* Shift the data (or padding) into our buffer */ /* Shift the data (or padding) into our buffer */
@ -381,6 +386,9 @@ binascii_b2a_uu_impl(PyObject *module, Py_buffer *data)
while ( leftbits >= 6 ) { while ( leftbits >= 6 ) {
this_ch = (leftchar >> (leftbits-6)) & 0x3f; this_ch = (leftchar >> (leftbits-6)) & 0x3f;
leftbits -= 6; leftbits -= 6;
if (backtick && !this_ch)
*ascii_data++ = '`';
else
*ascii_data++ = this_ch + ' '; *ascii_data++ = this_ch + ' ';
} }
} }

View File

@ -34,27 +34,31 @@ exit:
} }
PyDoc_STRVAR(binascii_b2a_uu__doc__, PyDoc_STRVAR(binascii_b2a_uu__doc__,
"b2a_uu($module, data, /)\n" "b2a_uu($module, data, /, *, backtick=False)\n"
"--\n" "--\n"
"\n" "\n"
"Uuencode line of data."); "Uuencode line of data.");
#define BINASCII_B2A_UU_METHODDEF \ #define BINASCII_B2A_UU_METHODDEF \
{"b2a_uu", (PyCFunction)binascii_b2a_uu, METH_O, binascii_b2a_uu__doc__}, {"b2a_uu", (PyCFunction)binascii_b2a_uu, METH_FASTCALL, binascii_b2a_uu__doc__},
static PyObject * static PyObject *
binascii_b2a_uu_impl(PyObject *module, Py_buffer *data); binascii_b2a_uu_impl(PyObject *module, Py_buffer *data, int backtick);
static PyObject * static PyObject *
binascii_b2a_uu(PyObject *module, PyObject *arg) binascii_b2a_uu(PyObject *module, PyObject **args, Py_ssize_t nargs, PyObject *kwnames)
{ {
PyObject *return_value = NULL; PyObject *return_value = NULL;
static const char * const _keywords[] = {"", "backtick", NULL};
static _PyArg_Parser _parser = {"y*|$i:b2a_uu", _keywords, 0};
Py_buffer data = {NULL, NULL}; Py_buffer data = {NULL, NULL};
int backtick = 0;
if (!PyArg_Parse(arg, "y*:b2a_uu", &data)) { if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser,
&data, &backtick)) {
goto exit; goto exit;
} }
return_value = binascii_b2a_uu_impl(module, &data); return_value = binascii_b2a_uu_impl(module, &data, backtick);
exit: exit:
/* Cleanup for data */ /* Cleanup for data */
@ -558,4 +562,4 @@ exit:
return return_value; return return_value;
} }
/*[clinic end generated code: output=35821bce7e0e4714 input=a9049054013a1b77]*/ /*[clinic end generated code: output=9db57e86dbe7b2fa input=a9049054013a1b77]*/