refactor: migrate Proxy Configuration URL validation to modular

This commit is contained in:
divocat
2025-10-03 03:21:18 +03:00
parent b99116fbf3
commit 65d3a9253f
2 changed files with 171 additions and 87 deletions

View File

@@ -78,8 +78,9 @@ function createConfigSection(section, map, network) {
};
o.validate = function (section_id, value) {
// Optional
if (!value || value.length === 0) {
return true;
return true
}
try {
@@ -91,92 +92,30 @@ function createConfigSection(section, map, network) {
return _('No active configuration found. At least one non-commented line is required.');
}
if (!activeConfig.startsWith('vless://') && !activeConfig.startsWith('ss://')) {
return _('URL must start with vless:// or ss://');
}
if (activeConfig.startsWith('ss://')) {
let encrypted_part;
try {
let mainPart = activeConfig.includes('?') ? activeConfig.split('?')[0] : activeConfig.split('#')[0];
encrypted_part = mainPart.split('/')[2].split('@')[0];
try {
let decoded = atob(encrypted_part);
if (!decoded.includes(':')) {
if (!encrypted_part.includes(':') && !encrypted_part.includes('-')) {
return _('Invalid Shadowsocks URL format: missing method and password separator ":"');
}
}
} catch (e) {
if (!encrypted_part.includes(':') && !encrypted_part.includes('-')) {
return _('Invalid Shadowsocks URL format: missing method and password separator ":"');
}
}
} catch (e) {
return _('Invalid Shadowsocks URL format');
const validation = main.validateShadowsocksUrl(activeConfig);
if (validation.valid) {
return true;
}
try {
let serverPart = activeConfig.split('@')[1];
if (!serverPart) return _('Invalid Shadowsocks URL: missing server address');
let [server, portAndRest] = serverPart.split(':');
if (!server) return _('Invalid Shadowsocks URL: missing server');
let port = portAndRest ? portAndRest.split(/[?#]/)[0] : null;
if (!port) return _('Invalid Shadowsocks URL: missing port');
let portNum = parseInt(port);
if (isNaN(portNum) || portNum < 1 || portNum > 65535) {
return _('Invalid port number. Must be between 1 and 65535');
}
} catch (e) {
return _('Invalid Shadowsocks URL: missing or invalid server/port format');
}
return _(validation.message)
}
if (activeConfig.startsWith('vless://')) {
let uuid = activeConfig.split('/')[2].split('@')[0];
if (!uuid || uuid.length === 0) return _('Invalid VLESS URL: missing UUID');
const validation = main.validateVlessUrl(activeConfig);
try {
let serverPart = activeConfig.split('@')[1];
if (!serverPart) return _('Invalid VLESS URL: missing server address');
let [server, portAndRest] = serverPart.split(':');
if (!server) return _('Invalid VLESS URL: missing server');
let port = portAndRest ? portAndRest.split(/[/?#]/)[0] : null;
if (!port) return _('Invalid VLESS URL: missing port');
let portNum = parseInt(port);
if (isNaN(portNum) || portNum < 1 || portNum > 65535) {
return _('Invalid port number. Must be between 1 and 65535');
}
} catch (e) {
return _('Invalid VLESS URL: missing or invalid server/port format');
if (validation.valid) {
return true;
}
let queryString = activeConfig.split('?')[1];
if (!queryString) return _('Invalid VLESS URL: missing query parameters');
let params = new URLSearchParams(queryString.split('#')[0]);
let type = params.get('type');
const validTypes = ['tcp', 'raw', 'udp', 'grpc', 'http', 'ws'];
if (!type || !validTypes.includes(type)) {
return _('Invalid VLESS URL: type must be one of tcp, raw, udp, grpc, http, ws');
}
let security = params.get('security');
const validSecurities = ['tls', 'reality', 'none'];
if (!security || !validSecurities.includes(security)) {
return _('Invalid VLESS URL: security must be one of tls, reality, none');
}
if (security === 'reality') {
if (!params.get('pbk')) return _('Invalid VLESS URL: missing pbk parameter for reality security');
if (!params.get('fp')) return _('Invalid VLESS URL: missing fp parameter for reality security');
}
return _(validation.message)
}
return true;
return _('URL must start with vless:// or ss://')
} catch (e) {
console.error('Validation error:', e);
return _('Invalid URL format: ') + e.message;
return `${_('Invalid URL format:')} ${e?.message}`;
}
};

View File

@@ -118,6 +118,160 @@ function bulkValidate(values, validate) {
};
}
// src/validators/validateShadowsocksUrl.ts
function validateShadowsocksUrl(url) {
if (!url.startsWith("ss://")) {
return {
valid: false,
message: "Invalid Shadowsocks URL: must start with ss://"
};
}
try {
const mainPart = url.includes("?") ? url.split("?")[0] : url.split("#")[0];
const encryptedPart = mainPart.split("/")[2]?.split("@")[0];
if (!encryptedPart) {
return {
valid: false,
message: "Invalid Shadowsocks URL: missing credentials"
};
}
try {
const decoded = atob(encryptedPart);
if (!decoded.includes(":")) {
return {
valid: false,
message: "Invalid Shadowsocks URL: decoded credentials must contain method:password"
};
}
} catch (e) {
if (!encryptedPart.includes(":") && !encryptedPart.includes("-")) {
return {
valid: false,
message: 'Invalid Shadowsocks URL: missing method and password separator ":"'
};
}
}
const serverPart = url.split("@")[1];
if (!serverPart) {
return {
valid: false,
message: "Invalid Shadowsocks URL: missing server address"
};
}
const [server, portAndRest] = serverPart.split(":");
if (!server) {
return {
valid: false,
message: "Invalid Shadowsocks URL: missing server"
};
}
const port = portAndRest ? portAndRest.split(/[?#]/)[0] : null;
if (!port) {
return { valid: false, message: "Invalid Shadowsocks URL: missing port" };
}
const portNum = parseInt(port, 10);
if (isNaN(portNum) || portNum < 1 || portNum > 65535) {
return {
valid: false,
message: "Invalid port number. Must be between 1 and 65535"
};
}
} catch (e) {
return { valid: false, message: "Invalid Shadowsocks URL: parsing failed" };
}
return { valid: true, message: "Valid" };
}
// src/validators/validateVlessUrl.ts
function validateVlessUrl(url) {
if (!url.startsWith("vless://")) {
return {
valid: false,
message: "Invalid VLESS URL: must start with vless://"
};
}
try {
const uuid = url.split("/")[2]?.split("@")[0];
if (!uuid) {
return { valid: false, message: "Invalid VLESS URL: missing UUID" };
}
const serverPart = url.split("@")[1];
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" };
}
const port = portAndRest ? portAndRest.split(/[/?#]/)[0] : null;
if (!port) {
return { valid: false, message: "Invalid VLESS URL: missing port" };
}
const portNum = parseInt(port, 10);
if (isNaN(portNum) || portNum < 1 || portNum > 65535) {
return {
valid: false,
message: "Invalid port number. Must be between 1 and 65535"
};
}
const queryString = url.split("?")[1];
if (!queryString) {
return {
valid: false,
message: "Invalid VLESS URL: missing query parameters"
};
}
const params = new URLSearchParams(queryString.split("#")[0]);
const type = params.get("type");
const validTypes = ["tcp", "raw", "udp", "grpc", "http", "ws"];
if (!type || !validTypes.includes(type)) {
return {
valid: false,
message: "Invalid VLESS URL: type must be one of tcp, raw, udp, grpc, http, ws"
};
}
const security = params.get("security");
const validSecurities = ["tls", "reality", "none"];
if (!security || !validSecurities.includes(security)) {
return {
valid: false,
message: "Invalid VLESS URL: security must be one of tls, reality, none"
};
}
if (security === "reality") {
if (!params.get("pbk")) {
return {
valid: false,
message: "Invalid VLESS URL: missing pbk parameter for reality security"
};
}
if (!params.get("fp")) {
return {
valid: false,
message: "Invalid VLESS URL: missing fp parameter for reality security"
};
}
}
} catch (e) {
return { valid: false, message: "Invalid VLESS URL: parsing failed" };
}
return { valid: true, message: "Valid" };
}
// src/helpers/getBaseUrl.ts
function getBaseUrl() {
const { protocol, hostname } = window.location;
return `${protocol}//${hostname}`;
}
// src/helpers/parseValueList.ts
function parseValueList(value) {
return value.split(/\n/).map((line) => line.split("//")[0]).join(" ").split(/[,\s]+/).map((s) => s.trim()).filter(Boolean);
}
// src/constants.ts
var STATUS_COLORS = {
SUCCESS: "#4caf50",
@@ -227,17 +381,6 @@ var COMMAND_SCHEDULING = {
P10_PRIORITY: 1900
// Lowest priority
};
// src/helpers/getBaseUrl.ts
function getBaseUrl() {
const { protocol, hostname } = window.location;
return `${protocol}//${hostname}`;
}
// src/helpers/parseValueList.ts
function parseValueList(value) {
return value.split(/\n/).map((line) => line.split("//")[0]).join(" ").split(/[,\s]+/).map((s) => s.trim()).filter(Boolean);
}
return baseclass.extend({
ALLOWED_WITH_RUSSIA_INSIDE,
BOOTSTRAP_DNS_SERVER_OPTIONS,
@@ -263,6 +406,8 @@ return baseclass.extend({
validateDomain,
validateIPV4,
validatePath,
validateShadowsocksUrl,
validateSubnet,
validateUrl
validateUrl,
validateVlessUrl
});