Files
NeoTachyon/fe-app-tachyon/src/validators/validateVmessUrl.ts
T

71 lines
1.9 KiB
TypeScript

import { ValidationResult } from './types';
import { isValidHost, isValidPort } from './hostPort';
function decodeBase64Json(value: string): unknown {
const normalized = value.replace(/-/g, '+').replace(/_/g, '/');
const padded = normalized.padEnd(
normalized.length + ((4 - (normalized.length % 4)) % 4),
'=',
);
const binary = atob(padded);
const decoded = decodeURIComponent(
Array.from(binary)
.map((char) => `%${char.charCodeAt(0).toString(16).padStart(2, '0')}`)
.join(''),
);
return JSON.parse(decoded);
}
export function validateVmessUrl(url: string): ValidationResult {
try {
if (!url.startsWith('vmess://')) {
return {
valid: false,
message: 'Invalid VMess URL: must start with vmess://',
};
}
if (/\s/.test(url)) {
return {
valid: false,
message: 'Invalid VMess URL: must not contain spaces',
};
}
const body = url.slice('vmess://'.length);
const [encoded] = body.split('#');
if (!encoded) {
return {
valid: false,
message: 'Invalid VMess URL: missing encoded config',
};
}
const config = decodeBase64Json(encoded);
if (!config || typeof config !== 'object') {
return { valid: false, message: 'Invalid VMess URL: invalid config' };
}
const { add, port, id } = config as Record<string, unknown>;
if (!add || typeof add !== 'string' || !isValidHost(add)) {
return { valid: false, message: 'Invalid VMess URL: invalid server' };
}
if (!isValidPort(port)) {
return {
valid: false,
message: 'Invalid VMess URL: invalid port number',
};
}
if (!id || typeof id !== 'string') {
return { valid: false, message: 'Invalid VMess URL: missing UUID' };
}
return { valid: true, message: _('Valid') };
} catch (_e) {
return { valid: false, message: _('Invalid VMess URL: parsing failed') };
}
}