116 lines
2.7 KiB
Go
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
|
|
}
|