diff --git a/fe-app-podkop/src/main.ts b/fe-app-podkop/src/main.ts index 5a2e1ba..138d3fc 100644 --- a/fe-app-podkop/src/main.ts +++ b/fe-app-podkop/src/main.ts @@ -2,5 +2,5 @@ 'require baseclass'; export * from './validators'; -export * from './constants'; export * from './helpers'; +export * from './constants'; diff --git a/fe-app-podkop/src/validators/index.ts b/fe-app-podkop/src/validators/index.ts index 291795e..bedc361 100644 --- a/fe-app-podkop/src/validators/index.ts +++ b/fe-app-podkop/src/validators/index.ts @@ -5,3 +5,5 @@ export * from './validateUrl'; export * from './validatePath'; export * from './validateSubnet'; export * from './bulkValidate'; +export * from './validateShadowsocksUrl'; +export * from './validateVlessUrl'; diff --git a/fe-app-podkop/src/validators/validateShadowsocksUrl.ts b/fe-app-podkop/src/validators/validateShadowsocksUrl.ts new file mode 100644 index 0000000..8c2adb1 --- /dev/null +++ b/fe-app-podkop/src/validators/validateShadowsocksUrl.ts @@ -0,0 +1,81 @@ +import { ValidationResult } from './types.js'; + +// TODO refactor current validation and add tests +export function validateShadowsocksUrl(url: string): ValidationResult { + 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' }; +} diff --git a/fe-app-podkop/src/validators/validateVlessUrl.ts b/fe-app-podkop/src/validators/validateVlessUrl.ts new file mode 100644 index 0000000..56aeb09 --- /dev/null +++ b/fe-app-podkop/src/validators/validateVlessUrl.ts @@ -0,0 +1,102 @@ +import { ValidationResult } from './types'; + +// TODO refactor current validation and add tests +export function validateVlessUrl(url: string): ValidationResult { + 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' }; +}