feat: add hy2 validator

This commit is contained in:
divocat
2025-11-30 18:35:06 +02:00
parent 82345047cb
commit 622e092317
10 changed files with 581 additions and 81 deletions

View File

@@ -3,35 +3,35 @@
"call": "✔ Enabled", "call": "✔ Enabled",
"key": "✔ Enabled", "key": "✔ Enabled",
"places": [ "places": [
"src/podkop/tabs/dashboard/initController.ts:342" "src/podkop/tabs/dashboard/initController.ts:345"
] ]
}, },
{ {
"call": "✔ Running", "call": "✔ Running",
"key": "✔ Running", "key": "✔ Running",
"places": [ "places": [
"src/podkop/tabs/dashboard/initController.ts:353" "src/podkop/tabs/dashboard/initController.ts:356"
] ]
}, },
{ {
"call": "✘ Disabled", "call": "✘ Disabled",
"key": "✘ Disabled", "key": "✘ Disabled",
"places": [ "places": [
"src/podkop/tabs/dashboard/initController.ts:343" "src/podkop/tabs/dashboard/initController.ts:346"
] ]
}, },
{ {
"call": "✘ Stopped", "call": "✘ Stopped",
"key": "✘ Stopped", "key": "✘ Stopped",
"places": [ "places": [
"src/podkop/tabs/dashboard/initController.ts:354" "src/podkop/tabs/dashboard/initController.ts:357"
] ]
}, },
{ {
"call": "Active Connections", "call": "Active Connections",
"key": "Active Connections", "key": "Active Connections",
"places": [ "places": [
"src/podkop/tabs/dashboard/initController.ts:304" "src/podkop/tabs/dashboard/initController.ts:307"
] ]
}, },
{ {
@@ -379,8 +379,8 @@
"call": "Downlink", "call": "Downlink",
"key": "Downlink", "key": "Downlink",
"places": [ "places": [
"src/podkop/tabs/dashboard/initController.ts:238", "src/podkop/tabs/dashboard/initController.ts:241",
"src/podkop/tabs/dashboard/initController.ts:272" "src/podkop/tabs/dashboard/initController.ts:275"
] ]
}, },
{ {
@@ -637,6 +637,83 @@
"src/validators/validateSubnet.ts:11" "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", "call": "Invalid IP address",
"key": "Invalid IP address", "key": "Invalid IP address",
@@ -880,7 +957,7 @@
"call": "Memory Usage", "call": "Memory Usage",
"key": "Memory Usage", "key": "Memory Usage",
"places": [ "places": [
"src/podkop/tabs/dashboard/initController.ts:308" "src/podkop/tabs/dashboard/initController.ts:311"
] ]
}, },
{ {
@@ -1023,7 +1100,7 @@
"call": "Podkop", "call": "Podkop",
"key": "Podkop", "key": "Podkop",
"places": [ "places": [
"src/podkop/tabs/dashboard/initController.ts:340" "src/podkop/tabs/dashboard/initController.ts:343"
] ]
}, },
{ {
@@ -1290,7 +1367,7 @@
"call": "Services info", "call": "Services info",
"key": "Services info", "key": "Services info",
"places": [ "places": [
"src/podkop/tabs/dashboard/initController.ts:337" "src/podkop/tabs/dashboard/initController.ts:340"
] ]
}, },
{ {
@@ -1312,7 +1389,7 @@
"call": "Sing-box", "call": "Sing-box",
"key": "Sing-box", "key": "Sing-box",
"places": [ "places": [
"src/podkop/tabs/dashboard/initController.ts:351" "src/podkop/tabs/dashboard/initController.ts:354"
] ]
}, },
{ {
@@ -1425,7 +1502,7 @@
"call": "System info", "call": "System info",
"key": "System info", "key": "System info",
"places": [ "places": [
"src/podkop/tabs/dashboard/initController.ts:301" "src/podkop/tabs/dashboard/initController.ts:304"
] ]
}, },
{ {
@@ -1453,13 +1530,7 @@
"call": "Text List", "call": "Text List",
"key": "Text List", "key": "Text List",
"places": [ "places": [
"../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:368" "../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:448" "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:448"
] ]
}, },
@@ -1502,14 +1573,14 @@
"call": "Traffic", "call": "Traffic",
"key": "Traffic", "key": "Traffic",
"places": [ "places": [
"src/podkop/tabs/dashboard/initController.ts:235" "src/podkop/tabs/dashboard/initController.ts:238"
] ]
}, },
{ {
"call": "Traffic Total", "call": "Traffic Total",
"key": "Traffic Total", "key": "Traffic Total",
"places": [ "places": [
"src/podkop/tabs/dashboard/initController.ts:265" "src/podkop/tabs/dashboard/initController.ts:268"
] ]
}, },
{ {
@@ -1572,15 +1643,15 @@
"call": "Uplink", "call": "Uplink",
"key": "Uplink", "key": "Uplink",
"places": [ "places": [
"src/podkop/tabs/dashboard/initController.ts:237", "src/podkop/tabs/dashboard/initController.ts:240",
"src/podkop/tabs/dashboard/initController.ts:268" "src/podkop/tabs/dashboard/initController.ts:271"
] ]
}, },
{ {
"call": "URL must start with vless://, ss://, trojan://, or socks4/5://", "call": "URL must start with vless://, ss://, trojan://, or socks4/5://",
"key": "URL must start with vless://, ss://, trojan://, or socks4/5://", "key": "URL must start with vless://, ss://, trojan://, or socks4/5://",
"places": [ "places": [
"src/validators/validateProxyUrl.ts:29" "src/validators/validateProxyUrl.ts:37"
] ]
}, },
{ {
@@ -1675,6 +1746,7 @@
"src/validators/validateDns.ts:18", "src/validators/validateDns.ts:18",
"src/validators/validateDomain.ts:13", "src/validators/validateDomain.ts:13",
"src/validators/validateDomain.ts:30", "src/validators/validateDomain.ts:30",
"src/validators/validateHysteriaUrl.ts:101",
"src/validators/validateIp.ts:8", "src/validators/validateIp.ts:8",
"src/validators/validateOutboundJson.ts:7", "src/validators/validateOutboundJson.ts:7",
"src/validators/validatePath.ts:16", "src/validators/validatePath.ts:16",

View File

@@ -7,8 +7,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PODKOP\n" "Project-Id-Version: PODKOP\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-11-06 14:19+0200\n" "POT-Creation-Date: 2025-11-30 16:34+0200\n"
"PO-Revision-Date: 2025-11-06 14:19+0200\n" "PO-Revision-Date: 2025-11-30 16:34+0200\n"
"Last-Translator: divocat <divocatt@gmail.com>\n" "Last-Translator: divocat <divocatt@gmail.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n" "Language: \n"
@@ -16,23 +16,23 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: src/podkop/tabs/dashboard/initController.ts:342 #: src/podkop/tabs/dashboard/initController.ts:345
msgid "✔ Enabled" msgid "✔ Enabled"
msgstr "" msgstr ""
#: src/podkop/tabs/dashboard/initController.ts:353 #: src/podkop/tabs/dashboard/initController.ts:356
msgid "✔ Running" msgid "✔ Running"
msgstr "" msgstr ""
#: src/podkop/tabs/dashboard/initController.ts:343 #: src/podkop/tabs/dashboard/initController.ts:346
msgid "✘ Disabled" msgid "✘ Disabled"
msgstr "" msgstr ""
#: src/podkop/tabs/dashboard/initController.ts:354 #: src/podkop/tabs/dashboard/initController.ts:357
msgid "✘ Stopped" msgid "✘ Stopped"
msgstr "" msgstr ""
#: src/podkop/tabs/dashboard/initController.ts:304 #: src/podkop/tabs/dashboard/initController.ts:307
msgid "Active Connections" msgid "Active Connections"
msgstr "" msgstr ""
@@ -236,8 +236,8 @@ msgstr ""
msgid "Dont Touch My DHCP!" msgid "Dont Touch My DHCP!"
msgstr "" msgstr ""
#: src/podkop/tabs/dashboard/initController.ts:238 #: src/podkop/tabs/dashboard/initController.ts:241
#: src/podkop/tabs/dashboard/initController.ts:272 #: src/podkop/tabs/dashboard/initController.ts:275
msgid "Downlink" msgid "Downlink"
msgstr "" msgstr ""
@@ -390,6 +390,50 @@ msgstr ""
msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y" msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y"
msgstr "" 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 #: src/validators/validateIp.ts:11
msgid "Invalid IP address" msgid "Invalid IP address"
msgstr "" msgstr ""
@@ -527,7 +571,7 @@ msgstr ""
msgid "Main DNS" msgid "Main DNS"
msgstr "" msgstr ""
#: src/podkop/tabs/dashboard/initController.ts:308 #: src/podkop/tabs/dashboard/initController.ts:311
msgid "Memory Usage" msgid "Memory Usage"
msgstr "" msgstr ""
@@ -613,7 +657,7 @@ msgstr ""
msgid "Pending" msgid "Pending"
msgstr "" msgstr ""
#: src/podkop/tabs/dashboard/initController.ts:340 #: src/podkop/tabs/dashboard/initController.ts:343
msgid "Podkop" msgid "Podkop"
msgstr "" msgstr ""
@@ -766,7 +810,7 @@ msgstr ""
msgid "Select the WAN interfaces to be monitored" msgid "Select the WAN interfaces to be monitored"
msgstr "" msgstr ""
#: src/podkop/tabs/dashboard/initController.ts:337 #: src/podkop/tabs/dashboard/initController.ts:340
msgid "Services info" msgid "Services info"
msgstr "" msgstr ""
@@ -779,7 +823,7 @@ msgstr ""
msgid "Show sing-box config" msgid "Show sing-box config"
msgstr "" msgstr ""
#: src/podkop/tabs/dashboard/initController.ts:351 #: src/podkop/tabs/dashboard/initController.ts:354
msgid "Sing-box" msgid "Sing-box"
msgstr "" msgstr ""
@@ -844,7 +888,7 @@ msgstr ""
msgid "Successfully copied!" msgid "Successfully copied!"
msgstr "" msgstr ""
#: src/podkop/tabs/dashboard/initController.ts:301 #: src/podkop/tabs/dashboard/initController.ts:304
msgid "System info" msgid "System info"
msgstr "" msgstr ""
@@ -861,11 +905,8 @@ msgid "Test latency"
msgstr "" msgstr ""
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:368 #: ../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 #: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:448
msgid "Text List (comma/space/newline separated)" msgid "Text List"
msgstr "" msgstr ""
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:46 #: ../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)" msgid "Time in seconds for DNS record caching (default: 60)"
msgstr "" msgstr ""
#: src/podkop/tabs/dashboard/initController.ts:235 #: src/podkop/tabs/dashboard/initController.ts:238
msgid "Traffic" msgid "Traffic"
msgstr "" msgstr ""
#: src/podkop/tabs/dashboard/initController.ts:265 #: src/podkop/tabs/dashboard/initController.ts:268
msgid "Traffic Total" msgid "Traffic Total"
msgstr "" msgstr ""
@@ -931,12 +972,12 @@ msgstr ""
msgid "Unknown error" msgid "Unknown error"
msgstr "" msgstr ""
#: src/podkop/tabs/dashboard/initController.ts:237 #: src/podkop/tabs/dashboard/initController.ts:240
#: src/podkop/tabs/dashboard/initController.ts:268 #: src/podkop/tabs/dashboard/initController.ts:271
msgid "Uplink" msgid "Uplink"
msgstr "" msgstr ""
#: src/validators/validateProxyUrl.ts:29 #: src/validators/validateProxyUrl.ts:37
msgid "URL must start with vless://, ss://, trojan://, or socks4/5://" msgid "URL must start with vless://, ss://, trojan://, or socks4/5://"
msgstr "" msgstr ""
@@ -992,6 +1033,7 @@ msgstr ""
#: src/validators/validateDns.ts:18 #: src/validators/validateDns.ts:18
#: src/validators/validateDomain.ts:13 #: src/validators/validateDomain.ts:13
#: src/validators/validateDomain.ts:30 #: src/validators/validateDomain.ts:30
#: src/validators/validateHysteriaUrl.ts:101
#: src/validators/validateIp.ts:8 #: src/validators/validateIp.ts:8
#: src/validators/validateOutboundJson.ts:7 #: src/validators/validateOutboundJson.ts:7
#: src/validators/validatePath.ts:16 #: src/validators/validatePath.ts:16

View File

@@ -7,8 +7,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PODKOP\n" "Project-Id-Version: PODKOP\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-11-06 16:19+0200\n" "POT-Creation-Date: 2025-11-30 18:34+0200\n"
"PO-Revision-Date: 2025-11-06 16:19+0200\n" "PO-Revision-Date: 2025-11-30 18:34+0200\n"
"Last-Translator: divocat\n" "Last-Translator: divocat\n"
"Language-Team: none\n" "Language-Team: none\n"
"Language: ru\n" "Language: ru\n"
@@ -281,6 +281,39 @@ msgstr "Неверный домен"
msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y" msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y"
msgstr "Неверный формат. Используйте X.X.X.X или 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" msgid "Invalid IP address"
msgstr "Неверный IP-адрес" msgstr "Неверный IP-адрес"
@@ -626,9 +659,6 @@ msgstr "Тестирование задержки"
msgid "Text List" msgid "Text List"
msgstr "Текстовый список" 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" msgid "The DNS server used to look up the IP address of an upstream DNS server"
msgstr "DNS-сервер, используемый для поиска IP-адреса вышестоящего DNS-сервера" msgstr "DNS-сервер, используемый для поиска IP-адреса вышестоящего DNS-сервера"

View File

@@ -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);
});
});

View File

@@ -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') };
}
}

View File

@@ -3,6 +3,7 @@ import { validateShadowsocksUrl } from './validateShadowsocksUrl';
import { validateVlessUrl } from './validateVlessUrl'; import { validateVlessUrl } from './validateVlessUrl';
import { validateTrojanUrl } from './validateTrojanUrl'; import { validateTrojanUrl } from './validateTrojanUrl';
import { validateSocksUrl } from './validateSocksUrl'; import { validateSocksUrl } from './validateSocksUrl';
import { validateHysteria2Url } from './validateHysteriaUrl';
// TODO refactor current validation and add tests // TODO refactor current validation and add tests
export function validateProxyUrl(url: string): ValidationResult { export function validateProxyUrl(url: string): ValidationResult {
@@ -24,6 +25,13 @@ export function validateProxyUrl(url: string): ValidationResult {
return validateSocksUrl(trimmedUrl); return validateSocksUrl(trimmedUrl);
} }
if (
trimmedUrl.startsWith('hysteria2://') ||
trimmedUrl.startsWith('hy2://')
) {
return validateHysteria2Url(trimmedUrl);
}
return { return {
valid: false, valid: false,
message: _( message: _(

View File

@@ -448,6 +448,90 @@ function validateSocksUrl(url) {
return { valid: true, message: _("Valid") }; 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 // src/validators/validateProxyUrl.ts
function validateProxyUrl(url) { function validateProxyUrl(url) {
const trimmedUrl = url.trim(); const trimmedUrl = url.trim();
@@ -463,6 +547,9 @@ function validateProxyUrl(url) {
if (/^socks(4|4a|5):\/\//.test(trimmedUrl)) { if (/^socks(4|4a|5):\/\//.test(trimmedUrl)) {
return validateSocksUrl(trimmedUrl); return validateSocksUrl(trimmedUrl);
} }
if (trimmedUrl.startsWith("hysteria2://") || trimmedUrl.startsWith("hy2://")) {
return validateHysteria2Url(trimmedUrl);
}
return { return {
valid: false, valid: false,
message: _( message: _(

View File

@@ -87,7 +87,7 @@ function createSectionContent(section) {
_("URLTest Proxy Links"), _("URLTest Proxy Links"),
); );
o.depends("proxy_config_type", "urltest"); 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.rmempty = false;
o.validate = function (section_id, value) { o.validate = function (section_id, value) {
// Optional // Optional

View File

@@ -7,8 +7,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PODKOP\n" "Project-Id-Version: PODKOP\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-11-06 16:19+0200\n" "POT-Creation-Date: 2025-11-30 18:34+0200\n"
"PO-Revision-Date: 2025-11-06 16:19+0200\n" "PO-Revision-Date: 2025-11-30 18:34+0200\n"
"Last-Translator: divocat\n" "Last-Translator: divocat\n"
"Language-Team: none\n" "Language-Team: none\n"
"Language: ru\n" "Language: ru\n"
@@ -281,6 +281,39 @@ msgstr "Неверный домен"
msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y" msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y"
msgstr "Неверный формат. Используйте X.X.X.X или 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" msgid "Invalid IP address"
msgstr "Неверный IP-адрес" msgstr "Неверный IP-адрес"
@@ -626,9 +659,6 @@ msgstr "Тестирование задержки"
msgid "Text List" msgid "Text List"
msgstr "Текстовый список" 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" msgid "The DNS server used to look up the IP address of an upstream DNS server"
msgstr "DNS-сервер, используемый для поиска IP-адреса вышестоящего DNS-сервера" msgstr "DNS-сервер, используемый для поиска IP-адреса вышестоящего DNS-сервера"

View File

@@ -7,8 +7,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PODKOP\n" "Project-Id-Version: PODKOP\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-11-06 14:19+0200\n" "POT-Creation-Date: 2025-11-30 16:34+0200\n"
"PO-Revision-Date: 2025-11-06 14:19+0200\n" "PO-Revision-Date: 2025-11-30 16:34+0200\n"
"Last-Translator: divocat <divocatt@gmail.com>\n" "Last-Translator: divocat <divocatt@gmail.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n" "Language: \n"
@@ -16,23 +16,23 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: src/podkop/tabs/dashboard/initController.ts:342 #: src/podkop/tabs/dashboard/initController.ts:345
msgid "✔ Enabled" msgid "✔ Enabled"
msgstr "" msgstr ""
#: src/podkop/tabs/dashboard/initController.ts:353 #: src/podkop/tabs/dashboard/initController.ts:356
msgid "✔ Running" msgid "✔ Running"
msgstr "" msgstr ""
#: src/podkop/tabs/dashboard/initController.ts:343 #: src/podkop/tabs/dashboard/initController.ts:346
msgid "✘ Disabled" msgid "✘ Disabled"
msgstr "" msgstr ""
#: src/podkop/tabs/dashboard/initController.ts:354 #: src/podkop/tabs/dashboard/initController.ts:357
msgid "✘ Stopped" msgid "✘ Stopped"
msgstr "" msgstr ""
#: src/podkop/tabs/dashboard/initController.ts:304 #: src/podkop/tabs/dashboard/initController.ts:307
msgid "Active Connections" msgid "Active Connections"
msgstr "" msgstr ""
@@ -236,8 +236,8 @@ msgstr ""
msgid "Dont Touch My DHCP!" msgid "Dont Touch My DHCP!"
msgstr "" msgstr ""
#: src/podkop/tabs/dashboard/initController.ts:238 #: src/podkop/tabs/dashboard/initController.ts:241
#: src/podkop/tabs/dashboard/initController.ts:272 #: src/podkop/tabs/dashboard/initController.ts:275
msgid "Downlink" msgid "Downlink"
msgstr "" msgstr ""
@@ -390,6 +390,50 @@ msgstr ""
msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y" msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y"
msgstr "" 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 #: src/validators/validateIp.ts:11
msgid "Invalid IP address" msgid "Invalid IP address"
msgstr "" msgstr ""
@@ -527,7 +571,7 @@ msgstr ""
msgid "Main DNS" msgid "Main DNS"
msgstr "" msgstr ""
#: src/podkop/tabs/dashboard/initController.ts:308 #: src/podkop/tabs/dashboard/initController.ts:311
msgid "Memory Usage" msgid "Memory Usage"
msgstr "" msgstr ""
@@ -613,7 +657,7 @@ msgstr ""
msgid "Pending" msgid "Pending"
msgstr "" msgstr ""
#: src/podkop/tabs/dashboard/initController.ts:340 #: src/podkop/tabs/dashboard/initController.ts:343
msgid "Podkop" msgid "Podkop"
msgstr "" msgstr ""
@@ -766,7 +810,7 @@ msgstr ""
msgid "Select the WAN interfaces to be monitored" msgid "Select the WAN interfaces to be monitored"
msgstr "" msgstr ""
#: src/podkop/tabs/dashboard/initController.ts:337 #: src/podkop/tabs/dashboard/initController.ts:340
msgid "Services info" msgid "Services info"
msgstr "" msgstr ""
@@ -779,7 +823,7 @@ msgstr ""
msgid "Show sing-box config" msgid "Show sing-box config"
msgstr "" msgstr ""
#: src/podkop/tabs/dashboard/initController.ts:351 #: src/podkop/tabs/dashboard/initController.ts:354
msgid "Sing-box" msgid "Sing-box"
msgstr "" msgstr ""
@@ -844,7 +888,7 @@ msgstr ""
msgid "Successfully copied!" msgid "Successfully copied!"
msgstr "" msgstr ""
#: src/podkop/tabs/dashboard/initController.ts:301 #: src/podkop/tabs/dashboard/initController.ts:304
msgid "System info" msgid "System info"
msgstr "" msgstr ""
@@ -861,11 +905,8 @@ msgid "Test latency"
msgstr "" msgstr ""
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:368 #: ../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 #: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:448
msgid "Text List (comma/space/newline separated)" msgid "Text List"
msgstr "" msgstr ""
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:46 #: ../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)" msgid "Time in seconds for DNS record caching (default: 60)"
msgstr "" msgstr ""
#: src/podkop/tabs/dashboard/initController.ts:235 #: src/podkop/tabs/dashboard/initController.ts:238
msgid "Traffic" msgid "Traffic"
msgstr "" msgstr ""
#: src/podkop/tabs/dashboard/initController.ts:265 #: src/podkop/tabs/dashboard/initController.ts:268
msgid "Traffic Total" msgid "Traffic Total"
msgstr "" msgstr ""
@@ -931,12 +972,12 @@ msgstr ""
msgid "Unknown error" msgid "Unknown error"
msgstr "" msgstr ""
#: src/podkop/tabs/dashboard/initController.ts:237 #: src/podkop/tabs/dashboard/initController.ts:240
#: src/podkop/tabs/dashboard/initController.ts:268 #: src/podkop/tabs/dashboard/initController.ts:271
msgid "Uplink" msgid "Uplink"
msgstr "" msgstr ""
#: src/validators/validateProxyUrl.ts:29 #: src/validators/validateProxyUrl.ts:37
msgid "URL must start with vless://, ss://, trojan://, or socks4/5://" msgid "URL must start with vless://, ss://, trojan://, or socks4/5://"
msgstr "" msgstr ""
@@ -992,6 +1033,7 @@ msgstr ""
#: src/validators/validateDns.ts:18 #: src/validators/validateDns.ts:18
#: src/validators/validateDomain.ts:13 #: src/validators/validateDomain.ts:13
#: src/validators/validateDomain.ts:30 #: src/validators/validateDomain.ts:30
#: src/validators/validateHysteriaUrl.ts:101
#: src/validators/validateIp.ts:8 #: src/validators/validateIp.ts:8
#: src/validators/validateOutboundJson.ts:7 #: src/validators/validateOutboundJson.ts:7
#: src/validators/validatePath.ts:16 #: src/validators/validatePath.ts:16