diff --git a/cli/server.go b/cli/server.go index 548f350bae9bc..59b46a5726d75 100644 --- a/cli/server.go +++ b/cli/server.go @@ -2565,6 +2565,8 @@ func parseExternalAuthProvidersFromEnv(prefix string, environ []string) ([]coder return providers, nil } +var reInvalidPortAfterHost = regexp.MustCompile(`invalid port ".+" after host`) + // If the user provides a postgres URL with a password that contains special // characters, the URL will be invalid. We need to escape the password so that // the URL parse doesn't fail at the DB connector level. @@ -2573,7 +2575,11 @@ func escapePostgresURLUserInfo(v string) (string, error) { // I wish I could use errors.Is here, but this error is not declared as a // variable in net/url. :( if err != nil { - if strings.Contains(err.Error(), "net/url: invalid userinfo") { + // Warning: The parser may also fail with an "invalid port" error if the password contains special + // characters. It does not detect invalid user information but instead incorrectly reports an invalid port. + // + // See: https://github.com/coder/coder/issues/16319 + if strings.Contains(err.Error(), "net/url: invalid userinfo") || reInvalidPortAfterHost.MatchString(err.Error()) { // If the URL is invalid, we assume it is because the password contains // special characters that need to be escaped. diff --git a/cli/server_internal_test.go b/cli/server_internal_test.go index 4bdf54f4f0583..b5417ceb04b8e 100644 --- a/cli/server_internal_test.go +++ b/cli/server_internal_test.go @@ -351,13 +351,23 @@ func TestEscapePostgresURLUserInfo(t *testing.T) { output: "", err: xerrors.New("parse postgres url: parse \"postgres://local host:5432/coder\": invalid character \" \" in host name"), }, + { + input: "postgres://coder:co?der@localhost:5432/coder", + output: "postgres://coder:co%3Fder@localhost:5432/coder", + err: nil, + }, + { + input: "postgres://coder:co#der@localhost:5432/coder", + output: "postgres://coder:co%23der@localhost:5432/coder", + err: nil, + }, } for _, tc := range testcases { tc := tc t.Run(tc.input, func(t *testing.T) { t.Parallel() o, err := escapePostgresURLUserInfo(tc.input) - require.Equal(t, tc.output, o) + assert.Equal(t, tc.output, o) if tc.err != nil { require.Error(t, err) require.EqualValues(t, tc.err.Error(), err.Error())