Files
navi/internal/protocols/hysteria2/uri.go
T

114 lines
2.8 KiB
Go

package hysteria2
import (
"fmt"
"net/url"
"strings"
)
// NormalizeShareLink accepts hysteria2:// or hy2:// share links (or already-normalized URI).
// Returns a canonical hysteria2:// URI suitable for the official client's `server` field.
func NormalizeShareLink(raw string) (string, string, error) {
raw = strings.TrimSpace(raw)
if raw == "" {
return "", "", fmt.Errorf("empty hysteria2 URI")
}
remark := ""
if i := strings.IndexByte(raw, '#'); i >= 0 {
remark = strings.TrimSpace(raw[i+1:])
if u, err := url.QueryUnescape(remark); err == nil {
remark = u
}
raw = raw[:i]
}
lower := strings.ToLower(raw)
switch {
case strings.HasPrefix(lower, "hy2://"):
raw = "hysteria2://" + raw[len("hy2://"):]
case strings.HasPrefix(lower, "hysteria2://"):
// ok
default:
return "", "", fmt.Errorf("ожидалась ссылка hysteria2:// или hy2://")
}
u, err := url.Parse(raw)
if err != nil {
return "", "", fmt.Errorf("parse hysteria2 URI: %w", err)
}
if u.Host == "" {
return "", "", fmt.Errorf("hysteria2 URI missing host")
}
if u.Scheme != "hysteria2" {
u.Scheme = "hysteria2"
}
return u.String(), remark, nil
}
// Detect returns true if raw looks like a Hysteria 2 share link.
func Detect(raw string) bool {
lower := strings.ToLower(strings.TrimSpace(raw))
return strings.HasPrefix(lower, "hysteria2://") || strings.HasPrefix(lower, "hy2://")
}
// HostPort extracts host and a single port for reachability checks.
func HostPort(shareURI string) (host, port string, err error) {
normalized, _, err := NormalizeShareLink(shareURI)
if err != nil {
return "", "", err
}
u, err := url.Parse(normalized)
if err != nil {
return "", "", err
}
host = u.Hostname()
port = u.Port()
if port == "" {
// Multi-port may be in Hostname() for weird parses; Host may be "ex.com:443,5000-6000"
hostField := u.Host
if h, p, ok := splitMultiPortHost(hostField); ok {
return h, p, nil
}
port = "443"
} else if strings.Contains(port, ",") || strings.Contains(port, "-") {
port = firstPortToken(port)
}
if host == "" {
return "", "", fmt.Errorf("нет хоста")
}
if port == "" {
port = "443"
}
return host, port, nil
}
func splitMultiPortHost(hostField string) (host, port string, ok bool) {
// example.com:443,5000-6000
i := strings.LastIndex(hostField, ":")
if i < 0 {
return "", "", false
}
host = hostField[:i]
port = firstPortToken(hostField[i+1:])
if host == "" || port == "" {
return "", "", false
}
return host, port, true
}
func firstPortToken(ports string) string {
ports = strings.TrimSpace(ports)
if ports == "" {
return ""
}
part := ports
if i := strings.IndexByte(ports, ','); i >= 0 {
part = ports[:i]
}
if i := strings.IndexByte(part, '-'); i >= 0 {
part = part[:i]
}
return strings.TrimSpace(part)
}