Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/MHSanaei/3x-ui.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpwnnex <pwnnex@proton.me>2026-04-21 21:30:02 +0300
committerGitHub <noreply@github.com>2026-04-21 21:30:02 +0300
commit15be803da982ab362f6859c0ec91a582c4b1fea6 (patch)
treea4e716380ede864b97ef98b6dccb05926ffec32f /web/controller/xray_setting.go
parentc79b45e512bd41d24f668a373fdabb922c437877 (diff)
Fix blank Xray Settings page from wrapped xrayTemplateConfig (#4059) (#4069)
`getXraySetting` builds its response as { "xraySetting": <db value>, "inboundTags": ..., "outboundTestUrl": ... } and embeds the raw DB value as the `xraySetting` field without checking whether the stored value already has that exact shape. The frontend pulls the textarea content from `result.xraySetting` and saves it back verbatim. If the DB ever ends up holding the response-shaped wrapper instead of a real xray config (older installs where this happened at least once, users who imported a copy-pasted response into the textarea, a botched migration, etc.), the next save nests another layer, the one after that nests a third, and the Vue-side JSON.parse of the resulting blob silently fails — the Xray Settings page goes blank. Fix both ends of the round-trip: * Add `service.UnwrapXrayTemplateConfig`. It peels off any number of `xraySetting`-keyed layers, leaving a real xray config behind. The check is conservative: if the outer object already contains any top-level xray key (`inbounds`, `outbounds`, `routing`, `api`, `dns`, `log`, `policy`, `stats`), it is returned unchanged, and there is a depth cap to avoid pathological inputs. * `SaveXraySetting` unwraps before validation so a round-tripped wrapper from an already-corrupted page can no longer re-poison the DB on save. * `getXraySetting` unwraps on read and, when it finds a wrapper, rewrites the DB with the corrected value. Existing broken installs heal themselves on the next visit to the page. Includes unit tests for the passthrough, single-wrap, multi-wrap, string-encoded-inner, and false-positive cases. Co-authored-by: pwnnex <eternxles@gmail.com>
Diffstat (limited to 'web/controller/xray_setting.go')
-rw-r--r--web/controller/xray_setting.go17
1 files changed, 17 insertions, 0 deletions
diff --git a/web/controller/xray_setting.go b/web/controller/xray_setting.go
index 0c382fb9..7e4c7966 100644
--- a/web/controller/xray_setting.go
+++ b/web/controller/xray_setting.go
@@ -49,6 +49,23 @@ func (a *XraySettingController) getXraySetting(c *gin.Context) {
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
return
}
+ // Older versions of this handler embedded the raw DB value as
+ // `xraySetting` in the response without checking if the value
+ // already had that wrapper shape. When the frontend saved it
+ // back through the textarea verbatim, the wrapper got persisted
+ // and every subsequent save nested another layer, which is what
+ // eventually produced the blank Xray Settings page in #4059.
+ // Strip any such wrapper here, and heal the DB if we found one so
+ // the next read is O(1) instead of climbing the same pile again.
+ if unwrapped := service.UnwrapXrayTemplateConfig(xraySetting); unwrapped != xraySetting {
+ if saveErr := a.XraySettingService.SaveXraySetting(unwrapped); saveErr == nil {
+ xraySetting = unwrapped
+ } else {
+ // Don't fail the read — just serve the unwrapped value
+ // and leave the DB healing for a later save.
+ xraySetting = unwrapped
+ }
+ }
inboundTags, err := a.InboundService.GetInboundTags()
if err != nil {
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)