From c424b92036f2077aa8ed6fbfd18cae7ffbadc20f Mon Sep 17 00:00:00 2001 From: Zen Wen Date: Mon, 13 Oct 2025 00:16:31 +0800 Subject: [PATCH 1/3] Move function:NormalizeDomain to utils module --- src/mod/acme/acme.go | 2 +- src/mod/acme/utils.go | 47 ------------------------------------------ src/mod/utils/utils.go | 47 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 47 insertions(+), 49 deletions(-) diff --git a/src/mod/acme/acme.go b/src/mod/acme/acme.go index e38415a..a52d0be 100644 --- a/src/mod/acme/acme.go +++ b/src/mod/acme/acme.go @@ -438,7 +438,7 @@ func (a *ACMEHandler) HandleRenewCertificate(w http.ResponseWriter, r *http.Requ if (domainPara != "") { for _, d := range strings.Split(domainPara, ",") { // Apply normalization on each domain - nd, err := NormalizeDomain(d) + nd, err := utils.NormalizeDomain(d) if err != nil { utils.SendErrorResponse(w, jsonEscape(err.Error())) return diff --git a/src/mod/acme/utils.go b/src/mod/acme/utils.go index 0a2c3e3..b15b355 100644 --- a/src/mod/acme/utils.go +++ b/src/mod/acme/utils.go @@ -7,8 +7,6 @@ import ( "fmt" "os" "time" - "strings" - "unicode" ) // Get the issuer name from pem file @@ -116,48 +114,3 @@ func CertExpireSoon(certBytes []byte, numberOfDays int) bool { } return false } - - -// NormalizeDomain cleans and validates a domain string. -// - Trims spaces around the domain -// - Converts to lowercase -// - Removes trailing dot (FQDN canonicalization) -// - Checks that the domain conforms to standard rules: -// * Each label ≤ 63 characters -// * Only letters, digits, and hyphens -// * Labels do not start or end with a hyphen -// * Full domain ≤ 253 characters -// Returns an empty string if the domain is invalid. -func NormalizeDomain(d string) (string, error) { - d = strings.TrimSpace(d) - d = strings.ToLower(d) - d = strings.TrimSuffix(d, ".") - - if len(d) == 0 { - return "", errors.New("domain is empty") - } - if len(d) > 253 { - return "", errors.New("domain exceeds 253 characters") - } - - labels := strings.Split(d, ".") - for _, label := range labels { - if len(label) == 0 { - return "", errors.New("Domain '" + d + "' not valid: Empty label") - } - if len(label) > 63 { - return "", errors.New("Domain not valid: label exceeds 63 characters") - } - - for i, r := range label { - if !(unicode.IsLetter(r) || unicode.IsDigit(r) || r == '-') { - return "", errors.New("Domain '" + d + "' not valid: Invalid character '" + string(r) + "' in label") - } - if (i == 0 || i == len(label)-1) && r == '-' { - return "", errors.New("Domain '" + d + "' not valid: label '"+ label +"' starts or ends with hyphen") - } - } - } - - return d, nil -} diff --git a/src/mod/utils/utils.go b/src/mod/utils/utils.go index 2fe1ffd..3c80c5f 100644 --- a/src/mod/utils/utils.go +++ b/src/mod/utils/utils.go @@ -9,6 +9,7 @@ import ( "strconv" "strings" "time" + "unicode" ) /* @@ -199,4 +200,48 @@ func ValidateListeningAddress(address string) bool { } return true -} \ No newline at end of file +} + +// NormalizeDomain cleans and validates a domain string. +// - Trims spaces around the domain +// - Converts to lowercase +// - Removes trailing dot (FQDN canonicalization) +// - Checks that the domain conforms to standard rules: +// * Each label ≤ 63 characters +// * Only letters, digits, and hyphens +// * Labels do not start or end with a hyphen +// * Full domain ≤ 253 characters +// Returns an empty string if the domain is invalid. +func NormalizeDomain(d string) (string, error) { + d = strings.TrimSpace(d) + d = strings.ToLower(d) + d = strings.TrimSuffix(d, ".") + + if len(d) == 0 { + return "", errors.New("domain is empty") + } + if len(d) > 253 { + return "", errors.New("domain exceeds 253 characters") + } + + labels := strings.Split(d, ".") + for _, label := range labels { + if len(label) == 0 { + return "", errors.New("Domain '" + d + "' not valid: Empty label") + } + if len(label) > 63 { + return "", errors.New("Domain not valid: label exceeds 63 characters") + } + + for i, r := range label { + if !(unicode.IsLetter(r) || unicode.IsDigit(r) || r == '-') { + return "", errors.New("Domain '" + d + "' not valid: Invalid character '" + string(r) + "' in label") + } + if (i == 0 || i == len(label)-1) && r == '-' { + return "", errors.New("Domain '" + d + "' not valid: label '"+ label +"' starts or ends with hyphen") + } + } + } + + return d, nil +} From dd610e5f754c6cc970a9dcf50213bb9240ccbb36 Mon Sep 17 00:00:00 2001 From: Zen Wen Date: Mon, 13 Oct 2025 00:23:15 +0800 Subject: [PATCH 2/3] fix #845 --- src/mod/utils/utils.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/mod/utils/utils.go b/src/mod/utils/utils.go index 3c80c5f..219ead4 100644 --- a/src/mod/utils/utils.go +++ b/src/mod/utils/utils.go @@ -225,7 +225,12 @@ func NormalizeDomain(d string) (string, error) { } labels := strings.Split(d, ".") - for _, label := range labels { + for index, label := range labels { + if index ==0 { + if len(label) == 1 && label == "*" { + continue + } + } if len(label) == 0 { return "", errors.New("Domain '" + d + "' not valid: Empty label") } From d155ea3795a92abc699e7800c1e27e22a2dd4942 Mon Sep 17 00:00:00 2001 From: Zen Wen Date: Mon, 13 Oct 2025 00:50:28 +0800 Subject: [PATCH 3/3] Linting and formatting --- src/mod/acme/acme.go | 10 ++++------ src/mod/acme/utils.go | 6 ++---- src/mod/utils/utils.go | 17 +++++++++-------- 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/src/mod/acme/acme.go b/src/mod/acme/acme.go index a52d0be..334e9c0 100644 --- a/src/mod/acme/acme.go +++ b/src/mod/acme/acme.go @@ -432,18 +432,18 @@ func (a *ACMEHandler) HandleGetExpiredDomains(w http.ResponseWriter, r *http.Req // to renew the certificate, and sends a JSON response indicating the result of the renewal process. func (a *ACMEHandler) HandleRenewCertificate(w http.ResponseWriter, r *http.Request) { domainPara, err := utils.PostPara(r, "domains") - + //Clean each domain cleanedDomains := []string{} - if (domainPara != "") { + if domainPara != "" { for _, d := range strings.Split(domainPara, ",") { // Apply normalization on each domain nd, err := utils.NormalizeDomain(d) if err != nil { utils.SendErrorResponse(w, jsonEscape(err.Error())) return - } - cleanedDomains = append(cleanedDomains, nd) + } + cleanedDomains = append(cleanedDomains, nd) } } @@ -507,7 +507,6 @@ func (a *ACMEHandler) HandleRenewCertificate(w http.ResponseWriter, r *http.Requ dns = true } - // Default propagation timeout is 300 seconds propagationTimeout := 300 if dns { @@ -549,7 +548,6 @@ func (a *ACMEHandler) HandleRenewCertificate(w http.ResponseWriter, r *http.Requ a.Logf("Could not extract SANs from PEM, using domainPara only", err) } - // Extract DNS servers from the request var dnsServers []string dnsServersPara, err := utils.PostPara(r, "dnsServers") diff --git a/src/mod/acme/utils.go b/src/mod/acme/utils.go index b15b355..df05f2e 100644 --- a/src/mod/acme/utils.go +++ b/src/mod/acme/utils.go @@ -40,8 +40,6 @@ func ExtractDomains(certBytes []byte) ([]string, error) { return []string{}, errors.New("decode cert bytes failed") } - - func ExtractIssuerName(certBytes []byte) (string, error) { // Parse the PEM block block, _ := pem.Decode(certBytes) @@ -71,9 +69,9 @@ func ExtractDomainsFromPEM(pemFilePath string) ([]string, error) { certBytes, err := os.ReadFile(pemFilePath) if err != nil { - return nil, err + return nil, err } - domains,err := ExtractDomains(certBytes) + domains, err := ExtractDomains(certBytes) if err != nil { return nil, err } diff --git a/src/mod/utils/utils.go b/src/mod/utils/utils.go index 219ead4..c99de72 100644 --- a/src/mod/utils/utils.go +++ b/src/mod/utils/utils.go @@ -207,16 +207,17 @@ func ValidateListeningAddress(address string) bool { // - Converts to lowercase // - Removes trailing dot (FQDN canonicalization) // - Checks that the domain conforms to standard rules: -// * Each label ≤ 63 characters -// * Only letters, digits, and hyphens -// * Labels do not start or end with a hyphen -// * Full domain ≤ 253 characters +// - Each label ≤ 63 characters +// - Only letters, digits, and hyphens +// - Labels do not start or end with a hyphen +// - Full domain ≤ 253 characters +// // Returns an empty string if the domain is invalid. func NormalizeDomain(d string) (string, error) { d = strings.TrimSpace(d) d = strings.ToLower(d) d = strings.TrimSuffix(d, ".") - + if len(d) == 0 { return "", errors.New("domain is empty") } @@ -226,7 +227,7 @@ func NormalizeDomain(d string) (string, error) { labels := strings.Split(d, ".") for index, label := range labels { - if index ==0 { + if index == 0 { if len(label) == 1 && label == "*" { continue } @@ -239,11 +240,11 @@ func NormalizeDomain(d string) (string, error) { } for i, r := range label { - if !(unicode.IsLetter(r) || unicode.IsDigit(r) || r == '-') { + if !unicode.IsLetter(r) && !unicode.IsDigit(r) && r != '-' { return "", errors.New("Domain '" + d + "' not valid: Invalid character '" + string(r) + "' in label") } if (i == 0 || i == len(label)-1) && r == '-' { - return "", errors.New("Domain '" + d + "' not valid: label '"+ label +"' starts or ends with hyphen") + return "", errors.New("Domain '" + d + "' not valid: label '" + label + "' starts or ends with hyphen") } } }