Files
Wormhole/internal/store/domain_forward.go
T
rose_cat707 612d68f56f feat: Wormhole 内网穿透与反向代理网关
Go + Vue 管理台,SQLite 持久化;支持隧道/域名穿透、域名转发插件链、访客认证、
IP 规则、API Key、Docker 单镜像部署;配置通过 Web 系统设置管理,无需 YAML 文件。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 12:15:02 +08:00

177 lines
4.7 KiB
Go

package store
import (
"fmt"
"net/url"
"strings"
"github.com/wormhole/wormhole/internal/forwardplugin"
)
func normalizeDomainForwardRoute(host, location string) (string, string) {
host = strings.TrimSpace(strings.ToLower(host))
location = strings.TrimSpace(location)
if location == "" {
location = "/"
}
if !strings.HasPrefix(location, "/") {
location = "/" + location
}
return host, location
}
func (s *Store) ListDomainForwards() ([]DomainForward, error) {
var items []DomainForward
err := s.db.Order("id asc").Find(&items).Error
return items, err
}
func (s *Store) GetDomainForwardByID(id uint) (*DomainForward, error) {
var df DomainForward
if err := s.db.First(&df, id).Error; err != nil {
return nil, err
}
return &df, nil
}
func (s *Store) CreateDomainForward(df *DomainForward) error {
df.SourceHost, df.SourceLocation = normalizeDomainForwardRoute(df.SourceHost, df.SourceLocation)
return s.db.Create(df).Error
}
func (s *Store) UpdateDomainForward(df *DomainForward) error {
df.SourceHost, df.SourceLocation = normalizeDomainForwardRoute(df.SourceHost, df.SourceLocation)
return s.db.Save(df).Error
}
func (s *Store) DeleteDomainForward(id uint) error {
return s.db.Delete(&DomainForward{}, id).Error
}
func (s *Store) DomainForwardRouteInUse(host, location string, excludeID uint) (bool, error) {
host, location = normalizeDomainForwardRoute(host, location)
q := s.db.Model(&DomainForward{}).Where("source_host = ? AND source_location = ?", host, location)
if excludeID > 0 {
q = q.Where("id <> ?", excludeID)
}
var count int64
if err := q.Count(&count).Error; err != nil {
return false, err
}
return count > 0, nil
}
// RouteConflict checks domain forward vs penetration host on same host+location.
func (s *Store) RouteConflict(host, location string, excludeForwardID, excludeHostID uint) error {
host, location = normalizeDomainForwardRoute(host, location)
inUse, err := s.DomainForwardRouteInUse(host, location, excludeForwardID)
if err != nil {
return err
}
if inUse {
return fmt.Errorf("域名与路径前缀已被其他转发规则占用")
}
hostInUse, err := s.HostRouteInUse(host, location, excludeHostID)
if err != nil {
return err
}
if hostInUse {
return fmt.Errorf("域名与路径前缀已被穿透域名占用")
}
return nil
}
func ValidateDomainForward(df *DomainForward) error {
if strings.TrimSpace(df.SourceHost) == "" {
return fmt.Errorf("源域名不能为空")
}
target := strings.TrimSpace(df.TargetURL)
if target == "" {
return fmt.Errorf("目标 URL 不能为空")
}
u, err := url.Parse(target)
if err != nil || u.Scheme == "" || u.Host == "" {
return fmt.Errorf("目标 URL 格式无效,需包含协议和主机,如 https://example.com")
}
if u.Scheme != "http" && u.Scheme != "https" {
return fmt.Errorf("目标 URL 仅支持 http/https")
}
df.TargetURL = u.String()
switch df.Mode {
case "redirect", "proxy":
default:
return fmt.Errorf("模式无效")
}
if df.Mode == "redirect" {
if df.RedirectMode == "" {
df.RedirectMode = "preserve"
}
if df.RedirectMode != "preserve" && df.RedirectMode != "fixed" {
return fmt.Errorf("跳转策略无效")
}
if df.RedirectCode == 0 {
df.RedirectCode = 302
}
if df.RedirectCode != 301 && df.RedirectCode != 302 {
return fmt.Errorf("跳转状态码仅支持 301 或 302")
}
}
if df.Mode == "proxy" {
if df.ProxyType == "" {
df.ProxyType = "http"
}
if df.ProxyType != "http" && df.ProxyType != "socks5" && df.ProxyType != "http_proxy" {
return fmt.Errorf("代理类型无效")
}
if df.ProxyType == "socks5" && strings.TrimSpace(df.Socks5Addr) == "" {
return fmt.Errorf("SOCKS5 代理地址不能为空")
}
if df.ProxyType == "http_proxy" && strings.TrimSpace(df.HTTPProxyAddr) == "" {
return fmt.Errorf("HTTP 代理地址不能为空")
}
normalized, err := forwardplugin.ValidateAndNormalizePlugins(toPluginEntries(df.Plugins))
if err != nil {
return err
}
df.Plugins = fromPluginEntries(normalized)
}
if df.SourceScheme == "" {
df.SourceScheme = "all"
}
if df.SourceLocation == "" {
df.SourceLocation = "/"
}
return nil
}
func toPluginEntries(plugins ForwardPlugins) []forwardplugin.PluginEntry {
if len(plugins) == 0 {
return nil
}
out := make([]forwardplugin.PluginEntry, len(plugins))
for i, p := range plugins {
out[i] = forwardplugin.PluginEntry{
Type: p.Type,
Enabled: p.Enabled,
Config: p.Config,
}
}
return out
}
func fromPluginEntries(entries []forwardplugin.PluginEntry) ForwardPlugins {
if len(entries) == 0 {
return nil
}
out := make(ForwardPlugins, len(entries))
for i, e := range entries {
out[i] = ForwardPluginEntry{
Type: e.Type,
Enabled: e.Enabled,
Config: e.Config,
}
}
return out
}