mirror of
https://github.com/chubin/wttr.in
synced 2025-01-13 04:28:46 +00:00
167 lines
3.8 KiB
Go
167 lines
3.8 KiB
Go
package ip
|
|
|
|
import (
|
|
"errors"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"path"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/chubin/wttr.in/internal/config"
|
|
"github.com/chubin/wttr.in/internal/routing"
|
|
"github.com/chubin/wttr.in/internal/util"
|
|
)
|
|
|
|
var (
|
|
ErrNotFound = errors.New("cache entry not found")
|
|
ErrInvalidCacheEntry = errors.New("invalid cache entry format")
|
|
)
|
|
|
|
// Location information.
|
|
type Location struct {
|
|
IP string `db:"ip,key"`
|
|
CountryCode string `db:"countryCode"`
|
|
Country string `db:"country"`
|
|
Region string `db:"region"`
|
|
City string `db:"city"`
|
|
Latitude float64 `db:"latitude"`
|
|
Longitude float64 `db:"longitude"`
|
|
}
|
|
|
|
// Cache provides access to the IP Geodata cache.
|
|
type Cache struct {
|
|
config *config.Config
|
|
}
|
|
|
|
// NewCache returns new cache reader for the specified config.
|
|
func NewCache(config *config.Config) *Cache {
|
|
return &Cache{
|
|
config: config,
|
|
}
|
|
}
|
|
|
|
// Read returns location information from the cache, if found,
|
|
// or ErrNotFound if not found. If the entry is found, but its format
|
|
// is invalid, ErrInvalidCacheEntry is returned.
|
|
//
|
|
// Format:
|
|
//
|
|
// [CountryCode];Country;Region;City;[Latitude];[Longitude]
|
|
//
|
|
// Example:
|
|
//
|
|
// DE;Germany;Free and Hanseatic City of Hamburg;Hamburg;53.5736;9.9782
|
|
//
|
|
func (c *Cache) Read(addr string) (*Location, error) {
|
|
bytes, err := os.ReadFile(c.cacheFile(addr))
|
|
if err != nil {
|
|
return nil, ErrNotFound
|
|
}
|
|
return parseCacheEntry(addr, string(bytes))
|
|
}
|
|
|
|
// cacheFile retuns path to the cache entry for addr.
|
|
func (c *Cache) cacheFile(addr string) string {
|
|
return path.Join(c.config.Geo.IPCache, addr)
|
|
}
|
|
|
|
// parseCacheEntry parses the location cache entry s,
|
|
// and return location, or error, if the cache entry is invalid.
|
|
func parseCacheEntry(addr, s string) (*Location, error) {
|
|
var (
|
|
lat float64 = -1000
|
|
long float64 = -1000
|
|
err error
|
|
)
|
|
|
|
parts := strings.Split(s, ";")
|
|
if len(parts) < 4 {
|
|
return nil, ErrInvalidCacheEntry
|
|
}
|
|
|
|
if len(parts) >= 6 {
|
|
lat, err = strconv.ParseFloat(parts[4], 64)
|
|
if err != nil {
|
|
return nil, ErrInvalidCacheEntry
|
|
}
|
|
|
|
long, err = strconv.ParseFloat(parts[5], 64)
|
|
if err != nil {
|
|
return nil, ErrInvalidCacheEntry
|
|
}
|
|
}
|
|
|
|
return &Location{
|
|
IP: addr,
|
|
CountryCode: parts[0],
|
|
Country: parts[1],
|
|
Region: parts[2],
|
|
City: parts[3],
|
|
Latitude: lat,
|
|
Longitude: long,
|
|
}, nil
|
|
}
|
|
|
|
// Reponse provides routing interface to the geo cache.
|
|
//
|
|
// Temporary workaround to switch IP addresses handling to the Go server.
|
|
// Handles two queries:
|
|
//
|
|
// /:geo-ip-put?ip=IP&value=VALUE
|
|
// /:geo-ip-get?ip=IP
|
|
//
|
|
func (c *Cache) Response(r *http.Request) *routing.Cadre {
|
|
var (
|
|
respERR = &routing.Cadre{Body: []byte("ERR")}
|
|
respOK = &routing.Cadre{Body: []byte("OK")}
|
|
)
|
|
|
|
if ip := util.ReadUserIP(r); ip != "127.0.0.1" {
|
|
log.Printf("geoIP access from %s rejected\n", ip)
|
|
return nil
|
|
}
|
|
|
|
if r.URL.Path == "/:geo-ip-put" {
|
|
ip := r.URL.Query().Get("ip")
|
|
value := r.URL.Query().Get("value")
|
|
if !validIP4(ip) || value == "" {
|
|
log.Printf("invalid geoIP put query: ip='%s' value='%s'\n", ip, value)
|
|
return respERR
|
|
}
|
|
|
|
err := c.putRaw(ip, value)
|
|
if err != nil {
|
|
return respERR
|
|
}
|
|
return respOK
|
|
}
|
|
if r.URL.Path == "/:geo-ip-get" {
|
|
ip := r.URL.Query().Get("ip")
|
|
if !validIP4(ip) {
|
|
return respERR
|
|
}
|
|
|
|
result, err := c.getRaw(ip)
|
|
if err != nil {
|
|
return respERR
|
|
}
|
|
return &routing.Cadre{Body: result}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Cache) getRaw(addr string) ([]byte, error) {
|
|
return os.ReadFile(c.cacheFile(addr))
|
|
}
|
|
|
|
func (c *Cache) putRaw(addr, value string) error {
|
|
return os.WriteFile(c.cacheFile(addr), []byte(value), 0644)
|
|
}
|
|
|
|
func validIP4(ipAddress string) bool {
|
|
re, _ := regexp.Compile(`^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$`)
|
|
return re.MatchString(strings.Trim(ipAddress, " "))
|
|
}
|