forked from elastic/beats
-
Notifications
You must be signed in to change notification settings - Fork 1
/
url.go
191 lines (166 loc) · 4.78 KB
/
url.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
package parse
import (
"fmt"
"net"
"net/url"
"strings"
"github.com/elastic/beats/metricbeat/mb"
"github.com/pkg/errors"
)
// URLHostParserBuilder builds a tailored HostParser for used with host strings
// that are URLs.
type URLHostParserBuilder struct {
PathConfigKey string
DefaultPath string
DefaultScheme string
QueryParams string
}
// Build returns a new HostParser function whose behavior is influenced by the
// options set in URLHostParserBuilder.
func (b URLHostParserBuilder) Build() mb.HostParser {
return func(module mb.Module, host string) (mb.HostData, error) {
conf := map[string]interface{}{}
err := module.UnpackConfig(conf)
if err != nil {
return mb.HostData{}, err
}
var user, pass, path string
t, ok := conf["username"]
if ok {
user, ok = t.(string)
if !ok {
return mb.HostData{}, errors.Errorf("'username' config for module %v is not a string", module.Name())
}
}
t, ok = conf["password"]
if ok {
pass, ok = t.(string)
if !ok {
return mb.HostData{}, errors.Errorf("'password' config for module %v is not a string", module.Name())
}
}
t, ok = conf[b.PathConfigKey]
if ok {
path, ok = t.(string)
if !ok {
return mb.HostData{}, errors.Errorf("'%v' config for module %v is not a string", b.PathConfigKey, module.Name())
}
} else {
path = b.DefaultPath
}
return ParseURL(host, b.DefaultScheme, user, pass, path, b.QueryParams)
}
}
// NewHostDataFromURL returns a new HostData based on the contents of the URL.
// If the URLs scheme is "unix" or end is "unix" (e.g. "http+unix://") then
// the HostData.Host field is set to the URLs path instead of the URLs host.
func NewHostDataFromURL(u *url.URL) mb.HostData {
var user, pass string
if u.User != nil {
user = u.User.Username()
pass, _ = u.User.Password()
}
host := u.Host
if strings.HasSuffix(u.Scheme, "unix") {
host = u.Path
}
return mb.HostData{
URI: u.String(),
SanitizedURI: redactURLCredentials(u).String(),
Host: host,
User: user,
Password: pass,
}
}
// ParseURL returns HostData object from a raw 'host' value and a series of
// defaults that are added to the URL if not present in the rawHost value.
// Values from the rawHost take precedence over the defaults.
func ParseURL(rawHost, scheme, user, pass, path, query string) (mb.HostData, error) {
u, err := getURL(rawHost, scheme, user, pass, path, query)
if err != nil {
return mb.HostData{}, err
}
return NewHostDataFromURL(u), nil
}
// SetURLUser set the user credentials in the given URL. If the username or
// password is not set in the URL then the default is used (if provided).
func SetURLUser(u *url.URL, defaultUser, defaultPass string) {
var user, pass string
var userIsSet, passIsSet bool
if u.User != nil {
user = u.User.Username()
if user != "" {
userIsSet = true
}
pass, passIsSet = u.User.Password()
}
if !userIsSet && defaultUser != "" {
userIsSet = true
user = defaultUser
}
if !passIsSet && defaultPass != "" {
passIsSet = true
pass = defaultPass
}
if userIsSet && passIsSet {
u.User = url.UserPassword(user, pass)
} else if userIsSet {
u.User = url.User(user)
}
}
// getURL constructs a URL from the rawHost value and adds the provided user,
// password, path, and query params if one was not set in the rawURL value.
func getURL(rawURL, scheme, username, password, path, query string) (*url.URL, error) {
if parts := strings.SplitN(rawURL, "://", 2); len(parts) != 2 {
// Add scheme.
rawURL = fmt.Sprintf("%s://%s", scheme, rawURL)
}
u, err := url.Parse(rawURL)
if err != nil {
return nil, fmt.Errorf("error parsing URL: %v", err)
}
SetURLUser(u, username, password)
if !strings.HasSuffix(u.Scheme, "unix") {
if u.Host == "" {
return nil, fmt.Errorf("error parsing URL: empty host")
}
// Validate the host. The port is optional.
host, _, err := net.SplitHostPort(u.Host)
if err != nil {
if strings.Contains(err.Error(), "missing port") {
host = u.Host
} else {
return nil, fmt.Errorf("error parsing URL: %v", err)
}
}
if host == "" {
return nil, fmt.Errorf("error parsing URL: empty host")
}
}
if u.Path == "" && path != "" {
// The path given in the host config takes precedence over the
// default path.
if !strings.HasPrefix(path, "/") {
path = "/" + path
}
u.Path = path
}
// Add the query params to existing query parameters overwriting any
// keys that already exist.
q := u.Query()
params, err := url.ParseQuery(query)
for key, values := range params {
for _, v := range values {
q.Set(key, v)
}
}
u.RawQuery = q.Encode()
return u, nil
}
// redactURLCredentials returns the URL as a string with the username and
// password redacted.
func redactURLCredentials(u *url.URL) *url.URL {
redacted := *u
redacted.User = nil
return &redacted
}