fix: decode tag names from the URL only once
The tag lookup, rename and delete routes ran urllib.parse.unquote on match_info values that aiohttp had already percent-decoded, so a tag literally named '%41' (requested as %2541) was handled as 'A'. The second decode is dropped.
This commit is contained in:
+9
-9
@@ -387,9 +387,9 @@ class PromptRoutesMixin:
|
||||
async def get_tag_prompts(self, request):
|
||||
"""Get prompts for a single tag."""
|
||||
try:
|
||||
from urllib.parse import unquote
|
||||
|
||||
tag_name = unquote(request.match_info.get("tag_name", ""))
|
||||
# aiohttp has already percent-decoded match_info; decoding again
|
||||
# would turn a tag literally named "%41" into "A".
|
||||
tag_name = request.match_info.get("tag_name", "")
|
||||
if not tag_name:
|
||||
return web.json_response(
|
||||
{"success": False, "error": "Tag name required"}, status=400
|
||||
@@ -497,9 +497,9 @@ class PromptRoutesMixin:
|
||||
async def rename_tag_endpoint(self, request):
|
||||
"""Rename a tag across all prompts."""
|
||||
try:
|
||||
from urllib.parse import unquote
|
||||
|
||||
tag_name = unquote(request.match_info.get("tag_name", ""))
|
||||
# aiohttp has already percent-decoded match_info; decoding again
|
||||
# would turn a tag literally named "%41" into "A".
|
||||
tag_name = request.match_info.get("tag_name", "")
|
||||
if not tag_name:
|
||||
return web.json_response(
|
||||
{"success": False, "error": "Tag name required"}, status=400
|
||||
@@ -542,9 +542,9 @@ class PromptRoutesMixin:
|
||||
async def delete_tag_endpoint(self, request):
|
||||
"""Delete a tag from all prompts."""
|
||||
try:
|
||||
from urllib.parse import unquote
|
||||
|
||||
tag_name = unquote(request.match_info.get("tag_name", ""))
|
||||
# aiohttp has already percent-decoded match_info; decoding again
|
||||
# would turn a tag literally named "%41" into "A".
|
||||
tag_name = request.match_info.get("tag_name", "")
|
||||
if not tag_name:
|
||||
return web.json_response(
|
||||
{"success": False, "error": "Tag name required"}, status=400
|
||||
|
||||
@@ -431,6 +431,39 @@ class TestPublicPathsInPromptResponses(RouteCoverageCase):
|
||||
self.assertIn("prompts.db", data["error"], route)
|
||||
|
||||
|
||||
class TestTagNamesAreDecodedOnce(RouteCoverageCase):
|
||||
"""aiohttp already decodes match_info; a tag literally named '%41' stays '%41'."""
|
||||
|
||||
async def setUpAsync(self):
|
||||
await super().setUpAsync()
|
||||
self.pid = self._save_prompt("percent tag", tags=["%41"])
|
||||
|
||||
async def test_tag_prompts_lookup(self):
|
||||
status, data = await self._json("GET", "/prompt_manager/tags/%2541/prompts")
|
||||
|
||||
self.assertEqual(status, 200)
|
||||
self.assertEqual(data["tag"], "%41")
|
||||
self.assertEqual(len(data["prompts"]), 1)
|
||||
|
||||
async def test_rename_tag(self):
|
||||
status, data = await self._json(
|
||||
"PUT", "/prompt_manager/tags/%2541", json={"new_name": "renamed"}
|
||||
)
|
||||
|
||||
self.assertEqual(status, 200)
|
||||
self.assertEqual(data["old_name"], "%41")
|
||||
self.assertEqual(data["affected_count"], 1)
|
||||
self.assertEqual(self.api.db.get_prompt_by_id(self.pid)["tags"], ["renamed"])
|
||||
|
||||
async def test_delete_tag(self):
|
||||
status, data = await self._json("DELETE", "/prompt_manager/tags/%2541")
|
||||
|
||||
self.assertEqual(status, 200)
|
||||
self.assertEqual(data["tag_name"], "%41")
|
||||
self.assertEqual(data["affected_count"], 1)
|
||||
self.assertEqual(self.api.db.get_prompt_by_id(self.pid)["tags"], [])
|
||||
|
||||
|
||||
class TestTagMutationRoutes(RouteCoverageCase):
|
||||
|
||||
async def test_rename_rejects_bad_json_and_empty_name(self):
|
||||
|
||||
Reference in New Issue
Block a user