diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index aea27d1..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: ci - -on: - push: - branches: [master] - pull_request: - branches: [master] - -jobs: - build: - runs-on: ubuntu-latest - env: - DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} - DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} - steps: - - uses: actions/checkout@v2 - - name: install go - uses: actions/setup-go@v2 - with: - go-version: 1.16 - - name: build and test - run: make - - name: enable experimental docker features - if: ${{ github.ref == 'refs/heads/master' }} - run: | - echo '{"experimental":true}' | sudo tee /etc/docker/daemon.json - sudo service docker restart - - name: publish multi-arch docker image - if: ${{ github.ref == 'refs/heads/master' }} - run: make docker-pushx diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..27c4e28 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,22 @@ +name: Go + +on: [push] + +jobs: + test: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v4 + with: + go-version: "1.13.x" + + - name: Display Go version + run: go version + + - name: Run tests + run: go test ./... diff --git a/.gitignore b/.gitignore index ac30f00..56d06b1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ /custom.html /vendor/ .vscode/ +.idea/ /bin/ diff --git a/cmd/echoip/main.go b/cmd/echoip/main.go index 7c0a668..b47e74c 100644 --- a/cmd/echoip/main.go +++ b/cmd/echoip/main.go @@ -7,11 +7,11 @@ import ( "os" - "github.com/mpolden/echoip/http" - "github.com/mpolden/echoip/iputil" - "github.com/mpolden/echoip/iputil/geo" - "github.com/mpolden/echoip/iputil/ipstack" - parser "github.com/mpolden/echoip/iputil/paser" + "github.com/levelsoftware/echoip/http" + "github.com/levelsoftware/echoip/iputil" + "github.com/levelsoftware/echoip/iputil/geo" + "github.com/levelsoftware/echoip/iputil/ipstack" + parser "github.com/levelsoftware/echoip/iputil/paser" ipstackApi "github.com/qioalice/ipstack" ) diff --git a/go.mod b/go.mod index 55d4af8..38293f9 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,9 @@ -module github.com/mpolden/echoip +module github.com/levelsoftware/echoip go 1.13 require ( github.com/oschwald/geoip2-golang v1.5.0 - github.com/qioalice/ipstack v1.0.1 // indirect + github.com/qioalice/ipstack v1.0.1 golang.org/x/sys v0.0.0-20210223212115-eede4237b368 // indirect ) diff --git a/go.sum b/go.sum index e79f682..8bfbc1b 100644 --- a/go.sum +++ b/go.sum @@ -12,7 +12,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ 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= -golang.org/x/sys v0.0.0-20191224085550-c709ea063b76 h1:Dho5nD6R3PcW2SH1or8vS0dszDaXRxIw55lBX7XiE5g= golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210223212115-eede4237b368 h1:fDE3p0qf2V1co1vfj3/o87Ps8Hq6QTGNxJ5Xe7xSp80= golang.org/x/sys v0.0.0-20210223212115-eede4237b368/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/html/index.html b/html/index.html index fd75ead..23fba4c 100644 --- a/html/index.html +++ b/html/index.html @@ -113,10 +113,9 @@ Timezone {{ .Timezone }} - {{ end }} {{ if .IsDayLightSavings }} Is Daylight Savings? - {{ .IsDayLightSavings }} + {{ .TimezoneEtc.IsDaylightSavings }} {{ end }} {{ if .ASN }} @@ -153,35 +152,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/cache.go b/http/cache.go index 2882fc9..99fcc79 100644 --- a/http/cache.go +++ b/http/cache.go @@ -7,7 +7,7 @@ import ( "net" "sync" - parser "github.com/mpolden/echoip/iputil/paser" + parser "github.com/levelsoftware/echoip/iputil/paser" ) type Cache struct { diff --git a/http/cache_test.go b/http/cache_test.go index c33aff9..76bbc1b 100644 --- a/http/cache_test.go +++ b/http/cache_test.go @@ -5,7 +5,7 @@ import ( "net" "testing" - parser "github.com/mpolden/echoip/iputil/paser" + parser "github.com/levelsoftware/echoip/iputil/paser" ) func TestCacheCapacity(t *testing.T) { diff --git a/http/http.go b/http/http.go index 9693d2b..39fe1c5 100644 --- a/http/http.go +++ b/http/http.go @@ -11,8 +11,8 @@ import ( "net/http/pprof" - parser "github.com/mpolden/echoip/iputil/paser" - "github.com/mpolden/echoip/useragent" + parser "github.com/levelsoftware/echoip/iputil/paser" + "github.com/levelsoftware/echoip/useragent" "net" "net/http" diff --git a/http/http_test.go b/http/http_test.go index b621645..f6c54e1 100644 --- a/http/http_test.go +++ b/http/http_test.go @@ -11,9 +11,9 @@ import ( "strings" "testing" - "github.com/mpolden/echoip/iputil" - "github.com/mpolden/echoip/iputil/geo" - parser "github.com/mpolden/echoip/iputil/paser" + "github.com/levelsoftware/echoip/iputil" + "github.com/levelsoftware/echoip/iputil/geo" + parser "github.com/levelsoftware/echoip/iputil/paser" ) func lookupAddr(net.IP) (string, error) { return "localhost", nil } @@ -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..d92f36b 100644 --- a/iputil/geo/geo.go +++ b/iputil/geo/geo.go @@ -5,8 +5,8 @@ import ( "math" "net" - "github.com/mpolden/echoip/iputil" - parser "github.com/mpolden/echoip/iputil/paser" + "github.com/levelsoftware/echoip/iputil" + parser "github.com/levelsoftware/echoip/iputil/paser" geoip2 "github.com/oschwald/geoip2-golang" ) @@ -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..6f0fe09 100644 --- a/iputil/ipstack/ipstack.go +++ b/iputil/ipstack/ipstack.go @@ -3,10 +3,11 @@ package ipstack import ( "fmt" "net" - "reflect" + "time" + + "github.com/levelsoftware/echoip/iputil" + parser "github.com/levelsoftware/echoip/iputil/paser" - "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.([]string), + } + } +} + +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..bef9948 100644 --- a/iputil/paser/parser.go +++ b/iputil/paser/parser.go @@ -4,7 +4,7 @@ import ( "math/big" "net" - "github.com/mpolden/echoip/useragent" + "github.com/levelsoftware/echoip/useragent" ) type Parser interface { @@ -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 []string `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"` }