mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 14:06:44 +08:00

主要改进: - 将output和parsers包中的硬编码错误消息迁移到i18n国际化系统 - 修复constants.go中未引用常量的使用问题 - 消除所有硬编码字符串、魔术数字和协议标识符 - 清理死代码常量MinIPv4OctetValue 具体变更: * output包: 删除19个硬编码错误消息,新增13个i18n消息键 * parsers包: 删除24个硬编码错误模板,新增22个i18n消息键 * 修复8个未引用的Default常量在对应默认选项函数中的使用 * 替换11处网络协议相关的硬编码值为常量引用 * 更新6个错误类型常量和验证逻辑的硬编码使用 * 修复2处IPv4八位组计数的硬编码为常量引用 提升效果: - 支持中英文错误消息国际化切换 - 统一常量管理,提高代码可维护性 - 消除代码重复,符合DRY原则 - 清理死代码,优化代码质量 涉及文件: 11个文件,新增210行,删除103行
369 lines
10 KiB
Go
369 lines
10 KiB
Go
package parsers
|
||
|
||
import (
|
||
"fmt"
|
||
"net/url"
|
||
"strconv"
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
|
||
"github.com/shadow1ng/fscan/common/i18n"
|
||
)
|
||
|
||
// NetworkParser 网络配置解析器
|
||
type NetworkParser struct {
|
||
mu sync.RWMutex //nolint:unused // reserved for future thread safety
|
||
options *NetworkParserOptions
|
||
}
|
||
|
||
// NetworkParserOptions 网络解析器选项
|
||
type NetworkParserOptions struct {
|
||
ValidateProxies bool `json:"validate_proxies"`
|
||
AllowInsecure bool `json:"allow_insecure"`
|
||
DefaultTimeout time.Duration `json:"default_timeout"`
|
||
DefaultWebTimeout time.Duration `json:"default_web_timeout"`
|
||
DefaultUserAgent string `json:"default_user_agent"`
|
||
}
|
||
|
||
// DefaultNetworkParserOptions 默认网络解析器选项
|
||
func DefaultNetworkParserOptions() *NetworkParserOptions {
|
||
return &NetworkParserOptions{
|
||
ValidateProxies: DefaultValidateProxies,
|
||
AllowInsecure: DefaultAllowInsecure,
|
||
DefaultTimeout: DefaultNetworkTimeout,
|
||
DefaultWebTimeout: DefaultWebTimeout,
|
||
DefaultUserAgent: DefaultUserAgent,
|
||
}
|
||
}
|
||
|
||
// NewNetworkParser 创建网络配置解析器
|
||
func NewNetworkParser(options *NetworkParserOptions) *NetworkParser {
|
||
if options == nil {
|
||
options = DefaultNetworkParserOptions()
|
||
}
|
||
|
||
return &NetworkParser{
|
||
options: options,
|
||
}
|
||
}
|
||
|
||
// NetworkInput 网络配置输入参数
|
||
type NetworkInput struct {
|
||
// 代理配置
|
||
HttpProxy string `json:"http_proxy"`
|
||
Socks5Proxy string `json:"socks5_proxy"`
|
||
|
||
// 超时配置
|
||
Timeout int64 `json:"timeout"`
|
||
WebTimeout int64 `json:"web_timeout"`
|
||
|
||
// 网络选项
|
||
DisablePing bool `json:"disable_ping"`
|
||
DnsLog bool `json:"dns_log"`
|
||
UserAgent string `json:"user_agent"`
|
||
Cookie string `json:"cookie"`
|
||
}
|
||
|
||
// Parse 解析网络配置
|
||
func (np *NetworkParser) Parse(input *NetworkInput, options *ParserOptions) (*ParseResult, error) {
|
||
if input == nil {
|
||
return nil, NewParseError("INPUT_ERROR", "网络配置输入为空", "", 0, ErrEmptyInput)
|
||
}
|
||
|
||
startTime := time.Now()
|
||
result := &ParseResult{
|
||
Config: &ParsedConfig{
|
||
Network: &NetworkConfig{
|
||
EnableDNSLog: input.DnsLog,
|
||
DisablePing: input.DisablePing,
|
||
},
|
||
},
|
||
Success: true,
|
||
}
|
||
|
||
var errors []error
|
||
var warnings []string
|
||
|
||
// 解析HTTP代理
|
||
httpProxy, httpErrors, httpWarnings := np.parseHttpProxy(input.HttpProxy)
|
||
errors = append(errors, httpErrors...)
|
||
warnings = append(warnings, httpWarnings...)
|
||
|
||
// 解析Socks5代理
|
||
socks5Proxy, socks5Errors, socks5Warnings := np.parseSocks5Proxy(input.Socks5Proxy)
|
||
errors = append(errors, socks5Errors...)
|
||
warnings = append(warnings, socks5Warnings...)
|
||
|
||
// 解析超时配置
|
||
timeout, webTimeout, timeoutErrors, timeoutWarnings := np.parseTimeouts(input.Timeout, input.WebTimeout)
|
||
errors = append(errors, timeoutErrors...)
|
||
warnings = append(warnings, timeoutWarnings...)
|
||
|
||
// 解析用户代理
|
||
userAgent, uaErrors, uaWarnings := np.parseUserAgent(input.UserAgent)
|
||
errors = append(errors, uaErrors...)
|
||
warnings = append(warnings, uaWarnings...)
|
||
|
||
// 解析Cookie
|
||
cookie, cookieErrors, cookieWarnings := np.parseCookie(input.Cookie)
|
||
errors = append(errors, cookieErrors...)
|
||
warnings = append(warnings, cookieWarnings...)
|
||
|
||
// 检查代理冲突
|
||
if httpProxy != "" && socks5Proxy != "" {
|
||
warnings = append(warnings, "同时配置了HTTP代理和Socks5代理,Socks5代理将被优先使用")
|
||
}
|
||
|
||
// 更新配置
|
||
result.Config.Network.HttpProxy = httpProxy
|
||
result.Config.Network.Socks5Proxy = socks5Proxy
|
||
result.Config.Network.Timeout = timeout
|
||
result.Config.Network.WebTimeout = webTimeout
|
||
result.Config.Network.UserAgent = userAgent
|
||
result.Config.Network.Cookie = cookie
|
||
|
||
// 设置结果状态
|
||
result.Errors = errors
|
||
result.Warnings = warnings
|
||
result.ParseTime = time.Since(startTime)
|
||
result.Success = len(errors) == 0
|
||
|
||
return result, nil
|
||
}
|
||
|
||
// parseHttpProxy 解析HTTP代理配置
|
||
func (np *NetworkParser) parseHttpProxy(proxyStr string) (string, []error, []string) {
|
||
var errors []error
|
||
var warnings []string
|
||
|
||
if proxyStr == "" {
|
||
return "", nil, nil
|
||
}
|
||
|
||
// 处理简写形式
|
||
normalizedProxy := np.normalizeHttpProxy(proxyStr)
|
||
|
||
// 验证代理URL
|
||
if np.options.ValidateProxies {
|
||
if err := np.validateProxyURL(normalizedProxy); err != nil {
|
||
errors = append(errors, NewParseError(ErrorTypeProxyError, err.Error(), "http_proxy", 0, err))
|
||
return "", errors, warnings
|
||
}
|
||
}
|
||
|
||
return normalizedProxy, errors, warnings
|
||
}
|
||
|
||
// parseSocks5Proxy 解析Socks5代理配置
|
||
func (np *NetworkParser) parseSocks5Proxy(proxyStr string) (string, []error, []string) {
|
||
var errors []error
|
||
var warnings []string
|
||
|
||
if proxyStr == "" {
|
||
return "", nil, nil
|
||
}
|
||
|
||
// 处理简写形式
|
||
normalizedProxy := np.normalizeSocks5Proxy(proxyStr)
|
||
|
||
// 验证代理URL
|
||
if np.options.ValidateProxies {
|
||
if err := np.validateProxyURL(normalizedProxy); err != nil {
|
||
errors = append(errors, NewParseError(ErrorTypeProxyError, err.Error(), "socks5_proxy", 0, err))
|
||
return "", errors, warnings
|
||
}
|
||
}
|
||
|
||
// 使用Socks5代理时建议禁用Ping
|
||
if normalizedProxy != "" {
|
||
warnings = append(warnings, "使用Socks5代理时建议禁用Ping检测")
|
||
}
|
||
|
||
return normalizedProxy, errors, warnings
|
||
}
|
||
|
||
// parseTimeouts 解析超时配置
|
||
func (np *NetworkParser) parseTimeouts(timeout, webTimeout int64) (time.Duration, time.Duration, []error, []string) {
|
||
var errors []error
|
||
var warnings []string
|
||
|
||
// 处理普通超时
|
||
finalTimeout := np.options.DefaultTimeout
|
||
if timeout > 0 {
|
||
if timeout > MaxTimeoutSeconds {
|
||
warnings = append(warnings, "超时时间过长,建议不超过300秒")
|
||
}
|
||
finalTimeout = time.Duration(timeout) * time.Second
|
||
}
|
||
|
||
// 处理Web超时
|
||
finalWebTimeout := np.options.DefaultWebTimeout
|
||
if webTimeout > 0 {
|
||
if webTimeout > MaxWebTimeoutSeconds {
|
||
warnings = append(warnings, "Web超时时间过长,建议不超过120秒")
|
||
}
|
||
finalWebTimeout = time.Duration(webTimeout) * time.Second
|
||
}
|
||
|
||
// 验证超时配置合理性
|
||
if finalWebTimeout > finalTimeout {
|
||
warnings = append(warnings, i18n.GetText("config_web_timeout_warning"))
|
||
}
|
||
|
||
return finalTimeout, finalWebTimeout, errors, warnings
|
||
}
|
||
|
||
// parseUserAgent 解析用户代理
|
||
func (np *NetworkParser) parseUserAgent(userAgent string) (string, []error, []string) {
|
||
var errors []error
|
||
var warnings []string
|
||
|
||
if userAgent == "" {
|
||
return np.options.DefaultUserAgent, errors, warnings
|
||
}
|
||
|
||
// 基本格式验证
|
||
if len(userAgent) > MaxUserAgentLength {
|
||
errors = append(errors, NewParseError(ErrorTypeUserAgentError, "用户代理字符串过长", "user_agent", 0, nil))
|
||
return "", errors, warnings
|
||
}
|
||
|
||
// 检查是否包含特殊字符
|
||
if strings.ContainsAny(userAgent, InvalidUserAgentChars) {
|
||
errors = append(errors, NewParseError(ErrorTypeUserAgentError, "用户代理包含非法字符", "user_agent", 0, nil))
|
||
return "", errors, warnings
|
||
}
|
||
|
||
// 检查是否为常见浏览器用户代理
|
||
if !np.isValidUserAgent(userAgent) {
|
||
warnings = append(warnings, "用户代理格式可能不被目标服务器识别")
|
||
}
|
||
|
||
return userAgent, errors, warnings
|
||
}
|
||
|
||
// parseCookie 解析Cookie
|
||
func (np *NetworkParser) parseCookie(cookie string) (string, []error, []string) {
|
||
var errors []error
|
||
var warnings []string
|
||
|
||
if cookie == "" {
|
||
return "", errors, warnings
|
||
}
|
||
|
||
// 基本格式验证
|
||
if len(cookie) > MaxCookieLength { // HTTP Cookie长度限制
|
||
errors = append(errors, NewParseError(ErrorTypeCookieError, "Cookie字符串过长", "cookie", 0, nil))
|
||
return "", errors, warnings
|
||
}
|
||
|
||
// 检查Cookie格式
|
||
if !np.isValidCookie(cookie) {
|
||
warnings = append(warnings, "Cookie格式可能不正确")
|
||
}
|
||
|
||
return cookie, errors, warnings
|
||
}
|
||
|
||
// normalizeHttpProxy 规范化HTTP代理URL
|
||
func (np *NetworkParser) normalizeHttpProxy(proxy string) string {
|
||
switch strings.ToLower(proxy) {
|
||
case ProxyShortcut1:
|
||
return ProxyShortcutHTTP
|
||
case ProxyShortcut2:
|
||
return ProxyShortcutSOCKS5
|
||
default:
|
||
// 如果没有协议前缀,默认使用HTTP
|
||
if !strings.Contains(proxy, ProtocolPrefix) {
|
||
if strings.Contains(proxy, ":") {
|
||
return HTTPPrefix + proxy
|
||
} else {
|
||
return HTTPPrefix + "127.0.0.1:" + proxy
|
||
}
|
||
}
|
||
return proxy
|
||
}
|
||
}
|
||
|
||
// normalizeSocks5Proxy 规范化Socks5代理URL
|
||
func (np *NetworkParser) normalizeSocks5Proxy(proxy string) string {
|
||
// 如果没有协议前缀,添加SOCKS5协议
|
||
if !strings.HasPrefix(proxy, SOCKS5Prefix) {
|
||
if strings.Contains(proxy, ":") {
|
||
return SOCKS5Prefix + proxy
|
||
} else {
|
||
return SOCKS5Prefix + "127.0.0.1:" + proxy
|
||
}
|
||
}
|
||
return proxy
|
||
}
|
||
|
||
// validateProxyURL 验证代理URL格式
|
||
func (np *NetworkParser) validateProxyURL(proxyURL string) error {
|
||
if proxyURL == "" {
|
||
return nil
|
||
}
|
||
|
||
parsedURL, err := url.Parse(proxyURL)
|
||
if err != nil {
|
||
return fmt.Errorf("代理URL格式无效: %v", err)
|
||
}
|
||
|
||
// 检查协议
|
||
switch parsedURL.Scheme {
|
||
case ProtocolHTTP, ProtocolHTTPS, ProtocolSOCKS5:
|
||
// 支持的协议
|
||
default:
|
||
return fmt.Errorf("不支持的代理协议: %s", parsedURL.Scheme)
|
||
}
|
||
|
||
// 检查主机名
|
||
if parsedURL.Hostname() == "" {
|
||
return fmt.Errorf("代理主机名为空")
|
||
}
|
||
|
||
// 检查端口
|
||
portStr := parsedURL.Port()
|
||
if portStr != "" {
|
||
port, err := strconv.Atoi(portStr)
|
||
if err != nil {
|
||
return fmt.Errorf("代理端口号无效: %s", portStr)
|
||
}
|
||
if port < 1 || port > 65535 {
|
||
return fmt.Errorf("代理端口号超出范围: %d", port)
|
||
}
|
||
}
|
||
|
||
// 安全检查
|
||
if !np.options.AllowInsecure && parsedURL.Scheme == ProtocolHTTP {
|
||
return fmt.Errorf("不允许使用不安全的HTTP代理")
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// isValidUserAgent 检查用户代理是否有效
|
||
func (np *NetworkParser) isValidUserAgent(userAgent string) bool {
|
||
// 检查是否包含常见的浏览器标识
|
||
commonBrowsers := GetCommonBrowsers()
|
||
|
||
userAgentLower := strings.ToLower(userAgent)
|
||
for _, browser := range commonBrowsers {
|
||
if strings.Contains(userAgentLower, strings.ToLower(browser)) {
|
||
return true
|
||
}
|
||
}
|
||
|
||
return false
|
||
}
|
||
|
||
// isValidCookie 检查Cookie格式是否有效
|
||
func (np *NetworkParser) isValidCookie(cookie string) bool {
|
||
// 基本Cookie格式检查 (name=value; name2=value2)
|
||
return CompiledCookieRegex.MatchString(strings.TrimSpace(cookie))
|
||
}
|
||
|
||
// =============================================================================================
|
||
// 已删除的死代码(未使用):Validate 和 GetStatistics 方法
|
||
// =============================================================================================
|