mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 05:56:46 +08:00
refactor: 重构Parse.go解析模块,优化参数验证和信息显示
主要改进: - 模块化设计:将549行Parse.go拆分为6个专用解析器 - 性能优化:添加文件缓存、并发处理和去重机制 - 增强验证:实现配置冲突检测和参数验证 - 改善体验:清理冗余警告,添加解析结果摘要显示 - 向后兼容:保持所有原有API接口不变 新增模块: - FileReader: 高性能文件读取和缓存 - CredentialParser: 用户名密码解析 - TargetParser: 主机目标解析 - NetworkParser: 网络配置解析 - ValidationParser: 参数验证和冲突检测 - Types: 统一的数据结构定义 修复问题: - 消除重复的"Web超时时间大于普通超时时间"警告 - 添加目标主机、端口、代理等配置信息显示 - 删除说教性安全警告,保留技术性提示
This commit is contained in:
parent
7077590bae
commit
c04bfcfd07
1088
Common/Parse.go
1088
Common/Parse.go
File diff suppressed because it is too large
Load Diff
376
Common/parsers/CredentialParser.go
Normal file
376
Common/parsers/CredentialParser.go
Normal file
@ -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,
|
||||
}
|
||||
}
|
360
Common/parsers/FileReader.go
Normal file
360
Common/parsers/FileReader.go
Normal file
@ -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,
|
||||
}
|
||||
}
|
384
Common/parsers/NetworkParser.go
Normal file
384
Common/parsers/NetworkParser.go
Normal file
@ -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,
|
||||
}
|
||||
}
|
712
Common/parsers/TargetParser.go
Normal file
712
Common/parsers/TargetParser.go
Normal file
@ -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,
|
||||
}
|
||||
}
|
169
Common/parsers/Types.go
Normal file
169
Common/parsers/Types.go
Normal file
@ -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,
|
||||
}
|
||||
}
|
303
Common/parsers/ValidationParser.go
Normal file
303
Common/parsers/ValidationParser.go
Normal file
@ -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,
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user