This commit is contained in:
兔子 2024-08-16 21:30:27 +08:00
parent 83cfd8c5e7
commit 129514a159
6 changed files with 679 additions and 40 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
.idea .idea
bin

2
go.mod
View File

@ -2,4 +2,4 @@ module b612.me/sdk/whois
go 1.21.2 go 1.21.2
require golang.org/x/net v0.24.0 require golang.org/x/net v0.28.0

4
go.sum
View File

@ -1,2 +1,2 @@
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=

499
parse.go
View File

@ -16,13 +16,29 @@ func parse(domain string, result string) (Result, error) {
data, err = dotJPParser(domain, result) data, err = dotJPParser(domain, result)
case "tw": case "tw":
data, err = dotTWParser(domain, result) data, err = dotTWParser(domain, result)
case "name":
data, err = dotNameParser(domain, result)
default: default:
data, err = commonParser(domain, result) data, err = commonParser(domain, result)
case "edu":
data, err = dotEduParser(domain, result)
case "int":
data, err = dotIntParser(domain, result)
case "ae":
data, err = dotAeParser(domain, result)
case "ai":
data, err = commonParserWithDate(domain, result, false, false, false)
case "am":
data, err = dotAmParser(domain, result)
} }
return data, err return data, err
} }
func commonParser(domain, data string) (Result, error) { func commonParser(domain, data string) (Result, error) {
return commonParserWithDate(domain, data, true, true, true)
}
func commonParserWithDate(domain, data string, hasCreate bool, hasExpire bool, hasUpdate bool) (Result, error) {
var res = Result{ var res = Result{
domain: domain, domain: domain,
rawData: data, rawData: data,
@ -34,8 +50,8 @@ func commonParser(domain, data string) (Result, error) {
for _, line := range split { for _, line := range split {
line = strings.TrimSpace(line) line = strings.TrimSpace(line)
if !res.exists { if !res.exists {
for _, token := range []string{"Domain not found.", "No match for", "No match", "No Data Found", "No entries found", "No match for domain", "No matching record", "No Found", "No Object"} { for _, token := range []string{"No Object Found", "Domain not found.", "No match for", "No match", "No Data Found", "No entries found", "No match for domain", "No matching record", "No Found", "No Object"} {
if strings.HasPrefix(token, line) { if strings.HasPrefix(line, token) {
res.exists = false res.exists = false
return res, nil return res, nil
} }
@ -43,6 +59,9 @@ func commonParser(domain, data string) (Result, error) {
} }
if strings.HasPrefix(line, "Domain Name:") { if strings.HasPrefix(line, "Domain Name:") {
res.exists = true res.exists = true
res.hasUpdateDate = hasUpdate
res.hasRegisterDate = hasCreate
res.hasExpireDate = hasExpire
res.nsServers = []string{} res.nsServers = []string{}
res.domain = strings.TrimSpace(strings.TrimPrefix(line, "Domain Name:")) res.domain = strings.TrimSpace(strings.TrimPrefix(line, "Domain Name:"))
} }
@ -50,13 +69,28 @@ func commonParser(domain, data string) (Result, error) {
res.domainID = strings.TrimSpace(strings.TrimPrefix(line, "Registry Domain ID:")) res.domainID = strings.TrimSpace(strings.TrimPrefix(line, "Registry Domain ID:"))
} }
if strings.HasPrefix(line, "Updated Date:") { if strings.HasPrefix(line, "Updated Date:") {
res.updateDate = parseDate(strings.TrimSpace(strings.TrimPrefix(line, "Updated Date:"))) tmpDate := parseDate(strings.TrimSpace(strings.TrimPrefix(line, "Updated Date:")))
if !tmpDate.IsZero() {
res.updateDate = tmpDate
}
} }
if strings.HasPrefix(line, "Creation Date:") { if strings.HasPrefix(line, "Creation Date:") {
res.registerDate = parseDate(strings.TrimSpace(strings.TrimPrefix(line, "Creation Date:"))) tmpDate := parseDate(strings.TrimSpace(strings.TrimPrefix(line, "Creation Date:")))
if !tmpDate.IsZero() {
res.registerDate = tmpDate
}
} }
if strings.HasPrefix(line, "Registry Expiry Date:") { if strings.HasPrefix(line, "Registry Expiry Date:") {
res.expireDate = parseDate(strings.TrimSpace(strings.TrimPrefix(line, "Registry Expiry Date:"))) tmpDate := parseDate(strings.TrimSpace(strings.TrimPrefix(line, "Registry Expiry Date:")))
if !tmpDate.IsZero() {
res.expireDate = tmpDate
}
}
if strings.HasPrefix(line, "Registrar Registration Expiration Date:") {
tmpDate := parseDate(strings.TrimSpace(strings.TrimPrefix(line, "Registrar Registration Expiration Date:")))
if !tmpDate.IsZero() {
res.expireDate = tmpDate
}
} }
if strings.HasPrefix(line, "Registrar:") { if strings.HasPrefix(line, "Registrar:") {
res.registar = strings.TrimSpace(strings.TrimPrefix(line, "Registrar:")) res.registar = strings.TrimSpace(strings.TrimPrefix(line, "Registrar:"))
@ -65,6 +99,10 @@ func commonParser(domain, data string) (Result, error) {
statusMap[strings.Split(strings.TrimSpace(strings.TrimPrefix(line, "Status:")), " ")[0]] = struct{}{} statusMap[strings.Split(strings.TrimSpace(strings.TrimPrefix(line, "Status:")), " ")[0]] = struct{}{}
} }
if strings.HasPrefix(line, "Domain Status:") { if strings.HasPrefix(line, "Domain Status:") {
if strings.Contains(line, "No Object Found") {
res.exists = false
return res, nil
}
statusMap[strings.Split(strings.TrimSpace(strings.TrimPrefix(line, "Domain Status:")), " ")[0]] = struct{}{} statusMap[strings.Split(strings.TrimSpace(strings.TrimPrefix(line, "Domain Status:")), " ")[0]] = struct{}{}
} }
if strings.HasPrefix(line, "Name Server:") { if strings.HasPrefix(line, "Name Server:") {
@ -73,9 +111,6 @@ func commonParser(domain, data string) (Result, error) {
if strings.HasPrefix(line, "DNSSEC:") { if strings.HasPrefix(line, "DNSSEC:") {
res.dnssec = strings.TrimSpace(strings.TrimPrefix(line, "DNSSEC:")) res.dnssec = strings.TrimSpace(strings.TrimPrefix(line, "DNSSEC:"))
} }
if strings.HasPrefix(line, "Registrar Registration Expiration Date:") {
res.expireDate = parseDate(strings.TrimSpace(strings.TrimPrefix(line, "Registrar Registration Expiration Date:")))
}
if strings.HasPrefix(line, "Registrant Name:") { if strings.HasPrefix(line, "Registrant Name:") {
r.Name = strings.TrimSpace(strings.TrimPrefix(line, "Registrant Name:")) r.Name = strings.TrimSpace(strings.TrimPrefix(line, "Registrant Name:"))
} }
@ -194,6 +229,61 @@ func commonParser(domain, data string) (Result, error) {
return res, nil return res, nil
} }
func dotNameParser(domain, data string) (Result, error) {
var res = Result{
domain: domain,
rawData: data,
}
var r, a, t PersonalInfo
split := strings.Split(data, "\n")
statusMap := make(map[string]struct{})
for _, line := range split {
line = strings.TrimSpace(line)
if !res.exists {
for _, token := range []string{"No match for"} {
if strings.HasPrefix(line, token) {
res.exists = false
return res, nil
}
}
}
if strings.HasPrefix(line, "Domain Name:") {
res.exists = true
res.hasUpdateDate = false
res.hasRegisterDate = false
res.hasExpireDate = false
res.nsServers = []string{}
res.domain = strings.TrimSpace(strings.TrimPrefix(line, "Domain Name:"))
}
if strings.HasPrefix(line, "Registry Domain ID:") {
res.domainID = strings.TrimSpace(strings.TrimPrefix(line, "Registry Domain ID:"))
}
if strings.HasPrefix(line, "Registrar:") {
res.registar = strings.TrimSpace(strings.TrimPrefix(line, "Registrar:"))
}
if strings.HasPrefix(line, "Status:") {
statusMap[strings.Split(strings.TrimSpace(strings.TrimPrefix(line, "Status:")), " ")[0]] = struct{}{}
}
if strings.HasPrefix(line, "Domain Status:") {
if strings.Contains(line, "No Object Found") {
res.exists = false
return res, nil
}
statusMap[strings.Split(strings.TrimSpace(strings.TrimPrefix(line, "Domain Status:")), " ")[0]] = struct{}{}
}
}
for status := range statusMap {
res.statusRaw = append(res.statusRaw, status)
}
res.registerInfo = r
res.adminInfo = a
res.techInfo = t
return res, nil
}
func dotCNParser(domain, data string) (Result, error) { func dotCNParser(domain, data string) (Result, error) {
var res = Result{ var res = Result{
domain: domain, domain: domain,
@ -204,6 +294,13 @@ func dotCNParser(domain, data string) (Result, error) {
res.exists = false res.exists = false
return res, nil return res, nil
} }
if strings.HasPrefix(strings.TrimSpace(data), "the Domain Name you apply can not be registered online") {
res.exists = false
return res, nil
}
res.hasUpdateDate = false
res.hasRegisterDate = true
res.hasExpireDate = true
res.exists = true res.exists = true
statusMap := make(map[string]struct{}) statusMap := make(map[string]struct{})
split := strings.Split(data, "\n") split := strings.Split(data, "\n")
@ -267,6 +364,9 @@ func dotJPParser(domain, data string) (Result, error) {
line = strings.TrimSpace(line) line = strings.TrimSpace(line)
if strings.HasPrefix(line, "[Domain Name]") { if strings.HasPrefix(line, "[Domain Name]") {
res.exists = true res.exists = true
res.hasUpdateDate = true
res.hasRegisterDate = true
res.hasExpireDate = true
res.nsServers = []string{} res.nsServers = []string{}
res.domain = strings.TrimSpace(strings.TrimPrefix(line, "[Domain Name]")) res.domain = strings.TrimSpace(strings.TrimPrefix(line, "[Domain Name]"))
} }
@ -341,6 +441,9 @@ func dotTWParser(domain, data string) (Result, error) {
} }
if strings.HasPrefix(line, "Domain Name:") { if strings.HasPrefix(line, "Domain Name:") {
res.exists = true res.exists = true
res.hasUpdateDate = true
res.hasRegisterDate = true
res.hasExpireDate = true
res.nsServers = []string{} res.nsServers = []string{}
res.domain = strings.TrimSpace(strings.TrimPrefix(line, "Domain Name:")) res.domain = strings.TrimSpace(strings.TrimPrefix(line, "Domain Name:"))
} }
@ -398,8 +501,373 @@ func dotTWParser(domain, data string) (Result, error) {
return res, nil return res, nil
} }
func dotEduParser(domain, data string) (Result, error) {
var res = Result{
domain: domain,
rawData: data,
}
split := strings.Split(data, "\n")
startNs := false
var currentStatus uint8 = 0
var startIdx int
var r, a, t, p PersonalInfo
for idx, line := range split {
line = strings.TrimSpace(line)
if startNs && len(line) == 0 {
startNs = false
}
if strings.HasPrefix(line, "NO MATCH:") {
res.exists = false
return res, nil
}
if strings.HasPrefix(line, "Domain Name:") {
res.exists = true
res.hasUpdateDate = true
res.hasRegisterDate = true
res.hasExpireDate = true
res.nsServers = []string{}
res.domain = strings.TrimSpace(strings.TrimPrefix(line, "Domain Name:"))
}
if strings.HasPrefix(line, "Domain record activated:") {
res.registerDate = parseEduDate(strings.TrimSpace(strings.TrimPrefix(line, "Domain record activated:")))
}
if strings.HasPrefix(line, "Domain record last updated:") {
res.updateDate = parseEduDate(strings.TrimSpace(strings.TrimPrefix(line, "Domain record last updated:")))
}
if strings.HasPrefix(line, "Domain expires:") {
res.expireDate = parseEduDate(strings.TrimSpace(strings.TrimPrefix(line, "Domain expires:")))
}
if strings.HasPrefix(line, "Registrant:") {
currentStatus = 1
startIdx = idx
p = PersonalInfo{}
continue
}
if strings.HasPrefix(line, "Administrative Contact:") {
currentStatus = 2
startIdx = idx
p = PersonalInfo{}
continue
}
if strings.HasPrefix(line, "Technical Contact:") {
currentStatus = 3
startIdx = idx
p = PersonalInfo{}
continue
}
if strings.HasPrefix(line, "Name Servers:") {
startNs = true
continue
}
if startNs {
res.nsServers = append(res.nsServers, line)
}
if currentStatus > 0 {
switch idx - startIdx {
case 1:
p.Name = line
case 2, 3, 4, 5:
p.Addr += line
case 6:
p.Phone = line
case 7:
p.Email = line
}
if len(line) == 0 {
switch currentStatus {
case 1:
r = p
case 2:
a = p
case 3:
t = p
}
currentStatus = 0
}
}
}
res.registerInfo = r
res.adminInfo = a
res.techInfo = t
return res, nil
}
func dotIntParser(domain, data string) (Result, error) {
var res = Result{
domain: domain,
rawData: data,
}
split := strings.Split(data, "\n")
var currentStatus uint8 = 0
var r, a, t, p PersonalInfo
for _, line := range split {
line = strings.TrimSpace(line)
if strings.Contains(line, "but this server does not have") {
res.exists = false
return res, nil
}
if strings.HasPrefix(line, "domain:") {
res.exists = true
res.hasUpdateDate = true
res.hasRegisterDate = true
res.hasExpireDate = false
res.nsServers = []string{}
res.domain = strings.TrimSpace(strings.TrimPrefix(line, "domain:"))
}
if strings.HasPrefix(line, "created:") {
res.registerDate = parseYMDDate(strings.TrimSpace(strings.TrimPrefix(line, "created:")))
}
if strings.HasPrefix(line, "changed:") {
res.updateDate = parseYMDDate(strings.TrimSpace(strings.TrimPrefix(line, "changed:")))
}
if strings.HasPrefix(line, "organisation:") {
currentStatus = 1
p = PersonalInfo{
Org: strings.TrimSpace(strings.TrimPrefix(line, "organisation:")),
}
continue
}
if strings.HasPrefix(line, "contact:") && strings.Contains(line, "administrative") {
currentStatus = 2
p = PersonalInfo{}
continue
}
if strings.HasPrefix(line, "contact:") && strings.Contains(line, "technical") {
currentStatus = 3
p = PersonalInfo{}
continue
}
if strings.HasPrefix(line, "nserver:") {
nsList := strings.Split(strings.TrimSpace(strings.TrimPrefix(line, "nserver:")), " ")
for idx, ns := range nsList {
if idx == 0 {
res.nsServers = append(res.nsServers, ns)
continue
}
res.nsIps = append(res.nsIps, ns)
}
}
if currentStatus > 0 {
if strings.HasPrefix(line, "address:") {
p.Addr += strings.TrimSpace(strings.TrimPrefix(line, "address:")) + "\n"
continue
}
if strings.HasPrefix(line, "phone:") {
p.Phone = strings.TrimSpace(strings.TrimPrefix(line, "phone:"))
continue
}
if strings.HasPrefix(line, "e-mail:") {
p.Email = strings.TrimSpace(strings.TrimPrefix(line, "e-mail:"))
continue
}
if strings.HasPrefix(line, "fax-no:") {
p.Fax = strings.TrimSpace(strings.TrimPrefix(line, "fax-no:"))
continue
}
if len(line) == 0 {
p.Addr = strings.TrimSpace(p.Addr)
switch currentStatus {
case 1:
r = p
case 2:
a = p
case 3:
t = p
}
}
}
}
res.registerInfo = r
res.adminInfo = a
res.techInfo = t
return res, nil
}
func dotAeParser(domain, data string) (Result, error) {
var res = Result{
domain: domain,
rawData: data,
}
var r, a, t PersonalInfo
split := strings.Split(data, "\n")
statusMap := make(map[string]struct{})
for _, line := range split {
line = strings.TrimSpace(line)
if !res.exists {
for _, token := range []string{"No Data Found"} {
if strings.HasPrefix(line, token) {
res.exists = false
return res, nil
}
}
}
if strings.HasPrefix(line, "Domain Name:") {
res.exists = true
res.hasUpdateDate = false
res.hasRegisterDate = false
res.hasExpireDate = false
res.nsServers = []string{}
res.domain = strings.TrimSpace(strings.TrimPrefix(line, "Domain Name:"))
}
if strings.HasPrefix(line, "Registrar Name:") {
res.registar = strings.TrimSpace(strings.TrimPrefix(line, "Registrar Name:"))
}
if strings.HasPrefix(line, "Status:") {
statusMap[strings.Split(strings.TrimSpace(strings.TrimPrefix(line, "Status:")), " ")[0]] = struct{}{}
}
if strings.HasPrefix(line, "Name Server:") {
res.nsServers = append(res.nsServers, strings.TrimSpace(strings.TrimPrefix(line, "Name Server:")))
}
if strings.HasPrefix(line, "Registrant Contact Name:") {
r.Name = strings.TrimSpace(strings.TrimPrefix(line, "Registrant Contact Name:"))
}
if strings.HasPrefix(line, "Registrant Contact Organisation::") {
r.Org = strings.TrimSpace(strings.TrimPrefix(line, "Registrant Contact Organisation:"))
}
if strings.HasPrefix(line, "Registrant Contact Email:") {
r.Email = strings.TrimSpace(strings.TrimPrefix(line, "Registrant Contact Email:"))
}
if strings.HasPrefix(line, "Tech Contact Name:") {
t.Name = strings.TrimSpace(strings.TrimPrefix(line, "Tech Contact Name:"))
}
if strings.HasPrefix(line, "Tech Contact Organisation::") {
t.Org = strings.TrimSpace(strings.TrimPrefix(line, "Tech Contact Organisation:"))
}
if strings.HasPrefix(line, "Tech Contact Email:") {
t.Email = strings.TrimSpace(strings.TrimPrefix(line, "Tech Contact Email:"))
}
}
for status := range statusMap {
res.statusRaw = append(res.statusRaw, status)
}
res.registerInfo = r
res.adminInfo = a
res.techInfo = t
return res, nil
}
func dotAmParser(domain, data string) (Result, error) {
var res = Result{
domain: domain,
rawData: data,
}
split := strings.Split(data, "\n")
var currentStatus uint8 = 0
statusMap := make(map[string]struct{})
var r, a, t, p PersonalInfo
startNs := false
var tmpSlice []string
for _, line := range split {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "Status:") {
statusMap[strings.Split(strings.TrimSpace(strings.TrimPrefix(line, "Status:")), " ")[0]] = struct{}{}
}
if strings.Contains(line, "No match") {
res.exists = false
return res, nil
}
if strings.HasPrefix(line, "Domain name:") {
res.exists = true
res.hasUpdateDate = true
res.hasRegisterDate = true
res.hasExpireDate = false
res.nsServers = []string{}
res.domain = strings.TrimSpace(strings.TrimPrefix(line, "Domain name:"))
}
if strings.HasPrefix(line, "Registered:") {
res.registerDate = parseYMDDate(strings.TrimSpace(strings.TrimPrefix(line, "Registered:")))
}
if strings.HasPrefix(line, "Last modified:") {
res.updateDate = parseYMDDate(strings.TrimSpace(strings.TrimPrefix(line, "Last modified:")))
}
if strings.HasPrefix(line, "Registrar:") {
res.registar = strings.TrimSpace(strings.TrimPrefix(line, "Registrar:"))
}
if strings.HasPrefix(line, "Registrant:") {
currentStatus = 1
p = PersonalInfo{}
continue
}
if strings.HasPrefix(line, "Administrative contact:") {
currentStatus = 2
p = PersonalInfo{}
continue
}
if strings.HasPrefix(line, "Technical contact:") {
currentStatus = 3
p = PersonalInfo{}
continue
}
if strings.HasPrefix(line, "DNS servers (") {
startNs = true
continue
}
if startNs {
tmp := strings.Split(line, "-")
for idx, ns := range tmp {
ns = strings.TrimSpace(ns)
if idx == 0 {
res.nsServers = append(res.nsServers, ns)
continue
}
res.nsIps = append(res.nsIps, ns)
}
}
if len(line) == 0 {
startNs = false
}
if currentStatus > 0 {
tmpSlice = append(tmpSlice, line)
if len(line) == 0 {
switch currentStatus {
case 1:
p.Addr = strings.Join(tmpSlice, "\n")
tmpSlice = []string{}
r = p
case 2:
if len(tmpSlice) > 2 {
p.Addr = strings.Join(tmpSlice[:len(tmpSlice)-2], "\n")
p.Phone = tmpSlice[len(tmpSlice)-1]
p.Email = tmpSlice[len(tmpSlice)-2]
} else {
p.Addr = strings.Join(tmpSlice, "\n")
}
tmpSlice = []string{}
a = p
case 3:
if len(tmpSlice) > 2 {
p.Addr = strings.Join(tmpSlice[:len(tmpSlice)-2], "\n")
p.Phone = tmpSlice[len(tmpSlice)-1]
p.Email = tmpSlice[len(tmpSlice)-2]
} else {
p.Addr = strings.Join(tmpSlice, "\n")
}
tmpSlice = []string{}
t = p
}
currentStatus = 0
}
}
}
for status := range statusMap {
res.statusRaw = append(res.statusRaw, status)
}
res.registerInfo = r
res.adminInfo = a
res.techInfo = t
return res, nil
}
func parseDate(date string) time.Time { func parseDate(date string) time.Time {
t, _ := time.Parse("2006-01-02T15:04:05Z", date) t, err := time.Parse("2006-01-02T15:04:05Z", date)
if err == nil {
t = t.In(time.Local)
return t
}
t, err = time.Parse("2006-01-02T15:04:05-0700", date)
t = t.In(time.Local) t = t.In(time.Local)
return t return t
} }
@ -420,3 +888,16 @@ func parseJPDate(date string, onlyMD bool) time.Time {
t = t.In(time.Local) t = t.In(time.Local)
return t return t
} }
func parseEduDate(date string) time.Time {
//example 20-Dec-1996
t, _ := time.Parse("02-Jan-2006", date)
t = t.In(time.Local)
return t
}
func parseYMDDate(date string) time.Time {
t, _ := time.Parse("2006-01-02", date)
t = t.In(time.Local)
return t
}

View File

@ -2,10 +2,128 @@ package whois
import ( import (
"fmt" "fmt"
"os"
"testing" "testing"
) )
func TestWhois(t *testing.T) { func TestWhoisInfo(t *testing.T) {
c := NewClient() c := NewClient()
fmt.Println(c.Whois("b612.in")) domain := "who.int"
h, err := c.Whois(domain)
if err != nil {
t.Fatal(err)
}
os.WriteFile("./bin/"+domain+".txt", []byte(h.RawData()), 0644)
fmt.Println("Domain:", h.Domain())
fmt.Println("Exists:", h.Exists())
fmt.Println("RegistrarDate:", h.RegisterDate())
fmt.Println("ExpireDate:", h.ExpireDate())
fmt.Println("UpdateDate:", h.UpdateDate())
fmt.Println("Status:", h.Status())
fmt.Println("NsServers:", h.NsServers())
fmt.Println("NsIp:", h.NsIps())
fmt.Println("Dnssec:", h.Dnssec())
fmt.Println("WhoisSer:", h.WhoisSer())
fmt.Println("IanaID:", h.IanaID())
fmt.Println("======RegisterInfo=======")
fmt.Println("Name:", h.RegisterInfo().Name)
fmt.Println("Organization:", h.RegisterInfo().Org)
fmt.Println("Addr:", h.RegisterInfo().Addr)
fmt.Println("State:", h.RegisterInfo().State)
fmt.Println("Country:", h.RegisterInfo().Country)
fmt.Println("Email:", h.RegisterInfo().Email)
fmt.Println("Phone:", h.RegisterInfo().Phone)
fmt.Println("Fax:", h.RegisterInfo().Fax)
fmt.Println("======AdminInfo=======")
fmt.Println("Name:", h.AdminInfo().Name)
fmt.Println("Organization:", h.AdminInfo().Org)
fmt.Println("Addr:", h.AdminInfo().Addr)
fmt.Println("State:", h.AdminInfo().State)
fmt.Println("Country:", h.AdminInfo().Country)
fmt.Println("Email:", h.AdminInfo().Email)
fmt.Println("Phone:", h.AdminInfo().Phone)
fmt.Println("Fax:", h.AdminInfo().Fax)
fmt.Println("======TechInfo=======")
fmt.Println("Name:", h.TechInfo().Name)
fmt.Println("Organization:", h.TechInfo().Org)
fmt.Println("Addr:", h.TechInfo().Addr)
fmt.Println("State:", h.TechInfo().State)
fmt.Println("Country:", h.TechInfo().Country)
fmt.Println("Email:", h.TechInfo().Email)
fmt.Println("Phone:", h.TechInfo().Phone)
fmt.Println("Fax:", h.TechInfo().Fax)
fmt.Println("====================")
fmt.Println("\nRaw:", h.RawData())
}
func TestWhois(t *testing.T) {
os.MkdirAll("./bin", 0755)
domainSuffix := []string{"com", "net", "org", "cn", "io", "me", "cc", "top", "xyz", "vip", "club", "site", "win", "bid",
"loan", "ek", "kim", "ren", "ltd", "link", "red", "pro", "info", "mobi", "name", "tv", "ws", "asia",
"biz", "gov", "edu", "mil", "int", "aero", "coop", "museum", "jobs", "travel", "xxx",
"ac", "ad", "ae", "af", "ag", "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au", "aw", "az",
"ba", "bb", "bd", "be", "bf", "bg", "bh", "bi", "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by", "bz",
"ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl", "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz",
"de", "dj", "dk", "dm", "do", "dz",
"ec", "ee", "eg", "eh", "er", "es", "et", "eu",
"fi", "fj", "fk", "fm", "fo", "fr",
"ga", "gb", "gd", "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq", "gr", "gs", "gt", "gu", "gw", "gy",
"hk", "hm", "hn", "hr", "ht", "hu",
"id", "ie", "il", "im", "in", "io", "iq", "ir", "is", "it",
"je", "jm", "jo", "jp",
"ke", "kg", "kh", "ki", "km", "kn", "kp", "kr", "kw", "ky", "kz",
"la", "lb", "lc", "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly",
"ma", "mc", "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq", "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz",
"na", "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu", "nz",
"om",
"pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm", "pn", "pr", "ps", "pt", "pw", "py",
"qa",
"re", "ro", "ru", "rw",
"sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj", "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
"tc", "td", "tf", "tg", "th", "tj", "tk", "tl", "tm", "tn", "to", "tp", "tr", "tt", "tv", "tw", "tz",
"ua", "ug", "uk", "us", "uy", "uz",
"va", "vc", "ve", "vg", "vi", "vn", "vu",
"wf", "ws",
"ye", "yt", "yu",
"za", "zm", "zw"}
prefix := "nic."
c := NewClient()
for idx, suffix := range domainSuffix {
if idx < 50 {
continue
}
domain := prefix + suffix
if suffix == "cn" {
domain = "cnnic.cn"
}
if suffix == "int" {
domain = "who.int"
}
h, err := c.Whois(domain)
if err != nil {
fmt.Println(idx, domain, "net fail", err)
continue
}
os.WriteFile("./bin/"+domain+".txt", []byte(h.RawData()), 0644)
if !h.exists {
fmt.Println(idx, h.Domain(), "fail")
t.Fatal(domain, "not exists")
}
fmt.Println(idx, h.Domain(), "ok")
fmt.Println(h.ExpireDate(), h.RegisterDate())
if h.HasExpireDate() && h.ExpireDate().IsZero() {
t.Fatal("ExpireDate is zero")
}
if h.Domain() == "" {
t.Fatal("Domain is empty")
}
if h.HasRegisterDate() && h.RegisterDate().IsZero() {
t.Fatal("RegisterDate is zero")
}
if len(h.NsServers()) == 0 {
fmt.Println("NsServers is empty")
}
fmt.Println(h.Status())
}
} }

View File

@ -7,6 +7,7 @@ import (
"net" "net"
"strconv" "strconv"
"strings" "strings"
"sync"
"time" "time"
"golang.org/x/net/proxy" "golang.org/x/net/proxy"
@ -76,8 +77,12 @@ var statusMap = map[string]string{
// DefaultClient is default whois client // DefaultClient is default whois client
var DefaultClient = NewClient() var DefaultClient = NewClient()
var defaultWhoisMap = map[string]string{}
// Client is whois client // Client is whois client
type Client struct { type Client struct {
extCache map[string]string
mu sync.Mutex
dialer proxy.Dialer dialer proxy.Dialer
timeout time.Duration timeout time.Duration
elapsed time.Duration elapsed time.Duration
@ -106,11 +111,15 @@ type Result struct {
domainID string domainID string
rawData string rawData string
registar string registar string
hasRegisterDate bool
registerDate time.Time registerDate time.Time
hasUpdateDate bool
updateDate time.Time updateDate time.Time
hasExpireDate bool
expireDate time.Time expireDate time.Time
statusRaw []string statusRaw []string
nsServers []string nsServers []string
nsIps []string
dnssec string dnssec string
whoisSer string whoisSer string
ianaID string ianaID string
@ -119,6 +128,22 @@ type Result struct {
techInfo PersonalInfo techInfo PersonalInfo
} }
func (r Result) NsIps() []string {
return r.nsIps
}
func (r Result) HasRegisterDate() bool {
return r.hasRegisterDate
}
func (r Result) HasUpdateDate() bool {
return r.hasUpdateDate
}
func (r Result) HasExpireDate() bool {
return r.hasExpireDate
}
func (r Result) Exists() bool { func (r Result) Exists() bool {
return r.exists return r.exists
} }
@ -189,12 +214,18 @@ func NewClient() *Client {
dialer: &net.Dialer{ dialer: &net.Dialer{
Timeout: defTimeout, Timeout: defTimeout,
}, },
extCache: make(map[string]string),
timeout: defTimeout, timeout: defTimeout,
} }
} }
func (c *Client) Whois(domain string, servers ...string) (Result, error) { func (c *Client) Whois(domain string, servers ...string) (Result, error) {
data, err := c.whois(domain) if len(servers) == 0 {
if v, ok := defaultWhoisMap[getExtension(domain)]; ok {
servers = append(servers, v)
}
}
data, err := c.whois(domain, servers...)
if err != nil { if err != nil {
return Result{}, err return Result{}, err
} }
@ -234,6 +265,7 @@ func (c *Client) whois(domain string, servers ...string) (result string, err err
port = defWhoisPort port = defWhoisPort
} else { } else {
ext := getExtension(domain) ext := getExtension(domain)
if _, ok := c.extCache[ext]; !ok {
result, err := c.rawQuery(ext, defWhoisServer, defWhoisPort) result, err := c.rawQuery(ext, defWhoisServer, defWhoisPort)
if err != nil { if err != nil {
return "", fmt.Errorf("whois: query for whois server failed: %w", err) return "", fmt.Errorf("whois: query for whois server failed: %w", err)
@ -242,6 +274,13 @@ func (c *Client) whois(domain string, servers ...string) (result string, err err
if server == "" { if server == "" {
return "", fmt.Errorf("%w: %s", errors.New("whois server not found"), domain) return "", fmt.Errorf("%w: %s", errors.New("whois server not found"), domain)
} }
c.mu.Lock()
c.extCache[ext] = fmt.Sprintf("%s:%s", server, port)
c.mu.Unlock()
} else {
v := strings.Split(c.extCache[ext], ":")
server, port = v[0], v[1]
}
} }
result, err = c.rawQuery(domain, server, port) result, err = c.rawQuery(domain, server, port)