2024-03-10 15:13:42 -04:00
|
|
|
package parser
|
|
|
|
|
|
|
|
import (
|
2024-08-04 05:01:09 -04:00
|
|
|
"fmt"
|
2024-03-10 15:13:42 -04:00
|
|
|
"net/url"
|
|
|
|
"strings"
|
2024-09-19 06:12:24 -04:00
|
|
|
|
|
|
|
"github.com/nitezs/sub2sing-box/constant"
|
|
|
|
"github.com/nitezs/sub2sing-box/model"
|
|
|
|
"github.com/nitezs/sub2sing-box/util"
|
2024-10-02 13:24:54 -04:00
|
|
|
"github.com/sagernet/sing-box/option"
|
2024-03-10 15:13:42 -04:00
|
|
|
)
|
|
|
|
|
2024-03-20 12:02:38 -04:00
|
|
|
func ParseShadowsocks(proxy string) (model.Outbound, error) {
|
2024-08-04 05:01:09 -04:00
|
|
|
if !strings.HasPrefix(proxy, constant.ShadowsocksPrefix) {
|
|
|
|
return model.Outbound{}, &ParseError{Type: ErrInvalidPrefix, Raw: proxy}
|
|
|
|
}
|
2024-10-08 23:37:28 -04:00
|
|
|
if !strings.Contains(proxy, "@") {
|
|
|
|
s := strings.SplitN(proxy, "#", 2)
|
|
|
|
d, err := util.DecodeBase64(strings.TrimPrefix(s[0], "ss://"))
|
|
|
|
if err != nil {
|
|
|
|
return model.Outbound{}, &ParseError{
|
|
|
|
Type: ErrInvalidStruct,
|
|
|
|
Message: "url parse error",
|
|
|
|
Raw: proxy,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(s) == 2 {
|
|
|
|
proxy = "ss://" + d + "#" + s[1]
|
|
|
|
} else {
|
|
|
|
proxy = "ss://" + d
|
|
|
|
}
|
|
|
|
}
|
2024-07-20 11:45:04 -04:00
|
|
|
link, err := url.Parse(proxy)
|
|
|
|
if err != nil {
|
|
|
|
return model.Outbound{}, &ParseError{
|
|
|
|
Type: ErrInvalidStruct,
|
|
|
|
Message: "url parse error",
|
|
|
|
Raw: proxy,
|
|
|
|
}
|
2024-03-10 15:13:42 -04:00
|
|
|
}
|
2024-03-22 04:10:15 -04:00
|
|
|
|
2024-07-20 11:45:04 -04:00
|
|
|
server := link.Hostname()
|
|
|
|
if server == "" {
|
2024-03-22 04:10:15 -04:00
|
|
|
return model.Outbound{}, &ParseError{
|
|
|
|
Type: ErrInvalidStruct,
|
2024-07-20 11:45:04 -04:00
|
|
|
Message: "missing server host",
|
2024-03-22 04:10:15 -04:00
|
|
|
Raw: proxy,
|
|
|
|
}
|
2024-03-10 15:13:42 -04:00
|
|
|
}
|
2024-03-22 04:10:15 -04:00
|
|
|
|
2024-08-04 05:01:09 -04:00
|
|
|
portStr := link.Port()
|
|
|
|
if portStr == "" {
|
|
|
|
return model.Outbound{}, &ParseError{
|
|
|
|
Type: ErrInvalidStruct,
|
|
|
|
Message: "missing server port",
|
|
|
|
Raw: proxy,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
port, err := ParsePort(portStr)
|
|
|
|
if err != nil {
|
|
|
|
return model.Outbound{}, &ParseError{
|
|
|
|
Type: ErrInvalidStruct,
|
|
|
|
Raw: proxy,
|
|
|
|
}
|
2024-03-10 15:13:42 -04:00
|
|
|
}
|
2024-07-20 11:45:04 -04:00
|
|
|
|
2024-11-06 05:38:59 -05:00
|
|
|
method := link.User.Username()
|
|
|
|
password, _ := link.User.Password()
|
|
|
|
|
|
|
|
if password == "" {
|
|
|
|
user, err := util.DecodeBase64(method)
|
|
|
|
if err == nil {
|
|
|
|
methodAndPass := strings.SplitN(user, ":", 2)
|
|
|
|
if len(methodAndPass) == 2 {
|
|
|
|
method = methodAndPass[0]
|
|
|
|
password = methodAndPass[1]
|
2024-10-08 23:37:28 -04:00
|
|
|
}
|
2024-03-22 04:10:15 -04:00
|
|
|
}
|
2024-11-06 05:38:59 -05:00
|
|
|
}
|
|
|
|
if isLikelyBase64(password) {
|
|
|
|
password, err = util.DecodeBase64(password)
|
|
|
|
if err != nil {
|
2024-10-08 23:37:28 -04:00
|
|
|
return model.Outbound{}, &ParseError{
|
|
|
|
Type: ErrInvalidStruct,
|
2024-11-06 05:38:59 -05:00
|
|
|
Message: "password decode error",
|
2024-10-08 23:37:28 -04:00
|
|
|
Raw: proxy,
|
|
|
|
}
|
2024-04-23 02:41:14 -04:00
|
|
|
}
|
2024-03-10 15:13:42 -04:00
|
|
|
}
|
2024-03-22 04:10:15 -04:00
|
|
|
|
2024-07-20 11:45:04 -04:00
|
|
|
query := link.Query()
|
|
|
|
pluginStr := query.Get("plugin")
|
|
|
|
plugin := ""
|
|
|
|
options := ""
|
|
|
|
if pluginStr != "" {
|
|
|
|
arr := strings.SplitN(pluginStr, ";", 2)
|
|
|
|
if len(arr) == 2 {
|
|
|
|
plugin = arr[0]
|
|
|
|
options = arr[1]
|
2024-03-10 15:13:42 -04:00
|
|
|
}
|
|
|
|
}
|
2024-03-22 04:10:15 -04:00
|
|
|
|
2024-08-04 05:01:09 -04:00
|
|
|
remarks := link.Fragment
|
|
|
|
if remarks == "" {
|
|
|
|
remarks = fmt.Sprintf("%s:%s", server, portStr)
|
|
|
|
}
|
|
|
|
remarks = strings.TrimSpace(remarks)
|
|
|
|
|
2024-03-20 12:02:38 -04:00
|
|
|
result := model.Outbound{
|
2024-10-02 13:24:54 -04:00
|
|
|
Outbound: option.Outbound{
|
|
|
|
Type: "shadowsocks",
|
|
|
|
Tag: remarks,
|
|
|
|
ShadowsocksOptions: option.ShadowsocksOutboundOptions{
|
|
|
|
ServerOptions: option.ServerOptions{
|
|
|
|
Server: server,
|
|
|
|
ServerPort: port,
|
|
|
|
},
|
|
|
|
Method: method,
|
|
|
|
Password: password,
|
|
|
|
Plugin: plugin,
|
|
|
|
PluginOptions: options,
|
2024-03-20 12:02:38 -04:00
|
|
|
},
|
2024-03-10 15:13:42 -04:00
|
|
|
},
|
|
|
|
}
|
|
|
|
return result, nil
|
|
|
|
}
|
2024-11-06 05:38:59 -05:00
|
|
|
|
|
|
|
func isLikelyBase64(s string) bool {
|
|
|
|
if len(s)%4 == 0 && strings.HasSuffix(s, "=") && !strings.Contains(strings.TrimSuffix(s, "="), "=") {
|
|
|
|
s = strings.TrimSuffix(s, "=")
|
|
|
|
chars := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
|
|
|
for _, c := range s {
|
|
|
|
if !strings.ContainsRune(chars, c) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|