diff --git a/AUTHORS b/AUTHORS index 691396045..ecf59a54c 100644 --- a/AUTHORS +++ b/AUTHORS @@ -155,6 +155,7 @@ Contributors: * Diego * Chris (ChrisJr404) * Pieter Ouwerkerk (pouwerkerk) + * jackwalkerlabs Creator: -------- diff --git a/changelog.rst b/changelog.rst index ee5d1fcf5..5b1c1aa1c 100644 --- a/changelog.rst +++ b/changelog.rst @@ -27,6 +27,10 @@ Features: Bug fixes: ---------- +* [breaking change] Passwords in service files (usually + ``~/.pg_service.conf``) are now read as they appear, as ``libpq`` and + ``psql`` do. Previously, hashes, percent signs and other values had to be + quoted. They must not be quoted anymore. * Fix special commands being broken while explain mode (F5) is on. Every input was prefixed with ``EXPLAIN (...)`` and sent to the server as SQL, including backslash commands and the bare words ``exit``/``quit``, so ``\q``, ``\d``, diff --git a/pgcli/main.py b/pgcli/main.py index 3ff820ce2..21439b813 100644 --- a/pgcli/main.py +++ b/pgcli/main.py @@ -1,5 +1,5 @@ from zoneinfo import ZoneInfoNotFoundError -from configobj import ConfigObj, ParseError +from configparser import ConfigParser from pgspecial.namedqueries import NamedQueries from .config import skip_initial_comment @@ -2251,14 +2251,13 @@ def parse_service_info(service): return None, service_file with open(service_file, newline="") as f: skipped_lines = skip_initial_comment(f) - try: - service_file_config = ConfigObj(f) - except ParseError as err: - err.line_number += skipped_lines - raise err + # libpq treats values literally: hashes, commas, quotes and percent + # signs are part of the value. + service_file_config = ConfigParser(interpolation=None, delimiters=("=",), comment_prefixes=("#",)) + service_file_config.read_file(itertools.chain(itertools.repeat("\n", skipped_lines), f), source=service_file) if service not in service_file_config: return None, service_file - service_conf = service_file_config.get(service) + service_conf = service_file_config[service] return service_conf, service_file diff --git a/tests/test_main.py b/tests/test_main.py index f18319312..1f12506c3 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -21,6 +21,7 @@ get_connect_timeout, get_editor, notify_callback, + parse_service_info, PGCli, OutputSettings, COLOR_CODE_REGEX, @@ -615,6 +616,18 @@ def test_quoted_db_uri(tmpdir): mock_connect.assert_called_with(database="testdb[", host="baz.com", user="bar^", passwd="]foo") +@pytest.mark.parametrize("password", ["abc#def", "#leading", "abc #def", "abc%def", "a,b", '"quoted"', "'quoted'"]) +def test_pg_service_password_is_literal(tmp_path, monkeypatch, password): + service_file = tmp_path / "pg_service.conf" + service_file.write_text(f"# comment\n[myservice]\npassword={password}\n") + monkeypatch.setenv("PGSERVICEFILE", str(service_file)) + + config, filename = parse_service_info("myservice") + + assert filename == str(service_file) + assert config["password"] == password + + def test_pg_service_file(tmpdir): with mock.patch.object(PGCli, "connect") as mock_connect: cli = PGCli(pgclirc_file=str(tmpdir.join("rcfile")))