aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/Lib/ntpath.py
diff options
context:
space:
mode:
authorSteve Dower <steve.dower@python.org>2019-11-15 09:49:21 -0800
committerGitHub <noreply@github.com>2019-11-15 09:49:21 -0800
commitabde52cd8e31830bfc06c5803221faae6172104a (patch)
tree32244b8a0ba8ec1899eb1549c151476fd264f867 /Lib/ntpath.py
parentb22030073b9327a3aeccb69507694bce078192aa (diff)
downloadcpython-abde52cd8e31830bfc06c5803221faae6172104a.tar.gz
cpython-abde52cd8e31830bfc06c5803221faae6172104a.zip
bpo-38453: Ensure ntpath.realpath correctly resolves relative paths (GH-16967)
Ensure isabs() is always True for \\?\ prefixed paths Avoid unnecessary usage of readlink() to avoid resolving broken links incorrectly Ensure shutil tests run in test directory
Diffstat (limited to 'Lib/ntpath.py')
-rw-r--r--Lib/ntpath.py49
1 files changed, 41 insertions, 8 deletions
diff --git a/Lib/ntpath.py b/Lib/ntpath.py
index d4ecff97c95..6f771773a7d 100644
--- a/Lib/ntpath.py
+++ b/Lib/ntpath.py
@@ -61,6 +61,14 @@ def normcase(s):
def isabs(s):
"""Test whether a path is absolute"""
s = os.fspath(s)
+ # Paths beginning with \\?\ are always absolute, but do not
+ # necessarily contain a drive.
+ if isinstance(s, bytes):
+ if s.replace(b'/', b'\\').startswith(b'\\\\?\\'):
+ return True
+ else:
+ if s.replace('/', '\\').startswith('\\\\?\\'):
+ return True
s = splitdrive(s)[1]
return len(s) > 0 and s[0] in _get_bothseps(s)
@@ -526,10 +534,7 @@ except ImportError:
# realpath is a no-op on systems without _getfinalpathname support.
realpath = abspath
else:
- def _readlink_deep(path, seen=None):
- if seen is None:
- seen = set()
-
+ def _readlink_deep(path):
# These error codes indicate that we should stop reading links and
# return the path we currently have.
# 1: ERROR_INVALID_FUNCTION
@@ -546,10 +551,22 @@ else:
# 4393: ERROR_REPARSE_TAG_INVALID
allowed_winerror = 1, 2, 3, 5, 21, 32, 50, 67, 87, 4390, 4392, 4393
+ seen = set()
while normcase(path) not in seen:
seen.add(normcase(path))
try:
+ old_path = path
path = _nt_readlink(path)
+ # Links may be relative, so resolve them against their
+ # own location
+ if not isabs(path):
+ # If it's something other than a symlink, we don't know
+ # what it's actually going to be resolved against, so
+ # just return the old path.
+ if not islink(old_path):
+ path = old_path
+ break
+ path = normpath(join(dirname(old_path), path))
except OSError as ex:
if ex.winerror in allowed_winerror:
break
@@ -579,23 +596,31 @@ else:
# Non-strict algorithm is to find as much of the target directory
# as we can and join the rest.
tail = ''
- seen = set()
while path:
try:
- path = _readlink_deep(path, seen)
path = _getfinalpathname(path)
return join(path, tail) if tail else path
except OSError as ex:
if ex.winerror not in allowed_winerror:
raise
+ try:
+ # The OS could not resolve this path fully, so we attempt
+ # to follow the link ourselves. If we succeed, join the tail
+ # and return.
+ new_path = _readlink_deep(path)
+ if new_path != path:
+ return join(new_path, tail) if tail else new_path
+ except OSError:
+ # If we fail to readlink(), let's keep traversing
+ pass
path, name = split(path)
# TODO (bpo-38186): Request the real file name from the directory
# entry using FindFirstFileW. For now, we will return the path
# as best we have it
if path and not name:
- return abspath(path + tail)
+ return path + tail
tail = join(name, tail) if tail else name
- return abspath(tail)
+ return tail
def realpath(path):
path = normpath(path)
@@ -604,12 +629,20 @@ else:
unc_prefix = b'\\\\?\\UNC\\'
new_unc_prefix = b'\\\\'
cwd = os.getcwdb()
+ # bpo-38081: Special case for realpath(b'nul')
+ if normcase(path) == normcase(os.fsencode(devnull)):
+ return b'\\\\.\\NUL'
else:
prefix = '\\\\?\\'
unc_prefix = '\\\\?\\UNC\\'
new_unc_prefix = '\\\\'
cwd = os.getcwd()
+ # bpo-38081: Special case for realpath('nul')
+ if normcase(path) == normcase(devnull):
+ return '\\\\.\\NUL'
had_prefix = path.startswith(prefix)
+ if not had_prefix and not isabs(path):
+ path = join(cwd, path)
try:
path = _getfinalpathname(path)
initial_winerror = 0