fix(links): bracket ipv6 hosts in share links and qr codes (#5310)

* fix(sub): bracket ipv6 hosts in share links

* fix(frontend): bracket ipv6 hosts in share links
This commit is contained in:
Nikan Zeyaei
2026-06-15 01:08:58 +03:30
committed by GitHub
parent 335470607f
commit 7c737820d1
4 changed files with 135 additions and 13 deletions
+18 -8
View File
@@ -9,6 +9,7 @@ import (
"net"
"net/url"
"slices"
"strconv"
"strings"
"time"
@@ -563,7 +564,7 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
params,
security,
func(dest string, port int) string {
return fmt.Sprintf("vless://%s@%s:%d", uuid, dest, port)
return fmt.Sprintf("vless://%s@%s", uuid, joinHostPort(dest, port))
},
func(ep map[string]any) string {
return s.genRemark(inbound, email, ep["remark"].(string))
@@ -571,7 +572,7 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
)
}
link := fmt.Sprintf("vless://%s@%s:%d", uuid, address, port)
link := fmt.Sprintf("vless://%s@%s", uuid, joinHostPort(address, port))
return buildLinkWithParams(link, params, s.genRemark(inbound, email, ""))
}
@@ -614,7 +615,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
params,
security,
func(dest string, port int) string {
return fmt.Sprintf("trojan://%s@%s:%d", password, dest, port)
return fmt.Sprintf("trojan://%s@%s", password, joinHostPort(dest, port))
},
func(ep map[string]any) string {
return s.genRemark(inbound, email, ep["remark"].(string))
@@ -622,7 +623,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
)
}
link := fmt.Sprintf("trojan://%s@%s:%d", password, address, port)
link := fmt.Sprintf("trojan://%s@%s", password, joinHostPort(address, port))
return buildLinkWithParams(link, params, s.genRemark(inbound, email, ""))
}
@@ -637,6 +638,15 @@ func encodeUserinfo(s string) string {
return strings.ReplaceAll(url.QueryEscape(s), "+", "%20")
}
// joinHostPort wraps an IPv6 host in square brackets the way RFC 3986
// requires for URI authorities, while leaving IPv4 addresses and hostnames
// untouched. It also strips any brackets already present on the input so
// callers don't have to normalize upstream.
func joinHostPort(host string, port int) string {
host = strings.Trim(host, "[]")
return net.JoinHostPort(host, strconv.Itoa(port))
}
func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) string {
if inbound.Protocol != model.Shadowsocks {
return ""
@@ -679,7 +689,7 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
proxyParams,
security,
func(dest string, port int) string {
return fmt.Sprintf("ss://%s@%s:%d", base64.RawURLEncoding.EncodeToString([]byte(encPart)), dest, port)
return fmt.Sprintf("ss://%s@%s", base64.RawURLEncoding.EncodeToString([]byte(encPart)), joinHostPort(dest, port))
},
func(ep map[string]any) string {
return s.genRemark(inbound, email, ep["remark"].(string))
@@ -687,7 +697,7 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
)
}
link := fmt.Sprintf("ss://%s@%s:%d", base64.RawURLEncoding.EncodeToString([]byte(encPart)), address, inbound.Port)
link := fmt.Sprintf("ss://%s@%s", base64.RawURLEncoding.EncodeToString([]byte(encPart)), joinHostPort(address, inbound.Port))
return buildLinkWithParams(link, params, s.genRemark(inbound, email, ""))
}
@@ -792,7 +802,7 @@ func (s *SubService) genHysteriaLink(inbound *model.Inbound, email string) strin
epParams := cloneStringMap(params)
applyExternalProxyHysteriaParams(ep, epParams)
link := fmt.Sprintf("%s://%s@%s:%d", protocol, auth, dest, int(portF))
link := fmt.Sprintf("%s://%s@%s", protocol, auth, joinHostPort(dest, int(portF)))
links = append(links, buildLinkWithParams(link, epParams, s.genRemark(inbound, email, epRemark)))
}
return strings.Join(links, "\n")
@@ -803,7 +813,7 @@ func (s *SubService) genHysteriaLink(inbound *model.Inbound, email string) strin
if hopPorts := hysteriaHopPorts(stream); hopPorts != "" {
params["mport"] = hopPorts
}
link := fmt.Sprintf("%s://%s@%s:%d", protocol, auth, s.resolveInboundAddress(inbound), inbound.Port)
link := fmt.Sprintf("%s://%s@%s", protocol, auth, joinHostPort(s.resolveInboundAddress(inbound), inbound.Port))
return buildLinkWithParams(link, params, s.genRemark(inbound, email, ""))
}
+19
View File
@@ -426,6 +426,25 @@ func TestCloneStringMap_Empty(t *testing.T) {
}
}
func TestJoinHostPort(t *testing.T) {
cases := []struct {
host string
port int
want string
}{
{"example.com", 443, "example.com:443"},
{"1.2.3.4", 443, "1.2.3.4:443"},
{"2001:db8::1", 443, "[2001:db8::1]:443"},
{"[2001:db8::1]", 443, "[2001:db8::1]:443"},
{"2001:db8::1", 8080, "[2001:db8::1]:8080"},
}
for _, c := range cases {
if got := joinHostPort(c.host, c.port); got != c.want {
t.Fatalf("joinHostPort(%q, %d) = %q, want %q", c.host, c.port, got, c.want)
}
}
}
func TestGetHostFromXFH_HostOnly(t *testing.T) {
got, err := getHostFromXFH("example.com")
if err != nil {