Files
fscan/common/parsers/network_test.go
T
ZacharyZcR 71b92d4408 feat: v2.1.0 核心重构与功能增强
## 架构重构
- 全局变量消除,迁移至 Config/State 对象
- SMB 插件融合(smb/smb2/smbghost/smbinfo)
- 服务探测重构,实现 Nmap 风格 fallback 机制
- 输出系统重构,TXT 实时刷盘 + 双写机制
- i18n 框架升级至 go-i18n

## 性能优化
- 正则表达式预编译
- 内存优化 map[string]struct{}
- 并发指纹匹配
- SOCKS5 连接复用
- 滑动窗口调度 + 自适应线程池

## 新功能
- Web 管理界面
- 多格式 POC 适配(xray/afrog)
- 增强指纹库(3139条)
- Favicon hash 指纹识别
- 插件选择性编译(Build Tags)
- fscan-lab 靶场环境
- 默认端口扩展(62→133)

## 构建系统
- 添加 no_local tag 支持排除本地插件
- 多版本构建:fscan/fscan-nolocal/fscan-web
- CI 添加 snapshot 模式支持仅测试构建

## Bug 修复
- 修复 120+ 个问题,包括 RDP panic、批量扫描漏报、
  JSON 输出格式、Redis 检测、Context 超时等

## 测试增强
- 单元测试覆盖率 74-100%
- 并发安全测试
- 集成测试(Web/端口/服务/SSH/ICMP)
2026-01-11 20:16:23 +08:00

721 lines
16 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 (
"strings"
"testing"
"time"
)
// =============================================================================
// NetworkParser 构造函数测试
// =============================================================================
func TestNewNetworkParser(t *testing.T) {
tests := []struct {
name string
options *NetworkParserOptions
wantNil bool
}{
{
name: "使用默认选项",
options: nil,
wantNil: false,
},
{
name: "使用自定义选项",
options: &NetworkParserOptions{
ValidateProxies: false,
AllowInsecure: true,
},
wantNil: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
parser := NewNetworkParser(tt.options)
if tt.wantNil && parser != nil {
t.Error("期望parser为nil,但不是")
}
if !tt.wantNil && parser == nil {
t.Error("期望parser不为nil,但是nil")
}
if parser != nil && parser.options == nil {
t.Error("parser.options为nil")
}
})
}
}
// =============================================================================
// Parse 主函数测试
// =============================================================================
func TestNetworkParser_Parse(t *testing.T) {
parser := NewNetworkParser(nil)
tests := []struct {
name string
input *NetworkInput
wantSuccess bool
wantError bool
}{
{
name: "空输入",
input: nil,
wantSuccess: false,
wantError: true,
},
{
name: "完整HTTPS代理配置",
input: &NetworkInput{
HTTPProxy: "https://127.0.0.1:8443",
},
wantSuccess: true,
},
{
name: "Socks5代理配置",
input: &NetworkInput{
Socks5Proxy: "socks5://127.0.0.1:1080",
},
wantSuccess: true,
},
{
name: "自定义超时",
input: &NetworkInput{
Timeout: 60,
WebTimeout: 30,
},
wantSuccess: true,
},
{
name: "自定义User-Agent",
input: &NetworkInput{
UserAgent: "Custom-Agent/1.0",
},
wantSuccess: true,
},
{
name: "Cookie配置",
input: &NetworkInput{
Cookie: "session=abc123; token=xyz789",
},
wantSuccess: true,
},
{
name: "禁用Ping",
input: &NetworkInput{
DisablePing: true,
},
wantSuccess: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := parser.Parse(tt.input, nil)
if tt.wantError {
if err == nil {
t.Error("期望错误,但没有错误")
}
return
}
if err != nil {
t.Errorf("意外错误: %v", err)
return
}
if result == nil {
t.Fatal("result为nil")
}
if result.Success != tt.wantSuccess {
t.Errorf("Success = %v, want %v (errors: %v)", result.Success, tt.wantSuccess, result.Errors)
}
})
}
}
// =============================================================================
// normalizeHttpProxy 测试
// =============================================================================
func TestNetworkParser_NormalizeHttpProxy(t *testing.T) {
parser := NewNetworkParser(nil)
tests := []struct {
name string
input string
expected string
}{
{
name: "快捷方式1",
input: "1",
expected: "http://127.0.0.1:8080",
},
{
name: "快捷方式2",
input: "2",
expected: "socks5://127.0.0.1:1080",
},
{
name: "只有IP和端口",
input: "192.168.1.1:8080",
expected: "http://192.168.1.1:8080",
},
{
name: "只有端口号",
input: "8080",
expected: "http://127.0.0.1:8080",
},
{
name: "完整HTTP URL",
input: "http://proxy.example.com:8080",
expected: "http://proxy.example.com:8080",
},
{
name: "完整HTTPS URL",
input: "https://proxy.example.com:8443",
expected: "https://proxy.example.com:8443",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := parser.normalizeHTTPProxy(tt.input)
if result != tt.expected {
t.Errorf("normalizeHTTPProxy(%s) = %s, want %s", tt.input, result, tt.expected)
}
})
}
}
// =============================================================================
// normalizeSocks5Proxy 测试
// =============================================================================
func TestNetworkParser_NormalizeSocks5Proxy(t *testing.T) {
parser := NewNetworkParser(nil)
tests := []struct {
name string
input string
expected string
}{
{
name: "IP和端口",
input: "192.168.1.1:1080",
expected: "socks5://192.168.1.1:1080",
},
{
name: "只有端口号",
input: "1080",
expected: "socks5://127.0.0.1:1080",
},
{
name: "已有socks5前缀",
input: "socks5://proxy.example.com:1080",
expected: "socks5://proxy.example.com:1080",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := parser.normalizeSocks5Proxy(tt.input)
if result != tt.expected {
t.Errorf("normalizeSocks5Proxy(%s) = %s, want %s", tt.input, result, tt.expected)
}
})
}
}
// =============================================================================
// validateProxyURL 测试
// =============================================================================
func TestNetworkParser_ValidateProxyURL(t *testing.T) {
// 使用AllowInsecure选项测试HTTP代理
parser := NewNetworkParser(&NetworkParserOptions{
ValidateProxies: true,
AllowInsecure: true,
})
tests := []struct {
name string
proxyURL string
wantError bool
}{
{
name: "有效HTTP代理(AllowInsecure=true",
proxyURL: "http://127.0.0.1:8080",
wantError: false,
},
{
name: "有效HTTPS代理",
proxyURL: "https://proxy.example.com:8443",
wantError: false,
},
{
name: "有效Socks5代理",
proxyURL: "socks5://127.0.0.1:1080",
wantError: false,
},
{
name: "空URL",
proxyURL: "",
wantError: false, // 空URL被认为是有效的(无代理)
},
{
name: "不支持的协议",
proxyURL: "ftp://proxy.example.com:21",
wantError: true,
},
{
name: "缺少主机名",
proxyURL: "http://:8080",
wantError: true,
},
{
name: "无效端口号",
proxyURL: "http://127.0.0.1:99999",
wantError: true,
},
{
name: "端口号为0",
proxyURL: "http://127.0.0.1:0",
wantError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := parser.validateProxyURL(tt.proxyURL)
if tt.wantError && err == nil {
t.Error("期望错误,但没有错误")
}
if !tt.wantError && err != nil {
t.Errorf("意外错误: %v", err)
}
})
}
}
// =============================================================================
// validateProxyURL 不允许不安全代理测试
// =============================================================================
func TestNetworkParser_ValidateProxyURL_Insecure(t *testing.T) {
parser := NewNetworkParser(&NetworkParserOptions{
ValidateProxies: true,
AllowInsecure: false,
})
err := parser.validateProxyURL("http://proxy.example.com:8080")
if err == nil {
t.Error("期望错误(不允许HTTP代理),但没有错误")
}
}
// =============================================================================
// parseTimeouts 测试
// =============================================================================
func TestNetworkParser_ParseTimeouts(t *testing.T) {
parser := NewNetworkParser(nil)
tests := []struct {
name string
timeout int64
webTimeout int64
wantTimeout time.Duration
wantWebTimeout time.Duration
wantWarnings int
}{
{
name: "使用默认超时",
timeout: 0,
webTimeout: 0,
wantTimeout: DefaultNetworkTimeout,
wantWebTimeout: DefaultWebTimeout,
wantWarnings: 0,
},
{
name: "自定义超时",
timeout: 60,
webTimeout: 30,
wantTimeout: 60 * time.Second,
wantWebTimeout: 30 * time.Second,
wantWarnings: 0,
},
{
name: "超时过长(警告)",
timeout: 400,
webTimeout: 200,
wantTimeout: 400 * time.Second,
wantWebTimeout: 200 * time.Second,
wantWarnings: 2,
},
{
name: "Web超时远大于普通超时(警告)",
timeout: 10,
webTimeout: 100,
wantTimeout: 10 * time.Second,
wantWebTimeout: 100 * time.Second,
wantWarnings: 1, // 只有Web超时远大于普通超时警告
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
timeout, webTimeout, _, warnings := parser.parseTimeouts(tt.timeout, tt.webTimeout)
if timeout != tt.wantTimeout {
t.Errorf("timeout = %v, want %v", timeout, tt.wantTimeout)
}
if webTimeout != tt.wantWebTimeout {
t.Errorf("webTimeout = %v, want %v", webTimeout, tt.wantWebTimeout)
}
if len(warnings) != tt.wantWarnings {
t.Errorf("警告数量 = %d, want %d (warnings: %v)", len(warnings), tt.wantWarnings, warnings)
}
})
}
}
// =============================================================================
// parseUserAgent 测试
// =============================================================================
func TestNetworkParser_ParseUserAgent(t *testing.T) {
parser := NewNetworkParser(nil)
tests := []struct {
name string
userAgent string
wantUA string
wantError bool
}{
{
name: "使用默认User-Agent",
userAgent: "",
wantUA: DefaultUserAgent,
wantError: false,
},
{
name: "自定义User-Agent",
userAgent: "Custom-Bot/1.0",
wantError: false,
},
{
name: "过长的User-Agent",
userAgent: strings.Repeat("a", 600),
wantError: true,
},
{
name: "包含非法字符的User-Agent",
userAgent: "Agent\nWith\nNewlines",
wantError: true,
},
{
name: "包含制表符的User-Agent",
userAgent: "Agent\tWith\tTabs",
wantError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ua, errors, _ := parser.parseUserAgent(tt.userAgent)
if tt.wantError {
if len(errors) == 0 {
t.Error("期望错误,但没有错误")
}
return
}
if len(errors) > 0 {
t.Errorf("意外错误: %v", errors)
return
}
if tt.userAgent == "" && ua != tt.wantUA {
t.Errorf("使用默认UA失败: got %s, want %s", ua, tt.wantUA)
}
})
}
}
// =============================================================================
// parseCookie 测试
// =============================================================================
func TestNetworkParser_ParseCookie(t *testing.T) {
parser := NewNetworkParser(nil)
tests := []struct {
name string
cookie string
wantError bool
}{
{
name: "空Cookie",
cookie: "",
wantError: false,
},
{
name: "单个Cookie",
cookie: "session=abc123",
wantError: false,
},
{
name: "多个Cookie",
cookie: "session=abc123; token=xyz789; user=admin",
wantError: false,
},
{
name: "过长的Cookie",
cookie: strings.Repeat("a", 5000),
wantError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, errors, _ := parser.parseCookie(tt.cookie)
if tt.wantError && len(errors) == 0 {
t.Error("期望错误,但没有错误")
}
if !tt.wantError && len(errors) > 0 {
t.Errorf("意外错误: %v", errors)
}
})
}
}
// =============================================================================
// isValidUserAgent 测试
// =============================================================================
func TestNetworkParser_IsValidUserAgent(t *testing.T) {
parser := NewNetworkParser(nil)
tests := []struct {
name string
userAgent string
wantValid bool
}{
{
name: "包含Mozilla",
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
wantValid: true,
},
{
name: "包含Chrome",
userAgent: "Chrome/104.0.0.0",
wantValid: true,
},
{
name: "包含Safari",
userAgent: "Safari/537.36",
wantValid: true,
},
{
name: "包含Firefox",
userAgent: "Firefox/100.0",
wantValid: true,
},
{
name: "自定义Agent(不在列表中)",
userAgent: "CustomBot/1.0",
wantValid: false,
},
{
name: "空User-Agent",
userAgent: "",
wantValid: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
valid := parser.isValidUserAgent(tt.userAgent)
if valid != tt.wantValid {
t.Errorf("isValidUserAgent(%s) = %v, want %v", tt.userAgent, valid, tt.wantValid)
}
})
}
}
// =============================================================================
// isValidCookie 测试
// =============================================================================
func TestNetworkParser_IsValidCookie(t *testing.T) {
parser := NewNetworkParser(nil)
tests := []struct {
name string
cookie string
wantValid bool
}{
{
name: "有效的简单Cookie",
cookie: "session=abc123",
wantValid: true,
},
{
name: "有效的多Cookie",
cookie: "session=abc123; token=xyz789",
wantValid: true,
},
{
name: "带空格的Cookie",
cookie: "session=abc123; token=xyz789",
wantValid: true,
},
{
name: "无值Cookie",
cookie: "session=; token=xyz",
wantValid: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
valid := parser.isValidCookie(tt.cookie)
if valid != tt.wantValid {
t.Errorf("isValidCookie(%s) = %v, want %v", tt.cookie, valid, tt.wantValid)
}
})
}
}
// =============================================================================
// 代理冲突警告测试
// =============================================================================
func TestNetworkParser_ProxyConflictWarning(t *testing.T) {
parser := NewNetworkParser(&NetworkParserOptions{
AllowInsecure: true, // 允许HTTP代理以测试冲突警告
})
input := &NetworkInput{
HTTPProxy: "http://127.0.0.1:8080",
Socks5Proxy: "socks5://127.0.0.1:1080",
}
result, err := parser.Parse(input, nil)
if err != nil {
t.Errorf("意外错误: %v", err)
return
}
// 应该有警告(同时配置了两种代理+Socks5建议)
if len(result.Warnings) < 2 {
t.Errorf("期望至少有2个警告,但只有 %d 个", len(result.Warnings))
}
}
// =============================================================================
// Socks5代理建议测试
// =============================================================================
func TestNetworkParser_Socks5ProxyHint(t *testing.T) {
parser := NewNetworkParser(nil)
input := &NetworkInput{
Socks5Proxy: "socks5://127.0.0.1:1080",
}
result, err := parser.Parse(input, nil)
if err != nil {
t.Errorf("意外错误: %v", err)
return
}
// 应该有建议禁用Ping的警告
foundPingHint := false
for _, warning := range result.Warnings {
if strings.Contains(warning, "Ping") || strings.Contains(warning, "ping") {
foundPingHint = true
break
}
}
if !foundPingHint {
t.Errorf("未找到Ping建议警告,warnings: %v", result.Warnings)
}
}
// =============================================================================
// 完整配置测试
// =============================================================================
func TestNetworkParser_FullConfiguration(t *testing.T) {
parser := NewNetworkParser(nil)
input := &NetworkInput{
HTTPProxy: "https://proxy.example.com:8443",
Timeout: 60,
WebTimeout: 30,
DisablePing: true,
DNSLog: true,
UserAgent: "Mozilla/5.0",
Cookie: "session=test123; token=abc",
}
result, err := parser.Parse(input, nil)
if err != nil {
t.Errorf("意外错误: %v", err)
return
}
if !result.Success {
t.Errorf("解析失败,错误: %v", result.Errors)
}
config := result.Config.Network
if config.HTTPProxy != "https://proxy.example.com:8443" {
t.Errorf("HTTPProxy = %s, want https://proxy.example.com:8443", config.HTTPProxy)
}
if config.Timeout != 60*time.Second {
t.Errorf("Timeout = %v, want 60s", config.Timeout)
}
if config.WebTimeout != 30*time.Second {
t.Errorf("WebTimeout = %v, want 30s", config.WebTimeout)
}
if !config.DisablePing {
t.Error("DisablePing应为true")
}
if !config.EnableDNSLog {
t.Error("EnableDNSLog应为true")
}
if config.UserAgent != "Mozilla/5.0" {
t.Errorf("UserAgent = %s, want Mozilla/5.0", config.UserAgent)
}
if config.Cookie != "session=test123; token=abc" {
t.Errorf("Cookie = %s, want session=test123; token=abc", config.Cookie)
}
}