Files
3x-ui/internal/sub/external_remark_test.go
T
sdhfsl d42e2133c7 fix(sub): keep serverDescription literal in external link fragments (#6580)
* fix(panel): accept 2FA codes from adjacent TOTP windows

CheckUser compared only gotp.Now(), so a code submitted at the end of
its 30s window (or with slight client/server clock drift) failed with
'invalid 2fa code', while the immediate retry in the next window
succeeded. Accept current +/-1 window, the standard TOTP skew
tolerance.

Fixes MHSanaei/3x-ui#6535

* fix(panel): share TOTP skew tolerance with VerifyTwoFactorCode

Move the +/-1 window helper to internal/util/totp so both 2FA
acceptance points use it: login (CheckUser) and disable/rebind plus
username/password changes (VerifyTwoFactorCode). Also shrink comments
to the 2-line house rule and anchor the unit test mid-window to avoid
a step-boundary flake.

Addresses review on #6546 (MEDIUM + 2 LOWs).

* fix(sub): keep serverDescription literal in external link fragments

Client external links escaped the whole remark, turning
?serverDescription=<base64> into %3F...%2F... so Happ lost its
subtitle. Split on ?serverDescription= like appendQueryAndFragment
(#6488): escape only the display name, keep a clean base64 tail
literal, fall back to full escaping otherwise.

Fixes MHSanaei/3x-ui#6575

* refactor(sub): share one serverDescription fragment split across link paths

#6488 fixed the split in appendQueryAndFragment and #6575 was the same
bug on the external-link path, which had its own copy. Both now call
escapeLinkFragment with their own escaper, so a later change to the tail
check cannot reach one path and miss the other.

---------

Co-authored-by: sdhfsl <sdhfsl@users.noreply.github.com>
Co-authored-by: MHSanaei <ho3ein.sanaei@gmail.com>
2026-09-26 22:00:09 +02:00

41 lines
1.5 KiB
Go

package sub
import (
"strings"
"testing"
)
// #6575: a trailing ?serverDescription=<base64> must stay literal in the
// fragment so Happ renders its subtitle; only the display name is escaped.
func TestApplyRemarkKeepsServerDescription(t *testing.T) {
link := "vless://00000000-0000-0000-0000-000000000000@example.com:443?type=tcp&security=reality&pbk=XXX&fp=chrome&sni=example.org&sid=00&flow=xtls-rprx-vision&encryption=none"
remark := "🇵🇱 Warsaw ⚡️?serverDescription=0JTQu9GPIExURSAo0LHQtdC70YvQtSDRgdC/0LjRgdC60Lgp"
out := applyRemarkToLink(link, remark)
frag := out[strings.IndexByte(out, '#')+1:]
if !strings.Contains(frag, "?serverDescription=") {
t.Fatalf("serverDescription escaped: %s", out)
}
if strings.Contains(frag, "%3F") || strings.Contains(frag, "%2F") {
t.Fatalf("fragment over-escaped: %s", out)
}
tail := frag[strings.Index(frag, "?serverDescription=")+len("?serverDescription="):]
if strings.ContainsAny(tail, " \r\n\t#&") {
t.Fatalf("tail not clean base64: %q", tail)
}
if !strings.HasPrefix(out, link+"#") {
t.Fatalf("link body altered: %s", out)
}
}
func TestApplyRemarkMalformedServerDescriptionFallsBack(t *testing.T) {
link := "vless://uuid@example.com:443?security=reality#old"
out := applyRemarkToLink(link, "name?serverDescription=not base64!!")
if strings.Contains(out, "?serverDescription=") {
t.Fatalf("malformed tail kept literal: %s", out)
}
if !strings.HasPrefix(out, link[:strings.IndexByte(link, '#')]+"#") {
t.Fatalf("link body altered: %s", out)
}
}