feat: implement validateSubnet

This commit is contained in:
divocat
2025-10-03 00:59:24 +03:00
parent 77e141b305
commit 547feb0e06
5 changed files with 121 additions and 15 deletions

View File

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

View File

@@ -0,0 +1,41 @@
import { describe, it, expect } from 'vitest';
import { validateSubnet } from '../validateSubnet';
export const validSubnets = [
['Simple IP', '192.168.1.1'],
['With CIDR /24', '192.168.1.1/24'],
['CIDR /0', '10.0.0.1/0'],
['CIDR /32', '172.16.0.1/32'],
['Loopback', '127.0.0.1'],
['Broadcast with mask', '255.255.255.255/32'],
];
export const invalidSubnets = [
['Empty string', ''],
['Bad format letters', 'abc.def.ghi.jkl'],
['Octet too large', '300.1.1.1'],
['Negative octet', '-1.2.3.4'],
['Too many octets', '1.2.3.4.5'],
['Not enough octets', '192.168.1'],
['Leading zero octet', '01.2.3.4'],
['Invalid CIDR (too high)', '192.168.1.1/33'],
['Invalid CIDR (negative)', '192.168.1.1/-1'],
['CIDR not number', '192.168.1.1/abc'],
['Forbidden 0.0.0.0', '0.0.0.0'],
];
describe('validateSubnet', () => {
describe.each(validSubnets)('Valid subnet: %s', (_desc, subnet) => {
it(`returns {valid:true} for "${subnet}"`, () => {
const res = validateSubnet(subnet);
expect(res.valid).toBe(true);
});
});
describe.each(invalidSubnets)('Invalid subnet: %s', (_desc, subnet) => {
it(`returns {valid:false} for "${subnet}"`, () => {
const res = validateSubnet(subnet);
expect(res.valid).toBe(false);
});
});
});

View File

@@ -0,0 +1,39 @@
import { ValidationResult } from './types.js';
import { validateIPV4 } from './validateIp';
export function validateSubnet(value: string): ValidationResult {
// Must be in form X.X.X.X or X.X.X.X/Y
const subnetRegex = /^(\d{1,3}\.){3}\d{1,3}(?:\/\d{1,2})?$/;
if (!subnetRegex.test(value)) {
return {
valid: false,
message: 'Invalid format. Use X.X.X.X or X.X.X.X/Y',
};
}
const [ip, cidr] = value.split('/');
if (ip === '0.0.0.0') {
return { valid: false, message: 'IP address 0.0.0.0 is not allowed' };
}
const ipCheck = validateIPV4(ip);
if (!ipCheck.valid) {
return ipCheck;
}
// Validate CIDR if present
if (cidr) {
const cidrNum = parseInt(cidr, 10);
if (cidrNum < 0 || cidrNum > 32) {
return {
valid: false,
message: 'CIDR must be between 0 and 32',
};
}
}
return { valid: true, message: 'Valid' };
}

View File

@@ -508,23 +508,18 @@ function createConfigSection(section, map, network) {
o.rmempty = false;
o.ucisection = s.section;
o.validate = function (section_id, value) {
if (!value || value.length === 0) return true;
const subnetRegex = /^(\d{1,3}\.){3}\d{1,3}(\/\d{1,2})?$/;
if (!subnetRegex.test(value)) return _('Invalid format. Use format: X.X.X.X or X.X.X.X/Y');
const [ip, cidr] = value.split('/');
if (ip === "0.0.0.0") {
return _('IP address 0.0.0.0 is not allowed');
}
const ipParts = ip.split('.');
for (const part of ipParts) {
const num = parseInt(part);
if (num < 0 || num > 255) return _('IP address parts must be between 0 and 255');
}
if (cidr !== undefined) {
const cidrNum = parseInt(cidr);
if (cidrNum < 0 || cidrNum > 32) return _('CIDR must be between 0 and 32');
// Optional
if (!value || value.length === 0) {
return true
}
const validation = main.validateSubnet(value);
if (validation.valid) {
return true;
}
return _(validation.message)
};
o = s.taboption('basic', form.TextValue, 'user_subnets_text', _('User Subnets List'), _('Enter subnets in CIDR notation or single IP addresses, separated by comma, space or newline. You can add comments after //'));

View File

@@ -80,6 +80,35 @@ function validatePath(value) {
};
}
// src/validators/validateSubnet.ts
function validateSubnet(value) {
const subnetRegex = /^(\d{1,3}\.){3}\d{1,3}(?:\/\d{1,2})?$/;
if (!subnetRegex.test(value)) {
return {
valid: false,
message: "Invalid format. Use X.X.X.X or X.X.X.X/Y"
};
}
const [ip, cidr] = value.split("/");
if (ip === "0.0.0.0") {
return { valid: false, message: "IP address 0.0.0.0 is not allowed" };
}
const ipCheck = validateIPV4(ip);
if (!ipCheck.valid) {
return ipCheck;
}
if (cidr) {
const cidrNum = parseInt(cidr, 10);
if (cidrNum < 0 || cidrNum > 32) {
return {
valid: false,
message: "CIDR must be between 0 and 32"
};
}
}
return { valid: true, message: "Valid" };
}
// src/constants.ts
var STATUS_COLORS = {
SUCCESS: "#4caf50",
@@ -218,5 +247,6 @@ return baseclass.extend({
validateDomain,
validateIPV4,
validatePath,
validateSubnet,
validateUrl
});