From df759eb01545a1bfddc8823e95dcdd1acf6f689c Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Thu, 17 Mar 2022 11:57:52 -0700 Subject: [PATCH 1/7] stab #1: parses cadillac.net --- .github/workflows/ci-test.yml | 2 +- .gitignore | 2 + .npmignore | 14 ++ index.js | 16 +++ package.json | 9 +- src/bind-grammar.ne | 226 +++++++++++++++++++++++++++++++ test/fixtures/zones/cadillac.net | 56 ++++++++ test/fixtures/zones/isi.edu | 20 +++ test/index.js | 94 +++++++++++++ 9 files changed, 435 insertions(+), 4 deletions(-) create mode 100644 .npmignore create mode 100644 src/bind-grammar.ne create mode 100644 test/fixtures/zones/cadillac.net create mode 100644 test/fixtures/zones/isi.edu diff --git a/.github/workflows/ci-test.yml b/.github/workflows/ci-test.yml index be19ef4..0986a1e 100644 --- a/.github/workflows/ci-test.yml +++ b/.github/workflows/ci-test.yml @@ -21,7 +21,7 @@ jobs: with: fetch-depth: 1 - - uses: actions/setup-node@v1 + - uses: actions/setup-node@v2 name: Use Node.js ${{ matrix.node-version }} with: node-version: ${{ matrix.node-version }} diff --git a/.gitignore b/.gitignore index 6704566..d72d10d 100644 --- a/.gitignore +++ b/.gitignore @@ -102,3 +102,5 @@ dist # TernJS port file .tern-port +grammar.js +package-lock.json diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..cd07f0e --- /dev/null +++ b/.npmignore @@ -0,0 +1,14 @@ +.github +.DS_Store +.editorconfig +.gitignore +.gitmodules +.lgtm.yml +appveyor.yml +codecov.yml +.release +.travis.yml +.eslintrc.yaml +.eslintrc.json +package-lock.json +grammar.js diff --git a/index.js b/index.js index e69de29..26f128f 100644 --- a/index.js +++ b/index.js @@ -0,0 +1,16 @@ + +const nearley = require('nearley') +const grammar = require('./grammar.js') +grammar.start = 'main' + +exports.parseZoneFile = str => { + + const parser = new nearley.Parser(nearley.Grammar.fromCompiled(grammar)) + parser.feed(str) + + if (parser.length > 1) { + console.error(`ERROR: ambigious parser rule`) + } + return parser.results[0] +} + diff --git a/package.json b/package.json index 20560be..6ba9c86 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,14 @@ { "name": "dns-zone-validator", - "version": "0.0.2", + "version": "0.1.0", "description": "DNS Zone Validator", "main": "index.js", "scripts": { "cover": "NODE_ENV=cov npx nyc --reporter=lcovonly npm run test", - "lint": "npx eslint *.js test/*.js", - "lintfix": "npx eslint --fix *.js test/*.js", + "grammar": "npx -p nearley nearleyc src/bind-grammar.ne -o grammar.js", + "lint": "npx eslint index.js test/*.js", + "lintfix": "npx eslint --fix index.js test/*.js", + "postinstall": "npx -p nearley nearleyc src/bind-grammar.ne -o grammar.js", "test": "npx mocha" }, "repository": { @@ -30,5 +32,6 @@ "mocha": "^9.1.3" }, "dependencies": { + "nearley": "^2.20.1" } } diff --git a/src/bind-grammar.ne b/src/bind-grammar.ne new file mode 100644 index 0000000..1fdf9ae --- /dev/null +++ b/src/bind-grammar.ne @@ -0,0 +1,226 @@ + +#@builtin "number.ne" +@builtin "string.ne" +@builtin "whitespace.ne" + + +main -> (statement "\n"):+ + +statement -> blank | ttl | origin | soa | ns | mx | a | txt | aaaa | cname | dname + +blank -> _ + +comment -> ";" [^\n]:* + +ttl -> "$TTL" __ uint _ (comment):? _ + {% (d) => ttlAsObject(d) %} + +origin -> "$ORIGIN" __ hostname (comment):? _ + {% (d) => originAsObject(d) %} + +soa -> hostname ( __ uint ):? ( __ class ):? __ "SOA" + __ hostname + __ hostname + __ "(" + __ uint (_ comment):? + __ uint (_ comment):? + __ uint (_ comment):? + __ uint (_ comment):? + __ uint (_ comment):? + _ ")" _ + {% (d) => soaAsObject(d) %} + +ns -> hostname (__ uint):? (__ class):? __ "NS" + __ hostname _ (comment):? _ + {% (d) => nsAsObject(d) %} + +mx -> hostname (__ uint):? (__ class):? __ "MX" + __ uint __ hostname _ (comment):? + {% (d) => mxAsObject(d) %} + +a -> hostname (__ uint):? (__ class):? __ "A" + __ ip4 _ (comment):? _ + {% (d) => aAsObject(d) %} + +txt -> hostname (__ uint):? (__ class):? __ "TXT" + __ (dqstring _):+ (comment):? _ + {% (d) => txtAsObject(d) %} + +aaaa -> hostname (__ uint):? (__ class):? __ "AAAA" + __ ip6 _ (comment):? _ + {% (d) => aaaaAsObject(d) %} + +cname -> hostname (__ uint):? (__ class):? __ "CNAME" + __ hostname _ (comment):? _ + {% (d) => cnameAsObject(d) %} + +dname -> hostname (__ uint):? (__ class):? __ "DNAME" + __ hostname _ (comment):? _ + {% (d) => dnameAsObject(d) %} + +uint -> [0-9]:+ {% (d) => parseInt(d[0].join("")) %} + +hostname -> ALPHA_NUM_DASH_U:* {% (d) => d[0].join("") %} + +ALPHA_NUM_DASH_U -> [-0-9A-Za-z\u0080-\uFFFF._] {% id %} + +class -> "IN" | "CH" | "HS" | "CHAOS" | "ANY" + +times_3[X] -> $X $X $X +times_5[X] -> $X $X $X $X $X +times_7[X] -> $X $X $X $X $X $X $X + +ip4 -> Snum times_3["." Snum] {% (d) => flat_string(d) %} + +ip6 -> IPv6_full | IPv6_comp | IPv6v4_full | IPv6v4_comp + +Snum -> DIGIT | + ( [1-9] DIGIT ) | + ( "1" DIGIT DIGIT ) | + ( "2" [0-4] DIGIT ) | + ( "2" "5" [0-5] ) + +DIGIT -> [0-9] {% id %} +HEXDIG -> [0-9A-Fa-f] {% id %} + +IPv6_hex -> HEXDIG | + ( HEXDIG HEXDIG ) | + ( HEXDIG HEXDIG HEXDIG ) | + ( HEXDIG HEXDIG HEXDIG HEXDIG ) + +IPv6_full -> IPv6_hex times_7[":" IPv6_hex] + {% (d) => flat_string(d) %} + +IPv6_comp -> (IPv6_hex times_5[":" IPv6_hex]):? "::" + (IPv6_hex times_5[":" IPv6_hex]):? + {% (d) => flat_string(d) %} + +IPv6v4_full -> IPv6_hex times_5[":" IPv6_hex] ":" ip4 + {% (d) => flat_string(d) %} + +IPv6v4_comp -> (IPv6_hex times_3[":" IPv6_hex]):? "::" + (IPv6_hex times_3[":" IPv6_hex] ":"):? + ip4 + {% (d) => flat_string(d) %} + + +#ALPHA_NUM -> [0-9A-Za-z] +#ALPHA_NUM_U -> [0-9A-Za-z\u0080-\uFFFF] {% id %} + + +# https://datatracker.ietf.org/doc/html/rfc1035#page-12 +#domain -> subdomain | " " +#subdomain -> label | subdomain "." label +#label -> letter ldh-str let-dig +#ldh-str -> let-dig-hyp | let-dig-hyp ldh-str +#let-dig-hyp -> let-dig | "-" +#let-dig -> letter | digit +#letter -> [a-zA-Z] +#digit -> [0-9] + + +@{% +function flat_string(d) { + if (d) { + if (Array.isArray(d)) return d.flat(Infinity).join("") + return d + } + return '' +} + +function ttlAsObject (d) { + return { ttl: d[2] } +} + +function originAsObject (d) { + return { origin: d[2] } +} + +function soaAsObject (d) { + return { + name : d[0], + ttl : d[1][1], + class : d[2][1][0], + type : d[4], + mname : d[6], + rname : d[8], + serial : d[12], + refresh: d[15], + retry : d[18], + expire : d[21], + minimum: d[24], + } +} + +function nsAsObject (d) { + return { + name: d[0], + ttl : d[1][1], + class: d[2][1][0], + type: d[4], + dname: d[6] + } +} + +function mxAsObject (d) { + return { + name: d[0], + ttl : d[1][1], + class: d[2][1][0], + type: d[4], + preference: d[6], + exchange : d[8] + } +} + +function aAsObject (d) { + return { + name : d[0], + ttl : d[1][1], + class : d[2][1][0], + type : d[4], + address: d[6], + } +} + +function txtAsObject (d) { + return { + name : d[0], + ttl : d[1][1], + class : d[2][1][0], + type : d[4], + data : d[6].map(e => e[0]), + } +} + +function aaaaAsObject (d) { + return { + name : d[0], + ttl : d[1][1], + class : d[2][1][0], + type : d[4], + address: d[6][0], + } +} + +function cnameAsObject (d) { + return { + name : d[0], + ttl : d[1][1], + class : d[2][1][0], + type : d[4], + cname : d[6][0], + } +} + +function dnameAsObject (d) { + return { + name : d[0], + ttl : d[1][1], + class : d[2][1][0], + type : d[4], + target : d[6][0], + } +} + +%} \ No newline at end of file diff --git a/test/fixtures/zones/cadillac.net b/test/fixtures/zones/cadillac.net new file mode 100644 index 0000000..6ea967c --- /dev/null +++ b/test/fixtures/zones/cadillac.net @@ -0,0 +1,56 @@ + +$TTL 86400; +$ORIGIN cadillac.net. +cadillac.net. 86400 IN SOA ns1.cadillac.net. hostmaster.cadillac.net. ( + 2021102100 ; serial + 16384 ; refresh + 2048 ; retry + 604800 ; expiry + 2560 ; minimum + ) + +cadillac.net. 14400 IN NS ns1.cadillac.net. +cadillac.net. 14400 IN NS ns2.cadillac.net. +cadillac.net. 14400 IN NS ns3.cadillac.net. + +cadillac.net. 86400 IN MX 0 mail.theartfarm.com. +cadillac.net. 86400 IN A 66.128.51.173 +cadillac.net. 86400 IN TXT "v=spf1 mx a include:mx.theartfarm.com -all" + +ns1 86400 IN MX 10 ns1.cadillac.net. +ns1 86400 IN A 138.210.133.61 + +ns2 86400 IN A 192.48.85.146 +ns2 86400 IN AAAA 2605:7900:0020:000a:0000:0000:0000:0004 +ns2 86400 IN A 204.11.99.4 +ns2 86400 IN A 173.45.131.4 +ns2 86400 IN MX 10 ns2.cadillac.net. + +ns3 86400 IN MX 10 ns3.cadillac.net. +ns3 86400 IN AAAA 2605:ae00:0329:0000:0000:0000:0000:000f +ns3 86400 IN A 66.128.51.174 +ns3 86400 IN AAAA 2001:41d0:0302:2100:0000:0000:0000:00a7 +ns3 86400 IN A 217.182.64.150 + +ns4 3600 IN A 208.78.70.12 +ns4 3600 IN AAAA 2001:0500:0090:0001:0000:0000:0000:0012 +ns4 3600 IN A 204.13.250.12 +ns4 3600 IN A 208.78.71.12 +ns4 3600 IN A 204.13.251.12 +ns4 3600 IN AAAA 2001:0500:0094:0001:0000:0000:0000:0012 + +localhost 86400 IN A 127.0.0.1 +localhost 86400 IN AAAA 0000:0000:0000:0000:0000:0000:0000:0001 + +www 28800 IN CNAME vhost0.theartfarm.com. +matt 86400 IN CNAME matt.simerson.net. +dns 3600 IN A 66.128.51.171 +jaycees 86400 IN CNAME www.cadillacjaycees.org. +kyna 86400 IN CNAME vhost0.theartfarm.com. +_tcp 86400 IN DNAME _tcp.theartfarm.com. +_dmarc 86400 IN TXT "v=DMARC1; p=reject; rua=mailto:dmarc-feedback@theartfarm.com; ruf=mailto:dmarc-feedback@theartfarm.com; pct=100" +apr2013._domainkey 86400 IN TXT "v=DKIM1;p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5BgmgaBHxIQSHCzFlkJ8/dCYFgppfpGxQB2mbB/nlspbZZPQPX7JrS8fglt6lVOQ/A82ErayWiABQd6GziiHbe+mA5glQSxG2o2LUtDa1AU269W1sZrgVEFkIq5sZ+T+s3KbcSjca21YOZt8NWxw5UvP1xTRHHO77JbcwUEB4rBAiZOs8eU9kuMLAuh8AQw0w17JW0+tN" "SNSphz0dY5S/5upHSdRqyOVrCJNE/Zuyzo1Ck+T1NIPt4ttd1VPkAMnjqXXjBQWP4BRObVEdmRqCxy4CRfbbiPJiNcut+iV2YezJqsVxBXwPFfsMwVb68aAHKKpdwrBfmNfv/yLdXY6RwIDAQAB" +_domainkey 86400 IN TXT "o=-; t=y; r=postmaster@cadillac.net" +france.ns3 86400 IN A 217.182.64.150 +dallas.ns3 86400 IN A 66.128.51.174 +xn--ber-goa 86400 IN A 127.0.0.1 diff --git a/test/fixtures/zones/isi.edu b/test/fixtures/zones/isi.edu new file mode 100644 index 0000000..6147c46 --- /dev/null +++ b/test/fixtures/zones/isi.edu @@ -0,0 +1,20 @@ +@ IN SOA VENERA Action\.domains ( + 20 ; SERIAL + 7200 ; REFRESH + 600 ; RETRY + 3600000; EXPIRE + 60) ; MINIMUM + + NS A.ISI.EDU. + NS VENERA + NS VAXA + MX 10 VENERA + MX 20 VAXA + +A A 26.3.0.103 + +VENERA A 10.1.0.52 + A 128.9.0.32 + +VAXA A 10.2.0.27 + A 128.9.0.33 \ No newline at end of file diff --git a/test/index.js b/test/index.js index e69de29..4f68a44 100644 --- a/test/index.js +++ b/test/index.js @@ -0,0 +1,94 @@ + +const assert = require('assert') +const fs = require('fs') + +const zv = require('../index') + +describe('parseZoneFile', function () { + + it('parses a blank line', async () => { + const r = zv.parseZoneFile(`\n`) + // console.dir(r[0][0][0][0][0], { depth: null }) + assert.deepStrictEqual(r[0][0][0][0][0], null) + }) + + it('parses two blank lines', async () => { + const r = zv.parseZoneFile(`\n\n`) + // console.dir(r[0][0], { depth: null }) + assert.deepStrictEqual(r[0][0][0][0][0], null) + assert.deepStrictEqual(r[0][1][0][0][0], null) + }) + + it('parses a $TTL line', async () => { + const r = zv.parseZoneFile(`$TTL 86400\n`) + // console.dir(r, { depth: null }) + assert.deepStrictEqual(r[0][0][0][0], { ttl: 86400 }) + }) + + it('parses a $TTL line with a comment', async () => { + const r = zv.parseZoneFile(`$TTL 86400; yikers\n`) + // console.dir(r, { depth: null }) + assert.deepStrictEqual(r[0][0][0][0], { ttl: 86400 }) + }) + + it(`parses a SOA`, async () => { + const r = zv.parseZoneFile(`example.com. 86400 IN SOA ns1.example.com. hostmaster.example.com. ( + 2021102100 ; serial + 16384 ; refresh + 2048 ; retry + 604800 ; expiry + 2560 ; minimum + )\n`) + + // console.dir(r, { depth: null }) + assert.deepStrictEqual(r[0][0][0][0], { + name : 'example.com.', + ttl : 86400, + class : 'IN', + type : 'SOA', + mname : 'ns1.example.com.', + rname : 'hostmaster.example.com.', + serial : 2021102100, + refresh: 16384, + retry : 2048, + expire : 604800, + minimum: 2560, + }) + }) + + it('parses a NS line', async () => { + const r = zv.parseZoneFile(`cadillac.net. 14400 IN NS ns1.cadillac.net.\n`) + // console.dir(r, { depth: null }) + assert.deepStrictEqual(r[0][0][0][0], { + name : 'cadillac.net.', + ttl : 14400, + class: 'IN', + type : 'NS', + dname: 'ns1.cadillac.net.', + }) + }) + + it('parses an A line', async () => { + const r = zv.parseZoneFile(`cadillac.net. 86400 IN A 66.128.51.173\n`) + // console.dir(r, { depth: null }) + assert.deepStrictEqual(r[0][0][0][0], { + name : 'cadillac.net.', + ttl : 86400, + class : 'IN', + type : 'A', + address: '66.128.51.173', + }) + }) + + it('parses a zone file', async () => { + const file = './test/fixtures/zones/cadillac.net' + fs.readFile(file, (err, buf) => { + if (err) throw err + + const r = zv.parseZoneFile(buf.toString()) + // console.dir(r[0], { depth: null }) + assert.equal(r[0].length, 47) + }) + }) +}) + From b8f50f671dc62ac613cf1ee2eeab9710ea661287 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Thu, 17 Mar 2022 12:42:32 -0700 Subject: [PATCH 2/7] test: add isi.edu zone - allow comments after SOA closing paren - DRY the grammar -> object functions --- index.js | 14 +++- src/bind-grammar.ne | 137 +++++++++++++----------------------- test/fixtures/zones/isi.edu | 2 +- test/index.js | 37 ++++++---- 4 files changed, 83 insertions(+), 107 deletions(-) diff --git a/index.js b/index.js index 26f128f..57488b7 100644 --- a/index.js +++ b/index.js @@ -7,10 +7,20 @@ exports.parseZoneFile = str => { const parser = new nearley.Parser(nearley.Grammar.fromCompiled(grammar)) parser.feed(str) + parser.feed(`\n`) // for no EOL after last record if (parser.length > 1) { console.error(`ERROR: ambigious parser rule`) } - return parser.results[0] -} + // flatten the parser generated array + const flat = [] + for (const e of parser.results[0][0]) { + + // discard blank lines + if (Array.isArray(e[0][0]) && e[0][0][0] === null) continue + + flat.push(e[0][0]) + } + return flat +} diff --git a/src/bind-grammar.ne b/src/bind-grammar.ne index 1fdf9ae..1c2b53e 100644 --- a/src/bind-grammar.ne +++ b/src/bind-grammar.ne @@ -27,42 +27,42 @@ soa -> hostname ( __ uint ):? ( __ class ):? __ "SOA" __ uint (_ comment):? __ uint (_ comment):? __ uint (_ comment):? - _ ")" _ - {% (d) => soaAsObject(d) %} + _ ")" _ (comment):? + {% (d) => toResourceRecord(d) %} ns -> hostname (__ uint):? (__ class):? __ "NS" __ hostname _ (comment):? _ - {% (d) => nsAsObject(d) %} + {% (d) => toResourceRecord(d) %} mx -> hostname (__ uint):? (__ class):? __ "MX" __ uint __ hostname _ (comment):? - {% (d) => mxAsObject(d) %} + {% (d) => toResourceRecord(d) %} a -> hostname (__ uint):? (__ class):? __ "A" __ ip4 _ (comment):? _ - {% (d) => aAsObject(d) %} + {% (d) => toResourceRecord(d) %} txt -> hostname (__ uint):? (__ class):? __ "TXT" __ (dqstring _):+ (comment):? _ - {% (d) => txtAsObject(d) %} + {% (d) => toResourceRecord(d) %} aaaa -> hostname (__ uint):? (__ class):? __ "AAAA" __ ip6 _ (comment):? _ - {% (d) => aaaaAsObject(d) %} + {% (d) => toResourceRecord(d) %} cname -> hostname (__ uint):? (__ class):? __ "CNAME" __ hostname _ (comment):? _ - {% (d) => cnameAsObject(d) %} + {% (d) => toResourceRecord(d) %} dname -> hostname (__ uint):? (__ class):? __ "DNAME" __ hostname _ (comment):? _ - {% (d) => dnameAsObject(d) %} + {% (d) => toResourceRecord(d) %} uint -> [0-9]:+ {% (d) => parseInt(d[0].join("")) %} hostname -> ALPHA_NUM_DASH_U:* {% (d) => d[0].join("") %} -ALPHA_NUM_DASH_U -> [-0-9A-Za-z\u0080-\uFFFF._] {% id %} +ALPHA_NUM_DASH_U -> [-0-9A-Za-z\u0080-\uFFFF._@] {% id %} class -> "IN" | "CH" | "HS" | "CHAOS" | "ANY" @@ -136,91 +136,48 @@ function originAsObject (d) { return { origin: d[2] } } -function soaAsObject (d) { - return { - name : d[0], - ttl : d[1][1], - class : d[2][1][0], - type : d[4], - mname : d[6], - rname : d[8], - serial : d[12], - refresh: d[15], - retry : d[18], - expire : d[21], - minimum: d[24], - } -} - -function nsAsObject (d) { - return { +function toResourceRecord (d) { + const r = { name: d[0], - ttl : d[1][1], - class: d[2][1][0], + ttl : d[1] ? d[1][1] : d[1], + class: d[2] ? d[2][1][0] : d[2], type: d[4], - dname: d[6] - } -} - -function mxAsObject (d) { - return { - name: d[0], - ttl : d[1][1], - class: d[2][1][0], - type: d[4], - preference: d[6], - exchange : d[8] - } -} - -function aAsObject (d) { - return { - name : d[0], - ttl : d[1][1], - class : d[2][1][0], - type : d[4], - address: d[6], } -} - -function txtAsObject (d) { - return { - name : d[0], - ttl : d[1][1], - class : d[2][1][0], - type : d[4], - data : d[6].map(e => e[0]), - } -} - -function aaaaAsObject (d) { - return { - name : d[0], - ttl : d[1][1], - class : d[2][1][0], - type : d[4], - address: d[6][0], - } -} - -function cnameAsObject (d) { - return { - name : d[0], - ttl : d[1][1], - class : d[2][1][0], - type : d[4], - cname : d[6][0], - } -} -function dnameAsObject (d) { - return { - name : d[0], - ttl : d[1][1], - class : d[2][1][0], - type : d[4], - target : d[6][0], + switch (r.type) { + case 'A': + r.address = d[6] + break + case 'AAAA': + r.address = d[6][0] + break + case 'CNAME': + r.cname = d[6][0] + break + case 'DNAME': + r.target = d[6][0] + break + case 'MX': + r.preference = d[6] + r.exchange = d[8] + break + case 'NS': + r.dname = d[6] + break + case 'SOA': + r.mname = d[6] + r.rname = d[8] + r.serial = d[12] + r.refresh = d[15] + r.retry = d[18] + r.expire = d[21] + r.minimum = d[24] + break + case 'TXT': + r.data = d[6].map(e => e[0]) + break } + return r } %} \ No newline at end of file diff --git a/test/fixtures/zones/isi.edu b/test/fixtures/zones/isi.edu index 6147c46..f851880 100644 --- a/test/fixtures/zones/isi.edu +++ b/test/fixtures/zones/isi.edu @@ -1,4 +1,4 @@ -@ IN SOA VENERA Action\.domains ( +@ IN SOA VENERA Action.domains ( 20 ; SERIAL 7200 ; REFRESH 600 ; RETRY diff --git a/test/index.js b/test/index.js index 4f68a44..ab80dfb 100644 --- a/test/index.js +++ b/test/index.js @@ -8,27 +8,26 @@ describe('parseZoneFile', function () { it('parses a blank line', async () => { const r = zv.parseZoneFile(`\n`) - // console.dir(r[0][0][0][0][0], { depth: null }) - assert.deepStrictEqual(r[0][0][0][0][0], null) + // console.dir(r, { depth: null }) + assert.deepStrictEqual(r, []) }) it('parses two blank lines', async () => { const r = zv.parseZoneFile(`\n\n`) - // console.dir(r[0][0], { depth: null }) - assert.deepStrictEqual(r[0][0][0][0][0], null) - assert.deepStrictEqual(r[0][1][0][0][0], null) + // console.dir(r, { depth: null }) + assert.deepStrictEqual(r, []) }) it('parses a $TTL line', async () => { const r = zv.parseZoneFile(`$TTL 86400\n`) // console.dir(r, { depth: null }) - assert.deepStrictEqual(r[0][0][0][0], { ttl: 86400 }) + assert.deepStrictEqual(r[0], { ttl: 86400 }) }) it('parses a $TTL line with a comment', async () => { const r = zv.parseZoneFile(`$TTL 86400; yikers\n`) // console.dir(r, { depth: null }) - assert.deepStrictEqual(r[0][0][0][0], { ttl: 86400 }) + assert.deepStrictEqual(r[0], { ttl: 86400 }) }) it(`parses a SOA`, async () => { @@ -41,7 +40,7 @@ describe('parseZoneFile', function () { )\n`) // console.dir(r, { depth: null }) - assert.deepStrictEqual(r[0][0][0][0], { + assert.deepStrictEqual(r[0], { name : 'example.com.', ttl : 86400, class : 'IN', @@ -59,7 +58,7 @@ describe('parseZoneFile', function () { it('parses a NS line', async () => { const r = zv.parseZoneFile(`cadillac.net. 14400 IN NS ns1.cadillac.net.\n`) // console.dir(r, { depth: null }) - assert.deepStrictEqual(r[0][0][0][0], { + assert.deepStrictEqual(r[0], { name : 'cadillac.net.', ttl : 14400, class: 'IN', @@ -71,7 +70,7 @@ describe('parseZoneFile', function () { it('parses an A line', async () => { const r = zv.parseZoneFile(`cadillac.net. 86400 IN A 66.128.51.173\n`) // console.dir(r, { depth: null }) - assert.deepStrictEqual(r[0][0][0][0], { + assert.deepStrictEqual(r[0], { name : 'cadillac.net.', ttl : 86400, class : 'IN', @@ -80,15 +79,25 @@ describe('parseZoneFile', function () { }) }) - it('parses a zone file', async () => { + it('parses the cadillac.net zone file', async () => { const file = './test/fixtures/zones/cadillac.net' fs.readFile(file, (err, buf) => { if (err) throw err const r = zv.parseZoneFile(buf.toString()) - // console.dir(r[0], { depth: null }) - assert.equal(r[0].length, 47) + // console.dir(r, { depth: null }) + assert.equal(r.length, 41) }) }) -}) + it('parses the isi.edu zone file', async () => { + const file = './test/fixtures/zones/isi.edu' + fs.readFile(file, (err, buf) => { + if (err) throw err + + const r = zv.parseZoneFile(buf.toString()) + // console.dir(r, { depth: null }) + assert.equal(r.length, 11) + }) + }) +}) From 101d6092d9a84abf9ffc370fef754d3544024583 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Thu, 17 Mar 2022 14:37:29 -0700 Subject: [PATCH 3/7] add \r to eol, for windows --- src/bind-grammar.ne | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/bind-grammar.ne b/src/bind-grammar.ne index 1c2b53e..f7a32fc 100644 --- a/src/bind-grammar.ne +++ b/src/bind-grammar.ne @@ -4,10 +4,12 @@ @builtin "whitespace.ne" -main -> (statement "\n"):+ +main -> (statement eol):+ statement -> blank | ttl | origin | soa | ns | mx | a | txt | aaaa | cname | dname +eol -> "\n" | "\r" + blank -> _ comment -> ";" [^\n]:* From ab37c691644025b4500b61a931d31f66d5b43a32 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Thu, 17 Mar 2022 14:58:28 -0700 Subject: [PATCH 4/7] and in comments --- src/bind-grammar.ne | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bind-grammar.ne b/src/bind-grammar.ne index f7a32fc..0e0e73d 100644 --- a/src/bind-grammar.ne +++ b/src/bind-grammar.ne @@ -12,7 +12,7 @@ eol -> "\n" | "\r" blank -> _ -comment -> ";" [^\n]:* +comment -> ";" [^\n\r]:* ttl -> "$TTL" __ uint _ (comment):? _ {% (d) => ttlAsObject(d) %} From ebc52323df24d40c6da55d9864f57d505646c865 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Thu, 17 Mar 2022 15:12:43 -0700 Subject: [PATCH 5/7] local copy of builtin-whitespace, adds \r char --- src/bind-grammar.ne | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/bind-grammar.ne b/src/bind-grammar.ne index 0e0e73d..0572101 100644 --- a/src/bind-grammar.ne +++ b/src/bind-grammar.ne @@ -1,8 +1,5 @@ -#@builtin "number.ne" @builtin "string.ne" -@builtin "whitespace.ne" - main -> (statement eol):+ @@ -105,6 +102,11 @@ IPv6v4_comp -> (IPv6_hex times_3[":" IPv6_hex]):? "::" ip4 {% (d) => flat_string(d) %} +# Whitespace: `_` is optional, `__` is mandatory. +_ -> wschar:* {% function(d) {return null;} %} +__ -> wschar:+ {% function(d) {return null;} %} + +wschar -> [ \t\n\r\v\f] {% id %} #ALPHA_NUM -> [0-9A-Za-z] #ALPHA_NUM_U -> [0-9A-Za-z\u0080-\uFFFF] {% id %} From e9788e44eed63389a146e4468f458df31c958b59 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Thu, 17 Mar 2022 15:20:31 -0700 Subject: [PATCH 6/7] test: try w/o async mocha --- test/index.js | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/test/index.js b/test/index.js index ab80dfb..0cf7908 100644 --- a/test/index.js +++ b/test/index.js @@ -6,31 +6,35 @@ const zv = require('../index') describe('parseZoneFile', function () { - it('parses a blank line', async () => { + it('parses a blank line', function (done) { const r = zv.parseZoneFile(`\n`) // console.dir(r, { depth: null }) assert.deepStrictEqual(r, []) + done() }) - it('parses two blank lines', async () => { + it('parses two blank lines', function (done) { const r = zv.parseZoneFile(`\n\n`) // console.dir(r, { depth: null }) assert.deepStrictEqual(r, []) + done() }) - it('parses a $TTL line', async () => { + it('parses a $TTL line', function (done) { const r = zv.parseZoneFile(`$TTL 86400\n`) // console.dir(r, { depth: null }) assert.deepStrictEqual(r[0], { ttl: 86400 }) + done() }) - it('parses a $TTL line with a comment', async () => { + it('parses a $TTL line with a comment', function (done) { const r = zv.parseZoneFile(`$TTL 86400; yikers\n`) // console.dir(r, { depth: null }) assert.deepStrictEqual(r[0], { ttl: 86400 }) + done() }) - it(`parses a SOA`, async () => { + it(`parses a SOA`, function (done) { const r = zv.parseZoneFile(`example.com. 86400 IN SOA ns1.example.com. hostmaster.example.com. ( 2021102100 ; serial 16384 ; refresh @@ -53,9 +57,10 @@ describe('parseZoneFile', function () { expire : 604800, minimum: 2560, }) + done() }) - it('parses a NS line', async () => { + it('parses a NS line', function (done) { const r = zv.parseZoneFile(`cadillac.net. 14400 IN NS ns1.cadillac.net.\n`) // console.dir(r, { depth: null }) assert.deepStrictEqual(r[0], { @@ -65,9 +70,10 @@ describe('parseZoneFile', function () { type : 'NS', dname: 'ns1.cadillac.net.', }) + done() }) - it('parses an A line', async () => { + it('parses an A line', function (done) { const r = zv.parseZoneFile(`cadillac.net. 86400 IN A 66.128.51.173\n`) // console.dir(r, { depth: null }) assert.deepStrictEqual(r[0], { @@ -77,9 +83,10 @@ describe('parseZoneFile', function () { type : 'A', address: '66.128.51.173', }) + done() }) - it('parses the cadillac.net zone file', async () => { + it('parses the cadillac.net zone file', function (done) { const file = './test/fixtures/zones/cadillac.net' fs.readFile(file, (err, buf) => { if (err) throw err @@ -88,9 +95,10 @@ describe('parseZoneFile', function () { // console.dir(r, { depth: null }) assert.equal(r.length, 41) }) + done() }) - it('parses the isi.edu zone file', async () => { + it('parses the isi.edu zone file', function (done) { const file = './test/fixtures/zones/isi.edu' fs.readFile(file, (err, buf) => { if (err) throw err @@ -98,6 +106,7 @@ describe('parseZoneFile', function () { const r = zv.parseZoneFile(buf.toString()) // console.dir(r, { depth: null }) assert.equal(r.length, 11) + done() }) }) }) From 834ec2c735e9ddbb5c1703ed44571d1e8d203ce7 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Thu, 17 Mar 2022 15:28:08 -0700 Subject: [PATCH 7/7] try adding --max-old-space-size to env --- .github/workflows/ci-test.yml | 1 + test/index.js | 27 +++++++++------------------ 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci-test.yml b/.github/workflows/ci-test.yml index 0986a1e..b1242c2 100644 --- a/.github/workflows/ci-test.yml +++ b/.github/workflows/ci-test.yml @@ -34,3 +34,4 @@ jobs: env: CI: true + NODE_OPTIONS: --max-old-space-size=4096 diff --git a/test/index.js b/test/index.js index 0cf7908..ab80dfb 100644 --- a/test/index.js +++ b/test/index.js @@ -6,35 +6,31 @@ const zv = require('../index') describe('parseZoneFile', function () { - it('parses a blank line', function (done) { + it('parses a blank line', async () => { const r = zv.parseZoneFile(`\n`) // console.dir(r, { depth: null }) assert.deepStrictEqual(r, []) - done() }) - it('parses two blank lines', function (done) { + it('parses two blank lines', async () => { const r = zv.parseZoneFile(`\n\n`) // console.dir(r, { depth: null }) assert.deepStrictEqual(r, []) - done() }) - it('parses a $TTL line', function (done) { + it('parses a $TTL line', async () => { const r = zv.parseZoneFile(`$TTL 86400\n`) // console.dir(r, { depth: null }) assert.deepStrictEqual(r[0], { ttl: 86400 }) - done() }) - it('parses a $TTL line with a comment', function (done) { + it('parses a $TTL line with a comment', async () => { const r = zv.parseZoneFile(`$TTL 86400; yikers\n`) // console.dir(r, { depth: null }) assert.deepStrictEqual(r[0], { ttl: 86400 }) - done() }) - it(`parses a SOA`, function (done) { + it(`parses a SOA`, async () => { const r = zv.parseZoneFile(`example.com. 86400 IN SOA ns1.example.com. hostmaster.example.com. ( 2021102100 ; serial 16384 ; refresh @@ -57,10 +53,9 @@ describe('parseZoneFile', function () { expire : 604800, minimum: 2560, }) - done() }) - it('parses a NS line', function (done) { + it('parses a NS line', async () => { const r = zv.parseZoneFile(`cadillac.net. 14400 IN NS ns1.cadillac.net.\n`) // console.dir(r, { depth: null }) assert.deepStrictEqual(r[0], { @@ -70,10 +65,9 @@ describe('parseZoneFile', function () { type : 'NS', dname: 'ns1.cadillac.net.', }) - done() }) - it('parses an A line', function (done) { + it('parses an A line', async () => { const r = zv.parseZoneFile(`cadillac.net. 86400 IN A 66.128.51.173\n`) // console.dir(r, { depth: null }) assert.deepStrictEqual(r[0], { @@ -83,10 +77,9 @@ describe('parseZoneFile', function () { type : 'A', address: '66.128.51.173', }) - done() }) - it('parses the cadillac.net zone file', function (done) { + it('parses the cadillac.net zone file', async () => { const file = './test/fixtures/zones/cadillac.net' fs.readFile(file, (err, buf) => { if (err) throw err @@ -95,10 +88,9 @@ describe('parseZoneFile', function () { // console.dir(r, { depth: null }) assert.equal(r.length, 41) }) - done() }) - it('parses the isi.edu zone file', function (done) { + it('parses the isi.edu zone file', async () => { const file = './test/fixtures/zones/isi.edu' fs.readFile(file, (err, buf) => { if (err) throw err @@ -106,7 +98,6 @@ describe('parseZoneFile', function () { const r = zv.parseZoneFile(buf.toString()) // console.dir(r, { depth: null }) assert.equal(r.length, 11) - done() }) }) })