Skip to content

Commit bdd5731

Browse files
committed
pathlib ABCs: check filename validity when copying
Use `target_path.joinpath('_').with_name(source_name)` to generate child paths in a target directory when copying. This formula ensures `ValueError` is raised if the filename is invalid for the target path type (e.g. empty or contains a path separator.) No change of public behaviour because `pathlib.Path` is the only exposed implementation of `ReadablePath`, and validity issues arise only when the source and target path parsers differ.
1 parent 805e336 commit bdd5731

File tree

6 files changed

+30
-14
lines changed

6 files changed

+30
-14
lines changed

Lib/pathlib/__init__.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1101,13 +1101,9 @@ def copy_into(self, target_dir, **kwargs):
11011101
"""
11021102
Copy this file or directory tree into the given existing directory.
11031103
"""
1104-
name = self.name
1105-
if not name:
1106-
raise ValueError(f"{self!r} has an empty name")
1107-
elif hasattr(target_dir, 'with_segments'):
1108-
target = target_dir / name
1109-
else:
1110-
target = self.with_segments(target_dir, name)
1104+
if not hasattr(target_dir, 'with_segments'):
1105+
target_dir = self.with_segments(target_dir)
1106+
target = target_dir.joinpath('_').with_name(self.name)
11111107
return self.copy(target, **kwargs)
11121108

11131109
def _copy_from(self, source, follow_symlinks=True, preserve_metadata=False):
@@ -1120,7 +1116,7 @@ def _copy_from(self, source, follow_symlinks=True, preserve_metadata=False):
11201116
children = source.iterdir()
11211117
os.mkdir(self)
11221118
for child in children:
1123-
self.joinpath(child.name)._copy_from(
1119+
self.joinpath('_').with_name(child.name)._copy_from(
11241120
child, follow_symlinks, preserve_metadata)
11251121
if preserve_metadata:
11261122
copy_info(source.info, self)

Lib/pathlib/types.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ def stem(self):
152152
def with_name(self, name):
153153
"""Return a new path with the file name changed."""
154154
split = self.parser.split
155-
if split(name)[0]:
155+
if not name or split(name)[0]:
156156
raise ValueError(f"Invalid name {name!r}")
157157
path = vfspath(self)
158158
path = path.removesuffix(split(path)[1]) + name
@@ -363,10 +363,8 @@ def copy_into(self, target_dir, **kwargs):
363363
"""
364364
Copy this file or directory tree into the given existing directory.
365365
"""
366-
name = self.name
367-
if not name:
368-
raise ValueError(f"{self!r} has an empty name")
369-
return self.copy(target_dir / name, **kwargs)
366+
target = target_dir.joinpath('_').with_name(self.name)
367+
return self.copy(target, **kwargs)
370368

371369

372370
class _WritablePath(_JoinablePath):
@@ -436,7 +434,7 @@ def _copy_from(self, source, follow_symlinks=True):
436434
children = src.iterdir()
437435
dst.mkdir()
438436
for child in children:
439-
stack.append((child, dst.joinpath(child.name)))
437+
stack.append((child, dst.joinpath('_').with_name(child.name)))
440438
else:
441439
ensure_different_files(src, dst)
442440
with vfsopen(src, 'rb') as source_f:

Lib/test/test_pathlib/support/local_path.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
class LocalPathGround:
2828
can_symlink = can_symlink
29+
seps = os.path.sep + (os.path.altsep or '')
2930

3031
def __init__(self, path_cls):
3132
self.path_cls = path_cls

Lib/test/test_pathlib/support/zip_path.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
class ZipPathGround:
2626
can_symlink = True
27+
seps = '/'
2728

2829
def __init__(self, path_cls):
2930
self.path_cls = path_cls

Lib/test/test_pathlib/test_copy.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,16 @@ def test_copy_dir_into_itself(self):
130130
self.assertRaises(OSError, source.copy, target)
131131
self.assertRaises(OSError, source.copy, target, follow_symlinks=False)
132132

133+
def test_copy_dir_backslash_name(self):
134+
if '\\' in self.source_ground.seps:
135+
self.skipTest('backslash is a source path separator')
136+
elif '\\' not in self.target_ground.seps:
137+
self.skipTest('backslash is not a target path separator')
138+
source = self.source_root / 'dirC'
139+
target = self.target_root / 'copyC'
140+
self.source_ground.create_file(source / 'back\\slash', b'')
141+
self.assertRaises(ValueError, source.copy, target)
142+
133143
def test_copy_into(self):
134144
source = self.source_root / 'fileA'
135145
target_dir = self.target_root / 'dirA'
@@ -146,6 +156,15 @@ def test_copy_into_empty_name(self):
146156
self.target_ground.create_dir(target_dir)
147157
self.assertRaises(ValueError, source.copy_into, target_dir)
148158

159+
def test_copy_into_backslash_name(self):
160+
if '\\' in self.source_ground.seps:
161+
self.skipTest('backslash is a source path separator')
162+
elif '\\' not in self.target_ground.seps:
163+
self.skipTest('backslash is not a target path separator')
164+
source = self.source_root / 'back\\slash'
165+
target_dir = self.target_root / 'dirA'
166+
self.assertRaises(ValueError, source.copy_into, target_dir)
167+
149168

150169
class ZipToZipPathCopyTest(CopyTestBase, unittest.TestCase):
151170
source_ground = ZipPathGround(ReadableZipPath)

Lib/test/test_pathlib/test_join.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,7 @@ def test_with_name(self):
308308
self.assertEqual(P('/a/b.py').with_name('d.xml'), P('/a/d.xml'))
309309
self.assertEqual(P('a/Dot ending.').with_name('d.xml'), P('a/d.xml'))
310310
self.assertEqual(P('/a/Dot ending.').with_name('d.xml'), P('/a/d.xml'))
311+
self.assertRaises(ValueError, P('a/b').with_name, '')
311312
self.assertRaises(ValueError, P('a/b').with_name, '/c')
312313
self.assertRaises(ValueError, P('a/b').with_name, 'c/')
313314
self.assertRaises(ValueError, P('a/b').with_name, 'c/d')

0 commit comments

Comments
 (0)