diff --git a/incorporealcms/pages.py b/incorporealcms/pages.py index 2714e7a..7b41627 100644 --- a/incorporealcms/pages.py +++ b/incorporealcms/pages.py @@ -22,7 +22,8 @@ def display_page(path): """Get the file contents of the requested path and render the file.""" try: resolved_path, render_type = request_path_to_instance_resource_path(path) - logger.debug("received request for path '%s', resolved to '%s'", path, resolved_path) + logger.debug("received request for path '%s', resolved to '%s', type '%s'", + path, resolved_path, render_type) except PermissionError: abort(400) except IsADirectoryError: @@ -32,6 +33,8 @@ def display_page(path): if render_type == 'file': return send_from_directory(app.instance_path, resolved_path) + elif render_type == 'symlink': + return redirect(instance_resource_path_to_request_path(resolved_path), code=301) elif render_type == 'markdown': try: with app.open_instance_resource(resolved_path, 'r') as entry_file: @@ -77,6 +80,15 @@ def request_path_to_instance_resource_path(path): logger.warning("client tried to request a path '%s' outside of the base_dir!", path) raise PermissionError + # if this is a (valid) symlink, find what it's pointed to and redirect the user + if os.path.islink(os.path.join(base_dir, path)): + logger.info("client requested a path '%s' that is actually a symlink to file '%s'", path, resolved_path) + return resolved_path.replace(f'{app.instance_path}{os.path.sep}', ''), 'symlink' + elif os.path.islink(os.path.join(base_dir, f'{path}.md')): + resolved_path = os.path.realpath(os.path.join(base_dir, f'{path}.md')) + logger.info("client requested a path '%s' that is actually a symlink to file '%s'", path, resolved_path) + return resolved_path.replace(f'{app.instance_path}{os.path.sep}', ''), 'symlink' + # if this is a file-like requset but actually a directory, redirect the user if os.path.isdir(resolved_path) and not path.endswith('/'): logger.info("client requested a path '%s' that is actually a directory", path) diff --git a/tests/functional_tests.py b/tests/functional_tests.py index e83ca73..e76fdb3 100644 --- a/tests/functional_tests.py +++ b/tests/functional_tests.py @@ -108,6 +108,21 @@ def test_that_page_request_redirects_to_directory(client): """ response = client.get('/subdir') assert response.status_code == 301 + assert response.location == 'http://localhost/subdir/' + + +def test_that_request_to_symlink_redirects_markdown(client): + """Test that a request to /foo redirects to /what-foo-points-at.""" + response = client.get('/symlink-to-no-title') + assert response.status_code == 301 + assert response.location == 'http://localhost/no-title' + + +def test_that_request_to_symlink_redirects_file(client): + """Test that a request to /foo.txt redirects to /what-foo-points-at.txt.""" + response = client.get('/symlink-to-foo.txt') + assert response.status_code == 301 + assert response.location == 'http://localhost/foo.txt' def test_that_dir_request_does_not_redirect(client): diff --git a/tests/instance/pages/symlink-to-foo.txt b/tests/instance/pages/symlink-to-foo.txt new file mode 120000 index 0000000..996f178 --- /dev/null +++ b/tests/instance/pages/symlink-to-foo.txt @@ -0,0 +1 @@ +foo.txt \ No newline at end of file diff --git a/tests/instance/pages/symlink-to-no-title.md b/tests/instance/pages/symlink-to-no-title.md new file mode 120000 index 0000000..e4c6749 --- /dev/null +++ b/tests/instance/pages/symlink-to-no-title.md @@ -0,0 +1 @@ +no-title.md \ No newline at end of file diff --git a/tests/test_pages.py b/tests/test_pages.py index b1fb102..c7c0172 100644 --- a/tests/test_pages.py +++ b/tests/test_pages.py @@ -132,6 +132,20 @@ def test_request_path_to_instance_resource_path_actual_file(app): ('pages/bss-square-no-bg.png', 'file')) +def test_request_path_to_instance_resource_path_markdown_symlink(app): + """Test that a request for e.g. '/foo' when foo.md is a symlink to another .md file redirects.""" + with app.test_request_context(): + assert (request_path_to_instance_resource_path('symlink-to-no-title') == + ('pages/no-title.md', 'symlink')) + + +def test_request_path_to_instance_resource_path_file_symlink(app): + """Test that a request for e.g. '/foo' when foo.txt is a symlink to another .txt file redirects.""" + with app.test_request_context(): + assert (request_path_to_instance_resource_path('symlink-to-foo.txt') == + ('pages/foo.txt', 'symlink')) + + def test_request_path_to_instance_resource_path_nonexistant_file_errors(app): """Test that a request for something not on disk errors.""" with app.test_request_context():