Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions Lib/posixpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,10 @@ def _realpath(filename, strict=False, sep=sep, curdir=curdir, pardir=pardir,
# very fast way of spelling list(reversed(...)).
rest = filename.split(sep)[::-1]

# Number of unprocessed parts in 'rest'. This can differ from len(rest)
# later, because 'rest' might contain markers for unresolved symlinks.
part_count = len(rest)

# The resolved path, which is absolute throughout this function.
# Note: getcwd() returns a normalized and symlink-free path.
path = sep if filename.startswith(sep) else getcwd()
Expand All @@ -426,12 +430,13 @@ def _realpath(filename, strict=False, sep=sep, curdir=curdir, pardir=pardir,
# by *maxlinks*, this is used instead of *seen* to detect symlink loops.
link_count = 0

while rest:
while part_count:
name = rest.pop()
if name is None:
# resolved symlink target
seen[rest.pop()] = path
continue
part_count -= 1
if not name or name == curdir:
# current dir
continue
Expand All @@ -444,8 +449,11 @@ def _realpath(filename, strict=False, sep=sep, curdir=curdir, pardir=pardir,
else:
newpath = path + sep + name
try:
st = lstat(newpath)
if not stat.S_ISLNK(st.st_mode):
st_mode = lstat(newpath).st_mode
if not stat.S_ISLNK(st_mode):
if strict and part_count and not stat.S_ISDIR(st_mode):
raise OSError(errno.ENOTDIR, os.strerror(errno.ENOTDIR),
newpath)
path = newpath
continue
elif maxlinks is not None:
Expand Down Expand Up @@ -487,7 +495,9 @@ def _realpath(filename, strict=False, sep=sep, curdir=curdir, pardir=pardir,
rest.append(newpath)
rest.append(None)
# Push the unresolved symlink target parts onto the stack.
rest.extend(target.split(sep)[::-1])
target_parts = target.split(sep)[::-1]
rest.extend(target_parts)
part_count += len(target_parts)

return path

Expand Down
59 changes: 59 additions & 0 deletions Lib/test/test_posixpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,65 @@ def test_realpath_unreadable_symlink(self):
os.chmod(ABSTFN, 0o755, follow_symlinks=False)
os.unlink(ABSTFN)

@skip_if_ABSTFN_contains_backslash
def test_realpath_nonterminal_file(self):
try:
with open(ABSTFN, 'w') as f:
f.write('test_posixpath wuz ere')
self.assertEqual(realpath(ABSTFN, strict=False), ABSTFN)
self.assertEqual(realpath(ABSTFN, strict=True), ABSTFN)
self.assertEqual(realpath(ABSTFN + "/", strict=False), ABSTFN)
self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/", strict=True)
self.assertEqual(realpath(ABSTFN + "/.", strict=False), ABSTFN)
self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/.", strict=True)
self.assertEqual(realpath(ABSTFN + "/..", strict=False), dirname(ABSTFN))
self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/..", strict=True)
self.assertEqual(realpath(ABSTFN + "/subdir", strict=False), ABSTFN + "/subdir")
self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/subdir", strict=True)
finally:
os_helper.unlink(ABSTFN)

@os_helper.skip_unless_symlink
@skip_if_ABSTFN_contains_backslash
def test_realpath_nonterminal_symlink_to_file(self):
try:
with open(ABSTFN + "1", 'w') as f:
f.write('test_posixpath wuz ere')
os.symlink(ABSTFN + "1", ABSTFN)
self.assertEqual(realpath(ABSTFN, strict=False), ABSTFN + "1")
self.assertEqual(realpath(ABSTFN, strict=True), ABSTFN + "1")
self.assertEqual(realpath(ABSTFN + "/", strict=False), ABSTFN + "1")
self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/", strict=True)
self.assertEqual(realpath(ABSTFN + "/.", strict=False), ABSTFN + "1")
self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/.", strict=True)
self.assertEqual(realpath(ABSTFN + "/..", strict=False), dirname(ABSTFN))
self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/..", strict=True)
self.assertEqual(realpath(ABSTFN + "/subdir", strict=False), ABSTFN + "1/subdir")
self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/subdir", strict=True)
finally:
os_helper.unlink(ABSTFN)

@os_helper.skip_unless_symlink
@skip_if_ABSTFN_contains_backslash
def test_realpath_nonterminal_symlink_to_symlinks_to_file(self):
try:
with open(ABSTFN + "2", 'w') as f:
f.write('test_posixpath wuz ere')
os.symlink(ABSTFN + "2", ABSTFN + "1")
os.symlink(ABSTFN + "1", ABSTFN)
self.assertEqual(realpath(ABSTFN, strict=False), ABSTFN + "2")
self.assertEqual(realpath(ABSTFN, strict=True), ABSTFN + "2")
self.assertEqual(realpath(ABSTFN + "/", strict=False), ABSTFN + "2")
self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/", strict=True)
self.assertEqual(realpath(ABSTFN + "/.", strict=False), ABSTFN + "2")
self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/.", strict=True)
self.assertEqual(realpath(ABSTFN + "/..", strict=False), dirname(ABSTFN))
self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/..", strict=True)
self.assertEqual(realpath(ABSTFN + "/subdir", strict=False), ABSTFN + "2/subdir")
self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/subdir", strict=True)
finally:
os_helper.unlink(ABSTFN)

def test_relpath(self):
(real_getcwd, os.getcwd) = (os.getcwd, lambda: r"/home/user/bar")
try:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
:func:`!posixpath.realpath` now raises :exc:`NotADirectoryError` when *strict*
mode is enabled and a non-directory path with a trailing slash is supplied.
Loading