icydns/src/utility/dns/reader.ts

103 lines
2.5 KiB
TypeScript

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[<keyof typeof 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<DNSZone> {
const lines = await fs.readFile(file, { encoding: 'utf-8' });
const splitLines = lines.split('\n');
return parseZoneFile(splitLines);
}