feat: create standalone native fnOS repository
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
package validate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func BasicAuthPasswordHash(username, password string) (string, error) {
|
||||
if err := Username(username); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := Password(password); err != nil {
|
||||
return "", err
|
||||
}
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("密码加密失败")
|
||||
}
|
||||
return string(hash), nil
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package validate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func IPOrCIDR(raw string) (string, error) {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return "", fmt.Errorf("IP 或 CIDR 不能为空")
|
||||
}
|
||||
if strings.Contains(raw, "/") {
|
||||
_, ipNet, err := net.ParseCIDR(raw)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("CIDR 格式无效")
|
||||
}
|
||||
return ipNet.String(), nil
|
||||
}
|
||||
ip := net.ParseIP(raw)
|
||||
if ip == nil {
|
||||
return "", fmt.Errorf("IP 格式无效")
|
||||
}
|
||||
if v4 := ip.To4(); v4 != nil {
|
||||
return v4.String(), nil
|
||||
}
|
||||
return ip.String(), nil
|
||||
}
|
||||
|
||||
func IPOrCIDRList(list []string) error {
|
||||
for _, item := range list {
|
||||
if _, err := IPOrCIDR(item); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package validate
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestFrontendAddress(t *testing.T) {
|
||||
host, port, err := FrontendAddress("example.com:8011")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if host != "example.com" || port != 8011 {
|
||||
t.Fatalf("got %q %d", host, port)
|
||||
}
|
||||
|
||||
host, port, err = FrontendAddress("nas.example.com")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if host != "nas.example.com" || port != 0 {
|
||||
t.Fatalf("got %q %d", host, port)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user