diff --git a/go.sum b/go.sum index e79f682..f585e6f 100644 --- a/go.sum +++ b/go.sum @@ -9,6 +9,7 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/qioalice/ipstack v1.0.1 h1:Ync2O+tR2AH9/TzTg4aeJxd9c1Xz2CeH1joECwnhvms= github.com/qioalice/ipstack v1.0.1/go.mod h1:6eB9LdNCUdUoOsfDB8Pn2GpmD2I+f2k3yR30ceuf/rY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -18,5 +19,6 @@ golang.org/x/sys v0.0.0-20210223212115-eede4237b368 h1:fDE3p0qf2V1co1vfj3/o87Ps8 golang.org/x/sys v0.0.0-20210223212115-eede4237b368/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/html/index.html b/html/index.html index 9657acb..6581a07 100644 --- a/html/index.html +++ b/html/index.html @@ -136,10 +136,9 @@ Timezone {{ .Timezone }} - {{ end }} {{ if .IsDayLightSavings }} Is Daylight Savings? - {{ .IsDayLightSavings }} + {{ .TimezoneEtc.IsDaylightSavings }} {{ end }} {{ if .ASN }} @@ -176,35 +175,35 @@ {{ if .IPStackSecurityEnabled }} Is Proxy? - {{ .IsProxy }} + {{ .Security.IsProxy }} Is Crawler? - {{ .IsCrawler }} + {{ .Security.IsCrawler }} - {{ if .IsCrawler }} + {{ if .Security.IsCrawler }} Crawler Name - {{ .CrawlerName }} + {{ .Security.CrawlerName }} Crawler Type - {{ .CrawlerType }} + {{ .Security.CrawlerType }} {{ end }} Is Tor? - {{ .IsTor }} + {{ .Security.IsTor }} Threat Level - {{ .ThreatLevel }} + {{ .Security.ThreatLevel }} Threat Types - {{ .ThreatTypes }} + {{ .Security.ThreatTypes }} {{ end }} diff --git a/http/http_test.go b/http/http_test.go index b621645..dd136a3 100644 --- a/http/http_test.go +++ b/http/http_test.go @@ -163,7 +163,7 @@ func TestDisabledHandlers(t *testing.T) { {s.URL + "/country", "404 page not found", 404}, {s.URL + "/country-iso", "404 page not found", 404}, {s.URL + "/city", "404 page not found", 404}, - {s.URL + "/json", "{\n \"UsingGeoIP\": true,\n \"UsingIPStack\": false,\n \"IPStackSecurityEnabled\": false,\n \"ip\": \"127.0.0.1\",\n \"ip_decimal\": 2130706433\n}", 200}, + {s.URL + "/json", "{\n \"UsingGeoIP\": true,\n \"UsingIPStack\": false,\n \"IPStackSecurityEnabled\": false,\n \"timezone_etc\": {},\n \"security\": {\n \"is_proxy\": false,\n \"is_crawler\": false,\n \"is_tor\": false\n },\n \"currency\": {},\n \"location\": {\n \"country_flag\": {}\n },\n \"ip\": \"127.0.0.1\",\n \"ip_decimal\": 2130706433\n}", 200}, } for _, tt := range tests { @@ -189,7 +189,7 @@ func TestJSONHandlers(t *testing.T) { out string status int }{ - {s.URL, "{\n \"UsingGeoIP\": true,\n \"UsingIPStack\": false,\n \"IPStackSecurityEnabled\": false,\n \"ip\": \"127.0.0.1\",\n \"ip_decimal\": 2130706433,\n \"country\": \"Elbonia\",\n \"country_iso\": \"EB\",\n \"country_eu\": false,\n \"region_name\": \"North Elbonia\",\n \"region_code\": \"1234\",\n \"metro_code\": 1234,\n \"zip_code\": \"1234\",\n \"city\": \"Bornyasherk\",\n \"latitude\": 63.416667,\n \"longitude\": 10.416667,\n \"time_zone\": \"Europe/Bornyasherk\",\n \"asn\": \"AS59795\",\n \"asn_org\": \"Hosting4Real\",\n \"hostname\": \"localhost\",\n \"user_agent\": {\n \"product\": \"curl\",\n \"version\": \"7.2.6.0\",\n \"raw_value\": \"curl/7.2.6.0\"\n }\n}", 200}, + {s.URL, "{\n \"UsingGeoIP\": true,\n \"UsingIPStack\": false,\n \"IPStackSecurityEnabled\": false,\n \"timezone_etc\": {},\n \"security\": {\n \"is_proxy\": false,\n \"is_crawler\": false,\n \"is_tor\": false\n },\n \"currency\": {},\n \"location\": {\n \"country_flag\": {}\n },\n \"ip\": \"127.0.0.1\",\n \"ip_decimal\": 2130706433,\n \"country\": \"Elbonia\",\n \"country_iso\": \"EB\",\n \"country_eu\": false,\n \"region_name\": \"North Elbonia\",\n \"region_code\": \"1234\",\n \"metro_code\": 1234,\n \"zip_code\": \"1234\",\n \"city\": \"Bornyasherk\",\n \"latitude\": 63.416667,\n \"longitude\": 10.416667,\n \"timezone\": \"Europe/Bornyasherk\",\n \"asn\": \"AS59795\",\n \"asn_org\": \"Hosting4Real\",\n \"hostname\": \"localhost\",\n \"user_agent\": {\n \"product\": \"curl\",\n \"version\": \"7.2.6.0\",\n \"raw_value\": \"curl/7.2.6.0\"\n }\n}", 200}, {s.URL + "/port/foo", "{\n \"status\": 400,\n \"error\": \"invalid port: foo\"\n}", 400}, {s.URL + "/port/0", "{\n \"status\": 400,\n \"error\": \"invalid port: 0\"\n}", 400}, {s.URL + "/port/65537", "{\n \"status\": 400,\n \"error\": \"invalid port: 65537\"\n}", 400}, diff --git a/iputil/geo/geo.go b/iputil/geo/geo.go index fdacf2c..a170a81 100644 --- a/iputil/geo/geo.go +++ b/iputil/geo/geo.go @@ -84,22 +84,24 @@ func (g *geoip) Parse(ip net.IP, hostname string) (parser.Response, error) { UsingGeoIP: true, UsingIPStack: false, IPStackSecurityEnabled: false, - IP: ip, - IPDecimal: ipDecimal, - Country: country.Name, - CountryISO: country.ISO, - CountryEU: country.IsEU, - RegionName: city.RegionName, - RegionCode: city.RegionCode, - MetroCode: city.MetroCode, - PostalCode: city.PostalCode, - City: city.Name, - Latitude: city.Latitude, - Longitude: city.Longitude, - Timezone: city.Timezone, - ASN: autonomousSystemNumber, - ASNOrg: asn.AutonomousSystemOrganization, - Hostname: hostname, + + /* kept for backward compatibility */ + IP: ip, + IPDecimal: ipDecimal, + Country: country.Name, + CountryISO: country.ISO, + CountryEU: country.IsEU, + RegionName: city.RegionName, + RegionCode: city.RegionCode, + MetroCode: city.MetroCode, + PostalCode: city.PostalCode, + City: city.Name, + Latitude: city.Latitude, + Longitude: city.Longitude, + Timezone: city.Timezone, + ASN: autonomousSystemNumber, + ASNOrg: asn.AutonomousSystemOrganization, + Hostname: hostname, }, nil } diff --git a/iputil/ipstack/ipstack.go b/iputil/ipstack/ipstack.go index 6ec3be5..207d6f9 100644 --- a/iputil/ipstack/ipstack.go +++ b/iputil/ipstack/ipstack.go @@ -3,10 +3,11 @@ package ipstack import ( "fmt" "net" - "reflect" + "time" "github.com/mpolden/echoip/iputil" parser "github.com/mpolden/echoip/iputil/paser" + "github.com/qioalice/ipstack" ) @@ -17,6 +18,7 @@ type IPStack struct { func (ips *IPStack) Parse(ip net.IP, hostname string) (parser.Response, error) { res, err := ipstack.IP(ip.String()) ips.response = res + if err != nil { return parser.Response{}, err } @@ -26,52 +28,91 @@ func (ips *IPStack) Parse(ip net.IP, hostname string) (parser.Response, error) { parserResponse := parser.Response{ UsingGeoIP: false, UsingIPStack: true, - Latitude: float64(res.Latitide), - Longitude: float64(res.Longitude), - Hostname: hostname, - IP: ip, - IPDecimal: ipDecimal, - Country: res.CountryName, - CountryISO: res.CountryCode, - RegionName: res.RegionName, - RegionCode: res.RegionCode, - MetroCode: 0, - PostalCode: res.Zip, - City: res.City, + + /* kept for backward compatibility */ + Latitude: float64(res.Latitide), + Longitude: float64(res.Longitude), + Hostname: hostname, + IP: ip, + IPDecimal: ipDecimal, + Country: res.CountryName, + CountryISO: res.CountryCode, + RegionName: res.RegionName, + RegionCode: res.RegionCode, + MetroCode: 0, + PostalCode: res.Zip, + City: res.City, } - if res.Timezone != nil { - parserResponse.Timezone = res.Timezone.ID - parserResponse.IsDayLightSavings = res.Timezone.IsDaylightSaving - } - - if res.Security != nil { - parserResponse.IPStackSecurityEnabled = true - parserResponse.IsProxy = res.Security.IsProxy - parserResponse.IsCrawler = res.Security.IsCrawler - parserResponse.CrawlerName = res.Security.CrawlerName - parserResponse.CrawlerType = res.Security.CrawlerType - parserResponse.IsTor = res.Security.IsTOR - parserResponse.ThreatLevel = res.Security.ThreatLevel - - if !reflect.ValueOf(&res.Security.ThreatTypes).IsNil() { - parserResponse.ThreatTypes = &res.Security.ThreatTypes - } - } - - if res.Location != nil { - parserResponse.CountryEU = &res.Location.IsEU - } - - if res.Connection != nil { - if res.Connection.ASN > 0 { - parserResponse.ASN = fmt.Sprintf("AS%d", res.Connection.ASN) - } - } + ips.ParseSecurityResponse(&parserResponse) + ips.ParseTimezoneResponse(&parserResponse) + ips.ParseLocationResponse(&parserResponse) + ips.ParseConnectionResponse(&parserResponse) return parserResponse, nil } +func (ips *IPStack) ParseSecurityResponse(parserResponse *parser.Response) { + if ips.response.Security != nil { + parserResponse.IPStackSecurityEnabled = true + + parserResponse.Security = parser.Security{ + IsProxy: ips.response.Security.IsProxy, + IsTor: ips.response.Security.IsTOR, + CrawlerName: ips.response.Security.CrawlerName, + CrawlerType: ips.response.Security.CrawlerType, + ThreatLevel: ips.response.Security.ThreatLevel, + ThreatTypes: ips.response.Security.ThreatTypes, + } + } +} + +func (ips *IPStack) ParseTimezoneResponse(parserResponse *parser.Response) { + if ips.response.Timezone != nil { + parserResponse.TimezoneEtc = parser.Timezone{ + ID: ips.response.Timezone.ID, + CurrentTime: ips.response.Timezone.CurrentTime.Format(time.RFC3339), + GmtOffset: ips.response.Timezone.GMTOffset, + Code: ips.response.Timezone.Code, + IsDaylightSavings: ips.response.Timezone.IsDaylightSaving, + } + + /* kept for backward compatibility */ + parserResponse.Timezone = ips.response.Timezone.ID + } +} + +func (ips *IPStack) ParseLocationResponse(parserResponse *parser.Response) { + if ips.response.Location != nil { + var languages []parser.Language + for i := 0; i < len(ips.response.Location.Languages); i++ { + languages = append(languages, parser.Language{ + Code: ips.response.Location.Languages[i].Code, + Name: ips.response.Location.Languages[i].Name, + Native: ips.response.Location.Languages[i].NativeName, + }) + } + parserResponse.Location = parser.Location{ + Languages: languages, + CountryFlag: parser.CountryFlag{ + Flag: ips.response.Location.CountryFlagLink, + Emoji: ips.response.Location.CountryFlagEmoji, + EmojiUnicode: ips.response.Location.CountryFlagEmojiUnicode, + }, + } + + /* kept for backward compatibility */ + parserResponse.CountryEU = &ips.response.Location.IsEU + } +} + +func (ips *IPStack) ParseConnectionResponse(parserResponse *parser.Response) { + if ips.response.Connection != nil && ips.response.Connection.ASN > 0 { + /* kept for backward compatibility */ + parserResponse.ASN = fmt.Sprintf("AS%d", ips.response.Connection.ASN) + } +} + func (ips *IPStack) IsEmpty() bool { return false } diff --git a/iputil/paser/parser.go b/iputil/paser/parser.go index 3a9635d..c4899c0 100644 --- a/iputil/paser/parser.go +++ b/iputil/paser/parser.go @@ -12,34 +12,75 @@ type Parser interface { IsEmpty() bool } -type Response struct { - UsingGeoIP bool `json:"UsingGeoIP"` - UsingIPStack bool `json:"UsingIPStack"` - IPStackSecurityEnabled bool `json:"IPStackSecurityEnabled"` - IP net.IP `json:"ip"` - IPDecimal *big.Int `json:"ip_decimal"` - Country string `json:"country,omitempty"` - CountryISO string `json:"country_iso,omitempty"` - CountryEU *bool `json:"country_eu,omitempty"` - RegionName string `json:"region_name,omitempty"` - RegionCode string `json:"region_code,omitempty"` - MetroCode uint `json:"metro_code,omitempty"` - PostalCode string `json:"zip_code,omitempty"` - City string `json:"city,omitempty"` - Latitude float64 `json:"latitude,omitempty"` - Longitude float64 `json:"longitude,omitempty"` - Timezone string `json:"time_zone,omitempty"` - IsDayLightSavings bool `json:"is_daylight_savings,omitempty"` - ASN string `json:"asn,omitempty"` - ASNOrg string `json:"asn_org,omitempty"` - Hostname string `json:"hostname,omitempty"` - UserAgent *useragent.UserAgent `json:"user_agent,omitempty"` - CurrencyCode string `json:"currency_code,omitempty"` - IsProxy bool `json:"is_proxy,omitempty"` - IsCrawler bool `json:"is_crawler,omitempty"` - CrawlerName string `json:"crawler_name,omitempty"` - CrawlerType string `json:"crawler_type,omitempty"` - IsTor bool `json:"is_tor,omitempty"` - ThreatLevel string `json:"threat_level,omitempty"` - ThreatTypes *interface{} `json:"threat_types,omitempty"` +type Currency struct { + Code string `json:"code,omitempty"` + Name string `json:"name,omitempty"` + Plural string `json:"plural,omitempty"` + Symbol string `json:"symbol,omitempty"` + SymbolNative string `json:"symbol_native,omitempty"` +} + +type Security struct { + IsProxy bool `json:"is_proxy"` + IsCrawler bool `json:"is_crawler"` + CrawlerName string `json:"crawler_name,omitempty"` + CrawlerType string `json:"crawler_type,omitempty"` + IsTor bool `json:"is_tor"` + ThreatLevel string `json:"threat_level,omitempty"` + ThreatTypes interface{} `json:"threat_types,omitempty"` +} + +type Timezone struct { + ID string `json:"id,omitempty"` + CurrentTime string `json:"current_time,omitempty"` + GmtOffset int `json:"gmt_offset,omitempty"` + Code string `json:"code,omitempty"` + IsDaylightSavings bool `json:"is_daylight_savings,omitempty"` +} + +type Language struct { + Code string `json:"code,omitempty"` + Name string `json:"name,omitempty"` + Native string `json:"native,omitempty"` +} + +type CountryFlag struct { + Flag string `json:"flag,omitempty"` + Emoji string `json:"emoji,omitempty"` + EmojiUnicode string `json:"emoji_unicode,omitempty"` +} + +type Location struct { + Languages interface{} `json:"languages,omitempty"` + CountryFlag CountryFlag `json:"country_flag,omitempty"` +} + +type Response struct { + UsingGeoIP bool `json:"UsingGeoIP"` + UsingIPStack bool `json:"UsingIPStack"` + IPStackSecurityEnabled bool `json:"IPStackSecurityEnabled"` + + TimezoneEtc Timezone `json:"timezone_etc,omitempty"` + Security Security `json:"security,omitempty"` + Currency Currency `json:"currency,omitempty"` + Location Location `json:"location,omitempty"` + + /* Kept to prevent breaking changes */ + IP net.IP `json:"ip"` + IPDecimal *big.Int `json:"ip_decimal"` + Country string `json:"country,omitempty"` + CountryISO string `json:"country_iso,omitempty"` + CountryEU *bool `json:"country_eu,omitempty"` + RegionName string `json:"region_name,omitempty"` + RegionCode string `json:"region_code,omitempty"` + MetroCode uint `json:"metro_code,omitempty"` + PostalCode string `json:"zip_code,omitempty"` + City string `json:"city,omitempty"` + Latitude float64 `json:"latitude,omitempty"` + Longitude float64 `json:"longitude,omitempty"` + Timezone string `json:"timezone,omitempty"` + ASN string `json:"asn,omitempty"` + ASNOrg string `json:"asn_org,omitempty"` + Hostname string `json:"hostname,omitempty"` + UserAgent *useragent.UserAgent `json:"user_agent,omitempty"` }