Release 3.9.0+3: subscription-URL routing, emoji flags, Remnawave provisioning.
Route credential-less http(s) URLs pasted into the add-link field to subscription import (fixes remaining 'proxy URI missing username'). Extend geoflag with RU country names and city hints; live Remnawave names already carrying emoji flags are kept as-is. Add admin provisioning via configs/remnawave-api.json (GET by-username / POST users, 50 GB MONTH plan) and the «Выдать доступ» UI panel. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2,6 +2,7 @@ package linknorm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"vpnclient/internal/config"
|
||||
@@ -11,6 +12,23 @@ import (
|
||||
"vpnclient/internal/protocols/xray"
|
||||
)
|
||||
|
||||
// LooksLikeSubscriptionURL reports whether raw is a plain http(s) URL without
|
||||
// userinfo — i.e. a subscription URL rather than a naive proxy share link
|
||||
// (which always carries user:pass@). Callers should route such input into the
|
||||
// subscription import flow instead of the proxy-URI parser.
|
||||
func LooksLikeSubscriptionURL(raw string) bool {
|
||||
raw = strings.TrimSpace(raw)
|
||||
lower := strings.ToLower(raw)
|
||||
if !strings.HasPrefix(lower, "http://") && !strings.HasPrefix(lower, "https://") {
|
||||
return false
|
||||
}
|
||||
u, err := url.Parse(raw)
|
||||
if err != nil || u.Host == "" {
|
||||
return false
|
||||
}
|
||||
return u.User == nil
|
||||
}
|
||||
|
||||
// Normalize normalizes a share/proxy URI for the given protocol (or auto-detect).
|
||||
func Normalize(proto config.Protocol, raw string) (normalized string, detected config.Protocol, remark string, err error) {
|
||||
raw = strings.TrimSpace(raw)
|
||||
@@ -42,6 +60,12 @@ func Normalize(proto config.Protocol, raw string) (normalized string, detected c
|
||||
u, rem, err := awg.NormalizeShareLink(raw)
|
||||
return u, config.ProtocolAWG, rem, err
|
||||
}
|
||||
// Safety net: a bare http(s) URL without user:pass@ is a subscription
|
||||
// URL, not a naive proxy link — callers should have routed it to the
|
||||
// subscription import flow (see LooksLikeSubscriptionURL).
|
||||
if LooksLikeSubscriptionURL(raw) {
|
||||
return "", config.ProtocolNaive, "", fmt.Errorf("это ссылка подписки (нет логина и пароля) — вставьте её в поле «URL подписки» или сохраните ещё раз: импорт запустится автоматически")
|
||||
}
|
||||
if xray.Detect(raw) {
|
||||
u, rem, err := xray.NormalizeShareLink(raw)
|
||||
return u, xray.DetectProtocol(raw), rem, err
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package linknorm
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLooksLikeSubscriptionURL(t *testing.T) {
|
||||
cases := []struct {
|
||||
in string
|
||||
want bool
|
||||
}{
|
||||
{"https://api.evilfox.win/ZKszcVcC3xbWb8qj", true},
|
||||
{"http://panel.example.com/api/sub/abc", true},
|
||||
{" https://api.evilfox.win/ZKszcVcC3xbWb8qj ", true},
|
||||
{"https://user:pass@host:443", false}, // naive proxy link
|
||||
{"https://user@host:443", false}, // has userinfo
|
||||
{"quic://user:pass@host", false},
|
||||
{"vless://uuid@host:443", false},
|
||||
{"naive+https://user:pass@host:443", false},
|
||||
{"not a url", false},
|
||||
{"", false},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := LooksLikeSubscriptionURL(c.in); got != c.want {
|
||||
t.Fatalf("%q: got %v want %v", c.in, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Direct Normalize of a credential-less https URL must never surface the raw
|
||||
// "proxy URI missing username" error — it explains this is a subscription URL.
|
||||
func TestNormalizeSubscriptionURLError(t *testing.T) {
|
||||
_, _, _, err := Normalize("", "https://api.evilfox.win/ZKszcVcC3xbWb8qj")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
if strings.Contains(err.Error(), "missing username") {
|
||||
t.Fatalf("raw parser error leaked: %v", err)
|
||||
}
|
||||
if !strings.Contains(err.Error(), "подписк") {
|
||||
t.Fatalf("error should mention subscription: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user