172 lines
3.9 KiB
Go
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
|
|
}
|
|
}
|