fix: no need for idna conversion in domain validator

This commit is contained in:
Stavros
2026-08-23 00:58:11 +03:00
parent 61d372b288
commit 8614f8768b
4 changed files with 20 additions and 66 deletions
+10 -1
View File
@@ -38,7 +38,16 @@ func SafeParseAppURL(str string) (string, error) {
return "", fmt.Errorf("ip addresses not allowed") return "", fmt.Errorf("ip addresses not allowed")
} }
hostname, err = idna.Lookup.ToASCII(hostname) i := idna.New(
idna.MapForLookup(),
idna.Transitional(false),
idna.BidiRule(),
idna.StrictDomainName(false),
idna.CheckHyphens(true),
idna.CheckJoiners(false),
)
hostname, err = i.ToASCII(hostname)
if err != nil { if err != nil {
return "", fmt.Errorf("failed to convert hostname to ascii: %w", err) return "", fmt.Errorf("failed to convert hostname to ascii: %w", err)
+8 -1
View File
@@ -43,6 +43,13 @@ func TestSafeParseAPPURL(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, expected, result) assert.Equal(t, expected, result)
// Underscores
appURL = "http://sub_tinyauth.app"
expected = "http://sub_tinyauth.app"
result, err = utils.SafeParseAppURL(appURL)
assert.NoError(t, err)
assert.Equal(t, expected, result)
// Lowercase // Lowercase
appURL = "HTTP://SUb.tinyAUth.aPP" appURL = "HTTP://SUb.tinyAUth.aPP"
expected = "http://sub.tinyauth.app" expected = "http://sub.tinyauth.app"
@@ -66,7 +73,7 @@ func TestSafeParseAPPURL(t *testing.T) {
assert.ErrorContains(t, err, "invalid url") assert.ErrorContains(t, err, "invalid url")
// Invalid punycode // Invalid punycode
appURL = "http://ab--cd.example.com" appURL = "http://xn--h-kva.example.com"
_, err = utils.SafeParseAppURL(appURL) _, err = utils.SafeParseAppURL(appURL)
assert.ErrorContains(t, err, "failed to convert hostname to ascii") assert.ErrorContains(t, err, "failed to convert hostname to ascii")
+2 -16
View File
@@ -11,8 +11,6 @@ import (
"net" "net"
"net/url" "net/url"
"strings" "strings"
"golang.org/x/net/idna"
) )
// Errors // Errors
@@ -114,23 +112,11 @@ func (v *DomainValidator) getURL(i string) (*url.URL, error) {
} }
func (v *DomainValidator) getHostname(hostname string) (string, error) { func (v *DomainValidator) getHostname(hostname string) (string, error) {
hostname = strings.ToLower(hostname)
hostname = strings.TrimSuffix(hostname, ".")
if net.ParseIP(hostname) != nil { if net.ParseIP(hostname) != nil {
return "", fmt.Errorf("ip addresses are not supported") return "", fmt.Errorf("ip addresses are not supported")
} }
i := idna.New( hostname = strings.ToLower(hostname)
idna.MapForLookup(), hostname = strings.TrimSuffix(hostname, ".")
idna.Transitional(false),
idna.BidiRule(),
idna.StrictDomainName(false),
idna.CheckHyphens(false),
idna.CheckJoiners(false),
)
hostname, err := i.ToASCII(hostname)
if err != nil {
return "", fmt.Errorf("failed to convert hostname to ascii: %w", err)
}
return hostname, nil return hostname, nil
} }
-48
View File
@@ -50,16 +50,6 @@ func TestDomainValidator_SafeHostname(t *testing.T) {
input: "https://example.com", input: "https://example.com",
expected: "example.com", expected: "example.com",
}, },
{
description: "Domain with underscores should pass",
input: "https://my_domain.com",
expected: "my_domain.com",
},
{
description: "Domain with leading hyphen should pass",
input: "https://-my-domain.com",
expected: "-my-domain.com",
},
{ {
description: "Domain without scheme should parse if scheme is disabled", description: "Domain without scheme should parse if scheme is disabled",
input: "example.com", input: "example.com",
@@ -111,18 +101,6 @@ func TestDomainValidator_SafeHostname(t *testing.T) {
assert.ErrorContains(t, e, "ip addresses are not supported") assert.ErrorContains(t, e, "ip addresses are not supported")
}, },
}, },
{
description: "Domains with unicode characters should be allowed",
input: "bücher.example.com",
expected: "xn--bcher-kva.example.com",
},
{
description: "Invalid IDNA domain should fail",
input: "xn--r-kva.example.com",
errorFunc: func(t *testing.T, e error) {
assert.ErrorContains(t, e, "invalid label")
},
},
{ {
description: "With port enabled without any port should work", description: "With port enabled without any port should work",
options: DomainValidatorOptions{WithPort: true}, options: DomainValidatorOptions{WithPort: true},
@@ -204,22 +182,6 @@ func TestDomainValidator_Validate(t *testing.T) {
expected: "https://example.com:443", expected: "https://example.com:443",
actual: "https://example.com:443", actual: "https://example.com:443",
}, },
{
description: "Failure to format expected domain should fail",
expected: "xn--r-kva.example.com",
actual: "example.com",
errorFunc: func(t *testing.T, e error) {
assert.ErrorContains(t, e, "idna: invalid label")
},
},
{
description: "Failure to format check domain should fail",
expected: "example.com",
actual: "xn--r-kva.example.com",
errorFunc: func(t *testing.T, e error) {
assert.ErrorContains(t, e, "idna: invalid label")
},
},
{ {
description: "Valid domains with matching schemes and ports should pass", description: "Valid domains with matching schemes and ports should pass",
options: DomainValidatorOptions{WithScheme: true, AllowedSchemes: []string{"https", "http"}, WithPort: true}, options: DomainValidatorOptions{WithScheme: true, AllowedSchemes: []string{"https", "http"}, WithPort: true},
@@ -246,16 +208,6 @@ func TestDomainValidator_Validate(t *testing.T) {
actual: "example.com", actual: "example.com",
expected: "example.com", expected: "example.com",
}, },
{
description: "Unicode valid domains should pass",
expected: "xn--bcher-kva.example.com",
actual: "bücher.example.com",
},
{
description: "Unicode valid domains should pass (reverse)",
expected: "bücher.example.com",
actual: "xn--bcher-kva.example.com",
},
{ {
description: "Non matching hostnames should fail", description: "Non matching hostnames should fail",
expected: "example.com", expected: "example.com",