Skip to content
Open
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
22 changes: 21 additions & 1 deletion Lib/ntpath.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -516,6 +516,26 @@ def normpath(path):
from nt import _getfullpathname

except ImportError: # not running on Windows - mock up something sensible
def _strip_dots_and_spaces(path):
# As the Windows path normalization does.
if isinstance(path, bytes):
sep = b'\\'
dot = b'.'
dots_and_spaces = b'. '
else:
sep = '\\'
dot = '.'
dots_and_spaces = '. '
drive, root, tail = splitroot(path)
if not tail:
return path
comps = tail.split(sep)
for i, comp in enumerate(comps[:-1]):
if len(comp) > 1 and comp.endswith(dot) and not comp.endswith(dot*2):
comps[i] = comp[:-1]
comps[-1] = comps[-1].rstrip(dots_and_spaces)
return drive + root + sep.join(comps)

def abspath(path):
"""Return the absolute version of a path."""
path = os.fspath(path)
Expand All@@ -525,7 +545,7 @@ def abspath(path):
else:
cwd = os.getcwd()
path = join(cwd, path)
return normpath(path)
return _strip_dots_and_spaces(normpath(path))

else: # use native Windows method on Windows
def abspath(path):
Expand Down
29 changes: 29 additions & 0 deletions Lib/test/test_ntpath.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -1210,6 +1210,35 @@ def test_expanduser(self):



def test_abspath_dots_and_spaces(self):
# gh-85681: trailing dots and spaces are stripped from the last
# component, and a single trailing dot from other components.
tester('ntpath.abspath("C:/spam. . .")', "C:\\spam")
tester('ntpath.abspath("C:/spam.")', "C:\\spam")
tester('ntpath.abspath("C:/spam ")', "C:\\spam")
tester('ntpath.abspath("C:/spam..")', "C:\\spam")
tester('ntpath.abspath("C:/.spam")', "C:\\.spam")
tester('ntpath.abspath("C:/spam./eggs")', "C:\\spam\\eggs")
tester('ntpath.abspath("C:/spam.b./eggs")', "C:\\spam.b\\eggs")
tester('ntpath.abspath("C:/spam ./eggs")', "C:\\spam \\eggs")
# A trailing dot preceded by a dot is not stripped.
tester('ntpath.abspath("C:/spam../eggs")', "C:\\spam..\\eggs")
tester('ntpath.abspath("C:/spam.../eggs")', "C:\\spam...\\eggs")
# Trailing dots and spaces are stripped even in extended paths.
tester('ntpath.abspath("\\\\?\\C:/spam. . .")', "\\\\?\\C:\\spam")
tester('ntpath.abspath("\\\\.\\C:/spam. . .")', "\\\\.\\C:\\spam")
tester('ntpath.abspath("//server/share/spam. ")',
"\\\\server\\share\\spam")

def test_relpath_dots_and_spaces(self):
# gh-85681: relpath() is based on abspath().
tester('ntpath.relpath("foo ", "foo")', ".")
tester('ntpath.relpath("foo.", "foo")', ".")
tester('ntpath.relpath("foo/bar ", "foo")', "bar")
tester('ntpath.relpath("foo/bar.", "foo")', "bar")
# "foo.." denotes "foo" as the last component, but not as other.
tester('ntpath.relpath("foo../bar", "foo..")', "..\\foo..\\bar")

@unittest.skipUnless(nt, "abspath requires 'nt' module")
def test_abspath(self):
tester('ntpath.abspath("C:\\")', "C:\\")
Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
:func:`ntpath.abspath` on non-Windows platforms now strips trailing dots and
spaces like the Windows path normalization does. This makes
:func:`ntpath.relpath` and other functions based on it platform independent.
Loading