-
-
Notifications
You must be signed in to change notification settings - Fork 32.3k
bpo-30103: Allow Uuencode in Python using backtick as zero instead of space #1326
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
a45a02b
7f39aee
9cf21f6
5cf9c40
8be2e95
7c4f0e6
4b387f2
fd64af5
e2d0112
2e43ff1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,11 +40,14 @@ The :mod:`binascii` module defines the following functions: | |
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 | ||
converted line, including a newline char. The length of *data* should be at most | ||
45. | ||
45. If *backtick* is true, zeros are represented by backticks instead of spaces. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Show the backtick character explicitly, as
And in all other cases where it is mentioned. |
||
|
||
.. versionchanged:: 3.7 | ||
Added the *backtick* parameter. | ||
|
||
|
||
.. function:: a2b_base64(string) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,11 +10,11 @@ | |
import uu | ||
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"""\ | ||
M5&AE('-M;V]T:\"US8V%L960@<'ET:&]N(&-R97!T(&]V97(@=&AE('-L965P | ||
(:6YG(&1O9PH """ | ||
M5&AE(\'-Y;6)O;\',@;VX@=&]P(&]F(\'EO=7(@:V5Y8F]A<F0@87)E("% (R0E | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don’t think you need to escape the quotes ( |
||
*7B8J*"E?*WQ^"@ """ | ||
|
||
# Stolen from io.py | ||
class FakeIO(io.TextIOWrapper): | ||
|
@@ -44,9 +44,14 @@ def getvalue(self): | |
return self.buffer.getvalue().decode(self._encoding, self._errors) | ||
|
||
|
||
def encodedtextwrapped(mode, filename): | ||
return (bytes("begin %03o %s\n" % (mode, filename), "ascii") + | ||
encodedtext + b"\n \nend\n") | ||
def encodedtextwrapped(mode, filename, backtick=False): | ||
if backtick: | ||
res = (bytes("begin %03o %s\n" % (mode, filename), "ascii") + | ||
encodedtext.replace(b' ', b'`') + b"\n`\nend\n") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems the only space in encodedtext is the padding space. It would be worth to change examples so that they include inner spaces. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch. I found it when write the test too but didn't change it. |
||
else: | ||
res = (bytes("begin %03o %s\n" % (mode, filename), "ascii") + | ||
encodedtext + b"\n \nend\n") | ||
return res | ||
|
||
class UUTest(unittest.TestCase): | ||
|
||
|
@@ -59,20 +64,27 @@ def test_encode(self): | |
out = io.BytesIO() | ||
uu.encode(inp, out, "t1", 0o644) | ||
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): | ||
inp = io.BytesIO(encodedtextwrapped(0o666, "t1")) | ||
out = io.BytesIO() | ||
uu.decode(inp, out) | ||
self.assertEqual(out.getvalue(), plaintext) | ||
inp = io.BytesIO( | ||
b"UUencoded files may contain many lines,\n" + | ||
b"even some that have 'begin' in them.\n" + | ||
encodedtextwrapped(0o666, "t1") | ||
) | ||
out = io.BytesIO() | ||
uu.decode(inp, out) | ||
self.assertEqual(out.getvalue(), plaintext) | ||
for backtick in True, False: | ||
inp = io.BytesIO(encodedtextwrapped(0o666, "t1", backtick=backtick)) | ||
out = io.BytesIO() | ||
uu.decode(inp, out) | ||
self.assertEqual(out.getvalue(), plaintext) | ||
inp = io.BytesIO( | ||
b"UUencoded files may contain many lines,\n" + | ||
b"even some that have 'begin' in them.\n" + | ||
encodedtextwrapped(0o666, "t1", backtick=backtick) | ||
) | ||
out = io.BytesIO() | ||
uu.decode(inp, out) | ||
self.assertEqual(out.getvalue(), plaintext) | ||
|
||
def test_truncatedinput(self): | ||
inp = io.BytesIO(b"begin 644 t1\n" + encodedtext) | ||
|
@@ -94,25 +106,33 @@ def test_missingbegin(self): | |
|
||
def test_garbage_padding(self): | ||
# Issue #22406 | ||
encodedtext = ( | ||
encodedtext1 = ( | ||
b"begin 644 file\n" | ||
# length 1; bits 001100 111111 111111 111111 | ||
b"\x21\x2C\x5F\x5F\x5F\n" | ||
b"\x20\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 | ||
|
||
with self.subTest("uu.decode()"): | ||
inp = io.BytesIO(encodedtext) | ||
out = io.BytesIO() | ||
uu.decode(inp, out, quiet=True) | ||
self.assertEqual(out.getvalue(), plaintext) | ||
for encodedtext in encodedtext1, encodedtext2: | ||
with self.subTest("uu.decode()"): | ||
inp = io.BytesIO(encodedtext) | ||
out = io.BytesIO() | ||
uu.decode(inp, out, quiet=True) | ||
self.assertEqual(out.getvalue(), plaintext) | ||
|
||
with self.subTest("uu_codec"): | ||
import codecs | ||
decoded = codecs.decode(encodedtext, "uu_codec") | ||
self.assertEqual(decoded, plaintext) | ||
with self.subTest("uu_codec"): | ||
import codecs | ||
decoded = codecs.decode(encodedtext, "uu_codec") | ||
self.assertEqual(decoded, plaintext) | ||
|
||
class UUStdIOTest(unittest.TestCase): | ||
|
||
|
@@ -251,10 +271,7 @@ def test_decodetwice(self): | |
self._kill(f) | ||
|
||
def test_main(): | ||
support.run_unittest(UUTest, | ||
UUStdIOTest, | ||
UUFileTest, | ||
) | ||
support.run_unittest(UUTest, UUStdIOTest, UUFileTest) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems like an insignificant change. Wouldn’t it be simple to use “unittest.main” instead? |
||
|
||
if __name__=="__main__": | ||
test_main() |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -334,14 +334,15 @@ binascii_a2b_uu_impl(PyObject *module, Py_buffer *data) | |
binascii.b2a_uu | ||
|
||
data: Py_buffer | ||
/ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Was this intended? If so, it needs documenting and testing, but it would be good to avoid if possible. Can’t you put a slash and asterisk after each other to have positional-only and then keyword-only parameters? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. AC currently seems doesn't support combining positional-only parameters with keyword-only parameters. So it's not intended but a side effect. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It does, I just checked this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ohh, nice. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While I support keeping data positional-only, but in bpo-25357 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks to me that change in bpo-25357 was not intentional. Perhaps we can revert it. Open a new issue for this. |
||
* | ||
backtick: bool(accept={int}) = False | ||
|
||
Uuencode line of data. | ||
[clinic start generated code]*/ | ||
|
||
static PyObject * | ||
binascii_b2a_uu_impl(PyObject *module, Py_buffer *data) | ||
/*[clinic end generated code: output=0070670e52e4aa6b input=00fdf458ce8b465b]*/ | ||
binascii_b2a_uu_impl(PyObject *module, Py_buffer *data, int backtick) | ||
/*[clinic end generated code: output=b1b99de62d9bbeb8 input=141f61b6ceb56af6]*/ | ||
{ | ||
unsigned char *ascii_data; | ||
const unsigned char *bin_data; | ||
|
@@ -367,7 +368,10 @@ binascii_b2a_uu_impl(PyObject *module, Py_buffer *data) | |
return NULL; | ||
|
||
/* Store the length */ | ||
*ascii_data++ = ' ' + (bin_len & 077); | ||
if (backtick && !bin_len) | ||
*ascii_data++ = '`'; | ||
else | ||
*ascii_data++ = ' ' + (bin_len & 077); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually |
||
|
||
for( ; bin_len > 0 || leftbits != 0 ; bin_len--, bin_data++ ) { | ||
/* Shift the data (or padding) into our buffer */ | ||
|
@@ -381,7 +385,10 @@ binascii_b2a_uu_impl(PyObject *module, Py_buffer *data) | |
while ( leftbits >= 6 ) { | ||
this_ch = (leftchar >> (leftbits-6)) & 0x3f; | ||
leftbits -= 6; | ||
*ascii_data++ = this_ch + ' '; | ||
if (backtick && !this_ch) | ||
*ascii_data++ = '`'; | ||
else | ||
*ascii_data++ = this_ch + ' '; | ||
} | ||
} | ||
*ascii_data++ = '\n'; /* Append a courtesy newline */ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAIK the backslash is not needed here.