mirror of
https://github.com/alireza0/s-ui.git
synced 2026-10-21 11:22:26 +00:00
links: accept unpadded and url-safe base64, legacy ss format #1281
This commit is contained in:
parent
bea9c79916
commit
bbff4ea852
3 changed files with 86 additions and 3 deletions
|
|
@ -1,10 +1,25 @@
|
|||
package util
|
||||
|
||||
import "encoding/base64"
|
||||
import (
|
||||
"encoding/base64"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func decodeBase64(str string) ([]byte, error) {
|
||||
str = strings.TrimSpace(str)
|
||||
var err error
|
||||
for _, enc := range []*base64.Encoding{base64.StdEncoding, base64.RawStdEncoding, base64.URLEncoding, base64.RawURLEncoding} {
|
||||
var decoded []byte
|
||||
if decoded, err = enc.DecodeString(str); err == nil {
|
||||
return decoded, nil
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Function to return decoded bytes if a string is Base64 encoded
|
||||
func StrOrBase64Encoded(str string) string {
|
||||
decoded, err := base64.StdEncoding.DecodeString(str)
|
||||
decoded, err := decodeBase64(str)
|
||||
if err == nil {
|
||||
return string(decoded)
|
||||
}
|
||||
|
|
@ -12,7 +27,7 @@ func StrOrBase64Encoded(str string) string {
|
|||
}
|
||||
|
||||
func B64StrToByte(str string) ([]byte, error) {
|
||||
return base64.StdEncoding.DecodeString(str)
|
||||
return decodeBase64(str)
|
||||
}
|
||||
|
||||
func ByteToB64Str(b []byte) string {
|
||||
|
|
|
|||
|
|
@ -351,6 +351,21 @@ func tuic(u *url.URL, i int) (*map[string]interface{}, string, error) {
|
|||
}
|
||||
|
||||
func ss(u *url.URL, i int) (*map[string]interface{}, string, error) {
|
||||
if u.User == nil {
|
||||
decoded, err := decodeBase64(u.Host + strings.TrimSuffix(u.Path, "/"))
|
||||
if err != nil {
|
||||
return nil, "", common.NewError("Unsupported shadowsocks")
|
||||
}
|
||||
idx := strings.LastIndex(string(decoded), "@")
|
||||
if idx == -1 {
|
||||
return nil, "", common.NewError("Unsupported shadowsocks")
|
||||
}
|
||||
legacy := *u
|
||||
legacy.User = url.UserPassword(splitMethod(string(decoded[:idx])))
|
||||
legacy.Host = string(decoded[idx+1:])
|
||||
legacy.Path = ""
|
||||
u = &legacy
|
||||
}
|
||||
query, _ := url.ParseQuery(u.RawQuery)
|
||||
host, portStr, _ := net.SplitHostPort(u.Host)
|
||||
port := 443
|
||||
|
|
@ -410,6 +425,11 @@ func ss(u *url.URL, i int) (*map[string]interface{}, string, error) {
|
|||
return &ss, tag, nil
|
||||
}
|
||||
|
||||
func splitMethod(userInfo string) (string, string) {
|
||||
method, password, _ := strings.Cut(userInfo, ":")
|
||||
return method, password
|
||||
}
|
||||
|
||||
func parseNaiveLink(u *url.URL, i int) (*map[string]interface{}, string, error) {
|
||||
var host, portStr, username, password string
|
||||
var port int
|
||||
|
|
|
|||
48
util/linkToJson_ss_test.go
Normal file
48
util/linkToJson_ss_test.go
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Shadowsocks links come base64-encoded in several flavours; strict standard
|
||||
// decoding rejected most of them and the node vanished from Clash output,
|
||||
// while the link subscription passed the line through untouched (#1281).
|
||||
func TestShadowsocksLinkFormats(t *testing.T) {
|
||||
const method = "2022-blake3-aes-256-gcm"
|
||||
const password = "bWFza2VkLXZhbHVlMDAwMDAwMDAwMDAwMDAwMDAwMDA=:bWFza2VkLXZhbHVlMDAwMDAwMDAwMDAwMDAwMDAwMDA="
|
||||
userInfo := method + ":" + password
|
||||
|
||||
cases := map[string]string{
|
||||
"padded standard": base64.StdEncoding.EncodeToString([]byte(userInfo)) + "@[240e:1:2::1234]:22851",
|
||||
"unpadded url-safe": base64.RawURLEncoding.EncodeToString([]byte(userInfo)) + "@[240e:1:2::1234]:22851",
|
||||
"plugin path": base64.RawURLEncoding.EncodeToString([]byte(userInfo)) + "@[240e:1:2::1234]:22851/?plugin=none",
|
||||
"legacy whole": base64.StdEncoding.EncodeToString([]byte(userInfo + "@[240e:1:2::1234]:22851")),
|
||||
"legacy unpadded": base64.RawURLEncoding.EncodeToString([]byte(userInfo + "@[240e:1:2::1234]:22851")),
|
||||
}
|
||||
for name, body := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
out, tag, err := GetOutbound("ss://"+body+"#node", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
o := *out
|
||||
if tag != "node" || o["method"] != method || o["password"] != password ||
|
||||
o["server"] != "240e:1:2::1234" || o["server_port"] != 22851 {
|
||||
t.Errorf("got %v", o)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// vmess payloads are often unpadded too.
|
||||
func TestVmessUnpaddedLink(t *testing.T) {
|
||||
payload := base64.RawStdEncoding.EncodeToString([]byte(`{"v":"2","ps":"vm","add":"1.2.3.4","port":"443","id":"b831381d-6324-4d53-ad4f-8cda48b30811","net":"tcp"}`))
|
||||
out, _, err := GetOutbound("vmess://"+payload, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if (*out)["server"] != "1.2.3.4" {
|
||||
t.Errorf("got %v", *out)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue