Files
podkop/fe-app-podkop/src/validators/validateHysteriaUrl.ts
2026-05-29 16:40:44 +03:00

125 lines
3.4 KiB
TypeScript

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 portEntries = cleanedPort.split(',');
const isValidPortNumber = (value: string) => {
if (!/^\d+$/.test(value)) return false;
const num = Number(value);
return num >= 1 && num <= 65535;
};
const isValidPortEntry = (entry: string) => {
if (!entry) return false;
if (!entry.includes('-')) return isValidPortNumber(entry);
const rangeParts = entry.split('-');
if (rangeParts.length !== 2) return false;
const [start, end] = rangeParts;
if (!isValidPortNumber(start) || !isValidPortNumber(end)) return false;
return Number(start) <= Number(end);
};
if (!portEntries.every(isValidPortEntry))
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') };
}
}