package network

import (
	"errors"
	"net"
	"net/http"
	"strings"
)

const forwardedForHeader = "X-Forwarded-For"

// ErrUnparseableIP occurs when no parseable IP address is available
var ErrUnparseableIP = errors.New("unable to parse IP address")

var internalBlocks []*net.IPNet

func init() {
	cidrs := []string{"127.0.0.0/8", "::1/128", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "fd00::/8"}
	for _, cidr := range cidrs {
		_, ipNet, err := net.ParseCIDR(cidr)
		if err != nil {
			panic(err)
		}
		internalBlocks = append(internalBlocks, ipNet)
	}
}

// IsInternalIP will return whether the IP address belongs in the loopback
// block or the private network block (as defined in RFC 1918 and RFC 4193).
func IsInternalIP(ip net.IP) bool {
	for _, block := range internalBlocks {
		if block.Contains(ip) {
			return true
		}
	}
	return false
}

// Returns whether the IP address is trusted. An ip is trusted if it is
// internal or contained in trustedBlocks.
func isTrustedIP(ip net.IP, trustedBlocks []*net.IPNet) bool {
	for _, block := range trustedBlocks {
		if block.Contains(ip) {
			return true
		}
	}

	return IsInternalIP(ip)
}

// ClientIP will determine the client IP address from a request. This will
// prefer the ip address provided in the X-Forwarded-For header, if available.
// Additional ip address blocks can be ignored with the trustedBlocks argument.
func ClientIP(r *http.Request, trustedBlocks []*net.IPNet) (net.IP, error) {
	forwardedFor := r.Header.Get(forwardedForHeader)
	if forwardedFor != "" {
		return forwardedForIP(forwardedFor, trustedBlocks)
	}
	return remoteAddrIP(r.RemoteAddr)
}

// When a request passes through a proxy, the requester's IP address is
// appended to the X-Forwarded-For header. Thus, the client IP is usually the
// first IP in the X-Forwarded-For header. However, malicious users or
// misbehaving proxies may add bogus IP addresses to the X-Forwarded-For, so
// we filter the list for the last valid IP that's external and not an ip
// address contained in the ip address blocks of trustedBlocks. If no such IP
// can be found (e.g. the client makes a request from the internal network or
// wants to ignore some ip addresses) we use the first trusted IP in the list.
func forwardedForIP(forwardedFor string, trustedBlocks []*net.IPNet) (net.IP, error) {
	var ips []net.IP
	for _, ipStr := range strings.Split(forwardedFor, ",") {
		ip := net.ParseIP(strings.TrimSpace(ipStr))
		if ip != nil {
			ips = append(ips, ip)
		}
	}

	if len(ips) == 0 {
		return nil, ErrUnparseableIP
	}

	ip := lastTrustedIP(ips, trustedBlocks)
	if ip != nil {
		return ip, nil
	}

	return ips[0], nil
}

func lastTrustedIP(ips []net.IP, trustedBlocks []*net.IPNet) net.IP {
	for k := len(ips) - 1; k >= 0; k-- {
		if ips[k] != nil && !isTrustedIP(ips[k], trustedBlocks) {
			return ips[k]
		}
	}
	return nil
}

func remoteAddrIP(remoteAddr string) (net.IP, error) {
	ipStr, _, err := net.SplitHostPort(remoteAddr)
	if err != nil {
		return nil, err
	}

	ip := net.ParseIP(ipStr)
	if ip == nil {
		return nil, ErrUnparseableIP
	}
	return ip, nil
}
