From 0afdc2574e77733390ce2e2d450742ed4d976205 Mon Sep 17 00:00:00 2001 From: breken-ai <312387581+breken-ai@users.noreply.github.com> Date: Wed, 30 Sep 2026 00:20:57 -0700 Subject: [PATCH] fix(pg-connection-string): keep %XX escapes with hex letters when re-encoding When a connection string contains a space or a stray `%`, parse() runs it through encodeURI() and then tries to undo the double encoding of escapes that were already there. The undo regex only matched two decimal digits, so escapes such as %2F, %3A, %2B or %3f stayed double encoded and came back literally: a password `s3cr%2Ft` became "s3cr%2Ft" instead of "s3cr/t" as soon as the URL also had, for example, `options=-c search_path=app`. Match any two hex digits, case-insensitively. Co-Authored-By: Claude Opus 5.5 --- packages/pg-connection-string/index.js | 5 +++-- packages/pg-connection-string/test/parse.ts | 12 ++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/pg-connection-string/index.js b/packages/pg-connection-string/index.js index 139cc17b1..2c0cf646f 100644 --- a/packages/pg-connection-string/index.js +++ b/packages/pg-connection-string/index.js @@ -18,8 +18,9 @@ function parse(str, options = {}) { let result let dummyHost = false if (/ |%[^a-f0-9]|%[a-f0-9][^a-f0-9]/i.test(str)) { - // Ensure spaces are encoded as %20 - str = encodeURI(str).replace(/%25(\d\d)/g, '%$1') + // Ensure spaces are encoded as %20, and undo the double encoding of + // existing percent-escapes (e.g. %2F, %3a) that encodeURI also produces + str = encodeURI(str).replace(/%25([0-9a-f]{2})/gi, '%$1') } try { diff --git a/packages/pg-connection-string/test/parse.ts b/packages/pg-connection-string/test/parse.ts index 562c3ece0..9cf22e682 100644 --- a/packages/pg-connection-string/test/parse.ts +++ b/packages/pg-connection-string/test/parse.ts @@ -125,6 +125,18 @@ describe('parse', function () { subject.database?.should.equal(' u%20rl') }) + it('keeps percent-encoded characters when the url also contains a space', function () { + const subject = parse('postgres://app:s3cr%2Ft%3A@localhost/db?options=-c search_path=app') + subject.password?.should.equal('s3cr/t:') + subject.options?.should.equal('-c search_path=app') + }) + + it('keeps lowercase percent-encoded characters when the url also contains a stray percent sign', function () { + const subject = parse('postgres://app:s3cr%2bt@localhost/50%off') + subject.password?.should.equal('s3cr+t') + subject.database?.should.equal('50%off') + }) + it('relative url sets database', function () { const relative = 'different_db_on_default_host' const subject = parse(relative)