2017-04-12 19:26:15 +00:00
|
|
|
package config
|
2017-02-03 15:22:02 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"net/url"
|
2018-11-26 23:49:39 +00:00
|
|
|
"regexp"
|
2017-02-03 15:22:02 +00:00
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
2017-04-12 19:35:15 +00:00
|
|
|
type URLConfig struct {
|
2017-04-12 19:26:15 +00:00
|
|
|
git Environment
|
|
|
|
}
|
|
|
|
|
2017-04-12 19:35:15 +00:00
|
|
|
func NewURLConfig(git Environment) *URLConfig {
|
2017-04-15 17:02:28 +00:00
|
|
|
if git == nil {
|
|
|
|
git = EnvironmentOf(make(mapFetcher))
|
|
|
|
}
|
|
|
|
|
2017-04-12 19:35:15 +00:00
|
|
|
return &URLConfig{
|
2017-04-12 19:26:15 +00:00
|
|
|
git: git,
|
|
|
|
}
|
2017-02-03 15:22:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get retrieves a `http.{url}.{key}` for the given key and urls, following the
|
|
|
|
// rules in https://git-scm.com/docs/git-config#git-config-httplturlgt.
|
|
|
|
// The value for `http.{key}` is returned as a fallback if no config keys are
|
|
|
|
// set for the given urls.
|
2017-04-15 16:42:19 +00:00
|
|
|
func (c *URLConfig) Get(prefix, rawurl, key string) (string, bool) {
|
2017-04-17 17:26:19 +00:00
|
|
|
if c == nil {
|
|
|
|
return "", false
|
|
|
|
}
|
|
|
|
|
2017-02-03 15:22:02 +00:00
|
|
|
key = strings.ToLower(key)
|
2017-04-12 19:27:27 +00:00
|
|
|
prefix = strings.ToLower(prefix)
|
2017-04-15 16:42:00 +00:00
|
|
|
if v := c.getAll(prefix, rawurl, key); len(v) > 0 {
|
2017-04-14 17:30:44 +00:00
|
|
|
return v[len(v)-1], true
|
2017-02-03 15:22:02 +00:00
|
|
|
}
|
2017-04-12 19:27:27 +00:00
|
|
|
return c.git.Get(strings.Join([]string{prefix, key}, "."))
|
2017-02-03 15:22:02 +00:00
|
|
|
}
|
|
|
|
|
2017-04-15 16:42:19 +00:00
|
|
|
func (c *URLConfig) GetAll(prefix, rawurl, key string) []string {
|
2017-04-17 17:26:19 +00:00
|
|
|
if c == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-04-13 15:14:34 +00:00
|
|
|
key = strings.ToLower(key)
|
|
|
|
prefix = strings.ToLower(prefix)
|
2017-04-15 16:42:00 +00:00
|
|
|
if v := c.getAll(prefix, rawurl, key); len(v) > 0 {
|
2017-04-13 15:14:34 +00:00
|
|
|
return v
|
|
|
|
}
|
|
|
|
return c.git.GetAll(strings.Join([]string{prefix, key}, "."))
|
|
|
|
}
|
|
|
|
|
2017-10-27 16:29:14 +00:00
|
|
|
func (c *URLConfig) Bool(prefix, rawurl, key string, def bool) bool {
|
|
|
|
s, _ := c.Get(prefix, rawurl, key)
|
|
|
|
return Bool(s, def)
|
|
|
|
}
|
|
|
|
|
2017-04-15 16:42:00 +00:00
|
|
|
func (c *URLConfig) getAll(prefix, rawurl, key string) []string {
|
2018-11-26 23:49:39 +00:00
|
|
|
type urlMatch struct {
|
|
|
|
key string // The full configuration key
|
|
|
|
hostScore int // A score indicating the strength of the host match
|
|
|
|
pathScore int // A score indicating the strength of the path match
|
|
|
|
userMatch int // Whether we matched on a username. 1 for yes, else 0
|
|
|
|
}
|
2017-02-03 15:22:02 +00:00
|
|
|
|
2018-11-26 23:49:39 +00:00
|
|
|
searchURL, err := url.Parse(rawurl)
|
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
config := c.git.All()
|
|
|
|
|
|
|
|
re := regexp.MustCompile(fmt.Sprintf(`%s.(\S+).%s`, prefix, key))
|
|
|
|
|
|
|
|
bestMatch := urlMatch{
|
|
|
|
key: "",
|
|
|
|
hostScore: 0,
|
|
|
|
pathScore: 0,
|
|
|
|
userMatch: 0,
|
|
|
|
}
|
|
|
|
|
|
|
|
for k := range config {
|
|
|
|
// Ensure we're examining the correct type of key and parse out the URL
|
|
|
|
matches := re.FindStringSubmatch(k)
|
|
|
|
if matches == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
configURL, err := url.Parse(matches[1])
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
match := urlMatch{
|
|
|
|
key: k,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Rule #1: Scheme must match exactly
|
|
|
|
if searchURL.Scheme != configURL.Scheme {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Rule #2: Hosts must match exactly, or through wildcards. More exact
|
|
|
|
// matches should take priority over wildcard matches
|
|
|
|
match.hostScore = compareHosts(searchURL.Hostname(), configURL.Hostname())
|
|
|
|
|
|
|
|
if match.hostScore == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if match.hostScore < bestMatch.hostScore {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Rule #3: Port Number must match exactly
|
|
|
|
if searchURL.Port() != configURL.Port() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Rule #4: Configured path must match exactly, or as a prefix of
|
|
|
|
// slash-delimited path elements
|
|
|
|
match.pathScore = comparePaths(searchURL.Path, configURL.Path)
|
|
|
|
|
|
|
|
if match.pathScore == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Rule #5: Username must match exactly if present in the config.
|
|
|
|
// If not present, config matches on any username but with lower
|
|
|
|
// priority than an exact username match.
|
|
|
|
if configURL.User != nil {
|
|
|
|
if searchURL.User == nil {
|
|
|
|
continue
|
2017-02-03 15:22:02 +00:00
|
|
|
}
|
2017-05-01 21:09:41 +00:00
|
|
|
|
2018-11-26 23:49:39 +00:00
|
|
|
if searchURL.User.Username() != configURL.User.Username() {
|
|
|
|
continue
|
2017-05-01 21:09:41 +00:00
|
|
|
}
|
2018-11-26 23:49:39 +00:00
|
|
|
|
|
|
|
match.userMatch = 1
|
2017-02-03 15:22:02 +00:00
|
|
|
}
|
2018-11-26 23:49:39 +00:00
|
|
|
|
|
|
|
// Now combine our various scores to determine if we have found a best
|
|
|
|
// match. Host score > path score > user score
|
|
|
|
if match.hostScore > bestMatch.hostScore {
|
|
|
|
bestMatch = match
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if match.pathScore > bestMatch.pathScore {
|
|
|
|
bestMatch = match
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if match.pathScore == bestMatch.pathScore && match.userMatch > bestMatch.userMatch {
|
|
|
|
bestMatch = match
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if bestMatch.key == "" {
|
|
|
|
return nil
|
2017-02-03 15:22:02 +00:00
|
|
|
}
|
|
|
|
|
2018-11-26 23:49:39 +00:00
|
|
|
return c.git.GetAll(bestMatch.key)
|
|
|
|
}
|
|
|
|
|
|
|
|
// compareHosts compares a hostname with a configuration hostname to determine
|
|
|
|
// a match. It returns an integer indicating the strength of the match, or 0 if
|
|
|
|
// the two hostnames did not match.
|
|
|
|
func compareHosts(searchHostname, configHostname string) int {
|
|
|
|
searchHost := strings.Split(searchHostname, ".")
|
|
|
|
configHost := strings.Split(configHostname, ".")
|
|
|
|
|
|
|
|
if len(searchHost) != len(configHost) {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
score := len(searchHost) + 1
|
|
|
|
|
|
|
|
for i, subdomain := range searchHost {
|
|
|
|
if configHost[i] == "*" {
|
|
|
|
score--
|
|
|
|
continue
|
2017-02-03 15:22:02 +00:00
|
|
|
}
|
2018-11-26 23:49:39 +00:00
|
|
|
|
|
|
|
if subdomain != configHost[i] {
|
|
|
|
return 0
|
2017-02-03 15:22:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-26 23:49:39 +00:00
|
|
|
return score
|
2017-02-03 15:22:02 +00:00
|
|
|
}
|
2018-11-26 23:49:39 +00:00
|
|
|
|
|
|
|
// comparePaths compares a path with a configuration path to determine a match.
|
|
|
|
// It returns an integer indicating the strength of the match, or 0 if the two
|
|
|
|
// paths did not match.
|
|
|
|
func comparePaths(rawSearchPath, rawConfigPath string) int {
|
|
|
|
f := func(c rune) bool {
|
|
|
|
return c == '/'
|
|
|
|
}
|
|
|
|
searchPath := strings.FieldsFunc(rawSearchPath, f)
|
|
|
|
configPath := strings.FieldsFunc(rawConfigPath, f)
|
|
|
|
|
|
|
|
if len(searchPath) < len(configPath) {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start with a base score of 1, so we return something above 0 for a
|
|
|
|
// zero-length path
|
|
|
|
score := 1
|
|
|
|
|
|
|
|
for i, element := range configPath {
|
|
|
|
searchElement := searchPath[i]
|
|
|
|
|
|
|
|
if element == searchElement {
|
|
|
|
score += 2
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if isDefaultLFSUrl(searchElement, searchPath, i+1) {
|
|
|
|
if searchElement[0:len(searchElement)-4] == element {
|
|
|
|
// Since we matched without the `.git` prefix, only add one
|
|
|
|
// point to the score instead of 2
|
|
|
|
score++
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
return score
|
|
|
|
}
|
|
|
|
|
2017-04-13 15:07:34 +00:00
|
|
|
func (c *URLConfig) hostsAndPaths(rawurl string) (hosts, paths []string) {
|
|
|
|
u, err := url.Parse(rawurl)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return c.hosts(u), c.paths(u.Path)
|
|
|
|
}
|
|
|
|
|
2017-04-13 15:03:56 +00:00
|
|
|
func (c *URLConfig) hosts(u *url.URL) []string {
|
|
|
|
hosts := make([]string, 0, 1)
|
|
|
|
|
|
|
|
if u.User != nil {
|
|
|
|
hosts = append(hosts, fmt.Sprintf("%s://%s@%s", u.Scheme, u.User.Username(), u.Host))
|
|
|
|
}
|
|
|
|
hosts = append(hosts, fmt.Sprintf("%s://%s", u.Scheme, u.Host))
|
|
|
|
|
|
|
|
return hosts
|
|
|
|
}
|
2017-04-13 15:05:57 +00:00
|
|
|
|
|
|
|
func (c *URLConfig) paths(path string) []string {
|
|
|
|
pLen := len(path)
|
|
|
|
if pLen <= 2 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
end := pLen
|
2017-05-01 21:09:41 +00:00
|
|
|
if strings.HasSuffix(path, slash) {
|
|
|
|
end--
|
|
|
|
}
|
|
|
|
return strings.Split(path[1:end], slash)
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
gitExt = ".git"
|
|
|
|
infoPart = "info"
|
|
|
|
lfsPart = "lfs"
|
|
|
|
slash = "/"
|
|
|
|
)
|
|
|
|
|
|
|
|
func isDefaultLFSUrl(path string, parts []string, index int) bool {
|
|
|
|
if len(path) < 5 {
|
|
|
|
return false // shorter than ".git"
|
|
|
|
}
|
|
|
|
|
|
|
|
if !strings.HasSuffix(path, gitExt) {
|
|
|
|
return false
|
2017-04-13 15:05:57 +00:00
|
|
|
}
|
2017-05-01 21:09:41 +00:00
|
|
|
|
|
|
|
if index > len(parts)-2 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return parts[index] == infoPart && parts[index+1] == lfsPart
|
2017-04-13 15:05:57 +00:00
|
|
|
}
|