diff --git a/.gitignore b/.gitignore index 723ef36..25c3120 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -.idea \ No newline at end of file +.idea +bin \ No newline at end of file diff --git a/go.mod b/go.mod index 507c8a4..21a3eb4 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,4 @@ module b612.me/sdk/whois go 1.21.2 -require golang.org/x/net v0.24.0 +require golang.org/x/net v0.28.0 diff --git a/go.sum b/go.sum index 4a8ba20..e890837 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,2 @@ -golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= -golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= diff --git a/parse.go b/parse.go index 44d3a73..c50c97e 100644 --- a/parse.go +++ b/parse.go @@ -16,13 +16,29 @@ func parse(domain string, result string) (Result, error) { data, err = dotJPParser(domain, result) case "tw": data, err = dotTWParser(domain, result) + case "name": + data, err = dotNameParser(domain, result) default: 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 } 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{ domain: domain, rawData: data, @@ -34,8 +50,8 @@ func commonParser(domain, data string) (Result, error) { for _, line := range split { line = strings.TrimSpace(line) 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"} { - if strings.HasPrefix(token, line) { + 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(line, token) { res.exists = false return res, nil } @@ -43,6 +59,9 @@ func commonParser(domain, data string) (Result, error) { } if strings.HasPrefix(line, "Domain Name:") { res.exists = true + res.hasUpdateDate = hasUpdate + res.hasRegisterDate = hasCreate + res.hasExpireDate = hasExpire res.nsServers = []string{} 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:")) } 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:") { - 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:") { - 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:") { 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{}{} } 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{}{} } if strings.HasPrefix(line, "Name Server:") { @@ -73,9 +111,6 @@ func commonParser(domain, data string) (Result, error) { if strings.HasPrefix(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:") { r.Name = strings.TrimSpace(strings.TrimPrefix(line, "Registrant Name:")) } @@ -194,6 +229,61 @@ func commonParser(domain, data string) (Result, error) { 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) { var res = Result{ domain: domain, @@ -204,6 +294,13 @@ func dotCNParser(domain, data string) (Result, error) { res.exists = false 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 statusMap := make(map[string]struct{}) split := strings.Split(data, "\n") @@ -267,6 +364,9 @@ func dotJPParser(domain, data string) (Result, error) { line = strings.TrimSpace(line) 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]")) } @@ -341,6 +441,9 @@ func dotTWParser(domain, data string) (Result, error) { } 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:")) } @@ -398,8 +501,373 @@ func dotTWParser(domain, data string) (Result, error) { 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 { - 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) return t } @@ -420,3 +888,16 @@ func parseJPDate(date string, onlyMD bool) time.Time { t = t.In(time.Local) 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 +} diff --git a/parse_test.go b/parse_test.go index 4fe2f49..f09e7e5 100644 --- a/parse_test.go +++ b/parse_test.go @@ -2,10 +2,128 @@ package whois import ( "fmt" + "os" "testing" ) -func TestWhois(t *testing.T) { +func TestWhoisInfo(t *testing.T) { 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()) + } } diff --git a/whois.go b/whois.go index 884e5d4..e6bdd17 100644 --- a/whois.go +++ b/whois.go @@ -7,6 +7,7 @@ import ( "net" "strconv" "strings" + "sync" "time" "golang.org/x/net/proxy" @@ -76,8 +77,12 @@ var statusMap = map[string]string{ // DefaultClient is default whois client var DefaultClient = NewClient() +var defaultWhoisMap = map[string]string{} + // Client is whois client type Client struct { + extCache map[string]string + mu sync.Mutex dialer proxy.Dialer timeout time.Duration elapsed time.Duration @@ -101,22 +106,42 @@ type PersonalInfo struct { } type Result struct { - exists bool - domain string - domainID string - rawData string - registar string - registerDate time.Time - updateDate time.Time - expireDate time.Time - statusRaw []string - nsServers []string - dnssec string - whoisSer string - ianaID string - registerInfo PersonalInfo - adminInfo PersonalInfo - techInfo PersonalInfo + exists bool + domain string + domainID string + rawData string + registar string + hasRegisterDate bool + registerDate time.Time + hasUpdateDate bool + updateDate time.Time + hasExpireDate bool + expireDate time.Time + statusRaw []string + nsServers []string + nsIps []string + dnssec string + whoisSer string + ianaID string + registerInfo PersonalInfo + adminInfo 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 { @@ -189,12 +214,18 @@ func NewClient() *Client { dialer: &net.Dialer{ Timeout: defTimeout, }, - timeout: defTimeout, + extCache: make(map[string]string), + timeout: defTimeout, } } 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 { return Result{}, err } @@ -234,13 +265,21 @@ func (c *Client) whois(domain string, servers ...string) (result string, err err port = defWhoisPort } else { ext := getExtension(domain) - result, err := c.rawQuery(ext, defWhoisServer, defWhoisPort) - if err != nil { - return "", fmt.Errorf("whois: query for whois server failed: %w", err) - } - server, port = getServer(result) - if server == "" { - return "", fmt.Errorf("%w: %s", errors.New("whois server not found"), domain) + if _, ok := c.extCache[ext]; !ok { + result, err := c.rawQuery(ext, defWhoisServer, defWhoisPort) + if err != nil { + return "", fmt.Errorf("whois: query for whois server failed: %w", err) + } + server, port = getServer(result) + if server == "" { + 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] } }