@@ -27,15 +27,162 @@ package main
27
27
28
28
import (
29
29
"fmt"
30
+
"net/url"
30
31
"os"
31
32
"sort"
32
33
"strings"
33
34
"unicode"
34
35
)
35
36
36
-
// parseConnInfo parses and converts a key=value connection string of
37
+
const (
38
+
CI_KEYVAL int = iota
39
+
CI_URI
40
+
)
41
+
42
+
type ConnInfo struct {
43
+
Kind int // See CI_* constants
44
+
Infos map[string]string
45
+
}
46
+
47
+
func parseConnInfo(connstring string) (*ConnInfo, error) {
48
+
c := ConnInfo{}
49
+
50
+
if strings.HasPrefix(connstring, "postgresql://") {
51
+
c.Kind = CI_URI
52
+
i, err := parseUrlConnInfo(connstring)
53
+
if err != nil {
54
+
return nil, err
55
+
}
56
+
c.Infos = i
57
+
return &c, nil
58
+
}
59
+
60
+
if strings.Contains(connstring, "=") {
61
+
c.Kind = CI_KEYVAL
62
+
i, err := parseKeywordConnInfo(connstring)
63
+
if err != nil {
64
+
return nil, err
65
+
}
66
+
c.Infos = i
67
+
return &c, nil
68
+
}
69
+
70
+
return nil, fmt.Errorf("parseConnInfo: invalid input connection string")
71
+
}
72
+
73
+
func (c *ConnInfo) String() string {
74
+
switch c.Kind {
75
+
case CI_KEYVAL:
76
+
return makeKeywordConnInfo(c.Infos)
77
+
case CI_URI:
78
+
return makeUrlConnInfo(c.Infos)
79
+
}
80
+
81
+
return ""
82
+
}
83
+
84
+
func (c *ConnInfo) Copy() *ConnInfo {
85
+
newC := ConnInfo{
86
+
Kind: c.Kind,
87
+
Infos: make(map[string]string, len(c.Infos)),
88
+
}
89
+
90
+
for k, v := range c.Infos {
91
+
newC.Infos[k] = v
92
+
}
93
+
94
+
return &newC
95
+
}
96
+
97
+
// Set returns a pointer to a full copy of the conninfo with the key added or
98
+
// the value updated
99
+
func (c *ConnInfo) Set(keyword, value string) *ConnInfo {
100
+
newC := c.Copy()
101
+
newC.Infos[keyword] = value
102
+
103
+
return newC
104
+
}
105
+
106
+
// Del returns a pointer to a full copy of the conninfo with the key removed
107
+
func (c *ConnInfo) Del(keyword string) *ConnInfo {
108
+
newC := c.Copy()
109
+
delete(newC.Infos, keyword)
110
+
111
+
return newC
112
+
}
113
+
114
+
func parseUrlConnInfo(connstring string) (map[string]string, error) {
115
+
u, err := url.Parse(connstring)
116
+
if err != nil {
117
+
return nil, fmt.Errorf("parsing of URI conninfo failed: %w", err)
118
+
}
119
+
120
+
connInfo := make(map[string]string, 0)
121
+
if u.Host != "" {
122
+
fullHosts := strings.Split(u.Host, ",")
123
+
if len(fullHosts) == 1 {
124
+
v := u.Hostname()
125
+
if v != "" {
126
+
connInfo["host"] = v
127
+
}
128
+
v = u.Port()
129
+
if v != "" {
130
+
connInfo["port"] = v
131
+
}
132
+
} else {
133
+
// We need to split and group hosts and ports
134
+
// ourselves, net/url does not handle multiple hosts
135
+
// correctly
136
+
hosts := make([]string, 0)
137
+
ports := make([]string, 0)
138
+
for _, fullHost := range fullHosts {
139
+
hostPort := make([]string, 0)
140
+
if strings.HasPrefix(fullHost, "[") {
141
+
// Handle literal IPv6 addresses
142
+
hostPort = strings.Split(strings.TrimPrefix(fullHost, "["), "]:")
143
+
} else {
144
+
hostPort = strings.Split(fullHost, ":")
145
+
}
146
+
if len(hostPort) == 1 {
147
+
hosts = append(hosts, strings.Trim(hostPort[0], "[]"))
148
+
} else {
149
+
hosts = append(hosts, strings.Trim(hostPort[0], "[]"))
150
+
ports = append(ports, hostPort[1])
151
+
}
152
+
}
153
+
connInfo["host"] = strings.Join(hosts, ",")
154
+
connInfo["port"] = strings.Join(ports, ",")
155
+
}
156
+
}
157
+
158
+
user := u.User.Username()
159
+
if user != "" {
160
+
connInfo["user"] = user
161
+
}
162
+
163
+
password, set := u.User.Password()
164
+
if password != "" && set {
165
+
connInfo["password"] = password
166
+
}
167
+
168
+
dbname := strings.TrimPrefix(u.Path, "/")
169
+
if dbname != "" {
170
+
connInfo["dbname"] = dbname
171
+
}
172
+
173
+
for k, vs := range u.Query() {
174
+
if k == "" {
175
+
continue
176
+
}
177
+
connInfo[k] = strings.Join(vs, ",")
178
+
}
179
+
180
+
return connInfo, nil
181
+
}
182
+
183
+
// parseKeywordConnInfo parses and converts a key=value connection string of
37
184
// PostgreSQL into a map
38
-
func parseConnInfo(connstring string) (map[string]string, error) {
185
+
func parseKeywordConnInfo(connstring string) (map[string]string, error) {
39
186
40
187
// Structure to hold the state of the parsing
41
188
s := struct {
@@ -173,7 +320,7 @@ func parseConnInfo(connstring string) (map[string]string, error) {
173
320
return pairs, nil
174
321
}
175
322
176
-
func makeConnInfo(infos map[string]string) string {
323
+
func makeKeywordConnInfo(infos map[string]string) string {
177
324
conninfo := ""
178
325
179
326
// Map keys are randomized, sort them so that the output is always the
@@ -204,42 +351,148 @@ func makeConnInfo(infos map[string]string) string {
204
351
return conninfo
205
352
}
206
353
207
-
func prepareConnInfo(host string, port int, username string, dbname string) string {
208
-
var conninfo string
354
+
func makeUrlConnInfo(infos map[string]string) string {
355
+
u := &url.URL{
356
+
Scheme: "postgresql",
357
+
}
358
+
359
+
// create user info
360
+
username, hasUser := infos["user"]
361
+
pass, hasPass := infos["password"]
362
+
363
+
var user *url.Userinfo
364
+
if hasPass {
365
+
user = url.UserPassword(username, pass)
366
+
} else if hasUser {
367
+
user = url.User(username)
368
+
}
369
+
u.User = user
370
+
371
+
// Manage host:port list with commas. When the hosts is a unix socket
372
+
// directory, do not set the Host field of the url because it won't be
373
+
// percent encoded, use the query part instead
374
+
if !strings.Contains(infos["host"], "/") {
375
+
hosts := strings.Split(infos["host"], ",")
376
+
ports := strings.Split(infos["port"], ",")
377
+
378
+
// Ensure we have lists of the same size to build host:port in a loop
379
+
if len(hosts) > len(ports) {
380
+
if len(ports) == 1 {
381
+
// same non default port for all hosts, duplicate it
382
+
// for the next loop
383
+
if ports[0] != "" {
384
+
for i := 0; i < len(hosts); i++ {
385
+
ports = append(ports, ports[0])
386
+
}
387
+
}
388
+
} else {
389
+
// fill with empty port to fix the list
390
+
for i := 0; i < len(hosts); i++ {
391
+
ports = append(ports, "")
392
+
}
393
+
}
394
+
}
395
+
396
+
hostnames := make([]string, 0, len(hosts))
209
397
210
-
// dbname may be a connstring. The database name option, usually -d for
211
-
// PostgreSQL binaires accept a connection string. We do a simple check
212
-
// for a = sign. If someone has a database name containing a space, one
213
-
// can still dump it by giving us connstring.
214
-
if strings.Contains(dbname, "=") {
215
-
conninfo = fmt.Sprintf("%v ", dbname)
398
+
for i, host := range hosts {
399
+
// Take care of IPv6 addresses
400
+
if strings.Contains(host, ":") && !strings.HasPrefix(host, "[") {
401
+
host = "[" + host + "]"
402
+
}
403
+
404
+
if ports[i] != "" {
405
+
hostnames = append(hostnames, host+":"+ports[i])
406
+
} else {
407
+
hostnames = append(hostnames, host)
408
+
}
409
+
}
410
+
411
+
u.Host = strings.Join(hostnames, ",")
412
+
}
413
+
414
+
// dbname
415
+
u.Path = "/" + infos["dbname"]
416
+
u.RawPath = "/" + url.PathEscape(infos["dbname"])
417
+
418
+
// compute query
419
+
query := url.Values{}
420
+
needPort := false
421
+
422
+
// Sort keys so that host comes before port and we can add port to the
423
+
// query when we are forced to add host to the query (unix socket
424
+
// directory) in the next loop
425
+
keys := make([]string, 0, len(infos))
426
+
for k := range infos {
427
+
keys = append(keys, k)
428
+
}
429
+
sort.Strings(keys)
430
+
431
+
for _, k := range keys {
432
+
if k == "host" && strings.Contains(infos[k], "/") || k == "port" && needPort {
433
+
needPort = true
434
+
query.Set(k, infos[k])
435
+
continue
436
+
}
437
+
438
+
if k != "host" && k != "port" && k != "user" && k != "password" && k != "dbname" {
439
+
query.Set(k, infos[k])
440
+
}
441
+
}
442
+
u.RawQuery = query.Encode()
443
+
444
+
return u.String()
445
+
}
446
+
447
+
// prepareConnInfo returns a connexion string computed from the input
448
+
// values. When the dbname is already a connection string or a postgresql://
449
+
// URI, it only add the application_name keyword if not set.
450
+
func prepareConnInfo(host string, port int, username string, dbname string) (*ConnInfo, error) {
451
+
var (
452
+
conninfo *ConnInfo
453
+
err error
454
+
)
455
+
456
+
// dbname may be a connstring or a URI. The database name option,
457
+
// usually -d for PostgreSQL binaires accept a connection string and
458
+
// URIs. We do a simple check for a = sign or the postgresql scheme. If
459
+
// someone has a database name containing a space, one can still dump
460
+
// it by giving us connstring.
461
+
if strings.HasPrefix(dbname, "postgresql://") || strings.Contains(dbname, "=") {
462
+
conninfo, err = parseConnInfo(dbname)
463
+
if err != nil {
464
+
return nil, err
465
+
}
216
466
} else {
467
+
conninfo = &ConnInfo{
468
+
Infos: make(map[string]string),
469
+
}
217
470
218
471
if host != "" {
219
-
conninfo += fmt.Sprintf("host=%v ", host)
472
+
conninfo.Infos["host"] = host
220
473
} else {
221
474
// driver lib/pq defaults to localhost for the host, so
222
475
// we have to check PGHOST and fallback to the unix
223
476
// socket directory to avoid overriding PGHOST
224
477
if _, ok := os.LookupEnv("PGHOST"); !ok {
225
-
conninfo += "host=/var/run/postgresql "
478
+
conninfo.Infos["host"] = "/var/run/postgresql"
226
479
}
227
480
}
228
481
if port != 0 {
229
-
conninfo += fmt.Sprintf("port=%v ", port)
482
+
conninfo.Infos["port"] = fmt.Sprintf("%v", port)
230
483
}
231
484
if username != "" {
232
-
conninfo += fmt.Sprintf("user=%v ", username)
485
+
conninfo.Infos["user"] = username
233
486
}
234
487
if dbname != "" {
235
-
conninfo += fmt.Sprintf("dbname=%v ", dbname)
488
+
conninfo.Infos["dbname"] = dbname
236
489
}
237
490
}
238
491
239
-
if !strings.Contains(conninfo, "application_name") {
492
+
if _, ok := conninfo.Infos["application_name"]; !ok {
240
493
l.Verboseln("using pg_back as application_name")
241
-
conninfo += "application_name=pg_back"
494
+
conninfo.Infos["application_name"] = "pg_back"
242
495
}
243
496
244
-
return conninfo
497
+
return conninfo, nil
245
498
}
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4