1 """
2 Tests for uu module.
3 Nick Mathewson
4 """
5
6 import unittest
7 from test.support import os_helper, warnings_helper
8
9 uu = warnings_helper.import_deprecated("uu")
10
11 import os
12 import stat
13 import sys
14 import io
15
16 plaintext = b"The symbols on top of your keyboard are !@#$%^&*()_+|~\n"
17
18 encodedtext = b"""\
19 M5&AE('-Y;6)O;',@;VX@=&]P(&]F('EO=7(@:V5Y8F]A<F0@87)E("% (R0E
20 *7B8J*"E?*WQ^"@ """
21
22 # Stolen from io.py
23 class ESC[4;38;5;81mFakeIO(ESC[4;38;5;149mioESC[4;38;5;149m.ESC[4;38;5;149mTextIOWrapper):
24 """Text I/O implementation using an in-memory buffer.
25
26 Can be a used as a drop-in replacement for sys.stdin and sys.stdout.
27 """
28
29 # XXX This is really slow, but fully functional
30
31 def __init__(self, initial_value="", encoding="utf-8",
32 errors="strict", newline="\n"):
33 super(FakeIO, self).__init__(io.BytesIO(),
34 encoding=encoding,
35 errors=errors,
36 newline=newline)
37 self._encoding = encoding
38 self._errors = errors
39 if initial_value:
40 if not isinstance(initial_value, str):
41 initial_value = str(initial_value)
42 self.write(initial_value)
43 self.seek(0)
44
45 def getvalue(self):
46 self.flush()
47 return self.buffer.getvalue().decode(self._encoding, self._errors)
48
49
50 def encodedtextwrapped(mode, filename, backtick=False):
51 if backtick:
52 res = (bytes("begin %03o %s\n" % (mode, filename), "ascii") +
53 encodedtext.replace(b' ', b'`') + b"\n`\nend\n")
54 else:
55 res = (bytes("begin %03o %s\n" % (mode, filename), "ascii") +
56 encodedtext + b"\n \nend\n")
57 return res
58
59 class ESC[4;38;5;81mUUTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
60
61 def test_encode(self):
62 inp = io.BytesIO(plaintext)
63 out = io.BytesIO()
64 uu.encode(inp, out, "t1")
65 self.assertEqual(out.getvalue(), encodedtextwrapped(0o666, "t1"))
66 inp = io.BytesIO(plaintext)
67 out = io.BytesIO()
68 uu.encode(inp, out, "t1", 0o644)
69 self.assertEqual(out.getvalue(), encodedtextwrapped(0o644, "t1"))
70 inp = io.BytesIO(plaintext)
71 out = io.BytesIO()
72 uu.encode(inp, out, "t1", backtick=True)
73 self.assertEqual(out.getvalue(), encodedtextwrapped(0o666, "t1", True))
74 with self.assertRaises(TypeError):
75 uu.encode(inp, out, "t1", 0o644, True)
76
77 @os_helper.skip_unless_working_chmod
78 def test_decode(self):
79 for backtick in True, False:
80 inp = io.BytesIO(encodedtextwrapped(0o666, "t1", backtick=backtick))
81 out = io.BytesIO()
82 uu.decode(inp, out)
83 self.assertEqual(out.getvalue(), plaintext)
84 inp = io.BytesIO(
85 b"UUencoded files may contain many lines,\n" +
86 b"even some that have 'begin' in them.\n" +
87 encodedtextwrapped(0o666, "t1", backtick=backtick)
88 )
89 out = io.BytesIO()
90 uu.decode(inp, out)
91 self.assertEqual(out.getvalue(), plaintext)
92
93 def test_truncatedinput(self):
94 inp = io.BytesIO(b"begin 644 t1\n" + encodedtext)
95 out = io.BytesIO()
96 try:
97 uu.decode(inp, out)
98 self.fail("No exception raised")
99 except uu.Error as e:
100 self.assertEqual(str(e), "Truncated input file")
101
102 def test_missingbegin(self):
103 inp = io.BytesIO(b"")
104 out = io.BytesIO()
105 try:
106 uu.decode(inp, out)
107 self.fail("No exception raised")
108 except uu.Error as e:
109 self.assertEqual(str(e), "No valid begin line found in input file")
110
111 def test_garbage_padding(self):
112 # Issue #22406
113 encodedtext1 = (
114 b"begin 644 file\n"
115 # length 1; bits 001100 111111 111111 111111
116 b"\x21\x2C\x5F\x5F\x5F\n"
117 b"\x20\n"
118 b"end\n"
119 )
120 encodedtext2 = (
121 b"begin 644 file\n"
122 # length 1; bits 001100 111111 111111 111111
123 b"\x21\x2C\x5F\x5F\x5F\n"
124 b"\x60\n"
125 b"end\n"
126 )
127 plaintext = b"\x33" # 00110011
128
129 for encodedtext in encodedtext1, encodedtext2:
130 with self.subTest("uu.decode()"):
131 inp = io.BytesIO(encodedtext)
132 out = io.BytesIO()
133 uu.decode(inp, out, quiet=True)
134 self.assertEqual(out.getvalue(), plaintext)
135
136 with self.subTest("uu_codec"):
137 import codecs
138 decoded = codecs.decode(encodedtext, "uu_codec")
139 self.assertEqual(decoded, plaintext)
140
141 def test_newlines_escaped(self):
142 # Test newlines are escaped with uu.encode
143 inp = io.BytesIO(plaintext)
144 out = io.BytesIO()
145 filename = "test.txt\n\roverflow.txt"
146 safefilename = b"test.txt\\n\\roverflow.txt"
147 uu.encode(inp, out, filename)
148 self.assertIn(safefilename, out.getvalue())
149
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
178 class ESC[4;38;5;81mUUStdIOTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
179
180 def setUp(self):
181 self.stdin = sys.stdin
182 self.stdout = sys.stdout
183
184 def tearDown(self):
185 sys.stdin = self.stdin
186 sys.stdout = self.stdout
187
188 def test_encode(self):
189 sys.stdin = FakeIO(plaintext.decode("ascii"))
190 sys.stdout = FakeIO()
191 uu.encode("-", "-", "t1", 0o666)
192 self.assertEqual(sys.stdout.getvalue(),
193 encodedtextwrapped(0o666, "t1").decode("ascii"))
194
195 def test_decode(self):
196 sys.stdin = FakeIO(encodedtextwrapped(0o666, "t1").decode("ascii"))
197 sys.stdout = FakeIO()
198 uu.decode("-", "-")
199 stdout = sys.stdout
200 sys.stdout = self.stdout
201 sys.stdin = self.stdin
202 self.assertEqual(stdout.getvalue(), plaintext.decode("ascii"))
203
204 class ESC[4;38;5;81mUUFileTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
205
206 def setUp(self):
207 # uu.encode() supports only ASCII file names
208 self.tmpin = os_helper.TESTFN_ASCII + "i"
209 self.tmpout = os_helper.TESTFN_ASCII + "o"
210 self.addCleanup(os_helper.unlink, self.tmpin)
211 self.addCleanup(os_helper.unlink, self.tmpout)
212
213 def test_encode(self):
214 with open(self.tmpin, 'wb') as fin:
215 fin.write(plaintext)
216
217 with open(self.tmpin, 'rb') as fin:
218 with open(self.tmpout, 'wb') as fout:
219 uu.encode(fin, fout, self.tmpin, mode=0o644)
220
221 with open(self.tmpout, 'rb') as fout:
222 s = fout.read()
223 self.assertEqual(s, encodedtextwrapped(0o644, self.tmpin))
224
225 # in_file and out_file as filenames
226 uu.encode(self.tmpin, self.tmpout, self.tmpin, mode=0o644)
227 with open(self.tmpout, 'rb') as fout:
228 s = fout.read()
229 self.assertEqual(s, encodedtextwrapped(0o644, self.tmpin))
230
231 # decode() calls chmod()
232 @os_helper.skip_unless_working_chmod
233 def test_decode(self):
234 with open(self.tmpin, 'wb') as f:
235 f.write(encodedtextwrapped(0o644, self.tmpout))
236
237 with open(self.tmpin, 'rb') as f:
238 uu.decode(f)
239
240 with open(self.tmpout, 'rb') as f:
241 s = f.read()
242 self.assertEqual(s, plaintext)
243 # XXX is there an xp way to verify the mode?
244
245 @os_helper.skip_unless_working_chmod
246 def test_decode_filename(self):
247 with open(self.tmpin, 'wb') as f:
248 f.write(encodedtextwrapped(0o644, self.tmpout))
249
250 uu.decode(self.tmpin)
251
252 with open(self.tmpout, 'rb') as f:
253 s = f.read()
254 self.assertEqual(s, plaintext)
255
256 @os_helper.skip_unless_working_chmod
257 def test_decodetwice(self):
258 # Verify that decode() will refuse to overwrite an existing file
259 with open(self.tmpin, 'wb') as f:
260 f.write(encodedtextwrapped(0o644, self.tmpout))
261 with open(self.tmpin, 'rb') as f:
262 uu.decode(f)
263
264 with open(self.tmpin, 'rb') as f:
265 self.assertRaises(uu.Error, uu.decode, f)
266
267 @os_helper.skip_unless_working_chmod
268 def test_decode_mode(self):
269 # Verify that decode() will set the given mode for the out_file
270 expected_mode = 0o444
271 with open(self.tmpin, 'wb') as f:
272 f.write(encodedtextwrapped(expected_mode, self.tmpout))
273
274 # make file writable again, so it can be removed (Windows only)
275 self.addCleanup(os.chmod, self.tmpout, expected_mode | stat.S_IWRITE)
276
277 with open(self.tmpin, 'rb') as f:
278 uu.decode(f)
279
280 self.assertEqual(
281 stat.S_IMODE(os.stat(self.tmpout).st_mode),
282 expected_mode
283 )
284
285
286 if __name__=="__main__":
287 unittest.main()