diff --git a/fe-app-podkop/locales/calls.json b/fe-app-podkop/locales/calls.json index f1bb11d..f80bfea 100644 --- a/fe-app-podkop/locales/calls.json +++ b/fe-app-podkop/locales/calls.json @@ -3,35 +3,35 @@ "call": "✔ Enabled", "key": "✔ Enabled", "places": [ - "src/podkop/tabs/dashboard/initController.ts:342" + "src/podkop/tabs/dashboard/initController.ts:345" ] }, { "call": "✔ Running", "key": "✔ Running", "places": [ - "src/podkop/tabs/dashboard/initController.ts:353" + "src/podkop/tabs/dashboard/initController.ts:356" ] }, { "call": "✘ Disabled", "key": "✘ Disabled", "places": [ - "src/podkop/tabs/dashboard/initController.ts:343" + "src/podkop/tabs/dashboard/initController.ts:346" ] }, { "call": "✘ Stopped", "key": "✘ Stopped", "places": [ - "src/podkop/tabs/dashboard/initController.ts:354" + "src/podkop/tabs/dashboard/initController.ts:357" ] }, { "call": "Active Connections", "key": "Active Connections", "places": [ - "src/podkop/tabs/dashboard/initController.ts:304" + "src/podkop/tabs/dashboard/initController.ts:307" ] }, { @@ -379,8 +379,8 @@ "call": "Downlink", "key": "Downlink", "places": [ - "src/podkop/tabs/dashboard/initController.ts:238", - "src/podkop/tabs/dashboard/initController.ts:272" + "src/podkop/tabs/dashboard/initController.ts:241", + "src/podkop/tabs/dashboard/initController.ts:275" ] }, { @@ -637,6 +637,83 @@ "src/validators/validateSubnet.ts:11" ] }, + { + "call": "Invalid HY2 URL: insecure must be 0 or 1", + "key": "Invalid HY2 URL: insecure must be 0 or 1", + "places": [ + "src/validators/validateHysteriaUrl.ts:73" + ] + }, + { + "call": "Invalid HY2 URL: invalid port number", + "key": "Invalid HY2 URL: invalid port number", + "places": [ + "src/validators/validateHysteriaUrl.ts:62" + ] + }, + { + "call": "Invalid HY2 URL: missing credentials/server", + "key": "Invalid HY2 URL: missing credentials/server", + "places": [ + "src/validators/validateHysteriaUrl.ts:32" + ] + }, + { + "call": "Invalid HY2 URL: missing host", + "key": "Invalid HY2 URL: missing host", + "places": [ + "src/validators/validateHysteriaUrl.ts:49" + ] + }, + { + "call": "Invalid HY2 URL: missing host & port", + "key": "Invalid HY2 URL: missing host & port", + "places": [ + "src/validators/validateHysteriaUrl.ts:43" + ] + }, + { + "call": "Invalid HY2 URL: missing password", + "key": "Invalid HY2 URL: missing password", + "places": [ + "src/validators/validateHysteriaUrl.ts:38" + ] + }, + { + "call": "Invalid HY2 URL: missing port", + "key": "Invalid HY2 URL: missing port", + "places": [ + "src/validators/validateHysteriaUrl.ts:53" + ] + }, + { + "call": "Invalid HY2 URL: must not contain spaces", + "key": "Invalid HY2 URL: must not contain spaces", + "places": [ + "src/validators/validateHysteriaUrl.ts:19" + ] + }, + { + "call": "Invalid HY2 URL: must start with hysteria2:// or hy2://", + "key": "Invalid HY2 URL: must start with hysteria2:// or hy2://", + "places": [ + "src/validators/validateHysteriaUrl.ts:12" + ] + }, + { + "call": "Invalid HY2 URL: parsing failed", + "key": "Invalid HY2 URL: parsing failed", + "places": [ + "src/validators/validateHysteriaUrl.ts:103" + ] + }, + { + "call": "Invalid HY2 URL: unsupported obfs type", + "key": "Invalid HY2 URL: unsupported obfs type", + "places": [ + "src/validators/validateHysteriaUrl.ts:82" + ] + }, { "call": "Invalid IP address", "key": "Invalid IP address", @@ -880,7 +957,7 @@ "call": "Memory Usage", "key": "Memory Usage", "places": [ - "src/podkop/tabs/dashboard/initController.ts:308" + "src/podkop/tabs/dashboard/initController.ts:311" ] }, { @@ -1023,7 +1100,7 @@ "call": "Podkop", "key": "Podkop", "places": [ - "src/podkop/tabs/dashboard/initController.ts:340" + "src/podkop/tabs/dashboard/initController.ts:343" ] }, { @@ -1290,7 +1367,7 @@ "call": "Services info", "key": "Services info", "places": [ - "src/podkop/tabs/dashboard/initController.ts:337" + "src/podkop/tabs/dashboard/initController.ts:340" ] }, { @@ -1312,7 +1389,7 @@ "call": "Sing-box", "key": "Sing-box", "places": [ - "src/podkop/tabs/dashboard/initController.ts:351" + "src/podkop/tabs/dashboard/initController.ts:354" ] }, { @@ -1425,7 +1502,7 @@ "call": "System info", "key": "System info", "places": [ - "src/podkop/tabs/dashboard/initController.ts:301" + "src/podkop/tabs/dashboard/initController.ts:304" ] }, { @@ -1453,13 +1530,7 @@ "call": "Text List", "key": "Text List", "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:368" - ] - }, - { - "call": "Text List (comma/space/newline separated)", - "key": "Text List (comma/space/newline separated)", - "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:368", "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:448" ] }, @@ -1502,14 +1573,14 @@ "call": "Traffic", "key": "Traffic", "places": [ - "src/podkop/tabs/dashboard/initController.ts:235" + "src/podkop/tabs/dashboard/initController.ts:238" ] }, { "call": "Traffic Total", "key": "Traffic Total", "places": [ - "src/podkop/tabs/dashboard/initController.ts:265" + "src/podkop/tabs/dashboard/initController.ts:268" ] }, { @@ -1572,15 +1643,15 @@ "call": "Uplink", "key": "Uplink", "places": [ - "src/podkop/tabs/dashboard/initController.ts:237", - "src/podkop/tabs/dashboard/initController.ts:268" + "src/podkop/tabs/dashboard/initController.ts:240", + "src/podkop/tabs/dashboard/initController.ts:271" ] }, { "call": "URL must start with vless://, ss://, trojan://, or socks4/5://", "key": "URL must start with vless://, ss://, trojan://, or socks4/5://", "places": [ - "src/validators/validateProxyUrl.ts:29" + "src/validators/validateProxyUrl.ts:37" ] }, { @@ -1675,6 +1746,7 @@ "src/validators/validateDns.ts:18", "src/validators/validateDomain.ts:13", "src/validators/validateDomain.ts:30", + "src/validators/validateHysteriaUrl.ts:101", "src/validators/validateIp.ts:8", "src/validators/validateOutboundJson.ts:7", "src/validators/validatePath.ts:16", diff --git a/fe-app-podkop/locales/podkop.pot b/fe-app-podkop/locales/podkop.pot index 0bf57c8..00c0264 100644 --- a/fe-app-podkop/locales/podkop.pot +++ b/fe-app-podkop/locales/podkop.pot @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: PODKOP\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-06 14:19+0200\n" -"PO-Revision-Date: 2025-11-06 14:19+0200\n" +"POT-Creation-Date: 2025-11-30 16:34+0200\n" +"PO-Revision-Date: 2025-11-30 16:34+0200\n" "Last-Translator: divocat \n" "Language-Team: LANGUAGE \n" "Language: \n" @@ -16,23 +16,23 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: src/podkop/tabs/dashboard/initController.ts:342 +#: src/podkop/tabs/dashboard/initController.ts:345 msgid "✔ Enabled" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:353 +#: src/podkop/tabs/dashboard/initController.ts:356 msgid "✔ Running" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:343 +#: src/podkop/tabs/dashboard/initController.ts:346 msgid "✘ Disabled" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:354 +#: src/podkop/tabs/dashboard/initController.ts:357 msgid "✘ Stopped" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:304 +#: src/podkop/tabs/dashboard/initController.ts:307 msgid "Active Connections" msgstr "" @@ -236,8 +236,8 @@ msgstr "" msgid "Dont Touch My DHCP!" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:238 -#: src/podkop/tabs/dashboard/initController.ts:272 +#: src/podkop/tabs/dashboard/initController.ts:241 +#: src/podkop/tabs/dashboard/initController.ts:275 msgid "Downlink" msgstr "" @@ -390,6 +390,50 @@ msgstr "" msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y" msgstr "" +#: src/validators/validateHysteriaUrl.ts:73 +msgid "Invalid HY2 URL: insecure must be 0 or 1" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:62 +msgid "Invalid HY2 URL: invalid port number" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:32 +msgid "Invalid HY2 URL: missing credentials/server" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:49 +msgid "Invalid HY2 URL: missing host" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:43 +msgid "Invalid HY2 URL: missing host & port" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:38 +msgid "Invalid HY2 URL: missing password" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:53 +msgid "Invalid HY2 URL: missing port" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:19 +msgid "Invalid HY2 URL: must not contain spaces" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:12 +msgid "Invalid HY2 URL: must start with hysteria2:// or hy2://" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:103 +msgid "Invalid HY2 URL: parsing failed" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:82 +msgid "Invalid HY2 URL: unsupported obfs type" +msgstr "" + #: src/validators/validateIp.ts:11 msgid "Invalid IP address" msgstr "" @@ -527,7 +571,7 @@ msgstr "" msgid "Main DNS" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:308 +#: src/podkop/tabs/dashboard/initController.ts:311 msgid "Memory Usage" msgstr "" @@ -613,7 +657,7 @@ msgstr "" msgid "Pending" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:340 +#: src/podkop/tabs/dashboard/initController.ts:343 msgid "Podkop" msgstr "" @@ -766,7 +810,7 @@ msgstr "" msgid "Select the WAN interfaces to be monitored" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:337 +#: src/podkop/tabs/dashboard/initController.ts:340 msgid "Services info" msgstr "" @@ -779,7 +823,7 @@ msgstr "" msgid "Show sing-box config" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:351 +#: src/podkop/tabs/dashboard/initController.ts:354 msgid "Sing-box" msgstr "" @@ -844,7 +888,7 @@ msgstr "" msgid "Successfully copied!" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:301 +#: src/podkop/tabs/dashboard/initController.ts:304 msgid "System info" msgstr "" @@ -861,11 +905,8 @@ msgid "Test latency" msgstr "" #: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:368 -msgid "Text List" -msgstr "" - #: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:448 -msgid "Text List (comma/space/newline separated)" +msgid "Text List" msgstr "" #: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:46 @@ -888,11 +929,11 @@ msgstr "" msgid "Time in seconds for DNS record caching (default: 60)" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:235 +#: src/podkop/tabs/dashboard/initController.ts:238 msgid "Traffic" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:265 +#: src/podkop/tabs/dashboard/initController.ts:268 msgid "Traffic Total" msgstr "" @@ -931,12 +972,12 @@ msgstr "" msgid "Unknown error" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:237 -#: src/podkop/tabs/dashboard/initController.ts:268 +#: src/podkop/tabs/dashboard/initController.ts:240 +#: src/podkop/tabs/dashboard/initController.ts:271 msgid "Uplink" msgstr "" -#: src/validators/validateProxyUrl.ts:29 +#: src/validators/validateProxyUrl.ts:37 msgid "URL must start with vless://, ss://, trojan://, or socks4/5://" msgstr "" @@ -992,6 +1033,7 @@ msgstr "" #: src/validators/validateDns.ts:18 #: src/validators/validateDomain.ts:13 #: src/validators/validateDomain.ts:30 +#: src/validators/validateHysteriaUrl.ts:101 #: src/validators/validateIp.ts:8 #: src/validators/validateOutboundJson.ts:7 #: src/validators/validatePath.ts:16 diff --git a/fe-app-podkop/locales/podkop.ru.po b/fe-app-podkop/locales/podkop.ru.po index 25971ad..60c47a0 100644 --- a/fe-app-podkop/locales/podkop.ru.po +++ b/fe-app-podkop/locales/podkop.ru.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: PODKOP\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-06 16:19+0200\n" -"PO-Revision-Date: 2025-11-06 16:19+0200\n" +"POT-Creation-Date: 2025-11-30 18:34+0200\n" +"PO-Revision-Date: 2025-11-30 18:34+0200\n" "Last-Translator: divocat\n" "Language-Team: none\n" "Language: ru\n" @@ -281,6 +281,39 @@ msgstr "Неверный домен" msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y" msgstr "Неверный формат. Используйте X.X.X.X или X.X.X.X/Y" +msgid "Invalid HY2 URL: insecure must be 0 or 1" +msgstr "Неверный URL Hysteria2: параметр insecure должен быть 0 или 1" + +msgid "Invalid HY2 URL: invalid port number" +msgstr "Неверный URL Hysteria2: неверный номер порта" + +msgid "Invalid HY2 URL: missing credentials/server" +msgstr "Неверный URL Hysteria2: отсутствуют учетные данные/сервер" + +msgid "Invalid HY2 URL: missing host" +msgstr "Неверный URL Hysteria2: отсутствует хост" + +msgid "Invalid HY2 URL: missing host & port" +msgstr "Неверный URL Hysteria2: отсутствуют хост и порт" + +msgid "Invalid HY2 URL: missing password" +msgstr "Неверный URL Hysteria2: отсутствует пароль" + +msgid "Invalid HY2 URL: missing port" +msgstr "Неверный URL Hysteria2: отсутствует порт" + +msgid "Invalid HY2 URL: must not contain spaces" +msgstr "Неверный URL Hysteria2: не должен содержать пробелов" + +msgid "Invalid HY2 URL: must start with hysteria2:// or hy2://" +msgstr "Неверный URL Hysteria2: должен начинаться с hysteria2:// или hy2://" + +msgid "Invalid HY2 URL: parsing failed" +msgstr "Неверный URL Hysteria2: ошибка разбора" + +msgid "Invalid HY2 URL: unsupported obfs type" +msgstr "Неверный URL Hysteria2: неподдерживаемый тип obfs" + msgid "Invalid IP address" msgstr "Неверный IP-адрес" @@ -626,9 +659,6 @@ msgstr "Тестирование задержки" msgid "Text List" msgstr "Текстовый список" -msgid "Text List (comma/space/newline separated)" -msgstr "Текстовый список (через запятую, пробел или новую строку)" - msgid "The DNS server used to look up the IP address of an upstream DNS server" msgstr "DNS-сервер, используемый для поиска IP-адреса вышестоящего DNS-сервера" diff --git a/fe-app-podkop/src/validators/tests/validateHysteriaUrl.test.js b/fe-app-podkop/src/validators/tests/validateHysteriaUrl.test.js new file mode 100644 index 0000000..b759fd2 --- /dev/null +++ b/fe-app-podkop/src/validators/tests/validateHysteriaUrl.test.js @@ -0,0 +1,74 @@ +import { describe, it, expect } from 'vitest'; +import { validateHysteria2Url } from '../validateHysteriaUrl.js'; + +const validUrls = [ + // Basic password-only + ['password basic', 'hysteria2://pass@example.com:443/#hy2-basic'], + + // insecure=1 + [ + 'insecure allowed', + 'hysteria2://pass@example.com:443/?insecure=1#hy2-insecure', + ], + + // SNI + ['SNI param', 'hysteria2://pass@example.com:443/?sni=google.com#hy2-sni'], + + // Obfuscation + [ + 'Obfs + password', + 'hysteria2://mypassword@1.1.1.1:8443/?obfs=salamander&obfs-password=abc123#hy2-obfs', + ], + + // All params + [ + 'All options combined', + 'hysteria2://pw@8.8.8.8:8443/?sni=example.com&obfs=salamander&obfs-password=hello&insecure=1#hy2-full', + ], + + // Explicit obfs=none (valid) + ['obfs none = ok', 'hysteria2://pw@example.com:443/?obfs=none#hy2-none'], +]; + +const invalidUrls = [ + ['No prefix', 'pw@example.com:443'], + ['Missing password', 'hysteria2://@example.com:443/'], + ['Missing host', 'hysteria2://pw@:443/'], + ['Missing port', 'hysteria2://pw@example.com/'], + ['Non-numeric port', 'hysteria2://pw@example.com:port/'], + ['Port out of range', 'hysteria2://pw@example.com:99999/'], + + // Obfuscation errors + ['Unknown obfs type', 'hysteria2://pw@example.com:443/?obfs=weird'], + [ + 'obfs without obfs-password', + 'hysteria2://pw@example.com:443/?obfs=salamander', + ], + + // insecure only accepts 0/1 + ['invalid insecure', 'hysteria2://pw@example.com:443/?insecure=5'], + + // SNI empty + ['empty sni', 'hysteria2://pw@example.com:443/?sni='], +]; + +describe('validateHysteria2Url', () => { + describe.each(validUrls)('Valid HY2 URL: %s', (_desc, url) => { + it(`returns valid=true for "${url}"`, () => { + const res = validateHysteria2Url(url); + expect(res.valid).toBe(true); + }); + }); + + describe.each(invalidUrls)('Invalid HY2 URL: %s', (_desc, url) => { + it(`returns valid=false for "${url}"`, () => { + const res = validateHysteria2Url(url); + expect(res.valid).toBe(false); + }); + }); + + it('detects invalid port range', () => { + const res = validateHysteria2Url('hysteria2://pw@example.com:70000/'); + expect(res.valid).toBe(false); + }); +}); diff --git a/fe-app-podkop/src/validators/validateHysteriaUrl.ts b/fe-app-podkop/src/validators/validateHysteriaUrl.ts new file mode 100644 index 0000000..53c7530 --- /dev/null +++ b/fe-app-podkop/src/validators/validateHysteriaUrl.ts @@ -0,0 +1,115 @@ +import { ValidationResult } from './types'; +import { parseQueryString } from '../helpers/parseQueryString'; + +export function validateHysteria2Url(url: string): ValidationResult { + try { + const isHY2 = url.startsWith('hysteria2://'); + const isHY2Short = url.startsWith('hy2://'); + + if (!isHY2 && !isHY2Short) { + return { + valid: false, + message: _('Invalid HY2 URL: must start with hysteria2:// or hy2://'), + }; + } + + if (/\s/.test(url)) { + return { + valid: false, + message: _('Invalid HY2 URL: must not contain spaces'), + }; + } + + const prefix = isHY2 ? 'hysteria2://' : 'hy2://'; + const body = url.slice(prefix.length); + + const [mainPart] = body.split('#'); + const [authHostPort, queryString] = mainPart.split('?'); + + if (!authHostPort) + return { + valid: false, + message: _('Invalid HY2 URL: missing credentials/server'), + }; + + const [passwordPart, hostPortPart] = authHostPort.split('@'); + + if (!passwordPart) + return { valid: false, message: _('Invalid HY2 URL: missing password') }; + + if (!hostPortPart) + return { + valid: false, + message: _('Invalid HY2 URL: missing host & port'), + }; + + const [host, port] = hostPortPart.split(':'); + + if (!host) { + return { valid: false, message: _('Invalid HY2 URL: missing host') }; + } + + if (!port) { + return { valid: false, message: _('Invalid HY2 URL: missing port') }; + } + + const cleanedPort = port.replace('/', ''); + const portNum = Number(cleanedPort); + + if (!Number.isInteger(portNum) || portNum < 1 || portNum > 65535) { + return { + valid: false, + message: _('Invalid HY2 URL: invalid port number'), + }; + } + + if (queryString) { + const params = parseQueryString(queryString); + const paramsKeys = Object.keys(params); + + if ( + paramsKeys.includes('insecure') && + !['0', '1'].includes(params.insecure) + ) { + return { + valid: false, + message: _('Invalid HY2 URL: insecure must be 0 or 1'), + }; + } + + const validObfsTypes = ['none', 'salamander']; + + if ( + paramsKeys.includes('obfs') && + !validObfsTypes.includes(params.obfs) + ) { + return { + valid: false, + message: _('Invalid HY2 URL: unsupported obfs type'), + }; + } + + if ( + paramsKeys.includes('obfs') && + params.obfs !== 'none' && + !params['obfs-password'] + ) { + return { + valid: false, + message: 'Invalid HY2 URL: obfs-password required when obfs is set', + }; + } + + if (paramsKeys.includes('sni') && !params.sni) { + return { + valid: false, + message: 'Invalid HY2 URL: sni cannot be empty', + }; + } + } + + return { valid: true, message: _('Valid') }; + } catch (_e) { + return { valid: false, message: _('Invalid HY2 URL: parsing failed') }; + } +} diff --git a/fe-app-podkop/src/validators/validateProxyUrl.ts b/fe-app-podkop/src/validators/validateProxyUrl.ts index 912bbca..24b0c19 100644 --- a/fe-app-podkop/src/validators/validateProxyUrl.ts +++ b/fe-app-podkop/src/validators/validateProxyUrl.ts @@ -3,6 +3,7 @@ import { validateShadowsocksUrl } from './validateShadowsocksUrl'; import { validateVlessUrl } from './validateVlessUrl'; import { validateTrojanUrl } from './validateTrojanUrl'; import { validateSocksUrl } from './validateSocksUrl'; +import { validateHysteria2Url } from './validateHysteriaUrl'; // TODO refactor current validation and add tests export function validateProxyUrl(url: string): ValidationResult { @@ -24,6 +25,13 @@ export function validateProxyUrl(url: string): ValidationResult { return validateSocksUrl(trimmedUrl); } + if ( + trimmedUrl.startsWith('hysteria2://') || + trimmedUrl.startsWith('hy2://') + ) { + return validateHysteria2Url(trimmedUrl); + } + return { valid: false, 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 638867e..986070c 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 @@ -448,6 +448,90 @@ function validateSocksUrl(url) { return { valid: true, message: _("Valid") }; } +// src/validators/validateHysteriaUrl.ts +function validateHysteria2Url(url) { + try { + const isHY2 = url.startsWith("hysteria2://"); + const isHY2Short = url.startsWith("hy2://"); + if (!isHY2 && !isHY2Short) { + return { + valid: false, + message: _("Invalid HY2 URL: must start with hysteria2:// or hy2://") + }; + } + if (/\s/.test(url)) { + return { + valid: false, + message: _("Invalid HY2 URL: must not contain spaces") + }; + } + const prefix = isHY2 ? "hysteria2://" : "hy2://"; + const body = url.slice(prefix.length); + const [mainPart] = body.split("#"); + const [authHostPort, queryString] = mainPart.split("?"); + if (!authHostPort) + return { + valid: false, + message: _("Invalid HY2 URL: missing credentials/server") + }; + const [passwordPart, hostPortPart] = authHostPort.split("@"); + if (!passwordPart) + return { valid: false, message: _("Invalid HY2 URL: missing password") }; + if (!hostPortPart) + return { + valid: false, + message: _("Invalid HY2 URL: missing host & port") + }; + const [host, port] = hostPortPart.split(":"); + if (!host) { + return { valid: false, message: _("Invalid HY2 URL: missing host") }; + } + if (!port) { + return { valid: false, message: _("Invalid HY2 URL: missing port") }; + } + const cleanedPort = port.replace("/", ""); + const portNum = Number(cleanedPort); + if (!Number.isInteger(portNum) || portNum < 1 || portNum > 65535) { + return { + valid: false, + message: _("Invalid HY2 URL: invalid port number") + }; + } + if (queryString) { + const params = parseQueryString(queryString); + const paramsKeys = Object.keys(params); + if (paramsKeys.includes("insecure") && !["0", "1"].includes(params.insecure)) { + return { + valid: false, + message: _("Invalid HY2 URL: insecure must be 0 or 1") + }; + } + const validObfsTypes = ["none", "salamander"]; + if (paramsKeys.includes("obfs") && !validObfsTypes.includes(params.obfs)) { + return { + valid: false, + message: _("Invalid HY2 URL: unsupported obfs type") + }; + } + if (paramsKeys.includes("obfs") && params.obfs !== "none" && !params["obfs-password"]) { + return { + valid: false, + message: "Invalid HY2 URL: obfs-password required when obfs is set" + }; + } + if (paramsKeys.includes("sni") && !params.sni) { + return { + valid: false, + message: "Invalid HY2 URL: sni cannot be empty" + }; + } + } + return { valid: true, message: _("Valid") }; + } catch (_e) { + return { valid: false, message: _("Invalid HY2 URL: parsing failed") }; + } +} + // src/validators/validateProxyUrl.ts function validateProxyUrl(url) { const trimmedUrl = url.trim(); @@ -463,6 +547,9 @@ function validateProxyUrl(url) { if (/^socks(4|4a|5):\/\//.test(trimmedUrl)) { return validateSocksUrl(trimmedUrl); } + if (trimmedUrl.startsWith("hysteria2://") || trimmedUrl.startsWith("hy2://")) { + return validateHysteria2Url(trimmedUrl); + } return { valid: false, message: _( diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js index 5d522fe..0529ac5 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js @@ -87,7 +87,7 @@ function createSectionContent(section) { _("URLTest Proxy Links"), ); o.depends("proxy_config_type", "urltest"); - o.placeholder = "vless://, ss://, trojan://, socks4/5:// links"; + o.placeholder = "vless://, ss://, trojan://, socks4/5://, hy2/hysteria2:// links"; o.rmempty = false; o.validate = function (section_id, value) { // Optional diff --git a/luci-app-podkop/po/ru/podkop.po b/luci-app-podkop/po/ru/podkop.po index 25971ad..60c47a0 100644 --- a/luci-app-podkop/po/ru/podkop.po +++ b/luci-app-podkop/po/ru/podkop.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: PODKOP\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-06 16:19+0200\n" -"PO-Revision-Date: 2025-11-06 16:19+0200\n" +"POT-Creation-Date: 2025-11-30 18:34+0200\n" +"PO-Revision-Date: 2025-11-30 18:34+0200\n" "Last-Translator: divocat\n" "Language-Team: none\n" "Language: ru\n" @@ -281,6 +281,39 @@ msgstr "Неверный домен" msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y" msgstr "Неверный формат. Используйте X.X.X.X или X.X.X.X/Y" +msgid "Invalid HY2 URL: insecure must be 0 or 1" +msgstr "Неверный URL Hysteria2: параметр insecure должен быть 0 или 1" + +msgid "Invalid HY2 URL: invalid port number" +msgstr "Неверный URL Hysteria2: неверный номер порта" + +msgid "Invalid HY2 URL: missing credentials/server" +msgstr "Неверный URL Hysteria2: отсутствуют учетные данные/сервер" + +msgid "Invalid HY2 URL: missing host" +msgstr "Неверный URL Hysteria2: отсутствует хост" + +msgid "Invalid HY2 URL: missing host & port" +msgstr "Неверный URL Hysteria2: отсутствуют хост и порт" + +msgid "Invalid HY2 URL: missing password" +msgstr "Неверный URL Hysteria2: отсутствует пароль" + +msgid "Invalid HY2 URL: missing port" +msgstr "Неверный URL Hysteria2: отсутствует порт" + +msgid "Invalid HY2 URL: must not contain spaces" +msgstr "Неверный URL Hysteria2: не должен содержать пробелов" + +msgid "Invalid HY2 URL: must start with hysteria2:// or hy2://" +msgstr "Неверный URL Hysteria2: должен начинаться с hysteria2:// или hy2://" + +msgid "Invalid HY2 URL: parsing failed" +msgstr "Неверный URL Hysteria2: ошибка разбора" + +msgid "Invalid HY2 URL: unsupported obfs type" +msgstr "Неверный URL Hysteria2: неподдерживаемый тип obfs" + msgid "Invalid IP address" msgstr "Неверный IP-адрес" @@ -626,9 +659,6 @@ msgstr "Тестирование задержки" msgid "Text List" msgstr "Текстовый список" -msgid "Text List (comma/space/newline separated)" -msgstr "Текстовый список (через запятую, пробел или новую строку)" - msgid "The DNS server used to look up the IP address of an upstream DNS server" msgstr "DNS-сервер, используемый для поиска IP-адреса вышестоящего DNS-сервера" diff --git a/luci-app-podkop/po/templates/podkop.pot b/luci-app-podkop/po/templates/podkop.pot index 0bf57c8..00c0264 100644 --- a/luci-app-podkop/po/templates/podkop.pot +++ b/luci-app-podkop/po/templates/podkop.pot @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: PODKOP\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-06 14:19+0200\n" -"PO-Revision-Date: 2025-11-06 14:19+0200\n" +"POT-Creation-Date: 2025-11-30 16:34+0200\n" +"PO-Revision-Date: 2025-11-30 16:34+0200\n" "Last-Translator: divocat \n" "Language-Team: LANGUAGE \n" "Language: \n" @@ -16,23 +16,23 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: src/podkop/tabs/dashboard/initController.ts:342 +#: src/podkop/tabs/dashboard/initController.ts:345 msgid "✔ Enabled" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:353 +#: src/podkop/tabs/dashboard/initController.ts:356 msgid "✔ Running" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:343 +#: src/podkop/tabs/dashboard/initController.ts:346 msgid "✘ Disabled" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:354 +#: src/podkop/tabs/dashboard/initController.ts:357 msgid "✘ Stopped" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:304 +#: src/podkop/tabs/dashboard/initController.ts:307 msgid "Active Connections" msgstr "" @@ -236,8 +236,8 @@ msgstr "" msgid "Dont Touch My DHCP!" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:238 -#: src/podkop/tabs/dashboard/initController.ts:272 +#: src/podkop/tabs/dashboard/initController.ts:241 +#: src/podkop/tabs/dashboard/initController.ts:275 msgid "Downlink" msgstr "" @@ -390,6 +390,50 @@ msgstr "" msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y" msgstr "" +#: src/validators/validateHysteriaUrl.ts:73 +msgid "Invalid HY2 URL: insecure must be 0 or 1" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:62 +msgid "Invalid HY2 URL: invalid port number" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:32 +msgid "Invalid HY2 URL: missing credentials/server" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:49 +msgid "Invalid HY2 URL: missing host" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:43 +msgid "Invalid HY2 URL: missing host & port" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:38 +msgid "Invalid HY2 URL: missing password" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:53 +msgid "Invalid HY2 URL: missing port" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:19 +msgid "Invalid HY2 URL: must not contain spaces" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:12 +msgid "Invalid HY2 URL: must start with hysteria2:// or hy2://" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:103 +msgid "Invalid HY2 URL: parsing failed" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:82 +msgid "Invalid HY2 URL: unsupported obfs type" +msgstr "" + #: src/validators/validateIp.ts:11 msgid "Invalid IP address" msgstr "" @@ -527,7 +571,7 @@ msgstr "" msgid "Main DNS" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:308 +#: src/podkop/tabs/dashboard/initController.ts:311 msgid "Memory Usage" msgstr "" @@ -613,7 +657,7 @@ msgstr "" msgid "Pending" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:340 +#: src/podkop/tabs/dashboard/initController.ts:343 msgid "Podkop" msgstr "" @@ -766,7 +810,7 @@ msgstr "" msgid "Select the WAN interfaces to be monitored" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:337 +#: src/podkop/tabs/dashboard/initController.ts:340 msgid "Services info" msgstr "" @@ -779,7 +823,7 @@ msgstr "" msgid "Show sing-box config" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:351 +#: src/podkop/tabs/dashboard/initController.ts:354 msgid "Sing-box" msgstr "" @@ -844,7 +888,7 @@ msgstr "" msgid "Successfully copied!" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:301 +#: src/podkop/tabs/dashboard/initController.ts:304 msgid "System info" msgstr "" @@ -861,11 +905,8 @@ msgid "Test latency" msgstr "" #: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:368 -msgid "Text List" -msgstr "" - #: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:448 -msgid "Text List (comma/space/newline separated)" +msgid "Text List" msgstr "" #: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:46 @@ -888,11 +929,11 @@ msgstr "" msgid "Time in seconds for DNS record caching (default: 60)" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:235 +#: src/podkop/tabs/dashboard/initController.ts:238 msgid "Traffic" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:265 +#: src/podkop/tabs/dashboard/initController.ts:268 msgid "Traffic Total" msgstr "" @@ -931,12 +972,12 @@ msgstr "" msgid "Unknown error" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:237 -#: src/podkop/tabs/dashboard/initController.ts:268 +#: src/podkop/tabs/dashboard/initController.ts:240 +#: src/podkop/tabs/dashboard/initController.ts:271 msgid "Uplink" msgstr "" -#: src/validators/validateProxyUrl.ts:29 +#: src/validators/validateProxyUrl.ts:37 msgid "URL must start with vless://, ss://, trojan://, or socks4/5://" msgstr "" @@ -992,6 +1033,7 @@ msgstr "" #: src/validators/validateDns.ts:18 #: src/validators/validateDomain.ts:13 #: src/validators/validateDomain.ts:30 +#: src/validators/validateHysteriaUrl.ts:101 #: src/validators/validateIp.ts:8 #: src/validators/validateOutboundJson.ts:7 #: src/validators/validatePath.ts:16