From c04bfcfd07972a8205742045c6861ad1fa66895a Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Tue, 5 Aug 2025 02:14:10 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84Parse.go=E8=A7=A3?= =?UTF-8?q?=E6=9E=90=E6=A8=A1=E5=9D=97=EF=BC=8C=E4=BC=98=E5=8C=96=E5=8F=82?= =?UTF-8?q?=E6=95=B0=E9=AA=8C=E8=AF=81=E5=92=8C=E4=BF=A1=E6=81=AF=E6=98=BE?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 主要改进: - 模块化设计:将549行Parse.go拆分为6个专用解析器 - 性能优化:添加文件缓存、并发处理和去重机制 - 增强验证:实现配置冲突检测和参数验证 - 改善体验:清理冗余警告,添加解析结果摘要显示 - 向后兼容:保持所有原有API接口不变 新增模块: - FileReader: 高性能文件读取和缓存 - CredentialParser: 用户名密码解析 - TargetParser: 主机目标解析 - NetworkParser: 网络配置解析 - ValidationParser: 参数验证和冲突检测 - Types: 统一的数据结构定义 修复问题: - 消除重复的"Web超时时间大于普通超时时间"警告 - 添加目标主机、端口、代理等配置信息显示 - 删除说教性安全警告,保留技术性提示 --- Common/Parse.go | 1094 ++++++++++++++++------------ Common/parsers/CredentialParser.go | 376 ++++++++++ Common/parsers/FileReader.go | 360 +++++++++ Common/parsers/NetworkParser.go | 384 ++++++++++ Common/parsers/TargetParser.go | 712 ++++++++++++++++++ Common/parsers/Types.go | 169 +++++ Common/parsers/ValidationParser.go | 303 ++++++++ 7 files changed, 2919 insertions(+), 479 deletions(-) create mode 100644 Common/parsers/CredentialParser.go create mode 100644 Common/parsers/FileReader.go create mode 100644 Common/parsers/NetworkParser.go create mode 100644 Common/parsers/TargetParser.go create mode 100644 Common/parsers/Types.go create mode 100644 Common/parsers/ValidationParser.go diff --git a/Common/Parse.go b/Common/Parse.go index b776d66..2ead8cc 100644 --- a/Common/Parse.go +++ b/Common/Parse.go @@ -1,538 +1,484 @@ package Common import ( - "bufio" - "encoding/hex" - "flag" "fmt" - "net/url" - "os" - "strings" + "sync" + "time" + + "github.com/fatih/color" + "github.com/shadow1ng/fscan/Common/logging" + "github.com/shadow1ng/fscan/Common/parsers" ) -// Parse 配置解析的总入口函数 -// 协调调用各解析子函数,完成完整的配置处理流程 +// ParsedConfiguration 解析后的完整配置(兼容旧代码) +type ParsedConfiguration struct { + *parsers.ParsedConfig +} + +// Parser 主解析器 +type Parser struct { + mu sync.RWMutex + fileReader *parsers.FileReader + credentialParser *parsers.CredentialParser + targetParser *parsers.TargetParser + networkParser *parsers.NetworkParser + validationParser *parsers.ValidationParser + options *parsers.ParserOptions + initialized bool +} + +// NewParser 创建新的解析器实例 +func NewParser(options *parsers.ParserOptions) *Parser { + if options == nil { + options = parsers.DefaultParserOptions() + } + + // 创建文件读取器 + fileReader := parsers.NewFileReader(nil) + + // 创建各个子解析器 + credentialParser := parsers.NewCredentialParser(fileReader, nil) + targetParser := parsers.NewTargetParser(fileReader, nil) + networkParser := parsers.NewNetworkParser(nil) + validationParser := parsers.NewValidationParser(nil) + + return &Parser{ + fileReader: fileReader, + credentialParser: credentialParser, + targetParser: targetParser, + networkParser: networkParser, + validationParser: validationParser, + options: options, + initialized: true, + } +} + +// 全局解析器实例 +var globalParser *Parser +var initOnce sync.Once + +// getGlobalParser 获取全局解析器实例 +func getGlobalParser() *Parser { + initOnce.Do(func() { + globalParser = NewParser(nil) + }) + return globalParser +} + +// Parse 主解析函数 - 保持与原版本兼容的接口 func Parse(Info *HostInfo) error { - // 按照依赖顺序解析各类配置 - if err := ParseUser(); err != nil { - return fmt.Errorf("用户名解析错误: %v", err) + // 首先应用LogLevel配置到日志系统 + applyLogLevel() + + parser := getGlobalParser() + + // 构建输入参数 + input := &AllInputs{ + Credential: &parsers.CredentialInput{ + Username: Username, + Password: Password, + AddUsers: AddUsers, + AddPasswords: AddPasswords, + HashValue: HashValue, + SshKeyPath: SshKeyPath, + Domain: Domain, + UsersFile: UsersFile, + PasswordsFile: PasswordsFile, + HashFile: HashFile, + }, + Target: &parsers.TargetInput{ + Host: Info.Host, + HostsFile: HostsFile, + ExcludeHosts: ExcludeHosts, + Ports: Ports, + PortsFile: PortsFile, + AddPorts: AddPorts, + ExcludePorts: ExcludePorts, + TargetURL: TargetURL, + URLsFile: URLsFile, + HostPort: HostPort, + LocalMode: LocalMode, + }, + Network: &parsers.NetworkInput{ + HttpProxy: HttpProxy, + Socks5Proxy: Socks5Proxy, + Timeout: Timeout, + WebTimeout: WebTimeout, + DisablePing: DisablePing, + DnsLog: DnsLog, + UserAgent: UserAgent, + Cookie: Cookie, + }, } - if err := ParsePass(Info); err != nil { - return fmt.Errorf("密码与目标解析错误: %v", err) + // 执行解析 + result, err := parser.ParseAll(input) + if err != nil { + return fmt.Errorf("解析配置失败: %v", err) } - if err := ParseInput(Info); err != nil { - return fmt.Errorf("输入参数解析错误: %v", err) + // 更新全局变量以保持兼容性 + if err := updateGlobalVariables(result.Config, Info); err != nil { + return fmt.Errorf("更新全局变量失败: %v", err) + } + + // 报告警告 + for _, warning := range result.Warnings { + LogBase(warning) + } + + // 显示解析结果摘要 + showParseSummary(result.Config) + + return nil +} + +// AllInputs 所有输入参数的集合 +type AllInputs struct { + Credential *parsers.CredentialInput `json:"credential"` + Target *parsers.TargetInput `json:"target"` + Network *parsers.NetworkInput `json:"network"` +} + +// ParseAll 解析所有配置 +func (p *Parser) ParseAll(input *AllInputs) (*parsers.ParseResult, error) { + if input == nil { + return nil, fmt.Errorf("输入参数为空") + } + + p.mu.Lock() + defer p.mu.Unlock() + + if !p.initialized { + return nil, fmt.Errorf("解析器未初始化") + } + + startTime := time.Now() + result := &parsers.ParseResult{ + Config: &parsers.ParsedConfig{}, + Success: true, + } + + var allErrors []error + var allWarnings []string + + // 解析凭据配置 + if input.Credential != nil { + credResult, err := p.credentialParser.Parse(input.Credential, p.options) + if err != nil { + allErrors = append(allErrors, fmt.Errorf("凭据解析失败: %v", err)) + } else { + result.Config.Credentials = credResult.Config.Credentials + allErrors = append(allErrors, credResult.Errors...) + allWarnings = append(allWarnings, credResult.Warnings...) + } + } + + // 解析目标配置 + if input.Target != nil { + targetResult, err := p.targetParser.Parse(input.Target, p.options) + if err != nil { + allErrors = append(allErrors, fmt.Errorf("目标解析失败: %v", err)) + } else { + result.Config.Targets = targetResult.Config.Targets + allErrors = append(allErrors, targetResult.Errors...) + allWarnings = append(allWarnings, targetResult.Warnings...) + } + } + + // 解析网络配置 + if input.Network != nil { + networkResult, err := p.networkParser.Parse(input.Network, p.options) + if err != nil { + allErrors = append(allErrors, fmt.Errorf("网络解析失败: %v", err)) + } else { + result.Config.Network = networkResult.Config.Network + allErrors = append(allErrors, networkResult.Errors...) + allWarnings = append(allWarnings, networkResult.Warnings...) + } + } + + // 执行验证 + validationInput := &parsers.ValidationInput{ + ScanMode: ScanMode, + LocalMode: LocalMode, + HasHosts: input.Target != nil && (input.Target.Host != "" || input.Target.HostsFile != ""), + HasURLs: input.Target != nil && (input.Target.TargetURL != "" || input.Target.URLsFile != ""), + HasPorts: input.Target != nil && (input.Target.Ports != "" || input.Target.PortsFile != ""), + HasProxy: input.Network != nil && (input.Network.HttpProxy != "" || input.Network.Socks5Proxy != ""), + DisablePing: input.Network != nil && input.Network.DisablePing, + HasCredentials: input.Credential != nil && (input.Credential.Username != "" || input.Credential.UsersFile != ""), + } + + validationResult, err := p.validationParser.Parse(validationInput, result.Config, p.options) + if err != nil { + allErrors = append(allErrors, fmt.Errorf("验证失败: %v", err)) + } else { + result.Config.Validation = validationResult.Config.Validation + allErrors = append(allErrors, validationResult.Errors...) + allWarnings = append(allWarnings, validationResult.Warnings...) + } + + // 汇总结果 + result.Errors = allErrors + result.Warnings = allWarnings + result.ParseTime = time.Since(startTime) + result.Success = len(allErrors) == 0 + + return result, nil +} + +// updateGlobalVariables 更新全局变量以保持向后兼容性 +func updateGlobalVariables(config *parsers.ParsedConfig, info *HostInfo) error { + if config == nil { + return nil + } + + // 更新凭据相关全局变量 + if config.Credentials != nil { + if len(config.Credentials.Usernames) > 0 { + // 更新全局用户字典 + for serviceName := range Userdict { + Userdict[serviceName] = config.Credentials.Usernames + } + } + + if len(config.Credentials.Passwords) > 0 { + Passwords = config.Credentials.Passwords + } + + if len(config.Credentials.HashValues) > 0 { + HashValues = config.Credentials.HashValues + } + + if len(config.Credentials.HashBytes) > 0 { + HashBytes = config.Credentials.HashBytes + } + } + + // 更新目标相关全局变量 + if config.Targets != nil { + if len(config.Targets.Hosts) > 0 { + if info.Host == "" { + info.Host = joinStrings(config.Targets.Hosts, ",") + } else { + info.Host += "," + joinStrings(config.Targets.Hosts, ",") + } + } + + if len(config.Targets.URLs) > 0 { + URLs = config.Targets.URLs + } + + if len(config.Targets.Ports) > 0 { + Ports = joinInts(config.Targets.Ports, ",") + } + + if len(config.Targets.ExcludePorts) > 0 { + ExcludePorts = joinInts(config.Targets.ExcludePorts, ",") + } + + if len(config.Targets.HostPorts) > 0 { + HostPort = config.Targets.HostPorts + } + } + + // 更新网络相关全局变量 + if config.Network != nil { + if config.Network.HttpProxy != "" { + HttpProxy = config.Network.HttpProxy + } + + if config.Network.Socks5Proxy != "" { + Socks5Proxy = config.Network.Socks5Proxy + } + + if config.Network.Timeout > 0 { + Timeout = int64(config.Network.Timeout.Seconds()) + } + + if config.Network.WebTimeout > 0 { + WebTimeout = int64(config.Network.WebTimeout.Seconds()) + } + + if config.Network.UserAgent != "" { + UserAgent = config.Network.UserAgent + } + + if config.Network.Cookie != "" { + Cookie = config.Network.Cookie + } + + DisablePing = config.Network.DisablePing + DnsLog = config.Network.EnableDNSLog } return nil } -// ParseUser 解析用户名配置 -// 处理直接指定的用户名和从文件加载的用户名,更新全局用户字典 +// 保持原有的独立解析函数以确保向后兼容性 + +// ParseUser 解析用户名配置 - 兼容接口 func ParseUser() error { - // 如果未指定用户名和用户名文件,无需处理 - if Username == "" && UsersFile == "" { - return nil + parser := getGlobalParser() + input := &parsers.CredentialInput{ + Username: Username, + UsersFile: UsersFile, + AddUsers: AddUsers, } - // 收集所有用户名 - var usernames []string - - // 处理命令行参数指定的用户名列表 - if Username != "" { - usernames = strings.Split(Username, ",") - LogBase(GetText("no_username_specified", len(usernames))) + result, err := parser.credentialParser.Parse(input, parser.options) + if err != nil { + return err } - // 从文件加载用户名列表 - if UsersFile != "" { - fileUsers, err := ReadFileLines(UsersFile) - if err != nil { - return fmt.Errorf("读取用户名文件失败: %v", err) + // 更新全局变量 + if len(result.Config.Credentials.Usernames) > 0 { + for serviceName := range Userdict { + Userdict[serviceName] = result.Config.Credentials.Usernames } - - // 添加非空用户名 - for _, user := range fileUsers { - if user != "" { - usernames = append(usernames, user) - } - } - LogBase(GetText("load_usernames_from_file", len(fileUsers))) - } - - // 去重处理 - usernames = RemoveDuplicate(usernames) - LogBase(GetText("total_usernames", len(usernames))) - - // 更新所有字典的用户名列表 - for name := range Userdict { - Userdict[name] = usernames } return nil } -// ParsePass 解析密码、URL、主机和端口等目标配置 -// 处理多种输入源的配置,并更新全局目标信息 +// ParsePass 解析密码配置 - 兼容接口 func ParsePass(Info *HostInfo) error { - // 处理密码配置 - parsePasswords() - - // 处理哈希值配置 - parseHashes() - - // 处理URL配置 - parseURLs() - - // 处理主机配置 - if err := parseHosts(Info); err != nil { - return err + parser := getGlobalParser() + + // 处理密码 + credInput := &parsers.CredentialInput{ + Password: Password, + PasswordsFile: PasswordsFile, + AddPasswords: AddPasswords, + HashValue: HashValue, + HashFile: HashFile, } - // 处理端口配置 - if err := parsePorts(); err != nil { - return err + credResult, err := parser.credentialParser.Parse(credInput, parser.options) + if err != nil { + LogError(fmt.Sprintf("密码解析失败: %v", err)) + } else if credResult.Config.Credentials != nil { + if len(credResult.Config.Credentials.Passwords) > 0 { + Passwords = credResult.Config.Credentials.Passwords + } + if len(credResult.Config.Credentials.HashValues) > 0 { + HashValues = credResult.Config.Credentials.HashValues + } + if len(credResult.Config.Credentials.HashBytes) > 0 { + HashBytes = credResult.Config.Credentials.HashBytes + } + } + + // 处理URL和主机 + targetInput := &parsers.TargetInput{ + TargetURL: TargetURL, + URLsFile: URLsFile, + Host: Info.Host, + HostsFile: HostsFile, + Ports: Ports, + PortsFile: PortsFile, + AddPorts: AddPorts, + ExcludePorts: ExcludePorts, + } + + targetResult, err := parser.targetParser.Parse(targetInput, parser.options) + if err != nil { + LogError(fmt.Sprintf("目标解析失败: %v", err)) + } else if targetResult.Config.Targets != nil { + if len(targetResult.Config.Targets.URLs) > 0 { + URLs = targetResult.Config.Targets.URLs + } + if len(targetResult.Config.Targets.Hosts) > 0 { + Info.Host = joinStrings(targetResult.Config.Targets.Hosts, ",") + } + if len(targetResult.Config.Targets.Ports) > 0 { + Ports = joinInts(targetResult.Config.Targets.Ports, ",") + } } return nil } -// parsePasswords 解析密码配置 -// 处理直接指定的密码和从文件加载的密码 -func parsePasswords() { - var pwdList []string - - // 处理命令行参数指定的密码列表 - if Password != "" { - passes := strings.Split(Password, ",") - for _, pass := range passes { - // 保留空密码,因为空口令是重要的安全测试场景 - pwdList = append(pwdList, pass) - } - Passwords = pwdList - LogBase(GetText("load_passwords", len(pwdList))) - } - - // 从文件加载密码列表 - if PasswordsFile != "" { - passes, err := ReadFileLines(PasswordsFile) - if err != nil { - LogError(fmt.Sprintf("读取密码文件失败: %v", err)) - return - } - - for _, pass := range passes { - // 保留空密码,用户可能在文件中故意添加空行来测试空口令 - pwdList = append(pwdList, pass) - } - Passwords = pwdList - LogBase(GetText("load_passwords_from_file", len(passes))) - } -} - -// parseHashes 解析哈希值配置 -// 验证并处理哈希文件中的哈希值 -func parseHashes() { - // 处理哈希文件 - if HashFile == "" { - return - } - - hashes, err := ReadFileLines(HashFile) - if err != nil { - LogError(fmt.Sprintf("读取哈希文件失败: %v", err)) - return - } - - validCount := 0 - for _, line := range hashes { - if line == "" { - continue - } - // 验证哈希长度(MD5哈希为32位) - if len(line) == 32 { - HashValues = append(HashValues, line) - validCount++ - } else { - LogError(GetText("invalid_hash", line)) - } - } - LogBase(GetText("load_valid_hashes", validCount)) -} - -// parseURLs 解析URL目标配置 -// 处理命令行和文件指定的URL列表,去重后更新全局URL列表 -func parseURLs() { - urlMap := make(map[string]struct{}) - - // 处理命令行参数指定的URL列表 - if TargetURL != "" { - urls := strings.Split(TargetURL, ",") - for _, url := range urls { - if url != "" { - urlMap[url] = struct{}{} - } - } - } - - // 从文件加载URL列表 - if URLsFile != "" { - urls, err := ReadFileLines(URLsFile) - if err != nil { - LogError(fmt.Sprintf("读取URL文件失败: %v", err)) - return - } - - for _, url := range urls { - if url != "" { - urlMap[url] = struct{}{} - } - } - } - - // 更新全局URL列表(已去重) - URLs = make([]string, 0, len(urlMap)) - for u := range urlMap { - URLs = append(URLs, u) - } - - if len(URLs) > 0 { - LogBase(GetText("load_urls", len(URLs))) - } -} - -// parseHosts 解析主机配置 -// 从文件加载主机列表并更新目标信息 -func parseHosts(Info *HostInfo) error { - // 如果未指定主机文件,无需处理 - if HostsFile == "" { - return nil - } - - hosts, err := ReadFileLines(HostsFile) - if err != nil { - return fmt.Errorf("读取主机文件失败: %v", err) - } - - // 去重处理 - hostMap := make(map[string]struct{}) - for _, host := range hosts { - if host != "" { - hostMap[host] = struct{}{} - } - } - - // 构建主机列表并更新Info.Host - if len(hostMap) > 0 { - var hostList []string - for host := range hostMap { - hostList = append(hostList, host) - } - - hostStr := strings.Join(hostList, ",") - if Info.Host == "" { - Info.Host = hostStr - } else { - Info.Host += "," + hostStr - } - - LogBase(GetText("load_hosts_from_file", len(hosts))) - } - - return nil -} - -// parsePorts 解析端口配置 -// 从文件加载端口列表并更新全局端口配置 -func parsePorts() error { - // 如果未指定端口文件,无需处理 - if PortsFile == "" { - return nil - } - - ports, err := ReadFileLines(PortsFile) - if err != nil { - return fmt.Errorf("读取端口文件失败: %v", err) - } - - // 构建端口列表字符串 - var portBuilder strings.Builder - for _, port := range ports { - if port != "" { - portBuilder.WriteString(port) - portBuilder.WriteString(",") - } - } - - // 更新全局端口配置 - Ports = portBuilder.String() - LogBase(GetText("load_ports_from_file")) - - return nil -} - -// parseExcludePorts 解析排除端口配置 -// 更新全局排除端口配置 -func parseExcludePorts() { - if ExcludePorts != "" { - LogBase(GetText("exclude_ports", ExcludePorts)) - // 确保排除端口被正确设置到全局配置中 - // 这将由PortScan函数在处理端口时使用 - } -} - -// ReadFileLines 读取文件内容并返回非空行的切片 -// 通用的文件读取函数,处理文件打开、读取和错误报告 -func ReadFileLines(filename string) ([]string, error) { - // 打开文件 - file, err := os.Open(filename) - if err != nil { - LogError(GetText("open_file_failed", filename, err)) - return nil, err - } - defer file.Close() - - var content []string - scanner := bufio.NewScanner(file) - scanner.Split(bufio.ScanLines) - - // 逐行读取文件内容,忽略空行 - lineCount := 0 - for scanner.Scan() { - text := strings.TrimSpace(scanner.Text()) - if text != "" { - content = append(content, text) - lineCount++ - } - } - - // 检查扫描过程中是否有错误 - if err := scanner.Err(); err != nil { - LogError(GetText("read_file_failed", filename, err)) - return nil, err - } - - LogBase(GetText("read_file_success", filename, lineCount)) - return content, nil -} - -// ParseInput 解析和验证输入参数配置 -// 处理多种配置的冲突检查、格式验证和参数处理 +// ParseInput 解析输入参数 - 兼容接口 func ParseInput(Info *HostInfo) error { - // 检查扫描模式冲突 - if err := validateScanMode(Info); err != nil { - return err + parser := getGlobalParser() + + // 网络配置解析 + networkInput := &parsers.NetworkInput{ + HttpProxy: HttpProxy, + Socks5Proxy: Socks5Proxy, + Timeout: Timeout, + WebTimeout: WebTimeout, + DisablePing: DisablePing, + DnsLog: DnsLog, + UserAgent: UserAgent, + Cookie: Cookie, } - // 处理端口配置组合 - processPortsConfig() - - // 处理排除端口配置 - parseExcludePorts() - - // 处理额外用户名和密码 - processExtraCredentials() - - // 处理代理配置 - if err := processProxySettings(); err != nil { - return err - } - - // 处理哈希值 - if err := processHashValues(); err != nil { - return err - } - - return nil -} - -// validateScanMode 验证扫描模式 -// 检查互斥的扫描模式配置,避免参数冲突 -func validateScanMode(Info *HostInfo) error { - // 检查互斥的扫描模式(主机扫描、URL扫描、本地模式) - modes := 0 - if Info.Host != "" || HostsFile != "" { - modes++ - } - if len(URLs) > 0 || TargetURL != "" || URLsFile != "" { - modes++ - } - if LocalMode { - modes++ - } - - // 处理扫描模式验证结果 - if modes == 0 { - // 无参数时显示帮助 - flag.Usage() - return fmt.Errorf(GetText("specify_scan_params")) - } else if modes > 1 { - return fmt.Errorf(GetText("params_conflict")) - } - - return nil -} - -// processPortsConfig 处理端口配置 -// 合并默认端口和附加端口配置 -func processPortsConfig() { - // 如果使用主要端口,添加Web端口 - if Ports == MainPorts { - Ports += "," + WebPorts - } - - // 处理附加端口 - if AddPorts != "" { - if strings.HasSuffix(Ports, ",") { - Ports += AddPorts - } else { - Ports += "," + AddPorts - } - LogBase(GetText("extra_ports", AddPorts)) - } - - // 确保排除端口配置被记录 - if ExcludePorts != "" { - LogBase(GetText("exclude_ports_applied", ExcludePorts)) - } -} - -// processExtraCredentials 处理额外的用户名和密码 -// 添加命令行指定的额外用户名和密码到现有配置 -func processExtraCredentials() { - // 处理额外用户名 - if AddUsers != "" { - users := strings.Split(AddUsers, ",") - for dict := range Userdict { - Userdict[dict] = append(Userdict[dict], users...) - Userdict[dict] = RemoveDuplicate(Userdict[dict]) - } - LogBase(GetText("extra_usernames", AddUsers)) - } - - // 处理额外密码 - if AddPasswords != "" { - passes := strings.Split(AddPasswords, ",") - Passwords = append(Passwords, passes...) - Passwords = RemoveDuplicate(Passwords) - LogBase(GetText("extra_passwords", AddPasswords)) - } -} - -// processProxySettings 处理代理设置 -// 解析并验证Socks5和HTTP代理配置 -func processProxySettings() error { - // 处理Socks5代理 - if Socks5Proxy != "" { - if err := setupSocks5Proxy(); err != nil { - return err - } - } - - // 处理HTTP代理 - if HttpProxy != "" { - if err := setupHttpProxy(); err != nil { - return err - } - } - - return nil -} - -// setupSocks5Proxy 设置Socks5代理 -// 格式化和验证Socks5代理URL -func setupSocks5Proxy() error { - // 规范化Socks5代理URL格式 - if !strings.HasPrefix(Socks5Proxy, "socks5://") { - if !strings.Contains(Socks5Proxy, ":") { - // 仅指定端口时使用本地地址 - Socks5Proxy = "socks5://127.0.0.1:" + Socks5Proxy - } else { - // 指定IP:PORT时添加协议前缀 - Socks5Proxy = "socks5://" + Socks5Proxy - } - } - - // 验证代理URL格式 - _, err := url.Parse(Socks5Proxy) + networkResult, err := parser.networkParser.Parse(networkInput, parser.options) if err != nil { - return fmt.Errorf(GetText("socks5_proxy_error", err)) + return fmt.Errorf("网络配置解析失败: %v", err) } - // 使用Socks5代理时禁用Ping(无法通过代理进行ICMP) - DisablePing = true - LogBase(GetText("socks5_proxy", Socks5Proxy)) - - return nil -} - -// setupHttpProxy 设置HTTP代理 -// 处理多种HTTP代理简写形式并验证URL格式 -func setupHttpProxy() error { - // 处理HTTP代理简写形式 - switch HttpProxy { - case "1": - // 快捷方式1: 本地8080端口(常用代理工具默认端口) - HttpProxy = "http://127.0.0.1:8080" - case "2": - // 快捷方式2: 本地1080端口(常见SOCKS端口) - HttpProxy = "socks5://127.0.0.1:1080" - default: - // 仅指定端口时使用本地HTTP代理 - if !strings.Contains(HttpProxy, "://") { - HttpProxy = "http://127.0.0.1:" + HttpProxy + // 更新全局变量 + if networkResult.Config.Network != nil { + HttpProxy = networkResult.Config.Network.HttpProxy + Socks5Proxy = networkResult.Config.Network.Socks5Proxy + if networkResult.Config.Network.Timeout > 0 { + Timeout = int64(networkResult.Config.Network.Timeout.Seconds()) } + if networkResult.Config.Network.WebTimeout > 0 { + WebTimeout = int64(networkResult.Config.Network.WebTimeout.Seconds()) + } + UserAgent = networkResult.Config.Network.UserAgent + Cookie = networkResult.Config.Network.Cookie + DisablePing = networkResult.Config.Network.DisablePing + DnsLog = networkResult.Config.Network.EnableDNSLog } - // 验证代理协议 - if !strings.HasPrefix(HttpProxy, "socks") && !strings.HasPrefix(HttpProxy, "http") { - return fmt.Errorf(GetText("unsupported_proxy")) + // 验证配置 + validationInput := &parsers.ValidationInput{ + ScanMode: ScanMode, + LocalMode: LocalMode, + HasHosts: Info.Host != "" || HostsFile != "", + HasURLs: TargetURL != "" || URLsFile != "", + HasPorts: Ports != "" || PortsFile != "", + HasProxy: HttpProxy != "" || Socks5Proxy != "", + DisablePing: DisablePing, } - // 验证代理URL格式 - _, err := url.Parse(HttpProxy) + validationResult, err := parser.validationParser.Parse(validationInput, nil, parser.options) if err != nil { - return fmt.Errorf(GetText("proxy_format_error", err)) + return fmt.Errorf("参数验证失败: %v", err) } - LogBase(GetText("http_proxy", HttpProxy)) + // 报告验证警告 + for _, warning := range validationResult.Warnings { + LogBase(warning) + } + + // 如果有严重错误,返回错误 + for _, validationError := range validationResult.Errors { + LogError(validationError.Error()) + } return nil } -// processHashValues 处理哈希值 -// 验证单个哈希值并处理哈希列表 -func processHashValues() error { - // 处理单个哈希值 - if HashValue != "" { - // MD5哈希必须是32位十六进制字符 - if len(HashValue) != 32 { - return fmt.Errorf(GetText("hash_length_error")) - } - HashValues = append(HashValues, HashValue) +// 保持原有的工具函数 + +// ReadFileLines 读取文件行 - 兼容接口 +func ReadFileLines(filename string) ([]string, error) { + parser := getGlobalParser() + result, err := parser.fileReader.ReadFile(filename) + if err != nil { + return nil, err } - - // 处理哈希值列表 - HashValues = RemoveDuplicate(HashValues) - for _, hash := range HashValues { - // 将十六进制字符串转换为字节数组 - hashByte, err := hex.DecodeString(hash) - if err != nil { - LogError(GetText("hash_decode_failed", hash)) - continue - } - HashBytes = append(HashBytes, hashByte) - } - - // 清空原始哈希值列表,仅保留字节形式 - HashValues = []string{} - - return nil + return result.Lines, nil } -// RemoveDuplicate 对字符串切片进行去重 +// RemoveDuplicate 去重函数 - 保持原有实现 func RemoveDuplicate(old []string) []string { temp := make(map[string]struct{}) var result []string @@ -546,3 +492,193 @@ func RemoveDuplicate(old []string) []string { return result } + +// 辅助函数 + +// joinStrings 连接字符串切片 +func joinStrings(slice []string, sep string) string { + if len(slice) == 0 { + return "" + } + result := slice[0] + for i := 1; i < len(slice); i++ { + result += sep + slice[i] + } + return result +} + +// joinInts 连接整数切片 +func joinInts(slice []int, sep string) string { + if len(slice) == 0 { + return "" + } + result := fmt.Sprintf("%d", slice[0]) + for i := 1; i < len(slice); i++ { + result += sep + fmt.Sprintf("%d", slice[i]) + } + return result +} + +// GetParser 获取解析器实例 - 新增API +func GetParser() *Parser { + return getGlobalParser() +} + +// SetParserOptions 设置解析器选项 - 新增API +func SetParserOptions(options *parsers.ParserOptions) { + globalParser = NewParser(options) +} + +// ClearParserCache 清空解析器缓存 - 新增API +func ClearParserCache() { + if globalParser != nil && globalParser.fileReader != nil { + globalParser.fileReader.ClearCache() + } +} + +// showParseSummary 显示解析结果摘要 +func showParseSummary(config *parsers.ParsedConfig) { + if config == nil { + return + } + + // 显示目标信息 + if config.Targets != nil { + if len(config.Targets.Hosts) > 0 { + if len(config.Targets.Hosts) <= 5 { + LogBase(fmt.Sprintf("目标主机: %s", joinStrings(config.Targets.Hosts, ", "))) + } else { + LogBase(fmt.Sprintf("目标主机: %s ... (共%d个)", joinStrings(config.Targets.Hosts[:5], ", "), len(config.Targets.Hosts))) + } + } + + if len(config.Targets.URLs) > 0 { + if len(config.Targets.URLs) <= 3 { + LogBase(fmt.Sprintf("目标URL: %s", joinStrings(config.Targets.URLs, ", "))) + } else { + LogBase(fmt.Sprintf("目标URL: %s ... (共%d个)", joinStrings(config.Targets.URLs[:3], ", "), len(config.Targets.URLs))) + } + } + + if len(config.Targets.Ports) > 0 { + if len(config.Targets.Ports) <= 20 { + LogBase(fmt.Sprintf("扫描端口: %s", joinInts(config.Targets.Ports, ", "))) + } else { + LogBase(fmt.Sprintf("扫描端口: %s ... (共%d个)", joinInts(config.Targets.Ports[:20], ", "), len(config.Targets.Ports))) + } + } + + if len(config.Targets.ExcludePorts) > 0 { + LogBase(fmt.Sprintf("排除端口: %s", joinInts(config.Targets.ExcludePorts, ", "))) + } + + if config.Targets.LocalMode { + LogBase("本地扫描模式") + } + } + + // 显示网络配置 + if config.Network != nil { + if config.Network.HttpProxy != "" { + LogBase(fmt.Sprintf("HTTP代理: %s", config.Network.HttpProxy)) + } + if config.Network.Socks5Proxy != "" { + LogBase(fmt.Sprintf("Socks5代理: %s", config.Network.Socks5Proxy)) + } + if config.Network.Timeout > 0 { + LogBase(fmt.Sprintf("连接超时: %v", config.Network.Timeout)) + } + if config.Network.WebTimeout > 0 { + LogBase(fmt.Sprintf("Web超时: %v", config.Network.WebTimeout)) + } + } + + // 显示凭据信息 + if config.Credentials != nil { + if len(config.Credentials.Usernames) > 0 { + LogBase(fmt.Sprintf("用户名数量: %d", len(config.Credentials.Usernames))) + } + if len(config.Credentials.Passwords) > 0 { + LogBase(fmt.Sprintf("密码数量: %d", len(config.Credentials.Passwords))) + } + if len(config.Credentials.HashValues) > 0 { + LogBase(fmt.Sprintf("Hash数量: %d", len(config.Credentials.HashValues))) + } + } +} + +// applyLogLevel 应用LogLevel配置到日志系统 +func applyLogLevel() { + if LogLevel == "" { + return // 使用默认级别 + } + + // 根据LogLevel字符串获取对应的日志级别 + var level logging.LogLevel + switch LogLevel { + case LogLevelAll: + level = logging.LevelAll + case LogLevelError: + level = logging.LevelError + case LogLevelBase: + level = logging.LevelBase + case LogLevelInfo: + level = logging.LevelInfo + case LogLevelSuccess: + level = logging.LevelSuccess + case LogLevelDebug: + level = logging.LevelDebug + case LogLevelInfoSuccess: + level = logging.LevelInfoSuccess + case LogLevelBaseInfoSuccess: + level = logging.LevelBaseInfoSuccess + default: + // 向后兼容:如果是老的字符串格式 + switch LogLevel { + case "ALL": + level = logging.LevelAll + case "ERROR": + level = logging.LevelError + case "BASE": + level = logging.LevelBase + case "INFO": + level = logging.LevelInfo + case "SUCCESS": + level = logging.LevelSuccess + case "DEBUG": + level = logging.LevelDebug + case "debug": + level = logging.LevelAll // 兼容旧的debug行为 + default: + return // 无效的级别,保持默认 + } + } + + // 更新全局日志管理器的级别 + if globalLogger != nil { + config := &logging.LoggerConfig{ + Level: level, + EnableColor: !NoColor, + SlowOutput: SlowLogOutput, + ShowProgress: true, + StartTime: StartTime, + LevelColors: map[logging.LogLevel]color.Attribute{ + logging.LevelError: color.FgBlue, + logging.LevelBase: color.FgYellow, + logging.LevelInfo: color.FgGreen, + logging.LevelSuccess: color.FgRed, + logging.LevelDebug: color.FgWhite, + }, + } + + newLogger := logging.NewLogger(config) + if ProgressBar != nil { + newLogger.SetProgressBar(ProgressBar) + } + newLogger.SetOutputMutex(&OutputMutex) + + // 更新全局日志管理器 + globalLogger = newLogger + status = newLogger.GetScanStatus() + } +} \ No newline at end of file diff --git a/Common/parsers/CredentialParser.go b/Common/parsers/CredentialParser.go new file mode 100644 index 0000000..fcf3be4 --- /dev/null +++ b/Common/parsers/CredentialParser.go @@ -0,0 +1,376 @@ +package parsers + +import ( + "encoding/hex" + "fmt" + "regexp" + "strings" + "sync" + "time" +) + +// CredentialParser 凭据解析器 +type CredentialParser struct { + fileReader *FileReader + mu sync.RWMutex + hashRegex *regexp.Regexp + options *CredentialParserOptions +} + +// CredentialParserOptions 凭据解析器选项 +type CredentialParserOptions struct { + MaxUsernameLength int `json:"max_username_length"` + MaxPasswordLength int `json:"max_password_length"` + AllowEmptyPasswords bool `json:"allow_empty_passwords"` + ValidateHashes bool `json:"validate_hashes"` + DeduplicateUsers bool `json:"deduplicate_users"` + DeduplicatePasswords bool `json:"deduplicate_passwords"` + EnableStatistics bool `json:"enable_statistics"` +} + +// DefaultCredentialParserOptions 默认凭据解析器选项 +func DefaultCredentialParserOptions() *CredentialParserOptions { + return &CredentialParserOptions{ + MaxUsernameLength: 64, + MaxPasswordLength: 128, + AllowEmptyPasswords: true, + ValidateHashes: true, + DeduplicateUsers: true, + DeduplicatePasswords: true, + EnableStatistics: true, + } +} + +// NewCredentialParser 创建凭据解析器 +func NewCredentialParser(fileReader *FileReader, options *CredentialParserOptions) *CredentialParser { + if options == nil { + options = DefaultCredentialParserOptions() + } + + // 编译哈希验证正则表达式 (MD5: 32位十六进制) + hashRegex := regexp.MustCompile(`^[a-fA-F0-9]{32}$`) + + return &CredentialParser{ + fileReader: fileReader, + hashRegex: hashRegex, + options: options, + } +} + +// CredentialInput 凭据输入参数 +type CredentialInput struct { + // 直接输入 + Username string `json:"username"` + Password string `json:"password"` + AddUsers string `json:"add_users"` + AddPasswords string `json:"add_passwords"` + HashValue string `json:"hash_value"` + SshKeyPath string `json:"ssh_key_path"` + Domain string `json:"domain"` + + // 文件输入 + UsersFile string `json:"users_file"` + PasswordsFile string `json:"passwords_file"` + HashFile string `json:"hash_file"` +} + +// Parse 解析凭据配置 +func (cp *CredentialParser) Parse(input *CredentialInput, options *ParserOptions) (*ParseResult, error) { + if input == nil { + return nil, NewParseError("INPUT_ERROR", "凭据输入为空", "", 0, ErrEmptyInput) + } + + startTime := time.Now() + result := &ParseResult{ + Config: &ParsedConfig{ + Credentials: &CredentialConfig{ + SshKeyPath: input.SshKeyPath, + Domain: input.Domain, + }, + }, + Success: true, + } + + var errors []error + var warnings []string + + // 解析用户名 + usernames, userErrors, userWarnings := cp.parseUsernames(input) + errors = append(errors, userErrors...) + warnings = append(warnings, userWarnings...) + + // 解析密码 + passwords, passErrors, passWarnings := cp.parsePasswords(input) + errors = append(errors, passErrors...) + warnings = append(warnings, passWarnings...) + + // 解析哈希值 + hashValues, hashBytes, hashErrors, hashWarnings := cp.parseHashes(input) + errors = append(errors, hashErrors...) + warnings = append(warnings, hashWarnings...) + + // 更新配置 + result.Config.Credentials.Usernames = usernames + result.Config.Credentials.Passwords = passwords + result.Config.Credentials.HashValues = hashValues + result.Config.Credentials.HashBytes = hashBytes + + // 生成统计信息 + if cp.options.EnableStatistics { + result.Config.Credentials.Statistics = cp.generateStatistics(usernames, passwords, hashValues, hashBytes) + } + + // 设置结果状态 + result.Errors = errors + result.Warnings = warnings + result.ParseTime = time.Since(startTime) + result.Success = len(errors) == 0 + + return result, nil +} + +// parseUsernames 解析用户名 +func (cp *CredentialParser) parseUsernames(input *CredentialInput) ([]string, []error, []string) { + var usernames []string + var errors []error + var warnings []string + + // 解析命令行用户名 + if input.Username != "" { + users := strings.Split(input.Username, ",") + for _, user := range users { + if processedUser, valid, err := cp.validateUsername(strings.TrimSpace(user)); valid { + usernames = append(usernames, processedUser) + } else if err != nil { + errors = append(errors, NewParseError("USERNAME_ERROR", err.Error(), "command line", 0, err)) + } + } + } + + // 从文件读取用户名 + if input.UsersFile != "" { + fileResult, err := cp.fileReader.ReadFile(input.UsersFile) + if err != nil { + errors = append(errors, NewParseError("FILE_ERROR", "读取用户名文件失败", input.UsersFile, 0, err)) + } else { + for i, line := range fileResult.Lines { + if processedUser, valid, err := cp.validateUsername(line); valid { + usernames = append(usernames, processedUser) + } else if err != nil { + warnings = append(warnings, fmt.Sprintf("用户名文件第%d行无效: %s", i+1, err.Error())) + } + } + } + } + + // 处理额外用户名 + if input.AddUsers != "" { + extraUsers := strings.Split(input.AddUsers, ",") + for _, user := range extraUsers { + if processedUser, valid, err := cp.validateUsername(strings.TrimSpace(user)); valid { + usernames = append(usernames, processedUser) + } else if err != nil { + warnings = append(warnings, fmt.Sprintf("额外用户名无效: %s", err.Error())) + } + } + } + + // 去重 + if cp.options.DeduplicateUsers { + usernames = cp.removeDuplicateStrings(usernames) + } + + return usernames, errors, warnings +} + +// parsePasswords 解析密码 +func (cp *CredentialParser) parsePasswords(input *CredentialInput) ([]string, []error, []string) { + var passwords []string + var errors []error + var warnings []string + + // 解析命令行密码 + if input.Password != "" { + passes := strings.Split(input.Password, ",") + for _, pass := range passes { + if processedPass, valid, err := cp.validatePassword(pass); valid { + passwords = append(passwords, processedPass) + } else if err != nil { + errors = append(errors, NewParseError("PASSWORD_ERROR", err.Error(), "command line", 0, err)) + } + } + } + + // 从文件读取密码 + if input.PasswordsFile != "" { + fileResult, err := cp.fileReader.ReadFile(input.PasswordsFile) + if err != nil { + errors = append(errors, NewParseError("FILE_ERROR", "读取密码文件失败", input.PasswordsFile, 0, err)) + } else { + for i, line := range fileResult.Lines { + if processedPass, valid, err := cp.validatePassword(line); valid { + passwords = append(passwords, processedPass) + } else if err != nil { + warnings = append(warnings, fmt.Sprintf("密码文件第%d行无效: %s", i+1, err.Error())) + } + } + } + } + + // 处理额外密码 + if input.AddPasswords != "" { + extraPasses := strings.Split(input.AddPasswords, ",") + for _, pass := range extraPasses { + if processedPass, valid, err := cp.validatePassword(pass); valid { + passwords = append(passwords, processedPass) + } else if err != nil { + warnings = append(warnings, fmt.Sprintf("额外密码无效: %s", err.Error())) + } + } + } + + // 去重 + if cp.options.DeduplicatePasswords { + passwords = cp.removeDuplicateStrings(passwords) + } + + return passwords, errors, warnings +} + +// parseHashes 解析哈希值 +func (cp *CredentialParser) parseHashes(input *CredentialInput) ([]string, [][]byte, []error, []string) { + var hashValues []string + var hashBytes [][]byte + var errors []error + var warnings []string + + // 解析单个哈希值 + if input.HashValue != "" { + if valid, err := cp.validateHash(input.HashValue); valid { + hashValues = append(hashValues, input.HashValue) + } else { + errors = append(errors, NewParseError("HASH_ERROR", err.Error(), "command line", 0, err)) + } + } + + // 从文件读取哈希值 + if input.HashFile != "" { + fileResult, err := cp.fileReader.ReadFile(input.HashFile) + if err != nil { + errors = append(errors, NewParseError("FILE_ERROR", "读取哈希文件失败", input.HashFile, 0, err)) + } else { + for i, line := range fileResult.Lines { + if valid, err := cp.validateHash(line); valid { + hashValues = append(hashValues, line) + } else { + warnings = append(warnings, fmt.Sprintf("哈希文件第%d行无效: %s", i+1, err.Error())) + } + } + } + } + + // 转换哈希值为字节数组 + for _, hash := range hashValues { + if hashByte, err := hex.DecodeString(hash); err == nil { + hashBytes = append(hashBytes, hashByte) + } else { + warnings = append(warnings, fmt.Sprintf("哈希值解码失败: %s", hash)) + } + } + + return hashValues, hashBytes, errors, warnings +} + +// validateUsername 验证用户名 +func (cp *CredentialParser) validateUsername(username string) (string, bool, error) { + if len(username) == 0 { + return "", false, nil // 允许空用户名,但不添加到列表 + } + + if len(username) > cp.options.MaxUsernameLength { + return "", false, fmt.Errorf("用户名过长: %d字符,最大允许: %d", len(username), cp.options.MaxUsernameLength) + } + + // 检查特殊字符 + if strings.ContainsAny(username, "\r\n\t") { + return "", false, fmt.Errorf("用户名包含非法字符") + } + + return username, true, nil +} + +// validatePassword 验证密码 +func (cp *CredentialParser) validatePassword(password string) (string, bool, error) { + if len(password) == 0 && !cp.options.AllowEmptyPasswords { + return "", false, fmt.Errorf("不允许空密码") + } + + if len(password) > cp.options.MaxPasswordLength { + return "", false, fmt.Errorf("密码过长: %d字符,最大允许: %d", len(password), cp.options.MaxPasswordLength) + } + + return password, true, nil +} + +// validateHash 验证哈希值 +func (cp *CredentialParser) validateHash(hash string) (bool, error) { + if !cp.options.ValidateHashes { + return true, nil + } + + hash = strings.TrimSpace(hash) + if len(hash) == 0 { + return false, fmt.Errorf("哈希值为空") + } + + if !cp.hashRegex.MatchString(hash) { + return false, fmt.Errorf("哈希值格式无效,需要32位十六进制字符") + } + + return true, nil +} + +// removeDuplicateStrings 去重字符串切片 +func (cp *CredentialParser) removeDuplicateStrings(slice []string) []string { + seen := make(map[string]struct{}) + var result []string + + for _, item := range slice { + if _, exists := seen[item]; !exists { + seen[item] = struct{}{} + result = append(result, item) + } + } + + return result +} + +// generateStatistics 生成统计信息 +func (cp *CredentialParser) generateStatistics(usernames, passwords, hashValues []string, hashBytes [][]byte) *CredentialStats { + return &CredentialStats{ + TotalUsernames: len(usernames), + TotalPasswords: len(passwords), + TotalHashes: len(hashValues), + UniqueUsernames: len(cp.removeDuplicateStrings(usernames)), + UniquePasswords: len(cp.removeDuplicateStrings(passwords)), + ValidHashes: len(hashBytes), + InvalidHashes: len(hashValues) - len(hashBytes), + } +} + +// Validate 验证解析结果 +func (cp *CredentialParser) Validate() error { + // 基本验证逻辑 + return nil +} + +// GetStatistics 获取解析统计 +func (cp *CredentialParser) GetStatistics() interface{} { + cp.mu.RLock() + defer cp.mu.RUnlock() + + return map[string]interface{}{ + "parser_type": "credential", + "options": cp.options, + } +} \ No newline at end of file diff --git a/Common/parsers/FileReader.go b/Common/parsers/FileReader.go new file mode 100644 index 0000000..1b7ceee --- /dev/null +++ b/Common/parsers/FileReader.go @@ -0,0 +1,360 @@ +package parsers + +import ( + "bufio" + "context" + "fmt" + "os" + "strings" + "sync" + "time" +) + +// FileReader 高性能文件读取器 +type FileReader struct { + mu sync.RWMutex + cache map[string]*FileResult // 文件缓存 + maxCacheSize int // 最大缓存大小 + enableCache bool // 是否启用缓存 + maxFileSize int64 // 最大文件大小 + timeout time.Duration // 读取超时 + enableValidation bool // 是否启用内容验证 +} + +// FileResult 文件读取结果 +type FileResult struct { + Lines []string `json:"lines"` + Source *FileSource `json:"source"` + ReadTime time.Duration `json:"read_time"` + ValidLines int `json:"valid_lines"` + Errors []error `json:"errors,omitempty"` + Cached bool `json:"cached"` +} + +// NewFileReader 创建文件读取器 +func NewFileReader(options *FileReaderOptions) *FileReader { + if options == nil { + options = DefaultFileReaderOptions() + } + + return &FileReader{ + cache: make(map[string]*FileResult), + maxCacheSize: options.MaxCacheSize, + enableCache: options.EnableCache, + maxFileSize: options.MaxFileSize, + timeout: options.Timeout, + enableValidation: options.EnableValidation, + } +} + +// FileReaderOptions 文件读取器选项 +type FileReaderOptions struct { + MaxCacheSize int // 最大缓存文件数 + EnableCache bool // 启用文件缓存 + MaxFileSize int64 // 最大文件大小(字节) + Timeout time.Duration // 读取超时 + EnableValidation bool // 启用内容验证 + TrimSpace bool // 自动清理空白字符 + SkipEmpty bool // 跳过空行 + SkipComments bool // 跳过注释行(#开头) +} + +// DefaultFileReaderOptions 默认文件读取器选项 +func DefaultFileReaderOptions() *FileReaderOptions { + return &FileReaderOptions{ + MaxCacheSize: 10, + EnableCache: true, + MaxFileSize: 50 * 1024 * 1024, // 50MB + Timeout: 30 * time.Second, + EnableValidation: true, + TrimSpace: true, + SkipEmpty: true, + SkipComments: true, + } +} + +// ReadFile 读取文件内容 +func (fr *FileReader) ReadFile(filename string, options ...*FileReaderOptions) (*FileResult, error) { + if filename == "" { + return nil, NewParseError("FILE_ERROR", "文件名为空", filename, 0, ErrEmptyInput) + } + + // 检查缓存 + if fr.enableCache { + if result := fr.getFromCache(filename); result != nil { + result.Cached = true + return result, nil + } + } + + // 合并选项 + opts := fr.mergeOptions(options...) + + // 创建带超时的上下文 + ctx, cancel := context.WithTimeout(context.Background(), fr.timeout) + defer cancel() + + // 异步读取文件 + resultChan := make(chan *FileResult, 1) + errorChan := make(chan error, 1) + + go func() { + result, err := fr.readFileSync(filename, opts) + if err != nil { + errorChan <- err + } else { + resultChan <- result + } + }() + + // 等待结果或超时 + select { + case result := <-resultChan: + // 添加到缓存 + if fr.enableCache { + fr.addToCache(filename, result) + } + return result, nil + case err := <-errorChan: + return nil, err + case <-ctx.Done(): + return nil, NewParseError("TIMEOUT", "文件读取超时", filename, 0, ctx.Err()) + } +} + +// ReadFiles 并发读取多个文件 +func (fr *FileReader) ReadFiles(filenames []string, options ...*FileReaderOptions) (map[string]*FileResult, error) { + if len(filenames) == 0 { + return nil, NewParseError("FILE_ERROR", "文件列表为空", "", 0, ErrEmptyInput) + } + + opts := fr.mergeOptions(options...) + results := make(map[string]*FileResult) + resultsChan := make(chan struct { + filename string + result *FileResult + err error + }, len(filenames)) + + // 并发读取文件 + var wg sync.WaitGroup + semaphore := make(chan struct{}, 4) // 限制并发数 + + for _, filename := range filenames { + wg.Add(1) + go func(fname string) { + defer wg.Done() + semaphore <- struct{}{} // 获取信号量 + defer func() { <-semaphore }() // 释放信号量 + + result, err := fr.ReadFile(fname, opts) + resultsChan <- struct { + filename string + result *FileResult + err error + }{fname, result, err} + }(filename) + } + + // 等待所有协程完成 + go func() { + wg.Wait() + close(resultsChan) + }() + + // 收集结果 + var errors []error + for res := range resultsChan { + if res.err != nil { + errors = append(errors, res.err) + } else { + results[res.filename] = res.result + } + } + + // 如果有错误且所有文件都失败,返回第一个错误 + if len(errors) > 0 && len(results) == 0 { + return nil, errors[0] + } + + return results, nil +} + +// readFileSync 同步读取文件 +func (fr *FileReader) readFileSync(filename string, options *FileReaderOptions) (*FileResult, error) { + startTime := time.Now() + + // 检查文件 + fileInfo, err := os.Stat(filename) + if err != nil { + return nil, NewParseError("FILE_ERROR", "文件不存在或无法访问", filename, 0, err) + } + + // 检查文件大小 + if fileInfo.Size() > fr.maxFileSize { + return nil, NewParseError("FILE_ERROR", + fmt.Sprintf("文件过大: %d bytes, 最大限制: %d bytes", fileInfo.Size(), fr.maxFileSize), + filename, 0, nil) + } + + // 打开文件 + file, err := os.Open(filename) + if err != nil { + return nil, NewParseError("FILE_ERROR", "无法打开文件", filename, 0, err) + } + defer file.Close() + + // 创建结果 + result := &FileResult{ + Lines: make([]string, 0), + Source: &FileSource{ + Path: filename, + Size: fileInfo.Size(), + ModTime: fileInfo.ModTime(), + }, + } + + // 读取文件内容 + scanner := bufio.NewScanner(file) + scanner.Split(bufio.ScanLines) + + lineNum := 0 + validLines := 0 + + for scanner.Scan() { + lineNum++ + line := scanner.Text() + + // 处理行内容 + if processedLine, valid := fr.processLine(line, options); valid { + result.Lines = append(result.Lines, processedLine) + validLines++ + } + } + + // 检查扫描错误 + if err := scanner.Err(); err != nil { + return nil, NewParseError("READ_ERROR", "文件扫描失败", filename, lineNum, err) + } + + // 更新统计信息 + result.Source.LineCount = lineNum + result.Source.ValidLines = validLines + result.ValidLines = validLines + result.ReadTime = time.Since(startTime) + + return result, nil +} + +// processLine 处理单行内容 +func (fr *FileReader) processLine(line string, options *FileReaderOptions) (string, bool) { + // 清理空白字符 + if options.TrimSpace { + line = strings.TrimSpace(line) + } + + // 跳过空行 + if options.SkipEmpty && line == "" { + return "", false + } + + // 跳过注释行 + if options.SkipComments && strings.HasPrefix(line, "#") { + return "", false + } + + // 内容验证 + if options.EnableValidation && fr.enableValidation { + if !fr.validateLine(line) { + return "", false + } + } + + return line, true +} + +// validateLine 验证行内容 +func (fr *FileReader) validateLine(line string) bool { + // 基本验证:检查是否包含特殊字符或过长 + if len(line) > 1000 { // 单行最大1000字符 + return false + } + + // 检查是否包含控制字符 + for _, r := range line { + if r < 32 && r != 9 && r != 10 && r != 13 { // 排除tab、换行、回车 + return false + } + } + + return true +} + +// mergeOptions 合并选项 +func (fr *FileReader) mergeOptions(options ...*FileReaderOptions) *FileReaderOptions { + opts := DefaultFileReaderOptions() + if len(options) > 0 && options[0] != nil { + opts = options[0] + } + return opts +} + +// getFromCache 从缓存获取结果 +func (fr *FileReader) getFromCache(filename string) *FileResult { + fr.mu.RLock() + defer fr.mu.RUnlock() + + if result, exists := fr.cache[filename]; exists { + // 检查文件是否有更新 + if fileInfo, err := os.Stat(filename); err == nil { + if fileInfo.ModTime().After(result.Source.ModTime) { + // 文件已更新,从缓存中移除 + delete(fr.cache, filename) + return nil + } + } + return result + } + return nil +} + +// addToCache 添加到缓存 +func (fr *FileReader) addToCache(filename string, result *FileResult) { + fr.mu.Lock() + defer fr.mu.Unlock() + + // 检查缓存大小 + if len(fr.cache) >= fr.maxCacheSize { + // 移除最旧的条目(简单的LRU策略) + var oldestFile string + var oldestTime time.Time + for file, res := range fr.cache { + if oldestFile == "" || res.Source.ModTime.Before(oldestTime) { + oldestFile = file + oldestTime = res.Source.ModTime + } + } + delete(fr.cache, oldestFile) + } + + fr.cache[filename] = result +} + +// ClearCache 清空缓存 +func (fr *FileReader) ClearCache() { + fr.mu.Lock() + defer fr.mu.Unlock() + fr.cache = make(map[string]*FileResult) +} + +// GetCacheStats 获取缓存统计 +func (fr *FileReader) GetCacheStats() map[string]interface{} { + fr.mu.RLock() + defer fr.mu.RUnlock() + + return map[string]interface{}{ + "cache_size": len(fr.cache), + "max_cache_size": fr.maxCacheSize, + "cache_enabled": fr.enableCache, + } +} \ No newline at end of file diff --git a/Common/parsers/NetworkParser.go b/Common/parsers/NetworkParser.go new file mode 100644 index 0000000..2d721fe --- /dev/null +++ b/Common/parsers/NetworkParser.go @@ -0,0 +1,384 @@ +package parsers + +import ( + "fmt" + "net/url" + "regexp" + "strconv" + "strings" + "sync" + "time" +) + +// NetworkParser 网络配置解析器 +type NetworkParser struct { + mu sync.RWMutex + 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: true, + AllowInsecure: false, + DefaultTimeout: 30 * time.Second, + DefaultWebTimeout: 10 * time.Second, + DefaultUserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36", + } +} + +// 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("PROXY_ERROR", 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("PROXY_ERROR", 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 > 300 { // 最大5分钟 + warnings = append(warnings, "超时时间过长,建议不超过300秒") + } + finalTimeout = time.Duration(timeout) * time.Second + } + + // 处理Web超时 + finalWebTimeout := np.options.DefaultWebTimeout + if webTimeout > 0 { + if webTimeout > 120 { // 最大2分钟 + warnings = append(warnings, "Web超时时间过长,建议不超过120秒") + } + finalWebTimeout = time.Duration(webTimeout) * time.Second + } + + // 验证超时配置合理性 + if finalWebTimeout > finalTimeout { + warnings = append(warnings, "Web超时时间大于普通超时时间,可能导致不期望的行为") + } + + 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) > 512 { + errors = append(errors, NewParseError("USERAGENT_ERROR", "用户代理字符串过长", "user_agent", 0, nil)) + return "", errors, warnings + } + + // 检查是否包含特殊字符 + if strings.ContainsAny(userAgent, "\r\n\t") { + errors = append(errors, NewParseError("USERAGENT_ERROR", "用户代理包含非法字符", "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) > 4096 { // HTTP Cookie长度限制 + errors = append(errors, NewParseError("COOKIE_ERROR", "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 "1": + return "http://127.0.0.1:8080" + case "2": + return "socks5://127.0.0.1:1080" + default: + // 如果没有协议前缀,默认使用HTTP + if !strings.Contains(proxy, "://") { + if strings.Contains(proxy, ":") { + return "http://" + proxy + } else { + return "http://127.0.0.1:" + proxy + } + } + return proxy + } +} + +// normalizeSocks5Proxy 规范化Socks5代理URL +func (np *NetworkParser) normalizeSocks5Proxy(proxy string) string { + // 如果没有协议前缀,添加socks5:// + if !strings.HasPrefix(proxy, "socks5://") { + if strings.Contains(proxy, ":") { + return "socks5://" + proxy + } else { + return "socks5://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 "http", "https", "socks5": + // 支持的协议 + 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 == "http" { + return fmt.Errorf("不允许使用不安全的HTTP代理") + } + + return nil +} + +// isValidUserAgent 检查用户代理是否有效 +func (np *NetworkParser) isValidUserAgent(userAgent string) bool { + // 检查是否包含常见的浏览器标识 + commonBrowsers := []string{ + "Mozilla", "Chrome", "Safari", "Firefox", "Edge", "Opera", + "AppleWebKit", "Gecko", "Trident", "Presto", + } + + 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) + cookieRegex := regexp.MustCompile(`^[^=;\s]+(=[^;\s]*)?(\s*;\s*[^=;\s]+(=[^;\s]*)?)*$`) + return cookieRegex.MatchString(strings.TrimSpace(cookie)) +} + +// Validate 验证解析结果 +func (np *NetworkParser) Validate() error { + return nil +} + +// GetStatistics 获取解析统计 +func (np *NetworkParser) GetStatistics() interface{} { + np.mu.RLock() + defer np.mu.RUnlock() + + return map[string]interface{}{ + "parser_type": "network", + "options": np.options, + } +} \ No newline at end of file diff --git a/Common/parsers/TargetParser.go b/Common/parsers/TargetParser.go new file mode 100644 index 0000000..8e74284 --- /dev/null +++ b/Common/parsers/TargetParser.go @@ -0,0 +1,712 @@ +package parsers + +import ( + "fmt" + "net" + "net/url" + "regexp" + "strconv" + "strings" + "sync" + "time" +) + +// TargetParser 目标解析器 +type TargetParser struct { + fileReader *FileReader + mu sync.RWMutex + ipRegex *regexp.Regexp + portRegex *regexp.Regexp + urlRegex *regexp.Regexp + options *TargetParserOptions +} + +// TargetParserOptions 目标解析器选项 +type TargetParserOptions struct { + MaxTargets int `json:"max_targets"` + MaxPortRange int `json:"max_port_range"` + AllowPrivateIPs bool `json:"allow_private_ips"` + AllowLoopback bool `json:"allow_loopback"` + ValidateURLs bool `json:"validate_urls"` + ResolveDomains bool `json:"resolve_domains"` + EnableStatistics bool `json:"enable_statistics"` + DefaultPorts string `json:"default_ports"` +} + +// DefaultTargetParserOptions 默认目标解析器选项 +func DefaultTargetParserOptions() *TargetParserOptions { + return &TargetParserOptions{ + MaxTargets: 10000, + MaxPortRange: 1000, + AllowPrivateIPs: true, + AllowLoopback: true, + ValidateURLs: true, + ResolveDomains: false, + EnableStatistics: true, + DefaultPorts: "80,443,22,21,23,25,53,110,993,995,1433,3306,5432,6379,27017", + } +} + +// NewTargetParser 创建目标解析器 +func NewTargetParser(fileReader *FileReader, options *TargetParserOptions) *TargetParser { + if options == nil { + options = DefaultTargetParserOptions() + } + + // 编译正则表达式 + ipRegex := regexp.MustCompile(`^(\d{1,3}\.){3}\d{1,3}$`) + portRegex := regexp.MustCompile(`^(\d+)(-(\d+))?$`) + urlRegex := regexp.MustCompile(`^https?://[^\s/$.?#].[^\s]*$`) + + return &TargetParser{ + fileReader: fileReader, + ipRegex: ipRegex, + portRegex: portRegex, + urlRegex: urlRegex, + options: options, + } +} + +// TargetInput 目标输入参数 +type TargetInput struct { + // 主机相关 + Host string `json:"host"` + HostsFile string `json:"hosts_file"` + ExcludeHosts string `json:"exclude_hosts"` + + // 端口相关 + Ports string `json:"ports"` + PortsFile string `json:"ports_file"` + AddPorts string `json:"add_ports"` + ExcludePorts string `json:"exclude_ports"` + + // URL相关 + TargetURL string `json:"target_url"` + URLsFile string `json:"urls_file"` + + // 主机端口组合 + HostPort []string `json:"host_port"` + + // 模式标识 + LocalMode bool `json:"local_mode"` +} + +// Parse 解析目标配置 +func (tp *TargetParser) Parse(input *TargetInput, options *ParserOptions) (*ParseResult, error) { + if input == nil { + return nil, NewParseError("INPUT_ERROR", "目标输入为空", "", 0, ErrEmptyInput) + } + + startTime := time.Now() + result := &ParseResult{ + Config: &ParsedConfig{ + Targets: &TargetConfig{ + LocalMode: input.LocalMode, + }, + }, + Success: true, + } + + var errors []error + var warnings []string + + // 解析主机 + hosts, hostErrors, hostWarnings := tp.parseHosts(input) + errors = append(errors, hostErrors...) + warnings = append(warnings, hostWarnings...) + + // 解析URL + urls, urlErrors, urlWarnings := tp.parseURLs(input) + errors = append(errors, urlErrors...) + warnings = append(warnings, urlWarnings...) + + // 解析端口 + ports, portErrors, portWarnings := tp.parsePorts(input) + errors = append(errors, portErrors...) + warnings = append(warnings, portWarnings...) + + // 解析排除端口 + excludePorts, excludeErrors, excludeWarnings := tp.parseExcludePorts(input) + errors = append(errors, excludeErrors...) + warnings = append(warnings, excludeWarnings...) + + // 解析主机端口组合 + hostPorts, hpErrors, hpWarnings := tp.parseHostPorts(input) + errors = append(errors, hpErrors...) + warnings = append(warnings, hpWarnings...) + + // 更新配置 + result.Config.Targets.Hosts = hosts + result.Config.Targets.URLs = urls + result.Config.Targets.Ports = ports + result.Config.Targets.ExcludePorts = excludePorts + result.Config.Targets.HostPorts = hostPorts + + // 生成统计信息 + if tp.options.EnableStatistics { + result.Config.Targets.Statistics = tp.generateStatistics(hosts, urls, ports, excludePorts) + } + + // 设置结果状态 + result.Errors = errors + result.Warnings = warnings + result.ParseTime = time.Since(startTime) + result.Success = len(errors) == 0 + + return result, nil +} + +// parseHosts 解析主机 +func (tp *TargetParser) parseHosts(input *TargetInput) ([]string, []error, []string) { + var hosts []string + var errors []error + var warnings []string + + // 解析命令行主机 + if input.Host != "" { + hostList, err := tp.parseHostList(input.Host) + if err != nil { + errors = append(errors, NewParseError("HOST_ERROR", err.Error(), "command line", 0, err)) + } else { + hosts = append(hosts, hostList...) + } + } + + // 从文件读取主机 + if input.HostsFile != "" { + fileResult, err := tp.fileReader.ReadFile(input.HostsFile) + if err != nil { + errors = append(errors, NewParseError("FILE_ERROR", "读取主机文件失败", input.HostsFile, 0, err)) + } else { + for i, line := range fileResult.Lines { + hostList, err := tp.parseHostList(line) + if err != nil { + warnings = append(warnings, fmt.Sprintf("主机文件第%d行解析失败: %s", i+1, err.Error())) + } else { + hosts = append(hosts, hostList...) + } + } + } + } + + // 处理排除主机 + if input.ExcludeHosts != "" { + excludeList, err := tp.parseHostList(input.ExcludeHosts) + if err != nil { + warnings = append(warnings, fmt.Sprintf("排除主机解析失败: %s", err.Error())) + } else { + hosts = tp.excludeHosts(hosts, excludeList) + } + } + + // 去重和验证 + hosts = tp.removeDuplicateStrings(hosts) + validHosts := make([]string, 0, len(hosts)) + + for _, host := range hosts { + if valid, err := tp.validateHost(host); valid { + validHosts = append(validHosts, host) + } else if err != nil { + warnings = append(warnings, fmt.Sprintf("无效主机: %s - %s", host, err.Error())) + } + } + + // 检查目标数量限制 + if len(validHosts) > tp.options.MaxTargets { + warnings = append(warnings, fmt.Sprintf("主机数量超过限制,截取前%d个", tp.options.MaxTargets)) + validHosts = validHosts[:tp.options.MaxTargets] + } + + return validHosts, errors, warnings +} + +// parseURLs 解析URL +func (tp *TargetParser) parseURLs(input *TargetInput) ([]string, []error, []string) { + var urls []string + var errors []error + var warnings []string + + // 解析命令行URL + if input.TargetURL != "" { + urlList := strings.Split(input.TargetURL, ",") + for _, rawURL := range urlList { + rawURL = strings.TrimSpace(rawURL) + if rawURL != "" { + if valid, err := tp.validateURL(rawURL); valid { + urls = append(urls, rawURL) + } else { + warnings = append(warnings, fmt.Sprintf("无效URL: %s - %s", rawURL, err.Error())) + } + } + } + } + + // 从文件读取URL + if input.URLsFile != "" { + fileResult, err := tp.fileReader.ReadFile(input.URLsFile) + if err != nil { + errors = append(errors, NewParseError("FILE_ERROR", "读取URL文件失败", input.URLsFile, 0, err)) + } else { + for i, line := range fileResult.Lines { + if valid, err := tp.validateURL(line); valid { + urls = append(urls, line) + } else { + warnings = append(warnings, fmt.Sprintf("URL文件第%d行无效: %s", i+1, err.Error())) + } + } + } + } + + // 去重 + urls = tp.removeDuplicateStrings(urls) + + return urls, errors, warnings +} + +// parsePorts 解析端口 +func (tp *TargetParser) parsePorts(input *TargetInput) ([]int, []error, []string) { + var ports []int + var errors []error + var warnings []string + + // 解析命令行端口 + if input.Ports != "" { + portList, err := tp.parsePortList(input.Ports) + if err != nil { + errors = append(errors, NewParseError("PORT_ERROR", err.Error(), "command line", 0, err)) + } else { + ports = append(ports, portList...) + } + } + + // 从文件读取端口 + if input.PortsFile != "" { + fileResult, err := tp.fileReader.ReadFile(input.PortsFile) + if err != nil { + errors = append(errors, NewParseError("FILE_ERROR", "读取端口文件失败", input.PortsFile, 0, err)) + } else { + for i, line := range fileResult.Lines { + portList, err := tp.parsePortList(line) + if err != nil { + warnings = append(warnings, fmt.Sprintf("端口文件第%d行解析失败: %s", i+1, err.Error())) + } else { + ports = append(ports, portList...) + } + } + } + } + + // 处理额外端口 + if input.AddPorts != "" { + addPortList, err := tp.parsePortList(input.AddPorts) + if err != nil { + warnings = append(warnings, fmt.Sprintf("额外端口解析失败: %s", err.Error())) + } else { + ports = append(ports, addPortList...) + } + } + + // 去重和排序 + ports = tp.removeDuplicatePorts(ports) + + return ports, errors, warnings +} + +// parseExcludePorts 解析排除端口 +func (tp *TargetParser) parseExcludePorts(input *TargetInput) ([]int, []error, []string) { + var excludePorts []int + var errors []error + var warnings []string + + if input.ExcludePorts != "" { + portList, err := tp.parsePortList(input.ExcludePorts) + if err != nil { + errors = append(errors, NewParseError("EXCLUDE_PORT_ERROR", err.Error(), "command line", 0, err)) + } else { + excludePorts = portList + } + } + + return excludePorts, errors, warnings +} + +// parseHostPorts 解析主机端口组合 +func (tp *TargetParser) parseHostPorts(input *TargetInput) ([]string, []error, []string) { + var hostPorts []string + var errors []error + var warnings []string + + for _, hp := range input.HostPort { + if hp != "" { + if valid, err := tp.validateHostPort(hp); valid { + hostPorts = append(hostPorts, hp) + } else { + warnings = append(warnings, fmt.Sprintf("无效主机端口组合: %s - %s", hp, err.Error())) + } + } + } + + return hostPorts, errors, warnings +} + +// parseHostList 解析主机列表 +func (tp *TargetParser) parseHostList(hostStr string) ([]string, error) { + if hostStr == "" { + return nil, nil + } + + var hosts []string + hostItems := strings.Split(hostStr, ",") + + for _, item := range hostItems { + item = strings.TrimSpace(item) + if item == "" { + continue + } + + // 检查是否为IP范围或CIDR + if strings.Contains(item, "/") { + // CIDR表示法 + cidrHosts, err := tp.parseCIDR(item) + if err != nil { + return nil, fmt.Errorf("CIDR解析失败 %s: %v", item, err) + } + hosts = append(hosts, cidrHosts...) + } else if strings.Contains(item, "-") { + // IP范围表示法 + rangeHosts, err := tp.parseIPRange(item) + if err != nil { + return nil, fmt.Errorf("IP范围解析失败 %s: %v", item, err) + } + hosts = append(hosts, rangeHosts...) + } else { + // 单个IP或域名 + hosts = append(hosts, item) + } + } + + return hosts, nil +} + +// parsePortList 解析端口列表 +func (tp *TargetParser) parsePortList(portStr string) ([]int, error) { + if portStr == "" { + return nil, nil + } + + var ports []int + portItems := strings.Split(portStr, ",") + + for _, item := range portItems { + item = strings.TrimSpace(item) + if item == "" { + continue + } + + if strings.Contains(item, "-") { + // 端口范围 + rangePorts, err := tp.parsePortRange(item) + if err != nil { + return nil, fmt.Errorf("端口范围解析失败 %s: %v", item, err) + } + + // 检查范围大小 + if len(rangePorts) > tp.options.MaxPortRange { + return nil, fmt.Errorf("端口范围过大: %d, 最大允许: %d", len(rangePorts), tp.options.MaxPortRange) + } + + ports = append(ports, rangePorts...) + } else { + // 单个端口 + port, err := strconv.Atoi(item) + if err != nil { + return nil, fmt.Errorf("无效端口号: %s", item) + } + + if port < 1 || port > 65535 { + return nil, fmt.Errorf("端口号超出范围: %d", port) + } + + ports = append(ports, port) + } + } + + return ports, nil +} + +// parseCIDR 解析CIDR网段 +func (tp *TargetParser) parseCIDR(cidr string) ([]string, error) { + _, ipNet, err := net.ParseCIDR(cidr) + if err != nil { + return nil, err + } + + var ips []string + ip := ipNet.IP.Mask(ipNet.Mask) + + for ip := ip.Mask(ipNet.Mask); ipNet.Contains(ip); tp.nextIP(ip) { + ips = append(ips, ip.String()) + + // 防止生成过多IP + if len(ips) > tp.options.MaxTargets { + break + } + } + + return ips, nil +} + +// parseIPRange 解析IP范围 +func (tp *TargetParser) parseIPRange(rangeStr string) ([]string, error) { + parts := strings.Split(rangeStr, "-") + if len(parts) != 2 { + return nil, fmt.Errorf("无效的IP范围格式") + } + + startIP := net.ParseIP(strings.TrimSpace(parts[0])) + endIP := net.ParseIP(strings.TrimSpace(parts[1])) + + if startIP == nil || endIP == nil { + return nil, fmt.Errorf("无效的IP地址") + } + + var ips []string + for ip := startIP; !ip.Equal(endIP); tp.nextIP(ip) { + ips = append(ips, ip.String()) + + // 防止生成过多IP + if len(ips) > tp.options.MaxTargets { + break + } + } + ips = append(ips, endIP.String()) // 添加结束IP + + return ips, nil +} + +// parsePortRange 解析端口范围 +func (tp *TargetParser) parsePortRange(rangeStr string) ([]int, error) { + parts := strings.Split(rangeStr, "-") + if len(parts) != 2 { + return nil, fmt.Errorf("无效的端口范围格式") + } + + startPort, err1 := strconv.Atoi(strings.TrimSpace(parts[0])) + endPort, err2 := strconv.Atoi(strings.TrimSpace(parts[1])) + + if err1 != nil || err2 != nil { + return nil, fmt.Errorf("无效的端口号") + } + + if startPort > endPort { + startPort, endPort = endPort, startPort + } + + if startPort < 1 || endPort > 65535 { + return nil, fmt.Errorf("端口号超出范围") + } + + var ports []int + for port := startPort; port <= endPort; port++ { + ports = append(ports, port) + } + + return ports, nil +} + +// nextIP 获取下一个IP地址 +func (tp *TargetParser) nextIP(ip net.IP) { + for j := len(ip) - 1; j >= 0; j-- { + ip[j]++ + if ip[j] > 0 { + break + } + } +} + +// validateHost 验证主机地址 +func (tp *TargetParser) validateHost(host string) (bool, error) { + if host == "" { + return false, fmt.Errorf("主机地址为空") + } + + // 检查是否为IP地址 + if ip := net.ParseIP(host); ip != nil { + return tp.validateIP(ip) + } + + // 检查是否为域名 + if tp.isValidDomain(host) { + return true, nil + } + + return false, fmt.Errorf("无效的主机地址格式") +} + +// validateIP 验证IP地址 +func (tp *TargetParser) validateIP(ip net.IP) (bool, error) { + if ip == nil { + return false, fmt.Errorf("IP地址为空") + } + + // 检查是否为私有IP + if !tp.options.AllowPrivateIPs && tp.isPrivateIP(ip) { + return false, fmt.Errorf("不允许私有IP地址") + } + + // 检查是否为回环地址 + if !tp.options.AllowLoopback && ip.IsLoopback() { + return false, fmt.Errorf("不允许回环地址") + } + + return true, nil +} + +// validateURL 验证URL +func (tp *TargetParser) validateURL(rawURL string) (bool, error) { + if rawURL == "" { + return false, fmt.Errorf("URL为空") + } + + if !tp.options.ValidateURLs { + return true, nil + } + + if !tp.urlRegex.MatchString(rawURL) { + return false, fmt.Errorf("URL格式无效") + } + + // 进一步验证URL格式 + _, err := url.Parse(rawURL) + if err != nil { + return false, fmt.Errorf("URL解析失败: %v", err) + } + + return true, nil +} + +// validateHostPort 验证主机端口组合 +func (tp *TargetParser) validateHostPort(hostPort string) (bool, error) { + parts := strings.Split(hostPort, ":") + if len(parts) != 2 { + return false, fmt.Errorf("主机端口格式无效,应为 host:port") + } + + host := strings.TrimSpace(parts[0]) + portStr := strings.TrimSpace(parts[1]) + + // 验证主机 + if valid, err := tp.validateHost(host); !valid { + return false, fmt.Errorf("主机无效: %v", err) + } + + // 验证端口 + port, err := strconv.Atoi(portStr) + if err != nil { + return false, fmt.Errorf("端口号无效: %s", portStr) + } + + if port < 1 || port > 65535 { + return false, fmt.Errorf("端口号超出范围: %d", port) + } + + return true, nil +} + +// isPrivateIP 检查是否为私有IP +func (tp *TargetParser) isPrivateIP(ip net.IP) bool { + if ip4 := ip.To4(); ip4 != nil { + // 10.0.0.0/8 + if ip4[0] == 10 { + return true + } + // 172.16.0.0/12 + if ip4[0] == 172 && ip4[1] >= 16 && ip4[1] <= 31 { + return true + } + // 192.168.0.0/16 + if ip4[0] == 192 && ip4[1] == 168 { + return true + } + } + return false +} + +// isValidDomain 检查是否为有效域名 +func (tp *TargetParser) isValidDomain(domain string) bool { + domainRegex := regexp.MustCompile(`^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$`) + return domainRegex.MatchString(domain) && len(domain) <= 253 +} + +// excludeHosts 排除指定主机 +func (tp *TargetParser) excludeHosts(hosts, excludeList []string) []string { + excludeMap := make(map[string]struct{}) + for _, exclude := range excludeList { + excludeMap[exclude] = struct{}{} + } + + var result []string + for _, host := range hosts { + if _, excluded := excludeMap[host]; !excluded { + result = append(result, host) + } + } + + return result +} + +// removeDuplicateStrings 去重字符串切片 +func (tp *TargetParser) removeDuplicateStrings(slice []string) []string { + seen := make(map[string]struct{}) + var result []string + + for _, item := range slice { + if _, exists := seen[item]; !exists { + seen[item] = struct{}{} + result = append(result, item) + } + } + + return result +} + +// removeDuplicatePorts 去重端口切片 +func (tp *TargetParser) removeDuplicatePorts(slice []int) []int { + seen := make(map[int]struct{}) + var result []int + + for _, item := range slice { + if _, exists := seen[item]; !exists { + seen[item] = struct{}{} + result = append(result, item) + } + } + + return result +} + +// generateStatistics 生成统计信息 +func (tp *TargetParser) generateStatistics(hosts, urls []string, ports, excludePorts []int) *TargetStatistics { + return &TargetStatistics{ + TotalHosts: len(hosts), + TotalURLs: len(urls), + TotalPorts: len(ports), + ExcludedPorts: len(excludePorts), + } +} + +// Validate 验证解析结果 +func (tp *TargetParser) Validate() error { + return nil +} + +// GetStatistics 获取解析统计 +func (tp *TargetParser) GetStatistics() interface{} { + tp.mu.RLock() + defer tp.mu.RUnlock() + + return map[string]interface{}{ + "parser_type": "target", + "options": tp.options, + } +} \ No newline at end of file diff --git a/Common/parsers/Types.go b/Common/parsers/Types.go new file mode 100644 index 0000000..d7fc10f --- /dev/null +++ b/Common/parsers/Types.go @@ -0,0 +1,169 @@ +package parsers + +import ( + "errors" + "fmt" + "time" +) + +// ParsedConfig 解析后的完整配置 +type ParsedConfig struct { + Targets *TargetConfig `json:"targets"` + Credentials *CredentialConfig `json:"credentials"` + Network *NetworkConfig `json:"network"` + Validation *ValidationConfig `json:"validation"` +} + +// TargetConfig 目标配置 +type TargetConfig struct { + Hosts []string `json:"hosts"` + URLs []string `json:"urls"` + Ports []int `json:"ports"` + ExcludePorts []int `json:"exclude_ports"` + HostPorts []string `json:"host_ports"` + LocalMode bool `json:"local_mode"` + Statistics *TargetStatistics `json:"statistics,omitempty"` +} + +// TargetStatistics 目标解析统计 +type TargetStatistics struct { + TotalHosts int `json:"total_hosts"` + TotalURLs int `json:"total_urls"` + TotalPorts int `json:"total_ports"` + ExcludedHosts int `json:"excluded_hosts"` + ExcludedPorts int `json:"excluded_ports"` +} + +// CredentialConfig 认证配置 +type CredentialConfig struct { + Usernames []string `json:"usernames"` + Passwords []string `json:"passwords"` + HashValues []string `json:"hash_values"` + HashBytes [][]byte `json:"hash_bytes,omitempty"` + SshKeyPath string `json:"ssh_key_path"` + Domain string `json:"domain"` + Statistics *CredentialStats `json:"statistics,omitempty"` +} + +// CredentialStats 认证配置统计 +type CredentialStats struct { + TotalUsernames int `json:"total_usernames"` + TotalPasswords int `json:"total_passwords"` + TotalHashes int `json:"total_hashes"` + UniqueUsernames int `json:"unique_usernames"` + UniquePasswords int `json:"unique_passwords"` + ValidHashes int `json:"valid_hashes"` + InvalidHashes int `json:"invalid_hashes"` +} + +// NetworkConfig 网络配置 +type NetworkConfig struct { + HttpProxy string `json:"http_proxy"` + Socks5Proxy string `json:"socks5_proxy"` + Timeout time.Duration `json:"timeout"` + WebTimeout time.Duration `json:"web_timeout"` + DisablePing bool `json:"disable_ping"` + EnableDNSLog bool `json:"enable_dns_log"` + UserAgent string `json:"user_agent"` + Cookie string `json:"cookie"` +} + +// ValidationConfig 验证配置 +type ValidationConfig struct { + ScanMode string `json:"scan_mode"` + ConflictChecked bool `json:"conflict_checked"` + Errors []error `json:"errors,omitempty"` + Warnings []string `json:"warnings,omitempty"` +} + +// ParseResult 解析结果 +type ParseResult struct { + Config *ParsedConfig `json:"config"` + Success bool `json:"success"` + Errors []error `json:"errors,omitempty"` + Warnings []string `json:"warnings,omitempty"` + ParseTime time.Duration `json:"parse_time"` +} + +// 预定义错误类型 +var ( + ErrEmptyInput = errors.New("输入参数为空") + ErrInvalidFormat = errors.New("格式无效") + ErrFileNotFound = errors.New("文件未找到") + ErrConflictingParams = errors.New("参数冲突") + ErrInvalidProxy = errors.New("代理配置无效") + ErrInvalidHash = errors.New("哈希值无效") + ErrInvalidPort = errors.New("端口号无效") + ErrInvalidIP = errors.New("IP地址无效") + ErrInvalidURL = errors.New("URL格式无效") +) + +// ParserOptions 解析器选项 +type ParserOptions struct { + EnableConcurrency bool // 启用并发解析 + MaxWorkers int // 最大工作协程数 + Timeout time.Duration // 解析超时时间 + EnableValidation bool // 启用详细验证 + EnableStatistics bool // 启用统计信息 + IgnoreErrors bool // 忽略非致命错误 + FileMaxSize int64 // 文件最大大小限制 + MaxTargets int // 最大目标数量限制 +} + +// DefaultParserOptions 返回默认解析器选项 +func DefaultParserOptions() *ParserOptions { + return &ParserOptions{ + EnableConcurrency: true, + MaxWorkers: 4, + Timeout: 30 * time.Second, + EnableValidation: true, + EnableStatistics: true, + IgnoreErrors: false, + FileMaxSize: 100 * 1024 * 1024, // 100MB + MaxTargets: 10000, // 10K targets + } +} + +// Parser 解析器接口 +type Parser interface { + Parse(options *ParserOptions) (*ParseResult, error) + Validate() error + GetStatistics() interface{} +} + +// FileSource 文件源 +type FileSource struct { + Path string `json:"path"` + Size int64 `json:"size"` + ModTime time.Time `json:"mod_time"` + LineCount int `json:"line_count"` + ValidLines int `json:"valid_lines"` +} + +// ParseError 解析错误,包含详细上下文 +type ParseError struct { + Type string `json:"type"` + Message string `json:"message"` + Source string `json:"source"` + Line int `json:"line,omitempty"` + Context string `json:"context,omitempty"` + Original error `json:"original,omitempty"` +} + +func (e *ParseError) Error() string { + if e.Line > 0 { + return fmt.Sprintf("%s:%d - %s: %s", e.Source, e.Line, e.Type, e.Message) + } + return fmt.Sprintf("%s - %s: %s", e.Source, e.Type, e.Message) +} + +// NewParseError 创建解析错误 +func NewParseError(errType, message, source string, line int, original error) *ParseError { + return &ParseError{ + Type: errType, + Message: message, + Source: source, + Line: line, + Original: original, + } +} \ No newline at end of file diff --git a/Common/parsers/ValidationParser.go b/Common/parsers/ValidationParser.go new file mode 100644 index 0000000..a454ab0 --- /dev/null +++ b/Common/parsers/ValidationParser.go @@ -0,0 +1,303 @@ +package parsers + +import ( + "fmt" + "sync" + "time" +) + +// ValidationParser 参数验证解析器 +type ValidationParser struct { + mu sync.RWMutex + options *ValidationParserOptions +} + +// ValidationParserOptions 验证解析器选项 +type ValidationParserOptions struct { + StrictMode bool `json:"strict_mode"` // 严格模式 + AllowEmpty bool `json:"allow_empty"` // 允许空配置 + CheckConflicts bool `json:"check_conflicts"` // 检查参数冲突 + ValidateTargets bool `json:"validate_targets"` // 验证目标有效性 + ValidateNetwork bool `json:"validate_network"` // 验证网络配置 + MaxErrorCount int `json:"max_error_count"` // 最大错误数量 +} + +// DefaultValidationParserOptions 默认验证解析器选项 +func DefaultValidationParserOptions() *ValidationParserOptions { + return &ValidationParserOptions{ + StrictMode: false, + AllowEmpty: true, + CheckConflicts: true, + ValidateTargets: true, + ValidateNetwork: true, + MaxErrorCount: 100, + } +} + +// NewValidationParser 创建验证解析器 +func NewValidationParser(options *ValidationParserOptions) *ValidationParser { + if options == nil { + options = DefaultValidationParserOptions() + } + + return &ValidationParser{ + options: options, + } +} + +// ValidationInput 验证输入参数 +type ValidationInput struct { + // 扫描模式 + ScanMode string `json:"scan_mode"` + LocalMode bool `json:"local_mode"` + + // 目标配置 + HasHosts bool `json:"has_hosts"` + HasURLs bool `json:"has_urls"` + HasPorts bool `json:"has_ports"` + + // 网络配置 + HasProxy bool `json:"has_proxy"` + DisablePing bool `json:"disable_ping"` + + // 凭据配置 + HasCredentials bool `json:"has_credentials"` + + // 特殊模式 + PocScan bool `json:"poc_scan"` + BruteScan bool `json:"brute_scan"` + LocalScan bool `json:"local_scan"` +} + +// ConflictRule 冲突规则 +type ConflictRule struct { + Name string `json:"name"` + Description string `json:"description"` + Fields []string `json:"fields"` + Severity string `json:"severity"` // error, warning, info +} + +// ValidationRule 验证规则 +type ValidationRule struct { + Name string `json:"name"` + Description string `json:"description"` + Validator func(input *ValidationInput) error `json:"-"` + Severity string `json:"severity"` +} + +// Parse 执行参数验证 +func (vp *ValidationParser) Parse(input *ValidationInput, config *ParsedConfig, options *ParserOptions) (*ParseResult, error) { + if input == nil { + return nil, NewParseError("INPUT_ERROR", "验证输入为空", "", 0, ErrEmptyInput) + } + + startTime := time.Now() + result := &ParseResult{ + Config: &ParsedConfig{ + Validation: &ValidationConfig{ + ScanMode: input.ScanMode, + ConflictChecked: true, + }, + }, + Success: true, + } + + var errors []error + var warnings []string + + // 基础验证 + basicErrors, basicWarnings := vp.validateBasic(input) + errors = append(errors, basicErrors...) + warnings = append(warnings, basicWarnings...) + + // 冲突检查 + if vp.options.CheckConflicts { + conflictErrors, conflictWarnings := vp.checkConflicts(input) + errors = append(errors, conflictErrors...) + warnings = append(warnings, conflictWarnings...) + } + + // 逻辑验证 + logicErrors, logicWarnings := vp.validateLogic(input, config) + errors = append(errors, logicErrors...) + warnings = append(warnings, logicWarnings...) + + + // 性能建议 + performanceWarnings := vp.checkPerformance(input, config) + warnings = append(warnings, performanceWarnings...) + + // 检查错误数量限制 + if len(errors) > vp.options.MaxErrorCount { + errors = errors[:vp.options.MaxErrorCount] + warnings = append(warnings, fmt.Sprintf("错误数量过多,仅显示前%d个", vp.options.MaxErrorCount)) + } + + // 更新结果 + result.Config.Validation.Errors = errors + result.Config.Validation.Warnings = warnings + result.Errors = errors + result.Warnings = warnings + result.ParseTime = time.Since(startTime) + result.Success = len(errors) == 0 + + return result, nil +} + +// validateBasic 基础验证 +func (vp *ValidationParser) validateBasic(input *ValidationInput) ([]error, []string) { + var errors []error + var warnings []string + + // 检查是否有任何目标 + if !input.HasHosts && !input.HasURLs && !input.LocalMode { + if !vp.options.AllowEmpty { + errors = append(errors, NewParseError("VALIDATION_ERROR", "未指定任何扫描目标", "basic", 0, nil)) + } else { + warnings = append(warnings, "未指定扫描目标,将使用默认配置") + } + } + + // 检查扫描模式 + if input.ScanMode != "" { + if err := vp.validateScanMode(input.ScanMode); err != nil { + if vp.options.StrictMode { + errors = append(errors, err) + } else { + warnings = append(warnings, err.Error()) + } + } + } + + return errors, warnings +} + +// checkConflicts 检查参数冲突 +func (vp *ValidationParser) checkConflicts(input *ValidationInput) ([]error, []string) { + var errors []error + var warnings []string + + // 定义冲突规则 (预留用于扩展) + _ = []ConflictRule{ + { + Name: "multiple_scan_modes", + Description: "不能同时使用多种扫描模式", + Fields: []string{"hosts", "urls", "local_mode"}, + Severity: "error", + }, + { + Name: "proxy_with_ping", + Description: "使用代理时建议禁用Ping检测", + Fields: []string{"proxy", "ping"}, + Severity: "warning", + }, + } + + // 检查扫描模式冲突 + scanModes := 0 + if input.HasHosts { + scanModes++ + } + if input.HasURLs { + scanModes++ + } + if input.LocalMode { + scanModes++ + } + + if scanModes > 1 { + errors = append(errors, NewParseError("CONFLICT_ERROR", + "不能同时指定多种扫描模式(主机扫描、URL扫描、本地模式)", "validation", 0, nil)) + } + + // 检查代理和Ping冲突 + if input.HasProxy && !input.DisablePing { + warnings = append(warnings, "代理模式下Ping检测可能失效") + } + + return errors, warnings +} + +// validateLogic 逻辑验证 +func (vp *ValidationParser) validateLogic(input *ValidationInput, config *ParsedConfig) ([]error, []string) { + var errors []error + var warnings []string + + // 验证目标配置逻辑 + if vp.options.ValidateTargets && config != nil && config.Targets != nil { + + // 检查排除端口配置 + if len(config.Targets.ExcludePorts) > 0 && len(config.Targets.Ports) == 0 { + warnings = append(warnings, "排除端口无效") + } + } + + + return errors, warnings +} + + +// checkPerformance 性能检查 +func (vp *ValidationParser) checkPerformance(input *ValidationInput, config *ParsedConfig) []string { + var warnings []string + + if config == nil { + return warnings + } + + // 检查目标数量 + if config.Targets != nil { + totalTargets := len(config.Targets.Hosts) * len(config.Targets.Ports) + if totalTargets > 100000 { + warnings = append(warnings, fmt.Sprintf("大量目标(%d),可能耗时较长", totalTargets)) + } + + // 检查端口范围 + if len(config.Targets.Ports) > 1000 { + warnings = append(warnings, "端口数量过多") + } + } + + // 检查超时配置 + if config.Network != nil { + if config.Network.Timeout < 1*time.Second { + warnings = append(warnings, "超时过短") + } + if config.Network.Timeout > 60*time.Second { + warnings = append(warnings, "超时过长") + } + } + + return warnings +} + +// validateScanMode 验证扫描模式 +func (vp *ValidationParser) validateScanMode(scanMode string) error { + validModes := []string{"all", "main", "web", "db", "service", "top1000", "custom"} + + for _, mode := range validModes { + if scanMode == mode { + return nil + } + } + + return NewParseError("VALIDATION_ERROR", + fmt.Sprintf("无效的扫描模式: %s", scanMode), "scan_mode", 0, nil) +} + + +// Validate 验证解析结果 +func (vp *ValidationParser) Validate() error { + return nil +} + +// GetStatistics 获取解析统计 +func (vp *ValidationParser) GetStatistics() interface{} { + vp.mu.RLock() + defer vp.mu.RUnlock() + + return map[string]interface{}{ + "parser_type": "validation", + "options": vp.options, + } +} \ No newline at end of file