CrunchyData / pg_featureserv

Lightweight RESTful Geospatial Feature Server for PostGIS in Go
Apache License 2.0
459 stars 91 forks source link

A DbConnection param with a random password (with non char signs) is misinterpreted #150

Open rduivenvoorde opened 1 year ago

rduivenvoorde commented 1 year ago

I had a db connection with a random password,

So something like (I replaced alle names except the changed but lookalike passwd): DbConnection = "postgresql://user:6Ni,ron.TSDk3Y#a@host/dbname"

I was not able to start pg_featureserv with that because:

cannot parse 'postgresql://user:6Ni,ron.TSDk3Y#a@host/dbname': failed to parse as URL (parse "postgresql://user:6Ni,ron.TSDk3Y": invalid port ":6Ni,ron.TSDk3Y" after host)

Apparently the :6 made pg_featureserv think that that was a portnumber?

I understand it is difficult to find heuristics to handle this, but thought to let it know.

I was able to work around this (I'm not able to change the password...), by using a DATABASE_URL environment param:

export DATABASE_URL="host=host port=5432 dbname=dbname user=user password=6Ni,ron.TSDk3Y#a connect_timeout=10 sslmode=require"

jamesscottbrown commented 8 months ago

tl;dr - URL encode the # as %23.

Apparently the :6 made pg_featureserv think that that was a portnumber?

I think the problem is actually the #, rather than the :6.

The connection string gets passed through topgxpool.ParseConfig (in pgx/v4/pgxpool), and then to parseURLSettings in pgx/pgconn, which tries to parse it using the url.Parse function provided by the net/url package in Go's standard library.

This function expects URLs to have the scheme: [scheme:][//[userinfo@]host][/]path[?query][#fragment]. It thinks everything after the # is the fragment of the URL; as this removes the @, it doesn't expect to see any userinfo, so thinks that user:6Ni,ron.TSDk3Y is a host:port, and complains that the port in invalid.

Demo:

package main

import (
    "fmt"
    "net/url"
)

func main() {
    connString := "postgresql://user:6Ni,ron.TSDk3Y%23a@host/dbname"

    url, err := url.Parse(connString)
    if err != nil {
        fmt.Print("FAILED TO PARSE", err)
    }

    if password, present := url.User.Password(); present {
        fmt.Print("The extracted password: ", password)
    }
}

prints:

The extracted password: 6Ni,ron.TSDk3Y#a