fscan/Common/Parse.go
ZacharyZcR c04bfcfd07 refactor: 重构Parse.go解析模块,优化参数验证和信息显示
主要改进:
- 模块化设计:将549行Parse.go拆分为6个专用解析器
- 性能优化:添加文件缓存、并发处理和去重机制
- 增强验证:实现配置冲突检测和参数验证
- 改善体验:清理冗余警告,添加解析结果摘要显示
- 向后兼容:保持所有原有API接口不变

新增模块:
- FileReader: 高性能文件读取和缓存
- CredentialParser: 用户名密码解析
- TargetParser: 主机目标解析
- NetworkParser: 网络配置解析
- ValidationParser: 参数验证和冲突检测
- Types: 统一的数据结构定义

修复问题:
- 消除重复的"Web超时时间大于普通超时时间"警告
- 添加目标主机、端口、代理等配置信息显示
- 删除说教性安全警告,保留技术性提示
2025-08-05 02:14:10 +08:00

684 lines
18 KiB
Go

package Common
import (
"fmt"
"sync"
"time"
"github.com/fatih/color"
"github.com/shadow1ng/fscan/Common/logging"
"github.com/shadow1ng/fscan/Common/parsers"
)
// 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 {
// 首先应用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,
},
}
// 执行解析
result, err := parser.ParseAll(input)
if 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 解析用户名配置 - 兼容接口
func ParseUser() error {
parser := getGlobalParser()
input := &parsers.CredentialInput{
Username: Username,
UsersFile: UsersFile,
AddUsers: AddUsers,
}
result, err := parser.credentialParser.Parse(input, parser.options)
if err != nil {
return err
}
// 更新全局变量
if len(result.Config.Credentials.Usernames) > 0 {
for serviceName := range Userdict {
Userdict[serviceName] = result.Config.Credentials.Usernames
}
}
return nil
}
// ParsePass 解析密码配置 - 兼容接口
func ParsePass(Info *HostInfo) error {
parser := getGlobalParser()
// 处理密码
credInput := &parsers.CredentialInput{
Password: Password,
PasswordsFile: PasswordsFile,
AddPasswords: AddPasswords,
HashValue: HashValue,
HashFile: HashFile,
}
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
}
// ParseInput 解析输入参数 - 兼容接口
func ParseInput(Info *HostInfo) error {
parser := getGlobalParser()
// 网络配置解析
networkInput := &parsers.NetworkInput{
HttpProxy: HttpProxy,
Socks5Proxy: Socks5Proxy,
Timeout: Timeout,
WebTimeout: WebTimeout,
DisablePing: DisablePing,
DnsLog: DnsLog,
UserAgent: UserAgent,
Cookie: Cookie,
}
networkResult, err := parser.networkParser.Parse(networkInput, parser.options)
if err != nil {
return fmt.Errorf("网络配置解析失败: %v", err)
}
// 更新全局变量
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
}
// 验证配置
validationInput := &parsers.ValidationInput{
ScanMode: ScanMode,
LocalMode: LocalMode,
HasHosts: Info.Host != "" || HostsFile != "",
HasURLs: TargetURL != "" || URLsFile != "",
HasPorts: Ports != "" || PortsFile != "",
HasProxy: HttpProxy != "" || Socks5Proxy != "",
DisablePing: DisablePing,
}
validationResult, err := parser.validationParser.Parse(validationInput, nil, parser.options)
if err != nil {
return fmt.Errorf("参数验证失败: %v", err)
}
// 报告验证警告
for _, warning := range validationResult.Warnings {
LogBase(warning)
}
// 如果有严重错误,返回错误
for _, validationError := range validationResult.Errors {
LogError(validationError.Error())
}
return nil
}
// 保持原有的工具函数
// ReadFileLines 读取文件行 - 兼容接口
func ReadFileLines(filename string) ([]string, error) {
parser := getGlobalParser()
result, err := parser.fileReader.ReadFile(filename)
if err != nil {
return nil, err
}
return result.Lines, nil
}
// RemoveDuplicate 去重函数 - 保持原有实现
func RemoveDuplicate(old []string) []string {
temp := make(map[string]struct{})
var result []string
for _, item := range old {
if _, exists := temp[item]; !exists {
temp[item] = struct{}{}
result = append(result, item)
}
}
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()
}
}