250 lines
6.7 KiB
Go
250 lines
6.7 KiB
Go
// Licensed to Elasticsearch B.V. under one or more contributor
|
|
// license agreements. See the NOTICE file distributed with
|
|
// this work for additional information regarding copyright
|
|
// ownership. Elasticsearch B.V. licenses this file to you under
|
|
// the Apache License, Version 2.0 (the "License"); you may
|
|
// not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing,
|
|
// software distributed under the License is distributed on an
|
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
// KIND, either express or implied. See the License for the
|
|
// specific language governing permissions and limitations
|
|
// under the License.
|
|
|
|
package parse
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"net/url"
|
|
p "path"
|
|
"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
|
|
DefaultUsername string
|
|
DefaultPassword 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
|
|
}
|
|
|
|
query, ok := conf["query"]
|
|
if ok {
|
|
queryMap, ok := query.(map[string]interface{})
|
|
if !ok {
|
|
return mb.HostData{}, errors.Errorf("'query' config for module %v is not a map", module.Name())
|
|
}
|
|
|
|
b.QueryParams = mb.QueryParams(queryMap).String()
|
|
}
|
|
|
|
var user, pass, path, basePath 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())
|
|
}
|
|
} else {
|
|
user = b.DefaultUsername
|
|
}
|
|
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())
|
|
}
|
|
} else {
|
|
pass = b.DefaultPassword
|
|
}
|
|
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
|
|
}
|
|
// Normalize path
|
|
path = strings.Trim(path, "/")
|
|
|
|
t, ok = conf["basepath"]
|
|
if ok {
|
|
basePath, ok = t.(string)
|
|
if !ok {
|
|
return mb.HostData{}, errors.Errorf("'basepath' config for module %v is not a string", module.Name())
|
|
}
|
|
}
|
|
// Normalize basepath
|
|
basePath = strings.Trim(basePath, "/")
|
|
|
|
// Combine paths and normalize
|
|
fullPath := strings.Trim(p.Join(basePath, path), "/")
|
|
|
|
return ParseURL(host, b.DefaultScheme, user, pass, fullPath, 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,
|
|
// the same happens for "npipe".
|
|
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") || strings.HasSuffix(u.Scheme, "npipe") {
|
|
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") && !strings.HasSuffix(u.Scheme, "npipe") {
|
|
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
|
|
}
|
|
|
|
//Adds the query params in the url
|
|
u, err = SetQueryParams(u, query)
|
|
return u, err
|
|
}
|
|
|
|
// SetQueryParams adds the query params to existing query parameters overwriting any
|
|
// keys that already exist.
|
|
func SetQueryParams(u *url.URL, query string) (*url.URL, error) {
|
|
q := u.Query()
|
|
params, err := url.ParseQuery(query)
|
|
if err != nil {
|
|
return u, err
|
|
}
|
|
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
|
|
}
|