8000 GH-80486: Fix handling of NTFS alternate data streams in pathlib by barneygale · Pull Request #102454 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

GH-80486: Fix handling of NTFS alternate data streams in pathlib #102454

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

Merged
merged 12 commits into from
Mar 10, 2023
Prev Previous commit
Next Next commit
Undo pathlib changes
  • Loading branch information
barneygale committed Feb 22, 2023
commit 86c04080887c1a557e37e011265413057697d313
80 changes: 28 additions & 52 deletions Lib/pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,49 +60,38 @@ class _Flavour(object):
def __init__(self):
self.join = self.sep.join

def _split_part(self, part):
"""
Return the drive, root and path parts from a given part.
If the part is a tuple, it already contains these values and therefore is returned.
Otherwise, splitroot is used to parse the part.
"""
if isinstance(part, tuple):
return part
elif isinstance(part, str):
if self.altsep:
part = part.replace(self.altsep, self.sep)
drv, root, rel = self.splitroot(part)
return drv, root, rel.split(self.sep)
else:
raise TypeError(f'argument should be a tuple or an str object, not {type(part)}')

def parse_parts(self, parts):
"""
Parse and join multiple path strings, and
return a tuple of the final drive, root and path parts.
The given parts can be either strings of paths,
or tuples that represent paths, containing the drive, root and list of path parts.
The option for passing a tuple is needed, as the part 'a:b' could be interpreted
either as the relative path 'b' with the drive 'a:',
or as a file 'a' with the NTFS data-stream 'b'.
For example, passing either ('a:', '', ['b']) or ('', '', ['a:b']) instead of 'a:b'
will allow parse_parts to behave properly in these cases.
"""
parsed = []
sep = self.sep
altsep = self.altsep
drv = root = ''
it = reversed(parts)
for part in it:
if not part:
continue
current_drv, current_root, rel_parts = self._split_part(part)
if not drv:
drv = current_drv
if not root:
root = current_root
for x in reversed(rel_parts):
if altsep:
part = part.replace(altsep, sep)
drv, root, rel = self.splitroot(part)
if sep in rel:
for x in reversed(rel.split(sep)):
if x and x != '.':
parsed.append(sys.intern(x))
if root and drv:
else:
if rel and rel != '.':
parsed.append(sys.intern(rel))
if drv or root:
if not drv:
# If no drive is present, try to find one in the previous
# parts. This makes the result of parsing e.g.
# ("C:", "/", "a") reasonably intuitive.
for part in it:
if not part:
continue
if altsep:
part = part.replace(altsep, sep)
drv = self.splitroot(part)[0]
if drv:
break
break
if drv or root:
parsed.append(drv + root)
Expand All @@ -126,9 +115,6 @@ def join_parsed_parts(self, drv, root, parts, drv2, root2, parts2):
return drv, root, parts + parts2
return drv2, root2, parts2

def has_drive(self, part):
return self.splitroot(part)[0] != ''


class _WindowsFlavour(_Flavour):
# Reference for Windows paths can be found at
Expand Down Expand Up @@ -208,21 +194,18 @@ def resolve(self, path, strict=False):
s = str(path)
if not s:
return os.getcwd()
8000 previous_s = None
if _getfinalpathname is not None:
if strict:
return self._ext_to_normal(_getfinalpathname(s))
else:
previous_s = None
tail_parts = [] # End of the path after the first one not found
while True:
try:
s = self._ext_to_normal(_getfinalpathname(s))
except FileNotFoundError:
previous_s = s
s, tail = os.path.split(s)
if self.has_drive(tail):
# To avoid confusing between a filename with a data-stream and a drive letter
tail = f'.{self.sep}{tail}'
tail_parts.append(tail)
if previous_s == s:
return path
Expand Down Expand Up @@ -670,10 +653,7 @@ def _parse_args(cls, args):
parts = []
for a in args:
if isinstance(a, PurePath):
path_parts = a._parts
if a._drv or a._root:
path_parts = path_parts[1:]
parts.append((a._drv, a._root, path_parts))
parts += a._parts
else:
a = os.fspath(a)
if isinstance(a, str):
Expand Down Expand Up @@ -711,10 +691,6 @@ def _from_parsed_parts(cls, drv, root, parts, init=True):

@classmethod
def _format_parsed_parts(cls, drv, root, parts):
if parts and not drv and cls._flavour.has_drive(parts[0]):
# In case there is no drive, and the first part might be interpreted as a drive,
# we add a dot to clarify the first part is not a drive.
parts = ['.'] + parts
if drv or root:
return drv + root + cls._flavour.join(parts[1:])
else:
Expand Down Expand Up @@ -965,7 +941,7 @@ def __truediv__(self, key):

def __rtruediv__(self, key):
try:
return self._from_parts([key, self])
return self._from_parts([key] + self._parts)
except TypeError:
return NotImplemented

Expand Down Expand Up @@ -1195,7 +1171,7 @@ def absolute(self):
return self
# FIXME this must defer to the specific flavour (and, under Windows,
# use nt._getfullpathname())
obj = self._from_parts([os.getcwd(), self], init=False)
obj = self._from_parts([os.getcwd()] + self._parts, init=False)
obj._init(template=self)
return obj

Expand Down Expand Up @@ -1577,7 +1553,7 @@ def expanduser(self):
if (not (self._drv or self._root) and
self._parts and self._parts[0][:1] == '~'):
homedir = self._flavour.gethomedir(self._parts[0][1:])
return self._from_parts([homedir, self.relative_to(self._parts[0])])
return self._from_parts([homedir] + self._parts[1:])

return self

Expand Down
0