Skip to content

Commit 4c1c2fe

Browse files
GLobyNewretlehsclaude
authored
Resolve IDN domains (#57)
* Resolve IDN domains * Reject malformed IDNs instead of passing them through toASCII swallowed idna.ToASCII errors and fell back to the lowercased raw input, so a malformed IDN (e.g. a Bidi-rule violation like "صلa1.com") slipped past RegistrableDomain and got dispatched as a non-ASCII WHOIS/RDAP query — the exact "doomed query" the validation is meant to prevent. Make toASCII return (string, error) and surface it: NormalizeDomain now returns an error for invalid IDNs (passing IP literals through untouched, since IDNA rejects IPv6 colons), and RegistrableDomain reports its standard "not a valid domain" error on IDNA failure. All cmd entry points thread the error through so DNS/mail/TLS/HTTP/SEO/stack/whois reject malformed Unicode up front. Adds a doc comment for the exported NormalizeDomain and a rejection test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Ben Word <ben@benword.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent e11bcea commit 4c1c2fe

13 files changed

Lines changed: 205 additions & 12 deletions

File tree

cmd/all.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ var allCmd = &cobra.Command{
3030
Short: "Run all lookups combined (JSON output)",
3131
Args: cobra.ExactArgs(1),
3232
RunE: func(cmd *cobra.Command, args []string) error {
33-
input := normalizeDomain(args[0])
33+
input, err := normalizeDomain(args[0])
34+
if err != nil {
35+
return err
36+
}
3437
result := allResult{}
3538

3639
isIP := net.ParseIP(input) != nil

cmd/dns.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ package cmd
33
import (
44
"encoding/json"
55
"fmt"
6-
"strings"
76

87
"github.com/retlehs/quien/internal/dns"
8+
"github.com/retlehs/quien/internal/resolver"
99
"github.com/retlehs/quien/internal/retry"
1010
"github.com/spf13/cobra"
1111
)
@@ -15,7 +15,10 @@ var dnsCmd = &cobra.Command{
1515
Short: "DNS record lookup (JSON output)",
1616
Args: cobra.ExactArgs(1),
1717
RunE: func(cmd *cobra.Command, args []string) error {
18-
domain := normalizeDomain(args[0])
18+
domain, err := normalizeDomain(args[0])
19+
if err != nil {
20+
return err
21+
}
1922
records, err := retry.Do(func() (*dns.Records, error) {
2023
return dns.Lookup(domain)
2124
})
@@ -30,8 +33,8 @@ func init() {
3033
rootCmd.AddCommand(dnsCmd)
3134
}
3235

33-
func normalizeDomain(s string) string {
34-
return strings.TrimSuffix(strings.ToLower(strings.TrimSpace(s)), ".")
36+
func normalizeDomain(s string) (string, error) {
37+
return resolver.NormalizeDomain(s)
3538
}
3639

3740
func printJSON(v any) error {

cmd/http.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ var httpCmd = &cobra.Command{
1313
Short: "HTTP header and redirect lookup (JSON output)",
1414
Args: cobra.ExactArgs(1),
1515
RunE: func(cmd *cobra.Command, args []string) error {
16-
domain := normalizeDomain(args[0])
16+
domain, err := normalizeDomain(args[0])
17+
if err != nil {
18+
return err
19+
}
1720
result, err := retry.Do(func() (*httpinfo.Result, error) {
1821
return httpinfo.Lookup(domain)
1922
})

cmd/mail.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ var mailCmd = &cobra.Command{
1313
Short: "Mail configuration lookup — MX, SPF, DMARC, DKIM, BIMI (JSON output)",
1414
Args: cobra.ExactArgs(1),
1515
RunE: func(cmd *cobra.Command, args []string) error {
16-
domain := normalizeDomain(args[0])
16+
domain, err := normalizeDomain(args[0])
17+
if err != nil {
18+
return err
19+
}
1720
records, err := retry.Do(func() (*mail.Records, error) {
1821
return mail.Lookup(domain)
1922
})

cmd/root.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,10 @@ var rootCmd = &cobra.Command{
7070
return runLookup(input, isIP)
7171
}
7272

73-
input := strings.TrimSuffix(strings.ToLower(strings.TrimSpace(args[0])), ".")
73+
input, err := normalizeDomain(args[0])
74+
if err != nil {
75+
return err
76+
}
7477

7578
isIP := net.ParseIP(input) != nil
7679

@@ -97,6 +100,11 @@ var rootCmd = &cobra.Command{
97100

98101
func runLookup(input string, isIP bool) error {
99102
if !isIP {
103+
normalized, err := normalizeDomain(input)
104+
if err != nil {
105+
return err
106+
}
107+
input = normalized
100108
if _, err := resolver.RegistrableDomain(input); err != nil {
101109
return err
102110
}

cmd/seo.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ var seoCmd = &cobra.Command{
1313
Short: "SEO and Core Web Vitals analysis (JSON output)",
1414
Args: cobra.ExactArgs(1),
1515
RunE: func(cmd *cobra.Command, args []string) error {
16-
domain := normalizeDomain(args[0])
16+
domain, err := normalizeDomain(args[0])
17+
if err != nil {
18+
return err
19+
}
1720
result, err := retry.Do(func() (*seo.Result, error) {
1821
return seo.Analyze(domain)
1922
})

cmd/stack.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ var stackCmd = &cobra.Command{
1313
Short: "Detect technology stack — CMS, frameworks, libraries (JSON output)",
1414
Args: cobra.ExactArgs(1),
1515
RunE: func(cmd *cobra.Command, args []string) error {
16-
domain := normalizeDomain(args[0])
16+
domain, err := normalizeDomain(args[0])
17+
if err != nil {
18+
return err
19+
}
1720
result, err := retry.Do(func() (*stack.Result, error) {
1821
return stack.Detect(domain)
1922
})

cmd/tls.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ var tlsCmd = &cobra.Command{
1414
Short: "SSL/TLS certificate lookup (JSON output)",
1515
Args: cobra.ExactArgs(1),
1616
RunE: func(cmd *cobra.Command, args []string) error {
17-
domain := normalizeDomain(args[0])
17+
domain, err := normalizeDomain(args[0])
18+
if err != nil {
19+
return err
20+
}
1821
cert, err := retry.Do(func() (*tlsinfo.CertInfo, error) {
1922
return tlsinfo.Lookup(domain)
2023
})

cmd/whois.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ var whoisCmd = &cobra.Command{
1313
Short: "WHOIS/RDAP registration lookup (JSON output)",
1414
Args: cobra.ExactArgs(1),
1515
RunE: func(cmd *cobra.Command, args []string) error {
16-
input := normalizeDomain(args[0])
16+
input, err := normalizeDomain(args[0])
17+
if err != nil {
18+
return err
19+
}
1720
if net.ParseIP(input) != nil {
1821
info, err := resolver.LookupIP(input)
1922
if err != nil {

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,6 @@ require (
3232
golang.org/x/mod v0.36.0 // indirect
3333
golang.org/x/sync v0.20.0 // indirect
3434
golang.org/x/sys v0.45.0 // indirect
35+
golang.org/x/text v0.37.0 // indirect
3536
golang.org/x/tools v0.45.0 // indirect
3637
)

0 commit comments

Comments
 (0)