package proxy import ( "encoding/json" "net" "net/http" "strings" "github.com/wormhole/wormhole/internal/auth" "github.com/wormhole/wormhole/internal/store" ) const ( // IPForwardReplace 覆盖 X-Forwarded-For / X-Real-IP 为访客真实 IP。 IPForwardReplace = "replace" // IPForwardAppend 按反向代理规范追加 X-Forwarded-For(Nginx proxy_add_x_forwarded_for)。 IPForwardAppend = "append" ) func normalizeIPForwardMode(mode string) string { switch mode { case IPForwardReplace, IPForwardAppend: return mode case "real": // 兼容旧值 return IPForwardReplace case "proxy": // 兼容旧值 return IPForwardAppend default: return IPForwardReplace } } func parseHeaderMap(raw string) map[string]string { raw = strings.TrimSpace(raw) if raw == "" || raw == "{}" { return nil } var m map[string]string if err := json.Unmarshal([]byte(raw), &m); err != nil { return nil } return m } func mergeHeaderMaps(base, override map[string]string) map[string]string { out := make(map[string]string) for k, v := range base { out[k] = v } for k, v := range override { out[k] = v } if len(out) == 0 { return nil } return out } func effectiveIPMode(resourceMode, clientMode string) string { if resourceMode != "" { return normalizeIPForwardMode(resourceMode) } if clientMode != "" { return normalizeIPForwardMode(clientMode) } return IPForwardReplace } func resolveProxyConfig(st *store.Store, clientID uint, resourceMode, resourceHeaders string) (mode string, headers map[string]string) { client, _ := st.GetClientByID(clientID) clientMode := "" clientHeaders := "" if client != nil { clientMode = client.IPForwardMode clientHeaders = client.CustomHeaders } mode = effectiveIPMode(resourceMode, clientMode) headers = mergeHeaderMaps(parseHeaderMap(clientHeaders), parseHeaderMap(resourceHeaders)) return mode, headers } func hostOnly(addr string) string { if addr == "" { return "" } host, _, err := net.SplitHostPort(addr) if err != nil { return strings.Trim(addr, "[]") } return host } func directClientIP(r *http.Request) string { return hostOnly(r.RemoteAddr) } func forwardIPForHTTP(r *http.Request, mode string) string { mode = normalizeIPForwardMode(mode) if mode == IPForwardAppend { return directClientIP(r) } return auth.ExtractRemoteIP(r) } func forwardIPForTCP(clientAddr string, _ string) string { return hostOnly(clientAddr) } func isForbiddenHeader(name string) bool { switch strings.ToLower(name) { case "connection", "transfer-encoding", "keep-alive", "proxy-connection", "te", "trailer", "upgrade", "content-length", "host": return true default: return false } } func applyOutboundHeaders(req *http.Request, r *http.Request, mode string, custom map[string]string) { mode = normalizeIPForwardMode(mode) req.Header.Del("X-Forwarded-For") req.Header.Del("X-Real-IP") req.Header.Del("Forwarded") switch mode { case IPForwardAppend: clientIP := directClientIP(r) if clientIP == "" { break } existing := strings.TrimSpace(r.Header.Get("X-Forwarded-For")) xff := clientIP if existing != "" { xff = existing + ", " + clientIP } req.Header.Set("X-Forwarded-For", xff) req.Header.Set("X-Real-IP", clientIP) default: visitorIP := auth.ExtractRemoteIP(r) if visitorIP != "" { req.Header.Set("X-Forwarded-For", visitorIP) req.Header.Set("X-Real-IP", visitorIP) } } for k, v := range custom { if k == "" || isForbiddenHeader(k) { continue } req.Header.Set(k, v) } }