460 lines
13 KiB
Go
460 lines
13 KiB
Go
package whois
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestLookupAutoPreferRDAP(t *testing.T) {
|
|
rdapSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/rdap/domain/example.com" {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/rdap+json")
|
|
_, _ = w.Write([]byte(`{
|
|
"objectClassName":"domain",
|
|
"ldhName":"example.com",
|
|
"status":["active"],
|
|
"nameservers":[{"ldhName":"ns1.example.com"}],
|
|
"events":[{"eventAction":"registration","eventDate":"2020-01-01T00:00:00Z"}]
|
|
}`))
|
|
}))
|
|
defer rdapSrv.Close()
|
|
|
|
rdc, err := NewRDAPClientWithBootstrap(&RDAPBootstrap{
|
|
Version: "1.0",
|
|
Services: []RDAPService{
|
|
{TLDs: []string{"com"}, URLs: []string{rdapSrv.URL + "/rdap/"}},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("NewRDAPClientWithBootstrap() error: %v", err)
|
|
}
|
|
|
|
c := NewClient()
|
|
got, meta, err := c.Lookup("example.com",
|
|
WithLookupMode(LookupModeAutoPreferRDAP),
|
|
WithLookupRDAPClient(rdc),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("Lookup() error: %v", err)
|
|
}
|
|
if meta.Source != LookupSourceRDAP {
|
|
t.Fatalf("unexpected source: %s", meta.Source)
|
|
}
|
|
if !got.Exists() || got.Domain() != "example.com" {
|
|
t.Fatalf("unexpected result: exists=%v domain=%q", got.Exists(), got.Domain())
|
|
}
|
|
if len(got.NsServers()) != 1 || got.NsServers()[0] != "ns1.example.com" {
|
|
t.Fatalf("unexpected ns list: %#v", got.NsServers())
|
|
}
|
|
}
|
|
|
|
func TestLookupAutoFallbackToWhois(t *testing.T) {
|
|
rdapSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
http.Error(w, "temporary", http.StatusInternalServerError)
|
|
}))
|
|
defer rdapSrv.Close()
|
|
|
|
rdc, err := NewRDAPClientWithBootstrap(&RDAPBootstrap{
|
|
Version: "1.0",
|
|
Services: []RDAPService{
|
|
{TLDs: []string{"com"}, URLs: []string{rdapSrv.URL}},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("NewRDAPClientWithBootstrap() error: %v", err)
|
|
}
|
|
|
|
whoisAddr, shutdown := startMockWhoisServer(t, strings.Join([]string{
|
|
"Domain Name: EXAMPLE.COM",
|
|
"Registrar: TEST-REG",
|
|
"Creation Date: 2020-01-01T00:00:00Z",
|
|
"Registry Expiry Date: 2030-01-01T00:00:00Z",
|
|
"Updated Date: 2024-01-01T00:00:00Z",
|
|
"Name Server: NS2.EXAMPLE.COM",
|
|
"Status: clientTransferProhibited",
|
|
"",
|
|
}, "\n"))
|
|
defer shutdown()
|
|
|
|
c := NewClient()
|
|
got, meta, err := c.Lookup("example.com",
|
|
WithLookupMode(LookupModeAutoPreferRDAP),
|
|
WithLookupRDAPClient(rdc),
|
|
WithLookupWhoisOptions(QueryOptions{
|
|
Level: QueryAuto,
|
|
OverrideServer: whoisAddr,
|
|
}),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("Lookup() error: %v", err)
|
|
}
|
|
if meta.Source != LookupSourceWHOIS {
|
|
t.Fatalf("unexpected source: %s", meta.Source)
|
|
}
|
|
if got.Registrar() != "TEST-REG" {
|
|
t.Fatalf("unexpected registrar: %q", got.Registrar())
|
|
}
|
|
if !got.HasExpireDate() {
|
|
t.Fatal("expected expire date from whois fallback")
|
|
}
|
|
if len(meta.WarningList) == 0 {
|
|
t.Fatal("expected fallback warning")
|
|
}
|
|
}
|
|
|
|
func TestLookupBothPreferRDAPMerge(t *testing.T) {
|
|
rdapSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
_, _ = w.Write([]byte(`{
|
|
"objectClassName":"domain",
|
|
"ldhName":"example.com",
|
|
"status":["active"],
|
|
"nameservers":[{"ldhName":"ns1.example.com"}]
|
|
}`))
|
|
}))
|
|
defer rdapSrv.Close()
|
|
|
|
rdc, err := NewRDAPClientWithBootstrap(&RDAPBootstrap{
|
|
Version: "1.0",
|
|
Services: []RDAPService{
|
|
{TLDs: []string{"com"}, URLs: []string{rdapSrv.URL}},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("NewRDAPClientWithBootstrap() error: %v", err)
|
|
}
|
|
|
|
whoisAddr, shutdown := startMockWhoisServer(t, strings.Join([]string{
|
|
"Domain Name: EXAMPLE.COM",
|
|
"Registrar: TEST-REG",
|
|
"Name Server: NS2.EXAMPLE.COM",
|
|
"Status: clientTransferProhibited",
|
|
"",
|
|
}, "\n"))
|
|
defer shutdown()
|
|
|
|
c := NewClient()
|
|
got, meta, err := c.LookupContext(context.Background(), "example.com",
|
|
WithLookupMode(LookupModeBothPreferRDAP),
|
|
WithLookupRDAPClient(rdc),
|
|
WithLookupWhoisOptions(QueryOptions{OverrideServer: whoisAddr, Level: QueryAuto}),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("LookupContext() error: %v", err)
|
|
}
|
|
if meta.Source != LookupSourceMerged {
|
|
t.Fatalf("unexpected source: %s", meta.Source)
|
|
}
|
|
if got.Registrar() != "TEST-REG" {
|
|
t.Fatalf("expected merged registrar from whois, got %q", got.Registrar())
|
|
}
|
|
if len(got.NsServers()) != 2 {
|
|
t.Fatalf("expected merged ns servers, got %#v", got.NsServers())
|
|
}
|
|
}
|
|
|
|
func TestLookupRDAPHostsOps(t *testing.T) {
|
|
rdapSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/rdap/domain/example.com" {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
_, _ = w.Write([]byte(`{"objectClassName":"domain","ldhName":"example.com"}`))
|
|
}))
|
|
defer rdapSrv.Close()
|
|
|
|
u, err := url.Parse(rdapSrv.URL)
|
|
if err != nil {
|
|
t.Fatalf("url.Parse() error: %v", err)
|
|
}
|
|
fakeHost := "rdap.invalid"
|
|
fakeBase := "http://" + fakeHost + ":" + u.Port() + "/rdap/"
|
|
|
|
rdc, err := NewRDAPClientWithBootstrap(&RDAPBootstrap{
|
|
Version: "1.0",
|
|
Services: []RDAPService{
|
|
{TLDs: []string{"com"}, URLs: []string{fakeBase}},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("NewRDAPClientWithBootstrap() error: %v", err)
|
|
}
|
|
|
|
c := NewClient()
|
|
_, _, err = c.Lookup("example.com",
|
|
WithLookupMode(LookupModeRDAPOnly),
|
|
WithLookupRDAPClient(rdc),
|
|
)
|
|
if err == nil {
|
|
t.Fatal("expected rdap-only lookup to fail without hosts override")
|
|
}
|
|
|
|
got, meta, err := c.Lookup("example.com",
|
|
WithLookupMode(LookupModeRDAPOnly),
|
|
WithLookupRDAPClient(rdc),
|
|
WithLookupRDAPOps(LookupRDAPOps{
|
|
Hosts: map[string]string{
|
|
fakeHost: "127.0.0.1",
|
|
},
|
|
}),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("Lookup() with rdap hosts ops error: %v", err)
|
|
}
|
|
if meta.Source != LookupSourceRDAP || !got.Exists() {
|
|
t.Fatalf("unexpected result source=%s exists=%v", meta.Source, got.Exists())
|
|
}
|
|
}
|
|
|
|
func TestLookupWHOISTimeoutOps(t *testing.T) {
|
|
whoisAddr, shutdown := startMockWhoisServerWithDelay(t, strings.Join([]string{
|
|
"Domain Name: EXAMPLE.COM",
|
|
"Registrar: TEST-REG",
|
|
"",
|
|
}, "\n"), 300*time.Millisecond)
|
|
defer shutdown()
|
|
|
|
c := NewClient()
|
|
_, _, err := c.Lookup("example.com",
|
|
WithLookupMode(LookupModeWHOISOnly),
|
|
WithLookupWhoisOptions(QueryOptions{OverrideServer: whoisAddr, Level: QueryAuto}),
|
|
WithLookupWHOISTimeout(80*time.Millisecond),
|
|
)
|
|
if err == nil {
|
|
t.Fatal("expected whois timeout error")
|
|
}
|
|
}
|
|
|
|
func TestLookupWhoisOnlyIgnoresRDAPProxyOps(t *testing.T) {
|
|
whoisAddr, shutdown := startMockWhoisServer(t, strings.Join([]string{
|
|
"Domain Name: EXAMPLE.COM",
|
|
"Registrar: TEST-REG",
|
|
"",
|
|
}, "\n"))
|
|
defer shutdown()
|
|
|
|
c := NewClient()
|
|
got, meta, err := c.Lookup("example.com",
|
|
WithLookupMode(LookupModeWHOISOnly),
|
|
WithLookupWhoisOptions(QueryOptions{OverrideServer: whoisAddr, Level: QueryAuto}),
|
|
WithLookupRDAPProxy("http://127.0.0.1:1"),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("whois-only lookup should not be affected by rdap proxy ops: %v", err)
|
|
}
|
|
if !got.Exists() || meta.Source != LookupSourceWHOIS {
|
|
t.Fatalf("unexpected whois-only result source=%s exists=%v", meta.Source, got.Exists())
|
|
}
|
|
}
|
|
|
|
func TestLookupWhoisOnlyInvalidCommonProxyFailsFast(t *testing.T) {
|
|
c := NewClient()
|
|
_, _, err := c.Lookup("example.com",
|
|
WithLookupMode(LookupModeWHOISOnly),
|
|
WithLookupProxy("http://127.0.0.1:8080"),
|
|
)
|
|
if err == nil {
|
|
t.Fatal("expected whois-only lookup to fail on unsupported common proxy")
|
|
}
|
|
if !strings.Contains(err.Error(), "whois proxy unsupported") {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestLookupAutoUsesTLDStrategyWhoisOnly(t *testing.T) {
|
|
var rdapHit int32
|
|
rdapSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
atomic.AddInt32(&rdapHit, 1)
|
|
_, _ = w.Write([]byte(`{"objectClassName":"domain","ldhName":"example.com"}`))
|
|
}))
|
|
defer rdapSrv.Close()
|
|
|
|
rdc, err := NewRDAPClientWithBootstrap(&RDAPBootstrap{
|
|
Version: "1.0",
|
|
Services: []RDAPService{
|
|
{TLDs: []string{"com"}, URLs: []string{rdapSrv.URL}},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("NewRDAPClientWithBootstrap() error: %v", err)
|
|
}
|
|
|
|
whoisAddr, shutdown := startMockWhoisServer(t, strings.Join([]string{
|
|
"Domain Name: EXAMPLE.COM",
|
|
"Registrar: TEST-REG",
|
|
"",
|
|
}, "\n"))
|
|
defer shutdown()
|
|
|
|
c := NewClient()
|
|
got, meta, err := c.Lookup("example.com",
|
|
WithLookupMode(LookupModeAutoPreferRDAP),
|
|
WithLookupTLDStrategy("com", LookupModeWHOISOnly),
|
|
WithLookupRDAPClient(rdc),
|
|
WithLookupWhoisOptions(QueryOptions{OverrideServer: whoisAddr, Level: QueryAuto}),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("Lookup() error: %v", err)
|
|
}
|
|
if meta.Mode != LookupModeWHOISOnly {
|
|
t.Fatalf("unexpected resolved mode: %s", meta.Mode)
|
|
}
|
|
if meta.Source != LookupSourceWHOIS || !got.Exists() {
|
|
t.Fatalf("unexpected result source=%s exists=%v", meta.Source, got.Exists())
|
|
}
|
|
if atomic.LoadInt32(&rdapHit) != 0 {
|
|
t.Fatalf("rdap should not be called when strategy forces whois-only, hits=%d", atomic.LoadInt32(&rdapHit))
|
|
}
|
|
}
|
|
|
|
func TestLookupStrategyDefaultMode(t *testing.T) {
|
|
whoisAddr, shutdown := startMockWhoisServer(t, strings.Join([]string{
|
|
"Domain Name: EXAMPLE.NET",
|
|
"Registrar: TEST-REG",
|
|
"",
|
|
}, "\n"))
|
|
defer shutdown()
|
|
|
|
c := NewClient()
|
|
got, meta, err := c.Lookup("example.net",
|
|
WithLookupMode(LookupModeAutoPreferRDAP),
|
|
WithLookupStrategyDefaultMode(LookupModeWHOISOnly),
|
|
WithLookupWhoisOptions(QueryOptions{OverrideServer: whoisAddr, Level: QueryAuto}),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("Lookup() error: %v", err)
|
|
}
|
|
if meta.Mode != LookupModeWHOISOnly {
|
|
t.Fatalf("unexpected resolved mode: %s", meta.Mode)
|
|
}
|
|
if meta.Source != LookupSourceWHOIS || !got.Exists() {
|
|
t.Fatalf("unexpected source=%s exists=%v", meta.Source, got.Exists())
|
|
}
|
|
}
|
|
|
|
func TestLookupRDAPBootstrapConvenienceOptions(t *testing.T) {
|
|
cacheKey := t.Name() + "-cache"
|
|
ClearRDAPBootstrapLayeredCache(cacheKey)
|
|
|
|
rdapSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/rdap/domain/example.com" {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
_, _ = w.Write([]byte(`{"objectClassName":"domain","ldhName":"example.com"}`))
|
|
}))
|
|
defer rdapSrv.Close()
|
|
|
|
bootstrapRaw := `{"version":"1.0","services":[[["com"],["` + rdapSrv.URL + `/rdap/"]]]}`
|
|
bootstrapSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write([]byte(bootstrapRaw))
|
|
}))
|
|
defer bootstrapSrv.Close()
|
|
|
|
c := NewClient()
|
|
got, meta, err := c.Lookup("example.com",
|
|
WithLookupMode(LookupModeRDAPOnly),
|
|
WithLookupRDAPBootstrapRemoteRefresh(true, bootstrapSrv.URL),
|
|
WithLookupRDAPBootstrapCache(time.Minute, cacheKey),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("Lookup() error: %v", err)
|
|
}
|
|
if meta.Source != LookupSourceRDAP || !got.Exists() {
|
|
t.Fatalf("unexpected source=%s exists=%v", meta.Source, got.Exists())
|
|
}
|
|
}
|
|
|
|
func TestLookupRDAPBootstrapIgnoreRemoteError(t *testing.T) {
|
|
cacheKey := t.Name() + "-cache"
|
|
ClearRDAPBootstrapLayeredCache(cacheKey)
|
|
|
|
rdapSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/rdap/domain/example.com" {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
_, _ = w.Write([]byte(`{"objectClassName":"domain","ldhName":"example.com"}`))
|
|
}))
|
|
defer rdapSrv.Close()
|
|
|
|
localRaw := `{"version":"1.0","services":[[["com"],["` + rdapSrv.URL + `/rdap/"]]]}`
|
|
localPath := filepath.Join(t.TempDir(), "rdap_local.json")
|
|
if err := os.WriteFile(localPath, []byte(localRaw), 0644); err != nil {
|
|
t.Fatalf("write local bootstrap failed: %v", err)
|
|
}
|
|
|
|
remoteSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
http.Error(w, "refresh failed", http.StatusInternalServerError)
|
|
}))
|
|
defer remoteSrv.Close()
|
|
|
|
c := NewClient()
|
|
got, meta, err := c.Lookup("example.com",
|
|
WithLookupMode(LookupModeRDAPOnly),
|
|
WithLookupRDAPBootstrapLocalFiles(localPath),
|
|
WithLookupRDAPBootstrapRemoteRefresh(true, remoteSrv.URL),
|
|
WithLookupRDAPBootstrapIgnoreRemoteError(true),
|
|
WithLookupRDAPBootstrapCache(time.Minute, cacheKey),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("Lookup() with ignore remote error failed: %v", err)
|
|
}
|
|
if meta.Source != LookupSourceRDAP || !got.Exists() {
|
|
t.Fatalf("unexpected source=%s exists=%v", meta.Source, got.Exists())
|
|
}
|
|
}
|
|
|
|
func startMockWhoisServer(t *testing.T, response string) (addr string, shutdown func()) {
|
|
return startMockWhoisServerWithDelay(t, response, 0)
|
|
}
|
|
|
|
func startMockWhoisServerWithDelay(t *testing.T, response string, delay time.Duration) (addr string, shutdown func()) {
|
|
t.Helper()
|
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatalf("listen mock 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 delay > 0 {
|
|
time.Sleep(delay)
|
|
}
|
|
_, _ = io.WriteString(c, response)
|
|
}(conn)
|
|
}
|
|
}()
|
|
return ln.Addr().String(), func() {
|
|
close(done)
|
|
_ = ln.Close()
|
|
}
|
|
}
|