diff --git a/tests/domains.test.js b/tests/domains.test.js index 14dc45ad6..eddb7833c 100644 --- a/tests/domains.test.js +++ b/tests/domains.test.js @@ -12,10 +12,7 @@ t("Nested subdomains should not exist without a parent subdomain", (t) => { if (subdomain.split(".").length > 1) { const parentSubdomain = subdomain.split(".").pop(); - t.true( - files.includes(`${parentSubdomain}.json`), - `${file}: Parent subdomain does not exist` - ); + t.true(files.includes(`${parentSubdomain}.json`), `${file}: Parent subdomain does not exist`); } }); diff --git a/tests/json.test.js b/tests/json.test.js index 2aec31c87..1a737900e 100644 --- a/tests/json.test.js +++ b/tests/json.test.js @@ -4,25 +4,24 @@ const path = require("path"); const requiredFields = { owner: "object", - record: "object", + record: "object" }; const optionalFields = { proxied: "boolean", - reserved: "boolean", + reserved: "boolean" }; const requiredOwnerFields = { - username: "string", + username: "string" }; const optionalOwnerFields = { - email: "string", + email: "string" }; const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; -const hostnameRegex = - /^(?=.{1,253}$)(?:(?:[_a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)\.)+[a-zA-Z]{2,63}$/; +const hostnameRegex = /^(?=.{1,253}$)(?:(?:[_a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)\.)+[a-zA-Z]{2,63}$/; const domainsPath = path.resolve("domains"); const files = fs.readdirSync(domainsPath); @@ -30,13 +29,9 @@ const files = fs.readdirSync(domainsPath); function validateRequiredFields(t, obj, requiredFields, file) { Object.keys(requiredFields).forEach((key) => { t.true(obj.hasOwnProperty(key), `${file}: Missing required field: ${key}`); - t.is( - typeof obj[key], - requiredFields[key], - `${file}: Field ${key} should be of type ${requiredFields[key]}` - ); + t.is(typeof obj[key], requiredFields[key], `${file}: Field ${key} should be of type ${requiredFields[key]}`); }); -}; +} function validateOptionalFields(t, obj, optionalFields, file) { Object.keys(optionalFields).forEach((key) => { @@ -48,7 +43,7 @@ function validateOptionalFields(t, obj, optionalFields, file) { ); } }); -}; +} t("All files should be valid JSON", (t) => { files.forEach((file) => { @@ -94,11 +89,7 @@ t("All files should have valid optional fields", (t) => { validateOptionalFields(t, data.owner, optionalOwnerFields, file); if (data.owner.email) { - t.regex( - data.owner.email, - emailRegex, - `${file}: Owner email should be a valid email address` - ); + t.regex(data.owner.email, emailRegex, `${file}: Owner email should be a valid email address`); } }); }); diff --git a/tests/proxy.test.js b/tests/proxy.test.js index 89b35eb07..7e8299c70 100644 --- a/tests/proxy.test.js +++ b/tests/proxy.test.js @@ -2,11 +2,8 @@ const t = require("ava"); const fs = require("fs-extra"); const path = require("path"); -const requiredRecordsToProxy = [ - "A", - "AAAA", - "CNAME" -]; +const requiredRecordsToProxy = ["A", "AAAA", "CNAME"]; +// URL records are not listed here because they are proxied by default, so they don't need the proxied flag function validateProxiedRecords(t, data, file) { if (data.proxied) { @@ -14,7 +11,7 @@ function validateProxiedRecords(t, data, file) { t.true(hasProxiedRecord, `${file}: Proxied is true but there are no records that can be proxied`); } -}; +} const domainsPath = path.resolve("domains"); const files = fs.readdirSync(domainsPath); @@ -24,5 +21,5 @@ t("Domains with proxy enabled should have have at least one record that can be p const domain = fs.readJsonSync(path.join(domainsPath, file)); validateProxiedRecords(t, domain, file); - }) + }); }); diff --git a/tests/records.test.js b/tests/records.test.js index 7cfd618a8..56039e741 100644 --- a/tests/records.test.js +++ b/tests/records.test.js @@ -14,6 +14,80 @@ const ipv6Regex = const domainsPath = path.resolve("domains"); const files = fs.readdirSync(domainsPath); +function expandIPv6(ip) { + // Split into segments by ":" + let segments = ip.split(":"); + + // Count the number of segments that are empty due to "::" shorthand + const emptyIndex = segments.indexOf(""); + if (emptyIndex !== -1) { + // Calculate how many "0000" segments are missing + const nonEmptySegments = segments.filter((seg) => seg !== ""); + const missingSegments = 8 - nonEmptySegments.length; + + // Insert the missing "0000" segments into the position of the empty segment + segments = [ + ...nonEmptySegments.slice(0, emptyIndex), + ...Array(missingSegments).fill("0000"), + ...nonEmptySegments.slice(emptyIndex) + ]; + } + + // Expand each segment to 4 characters, padding with leading zeros + const expandedSegments = segments.map((segment) => segment.padStart(4, "0")); + + // Join the segments back together + return expandedSegments.join(":"); +} + +function isPublicIPv4(ip, proxied) { + const parts = ip.split(".").map(Number); + + // Validate IPv4 address format + if (parts.length !== 4 || parts.some((part) => isNaN(part) || part < 0 || part > 255)) { + return false; + } + + // Exception for 192.0.2.1, assuming the domain is proxied + if (ip === "192.0.2.1" && proxied) { + return true; + } + + // Check for private and reserved IPv4 ranges + return !( + // Private ranges + ( + parts[0] === 10 || + (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) || + (parts[0] === 192 && parts[1] === 168) || + // Reserved or special-use ranges + (parts[0] === 100 && parts[1] >= 64 && parts[1] <= 127) || // Carrier-grade NAT + (parts[0] === 169 && parts[1] === 254) || // Link-local + (parts[0] === 192 && parts[1] === 0 && parts[2] === 0) || // IETF Protocol Assignments + (parts[0] === 192 && parts[1] === 0 && parts[2] === 2) || // Documentation (TEST-NET-1) + (parts[0] === 198 && parts[1] === 18) || // Network Interconnect Devices + (parts[0] === 198 && parts[1] === 51 && parts[2] === 100) || // Documentation (TEST-NET-2) + (parts[0] === 203 && parts[1] === 0 && parts[2] === 113) || // Documentation (TEST-NET-3) + parts[0] >= 224 + ) // Multicast and reserved ranges + ); +} + +function isPublicIPv6(ip) { + const normalizedIP = ip.toLowerCase(); + + // Check for private or special-use IPv6 ranges + return !( + ( + normalizedIP.startsWith("fc") || // Unique Local Address (ULA) + normalizedIP.startsWith("fd") || // Unique Local Address (ULA) + normalizedIP.startsWith("fe80") || // Link-local + normalizedIP.startsWith("::1") || // Loopback address (::1) + normalizedIP.startsWith("2001:db8") + ) // Documentation range + ); +} + t("All files should have valid record types", (t) => { files.forEach((file) => { const data = fs.readJsonSync(path.join(domainsPath, file)); diff --git a/utils/functions.js b/utils/functions.js deleted file mode 100644 index 85a49a005..000000000 --- a/utils/functions.js +++ /dev/null @@ -1,66 +0,0 @@ -module.exports.expandIPv6 = function (ip) { - // Split into segments by ":" - let segments = ip.split(":"); - - // Count the number of segments that are empty due to "::" shorthand - const emptyIndex = segments.indexOf(""); - if (emptyIndex !== -1) { - // Calculate how many "0000" segments are missing - const nonEmptySegments = segments.filter((seg) => seg !== ""); - const missingSegments = 8 - nonEmptySegments.length; - - // Insert the missing "0000" segments into the position of the empty segment - segments = [...nonEmptySegments.slice(0, emptyIndex), ...Array(missingSegments).fill("0000"), ...nonEmptySegments.slice(emptyIndex)]; - } - - // Expand each segment to 4 characters, padding with leading zeros - const expandedSegments = segments.map((segment) => segment.padStart(4, "0")); - - // Join the segments back together - return expandedSegments.join(":"); -}; - -module.exports.isPublicIPv4 = function (ip, proxied) { - const parts = ip.split('.').map(Number); - - // Validate IPv4 address format - if (parts.length !== 4 || parts.some(part => isNaN(part) || part < 0 || part > 255)) { - return false; - } - - // Exception for 192.0.2.1, assuming the domain is proxied - if (ip === "192.0.2.1" && proxied) { - return true; - } - - // Check for private and reserved IPv4 ranges - return !( - // Private ranges - parts[0] === 10 || - (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) || - (parts[0] === 192 && parts[1] === 168) || - - // Reserved or special-use ranges - (parts[0] === 100 && parts[1] >= 64 && parts[1] <= 127) || // Carrier-grade NAT - (parts[0] === 169 && parts[1] === 254) || // Link-local - (parts[0] === 192 && parts[1] === 0 && parts[2] === 0) || // IETF Protocol Assignments - (parts[0] === 192 && parts[1] === 0 && parts[2] === 2) || // Documentation (TEST-NET-1) - (parts[0] === 198 && parts[1] === 18) || // Network Interconnect Devices - (parts[0] === 198 && parts[1] === 51 && parts[2] === 100) || // Documentation (TEST-NET-2) - (parts[0] === 203 && parts[1] === 0 && parts[2] === 113) || // Documentation (TEST-NET-3) - (parts[0] >= 224) // Multicast and reserved ranges - ); -}; - -module.exports.isPublicIPv6 = function (ip) { - const normalizedIP = ip.toLowerCase(); - - // Check for private or special-use IPv6 ranges - return !( - normalizedIP.startsWith("fc") || // Unique Local Address (ULA) - normalizedIP.startsWith("fd") || // Unique Local Address (ULA) - normalizedIP.startsWith("fe80") || // Link-local - normalizedIP.startsWith("::1") || // Loopback address (::1) - normalizedIP.startsWith("2001:db8") // Documentation range - ); -};