package surf import ( "net" "net/http" "net/netip" "strings" "git.golem15.com/golem15/summercms/modules/compass" ) // ClientIP is the single source of client IP for limiter keys (D-04). // RemoteAddr is used unless it parses as being inside one of trusted; // in that case the rightmost X-Forwarded-For hop NOT inside any trusted // prefix is used. An empty trusted list means RemoteAddr only. func ClientIP(r *http.Request, trusted []netip.Prefix) string { if r == nil { return "" } remote := parseIP(r.RemoteAddr) if len(trusted) == 0 || remote == (netip.Addr{}) || !addrTrusted(remote, trusted) { if remote == (netip.Addr{}) { return "" } return remote.String() } xff := r.Header.Get("X-Forwarded-For") if xff == "" { return remote.String() } hops := strings.Split(xff, ",") for i := len(hops) - 1; i >= 0; i-- { hop := strings.TrimSpace(hops[i]) if hop == "" { continue } addr, err := netip.ParseAddr(hop) if err != nil { continue } addr = addr.Unmap() if !addrTrusted(addr, trusted) { return addr.String() } } return remote.String() } // TrustedProxies reads http.trusted_proxies (a []string of CIDRs) from cfg // and parses it once into []netip.Prefix. A malformed entry is skipped, not // fatal (logged by the caller if desired). func TrustedProxies(cfg *compass.Config) []netip.Prefix { if cfg == nil { return nil } raw, ok := cfg.Lookup("http.trusted_proxies") if !ok { return nil } var entries []string switch v := raw.(type) { case []string: entries = v case []any: for _, item := range v { s, _ := item.(string) if s != "" { entries = append(entries, s) } } default: return nil } var out []netip.Prefix for _, e := range entries { p, err := netip.ParsePrefix(strings.TrimSpace(e)) if err != nil { continue } out = append(out, p) } return out } func parseIP(remoteAddr string) netip.Addr { host, _, err := net.SplitHostPort(remoteAddr) if err != nil { host = remoteAddr } host = strings.Trim(host, "[]") addr, err := netip.ParseAddr(host) if err != nil { return netip.Addr{} } return addr.Unmap() } func addrTrusted(addr netip.Addr, trusted []netip.Prefix) bool { for _, p := range trusted { if p.Contains(addr) { return true } } return false }