mirror of
https://github.com/bestnite/sub2clash.git
synced 2025-06-17 04:33:18 +08:00
Refactor Shadowsocks and Socks parsers to improve username and password handling
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@ -5,6 +5,4 @@ subs
|
|||||||
logs
|
logs
|
||||||
data
|
data
|
||||||
.env
|
.env
|
||||||
.vscode/settings.json
|
.vscode/settings.json
|
||||||
test
|
|
||||||
*test.go
|
|
@ -92,19 +92,35 @@ func (p *ShadowsocksParser) Parse(proxy string) (P.Proxy, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
method := link.User.Username()
|
method := link.User.Username()
|
||||||
password, _ := link.User.Password()
|
password, hasPassword := link.User.Password()
|
||||||
|
|
||||||
if password == "" {
|
if !hasPassword && isLikelyBase64(method) {
|
||||||
user, err := DecodeBase64(method)
|
decodedStr, err := DecodeBase64(method)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
methodAndPass := strings.SplitN(user, ":", 2)
|
methodAndPass := strings.SplitN(decodedStr, ":", 2)
|
||||||
if len(methodAndPass) == 2 {
|
if len(methodAndPass) == 2 {
|
||||||
method = methodAndPass[0]
|
method, err = url.QueryUnescape(methodAndPass[0])
|
||||||
password = methodAndPass[1]
|
if err != nil {
|
||||||
|
return P.Proxy{}, &ParseError{
|
||||||
|
Type: ErrInvalidStruct,
|
||||||
|
Message: "invalid method",
|
||||||
|
Raw: proxy,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
password, err = url.QueryUnescape(methodAndPass[1])
|
||||||
|
if err != nil {
|
||||||
|
return P.Proxy{}, &ParseError{
|
||||||
|
Type: ErrInvalidStruct,
|
||||||
|
Message: "invalid password",
|
||||||
|
Raw: proxy,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
method = decodedStr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if isLikelyBase64(password) {
|
if password != "" && isLikelyBase64(password) {
|
||||||
password, err = DecodeBase64(password)
|
password, err = DecodeBase64(password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return P.Proxy{}, &ParseError{
|
return P.Proxy{}, &ParseError{
|
||||||
|
@ -68,23 +68,45 @@ func (p *SocksParser) Parse(proxy string) (P.Proxy, error) {
|
|||||||
}
|
}
|
||||||
remarks = strings.TrimSpace(remarks)
|
remarks = strings.TrimSpace(remarks)
|
||||||
|
|
||||||
encodeStr := link.User.Username()
|
|
||||||
var username, password string
|
var username, password string
|
||||||
if encodeStr != "" {
|
|
||||||
decodeStr, err := DecodeBase64(encodeStr)
|
username = link.User.Username()
|
||||||
splitStr := strings.Split(decodeStr, ":")
|
password, hasPassword := link.User.Password()
|
||||||
if err != nil {
|
|
||||||
return P.Proxy{}, &ParseError{
|
if !hasPassword && isLikelyBase64(username) {
|
||||||
Type: ErrInvalidStruct,
|
decodedStr, err := DecodeBase64(username)
|
||||||
Message: "url parse error",
|
if err == nil {
|
||||||
Raw: proxy,
|
usernameAndPassword := strings.SplitN(decodedStr, ":", 2)
|
||||||
|
if len(usernameAndPassword) == 2 {
|
||||||
|
username, err = url.QueryUnescape(usernameAndPassword[0])
|
||||||
|
if err != nil {
|
||||||
|
return P.Proxy{}, &ParseError{
|
||||||
|
Type: ErrInvalidStruct,
|
||||||
|
Message: "invalid username",
|
||||||
|
Raw: proxy,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
password, err = url.QueryUnescape(usernameAndPassword[1])
|
||||||
|
if err != nil {
|
||||||
|
return P.Proxy{}, &ParseError{
|
||||||
|
Type: ErrInvalidStruct,
|
||||||
|
Message: "invalid password",
|
||||||
|
Raw: proxy,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
username, err = url.QueryUnescape(decodedStr)
|
||||||
|
if err != nil {
|
||||||
|
return P.Proxy{}, &ParseError{
|
||||||
|
Type: ErrInvalidStruct,
|
||||||
|
Message: "invalid username",
|
||||||
|
Raw: proxy,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
username = splitStr[0]
|
|
||||||
if len(splitStr) == 2 {
|
|
||||||
password = splitStr[1]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return P.Proxy{
|
return P.Proxy{
|
||||||
Type: p.GetType(),
|
Type: p.GetType(),
|
||||||
Name: remarks,
|
Name: remarks,
|
||||||
@ -95,7 +117,6 @@ func (p *SocksParser) Parse(proxy string) (P.Proxy, error) {
|
|||||||
Password: password,
|
Password: password,
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
221
test/socks_test.go
Normal file
221
test/socks_test.go
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/bestnite/sub2clash/model/proxy"
|
||||||
|
"github.com/bestnite/sub2clash/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSocksParser_Parse(t *testing.T) {
|
||||||
|
p := &parser.SocksParser{}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
expected proxy.Proxy
|
||||||
|
expectError bool
|
||||||
|
errorType string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "无认证SOCKS5代理 - IPv4",
|
||||||
|
input: "socks://127.0.0.1:1080",
|
||||||
|
expected: proxy.Proxy{
|
||||||
|
Type: "socks5",
|
||||||
|
Name: "127.0.0.1:1080",
|
||||||
|
Socks: proxy.Socks{
|
||||||
|
Server: "127.0.0.1",
|
||||||
|
Port: 1080,
|
||||||
|
UserName: "",
|
||||||
|
Password: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "无认证SOCKS5代理 - IPv6",
|
||||||
|
input: "socks://[2001:db8::1]:1080",
|
||||||
|
expected: proxy.Proxy{
|
||||||
|
Type: "socks5",
|
||||||
|
Name: "2001:db8::1:1080",
|
||||||
|
Socks: proxy.Socks{
|
||||||
|
Server: "2001:db8::1",
|
||||||
|
Port: 1080,
|
||||||
|
UserName: "",
|
||||||
|
Password: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "明文用户名密码认证 - IPv4",
|
||||||
|
input: "socks://user:pass@127.0.0.1:1080",
|
||||||
|
expected: proxy.Proxy{
|
||||||
|
Type: "socks5",
|
||||||
|
Name: "127.0.0.1:1080",
|
||||||
|
Socks: proxy.Socks{
|
||||||
|
Server: "127.0.0.1",
|
||||||
|
Port: 1080,
|
||||||
|
UserName: "user",
|
||||||
|
Password: "pass",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "明文用户名密码认证 - IPv6",
|
||||||
|
input: "socks://user:pass@[2001:db8::1]:1080",
|
||||||
|
expected: proxy.Proxy{
|
||||||
|
Type: "socks5",
|
||||||
|
Name: "2001:db8::1:1080",
|
||||||
|
Socks: proxy.Socks{
|
||||||
|
Server: "2001:db8::1",
|
||||||
|
Port: 1080,
|
||||||
|
UserName: "user",
|
||||||
|
Password: "pass",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Base64编码的用户名密码",
|
||||||
|
input: "socks://dXNlcm5hbWU6cGFzc3dvcmQ=@127.0.0.1:1080",
|
||||||
|
expected: proxy.Proxy{
|
||||||
|
Type: "socks5",
|
||||||
|
Name: "127.0.0.1:1080",
|
||||||
|
Socks: proxy.Socks{
|
||||||
|
Server: "127.0.0.1",
|
||||||
|
Port: 1080,
|
||||||
|
UserName: "username",
|
||||||
|
Password: "password",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "带Fragment的URL",
|
||||||
|
input: "socks://username:password@proxy.example.com:1080#MyProxy",
|
||||||
|
expected: proxy.Proxy{
|
||||||
|
Type: "socks5",
|
||||||
|
Name: "MyProxy",
|
||||||
|
Socks: proxy.Socks{
|
||||||
|
Server: "proxy.example.com",
|
||||||
|
Port: 1080,
|
||||||
|
UserName: "username",
|
||||||
|
Password: "password",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "只有用户名没有密码",
|
||||||
|
input: "socks://username@127.0.0.1:1080",
|
||||||
|
expected: proxy.Proxy{
|
||||||
|
Type: "socks5",
|
||||||
|
Name: "127.0.0.1:1080",
|
||||||
|
Socks: proxy.Socks{
|
||||||
|
Server: "127.0.0.1",
|
||||||
|
Port: 1080,
|
||||||
|
UserName: "username",
|
||||||
|
Password: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Base64编码只有用户名",
|
||||||
|
input: "socks://dXNlcm5hbWU=@127.0.0.1:1080",
|
||||||
|
expected: proxy.Proxy{
|
||||||
|
Type: "socks5",
|
||||||
|
Name: "127.0.0.1:1080",
|
||||||
|
Socks: proxy.Socks{
|
||||||
|
Server: "127.0.0.1",
|
||||||
|
Port: 1080,
|
||||||
|
UserName: "username",
|
||||||
|
Password: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "域名代理",
|
||||||
|
input: "socks://proxy.example.com:8080",
|
||||||
|
expected: proxy.Proxy{
|
||||||
|
Type: "socks5",
|
||||||
|
Name: "proxy.example.com:8080",
|
||||||
|
Socks: proxy.Socks{
|
||||||
|
Server: "proxy.example.com",
|
||||||
|
Port: 8080,
|
||||||
|
UserName: "",
|
||||||
|
Password: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "特殊字符用户名密码",
|
||||||
|
input: "socks://user%40domain:p%40ss%21@127.0.0.1:1080",
|
||||||
|
expected: proxy.Proxy{
|
||||||
|
Type: "socks5",
|
||||||
|
Name: "127.0.0.1:1080",
|
||||||
|
Socks: proxy.Socks{
|
||||||
|
Server: "127.0.0.1",
|
||||||
|
Port: 1080,
|
||||||
|
UserName: "user@domain",
|
||||||
|
Password: "p@ss!",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result, err := p.Parse(tt.input)
|
||||||
|
|
||||||
|
if tt.expectError {
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("期望出现错误,但没有错误")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if parseErr, ok := err.(*parser.ParseError); ok {
|
||||||
|
if tt.errorType != "" && string(parseErr.Type) != tt.errorType {
|
||||||
|
t.Errorf("错误类型不匹配: 期望 %s, 实际 %s", tt.errorType, string(parseErr.Type))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("意外的错误: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证解析结果
|
||||||
|
if result.Type != tt.expected.Type {
|
||||||
|
t.Errorf("Type不匹配: 期望 %s, 实际 %s", tt.expected.Type, result.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Name != tt.expected.Name {
|
||||||
|
t.Errorf("Name不匹配: 期望 %s, 实际 %s", tt.expected.Name, result.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Socks.Server != tt.expected.Socks.Server {
|
||||||
|
t.Errorf("Server不匹配: 期望 %s, 实际 %s", tt.expected.Socks.Server, result.Socks.Server)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Socks.Port != tt.expected.Socks.Port {
|
||||||
|
t.Errorf("Port不匹配: 期望 %d, 实际 %d", tt.expected.Socks.Port, result.Socks.Port)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Socks.UserName != tt.expected.Socks.UserName {
|
||||||
|
t.Errorf("UserName不匹配: 期望 %s, 实际 %s", tt.expected.Socks.UserName, result.Socks.UserName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Socks.Password != tt.expected.Socks.Password {
|
||||||
|
t.Errorf("Password不匹配: 期望 %s, 实际 %s", tt.expected.Socks.Password, result.Socks.Password)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user