import * as fs from 'fs/promises'; import { DNSRecordType } from 'src/types/dns.enum'; import { DNSRecord, SOARecord, DNSZone } from 'src/types/dns.interfaces'; function cleanString(str: string): string { return str .replace(/;[^"]+$/, '') // Remove comments from the end .replace(/\s+/g, ' ') // Remove duplicate whitespace .trim(); // Remove whitespace from start and end } function parseRecordLine( line: string, index: number, lines: string[], ): DNSRecord | SOARecord | null { let actualLine = ''; const clean = cleanString(line); if (clean.includes('(')) { actualLine += clean; const trimLines = lines.slice(index + 1); for (const trimLine of trimLines) { actualLine += ' ' + cleanString(trimLine); if (trimLine.includes(')')) { break; } } actualLine = cleanString(actualLine.replace(/\(|\)/g, '')); } else { actualLine = clean; } const split = actualLine.replace(/"\s"/g, '').split(' '); if (split[0] === 'IN' && split[1] === 'NS') { return { name: '', type: DNSRecordType.NS, value: split.slice(2).join(' '), }; } if (split[2] === 'SOA') { return { name: split[0], type: DNSRecordType.SOA, value: split.slice(3).join(' '), nameserver: split[3], email: split[4], serial: parseInt(split[5], 10), refresh: parseInt(split[6], 10), retry: parseInt(split[7], 10), expire: parseInt(split[8], 10), minimum: parseInt(split[9], 10), }; } if (!actualLine.includes('IN')) { return null; } return { name: split[0], type: DNSRecordType[split[2]], value: split.slice(3).join(' '), }; } export function parseZoneFile(lines: string[]): DNSZone { let ttl = 0; const includes = []; const records: DNSRecord[] = []; for (const index in lines) { const line = lines[index]; if (line.startsWith('$TTL')) { ttl = parseInt(line.split(' ')[1], 10); continue; } if (line.startsWith('$INCLUDE')) { includes.push(...line.split(' ').slice(1)); continue; } const record = parseRecordLine(line, parseInt(index, 10), lines); if (record) { records.push(record); } } return { ttl, includes, records, }; } export async function readZoneFile(file: string): Promise { const lines = await fs.readFile(file, { encoding: 'utf-8' }); const splitLines = lines.split('\n'); return parseZoneFile(splitLines); }