bpo-9949: Enable symlink traversal for ntpath.realpath (GH-15287) · python/cpython@c30c869 (original) (raw)
`@@ -7,13 +7,22 @@
`
7
7
`from test import support, test_genericpath
`
8
8
`from tempfile import TemporaryFile
`
9
9
``
``
10
+
10
11
`try:
`
11
12
`import nt
`
12
13
`except ImportError:
`
13
14
`# Most tests can complete without the nt module,
`
14
15
`# but for those that require it we import here.
`
15
16
`nt = None
`
16
17
``
``
18
`+
try:
`
``
19
`+
ntpath._getfinalpathname
`
``
20
`+
except AttributeError:
`
``
21
`+
HAVE_GETFINALPATHNAME = False
`
``
22
`+
else:
`
``
23
`+
HAVE_GETFINALPATHNAME = True
`
``
24
+
``
25
+
17
26
`def tester(fn, wantResult):
`
18
27
`fn = fn.replace("\", "\\")
`
19
28
`gotResult = eval(fn)
`
`@@ -194,6 +203,189 @@ def test_normpath(self):
`
194
203
`tester("ntpath.normpath('\\.\NUL')", r'\.\NUL')
`
195
204
`tester("ntpath.normpath('\\?\D:/XY\Z')", r'\?\D:/XY\Z')
`
196
205
``
``
206
`+
def test_realpath_curdir(self):
`
``
207
`+
expected = ntpath.normpath(os.getcwd())
`
``
208
`+
tester("ntpath.realpath('.')", expected)
`
``
209
`+
tester("ntpath.realpath('./.')", expected)
`
``
210
`+
tester("ntpath.realpath('/'.join(['.'] * 100))", expected)
`
``
211
`+
tester("ntpath.realpath('.\.')", expected)
`
``
212
`+
tester("ntpath.realpath('\'.join(['.'] * 100))", expected)
`
``
213
+
``
214
`+
def test_realpath_pardir(self):
`
``
215
`+
expected = ntpath.normpath(os.getcwd())
`
``
216
`+
tester("ntpath.realpath('..')", ntpath.dirname(expected))
`
``
217
`+
tester("ntpath.realpath('../..')",
`
``
218
`+
ntpath.dirname(ntpath.dirname(expected)))
`
``
219
`+
tester("ntpath.realpath('/'.join(['..'] * 50))",
`
``
220
`+
ntpath.splitdrive(expected)[0] + '\')
`
``
221
`+
tester("ntpath.realpath('..\..')",
`
``
222
`+
ntpath.dirname(ntpath.dirname(expected)))
`
``
223
`+
tester("ntpath.realpath('\'.join(['..'] * 50))",
`
``
224
`+
ntpath.splitdrive(expected)[0] + '\')
`
``
225
+
``
226
`+
@support.skip_unless_symlink
`
``
227
`+
@unittest.skipUnless(HAVE_GETFINALPATHNAME, 'need _getfinalpathname')
`
``
228
`+
def test_realpath_basic(self):
`
``
229
`+
ABSTFN = ntpath.abspath(support.TESTFN)
`
``
230
`+
open(ABSTFN, "wb").close()
`
``
231
`+
self.addCleanup(support.unlink, ABSTFN)
`
``
232
`+
self.addCleanup(support.unlink, ABSTFN + "1")
`
``
233
+
``
234
`+
os.symlink(ABSTFN, ABSTFN + "1")
`
``
235
`+
self.assertEqual(ntpath.realpath(ABSTFN + "1"), ABSTFN)
`
``
236
`+
self.assertEqual(ntpath.realpath(os.fsencode(ABSTFN + "1")),
`
``
237
`+
os.fsencode(ABSTFN))
`
``
238
+
``
239
`+
@support.skip_unless_symlink
`
``
240
`+
@unittest.skipUnless(HAVE_GETFINALPATHNAME, 'need _getfinalpathname')
`
``
241
`+
def test_realpath_relative(self):
`
``
242
`+
ABSTFN = ntpath.abspath(support.TESTFN)
`
``
243
`+
open(ABSTFN, "wb").close()
`
``
244
`+
self.addCleanup(support.unlink, ABSTFN)
`
``
245
`+
self.addCleanup(support.unlink, ABSTFN + "1")
`
``
246
+
``
247
`+
os.symlink(ABSTFN, ntpath.relpath(ABSTFN + "1"))
`
``
248
`+
self.assertEqual(ntpath.realpath(ABSTFN + "1"), ABSTFN)
`
``
249
+
``
250
`+
@support.skip_unless_symlink
`
``
251
`+
@unittest.skipUnless(HAVE_GETFINALPATHNAME, 'need _getfinalpathname')
`
``
252
`+
def test_realpath_broken_symlinks(self):
`
``
253
`+
ABSTFN = ntpath.abspath(support.TESTFN)
`
``
254
`+
os.mkdir(ABSTFN)
`
``
255
`+
self.addCleanup(support.rmtree, ABSTFN)
`
``
256
+
``
257
`+
with support.change_cwd(ABSTFN):
`
``
258
`+
os.mkdir("subdir")
`
``
259
`+
os.chdir("subdir")
`
``
260
`+
os.symlink(".", "recursive")
`
``
261
`+
os.symlink("..", "parent")
`
``
262
`+
os.chdir("..")
`
``
263
`+
os.symlink(".", "self")
`
``
264
`+
os.symlink("missing", "broken")
`
``
265
`+
os.symlink(r"broken\bar", "broken1")
`
``
266
`+
os.symlink(r"self\self\broken", "broken2")
`
``
267
`+
os.symlink(r"subdir\parent\subdir\parent\broken", "broken3")
`
``
268
`+
os.symlink(ABSTFN + r"\broken", "broken4")
`
``
269
`+
os.symlink(r"recursive..\broken", "broken5")
`
``
270
+
``
271
`+
self.assertEqual(ntpath.realpath("broken"),
`
``
272
`+
ABSTFN + r"\missing")
`
``
273
`+
self.assertEqual(ntpath.realpath(r"broken\foo"),
`
``
274
`+
ABSTFN + r"\missing\foo")
`
``
275
`+
self.assertEqual(ntpath.realpath(r"broken1"),
`
``
276
`+
ABSTFN + r"\missing\bar")
`
``
277
`+
self.assertEqual(ntpath.realpath(r"broken1\baz"),
`
``
278
`+
ABSTFN + r"\missing\bar\baz")
`
``
279
`+
self.assertEqual(ntpath.realpath("broken2"),
`
``
280
`+
ABSTFN + r"\missing")
`
``
281
`+
self.assertEqual(ntpath.realpath("broken3"),
`
``
282
`+
ABSTFN + r"\missing")
`
``
283
`+
self.assertEqual(ntpath.realpath("broken4"),
`
``
284
`+
ABSTFN + r"\missing")
`
``
285
`+
self.assertEqual(ntpath.realpath("broken5"),
`
``
286
`+
ABSTFN + r"\missing")
`
``
287
+
``
288
`+
self.assertEqual(ntpath.realpath(b"broken"),
`
``
289
`+
os.fsencode(ABSTFN + r"\missing"))
`
``
290
`+
self.assertEqual(ntpath.realpath(rb"broken\foo"),
`
``
291
`+
os.fsencode(ABSTFN + r"\missing\foo"))
`
``
292
`+
self.assertEqual(ntpath.realpath(rb"broken1"),
`
``
293
`+
os.fsencode(ABSTFN + r"\missing\bar"))
`
``
294
`+
self.assertEqual(ntpath.realpath(rb"broken1\baz"),
`
``
295
`+
os.fsencode(ABSTFN + r"\missing\bar\baz"))
`
``
296
`+
self.assertEqual(ntpath.realpath(b"broken2"),
`
``
297
`+
os.fsencode(ABSTFN + r"\missing"))
`
``
298
`+
self.assertEqual(ntpath.realpath(rb"broken3"),
`
``
299
`+
os.fsencode(ABSTFN + r"\missing"))
`
``
300
`+
self.assertEqual(ntpath.realpath(b"broken4"),
`
``
301
`+
os.fsencode(ABSTFN + r"\missing"))
`
``
302
`+
self.assertEqual(ntpath.realpath(b"broken5"),
`
``
303
`+
os.fsencode(ABSTFN + r"\missing"))
`
``
304
+
``
305
`+
@support.skip_unless_symlink
`
``
306
`+
@unittest.skipUnless(HAVE_GETFINALPATHNAME, 'need _getfinalpathname')
`
``
307
`+
def test_realpath_symlink_loops(self):
`
``
308
`+
Bug #930024, return the path unchanged if we get into an infinite
`
``
309
`+
symlink loop.
`
``
310
`+
ABSTFN = ntpath.abspath(support.TESTFN)
`
``
311
`+
self.addCleanup(support.unlink, ABSTFN)
`
``
312
`+
self.addCleanup(support.unlink, ABSTFN + "1")
`
``
313
`+
self.addCleanup(support.unlink, ABSTFN + "2")
`
``
314
`+
self.addCleanup(support.unlink, ABSTFN + "y")
`
``
315
`+
self.addCleanup(support.unlink, ABSTFN + "c")
`
``
316
`+
self.addCleanup(support.unlink, ABSTFN + "a")
`
``
317
+
``
318
`+
P = "\\?\"
`
``
319
+
``
320
`+
os.symlink(ABSTFN, ABSTFN)
`
``
321
`+
self.assertEqual(ntpath.realpath(ABSTFN), P + ABSTFN)
`
``
322
+
``
323
`+
cycles are non-deterministic as to which path is returned, but
`
``
324
`+
it will always be the fully resolved path of one member of the cycle
`
``
325
`+
os.symlink(ABSTFN + "1", ABSTFN + "2")
`
``
326
`+
os.symlink(ABSTFN + "2", ABSTFN + "1")
`
``
327
`+
expected = (P + ABSTFN + "1", P + ABSTFN + "2")
`
``
328
`+
self.assertIn(ntpath.realpath(ABSTFN + "1"), expected)
`
``
329
`+
self.assertIn(ntpath.realpath(ABSTFN + "2"), expected)
`
``
330
+
``
331
`+
self.assertIn(ntpath.realpath(ABSTFN + "1\x"),
`
``
332
`+
(ntpath.join(r, "x") for r in expected))
`
``
333
`+
self.assertEqual(ntpath.realpath(ABSTFN + "1\.."),
`
``
334
`+
ntpath.dirname(ABSTFN))
`
``
335
`+
self.assertEqual(ntpath.realpath(ABSTFN + "1\..\x"),
`
``
336
`+
ntpath.dirname(P + ABSTFN) + "\x")
`
``
337
`+
os.symlink(ABSTFN + "x", ABSTFN + "y")
`
``
338
`+
self.assertEqual(ntpath.realpath(ABSTFN + "1\..\"
`
``
339
`+
- ntpath.basename(ABSTFN) + "y"),
`
``
340
`+
P + ABSTFN + "x")
`
``
341
`+
self.assertIn(ntpath.realpath(ABSTFN + "1\..\"
`
``
342
`+
- ntpath.basename(ABSTFN) + "1"),
`
``
343
`+
expected)
`
``
344
+
``
345
`+
os.symlink(ntpath.basename(ABSTFN) + "a\b", ABSTFN + "a")
`
``
346
`+
self.assertEqual(ntpath.realpath(ABSTFN + "a"), P + ABSTFN + "a")
`
``
347
+
``
348
`+
os.symlink("..\" + ntpath.basename(ntpath.dirname(ABSTFN))
`
``
349
`+
- "\" + ntpath.basename(ABSTFN) + "c", ABSTFN + "c")
`
``
350
`+
self.assertEqual(ntpath.realpath(ABSTFN + "c"), P + ABSTFN + "c")
`
``
351
+
``
352
`+
Test using relative path as well.
`
``
353
`+
self.assertEqual(ntpath.realpath(ntpath.basename(ABSTFN)), P + ABSTFN)
`
``
354
+
``
355
`+
@support.skip_unless_symlink
`
``
356
`+
@unittest.skipUnless(HAVE_GETFINALPATHNAME, 'need _getfinalpathname')
`
``
357
`+
def test_realpath_symlink_prefix(self):
`
``
358
`+
ABSTFN = ntpath.abspath(support.TESTFN)
`
``
359
`+
self.addCleanup(support.unlink, ABSTFN + "3")
`
``
360
`+
self.addCleanup(support.unlink, "\\?\" + ABSTFN + "3.")
`
``
361
`+
self.addCleanup(support.unlink, ABSTFN + "3link")
`
``
362
`+
self.addCleanup(support.unlink, ABSTFN + "3.link")
`
``
363
+
``
364
`+
with open(ABSTFN + "3", "wb") as f:
`
``
365
`+
f.write(b'0')
`
``
366
`+
os.symlink(ABSTFN + "3", ABSTFN + "3link")
`
``
367
+
``
368
`+
with open("\\?\" + ABSTFN + "3.", "wb") as f:
`
``
369
`+
f.write(b'1')
`
``
370
`+
os.symlink("\\?\" + ABSTFN + "3.", ABSTFN + "3.link")
`
``
371
+
``
372
`+
self.assertEqual(ntpath.realpath(ABSTFN + "3link"),
`
``
373
`+
ABSTFN + "3")
`
``
374
`+
self.assertEqual(ntpath.realpath(ABSTFN + "3.link"),
`
``
375
`+
"\\?\" + ABSTFN + "3.")
`
``
376
+
``
377
`+
Resolved paths should be usable to open target files
`
``
378
`+
with open(ntpath.realpath(ABSTFN + "3link"), "rb") as f:
`
``
379
`+
self.assertEqual(f.read(), b'0')
`
``
380
`+
with open(ntpath.realpath(ABSTFN + "3.link"), "rb") as f:
`
``
381
`+
self.assertEqual(f.read(), b'1')
`
``
382
+
``
383
`+
When the prefix is included, it is not stripped
`
``
384
`+
self.assertEqual(ntpath.realpath("\\?\" + ABSTFN + "3link"),
`
``
385
`+
"\\?\" + ABSTFN + "3")
`
``
386
`+
self.assertEqual(ntpath.realpath("\\?\" + ABSTFN + "3.link"),
`
``
387
`+
"\\?\" + ABSTFN + "3.")
`
``
388
+
197
389
`def test_expandvars(self):
`
198
390
`with support.EnvironmentVarGuard() as env:
`
199
391
`env.clear()
`
`@@ -288,11 +480,11 @@ def test_abspath(self):
`
288
480
``
289
481
`def test_relpath(self):
`
290
482
`tester('ntpath.relpath("a")', 'a')
`
291
``
`-
tester('ntpath.relpath(os.path.abspath("a"))', 'a')
`
``
483
`+
tester('ntpath.relpath(ntpath.abspath("a"))', 'a')
`
292
484
`tester('ntpath.relpath("a/b")', 'a\b')
`
293
485
`tester('ntpath.relpath("../a/b")', '..\a\b')
`
294
486
`with support.temp_cwd(support.TESTFN) as cwd_dir:
`
295
``
`-
currentdir = os.path.basename(cwd_dir)
`
``
487
`+
currentdir = ntpath.basename(cwd_dir)
`
296
488
`tester('ntpath.relpath("a", "../b")', '..\'+currentdir+'\a')
`
297
489
`tester('ntpath.relpath("a/b", "../c")', '..\'+currentdir+'\a\b')
`
298
490
`tester('ntpath.relpath("a", "b/c")', '..\..\a')
`
`@@ -417,7 +609,7 @@ def test_ismount(self):
`
417
609
`# locations below cannot then refer to mount points
`
418
610
`#
`
419
611
`drive, path = ntpath.splitdrive(sys.executable)
`
420
``
`-
with support.change_cwd(os.path.dirname(sys.executable)):
`
``
612
`+
with support.change_cwd(ntpath.dirname(sys.executable)):
`
421
613
`self.assertFalse(ntpath.ismount(drive.lower()))
`
422
614
`self.assertFalse(ntpath.ismount(drive.upper()))
`
423
615
``