From 9c60aef6371c608d715f3ded92afa91007397c48 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Thu, 20 May 2021 14:22:06 +0300 Subject: [PATCH] Pull request: home: imp whois parse Updates #2646. Squashed commit of the following: commit 0a5ff6ae74c532a296c0594a598a99c7cfaccf8c Author: Ainar Garipov Date: Thu May 20 14:07:08 2021 +0300 home: imp code commit 2af0f463a77b81e827d9faca079a19c5437e1cd9 Author: Ainar Garipov Date: Thu May 20 13:48:08 2021 +0300 home: imp whois parse --- internal/home/whois.go | 107 ++++++++++++++++++++---------------- internal/home/whois_test.go | 74 +++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 46 deletions(-) diff --git a/internal/home/whois.go b/internal/home/whois.go index 7c77a903..78721edd 100644 --- a/internal/home/whois.go +++ b/internal/home/whois.go @@ -10,7 +10,6 @@ import ( "time" "github.com/AdguardTeam/AdGuardHome/internal/aghio" - "github.com/AdguardTeam/AdGuardHome/internal/aghstrings" "github.com/AdguardTeam/golibs/cache" "github.com/AdguardTeam/golibs/log" ) @@ -66,55 +65,71 @@ func trimValue(s string) string { return s[:maxValueLength-3] + "..." } -// Parse plain-text data from the response -func whoisParse(data string) map[string]string { - m := map[string]string{} - descr := "" - netname := "" - for len(data) != 0 { - ln := aghstrings.SplitNext(&data, '\n') - if len(ln) == 0 || ln[0] == '#' || ln[0] == '%' { - continue - } - - kv := strings.SplitN(ln, ":", 2) - if len(kv) != 2 { - continue - } - k := strings.TrimSpace(kv[0]) - k = strings.ToLower(k) - v := strings.TrimSpace(kv[1]) - - switch k { - case "org-name": - m["orgname"] = trimValue(v) - case "city", "country", "orgname": - m[k] = trimValue(v) - case "descr": - if len(descr) == 0 { - descr = v - } - case "netname": - netname = v - case "whois": // "whois: whois.arin.net" - m["whois"] = v - case "referralserver": // "ReferralServer: whois://whois.ripe.net" - if strings.HasPrefix(v, "whois://") { - m["whois"] = v[len("whois://"):] - } +// coalesceStr returns the first non-empty string. +// +// TODO(a.garipov): Move to aghstrings? +func coalesceStr(strs ...string) (res string) { + for _, s := range strs { + if s != "" { + return s } } - _, ok := m["orgname"] - if !ok { - // Set orgname from either descr or netname for the frontent. - // - // TODO(a.garipov): Perhaps don't do that in the V1 HTTP API? - if descr != "" { - m["orgname"] = trimValue(descr) - } else if netname != "" { - m["orgname"] = trimValue(netname) + return "" +} + +// isWhoisComment returns true if the string is empty or is a WHOIS comment. +func isWhoisComment(s string) (ok bool) { + return len(s) == 0 || s[0] == '#' || s[0] == '%' +} + +// strmap is an alias for convenience. +type strmap = map[string]string + +// whoisParse parses a subset of plain-text data from the WHOIS response into +// a string map. +func whoisParse(data string) (m strmap) { + m = strmap{} + + var orgname string + lines := strings.Split(data, "\n") + for _, l := range lines { + if isWhoisComment(l) { + continue } + + kv := strings.SplitN(l, ":", 2) + if len(kv) != 2 { + continue + } + + k := strings.ToLower(strings.TrimSpace(kv[0])) + v := strings.TrimSpace(kv[1]) + if v == "" { + continue + } + + switch k { + case "orgname", "org-name": + k = "orgname" + v = trimValue(v) + orgname = v + case "city", "country": + v = trimValue(v) + case "descr", "netname": + k = "orgname" + v = coalesceStr(orgname, v) + orgname = v + case "whois": + k = "whois" + case "referralserver": + k = "whois" + v = strings.TrimPrefix(v, "whois://") + default: + continue + } + + m[k] = v } return m diff --git a/internal/home/whois_test.go b/internal/home/whois_test.go index 12511bbd..f87fa4ae 100644 --- a/internal/home/whois_test.go +++ b/internal/home/whois_test.go @@ -76,3 +76,77 @@ func TestWhois(t *testing.T) { assert.Equal(t, "Imagiland", m["country"]) assert.Equal(t, "Nonreal", m["city"]) } + +func TestWhoisParse(t *testing.T) { + const ( + city = "Nonreal" + country = "Imagiland" + orgname = "FakeOrgLLC" + whois = "whois.example.net" + ) + + testCases := []struct { + want strmap + name string + in string + }{{ + want: strmap{}, + name: "empty", + in: ``, + }, { + want: strmap{}, + name: "comments", + in: "%\n#", + }, { + want: strmap{}, + name: "no_colon", + in: "city", + }, { + want: strmap{}, + name: "no_value", + in: "city:", + }, { + want: strmap{"city": city}, + name: "city", + in: `city: ` + city, + }, { + want: strmap{"country": country}, + name: "country", + in: `country: ` + country, + }, { + want: strmap{"orgname": orgname}, + name: "orgname", + in: `orgname: ` + orgname, + }, { + want: strmap{"orgname": orgname}, + name: "orgname_hyphen", + in: `org-name: ` + orgname, + }, { + want: strmap{"orgname": orgname}, + name: "orgname_descr", + in: `descr: ` + orgname, + }, { + want: strmap{"orgname": orgname}, + name: "orgname_netname", + in: `netname: ` + orgname, + }, { + want: strmap{"whois": whois}, + name: "whois", + in: `whois: ` + whois, + }, { + want: strmap{"whois": whois}, + name: "referralserver", + in: `referralserver: whois://` + whois, + }, { + want: strmap{}, + name: "other", + in: `other: value`, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + got := whoisParse(tc.in) + assert.Equal(t, tc.want, got) + }) + } +}