/* * Copyright 2014-2024 Li Kexian * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Go module for domain whois information parsing * https://www.likexian.com/ */ package whoisparser import ( "fmt" "regexp" "strings" "github.com/likexian/gokit/assert" "github.com/likexian/gokit/xslice" ) // Prepare do prepare the whois info for parsing func Prepare(text, ext string) (string, bool) { //nolint:cyclop text = strings.Replace(text, "\r", "", -1) text = strings.Replace(text, "\t", " ", -1) text = strings.TrimSpace(text) switch ext { case "": return prepareTLD(text), true case "edu": return prepareEDU(text), true case "int": return prepareINT(text), true case "mo": return prepareMO(text), true case "hk": return prepareHK(text), true case "tw": return prepareTW(text), true case "ch": return prepareCH(text), true case "it": return prepareIT(text), true case "fr", "re", "tf", "yt", "pm", "wf": return prepareFR(text), true case "ru", "su", "xn--p1ai": return prepareRU(text), true case "fi": return prepareFI(text), true case "jp": return prepareJP(text), true case "uk": return prepareUK(text), true case "kr": return prepareKR(text), true case "nz": return prepareNZ(text), true case "tk": return prepareTK(text), true case "nl": return prepareNL(text), true case "eu": return prepareEU(text), true case "br": return prepareBR(text), true case "ir", "xn--mgba3a4f16a": return prepareIR(text), true case "rs": return prepareRS(text), true case "kz": return prepareKZ(text), true case "ee": return prepareEE(text), true case "cn", "xn--fiqs8s", "xn--fiqz9s": return prepareCN(text), true case "pl": return preparePL(text), true case "dk": return prepareDK(text), true case "by": return prepareBY(text), true case "ua": return prepareUA(text), true case "at": return prepareAT(text), true default: return text, false } } // prepareTLD do prepare the tld domain func prepareTLD(text string) string { token := "" result := "" for _, v := range strings.Split(text, "\n") { v = strings.TrimSpace(v) if v == "" { token = "" continue } if strings.Contains(v, ":") { vs := strings.Split(v, ":") if strings.TrimSpace(vs[0]) == "organisation" { if token == "" { token = "registrant" } } if strings.TrimSpace(vs[0]) == "contact" { token = strings.TrimSpace(vs[1]) } else { if token != "" { v = fmt.Sprintf("%s %s", token, v) } } } result += "\n" + v } return result } // prepareEDU do prepare the .edu domain func prepareEDU(text string) string { tokens := map[string][]string{ "Registrant:": { "Organization", "Address", "Phone", "Email", }, "Administrative Contact:": { "Name", "Organization", "Address", "Phone", "Email", }, "Technical Contact:": { "Name", "Organization", "Address", "Phone", "Email", }, } token := "" index := 0 result := "" for _, v := range strings.Split(text, "\n") { v = strings.TrimSpace(v) if v == "" { continue } if strings.HasSuffix(v, ":") { token = "" index = 0 } if _, ok := tokens[v]; ok { token = v } else { if token == "" { result += "\n" + v } else { // address ending now jump to phone if tokens[token][index] == "Address" && strings.HasPrefix(v, "+") { found := xslice.Index(tokens[token], "Phone") if found != -1 { index = found } } result += fmt.Sprintf("\n%s %s: %s", token[:len(token)-1], tokens[token][index], v) if tokens[token][index] != "Address" { index++ } } } } return result } // prepareINT do prepare the .int domain func prepareINT(text string) string { token := "" result := "" for _, v := range strings.Split(text, "\n") { v = strings.TrimSpace(v) if v == "" { token = "" continue } if strings.Contains(v, ":") { vs := strings.Split(v, ":") if strings.TrimSpace(vs[0]) == "organisation" { if token == "" { token = "registrant" } } if strings.TrimSpace(vs[0]) == "contact" { token = strings.TrimSpace(vs[1]) } else { if token != "" { v = fmt.Sprintf("%s %s", token, v) } } } result += "\n" + v } return result } // prepareKZ do prepare the .kz domain func prepareKZ(text string) string { groupTokens := map[string]string{ "Organization Using Domain Name": "Registrant ", "Administrative Contact/Agent": "Administrative ", } topTokens := map[string]string{ "Domain status": "Domain status : ", } tokens := map[string]string{ "Primary server": "name server", "Secondary server": "name server", "Current Registar": "Registrar Name", } groupToken := "" topToken := "" result := "" for _, v := range strings.Split(text, "\n") { v = strings.TrimSpace(v) if v == "" { groupToken = "" continue } if token, ok := groupTokens[v]; ok { groupToken = token continue } if !strings.Contains(v, ":") { if topToken != "" { v = fmt.Sprintf("%s%s", topToken, v) } else { continue } } vs := strings.SplitN(v, ":", 2) key := strings.TrimSpace(strings.Replace(vs[0], ".", "", -1)) key = fmt.Sprintf("%s%s", groupToken, key) if token, ok := tokens[key]; ok { key = token } value := vs[1] if token, ok := topTokens[key]; ok { topToken = token } else { topToken = "" } v = fmt.Sprintf("%s: %s", key, value) result += "\n" + v } return result } // prepareMO do prepare the .mo domain func prepareMO(text string) string { tokens := map[string]string{ "Registrant:": "Registrant", "Admin Contact(s):": "Admin", "Billing Contact(s):": "Billing", "Technical Contact(s):": "Technical", } token := "" result := "" for _, v := range strings.Split(text, "\n") { v = strings.TrimSpace(v) if v == "" { token = "" continue } if v[0] == '-' { continue } for _, s := range []string{"Record created on", "Record expires on"} { if strings.HasPrefix(v, s) { v = strings.Replace(v, s, s+":", 1) } } if _, ok := tokens[v]; ok { token = tokens[v] } else { if token != "" { v = fmt.Sprintf("%s %s", token, v) } } result += "\n" + v } return result } var prepareHKEmailRx = regexp.MustCompile(`Email\:\s+([^\s]+)(\s+Hotline\:(.*))?`) // prepareHK do prepare the .hk domain func prepareHK(text string) string { tokens := map[string]string{ "Registrant Contact Information:": "Registrant", "Administrative Contact Information:": "Admin", "Technical Contact Information:": "Technical", "Name Servers Information:": "Name Servers:", } dateTokens := []string{ "Domain Name Commencement Date", "Expiry Date", } token := "" addressToken := false text = strings.Replace(text, "\n\n", "\n", -1) result := "" for _, v := range strings.Split(text, "\n") { v = strings.TrimSpace(v) if v == "" { token = "" continue } field := "" if strings.Contains(v, ":") { vs := strings.SplitN(v, ":", 2) field = strings.TrimSpace(vs[0]) if strings.Contains(field, "(") { field = strings.Split(field, "(")[0] v = fmt.Sprintf("%s: %s", field, vs[1]) } addressToken = field == "Address" if field == "Registrar Contact Information" { m := prepareHKEmailRx.FindStringSubmatch(vs[1]) if len(m) == 4 { v = "" if m[1] != "" { v += "Registrar Contact Email: " + m[1] + "\n" } if m[3] != "" { v += "Registrar Contact Phone: " + m[3] + "\n" } v = strings.TrimSpace(v) } } if field == "Family name" { vv := strings.TrimSpace(vs[1]) if vv != "" && vv != "." { result += " " + vv } continue } } else { if addressToken { result += ", " + v continue } } if _, ok := tokens[v]; ok { token = tokens[v] } else { if token != "" && !assert.IsContains(dateTokens, field) { v = fmt.Sprintf("%s %s", token, v) } } result += "\n" + v } return result } var prepareTWEmailRx = regexp.MustCompile(`(.*)\s+([^\s]+@[^\s]+)`) // prepareTW do prepare the .tw domain func prepareTW(text string) string { //nolint:cyclop tokens := map[string][]string{ "Registrant:": { "Organization", "Organization", "Name,Email", "Phone", "Fax", "Address", "Address", "Address", }, "Administrative Contact:": { "Name,Email", "Phone", "Fax", }, "Technical Contact:": { "Name,Email", "Phone", "Fax", }, "Contact:": { "Name", "Email", }, } token := "" index := -1 result := "" for _, v := range strings.Split(text, "\n") { v = strings.TrimSpace(v) if token == "" && v == "" { continue } for _, s := range []string{"Record created on", "Record expires on"} { if strings.HasPrefix(v, s) { v = strings.Replace(v, s, s+":", 1) } } if strings.Contains(v, ":") { token = "" index = -1 } if _, ok := tokens[v]; ok { token = v } else { if token == "" { result += "\n" + v } else { index++ if index > len(tokens[token])-1 { continue } tokenName := token[:len(token)-1] // Organization may be one line or two lines if tokenName == "Registrant" && tokens[token][index] == "Organization" { if strings.Contains(v, "@") { // Organization one line, jump to next index++ } else if index == 1 { // Organization two line, join it result = strings.TrimSpace(result) if !strings.HasSuffix(result, ":") { result += ", " + v } else { result += " " + v } continue } } // See testdata/noterror/tw_git.tw if tokenName == "Registrant" && tokens[token][index] != "Address" { if len(v) == 2 && strings.ToLower(v) != v { index = xslice.Index(tokens[token], "Address") } } indexName := tokens[token][index] if tokenName == "Contact" { tokenName = "Registrant Contact" } if strings.Contains(indexName, ",") { ins := strings.Split(indexName, ",") m := prepareTWEmailRx.FindStringSubmatch(v) if len(m) == 3 { result += fmt.Sprintf("\n%s %s: %s", tokenName, ins[0], strings.TrimSpace(m[1])) result += fmt.Sprintf("\n%s %s: %s", tokenName, ins[1], strings.TrimSpace(m[2])) } else { result += fmt.Sprintf("\n%s %s: %s", tokenName, ins[0], strings.TrimSpace(v)) } continue } result += fmt.Sprintf("\n%s %s: %s", tokenName, indexName, v) } } } return result } // prepareCH do prepare the .ch domain func prepareCH(text string) string { phoneMark := "Phone +" tokens := map[string][]string{ "Domain name": {}, "Registrar": {"Registrar name", "Registrar street", "Registrar Phone", "Registrar Email"}, "DNSSEC": {}, "Name servers": {}, "First registration date": {}, } var result string var lastToken string var lastTokenIndex int for _, v := range strings.Split(text, "\n") { v = strings.TrimSpace(v) if v == "" { continue } for t := range tokens { if strings.HasPrefix(strings.ToLower(v)+" ", strings.ToLower(t+" ")) { lastToken = t lastTokenIndex = 0 v = strings.TrimSpace(v[len(t):]) break } } if v == "" { continue } if len(tokens[lastToken]) > 0 { if strings.HasPrefix(v, phoneMark) { lastTokenIndex++ v = strings.TrimSpace(v[len(phoneMark)-1:]) } result += fmt.Sprintf("\n%s: %s", tokens[lastToken][lastTokenIndex], v) if tokens[lastToken][lastTokenIndex] != "Registrar street" { lastTokenIndex++ } } else { result += fmt.Sprintf("\n%s: %s", lastToken, v) } } return result } // prepareIT do prepare the .it domain func prepareIT(text string) string { topTokens := []string{ "Registrant", "Admin Contact", "Technical Contacts", "Registrar", "Nameservers", } topToken := "" subToken := "" result := "" for _, v := range strings.Split(text, "\n") { v = strings.TrimSpace(v) if v == "" { continue } if assert.IsContains(topTokens, v) { topToken = v + " " subToken = "" } else { if v[0] != '*' && strings.Contains(v, ":") { vs := strings.Split(v, ":") subToken = vs[0] } else { if subToken != "" { result += ", " + v continue } } if topToken != "" && !strings.Contains(v, ":") { result += fmt.Sprintf("\n%s: %s", topToken, v) } else { result += fmt.Sprintf("\n%s%s", topToken, v) } } } return result } // prepareFR do prepare the .fr domain func prepareFR(text string) string { dsToken := "dsl-id" hdlToken := "nic-hdl" regToken := "registrar" tokens := map[string]string{ "holder-c": "holder", "admin-c": "admin", "tech-c": "tech", } token := "" newBlock := false hdls := map[string]string{} result := "" for _, v := range strings.Split(text, "\n") { v = strings.TrimSpace(v) if v == "" { newBlock = true continue } vs := strings.Split(v, ":") if newBlock && strings.TrimSpace(vs[0]) == regToken { token = regToken + " " v = fmt.Sprintf("name: %s", strings.TrimSpace(vs[1])) } newBlock = false if t, ok := tokens[strings.TrimSpace(vs[0])]; ok { hdls[t] = strings.TrimSpace(vs[1]) } if strings.TrimSpace(vs[0]) == dsToken && strings.TrimSpace(vs[1]) != "" { v += "\nDNSSEC: signed" } if strings.TrimSpace(vs[0]) == hdlToken { for _, kk := range keys(hdls) { if strings.TrimSpace(vs[1]) == hdls[kk] { token = kk + " " delete(hdls, kk) break } } } result += fmt.Sprintf("\n%s%s", token, v) } return result } // prepareRU do prepare the .ru domain func prepareRU(text string) string { tokens := map[string]string{ "person": "Registrant Name", "e-mail": "Registrant Email", "org": "Registrant Organization", } result := "" for _, v := range strings.Split(text, "\n") { v = strings.TrimSpace(v) if v == "" { continue } if !strings.Contains(v, ":") { continue } vs := strings.Split(v, ":") if vv, ok := tokens[strings.TrimSpace(vs[0])]; ok { v = fmt.Sprintf("%s: %s", vv, vs[1]) } else if vs[0] == "nserver" { v = strings.Replace(v, ",", " ", -1) } result += v + "\n" } return result } var prepareJPreplacerRx = regexp.MustCompile(`\n(?:\w+\.\s)?\[(.+?)\][\ ]*(.+?)?`) // prepareJP do prepare the .jp domain func prepareJP(text string) string { text = prepareJPreplacerRx.ReplaceAllString(text, "\n$1: $2") adminToken := "Contact Information" addressToken := "Postal Address" token := "" prefixToken := "" result := "" for _, v := range strings.Split(text, "\n") { v = strings.TrimSpace(v) if v == "" { continue } if strings.Contains(v, ":") { vs := strings.SplitN(v, ":", 2) token = strings.TrimSpace(vs[0]) if token == adminToken { prefixToken = "admin " } if strings.ToLower(token) == "registrant" { v = fmt.Sprintf("registrant name: %s", vs[1]) } v = prepareSecondLevelJP(v, token, vs[1]) } else { if token == addressToken { result += ", " + v continue } } result += "\n" + prefixToken + v } return result } // prepareJP prepares specific mappings for second level .jp domains // examples include: // - co.jp // - ac.jp // - go.jp // - or.jp // - ad.jp // - ne.jp // - gr.jp // - ed.jp func prepareSecondLevelJP(original string, token string, value string) string { if strings.ToLower(token) == "administrative contact" { return fmt.Sprintf("Administrative Contact ID: %s", strings.TrimSpace(value)) } if strings.ToLower(token) == "technical contact" { return fmt.Sprintf("Technical Contact ID: %s", strings.TrimSpace(value)) } if strings.ToLower(token) == "organization" || strings.ToLower(token) == "network service name" { return fmt.Sprintf("Registrant Organization: %s", strings.TrimSpace(value)) } return original } // prepareUK do prepare the .uk domain func prepareUK(text string) string { tokens := map[string]string{ "URL": "Registrar URL", } result := "" for _, v := range strings.Split(text, "\n") { v = strings.TrimSpace(v) if v == "" { continue } if strings.Contains(v, ":") { vs := strings.SplitN(v, ":", 2) if vv, ok := tokens[strings.TrimSpace(vs[0])]; ok { v = fmt.Sprintf("%s: %s", vv, vs[1]) } } result += "\n" + v } return result } // prepareKR do prepare the .kr domain func prepareKR(text string) string { english := "# ENGLISH" tokens := map[string]string{ "Administrative Contact(AC)": "Administrative Contact Name", "AC E-Mail": "Administrative Contact E-Mail", "AC Phone Number": "Administrative Contact Phone Number", "Authorized Agency": "Registrar Name", "Registrant": "Registrant Name", } pos := strings.Index(text, english) if pos != -1 { text = text[pos+len(english):] } result := "" for _, v := range strings.Split(text, "\n") { v = strings.TrimSpace(v) if v == "" { continue } if v[0] == '\'' || v[0] == '-' { continue } if strings.Contains(v, ":") { vs := strings.SplitN(v, ":", 2) if vv, ok := tokens[strings.TrimSpace(vs[0])]; ok { v = fmt.Sprintf("%s: %s", vv, vs[1]) } } result += "\n" + v } return result } // prepareNZ do prepare the .nz domain func prepareNZ(text string) string { result := "" for _, v := range strings.Split(text, "\n") { v = strings.TrimSpace(v) if strings.Contains(v, ":") { vs := strings.SplitN(v, ":", 2) if strings.HasPrefix(strings.TrimSpace(vs[0]), "ns_name_") { v = fmt.Sprintf("name server: %s", vs[1]) } } result += "\n" + v } return result } // prepareTK do prepare the .tk domain func prepareTK(text string) string { tokens := map[string]string{ "Domain name:": "Domain", "Domain Nameservers:": "Nameservers", "Owner contact:": "Registrant", "Admin contact:": "Admin", "Billing contact:": "Billing", "Tech contact:": "Technical", "Organisation:": "Registrant", } fields := map[string][]string{ "Registrant": { "Organization", "Name", "Address", "Address", "Address", "Country", }, } token := "" result := "" index := 0 for _, v := range strings.Split(text, "\n") { v = strings.TrimSpace(v) if v == "" { token = "" continue } if _, ok := tokens[v]; ok { token = tokens[v] continue } if token == "Domain" && strings.Contains(v, " is ") { vv := strings.Split(v, " is ") v = fmt.Sprintf("Name: %s\nStatus: %s", vv[0], vv[1]) } else if token == "Registrant" && !strings.Contains(v, ":") { v = fmt.Sprintf("%s: %s", fields[token][index], v) index++ } if token != "" { if !strings.Contains(v, ":") { v = fmt.Sprintf("%s: %s", token, v) } else { v = fmt.Sprintf("%s %s", token, v) } } result += "\n" + strings.TrimSpace(v) } return result } // prepareNL do prepare the .nl domain func prepareNL(text string) string { tokens := map[string][]string{ "Reseller:": { "Name", "Address", "Address", "Address", "Address", }, "Registrar:": { "Name", "Address", "Address", "Address", "Address", }, } token := "" index := 0 result := "" for _, v := range strings.Split(text, "\n") { v = strings.TrimSpace(v) if v == "" { continue } if strings.HasSuffix(v, ":") { token = "" index = 0 } if _, ok := tokens[v]; ok { token = v } else { if token == "" { result += "\n" + v } else { result += fmt.Sprintf("\n%s %s: %s", token[:len(token)-1], tokens[token][index], v) index++ } } } return result } // prepareEU do prepare the .eu domain func prepareEU(text string) string { tokens := map[string]string{ "Registrant:": "Registrant", "Technical:": "Technical", "Registrar:": "Registrar", "Onsite(s):": "Onsite", "Name servers:": "Name servers", } token := "" result := "" for _, v := range strings.Split(text, "\n") { v = strings.TrimSpace(v) if v == "" { token = "" continue } if _, ok := tokens[v]; ok { token = tokens[v] continue } if token != "" { if strings.Contains(v, ":") { v = fmt.Sprintf("%s %s", token, v) } else { if strings.HasPrefix(v, "Visit www.eurid.eu") { continue } v = fmt.Sprintf("%s: %s", token, v) } } result += "\n" + v } return result } // prepareBR do prepare the .br domain func prepareBR(text string) string { hdlToken := "nic-hdl-br" tokens := map[string]string{ "owner-c": "registrant", "admin-c": "admin", "tech-c": "tech", "billing-c": "billing", } token := "" hdlMap := map[string]string{} for _, v := range strings.Split(text, "\n") { v = strings.TrimSpace(v) if v == "" { token = "" continue } if strings.Contains(v, ":") { vs := strings.SplitN(v, ":", 2) if strings.TrimSpace(vs[0]) == hdlToken { token = strings.TrimSpace(vs[1]) hdlMap[token] = "" } } if token != "" { hdlMap[token] += "\n" + v } } result := "" for _, v := range strings.Split(text, "\n") { v = strings.TrimSpace(v) if v == "" { continue } if strings.Contains(v, ":") { vs := strings.SplitN(v, ":", 2) if strings.TrimSpace(vs[0]) == "owner" { v = fmt.Sprintf("registrant organization: %s", vs[1]) } if vv, ok := tokens[strings.TrimSpace(vs[0])]; ok { for _, tt := range strings.Split(hdlMap[strings.TrimSpace(vs[1])], "\n") { if strings.TrimSpace(tt) == "" { continue } result += fmt.Sprintf("\n%s %s", vv, tt) } continue } } result += "\n" + v } return result } // prepareIR do prepare the .ir domain func prepareIR(text string) string { hdlToken := "nic-hdl" tokens := map[string]string{ "holder-c": "registrant", "admin-c": "admin", "tech-c": "tech", "bill-c": "billing", } token := "" hdlMap := map[string]string{} for _, v := range strings.Split(text, "\n") { v = strings.TrimSpace(v) if v == "" { token = "" continue } if strings.Contains(v, ":") { vs := strings.SplitN(v, ":", 2) if strings.TrimSpace(vs[0]) == hdlToken { token = strings.TrimSpace(vs[1]) hdlMap[token] = "" } } if token != "" { hdlMap[token] += "\n" + v } } result := "" for _, v := range strings.Split(text, "\n") { v = strings.TrimSpace(v) if v == "" { continue } if strings.Contains(v, ":") { vs := strings.SplitN(v, ":", 2) if vv, ok := tokens[strings.TrimSpace(vs[0])]; ok { for _, tt := range strings.Split(hdlMap[strings.TrimSpace(vs[1])], "\n") { if strings.TrimSpace(tt) == "" { continue } result += fmt.Sprintf("\n%s %s", vv, tt) } continue } } result += "\n" + v } return result } // prepareFI do prepare the .fi domain func prepareFI(text string) string { tokens := map[string]string{ "Holder": "Registrant", "Registrar": "Registrar", "Tech": "Technical", } token := "" result := "" for _, v := range strings.Split(text, "\n") { v = strings.TrimSpace(v) if len(v) == 0 { continue } if v[0] == '>' { token = "" } if _, ok := tokens[v]; ok { token = tokens[v] } else { if strings.Contains(v, ":") { vv := strings.SplitN(v, ":", 2) vv[0] = strings.Trim(vv[0], ".") if token == "Registrar" && vv[0] == "registrar" { vv[0] = "name" } v = fmt.Sprintf("%s: %s", vv[0], vv[1]) } if token != "" { v = fmt.Sprintf("%s %s", token, v) } } result += "\n" + v } return result } // prepareRS do prepare the .rs domain func prepareRS(text string) string { tokens := map[string]string{ "Registrant": "Registrant", "Administrative contact": "Administrative", "Technical contact": "Technical", } token := "" result := "" for _, v := range strings.Split(text, "\n") { v = strings.TrimSpace(v) if len(v) == 0 { token = "" continue } if strings.Contains(v, ":") { vv := strings.SplitN(v, ":", 2) vv[0] = strings.TrimSpace(vv[0]) if t, ok := tokens[vv[0]]; ok { token = t } else if token != "" { v = fmt.Sprintf("%s %s", token, v) } } result += "\n" + v } return result } // prepareEE do prepare the .ee domain func prepareEE(text string) string { tokens := map[string]string{ "Domain:": "Domain", "Registrar:": "Registrar", "Registrant:": "Registrant", "Administrative contact:": "Administrative", "Technical contact:": "Technical", "Name servers:": "", } token := "" result := "" for _, v := range strings.Split(text, "\n") { v = strings.TrimSpace(v) if len(v) == 0 { token = "" continue } if t, ok := tokens[v]; ok { token = t continue } v = fmt.Sprintf("%s %s", token, v) result += "\n" + strings.TrimSpace(v) } return result } // prepareCN do prepare the .cn domain func prepareCN(text string) string { var result string for _, v := range strings.Split(text, "\n") { v = strings.TrimSpace(v) if strings.Contains(v, ":") { vs := strings.SplitN(v, ":", 2) if strings.ToLower(strings.TrimSpace(vs[0])) == "registrant" { vs[0] = "registrant name" } v = fmt.Sprintf("%s: %s", vs[0], vs[1]) } result += "\n" + v } return result } // preparePL prepares the .pl domain func preparePL(text string) string { result := "" special := "" registrarLine := 0 for _, v := range strings.Split(text, "\n") { if special == "nameservers" { if strings.HasPrefix(v, " ") { ns := strings.SplitN(v, "[", 2) result += fmt.Sprintf("\nnameservers: %s", strings.TrimSpace(ns[0])) continue } special = "" } else if special == "REGISTRAR" { if strings.TrimSpace(v) == "" { special = "" } else { switch registrarLine { case 0: // always name result += fmt.Sprintf("\nregistrar name: %s", strings.TrimSpace(v)) case 1: // always street address result += fmt.Sprintf("\nregistrar street: %s", strings.TrimSpace(v)) case 2: // postal code, city, state, sometimes country in an undefined format // there's no way we can reliably unpack that registrarLine++ continue default: // usually country unless it was on previous line, then phones/emails/www if strings.Contains(v, "@") { // email may have an "e-mail" prefix, but mostly does not result += fmt.Sprintf("\nregistrar email: %s", strings.TrimSpace(strings.TrimLeft(v, "e-mail:"))) } else if strings.HasPrefix(v, "+") { // phone numbers helpfully always start with + result += fmt.Sprintf("\nregistrar phone: %s", strings.TrimSpace(v)) } else if strings.Contains(v, ".") { // WWW addresses sometimes include http/https, sometimes do not result += fmt.Sprintf("\nregistrar www: %s", strings.TrimSpace(v)) } else { result += fmt.Sprintf("\nregistrar country: %s", strings.TrimSpace(v)) } } registrarLine++ continue } } v = strings.TrimSpace(v) if v == "" { continue } if strings.HasPrefix(v, "nameservers: ") { special = "nameservers" ns := strings.SplitN(v, "[", 2) result += fmt.Sprintf("\n%s", strings.TrimSpace(ns[0])) continue } if strings.HasPrefix(v, "REGISTRAR:") { special = "REGISTRAR" continue } result += fmt.Sprintf("\n%s", strings.ReplaceAll(v, "WHOIS database responses:", "whois:")) } return result } // prepareDK do prepare the .dk domain func prepareDK(text string) string { var result string for _, v := range strings.Split(text, "\n") { if strings.HasPrefix(v, "DNS:") { continue } result += v + "\n" } return result } // prepareBY do prepare the .by domain func prepareBY(text string) string { var result string tokens := map[string]string{ "Person": "Registrant Person", "Org": "Registrant Org", "Country": "Registrant Country", "Address": "Registrant Address", "Phone": "Registrant Phone", "Email": "Registrant Email", } for _, v := range strings.Split(text, "\n") { v = strings.TrimSpace(v) if strings.Contains(v, ":") { vs := strings.SplitN(v, ":", 2) if t, ok := tokens[strings.TrimSpace(vs[0])]; ok { v = fmt.Sprintf("%s: %s", t, vs[1]) } } result += v + "\n" } return result } // prepareUA do prepare the .ua domain func prepareUA(text string) string { var result string tokens := map[string]string{ "% Registrar:": "Registrar", "% Registrant:": "Registrant", "% Administrative Contacts:": "Administrative", "% Technical Contacts:": "Technical", } var token string uniqueLine := map[string]bool{} for _, v := range strings.Split(text, "\n") { v = strings.TrimSpace(v) if v, ok := tokens[v]; ok { token = v continue } if token != "" && v != "" && !strings.HasPrefix(v, "%") { vs := strings.SplitN(v, ":", 2) vs[0] = strings.TrimSuffix(strings.TrimSpace(vs[0]), "-loc") if vs[0] == "registrar" { vs[0] = "name" } vs[1] = strings.TrimSpace(vs[1]) if vs[1] == "n/a" { continue } v = fmt.Sprintf("%s %s", token, strings.Join(vs, ":")) } if v != "" && uniqueLine[v] { continue } uniqueLine[v] = true result += v + "\n" } return result } // prepareAT prepares the .at domain func prepareAT(text string) string { result := "" registrantID := "" techID := "" tokens := map[string]string{ "street address": "address", "postal code": "address", "city": "address", "country": "address", "e-mail": "email", "nic-hdl": "id", "personname": "name", } formatLine := func(line, token string) string { before, after, _ := strings.Cut(line, ":") key := strings.TrimSpace(before) if t, ok := tokens[key]; ok { key = t } val := strings.TrimSpace(after) return fmt.Sprintf("%s %s: %s", token, key, val) } for _, v := range strings.Split(text, "\n\n") { v = strings.TrimSpace(v) if strings.HasPrefix(v, "%") { continue } if strings.Contains(v, ":") { b := strings.Split(v, "\n") if strings.HasPrefix(b[0], "domain") { for _, l := range b { w := "" if before, after, ok := strings.Cut(l, ":"); ok { key := strings.TrimSpace(before) val := strings.TrimSpace(after) switch key { case "domain": w = fmt.Sprintf("%s: %s", "domain name", val) case "registrant": registrantID = val case "tech-c": techID = val case "changed": w = fmt.Sprintf("%s: %s", "updated_date", val) case "nserver": w = fmt.Sprintf("%s: %s", "name_servers", val) default: w = fmt.Sprintf("domain %s: %s", key, val) } if w != "" { result += w + "\n" } } } } else if strings.HasPrefix(b[0], "personname") { token := "" if strings.Contains(v, registrantID) { token = "registrant" } else if strings.Contains(v, techID) { token = "technical contact" } for _, l := range strings.Split(v, "\n") { result += formatLine(l, token) + "\n" } } } } return result }