|
1 | 1 | import os |
| 2 | +import re |
2 | 3 | from pathlib import Path |
| 4 | +from tempfile import gettempdir |
3 | 5 |
|
4 | 6 | import pytest |
5 | 7 | from plumbum import local |
6 | 8 |
|
7 | 9 | from copier import run_copy, run_update |
8 | | -from copier.errors import DirtyLocalWarning |
| 10 | +from copier.errors import DirtyLocalWarning, ForbiddenPathError |
9 | 11 |
|
10 | 12 | from .helpers import build_file_tree, git |
11 | 13 |
|
@@ -569,3 +571,78 @@ def test_symlinked_to_outside_destination_relative( |
569 | 571 | assert (dst / "symlink_dir" / "outside.txt").read_text() == "outside" |
570 | 572 | assert (dst / "a_symlink.txt").is_symlink() |
571 | 573 | assert (dst / "a_symlink.txt").read_text() == "outside" |
| 574 | + |
| 575 | + |
| 576 | +def test_resolve_relative_symlink_outside_template_root_raises_error( |
| 577 | + tmp_path_factory: pytest.TempPathFactory, |
| 578 | +) -> None: |
| 579 | + src, dst, other = map(tmp_path_factory.mktemp, ("src", "dst", "other")) |
| 580 | + |
| 581 | + build_file_tree( |
| 582 | + { |
| 583 | + src / ".copier-answers.yml.jinja": """\ |
| 584 | + # Changes here will be overwritten by Copier |
| 585 | + {{ _copier_answers|to_nice_yaml }} |
| 586 | + """, |
| 587 | + src / "copier.yaml": """\ |
| 588 | + _preserve_symlinks: false |
| 589 | + """, |
| 590 | + src / "symlink.txt": Path( |
| 591 | + # HACK: This is the path to `outside.txt` relative to the location the |
| 592 | + # symlink file in the template's local clone location. To construct |
| 593 | + # this path dynamically, we rely on internal knowledge where Copier |
| 594 | + # clones the template. |
| 595 | + os.path.relpath(other, start=Path(gettempdir(), "template-clone")), |
| 596 | + "outside.txt", |
| 597 | + ), |
| 598 | + other / "outside.txt": "outside", |
| 599 | + } |
| 600 | + ) |
| 601 | + |
| 602 | + with local.cwd(src): |
| 603 | + git("init") |
| 604 | + git("add", "-A") |
| 605 | + git("commit", "-m", "init") |
| 606 | + |
| 607 | + with pytest.raises( |
| 608 | + ForbiddenPathError, |
| 609 | + match=re.escape('"symlink.txt" is forbidden'), |
| 610 | + ): |
| 611 | + run_copy(str(src), dst, defaults=True, overwrite=True, cleanup_on_error=False) |
| 612 | + |
| 613 | + assert not (dst / "symlink.txt").exists() |
| 614 | + |
| 615 | + |
| 616 | +@pytest.mark.skipif( |
| 617 | + os.name == "nt", reason="Absolute paths not created as symlinks on Windows" |
| 618 | +) |
| 619 | +def test_resolve_absolute_symlink_outside_template_root_raises_error( |
| 620 | + tmp_path_factory: pytest.TempPathFactory, |
| 621 | +) -> None: |
| 622 | + src, dst, other = map(tmp_path_factory.mktemp, ("src", "dst", "other")) |
| 623 | + build_file_tree( |
| 624 | + { |
| 625 | + src / ".copier-answers.yml.jinja": """\ |
| 626 | + # Changes here will be overwritten by Copier |
| 627 | + {{ _copier_answers|to_nice_yaml }} |
| 628 | + """, |
| 629 | + src / "copier.yaml": """\ |
| 630 | + _preserve_symlinks: false |
| 631 | + """, |
| 632 | + src / "symlink.txt": other / "outside.txt", |
| 633 | + other / "outside.txt": "outside", |
| 634 | + } |
| 635 | + ) |
| 636 | + |
| 637 | + with local.cwd(src): |
| 638 | + git("init") |
| 639 | + git("add", "-A") |
| 640 | + git("commit", "-m", "init") |
| 641 | + |
| 642 | + with pytest.raises( |
| 643 | + ForbiddenPathError, |
| 644 | + match=re.escape('"symlink.txt" is forbidden'), |
| 645 | + ): |
| 646 | + run_copy(str(src), dst, defaults=True, overwrite=True) |
| 647 | + |
| 648 | + assert not (dst / "symlink.txt").exists() |
0 commit comments