139 lines
3.5 KiB
Go
139 lines
3.5 KiB
Go
package validate
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"fmt"
|
||
|
|
"net"
|
||
|
|
"net/url"
|
||
|
|
"regexp"
|
||
|
|
"strconv"
|
||
|
|
"strings"
|
||
|
|
)
|
||
|
|
|
||
|
|
var domainRe = regexp.MustCompile(`^(?i:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)(?:\.(?i:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?))*$`)
|
||
|
|
|
||
|
|
func CertDomain(domain string) error {
|
||
|
|
domain = strings.TrimSuffix(strings.ToLower(strings.TrimSpace(domain)), ".")
|
||
|
|
if domain == "" {
|
||
|
|
return fmt.Errorf("域名不能为空")
|
||
|
|
}
|
||
|
|
if strings.HasPrefix(domain, "*.") {
|
||
|
|
return Domain(strings.TrimPrefix(domain, "*."))
|
||
|
|
}
|
||
|
|
return Domain(domain)
|
||
|
|
}
|
||
|
|
|
||
|
|
func Domain(domain string) error {
|
||
|
|
domain = strings.TrimSpace(domain)
|
||
|
|
if domain == "" {
|
||
|
|
return fmt.Errorf("域名不能为空")
|
||
|
|
}
|
||
|
|
if len(domain) > 253 {
|
||
|
|
return fmt.Errorf("域名过长")
|
||
|
|
}
|
||
|
|
if strings.Contains(domain, "..") || strings.HasPrefix(domain, ".") || strings.HasSuffix(domain, ".") {
|
||
|
|
return fmt.Errorf("域名格式无效")
|
||
|
|
}
|
||
|
|
if !domainRe.MatchString(domain) {
|
||
|
|
return fmt.Errorf("域名格式无效")
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func ListenPort(port int) error {
|
||
|
|
if port < 1 || port > 65535 {
|
||
|
|
return fmt.Errorf("监听端口无效")
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// FrontendAddress parses a frontend binding like "example.com" or "example.com:8011".
|
||
|
|
// A zero port means the rule-level default should be used.
|
||
|
|
func FrontendAddress(raw string) (hostname string, port int, err error) {
|
||
|
|
raw = strings.TrimSpace(raw)
|
||
|
|
if raw == "" {
|
||
|
|
return "", 0, fmt.Errorf("前端地址不能为空")
|
||
|
|
}
|
||
|
|
|
||
|
|
host := raw
|
||
|
|
port = 0
|
||
|
|
if strings.Count(raw, ":") == 1 && !strings.Contains(raw, "]") {
|
||
|
|
parts := strings.SplitN(raw, ":", 2)
|
||
|
|
host = parts[0]
|
||
|
|
p, parseErr := strconv.Atoi(parts[1])
|
||
|
|
if parseErr != nil || p < 1 || p > 65535 {
|
||
|
|
return "", 0, fmt.Errorf("前端地址端口无效")
|
||
|
|
}
|
||
|
|
port = p
|
||
|
|
}
|
||
|
|
|
||
|
|
host = strings.ToLower(strings.TrimSpace(host))
|
||
|
|
if err := Domain(host); err != nil {
|
||
|
|
return "", 0, err
|
||
|
|
}
|
||
|
|
return host, port, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func Upstream(raw string) (string, error) {
|
||
|
|
raw = strings.TrimSpace(raw)
|
||
|
|
if raw == "" {
|
||
|
|
return "", fmt.Errorf("目标地址不能为空")
|
||
|
|
}
|
||
|
|
|
||
|
|
u, err := url.Parse(raw)
|
||
|
|
if err != nil {
|
||
|
|
return "", fmt.Errorf("目标地址格式无效")
|
||
|
|
}
|
||
|
|
if u.Scheme != "http" && u.Scheme != "https" {
|
||
|
|
return "", fmt.Errorf("目标地址仅支持 http:// 或 https://")
|
||
|
|
}
|
||
|
|
if u.User != nil {
|
||
|
|
return "", fmt.Errorf("目标地址不能包含用户名或密码")
|
||
|
|
}
|
||
|
|
if u.Host == "" {
|
||
|
|
return "", fmt.Errorf("目标地址缺少主机名")
|
||
|
|
}
|
||
|
|
if u.Path != "" && u.Path != "/" {
|
||
|
|
return "", fmt.Errorf("目标地址不能包含路径")
|
||
|
|
}
|
||
|
|
if u.RawQuery != "" || u.Fragment != "" {
|
||
|
|
return "", fmt.Errorf("目标地址不能包含查询参数或片段")
|
||
|
|
}
|
||
|
|
|
||
|
|
host := u.Hostname()
|
||
|
|
port := u.Port()
|
||
|
|
if host == "" {
|
||
|
|
return "", fmt.Errorf("目标地址主机名无效")
|
||
|
|
}
|
||
|
|
if ip := net.ParseIP(host); ip != nil {
|
||
|
|
if port == "" {
|
||
|
|
return "", fmt.Errorf("IP 地址目标必须指定端口")
|
||
|
|
}
|
||
|
|
} else if err := Domain(host); err != nil {
|
||
|
|
return "", fmt.Errorf("目标地址主机名无效")
|
||
|
|
}
|
||
|
|
|
||
|
|
normalized := fmt.Sprintf("%s://%s", u.Scheme, u.Host)
|
||
|
|
if !strings.HasPrefix(normalized, "http://") && !strings.HasPrefix(normalized, "https://") {
|
||
|
|
return "", fmt.Errorf("目标地址格式无效")
|
||
|
|
}
|
||
|
|
return normalized, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func Password(password string) error {
|
||
|
|
if len(password) < 8 {
|
||
|
|
return fmt.Errorf("密码长度至少 8 位")
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func Username(username string) error {
|
||
|
|
username = strings.TrimSpace(username)
|
||
|
|
if username == "" {
|
||
|
|
return fmt.Errorf("用户名不能为空")
|
||
|
|
}
|
||
|
|
if len(username) > 64 {
|
||
|
|
return fmt.Errorf("用户名过长")
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|