package app import ( "context" "crypto/tls" "errors" "fmt" "net" "net/http" "net/netip" "net/url" "strings" "sync" "time" ) type coverRequestKey struct{} type coverSourceKey struct{} var nonPublicImageNetworks = []netip.Prefix{ netip.MustParsePrefix("0.0.0.0/8"), netip.MustParsePrefix("100.64.0.0/10"), netip.MustParsePrefix("192.0.0.0/24"), netip.MustParsePrefix("192.0.2.0/24"), netip.MustParsePrefix("198.18.0.0/15"), netip.MustParsePrefix("198.51.100.0/24"), netip.MustParsePrefix("203.0.113.0/24"), netip.MustParsePrefix("240.0.0.0/4"), netip.MustParsePrefix("64:ff9b::/96"), netip.MustParsePrefix("64:ff9b:1::/48"), netip.MustParsePrefix("100::/64"), netip.MustParsePrefix("2001::/32"), netip.MustParsePrefix("2001:db8::/32"), netip.MustParsePrefix("2002::/16"), } func publicImageAddress(value string) bool { address, err := netip.ParseAddr(value) if err != nil || address.Zone() != "" { return false } address = address.Unmap() if !address.IsGlobalUnicast() || address.IsPrivate() { return false } for _, network := range nonPublicImageNetworks { if network.Contains(address) { return false } } return true } func validImageURL(remote *url.URL) bool { if remote == nil || remote.Scheme != "https" || remote.Opaque != "" || remote.User != nil || (remote.Port() != "" && remote.Port() != "443") { return false } host := strings.ToLower(strings.TrimSuffix(remote.Hostname(), ".")) if _, err := netip.ParseAddr(host); err == nil { return publicImageAddress(host) } if len(host) > 253 || !strings.Contains(host, ".") || strings.ContainsAny(host, ":%\\") { return false } for _, suffix := range []string{"localhost", "local", "internal", "lan", "home", "invalid", "test", "example"} { if host == suffix || strings.HasSuffix(host, "."+suffix) { return false } } for _, label := range strings.Split(host, ".") { if label == "" || len(label) > 63 || label[0] == '-' || label[len(label)-1] == '-' { return false } for _, ch := range label { if ch != '-' && (ch < 'a' || ch > 'z') && (ch < '0' || ch > '9') { return false } } } return true } type imageTransport struct { base http.RoundTripper standard *http.Transport resolver *dnsResolver lookup func(context.Context, string) ([]string, error) fallback func(context.Context, string) ([]string, error) mu sync.Mutex transports map[string]*http.Transport } func newImageTransport(base http.RoundTripper, standard *http.Transport) *imageTransport { resolver := newDNSResolver(standard) return &imageTransport{ base: base, standard: standard, resolver: resolver, lookup: net.DefaultResolver.LookupHost, transports: map[string]*http.Transport{}, fallback: func(ctx context.Context, host string) ([]string, error) { entry, err := resolver.lookupEntry(ctx, host, dnsAlternateSubnet) return entry.addresses, err }, } } func (transport *imageTransport) RoundTrip(request *http.Request) (*http.Response, error) { if request.Context().Value(coverRequestKey{}) != true { return transport.base.RoundTrip(request) } if !validImageURL(request.URL) || request.Method != http.MethodGet { return nil, errors.New("封面地址不受支持") } host := strings.TrimSuffix(request.URL.Hostname(), ".") fallbackUsed := false fallback := func() ([]string, error) { fallbackUsed = true return transport.fallback(request.Context(), host) } addresses, err := transport.lookup(request.Context(), host) if (err != nil || len(addresses) == 0) && transport.fallback != nil && request.Context().Err() == nil { addresses, err = fallback() } if err != nil { return nil, fmt.Errorf("封面域名解析失败: %w", err) } var proxyURL *url.URL if transport.standard.Proxy != nil { proxyURL, err = transport.standard.Proxy(request) if err != nil { return nil, err } } tried := map[string]bool{} lastErr := errors.New("封面没有可用的公网地址") for attempt := 0; attempt < 2; attempt++ { if addressErr := validateImageAddresses(addresses); addressErr != nil { source, _ := request.Context().Value(coverSourceKey{}).(string) if fallbackUsed || transport.fallback == nil || accountSourceGroup(source) == "" || net.ParseIP(host) != nil || request.Context().Err() != nil { return nil, addressErr } addresses, err = fallback() if err != nil { return nil, fmt.Errorf("%v;可信 DNS 重解析失败:%w", addressErr, err) } if err = validateImageAddresses(addresses); err != nil { return nil, fmt.Errorf("可信 DNS 未提供可用封面地址:%w", err) } } for _, address := range addresses { if tried[address] || len(tried) >= 4 { continue } tried[address] = true pinned := transport.pinned(host, address, proxyURL) upstream := request.Clone(request.Context()) upstream.Host = request.URL.Host upstream.URL.Host = net.JoinHostPort(address, "443") response, roundTripErr := pinned.RoundTrip(upstream) if response != nil { response.Request = request } if roundTripErr == nil { return response, nil } lastErr = roundTripErr if request.Context().Err() != nil { return nil, request.Context().Err() } } if fallbackUsed || transport.fallback == nil || len(tried) >= 4 { break } addresses, err = fallback() if err != nil { break } } return nil, fmt.Errorf("封面 CDN 连接失败: %w", lastErr) } func validateImageAddresses(addresses []string) error { if len(addresses) == 0 { return errors.New("封面没有可用的公网地址") } for _, address := range addresses { if !publicImageAddress(address) { return fmt.Errorf("封面地址指向本机、内网或保留网络(%s)", address) } } return nil } func (transport *imageTransport) pinned(host, address string, proxyURL *url.URL) *http.Transport { key := host + "|" + address if proxyURL != nil { key += "|" + proxyURL.String() } transport.mu.Lock() defer transport.mu.Unlock() if existing := transport.transports[key]; existing != nil { return existing } pinned := transport.standard.Clone() if pinned.TLSClientConfig == nil { pinned.TLSClientConfig = &tls.Config{} } pinned.TLSClientConfig.ServerName = host pinned.Proxy = http.ProxyURL(proxyURL) if proxyURL != nil && proxyURL.Scheme == "https" { plain := *proxyURL plain.Scheme = "http" if plain.Port() == "" { plain.Host = net.JoinHostPort(plain.Hostname(), "443") } pinned.Proxy = http.ProxyURL(&plain) pinned.DialContext = transport.tlsProxyDialer(proxyURL.Hostname()) } if len(transport.transports) >= 32 { for key, stale := range transport.transports { stale.CloseIdleConnections() delete(transport.transports, key) break } } transport.transports[key] = pinned return pinned } func (transport *imageTransport) tlsProxyDialer(host string) func(context.Context, string, string) (net.Conn, error) { return func(ctx context.Context, network, address string) (net.Conn, error) { dial := transport.standard.DialContext if dial == nil { dialer := &net.Dialer{Timeout: 10 * time.Second} dial = dialer.DialContext } connection, err := dial(ctx, network, address) if err != nil { return nil, err } config := &tls.Config{} if transport.standard.TLSClientConfig != nil { config = transport.standard.TLSClientConfig.Clone() } config.ServerName, config.NextProtos = host, []string{"http/1.1"} secured := tls.Client(connection, config) if transport.standard.TLSHandshakeTimeout > 0 { var cancel context.CancelFunc ctx, cancel = context.WithTimeout(ctx, transport.standard.TLSHandshakeTimeout) defer cancel() } if err := secured.HandshakeContext(ctx); err != nil { connection.Close() return nil, err } return secured, nil } } func (transport *imageTransport) CloseIdleConnections() { transport.resolver.client.CloseIdleConnections() if closer, ok := transport.base.(interface{ CloseIdleConnections() }); ok { closer.CloseIdleConnections() } transport.mu.Lock() defer transport.mu.Unlock() for key, pinned := range transport.transports { pinned.CloseIdleConnections() delete(transport.transports, key) } }