fscan/common/output/writers.go
ZacharyZcR a3177b28a6 fix: 修复插件系统逻辑Bug和架构问题
主要修复:
1. 修复时间显示Bug - StartTime初始化问题
2. 修复Web智能探测错误检测预定义端口而非用户指定端口
3. 修复本地插件被错误调用到端口扫描中的问题
4. 修复host:port格式双重处理导致的多余端口扫描
5. 统一插件过滤逻辑,消除接口不一致性
6. 优化Web检测缓存机制,减少重复HTTP请求

技术改进:
- 重构插件适用性检查逻辑,确保策略过滤器正确工作
- 区分Web检测的自动发现模式和用户指定端口模式
- 在解析阶段正确处理host:port格式,避免与默认端口冲突
- 完善缓存机制,提升性能

测试验证:
- ./fscan -h 127.0.0.1:3306 现在只检测3306端口
- 本地插件不再参与端口扫描
- Web检测只对指定端口进行协议检测
- 时间显示正确
2025-09-01 23:50:32 +00:00

459 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 output
import (
"encoding/csv"
"encoding/json"
"fmt"
"os"
"strings"
"sync"
"time"
"github.com/shadow1ng/fscan/common/i18n"
)
// TXTWriter 文本格式写入器
type TXTWriter struct {
file *os.File
mu sync.Mutex
closed bool
}
// NewTXTWriter 创建文本写入器
func NewTXTWriter(filePath string) (*TXTWriter, error) {
file, err := os.OpenFile(filePath, DefaultFileFlags, DefaultFilePermissions)
if err != nil {
return nil, fmt.Errorf(i18n.GetText("output_create_file_failed"), "文本", err)
}
return &TXTWriter{
file: file,
}, nil
}
// WriteHeader 写入头部(文本格式无需头部)
func (w *TXTWriter) WriteHeader() error {
return nil
}
// Write 写入扫描结果
func (w *TXTWriter) Write(result *ScanResult) error {
w.mu.Lock()
defer w.mu.Unlock()
if w.closed {
return fmt.Errorf(i18n.GetText("output_writer_closed"))
}
// 格式化 Details 为键值对字符串
var details string
if len(result.Details) > 0 {
pairs := make([]string, 0, len(result.Details))
for k, v := range result.Details {
pairs = append(pairs, fmt.Sprintf(TxtKeyValueFormat, k, v))
}
details = strings.Join(pairs, TxtDetailsSeparator)
}
// 使用类似原有格式的文本输出
txt := fmt.Sprintf(TxtOutputTemplate,
result.Time.Format(TxtTimeFormat),
result.Type,
result.Target,
result.Status,
)
if details != "" {
txt += fmt.Sprintf(TxtDetailsFormat, details)
}
txt += TxtNewline
_, err := w.file.WriteString(txt)
return err
}
// Flush 刷新缓冲区
func (w *TXTWriter) Flush() error {
w.mu.Lock()
defer w.mu.Unlock()
if w.closed {
return nil
}
return w.file.Sync()
}
// Close 关闭写入器
func (w *TXTWriter) Close() error {
w.mu.Lock()
defer w.mu.Unlock()
if w.closed {
return nil
}
w.closed = true
return w.file.Close()
}
// GetFormat 获取格式类型
func (w *TXTWriter) GetFormat() OutputFormat {
return FormatTXT
}
// JSONWriter JSON格式写入器
type JSONWriter struct {
file *os.File
encoder *json.Encoder
mu sync.Mutex
closed bool
}
// NewJSONWriter 创建JSON写入器
func NewJSONWriter(filePath string) (*JSONWriter, error) {
file, err := os.OpenFile(filePath, DefaultFileFlags, DefaultFilePermissions)
if err != nil {
return nil, fmt.Errorf(i18n.GetText("output_create_file_failed"), "JSON", err)
}
encoder := json.NewEncoder(file)
encoder.SetIndent(JSONIndentPrefix, JSONIndentString)
return &JSONWriter{
file: file,
encoder: encoder,
}, nil
}
// WriteHeader 写入头部JSON格式无需头部
func (w *JSONWriter) WriteHeader() error {
return nil
}
// Write 写入扫描结果
func (w *JSONWriter) Write(result *ScanResult) error {
w.mu.Lock()
defer w.mu.Unlock()
if w.closed {
return fmt.Errorf(i18n.GetText("output_writer_closed"))
}
return w.encoder.Encode(result)
}
// Flush 刷新缓冲区
func (w *JSONWriter) Flush() error {
w.mu.Lock()
defer w.mu.Unlock()
if w.closed {
return nil
}
return w.file.Sync()
}
// Close 关闭写入器
func (w *JSONWriter) Close() error {
w.mu.Lock()
defer w.mu.Unlock()
if w.closed {
return nil
}
w.closed = true
return w.file.Close()
}
// GetFormat 获取格式类型
func (w *JSONWriter) GetFormat() OutputFormat {
return FormatJSON
}
// CSVWriter CSV格式写入器
type CSVWriter struct {
file *os.File
csvWriter *csv.Writer
mu sync.Mutex
closed bool
headerWritten bool
}
// NewCSVWriter 创建CSV写入器
func NewCSVWriter(filePath string) (*CSVWriter, error) {
file, err := os.OpenFile(filePath, DefaultFileFlags, DefaultFilePermissions)
if err != nil {
return nil, fmt.Errorf(i18n.GetText("output_create_file_failed"), "CSV", err)
}
csvWriter := csv.NewWriter(file)
return &CSVWriter{
file: file,
csvWriter: csvWriter,
}, nil
}
// WriteHeader 写入CSV头部
func (w *CSVWriter) WriteHeader() error {
w.mu.Lock()
defer w.mu.Unlock()
if w.headerWritten {
return nil
}
headers := CSVHeaders
err := w.csvWriter.Write(headers)
if err != nil {
return fmt.Errorf(i18n.GetText("output_write_header_failed"), err)
}
w.csvWriter.Flush()
w.headerWritten = true
return w.csvWriter.Error()
}
// Write 写入扫描结果
func (w *CSVWriter) Write(result *ScanResult) error {
w.mu.Lock()
defer w.mu.Unlock()
if w.closed {
return fmt.Errorf(i18n.GetText("output_writer_closed"))
}
// 确保头部已写入
if !w.headerWritten {
if err := w.writeHeaderUnsafe(); err != nil {
return err
}
}
// 序列化Details为JSON字符串
details, err := json.Marshal(result.Details)
if err != nil {
details = []byte(EmptyJSONObject)
}
record := []string{
result.Time.Format(DefaultTimeFormat),
string(result.Type),
result.Target,
result.Status,
string(details),
}
err = w.csvWriter.Write(record)
if err != nil {
return err
}
w.csvWriter.Flush()
return w.csvWriter.Error()
}
// writeHeaderUnsafe 不安全的写入头部(内部使用,无锁)
func (w *CSVWriter) writeHeaderUnsafe() error {
if w.headerWritten {
return nil
}
headers := CSVHeaders
err := w.csvWriter.Write(headers)
if err != nil {
return fmt.Errorf(i18n.GetText("output_write_header_failed"), err)
}
w.csvWriter.Flush()
w.headerWritten = true
return w.csvWriter.Error()
}
// Flush 刷新缓冲区
func (w *CSVWriter) Flush() error {
w.mu.Lock()
defer w.mu.Unlock()
if w.closed {
return nil
}
w.csvWriter.Flush()
return w.csvWriter.Error()
}
// Close 关闭写入器
func (w *CSVWriter) Close() error {
w.mu.Lock()
defer w.mu.Unlock()
if w.closed {
return nil
}
w.csvWriter.Flush()
err := w.csvWriter.Error()
w.closed = true
if fileErr := w.file.Close(); fileErr != nil && err == nil {
err = fileErr
}
return err
}
// GetFormat 获取格式类型
func (w *CSVWriter) GetFormat() OutputFormat {
return FormatCSV
}
// CSVReader CSV格式读取器
type CSVReader struct {
filePath string
mu sync.Mutex
}
// NewCSVReader 创建CSV读取器
func NewCSVReader(filePath string) *CSVReader {
return &CSVReader{
filePath: filePath,
}
}
// Read 读取所有结果
func (r *CSVReader) Read() ([]*ScanResult, error) {
return r.ReadWithFilter(nil)
}
// ReadWithFilter 带过滤条件读取结果
func (r *CSVReader) ReadWithFilter(filter *ResultFilter) ([]*ScanResult, error) {
r.mu.Lock()
defer r.mu.Unlock()
file, err := os.Open(r.filePath)
if err != nil {
return nil, fmt.Errorf(i18n.GetText("output_open_file_failed"), err)
}
defer file.Close()
reader := csv.NewReader(file)
records, err := reader.ReadAll()
if err != nil {
return nil, fmt.Errorf(i18n.GetText("output_read_file_failed"), err)
}
var results []*ScanResult
for i, row := range records {
// 跳过CSV头部
if i == CSVHeaderRowIndex {
continue
}
if len(row) < CSVMinColumns {
continue // 数据不完整
}
result, err := r.parseCSVRow(row)
if err != nil {
continue // 跳过解析失败的行
}
// 应用过滤器
if filter != nil && !r.matchFilter(result, filter) {
continue
}
results = append(results, result)
// 应用限制
if filter != nil && filter.Limit > 0 && len(results) >= filter.Limit {
break
}
}
return results, nil
}
// parseCSVRow 解析CSV行
func (r *CSVReader) parseCSVRow(row []string) (*ScanResult, error) {
// 解析时间
t, err := parseTime(row[CSVTimeColumnIndex])
if err != nil {
return nil, err
}
// 解析Details
var details map[string]interface{}
if err := json.Unmarshal([]byte(row[CSVDetailsColumnIndex]), &details); err != nil {
details = make(map[string]interface{})
}
return &ScanResult{
Time: t,
Type: ResultType(row[CSVTypeColumnIndex]),
Target: row[CSVTargetColumnIndex],
Status: row[CSVStatusColumnIndex],
Details: details,
}, nil
}
// matchFilter 检查结果是否匹配过滤器
func (r *CSVReader) matchFilter(result *ScanResult, filter *ResultFilter) bool {
// 检查类型过滤
if len(filter.Types) > 0 {
found := false
for _, t := range filter.Types {
if result.Type == t {
found = true
break
}
}
if !found {
return false
}
}
// 检查目标过滤
if len(filter.Targets) > 0 {
found := false
for _, target := range filter.Targets {
if strings.Contains(result.Target, target) {
found = true
break
}
}
if !found {
return false
}
}
// 检查时间范围过滤
if filter.TimeRange != nil {
if result.Time.Before(filter.TimeRange.Start) || result.Time.After(filter.TimeRange.End) {
return false
}
}
return true
}
// Close 关闭读取器
func (r *CSVReader) Close() error {
return nil // CSV读取器无需特殊关闭操作
}
// parseTime 解析时间字符串
func parseTime(timeStr string) (time.Time, error) {
// 尝试多种时间格式
formats := GetSupportedTimeFormats()
for _, format := range formats {
if t, err := time.Parse(format, timeStr); err == nil {
return t, nil
}
}
return time.Time{}, fmt.Errorf(i18n.GetText("output_parse_time_failed"), timeStr)
}