Files
sub2api/backend/internal/pkg/proxyurl/parse.go
T
李建琦 6d655c9903
Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
Sub2API v1.0 - AI API 网关(二开初始版本,基于上游 Wei-Shaw/sub2api)
2026-08-21 18:30:13 +08:00

67 lines
2.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package proxyurl 提供代理 URL 的统一验证(fail-fast,无效代理不回退直连)
//
// 所有需要解析代理 URL 的地方必须通过此包的 Parse 函数。
// 直接使用 url.Parse 处理代理 URL 是被禁止的。
// 这确保了 fail-fast 行为:无效代理配置在创建时立即失败,
// 而不是在运行时静默回退到直连(产生 IP 关联风险)。
package proxyurl
import (
"fmt"
"net/url"
"strings"
)
// allowedSchemes 代理协议白名单
var allowedSchemes = map[string]bool{
"http": true,
"https": true,
"socks5": true,
"socks5h": true,
}
// Parse 解析并验证代理 URL。
//
// 语义:
// - 空字符串 → ("", nil, nil),表示直连
// - 非空且有效 → (trimmed, *url.URL, nil)
// - 非空但无效 → ("", nil, error)fail-fast 不回退
//
// 验证规则:
// - TrimSpace 后为空视为直连
// - url.Parse 失败返回 error(不含原始 URL,防凭据泄露)
// - Host 为空返回 error(用 Redacted() 脱敏)
// - Scheme 必须为 http/https/socks5/socks5h
// - socks5:// 自动升级为 socks5h://(确保 DNS 由代理端解析,防止 DNS 泄漏)
func Parse(raw string) (trimmed string, parsed *url.URL, err error) {
trimmed = strings.TrimSpace(raw)
if trimmed == "" {
return "", nil, nil
}
parsed, err = url.Parse(trimmed)
if err != nil {
// 不使用 %w 包装,避免 url.Parse 的底层错误消息泄漏原始 URL(可能含凭据)
return "", nil, fmt.Errorf("invalid proxy URL: %v", err)
}
if parsed.Host == "" || parsed.Hostname() == "" {
return "", nil, fmt.Errorf("proxy URL missing host: %s", parsed.Redacted())
}
scheme := strings.ToLower(parsed.Scheme)
if !allowedSchemes[scheme] {
return "", nil, fmt.Errorf("unsupported proxy scheme %q (allowed: http, https, socks5, socks5h)", scheme)
}
// 自动升级 socks5 → socks5h,确保 DNS 由代理端解析,防止 DNS 泄漏。
// Go 的 golang.org/x/net/proxy 对 socks5:// 默认在客户端本地解析 DNS,
// 仅 socks5h:// 才将域名发送给代理端做远程 DNS 解析。
if scheme == "socks5" {
parsed.Scheme = "socks5h"
trimmed = parsed.String()
}
return trimmed, parsed, nil
}