mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-02 11:15:03 +03:00
fix(outbound): store subscription refreshes without trusting the row read
The refresh rewrote the whole row it read before a download whose length the provider controls, so an admin's delete, disable or edit made in the meantime was undone; Update had the mirror problem. Both now write only their own columns, and a refresh of a row that was deleted or re-pointed stores nothing. Around the same path: - the preview fetches without creating (and leaking) a scratch row, and Create keeps enabled=false instead of falling back to the column default; - fetch errors name the host but not the subscription URL, which is a credential and ends up in the log and in lastError; - each fetch closes its transport's idle connections; - one document loads at most 2000 links, and the cut is reported; - wireguard outbounds from a subscription always run on the userspace TUN; - editing a subscription schedules an Xray reload like delete and move do.
This commit is contained in:
@@ -565,6 +565,8 @@ func (a *XraySettingController) updateOutboundSub(c *gin.Context) {
|
||||
jsonMsg(c, "Failed to update outbound subscription", err)
|
||||
return
|
||||
}
|
||||
// enabled and prepend decide which outbounds are merged and where.
|
||||
a.XrayService.SetToNeedRestart()
|
||||
jsonObj(c, "", nil)
|
||||
}
|
||||
|
||||
@@ -630,19 +632,7 @@ func (a *XraySettingController) parseOutboundSubURL(c *gin.Context) {
|
||||
allowPrivate := c.PostForm("allowPrivate") == "true"
|
||||
allowInsecure := c.PostForm("allowInsecure") == "true"
|
||||
userAgent := c.PostForm("userAgent")
|
||||
// Use a throw-away service instance; it only needs the settingService for proxy.
|
||||
svc := service.OutboundSubscriptionService{}
|
||||
// We don't have a direct "fetch once" that returns without storing, so we
|
||||
// temporarily create a disabled row, refresh it, then delete. Cleaner would
|
||||
// be to expose a pure ParseURL on the service, but this keeps the surface small.
|
||||
tmp, err := svc.Create("preview", rawURL, "", userAgent, false, 600, allowPrivate, false, allowInsecure)
|
||||
if err != nil {
|
||||
jsonMsg(c, "Failed to preview subscription", err)
|
||||
return
|
||||
}
|
||||
obs, err := svc.Refresh(tmp.Id)
|
||||
// best-effort cleanup
|
||||
_ = svc.Delete(tmp.Id)
|
||||
obs, err := a.OutboundSubscriptionService.Preview(rawURL, userAgent, allowPrivate, allowInsecure)
|
||||
if err != nil {
|
||||
jsonMsg(c, "Failed to fetch/parse subscription", err)
|
||||
return
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -67,3 +68,32 @@ func TestWarpIntervalReportsClockPersistenceFailure(t *testing.T) {
|
||||
t.Fatalf("response omitted clock persistence error: %s", w.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
// enabled and prepend decide which subscription outbounds the core runs, so an edit
|
||||
// must schedule a reload like the sibling handlers do.
|
||||
func TestUpdateOutboundSubSchedulesXrayReload(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
dbtest.InitDB(t, filepath.Join(t.TempDir(), "x-ui.db"))
|
||||
sub := &model.OutboundSubscription{Url: "https://provider.example/sub", TagPrefix: "p-", Enabled: true}
|
||||
if err := database.GetDB().Create(sub).Error; err != nil {
|
||||
t.Fatalf("seed subscription: %v", err)
|
||||
}
|
||||
engine := gin.New()
|
||||
engine.Use(func(c *gin.Context) {
|
||||
c.Set("I18n", func(_ locale.I18nType, key string, _ ...string) string { return key })
|
||||
c.Next()
|
||||
})
|
||||
a := NewXraySettingController(engine.Group("/panel/api"))
|
||||
a.XrayService.IsNeedRestartAndSetFalse()
|
||||
form := url.Values{"url": {"https://1.1.1.1/sub"}, "enabled": {"false"}}
|
||||
req := httptest.NewRequest(http.MethodPost, "/panel/api/xray/outbound-subs/"+strconv.Itoa(sub.Id), strings.NewReader(form.Encode()))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
rec := httptest.NewRecorder()
|
||||
engine.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusOK || !strings.Contains(rec.Body.String(), `"success":true`) {
|
||||
t.Fatalf("update = %d %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
if !a.XrayService.IsNeedRestartAndSetFalse() {
|
||||
t.Fatalf("disabling an outbound subscription did not schedule an Xray reload")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user