Some checks failed
CI/CD Pipeline / Test (push) Failing after 22m19s
CI/CD Pipeline / Security Scan (push) Failing after 5m57s
CI/CD Pipeline / Build (amd64, darwin) (push) Has been skipped
CI/CD Pipeline / Build (amd64, linux) (push) Has been skipped
CI/CD Pipeline / Build (amd64, windows) (push) Has been skipped
CI/CD Pipeline / Build (arm64, darwin) (push) Has been skipped
CI/CD Pipeline / Build (arm64, linux) (push) Has been skipped
CI/CD Pipeline / Build Docker Image (push) Has been skipped
CI/CD Pipeline / Create Release (push) Has been skipped
215 lines
5.0 KiB
Go
215 lines
5.0 KiB
Go
package parser
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/subconverter-go/internal/logging"
|
|
)
|
|
|
|
// Socks5Parser Socks5协议解析器
|
|
// 实现Socks5代理配置的解析功能
|
|
type Socks5Parser struct {
|
|
logger *logging.Logger
|
|
}
|
|
|
|
// NewSocks5Parser 创建新的Socks5解析器
|
|
// 返回初始化好的Socks5Parser实例
|
|
func NewSocks5Parser(logger *logging.Logger) *Socks5Parser {
|
|
return &Socks5Parser{
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// Parse 解析Socks5配置
|
|
// 支持标准URL格式
|
|
func (p *Socks5Parser) Parse(input string) (*ProxyConfig, error) {
|
|
p.logger.Debugf("Parsing Socks5 configuration")
|
|
|
|
normalized, err := p.normalizeInput(input)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 去除前缀
|
|
trimmed := strings.TrimPrefix(normalized, "socks5://")
|
|
|
|
// 解析URL
|
|
parsedURL, err := url.Parse("socks5://" + trimmed)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse Socks5 URL: %v", err)
|
|
}
|
|
|
|
config := &ProxyConfig{
|
|
Type: "socks5",
|
|
Protocol: "socks5",
|
|
Server: parsedURL.Hostname(),
|
|
}
|
|
|
|
// 解析端口
|
|
port, err := strconv.Atoi(parsedURL.Port())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid port number: %v", err)
|
|
}
|
|
config.Port = port
|
|
|
|
// 解析用户名和密码
|
|
var username, password string
|
|
if parsedURL.User != nil {
|
|
username = parsedURL.User.Username()
|
|
password, _ = parsedURL.User.Password()
|
|
}
|
|
|
|
// 解析备注
|
|
if parsedURL.Fragment != "" {
|
|
config.Name = parsedURL.Fragment
|
|
} else {
|
|
config.Name = fmt.Sprintf("socks5-%s:%d", config.Server, config.Port)
|
|
}
|
|
|
|
// 创建设置映射
|
|
settings := map[string]interface{}{
|
|
"udp": true,
|
|
}
|
|
|
|
// 添加认证信息
|
|
if username != "" {
|
|
settings["username"] = username
|
|
if password != "" {
|
|
settings["password"] = password
|
|
}
|
|
}
|
|
|
|
config.Settings = settings
|
|
config.UDP = true
|
|
|
|
p.logger.Infof("Successfully parsed Socks5 configuration for server: %s:%d", config.Server, config.Port)
|
|
return config, nil
|
|
}
|
|
|
|
func (p *Socks5Parser) normalizeInput(input string) (string, error) {
|
|
trimmed := strings.TrimSpace(input)
|
|
if trimmed == "" {
|
|
return "", fmt.Errorf("empty socks proxy string")
|
|
}
|
|
|
|
lower := strings.ToLower(trimmed)
|
|
|
|
switch {
|
|
case strings.HasPrefix(lower, "socks5://"):
|
|
return trimmed, nil
|
|
case strings.HasPrefix(lower, "socks://"):
|
|
return "socks5://" + trimmed[len("socks://"):], nil
|
|
case strings.HasPrefix(lower, "tg://socks"), strings.HasPrefix(lower, "https://t.me/socks"):
|
|
return p.normalizeTelegramLink(trimmed)
|
|
default:
|
|
return trimmed, nil
|
|
}
|
|
}
|
|
|
|
func (p *Socks5Parser) normalizeTelegramLink(link string) (string, error) {
|
|
parsed, err := url.Parse(link)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to parse Telegram socks link: %w", err)
|
|
}
|
|
|
|
values := parsed.Query()
|
|
host := values.Get("server")
|
|
if host == "" {
|
|
host = values.Get("host")
|
|
}
|
|
port := values.Get("port")
|
|
if port == "" {
|
|
return "", fmt.Errorf("missing port in Telegram socks link")
|
|
}
|
|
|
|
username := values.Get("user")
|
|
if username == "" {
|
|
username = values.Get("username")
|
|
}
|
|
password := values.Get("pass")
|
|
if password == "" {
|
|
password = values.Get("password")
|
|
}
|
|
|
|
if host == "" {
|
|
return "", fmt.Errorf("missing server in Telegram socks link")
|
|
}
|
|
|
|
credentials := ""
|
|
if username != "" {
|
|
var userInfo *url.Userinfo
|
|
if password != "" {
|
|
userInfo = url.UserPassword(username, password)
|
|
} else {
|
|
userInfo = url.User(username)
|
|
}
|
|
credentials = userInfo.String() + "@"
|
|
}
|
|
|
|
fragment := parsed.Fragment
|
|
if fragment != "" {
|
|
fragment = "#" + fragment
|
|
}
|
|
|
|
return fmt.Sprintf("socks5://%s%s:%s%s", credentials, host, port, fragment), nil
|
|
}
|
|
|
|
// Validate 验证Socks5配置
|
|
func (p *Socks5Parser) Validate(config *ProxyConfig) error {
|
|
p.logger.Debugf("Validating Socks5 configuration")
|
|
|
|
// 检查必要字段
|
|
if config.Server == "" {
|
|
return fmt.Errorf("server address is required")
|
|
}
|
|
|
|
if config.Port <= 0 || config.Port > 65535 {
|
|
return fmt.Errorf("invalid port number: %d", config.Port)
|
|
}
|
|
|
|
// 验证认证信息
|
|
username := p.getUsername(config)
|
|
password := p.getPassword(config)
|
|
|
|
// 如果提供了用户名,验证密码是否也提供
|
|
if username != "" && password == "" {
|
|
p.logger.Warn("Username provided but password is empty")
|
|
}
|
|
|
|
// 如果没有提供用户名,确保也没有密码
|
|
if username == "" && password != "" {
|
|
return fmt.Errorf("password provided without username")
|
|
}
|
|
|
|
p.logger.Debug("Socks5 configuration validation passed")
|
|
return nil
|
|
}
|
|
|
|
// getUsername 从配置中获取用户名
|
|
func (p *Socks5Parser) getUsername(config *ProxyConfig) string {
|
|
if username, exists := config.Settings["username"]; exists {
|
|
if usernameStr, ok := username.(string); ok {
|
|
return usernameStr
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// getPassword 从配置中获取密码
|
|
func (p *Socks5Parser) getPassword(config *ProxyConfig) string {
|
|
if password, exists := config.Settings["password"]; exists {
|
|
if passwordStr, ok := password.(string); ok {
|
|
return passwordStr
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// GetSupportedProtocols 获取支持的协议
|
|
func (p *Socks5Parser) GetSupportedProtocols() []string {
|
|
return []string{"socks5"}
|
|
}
|