Adding upstream version 1.1.10.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
0935a35f2a
commit
504a5578e5
12 changed files with 537 additions and 0 deletions
191
whois.go
Normal file
191
whois.go
Normal file
|
@ -0,0 +1,191 @@
|
|||
package whois
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
ianaWHOISServerAddress = "whois.iana.org:43"
|
||||
)
|
||||
|
||||
var tldWithoutExpirationDate = []string{"at", "be", "ch", "co.at", "com.br", "or.at", "de", "fr", "nl"}
|
||||
|
||||
type Client struct {
|
||||
whoisServerAddress string
|
||||
|
||||
isCachingReferralWHOISServers bool
|
||||
referralWHOISServersCache map[string]string
|
||||
}
|
||||
|
||||
func NewClient() *Client {
|
||||
return &Client{
|
||||
whoisServerAddress: ianaWHOISServerAddress,
|
||||
referralWHOISServersCache: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
// WithReferralCache allows you to enable or disable the referral WHOIS server cache.
|
||||
// While ianaWHOISServerAddress is the "entry point" for WHOIS queries, it sometimes has
|
||||
// availability issues. One way to mitigate this is to cache the referral WHOIS server.
|
||||
//
|
||||
// This is disabled by default
|
||||
func (c *Client) WithReferralCache(enabled bool) *Client {
|
||||
c.isCachingReferralWHOISServers = enabled
|
||||
if enabled {
|
||||
// We'll set a couple of common ones right away to avoid unnecessary queries
|
||||
c.referralWHOISServersCache = map[string]string{
|
||||
"com": "whois.verisign-grs.com",
|
||||
"black": "whois.nic.black",
|
||||
"dev": "whois.nic.google",
|
||||
"green": "whois.nic.green",
|
||||
"io": "whois.nic.io",
|
||||
"net": "whois.verisign-grs.com",
|
||||
"org": "whois.publicinterestregistry.org",
|
||||
"red": "whois.nic.red",
|
||||
"sh": "whois.nic.sh",
|
||||
"uk": "whois.nic.uk",
|
||||
"mx": "whois.nic.mx",
|
||||
}
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func doesTLDHaveExpirationDate(e string) bool {
|
||||
for _, a := range tldWithoutExpirationDate {
|
||||
if a == e {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *Client) Query(domain string) (string, error) {
|
||||
parts := strings.Split(domain, ".")
|
||||
domainExtension := parts[len(parts)-1]
|
||||
if doesTLDHaveExpirationDate(domainExtension) {
|
||||
return "", errors.New("domain extension " + domainExtension + " does not have a grace period.")
|
||||
}
|
||||
if c.isCachingReferralWHOISServers {
|
||||
if cachedWHOISServer, ok := c.referralWHOISServersCache[domain]; ok {
|
||||
return c.query(cachedWHOISServer, domain)
|
||||
}
|
||||
}
|
||||
var output string
|
||||
var err error
|
||||
switch domainExtension {
|
||||
case "ua":
|
||||
if len(parts) > 2 && len(parts[len(parts)-2]) < 4 {
|
||||
domainExtension = parts[len(parts)-2] + "." + domainExtension
|
||||
}
|
||||
output, err = c.query("whois."+domainExtension+":43", domain)
|
||||
default:
|
||||
output, err = c.query(c.whoisServerAddress, domainExtension)
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if strings.Contains(output, "whois:") {
|
||||
startIndex := strings.Index(output, "whois:") + 6
|
||||
endIndex := strings.Index(output[startIndex:], "\n") + startIndex
|
||||
whois := strings.TrimSpace(output[startIndex:endIndex])
|
||||
if referOutput, err := c.query(whois+":43", domain); err == nil {
|
||||
if c.isCachingReferralWHOISServers {
|
||||
c.referralWHOISServersCache[domain] = whois + ":43"
|
||||
}
|
||||
return referOutput, nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func (c *Client) query(whoisServerAddress, domain string) (string, error) {
|
||||
connection, err := net.DialTimeout("tcp", whoisServerAddress, 10*time.Second)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer connection.Close()
|
||||
_ = connection.SetDeadline(time.Now().Add(5 * time.Second))
|
||||
_, err = connection.Write([]byte(domain + "\r\n"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
output, err := io.ReadAll(connection)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(output), nil
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
ExpirationDate time.Time
|
||||
DomainStatuses []string
|
||||
NameServers []string
|
||||
}
|
||||
|
||||
// QueryAndParse tries to parse the response from the WHOIS server
|
||||
// There is no standardized format for WHOIS responses, so this is an attempt at best.
|
||||
//
|
||||
// Being the selfish person that I am, I also only parse the fields that I need.
|
||||
// If you need more fields, please open an issue or pull request.
|
||||
func (c *Client) QueryAndParse(domain string) (*Response, error) {
|
||||
text, err := c.Query(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response := Response{}
|
||||
for _, line := range strings.Split(text, "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
valueStartIndex := strings.Index(line, ":")
|
||||
if valueStartIndex == -1 {
|
||||
continue
|
||||
}
|
||||
key := strings.ToLower(strings.TrimSpace(line[:valueStartIndex]))
|
||||
value := strings.TrimSpace(line[valueStartIndex+1:])
|
||||
if strings.Contains(key, "expir") {
|
||||
switch {
|
||||
case strings.HasSuffix(domain, ".pp.ua"):
|
||||
response.ExpirationDate, _ = time.Parse("02-Jan-2006 15:04:05 MST", strings.ToUpper(value))
|
||||
case strings.HasSuffix(domain, ".ua"):
|
||||
response.ExpirationDate, _ = time.Parse("2006-01-02 15:04:05Z07", strings.ToUpper(value))
|
||||
case strings.HasSuffix(domain, ".uk"):
|
||||
response.ExpirationDate, _ = time.Parse("02-Jan-2006", strings.ToUpper(value))
|
||||
case strings.HasSuffix(domain, ".cz"):
|
||||
response.ExpirationDate, _ = time.Parse("02.01.2006", strings.ToUpper(value))
|
||||
case strings.HasSuffix(domain, ".im"):
|
||||
response.ExpirationDate, _ = time.Parse("02/01/2006 15:04:05", strings.ToUpper(value))
|
||||
case strings.HasSuffix(domain, ".scot"):
|
||||
if !strings.Contains(key, "registrar") {
|
||||
response.ExpirationDate, _ = time.Parse(time.RFC3339, strings.ToUpper(value))
|
||||
}
|
||||
case strings.HasSuffix(domain, ".br"):
|
||||
response.ExpirationDate, _ = time.Parse("20060102", strings.ToUpper(value))
|
||||
case strings.HasSuffix(domain, ".cn"):
|
||||
response.ExpirationDate, _ = time.Parse("2006-01-02 15:04:05", strings.ToUpper(value))
|
||||
case strings.HasSuffix(domain, ".mx"):
|
||||
response.ExpirationDate, _ = time.Parse(time.DateOnly, strings.ToUpper(value))
|
||||
default:
|
||||
response.ExpirationDate, _ = time.Parse(time.RFC3339, strings.ToUpper(value))
|
||||
}
|
||||
} else if key == "paid-till" {
|
||||
// example for ru/su domains -> paid-till: 2024-05-26T21:00:00Z
|
||||
if strings.HasSuffix(domain, ".ru") || strings.HasSuffix(domain, ".su") {
|
||||
response.ExpirationDate, _ = time.Parse(time.RFC3339, strings.ToUpper(value))
|
||||
}
|
||||
} else if strings.Contains(key, "status") {
|
||||
response.DomainStatuses = append(response.DomainStatuses, value)
|
||||
} else if key == "state" {
|
||||
// example for ru/su domains -> state: DELEGATED, VERIFIED
|
||||
if strings.HasSuffix(domain, ".ru") || strings.HasSuffix(domain, ".su") {
|
||||
response.DomainStatuses = strings.Split(value, ", ")
|
||||
}
|
||||
} else if strings.Contains(key, "name server") || strings.Contains(key, "nserver") {
|
||||
response.NameServers = append(response.NameServers, value)
|
||||
}
|
||||
}
|
||||
return &response, nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue