mirror of
https://github.com/kemko/reproxy.git
synced 2026-01-01 15:55:49 +03:00
102 lines
2.7 KiB
Go
102 lines
2.7 KiB
Go
// Package libstring provides various string related functions.
|
|
package libstring
|
|
|
|
import (
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
// StringInSlice finds needle in a slice of strings.
|
|
func StringInSlice(sliceString []string, needle string) bool {
|
|
for _, b := range sliceString {
|
|
if b == needle {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// RemoteIP finds IP Address given http.Request struct.
|
|
func RemoteIP(ipLookups []string, forwardedForIndexFromBehind int, r *http.Request) string {
|
|
realIP := r.Header.Get("X-Real-IP")
|
|
forwardedFor := r.Header.Get("X-Forwarded-For")
|
|
|
|
for _, lookup := range ipLookups {
|
|
if lookup == "RemoteAddr" {
|
|
// 1. Cover the basic use cases for both ipv4 and ipv6
|
|
ip, _, err := net.SplitHostPort(r.RemoteAddr)
|
|
if err != nil {
|
|
// 2. Upon error, just return the remote addr.
|
|
return r.RemoteAddr
|
|
}
|
|
return ip
|
|
}
|
|
if lookup == "X-Forwarded-For" && forwardedFor != "" {
|
|
// X-Forwarded-For is potentially a list of addresses separated with ","
|
|
parts := strings.Split(forwardedFor, ",")
|
|
for i, p := range parts {
|
|
parts[i] = strings.TrimSpace(p)
|
|
}
|
|
|
|
partIndex := len(parts) - 1 - forwardedForIndexFromBehind
|
|
if partIndex < 0 {
|
|
partIndex = 0
|
|
}
|
|
|
|
return parts[partIndex]
|
|
}
|
|
if lookup == "X-Real-IP" && realIP != "" {
|
|
return realIP
|
|
}
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// CanonicalizeIP returns a form of ip suitable for comparison to other IPs.
|
|
// For IPv4 addresses, this is simply the whole string.
|
|
// For IPv6 addresses, this is the /64 prefix.
|
|
func CanonicalizeIP(ip string) string {
|
|
isIPv6 := false
|
|
// This is how net.ParseIP decides if an address is IPv6
|
|
// https://cs.opensource.google/go/go/+/refs/tags/go1.17.7:src/net/ip.go;l=704
|
|
for i := 0; !isIPv6 && i < len(ip); i++ {
|
|
switch ip[i] {
|
|
case '.':
|
|
// IPv4
|
|
return ip
|
|
case ':':
|
|
// IPv6
|
|
isIPv6 = true
|
|
}
|
|
}
|
|
if !isIPv6 {
|
|
// Not an IP address at all
|
|
return ip
|
|
}
|
|
|
|
// By default, the string representation of a net.IPNet (masked IP address) is just
|
|
// "full_address/mask_bits". But using that will result in different addresses with
|
|
// the same /64 prefix comparing differently. So we need to zero out the last 64 bits
|
|
// so that all IPs in the same prefix will be the same.
|
|
//
|
|
// Note: When 1.18 is the minimum Go version, this can be written more cleanly like:
|
|
// netip.PrefixFrom(netip.MustParseAddr(ipv6), 64).Masked().Addr().String()
|
|
// (With appropriate error checking.)
|
|
|
|
ipv6 := net.ParseIP(ip)
|
|
if ipv6 == nil {
|
|
return ip
|
|
}
|
|
|
|
const bytesToZero = (128 - 64) / 8
|
|
for i := len(ipv6) - bytesToZero; i < len(ipv6); i++ {
|
|
ipv6[i] = 0
|
|
}
|
|
|
|
// Note that this doesn't have the "/64" suffix customary with a CIDR representation,
|
|
// but those three bytes add nothing for us.
|
|
return ipv6.String()
|
|
}
|