mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-10-24 08:54:27 +00:00
合并扫描状态与结果备份修复
This commit is contained in:
commit
8e78ba557c
14 files changed
+444
-95
No files matched your search
@@ -245,8 +245,20 @@ func parseHashes(fv *FlagVars) ([]string, [][]byte, error) {
|
||||
// =============================================================================
|
||||
|
||||
func parseTargets(fv *FlagVars, info *HostInfo, cfg *Config, state *State) error {
|
||||
// 检查是否为 host:port 格式
|
||||
ports := fv.Ports
|
||||
if fv.PortsFile != "" {
|
||||
lines, err := parsers.ReadLinesFromFile(fv.PortsFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s", i18n.Tr("config_read_ports_failed", fv.PortsFile, err))
|
||||
}
|
||||
ports = strings.Join(lines, ",")
|
||||
if len(parsers.ParsePort(ports)) == 0 {
|
||||
return fmt.Errorf("%s", i18n.Tr("invalid_port", ports))
|
||||
}
|
||||
cfg.Target.Ports = ports
|
||||
}
|
||||
|
||||
// 检查是否为 host:port 格式
|
||||
if info.Host != "" && strings.Contains(info.Host, ":") {
|
||||
if _, portStr, err := net.SplitHostPort(info.Host); err == nil {
|
||||
if port, portErr := strconv.Atoi(portStr); portErr == nil && port >= 1 && port <= 65535 {
|
||||
|
||||
@@ -315,6 +315,8 @@ config_read_passwords_failed:
|
||||
other: "Failed to read password file {{.Arg1}}: {{.Arg2}}"
|
||||
config_read_urls_failed:
|
||||
other: "Failed to read URL file {{.Arg1}}: {{.Arg2}}"
|
||||
config_read_ports_failed:
|
||||
other: "Failed to read port file {{.Arg1}}: {{.Arg2}}"
|
||||
|
||||
# ========================= Plugin Scan Messages (with parameters) =========================
|
||||
scan_plugin_not_found:
|
||||
|
||||
@@ -315,6 +315,8 @@ config_read_passwords_failed:
|
||||
other: "读取密码文件 {{.Arg1}} 失败: {{.Arg2}}"
|
||||
config_read_urls_failed:
|
||||
other: "读取URL文件 {{.Arg1}} 失败: {{.Arg2}}"
|
||||
config_read_ports_failed:
|
||||
other: "读取端口文件 {{.Arg1}} 失败: {{.Arg2}}"
|
||||
|
||||
# ========================= 插件扫描消息 (带参数) =========================
|
||||
scan_plugin_not_found:
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package output
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestWritersPreserveBackupOnFinalWriteFailure(t *testing.T) {
|
||||
for _, format := range []Format{FormatTXT, FormatJSON, FormatCSV} {
|
||||
t.Run(string(format), func(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "result."+string(format))
|
||||
manager, err := NewManager(DefaultManagerConfig(path, format))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer manager.Close()
|
||||
if err := manager.SaveResult(&ScanResult{Type: TypeHost, Target: "127.0.0.1"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
backupPath := path + ".realtime.tmp"
|
||||
before, err := os.ReadFile(backupPath)
|
||||
if err != nil || len(before) == 0 {
|
||||
t.Fatalf("backup before Close: %q, %v", before, err)
|
||||
}
|
||||
var file *os.File
|
||||
switch writer := manager.writer.(type) {
|
||||
case *TXTWriter:
|
||||
file = writer.file
|
||||
case *JSONWriter:
|
||||
file = writer.file
|
||||
case *CSVWriter:
|
||||
file = writer.file
|
||||
}
|
||||
if err := file.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := manager.Close(); err == nil {
|
||||
t.Fatal("final write failure was not reported")
|
||||
}
|
||||
after, err := os.ReadFile(backupPath)
|
||||
if err != nil || !bytes.Equal(before, after) {
|
||||
t.Fatalf("recovery backup was lost after final write failed: %q, %v", after, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+27
-43
@@ -71,6 +71,25 @@ func targetWithPort(target string, port interface{}) string {
|
||||
return net.JoinHostPort(target, portText)
|
||||
}
|
||||
|
||||
// closeOutputFiles 仅在最终结果完整落盘并关闭后删除恢复备份。
|
||||
func closeOutputFiles(file, backup *os.File, backupPath string, writeErr error) error {
|
||||
if writeErr == nil {
|
||||
writeErr = file.Sync()
|
||||
}
|
||||
if err := file.Close(); err != nil && writeErr == nil {
|
||||
writeErr = err
|
||||
}
|
||||
if backup != nil {
|
||||
if err := backup.Close(); err != nil && writeErr == nil {
|
||||
writeErr = err
|
||||
}
|
||||
if writeErr == nil {
|
||||
writeErr = os.Remove(backupPath)
|
||||
}
|
||||
}
|
||||
return writeErr
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// TXTWriter - 文本格式写入器
|
||||
// =============================================================================
|
||||
@@ -347,23 +366,7 @@ func (w *TXTWriter) Close() error {
|
||||
|
||||
w.closed = true
|
||||
|
||||
// 关闭并删除实时备份文件(正常结束,不再需要)
|
||||
if w.realtimeFile != nil {
|
||||
w.realtimeFile.Close()
|
||||
os.Remove(w.realtimePath)
|
||||
}
|
||||
|
||||
var firstErr error
|
||||
if err := w.bufWriter.Flush(); err != nil {
|
||||
firstErr = err
|
||||
}
|
||||
if err := w.file.Sync(); err != nil && firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
if err := w.file.Close(); err != nil && firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
return firstErr
|
||||
return closeOutputFiles(w.file, w.realtimeFile, w.realtimePath, w.bufWriter.Flush())
|
||||
}
|
||||
|
||||
// writeSection 写入一个分类的所有结果
|
||||
@@ -565,22 +568,11 @@ func (w *JSONWriter) Close() error {
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(output, JSONIndentPrefix, JSONIndentString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.closed = true
|
||||
|
||||
// 关闭并删除实时备份文件(正常结束,不再需要)
|
||||
if w.realtimeFile != nil {
|
||||
w.realtimeFile.Close()
|
||||
os.Remove(w.realtimePath)
|
||||
if err == nil {
|
||||
_, err = w.file.Write(data)
|
||||
}
|
||||
|
||||
if _, err := w.file.Write(data); err != nil {
|
||||
return err
|
||||
}
|
||||
return w.file.Close()
|
||||
return closeOutputFiles(w.file, w.realtimeFile, w.realtimePath, err)
|
||||
}
|
||||
|
||||
// GetFormat 获取格式类型
|
||||
@@ -692,20 +684,12 @@ func (w *CSVWriter) Close() error {
|
||||
|
||||
w.closed = true
|
||||
|
||||
// 关闭并删除实时备份文件(正常结束,不再需要)
|
||||
if w.realtimeFile != nil {
|
||||
w.realtimeFile.Close()
|
||||
os.Remove(w.realtimePath)
|
||||
}
|
||||
|
||||
w.csvWriter.Flush()
|
||||
if err := w.csvWriter.Error(); err != nil {
|
||||
return err
|
||||
err := w.csvWriter.Error()
|
||||
if err == nil {
|
||||
err = w.bufWriter.Flush()
|
||||
}
|
||||
if err := w.bufWriter.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
return w.file.Close()
|
||||
return closeOutputFiles(w.file, w.realtimeFile, w.realtimePath, err)
|
||||
}
|
||||
|
||||
func (w *CSVWriter) writeSection(title string, headers []string, results []*ScanResult, formatter func(*ScanResult) []string) {
|
||||
|
||||
@@ -73,6 +73,11 @@ func (it *HostIterator) Close() error {
|
||||
return firstErr
|
||||
}
|
||||
|
||||
// IsExcluded 检查单个主机是否匹配当前迭代器的排除规则。
|
||||
func (it *HostIterator) IsExcluded(host string) bool {
|
||||
return it.exclude != nil && it.exclude.match(host)
|
||||
}
|
||||
|
||||
func (it *HostIterator) Next() (string, bool, error) {
|
||||
for {
|
||||
if it.current == nil {
|
||||
@@ -94,7 +99,7 @@ func (it *HostIterator) Next() (string, bool, error) {
|
||||
it.current = nil
|
||||
continue
|
||||
}
|
||||
if it.exclude != nil && it.exclude.match(host) {
|
||||
if it.IsExcluded(host) {
|
||||
continue
|
||||
}
|
||||
return host, true, nil
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/shadow1ng/fscan/common/config"
|
||||
"github.com/shadow1ng/fscan/common/parsers"
|
||||
)
|
||||
|
||||
func TestBuildConfigLoadsPortsFile(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "ports.txt")
|
||||
if err := os.WriteFile(path, []byte("# ports\n22\n8000-8002\n443,22\n"), 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cfg, _, err := BuildConfig(&FlagVars{Ports: config.MainPorts, PortsFile: path}, &HostInfo{Host: "127.0.0.1"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got, want := parsers.ParsePort(cfg.Target.Ports), []int{22, 443, 8000, 8001, 8002}; !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("ports = %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildConfigRejectsUnusablePortsFile(t *testing.T) {
|
||||
for _, contents := range []string{"", "# no ports\n", "invalid\n0\n65536\n"} {
|
||||
path := filepath.Join(t.TempDir(), "ports.txt")
|
||||
if err := os.WriteFile(path, []byte(contents), 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, _, err := BuildConfig(&FlagVars{Ports: "22", PortsFile: path}, &HostInfo{}); err == nil {
|
||||
t.Errorf("accepted unusable port file %q", contents)
|
||||
}
|
||||
}
|
||||
if _, _, err := BuildConfig(&FlagVars{PortsFile: filepath.Join(t.TempDir(), "missing.txt")}, &HostInfo{}); err == nil {
|
||||
t.Error("accepted missing port file")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user