@@ -20,6 +20,7 @@ import (
2020 "os"
2121 "regexp"
2222 "strings"
23+ "unicode"
2324
2425 "github.com/go-kit/log/level"
2526 "github.com/prometheus/client_golang/prometheus"
@@ -172,3 +173,196 @@ func getDataSources() ([]string, error) {
172173
173174 return []string {dsn }, nil
174175}
176+
177+ // dsn represents a parsed datasource. It contains fields for the individual connection components.
178+ type dsn struct {
179+ scheme string
180+ username string
181+ password string
182+ host string
183+ path string
184+ query string
185+ }
186+
187+ // String makes a dsn safe to print by excluding any passwords. This allows dsn to be used in
188+ // strings and log messages without needing to call a redaction function first.
189+ func (d dsn ) String () string {
190+ if d .password != "" {
191+ return fmt .Sprintf ("%s://%s:******@%s%s?%s" , d .scheme , d .username , d .host , d .path , d .query )
192+ }
193+
194+ if d .username != "" {
195+ return fmt .Sprintf ("%s://%s@%s%s?%s" , d .scheme , d .username , d .host , d .path , d .query )
196+ }
197+
198+ return fmt .Sprintf ("%s://%s%s?%s" , d .scheme , d .host , d .path , d .query )
199+ }
200+
201+ // dsnFromString parses a connection string into a dsn. It will attempt to parse the string as
202+ // a URL and as a set of key=value pairs. If both attempts fail, dsnFromString will return an error.
203+ func dsnFromString (in string ) (dsn , error ) {
204+ if strings .HasPrefix (in , "postgresql://" ) {
205+ return dsnFromURL (in )
206+ }
207+
208+ // Try to parse as key=value pairs
209+ d , err := dsnFromKeyValue (in )
210+ if err == nil {
211+ return d , nil
212+ }
213+
214+ return dsn {}, fmt .Errorf ("could not understand DSN" )
215+ }
216+
217+ // dsnFromURL parses the input as a URL and returns the dsn representation.
218+ func dsnFromURL (in string ) (dsn , error ) {
219+ u , err := url .Parse (in )
220+ if err != nil {
221+ return dsn {}, err
222+ }
223+ pass , _ := u .User .Password ()
224+ user := u .User .Username ()
225+
226+ query := u .Query ()
227+
228+ if queryPass := query .Get ("password" ); queryPass != "" {
229+ if pass == "" {
230+ pass = queryPass
231+ }
232+ }
233+ query .Del ("password" )
234+
235+ if queryUser := query .Get ("user" ); queryUser != "" {
236+ if user == "" {
237+ user = queryUser
238+ }
239+ }
240+ query .Del ("user" )
241+
242+ d := dsn {
243+ scheme : u .Scheme ,
244+ username : user ,
245+ password : pass ,
246+ host : u .Host ,
247+ path : u .Path ,
248+ query : query .Encode (),
249+ }
250+
251+ return d , nil
252+ }
253+
254+ // dsnFromKeyValue parses the input as a set of key=value pairs and returns the dsn representation.
255+ func dsnFromKeyValue (in string ) (dsn , error ) {
256+ // Attempt to confirm at least one key=value pair before starting the rune parser
257+ connstringRe := regexp .MustCompile (`^ *[a-zA-Z0-9]+ *= *[^= ]+` )
258+ if ! connstringRe .MatchString (in ) {
259+ return dsn {}, fmt .Errorf ("input is not a key-value DSN" )
260+ }
261+
262+ // Anything other than known fields should be part of the querystring
263+ query := url.Values {}
264+
265+ pairs , err := parseKeyValue (in )
266+ if err != nil {
267+ return dsn {}, fmt .Errorf ("failed to parse key-value DSN: %v" , err )
268+ }
269+
270+ // Build the dsn from the key=value pairs
271+ d := dsn {
272+ scheme : "postgresql" ,
273+ }
274+
275+ hostname := ""
276+ port := ""
277+
278+ for k , v := range pairs {
279+ switch k {
280+ case "host" :
281+ hostname = v
282+ case "port" :
283+ port = v
284+ case "user" :
285+ d .username = v
286+ case "password" :
287+ d .password = v
288+ default :
289+ query .Set (k , v )
290+ }
291+ }
292+
293+ if hostname == "" {
294+ hostname = "localhost"
295+ }
296+
297+ if port == "" {
298+ d .host = hostname
299+ } else {
300+ d .host = fmt .Sprintf ("%s:%s" , hostname , port )
301+ }
302+
303+ d .query = query .Encode ()
304+
305+ return d , nil
306+ }
307+
308+ // parseKeyValue is a key=value parser. It loops over each rune to split out keys and values
309+ // and attempting to honor quoted values. parseKeyValue will return an error if it is unable
310+ // to properly parse the input.
311+ func parseKeyValue (in string ) (map [string ]string , error ) {
312+ out := map [string ]string {}
313+
314+ inPart := false
315+ inQuote := false
316+ part := []rune {}
317+ key := ""
318+ for _ , c := range in {
319+ switch {
320+ case unicode .In (c , unicode .Quotation_Mark ):
321+ if inQuote {
322+ inQuote = false
323+ } else {
324+ inQuote = true
325+ }
326+ case unicode .In (c , unicode .White_Space ):
327+ if inPart {
328+ if inQuote {
329+ part = append (part , c )
330+ } else {
331+ // Are we finishing a key=value?
332+ if key == "" {
333+ return out , fmt .Errorf ("invalid input" )
334+ }
335+ out [key ] = string (part )
336+ inPart = false
337+ part = []rune {}
338+ }
339+ } else {
340+ // Are we finishing a key=value?
341+ if key == "" {
342+ return out , fmt .Errorf ("invalid input" )
343+ }
344+ out [key ] = string (part )
345+ inPart = false
346+ part = []rune {}
347+ // Do something with the value
348+ }
349+ case c == '=' :
350+ if inPart {
351+ inPart = false
352+ key = string (part )
353+ part = []rune {}
354+ } else {
355+ return out , fmt .Errorf ("invalid input" )
356+ }
357+ default :
358+ inPart = true
359+ part = append (part , c )
360+ }
361+ }
362+
363+ if key != "" && len (part ) > 0 {
364+ out [key ] = string (part )
365+ }
366+
367+ return out , nil
368+ }
0 commit comments