github.com/yaling888/clash@v1.53.0/common/convert/converter.go (about)

     1  package convert
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"encoding/base64"
     7  	"encoding/json"
     8  	"fmt"
     9  	"net"
    10  	"net/netip"
    11  	"net/url"
    12  	"strconv"
    13  	"strings"
    14  )
    15  
    16  var enc = base64.StdEncoding
    17  
    18  func DecodeBase64(buf []byte) ([]byte, error) {
    19  	buff := bytes.TrimSpace(buf)
    20  	dBuf := make([]byte, enc.DecodedLen(len(buff)))
    21  	n, err := enc.Decode(dBuf, buff)
    22  	if err != nil {
    23  		return nil, err
    24  	}
    25  
    26  	return dBuf[:n], nil
    27  }
    28  
    29  func DecodeRawBase64(buf []byte) ([]byte, error) {
    30  	buff := bytes.TrimSpace(buf)
    31  	dBuf := make([]byte, base64.RawStdEncoding.DecodedLen(len(buff)))
    32  	n, err := base64.RawStdEncoding.Decode(dBuf, buff)
    33  	if err != nil {
    34  		return nil, err
    35  	}
    36  
    37  	return dBuf[:n], nil
    38  }
    39  
    40  // ConvertsV2Ray convert V2Ray subscribe proxies data to clash proxies config
    41  func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
    42  	data, err := DecodeBase64(buf)
    43  	if err != nil {
    44  		data, err = DecodeRawBase64(buf)
    45  		if err != nil {
    46  			data = buf
    47  		}
    48  	}
    49  
    50  	arr := strings.Split(string(data), "\n")
    51  
    52  	proxies := make([]map[string]any, 0, len(arr))
    53  	names := make(map[string]int, 200)
    54  
    55  	for _, line := range arr {
    56  		line = strings.TrimRight(line, " \r")
    57  		if line == "" {
    58  			continue
    59  		}
    60  
    61  		scheme, body, found := strings.Cut(line, "://")
    62  		if !found {
    63  			continue
    64  		}
    65  
    66  		scheme = strings.ToLower(scheme)
    67  		switch scheme {
    68  		case "trojan":
    69  			urlTrojan, err := url.Parse(line)
    70  			if err != nil {
    71  				continue
    72  			}
    73  
    74  			query := urlTrojan.Query()
    75  
    76  			name := uniqueName(names, urlTrojan.Fragment)
    77  			trojan := make(map[string]any, 20)
    78  
    79  			trojan["name"] = name
    80  			trojan["type"] = scheme
    81  			trojan["server"] = urlTrojan.Hostname()
    82  			trojan["port"] = urlTrojan.Port()
    83  			trojan["password"] = urlTrojan.User.Username()
    84  			trojan["udp"] = true
    85  			trojan["skip-cert-verify"] = false
    86  
    87  			sni := query.Get("sni")
    88  			if sni != "" {
    89  				trojan["sni"] = sni
    90  			}
    91  
    92  			network := strings.ToLower(query.Get("type"))
    93  			if network != "" {
    94  				trojan["network"] = network
    95  			}
    96  
    97  			if network == "ws" {
    98  				headers := make(map[string]any)
    99  				wsOpts := make(map[string]any)
   100  
   101  				headers["User-Agent"] = RandUserAgent()
   102  
   103  				wsOpts["path"] = query.Get("path")
   104  				wsOpts["headers"] = headers
   105  
   106  				trojan["ws-opts"] = wsOpts
   107  			}
   108  
   109  			proxies = append(proxies, trojan)
   110  		case "vmess":
   111  			dcBuf, err := enc.DecodeString(body)
   112  			if err != nil {
   113  				continue
   114  			}
   115  
   116  			jsonDc := json.NewDecoder(bytes.NewReader(dcBuf))
   117  			values := make(map[string]any, 20)
   118  
   119  			if jsonDc.Decode(&values) != nil {
   120  				continue
   121  			}
   122  
   123  			name, ok := values["ps"].(string)
   124  			if !ok {
   125  				continue
   126  			}
   127  
   128  			name = uniqueName(names, name)
   129  			vmess := make(map[string]any, 20)
   130  
   131  			vmess["name"] = name
   132  			vmess["type"] = scheme
   133  			vmess["server"] = values["add"]
   134  			vmess["port"] = values["port"]
   135  			vmess["uuid"] = values["id"]
   136  			vmess["alterId"] = values["aid"]
   137  			vmess["cipher"] = "auto"
   138  			vmess["udp"] = true
   139  			vmess["skip-cert-verify"] = false
   140  
   141  			var (
   142  				sni     = values["sni"]
   143  				host    = values["host"]
   144  				network = "tcp"
   145  			)
   146  			if n, ok := values["net"].(string); ok {
   147  				network = strings.ToLower(n)
   148  			}
   149  			vmess["network"] = network
   150  
   151  			var (
   152  				tls   = ""
   153  				isTls = false
   154  			)
   155  			if t, ok := values["tls"].(string); ok {
   156  				tls = strings.ToLower(t)
   157  			}
   158  			if tls != "" && tls != "0" && tls != "null" {
   159  				if sni != nil {
   160  					vmess["servername"] = sni
   161  				}
   162  				vmess["tls"] = true
   163  				isTls = true
   164  			}
   165  
   166  			if network == "ws" {
   167  				headers := make(map[string]any)
   168  				wsOpts := make(map[string]any)
   169  
   170  				if !isTls {
   171  					if _, ok = host.(string); ok {
   172  						headers["Host"] = host
   173  					} else {
   174  						headers["Host"] = RandHost()
   175  					}
   176  				}
   177  
   178  				headers["User-Agent"] = RandUserAgent()
   179  
   180  				if values["path"] != nil {
   181  					wsOpts["path"] = values["path"]
   182  				}
   183  				wsOpts["headers"] = headers
   184  
   185  				vmess["ws-opts"] = wsOpts
   186  			} else if network == "http" {
   187  				headers := make(map[string][]string)
   188  				httpOpts := make(map[string]any)
   189  
   190  				if !isTls {
   191  					if h, ok := host.(string); ok {
   192  						headers["Host"] = []string{h}
   193  					} else {
   194  						headers["Host"] = []string{RandHost()}
   195  					}
   196  				}
   197  
   198  				headers["User-Agent"] = []string{RandUserAgent()}
   199  
   200  				if values["path"] != nil {
   201  					httpOpts["path"] = values["path"]
   202  				}
   203  				httpOpts["Host"] = values["add"]
   204  				httpOpts["headers"] = headers
   205  
   206  				vmess["http-opts"] = httpOpts
   207  			}
   208  
   209  			proxies = append(proxies, vmess)
   210  		case "ss":
   211  			urlSS, err := url.Parse(line)
   212  			if err != nil {
   213  				continue
   214  			}
   215  
   216  			name := uniqueName(names, urlSS.Fragment)
   217  			port := urlSS.Port()
   218  
   219  			if port == "" {
   220  				dcBuf, err := enc.DecodeString(urlSS.Host)
   221  				if err != nil {
   222  					continue
   223  				}
   224  
   225  				urlSS, err = url.Parse("ss://" + string(dcBuf))
   226  				if err != nil {
   227  					continue
   228  				}
   229  			}
   230  
   231  			var (
   232  				cipher   = urlSS.User.Username()
   233  				password string
   234  			)
   235  
   236  			if password, found = urlSS.User.Password(); !found {
   237  				dcBuf, err := enc.DecodeString(cipher)
   238  				if err != nil {
   239  					continue
   240  				}
   241  
   242  				cipher, password, found = strings.Cut(string(dcBuf), ":")
   243  				if !found {
   244  					continue
   245  				}
   246  			}
   247  
   248  			ss := make(map[string]any, 20)
   249  
   250  			ss["name"] = name
   251  			ss["type"] = scheme
   252  			ss["server"] = urlSS.Hostname()
   253  			ss["port"] = urlSS.Port()
   254  			ss["cipher"] = cipher
   255  			ss["password"] = password
   256  			ss["udp"] = true
   257  
   258  			proxies = append(proxies, ss)
   259  		case "ssr":
   260  			dcBuf, err := enc.DecodeString(body)
   261  			if err != nil {
   262  				continue
   263  			}
   264  
   265  			// ssr://host:port:protocol:method:obfs:urlsafebase64pass/?obfsparam=urlsafebase64&protoparam=&remarks=urlsafebase64&group=urlsafebase64&udpport=0&uot=1
   266  
   267  			before, after, ok := strings.Cut(string(dcBuf), "/?")
   268  			if !ok {
   269  				continue
   270  			}
   271  
   272  			beforeArr := strings.Split(before, ":")
   273  
   274  			if len(beforeArr) != 6 {
   275  				continue
   276  			}
   277  
   278  			host := beforeArr[0]
   279  			port := beforeArr[1]
   280  			protocol := beforeArr[2]
   281  			method := beforeArr[3]
   282  			obfs := beforeArr[4]
   283  			password := decodeUrlSafe(urlSafe(beforeArr[5]))
   284  
   285  			query, err := url.ParseQuery(urlSafe(after))
   286  			if err != nil {
   287  				continue
   288  			}
   289  
   290  			remarks := decodeUrlSafe(query.Get("remarks"))
   291  			name := uniqueName(names, remarks)
   292  
   293  			obfsParam := decodeUrlSafe(query.Get("obfsparam"))
   294  			protocolParam := query.Get("protoparam")
   295  
   296  			ssr := make(map[string]any, 20)
   297  
   298  			ssr["name"] = name
   299  			ssr["type"] = scheme
   300  			ssr["server"] = host
   301  			ssr["port"] = port
   302  			ssr["cipher"] = method
   303  			ssr["password"] = password
   304  			ssr["obfs"] = obfs
   305  			ssr["protocol"] = protocol
   306  			ssr["udp"] = true
   307  
   308  			if obfsParam != "" {
   309  				ssr["obfs-param"] = obfsParam
   310  			}
   311  
   312  			if protocolParam != "" {
   313  				ssr["protocol-param"] = protocolParam
   314  			}
   315  
   316  			proxies = append(proxies, ssr)
   317  		case "vless":
   318  			urlVless, err := url.Parse(line)
   319  			if err != nil {
   320  				continue
   321  			}
   322  
   323  			query := urlVless.Query()
   324  
   325  			name := uniqueName(names, urlVless.Fragment)
   326  			vless := make(map[string]any, 20)
   327  
   328  			vless["name"] = name
   329  			vless["type"] = scheme
   330  			vless["server"] = urlVless.Hostname()
   331  			vless["port"] = urlVless.Port()
   332  			vless["uuid"] = urlVless.User.Username()
   333  			vless["udp"] = true
   334  			vless["tls"] = true
   335  			vless["skip-cert-verify"] = false
   336  
   337  			sni := query.Get("sni")
   338  			if sni != "" {
   339  				vless["servername"] = sni
   340  			}
   341  
   342  			network := strings.ToLower(query.Get("type"))
   343  			if network != "" {
   344  				vless["network"] = network
   345  			}
   346  
   347  			if network == "ws" {
   348  				security := strings.ToLower(query.Get("security"))
   349  				vless["tls"] = security == "tls"
   350  
   351  				headers := make(map[string]any)
   352  				wsOpts := make(map[string]any)
   353  
   354  				headers["User-Agent"] = RandUserAgent()
   355  
   356  				wsOpts["path"] = query.Get("path")
   357  				wsOpts["headers"] = headers
   358  
   359  				vless["ws-opts"] = wsOpts
   360  			}
   361  
   362  			proxies = append(proxies, vless)
   363  		}
   364  	}
   365  
   366  	if len(proxies) == 0 {
   367  		return nil, fmt.Errorf("convert v2ray subscribe error: format invalid")
   368  	}
   369  
   370  	return proxies, nil
   371  }
   372  
   373  func urlSafe(data string) string {
   374  	return strings.ReplaceAll(strings.ReplaceAll(data, "+", "-"), "/", "_")
   375  }
   376  
   377  func decodeUrlSafe(data string) string {
   378  	dcBuf, err := base64.URLEncoding.DecodeString(data)
   379  	if err != nil {
   380  		return ""
   381  	}
   382  	return string(dcBuf)
   383  }
   384  
   385  func uniqueName(names map[string]int, name string) string {
   386  	if index, ok := names[name]; ok {
   387  		index++
   388  		names[name] = index
   389  		name = fmt.Sprintf("%s-%02d", name, index)
   390  	} else {
   391  		index = 0
   392  		names[name] = index
   393  	}
   394  	return name
   395  }
   396  
   397  func ConvertsWireGuard(buf []byte) ([]map[string]any, error) {
   398  	var (
   399  		proxies = make([]map[string]any, 0, 50)
   400  		wgMap   map[string]any
   401  		scanner = bufio.NewScanner(bytes.NewReader(buf))
   402  	)
   403  	for scanner.Scan() {
   404  		line := scanner.Text()
   405  		if line == "" {
   406  			continue
   407  		}
   408  
   409  		key, value, ok := strings.Cut(line, "=")
   410  		if !ok {
   411  			line = strings.ToLower(strings.TrimSpace(line))
   412  			if line == "[interface]" {
   413  				if wgMap != nil && wgMap["name"] == nil {
   414  					if pk, ok := wgMap["public-key"].(string); ok && len(pk) >= 8 {
   415  						wgMap["name"] = fmt.Sprintf("wg-%s", pk[:8])
   416  					}
   417  				}
   418  				wgMap = make(map[string]any, 12)
   419  				wgMap["type"] = "wireguard"
   420  				wgMap["dns"] = make([]string, 0, 5)
   421  				wgMap["udp"] = true
   422  				proxies = append(proxies, wgMap)
   423  			}
   424  			continue
   425  		}
   426  
   427  		key = strings.ToLower(strings.TrimSpace(key))
   428  		value = strings.TrimSpace(value)
   429  
   430  		switch key {
   431  		case "name":
   432  			wgMap["name"] = value
   433  		case "endpoint":
   434  			host, port, err := net.SplitHostPort(value)
   435  			if err != nil {
   436  				return nil, err
   437  			}
   438  			p, err := strconv.Atoi(port)
   439  			if err != nil {
   440  				return nil, err
   441  			}
   442  			wgMap["server"] = host
   443  			wgMap["port"] = p
   444  		case "address":
   445  			ips := strings.Split(value, ",")
   446  			for _, v := range ips {
   447  				e, _, _ := strings.Cut(v, "/")
   448  				ip, err := netip.ParseAddr(strings.TrimSpace(e))
   449  				if err != nil {
   450  					return nil, err
   451  				}
   452  				if ip.Is4() {
   453  					wgMap["ip"] = ip.String()
   454  				} else {
   455  					wgMap["ipv6"] = ip.String()
   456  				}
   457  			}
   458  		case "privatekey":
   459  			wgMap["private-key"] = value
   460  		case "publickey":
   461  			wgMap["public-key"] = value
   462  		case "presharedkey":
   463  			wgMap["preshared-key"] = value
   464  		case "dns":
   465  			ips := strings.Split(value, ",")
   466  			for _, v := range ips {
   467  				v = strings.TrimSpace(v)
   468  				ip, err := netip.ParseAddr(v)
   469  				if err != nil {
   470  					return nil, err
   471  				}
   472  				dnses := wgMap["dns"].([]string)
   473  				wgMap["dns"] = append(dnses, ip.String())
   474  			}
   475  		case "mtu":
   476  			v, err := strconv.Atoi(value)
   477  			if err != nil {
   478  				return nil, err
   479  			}
   480  			wgMap["mtu"] = v
   481  		}
   482  	}
   483  
   484  	if len(proxies) == 0 {
   485  		return nil, fmt.Errorf("convert WireGuard error: format invalid")
   486  	}
   487  
   488  	if pk, ok := wgMap["public-key"].(string); ok && wgMap["name"] == nil && len(pk) >= 8 {
   489  		wgMap["name"] = fmt.Sprintf("wg-%s", pk[:8])
   490  	}
   491  
   492  	if err := scanner.Err(); err != nil {
   493  		return nil, err
   494  	}
   495  	return proxies, nil
   496  }