Files

116 lines
2.7 KiB
Go

package hysteria2
import (
"fmt"
"net/url"
"strconv"
"strings"
)
// Endpoint is a parsed Hysteria 2 share link plus optional overrides.
type Endpoint struct {
Server string // host:port or multi-port host
Auth string
SNI string
Insecure bool
PinSHA256 string
Obfs string // salamander | gecko
ObfsPassword string
Remark string
RawURI string
}
// ParseEndpoint parses hysteria2:// / hy2:// into discrete fields.
func ParseEndpoint(raw string) (Endpoint, error) {
normalized, remark, err := NormalizeShareLink(raw)
if err != nil {
return Endpoint{}, err
}
u, err := url.Parse(normalized)
if err != nil {
return Endpoint{}, err
}
ep := Endpoint{Remark: remark, RawURI: normalized}
if u.User != nil {
user := u.User.Username()
if pass, ok := u.User.Password(); ok {
ep.Auth = user + ":" + pass
} else {
ep.Auth, _ = url.QueryUnescape(user)
}
}
ep.Server = u.Host
if ep.Server == "" {
return Endpoint{}, fmt.Errorf("hysteria2 URI missing host")
}
q := u.Query()
ep.SNI = first(q, "sni", "peer")
ep.PinSHA256 = first(q, "pinSHA256", "pinsha256")
ep.Obfs = first(q, "obfs")
ep.ObfsPassword = first(q, "obfs-password", "obfs_password")
if v := first(q, "insecure"); v == "1" || strings.EqualFold(v, "true") {
ep.Insecure = true
}
return ep, nil
}
func first(q url.Values, keys ...string) string {
for _, k := range keys {
if v := strings.TrimSpace(q.Get(k)); v != "" {
return v
}
}
return ""
}
// ApplyOverrides merges non-empty profile overrides into the endpoint.
func (ep *Endpoint) ApplyOverrides(sni, obfs, obfsPass, pin string, insecure *bool) {
if sni != "" {
ep.SNI = sni
}
if obfs != "" {
ep.Obfs = obfs
}
if obfsPass != "" {
ep.ObfsPassword = obfsPass
}
if pin != "" {
ep.PinSHA256 = pin
}
if insecure != nil {
ep.Insecure = *insecure
}
}
// BoolPtr helper for optional bool overrides.
func BoolPtr(v bool) *bool { return &v }
// HasTLSExtras reports whether discrete TLS/obfs fields should be written.
func (ep Endpoint) HasTLSExtras() bool {
return ep.SNI != "" || ep.Insecure || ep.PinSHA256 != "" || ep.Obfs != ""
}
// PortHint returns a single port for ping.
func (ep Endpoint) PortHint() string {
_, port, err := HostPort(ep.RawURI)
if err != nil || port == "" {
return "443"
}
return port
}
// ParseBandwidthMbps tries to normalize user input like "100" or "100 mbps".
func ParseBandwidthMbps(raw string) string {
raw = strings.TrimSpace(strings.ToLower(raw))
if raw == "" || raw == "0" || raw == "auto" || raw == "bbr" {
return ""
}
if strings.ContainsAny(raw, "bkmgt") {
return raw
}
if _, err := strconv.Atoi(raw); err == nil {
return raw + " mbps"
}
return raw
}