feat: migrate validatePath to modular

This commit is contained in:
divocat
2025-10-02 22:37:36 +03:00
parent 5e95148492
commit f58472a53d
7 changed files with 183 additions and 74 deletions

View File

@@ -1,70 +1,76 @@
export const STATUS_COLORS = { export const STATUS_COLORS = {
SUCCESS: '#4caf50', SUCCESS: '#4caf50',
ERROR: '#f44336', ERROR: '#f44336',
WARNING: '#ff9800' WARNING: '#ff9800',
}; };
export const FAKEIP_CHECK_DOMAIN = 'fakeip.podkop.fyi'; export const FAKEIP_CHECK_DOMAIN = 'fakeip.podkop.fyi';
export const IP_CHECK_DOMAIN = 'ip.podkop.fyi'; export const IP_CHECK_DOMAIN = 'ip.podkop.fyi';
export const REGIONAL_OPTIONS = ['russia_inside', 'russia_outside', 'ukraine_inside']; export const REGIONAL_OPTIONS = [
'russia_inside',
'russia_outside',
'ukraine_inside',
];
export const ALLOWED_WITH_RUSSIA_INSIDE = [ export const ALLOWED_WITH_RUSSIA_INSIDE = [
'russia_inside', 'russia_inside',
'meta', 'meta',
'twitter', 'twitter',
'discord', 'discord',
'telegram', 'telegram',
'cloudflare', 'cloudflare',
'google_ai', 'google_ai',
'google_play', 'google_play',
'hetzner', 'hetzner',
'ovh', 'ovh',
'hodca', 'hodca',
'digitalocean', 'digitalocean',
'cloudfront' 'cloudfront',
]; ];
export const DOMAIN_LIST_OPTIONS = { export const DOMAIN_LIST_OPTIONS = {
russia_inside: 'Russia inside', russia_inside: 'Russia inside',
russia_outside: 'Russia outside', russia_outside: 'Russia outside',
ukraine_inside: 'Ukraine', ukraine_inside: 'Ukraine',
geoblock: 'Geo Block', geoblock: 'Geo Block',
block: 'Block', block: 'Block',
porn: 'Porn', porn: 'Porn',
news: 'News', news: 'News',
anime: 'Anime', anime: 'Anime',
youtube: 'Youtube', youtube: 'Youtube',
discord: 'Discord', discord: 'Discord',
meta: 'Meta', meta: 'Meta',
twitter: 'Twitter (X)', twitter: 'Twitter (X)',
hdrezka: 'HDRezka', hdrezka: 'HDRezka',
tiktok: 'Tik-Tok', tiktok: 'Tik-Tok',
telegram: 'Telegram', telegram: 'Telegram',
cloudflare: 'Cloudflare', cloudflare: 'Cloudflare',
google_ai: 'Google AI', google_ai: 'Google AI',
google_play: 'Google Play', google_play: 'Google Play',
hodca: 'H.O.D.C.A', hodca: 'H.O.D.C.A',
hetzner: 'Hetzner ASN', hetzner: 'Hetzner ASN',
ovh: 'OVH ASN', ovh: 'OVH ASN',
digitalocean: 'Digital Ocean ASN', digitalocean: 'Digital Ocean ASN',
cloudfront: 'CloudFront ASN' cloudfront: 'CloudFront ASN',
}; };
export const UPDATE_INTERVAL_OPTIONS = { export const UPDATE_INTERVAL_OPTIONS = {
'1h': 'Every hour', '1h': 'Every hour',
'3h': 'Every 3 hours', '3h': 'Every 3 hours',
'12h': 'Every 12 hours', '12h': 'Every 12 hours',
'1d': 'Every day', '1d': 'Every day',
'3d': 'Every 3 days' '3d': 'Every 3 days',
}; };
export const DNS_SERVER_OPTIONS = { export const DNS_SERVER_OPTIONS = {
'1.1.1.1': '1.1.1.1 (Cloudflare)', '1.1.1.1': '1.1.1.1 (Cloudflare)',
'8.8.8.8': '8.8.8.8 (Google)', '8.8.8.8': '8.8.8.8 (Google)',
'9.9.9.9': '9.9.9.9 (Quad9)', '9.9.9.9': '9.9.9.9 (Quad9)',
'dns.adguard-dns.com': 'dns.adguard-dns.com (AdGuard Default)', 'dns.adguard-dns.com': 'dns.adguard-dns.com (AdGuard Default)',
'unfiltered.adguard-dns.com': 'unfiltered.adguard-dns.com (AdGuard Unfiltered)', 'unfiltered.adguard-dns.com':
'family.adguard-dns.com': 'family.adguard-dns.com (AdGuard Family)' 'unfiltered.adguard-dns.com (AdGuard Unfiltered)',
'family.adguard-dns.com': 'family.adguard-dns.com (AdGuard Family)',
}; };
export const DIAGNOSTICS_UPDATE_INTERVAL = 10000; // 10 seconds export const DIAGNOSTICS_UPDATE_INTERVAL = 10000; // 10 seconds
@@ -77,15 +83,15 @@ export const DIAGNOSTICS_INITIAL_DELAY = 100; // 100 milliseconds
// Command scheduling intervals in diagnostics (in milliseconds) // Command scheduling intervals in diagnostics (in milliseconds)
export const COMMAND_SCHEDULING = { export const COMMAND_SCHEDULING = {
P0_PRIORITY: 0, // Highest priority (no delay) P0_PRIORITY: 0, // Highest priority (no delay)
P1_PRIORITY: 100, // Very high priority P1_PRIORITY: 100, // Very high priority
P2_PRIORITY: 300, // High priority P2_PRIORITY: 300, // High priority
P3_PRIORITY: 500, // Above average P3_PRIORITY: 500, // Above average
P4_PRIORITY: 700, // Standard priority P4_PRIORITY: 700, // Standard priority
P5_PRIORITY: 900, // Below average P5_PRIORITY: 900, // Below average
P6_PRIORITY: 1100, // Low priority P6_PRIORITY: 1100, // Low priority
P7_PRIORITY: 1300, // Very low priority P7_PRIORITY: 1300, // Very low priority
P8_PRIORITY: 1500, // Background execution P8_PRIORITY: 1500, // Background execution
P9_PRIORITY: 1700, // Idle mode execution P9_PRIORITY: 1700, // Idle mode execution
P10_PRIORITY: 1900 // Lowest priority P10_PRIORITY: 1900, // Lowest priority
}; };

View File

@@ -2,4 +2,4 @@
'require baseclass'; 'require baseclass';
export * from './validators'; export * from './validators';
export * from './constants' export * from './constants';

View File

@@ -2,3 +2,4 @@ export * from './validateIp';
export * from './validateDomain'; export * from './validateDomain';
export * from './validateDns'; export * from './validateDns';
export * from './validateUrl'; export * from './validateUrl';
export * from './validatePath';

View File

@@ -0,0 +1,39 @@
import { describe, expect, it } from 'vitest';
import { validatePath } from '../validatePath';
export const validPaths = [
['Single level', '/etc'],
['Nested path', '/usr/local/bin'],
['With dash', '/var/log/nginx-access'],
['With underscore', '/opt/my_app/config'],
['With numbers', '/data123/files'],
['With dots', '/home/user/.config'],
['Deep nested', '/a/b/c/d/e/f/g'],
];
export const invalidPaths = [
['Empty string', ''],
['Missing starting slash', 'usr/local'],
['Only dot', '.'],
['Space inside', '/path with space'],
['Illegal char', '/path$'],
['Backslash not allowed', '\\windows\\path'],
['Relative path ./', './relative'],
['Relative path ../', '../parent'],
];
describe('validatePath', () => {
describe.each(validPaths)('Valid path: %s', (_desc, path) => {
it(`returns valid=true for "${path}"`, () => {
const res = validatePath(path);
expect(res.valid).toBe(true);
});
});
describe.each(invalidPaths)('Invalid path: %s', (_desc, path) => {
it(`returns valid=false for "${path}"`, () => {
const res = validatePath(path);
expect(res.valid).toBe(false);
});
});
});

View File

@@ -0,0 +1,25 @@
import { ValidationResult } from './types';
export function validatePath(value: string): ValidationResult {
if (!value) {
return {
valid: false,
message: 'Path cannot be empty',
};
}
const pathRegex = /^\/[a-zA-Z0-9_\-/.]+$/;
if (pathRegex.test(value)) {
return {
valid: true,
message: 'Valid',
};
}
return {
valid: false,
message:
'Invalid path format. Path must start with "/" and contain valid characters',
};
}

View File

@@ -409,12 +409,18 @@ function createConfigSection(section, map, network) {
o.rmempty = false; o.rmempty = false;
o.ucisection = s.section; o.ucisection = s.section;
o.validate = function (section_id, value) { o.validate = function (section_id, value) {
if (!value || value.length === 0) return true; // Optional
const pathRegex = /^\/[a-zA-Z0-9_\-\/\.]+$/; if (!value || value.length === 0) {
if (!pathRegex.test(value)) { return true
return _('Invalid path format. Path must start with "/" and contain valid characters');
} }
return true;
const validation = main.validatePath(value);
if (validation.valid) {
return true;
}
return _(validation.message)
}; };
o = s.taboption('basic', form.Flag, 'remote_domain_lists_enabled', _('Remote Domain Lists'), _('Download and use domain lists from remote URLs')); o = s.taboption('basic', form.Flag, 'remote_domain_lists_enabled', _('Remote Domain Lists'), _('Download and use domain lists from remote URLs'));
@@ -453,12 +459,18 @@ function createConfigSection(section, map, network) {
o.rmempty = false; o.rmempty = false;
o.ucisection = s.section; o.ucisection = s.section;
o.validate = function (section_id, value) { o.validate = function (section_id, value) {
if (!value || value.length === 0) return true; // Optional
const pathRegex = /^\/[a-zA-Z0-9_\-\/\.]+$/; if (!value || value.length === 0) {
if (!pathRegex.test(value)) { return true
return _('Invalid path format. Path must start with "/" and contain valid characters');
} }
return true;
const validation = main.validatePath(value);
if (validation.valid) {
return true;
}
return _(validation.message)
}; };
o = s.taboption('basic', form.ListValue, 'user_subnet_list_type', _('User Subnet List Type'), _('Select how to add your custom subnets')); o = s.taboption('basic', form.ListValue, 'user_subnet_list_type', _('User Subnet List Type'), _('Select how to add your custom subnets'));

View File

@@ -59,6 +59,27 @@ function validateUrl(url, protocols = ["http:", "https:"]) {
} }
} }
// src/validators/validatePath.ts
function validatePath(value) {
if (!value) {
return {
valid: false,
message: "Path cannot be empty"
};
}
const pathRegex = /^\/[a-zA-Z0-9_\-/.]+$/;
if (pathRegex.test(value)) {
return {
valid: true,
message: "Valid"
};
}
return {
valid: false,
message: 'Invalid path format. Path must start with "/" and contain valid characters'
};
}
// src/constants.ts // src/constants.ts
var STATUS_COLORS = { var STATUS_COLORS = {
SUCCESS: "#4caf50", SUCCESS: "#4caf50",
@@ -67,7 +88,11 @@ var STATUS_COLORS = {
}; };
var FAKEIP_CHECK_DOMAIN = "fakeip.podkop.fyi"; var FAKEIP_CHECK_DOMAIN = "fakeip.podkop.fyi";
var IP_CHECK_DOMAIN = "ip.podkop.fyi"; var IP_CHECK_DOMAIN = "ip.podkop.fyi";
var REGIONAL_OPTIONS = ["russia_inside", "russia_outside", "ukraine_inside"]; var REGIONAL_OPTIONS = [
"russia_inside",
"russia_outside",
"ukraine_inside"
];
var ALLOWED_WITH_RUSSIA_INSIDE = [ var ALLOWED_WITH_RUSSIA_INSIDE = [
"russia_inside", "russia_inside",
"meta", "meta",
@@ -174,5 +199,6 @@ return baseclass.extend({
validateDNS, validateDNS,
validateDomain, validateDomain,
validateIPV4, validateIPV4,
validatePath,
validateUrl validateUrl
}); });