mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-29 22:06:38 +08:00
228 lines
6.0 KiB
Go
228 lines
6.0 KiB
Go
package service
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"openflare/model"
|
|
"sort"
|
|
"strings"
|
|
"unicode"
|
|
)
|
|
|
|
const (
|
|
ManagedDomainMatchTypeExact = "exact"
|
|
ManagedDomainMatchTypeWildcard = "wildcard"
|
|
)
|
|
|
|
type ManagedDomainInput struct {
|
|
Domain string `json:"domain"`
|
|
CertID *uint `json:"cert_id"`
|
|
Enabled bool `json:"enabled"`
|
|
Remark string `json:"remark"`
|
|
}
|
|
|
|
type ManagedDomainMatchCandidate struct {
|
|
ManagedDomainID uint `json:"managed_domain_id"`
|
|
Domain string `json:"domain"`
|
|
MatchType string `json:"match_type"`
|
|
CertificateID uint `json:"certificate_id"`
|
|
CertificateName string `json:"certificate_name"`
|
|
}
|
|
|
|
type ManagedDomainMatchResult struct {
|
|
Domain string `json:"domain"`
|
|
Matched bool `json:"matched"`
|
|
Candidate *ManagedDomainMatchCandidate `json:"candidate,omitempty"`
|
|
Candidates []ManagedDomainMatchCandidate `json:"candidates"`
|
|
}
|
|
|
|
func ListManagedDomains() ([]*model.ManagedDomain, error) {
|
|
return model.ListManagedDomains()
|
|
}
|
|
|
|
func CreateManagedDomain(input ManagedDomainInput) (*model.ManagedDomain, error) {
|
|
domain, err := buildManagedDomain(nil, input)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err = domain.Insert(); err != nil {
|
|
if isUniqueConstraintError(err) {
|
|
return nil, errors.New("域名已存在")
|
|
}
|
|
return nil, err
|
|
}
|
|
return domain, nil
|
|
}
|
|
|
|
func UpdateManagedDomain(id uint, input ManagedDomainInput) (*model.ManagedDomain, error) {
|
|
domain, err := model.GetManagedDomainByID(id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
domain, err = buildManagedDomain(domain, input)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err = domain.Update(); err != nil {
|
|
if isUniqueConstraintError(err) {
|
|
return nil, errors.New("域名已存在")
|
|
}
|
|
return nil, err
|
|
}
|
|
return domain, nil
|
|
}
|
|
|
|
func DeleteManagedDomain(id uint) error {
|
|
domain, err := model.GetManagedDomainByID(id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return domain.Delete()
|
|
}
|
|
|
|
func MatchManagedDomainCertificate(rawDomain string) (*ManagedDomainMatchResult, error) {
|
|
domain := normalizeManagedDomain(rawDomain)
|
|
if err := validateManagedDomainPattern(domain); err != nil {
|
|
return nil, err
|
|
}
|
|
managedDomains, err := model.ListEnabledManagedDomainsWithCertificate()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
candidates := make([]ManagedDomainMatchCandidate, 0)
|
|
for _, item := range managedDomains {
|
|
if item.CertID == nil || *item.CertID == 0 {
|
|
continue
|
|
}
|
|
matchType := detectManagedDomainMatchType(item.Domain, domain)
|
|
if matchType == "" {
|
|
continue
|
|
}
|
|
certificate, err := model.GetTLSCertificateByID(*item.CertID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("托管域名 %s 关联证书不存在", item.Domain)
|
|
}
|
|
candidates = append(candidates, ManagedDomainMatchCandidate{
|
|
ManagedDomainID: item.ID,
|
|
Domain: item.Domain,
|
|
MatchType: matchType,
|
|
CertificateID: certificate.ID,
|
|
CertificateName: certificate.Name,
|
|
})
|
|
}
|
|
sortManagedDomainCandidates(candidates)
|
|
result := &ManagedDomainMatchResult{
|
|
Domain: domain,
|
|
Matched: len(candidates) > 0,
|
|
Candidates: candidates,
|
|
}
|
|
if len(candidates) > 0 {
|
|
candidate := candidates[0]
|
|
result.Candidate = &candidate
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func buildManagedDomain(existing *model.ManagedDomain, input ManagedDomainInput) (*model.ManagedDomain, error) {
|
|
domain := normalizeManagedDomain(input.Domain)
|
|
remark := strings.TrimSpace(input.Remark)
|
|
if err := validateManagedDomainPattern(domain); err != nil {
|
|
return nil, err
|
|
}
|
|
if input.CertID != nil && *input.CertID != 0 {
|
|
if _, err := model.GetTLSCertificateByID(*input.CertID); err != nil {
|
|
return nil, errors.New("所选证书不存在")
|
|
}
|
|
} else {
|
|
input.CertID = nil
|
|
}
|
|
if existing == nil {
|
|
existing = &model.ManagedDomain{}
|
|
}
|
|
existing.Domain = domain
|
|
existing.CertID = input.CertID
|
|
existing.Enabled = input.Enabled
|
|
existing.Remark = remark
|
|
return existing, nil
|
|
}
|
|
|
|
func normalizeManagedDomain(domain string) string {
|
|
return strings.ToLower(strings.TrimSpace(domain))
|
|
}
|
|
|
|
func validateManagedDomainPattern(domain string) error {
|
|
if domain == "" {
|
|
return errors.New("域名不能为空")
|
|
}
|
|
if strings.Contains(domain, "://") || strings.Contains(domain, "/") {
|
|
return errors.New("域名格式不合法")
|
|
}
|
|
if strings.Contains(domain, "*") {
|
|
if !strings.HasPrefix(domain, "*.") || strings.Count(domain, "*") != 1 {
|
|
return errors.New("通配符域名仅支持 *.example.com 格式")
|
|
}
|
|
return validateHostname(strings.TrimPrefix(domain, "*."))
|
|
}
|
|
return validateHostname(domain)
|
|
}
|
|
|
|
func validateHostname(domain string) error {
|
|
if domain == "" {
|
|
return errors.New("域名不能为空")
|
|
}
|
|
if len(domain) > 253 {
|
|
return errors.New("域名格式不合法")
|
|
}
|
|
labels := strings.Split(domain, ".")
|
|
if len(labels) < 2 {
|
|
return errors.New("域名格式不合法")
|
|
}
|
|
for _, label := range labels {
|
|
if len(label) == 0 || len(label) > 63 {
|
|
return errors.New("域名格式不合法")
|
|
}
|
|
if label[0] == '-' || label[len(label)-1] == '-' {
|
|
return errors.New("域名格式不合法")
|
|
}
|
|
for _, r := range label {
|
|
if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '-' {
|
|
continue
|
|
}
|
|
return errors.New("域名格式不合法")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func detectManagedDomainMatchType(pattern string, domain string) string {
|
|
if pattern == domain {
|
|
return ManagedDomainMatchTypeExact
|
|
}
|
|
if !strings.HasPrefix(pattern, "*.") {
|
|
return ""
|
|
}
|
|
suffix := strings.TrimPrefix(pattern, "*.")
|
|
if !strings.HasSuffix(domain, "."+suffix) {
|
|
return ""
|
|
}
|
|
prefix := strings.TrimSuffix(domain, "."+suffix)
|
|
if prefix == "" || strings.Contains(prefix, ".") {
|
|
return ""
|
|
}
|
|
return ManagedDomainMatchTypeWildcard
|
|
}
|
|
|
|
func sortManagedDomainCandidates(candidates []ManagedDomainMatchCandidate) {
|
|
sort.Slice(candidates, func(i int, j int) bool {
|
|
left := candidates[i]
|
|
right := candidates[j]
|
|
if left.MatchType != right.MatchType {
|
|
return left.MatchType == ManagedDomainMatchTypeExact
|
|
}
|
|
if len(left.Domain) != len(right.Domain) {
|
|
return len(left.Domain) > len(right.Domain)
|
|
}
|
|
return left.ManagedDomainID < right.ManagedDomainID
|
|
})
|
|
}
|