From 891a82249152040ba38b982c56e6ae140f125746 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Sat, 28 Mar 2026 10:31:40 +0100 Subject: [PATCH 1/2] Fix AttributeError when invalid tool ID passed to shed_tool_static Return a 404 instead of crashing with an unhandled AttributeError when toolbox.get_tool() returns None for an invalid/unknown tool GUID. Fixes https://github.com/galaxyproject/galaxy/issues/22126 --- lib/galaxy/webapps/galaxy/controllers/shed_tool_static.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/galaxy/webapps/galaxy/controllers/shed_tool_static.py b/lib/galaxy/webapps/galaxy/controllers/shed_tool_static.py index 069ff8a8f01..6e88939b2ee 100644 --- a/lib/galaxy/webapps/galaxy/controllers/shed_tool_static.py +++ b/lib/galaxy/webapps/galaxy/controllers/shed_tool_static.py @@ -2,7 +2,10 @@ import logging import os from galaxy import web -from galaxy.exceptions import RequestParameterInvalidException +from galaxy.exceptions import ( + ObjectNotFound, + RequestParameterInvalidException, +) from galaxy.util.path import ( join, safe_contains, @@ -34,6 +37,8 @@ class ShedToolStatic(BaseUIController): """ guid = "/".join((shed, "repos", owner, repo, tool, version)) tool = trans.app.toolbox.get_tool(guid) + if tool is None: + raise ObjectNotFound(f"Could not find tool with guid '{guid}'.") repo_path = os.path.abspath(tool._repository_dir) found_path = None From 4a686851a1c64ca78c5d38df5159accc2d283475 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Sat, 28 Mar 2026 10:31:40 +0100 Subject: [PATCH 2/2] URL-encode tool ID in shed_tool_static image paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tool IDs with special characters (e.g., "EMBOSS: isochore47") were embedded into URL paths without percent-encoding. The space would be lost in transit through RST→HTML→browser, causing the server to reconstruct a GUID that didn't match any tool. Apply urllib.parse.quote(safe="/") to the entire route_to_images path so special characters are properly percent-encoded. The WSGI layer auto-decodes PATH_INFO before routing, so the controller receives the correct values. Fixes https://github.com/galaxyproject/galaxy/issues/22126 --- lib/galaxy/tool_shed/util/shed_util_common.py | 6 +- test/unit/tool_shed/test_shed_util_common.py | 56 +++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 test/unit/tool_shed/test_shed_util_common.py diff --git a/lib/galaxy/tool_shed/util/shed_util_common.py b/lib/galaxy/tool_shed/util/shed_util_common.py index 648b9de4eaf..90b8079f8a0 100644 --- a/lib/galaxy/tool_shed/util/shed_util_common.py +++ b/lib/galaxy/tool_shed/util/shed_util_common.py @@ -1,5 +1,6 @@ import logging import re +from urllib.parse import quote from galaxy import util from galaxy.tool_shed.util import repository_util @@ -149,7 +150,10 @@ def set_image_paths(app, text, encoded_repository_id=None, tool_shed_repository= # We're in the tool shed. route_to_images = f"/repository/static/images/{encoded_repository_id}" elif tool_shed_repository and tool_id and tool_version: - route_to_images = f"shed_tool_static/{tool_shed_repository.tool_shed}/{tool_shed_repository.owner}/{tool_shed_repository.name}/{tool_id}/{tool_version}" + route_to_images = quote( + f"shed_tool_static/{tool_shed_repository.tool_shed}/{tool_shed_repository.owner}/{tool_shed_repository.name}/{tool_id}/{tool_version}", + safe="/", + ) else: raise Exception( "encoded_repository_id or tool_shed_repository and tool_id and tool_version must be provided" diff --git a/test/unit/tool_shed/test_shed_util_common.py b/test/unit/tool_shed/test_shed_util_common.py new file mode 100644 index 00000000000..3b49dfbec9b --- /dev/null +++ b/test/unit/tool_shed/test_shed_util_common.py @@ -0,0 +1,56 @@ +from types import SimpleNamespace + +from galaxy.tool_shed.util.shed_util_common import set_image_paths + + +def test_set_image_paths_encodes_special_characters_in_tool_id(): + tool_shed_repository = SimpleNamespace( + tool_shed="toolshed.g2.bx.psu.edu", + owner="devteam", + name="emboss_5", + ) + text = ".. image:: static/images/isochore.png" + result = set_image_paths( + app=None, + text=text, + tool_shed_repository=tool_shed_repository, + tool_id="EMBOSS: isochore47", + tool_version="5.0.0.1", + ) + assert "EMBOSS%3A%20isochore47" in result + assert "EMBOSS: isochore47" not in result + + +def test_set_image_paths_preserves_slashes_in_route(): + tool_shed_repository = SimpleNamespace( + tool_shed="toolshed.g2.bx.psu.edu", + owner="devteam", + name="emboss_5", + ) + text = ".. image:: isochore.png" + result = set_image_paths( + app=None, + text=text, + tool_shed_repository=tool_shed_repository, + tool_id="isochore", + tool_version="5.0.0", + ) + assert "shed_tool_static/toolshed.g2.bx.psu.edu/devteam/emboss_5/isochore/5.0.0/" in result + + +def test_set_image_paths_does_not_modify_http_urls(): + tool_shed_repository = SimpleNamespace( + tool_shed="toolshed.g2.bx.psu.edu", + owner="devteam", + name="emboss_5", + ) + text = ".. image:: https://example.com/image.png" + result = set_image_paths( + app=None, + text=text, + tool_shed_repository=tool_shed_repository, + tool_id="mytool", + tool_version="1.0", + ) + assert ".. image:: https://example.com/image.png" in result + assert "shed_tool_static" not in result