fscan/Common/parsers/FileReader.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

360 lines
8.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package 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,
}
}