mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-19 00:27:14 +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) {
|
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) {
|
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) {
|
func (s *SettingService) GetSubAnnounce() (string, error) {
|
||||||
@@ -1177,6 +1179,12 @@ func validateSettingsURLs(allSetting *entity.AllSetting) error {
|
|||||||
}
|
}
|
||||||
allSetting.TgBotAPIServer = u
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user