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  }