init commit
This commit is contained in:
177
core/parser.go
Normal file
177
core/parser.go
Normal file
@@ -0,0 +1,177 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ParseLink parses a VPN link and returns a ProxyLink.
|
||||
// Supported protocols: trojan, vless.
|
||||
func ParseLink(link string) (*ProxyLink, error) {
|
||||
link = strings.TrimSpace(link)
|
||||
if link == "" {
|
||||
return nil, fmt.Errorf("link cannot be empty")
|
||||
}
|
||||
|
||||
protocol, err := getProtocol(link)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch protocol {
|
||||
case "trojan", "vless":
|
||||
return parseTrojanOrVless(link, protocol)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported protocol: %s", protocol)
|
||||
}
|
||||
}
|
||||
|
||||
// TryParseLink attempts to parse a VPN link, returning nil on failure.
|
||||
func TryParseLink(link string) *ProxyLink {
|
||||
result, err := ParseLink(link)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func getProtocol(link string) (string, error) {
|
||||
idx := strings.Index(link, "://")
|
||||
if idx < 0 {
|
||||
return "", fmt.Errorf("invalid link format: scheme (protocol://) is missing")
|
||||
}
|
||||
return strings.ToLower(link[:idx]), nil
|
||||
}
|
||||
|
||||
// parseTrojanOrVless parses links of format: protocol://credential@host:port?params#remark
|
||||
func parseTrojanOrVless(link, protocol string) (*ProxyLink, error) {
|
||||
result := &ProxyLink{
|
||||
Protocol: protocol,
|
||||
Security: "none",
|
||||
Fingerprint: "chrome",
|
||||
Transport: "tcp",
|
||||
}
|
||||
|
||||
// Remove scheme
|
||||
remainder := link[len(protocol)+3:]
|
||||
|
||||
// Extract fragment (#remark)
|
||||
if fragmentIdx := strings.LastIndex(remainder, "#"); fragmentIdx >= 0 {
|
||||
decoded, err := url.PathUnescape(remainder[fragmentIdx+1:])
|
||||
if err != nil {
|
||||
result.Remark = remainder[fragmentIdx+1:]
|
||||
} else {
|
||||
result.Remark = decoded
|
||||
}
|
||||
remainder = remainder[:fragmentIdx]
|
||||
}
|
||||
|
||||
// Extract query string (?params)
|
||||
var queryParams url.Values
|
||||
if queryIdx := strings.Index(remainder, "?"); queryIdx >= 0 {
|
||||
var err error
|
||||
queryParams, err = url.ParseQuery(remainder[queryIdx+1:])
|
||||
if err != nil {
|
||||
queryParams = url.Values{}
|
||||
}
|
||||
remainder = remainder[:queryIdx]
|
||||
} else {
|
||||
queryParams = url.Values{}
|
||||
}
|
||||
|
||||
// Extract credential@host:port
|
||||
atIdx := strings.Index(remainder, "@")
|
||||
if atIdx < 0 {
|
||||
return nil, fmt.Errorf("invalid format: missing credential@host:port")
|
||||
}
|
||||
|
||||
decoded, err := url.PathUnescape(remainder[:atIdx])
|
||||
if err != nil {
|
||||
result.Credential = remainder[:atIdx]
|
||||
} else {
|
||||
result.Credential = decoded
|
||||
}
|
||||
|
||||
hostPort := remainder[atIdx+1:]
|
||||
|
||||
// Parse host:port
|
||||
if err := parseHostPort(hostPort, result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Parse query parameters
|
||||
parseQueryParams(queryParams, result)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func parseHostPort(hostPort string, result *ProxyLink) error {
|
||||
var lastColon int
|
||||
|
||||
if strings.HasPrefix(hostPort, "[") {
|
||||
// IPv6: [::1]:443
|
||||
bracketEnd := strings.Index(hostPort, "]")
|
||||
if bracketEnd < 0 {
|
||||
return fmt.Errorf("invalid IPv6 address: missing closing bracket")
|
||||
}
|
||||
result.Address = hostPort[1:bracketEnd]
|
||||
lastColon = strings.Index(hostPort[bracketEnd:], ":")
|
||||
if lastColon >= 0 {
|
||||
lastColon += bracketEnd
|
||||
}
|
||||
} else {
|
||||
lastColon = strings.LastIndex(hostPort, ":")
|
||||
if lastColon < 0 {
|
||||
return fmt.Errorf("invalid format: missing port")
|
||||
}
|
||||
result.Address = hostPort[:lastColon]
|
||||
}
|
||||
|
||||
if lastColon < 0 || lastColon >= len(hostPort)-1 {
|
||||
return fmt.Errorf("invalid format: missing port")
|
||||
}
|
||||
|
||||
portStr := hostPort[lastColon+1:]
|
||||
port, err := strconv.Atoi(portStr)
|
||||
if err != nil || port < 1 || port > 65535 {
|
||||
return fmt.Errorf("invalid port: %s", portStr)
|
||||
}
|
||||
result.Port = port
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseQueryParams(params url.Values, result *ProxyLink) {
|
||||
if v := params.Get("security"); v != "" {
|
||||
result.Security = strings.ToLower(v)
|
||||
}
|
||||
if v := params.Get("sni"); v != "" {
|
||||
result.Sni = v
|
||||
}
|
||||
if v := params.Get("fp"); v != "" {
|
||||
result.Fingerprint = v
|
||||
}
|
||||
if v := params.Get("pbk"); v != "" {
|
||||
result.PublicKey = v
|
||||
}
|
||||
if v := params.Get("sid"); v != "" {
|
||||
result.ShortId = v
|
||||
}
|
||||
if v := params.Get("type"); v != "" {
|
||||
result.Transport = strings.ToLower(v)
|
||||
}
|
||||
if v := params.Get("serviceName"); v != "" {
|
||||
result.ServiceName = v
|
||||
}
|
||||
if v := params.Get("path"); v != "" {
|
||||
result.Path = v
|
||||
}
|
||||
if v := params.Get("host"); v != "" {
|
||||
result.Host = v
|
||||
}
|
||||
if v := params.Get("flow"); v != "" {
|
||||
result.Flow = v
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user