Skip to content

Commit

Permalink
Merge pull request #133 from DuckSoft/patch-6
Browse files Browse the repository at this point in the history
url: add share link parser
  • Loading branch information
p4gefau1t authored Jul 12, 2020
2 parents 477ed96 + 6fa104d commit 7e31366
Show file tree
Hide file tree
Showing 4 changed files with 462 additions and 2 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/refraction-networking/utls v0.0.0-20200601200209-ada0bb9b38a0
github.com/shadowsocks/go-shadowsocks2 v0.1.0
github.com/smartystreets/goconvey v1.6.4
github.com/stretchr/testify v1.6.1
github.com/txthinking/runnergroup v0.0.0-20200327135940-540a793bb997 // indirect
github.com/txthinking/socks5 v0.0.0-20200531111549-252709fcb919
github.com/txthinking/x v0.0.0-20200330144832-5ad2416896a9 // indirect
Expand Down
11 changes: 9 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
Expand Down Expand Up @@ -55,6 +57,8 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/refraction-networking/utls v0.0.0-20190909200633-43c36d3c1f57/go.mod h1:tz9gX959MEFfFN5whTIocCLUG57WiILqtdVxI8c6Wj0=
github.com/refraction-networking/utls v0.0.0-20200601200209-ada0bb9b38a0 h1:vIkvetWOJZSADSKCF9MLTsQNW2httdBmYz47dQQteP8=
Expand All @@ -65,6 +69,9 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykE
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/txthinking/runnergroup v0.0.0-20200327135940-540a793bb997 h1:vlDgnShahmE2XLslpr0hnzxfAmSj3JLX2CYi8Xct7G4=
github.com/txthinking/runnergroup v0.0.0-20200327135940-540a793bb997/go.mod h1:CLUSJbazqETbaR+i0YAhXBICV9TrKH93pziccMhmhpM=
github.com/txthinking/socks5 v0.0.0-20200531111549-252709fcb919 h1:yu9Bs+KssCJNxu/9fzRag6QgzOnxoH1Q6TvIiD4L6rQ=
Expand Down Expand Up @@ -112,8 +119,6 @@ golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omNUX/JuqbFSaRGqU8CcLI=
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s=
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
Expand Down Expand Up @@ -160,6 +165,8 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8X
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
h12.io/socks v1.0.0/go.mod h1:MdYbo5/eB9ka7u5dzW2Qh0iSyJENwB3KI5H5ngenFGA=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
200 changes: 200 additions & 0 deletions url/share_link.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
package url

import (
"errors"
"fmt"
"net/url"
"strconv"
"strings"
)

const (
ShareInfoTypeOriginal = "original"
ShareInfoTypeWebSocket = "ws"
)

var validTypes = map[string]struct{}{
ShareInfoTypeOriginal: {},
ShareInfoTypeWebSocket: {},
}

var validSSEncryptionMap = map[string]struct{}{
"aes-128-gcm": {},
"aes-256-gcm": {},
"chacha20-ietf-poly1305": {},
}

type ShareInfo struct {
TrojanHost string // 节点 IP / 域名
Port uint16 // 节点端口
TrojanPassword string // Trojan 密码

SNI string // SNI
Type string // 类型
Host string // HTTP Host Header

Path string // WebSocket / H2 Path
Encryption string // 额外加密
Plugin string // 插件设定

Description string // 节点说明
}

func NewShareInfoFromURL(shareLink string) (info ShareInfo, e error) {
// share link must be valid url
parse, e := url.Parse(shareLink)
if e != nil {
e = errors.New(fmt.Sprintf("invalid url: %s", e.Error()))
return
}

// share link must have `trojan-go://` scheme
if parse.Scheme != "trojan-go" {
e = errors.New("url does not have a trojan-go:// scheme")
return
}

// password
if info.TrojanPassword = parse.User.Username(); info.TrojanPassword == "" {
e = errors.New("no password specified")
return
} else if _, hasPassword := parse.User.Password(); hasPassword {
e = errors.New("password possibly missing percentage encoding for colon")
return
}

// trojanHost: not empty & strip [] from IPv6 addresses
if info.TrojanHost = parse.Hostname(); info.TrojanHost == "" {
e = errors.New("host is empty")
return
}

// port
if info.Port, e = handleTrojanPort(parse.Port()); e != nil {
return
}

// strictly parse the query
query, e := url.ParseQuery(parse.RawQuery)
if e != nil {
return
}

// sni
if SNIs, ok := query["sni"]; !ok {
info.SNI = info.TrojanHost
} else if len(SNIs) > 1 {
e = errors.New("multiple SNIs")
return
} else if info.SNI = SNIs[0]; info.SNI == ""{
e = errors.New("empty SNI")
return
}

// type
if types, ok := query["type"]; !ok {
info.Type = ShareInfoTypeOriginal
} else if len(types) > 1 {
e = errors.New("multiple transport types")
return
} else if info.Type = types[0]; info.Type == "" {
e = errors.New("empty transport type")
return
} else if _, ok := validTypes[info.Type]; !ok {
e = errors.New(fmt.Sprintf("unknown transport type: %s", info.Type))
return
}

// host
if hosts, ok := query["host"]; !ok {
info.Host = info.TrojanHost
} else if len(hosts) > 1 {
e = errors.New("multiple hosts")
return
} else if info.Host = hosts[0]; info.Host == ""{
e = errors.New("empty host")
return
}

// path
if info.Type == ShareInfoTypeWebSocket {
if paths, ok := query["path"]; !ok {
e = errors.New("path is required in websocket")
return
} else if len(paths) > 1 {
e = errors.New("multiple paths")
return
} else if info.Path = paths[0]; info.Path == "" {
e = errors.New("empty path")
return
}

if !strings.HasPrefix(info.Path, "/") {
e = errors.New("path must start with /")
return
}
}

// encryption
if encryptionArr, ok := query["encryption"]; !ok {
// no encryption. that's okay.
} else if len(encryptionArr) > 1 {
e = errors.New("multiple encryption fields")
return
} else if info.Encryption = encryptionArr[0]; info.Encryption == "" {
e = errors.New("empty encryption")
return
} else {
if strings.HasPrefix(info.Encryption, "ss;") {
if parts := strings.SplitN(info.Encryption, ";", 3); len(parts) != 3 {
e = errors.New("expected 3 ss encryption params")
return
} else if _, ok := validSSEncryptionMap[parts[1]]; !ok {
e = errors.New(fmt.Sprintf("unsupported ss encryption method: %s", parts[1]))
return
} else if parts[2] == "" {
e = errors.New("empty ss encryption key")
return
}
} else {
// add more encryption support here
e = errors.New(fmt.Sprintf("encryption param %s is not supported", info.Encryption))
return
}
}

// plugin
if plugins, ok := query["plugin"]; !ok {
// no plugin. that's okay.
} else if len(plugins) > 1 {
e = errors.New("multiple plugins")
return
} else if info.Plugin = plugins[0]; info.Plugin == "" {
e = errors.New("empty plugin")
return
}

// description
info.Description = parse.Fragment

return
}

func handleTrojanPort(p string) (port uint16, e error) {
if p == "" {
return 443, nil
}

portParsed, e := strconv.Atoi(p)
if e != nil {
return
}

if portParsed < 1 || portParsed > 65535 {
e = errors.New(fmt.Sprintf("invalid port %d", portParsed))
return
}

port = uint16(portParsed)
return
}
Loading

0 comments on commit 7e31366

Please sign in to comment.