From 99f22ac8e4c9c142d9541ab648274e7663357fab Mon Sep 17 00:00:00 2001 From: Jack Christensen Date: Sat, 14 Sep 2019 18:37:33 -0500 Subject: [PATCH] Port DSN parser from pgx v3 Original implementation: 2d9d8dc52ac211c6191c08e050c03588aa633038 by Joshua Barone . Also changed DSN tests to use "dbname" as key rather than "database" as that is what the PostgreSQL documentation specifies. "database" still actually works but it should not be encouraged as it is non-standard. --- .travis.yml | 8 ++--- README.md | 2 +- config.go | 61 ++++++++++++++++++++++++++++++++++--- config_test.go | 82 +++++++++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 139 insertions(+), 14 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2c547abf..abff8515 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,15 +17,15 @@ env: - GOPROXY=https://proxy.golang.org - GOFLAGS=-mod=readonly - PGX_TEST_CONN_STRING=postgres://pgx_md5:secret@127.0.0.1/pgx_test - - PGX_TEST_UNIX_SOCKET_CONN_STRING="host=/var/run/postgresql database=pgx_test" + - PGX_TEST_UNIX_SOCKET_CONN_STRING="host=/var/run/postgresql dbname=pgx_test" - PGX_TEST_TCP_CONN_STRING=postgres://pgx_md5:secret@127.0.0.1/pgx_test - PGX_TEST_TLS_CONN_STRING=postgres://pgx_md5:secret@127.0.0.1/pgx_test?sslmode=require - PGX_TEST_MD5_PASSWORD_CONN_STRING=postgres://pgx_md5:secret@127.0.0.1/pgx_test - PGX_TEST_PLAIN_PASSWORD_CONN_STRING=postgres://pgx_pw:secret@127.0.0.1/pgx_test matrix: - - CRATEVERSION=2.1 PGX_TEST_CRATEDB_CONN_STRING="host=127.0.0.1 port=6543 user=pgx database=pgx_test" - - PGVERSION=10 PGX_TEST_REPLICATION_CONN_STRING="host=127.0.0.1 port=6543 user=pgx_replication password=secret database=pgx_test" - - PGVERSION=9.6 PGX_TEST_REPLICATION_CONN_STRING="host=127.0.0.1 port=6543 user=pgx_replication password=secret database=pgx_test" + - CRATEVERSION=2.1 PGX_TEST_CRATEDB_CONN_STRING="host=127.0.0.1 port=6543 user=pgx dbname=pgx_test" + - PGVERSION=10 PGX_TEST_REPLICATION_CONN_STRING="host=127.0.0.1 port=6543 user=pgx_replication password=secret dbname=pgx_test" + - PGVERSION=9.6 PGX_TEST_REPLICATION_CONN_STRING="host=127.0.0.1 port=6543 user=pgx_replication password=secret dbname=pgx_test" - PGVERSION=9.5 - PGVERSION=9.4 - PGVERSION=9.3 diff --git a/README.md b/README.md index 9e35a0f5..aa980b6d 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ create database pgx_test; Now you can run the tests: ``` -PGX_TEST_CONN_STRING="host=/var/run/postgresql database=pgx_test" go test ./... +PGX_TEST_CONN_STRING="host=/var/run/postgresql dbname=pgx_test" go test ./... ``` ### Connection and Authentication Tests diff --git a/config.go b/config.go index 2ec6ae3f..6eb0065a 100644 --- a/config.go +++ b/config.go @@ -13,7 +13,6 @@ import ( "os" "os/user" "path/filepath" - "regexp" "strconv" "strings" "time" @@ -389,13 +388,65 @@ func addURLSettings(settings map[string]string, connString string) error { return nil } -var dsnRegexp = regexp.MustCompile(`([a-zA-Z_]+)=((?:"[^"]+")|(?:[^ ]+))`) +var asciiSpace = [256]uint8{'\t': 1, '\n': 1, '\v': 1, '\f': 1, '\r': 1, ' ': 1} func addDSNSettings(settings map[string]string, s string) error { - m := dsnRegexp.FindAllStringSubmatch(s, -1) + nameMap := map[string]string{ + "dbname": "database", + } - for _, b := range m { - settings[b[1]] = b[2] + for len(s) > 0 { + var key, val string + eqIdx := strings.IndexRune(s, '=') + if eqIdx < 0 { + return errors.New("invalid dsn") + } + + key = strings.Trim(s[:eqIdx], " \t\n\r\v\f") + s = strings.TrimLeft(s[eqIdx+1:], " \t\n\r\v\f") + if s[0] != '\'' { + end := 0 + for ; end < len(s); end++ { + if asciiSpace[s[end]] == 1 { + break + } + if s[end] == '\\' { + end++ + } + } + val = strings.Replace(strings.Replace(s[:end], "\\\\", "\\", -1), "\\'", "'", -1) + if end == len(s) { + s = "" + } else { + s = s[end+1:] + } + } else { // quoted string + s = s[1:] + end := 0 + for ; end < len(s); end++ { + if s[end] == '\'' { + break + } + if s[end] == '\\' { + end++ + } + } + if end == len(s) { + return errors.New("unterminated quoted string in connection info string") + } + val = strings.Replace(strings.Replace(s[:end], "\\\\", "\\", -1), "\\'", "'", -1) + if end == len(s) { + s = "" + } else { + s = s[end+1:] + } + } + + if k, ok := nameMap[key]; ok { + key = k + } + + settings[key] = val } return nil diff --git a/config_test.go b/config_test.go index 090302a2..9eb5df2f 100644 --- a/config_test.go +++ b/config_test.go @@ -228,7 +228,7 @@ func TestParseConfig(t *testing.T) { }, { name: "DSN everything", - connString: "user=jack password=secret host=localhost port=5432 database=mydb sslmode=disable application_name=pgxtest search_path=myschema", + connString: "user=jack password=secret host=localhost port=5432 dbname=mydb sslmode=disable application_name=pgxtest search_path=myschema", config: &pgconn.Config{ User: "jack", Password: "secret", @@ -242,6 +242,80 @@ func TestParseConfig(t *testing.T) { }, }, }, + { + name: "DSN with escaped single quote", + connString: "user=jack\\'s password=secret host=localhost port=5432 dbname=mydb sslmode=disable", + config: &pgconn.Config{ + User: "jack's", + Password: "secret", + Host: "localhost", + Port: 5432, + Database: "mydb", + TLSConfig: nil, + RuntimeParams: map[string]string{}, + }, + }, + { + name: "DSN with escaped backslash", + connString: "user=jack password=sooper\\\\secret host=localhost port=5432 dbname=mydb sslmode=disable", + config: &pgconn.Config{ + User: "jack", + Password: "sooper\\secret", + Host: "localhost", + Port: 5432, + Database: "mydb", + TLSConfig: nil, + RuntimeParams: map[string]string{}, + }, + }, + { + name: "DSN with single quoted values", + connString: "user='jack' host='localhost' dbname='mydb' sslmode='disable'", + config: &pgconn.Config{ + User: "jack", + Host: "localhost", + Port: 5432, + Database: "mydb", + TLSConfig: nil, + RuntimeParams: map[string]string{}, + }, + }, + { + name: "DSN with single quoted value with escaped single quote", + connString: "user='jack\\'s' host='localhost' dbname='mydb' sslmode='disable'", + config: &pgconn.Config{ + User: "jack's", + Host: "localhost", + Port: 5432, + Database: "mydb", + TLSConfig: nil, + RuntimeParams: map[string]string{}, + }, + }, + { + name: "DSN with empty single quoted value", + connString: "user='jack' password='' host='localhost' dbname='mydb' sslmode='disable'", + config: &pgconn.Config{ + User: "jack", + Host: "localhost", + Port: 5432, + Database: "mydb", + TLSConfig: nil, + RuntimeParams: map[string]string{}, + }, + }, + { + name: "DSN with space between key and value", + connString: "user = 'jack' password = '' host = 'localhost' dbname = 'mydb' sslmode='disable'", + config: &pgconn.Config{ + User: "jack", + Host: "localhost", + Port: 5432, + Database: "mydb", + TLSConfig: nil, + RuntimeParams: map[string]string{}, + }, + }, { name: "URL multiple hosts", connString: "postgres://jack:secret@foo,bar,baz/mydb?sslmode=disable", @@ -294,7 +368,7 @@ func TestParseConfig(t *testing.T) { }, { name: "DSN multiple hosts one port", - connString: "user=jack password=secret host=foo,bar,baz port=5432 database=mydb sslmode=disable", + connString: "user=jack password=secret host=foo,bar,baz port=5432 dbname=mydb sslmode=disable", config: &pgconn.Config{ User: "jack", Password: "secret", @@ -319,7 +393,7 @@ func TestParseConfig(t *testing.T) { }, { name: "DSN multiple hosts multiple ports", - connString: "user=jack password=secret host=foo,bar,baz port=1,2,3 database=mydb sslmode=disable", + connString: "user=jack password=secret host=foo,bar,baz port=1,2,3 dbname=mydb sslmode=disable", config: &pgconn.Config{ User: "jack", Password: "secret", @@ -344,7 +418,7 @@ func TestParseConfig(t *testing.T) { }, { name: "multiple hosts and fallback tsl", - connString: "user=jack password=secret host=foo,bar,baz database=mydb sslmode=prefer", + connString: "user=jack password=secret host=foo,bar,baz dbname=mydb sslmode=prefer", config: &pgconn.Config{ User: "jack", Password: "secret",