feat(publicip): support custom API url echoip#https://... (#2529)
This commit is contained in:
@@ -3,7 +3,11 @@ package api
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -16,6 +20,8 @@ const (
|
||||
IP2Location Provider = "ip2location"
|
||||
)
|
||||
|
||||
const echoipPrefix = "echoip#"
|
||||
|
||||
type NameToken struct {
|
||||
Name string
|
||||
Token string
|
||||
@@ -30,15 +36,19 @@ func New(nameTokenPairs []NameToken, client *http.Client) (
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing API name: %w", err)
|
||||
}
|
||||
switch provider {
|
||||
case Cloudflare:
|
||||
switch {
|
||||
case provider == Cloudflare:
|
||||
fetchers[i] = newCloudflare(client)
|
||||
case IfConfigCo:
|
||||
fetchers[i] = newIfConfigCo(client)
|
||||
case IPInfo:
|
||||
case provider == IfConfigCo:
|
||||
const ifConfigCoURL = "https://ifconfig.co"
|
||||
fetchers[i] = newEchoip(client, ifConfigCoURL)
|
||||
case provider == IPInfo:
|
||||
fetchers[i] = newIPInfo(client, nameTokenPair.Token)
|
||||
case IP2Location:
|
||||
case provider == IP2Location:
|
||||
fetchers[i] = newIP2Location(client, nameTokenPair.Token)
|
||||
case strings.HasPrefix(string(provider), echoipPrefix):
|
||||
url := strings.TrimPrefix(string(provider), echoipPrefix)
|
||||
fetchers[i] = newEchoip(client, url)
|
||||
default:
|
||||
panic("provider not valid: " + provider)
|
||||
}
|
||||
@@ -46,20 +56,88 @@ func New(nameTokenPairs []NameToken, client *http.Client) (
|
||||
return fetchers, nil
|
||||
}
|
||||
|
||||
var regexEchoipURL = regexp.MustCompile(`^http(s|):\/\/.+$`)
|
||||
|
||||
var ErrProviderNotValid = errors.New("API name is not valid")
|
||||
|
||||
func ParseProvider(s string) (provider Provider, err error) {
|
||||
switch strings.ToLower(s) {
|
||||
case "cloudflare":
|
||||
return Cloudflare, nil
|
||||
case string(IfConfigCo):
|
||||
return IfConfigCo, nil
|
||||
case "ipinfo":
|
||||
return IPInfo, nil
|
||||
case "ip2location":
|
||||
return IP2Location, nil
|
||||
default:
|
||||
return "", fmt.Errorf(`%w: %q can only be "cloudflare", "ifconfigco", "ip2location" or "ipinfo"`,
|
||||
ErrProviderNotValid, s)
|
||||
possibleProviders := []Provider{
|
||||
Cloudflare,
|
||||
IfConfigCo,
|
||||
IP2Location,
|
||||
IPInfo,
|
||||
}
|
||||
stringToProvider := make(map[string]Provider, len(possibleProviders))
|
||||
for _, provider := range possibleProviders {
|
||||
stringToProvider[string(provider)] = provider
|
||||
}
|
||||
provider, ok := stringToProvider[strings.ToLower(s)]
|
||||
if ok {
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
customPrefixToURLRegex := map[string]*regexp.Regexp{
|
||||
echoipPrefix: regexEchoipURL,
|
||||
}
|
||||
for prefix, urlRegex := range customPrefixToURLRegex {
|
||||
match, err := checkCustomURL(s, prefix, urlRegex)
|
||||
if !match {
|
||||
continue
|
||||
} else if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return Provider(s), nil
|
||||
}
|
||||
|
||||
providerStrings := make([]string, 0, len(stringToProvider)+len(customPrefixToURLRegex))
|
||||
for _, providerString := range slices.Sorted(maps.Keys(stringToProvider)) {
|
||||
providerStrings = append(providerStrings, `"`+providerString+`"`)
|
||||
}
|
||||
for _, prefix := range slices.Sorted(maps.Keys(customPrefixToURLRegex)) {
|
||||
providerStrings = append(providerStrings, "a custom "+prefix+" url")
|
||||
}
|
||||
|
||||
return "", fmt.Errorf(`%w: %q can only be %s`,
|
||||
ErrProviderNotValid, s, orStrings(providerStrings))
|
||||
}
|
||||
|
||||
var ErrCustomURLNotValid = errors.New("custom URL is not valid")
|
||||
|
||||
func checkCustomURL(s, prefix string, regex *regexp.Regexp) (match bool, err error) {
|
||||
if !strings.HasPrefix(s, prefix) {
|
||||
return false, nil
|
||||
}
|
||||
s = strings.TrimPrefix(s, prefix)
|
||||
_, err = url.Parse(s)
|
||||
if err != nil {
|
||||
return true, fmt.Errorf("%s %w: %w", prefix, ErrCustomURLNotValid, err)
|
||||
}
|
||||
|
||||
if regex.MatchString(s) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return true, fmt.Errorf("%s %w: %q does not match regular expression: %s",
|
||||
prefix, ErrCustomURLNotValid, s, regex)
|
||||
}
|
||||
|
||||
func orStrings(strings []string) (result string) {
|
||||
return joinStrings(strings, "or")
|
||||
}
|
||||
|
||||
func joinStrings(strings []string, lastJoin string) (result string) {
|
||||
if len(strings) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
result = strings[0]
|
||||
for i := 1; i < len(strings); i++ {
|
||||
if i < len(strings)-1 {
|
||||
result += ", " + strings[i]
|
||||
} else {
|
||||
result += " " + lastJoin + " " + strings[i]
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user