Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-106242: Make ntpath.realpath errors consistent with abspath when there are embedded nulls #108248

Merged
merged 2 commits into from Aug 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 12 additions & 0 deletions Lib/ntpath.py
Expand Up @@ -721,6 +721,14 @@ def realpath(path, *, strict=False):
try:
path = _getfinalpathname(path)
initial_winerror = 0
except ValueError as ex:
# gh-106242: Raised for embedded null characters
# In strict mode, we convert into an OSError.
# Non-strict mode returns the path as-is, since we've already
# made it absolute.
if strict:
raise OSError(str(ex)) from None
path = normpath(path)
except OSError as ex:
if strict:
raise
Expand All @@ -740,6 +748,10 @@ def realpath(path, *, strict=False):
try:
if _getfinalpathname(spath) == path:
path = spath
except ValueError as ex:
# Unexpected, as an invalid path should not have gained a prefix
# at any point, but we ignore this error just in case.
pass
except OSError as ex:
# If the path does not exist and originally did not exist, then
# strip the prefix anyway.
Expand Down
6 changes: 6 additions & 0 deletions Lib/test/test_ntpath.py
Expand Up @@ -394,6 +394,10 @@ def test_realpath_basic(self):
d = drives.pop().encode()
self.assertEqual(ntpath.realpath(d), d)

# gh-106242: Embedded nulls and non-strict fallback to abspath
self.assertEqual(ABSTFN + "\0spam",
ntpath.realpath(os_helper.TESTFN + "\0spam", strict=False))

@os_helper.skip_unless_symlink
@unittest.skipUnless(HAVE_GETFINALPATHNAME, 'need _getfinalpathname')
def test_realpath_strict(self):
Expand All @@ -404,6 +408,8 @@ def test_realpath_strict(self):
self.addCleanup(os_helper.unlink, ABSTFN)
self.assertRaises(FileNotFoundError, ntpath.realpath, ABSTFN, strict=True)
self.assertRaises(FileNotFoundError, ntpath.realpath, ABSTFN + "2", strict=True)
# gh-106242: Embedded nulls should raise OSError (not ValueError)
self.assertRaises(OSError, ntpath.realpath, ABSTFN + "\0spam", strict=True)

@os_helper.skip_unless_symlink
@unittest.skipUnless(HAVE_GETFINALPATHNAME, 'need _getfinalpathname')
Expand Down
@@ -0,0 +1,4 @@
Fixes :func:`~os.path.realpath` to behave consistently when passed a path
containing an embedded null character on Windows. In strict mode, it now
raises :exc:`OSError` instead of the unexpected :exc:`ValueError`, and in
non-strict mode will make the path absolute.