Skip to content

Commit 871d269

Browse files
gh-117596: Add more tests for os.path with invalid paths (GH-134189)
1 parent e79f640 commit 871d269

File tree

2 files changed

+243
-10
lines changed

2 files changed

+243
-10
lines changed

Lib/test/test_ntpath.py

Lines changed: 169 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,22 @@ def test_splitdrive(self):
124124
tester('ntpath.splitdrive("//?/UNC/server/share/dir")',
125125
("//?/UNC/server/share", "/dir"))
126126

127+
def test_splitdrive_invalid_paths(self):
128+
splitdrive = ntpath.splitdrive
129+
self.assertEqual(splitdrive('\\\\ser\x00ver\\sha\x00re\\di\x00r'),
130+
('\\\\ser\x00ver\\sha\x00re', '\\di\x00r'))
131+
self.assertEqual(splitdrive(b'\\\\ser\x00ver\\sha\x00re\\di\x00r'),
132+
(b'\\\\ser\x00ver\\sha\x00re', b'\\di\x00r'))
133+
self.assertEqual(splitdrive("\\\\\udfff\\\udffe\\\udffd"),
134+
('\\\\\udfff\\\udffe', '\\\udffd'))
135+
if sys.platform == 'win32':
136+
self.assertRaises(UnicodeDecodeError, splitdrive, b'\\\\\xff\\share\\dir')
137+
self.assertRaises(UnicodeDecodeError, splitdrive, b'\\\\server\\\xff\\dir')
138+
self.assertRaises(UnicodeDecodeError, splitdrive, b'\\\\server\\share\\\xff')
139+
else:
140+
self.assertEqual(splitdrive(b'\\\\\xff\\\xfe\\\xfd'),
141+
(b'\\\\\xff\\\xfe', b'\\\xfd'))
142+
127143
def test_splitroot(self):
128144
tester("ntpath.splitroot('')", ('', '', ''))
129145
tester("ntpath.splitroot('foo')", ('', '', 'foo'))
@@ -214,6 +230,22 @@ def test_splitroot(self):
214230
tester('ntpath.splitroot(" :/foo")', (" :", "/", "foo"))
215231
tester('ntpath.splitroot("/:/foo")', ("", "/", ":/foo"))
216232

233+
def test_splitroot_invalid_paths(self):
234+
splitroot = ntpath.splitroot
235+
self.assertEqual(splitroot('\\\\ser\x00ver\\sha\x00re\\di\x00r'),
236+
('\\\\ser\x00ver\\sha\x00re', '\\', 'di\x00r'))
237+
self.assertEqual(splitroot(b'\\\\ser\x00ver\\sha\x00re\\di\x00r'),
238+
(b'\\\\ser\x00ver\\sha\x00re', b'\\', b'di\x00r'))
239+
self.assertEqual(splitroot("\\\\\udfff\\\udffe\\\udffd"),
240+
('\\\\\udfff\\\udffe', '\\', '\udffd'))
241+
if sys.platform == 'win32':
242+
self.assertRaises(UnicodeDecodeError, splitroot, b'\\\\\xff\\share\\dir')
243+
self.assertRaises(UnicodeDecodeError, splitroot, b'\\\\server\\\xff\\dir')
244+
self.assertRaises(UnicodeDecodeError, splitroot, b'\\\\server\\share\\\xff')
245+
else:
246+
self.assertEqual(splitroot(b'\\\\\xff\\\xfe\\\xfd'),
247+
(b'\\\\\xff\\\xfe', b'\\', b'\xfd'))
248+
217249
def test_split(self):
218250
tester('ntpath.split("c:\\foo\\bar")', ('c:\\foo', 'bar'))
219251
tester('ntpath.split("\\\\conky\\mountpoint\\foo\\bar")',
@@ -226,6 +258,21 @@ def test_split(self):
226258
tester('ntpath.split("c:/")', ('c:/', ''))
227259
tester('ntpath.split("//conky/mountpoint/")', ('//conky/mountpoint/', ''))
228260

261+
def test_split_invalid_paths(self):
262+
split = ntpath.split
263+
self.assertEqual(split('c:\\fo\x00o\\ba\x00r'),
264+
('c:\\fo\x00o', 'ba\x00r'))
265+
self.assertEqual(split(b'c:\\fo\x00o\\ba\x00r'),
266+
(b'c:\\fo\x00o', b'ba\x00r'))
267+
self.assertEqual(split('c:\\\udfff\\\udffe'),
268+
('c:\\\udfff', '\udffe'))
269+
if sys.platform == 'win32':
270+
self.assertRaises(UnicodeDecodeError, split, b'c:\\\xff\\bar')
271+
self.assertRaises(UnicodeDecodeError, split, b'c:\\foo\\\xff')
272+
else:
273+
self.assertEqual(split(b'c:\\\xff\\\xfe'),
274+
(b'c:\\\xff', b'\xfe'))
275+
229276
def test_isabs(self):
230277
tester('ntpath.isabs("foo\\bar")', 0)
231278
tester('ntpath.isabs("foo/bar")', 0)
@@ -333,6 +380,30 @@ def test_join(self):
333380
tester("ntpath.join('D:a', './c:b')", 'D:a\\.\\c:b')
334381
tester("ntpath.join('D:/a', './c:b')", 'D:\\a\\.\\c:b')
335382

383+
def test_normcase(self):
384+
normcase = ntpath.normcase
385+
self.assertEqual(normcase(''), '')
386+
self.assertEqual(normcase(b''), b'')
387+
self.assertEqual(normcase('ABC'), 'abc')
388+
self.assertEqual(normcase(b'ABC'), b'abc')
389+
self.assertEqual(normcase('\xc4\u0141\u03a8'), '\xe4\u0142\u03c8')
390+
expected = '\u03c9\u2126' if sys.platform == 'win32' else '\u03c9\u03c9'
391+
self.assertEqual(normcase('\u03a9\u2126'), expected)
392+
if sys.platform == 'win32' or sys.getfilesystemencoding() == 'utf-8':
393+
self.assertEqual(normcase('\xc4\u0141\u03a8'.encode()),
394+
'\xe4\u0142\u03c8'.encode())
395+
self.assertEqual(normcase('\u03a9\u2126'.encode()),
396+
expected.encode())
397+
398+
def test_normcase_invalid_paths(self):
399+
normcase = ntpath.normcase
400+
self.assertEqual(normcase('abc\x00def'), 'abc\x00def')
401+
self.assertEqual(normcase(b'abc\x00def'), b'abc\x00def')
402+
self.assertEqual(normcase('\udfff'), '\udfff')
403+
if sys.platform == 'win32':
404+
path = b'ABC' + bytes(range(128, 256))
405+
self.assertEqual(normcase(path), path.lower())
406+
336407
def test_normpath(self):
337408
tester("ntpath.normpath('A//////././//.//B')", r'A\B')
338409
tester("ntpath.normpath('A/./B')", r'A\B')
@@ -381,6 +452,21 @@ def test_normpath(self):
381452
tester("ntpath.normpath('\\\\')", '\\\\')
382453
tester("ntpath.normpath('//?/UNC/server/share/..')", '\\\\?\\UNC\\server\\share\\')
383454

455+
def test_normpath_invalid_paths(self):
456+
normpath = ntpath.normpath
457+
self.assertEqual(normpath('fo\x00o'), 'fo\x00o')
458+
self.assertEqual(normpath(b'fo\x00o'), b'fo\x00o')
459+
self.assertEqual(normpath('fo\x00o\\..\\bar'), 'bar')
460+
self.assertEqual(normpath(b'fo\x00o\\..\\bar'), b'bar')
461+
self.assertEqual(normpath('\udfff'), '\udfff')
462+
self.assertEqual(normpath('\udfff\\..\\foo'), 'foo')
463+
if sys.platform == 'win32':
464+
self.assertRaises(UnicodeDecodeError, normpath, b'\xff')
465+
self.assertRaises(UnicodeDecodeError, normpath, b'\xff\\..\\foo')
466+
else:
467+
self.assertEqual(normpath(b'\xff'), b'\xff')
468+
self.assertEqual(normpath(b'\xff\\..\\foo'), b'foo')
469+
384470
def test_realpath_curdir(self):
385471
expected = ntpath.normpath(os.getcwd())
386472
tester("ntpath.realpath('.')", expected)
@@ -420,10 +506,6 @@ def test_realpath_basic(self):
420506
d = drives.pop().encode()
421507
self.assertEqual(ntpath.realpath(d), d)
422508

423-
# gh-106242: Embedded nulls and non-strict fallback to abspath
424-
self.assertEqual(ABSTFN + "\0spam",
425-
ntpath.realpath(os_helper.TESTFN + "\0spam", strict=False))
426-
427509
@os_helper.skip_unless_symlink
428510
@unittest.skipUnless(HAVE_GETFINALPATHNAME, 'need _getfinalpathname')
429511
def test_realpath_strict(self):
@@ -434,8 +516,51 @@ def test_realpath_strict(self):
434516
self.addCleanup(os_helper.unlink, ABSTFN)
435517
self.assertRaises(FileNotFoundError, ntpath.realpath, ABSTFN, strict=True)
436518
self.assertRaises(FileNotFoundError, ntpath.realpath, ABSTFN + "2", strict=True)
519+
520+
@unittest.skipUnless(HAVE_GETFINALPATHNAME, 'need _getfinalpathname')
521+
def test_realpath_invalid_paths(self):
522+
realpath = ntpath.realpath
523+
ABSTFN = ntpath.abspath(os_helper.TESTFN)
524+
ABSTFNb = os.fsencode(ABSTFN)
525+
path = ABSTFN + '\x00'
526+
# gh-106242: Embedded nulls and non-strict fallback to abspath
527+
self.assertEqual(realpath(path, strict=False), path)
437528
# gh-106242: Embedded nulls should raise OSError (not ValueError)
438-
self.assertRaises(OSError, ntpath.realpath, ABSTFN + "\0spam", strict=True)
529+
self.assertRaises(OSError, realpath, path, strict=True)
530+
path = ABSTFNb + b'\x00'
531+
self.assertEqual(realpath(path, strict=False), path)
532+
self.assertRaises(OSError, realpath, path, strict=True)
533+
path = ABSTFN + '\\nonexistent\\x\x00'
534+
self.assertEqual(realpath(path, strict=False), path)
535+
self.assertRaises(OSError, realpath, path, strict=True)
536+
path = ABSTFNb + b'\\nonexistent\\x\x00'
537+
self.assertEqual(realpath(path, strict=False), path)
538+
self.assertRaises(OSError, realpath, path, strict=True)
539+
path = ABSTFN + '\x00\\..'
540+
self.assertEqual(realpath(path, strict=False), os.getcwd())
541+
self.assertEqual(realpath(path, strict=True), os.getcwd())
542+
path = ABSTFNb + b'\x00\\..'
543+
self.assertEqual(realpath(path, strict=False), os.getcwdb())
544+
self.assertEqual(realpath(path, strict=True), os.getcwdb())
545+
path = ABSTFN + '\\nonexistent\\x\x00\\..'
546+
self.assertEqual(realpath(path, strict=False), ABSTFN + '\\nonexistent')
547+
self.assertRaises(OSError, realpath, path, strict=True)
548+
path = ABSTFNb + b'\\nonexistent\\x\x00\\..'
549+
self.assertEqual(realpath(path, strict=False), ABSTFNb + b'\\nonexistent')
550+
self.assertRaises(OSError, realpath, path, strict=True)
551+
552+
path = ABSTFNb + b'\xff'
553+
self.assertRaises(UnicodeDecodeError, realpath, path, strict=False)
554+
self.assertRaises(UnicodeDecodeError, realpath, path, strict=True)
555+
path = ABSTFNb + b'\\nonexistent\\\xff'
556+
self.assertRaises(UnicodeDecodeError, realpath, path, strict=False)
557+
self.assertRaises(UnicodeDecodeError, realpath, path, strict=True)
558+
path = ABSTFNb + b'\xff\\..'
559+
self.assertRaises(UnicodeDecodeError, realpath, path, strict=False)
560+
self.assertRaises(UnicodeDecodeError, realpath, path, strict=True)
561+
path = ABSTFNb + b'\\nonexistent\\\xff\\..'
562+
self.assertRaises(UnicodeDecodeError, realpath, path, strict=False)
563+
self.assertRaises(UnicodeDecodeError, realpath, path, strict=True)
439564

440565
@os_helper.skip_unless_symlink
441566
@unittest.skipUnless(HAVE_GETFINALPATHNAME, 'need _getfinalpathname')
@@ -812,8 +937,6 @@ def test_abspath(self):
812937
tester('ntpath.abspath("C:/nul")', "\\\\.\\nul")
813938
tester('ntpath.abspath("C:\\nul")', "\\\\.\\nul")
814939
self.assertTrue(ntpath.isabs(ntpath.abspath("C:spam")))
815-
self.assertEqual(ntpath.abspath("C:\x00"), ntpath.join(ntpath.abspath("C:"), "\x00"))
816-
self.assertEqual(ntpath.abspath("\x00:spam"), "\x00:\\spam")
817940
tester('ntpath.abspath("//..")', "\\\\")
818941
tester('ntpath.abspath("//../")', "\\\\..\\")
819942
tester('ntpath.abspath("//../..")', "\\\\..\\")
@@ -847,6 +970,26 @@ def test_abspath(self):
847970
drive, _ = ntpath.splitdrive(cwd_dir)
848971
tester('ntpath.abspath("/abc/")', drive + "\\abc")
849972

973+
def test_abspath_invalid_paths(self):
974+
abspath = ntpath.abspath
975+
if sys.platform == 'win32':
976+
self.assertEqual(abspath("C:\x00"), ntpath.join(abspath("C:"), "\x00"))
977+
self.assertEqual(abspath(b"C:\x00"), ntpath.join(abspath(b"C:"), b"\x00"))
978+
self.assertEqual(abspath("\x00:spam"), "\x00:\\spam")
979+
self.assertEqual(abspath(b"\x00:spam"), b"\x00:\\spam")
980+
self.assertEqual(abspath('c:\\fo\x00o'), 'c:\\fo\x00o')
981+
self.assertEqual(abspath(b'c:\\fo\x00o'), b'c:\\fo\x00o')
982+
self.assertEqual(abspath('c:\\fo\x00o\\..\\bar'), 'c:\\bar')
983+
self.assertEqual(abspath(b'c:\\fo\x00o\\..\\bar'), b'c:\\bar')
984+
self.assertEqual(abspath('c:\\\udfff'), 'c:\\\udfff')
985+
self.assertEqual(abspath('c:\\\udfff\\..\\foo'), 'c:\\foo')
986+
if sys.platform == 'win32':
987+
self.assertRaises(UnicodeDecodeError, abspath, b'c:\\\xff')
988+
self.assertRaises(UnicodeDecodeError, abspath, b'c:\\\xff\\..\\foo')
989+
else:
990+
self.assertEqual(abspath(b'c:\\\xff'), b'c:\\\xff')
991+
self.assertEqual(abspath(b'c:\\\xff\\..\\foo'), b'c:\\foo')
992+
850993
def test_relpath(self):
851994
tester('ntpath.relpath("a")', 'a')
852995
tester('ntpath.relpath(ntpath.abspath("a"))', 'a')
@@ -989,6 +1132,18 @@ def test_ismount(self):
9891132
self.assertTrue(ntpath.ismount(b"\\\\localhost\\c$"))
9901133
self.assertTrue(ntpath.ismount(b"\\\\localhost\\c$\\"))
9911134

1135+
def test_ismount_invalid_paths(self):
1136+
ismount = ntpath.ismount
1137+
self.assertFalse(ismount("c:\\\udfff"))
1138+
if sys.platform == 'win32':
1139+
self.assertRaises(ValueError, ismount, "c:\\\x00")
1140+
self.assertRaises(ValueError, ismount, b"c:\\\x00")
1141+
self.assertRaises(UnicodeDecodeError, ismount, b"c:\\\xff")
1142+
else:
1143+
self.assertFalse(ismount("c:\\\x00"))
1144+
self.assertFalse(ismount(b"c:\\\x00"))
1145+
self.assertFalse(ismount(b"c:\\\xff"))
1146+
9921147
def test_isreserved(self):
9931148
self.assertFalse(ntpath.isreserved(''))
9941149
self.assertFalse(ntpath.isreserved('.'))
@@ -1095,6 +1250,13 @@ def test_isjunction(self):
10951250
self.assertFalse(ntpath.isjunction('tmpdir'))
10961251
self.assertPathEqual(ntpath.realpath('testjunc'), ntpath.realpath('tmpdir'))
10971252

1253+
def test_isfile_invalid_paths(self):
1254+
isfile = ntpath.isfile
1255+
self.assertIs(isfile('/tmp\udfffabcds'), False)
1256+
self.assertIs(isfile(b'/tmp\xffabcds'), False)
1257+
self.assertIs(isfile('/tmp\x00abcds'), False)
1258+
self.assertIs(isfile(b'/tmp\x00abcds'), False)
1259+
10981260
@unittest.skipIf(sys.platform != 'win32', "drive letters are a windows concept")
10991261
def test_isfile_driveletter(self):
11001262
drive = os.environ.get('SystemDrive')
@@ -1195,9 +1357,6 @@ def _check_function(self, func):
11951357

11961358
def test_path_normcase(self):
11971359
self._check_function(self.path.normcase)
1198-
if sys.platform == 'win32':
1199-
self.assertEqual(ntpath.normcase('\u03a9\u2126'), 'ωΩ')
1200-
self.assertEqual(ntpath.normcase('abc\x00def'), 'abc\x00def')
12011360

12021361
def test_path_isabs(self):
12031362
self._check_function(self.path.isabs)

Lib/test/test_posixpath.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ def test_ismount_non_existent(self):
229229
finally:
230230
safe_rmdir(ABSTFN)
231231

232+
def test_ismount_invalid_paths(self):
232233
self.assertIs(posixpath.ismount('/\udfff'), False)
233234
self.assertIs(posixpath.ismount(b'/\xff'), False)
234235
self.assertIs(posixpath.ismount('/\x00'), False)
@@ -489,6 +490,79 @@ def test_realpath_strict(self):
489490
finally:
490491
os_helper.unlink(ABSTFN)
491492

493+
def test_realpath_invalid_paths(self):
494+
path = '/\x00'
495+
self.assertRaises(ValueError, realpath, path, strict=False)
496+
self.assertRaises(ValueError, realpath, path, strict=True)
497+
path = b'/\x00'
498+
self.assertRaises(ValueError, realpath, path, strict=False)
499+
self.assertRaises(ValueError, realpath, path, strict=True)
500+
path = '/nonexistent/x\x00'
501+
self.assertRaises(ValueError, realpath, path, strict=False)
502+
self.assertRaises(FileNotFoundError, realpath, path, strict=True)
503+
path = b'/nonexistent/x\x00'
504+
self.assertRaises(ValueError, realpath, path, strict=False)
505+
self.assertRaises(FileNotFoundError, realpath, path, strict=True)
506+
path = '/\x00/..'
507+
self.assertRaises(ValueError, realpath, path, strict=False)
508+
self.assertRaises(ValueError, realpath, path, strict=True)
509+
path = b'/\x00/..'
510+
self.assertRaises(ValueError, realpath, path, strict=False)
511+
self.assertRaises(ValueError, realpath, path, strict=True)
512+
path = '/nonexistent/x\x00/..'
513+
self.assertRaises(ValueError, realpath, path, strict=False)
514+
self.assertRaises(FileNotFoundError, realpath, path, strict=True)
515+
path = b'/nonexistent/x\x00/..'
516+
self.assertRaises(ValueError, realpath, path, strict=False)
517+
self.assertRaises(FileNotFoundError, realpath, path, strict=True)
518+
519+
path = '/\udfff'
520+
if sys.platform == 'win32':
521+
self.assertEqual(realpath(path, strict=False), path)
522+
self.assertRaises(FileNotFoundError, realpath, path, strict=True)
523+
else:
524+
self.assertRaises(UnicodeEncodeError, realpath, path, strict=False)
525+
self.assertRaises(UnicodeEncodeError, realpath, path, strict=True)
526+
path = '/nonexistent/\udfff'
527+
if sys.platform == 'win32':
528+
self.assertEqual(realpath(path, strict=False), path)
529+
else:
530+
self.assertRaises(UnicodeEncodeError, realpath, path, strict=False)
531+
self.assertRaises(FileNotFoundError, realpath, path, strict=True)
532+
path = '/\udfff/..'
533+
if sys.platform == 'win32':
534+
self.assertEqual(realpath(path, strict=False), '/')
535+
self.assertRaises(FileNotFoundError, realpath, path, strict=True)
536+
else:
537+
self.assertRaises(UnicodeEncodeError, realpath, path, strict=False)
538+
self.assertRaises(UnicodeEncodeError, realpath, path, strict=True)
539+
path = '/nonexistent/\udfff/..'
540+
if sys.platform == 'win32':
541+
self.assertEqual(realpath(path, strict=False), '/nonexistent')
542+
else:
543+
self.assertRaises(UnicodeEncodeError, realpath, path, strict=False)
544+
self.assertRaises(FileNotFoundError, realpath, path, strict=True)
545+
546+
path = b'/\xff'
547+
if sys.platform == 'win32':
548+
self.assertRaises(UnicodeDecodeError, realpath, path, strict=False)
549+
self.assertRaises(UnicodeDecodeError, realpath, path, strict=True)
550+
else:
551+
self.assertEqual(realpath(path, strict=False), path)
552+
if support.is_wasi:
553+
self.assertRaises(OSError, realpath, path, strict=True)
554+
else:
555+
self.assertRaises(FileNotFoundError, realpath, path, strict=True)
556+
path = b'/nonexistent/\xff'
557+
if sys.platform == 'win32':
558+
self.assertRaises(UnicodeDecodeError, realpath, path, strict=False)
559+
else:
560+
self.assertEqual(realpath(path, strict=False), path)
561+
if support.is_wasi:
562+
self.assertRaises(OSError, realpath, path, strict=True)
563+
else:
564+
self.assertRaises(FileNotFoundError, realpath, path, strict=True)
565+
492566
@os_helper.skip_unless_symlink
493567
@skip_if_ABSTFN_contains_backslash
494568
def test_realpath_relative(self):

0 commit comments

Comments
 (0)