1""" 2Tests for uu module. 3Nick Mathewson 4""" 5 6import unittest 7from test.support import os_helper, warnings_helper 8 9uu = warnings_helper.import_deprecated("uu") 10 11import os 12import stat 13import sys 14import io 15 16plaintext = b"The symbols on top of your keyboard are !@#$%^&*()_+|~\n" 17 18encodedtext = b"""\ 19M5&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 23class FakeIO(io.TextIOWrapper): 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 50def 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 59class UUTest(unittest.TestCase): 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"""\ 152begin 644 ../../../../../../../../tmp/test1 153$86)C"@`` 154` 155end 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"""\ 165begin 644 /tmp/test2 166$86)C"@`` 167` 168end 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 178class UUStdIOTest(unittest.TestCase): 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 204class UUFileTest(unittest.TestCase): 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 286if __name__=="__main__": 287 unittest.main() 288