fscan/plugins/web/webtitle.go
ZacharyZcR 8f54702c02 refactor: 精准修复插件系统三个设计问题
经Linus式架构审计,发现并修复插件系统中的具体问题:

## 核心修复

### 1. 消除local插件GetPorts()方法冗余
- 删除21个local插件中无意义的GetPorts()方法
- 简化local.Plugin接口:移除端口概念
- 理由:本地插件不涉及网络,端口概念完全多余

### 2. 消除web插件GetPorts()方法冗余
- 删除2个web插件中无用的GetPorts()方法
- 简化web.WebPlugin接口:专注智能HTTP检测
- 理由:Web插件使用动态HTTP检测,预定义端口无价值

### 3. 统一插件命名规范
- 统一所有插件接口使用Name()方法(符合Go惯例)
- 消除GetName()与Name()不一致问题
- 简化适配器:不再需要方法名转换

## 技术改进

接口精简:
- local插件:GetName() + GetPorts() → Name()
- web插件:GetName() + GetPorts() → Name()
- services插件:GetName() → Name()(保留GetPorts(),业务必需)

代码减少:
- 删除23个无用GetPorts()方法
- 重命名52个Name()方法
- 简化3个插件接口定义

## 影响范围

修改文件:55个插件文件
代码变更:-155行 +61行(净减少94行)
功能影响:零破坏性,保持所有业务逻辑不变

这是基于业务需求分析的精准重构,消除真正多余的部分,
保持系统架构合理性和向后兼容性。
2025-08-26 20:38:39 +08:00

152 lines
3.1 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 web
import (
"context"
"crypto/tls"
"fmt"
"io"
"net/http"
"regexp"
"strings"
"time"
"unicode/utf8"
"github.com/shadow1ng/fscan/common"
)
// WebTitlePlugin Web标题获取插件
type WebTitlePlugin struct {
name string
}
// NewWebTitlePlugin 创建WebTitle插件
func NewWebTitlePlugin() *WebTitlePlugin {
return &WebTitlePlugin{
name: "webtitle",
}
}
// GetName 实现Plugin接口
func (p *WebTitlePlugin) Name() string {
return p.name
}
// Scan 执行WebTitle扫描
func (p *WebTitlePlugin) Scan(ctx context.Context, info *common.HostInfo) *WebScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
title, status, server, err := p.getWebTitle(ctx, info)
if err != nil {
return &WebScanResult{
Success: false,
Error: err,
}
}
msg := fmt.Sprintf("WebTitle %s", target)
if title != "" {
msg += fmt.Sprintf(" [%s]", title)
}
if status != 0 {
msg += fmt.Sprintf(" %d", status)
}
if server != "" {
msg += fmt.Sprintf(" %s", server)
}
common.LogSuccess(msg)
return &WebScanResult{
Success: true,
Title: title,
Status: status,
Server: server,
}
}
func (p *WebTitlePlugin) getWebTitle(ctx context.Context, info *common.HostInfo) (string, int, string, error) {
// 智能协议检测
protocol := p.detectProtocol(info)
url := fmt.Sprintf("%s://%s:%s", protocol, info.Host, info.Ports)
client := &http.Client{
Timeout: time.Duration(common.WebTimeout) * time.Second,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
DisableKeepAlives: true,
},
}
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return "", 0, "", err
}
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
resp, err := client.Do(req)
if err != nil {
return "", 0, "", err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", resp.StatusCode, resp.Header.Get("Server"), err
}
title := p.extractTitle(string(body))
return title, resp.StatusCode, resp.Header.Get("Server"), nil
}
// detectProtocol 智能检测HTTP/HTTPS协议
func (p *WebTitlePlugin) detectProtocol(info *common.HostInfo) string {
port := info.Ports
// 已知的HTTPS端口
httpsPorts := []string{"443", "8443", "9443"}
for _, httpsPort := range httpsPorts {
if port == httpsPort {
return "https"
}
}
// 常见HTTP端口优先使用HTTP
httpPorts := []string{"80", "8080", "8000", "8888", "8090", "3000", "5000", "9000"}
for _, httpPort := range httpPorts {
if port == httpPort {
return "http"
}
}
// 对于其他端口优先尝试HTTP
return "http"
}
func (p *WebTitlePlugin) extractTitle(html string) string {
titleRe := regexp.MustCompile(`(?i)<title[^>]*>([^<]+)</title>`)
matches := titleRe.FindStringSubmatch(html)
if len(matches) > 1 {
title := strings.TrimSpace(matches[1])
title = regexp.MustCompile(`\s+`).ReplaceAllString(title, " ")
if len(title) > 100 {
title = title[:100] + "..."
}
if utf8.ValidString(title) {
return title
}
}
return ""
}
func init() {
RegisterWebPlugin("webtitle", func() WebPlugin {
return NewWebTitlePlugin()
})
}