Files
proxypool/pkg/proxy/shadowsocksr.go
2020-09-04 17:51:15 +08:00

187 lines
4.9 KiB
Go

package proxy
import (
"encoding/json"
"errors"
"fmt"
"math/rand"
"net"
"net/url"
"regexp"
"strconv"
"strings"
"github.com/zu1k/proxypool/pkg/tool"
)
var (
ErrorNotSSRLink = errors.New("not a correct ssr link")
ErrorPasswordParseFail = errors.New("password parse failed")
ErrorPathNotComplete = errors.New("path not complete")
ErrorMissingQuery = errors.New("link missing query")
ErrorProtocolParamParseFail = errors.New("protocol param parse failed")
ErrorObfsParamParseFail = errors.New("obfs param parse failed")
)
type ShadowsocksR struct {
Base
Password string `yaml:"password" json:"password"`
Cipher string `yaml:"cipher" json:"cipher"`
Protocol string `yaml:"protocol" json:"protocol"`
ProtocolParam string `yaml:"protocol-param,omitempty" json:"protocol_param,omitempty"`
Obfs string `yaml:"obfs" json:"obfs"`
ObfsParam string `yaml:"obfs-param,omitempty" json:"obfs_param,omitempty"`
Group string `yaml:"group,omitempty" json:"group,omitempty"`
}
func (ssr ShadowsocksR) Identifier() string {
return net.JoinHostPort(ssr.Server, strconv.Itoa(ssr.Port)) + ssr.Password + ssr.ProtocolParam
}
func (ssr ShadowsocksR) String() string {
data, err := json.Marshal(ssr)
if err != nil {
return ""
}
return string(data)
}
func (ssr ShadowsocksR) ToClash() string {
data, err := json.Marshal(ssr)
if err != nil {
return ""
}
return "- " + string(data)
}
func (ssr ShadowsocksR) ToSurge() string {
return ""
}
func (ssr ShadowsocksR) Clone() Proxy {
return &ssr
}
// https://github.com/HMBSbige/ShadowsocksR-Windows/wiki/SSR-QRcode-scheme
func (ssr ShadowsocksR) Link() (link string) {
payload := fmt.Sprintf("%s:%d:%s:%s:%s:%s",
ssr.Server, ssr.Port, ssr.Protocol, ssr.Cipher, ssr.Obfs, tool.Base64EncodeString(ssr.Password, true))
query := url.Values{}
query.Add("obfsparam", tool.Base64EncodeString(ssr.ObfsParam, true))
query.Add("protoparam", tool.Base64EncodeString(ssr.ProtocolParam, true))
query.Add("remarks", tool.Base64EncodeString(ssr.Name, true))
query.Add("group", tool.Base64EncodeString("proxy.tgbot.co", true))
payload = tool.Base64EncodeString(fmt.Sprintf("%s/?%s", payload, query.Encode()), true)
return fmt.Sprintf("ssr://%s", payload)
}
func ParseSSRLink(link string) (*ShadowsocksR, error) {
if !strings.HasPrefix(link, "ssr") {
return nil, ErrorNotSSRLink
}
ssrmix := strings.SplitN(link, "://", 2)
if len(ssrmix) < 2 {
return nil, ErrorNotSSRLink
}
linkPayloadBase64 := ssrmix[1]
payload, err := tool.Base64DecodeString(linkPayloadBase64)
if err != nil {
return nil, ErrorMissingQuery
}
infoPayload := strings.SplitN(payload, "/?", 2)
if len(infoPayload) < 2 {
return nil, ErrorNotSSRLink
}
ssrpath := strings.Split(infoPayload[0], ":")
if len(ssrpath) < 6 {
return nil, ErrorPathNotComplete
}
// base info
server := strings.ToLower(ssrpath[0])
port, _ := strconv.Atoi(ssrpath[1])
protocol := strings.ToLower(ssrpath[2])
cipher := strings.ToLower(ssrpath[3])
obfs := strings.ToLower(ssrpath[4])
password, err := tool.Base64DecodeString(ssrpath[5])
if err != nil {
return nil, ErrorPasswordParseFail
}
moreInfo, _ := url.ParseQuery(infoPayload[1])
// remarks
remarks := moreInfo.Get("remarks")
remarks, err = tool.Base64DecodeString(remarks)
if err != nil {
remarks = ""
err = nil
}
if strings.ContainsAny(remarks, "\t\r\n ") {
remarks = strings.ReplaceAll(remarks, "\t", "")
remarks = strings.ReplaceAll(remarks, "\r", "")
remarks = strings.ReplaceAll(remarks, "\n", "")
remarks = strings.ReplaceAll(remarks, " ", "")
}
// protocol param
protocolParam, err := tool.Base64DecodeString(moreInfo.Get("protoparam"))
if err != nil {
return nil, ErrorProtocolParamParseFail
}
if tool.ContainChineseChar(protocolParam) {
protocolParam = ""
}
if strings.HasSuffix(protocol, "_compatible") {
protocol = strings.ReplaceAll(protocol, "_compatible", "")
}
// obfs param
obfsParam, err := tool.Base64DecodeString(moreInfo.Get("obfsparam"))
if err != nil {
return nil, ErrorObfsParamParseFail
}
if tool.ContainChineseChar(obfsParam) {
obfsParam = ""
}
if strings.HasSuffix(obfs, "_compatible") {
obfs = strings.ReplaceAll(obfs, "_compatible", "")
}
//group, err := tool.Base64DecodeString(moreInfo.Get("group"))
//if err != nil {
// group = ""
//}
group := ""
return &ShadowsocksR{
Base: Base{
Name: remarks + "_" + strconv.Itoa(rand.Int()),
Server: server,
Port: port,
Type: "ssr",
},
Password: password,
Cipher: cipher,
Protocol: protocol,
ProtocolParam: protocolParam,
Obfs: obfs,
ObfsParam: obfsParam,
Group: group,
}, nil
}
var (
ssrPlainRe = regexp.MustCompile("ssr://([A-Za-z0-9+/_-])+")
)
func GrepSSRLinkFromString(text string) []string {
results := make([]string, 0)
texts := strings.Split(text, "ssr://")
for _, text := range texts {
results = append(results, ssrPlainRe.FindAllString("ssr://"+text, -1)...)
}
return results
}