Browse Source

Entirely refactored application, embedded html into binary, removed geoip features, overhauled and simplified html assets

main
gryffyn 4 weeks ago
parent
commit
f2014447fe
Signed by: gryffyn GPG Key ID: 6948DD6514D02BEF
  1. 2
      .dockerignore
  2. 28
      .github/workflows/ci.yml
  3. 1
      .gitignore
  4. 18
      Dockerfile
  5. 3
      LICENSE
  6. 54
      Makefile
  7. 67
      README.md
  8. 32
      cmd/ipinfo/main.go
  9. 9
      go.mod
  10. 20
      go.sum
  11. 332
      html/index.html
  12. 13
      html/leafcloud-logo.svg
  13. 88
      html/script.html
  14. 184
      html/styles.html
  15. 154
      http/html/index.html
  16. 141
      http/html/styles.html
  17. 141
      http/http.go
  18. 21
      http/http_test.go
  19. 157
      iputil/geo/geo.go

2
.dockerignore

@ -1,2 +0,0 @@
Dockerfile
Dockerfile.geoip

28
.github/workflows/ci.yml

@ -1,28 +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.15
- name: build and test
run: make
- name: enable experimental docker features
run: |
echo '{"experimental":true}' | sudo tee /etc/docker/daemon.json
sudo service docker restart
- name: publish multi-arch docker image
run: make docker-pushx

1
.gitignore

@ -3,3 +3,4 @@
/vendor/
.vscode/
/bin/
/.idea/

18
Dockerfile

@ -1,18 +0,0 @@
# Build
FROM golang:1.15-buster AS build
WORKDIR /go/src/github.com/mpolden/echoip
COPY . .
# Must build without cgo because libc is unavailable in runtime image
ENV GO111MODULE=on CGO_ENABLED=0
RUN make
# Run
FROM scratch
EXPOSE 8080
COPY --from=build /go/bin/echoip /opt/echoip/
COPY html /opt/echoip/html
WORKDIR /opt/echoip
ENTRYPOINT ["/opt/echoip/echoip"]

3
LICENSE

@ -1,4 +1,5 @@
Copyright (c) 2012-2020, Martin Polden
Copyright (c) 2012-2020, Martin Polden,
2021-2021, gryffyn
All rights reserved.
Redistribution and use in source and binary forms, with or without

54
Makefile

@ -1,13 +1,3 @@
DOCKER ?= docker
DOCKER_IMAGE ?= mpolden/echoip
OS := $(shell uname)
ifeq ($(OS),Linux)
TAR_OPTS := --wildcards
endif
XGOARCH := amd64
XGOOS := linux
XBIN := $(XGOOS)_$(XGOARCH)/echoip
all: lint test install
test:
@ -24,48 +14,6 @@ lint: check-fmt vet
install:
go install ./...
databases := GeoLite2-City GeoLite2-Country GeoLite2-ASN
$(databases):
ifndef GEOIP_LICENSE_KEY
$(error GEOIP_LICENSE_KEY must be set. Please see https://blog.maxmind.com/2019/12/18/significant-changes-to-accessing-and-using-geolite2-databases/)
endif
mkdir -p data
@curl -fsSL -m 30 "https://download.maxmind.com/app/geoip_download?edition_id=$@&license_key=$(GEOIP_LICENSE_KEY)&suffix=tar.gz" | tar $(TAR_OPTS) --strip-components=1 -C $(CURDIR)/data -xzf - '*.mmdb'
test ! -f data/GeoLite2-City.mmdb || mv data/GeoLite2-City.mmdb data/city.mmdb
test ! -f data/GeoLite2-Country.mmdb || mv data/GeoLite2-Country.mmdb data/country.mmdb
test ! -f data/GeoLite2-ASN.mmdb || mv data/GeoLite2-ASN.mmdb data/asn.mmdb
geoip-download: $(databases)
# Create an environment to build multiarch containers (https://github.com/docker/buildx/)
docker-multiarch-builder:
DOCKER_BUILDKIT=1 $(DOCKER) build -o . git://github.com/docker/buildx
mkdir -p ~/.docker/cli-plugins
mv buildx ~/.docker/cli-plugins/docker-buildx
$(DOCKER) buildx create --name multiarch-builder --node multiarch-builder --driver docker-container --use
$(DOCKER) run --rm --privileged multiarch/qemu-user-static --reset -p yes
docker-build:
$(DOCKER) build -t $(DOCKER_IMAGE) .
docker-login:
@echo "$(DOCKER_PASSWORD)" | $(DOCKER) login -u "$(DOCKER_USERNAME)" --password-stdin
docker-test:
$(eval CONTAINER=$(shell $(DOCKER) run --rm --detach --publish-all $(DOCKER_IMAGE)))
$(eval DOCKER_PORT=$(shell $(DOCKER) port $(CONTAINER) | cut -d ":" -f 2))
curl -fsS -m 5 localhost:$(DOCKER_PORT) > /dev/null; $(DOCKER) stop $(CONTAINER)
docker-push: docker-test docker-login
$(DOCKER) push $(DOCKER_IMAGE)
docker-pushx: docker-multiarch-builder docker-test docker-login
$(DOCKER) buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t $(DOCKER_IMAGE) --push .
xinstall:
env GOOS=$(XGOOS) GOARCH=$(XGOARCH) go install ./...
publish:
ifndef DEST_PATH
$(error DEST_PATH must be set when publishing)
@ -74,4 +22,4 @@ endif
@sha256sum $(GOPATH)/bin/$(XBIN)
run:
go run cmd/echoip/main.go -a data/asn.mmdb -c data/city.mmdb -f data/country.mmdb -H x-forwarded-for -r -s -p
go run cmd/ipinfo/main.go -H x-forwarded-for -r -s -p

67
README.md

@ -1,66 +1,44 @@
# echoip
# ipinfo
![Build Status](https://github.com/mpolden/echoip/workflows/ci/badge.svg)
Simplified fork of [echoip](https://github.com/mpolden/echoip).
A simple service for looking up your IP address. This is the code that powers
https://ifconfig.co.
A simple service for looking up your IP address.
## Usage
Just the business, please:
```
$ curl ifconfig.co
$ curl example.tld
127.0.0.1
$ http ifconfig.co
$ http example.tld
127.0.0.1
$ wget -qO- ifconfig.co
$ wget -qO- example.tld
127.0.0.1
$ fetch -qo- https://ifconfig.co
$ fetch -qo- https://example.tld
127.0.0.1
$ bat -print=b ifconfig.co/ip
$ bat -print=b example.tld/ip
127.0.0.1
```
Country and city lookup:
```
$ curl ifconfig.co/country
Elbonia
$ curl ifconfig.co/country-iso
EB
$ curl ifconfig.co/city
Bornyasherk
$ curl ifconfig.co/asn
AS59795
```
As JSON:
```
$ curl -H 'Accept: application/json' ifconfig.co # or curl ifconfig.co/json
$ curl -H 'Accept: application/json' ifconfig.co # or curl example.tld/json
{
"city": "Bornyasherk",
"country": "Elbonia",
"country_iso": "EB",
"ip": "127.0.0.1",
"ip_decimal": 2130706433,
"asn": "AS59795",
"asn_org": "Hosting4Real"
}
```
Port testing:
```
$ curl ifconfig.co/port/80
$ curl example.tld/port/80
{
"ip": "127.0.0.1",
"port": 80,
@ -73,40 +51,25 @@ between IPv4 and IPv6 lookup.
## Features
* Easy to remember domain name
* Fast
* Supports IPv6
* Supports HTTPS
* Supports common command-line clients (e.g. `curl`, `httpie`, `ht`, `wget` and `fetch`)
* JSON output
* ASN, country and city lookup using the MaxMind GeoIP database
* Port testing
* All endpoints (except `/port`) can return information about a custom IP address specified via `?ip=` query parameter
* Open source under the [BSD 3-Clause license](https://opensource.org/licenses/BSD-3-Clause)
## Why?
* To scratch an itch
* An excuse to use Go for something
* Faster than ifconfig.me and has IPv6 support
## Building
Compiling requires the [Golang compiler](https://golang.org/) to be installed.
This package can be installed with `go get`:
`go get github.com/mpolden/echoip/...`
`go get github.com/gryffyn/ipinfo/...`
For more information on building a Go project, see the [official Go
documentation](https://golang.org/doc/code.html).
## Docker image
A Docker image is available on [Docker
Hub](https://hub.docker.com/r/mpolden/echoip), which can be downloaded with:
`docker pull mpolden/echoip`
### Usage
```
@ -116,16 +79,8 @@ Usage of echoip:
Size of response cache. Set to 0 to disable
-H value
Header to trust for remote IP, if present (e.g. X-Real-IP)
-a string
Path to GeoIP ASN database
-c string
Path to GeoIP city database
-f string
Path to GeoIP country database
-l string
Listening address (default ":8080")
-p Enable port lookup
-r Perform reverse hostname lookups
-t string
Path to template directory (default "html")
```

32
cmd/echoip/main.go → cmd/ipinfo/main.go

@ -4,11 +4,8 @@ import (
"flag"
"log"
"os"
"github.com/mpolden/echoip/http"
"github.com/mpolden/echoip/iputil"
"github.com/mpolden/echoip/iputil/geo"
"github.com/gryffyn/ipinfo/http"
"github.com/gryffyn/ipinfo/iputil"
)
type multiValueFlag []string
@ -30,41 +27,28 @@ func (f *multiValueFlag) Set(v string) error {
}
func init() {
log.SetPrefix("echoip: ")
log.SetPrefix("ipinfo: ")
log.SetFlags(log.Lshortfile)
}
func main() {
countryFile := flag.String("f", "", "Path to GeoIP country database")
cityFile := flag.String("c", "", "Path to GeoIP city database")
asnFile := flag.String("a", "", "Path to GeoIP ASN database")
listen := flag.String("l", ":8080", "Listening address")
reverseLookup := flag.Bool("r", false, "Perform reverse hostname lookups")
portLookup := flag.Bool("p", false, "Enable port lookup")
template := flag.String("t", "html", "Path to template dir")
cacheSize := flag.Int("C", 0, "Size of response cache. Set to 0 to disable")
profile := flag.Bool("P", false, "Enables profiling handlers")
sponsor := flag.Bool("s", false, "Show sponsor logo")
var headers multiValueFlag
flag.Var(&headers, "H", "Header to trust for remote IP, if present (e.g. X-Real-IP)")
flag.Var(&headers, "H", "Header to trust for remote IP, if present (e.g. X-Forwarded-For)")
flag.Parse()
if len(flag.Args()) != 0 {
flag.Usage()
return
}
r, err := geo.Open(*countryFile, *cityFile, *asnFile)
if err != nil {
log.Fatal(err)
}
cache := http.NewCache(*cacheSize)
server := http.New(r, cache, *profile)
server := http.New(cache, *profile)
server.IPHeaders = headers
if _, err := os.Stat(*template); err == nil {
server.Template = *template
} else {
log.Printf("Not configuring default handler: Template not found: %s", *template)
}
server.Template = "html"
if *reverseLookup {
log.Println("Enabling reverse lookup")
server.LookupAddr = iputil.LookupAddr
@ -73,10 +57,6 @@ func main() {
log.Println("Enabling port lookup")
server.LookupPort = iputil.LookupPort
}
if *sponsor {
log.Println("Enabling sponsor logo")
server.Sponsor = *sponsor
}
if len(headers) > 0 {
log.Printf("Trusting remote IP from header(s): %s", headers.String())
}

9
go.mod

@ -1,8 +1,3 @@
module github.com/mpolden/echoip
module github.com/gryffyn/ipinfo
go 1.13
require (
github.com/oschwald/geoip2-golang v1.5.0
golang.org/x/sys v0.0.0-20210223212115-eede4237b368 // indirect
)
go 1.16

20
go.sum

@ -1,20 +0,0 @@
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/geoip2-golang v1.5.0 h1:igg2yQIrrcRccB1ytFXqBfOHCjXWIoMv85lVJ1ONZzw=
github.com/oschwald/geoip2-golang v1.5.0/go.mod h1:xdvYt5xQzB8ORWFqPnqMwZpCpgNagttWdoZLlJQzg7s=
github.com/oschwald/maxminddb-golang v1.8.0 h1:Uh/DSnGoxsyp/KYbY1AuP0tYEwfs0sCph9p/UMXK/Hk=
github.com/oschwald/maxminddb-golang v1.8.0/go.mod h1:RXZtst0N6+FY/3qCNmZMBApR19cdQj43/NM9VkrNAis=
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.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=
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.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

332
html/index.html

@ -1,332 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>What is my IP address? &mdash; {{ .Host }}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="description"
content="{{ .Host }} • What is my IP address? &mdash; The best tool to find your own IP address, and information about it."
/>
<link rel="canonical" href="https://ifconfig.co/" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,400;0,700;1,400&display=swap"
rel="stylesheet"
/>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/pure/1.0.0/pure-min.css"
integrity="sha384-nn4HPE8lTHyVtfCBi5yW9d20FjT8BJwUXyWZT9InLYax14RDjBj46LmSztkmNP9w"
crossorigin="anonymous"
/>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/pure/1.0.0/grids-responsive-min.css"
integrity="sha384-b92sF+wDNTHrfEtRaYo+EpcA8FUyHOSXrdxKc9XB9kaaX1rSQAgMevW6cYeE5Bdv"
crossorigin="anonymous"
/>
{{ template "script.html" . }} {{ template "styles.html" . }}
</head>
<body>
<div class="content">
<div class="pure-g gutters center">
<div class="pure-u-1 pure-u-md-2-3">
<div class="l-box">
<h1>{{ .Host }} — What is my IP address?</h1>
<p><code class="ip">{{ .IP }}</code></p>
<p>
The best tool to find your own IP address, and information about
it.
</p>
</div>
</div>
<div class="pure-u-1 pure-u-md-1-3">
{{ if .Sponsor }}
<div class="l-box leafcloud-placement">
<div class="pure-g">
<div class="pure-u pure-u-md-1">
<div class="leafcloud-logo">
<a href="https://www.leaf.cloud?utm_source=ifconfig">
{{ template "leafcloud-logo.svg" "." }}
</a>
</div>
</div>
<div class="pure-u pure-u-md-1">
<p>
This site is graciously hosted by<br />
<a href="https://www.leaf.cloud?utm_source=ifconfig">
Leafcloud – The Truly Sustainable Cloud
</a>
</p>
</div>
</div>
</div>
{{ end }}
</div>
</div>
<div class="pure-g gutters center">
<!-- COLUMN 1 -->
<div class="pure-u-1 pure-u-md-1-2 col">
<div class="l-box">
<h2>What do we know about this IP address?</h2>
<table class="info-table">
<tr>
<th scope="row">IP&nbsp;address</th>
<td>{{ .IP }}</td>
</tr>
<tr>
<th scope="row">IP&nbsp;address (decimal)</th>
<td>{{ .IPDecimal }}</td>
</tr>
{{ if .Country }}
<tr>
<th scope="row">Country</th>
<td>{{ .Country }}</td>
</tr>
{{ end }} {{ if .CountryISO }}
<tr>
<th scope="row">Country (ISO code)</th>
<td>{{ .CountryISO }}</td>
</tr>
{{ end }} {{ if .CountryEU }}
<tr>
<th scope="row">In EU?</th>
<td>{{ .CountryEU }}</td>
</tr>
{{ end }} {{ if .RegionName }}
<tr>
<th scope="row">Region</th>
<td>{{ .RegionName }}</td>
</tr>
{{ end }} {{ if .RegionCode }}
<tr>
<th scope="row">Region&nbsp;code</th>
<td>{{ .RegionCode }}</td>
</tr>
{{ end }} {{ if .MetroCode }}
<tr>
<th scope="row">Metro code</th>
<td>{{ .MetroCode }}</td>
</tr>
{{ end }} {{ if .PostalCode }}
<tr>
<th scope="row">Postal&nbsp;code</th>
<td>{{ .PostalCode }}</td>
</tr>
{{ end }} {{ if .City }}
<tr>
<th scope="row">City</th>
<td>{{ .City }}</td>
</tr>
{{ end }} {{ if .Latitude }}
<tr>
<th scope="row">Latitude</th>
<td>{{ .Latitude }}</td>
</tr>
{{ end }} {{ if .Longitude }}
<tr>
<th scope="row">Longitude</th>
<td>{{ .Longitude }}</td>
</tr>
{{ end }} {{ if .Timezone }}
<tr>
<th scope="row">Timezone</th>
<td>{{ .Timezone }}</td>
</tr>
{{ end }} {{ if .ASN }}
<tr>
<th scope="row">ASN</th>
<td>{{ .ASN }}</td>
</tr>
{{ end }} {{ if .ASNOrg }}
<tr>
<th scope="row">ASN (organization)</th>
<td>{{ .ASNOrg }}</td>
</tr>
{{ end }} {{ if .Hostname }}
<tr>
<th scope="row">Hostname</th>
<td>{{ .Hostname }}</td>
</tr>
{{ end }} {{ if .UserAgent }} {{ if .UserAgent.Comment }}
<tr>
<th scope="row">User&nbsp;agent</th>
<td>{{ .UserAgent.Product }}/{{ .UserAgent.Version }}</td>
</tr>
{{ end }} {{ if .UserAgent.Comment }}
<tr>
<th scope="row">User&nbsp;agent: Comment</th>
<td>{{ .UserAgent.Comment }}</td>
</tr>
{{ end }} {{ if .UserAgent.RawValue }}
<tr>
<th scope="row">User&nbsp;agent: Raw</th>
<td>{{ .UserAgent.RawValue }}</td>
</tr>
{{ end }} {{ end }}
</table>
{{ if .Country }}
<p>
This information is provided from the GeoLite2 database created by
MaxMind, available from
<a href="https://www.maxmind.com">www.maxmind.com</a>
</p>
{{ end }} {{ if .Latitude }}
<div class="pure-u-1 pure-u-md-1-1">
<h2>Map</h2>
<iframe
width="100%"
height="350"
frameborder="0"
scrolling="no"
marginheight="0"
marginwidth="0"
src="https://www.openstreetmap.org/export/embed.html?bbox={{ .BoxLonLeft }}%2C{{ .BoxLatBottom }}%2C{{ .BoxLonRight }}%2C{{ .BoxLatTop }}&amp;layer=mapnik&amp;marker={{ .Latitude }}%2C{{ .Longitude }}"
></iframe>
</div>
{{ end }}
</div>
</div>
<!-- COLUMN 2 -->
<div class="pure-u-1 pure-u-md-1-2">
<div class="l-box">
<h2>How do I get this programmatically?</h2>
<p>
With the widget below you can build your query, and see what the
result will look like.
</p>
<div class="pure-form">
<!-- COMMAND WIDGET -->
<div class="input-buttons">
<button
name="ip"
class="pure-button widget-select"
onclick="changeInput(this.name, this)"
>
ip
</button>
<button
name="country"
class="pure-button widget-select"
onclick="changeInput(this.name, this)"
>
country
</button>
<button
name="country-iso"
class="pure-button widget-select"
onclick="changeInput(this.name, this)"
>
country-iso
</button>
<button
name="city"
class="pure-button widget-select"
onclick="changeInput(this.name, this)"
>
city
</button>
<button
name="asn"
class="pure-button widget-select"
onclick="changeInput(this.name, this)"
>
asn
</button>
<button
name="json"
class="pure-button widget-select"
onclick="changeInput(this.name, this)"
>
json
</button>
<button
name="port"
class="pure-button widget-select"
onclick="changeInput(this.name, this)"
>
port
</button>
<input
id="portInput"
type="number"
min="1"
max="40000"
value="8080"
class="narrow-input pure-input"
placeholder="8080"
onchange="updatePort(this.value)"
/>
</div>
<div class="widgetbox input">
<code id="command"></code>
</div>
<div id="output" class="widgetbox output"></div>
<form class="pure-form input-buttons">
<fieldset>
<label for="ipInput">
Check another IP (optional)
<input
id="ipInput"
class=""
type="text"
placeholder="IP to query"
onkeyup="updateIP(this.value)"
/>
</label>
<button
type="button"
class="pure-button"
onclick="navigate()"
>
Open
</button>
</fieldset>
</form>
</div>
<!-- FAQ -->
<div class="FAQ">
<h2>FAQ</h2>
<h3>How do I force IPv4 or IPv6 lookup?</h3>
<p>
As of 2018-07-25 it's no longer possible to force protocol using
the
<i>v4</i> and <i>v6</i> subdomains. IPv4 or IPv6 still can be
forced by passing the appropiate flag to your client, e.g
<code>curl -4</code> or <code>curl -6</code>.
</p>
<h3>Can I force getting JSON?</h3>
<p>
Setting the <code>Accept: application/json</code> header works
as expected.
</p>
<h3>Is automated use of this service permitted?</h3>
<p>
Yes, as long as the rate limit is respected. The rate limit is
in place to ensure a fair service for all.
</p>
<p>
<em>Please limit automated requests to 1 request per minute</em
>. No guarantee is made for requests that exceed this limit.
They may be rate-limited, with a 429 status code, or dropped
entirely.
</p>
<h3>Can I run my own service?</h3>
<p>
Yes, the source code and documentation is available on
<a href="https://github.com/mpolden/echoip">GitHub</a>.
</p>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

13
html/leafcloud-logo.svg

@ -1,13 +0,0 @@
<svg width="96" height="68" viewBox="0 0 96 68" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 20.0787H19.7445C27.6337 20.0787 34.0296 26.4439 34.0296 34.2951V46.1525H14.2851C6.39594 46.1541 0 39.789 0 31.9361V20.0787Z" fill="#32F295" />
<path d="M95.9969 0H61.0472C47.0825 0 35.7608 11.2672 35.7608 25.1647V46.1539H70.7089C84.6736 46.1539 95.9953 34.8867 95.9953 20.9892V0H95.9969Z" fill="#3FA9F5" />
<path d="M1 49.9344H2.67242V67.7596H1V49.9344Z" fill="white" class="letters" />
<path d="M4.36731 61.819C4.36731 58.3727 6.92285 55.6395 10.5524 55.6395C13.6578 55.6395 16.1406 57.8496 16.1648 61.6532V62.1055H6.08664C6.1109 64.5537 7.9014 66.4547 10.5055 66.4547C12.4642 66.4547 13.6336 65.6226 14.4455 64.2205L15.8074 65.0994C14.7561 66.8813 12.9882 67.9984 10.5055 67.9984C6.75464 67.9984 4.36731 65.2411 4.36731 61.819ZM6.23059 60.631H14.4229C14.1366 58.4918 12.4884 57.1848 10.4828 57.1848C8.49823 57.1848 6.63656 58.4677 6.23059 60.631Z" fill="white" class="letters" />
<path d="M17.7646 61.8189C17.7646 58.4194 20.3201 55.6395 23.7119 55.6395C25.5509 55.6395 27.2702 56.5908 28.1307 57.8737V55.8778H29.8031V67.7618H28.1307V65.7658C27.2702 67.0487 25.5509 68 23.7119 68C20.3201 67.9984 17.7646 65.2169 17.7646 61.8189ZM28.273 61.8189C28.273 59.2516 26.4825 57.1848 23.8785 57.1848C21.2744 57.1848 19.4839 59.2532 19.4839 61.8189C19.4839 64.3847 21.2744 66.4531 23.8785 66.4531C26.4825 66.4531 28.273 64.3863 28.273 61.8189Z" fill="white" class="letters" />
<path d="M34.3386 57.4456H31.7831V55.8762H34.3386V53.2138C34.3386 50.8847 35.7717 49.7917 37.7061 49.7917C38.3741 49.7917 38.9952 49.9109 39.4966 50.1008V51.5978C39.1149 51.4545 38.4453 51.337 37.9681 51.337C36.7017 51.337 36.0094 51.836 36.0094 53.3571V55.8762H39.4966V57.4456H36.0094V67.7602H34.337V57.4456H34.3386Z" fill="white" class="letters" />
<path d="M39.6161 61.819C39.6161 58.4194 42.2185 55.6395 45.8497 55.6395C48.3098 55.6395 50.2685 56.8999 51.3182 58.7526L49.8609 59.5848C49.1444 58.1586 47.7113 57.1848 45.848 57.1848C43.1259 57.1848 41.3338 59.2757 41.3338 61.819C41.3338 64.3622 43.1243 66.4531 45.848 66.4531C47.7113 66.4531 49.1444 65.4793 49.8609 64.0531L51.3182 64.8853C50.2669 66.7396 48.3098 67.9984 45.8497 67.9984C42.2202 67.9984 39.6161 65.2169 39.6161 61.819Z" fill="white" class="letters" />
<path d="M53.2057 49.9344H54.8782V67.7596H53.2057V49.9344Z" fill="white" class="letters" />
<path d="M57.0261 61.819C57.0261 58.4194 59.6286 55.6395 63.2597 55.6395C66.8908 55.6395 69.4933 58.421 69.4933 61.819C69.4933 65.2169 66.8892 67.9984 63.2597 67.9984C59.6302 67.9984 57.0261 65.2169 57.0261 61.819ZM67.7723 61.819C67.7723 59.2757 65.9818 57.1848 63.2581 57.1848C60.5343 57.1848 58.7438 59.2757 58.7438 61.819C58.7438 64.3622 60.5343 66.4531 63.2581 66.4531C65.9818 66.4531 67.7723 64.3622 67.7723 61.819Z" fill="white" class="letters" />
<path d="M71.4506 63.3162V55.8765H73.123V63.078C73.123 65.1223 74.3167 66.4534 76.036 66.4534C78.0417 66.4534 79.6187 64.6716 79.6187 61.7951V55.8765H81.2911V67.7605H79.6187V65.7645C78.7824 67.2856 77.3267 67.9987 75.8209 67.9987C73.2896 67.9987 71.4506 66.1685 71.4506 63.3162Z" fill="white" class="letters" />
<path d="M82.9614 61.8184C82.9614 58.4189 85.517 55.639 88.9087 55.639C90.7477 55.639 92.4671 56.5903 93.3275 57.8732V49.9344H95V67.7596H93.3275V65.7637C92.4655 67.0466 90.7461 67.9979 88.9071 67.9979C85.517 67.9979 82.9614 65.2164 82.9614 61.8184ZM93.4699 61.8184C93.4699 59.2511 91.6794 57.1843 89.0753 57.1843C86.4712 57.1843 84.6808 59.2527 84.6808 61.8184C84.6808 64.3842 86.4712 66.4526 89.0753 66.4526C91.6794 66.4526 93.4699 64.3858 93.4699 61.8184Z" fill="white" class="letters" />
</svg>

Before

Width:  |  Height:  |  Size: 3.6 KiB

88
html/script.html

@ -1,88 +0,0 @@
<script lang="text/javascript">
let host = "{{ .Host }}";
let jsonObj = "{{ .JSON }}";
let data = JSON.parse(jsonObj);
let tool = "curl";
let commandBox, widgetBox, compositePath, commandStr;
let path
let ipQuery, portQuery
let ipCheckBox, portCheckBox, portInput
let ip = ''
window.onload = (event) => {
commandBox = document.getElementById('command');
widgetBox = document.getElementById('output');
ipCheckBox = document.getElementById('ipCheckBox')
portCheckBox = document.getElementById('portCheckBox')
portInput = document.getElementById('portInput')
reset()
setcommdStr()
changeInput("ip")
}
function reset() {
path = '';
ipQuery = '';
portQuery = '';
}
function setcommdStr() {
compositePath = `${path}${portQuery}${ipQuery}`;
commandStr = `${tool} ${host}/${compositePath}`;
commandBox.innerText = commandStr;
}
function changeInput(input, button) {
path = input
portQuery = ""
portInput.classList.add("hidden");
switch(path) {
case "json":
output.innerText = jsonObj
break
case "country-iso":
output.innerText = data["country_iso"]
break
case "port":
portInput.classList.remove("hidden");
path = "port";
output.innerText = "{}";
let currentPort = document.querySelector("#portInput").value;
updatePort(currentPort);
break
case "ip":
output.innerText = data["ip"]
path = ""
break
default:
output.innerText = data[path]
}
setcommdStr();
// set button selected
if (button) {
allButtons = document.querySelectorAll(('button.selected'));
allButtons.forEach((btn) => {btn.classList.remove("selected")})
button.classList.add("selected");
}
}
function navigate(event) {
console.log("navigate", compositePath)
window.location = compositePath
}
function updatePort(value) {
port = value
portQuery = `/${port}`
setcommdStr()
}
function updateIP(value) {
ip = value
ipQuery = `?ip=${ip}`;
setcommdStr()
changeInput("ip", null)
}
</script>

184
html/styles.html

@ -1,184 +0,0 @@
<style>
html,
.pure-g [class*="pure-u"] {
background-color: white;
font-family: "Open Sans", sans-serif;
}
pre {
font-family: "Monaco", "Menlo", "Consolas", "Courier New", monospace;
white-space: pre-wrap; /* Since CSS 2.1 */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word;
}
a {
/* background: #e3e3e3; */
text-decoration: underline;
color: #000;
}
a:hover,
active {
background: #d7d7d7;
}
.ip {
border: 1px solid #cbcbcb;
background: #f2f2f2;
font-size: 36px;
padding: 6px;
word-wrap: break-word;
}
svg.github-corner {
fill: #151513;
color: #fff;
}
.footer {
margin-top: 34px;
border-top: 1px solid #cbcbcb;
}
.content {
margin-left: auto;
margin-right: auto;
padding-left: 1em;
padding-right: 1em;
max-width: 1024px;
}
.center {
justify-content: center;
}
.info-table td,
.info-table th {
padding: 5px;
border: 2px solid #ababab;
word-wrap: break-word;
}
.info-table th[scope="row"] {
background-color: #d5d5d5;
text-align: left;
}
.widgetbox {
width: 100%;
padding: 0.5rem;
border: 1px solid grey;
font-family: "Courier New", Courier, monospace;
margin-top: 0.5rem;
box-sizing: border-box;
}
.widgetbox.input :first-child::before {
content: "$ ";
white-space: pre;
}
.widgetbox.output {
min-height: 4em;
white-space: pre;
overflow-x: scroll;
}
.l-box {
margin: 0 1rem;
}
.align-right {
text-align: right;
}
.medium-input {
width: 10em;
}
button.selected {
background-color: rgb(208 208 208);
}
.input-buttons {
line-height: 2.6em;
font-size: smaller;
}
/* POST CORRECTION */
.leafcloud-logo .letters {
fill: black;
}
/* DARK MODE OVERRIDES */
@media (prefers-color-scheme: dark) {
html,
.pure-g [class*="pure-u"],
a {
background-color: #161719;
color: #d8d9da;
}
.ip {
border: 1px solid #313233;
background: #212223;
}
.footer {
color: #8e8e8e !important;
border-top: 1px solid #313233;
}
a:hover,
active {
background: #3d3e3f;
}
svg.github-corner {
fill: #f8f9fa;
color: #161719;
}
.info-table th[scope="row"] {
background-color: #2e2e2e;
color: rgb(220, 220, 220);
text-align: left;
}
.pure-button {
background-color: #2e2e2e;
color: rgb(220, 220, 220);
}
.pure-button.selected {
background-color: rgb(125 125 125);
}
.pure-input {
background-color: #e6e6e6;
color: #666;
}
.pure-input::placeholder {
color: #bbb;
}
.leafcloud-logo .letters {
fill: white;
max-width: 100%;
}
}
@media (min-width: 768px) {
.leafcloud-placement {
text-align: right;
}
.leafcloud-logo {
height: 80px;
margin: 2em 0 -0.5em 0;
}
}
@media (max-width: 768px) {
.leafcloud-logo {
margin-right: 1em;
}
}
.debug {
outline: 1px dotted pink;
}
</style>

154
http/html/index.html

@ -0,0 +1,154 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="canonical" href="https://ip.neveris.one/"/>
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Source+Code+Pro:wght@400;700&display=swap" rel="stylesheet">
<title>ip.ni.o</title>
<link rel="icon" href="https://neveris.one/favicon.ico">
<link rel="icon" type="image/png" sizes="16x16" href="https://neveris.one/favicon-16x16.png">
<link rel="icon" type="image/png" sizes="32x32" href="https://neveris.one/favicon-32x32.png">
<link rel="apple-touch-icon" href="https://neveris.one/apple-touch-icon.png">
<link rel="mask-icon" href="https://neveris.one/safari-pinned-tab.svg">
{{ template "styles.html" . }}
</head>
<body>
<div class="content">
<div class="centcol">
<div class="head">
<div class="l-box">
<p><code class="ip">{{ .IP }}</code></p>
</div>
</div>
</div>
<div class="centcol">
<!-- COLUMN 1 -->
<div class="col">
<div class="l-box">
<table class="info-table">
<tr>
<th scope="row">IP&nbsp;address</th>
<td>{{ .IP }}</td>
</tr>
<tr>
<th scope="row">IP&nbsp;address (decimal)</th>
<td>{{ .IPDecimal }}</td>
</tr>
{{ if .Country }}
<tr>
<th scope="row">Country</th>
<td>{{ .Country }}</td>
</tr>
{{ end }} {{ if .CountryISO }}
<tr>
<th scope="row">Country (ISO code)</th>
<td>{{ .CountryISO }}</td>
</tr>
{{ end }} {{ if .CountryEU }}
<tr>
<th scope="row">In EU?</th>
<td>{{ .CountryEU }}</td>
</tr>
{{ end }} {{ if .RegionName }}
<tr>
<th scope="row">Region</th>
<td>{{ .RegionName }}</td>
</tr>
{{ end }} {{ if .RegionCode }}
<tr>
<th scope="row">Region&nbsp;code</th>
<td>{{ .RegionCode }}</td>
</tr>
{{ end }} {{ if .MetroCode }}
<tr>
<th scope="row">Metro code</th>
<td>{{ .MetroCode }}</td>
</tr>
{{ end }} {{ if .PostalCode }}
<tr>
<th scope="row">Postal&nbsp;code</th>
<td>{{ .PostalCode }}</td>
</tr>
{{ end }} {{ if .City }}
<tr>
<th scope="row">City</th>
<td>{{ .City }}</td>
</tr>
{{ end }} {{ if .Latitude }}
<tr>
<th scope="row">Latitude</th>
<td>{{ .Latitude }}</td>
</tr>
{{ end }} {{ if .Longitude }}
<tr>
<th scope="row">Longitude</th>
<td>{{ .Longitude }}</td>
</tr>
{{ end }} {{ if .Timezone }}
<tr>
<th scope="row">Timezone</th>
<td>{{ .Timezone }}</td>
</tr>
{{ end }} {{ if .ASN }}
<tr>
<th scope="row">ASN</th>
<td>{{ .ASN }}</td>
</tr>
{{ end }} {{ if .ASNOrg }}
<tr>
<th scope="row">ASN (organization)</th>
<td>{{ .ASNOrg }}</td>
</tr>
{{ end }} {{ if .Hostname }}
<tr>
<th scope="row">Hostname</th>
<td>{{ .Hostname }}</td>
</tr>
{{ end }} {{ if .UserAgent }} {{ if .UserAgent.Comment }}
<tr>
<th scope="row">User&nbsp;agent</th>
<td>{{ .UserAgent.Product }}/{{ .UserAgent.Version }}</td>
</tr>
{{ end }} {{ if .UserAgent.Comment }}
<tr>
<th scope="row">User&nbsp;agent: Comment</th>
<td>{{ .UserAgent.Comment }}</td>
</tr>
{{ end }} {{ if .UserAgent.RawValue }}
<tr>
<th scope="row">User&nbsp;agent: Raw</th>
<td>{{ .UserAgent.RawValue }}</td>
</tr>
{{ end }} {{ end }}
</table>
</div>
</div>
<!-- COLUMN 2 -->
<div class="FAQ">
<h2>FAQ</h2>
<h3>How do I force IPv4 or IPv6 lookup?</h3>
<p>
IPv4 or IPv6 still can be forced by passing the appropriate flag to
your client,<br>e.g <code>curl -4</code> or <code>curl -6</code>.
</p>
<h3>Can I force getting JSON?</h3>
<p>
Setting the <code>Accept: application/json</code> header works
as expected.
</p>
<h3>Is automated use of this service permitted?</h3>
<p>
Yes, as long as the rate limit (1 req/min) is respected. <br> The rate limit is
in place to ensure a fair service for all.
</p>
</div>
</div>
</div>
</body>
</html>

141
http/html/styles.html

@ -0,0 +1,141 @@
<style>
:root {
--bg-color: #1d1e20;
--fg-color: #dadadb;
--hl-color: #f5a9c9;
--bg-l-color: #2c2e31;
--font-stack: "Source Code Pro", "Menlo", "Monaco", "Lucida Console", "Liberation Mono",
"DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Courier New", "monospace", "serif";
}
html,
.centcol {
background-color: var(--bg-color);
color: var(--fg-color);
font-family: var(--font-stack);
}
h1 {
text-decoration: underline var(--hl-color);
}
h2 {
font-size: 2em;
margin: -0.2em 0;
text-decoration: underline var(--hl-color);
}
code {
font-family: var(--font-stack);
font-size: 14px;
padding: 4px 6px;
background: var(--bg-l-color);
}
pre {
font-family: var(--font-stack);
white-space: pre-wrap; /* Since CSS 2.1 */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word;
}
a {
background-color: var(--bg-color);
color: var(--fg-color);
}
a:hover,
active {
background: #3d3e3f;
}
.ip {
border: 1px solid var(--hl-color);
background: var(--bg-color);
font-size: 4em;
padding: 0 12px;
word-wrap: break-word;
font-weight: bold;
}
.footer {
margin-top: 34px;
color: #8e8e8e !important;
border-top: 1px solid var(--bg-l-color);
}
.content {
margin-left: auto;
margin-right: auto;
padding-left: 1em;
padding-right: 1em;
max-width: 64em;
}
.center {
justify-content: center;
}
.info-table td,
.info-table th {
padding: 5px;
border: 2px solid #ababab;
word-wrap: break-word;
}
.info-table th[scope="row"] {
background-color: var(--bg-l-color);
color: rgb(220, 220, 220);
text-align: left;
}
.l-box,
.FAQ {
margin: 0 1em;
}
.align-right {
text-align: right;
}
.info-table {
width: 100%;
}
.logo .letters {
fill: white;
max-width: 100%;
}
.head,
.col {
width: 100%;
margin: 2em 0;
}
@media (min-width: 768px) {
.logo-placement {
text-align: right;
}
.logo {
height: 80px;
margin: 2em 0 -0.5em 0;
}
}
@media only screen and (max-width: 768px) {
.ip {
font-size: 2.5em;
}
.logo {
margin-right: 1em;
}
}
.debug {
outline: 1px dotted pink;
}
</style>

141
http/http.go

@ -1,9 +1,11 @@
package http
import (
"embed"
"encoding/json"
"fmt"
"html/template"
"io/fs"
"io/ioutil"
"log"
"path/filepath"
@ -11,9 +13,8 @@ import (
"net/http/pprof"
"github.com/mpolden/echoip/iputil"
"github.com/mpolden/echoip/iputil/geo"
"github.com/mpolden/echoip/useragent"
"github.com/gryffyn/ipinfo/iputil"
"github.com/gryffyn/ipinfo/useragent"
"math/big"
"net"
@ -32,29 +33,15 @@ type Server struct {
LookupAddr func(net.IP) (string, error)
LookupPort func(net.IP, uint64) error
cache *Cache
gr geo.Reader
profile bool
Sponsor bool
}
type Response struct {
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"`
ASN string `json:"asn,omitempty"`
ASNOrg string `json:"asn_org,omitempty"`
Hostname string `json:"hostname,omitempty"`
UserAgent *useragent.UserAgent `json:"user_agent,omitempty"`
IP net.IP `json:"ip"`
IPDecimal *big.Int `json:"ip_decimal"`
Hostname string `json:"hostname,omitempty"`
UserAgent *useragent.UserAgent `json:"user_agent,omitempty"`
}
type PortResponse struct {
@ -63,8 +50,8 @@ type PortResponse struct {
Reachable bool `json:"reachable"`
}
func New(db geo.Reader, cache *Cache, profile bool) *Server {
return &Server{cache: cache, gr: db, profile: profile}
func New(cache *Cache, profile bool) *Server {
return &Server{cache: cache, profile: profile}
}
func ipFromForwardedForHeader(v string) string {
@ -134,34 +121,15 @@ func (s *Server) newResponse(r *http.Request) (Response, error) {
return response, nil
}
ipDecimal := iputil.ToDecimal(ip)
country, _ := s.gr.Country(ip)
city, _ := s.gr.City(ip)
asn, _ := s.gr.ASN(ip)
var hostname string
if s.LookupAddr != nil {
hostname, _ = s.LookupAddr(ip)
}
var autonomousSystemNumber string
if asn.AutonomousSystemNumber > 0 {
autonomousSystemNumber = fmt.Sprintf("AS%d", asn.AutonomousSystemNumber)
}
response = Response{
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,
IP: ip,
IPDecimal: ipDecimal,
Hostname: hostname,
}
s.cache.Set(ip, response)
response.UserAgent = userAgentFromRequest(r)
@ -195,51 +163,6 @@ func (s *Server) CLIHandler(w http.ResponseWriter, r *http.Request) *appError {
return nil
}
func (s *Server) CLICountryHandler(w http.ResponseWriter, r *http.Request) *appError {
response, err := s.newResponse(r)
if err != nil {
return badRequest(err).WithMessage(err.Error()).AsJSON()
}
fmt.Fprintln(w, response.Country)
return nil
}
func (s *Server) CLICountryISOHandler(w http.ResponseWriter, r *http.Request) *appError {
response, err := s.newResponse(r)
if err != nil {
return badRequest(err).WithMessage(err.Error()).AsJSON()
}
fmt.Fprintln(w, response.CountryISO)
return nil
}
func (s *Server) CLICityHandler(w http.ResponseWriter, r *http.Request) *appError {
response, err := s.newResponse(r)
if err != nil {
return badRequest(err).WithMessage(err.Error()).AsJSON()
}
fmt.Fprintln(w, response.City)
return nil
}
func (s *Server) CLICoordinatesHandler(w http.ResponseWriter, r *http.Request) *appError {
response, err := s.newResponse(r)
if err != nil {
return badRequest(err).WithMessage(err.Error()).AsJSON()
}
fmt.Fprintf(w, "%s,%s\n", formatCoordinate(response.Latitude), formatCoordinate(response.Longitude))
return nil
}
func (s *Server) CLIASNHandler(w http.ResponseWriter, r *http.Request) *appError {
response, err := s.newResponse(r)
if err != nil {
return badRequest(err).WithMessage(err.Error()).AsJSON()
}
fmt.Fprintf(w, "%s\n", response.ASN)
return nil
}
func (s *Server) JSONHandler(w http.ResponseWriter, r *http.Request) *appError {
response, err := s.newResponse(r)
if err != nil {
@ -318,38 +241,37 @@ func (s *Server) cacheHandler(w http.ResponseWriter, r *http.Request) *appError
return nil
}
//go:embed html/*
var Assets embed.FS
func (s *Server) DefaultHandler(w http.ResponseWriter, r *http.Request) *appError {
fsys, err := fs.Sub(Assets, "html")
if err != nil {
panic(err)
}
response, err := s.newResponse(r)
if err != nil {
return badRequest(err).WithMessage(err.Error())
}
t, err := template.ParseGlob(s.Template + "/*")
t, err := template.ParseFS(fsys, "*")
if err != nil {
return internalServerError(err)
}
json, err := json.MarshalIndent(response, "", " ")
jsonR, err := json.MarshalIndent(response, "", " ")
if err != nil {
return internalServerError(err)
}
var data = struct {
Response
Host string
BoxLatTop float64
BoxLatBottom float64
BoxLonLeft float64
BoxLonRight float64
JSON string
Port bool
Sponsor bool
Host string
JSON string
Port bool
Sponsor bool
}{
response,
r.Host,
response.Latitude + 0.05,
response.Latitude - 0.05,
response.Longitude - 0.05,
response.Longitude + 0.05,
string(json),
string(jsonR),
s.LookupPort != nil,
s.Sponsor,
}
@ -425,13 +347,6 @@ func (s *Server) Handler() http.Handler {
r.Route("GET", "/", s.CLIHandler).MatcherFunc(cliMatcher)
r.Route("GET", "/", s.CLIHandler).Header("Accept", textMediaType)
r.Route("GET", "/ip", s.CLIHandler)
if !s.gr.IsEmpty() {
r.Route("GET", "/country", s.CLICountryHandler)
r.Route("GET", "/country-iso", s.CLICountryISOHandler)
r.Route("GET", "/city", s.CLICityHandler)
r.Route("GET", "/coordinates", s.CLICoordinatesHandler)
r.Route("GET", "/asn", s.CLIASNHandler)
}
// Browser
if s.Template != "" {

21
http/http_test.go

@ -9,31 +9,13 @@ import (
"net/url"
"strings"
"testing"
"github.com/mpolden/echoip/iputil/geo"
)
func lookupAddr(net.IP) (string, error) { return "localhost", nil }
func lookupPort(net.IP, uint64) error { return nil }
type testDb struct{}
func (t *testDb) Country(net.IP) (geo.Country, error) {
return geo.Country{Name: "Elbonia", ISO: "EB", IsEU: new(bool)}, nil
}
func (t *testDb) City(net.IP) (geo.City, error) {
return geo.City{Name: "Bornyasherk", RegionName: "North Elbonia", RegionCode: "1234", MetroCode: 1234, PostalCode: "1234", Latitude: 63.416667, Longitude: 10.416667, Timezone: "Europe/Bornyasherk"}, nil
}
func (t *testDb) ASN(net.IP) (geo.ASN, error) {
return geo.ASN{AutonomousSystemNumber: 59795, AutonomousSystemOrganization: "Hosting4Real"}, nil
}
func (t *testDb) IsEmpty() bool { return false }
func testServer() *Server {
return &Server{cache: NewCache(100), gr: &testDb{}, LookupAddr: lookupAddr, LookupPort: lookupPort}
return &Server{cache: NewCache(100), LookupAddr: lookupAddr, LookupPort: lookupPort}
}
func httpGet(url string, acceptMediaType string, userAgent string) (string, int, error) {
@ -115,7 +97,6 @@ func TestDisabledHandlers(t *testing.T) {
server := testServer()
server.LookupPort = nil
server.LookupAddr = nil
server.gr, _ = geo.Open("", "", "")
s := httptest.NewServer(server.Handler())
var tests = []struct {

157
iputil/geo/geo.go

@ -1,157 +0,0 @@
package geo
import (
"math"
"net"
geoip2 "github.com/oschwald/geoip2-golang"
)
type Reader interface {
Country(net.IP) (Country, error)
City(net.IP) (City, error)
ASN(net.IP) (ASN, error)
IsEmpty() bool
}
type Country struct {
Name string
ISO string
IsEU *bool
}
type City struct {
Name string
Latitude float64
Longitude float64
PostalCode string
Timezone string
MetroCode uint
RegionName string
RegionCode string
}
type ASN struct {
AutonomousSystemNumber uint
AutonomousSystemOrganization string
}
type geoip struct {
country *geoip2.Reader
city *geoip2.Reader
asn *geoip2.Reader
}
func Open(countryDB, cityDB string, asnDB string) (Reader, error) {
var country, city, asn *geoip2.Reader
if countryDB != "" {
r, err := geoip2.Open(countryDB)
if err != nil {
return nil, err
}
country = r
}
if cityDB != "" {
r, err := geoip2.Open(cityDB)
if err != nil {
return nil, err
}
city = r
}
if asnDB != "" {
r, err := geoip2.Open(asnDB)