properly handle symlinks as relative to the output dir

the code meant to make symlinks relative to the output directory, but
only actually succeeded at it for targets in the root of the output
directory; since we already check that the target is not breaking out of
the instance, we can generate the output symlink relative to itself and
fix the subdirred-symlink behavior

Signed-off-by: Brian S. Stephan <bss@incorporeal.org>
This commit is contained in:
Brian S. Stephan 2025-03-21 11:43:18 -05:00
parent 8c75947088
commit 88b678931e
Signed by: bss
GPG Key ID: 3DE06D3180895FCB
3 changed files with 24 additions and 2 deletions

View File

@ -177,6 +177,10 @@ class StaticSiteGenerator(object):
def symlink_to_relative_dest(self, base_dir: str, source: str) -> str:
"""Given a symlink, make sure it points to something inside the instance and provide its real destination.
This is made to be relative to the location of the symlink in all
circumstances, in order to avoid breaking out of the instance or output
dirs.
Args:
base_dir: the full absolute path of the instance's pages dir, which the symlink destination must be in.
source: the symlink to check
@ -185,8 +189,8 @@ class StaticSiteGenerator(object):
"""
if not os.path.realpath(source).startswith(base_dir):
raise ValueError(f"symlink destination {os.path.realpath(source)} is outside the instance!")
# this symlink points to realpath inside base_dir, so relative to base_dir, the symlink dest is...
return os.path.relpath(os.path.realpath(source), base_dir)
# this symlink points to realpath inside base_dir, so relative to the source, the symlink dest is...
return os.path.relpath(os.path.realpath(source), os.path.dirname(source))
def build():

View File

@ -0,0 +1 @@
../more-metadata.md

View File

@ -52,6 +52,23 @@ def test_file_copy_symlink():
assert os.path.islink(os.path.join(tmpdir, 'symlink-to-foo.txt'))
def test_file_copy_subdir_symlink():
"""Test the ability to sync a symlink in a subdirectory to the output dir."""
with tempfile.TemporaryDirectory() as tmpdir:
src_dir = os.path.join(HERE, 'instance')
generator = ssg.StaticSiteGenerator(src_dir, tmpdir)
# need to make the subdirectory as if the generator already did
os.mkdir(os.path.join(tmpdir, 'subdir'))
generator.build_file_in_destination(os.path.join(instance_dir, 'pages'), 'subdir',
'relative-symlink-to-parent.md', tmpdir)
# need to copy the destination for os.path.exists to be happy with this
generator.build_file_in_destination(os.path.join(instance_dir, 'pages'), '', 'more-metadata.md', tmpdir)
logger.warning("created symlink %s",
os.readlink(os.path.join(tmpdir, 'subdir', 'relative-symlink-to-parent.md')))
assert os.path.islink(os.path.join(tmpdir, 'subdir', 'relative-symlink-to-parent.md'))
assert os.path.exists(os.path.join(tmpdir, 'subdir', 'relative-symlink-to-parent.md'))
def test_file_copy_symlink_of_markdown_also_has_html_symlink():
"""Test the ability to sync source and generated symlinks to the output dir."""
with tempfile.TemporaryDirectory() as tmpdir: