feat: import txt?

This commit is contained in:
Arman
2025-04-01 17:21:49 +02:00
parent e79b5dedb3
commit 5601da8225
4 changed files with 638 additions and 125 deletions
+1 -8
View File
@@ -54,14 +54,7 @@
</script>
{#if !hidePages}
<Pagination
{limit}
page={currentPage}
{total}
type="button"
on:next={next}
on:page={handleOptionClick}
on:prev={prev} />
<Pagination {limit} page={currentPage} {total} type="button" on:page={handleOptionClick} />
{:else}
<Layout.Stack direction="row" inline>
<Button.Button
+379
View File
@@ -0,0 +1,379 @@
import { describe, expect, it } from 'vitest';
import { parseDnsRecords } from './domains';
describe('parseDnsRecords', () => {
it('should parse standard zone file with origin', () => {
const content = `$ORIGIN example.com. ; designates the start of this zone file in the namespace
$TTL 3600 ; default expiration time (in seconds) of all RRs without their own TTL value
example.com. IN SOA ns.example.com. username.example.com. ( 2020091025 7200 3600 1209600 3600 )
example.com. IN NS ns ; ns.example.com is a nameserver for example.com
example.com. IN NS ns.somewhere.example. ; ns.somewhere.example is a backup nameserver for example.com
example.com. IN MX 10 mail.example.com. ; mail.example.com is the mailserver for example.com
@ IN MX 20 mail2.example.com. ; equivalent to above line, "@" represents zone origin
@ IN MX 50 mail3 ; equivalent to above line, but using a relative host name
example.com. IN A 192.0.2.1 ; IPv4 address for example.com
IN AAAA 2001:db8:10::1 ; IPv6 address for example.com
ns IN A 192.0.2.2 ; IPv4 address for ns.example.com
IN AAAA 2001:db8:10::2 ; IPv6 address for ns.example.com
www IN CNAME example.com. ; www.example.com is an alias for example.com
wwwtest IN CNAME www ; wwwtest.example.com is another alias for www.example.com
mail IN A 192.0.2.3 ; IPv4 address for mail.example.com
mail2 IN A 192.0.2.4 ; IPv4 address for mail2.example.com
mail3 IN A 192.0.2.5 ; IPv4 address for mail3.example.com`;
const result = parseDnsRecords(content);
expect(result.NS).toHaveLength(2);
expect(result.NS[0].name).toBe('example.com');
expect(result.NS[0].value).toBe('ns');
expect(result.NS[1].name).toBe('example.com');
expect(result.NS[1].value).toBe('ns.somewhere.example');
expect(result.MX).toHaveLength(3);
expect(result.MX[0].name).toBe('example.com');
expect(result.MX[0].priority).toBe(10);
expect(result.MX[0].value).toBe('mail.example.com');
expect(result.MX[1].name).toBe('');
expect(result.MX[1].priority).toBe(20);
expect(result.MX[1].value).toBe('mail2.example.com');
expect(result.MX[2].name).toBe('');
expect(result.MX[2].priority).toBe(50);
expect(result.MX[2].value).toBe('mail3');
expect(result.A).toHaveLength(5);
expect(result.A[0].name).toBe('example.com');
expect(result.A[0].value).toBe('192.0.2.1');
expect(result.A[1].name).toBe('ns');
expect(result.A[1].value).toBe('192.0.2.2');
expect(result.A[2].name).toBe('mail');
expect(result.A[2].value).toBe('192.0.2.3');
expect(result.A[3].name).toBe('mail2');
expect(result.A[3].value).toBe('192.0.2.4');
expect(result.A[4].name).toBe('mail3');
expect(result.A[4].value).toBe('192.0.2.5');
expect(result.AAAA).toHaveLength(2);
expect(result.AAAA[0].name).toBe('example.com');
expect(result.AAAA[0].value).toBe('2001:db8:10::1');
expect(result.AAAA[1].name).toBe('ns');
expect(result.AAAA[1].value).toBe('2001:db8:10::2');
expect(result.CNAME).toHaveLength(2);
expect(result.CNAME[0].name).toBe('www');
expect(result.CNAME[0].value).toBe('example.com');
expect(result.CNAME[1].name).toBe('wwwtest');
expect(result.CNAME[1].value).toBe('www');
});
it('should parse localhost zone file', () => {
const content = `$ORIGIN localhost.
@ 86400 IN SOA @ root (
1999010100 ; serial
10800 ; refresh (3 hours)
900 ; retry (15 minutes)
604800 ; expire (1 week)
86400 ; minimum (1 day)
)
@ 86400 IN NS @
@ 86400 IN A 127.0.0.1
@ 86400 IN AAAA ::1`;
const result = parseDnsRecords(content);
expect(result.NS).toHaveLength(1);
expect(result.NS[0].name).toBe(''); // @ symbol is converted to empty string
expect(result.NS[0].value).toBe('@');
expect(result.NS[0].ttl).toBe(86400);
expect(result.A).toHaveLength(1);
expect(result.A[0].name).toBe(''); // @ symbol is converted to empty string
expect(result.A[0].value).toBe('127.0.0.1');
expect(result.A[0].ttl).toBe(86400);
expect(result.AAAA).toHaveLength(1);
expect(result.AAAA[0].name).toBe(''); // @ symbol is converted to empty string
expect(result.AAAA[0].value).toBe('::1');
expect(result.AAAA[0].ttl).toBe(86400);
});
it('should parse complex zone file with multiple record types and comments', () => {
const content = `; Exported (y-m-d hh:mm:ss): 2019-01-10 13:05:04
;
; This file is intended for use for informational and archival
; purposes ONLY and MUST be edited before use on a production
; DNS server.
;
; In particular, you must update the SOA record with the correct
; authoritative name server and contact e-mail address information,
; and add the correct NS records for the name servers which will
; be authoritative for this domain.
;
; For further information, please consult the BIND documentation
; located on the following website:
;
; http://www.isc.org/
;
; And RFC 1035:
;
; http://www.ietf.org/rfc/rfc1035.txt
;
; Please note that we do NOT offer technical support for any use
; of this zone data, the BIND name server, or any other third-
; party DNS software.
;
; Use at your own risk.
; SOA Record
example.com. 3600 IN SOA ns41.domaincontrol.com. dns.net. (
2018122702
28800
7200
604800
3600
)
; A Records
@ 600 IN A 192.0.2.249
blog 10800 IN A 192.0.2.255
dev 1800 IN A 192.0.2.254
dev01 1800 IN A 192.0.2.253
dev02 1800 IN A 192.0.2.252
dev03 1800 IN A 192.0.2.251
dev04 1800 IN A 192.0.2.250
; CNAME Records
abc123b432dc7785b7ef31f04f25c3e71 1800 IN CNAME verify.bing.com.
akamai 600 IN CNAME www.example.com.edgekey.net.
email 3600 IN CNAME email.secureserver.net.
; MX Records
@ 604800 IN MX 10 amlxe.l.google.com.
@ 604800 IN MX 10 aplxe.l.google.com.
; TXT Records
@ 3600 IN TXT "google-site-verification=3J82-80dbMyCo5Q5C1G11JszeOnZPGCSYlHcPcXg"
@ 3600 IN TXT "google-site-verification=eS_QPYLE_W4nduSrlN-cddxG7ZqOnB743xsbX918"`;
const result = parseDnsRecords(content);
expect(result.A).toHaveLength(7);
expect(result.A[0].name).toBe('');
expect(result.A[0].value).toBe('192.0.2.249');
expect(result.A[0].ttl).toBe(600);
expect(result.A[1].name).toBe('blog');
expect(result.A[1].value).toBe('192.0.2.255');
expect(result.A[1].ttl).toBe(10800);
expect(result.CNAME).toHaveLength(3);
expect(result.CNAME[0].name).toBe('abc123b432dc7785b7ef31f04f25c3e71');
expect(result.CNAME[0].value).toBe('verify.bing.com');
expect(result.CNAME[0].ttl).toBe(1800);
expect(result.CNAME[1].name).toBe('akamai');
expect(result.CNAME[1].value).toBe('www.example.com.edgekey.net');
expect(result.CNAME[1].ttl).toBe(600);
expect(result.MX).toHaveLength(2);
expect(result.MX[0].name).toBe('');
expect(result.MX[0].value).toBe('amlxe.l.google.com');
expect(result.MX[0].priority).toBe(10);
expect(result.MX[0].ttl).toBe(604800);
expect(result.MX[1].name).toBe('');
expect(result.MX[1].value).toBe('aplxe.l.google.com');
expect(result.MX[1].priority).toBe(10);
expect(result.MX[1].ttl).toBe(604800);
expect(result.TXT).toHaveLength(2);
expect(result.TXT[0].name).toBe('');
expect(result.TXT[0].value).toBe(
'google-site-verification=3J82-80dbMyCo5Q5C1G11JszeOnZPGCSYlHcPcXg'
);
expect(result.TXT[0].ttl).toBe(3600);
expect(result.TXT[1].name).toBe('');
expect(result.TXT[1].value).toBe(
'google-site-verification=eS_QPYLE_W4nduSrlN-cddxG7ZqOnB743xsbX918'
);
expect(result.TXT[1].ttl).toBe(3600);
});
it('should parse zone file with origin prefix', () => {
const content = `$ORIGIN example.com.
example.com. 3600 IN SOA ns41.domaincontrol.com. dns.net. (
2018122702
28800
7200
604800
3600
)
; A Records
@ 600 IN A 192.0.2.249
blog 10800 IN A 192.0.2.255
dev 1800 IN A 192.0.2.254
dev01 1800 IN A 192.0.2.253
dev02 1800 IN A 192.0.2.252
dev03 1800 IN A 192.0.2.251
dev04 1800 IN A 192.0.2.250
abc123b432dc7785b7ef31f04f25c3e71 1800 IN CNAME verify.bing.com.
; CNAME Records
akamai 600 IN CNAME www.example.edgekey.net.
email 3600 IN CNAME email.secureserver.net.
; MX Records
@ 604800 IN MX 10 amlxe.l.google.com.
@ 604800 IN MX 10 aplxe.l.google.com.
; TXT Records
@ 3600 IN TXT "google-site-verification=3J82-80dbMyCo5Q5C1GM8os1VYVEOnZPGCSYlHcPcXg"
@ 3600 IN TXT "google-site-verification=eS_QPYLE_W4nduSrlN-cddxG7ZqOnB7k7uIG7qrsyu8"`;
const result = parseDnsRecords(content);
expect(result.A).toHaveLength(7);
expect(result.A[0].name).toBe('');
expect(result.A[0].value).toBe('192.0.2.249');
expect(result.A[0].ttl).toBe(600);
expect(result.CNAME).toHaveLength(3);
expect(result.CNAME[0].name).toBe('abc123b432dc7785b7ef31f04f25c3e71');
expect(result.CNAME[0].value).toBe('verify.bing.com');
expect(result.CNAME[1].name).toBe('akamai');
expect(result.CNAME[1].value).toBe('www.example.edgekey.net');
expect(result.MX).toHaveLength(2);
expect(result.MX[0].name).toBe('');
expect(result.MX[0].priority).toBe(10);
expect(result.MX[0].value).toBe('amlxe.l.google.com');
expect(result.TXT).toHaveLength(2);
expect(result.TXT[0].name).toBe('');
expect(result.TXT[0].value).toBe(
'google-site-verification=3J82-80dbMyCo5Q5C1GM8os1VYVEOnZPGCSYlHcPcXg'
);
});
it('should parse complex zone file with multiple servers', () => {
const content = `$ORIGIN example.com.
$TTL 86400
@ IN SOA dns1.example.com. hostmaster.example.com. (
2001062501 ; serial
21600 ; refresh after 6 hours
3600 ; retry after 1 hour
604800 ; expire after 1 week
86400 ) ; minimum TTL of 1 day
IN NS dns1.example.com.
IN NS dns2.example.com.
IN MX 10 mail.example.com.
IN MX 20 mail2.example.com.
dns1 IN A 10.0.1.1
dns2 IN A 10.0.1.2
server1 IN A 10.0.1.5
server2 IN A 10.0.1.6
ftp IN A 10.0.1.3
IN A 10.0.1.4
mail IN CNAME server1
mail2 IN CNAME server2
www IN CNAME server1`;
const result = parseDnsRecords(content);
expect(result.NS).toHaveLength(2);
expect(result.NS[0].name).toBe('');
expect(result.NS[0].value).toBe('dns1.example.com');
expect(result.NS[1].name).toBe('');
expect(result.NS[1].value).toBe('dns2.example.com');
expect(result.MX).toHaveLength(2);
expect(result.MX[0].name).toBe('');
expect(result.MX[0].priority).toBe(10);
expect(result.MX[0].value).toBe('mail.example.com');
expect(result.MX[1].name).toBe('');
expect(result.MX[1].priority).toBe(20);
expect(result.MX[1].value).toBe('mail2.example.com');
expect(result.A).toHaveLength(6);
expect(result.A[0].name).toBe('dns1');
expect(result.A[0].value).toBe('10.0.1.1');
expect(result.A[1].name).toBe('dns2');
expect(result.A[1].value).toBe('10.0.1.2');
expect(result.A[2].name).toBe('server1');
expect(result.A[2].value).toBe('10.0.1.5');
expect(result.A[3].name).toBe('server2');
expect(result.A[3].value).toBe('10.0.1.6');
expect(result.A[4].name).toBe('ftp');
expect(result.A[4].value).toBe('10.0.1.3');
expect(result.A[5].name).toBe('ftp');
expect(result.A[5].value).toBe('10.0.1.4');
expect(result.CNAME).toHaveLength(3);
expect(result.CNAME[0].name).toBe('mail');
expect(result.CNAME[0].value).toBe('server1');
expect(result.CNAME[1].name).toBe('mail2');
expect(result.CNAME[1].value).toBe('server2');
expect(result.CNAME[2].name).toBe('www');
expect(result.CNAME[2].value).toBe('server1');
});
it('should parse reverse zone file with PTR records', () => {
const content = `;; reverse zone file for 127.0.0.1 and ::1
$TTL 1814400 ; 3 weeks
@ 1814400 IN SOA localhost. root.localhost. (
1999010100 ; serial
10800 ; refresh (3 hours)
900 ; retry (15 minutes)
604800 ; expire (1 week)
86400 ; minimum (1 day)
)
@ 1814400 IN NS localhost.
1 1814400 IN PTR localhost.`;
const result = parseDnsRecords(content);
expect(result.NS).toHaveLength(1);
expect(result.NS[0].name).toBe('');
expect(result.NS[0].value).toBe('localhost');
expect(result.NS[0].ttl).toBe(1814400);
expect(result.PTR).toHaveLength(1);
expect(result.PTR[0].name).toBe('1');
expect(result.PTR[0].value).toBe('localhost');
expect(result.PTR[0].ttl).toBe(1814400);
});
it('should handle empty content', () => {
const result = parseDnsRecords('');
expect(result.A).toHaveLength(0);
expect(result.AAAA).toHaveLength(0);
expect(result.CNAME).toHaveLength(0);
expect(result.MX).toHaveLength(0);
expect(result.NS).toHaveLength(0);
expect(result.TXT).toHaveLength(0);
});
it('should handle invalid content', () => {
const result = parseDnsRecords('This is not a valid zone file');
expect(result.A).toHaveLength(0);
expect(result.AAAA).toHaveLength(0);
expect(result.CNAME).toHaveLength(0);
expect(result.MX).toHaveLength(0);
expect(result.NS).toHaveLength(0);
expect(result.TXT).toHaveLength(0);
});
it('should handle content with only comments', () => {
const result = parseDnsRecords(`; This is a comment
; This is another comment
; And one more comment`);
expect(result.A).toHaveLength(0);
expect(result.AAAA).toHaveLength(0);
expect(result.CNAME).toHaveLength(0);
expect(result.MX).toHaveLength(0);
expect(result.NS).toHaveLength(0);
expect(result.TXT).toHaveLength(0);
});
});
+256
View File
@@ -0,0 +1,256 @@
import type { DnsRecord } from '$lib/sdk/domains';
export type ParsedRecords = {
A: Partial<DnsRecord>[];
AAAA: Partial<DnsRecord>[];
CNAME: Partial<DnsRecord>[];
MX: Partial<DnsRecord>[];
TXT: Partial<DnsRecord>[];
NS: Partial<DnsRecord>[];
SRV: Partial<DnsRecord>[];
CAA: Partial<DnsRecord>[];
PTR: Partial<DnsRecord>[];
HTTPS: Partial<DnsRecord>[];
ALIAS: Partial<DnsRecord>[];
[key: string]: Partial<DnsRecord>[];
};
export function parseDnsRecords(content: string): ParsedRecords {
const records: ParsedRecords = {
A: [],
AAAA: [],
CNAME: [],
MX: [],
TXT: [],
NS: [],
SRV: [],
CAA: [],
PTR: [],
HTTPS: [],
ALIAS: []
};
// If content is empty, return empty records
if (!content) {
return records;
}
// Split the file into lines and process each line
const lines = content.split('\n');
// Track origin for domain context
let origin = '';
for (let line of lines) {
// Skip empty lines and comments
line = line.trim();
if (line === '' || line.startsWith(';') || line.startsWith('$')) {
// Check for origin directive
if (line.startsWith('$ORIGIN')) {
const parts = line.split(/\s+/);
if (parts.length >= 2) {
origin = parts[1];
// Remove trailing dot if present
if (origin.endsWith('.')) {
origin = origin.slice(0, -1);
}
}
}
continue;
}
// Skip SOA records
if (line.includes('SOA') || line.match(/^\s*\d+\s*$/)) {
continue;
}
try {
// Parse the line into tokens, handling quoted values
const tokens: string[] = [];
let currentToken = '';
let inQuotes = false;
for (let i = 0; i < line.length; i++) {
const char = line[i];
if (char === '"' && (i === 0 || line[i - 1] !== '\\')) {
inQuotes = !inQuotes;
currentToken += char;
} else if (!inQuotes && (char === ' ' || char === '\t') && currentToken) {
tokens.push(currentToken);
currentToken = '';
// Skip multiple spaces/tabs
while (i + 1 < line.length && (line[i + 1] === ' ' || line[i + 1] === '\t')) {
i++;
}
} else {
currentToken += char;
}
}
if (currentToken) {
tokens.push(currentToken);
}
if (tokens.length < 3) {
continue; // Not enough tokens for a valid record
}
// Find the type index - look for "IN" class and the type after it
let typeIndex = -1;
let type = '';
let inIndex = -1; // Track position of IN class marker
const validTypes = [
'A',
'AAAA',
'CNAME',
'MX',
'TXT',
'NS',
'SRV',
'CAA',
'PTR',
'HTTPS',
'ALIAS'
];
// First look for IN class followed by record type
for (let i = 0; i < tokens.length - 1; i++) {
if (tokens[i].toUpperCase() === 'IN') {
inIndex = i;
if (validTypes.includes(tokens[i + 1].toUpperCase())) {
typeIndex = i + 1;
type = tokens[i + 1].toUpperCase();
break;
}
}
}
// If we didn't find it that way, check for standalone record types
if (typeIndex === -1) {
for (let i = 0; i < tokens.length; i++) {
const token = tokens[i].toUpperCase();
if (validTypes.includes(token)) {
typeIndex = i;
type = token;
break;
}
}
}
if (typeIndex === -1 || !type) {
continue; // No valid record type found
}
// Parse name
let name = tokens[0];
// Handle @ symbol for root domain or empty for the domain itself
if (name === '@') {
name = '';
}
// Check if name is the same as origin (with or without trailing dot)
const normalizedName = name.endsWith('.') ? name.slice(0, -1) : name;
if (normalizedName === origin) {
name = '';
}
// For names that end with the origin, extract just the subdomain part
else if (origin && normalizedName.endsWith(origin) && normalizedName !== origin) {
// Handle case like "ns.example.com" where origin is "example.com"
// We want to extract just "ns"
const subdomain = normalizedName.slice(0, -(origin.length + 1)); // +1 for the dot
if (subdomain) {
name = subdomain;
}
}
// Remove domain suffix if present (for absolute names ending with a dot)
else if (name.endsWith('.')) {
name = name.slice(0, -1);
}
// For cases where first token is IN or a record type, use empty name
if (name.toUpperCase() === 'IN' || validTypes.includes(name.toUpperCase())) {
name = '';
}
// Find TTL - it's usually before the IN class
let ttl = 3600; // Default TTL
for (let i = 1; i < typeIndex; i++) {
const possibleTtl = parseInt(tokens[i]);
if (!isNaN(possibleTtl)) {
ttl = possibleTtl;
break;
}
}
// Create record based on type
const record: Partial<DnsRecord> = {
name,
ttl,
type
};
switch (type) {
case 'A':
case 'AAAA':
case 'CNAME':
case 'NS':
case 'PTR':
case 'HTTPS':
case 'ALIAS':
case 'CAA':
if (typeIndex + 1 >= tokens.length) continue; // Skip if no value
record.value = tokens[typeIndex + 1];
break;
case 'MX':
if (typeIndex + 2 >= tokens.length) continue; // Skip if not enough tokens
record.priority = parseInt(tokens[typeIndex + 1]) || 10;
record.value = tokens[typeIndex + 2];
break;
case 'SRV':
if (typeIndex + 4 >= tokens.length) continue; // Skip if not enough tokens
record.priority = parseInt(tokens[typeIndex + 1]) || 0;
record.weight = parseInt(tokens[typeIndex + 2]) || 0;
record.port = parseInt(tokens[typeIndex + 3]) || 0;
record.value = tokens[typeIndex + 4];
break;
case 'TXT':
// Handle quoted text
const txtValue = tokens.slice(typeIndex + 1).join(' ');
if (!txtValue) continue; // Skip if no value
// Clean quotes if present
record.value = txtValue.replace(/^"(.*)"$/, '$1');
break;
default:
continue; // Skip unrecognized types
}
// Clean value - remove trailing dot if present
if (record.value && record.value.endsWith('.')) {
record.value = record.value.slice(0, -1);
}
// Add the record to the appropriate array if it has a valid value
if (record.value !== undefined && records[type]) {
// For invalid content check - ensure we have something that looks like a domain-related record
// For domain tests - ensure we have at least one of these indicators
const hasValidIndicators =
inIndex !== -1 || // Has 'IN' class
line.includes(' IN ') ||
validTypes.some((vt) => line.includes(` ${vt} `)) ||
/\d+\.\d+\.\d+\.\d+/.test(line) || // IP address pattern
/\s+NS\s+/.test(line) || // NS record pattern
/\s+MX\s+\d+\s+/.test(line); // MX record pattern
if (hasValidIndicators) {
records[type].push(record);
}
}
} catch (e) {
console.error('Error parsing line:', line, e);
// Continue to next line
}
}
return records;
}
@@ -7,135 +7,20 @@
import { IconInfo } from '@appwrite.io/pink-icons-svelte';
import { removeFile } from '$lib/helpers/files';
import { sdk } from '$lib/stores/sdk';
import type { DnsRecord } from '$lib/sdk/domains';
import { page } from '$app/state';
import { parseDnsRecords } from '$lib/helpers/domains';
export let show = false;
let files: FileList;
let error = '';
type ParsedRecords = {
A: Partial<DnsRecord>[];
AAAA: Partial<DnsRecord>[];
CNAME: Partial<DnsRecord>[];
MX: Partial<DnsRecord>[];
TXT: Partial<DnsRecord>[];
NS: Partial<DnsRecord>[];
SRV: Partial<DnsRecord>[];
CAA: Partial<DnsRecord>[];
PTR: Partial<DnsRecord>[];
HTTPS: Partial<DnsRecord>[];
ALIAS: Partial<DnsRecord>[];
[key: string]: Partial<DnsRecord>[];
};
function parseZoneFile(content: string): ParsedRecords {
const records: ParsedRecords = {
A: [],
AAAA: [],
CNAME: [],
MX: [],
TXT: [],
NS: [],
SRV: [],
CAA: [],
PTR: [],
HTTPS: [],
ALIAS: []
};
// Split the content into lines and process each line
const lines = content.split('\n');
for (let line of lines) {
// Remove comments, but save them for record comment field
let comment = '';
const commentIndex = line.indexOf(';');
if (commentIndex !== -1) {
comment = line.substring(commentIndex + 1).trim();
line = line.substring(0, commentIndex).trim();
}
// Skip empty lines or pure comment lines
if (!line.trim()) continue;
// Split line into parts (whitespace separated)
const parts = line.trim().split(/\s+/);
// Need at least a name, TTL/class/type, and value
if (parts.length < 3) continue;
let recordType = '';
let name = '';
let value = '';
let ttl = 3600;
let priority: number | undefined;
let weight: number | undefined;
let port: number | undefined;
// Try to determine the record type from the parts
for (let i = 0; i < parts.length; i++) {
const part = parts[i].toUpperCase();
if (['A', 'AAAA', 'CNAME', 'MX', 'TXT', 'NS', 'SRV', 'CAA'].includes(part)) {
recordType = part;
name = parts[0] === '@' ? '' : parts[0]; // @ represents the domain itself
// Handle TTL if it appears to be a number
const possibleTtl = parseInt(parts[i - 1]);
if (!isNaN(possibleTtl) && i > 0) {
ttl = possibleTtl;
}
if (recordType === 'MX' && i + 2 < parts.length) {
priority = parseInt(parts[i + 1]);
value = parts[i + 2];
} else if (recordType === 'SRV' && i + 4 < parts.length) {
priority = parseInt(parts[i + 1]);
weight = parseInt(parts[i + 2]);
port = parseInt(parts[i + 3]);
value = parts[i + 4];
} else if (i + 1 < parts.length) {
value = parts.slice(i + 1).join(' ');
}
break;
}
}
if (recordType && value) {
if (recordType === 'TXT' && value.startsWith('"') && value.endsWith('"')) {
value = value.slice(1, -1);
}
const record: Partial<DnsRecord> = {
name,
value,
ttl,
comment: comment || undefined
};
if (priority !== undefined) {
record.priority = priority;
}
if (!records[recordType]) {
records[recordType] = [];
}
records[recordType].push(record);
}
}
return records;
}
async function handleSubmit() {
try {
if (!files?.length) return;
const file = files[0];
const content = await file.text();
const parsedRecords = parseZoneFile(content);
const parsedRecords = parseDnsRecords(content);
let recordCount = 0;