diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/configSection.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/configSection.js index 43c5f10..7a88eed 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/configSection.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/configSection.js @@ -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}`; } }; diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index 3586973..1752146 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -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 });