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

主要更改: - 统一包目录命名为小写(Core→core, Plugins→plugins, WebScan→webscan) - 更新所有import路径以符合Go语言命名规范 - 重构parsers模块,简化复杂的工厂模式(从2000+行优化至400行) - 移除i18n兼容层,统一使用模块化i18n包 - 简化Core/Manager.go架构(从591行优化至133行) - 清理冗余文件:备份文件、构建产物、测试配置、重复图片 - 移除TestDocker测试环境配置目录 - 解决变量命名冲突问题 性能优化: - 减少代码复杂度60-70% - 提升构建和运行性能 - 保持完整功能兼容性 代码质量: - 符合Go语言最佳实践 - 统一命名规范 - 优化项目结构
431 lines
12 KiB
Go
431 lines
12 KiB
Go
package common
|
||
|
||
/*
|
||
Bridge.go - 统一桥接模块
|
||
|
||
将Config.go、Log.go、Output.go、Proxy.go的桥接功能合并到一个文件中,
|
||
减少文件数量,提高代码组织性。保持所有原有API的完全兼容性。
|
||
*/
|
||
|
||
import (
|
||
"context"
|
||
"crypto/tls"
|
||
"fmt"
|
||
"io"
|
||
"log"
|
||
"net"
|
||
"sync"
|
||
"time"
|
||
|
||
"github.com/fatih/color"
|
||
"github.com/schollz/progressbar/v3"
|
||
|
||
"github.com/shadow1ng/fscan/common/config"
|
||
"github.com/shadow1ng/fscan/common/i18n"
|
||
"github.com/shadow1ng/fscan/common/logging"
|
||
"github.com/shadow1ng/fscan/common/output"
|
||
"github.com/shadow1ng/fscan/common/proxy"
|
||
)
|
||
|
||
// =============================================================================
|
||
// 配置桥接 (Config.go 功能)
|
||
// =============================================================================
|
||
|
||
var version = "2.0.2"
|
||
|
||
// 向后兼容的输出配置变量
|
||
var (
|
||
Outputfile string
|
||
OutputFormat string
|
||
ProgressBar *progressbar.ProgressBar
|
||
OutputMutex sync.Mutex
|
||
)
|
||
|
||
// PocInfo POC详细信息结构
|
||
type PocInfo = config.PocInfo
|
||
|
||
func syncOutputConfig() {
|
||
cfg := config.GetGlobalConfig()
|
||
if cfg != nil && cfg.Output != nil {
|
||
Outputfile = cfg.Output.Outputfile
|
||
OutputFormat = cfg.Output.OutputFormat
|
||
}
|
||
ProgressBar = config.GetGlobalProgressBar()
|
||
}
|
||
|
||
// =============================================================================
|
||
// 日志桥接 (Log.go 功能)
|
||
// =============================================================================
|
||
|
||
// 全局日志状态和变量
|
||
var (
|
||
status = logging.NewScanStatus()
|
||
Num, End int64
|
||
StartTime = time.Now()
|
||
globalLogger *logging.Logger
|
||
loggerMutex sync.Mutex
|
||
)
|
||
|
||
// 日志级别常量
|
||
const (
|
||
LogLevelAll = string(logging.LevelAll)
|
||
LogLevelError = string(logging.LevelError)
|
||
LogLevelBase = string(logging.LevelBase)
|
||
LogLevelInfo = string(logging.LevelInfo)
|
||
LogLevelSuccess = string(logging.LevelSuccess)
|
||
LogLevelDebug = string(logging.LevelDebug)
|
||
LogLevelInfoSuccess = string(logging.LevelInfoSuccess)
|
||
LogLevelBaseInfoSuccess = string(logging.LevelBaseInfoSuccess)
|
||
)
|
||
|
||
// 类型别名
|
||
type LogEntry = logging.LogEntry
|
||
type ScanStatus = logging.ScanStatus
|
||
type ProgressDisplay = logging.ProgressDisplay
|
||
|
||
func getGlobalLogger() *logging.Logger {
|
||
loggerMutex.Lock()
|
||
defer loggerMutex.Unlock()
|
||
|
||
if globalLogger == nil {
|
||
// 获取正确的日志级别 - 动态获取确保使用最新值
|
||
level := getLogLevelFromString(LogLevel)
|
||
|
||
config := &logging.LoggerConfig{
|
||
Level: level, EnableColor: !NoColor, SlowOutput: SlowLogOutput,
|
||
ShowProgress: true, StartTime: StartTime,
|
||
LevelColors: map[logging.LogLevel]color.Attribute{
|
||
logging.LevelError: color.FgBlue, logging.LevelBase: color.FgYellow,
|
||
logging.LevelInfo: color.FgGreen, logging.LevelSuccess: color.FgRed, logging.LevelDebug: color.FgWhite,
|
||
},
|
||
}
|
||
globalLogger = logging.NewLogger(config)
|
||
if ProgressBar != nil {
|
||
globalLogger.SetProgressBar(ProgressBar)
|
||
}
|
||
globalLogger.SetOutputMutex(&OutputMutex)
|
||
status = globalLogger.GetScanStatus()
|
||
}
|
||
return globalLogger
|
||
}
|
||
|
||
// shouldLogLevel 检查是否应该记录该级别的日志
|
||
func shouldLogLevel(logger *logging.Logger, level logging.LogLevel) bool {
|
||
// 直接基于当前的LogLevel配置判断
|
||
switch LogLevel {
|
||
case "all", "ALL":
|
||
return true
|
||
case "error", "ERROR":
|
||
return level == logging.LevelError
|
||
case "base", "BASE":
|
||
return level == logging.LevelBase
|
||
case "info", "INFO":
|
||
return level == logging.LevelInfo
|
||
case "success", "SUCCESS":
|
||
return level == logging.LevelSuccess
|
||
case "debug", "DEBUG":
|
||
return level == logging.LevelDebug || level == logging.LevelError
|
||
case "info,success", "INFO_SUCCESS":
|
||
return level == logging.LevelInfo || level == logging.LevelSuccess
|
||
case "base,info,success", "BASE_INFO_SUCCESS":
|
||
return level == logging.LevelBase || level == logging.LevelInfo || level == logging.LevelSuccess
|
||
default:
|
||
// 默认使用BaseInfoSuccess级别,不包含DEBUG和ERROR级别
|
||
return level == logging.LevelBase || level == logging.LevelInfo || level == logging.LevelSuccess
|
||
}
|
||
}
|
||
|
||
// getLogLevelFromString 根据字符串获取日志级别
|
||
func getLogLevelFromString(levelStr string) logging.LogLevel {
|
||
switch levelStr {
|
||
case "all", "ALL":
|
||
return logging.LevelAll
|
||
case "error", "ERROR":
|
||
return logging.LevelError
|
||
case "base", "BASE":
|
||
return logging.LevelBase
|
||
case "info", "INFO":
|
||
return logging.LevelInfo
|
||
case "success", "SUCCESS":
|
||
return logging.LevelSuccess
|
||
case "debug", "DEBUG":
|
||
return logging.LevelDebug
|
||
case "info,success":
|
||
return logging.LevelInfoSuccess
|
||
case "base,info,success", "BASE_INFO_SUCCESS":
|
||
return logging.LevelBaseInfoSuccess
|
||
default:
|
||
// 默认使用InfoSuccess级别,只显示Info和Success消息
|
||
return logging.LevelInfoSuccess
|
||
}
|
||
}
|
||
|
||
// 日志相关函数
|
||
func InitLogger() {
|
||
// 重置全局logger以确保使用最新的LogLevel配置
|
||
loggerMutex.Lock()
|
||
globalLogger = nil
|
||
loggerMutex.Unlock()
|
||
|
||
log.SetOutput(io.Discard)
|
||
getGlobalLogger().Initialize()
|
||
}
|
||
func LogDebug(msg string) { logWithProgressCoordination(msg, "debug") }
|
||
func LogBase(msg string) { logWithProgressCoordination(msg, "base") }
|
||
func LogInfo(msg string) { logWithProgressCoordination(msg, "info") }
|
||
func LogSuccess(result string) { logWithProgressCoordination(result, "success") }
|
||
func LogError(errMsg string) { logWithProgressCoordination(errMsg, "error") }
|
||
func CheckErrs(err error) error { return logging.CheckErrs(err) }
|
||
|
||
// logWithProgressCoordination 协调日志输出与进度条的冲突
|
||
func logWithProgressCoordination(msg, level string) {
|
||
logger := getGlobalLogger()
|
||
|
||
// 首先检查是否应该记录这个级别的日志
|
||
var logLevel logging.LogLevel
|
||
switch level {
|
||
case "debug":
|
||
logLevel = logging.LevelDebug
|
||
case "base":
|
||
logLevel = logging.LevelBase
|
||
case "info":
|
||
logLevel = logging.LevelInfo
|
||
case "success":
|
||
logLevel = logging.LevelSuccess
|
||
case "error":
|
||
logLevel = logging.LevelError
|
||
default:
|
||
logLevel = logging.LevelDebug
|
||
}
|
||
|
||
// 如果当前日志级别不应该显示这条消息,直接返回
|
||
if !shouldLogLevel(logger, logLevel) {
|
||
return
|
||
}
|
||
|
||
// 如果进度条活跃,使用协调输出
|
||
if IsProgressActive() {
|
||
// 简单格式化消息,保持时间戳格式一致
|
||
elapsed := time.Since(StartTime)
|
||
var prefix, colorCode, resetCode string
|
||
|
||
if !NoColor {
|
||
resetCode = "\033[0m"
|
||
switch level {
|
||
case "debug":
|
||
colorCode = "\033[37m" // 白色
|
||
prefix = " "
|
||
case "base":
|
||
colorCode = "\033[33m" // 黄色
|
||
prefix = " "
|
||
case "info":
|
||
colorCode = "\033[32m" // 绿色
|
||
prefix = " [*] "
|
||
case "success":
|
||
colorCode = "\033[31m" // 红色
|
||
prefix = " [+] "
|
||
case "error":
|
||
colorCode = "\033[34m" // 蓝色
|
||
prefix = " [-] "
|
||
}
|
||
} else {
|
||
switch level {
|
||
case "info":
|
||
prefix = " [*] "
|
||
case "success":
|
||
prefix = " [+] "
|
||
case "error":
|
||
prefix = " [-] "
|
||
default:
|
||
prefix = " "
|
||
}
|
||
}
|
||
|
||
formattedMsg := fmt.Sprintf("[%.1fs]%s%s%s%s",
|
||
elapsed.Seconds(), prefix, colorCode, msg, resetCode)
|
||
LogWithProgress(formattedMsg)
|
||
} else {
|
||
// 如果进度条不活跃,使用原始日志方法
|
||
switch level {
|
||
case "debug":
|
||
logger.Debug(msg)
|
||
case "base":
|
||
logger.Base(msg)
|
||
case "info":
|
||
logger.Info(msg)
|
||
case "success":
|
||
logger.Success(msg)
|
||
case "error":
|
||
logger.Error(msg)
|
||
}
|
||
}
|
||
}
|
||
|
||
// =============================================================================
|
||
// 输出桥接 (Output.go 功能)
|
||
// =============================================================================
|
||
|
||
// 全局输出管理器
|
||
var ResultOutput *OutputManager
|
||
|
||
type OutputManager struct{ manager *output.Manager }
|
||
type ResultType = output.ResultType
|
||
|
||
const (
|
||
HOST ResultType = output.TypeHost
|
||
PORT ResultType = output.TypePort
|
||
SERVICE ResultType = output.TypeService
|
||
VULN ResultType = output.TypeVuln
|
||
)
|
||
|
||
type ScanResult = output.ScanResult
|
||
|
||
func createOutputManager(outputPath, outputFormat string) (*OutputManager, error) {
|
||
var format output.OutputFormat
|
||
switch outputFormat {
|
||
case "txt":
|
||
format = output.FormatTXT
|
||
case "json":
|
||
format = output.FormatJSON
|
||
case "csv":
|
||
format = output.FormatCSV
|
||
default:
|
||
return nil, fmt.Errorf(i18n.GetText("output_format_invalid", outputFormat))
|
||
}
|
||
|
||
config := output.DefaultManagerConfig(outputPath, format)
|
||
manager, err := output.NewManager(config)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return &OutputManager{manager: manager}, nil
|
||
}
|
||
|
||
// 输出相关函数
|
||
func InitOutput() error {
|
||
LogDebug(i18n.GetText("output_init_start"))
|
||
switch OutputFormat {
|
||
case "txt", "json", "csv":
|
||
default:
|
||
return fmt.Errorf(i18n.GetText("output_format_invalid", OutputFormat))
|
||
}
|
||
if Outputfile == "" {
|
||
return fmt.Errorf(i18n.GetText("output_path_empty"))
|
||
}
|
||
|
||
manager, err := createOutputManager(Outputfile, OutputFormat)
|
||
if err != nil {
|
||
LogDebug(i18n.GetText("output_init_failed", err))
|
||
return fmt.Errorf(i18n.GetText("output_init_failed", err))
|
||
}
|
||
ResultOutput = manager
|
||
output.SetGlobalManager(manager.manager)
|
||
LogDebug(i18n.GetText("output_init_success"))
|
||
return nil
|
||
}
|
||
|
||
func (om *OutputManager) saveResult(result *ScanResult) error {
|
||
if om.manager == nil {
|
||
return fmt.Errorf(i18n.GetText("output_not_init"))
|
||
}
|
||
LogDebug(i18n.GetText("output_saving_result", result.Type, result.Target))
|
||
return om.manager.SaveResult(result)
|
||
}
|
||
|
||
func SaveResult(result *ScanResult) error {
|
||
if ResultOutput == nil {
|
||
LogDebug(i18n.GetText("output_not_init"))
|
||
return fmt.Errorf(i18n.GetText("output_not_init"))
|
||
}
|
||
return ResultOutput.saveResult(result)
|
||
}
|
||
|
||
func CloseOutput() error {
|
||
if ResultOutput == nil {
|
||
return nil
|
||
}
|
||
LogDebug(i18n.GetText("output_closing"))
|
||
err := ResultOutput.manager.Close()
|
||
if err != nil {
|
||
return fmt.Errorf(i18n.GetText("output_close_failed", err))
|
||
}
|
||
LogDebug(i18n.GetText("output_closed"))
|
||
return nil
|
||
}
|
||
|
||
// =============================================================================
|
||
// 代理桥接 (Proxy.go 功能)
|
||
// =============================================================================
|
||
|
||
// 代理相关函数
|
||
func WrapperTcpWithTimeout(network, address string, timeout time.Duration) (net.Conn, error) {
|
||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||
defer cancel()
|
||
return proxy.DialContextWithProxy(ctx, network, address)
|
||
}
|
||
|
||
func WrapperTcpWithContext(ctx context.Context, network, address string) (net.Conn, error) {
|
||
return proxy.DialContextWithProxy(ctx, network, address)
|
||
}
|
||
|
||
func Socks5Dialer(forward *net.Dialer) (interface{}, error) {
|
||
if err := syncProxyConfig(); err != nil {
|
||
return nil, fmt.Errorf(i18n.GetText("socks5_create_failed", err))
|
||
}
|
||
manager := proxy.GetGlobalProxy()
|
||
dialer, err := manager.GetDialer()
|
||
if err != nil {
|
||
return nil, fmt.Errorf(i18n.GetText("socks5_create_failed", err))
|
||
}
|
||
return dialer, nil
|
||
}
|
||
|
||
func WrapperTlsWithContext(ctx context.Context, network, address string, tlsConfig *tls.Config) (net.Conn, error) {
|
||
if err := syncProxyConfig(); err != nil {
|
||
LogError(i18n.GetText("proxy_config_sync_failed", err.Error()))
|
||
}
|
||
conn, err := proxy.DialTLSContextWithProxy(ctx, network, address, tlsConfig)
|
||
if err != nil {
|
||
LogError(i18n.GetText("tls_conn_failed", err.Error()))
|
||
return nil, fmt.Errorf(i18n.GetText("tls_conn_failed", err))
|
||
}
|
||
return conn, nil
|
||
}
|
||
|
||
func syncProxyConfig() error {
|
||
var proxyURL string
|
||
if Socks5Proxy != "" {
|
||
proxyURL = Socks5Proxy
|
||
} else if HttpProxy != "" {
|
||
proxyURL = HttpProxy
|
||
}
|
||
|
||
if proxy.IsProxyEnabledGlobally() {
|
||
currentAddr := proxy.GetGlobalProxyAddress()
|
||
if (proxyURL == "" && currentAddr == "") ||
|
||
(proxyURL != "" && currentAddr != "" && (Socks5Proxy == currentAddr || HttpProxy == currentAddr)) {
|
||
return nil
|
||
}
|
||
}
|
||
|
||
if err := proxy.InitGlobalProxy(proxyURL); err != nil {
|
||
return fmt.Errorf(i18n.GetText("proxy_init_failed", err))
|
||
}
|
||
|
||
if proxyURL != "" {
|
||
LogBase(i18n.GetText("proxy_enabled", proxy.GetGlobalProxyType(), proxy.GetGlobalProxyAddress()))
|
||
} else {
|
||
LogBase(i18n.GetText("proxy_disabled"))
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// =============================================================================
|
||
// 初始化函数
|
||
// =============================================================================
|
||
|
||
func init() {
|
||
config.SetGlobalVersion(version)
|
||
syncOutputConfig()
|
||
}
|