diff --git a/src/postgres_mcp/server.py b/src/postgres_mcp/server.py index 90017d6..f3e2c8f 100644 --- a/src/postgres_mcp/server.py +++ b/src/postgres_mcp/server.py @@ -26,9 +26,9 @@ from .index.llm_opt import LLMOptimizerTool from .index.presentation import TextPresentation from .moldes.model import AccessMode -from .resource import register_resource_templates from .resource import format_error_response from .resource import format_text_response +from .resource import register_resource_templates from .sql import SafeSqlDriver from .sql import check_hypopg_installation_status from .sql import obfuscate_password diff --git a/src/postgres_mcp/utils/url.py b/src/postgres_mcp/utils/url.py index 748fc73..925f62b 100644 --- a/src/postgres_mcp/utils/url.py +++ b/src/postgres_mcp/utils/url.py @@ -1,4 +1,5 @@ from urllib.parse import quote +from urllib.parse import unquote def fix_connection_url(url: str) -> str: @@ -10,7 +11,9 @@ def fix_connection_url(url: str) -> str: user_pass = url[scheme_end:at_pos] if ":" in user_pass: username, password = user_pass.split(":", 1) - encoded_password = quote(password, safe="") + # If password is already encoded, decode it. + plain_password = unquote(password) + encoded_password = quote(plain_password, safe="") return url[:scheme_end] + username + ":" + encoded_password + url[at_pos:] except Exception as e: print(e) diff --git a/tests/unit/utils/test_url.py b/tests/unit/utils/test_url.py new file mode 100644 index 0000000..beb38b0 --- /dev/null +++ b/tests/unit/utils/test_url.py @@ -0,0 +1,23 @@ +import pytest + +from postgres_mcp.utils.url import fix_connection_url + + +@pytest.mark.parametrize( + "input_url, expected_output", + [ + ("postgresql://user:pass?word@localhost:5432/db", "postgresql://user:pass%3Fword@localhost:5432/db"), + ("postgresql://user:pass%3Fword@localhost:5432/db", "postgresql://user:pass%3Fword@localhost:5432/db"), + ("postgresql://user:pass%word@localhost:5432/db", "postgresql://user:pass%25word@localhost:5432/db"), + ("postgresql://user:?pass%25wo%%rd@localhost:5432/db", "postgresql://user:%3Fpass%25wo%25%25rd@localhost:5432/db"), + ], +) +def test_fix_connection_url_encoding(input_url: str, expected_output: str) -> None: + """Verifies that passwords are encoded once and only once.""" + assert fix_connection_url(input_url) == expected_output + + +def test_fix_connection_url_no_mutation(): + """Ensure a standard safe URL is not changed.""" + url = "postgresql://readonly:securepassword123@db.example.com:5432/postgres" + assert fix_connection_url(url) == url