package whois import ( "bufio" "context" "errors" "net" "strings" "testing" "time" "golang.org/x/text/encoding/simplifiedchinese" ) func TestGetServerReferralVariants(t *testing.T) { cases := []struct { name string raw string host string port string }{ { name: "registrar whois server", raw: "Registrar WHOIS Server: whois.markmonitor.com", host: "whois.markmonitor.com", port: "43", }, { name: "referral server with scheme and port", raw: "ReferralServer: whois://whois.example.net:4343", host: "whois.example.net", port: "4343", }, { name: "refer short key", raw: "refer: whois.nic.io", host: "whois.nic.io", port: "43", }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { host, port := getServer(tc.raw + "\n") if host != tc.host || port != tc.port { t.Fatalf("unexpected server, got=%s:%s want=%s:%s", host, port, tc.host, tc.port) } }) } } func TestNormalizeServerListDedupAndTrim(t *testing.T) { got := normalizeServerList([]string{" whois.a.com ", "", "WHOIS.A.COM", "whois.b.com"}) if len(got) != 2 { t.Fatalf("unexpected size: %d", len(got)) } if got[0] != "whois.a.com" || got[1] != "whois.b.com" { t.Fatalf("unexpected values: %#v", got) } } func TestWhoisWithOptionsContextCanceled(t *testing.T) { addr, shutdown := startMockWhoisServerWithDelay(t, strings.Join([]string{ "Domain Name: EXAMPLE.COM", "Registrar: TEST-REG", "", }, "\n"), 500*time.Millisecond) defer shutdown() c := NewClient().SetTimeout(2 * time.Second) ctx, cancel := context.WithTimeout(context.Background(), 60*time.Millisecond) defer cancel() _, err := c.WhoisWithOptionsContext(ctx, "example.com", QueryOptions{ Level: QueryAuto, OverrideServer: addr, }) if err == nil { t.Fatal("expected context timeout error") } if !errors.Is(err, context.DeadlineExceeded) { t.Fatalf("expected context deadline exceeded, got: %v", err) } } func TestWhoisEmptyResponseTypedError(t *testing.T) { addr, shutdown := startMockWhoisServer(t, "") defer shutdown() c := NewClient() _, err := c.WhoisWithOptions("example.com", QueryOptions{ Level: QueryAuto, OverrideServer: addr, }) if err == nil { t.Fatal("expected empty response error") } if !errors.Is(err, ErrEmptyResponse) { t.Fatalf("expected ErrEmptyResponse, got: %v", err) } } func TestResultMetaAndTypedError(t *testing.T) { addr, shutdown := startMockWhoisServer(t, strings.Join([]string{ "Domain Name: EXAMPLE.COM", "", }, "\n")) defer shutdown() c := NewClient() r, err := c.WhoisWithOptions("example.com", QueryOptions{ Level: QueryAuto, OverrideServer: addr, }) if err != nil { t.Fatalf("WhoisWithOptions() error: %v", err) } meta := r.Meta() if meta.Source != "whois" { t.Fatalf("unexpected source: %q", meta.Source) } if meta.Server == "" || !strings.Contains(meta.Server, ":") { t.Fatalf("unexpected server: %q", meta.Server) } if meta.RawLen <= 0 { t.Fatalf("unexpected raw length: %d", meta.RawLen) } if meta.ReasonCode != ErrorCodeParseWeak { t.Fatalf("unexpected reason code: %s", meta.ReasonCode) } if r.TypedError() == nil || !errors.Is(r.TypedError(), ErrParseWeak) { t.Fatalf("expected typed parse-weak error, got: %v", r.TypedError()) } } func TestAccessDeniedResultReason(t *testing.T) { addr, shutdown := startMockWhoisServer(t, strings.Join([]string{ "Requests of this client are not permitted. Please use https://www.nic.ch/whois/ for queries.", "", }, "\n")) defer shutdown() c := NewClient() r, err := c.WhoisWithOptions("nic.ch", QueryOptions{ Level: QueryAuto, OverrideServer: addr, }) if err != nil { t.Fatalf("WhoisWithOptions() error: %v", err) } if r.Meta().ReasonCode != ErrorCodeAccessDenied { t.Fatalf("unexpected reason code: %s", r.Meta().ReasonCode) } if !errors.Is(r.TypedError(), ErrAccessDenied) { t.Fatalf("expected ErrAccessDenied, got: %v", r.TypedError()) } } func TestWhoisDetectLegacyCharsetGBK(t *testing.T) { raw := strings.Join([]string{ "Domain Name: test.com", "Registrar: 测试注册商", "Name Server: ns1.test.com", "", }, "\n") gbkBytes, err := simplifiedchinese.GBK.NewEncoder().Bytes([]byte(raw)) if err != nil { t.Fatalf("encode GBK failed: %v", err) } addr, shutdown := startRawWhoisServer(t, gbkBytes) defer shutdown() c := NewClient() r, err := c.WhoisWithOptions("test.com", QueryOptions{ Level: QueryAuto, OverrideServer: addr, }) if err != nil { t.Fatalf("WhoisWithOptions() error: %v", err) } if r.Meta().Charset != "gbk" && r.Meta().Charset != "gb18030" { t.Fatalf("unexpected charset: %q", r.Meta().Charset) } if !strings.Contains(r.RawData(), "测试注册商") { t.Fatalf("decoded raw data does not contain expected chinese text: %q", r.RawData()) } } func startRawWhoisServer(t *testing.T, payload []byte) (addr string, shutdown func()) { t.Helper() ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatalf("listen raw whois failed: %v", err) } done := make(chan struct{}) go func() { for { conn, err := ln.Accept() if err != nil { select { case <-done: return default: return } } go func(c net.Conn) { defer c.Close() _ = c.SetDeadline(time.Now().Add(5 * time.Second)) _, _ = bufio.NewReader(c).ReadString('\n') if len(payload) > 0 { _, _ = c.Write(payload) } }(conn) } }() return ln.Addr().String(), func() { close(done) _ = ln.Close() } }