Files
ProxyPool/internal/importer/parser.go
2026-01-31 22:53:12 +08:00

172 lines
3.9 KiB
Go

package importer
import (
"fmt"
"net/url"
"regexp"
"strconv"
"strings"
"proxyrotator/internal/model"
)
var (
// 匹配 host:port 格式
hostPortRegex = regexp.MustCompile(`^([a-zA-Z0-9.-]+):(\d+)$`)
// 匹配 user:pass@host:port 格式
userPassHostPortRegex = regexp.MustCompile(`^([^:@]+):([^@]+)@([a-zA-Z0-9.-]+):(\d+)$`)
// 匹配 host:port:user:pass 格式
hostPortUserPassRegex = regexp.MustCompile(`^([a-zA-Z0-9.-]+):(\d+):([^:]+):(.+)$`)
)
// ParseProxyLine 解析单行代理格式
// 支持格式:
// - host:port
// - user:pass@host:port
// - host:port:user:pass
// - http://host:port
// - http://user:pass@host:port
// - socks5://host:port
// - socks5://user:pass@host:port
func ParseProxyLine(raw string, protocolHint string) (*model.Proxy, error) {
raw = strings.TrimSpace(raw)
if raw == "" {
return nil, fmt.Errorf("empty line")
}
// 尝试解析为 URL
if strings.Contains(raw, "://") {
return parseAsURL(raw)
}
// 尝试解析 host:port:user:pass 格式
if matches := hostPortUserPassRegex.FindStringSubmatch(raw); matches != nil {
port, err := strconv.Atoi(matches[2])
if err != nil || port <= 0 || port >= 65536 {
return nil, fmt.Errorf("invalid port: %s", matches[2])
}
protocol := inferProtocol(protocolHint, port)
return &model.Proxy{
Protocol: protocol,
Host: matches[1],
Port: port,
Username: matches[3],
Password: matches[4],
}, nil
}
// 尝试解析 user:pass@host:port 格式
if matches := userPassHostPortRegex.FindStringSubmatch(raw); matches != nil {
port, err := strconv.Atoi(matches[4])
if err != nil || port <= 0 || port >= 65536 {
return nil, fmt.Errorf("invalid port: %s", matches[4])
}
protocol := inferProtocol(protocolHint, port)
return &model.Proxy{
Protocol: protocol,
Host: matches[3],
Port: port,
Username: matches[1],
Password: matches[2],
}, nil
}
// 尝试解析 host:port 格式
if matches := hostPortRegex.FindStringSubmatch(raw); matches != nil {
port, err := strconv.Atoi(matches[2])
if err != nil || port <= 0 || port >= 65536 {
return nil, fmt.Errorf("invalid port: %s", matches[2])
}
protocol := inferProtocol(protocolHint, port)
return &model.Proxy{
Protocol: protocol,
Host: matches[1],
Port: port,
}, nil
}
return nil, fmt.Errorf("unrecognized format")
}
// parseAsURL 解析 URL 格式的代理
func parseAsURL(raw string) (*model.Proxy, error) {
u, err := url.Parse(raw)
if err != nil {
return nil, fmt.Errorf("invalid URL: %w", err)
}
var protocol model.ProxyProtocol
switch strings.ToLower(u.Scheme) {
case "http":
protocol = model.ProtoHTTP
case "https":
protocol = model.ProtoHTTPS
case "socks5":
protocol = model.ProtoSOCKS5
default:
return nil, fmt.Errorf("unsupported protocol: %s", u.Scheme)
}
host := u.Hostname()
if host == "" {
return nil, fmt.Errorf("missing host")
}
portStr := u.Port()
if portStr == "" {
// 默认端口
switch protocol {
case model.ProtoHTTP:
portStr = "80"
case model.ProtoHTTPS:
portStr = "443"
case model.ProtoSOCKS5:
portStr = "1080"
}
}
port, err := strconv.Atoi(portStr)
if err != nil || port <= 0 || port >= 65536 {
return nil, fmt.Errorf("invalid port: %s", portStr)
}
var username, password string
if u.User != nil {
username = u.User.Username()
password, _ = u.User.Password()
}
return &model.Proxy{
Protocol: protocol,
Host: host,
Port: port,
Username: username,
Password: password,
}, nil
}
// inferProtocol 根据提示和端口推断协议
func inferProtocol(hint string, port int) model.ProxyProtocol {
switch strings.ToLower(hint) {
case "http":
return model.ProtoHTTP
case "https":
return model.ProtoHTTPS
case "socks5":
return model.ProtoSOCKS5
}
// 根据端口推断
switch port {
case 443:
return model.ProtoHTTPS
case 1080:
return model.ProtoSOCKS5
default:
return model.ProtoHTTP
}
}