mirror of
https://github.com/itdoginfo/podkop.git
synced 2025-12-07 03:56:55 +03:00
refactor: change vless validation logic
This commit is contained in:
100
fe-app-podkop/src/validators/tests/validateVlessUrl.test.js
Normal file
100
fe-app-podkop/src/validators/tests/validateVlessUrl.test.js
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { validateVlessUrl } from '../validateVlessUrl';
|
||||||
|
|
||||||
|
const validUrls = [
|
||||||
|
// TCP
|
||||||
|
[
|
||||||
|
'tcp + none',
|
||||||
|
'vless://94792286-7bbe-4f33-8b36-18d1bbf70723@127.0.0.1:34520?type=tcp&encryption=none&security=none#vless-tcp-none',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'tcp + reality',
|
||||||
|
'vless://e95163dc-905e-480a-afe5-20b146288679@127.0.0.1:16399?type=tcp&encryption=none&security=reality&pbk=tqhSkeDR6jsqC-BYCnZWBrdL33g705ba8tV5-ZboWTM&fp=chrome&sni=google.com&sid=f6&spx=%2F#vless-tcp-reality',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'tcp + tls',
|
||||||
|
'vless://2e9e8288-060e-4da2-8b9f-a1c81826feb7@127.0.0.1:19316?type=tcp&encryption=none&security=tls&fp=chrome&alpn=h2%2Chttp%2F1.1&sni=google.com#vless-tcp-tls',
|
||||||
|
],
|
||||||
|
// mKCP
|
||||||
|
[
|
||||||
|
'mKCP + none',
|
||||||
|
'vless://72e201d7-7841-4a32-b266-4aa3eb776d51@127.0.0.1:17270?type=kcp&encryption=none&headerType=none&seed=AirziWi4ng&security=none#vless-mKCP',
|
||||||
|
],
|
||||||
|
// WebSocket
|
||||||
|
[
|
||||||
|
'ws + none',
|
||||||
|
'vless://d86daef7-565b-4ecd-a9ee-bac847ad38e6@127.0.0.1:12928?type=ws&encryption=none&path=%2Fwspath&host=google.com&security=none#vless-websocket-none',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'ws + tls',
|
||||||
|
'vless://fe0f0941-09a9-4e46-bc69-e00190d7bb9c@127.0.0.1:10156?type=ws&encryption=none&path=%2Fwspath&host=google.com&security=tls&fp=chrome&sni=google.com#vless-websocket-tls',
|
||||||
|
],
|
||||||
|
// gRPC
|
||||||
|
[
|
||||||
|
'grpc + none',
|
||||||
|
'vless://974b39e3-f7bf-42b9-933c-16699c635e77@127.0.0.1:15633?type=grpc&encryption=none&serviceName=TunService&security=none#vless-gRPC-none',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'grpc + reality',
|
||||||
|
'vless://651e7eca-5152-46f1-baf2-d502e0af7b27@127.0.0.1:28535?type=grpc&encryption=none&serviceName=TunService&security=reality&pbk=nhZ7NiKfcqESa5ZeBFfsq9o18W-OWOAHLln9UmuVXSk&fp=chrome&sni=google.com&sid=11cbaeaa&spx=%2F#vless-gRPC-reality',
|
||||||
|
],
|
||||||
|
// HTTPUpgrade
|
||||||
|
[
|
||||||
|
'httpupgrade + none',
|
||||||
|
'vless://2b98f144-847f-42f7-8798-e1a32d27bdc7@127.0.0.1:47154?type=httpupgrade&encryption=none&path=%2Fhttpupgradepath&host=google.com&security=none#vless-httpupgrade-none',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'httpupgrade + tls',
|
||||||
|
'vless://76dbd0ff-1a35-4f0c-a9ba-3c5890b7dea6@127.0.0.1:50639?type=httpupgrade&encryption=none&path=%2Fhttpupgradepath&host=google.com&security=tls&sni=google.com#vless-httpupgrade-tls',
|
||||||
|
],
|
||||||
|
// XHTTP
|
||||||
|
[
|
||||||
|
'xhttp + none',
|
||||||
|
'vless://c2841505-ec32-4b8d-b6dd-3e19d648c321@127.0.0.1:45507?type=xhttp&encryption=none&path=%2Fxhttppath&host=xhttp&mode=auto&security=none#vless-xhttp',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
const invalidUrls = [
|
||||||
|
['No prefix', 'uuid@host:443?type=tcp&security=tls'],
|
||||||
|
['No uuid', 'vless://@127.0.0.1:443?type=tcp&security=tls'],
|
||||||
|
['No host', 'vless://uuid@:443?type=tcp&security=tls'],
|
||||||
|
['No port', 'vless://uuid@127.0.0.1?type=tcp&security=tls'],
|
||||||
|
['Invalid port', 'vless://uuid@127.0.0.1:abc?type=tcp&security=tls'],
|
||||||
|
['Missing type', 'vless://uuid@127.0.0.1:443?security=tls'],
|
||||||
|
['Missing security', 'vless://uuid@127.0.0.1:443?type=tcp'],
|
||||||
|
[
|
||||||
|
'reality without pbk',
|
||||||
|
'vless://uuid@127.0.0.1:443?type=tcp&security=reality&fp=chrome',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'reality without fp',
|
||||||
|
'vless://uuid@127.0.0.1:443?type=tcp&security=reality&pbk=abc',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'tcp + reality + unexpected spaces',
|
||||||
|
'vless://e95163dc-905e-480a-afe5-20b146288679@127.0.0.1:16399?type=tcp&encryption=none&security=reality&pbk=tqhSkeDR6jsqC-BYCnZWBrdL33g705ba8tV5-ZboWTM&fp=chrome&sni= google.com&sid=f6&spx=%2F#vless-tcp-reality',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('validateVlessUrl', () => {
|
||||||
|
describe.each(validUrls)('Valid URL: %s', (_desc, url) => {
|
||||||
|
it(`returns valid=true for "${url}"`, () => {
|
||||||
|
const res = validateVlessUrl(url);
|
||||||
|
expect(res.valid).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.each(invalidUrls)('Invalid URL: %s', (_desc, url) => {
|
||||||
|
it(`returns valid=false for "${url}"`, () => {
|
||||||
|
const res = validateVlessUrl(url);
|
||||||
|
expect(res.valid).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('detects invalid port range', () => {
|
||||||
|
const res = validateVlessUrl(
|
||||||
|
'vless://uuid@127.0.0.1:99999?type=tcp&security=tls',
|
||||||
|
);
|
||||||
|
expect(res.valid).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -10,6 +10,13 @@ export function validateShadowsocksUrl(url: string): ValidationResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (!url || /\s/.test(url)) {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
message: 'Invalid Shadowsocks URL: must not contain spaces',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const mainPart = url.includes('?') ? url.split('?')[0] : url.split('#')[0];
|
const mainPart = url.includes('?') ? url.split('?')[0] : url.split('#')[0];
|
||||||
|
|
||||||
const encryptedPart = mainPart.split('/')[2]?.split('@')[0];
|
const encryptedPart = mainPart.split('/')[2]?.split('@')[0];
|
||||||
|
|||||||
@@ -9,6 +9,13 @@ export function validateTrojanUrl(url: string): ValidationResult {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!url || /\s/.test(url)) {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
message: 'Invalid Trojan URL: must not contain spaces',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const parsedUrl = new URL(url);
|
const parsedUrl = new URL(url);
|
||||||
|
|
||||||
|
|||||||
@@ -2,62 +2,68 @@ import { ValidationResult } from './types';
|
|||||||
|
|
||||||
// TODO refactor current validation and add tests
|
// TODO refactor current validation and add tests
|
||||||
export function validateVlessUrl(url: string): ValidationResult {
|
export function validateVlessUrl(url: string): ValidationResult {
|
||||||
if (!url.startsWith('vless://')) {
|
|
||||||
return {
|
|
||||||
valid: false,
|
|
||||||
message: 'Invalid VLESS URL: must start with vless://',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const uuid = url.split('/')[2]?.split('@')[0];
|
const parsedUrl = new URL(url);
|
||||||
|
|
||||||
if (!uuid) {
|
if (!url || /\s/.test(url)) {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
message: 'Invalid VLESS URL: must not contain spaces',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsedUrl.protocol !== 'vless:') {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
message: 'Invalid VLESS URL: must start with vless://',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parsedUrl.username) {
|
||||||
return { valid: false, message: 'Invalid VLESS URL: missing UUID' };
|
return { valid: false, message: 'Invalid VLESS URL: missing UUID' };
|
||||||
}
|
}
|
||||||
|
|
||||||
const serverPart = url.split('@')[1];
|
if (!parsedUrl.hostname) {
|
||||||
|
|
||||||
if (!serverPart) {
|
|
||||||
return {
|
|
||||||
valid: false,
|
|
||||||
message: 'Invalid VLESS URL: missing server address',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const [server, portAndRest] = serverPart.split(':');
|
|
||||||
|
|
||||||
if (!server) {
|
|
||||||
return { valid: false, message: 'Invalid VLESS URL: missing server' };
|
return { valid: false, message: 'Invalid VLESS URL: missing server' };
|
||||||
}
|
}
|
||||||
|
|
||||||
const port = portAndRest ? portAndRest.split(/[/?#]/)[0] : null;
|
if (!parsedUrl.port) {
|
||||||
|
|
||||||
if (!port) {
|
|
||||||
return { valid: false, message: 'Invalid VLESS URL: missing port' };
|
return { valid: false, message: 'Invalid VLESS URL: missing port' };
|
||||||
}
|
}
|
||||||
|
|
||||||
const portNum = parseInt(port, 10);
|
if (
|
||||||
|
isNaN(+parsedUrl.port) ||
|
||||||
if (isNaN(portNum) || portNum < 1 || portNum > 65535) {
|
+parsedUrl.port < 1 ||
|
||||||
|
+parsedUrl.port > 65535
|
||||||
|
) {
|
||||||
return {
|
return {
|
||||||
valid: false,
|
valid: false,
|
||||||
message: 'Invalid port number. Must be between 1 and 65535',
|
message:
|
||||||
|
'Invalid VLESS URL: invalid port number. Must be between 1 and 65535',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryString = url.split('?')[1];
|
if (!parsedUrl.search) {
|
||||||
|
|
||||||
if (!queryString) {
|
|
||||||
return {
|
return {
|
||||||
valid: false,
|
valid: false,
|
||||||
message: 'Invalid VLESS URL: missing query parameters',
|
message: 'Invalid VLESS URL: missing query parameters',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const params = new URLSearchParams(queryString.split('#')[0]);
|
const params = new URLSearchParams(parsedUrl.search);
|
||||||
|
|
||||||
const type = params.get('type');
|
const type = params.get('type');
|
||||||
const validTypes = ['tcp', 'raw', 'udp', 'grpc', 'http', 'ws'];
|
const validTypes = [
|
||||||
|
'tcp',
|
||||||
|
'raw',
|
||||||
|
'udp',
|
||||||
|
'grpc',
|
||||||
|
'http',
|
||||||
|
'httpupgrade',
|
||||||
|
'xhttp',
|
||||||
|
'ws',
|
||||||
|
'kcp',
|
||||||
|
];
|
||||||
|
|
||||||
if (!type || !validTypes.includes(type)) {
|
if (!type || !validTypes.includes(type)) {
|
||||||
return {
|
return {
|
||||||
@@ -94,9 +100,9 @@ export function validateVlessUrl(url: string): ValidationResult {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return { valid: true, message: 'Valid' };
|
||||||
} catch (_e) {
|
} catch (_e) {
|
||||||
return { valid: false, message: 'Invalid VLESS URL: parsing failed' };
|
return { valid: false, message: 'Invalid VLESS URL: parsing failed' };
|
||||||
}
|
}
|
||||||
|
|
||||||
return { valid: true, message: 'Valid' };
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ function createConfigSection(section) {
|
|||||||
let o = s.tab('basic', _('Basic Settings'));
|
let o = s.tab('basic', _('Basic Settings'));
|
||||||
|
|
||||||
o = s.taboption(
|
o = s.taboption(
|
||||||
'basic',
|
'basic',
|
||||||
form.ListValue,
|
form.ListValue,
|
||||||
'mode',
|
'mode',
|
||||||
_('Connection Type'),
|
_('Connection Type'),
|
||||||
_('Select between VPN and Proxy connection methods for traffic routing'),
|
_('Select between VPN and Proxy connection methods for traffic routing'),
|
||||||
);
|
);
|
||||||
o.value('proxy', 'Proxy');
|
o.value('proxy', 'Proxy');
|
||||||
o.value('vpn', 'VPN');
|
o.value('vpn', 'VPN');
|
||||||
@@ -24,11 +24,11 @@ function createConfigSection(section) {
|
|||||||
o.ucisection = s.section;
|
o.ucisection = s.section;
|
||||||
|
|
||||||
o = s.taboption(
|
o = s.taboption(
|
||||||
'basic',
|
'basic',
|
||||||
form.ListValue,
|
form.ListValue,
|
||||||
'proxy_config_type',
|
'proxy_config_type',
|
||||||
_('Configuration Type'),
|
_('Configuration Type'),
|
||||||
_('Select how to configure the proxy'),
|
_('Select how to configure the proxy'),
|
||||||
);
|
);
|
||||||
o.value('url', _('Connection URL'));
|
o.value('url', _('Connection URL'));
|
||||||
o.value('outbound', _('Outbound Config'));
|
o.value('outbound', _('Outbound Config'));
|
||||||
@@ -38,11 +38,11 @@ function createConfigSection(section) {
|
|||||||
o.ucisection = s.section;
|
o.ucisection = s.section;
|
||||||
|
|
||||||
o = s.taboption(
|
o = s.taboption(
|
||||||
'basic',
|
'basic',
|
||||||
form.TextValue,
|
form.TextValue,
|
||||||
'proxy_string',
|
'proxy_string',
|
||||||
_('Proxy Configuration URL'),
|
_('Proxy Configuration URL'),
|
||||||
'',
|
'',
|
||||||
);
|
);
|
||||||
o.depends('proxy_config_type', 'url');
|
o.depends('proxy_config_type', 'url');
|
||||||
o.rows = 5;
|
o.rows = 5;
|
||||||
@@ -52,7 +52,7 @@ function createConfigSection(section) {
|
|||||||
o.ucisection = s.section;
|
o.ucisection = s.section;
|
||||||
o.sectionDescriptions = new Map();
|
o.sectionDescriptions = new Map();
|
||||||
o.placeholder =
|
o.placeholder =
|
||||||
'vless://uuid@server:port?type=tcp&security=tls#main\n// backup ss://method:pass@server:port\n// backup2 vless://uuid@server:port?type=grpc&security=reality#alt\n// backup3 trojan://04agAQapcl@127.0.0.1:33641?type=tcp&security=none#trojan-tcp-none';
|
'vless://uuid@server:port?type=tcp&security=tls#main\n// backup ss://method:pass@server:port\n// backup2 vless://uuid@server:port?type=grpc&security=reality#alt\n// backup3 trojan://04agAQapcl@127.0.0.1:33641?type=tcp&security=none#trojan-tcp-none';
|
||||||
|
|
||||||
o.renderWidget = function (section_id, option_index, cfgvalue) {
|
o.renderWidget = function (section_id, option_index, cfgvalue) {
|
||||||
const original = form.TextValue.prototype.renderWidget.apply(this, [
|
const original = form.TextValue.prototype.renderWidget.apply(this, [
|
||||||
@@ -66,9 +66,9 @@ function createConfigSection(section) {
|
|||||||
if (cfgvalue) {
|
if (cfgvalue) {
|
||||||
try {
|
try {
|
||||||
const activeConfig = cfgvalue
|
const activeConfig = cfgvalue
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.map((line) => line.trim())
|
.map((line) => line.trim())
|
||||||
.find((line) => line && !line.startsWith('//'));
|
.find((line) => line && !line.startsWith('//'));
|
||||||
|
|
||||||
if (activeConfig) {
|
if (activeConfig) {
|
||||||
if (activeConfig.includes('#')) {
|
if (activeConfig.includes('#')) {
|
||||||
@@ -76,24 +76,24 @@ function createConfigSection(section) {
|
|||||||
if (label && label.trim()) {
|
if (label && label.trim()) {
|
||||||
const decodedLabel = decodeURIComponent(label);
|
const decodedLabel = decodeURIComponent(label);
|
||||||
const descDiv = E(
|
const descDiv = E(
|
||||||
'div',
|
'div',
|
||||||
{ class: 'cbi-value-description' },
|
{ class: 'cbi-value-description' },
|
||||||
_('Current config: ') + decodedLabel,
|
_('Current config: ') + decodedLabel,
|
||||||
);
|
);
|
||||||
container.appendChild(descDiv);
|
container.appendChild(descDiv);
|
||||||
} else {
|
} else {
|
||||||
const descDiv = E(
|
const descDiv = E(
|
||||||
'div',
|
'div',
|
||||||
{ class: 'cbi-value-description' },
|
{ class: 'cbi-value-description' },
|
||||||
_('Config without description'),
|
_('Config without description'),
|
||||||
);
|
);
|
||||||
container.appendChild(descDiv);
|
container.appendChild(descDiv);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const descDiv = E(
|
const descDiv = E(
|
||||||
'div',
|
'div',
|
||||||
{ class: 'cbi-value-description' },
|
{ class: 'cbi-value-description' },
|
||||||
_('Config without description'),
|
_('Config without description'),
|
||||||
);
|
);
|
||||||
container.appendChild(descDiv);
|
container.appendChild(descDiv);
|
||||||
}
|
}
|
||||||
@@ -101,19 +101,19 @@ function createConfigSection(section) {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error parsing config label:', e);
|
console.error('Error parsing config label:', e);
|
||||||
const descDiv = E(
|
const descDiv = E(
|
||||||
'div',
|
'div',
|
||||||
{ class: 'cbi-value-description' },
|
{ class: 'cbi-value-description' },
|
||||||
_('Config without description'),
|
_('Config without description'),
|
||||||
);
|
);
|
||||||
container.appendChild(descDiv);
|
container.appendChild(descDiv);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const defaultDesc = E(
|
const defaultDesc = E(
|
||||||
'div',
|
'div',
|
||||||
{ class: 'cbi-value-description' },
|
{ class: 'cbi-value-description' },
|
||||||
_(
|
_(
|
||||||
'Enter connection string starting with vless:// or ss:// for proxy configuration. Add comments with // for backup configs',
|
'Enter connection string starting with vless:// or ss:// for proxy configuration. Add comments with // for backup configs',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
container.appendChild(defaultDesc);
|
container.appendChild(defaultDesc);
|
||||||
}
|
}
|
||||||
@@ -128,18 +128,25 @@ function createConfigSection(section) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const activeConfig = value
|
const activeConfigs = value
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.map((line) => line.trim())
|
.map((line) => line.trim())
|
||||||
.find((line) => line && !line.startsWith('//'));
|
.filter((line) => !line.startsWith('//'))
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
if (!activeConfig) {
|
if (!activeConfigs.length) {
|
||||||
return _(
|
return _(
|
||||||
'No active configuration found. At least one non-commented line is required.',
|
'No active configuration found. One configuration is required.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const validation = main.validateProxyUrl(activeConfig);
|
if (activeConfigs.length > 1) {
|
||||||
|
return _(
|
||||||
|
'Multiply active configurations found. Please leave one configuration.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const validation = main.validateProxyUrl(activeConfigs[0]);
|
||||||
|
|
||||||
if (validation.valid) {
|
if (validation.valid) {
|
||||||
return true;
|
return true;
|
||||||
@@ -152,11 +159,11 @@ function createConfigSection(section) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
o = s.taboption(
|
o = s.taboption(
|
||||||
'basic',
|
'basic',
|
||||||
form.TextValue,
|
form.TextValue,
|
||||||
'outbound_json',
|
'outbound_json',
|
||||||
_('Outbound Configuration'),
|
_('Outbound Configuration'),
|
||||||
_('Enter complete outbound configuration in JSON format'),
|
_('Enter complete outbound configuration in JSON format'),
|
||||||
);
|
);
|
||||||
o.depends('proxy_config_type', 'outbound');
|
o.depends('proxy_config_type', 'outbound');
|
||||||
o.rows = 10;
|
o.rows = 10;
|
||||||
@@ -177,10 +184,10 @@ function createConfigSection(section) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
o = s.taboption(
|
o = s.taboption(
|
||||||
'basic',
|
'basic',
|
||||||
form.DynamicList,
|
form.DynamicList,
|
||||||
'urltest_proxy_links',
|
'urltest_proxy_links',
|
||||||
_('URLTest Proxy Links'),
|
_('URLTest Proxy Links'),
|
||||||
);
|
);
|
||||||
o.depends('proxy_config_type', 'urltest');
|
o.depends('proxy_config_type', 'urltest');
|
||||||
o.placeholder = 'vless://, ss://, trojan:// links';
|
o.placeholder = 'vless://, ss://, trojan:// links';
|
||||||
@@ -201,11 +208,11 @@ function createConfigSection(section) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
o = s.taboption(
|
o = s.taboption(
|
||||||
'basic',
|
'basic',
|
||||||
form.Flag,
|
form.Flag,
|
||||||
'ss_uot',
|
'ss_uot',
|
||||||
_('Shadowsocks UDP over TCP'),
|
_('Shadowsocks UDP over TCP'),
|
||||||
_('Apply for SS2022'),
|
_('Apply for SS2022'),
|
||||||
);
|
);
|
||||||
o.default = '0';
|
o.default = '0';
|
||||||
o.depends('mode', 'proxy');
|
o.depends('mode', 'proxy');
|
||||||
@@ -213,11 +220,11 @@ function createConfigSection(section) {
|
|||||||
o.ucisection = s.section;
|
o.ucisection = s.section;
|
||||||
|
|
||||||
o = s.taboption(
|
o = s.taboption(
|
||||||
'basic',
|
'basic',
|
||||||
widgets.DeviceSelect,
|
widgets.DeviceSelect,
|
||||||
'interface',
|
'interface',
|
||||||
_('Network Interface'),
|
_('Network Interface'),
|
||||||
_('Select network interface for VPN connection'),
|
_('Select network interface for VPN connection'),
|
||||||
);
|
);
|
||||||
o.depends('mode', 'vpn');
|
o.depends('mode', 'vpn');
|
||||||
o.ucisection = s.section;
|
o.ucisection = s.section;
|
||||||
@@ -255,17 +262,17 @@ function createConfigSection(section) {
|
|||||||
|
|
||||||
// Reject wireless-related devices
|
// Reject wireless-related devices
|
||||||
const isWireless =
|
const isWireless =
|
||||||
type === 'wifi' || type === 'wireless' || type.includes('wlan');
|
type === 'wifi' || type === 'wireless' || type.includes('wlan');
|
||||||
|
|
||||||
return !isWireless;
|
return !isWireless;
|
||||||
};
|
};
|
||||||
|
|
||||||
o = s.taboption(
|
o = s.taboption(
|
||||||
'basic',
|
'basic',
|
||||||
form.Flag,
|
form.Flag,
|
||||||
'domain_resolver_enabled',
|
'domain_resolver_enabled',
|
||||||
_('Domain Resolver'),
|
_('Domain Resolver'),
|
||||||
_('Enable built-in DNS resolver for domains handled by this section'),
|
_('Enable built-in DNS resolver for domains handled by this section'),
|
||||||
);
|
);
|
||||||
o.default = '0';
|
o.default = '0';
|
||||||
o.rmempty = false;
|
o.rmempty = false;
|
||||||
@@ -273,11 +280,11 @@ function createConfigSection(section) {
|
|||||||
o.ucisection = s.section;
|
o.ucisection = s.section;
|
||||||
|
|
||||||
o = s.taboption(
|
o = s.taboption(
|
||||||
'basic',
|
'basic',
|
||||||
form.ListValue,
|
form.ListValue,
|
||||||
'domain_resolver_dns_type',
|
'domain_resolver_dns_type',
|
||||||
_('DNS Protocol Type'),
|
_('DNS Protocol Type'),
|
||||||
_('Select the DNS protocol type for the domain resolver'),
|
_('Select the DNS protocol type for the domain resolver'),
|
||||||
);
|
);
|
||||||
o.value('doh', _('DNS over HTTPS (DoH)'));
|
o.value('doh', _('DNS over HTTPS (DoH)'));
|
||||||
o.value('dot', _('DNS over TLS (DoT)'));
|
o.value('dot', _('DNS over TLS (DoT)'));
|
||||||
@@ -288,11 +295,11 @@ function createConfigSection(section) {
|
|||||||
o.ucisection = s.section;
|
o.ucisection = s.section;
|
||||||
|
|
||||||
o = s.taboption(
|
o = s.taboption(
|
||||||
'basic',
|
'basic',
|
||||||
form.Value,
|
form.Value,
|
||||||
'domain_resolver_dns_server',
|
'domain_resolver_dns_server',
|
||||||
_('DNS Server'),
|
_('DNS Server'),
|
||||||
_('Select or enter DNS server address'),
|
_('Select or enter DNS server address'),
|
||||||
);
|
);
|
||||||
Object.entries(main.DNS_SERVER_OPTIONS).forEach(([key, label]) => {
|
Object.entries(main.DNS_SERVER_OPTIONS).forEach(([key, label]) => {
|
||||||
o.value(key, _(label));
|
o.value(key, _(label));
|
||||||
@@ -312,21 +319,21 @@ function createConfigSection(section) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
o = s.taboption(
|
o = s.taboption(
|
||||||
'basic',
|
'basic',
|
||||||
form.Flag,
|
form.Flag,
|
||||||
'community_lists_enabled',
|
'community_lists_enabled',
|
||||||
_('Community Lists'),
|
_('Community Lists'),
|
||||||
);
|
);
|
||||||
o.default = '0';
|
o.default = '0';
|
||||||
o.rmempty = false;
|
o.rmempty = false;
|
||||||
o.ucisection = s.section;
|
o.ucisection = s.section;
|
||||||
|
|
||||||
o = s.taboption(
|
o = s.taboption(
|
||||||
'basic',
|
'basic',
|
||||||
form.DynamicList,
|
form.DynamicList,
|
||||||
'community_lists',
|
'community_lists',
|
||||||
_('Service List'),
|
_('Service List'),
|
||||||
_('Select predefined service for routing') +
|
_('Select predefined service for routing') +
|
||||||
' <a href="https://github.com/itdoginfo/allow-domains" target="_blank">github.com/itdoginfo/allow-domains</a>',
|
' <a href="https://github.com/itdoginfo/allow-domains" target="_blank">github.com/itdoginfo/allow-domains</a>',
|
||||||
);
|
);
|
||||||
o.placeholder = 'Service list';
|
o.placeholder = 'Service list';
|
||||||
@@ -350,50 +357,50 @@ function createConfigSection(section) {
|
|||||||
let notifications = [];
|
let notifications = [];
|
||||||
|
|
||||||
const selectedRegionalOptions = main.REGIONAL_OPTIONS.filter((opt) =>
|
const selectedRegionalOptions = main.REGIONAL_OPTIONS.filter((opt) =>
|
||||||
newValues.includes(opt),
|
newValues.includes(opt),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (selectedRegionalOptions.length > 1) {
|
if (selectedRegionalOptions.length > 1) {
|
||||||
const lastSelected =
|
const lastSelected =
|
||||||
selectedRegionalOptions[selectedRegionalOptions.length - 1];
|
selectedRegionalOptions[selectedRegionalOptions.length - 1];
|
||||||
const removedRegions = selectedRegionalOptions.slice(0, -1);
|
const removedRegions = selectedRegionalOptions.slice(0, -1);
|
||||||
newValues = newValues.filter(
|
newValues = newValues.filter(
|
||||||
(v) => v === lastSelected || !main.REGIONAL_OPTIONS.includes(v),
|
(v) => v === lastSelected || !main.REGIONAL_OPTIONS.includes(v),
|
||||||
);
|
);
|
||||||
notifications.push(
|
notifications.push(
|
||||||
E('p', { class: 'alert-message warning' }, [
|
E('p', { class: 'alert-message warning' }, [
|
||||||
E('strong', {}, _('Regional options cannot be used together')),
|
E('strong', {}, _('Regional options cannot be used together')),
|
||||||
E('br'),
|
E('br'),
|
||||||
_(
|
_(
|
||||||
'Warning: %s cannot be used together with %s. Previous selections have been removed.',
|
'Warning: %s cannot be used together with %s. Previous selections have been removed.',
|
||||||
).format(removedRegions.join(', '), lastSelected),
|
).format(removedRegions.join(', '), lastSelected),
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newValues.includes('russia_inside')) {
|
if (newValues.includes('russia_inside')) {
|
||||||
const removedServices = newValues.filter(
|
const removedServices = newValues.filter(
|
||||||
(v) => !main.ALLOWED_WITH_RUSSIA_INSIDE.includes(v),
|
(v) => !main.ALLOWED_WITH_RUSSIA_INSIDE.includes(v),
|
||||||
);
|
);
|
||||||
if (removedServices.length > 0) {
|
if (removedServices.length > 0) {
|
||||||
newValues = newValues.filter((v) =>
|
newValues = newValues.filter((v) =>
|
||||||
main.ALLOWED_WITH_RUSSIA_INSIDE.includes(v),
|
main.ALLOWED_WITH_RUSSIA_INSIDE.includes(v),
|
||||||
);
|
);
|
||||||
notifications.push(
|
notifications.push(
|
||||||
E('p', { class: 'alert-message warning' }, [
|
E('p', { class: 'alert-message warning' }, [
|
||||||
E('strong', {}, _('Russia inside restrictions')),
|
E('strong', {}, _('Russia inside restrictions')),
|
||||||
E('br'),
|
E('br'),
|
||||||
_(
|
_(
|
||||||
'Warning: Russia inside can only be used with %s. %s already in Russia inside and have been removed from selection.',
|
'Warning: Russia inside can only be used with %s. %s already in Russia inside and have been removed from selection.',
|
||||||
).format(
|
).format(
|
||||||
main.ALLOWED_WITH_RUSSIA_INSIDE.map(
|
main.ALLOWED_WITH_RUSSIA_INSIDE.map(
|
||||||
(key) => main.DOMAIN_LIST_OPTIONS[key],
|
(key) => main.DOMAIN_LIST_OPTIONS[key],
|
||||||
)
|
)
|
||||||
.filter((label) => label !== 'Russia inside')
|
.filter((label) => label !== 'Russia inside')
|
||||||
.join(', '),
|
.join(', '),
|
||||||
removedServices.join(', '),
|
removedServices.join(', '),
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -403,7 +410,7 @@ function createConfigSection(section) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
notifications.forEach((notification) =>
|
notifications.forEach((notification) =>
|
||||||
ui.addNotification(null, notification),
|
ui.addNotification(null, notification),
|
||||||
);
|
);
|
||||||
lastValues = newValues;
|
lastValues = newValues;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -414,11 +421,11 @@ function createConfigSection(section) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
o = s.taboption(
|
o = s.taboption(
|
||||||
'basic',
|
'basic',
|
||||||
form.ListValue,
|
form.ListValue,
|
||||||
'user_domain_list_type',
|
'user_domain_list_type',
|
||||||
_('User Domain List Type'),
|
_('User Domain List Type'),
|
||||||
_('Select how to add your custom domains'),
|
_('Select how to add your custom domains'),
|
||||||
);
|
);
|
||||||
o.value('disabled', _('Disabled'));
|
o.value('disabled', _('Disabled'));
|
||||||
o.value('dynamic', _('Dynamic List'));
|
o.value('dynamic', _('Dynamic List'));
|
||||||
@@ -428,13 +435,13 @@ function createConfigSection(section) {
|
|||||||
o.ucisection = s.section;
|
o.ucisection = s.section;
|
||||||
|
|
||||||
o = s.taboption(
|
o = s.taboption(
|
||||||
'basic',
|
'basic',
|
||||||
form.DynamicList,
|
form.DynamicList,
|
||||||
'user_domains',
|
'user_domains',
|
||||||
_('User Domains'),
|
_('User Domains'),
|
||||||
_(
|
_(
|
||||||
'Enter domain names without protocols (example: sub.example.com or example.com)',
|
'Enter domain names without protocols (example: sub.example.com or example.com)',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
o.placeholder = 'Domains list';
|
o.placeholder = 'Domains list';
|
||||||
o.depends('user_domain_list_type', 'dynamic');
|
o.depends('user_domain_list_type', 'dynamic');
|
||||||
@@ -456,16 +463,16 @@ function createConfigSection(section) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
o = s.taboption(
|
o = s.taboption(
|
||||||
'basic',
|
'basic',
|
||||||
form.TextValue,
|
form.TextValue,
|
||||||
'user_domains_text',
|
'user_domains_text',
|
||||||
_('User Domains List'),
|
_('User Domains List'),
|
||||||
_(
|
_(
|
||||||
'Enter domain names separated by comma, space or newline. You can add comments after //',
|
'Enter domain names separated by comma, space or newline. You can add comments after //',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
o.placeholder =
|
o.placeholder =
|
||||||
'example.com, sub.example.com\n// Social networks\ndomain.com test.com // personal domains';
|
'example.com, sub.example.com\n// Social networks\ndomain.com test.com // personal domains';
|
||||||
o.depends('user_domain_list_type', 'text');
|
o.depends('user_domain_list_type', 'text');
|
||||||
o.rows = 8;
|
o.rows = 8;
|
||||||
o.rmempty = false;
|
o.rmempty = false;
|
||||||
@@ -480,7 +487,7 @@ function createConfigSection(section) {
|
|||||||
|
|
||||||
if (!domains.length) {
|
if (!domains.length) {
|
||||||
return _(
|
return _(
|
||||||
'At least one valid domain must be specified. Comments-only content is not allowed.',
|
'At least one valid domain must be specified. Comments-only content is not allowed.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -488,8 +495,8 @@ function createConfigSection(section) {
|
|||||||
|
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
const errors = results
|
const errors = results
|
||||||
.filter((validation) => !validation.valid) // Leave only failed validations
|
.filter((validation) => !validation.valid) // Leave only failed validations
|
||||||
.map((validation) => _(`${validation.value}: ${validation.message}`)); // Collect validation errors
|
.map((validation) => _(`${validation.value}: ${validation.message}`)); // Collect validation errors
|
||||||
|
|
||||||
return [_('Validation errors:'), ...errors].join('\n');
|
return [_('Validation errors:'), ...errors].join('\n');
|
||||||
}
|
}
|
||||||
@@ -498,22 +505,22 @@ function createConfigSection(section) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
o = s.taboption(
|
o = s.taboption(
|
||||||
'basic',
|
'basic',
|
||||||
form.Flag,
|
form.Flag,
|
||||||
'local_domain_lists_enabled',
|
'local_domain_lists_enabled',
|
||||||
_('Local Domain Lists'),
|
_('Local Domain Lists'),
|
||||||
_('Use the list from the router filesystem'),
|
_('Use the list from the router filesystem'),
|
||||||
);
|
);
|
||||||
o.default = '0';
|
o.default = '0';
|
||||||
o.rmempty = false;
|
o.rmempty = false;
|
||||||
o.ucisection = s.section;
|
o.ucisection = s.section;
|
||||||
|
|
||||||
o = s.taboption(
|
o = s.taboption(
|
||||||
'basic',
|
'basic',
|
||||||
form.DynamicList,
|
form.DynamicList,
|
||||||
'local_domain_lists',
|
'local_domain_lists',
|
||||||
_('Local Domain List Paths'),
|
_('Local Domain List Paths'),
|
||||||
_('Enter the list file path'),
|
_('Enter the list file path'),
|
||||||
);
|
);
|
||||||
o.placeholder = '/path/file.lst';
|
o.placeholder = '/path/file.lst';
|
||||||
o.depends('local_domain_lists_enabled', '1');
|
o.depends('local_domain_lists_enabled', '1');
|
||||||
@@ -535,22 +542,22 @@ function createConfigSection(section) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
o = s.taboption(
|
o = s.taboption(
|
||||||
'basic',
|
'basic',
|
||||||
form.Flag,
|
form.Flag,
|
||||||
'remote_domain_lists_enabled',
|
'remote_domain_lists_enabled',
|
||||||
_('Remote Domain Lists'),
|
_('Remote Domain Lists'),
|
||||||
_('Download and use domain lists from remote URLs'),
|
_('Download and use domain lists from remote URLs'),
|
||||||
);
|
);
|
||||||
o.default = '0';
|
o.default = '0';
|
||||||
o.rmempty = false;
|
o.rmempty = false;
|
||||||
o.ucisection = s.section;
|
o.ucisection = s.section;
|
||||||
|
|
||||||
o = s.taboption(
|
o = s.taboption(
|
||||||
'basic',
|
'basic',
|
||||||
form.DynamicList,
|
form.DynamicList,
|
||||||
'remote_domain_lists',
|
'remote_domain_lists',
|
||||||
_('Remote Domain URLs'),
|
_('Remote Domain URLs'),
|
||||||
_('Enter full URLs starting with http:// or https://'),
|
_('Enter full URLs starting with http:// or https://'),
|
||||||
);
|
);
|
||||||
o.placeholder = 'URL';
|
o.placeholder = 'URL';
|
||||||
o.depends('remote_domain_lists_enabled', '1');
|
o.depends('remote_domain_lists_enabled', '1');
|
||||||
@@ -572,22 +579,22 @@ function createConfigSection(section) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
o = s.taboption(
|
o = s.taboption(
|
||||||
'basic',
|
'basic',
|
||||||
form.Flag,
|
form.Flag,
|
||||||
'local_subnet_lists_enabled',
|
'local_subnet_lists_enabled',
|
||||||
_('Local Subnet Lists'),
|
_('Local Subnet Lists'),
|
||||||
_('Use the list from the router filesystem'),
|
_('Use the list from the router filesystem'),
|
||||||
);
|
);
|
||||||
o.default = '0';
|
o.default = '0';
|
||||||
o.rmempty = false;
|
o.rmempty = false;
|
||||||
o.ucisection = s.section;
|
o.ucisection = s.section;
|
||||||
|
|
||||||
o = s.taboption(
|
o = s.taboption(
|
||||||
'basic',
|
'basic',
|
||||||
form.DynamicList,
|
form.DynamicList,
|
||||||
'local_subnet_lists',
|
'local_subnet_lists',
|
||||||
_('Local Subnet List Paths'),
|
_('Local Subnet List Paths'),
|
||||||
_('Enter the list file path'),
|
_('Enter the list file path'),
|
||||||
);
|
);
|
||||||
o.placeholder = '/path/file.lst';
|
o.placeholder = '/path/file.lst';
|
||||||
o.depends('local_subnet_lists_enabled', '1');
|
o.depends('local_subnet_lists_enabled', '1');
|
||||||
@@ -609,11 +616,11 @@ function createConfigSection(section) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
o = s.taboption(
|
o = s.taboption(
|
||||||
'basic',
|
'basic',
|
||||||
form.ListValue,
|
form.ListValue,
|
||||||
'user_subnet_list_type',
|
'user_subnet_list_type',
|
||||||
_('User Subnet List Type'),
|
_('User Subnet List Type'),
|
||||||
_('Select how to add your custom subnets'),
|
_('Select how to add your custom subnets'),
|
||||||
);
|
);
|
||||||
o.value('disabled', _('Disabled'));
|
o.value('disabled', _('Disabled'));
|
||||||
o.value('dynamic', _('Dynamic List'));
|
o.value('dynamic', _('Dynamic List'));
|
||||||
@@ -623,13 +630,13 @@ function createConfigSection(section) {
|
|||||||
o.ucisection = s.section;
|
o.ucisection = s.section;
|
||||||
|
|
||||||
o = s.taboption(
|
o = s.taboption(
|
||||||
'basic',
|
'basic',
|
||||||
form.DynamicList,
|
form.DynamicList,
|
||||||
'user_subnets',
|
'user_subnets',
|
||||||
_('User Subnets'),
|
_('User Subnets'),
|
||||||
_(
|
_(
|
||||||
'Enter subnets in CIDR notation (example: 103.21.244.0/22) or single IP addresses',
|
'Enter subnets in CIDR notation (example: 103.21.244.0/22) or single IP addresses',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
o.placeholder = 'IP or subnet';
|
o.placeholder = 'IP or subnet';
|
||||||
o.depends('user_subnet_list_type', 'dynamic');
|
o.depends('user_subnet_list_type', 'dynamic');
|
||||||
@@ -651,16 +658,16 @@ function createConfigSection(section) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
o = s.taboption(
|
o = s.taboption(
|
||||||
'basic',
|
'basic',
|
||||||
form.TextValue,
|
form.TextValue,
|
||||||
'user_subnets_text',
|
'user_subnets_text',
|
||||||
_('User Subnets List'),
|
_('User Subnets List'),
|
||||||
_(
|
_(
|
||||||
'Enter subnets in CIDR notation or single IP addresses, separated by comma, space or newline. You can add comments after //',
|
'Enter subnets in CIDR notation or single IP addresses, separated by comma, space or newline. You can add comments after //',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
o.placeholder =
|
o.placeholder =
|
||||||
'103.21.244.0/22\n// Google DNS\n8.8.8.8\n1.1.1.1/32, 9.9.9.9 // Cloudflare and Quad9';
|
'103.21.244.0/22\n// Google DNS\n8.8.8.8\n1.1.1.1/32, 9.9.9.9 // Cloudflare and Quad9';
|
||||||
o.depends('user_subnet_list_type', 'text');
|
o.depends('user_subnet_list_type', 'text');
|
||||||
o.rows = 10;
|
o.rows = 10;
|
||||||
o.rmempty = false;
|
o.rmempty = false;
|
||||||
@@ -675,7 +682,7 @@ function createConfigSection(section) {
|
|||||||
|
|
||||||
if (!subnets.length) {
|
if (!subnets.length) {
|
||||||
return _(
|
return _(
|
||||||
'At least one valid subnet or IP must be specified. Comments-only content is not allowed.',
|
'At least one valid subnet or IP must be specified. Comments-only content is not allowed.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -683,8 +690,8 @@ function createConfigSection(section) {
|
|||||||
|
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
const errors = results
|
const errors = results
|
||||||
.filter((validation) => !validation.valid) // Leave only failed validations
|
.filter((validation) => !validation.valid) // Leave only failed validations
|
||||||
.map((validation) => _(`${validation.value}: ${validation.message}`)); // Collect validation errors
|
.map((validation) => _(`${validation.value}: ${validation.message}`)); // Collect validation errors
|
||||||
|
|
||||||
return [_('Validation errors:'), ...errors].join('\n');
|
return [_('Validation errors:'), ...errors].join('\n');
|
||||||
}
|
}
|
||||||
@@ -693,22 +700,22 @@ function createConfigSection(section) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
o = s.taboption(
|
o = s.taboption(
|
||||||
'basic',
|
'basic',
|
||||||
form.Flag,
|
form.Flag,
|
||||||
'remote_subnet_lists_enabled',
|
'remote_subnet_lists_enabled',
|
||||||
_('Remote Subnet Lists'),
|
_('Remote Subnet Lists'),
|
||||||
_('Download and use subnet lists from remote URLs'),
|
_('Download and use subnet lists from remote URLs'),
|
||||||
);
|
);
|
||||||
o.default = '0';
|
o.default = '0';
|
||||||
o.rmempty = false;
|
o.rmempty = false;
|
||||||
o.ucisection = s.section;
|
o.ucisection = s.section;
|
||||||
|
|
||||||
o = s.taboption(
|
o = s.taboption(
|
||||||
'basic',
|
'basic',
|
||||||
form.DynamicList,
|
form.DynamicList,
|
||||||
'remote_subnet_lists',
|
'remote_subnet_lists',
|
||||||
_('Remote Subnet URLs'),
|
_('Remote Subnet URLs'),
|
||||||
_('Enter full URLs starting with http:// or https://'),
|
_('Enter full URLs starting with http:// or https://'),
|
||||||
);
|
);
|
||||||
o.placeholder = 'URL';
|
o.placeholder = 'URL';
|
||||||
o.depends('remote_subnet_lists_enabled', '1');
|
o.depends('remote_subnet_lists_enabled', '1');
|
||||||
@@ -730,24 +737,24 @@ function createConfigSection(section) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
o = s.taboption(
|
o = s.taboption(
|
||||||
'basic',
|
'basic',
|
||||||
form.Flag,
|
form.Flag,
|
||||||
'all_traffic_from_ip_enabled',
|
'all_traffic_from_ip_enabled',
|
||||||
_('IP for full redirection'),
|
_('IP for full redirection'),
|
||||||
_(
|
_(
|
||||||
'Specify local IP addresses whose traffic will always use the configured route',
|
'Specify local IP addresses whose traffic will always use the configured route',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
o.default = '0';
|
o.default = '0';
|
||||||
o.rmempty = false;
|
o.rmempty = false;
|
||||||
o.ucisection = s.section;
|
o.ucisection = s.section;
|
||||||
|
|
||||||
o = s.taboption(
|
o = s.taboption(
|
||||||
'basic',
|
'basic',
|
||||||
form.DynamicList,
|
form.DynamicList,
|
||||||
'all_traffic_ip',
|
'all_traffic_ip',
|
||||||
_('Local IPs'),
|
_('Local IPs'),
|
||||||
_('Enter valid IPv4 addresses'),
|
_('Enter valid IPv4 addresses'),
|
||||||
);
|
);
|
||||||
o.placeholder = 'IP';
|
o.placeholder = 'IP';
|
||||||
o.depends('all_traffic_from_ip_enabled', '1');
|
o.depends('all_traffic_from_ip_enabled', '1');
|
||||||
|
|||||||
@@ -128,6 +128,12 @@ function validateShadowsocksUrl(url) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
if (!url || /\s/.test(url)) {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
message: "Invalid Shadowsocks URL: must not contain spaces"
|
||||||
|
};
|
||||||
|
}
|
||||||
const mainPart = url.includes("?") ? url.split("?")[0] : url.split("#")[0];
|
const mainPart = url.includes("?") ? url.split("?")[0] : url.split("#")[0];
|
||||||
const encryptedPart = mainPart.split("/")[2]?.split("@")[0];
|
const encryptedPart = mainPart.split("/")[2]?.split("@")[0];
|
||||||
if (!encryptedPart) {
|
if (!encryptedPart) {
|
||||||
@@ -185,49 +191,54 @@ function validateShadowsocksUrl(url) {
|
|||||||
|
|
||||||
// src/validators/validateVlessUrl.ts
|
// src/validators/validateVlessUrl.ts
|
||||||
function validateVlessUrl(url) {
|
function validateVlessUrl(url) {
|
||||||
if (!url.startsWith("vless://")) {
|
|
||||||
return {
|
|
||||||
valid: false,
|
|
||||||
message: "Invalid VLESS URL: must start with vless://"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const uuid = url.split("/")[2]?.split("@")[0];
|
const parsedUrl = new URL(url);
|
||||||
if (!uuid) {
|
if (!url || /\s/.test(url)) {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
message: "Invalid VLESS URL: must not contain spaces"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (parsedUrl.protocol !== "vless:") {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
message: "Invalid VLESS URL: must start with vless://"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (!parsedUrl.username) {
|
||||||
return { valid: false, message: "Invalid VLESS URL: missing UUID" };
|
return { valid: false, message: "Invalid VLESS URL: missing UUID" };
|
||||||
}
|
}
|
||||||
const serverPart = url.split("@")[1];
|
if (!parsedUrl.hostname) {
|
||||||
if (!serverPart) {
|
|
||||||
return {
|
|
||||||
valid: false,
|
|
||||||
message: "Invalid VLESS URL: missing server address"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const [server, portAndRest] = serverPart.split(":");
|
|
||||||
if (!server) {
|
|
||||||
return { valid: false, message: "Invalid VLESS URL: missing server" };
|
return { valid: false, message: "Invalid VLESS URL: missing server" };
|
||||||
}
|
}
|
||||||
const port = portAndRest ? portAndRest.split(/[/?#]/)[0] : null;
|
if (!parsedUrl.port) {
|
||||||
if (!port) {
|
|
||||||
return { valid: false, message: "Invalid VLESS URL: missing port" };
|
return { valid: false, message: "Invalid VLESS URL: missing port" };
|
||||||
}
|
}
|
||||||
const portNum = parseInt(port, 10);
|
if (isNaN(+parsedUrl.port) || +parsedUrl.port < 1 || +parsedUrl.port > 65535) {
|
||||||
if (isNaN(portNum) || portNum < 1 || portNum > 65535) {
|
|
||||||
return {
|
return {
|
||||||
valid: false,
|
valid: false,
|
||||||
message: "Invalid port number. Must be between 1 and 65535"
|
message: "Invalid VLESS URL: invalid port number. Must be between 1 and 65535"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const queryString = url.split("?")[1];
|
if (!parsedUrl.search) {
|
||||||
if (!queryString) {
|
|
||||||
return {
|
return {
|
||||||
valid: false,
|
valid: false,
|
||||||
message: "Invalid VLESS URL: missing query parameters"
|
message: "Invalid VLESS URL: missing query parameters"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const params = new URLSearchParams(queryString.split("#")[0]);
|
const params = new URLSearchParams(parsedUrl.search);
|
||||||
const type = params.get("type");
|
const type = params.get("type");
|
||||||
const validTypes = ["tcp", "raw", "udp", "grpc", "http", "ws"];
|
const validTypes = [
|
||||||
|
"tcp",
|
||||||
|
"raw",
|
||||||
|
"udp",
|
||||||
|
"grpc",
|
||||||
|
"http",
|
||||||
|
"httpupgrade",
|
||||||
|
"xhttp",
|
||||||
|
"ws",
|
||||||
|
"kcp"
|
||||||
|
];
|
||||||
if (!type || !validTypes.includes(type)) {
|
if (!type || !validTypes.includes(type)) {
|
||||||
return {
|
return {
|
||||||
valid: false,
|
valid: false,
|
||||||
@@ -256,10 +267,10 @@ function validateVlessUrl(url) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return { valid: true, message: "Valid" };
|
||||||
} catch (_e) {
|
} catch (_e) {
|
||||||
return { valid: false, message: "Invalid VLESS URL: parsing failed" };
|
return { valid: false, message: "Invalid VLESS URL: parsing failed" };
|
||||||
}
|
}
|
||||||
return { valid: true, message: "Valid" };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/validators/validateOutboundJson.ts
|
// src/validators/validateOutboundJson.ts
|
||||||
@@ -286,6 +297,12 @@ function validateTrojanUrl(url) {
|
|||||||
message: "Invalid Trojan URL: must start with trojan://"
|
message: "Invalid Trojan URL: must start with trojan://"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (!url || /\s/.test(url)) {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
message: "Invalid Trojan URL: must not contain spaces"
|
||||||
|
};
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const parsedUrl = new URL(url);
|
const parsedUrl = new URL(url);
|
||||||
if (!parsedUrl.username || !parsedUrl.hostname || !parsedUrl.port) {
|
if (!parsedUrl.username || !parsedUrl.hostname || !parsedUrl.port) {
|
||||||
|
|||||||
Reference in New Issue
Block a user