fix(xray): refuse geodata settings xray-core 26.10.29 refuses at startup

xray-core 26.10.29 (#7267) checks a scheduled geodata section when the
core starts: at most 100 assets, every file a .dat, a cron it can parse
and an @every interval of at least a minute. A refusal there stops the
core and every inbound with it, and the panel's own file-name pattern
still let any extension through.

The geodata editor now requires a .dat file name, and the template save
path applies the same checks the core does. The invalid-file hint names
the .dat requirement in all 14 locales.
This commit is contained in:
MHSanaei
2026-11-01 14:55:41 +01:00
parent e789583b63
commit 7982794291
18 changed files with 121 additions and 16 deletions
+2 -2
View File
@@ -19,8 +19,8 @@ interface GeodataSectionProps {
}
const DEFAULT_CRON = '0 4 * * *';
// Xray resolves `file` inside its asset directory; plain file names only.
const FILE_NAME_PATTERN = /^[A-Za-z0-9._-]+$/;
// Xray resolves `file` inside its asset directory; since 26.10.29 a scheduled one must be .dat.
const FILE_NAME_PATTERN = /^[A-Za-z0-9._-]+\.dat$/i;
function fileNameFromUrl(url: string): string {
try {
@@ -76,4 +76,33 @@ describe('GeodataSection', () => {
}
});
});
// xray-core 26.10.29 refuses a scheduled asset whose file is not .dat when it starts.
it('refuses to save a scheduled asset whose file is not .dat', async () => {
const post = vi.spyOn(HttpUtil, 'post').mockResolvedValue(
new Msg(
true,
'',
JSON.stringify({
xraySetting: {
outbounds: [],
geodata: { cron: '0 4 * * *', assets: [CUSTOM_SOURCE] },
},
geodataSources: [],
}),
),
);
const user = userEvent.setup();
render(<GeodataSection active onBusy={vi.fn()} onClose={vi.fn()} />);
const file = await screen.findByDisplayValue(CUSTOM_SOURCE.file);
await user.clear(file);
await user.type(file, 'geosite_custom.mmdb');
await user.click(screen.getByRole('button', { name: 'Save & Restart Xray' }));
expect(await screen.findByText(/File names must be/)).toBeTruthy();
expect(screen.queryByText('Save geodata settings?')).toBeNull();
expect(post).toHaveBeenCalledTimes(1);
});
});