Skip to content

Commit 59e9920

Browse files
committed
Update uu.py and test_uu.py from CPython v3.12.0
1 parent bdf228e commit 59e9920

File tree

2 files changed

+64
-18
lines changed

2 files changed

+64
-18
lines changed

Lib/test/test_uu.py

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@
44
"""
55

66
import unittest
7-
from test.support import os_helper
7+
from test.support import os_helper, warnings_helper
8+
9+
uu = warnings_helper.import_deprecated("uu")
810

911
import os
1012
import stat
1113
import sys
12-
import uu
1314
import io
1415

1516
plaintext = b"The symbols on top of your keyboard are !@#$%^&*()_+|~\n"
@@ -57,8 +58,6 @@ def encodedtextwrapped(mode, filename, backtick=False):
5758

5859
class UUTest(unittest.TestCase):
5960

60-
# TODO: RUSTPYTHON
61-
@unittest.expectedFailure
6261
def test_encode(self):
6362
inp = io.BytesIO(plaintext)
6463
out = io.BytesIO()
@@ -75,6 +74,7 @@ def test_encode(self):
7574
with self.assertRaises(TypeError):
7675
uu.encode(inp, out, "t1", 0o644, True)
7776

77+
@os_helper.skip_unless_working_chmod
7878
def test_decode(self):
7979
for backtick in True, False:
8080
inp = io.BytesIO(encodedtextwrapped(0o666, "t1", backtick=backtick))
@@ -138,8 +138,6 @@ def test_garbage_padding(self):
138138
decoded = codecs.decode(encodedtext, "uu_codec")
139139
self.assertEqual(decoded, plaintext)
140140

141-
# TODO: RUSTPYTHON
142-
@unittest.expectedFailure
143141
def test_newlines_escaped(self):
144142
# Test newlines are escaped with uu.encode
145143
inp = io.BytesIO(plaintext)
@@ -149,6 +147,34 @@ def test_newlines_escaped(self):
149147
uu.encode(inp, out, filename)
150148
self.assertIn(safefilename, out.getvalue())
151149

150+
def test_no_directory_traversal(self):
151+
relative_bad = b"""\
152+
begin 644 ../../../../../../../../tmp/test1
153+
$86)C"@``
154+
`
155+
end
156+
"""
157+
with self.assertRaisesRegex(uu.Error, 'directory'):
158+
uu.decode(io.BytesIO(relative_bad))
159+
if os.altsep:
160+
relative_bad_bs = relative_bad.replace(b'/', b'\\')
161+
with self.assertRaisesRegex(uu.Error, 'directory'):
162+
uu.decode(io.BytesIO(relative_bad_bs))
163+
164+
absolute_bad = b"""\
165+
begin 644 /tmp/test2
166+
$86)C"@``
167+
`
168+
end
169+
"""
170+
with self.assertRaisesRegex(uu.Error, 'directory'):
171+
uu.decode(io.BytesIO(absolute_bad))
172+
if os.altsep:
173+
absolute_bad_bs = absolute_bad.replace(b'/', b'\\')
174+
with self.assertRaisesRegex(uu.Error, 'directory'):
175+
uu.decode(io.BytesIO(absolute_bad_bs))
176+
177+
152178
class UUStdIOTest(unittest.TestCase):
153179

154180
def setUp(self):
@@ -202,6 +228,8 @@ def test_encode(self):
202228
s = fout.read()
203229
self.assertEqual(s, encodedtextwrapped(0o644, self.tmpin))
204230

231+
# decode() calls chmod()
232+
@os_helper.skip_unless_working_chmod
205233
def test_decode(self):
206234
with open(self.tmpin, 'wb') as f:
207235
f.write(encodedtextwrapped(0o644, self.tmpout))
@@ -214,6 +242,7 @@ def test_decode(self):
214242
self.assertEqual(s, plaintext)
215243
# XXX is there an xp way to verify the mode?
216244

245+
@os_helper.skip_unless_working_chmod
217246
def test_decode_filename(self):
218247
with open(self.tmpin, 'wb') as f:
219248
f.write(encodedtextwrapped(0o644, self.tmpout))
@@ -224,6 +253,7 @@ def test_decode_filename(self):
224253
s = f.read()
225254
self.assertEqual(s, plaintext)
226255

256+
@os_helper.skip_unless_working_chmod
227257
def test_decodetwice(self):
228258
# Verify that decode() will refuse to overwrite an existing file
229259
with open(self.tmpin, 'wb') as f:
@@ -234,8 +264,7 @@ def test_decodetwice(self):
234264
with open(self.tmpin, 'rb') as f:
235265
self.assertRaises(uu.Error, uu.decode, f)
236266

237-
# TODO: RUSTPYTHON
238-
@unittest.expectedFailure
267+
@os_helper.skip_unless_working_chmod
239268
def test_decode_mode(self):
240269
# Verify that decode() will set the given mode for the out_file
241270
expected_mode = 0o444

Lib/uu.py

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,23 @@
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
3434
import os
3535
import sys
36+
import warnings
37+
38+
warnings._deprecated(__name__, remove=(3, 13))
3639

3740
__all__ = ["Error", "encode", "decode"]
3841

3942
class Error(Exception):
4043
pass
4144

42-
def encode(in_file, out_file, name=None, mode=None):
45+
def encode(in_file, out_file, name=None, mode=None, *, backtick=False):
4346
"""Uuencode file"""
4447
#
4548
# If in_file is a pathname open it and change defaults
@@ -73,15 +76,25 @@ def encode(in_file, out_file, name=None, mode=None):
7376
name = '-'
7477
if mode is None:
7578
mode = 0o666
79+
80+
#
81+
# Remove newline chars from name
82+
#
83+
name = name.replace('\n','\\n')
84+
name = name.replace('\r','\\r')
85+
7686
#
7787
# Write the data
7888
#
7989
out_file.write(('begin %o %s\n' % ((mode & 0o777), name)).encode("ascii"))
8090
data = in_file.read(45)
8191
while len(data) > 0:
82-
out_file.write(binascii.b2a_uu(data))
92+
out_file.write(binascii.b2a_uu(data, backtick=backtick))
8393
data = in_file.read(45)
84-
out_file.write(b' \nend\n')
94+
if backtick:
95+
out_file.write(b'`\nend\n')
96+
else:
97+
out_file.write(b' \nend\n')
8598
finally:
8699
for f in opened_files:
87100
f.close()
@@ -120,7 +133,14 @@ def decode(in_file, out_file=None, mode=None, quiet=False):
120133
# If the filename isn't ASCII, what's up with that?!?
121134
out_file = hdrfields[2].rstrip(b' \t\r\n\f').decode("ascii")
122135
if os.path.exists(out_file):
123-
raise Error('Cannot overwrite existing file: %s' % out_file)
136+
raise Error(f'Cannot overwrite existing file: {out_file}')
137+
if (out_file.startswith(os.sep) or
138+
f'..{os.sep}' in out_file or (
139+
os.altsep and
140+
(out_file.startswith(os.altsep) or
141+
f'..{os.altsep}' in out_file))
142+
):
143+
raise Error(f'Refusing to write to {out_file} due to directory traversal')
124144
if mode is None:
125145
mode = int(hdrfields[1], 8)
126146
#
@@ -130,10 +150,7 @@ def decode(in_file, out_file=None, mode=None, quiet=False):
130150
out_file = sys.stdout.buffer
131151
elif isinstance(out_file, str):
132152
fp = open(out_file, 'wb')
133-
try:
134-
os.path.chmod(out_file, mode)
135-
except AttributeError:
136-
pass
153+
os.chmod(out_file, mode)
137154
out_file = fp
138155
opened_files.append(out_file)
139156
#

0 commit comments

Comments
 (0)