mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-07-06 12:54:20 +00:00
fix(sub): default https:// for scheme-less support and profile URLs
A support URL saved without a scheme (e.g. "t.me/handle") is served verbatim in the subscription Support-Url header and page data, and client apps resolve it relative to the subscription domain — clicking it lands on "https://panel.example/t.me/handle". Same hazard for the profile URL. Default the scheme to https:// when none is present, both when saving the settings and when reading already-stored values, so existing databases are covered without a migration. Deliberate non-http schemes (tg://, mailto:, tel:) pass through untouched, which is why these two fields don't go through SanitizeHTTPURL's http(s)-only validation. Closes #5738
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
package common
|
||||
|
||||
import "strings"
|
||||
|
||||
// EnsureURLScheme prepends https:// to a URL that carries no scheme, so
|
||||
// subscription apps and browsers don't resolve it relative to the panel's own
|
||||
// domain (e.g. "t.me/support" turning into "https://panel.example/t.me/support").
|
||||
// Values with an explicit scheme (https://, tg://, mailto:, tel:) and empty
|
||||
// strings pass through untouched.
|
||||
func EnsureURLScheme(raw string) string {
|
||||
trimmed := strings.TrimSpace(raw)
|
||||
if trimmed == "" {
|
||||
return ""
|
||||
}
|
||||
if strings.Contains(trimmed, "://") ||
|
||||
strings.HasPrefix(trimmed, "mailto:") ||
|
||||
strings.HasPrefix(trimmed, "tel:") {
|
||||
return trimmed
|
||||
}
|
||||
return "https://" + trimmed
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package common
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestEnsureURLScheme(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in string
|
||||
want string
|
||||
}{
|
||||
{"empty", "", ""},
|
||||
{"whitespace only", " ", ""},
|
||||
{"bare telegram handle", "t.me/xui_support", "https://t.me/xui_support"},
|
||||
{"bare domain with path", "example.com/help", "https://example.com/help"},
|
||||
{"already https", "https://t.me/xui_support", "https://t.me/xui_support"},
|
||||
{"already http", "http://example.com", "http://example.com"},
|
||||
{"telegram deep link", "tg://resolve?domain=xui_support", "tg://resolve?domain=xui_support"},
|
||||
{"mailto", "mailto:support@example.com", "mailto:support@example.com"},
|
||||
{"tel", "tel:+1234567890", "tel:+1234567890"},
|
||||
{"trims whitespace", " t.me/xui_support ", "https://t.me/xui_support"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := EnsureURLScheme(tt.in); got != tt.want {
|
||||
t.Errorf("EnsureURLScheme(%q) = %q, want %q", tt.in, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -710,11 +710,13 @@ func (s *SettingService) GetSubTitle() (string, error) {
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubSupportUrl() (string, error) {
|
||||
return s.getString("subSupportUrl")
|
||||
value, err := s.getString("subSupportUrl")
|
||||
return common.EnsureURLScheme(value), err
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubProfileUrl() (string, error) {
|
||||
return s.getString("subProfileUrl")
|
||||
value, err := s.getString("subProfileUrl")
|
||||
return common.EnsureURLScheme(value), err
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubAnnounce() (string, error) {
|
||||
@@ -1177,6 +1179,12 @@ func validateSettingsURLs(allSetting *entity.AllSetting) error {
|
||||
}
|
||||
allSetting.TgBotAPIServer = u
|
||||
}
|
||||
// Support/profile links land in subscription headers and page data, where
|
||||
// client apps resolve a scheme-less value against the panel's own domain.
|
||||
// Non-http schemes (tg://, mailto:) are legitimate here, so only default
|
||||
// the scheme instead of forcing SanitizeHTTPURL's http(s)-only rule.
|
||||
allSetting.SubSupportUrl = common.EnsureURLScheme(allSetting.SubSupportUrl)
|
||||
allSetting.SubProfileUrl = common.EnsureURLScheme(allSetting.SubProfileUrl)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user