diff --git a/frontend/src/lib/utils/postgresConnectionString.test.ts b/frontend/src/lib/utils/postgresConnectionString.test.ts index 33efc16797..e385195447 100644 --- a/frontend/src/lib/utils/postgresConnectionString.test.ts +++ b/frontend/src/lib/utils/postgresConnectionString.test.ts @@ -138,16 +138,13 @@ describe('unsupportedConnectionParam', () => { expect(parsePostgresConnectionString(disguised)?.sslmode).toBeUndefined() }) - // libpq matches parameter names case-insensitively. Folding in one reader and not the other - // is what lets a name through the allowlist and past the parser, so the string is saved as - // whatever the default happens to be rather than what it asked for. - it('reads a parameter whatever its case', () => { + // libpq rejects `?SslMode=` as an invalid URI query parameter rather than folding it, so a + // string carrying one does not connect anywhere. Naming it is the honest answer; honouring + // it would save a resource from a URI Postgres itself refuses. + it('refuses a parameter whose name is not the one libpq accepts', () => { const shouted = 'postgres://u:p@h/db?SslMode=verify-full' - expect(unsupportedConnectionParam(shouted)).toBeUndefined() - expect(parsePostgresConnectionString(shouted)?.sslmode).toBe('verify-full') - expect(unsupportedConnectionParam('postgres://u:p@h/db?Connect_Timeout=1')).toBe( - 'connect_timeout' - ) + expect(unsupportedConnectionParam(shouted)).toBe('SslMode') + expect(parsePostgresConnectionString(shouted)?.sslmode).toBeUndefined() }) // libpq takes the last of a repeated parameter. Taking the first reads a weaker mode than diff --git a/frontend/src/lib/utils/postgresConnectionString.ts b/frontend/src/lib/utils/postgresConnectionString.ts index e4da0825c9..9810a43ee3 100644 --- a/frontend/src/lib/utils/postgresConnectionString.ts +++ b/frontend/src/lib/utils/postgresConnectionString.ts @@ -25,16 +25,17 @@ const CONNECTION_STRING = /postgres(?:ql)?:\/\/(?[^:@]+)(?::(?[^@]+))?@(?\[[^\]]+\]|[^:\/?]+)(?::(?\d+))?\/(?[^\?]+)?/ /** - * The query parameters, read the way libpq reads them: names are case-insensitive, and a name + * The query parameters, read the way libpq reads them: names are case-sensitive — `SslMode` is + * rejected outright as an invalid URI query parameter, not folded to `sslmode` — and a name * repeated takes its last value. One reader for both the parser and the allowlist below, or - * they disagree about what a string says — a name only one of them folds is refused by neither - * and honoured by neither. + * they disagree about what a string says and a name is refused by neither and honoured by + * neither. */ function paramsOf(connectionString: string): Map { const query = connectionString.split('?').slice(1).join('?') const params = new Map() if (!query) return params - new URLSearchParams(query).forEach((value, name) => params.set(name.toLowerCase(), value)) + new URLSearchParams(query).forEach((value, name) => params.set(name, value)) return params }