github.com/ipfans/trojan-go@v0.11.0/url/share_link.go (about) 1 package url 2 3 import ( 4 "errors" 5 "fmt" 6 neturl "net/url" 7 "strconv" 8 "strings" 9 ) 10 11 const ( 12 ShareInfoTypeOriginal = "original" 13 ShareInfoTypeWebSocket = "ws" 14 ) 15 16 var validTypes = map[string]struct{}{ 17 ShareInfoTypeOriginal: {}, 18 ShareInfoTypeWebSocket: {}, 19 } 20 21 var validEncryptionProviders = map[string]struct{}{ 22 "ss": {}, 23 "none": {}, 24 } 25 26 var validSSEncryptionMap = map[string]struct{}{ 27 "aes-128-gcm": {}, 28 "aes-256-gcm": {}, 29 "chacha20-ietf-poly1305": {}, 30 } 31 32 type ShareInfo struct { 33 TrojanHost string // 节点 IP / 域名 34 Port uint16 // 节点端口 35 TrojanPassword string // Trojan 密码 36 37 SNI string // SNI 38 Type string // 类型 39 Host string // HTTP Host Header 40 41 Path string // WebSocket / H2 Path 42 Encryption string // 额外加密 43 Plugin string // 插件设定 44 45 Description string // 节点说明 46 } 47 48 func NewShareInfoFromURL(shareLink string) (info ShareInfo, e error) { 49 // share link must be valid url 50 parse, e := neturl.Parse(shareLink) 51 if e != nil { 52 e = fmt.Errorf("invalid url: %s", e.Error()) 53 return 54 } 55 56 // share link must have `trojan-go://` scheme 57 if parse.Scheme != "trojan-go" { 58 e = errors.New("url does not have a trojan-go:// scheme") 59 return 60 } 61 62 // password 63 if info.TrojanPassword = parse.User.Username(); info.TrojanPassword == "" { 64 e = errors.New("no password specified") 65 return 66 } else if _, hasPassword := parse.User.Password(); hasPassword { 67 e = errors.New("password possibly missing percentage encoding for colon") 68 return 69 } 70 71 // trojanHost: not empty & strip [] from IPv6 addresses 72 if info.TrojanHost = parse.Hostname(); info.TrojanHost == "" { 73 e = errors.New("host is empty") 74 return 75 } 76 77 // port 78 if info.Port, e = handleTrojanPort(parse.Port()); e != nil { 79 return 80 } 81 82 // strictly parse the query 83 query, e := neturl.ParseQuery(parse.RawQuery) 84 if e != nil { 85 return 86 } 87 88 // sni 89 if SNIs, ok := query["sni"]; !ok { 90 info.SNI = info.TrojanHost 91 } else if len(SNIs) > 1 { 92 e = errors.New("multiple SNIs") 93 return 94 } else if info.SNI = SNIs[0]; info.SNI == "" { 95 e = errors.New("empty SNI") 96 return 97 } 98 99 // type 100 if types, ok := query["type"]; !ok { 101 info.Type = ShareInfoTypeOriginal 102 } else if len(types) > 1 { 103 e = errors.New("multiple transport types") 104 return 105 } else if info.Type = types[0]; info.Type == "" { 106 e = errors.New("empty transport type") 107 return 108 } else if _, ok := validTypes[info.Type]; !ok { 109 e = fmt.Errorf("unknown transport type: %s", info.Type) 110 return 111 } 112 113 // host 114 if hosts, ok := query["host"]; !ok { 115 info.Host = info.TrojanHost 116 } else if len(hosts) > 1 { 117 e = errors.New("multiple hosts") 118 return 119 } else if info.Host = hosts[0]; info.Host == "" { 120 e = errors.New("empty host") 121 return 122 } 123 124 // path 125 if info.Type == ShareInfoTypeWebSocket { 126 if paths, ok := query["path"]; !ok { 127 e = errors.New("path is required in websocket") 128 return 129 } else if len(paths) > 1 { 130 e = errors.New("multiple paths") 131 return 132 } else if info.Path = paths[0]; info.Path == "" { 133 e = errors.New("empty path") 134 return 135 } 136 137 if !strings.HasPrefix(info.Path, "/") { 138 e = errors.New("path must start with /") 139 return 140 } 141 } 142 143 // encryption 144 if encryptionArr, ok := query["encryption"]; !ok { 145 // no encryption. that's okay. 146 } else if len(encryptionArr) > 1 { 147 e = errors.New("multiple encryption fields") 148 return 149 } else if info.Encryption = encryptionArr[0]; info.Encryption == "" { 150 e = errors.New("empty encryption") 151 return 152 } else { 153 encryptionParts := strings.SplitN(info.Encryption, ";", 2) 154 encryptionProviderName := encryptionParts[0] 155 156 if _, ok := validEncryptionProviders[encryptionProviderName]; !ok { 157 e = fmt.Errorf("unsupported encryption provider name: %s", encryptionProviderName) 158 return 159 } 160 161 var encryptionParams string 162 if len(encryptionParts) >= 2 { 163 encryptionParams = encryptionParts[1] 164 } 165 166 if encryptionProviderName == "ss" { 167 ssParams := strings.SplitN(encryptionParams, ":", 2) 168 if len(ssParams) < 2 { 169 e = errors.New("missing ss password") 170 return 171 } 172 173 ssMethod, ssPassword := ssParams[0], ssParams[1] 174 if _, ok := validSSEncryptionMap[ssMethod]; !ok { 175 e = fmt.Errorf("unsupported ss method: %s", ssMethod) 176 return 177 } 178 179 if ssPassword == "" { 180 e = errors.New("ss password cannot be empty") 181 return 182 } 183 } 184 } 185 186 // plugin 187 if plugins, ok := query["plugin"]; !ok { 188 // no plugin. that's okay. 189 } else if len(plugins) > 1 { 190 e = errors.New("multiple plugins") 191 return 192 } else if info.Plugin = plugins[0]; info.Plugin == "" { 193 e = errors.New("empty plugin") 194 return 195 } 196 197 // description 198 info.Description = parse.Fragment 199 200 return 201 } 202 203 func handleTrojanPort(p string) (port uint16, e error) { 204 if p == "" { 205 return 443, nil 206 } 207 208 portParsed, e := strconv.Atoi(p) 209 if e != nil { 210 return 211 } 212 213 if portParsed < 1 || portParsed > 65535 { 214 e = fmt.Errorf("invalid port %d", portParsed) 215 return 216 } 217 218 port = uint16(portParsed) 219 return 220 }