@@ -648,7 +648,12 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
648
648
options .Database = dbmem .New ()
649
649
options .Pubsub = pubsub .NewInMemory ()
650
650
} else {
651
- sqlDB , err := ConnectToPostgres (ctx , logger , sqlDriver , vals .PostgresURL .String ())
651
+ dbURL , err := escapePostgresURLUserInfo (vals .PostgresURL .String ())
652
+ if err != nil {
653
+ return xerrors .Errorf ("escaping postgres URL: %w" , err )
654
+ }
655
+
656
+ sqlDB , err := ConnectToPostgres (ctx , logger , sqlDriver , dbURL )
652
657
if err != nil {
653
658
return xerrors .Errorf ("connect to postgres: %w" , err )
654
659
}
@@ -657,7 +662,7 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
657
662
}()
658
663
659
664
options .Database = database .New (sqlDB )
660
- options .Pubsub , err = pubsub .New (ctx , sqlDB , vals . PostgresURL . String () )
665
+ options .Pubsub , err = pubsub .New (ctx , sqlDB , dbURL )
661
666
if err != nil {
662
667
return xerrors .Errorf ("create pubsub: %w" , err )
663
668
}
@@ -2433,3 +2438,41 @@ func parseExternalAuthProvidersFromEnv(prefix string, environ []string) ([]coder
2433
2438
}
2434
2439
return providers , nil
2435
2440
}
2441
+
2442
+ // If the user provides a postgres URL with a password that contains special
2443
+ // characters, the URL will be invalid. We need to escape the password so that
2444
+ // the URL parse doesn't fail at the DB connector level.
2445
+ func escapePostgresURLUserInfo (v string ) (string , error ) {
2446
+ _ , err := url .Parse (v )
2447
+ // I wish I could use errors.Is here, but this error is not declared as a
2448
+ // variable in net/url. :(
2449
+ if err != nil {
2450
+ if strings .Contains (err .Error (), "net/url: invalid userinfo" ) {
2451
+ // If the URL is invalid, we assume it is because the password contains
2452
+ // special characters that need to be escaped.
2453
+
2454
+ // get everything before first @
2455
+ parts := strings .SplitN (v , "@" , 2 )
2456
+ if len (parts ) != 2 {
2457
+ return "" , xerrors .Errorf ("invalid postgres url with userinfo: %s" , v )
2458
+ }
2459
+ start := parts [0 ]
2460
+ // get password, which is the last item in start when split by :
2461
+ startParts := strings .Split (start , ":" )
2462
+ password := startParts [len (startParts )- 1 ]
2463
+ // escape password, and replace the last item in the startParts slice
2464
+ // with the escaped password.
2465
+ //
2466
+ // url.PathEscape is used here because url.QueryEscape
2467
+ // will not escape spaces correctly.
2468
+ newPassword := url .PathEscape (password )
2469
+ startParts [len (startParts )- 1 ] = newPassword
2470
+ start = strings .Join (startParts , ":" )
2471
+ return start + "@" + parts [1 ], nil
2472
+ }
2473
+
2474
+ return "" , xerrors .Errorf ("parse postgres url: %w" , err )
2475
+ }
2476
+
2477
+ return v , nil
2478
+ }
0 commit comments