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

主要改进: - 模块化设计:将549行Parse.go拆分为6个专用解析器 - 性能优化:添加文件缓存、并发处理和去重机制 - 增强验证:实现配置冲突检测和参数验证 - 改善体验:清理冗余警告,添加解析结果摘要显示 - 向后兼容:保持所有原有API接口不变 新增模块: - FileReader: 高性能文件读取和缓存 - CredentialParser: 用户名密码解析 - TargetParser: 主机目标解析 - NetworkParser: 网络配置解析 - ValidationParser: 参数验证和冲突检测 - Types: 统一的数据结构定义 修复问题: - 消除重复的"Web超时时间大于普通超时时间"警告 - 添加目标主机、端口、代理等配置信息显示 - 删除说教性安全警告,保留技术性提示
360 lines
8.8 KiB
Go
360 lines
8.8 KiB
Go
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,
|
||
}
|
||
} |