diff --git a/.gitignore b/.gitignore index 2de2e27..fff681c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.mmdb .env** +.geoipupdate.lock diff --git a/db.go b/db.go index cda1e84..f176b48 100644 --- a/db.go +++ b/db.go @@ -1,32 +1,70 @@ package main import ( + "fmt" "log" + "os" + "os/exec" "sync" "github.com/oschwald/maxminddb-golang" ) -var cityDB *maxminddb.Reader -var asnDB *maxminddb.Reader -var dbMtx = new(sync.RWMutex) +var ( + cityDB *maxminddb.Reader + asnDB *maxminddb.Reader + dbMtx = new(sync.RWMutex) +) const ( cityDBPath = "./GeoLite2-City.mmdb" asnDBPath = "./GeoLite2-ASN.mmdb" ) +func downloadDB() error { + cmd := exec.Command("geoipupdate", "-d", "./") + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to download database: %v. Ensure geoipupdate is installed and configured", err) + } + return nil +} + func initDatabases() { var err error + dbMtx.Lock() + defer dbMtx.Unlock() + cityDB, err = maxminddb.Open(cityDBPath) if err != nil { - log.Fatalf("Error opening city database: %v", err) + if os.IsNotExist(err) { + log.Println("City database not found, attempting to download...") + if errDownload := downloadDB(); errDownload != nil { + log.Fatalf("Error downloading city database: %v", errDownload) + } + cityDB, err = maxminddb.Open(cityDBPath) + if err != nil { + log.Fatalf("Error opening city database after download: %v", err) + } + } else { + log.Fatalf("Error opening city database: %v", err) + } } asnDB, err = maxminddb.Open(asnDBPath) if err != nil { - log.Fatalf("Error opening ASN database: %v", err) + if os.IsNotExist(err) { + log.Println("ASN database not found, attempting to download...") + if errDownload := downloadDB(); errDownload != nil { + log.Fatalf("Error downloading ASN database: %v", errDownload) + } + asnDB, err = maxminddb.Open(asnDBPath) + if err != nil { + log.Fatalf("Error opening ASN database after download: %v", err) + } + } else { + log.Fatalf("Error opening ASN database: %v", err) + } } } diff --git a/go.mod b/go.mod index 3bcddf4..90db679 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,6 @@ module github.com/skidoodle/ipinfo go 1.22.4 -require github.com/oschwald/maxminddb-golang v1.6.0 +require github.com/oschwald/maxminddb-golang v1.13.1 -require golang.org/x/sys v0.1.0 // indirect +require golang.org/x/sys v0.29.0 // indirect diff --git a/go.sum b/go.sum index 7df9fff..91c8558 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,12 @@ -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/oschwald/maxminddb-golang v1.6.0 h1:KAJSjdHQ8Kv45nFIbtoLGrGWqHFajOIm7skTyz/+Dls= -github.com/oschwald/maxminddb-golang v1.6.0/go.mod h1:DUJFucBg2cvqx42YmDa/+xHvb0elJtOm3o4aFQ/nb/w= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE= +github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/iputils.go b/iputils.go index ce17d14..068518c 100644 --- a/iputils.go +++ b/iputils.go @@ -5,7 +5,6 @@ import ( "log" "net" "net/http" - "regexp" "strings" "sync" "time" @@ -13,62 +12,62 @@ import ( var bogonNets = []*net.IPNet{ // IPv4 - {IP: net.IPv4(0, 0, 0, 0), Mask: net.CIDRMask(8, 32)}, // "This" network - {IP: net.IPv4(10, 0, 0, 0), Mask: net.CIDRMask(8, 32)}, // Private-use networks - {IP: net.IPv4(100, 64, 0, 0), Mask: net.CIDRMask(10, 32)}, // Carrier-grade NAT - {IP: net.IPv4(127, 0, 0, 0), Mask: net.CIDRMask(8, 32)}, // Loopback - {IP: net.IPv4(127, 0, 53, 53), Mask: net.CIDRMask(32, 32)}, // Name collision occurrence - {IP: net.IPv4(169, 254, 0, 0), Mask: net.CIDRMask(16, 32)}, // Link-local - {IP: net.IPv4(172, 16, 0, 0), Mask: net.CIDRMask(12, 32)}, // Private-use networks - {IP: net.IPv4(192, 0, 0, 0), Mask: net.CIDRMask(24, 32)}, // IETF protocol assignments - {IP: net.IPv4(192, 0, 2, 0), Mask: net.CIDRMask(24, 32)}, // TEST-NET-1 - {IP: net.IPv4(192, 168, 0, 0), Mask: net.CIDRMask(16, 32)}, // Private-use networks - {IP: net.IPv4(198, 18, 0, 0), Mask: net.CIDRMask(15, 32)}, // Network interconnect device benchmark testing - {IP: net.IPv4(198, 51, 100, 0), Mask: net.CIDRMask(24, 32)}, // TEST-NET-2 - {IP: net.IPv4(203, 0, 113, 0), Mask: net.CIDRMask(24, 32)}, // TEST-NET-3 - {IP: net.IPv4(224, 0, 0, 0), Mask: net.CIDRMask(4, 32)}, // Multicast - {IP: net.IPv4(240, 0, 0, 0), Mask: net.CIDRMask(4, 32)}, // Reserved for future use + {IP: net.IPv4(0, 0, 0, 0), Mask: net.CIDRMask(8, 32)}, // "This" network + {IP: net.IPv4(10, 0, 0, 0), Mask: net.CIDRMask(8, 32)}, // Private-use networks + {IP: net.IPv4(100, 64, 0, 0), Mask: net.CIDRMask(10, 32)}, // Carrier-grade NAT + {IP: net.IPv4(127, 0, 0, 0), Mask: net.CIDRMask(8, 32)}, // Loopback + {IP: net.IPv4(127, 0, 53, 53), Mask: net.CIDRMask(32, 32)}, // Name collision occurrence + {IP: net.IPv4(169, 254, 0, 0), Mask: net.CIDRMask(16, 32)}, // Link-local + {IP: net.IPv4(172, 16, 0, 0), Mask: net.CIDRMask(12, 32)}, // Private-use networks + {IP: net.IPv4(192, 0, 0, 0), Mask: net.CIDRMask(24, 32)}, // IETF protocol assignments + {IP: net.IPv4(192, 0, 2, 0), Mask: net.CIDRMask(24, 32)}, // TEST-NET-1 + {IP: net.IPv4(192, 168, 0, 0), Mask: net.CIDRMask(16, 32)}, // Private-use networks + {IP: net.IPv4(198, 18, 0, 0), Mask: net.CIDRMask(15, 32)}, // Network interconnect device benchmark testing + {IP: net.IPv4(198, 51, 100, 0), Mask: net.CIDRMask(24, 32)}, // TEST-NET-2 + {IP: net.IPv4(203, 0, 113, 0), Mask: net.CIDRMask(24, 32)}, // TEST-NET-3 + {IP: net.IPv4(224, 0, 0, 0), Mask: net.CIDRMask(4, 32)}, // Multicast + {IP: net.IPv4(240, 0, 0, 0), Mask: net.CIDRMask(4, 32)}, // Reserved for future use {IP: net.IPv4(255, 255, 255, 255), Mask: net.CIDRMask(32, 32)}, // Limited broadcast // IPv6 - {IP: net.ParseIP("::/128"), Mask: net.CIDRMask(128, 128)}, // Node-scope unicast unspecified address - {IP: net.ParseIP("::1/128"), Mask: net.CIDRMask(128, 128)}, // Node-scope unicast loopback address + {IP: net.ParseIP("::/128"), Mask: net.CIDRMask(128, 128)}, // Node-scope unicast unspecified address + {IP: net.ParseIP("::1/128"), Mask: net.CIDRMask(128, 128)}, // Node-scope unicast loopback address {IP: net.ParseIP("::ffff:0:0/96"), Mask: net.CIDRMask(96, 128)}, // IPv4-mapped addresses - {IP: net.ParseIP("::/96"), Mask: net.CIDRMask(96, 128)}, // IPv4-compatible addresses - {IP: net.ParseIP("100::/64"), Mask: net.CIDRMask(64, 128)}, // Remotely triggered black hole addresses - {IP: net.ParseIP("2001:10::/28"), Mask: net.CIDRMask(28, 128)}, // Overlay routable cryptographic hash identifiers (ORCHID) + {IP: net.ParseIP("::/96"), Mask: net.CIDRMask(96, 128)}, // IPv4-compatible addresses + {IP: net.ParseIP("100::/64"), Mask: net.CIDRMask(64, 128)}, // Remotely triggered black hole addresses + {IP: net.ParseIP("2001:10::/28"), Mask: net.CIDRMask(28, 128)}, // Overlay routable cryptographic hash identifiers (ORCHID) {IP: net.ParseIP("2001:db8::/32"), Mask: net.CIDRMask(32, 128)}, // Documentation prefix - {IP: net.ParseIP("fc00::/7"), Mask: net.CIDRMask(7, 128)}, // Unique local addresses (ULA) - {IP: net.ParseIP("fe80::/10"), Mask: net.CIDRMask(10, 128)}, // Link-local unicast - {IP: net.ParseIP("fec0::/10"), Mask: net.CIDRMask(10, 128)}, // Site-local unicast (deprecated) - {IP: net.ParseIP("ff00::/8"), Mask: net.CIDRMask(8, 128)}, // Multicast + {IP: net.ParseIP("fc00::/7"), Mask: net.CIDRMask(7, 128)}, // Unique local addresses (ULA) + {IP: net.ParseIP("fe80::/10"), Mask: net.CIDRMask(10, 128)}, // Link-local unicast + {IP: net.ParseIP("fec0::/10"), Mask: net.CIDRMask(10, 128)}, // Site-local unicast (deprecated) + {IP: net.ParseIP("ff00::/8"), Mask: net.CIDRMask(8, 128)}, // Multicast // Additional Bogon Ranges - {IP: net.ParseIP("2002::/24"), Mask: net.CIDRMask(24, 128)}, // 6to4 bogon (0.0.0.0/8) - {IP: net.ParseIP("2002:a00::/24"), Mask: net.CIDRMask(24, 128)}, // 6to4 bogon (10.0.0.0/8) - {IP: net.ParseIP("2002:7f00::/24"), Mask: net.CIDRMask(24, 128)}, // 6to4 bogon (127.0.0.0/8) - {IP: net.ParseIP("2002:a9fe::/32"), Mask: net.CIDRMask(32, 128)}, // 6to4 bogon (169.254.0.0/16) - {IP: net.ParseIP("2002:ac10::/28"), Mask: net.CIDRMask(28, 128)}, // 6to4 bogon (172.16.0.0/12) - {IP: net.ParseIP("2002:c000::/40"), Mask: net.CIDRMask(40, 128)}, // 6to4 bogon (192.0.0.0/24) - {IP: net.ParseIP("2002:c000:200::/40"), Mask: net.CIDRMask(40, 128)}, // 6to4 bogon (192.0.2.0/24) - {IP: net.ParseIP("2002:c0a8::/32"), Mask: net.CIDRMask(32, 128)}, // 6to4 bogon (192.168.0.0/16) - {IP: net.ParseIP("2002:c612::/31"), Mask: net.CIDRMask(31, 128)}, // 6to4 bogon (198.18.0.0/15) - {IP: net.ParseIP("2002:c633:6400::/40"), Mask: net.CIDRMask(40, 128)}, // 6to4 bogon (198.51.100.0/24) - {IP: net.ParseIP("2002:cb00:7100::/40"), Mask: net.CIDRMask(40, 128)}, // 6to4 bogon (203.0.113.0/24) - {IP: net.ParseIP("2002:e000::/20"), Mask: net.CIDRMask(20, 128)}, // 6to4 bogon (224.0.0.0/4) - {IP: net.ParseIP("2002:f000::/20"), Mask: net.CIDRMask(20, 128)}, // 6to4 bogon (240.0.0.0/4) - {IP: net.ParseIP("2002:ffff:ffff::/48"), Mask: net.CIDRMask(48, 128)}, // 6to4 bogon (255.255.255.255/32) - {IP: net.ParseIP("2001::/40"), Mask: net.CIDRMask(40, 128)}, // Teredo bogon (0.0.0.0/8) - {IP: net.ParseIP("2001:0:a00::/40"), Mask: net.CIDRMask(40, 128)}, // Teredo bogon (10.0.0.0/8) - {IP: net.ParseIP("2001:0:7f00::/40"), Mask: net.CIDRMask(40, 128)}, // Teredo bogon (127.0.0.0/8) - {IP: net.ParseIP("2001:0:a9fe::/48"), Mask: net.CIDRMask(48, 128)}, // Teredo bogon (169.254.0.0/16) - {IP: net.ParseIP("2001:0:ac10::/44"), Mask: net.CIDRMask(44, 128)}, // Teredo bogon (172.16.0.0/12) - {IP: net.ParseIP("2001:0:c000::/56"), Mask: net.CIDRMask(56, 128)}, // Teredo bogon (192.0.0.0/24) - {IP: net.ParseIP("2001:0:c000:200::/56"), Mask: net.CIDRMask(56, 128)}, // Teredo bogon (192.0.2.0/24) - {IP: net.ParseIP("2001:0:c0a8::/48"), Mask: net.CIDRMask(48, 128)}, // Teredo bogon (192.168.0.0/16) - {IP: net.ParseIP("2001:0:c612::/47"), Mask: net.CIDRMask(47, 128)}, // Teredo bogon (198.18.0.0/15) + {IP: net.ParseIP("2002::/24"), Mask: net.CIDRMask(24, 128)}, // 6to4 bogon (0.0.0.0/8) + {IP: net.ParseIP("2002:a00::/24"), Mask: net.CIDRMask(24, 128)}, // 6to4 bogon (10.0.0.0/8) + {IP: net.ParseIP("2002:7f00::/24"), Mask: net.CIDRMask(24, 128)}, // 6to4 bogon (127.0.0.0/8) + {IP: net.ParseIP("2002:a9fe::/32"), Mask: net.CIDRMask(32, 128)}, // 6to4 bogon (169.254.0.0/16) + {IP: net.ParseIP("2002:ac10::/28"), Mask: net.CIDRMask(28, 128)}, // 6to4 bogon (172.16.0.0/12) + {IP: net.ParseIP("2002:c000::/40"), Mask: net.CIDRMask(40, 128)}, // 6to4 bogon (192.0.0.0/24) + {IP: net.ParseIP("2002:c000:200::/40"), Mask: net.CIDRMask(40, 128)}, // 6to4 bogon (192.0.2.0/24) + {IP: net.ParseIP("2002:c0a8::/32"), Mask: net.CIDRMask(32, 128)}, // 6to4 bogon (192.168.0.0/16) + {IP: net.ParseIP("2002:c612::/31"), Mask: net.CIDRMask(31, 128)}, // 6to4 bogon (198.18.0.0/15) + {IP: net.ParseIP("2002:c633:6400::/40"), Mask: net.CIDRMask(40, 128)}, // 6to4 bogon (198.51.100.0/24) + {IP: net.ParseIP("2002:cb00:7100::/40"), Mask: net.CIDRMask(40, 128)}, // 6to4 bogon (203.0.113.0/24) + {IP: net.ParseIP("2002:e000::/20"), Mask: net.CIDRMask(20, 128)}, // 6to4 bogon (224.0.0.0/4) + {IP: net.ParseIP("2002:f000::/20"), Mask: net.CIDRMask(20, 128)}, // 6to4 bogon (240.0.0.0/4) + {IP: net.ParseIP("2002:ffff:ffff::/48"), Mask: net.CIDRMask(48, 128)}, // 6to4 bogon (255.255.255.255/32) + {IP: net.ParseIP("2001::/40"), Mask: net.CIDRMask(40, 128)}, // Teredo bogon (0.0.0.0/8) + {IP: net.ParseIP("2001:0:a00::/40"), Mask: net.CIDRMask(40, 128)}, // Teredo bogon (10.0.0.0/8) + {IP: net.ParseIP("2001:0:7f00::/40"), Mask: net.CIDRMask(40, 128)}, // Teredo bogon (127.0.0.0/8) + {IP: net.ParseIP("2001:0:a9fe::/48"), Mask: net.CIDRMask(48, 128)}, // Teredo bogon (169.254.0.0/16) + {IP: net.ParseIP("2001:0:ac10::/44"), Mask: net.CIDRMask(44, 128)}, // Teredo bogon (172.16.0.0/12) + {IP: net.ParseIP("2001:0:c000::/56"), Mask: net.CIDRMask(56, 128)}, // Teredo bogon (192.0.0.0/24) + {IP: net.ParseIP("2001:0:c000:200::/56"), Mask: net.CIDRMask(56, 128)}, // Teredo bogon (192.0.2.0/24) + {IP: net.ParseIP("2001:0:c0a8::/48"), Mask: net.CIDRMask(48, 128)}, // Teredo bogon (192.168.0.0/16) + {IP: net.ParseIP("2001:0:c612::/47"), Mask: net.CIDRMask(47, 128)}, // Teredo bogon (198.18.0.0/15) {IP: net.ParseIP("2001:0:c633:6400::/56"), Mask: net.CIDRMask(56, 128)}, // Teredo bogon (198.51.100.0/24) {IP: net.ParseIP("2001:0:cb00:7100::/56"), Mask: net.CIDRMask(56, 128)}, // Teredo bogon (203.0.113.0/24) - {IP: net.ParseIP("2001:0:e000::/36"), Mask: net.CIDRMask(36, 128)}, // Teredo bogon (224.0.0.0/4) - {IP: net.ParseIP("2001:0:f000::/36"), Mask: net.CIDRMask(36, 128)}, // Teredo bogon (240.0.0.0/4) + {IP: net.ParseIP("2001:0:e000::/36"), Mask: net.CIDRMask(36, 128)}, // Teredo bogon (224.0.0.0/4) + {IP: net.ParseIP("2001:0:f000::/36"), Mask: net.CIDRMask(36, 128)}, // Teredo bogon (240.0.0.0/4) {IP: net.ParseIP("2001:0:ffff:ffff::/64"), Mask: net.CIDRMask(64, 128)}, // Teredo bogon (255.255.255.255/32) } @@ -84,13 +83,17 @@ func isBogon(ip net.IP) bool { // Get the real IP address from the request headers func getRealIP(r *http.Request) string { - if realIP := r.Header.Get("CF-Connecting-IP"); realIP != "" { - return realIP - } else if realIP := r.Header.Get("X-Forwarded-For"); realIP != "" { - return strings.Split(realIP, ",")[0] - } else { - return extractIP(r.RemoteAddr) + for _, header := range []string{"CF-Connecting-IP", "X-Real-IP", "X-Forwarded-For"} { + if ip := r.Header.Get(header); ip != "" { + return strings.TrimSpace(strings.Split(ip, ",")[0]) + } } + + host, _, err := net.SplitHostPort(r.RemoteAddr) + if err != nil { + return r.RemoteAddr + } + return host } var ipCache = sync.Map{} @@ -188,14 +191,3 @@ func toPtr(s string) *string { } return &s } - -// Validate JSONP callback name -var callbackJSONP = regexp.MustCompile(`^[a-zA-Z_\$][a-zA-Z0-9_\$]*$`) - -// Extract the IP address from a string, removing unwanted characters -func extractIP(ip string) string { - ip = strings.ReplaceAll(ip, "[", "") - ip = strings.ReplaceAll(ip, "]", "") - ss := strings.Split(ip, ":") - return strings.Join(ss[:len(ss)-1], ":") -} diff --git a/readme.md b/readme.md index 1617ecf..49b18ec 100644 --- a/readme.md +++ b/readme.md @@ -8,7 +8,6 @@ - **ASN Information**: Includes autonomous system number and organization. - **Hostname Lookup**: Retrieves the hostname associated with the IP address. - **Automatic Database Updates**: Keeps GeoIP databases up-to-date daily. -- **JSONP Support**: Allows JSONP responses for cross-domain requests. ## Example Endpoints @@ -39,33 +38,6 @@ $ curl https://ip.albert.lol/9.9.9.9/city } ``` -### Use JSONP callback function - -```sh -$ curl https://test.albert.lol/9.9.9.9?callback=Quad9 -/**/ typeof Quad9 === 'function' && Quad9({ - "ip": "9.9.9.9", - "hostname": "dns9.quad9.net", - "asn": "19281", - "org": "QUAD9-AS-1", - "city": "Berkeley", - "region": "California", - "country": "United States", - "continent": "North America", - "timezone": "America/Los_Angeles", - "loc": "37.8767,-122.2676" -}); -``` - -```html - - -``` - ## Running Locally ### With Docker diff --git a/server.go b/server.go index 581fa60..ec070ff 100644 --- a/server.go +++ b/server.go @@ -3,7 +3,6 @@ package main import ( "compress/gzip" "encoding/json" - "fmt" "log" "net" "net/http" @@ -11,6 +10,7 @@ import ( ) var invalidIPBytes = []byte("Please provide a valid IP address.") +var invalidFieldBytes = []byte("Please provide a valid field.") type dataStruct struct { IP *string `json:"ip"` @@ -46,7 +46,7 @@ func (w gzipResponseWriter) Write(b []byte) (int, error) { } func handler(w http.ResponseWriter, r *http.Request) { - requestedThings := strings.Split(r.URL.Path, "/") + requestedThings := strings.Split(strings.Trim(r.URL.Path, "/"), "/") // Enable gzip compression if requested if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") { @@ -56,26 +56,46 @@ func handler(w http.ResponseWriter, r *http.Request) { w = &gzipResponseWriter{Writer: gz, ResponseWriter: w} } - // Extract IP and field var IPAddress, field string - if len(requestedThings) > 1 && net.ParseIP(requestedThings[1]) != nil { - IPAddress = requestedThings[1] - if len(requestedThings) > 2 { - field = requestedThings[2] + + // Parse the request URL + switch len(requestedThings) { + case 0: + IPAddress = getRealIP(r) // Default to visitor's IP + case 1: + if requestedThings[0] == "" { + IPAddress = getRealIP(r) // Handle root page case + } else if _, ok := fieldMap[requestedThings[0]]; ok { + IPAddress = getRealIP(r) + field = requestedThings[0] + } else if net.ParseIP(requestedThings[0]) != nil { + IPAddress = requestedThings[0] // Valid IP provided + } else { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode(map[string]string{"error": string(invalidIPBytes)}) + return } - } else if len(requestedThings) > 1 { - IPAddress = requestedThings[1] // This might be an invalid IP + case 2: + IPAddress = requestedThings[0] + if _, ok := fieldMap[requestedThings[1]]; ok { + field = requestedThings[1] + } else { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode(map[string]string{"error": string(invalidFieldBytes)}) + return + } + default: + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode(map[string]string{"error": string(invalidIPBytes)}) + return } - // Check if the IP is the client's IP - if IPAddress == "" { - IPAddress = getRealIP(r) - } - - // Validate the IP address + // Validate the resolved IP ip := net.ParseIP(IPAddress) if ip == nil { - // Send 400 Bad Request with invalid IP message http.Error(w, string(invalidIPBytes), http.StatusBadRequest) return } @@ -83,18 +103,13 @@ func handler(w http.ResponseWriter, r *http.Request) { // Check if the IP is a bogon IP if isBogon(ip) { w.Header().Set("Content-Type", "application/json; charset=utf-8") - bogonData := bogonDataStruct{ - IP: ip.String(), - Bogon: true, - } - json.NewEncoder(w).Encode(bogonData) + json.NewEncoder(w).Encode(bogonDataStruct{IP: ip.String(), Bogon: true}) return } // Look up IP data data := lookupIPData(ip) if data == nil { - // Send 400 Bad Request with invalid IP message http.Error(w, string(invalidIPBytes), http.StatusBadRequest) return } @@ -102,34 +117,14 @@ func handler(w http.ResponseWriter, r *http.Request) { // Handle specific field requests if field != "" { value := getField(data, field) - if value != nil { - w.Header().Set("Content-Type", "application/json; charset=utf-8") - json.NewEncoder(w).Encode(map[string]*string{field: value}) - return - } else { - // Handle invalid field request - w.Header().Set("Content-Type", "application/json; charset=utf-8") - json.NewEncoder(w).Encode(map[string]*string{field: nil}) - return - } + w.Header().Set("Content-Type", "application/json; charset=utf-8") + json.NewEncoder(w).Encode(map[string]*string{field: value}) + return } - // If no specific field is requested, return the whole data + // Default case: return full IP data w.Header().Set("Content-Type", "application/json; charset=utf-8") - callback := r.URL.Query().Get("callback") - enableJSONP := callback != "" && len(callback) < 2000 && callbackJSONP.MatchString(callback) - if enableJSONP { - jsonData, _ := json.MarshalIndent(data, "", " ") - response := fmt.Sprintf("/**/ typeof %s === 'function' && %s(%s);", callback, callback, jsonData) - w.Write([]byte(response)) - } else { - enc := json.NewEncoder(w) - enc.SetIndent("", " ") - if r.URL.Query().Get("compact") == "true" { - enc.SetIndent("", "") - } - enc.Encode(data) - } + json.NewEncoder(w).Encode(data) } var fieldMap = map[string]func(*dataStruct) *string{