diff --git a/Lib/email/message.py b/Lib/email/message.py index b6512f2198..f932186875 100644 --- a/Lib/email/message.py +++ b/Lib/email/message.py @@ -7,7 +7,6 @@ __all__ = ['Message', 'EmailMessage'] import re -import uu import quopri from io import BytesIO, StringIO @@ -101,6 +100,35 @@ def _unquotevalue(value): return utils.unquote(value) +def _decode_uu(encoded): + """Decode uuencoded data.""" + decoded_lines = [] + encoded_lines_iter = iter(encoded.splitlines()) + for line in encoded_lines_iter: + if line.startswith(b"begin "): + mode, _, path = line.removeprefix(b"begin ").partition(b" ") + try: + int(mode, base=8) + except ValueError: + continue + else: + break + else: + raise ValueError("`begin` line not found") + for line in encoded_lines_iter: + if not line: + raise ValueError("Truncated input") + elif line.strip(b' \t\r\n\f') == b'end': + break + try: + decoded_line = binascii.a2b_uu(line) + except binascii.Error: + # Workaround for broken uuencoders by /Fredrik Lundh + nbytes = (((line[0]-32) & 63) * 4 + 5) // 3 + decoded_line = binascii.a2b_uu(line[:nbytes]) + decoded_lines.append(decoded_line) + + return b''.join(decoded_lines) class Message: """Basic message object. @@ -288,13 +316,10 @@ def get_payload(self, i=None, decode=False): self.policy.handle_defect(self, defect) return value elif cte in ('x-uuencode', 'uuencode', 'uue', 'x-uue'): - in_file = BytesIO(bpayload) - out_file = BytesIO() try: - uu.decode(in_file, out_file, quiet=True) - return out_file.getvalue() - except uu.Error: - # Some decoding problem + return _decode_uu(bpayload) + except ValueError: + # Some decoding problem. return bpayload if isinstance(payload, str): return bpayload diff --git a/Lib/test/test_uu.py b/Lib/test/test_uu.py deleted file mode 100644 index f71d877365..0000000000 --- a/Lib/test/test_uu.py +++ /dev/null @@ -1,258 +0,0 @@ -""" -Tests for uu module. -Nick Mathewson -""" - -import unittest -from test.support import os_helper - -import os -import stat -import sys -import uu -import io - -plaintext = b"The symbols on top of your keyboard are !@#$%^&*()_+|~\n" - -encodedtext = b"""\ -M5&AE('-Y;6)O;',@;VX@=&]P(&]F('EO=7(@:V5Y8F]A<F0@87)E("% (R0E -*7B8J*"E?*WQ^"@ """ - -# Stolen from io.py -class FakeIO(io.TextIOWrapper): - """Text I/O implementation using an in-memory buffer. - - Can be a used as a drop-in replacement for sys.stdin and sys.stdout. - """ - - # XXX This is really slow, but fully functional - - def __init__(self, initial_value="", encoding="utf-8", - errors="strict", newline="\n"): - super(FakeIO, self).__init__(io.BytesIO(), - encoding=encoding, - errors=errors, - newline=newline) - self._encoding = encoding - self._errors = errors - if initial_value: - if not isinstance(initial_value, str): - initial_value = str(initial_value) - self.write(initial_value) - self.seek(0) - - def getvalue(self): - self.flush() - return self.buffer.getvalue().decode(self._encoding, self._errors) - - -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") - else: - res = (bytes("begin %03o %s\n" % (mode, filename), "ascii") + - encodedtext + b"\n \nend\n") - return res - -class UUTest(unittest.TestCase): - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_encode(self): - inp = io.BytesIO(plaintext) - out = io.BytesIO() - uu.encode(inp, out, "t1") - self.assertEqual(out.getvalue(), encodedtextwrapped(0o666, "t1")) - inp = io.BytesIO(plaintext) - 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): - 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) - out = io.BytesIO() - try: - uu.decode(inp, out) - self.fail("No exception raised") - except uu.Error as e: - self.assertEqual(str(e), "Truncated input file") - - def test_missingbegin(self): - inp = io.BytesIO(b"") - out = io.BytesIO() - try: - uu.decode(inp, out) - self.fail("No exception raised") - except uu.Error as e: - self.assertEqual(str(e), "No valid begin line found in input file") - - def test_garbage_padding(self): - # Issue #22406 - 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 - - 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) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_newlines_escaped(self): - # Test newlines are escaped with uu.encode - inp = io.BytesIO(plaintext) - out = io.BytesIO() - filename = "test.txt\n\roverflow.txt" - safefilename = b"test.txt\\n\\roverflow.txt" - uu.encode(inp, out, filename) - self.assertIn(safefilename, out.getvalue()) - -class UUStdIOTest(unittest.TestCase): - - def setUp(self): - self.stdin = sys.stdin - self.stdout = sys.stdout - - def tearDown(self): - sys.stdin = self.stdin - sys.stdout = self.stdout - - def test_encode(self): - sys.stdin = FakeIO(plaintext.decode("ascii")) - sys.stdout = FakeIO() - uu.encode("-", "-", "t1", 0o666) - self.assertEqual(sys.stdout.getvalue(), - encodedtextwrapped(0o666, "t1").decode("ascii")) - - def test_decode(self): - sys.stdin = FakeIO(encodedtextwrapped(0o666, "t1").decode("ascii")) - sys.stdout = FakeIO() - uu.decode("-", "-") - stdout = sys.stdout - sys.stdout = self.stdout - sys.stdin = self.stdin - self.assertEqual(stdout.getvalue(), plaintext.decode("ascii")) - -class UUFileTest(unittest.TestCase): - - def setUp(self): - # uu.encode() supports only ASCII file names - self.tmpin = os_helper.TESTFN_ASCII + "i" - self.tmpout = os_helper.TESTFN_ASCII + "o" - self.addCleanup(os_helper.unlink, self.tmpin) - self.addCleanup(os_helper.unlink, self.tmpout) - - def test_encode(self): - with open(self.tmpin, 'wb') as fin: - fin.write(plaintext) - - with open(self.tmpin, 'rb') as fin: - with open(self.tmpout, 'wb') as fout: - uu.encode(fin, fout, self.tmpin, mode=0o644) - - with open(self.tmpout, 'rb') as fout: - s = fout.read() - self.assertEqual(s, encodedtextwrapped(0o644, self.tmpin)) - - # in_file and out_file as filenames - uu.encode(self.tmpin, self.tmpout, self.tmpin, mode=0o644) - with open(self.tmpout, 'rb') as fout: - s = fout.read() - self.assertEqual(s, encodedtextwrapped(0o644, self.tmpin)) - - def test_decode(self): - with open(self.tmpin, 'wb') as f: - f.write(encodedtextwrapped(0o644, self.tmpout)) - - with open(self.tmpin, 'rb') as f: - uu.decode(f) - - with open(self.tmpout, 'rb') as f: - s = f.read() - self.assertEqual(s, plaintext) - # XXX is there an xp way to verify the mode? - - def test_decode_filename(self): - with open(self.tmpin, 'wb') as f: - f.write(encodedtextwrapped(0o644, self.tmpout)) - - uu.decode(self.tmpin) - - with open(self.tmpout, 'rb') as f: - s = f.read() - self.assertEqual(s, plaintext) - - def test_decodetwice(self): - # Verify that decode() will refuse to overwrite an existing file - with open(self.tmpin, 'wb') as f: - f.write(encodedtextwrapped(0o644, self.tmpout)) - with open(self.tmpin, 'rb') as f: - uu.decode(f) - - with open(self.tmpin, 'rb') as f: - self.assertRaises(uu.Error, uu.decode, f) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_decode_mode(self): - # Verify that decode() will set the given mode for the out_file - expected_mode = 0o444 - with open(self.tmpin, 'wb') as f: - f.write(encodedtextwrapped(expected_mode, self.tmpout)) - - # make file writable again, so it can be removed (Windows only) - self.addCleanup(os.chmod, self.tmpout, expected_mode | stat.S_IWRITE) - - with open(self.tmpin, 'rb') as f: - uu.decode(f) - - self.assertEqual( - stat.S_IMODE(os.stat(self.tmpout).st_mode), - expected_mode - ) - - -if __name__=="__main__": - unittest.main() diff --git a/Lib/uu.py b/Lib/uu.py deleted file mode 100755 index d68d29374a..0000000000 --- a/Lib/uu.py +++ /dev/null @@ -1,199 +0,0 @@ -#! /usr/bin/env python3 - -# Copyright 1994 by Lance Ellinghouse -# Cathedral City, California Republic, United States of America. -# All Rights Reserved -# Permission to use, copy, modify, and distribute this software and its -# documentation for any purpose and without fee is hereby granted, -# provided that the above copyright notice appear in all copies and that -# both that copyright notice and this permission notice appear in -# supporting documentation, and that the name of Lance Ellinghouse -# not be used in advertising or publicity pertaining to distribution -# of the software without specific, written prior permission. -# LANCE ELLINGHOUSE DISCLAIMS ALL WARRANTIES WITH REGARD TO -# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -# FITNESS, IN NO EVENT SHALL LANCE ELLINGHOUSE CENTRUM BE LIABLE -# FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT -# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -# -# Modified by Jack Jansen, CWI, July 1995: -# - Use binascii module to do the actual line-by-line conversion -# between ascii and binary. This results in a 1000-fold speedup. The C -# version is still 5 times faster, though. -# - Arguments more compliant with python standard - -"""Implementation of the UUencode and UUdecode functions. - -encode(in_file, out_file [,name, mode]) -decode(in_file [, out_file, mode]) -""" - -import binascii -import os -import sys - -__all__ = ["Error", "encode", "decode"] - -class Error(Exception): - pass - -def encode(in_file, out_file, name=None, mode=None): - """Uuencode file""" - # - # If in_file is a pathname open it and change defaults - # - opened_files = [] - try: - if in_file == '-': - in_file = sys.stdin.buffer - elif isinstance(in_file, str): - if name is None: - name = os.path.basename(in_file) - if mode is None: - try: - mode = os.stat(in_file).st_mode - except AttributeError: - pass - in_file = open(in_file, 'rb') - opened_files.append(in_file) - # - # Open out_file if it is a pathname - # - if out_file == '-': - out_file = sys.stdout.buffer - elif isinstance(out_file, str): - out_file = open(out_file, 'wb') - opened_files.append(out_file) - # - # Set defaults for name and mode - # - if name is None: - name = '-' - if mode is None: - mode = 0o666 - # - # Write the data - # - out_file.write(('begin %o %s\n' % ((mode & 0o777), name)).encode("ascii")) - data = in_file.read(45) - while len(data) > 0: - out_file.write(binascii.b2a_uu(data)) - data = in_file.read(45) - out_file.write(b' \nend\n') - finally: - for f in opened_files: - f.close() - - -def decode(in_file, out_file=None, mode=None, quiet=False): - """Decode uuencoded file""" - # - # Open the input file, if needed. - # - opened_files = [] - if in_file == '-': - in_file = sys.stdin.buffer - elif isinstance(in_file, str): - in_file = open(in_file, 'rb') - opened_files.append(in_file) - - try: - # - # Read until a begin is encountered or we've exhausted the file - # - while True: - hdr = in_file.readline() - if not hdr: - raise Error('No valid begin line found in input file') - if not hdr.startswith(b'begin'): - continue - hdrfields = hdr.split(b' ', 2) - if len(hdrfields) == 3 and hdrfields[0] == b'begin': - try: - int(hdrfields[1], 8) - break - except ValueError: - pass - if out_file is None: - # If the filename isn't ASCII, what's up with that?!? - out_file = hdrfields[2].rstrip(b' \t\r\n\f').decode("ascii") - if os.path.exists(out_file): - raise Error('Cannot overwrite existing file: %s' % out_file) - if mode is None: - mode = int(hdrfields[1], 8) - # - # Open the output file - # - if out_file == '-': - out_file = sys.stdout.buffer - elif isinstance(out_file, str): - fp = open(out_file, 'wb') - try: - os.path.chmod(out_file, mode) - except AttributeError: - pass - out_file = fp - opened_files.append(out_file) - # - # Main decoding loop - # - s = in_file.readline() - while s and s.strip(b' \t\r\n\f') != b'end': - try: - data = binascii.a2b_uu(s) - except binascii.Error as v: - # Workaround for broken uuencoders by /Fredrik Lundh - nbytes = (((s[0]-32) & 63) * 4 + 5) // 3 - data = binascii.a2b_uu(s[:nbytes]) - if not quiet: - sys.stderr.write("Warning: %s\n" % v) - out_file.write(data) - s = in_file.readline() - if not s: - raise Error('Truncated input file') - finally: - for f in opened_files: - f.close() - -def test(): - """uuencode/uudecode main program""" - - import optparse - parser = optparse.OptionParser(usage='usage: %prog [-d] [-t] [input [output]]') - parser.add_option('-d', '--decode', dest='decode', help='Decode (instead of encode)?', default=False, action='store_true') - parser.add_option('-t', '--text', dest='text', help='data is text, encoded format unix-compatible text?', default=False, action='store_true') - - (options, args) = parser.parse_args() - if len(args) > 2: - parser.error('incorrect number of arguments') - sys.exit(1) - - # Use the binary streams underlying stdin/stdout - input = sys.stdin.buffer - output = sys.stdout.buffer - if len(args) > 0: - input = args[0] - if len(args) > 1: - output = args[1] - - if options.decode: - if options.text: - if isinstance(output, str): - output = open(output, 'wb') - else: - print(sys.argv[0], ': cannot do -t to stdout') - sys.exit(1) - decode(input, output) - else: - if options.text: - if isinstance(input, str): - input = open(input, 'rb') - else: - print(sys.argv[0], ': cannot do -t from stdin') - sys.exit(1) - encode(input, output) - -if __name__ == '__main__': - test()