@nictool/dns-resource-record 1.2.0 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.codeclimate.yml +25 -0
- package/.github/FUNDING.yml +3 -0
- package/.github/workflows/ci.yml +43 -0
- package/.github/workflows/codeql.yml +14 -0
- package/.github/workflows/publish.yml +16 -0
- package/.gitmodules +3 -0
- package/CHANGELOG.md +18 -0
- package/DEVELOP.md +9 -0
- package/README.md +6 -2
- package/eslint.config.mjs +41 -0
- package/index.js +6 -0
- package/package.json +12 -3
- package/rr/a.js +13 -3
- package/rr/aaaa.js +14 -4
- package/rr/caa.js +25 -13
- package/rr/cname.js +3 -3
- package/rr/dname.js +4 -4
- package/rr/dnskey.js +6 -11
- package/rr/ds.js +6 -8
- package/rr/hinfo.js +8 -6
- package/rr/https.js +62 -0
- package/rr/ipseckey.js +20 -9
- package/rr/key.js +2 -2
- package/rr/loc.js +4 -4
- package/rr/mx.js +15 -3
- package/rr/naptr.js +2 -2
- package/rr/ns.js +1 -1
- package/rr/nsec.js +2 -4
- package/rr/nsec3.js +6 -12
- package/rr/nsec3param.js +4 -9
- package/rr/nxt.js +2 -4
- package/rr/openpgpkey.js +7 -3
- package/rr/rrsig.js +3 -4
- package/rr/sig.js +1 -1
- package/rr/smimea.js +3 -5
- package/rr/soa.js +1 -2
- package/rr/spf.js +1 -1
- package/rr/srv.js +4 -4
- package/rr/sshfp.js +1 -1
- package/rr/svcb.js +64 -0
- package/rr/tlsa.js +10 -10
- package/rr/tsig.js +10 -2
- package/rr/txt.js +5 -5
- package/rr/uri.js +2 -2
- package/rr/wks.js +4 -3
- package/rr.js +32 -20
- package/test/a.js +75 -0
- package/test/aaaa.js +86 -0
- package/test/base.js +148 -0
- package/test/caa.js +111 -0
- package/test/cert.js +48 -0
- package/test/cname.js +40 -0
- package/test/dname.js +57 -0
- package/test/dnskey.js +45 -0
- package/test/ds.js +45 -0
- package/test/fake.js +34 -0
- package/test/hinfo.js +80 -0
- package/test/https.js +56 -0
- package/test/ipseckey.js +141 -0
- package/test/key.js +36 -0
- package/test/loc.js +75 -0
- package/test/mx.js +78 -0
- package/test/naptr.js +47 -0
- package/test/ns.js +56 -0
- package/test/nsec.js +37 -0
- package/test/nsec3.js +47 -0
- package/test/nsec3param.js +23 -0
- package/test/nxt.js +37 -0
- package/test/openpgpkey.js +85 -0
- package/test/ptr.js +56 -0
- package/test/rr.js +212 -0
- package/test/smimea.js +51 -0
- package/test/soa.js +99 -0
- package/test/spf.js +51 -0
- package/test/srv.js +101 -0
- package/test/sshfp.js +69 -0
- package/test/svcb.js +56 -0
- package/test/tinydns.js +163 -0
- package/test/tlsa.js +63 -0
- package/test/txt.js +82 -0
- package/test/uri.js +65 -0
package/rr/tsig.js
CHANGED
|
@@ -13,7 +13,15 @@ export default class TSIG extends RR {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
getRdataFields(arg) {
|
|
16
|
-
return [
|
|
16
|
+
return [
|
|
17
|
+
'algorithm name',
|
|
18
|
+
'time signed',
|
|
19
|
+
'fudge',
|
|
20
|
+
'mac',
|
|
21
|
+
'original id',
|
|
22
|
+
'error',
|
|
23
|
+
'other',
|
|
24
|
+
]
|
|
17
25
|
}
|
|
18
26
|
|
|
19
27
|
getRFCs() {
|
|
@@ -34,7 +42,7 @@ export default class TSIG extends RR {
|
|
|
34
42
|
ttl: parseInt(ttl, 10),
|
|
35
43
|
class: c,
|
|
36
44
|
type: type,
|
|
37
|
-
|
|
45
|
+
algorithm: algorithm,
|
|
38
46
|
// 'time signed': opts.bindline,
|
|
39
47
|
// fudge
|
|
40
48
|
// mac
|
package/rr/txt.js
CHANGED
|
@@ -54,7 +54,7 @@ export default class TXT extends RR {
|
|
|
54
54
|
// generic: :fqdn:n:rdata:ttl:timestamp:location
|
|
55
55
|
// eslint-disable-next-line prefer-const
|
|
56
56
|
let [fqdn, n, rdata, ttl, ts, loc] = str.substring(1).split(':')
|
|
57
|
-
if (n != 16)
|
|
57
|
+
if (n != 16) this.throwHelp('TXT fromTinydns, invalid n')
|
|
58
58
|
|
|
59
59
|
rdata = TINYDNS.octalToChar(rdata)
|
|
60
60
|
let s = ''
|
|
@@ -70,10 +70,10 @@ export default class TXT extends RR {
|
|
|
70
70
|
|
|
71
71
|
fromBind(opts) {
|
|
72
72
|
// test.example.com 3600 IN TXT "..."
|
|
73
|
-
const match = opts.bindline
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (!match)
|
|
73
|
+
const match = opts.bindline
|
|
74
|
+
.trim()
|
|
75
|
+
.split(/^([\S]{1,255})\s+([0-9]{1,10})\s+(IN)\s+(\w{3})\s+?\s*(.*?)$/i)
|
|
76
|
+
if (!match) this.throwHelp(`unable to parse TXT: ${opts.bindline}`)
|
|
77
77
|
const [owner, ttl, c, type, rdata] = match.slice(1)
|
|
78
78
|
|
|
79
79
|
return new this.constructor({
|
package/rr/uri.js
CHANGED
|
@@ -21,7 +21,7 @@ export default class URI extends RR {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
setTarget(val) {
|
|
24
|
-
if (!val)
|
|
24
|
+
if (!val) this.throwHelp(`URI: target is required`)
|
|
25
25
|
|
|
26
26
|
this.set('target', val)
|
|
27
27
|
}
|
|
@@ -30,7 +30,7 @@ export default class URI extends RR {
|
|
|
30
30
|
fromTinydns(opts) {
|
|
31
31
|
// URI via generic, :fqdn:n:rdata:ttl:timestamp:lo
|
|
32
32
|
const [fqdn, n, rdata, ttl, ts, loc] = opts.tinyline.substring(1).split(':')
|
|
33
|
-
if (n != 256)
|
|
33
|
+
if (n != 256) this.throwHelp('URI fromTinydns, invalid n')
|
|
34
34
|
|
|
35
35
|
return new URI({
|
|
36
36
|
type: 'URI',
|
package/rr/wks.js
CHANGED
|
@@ -28,15 +28,16 @@ export default class WKS extends RR {
|
|
|
28
28
|
|
|
29
29
|
fromBind(opts) {
|
|
30
30
|
// test.example.com 3600 IN WKS 192.168.1.1 TCP 25
|
|
31
|
-
const [owner, ttl, c, type, address, protocol, bitmap] =
|
|
31
|
+
const [owner, ttl, c, type, address, protocol, bitmap] =
|
|
32
|
+
opts.bindline.split(/\s+/)
|
|
32
33
|
return new WKS({
|
|
33
34
|
owner,
|
|
34
35
|
ttl: parseInt(ttl, 10),
|
|
35
36
|
class: c,
|
|
36
37
|
type,
|
|
37
|
-
address,
|
|
38
|
+
address, // 32-bit int
|
|
38
39
|
protocol, // 8-bit int, 6=TCP, 17=UDP
|
|
39
|
-
bitmap,
|
|
40
|
+
bitmap, // var len bit map, must be multiple of 8
|
|
40
41
|
})
|
|
41
42
|
}
|
|
42
43
|
|
package/rr.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import util from 'node:util'
|
|
2
|
+
|
|
1
3
|
export default class RR extends Map {
|
|
2
4
|
constructor(opts) {
|
|
3
5
|
super()
|
|
@@ -21,7 +23,7 @@ export default class RR extends Map {
|
|
|
21
23
|
for (const f of this.getFields('rdata')) {
|
|
22
24
|
const fnName = `set${this.ucfirst(f)}`
|
|
23
25
|
if (this[fnName] === undefined)
|
|
24
|
-
|
|
26
|
+
this.throwHelp(`Missing ${fnName} in class ${this.get('type')}`)
|
|
25
27
|
this[fnName](opts[f])
|
|
26
28
|
}
|
|
27
29
|
|
|
@@ -51,7 +53,7 @@ export default class RR extends Map {
|
|
|
51
53
|
this.set('class', c)
|
|
52
54
|
break
|
|
53
55
|
default:
|
|
54
|
-
|
|
56
|
+
this.throwHelp(`invalid class ${c}`)
|
|
55
57
|
}
|
|
56
58
|
}
|
|
57
59
|
|
|
@@ -74,20 +76,20 @@ export default class RR extends Map {
|
|
|
74
76
|
}
|
|
75
77
|
|
|
76
78
|
setOwner(n) {
|
|
77
|
-
if (n === undefined)
|
|
79
|
+
if (n === undefined) this.throwHelp(`owner is required`)
|
|
78
80
|
|
|
79
81
|
if (n.length < 1 || n.length > 255)
|
|
80
|
-
|
|
82
|
+
this.throwHelp(
|
|
81
83
|
'Domain names must have 1-255 octets (characters): RFC 2181',
|
|
82
84
|
)
|
|
83
85
|
|
|
84
|
-
this.isFullyQualified(
|
|
86
|
+
this.isFullyQualified(this.constructor.name, 'owner', n)
|
|
85
87
|
this.hasValidLabels(n)
|
|
86
88
|
|
|
87
89
|
// wildcard records: RFC 1034, 4592
|
|
88
90
|
if (/\*/.test(n)) {
|
|
89
91
|
if (!/^\*\./.test(n) && !/\.\*\./.test(n))
|
|
90
|
-
|
|
92
|
+
this.throwHelp('only *.something or * (by itself) is a valid wildcard')
|
|
91
93
|
}
|
|
92
94
|
|
|
93
95
|
this.set('owner', n.toLowerCase())
|
|
@@ -97,11 +99,11 @@ export default class RR extends Map {
|
|
|
97
99
|
if (t === undefined) t = this?.default?.ttl
|
|
98
100
|
if (t === undefined) {
|
|
99
101
|
if (['SOA', 'SSHPF'].includes(this.get('type'))) return
|
|
100
|
-
|
|
102
|
+
this.throwHelp('TTL is required, no default available')
|
|
101
103
|
}
|
|
102
104
|
|
|
103
105
|
if (typeof t !== 'number')
|
|
104
|
-
|
|
106
|
+
this.throwHelp(`TTL must be numeric (${typeof t})`)
|
|
105
107
|
|
|
106
108
|
// RFC 1035, 2181
|
|
107
109
|
this.is32bitInt(this.owner, 'TTL', t)
|
|
@@ -113,17 +115,27 @@ export default class RR extends Map {
|
|
|
113
115
|
switch (t) {
|
|
114
116
|
case '':
|
|
115
117
|
case undefined:
|
|
116
|
-
|
|
118
|
+
this.throwHelp(`type is required`)
|
|
117
119
|
}
|
|
118
120
|
|
|
119
121
|
if (t.toUpperCase() !== this.constructor.name)
|
|
120
|
-
|
|
122
|
+
this.throwHelp(`type ${t} doesn't match ${this.constructor.name}`)
|
|
121
123
|
|
|
122
124
|
this.set('type', t.toUpperCase())
|
|
123
125
|
}
|
|
124
126
|
|
|
127
|
+
throwHelp(e) {
|
|
128
|
+
if (this.constructor.name === 'RR') throw new Error(e)
|
|
129
|
+
|
|
130
|
+
const example = this.getCanonical
|
|
131
|
+
? `Example ${this.constructor.name}:\n${util.inspect(this.getCanonical(), { depth: null })}\n\n`
|
|
132
|
+
: `${this.constructor.name} records have the fields: ${this.getFields().join(', ')}\n\n`
|
|
133
|
+
|
|
134
|
+
throw new Error(`${e}\n\n${example}${this.citeRFC()}\n`)
|
|
135
|
+
}
|
|
136
|
+
|
|
125
137
|
citeRFC() {
|
|
126
|
-
return `see RFC ${this.getRFCs()}`
|
|
138
|
+
return `see RFC${this.getRFCs().length > 1 ? 's' : ''} ${this.getRFCs()}`
|
|
127
139
|
}
|
|
128
140
|
|
|
129
141
|
fullyQualify(hostname, origin) {
|
|
@@ -194,7 +206,7 @@ export default class RR extends Map {
|
|
|
194
206
|
|
|
195
207
|
getFQDN(field, zone_opts = {}) {
|
|
196
208
|
let fqdn = this.get(field)
|
|
197
|
-
if (!fqdn)
|
|
209
|
+
if (!fqdn) this.throwHelp(`empty value for field ${field}`)
|
|
198
210
|
if (!fqdn.endsWith('.')) fqdn += '.'
|
|
199
211
|
|
|
200
212
|
if (zone_opts.hide?.origin && zone_opts.origin) {
|
|
@@ -235,7 +247,7 @@ export default class RR extends Map {
|
|
|
235
247
|
const fq = hostname.endsWith('.') ? hostname.slice(0, -1) : hostname
|
|
236
248
|
for (const label of fq.split('.')) {
|
|
237
249
|
if (label.length < 1 || label.length > 63)
|
|
238
|
-
|
|
250
|
+
this.throwHelp('Labels must have 1-63 octets (characters), RFC 2181')
|
|
239
251
|
}
|
|
240
252
|
}
|
|
241
253
|
|
|
@@ -248,7 +260,7 @@ export default class RR extends Map {
|
|
|
248
260
|
)
|
|
249
261
|
return true
|
|
250
262
|
|
|
251
|
-
|
|
263
|
+
this.throwHelp(
|
|
252
264
|
`${type} ${field} must be a 8-bit integer (in the range 0-255)`,
|
|
253
265
|
)
|
|
254
266
|
}
|
|
@@ -262,7 +274,7 @@ export default class RR extends Map {
|
|
|
262
274
|
)
|
|
263
275
|
return true
|
|
264
276
|
|
|
265
|
-
|
|
277
|
+
this.throwHelp(
|
|
266
278
|
`${type} ${field} must be a 16-bit integer (in the range 0-65535)`,
|
|
267
279
|
)
|
|
268
280
|
}
|
|
@@ -276,7 +288,7 @@ export default class RR extends Map {
|
|
|
276
288
|
)
|
|
277
289
|
return true
|
|
278
290
|
|
|
279
|
-
|
|
291
|
+
this.throwHelp(
|
|
280
292
|
`${type} ${field} must be a 32-bit integer (in the range 0-2147483647)`,
|
|
281
293
|
)
|
|
282
294
|
}
|
|
@@ -285,10 +297,10 @@ export default class RR extends Map {
|
|
|
285
297
|
return /^["']/.test(val) && /["']$/.test(val)
|
|
286
298
|
}
|
|
287
299
|
|
|
288
|
-
isFullyQualified(type,
|
|
300
|
+
isFullyQualified(type, field, hostname) {
|
|
289
301
|
if (hostname.endsWith('.')) return true
|
|
290
302
|
|
|
291
|
-
|
|
303
|
+
this.throwHelp(`${type}: ${field} must be fully qualified`)
|
|
292
304
|
}
|
|
293
305
|
|
|
294
306
|
isValidHostname(type, field, hostname) {
|
|
@@ -296,7 +308,7 @@ export default class RR extends Map {
|
|
|
296
308
|
if (!allowed.test(hostname)) return true
|
|
297
309
|
|
|
298
310
|
const matches = allowed.exec(hostname)
|
|
299
|
-
|
|
311
|
+
this.throwHelp(
|
|
300
312
|
`${type}, ${field} has invalid hostname character (${matches[0]})`,
|
|
301
313
|
)
|
|
302
314
|
}
|
|
@@ -320,7 +332,7 @@ export default class RR extends Map {
|
|
|
320
332
|
}
|
|
321
333
|
|
|
322
334
|
toMaraGeneric() {
|
|
323
|
-
//
|
|
335
|
+
// this.throwHelp(`\nMaraDNS does not support ${type} records yet and this package does not support MaraDNS generic records. Yet.\n`)
|
|
324
336
|
return `${this.get('owner')}\t+${this.get('ttl')}\tRAW ${this.getTypeId()}\t'${this.getRdataFields()
|
|
325
337
|
.map((f) => this.getQuoted(f))
|
|
326
338
|
.join(' ')}' ~\n`
|
package/test/a.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import A from '../rr/a.js'
|
|
2
|
+
import * as base from './base.js'
|
|
3
|
+
|
|
4
|
+
const defaults = { class: 'IN', ttl: 3600, type: 'A', address: '192.0.2.127' }
|
|
5
|
+
|
|
6
|
+
const validRecords = [
|
|
7
|
+
{
|
|
8
|
+
...defaults,
|
|
9
|
+
owner: 'test.example.com.',
|
|
10
|
+
testB: 'test.example.com.\t3600\tIN\tA\t192.0.2.127\n',
|
|
11
|
+
testT: '+test.example.com:192.0.2.127:3600::\n',
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
...defaults,
|
|
15
|
+
owner: 'test.example.com.',
|
|
16
|
+
ttl: 2147483647,
|
|
17
|
+
testB: 'test.example.com.\t2147483647\tIN\tA\t192.0.2.127\n',
|
|
18
|
+
testT: '+test.example.com:192.0.2.127:2147483647::\n',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
...defaults,
|
|
22
|
+
owner: 'a.',
|
|
23
|
+
ttl: 86400,
|
|
24
|
+
testB: 'a.\t86400\tIN\tA\t192.0.2.127\n',
|
|
25
|
+
testT: '+a:192.0.2.127:86400::\n',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
...defaults,
|
|
29
|
+
owner: '*.example.com.',
|
|
30
|
+
testB: '*.example.com.\t3600\tIN\tA\t192.0.2.127\n',
|
|
31
|
+
testT: '+*.example.com:192.0.2.127:3600::\n',
|
|
32
|
+
},
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
const invalidRecords = [
|
|
36
|
+
{ ...defaults, owner: '', msg: /RFC/ },
|
|
37
|
+
{ ...defaults, owner: 'something*', msg: /fully/ },
|
|
38
|
+
{ ...defaults, owner: 'some*thing', msg: /fully/ },
|
|
39
|
+
{ ...defaults, owner: '*something', msg: /fully/ },
|
|
40
|
+
{ ...defaults, owner: 'something.*', msg: /fully/ },
|
|
41
|
+
{ ...defaults, address: 'hosts.not.valid.here', msg: /address must be IPv4/ },
|
|
42
|
+
{ ...defaults, address: '', msg: /address is required/ },
|
|
43
|
+
{ ...defaults, address: undefined, msg: /address is required/ },
|
|
44
|
+
{ ...defaults, address: '1.x.2.3', msg: /address must be IPv4/ },
|
|
45
|
+
{ ...defaults, address: '.1.2.3', msg: /address must be IPv4/ },
|
|
46
|
+
{ ...defaults, type: '', msg: /type is required/ },
|
|
47
|
+
{ ...defaults, type: undefined, msg: /type is required/ },
|
|
48
|
+
{ ...defaults, ttl: '', msg: /TTL must be numeric/ },
|
|
49
|
+
{ ...defaults, ttl: -299, msg: /TTL must be a 32-bit integer/ },
|
|
50
|
+
{ ...defaults, ttl: 2147483648, msg: /TTL must be a 32-bit integer/ },
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
// copy invalid properties to a valid object
|
|
54
|
+
for (let i = 0; i < invalidRecords.length; i++) {
|
|
55
|
+
const temp = JSON.parse(JSON.stringify(validRecords[0]))
|
|
56
|
+
Object.assign(temp, invalidRecords[i])
|
|
57
|
+
invalidRecords[i] = temp
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
describe('A record', function () {
|
|
61
|
+
base.valid(A, validRecords)
|
|
62
|
+
base.invalid(A, invalidRecords)
|
|
63
|
+
|
|
64
|
+
base.getDescription(A)
|
|
65
|
+
base.getRFCs(A, validRecords[0])
|
|
66
|
+
base.getRdataFields(A, ['address'])
|
|
67
|
+
base.getFields(A, ['address'])
|
|
68
|
+
base.getTypeId(A, 1)
|
|
69
|
+
|
|
70
|
+
base.toBind(A, validRecords)
|
|
71
|
+
base.toTinydns(A, validRecords)
|
|
72
|
+
|
|
73
|
+
base.fromBind(A, validRecords)
|
|
74
|
+
base.fromTinydns(A, validRecords)
|
|
75
|
+
})
|
package/test/aaaa.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import assert from 'assert'
|
|
2
|
+
|
|
3
|
+
import * as base from './base.js'
|
|
4
|
+
import AAAA from '../rr/aaaa.js'
|
|
5
|
+
|
|
6
|
+
const defaults = { class: 'IN', ttl: 3600, type: 'AAAA' }
|
|
7
|
+
|
|
8
|
+
const validRecords = [
|
|
9
|
+
{
|
|
10
|
+
...defaults,
|
|
11
|
+
owner: 'test.example.com.',
|
|
12
|
+
address: '2001:0db8:0020:000a:0000:0000:0000:0004',
|
|
13
|
+
testB: 'test.example.com.\t3600\tIN\tAAAA\t2001:db8:20:a::4\n',
|
|
14
|
+
testT:
|
|
15
|
+
':test.example.com:28:\\040\\001\\015\\270\\000\\040\\000\\012\\000\\000\\000\\000\\000\\000\\000\\004:3600::\n',
|
|
16
|
+
},
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
const invalidRecords = [
|
|
20
|
+
{
|
|
21
|
+
...defaults,
|
|
22
|
+
owner: 'test.example.com.',
|
|
23
|
+
address: '192.0.2.204',
|
|
24
|
+
msg: /address must be IPv6/,
|
|
25
|
+
},
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
describe('AAAA record', function () {
|
|
29
|
+
base.valid(AAAA, validRecords)
|
|
30
|
+
base.invalid(AAAA, invalidRecords)
|
|
31
|
+
|
|
32
|
+
base.getDescription(AAAA)
|
|
33
|
+
base.getRFCs(AAAA, validRecords[0])
|
|
34
|
+
base.getFields(AAAA, ['address'])
|
|
35
|
+
base.getTypeId(AAAA, 28)
|
|
36
|
+
|
|
37
|
+
base.toBind(AAAA, validRecords)
|
|
38
|
+
base.toTinydns(AAAA, validRecords)
|
|
39
|
+
|
|
40
|
+
base.fromBind(AAAA, validRecords)
|
|
41
|
+
base.fromTinydns(AAAA, validRecords)
|
|
42
|
+
|
|
43
|
+
for (const val of validRecords) {
|
|
44
|
+
it(`imports tinydns AAAA (generic) record (${val.owner})`, async function () {
|
|
45
|
+
const r = new AAAA({ tinyline: val.testT })
|
|
46
|
+
if (process.env.DEBUG) console.dir(r)
|
|
47
|
+
for (const f of ['owner', 'address', 'ttl']) {
|
|
48
|
+
assert.deepStrictEqual(
|
|
49
|
+
r.get(f),
|
|
50
|
+
val[f],
|
|
51
|
+
`${f}: ${r.get(f)} !== ${val[f]}`,
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const tests = [
|
|
58
|
+
{ e: '2001:0db8:0020:000a:0000:0000:0000:0004', c: '2001:db8:20:a::4' },
|
|
59
|
+
{ e: '0000:0000:0000:0000:0000:0000:0000:0000', c: '::' },
|
|
60
|
+
{ e: '0000:0000:0000:0000:0000:0000:0000:0001', c: '::1' },
|
|
61
|
+
{ e: '2001:0db8:0000:0000:0000:0000:0002:0001', c: '2001:db8::2:1' },
|
|
62
|
+
{ e: '2001:0db8:0000:0001:0001:0001:0001:0001', c: '2001:db8:0:1:1:1:1:1' },
|
|
63
|
+
{
|
|
64
|
+
e: '2001:0DB8:0000:0000:0008:0800:200C:417A',
|
|
65
|
+
c: '2001:DB8::8:800:200C:417A',
|
|
66
|
+
},
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
describe('compress', function () {
|
|
70
|
+
const r = new AAAA(null)
|
|
71
|
+
for (const t of tests) {
|
|
72
|
+
it(`compresses IPv6 address (${t.e})`, function () {
|
|
73
|
+
assert.equal(r.compress(t.e), t.c)
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
describe('expand', function () {
|
|
79
|
+
const r = new AAAA(null)
|
|
80
|
+
for (const t of tests) {
|
|
81
|
+
it(`expands IPv6 address (${t.c})`, function () {
|
|
82
|
+
assert.equal(r.expand(t.c), t.e)
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
})
|
package/test/base.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import assert from 'assert'
|
|
2
|
+
|
|
3
|
+
export function valid(type, validRecords, defaults) {
|
|
4
|
+
describe('valid', function () {
|
|
5
|
+
for (const val of validRecords) {
|
|
6
|
+
// console.log(val)
|
|
7
|
+
it(`parses record: ${val.owner}`, function () {
|
|
8
|
+
if (defaults) val.default = defaults
|
|
9
|
+
const r = new type(val)
|
|
10
|
+
if (defaults) delete val.default
|
|
11
|
+
if (process.env.DEBUG) console.dir(r)
|
|
12
|
+
|
|
13
|
+
for (const k of Object.keys(val)) {
|
|
14
|
+
if (/^test/.test(k)) continue
|
|
15
|
+
assert.strictEqual(
|
|
16
|
+
r.get(k),
|
|
17
|
+
val[k],
|
|
18
|
+
`${type.name} ${k} ${r.get(k)} !== ${val[k]}`,
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function invalid(type, invalidRecords, defaults) {
|
|
27
|
+
describe('invalid', function () {
|
|
28
|
+
for (const inv of invalidRecords) {
|
|
29
|
+
if (defaults) inv.default = defaults
|
|
30
|
+
it(`throws on record (${inv.owner})`, function () {
|
|
31
|
+
assert.throws(
|
|
32
|
+
() => {
|
|
33
|
+
new type(inv)
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
message: inv.msg,
|
|
37
|
+
},
|
|
38
|
+
)
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function toBind(type, validRecords) {
|
|
45
|
+
describe('toBind', function () {
|
|
46
|
+
for (const val of validRecords) {
|
|
47
|
+
it(`exports to BIND: ${val.owner}`, function () {
|
|
48
|
+
const r = new type(val).toBind()
|
|
49
|
+
if (process.env.DEBUG) console.dir(r)
|
|
50
|
+
assert.strictEqual(r, val.testB)
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function toTinydns(type, validRecords) {
|
|
57
|
+
describe('toTinydns', function () {
|
|
58
|
+
for (const val of validRecords) {
|
|
59
|
+
if (val.testT === undefined) continue
|
|
60
|
+
it(`exports to tinydns: ${val.owner}`, function () {
|
|
61
|
+
const r = new type(val).toTinydns()
|
|
62
|
+
if (process.env.DEBUG) console.dir(r)
|
|
63
|
+
assert.strictEqual(r, val.testT)
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function getDescription(type) {
|
|
70
|
+
describe('getDescription', function () {
|
|
71
|
+
const desc = new type(null).getDescription()
|
|
72
|
+
it(`gets description: ${desc}`, function () {
|
|
73
|
+
assert.ok(desc)
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function getRFCs(type, valid) {
|
|
79
|
+
describe('getRFCs', function () {
|
|
80
|
+
const r = new type(null)
|
|
81
|
+
const rfcs = r.getRFCs()
|
|
82
|
+
it(`can retrieve RFCs: ${rfcs.join(',')}`, function () {
|
|
83
|
+
assert.ok(rfcs.length)
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function checkFromNS(type, validRecords, nsName, nsLineName) {
|
|
89
|
+
for (const val of validRecords) {
|
|
90
|
+
const testLine = nsLineName === 'bindline' ? val.testB : val.testT
|
|
91
|
+
if (testLine == undefined) continue
|
|
92
|
+
it(`imports ${nsName} record: ${val.owner}`, function () {
|
|
93
|
+
const r = new type({ [nsLineName]: testLine })
|
|
94
|
+
if (process.env.DEBUG) console.dir(r)
|
|
95
|
+
for (const f of r.getFields()) {
|
|
96
|
+
if (f === 'class') continue
|
|
97
|
+
let expected = val[f]
|
|
98
|
+
if (f === 'data' && Array.isArray(expected))
|
|
99
|
+
expected = expected.join('') // TXT
|
|
100
|
+
assert.deepStrictEqual(
|
|
101
|
+
r.get(f),
|
|
102
|
+
expected,
|
|
103
|
+
`${f}: ${r.get(f)} !== ${expected}`,
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function fromTinydns(type, validRecords) {
|
|
111
|
+
describe('fromTinydns', function () {
|
|
112
|
+
checkFromNS(type, validRecords, 'tinydns', 'tinyline')
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function fromBind(type, validRecords) {
|
|
117
|
+
describe('fromBind', function () {
|
|
118
|
+
checkFromNS(type, validRecords, 'BIND', 'bindline')
|
|
119
|
+
})
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function getRdataFields(type, rdataFields) {
|
|
123
|
+
describe('getRdataFields', function () {
|
|
124
|
+
const r = new type(null)
|
|
125
|
+
it(`can retrieve rdata fields: (${r.getRdataFields('rdata')})`, function () {
|
|
126
|
+
assert.deepEqual(r.getRdataFields('rdata'), rdataFields)
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function getFields(type, rdataFields) {
|
|
132
|
+
describe('getFields', function () {
|
|
133
|
+
const r = new type(null)
|
|
134
|
+
it(`can retrieve record fields`, function () {
|
|
135
|
+
assert.deepEqual(r.getFields('rdata'), rdataFields)
|
|
136
|
+
assert.deepEqual(r.getFields(), r.getFields('common').concat(rdataFields))
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function getTypeId(type, val) {
|
|
142
|
+
describe('getTypeId', function () {
|
|
143
|
+
const r = new type(null)
|
|
144
|
+
it(`can retrieve record type ID (${r.getTypeId()})`, function () {
|
|
145
|
+
assert.deepEqual(r.getTypeId(), val)
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
}
|
package/test/caa.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import assert from 'assert'
|
|
2
|
+
|
|
3
|
+
import * as base from './base.js'
|
|
4
|
+
|
|
5
|
+
import CAA from '../rr/caa.js'
|
|
6
|
+
|
|
7
|
+
const validRecords = [
|
|
8
|
+
{
|
|
9
|
+
owner: 'ns1.example.com.',
|
|
10
|
+
ttl: 3600,
|
|
11
|
+
class: 'IN',
|
|
12
|
+
type: 'CAA',
|
|
13
|
+
flags: 0,
|
|
14
|
+
tag: 'issue',
|
|
15
|
+
value: 'http://letsencrypt.org',
|
|
16
|
+
testB: `ns1.example.com.\t3600\tIN\tCAA\t0\tissue\t"http://letsencrypt.org"\n`,
|
|
17
|
+
testT:
|
|
18
|
+
':ns1.example.com:257:\\000\\005issue"http\\072\\057\\057letsencrypt.org":3600::\n',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
owner: 'ns2.example.com.',
|
|
22
|
+
ttl: 3600,
|
|
23
|
+
class: 'IN',
|
|
24
|
+
type: 'CAA',
|
|
25
|
+
flags: 0,
|
|
26
|
+
tag: 'issue',
|
|
27
|
+
value: 'mailto:lets-crypt.org',
|
|
28
|
+
testB: `ns2.example.com.\t3600\tIN\tCAA\t0\tissue\t"mailto:lets-crypt.org"\n`,
|
|
29
|
+
testT:
|
|
30
|
+
':ns2.example.com:257:\\000\\005issue"mailto\\072lets-crypt.org":3600::\n',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
owner: 'example.net.',
|
|
34
|
+
ttl: 86400,
|
|
35
|
+
type: 'CAA',
|
|
36
|
+
flags: 0,
|
|
37
|
+
tag: 'issuewild',
|
|
38
|
+
value: 'https://letsencrypt.org',
|
|
39
|
+
testB:
|
|
40
|
+
'example.net.\t86400\tIN\tCAA\t0\tissuewild\t"https://letsencrypt.org"\n',
|
|
41
|
+
testT:
|
|
42
|
+
':example.net:257:\\000\\011issuewild"https\\072\\057\\057letsencrypt.org":86400::\n',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
owner: 'certs.example.com.',
|
|
46
|
+
ttl: 86400,
|
|
47
|
+
type: 'CAA',
|
|
48
|
+
flags: 0,
|
|
49
|
+
tag: 'issue',
|
|
50
|
+
value: 'ca1.example.net',
|
|
51
|
+
testB: 'certs.example.com.\t86400\tIN\tCAA\t0\tissue\t"ca1.example.net"\n',
|
|
52
|
+
testT: ':certs.example.com:257:\\000\\005issue"ca1.example.net":86400::\n',
|
|
53
|
+
},
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
const invalidRecords = [
|
|
57
|
+
{
|
|
58
|
+
owner: 'example.com.',
|
|
59
|
+
type: 'CAA',
|
|
60
|
+
flags: 128,
|
|
61
|
+
tag: 'iodef',
|
|
62
|
+
value: 'letsencrypt.org', // missing iodef prefix
|
|
63
|
+
msg: /RFC/,
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
owner: 'example.com.',
|
|
67
|
+
type: 'CAA',
|
|
68
|
+
flags: 128,
|
|
69
|
+
tag: 'invalid', // invalid
|
|
70
|
+
value: 'http://letsencrypt.org',
|
|
71
|
+
msg: /RFC/,
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
owner: 'example.com.',
|
|
75
|
+
type: 'CAA',
|
|
76
|
+
flags: 15, // invalid
|
|
77
|
+
tag: 'issue',
|
|
78
|
+
value: 'http://letsencrypt.org',
|
|
79
|
+
msg: /RFC/,
|
|
80
|
+
},
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
describe('CAA record', function () {
|
|
84
|
+
base.valid(CAA, validRecords)
|
|
85
|
+
base.invalid(CAA, invalidRecords, { ttl: 3600 })
|
|
86
|
+
|
|
87
|
+
base.getDescription(CAA)
|
|
88
|
+
base.getRFCs(CAA, validRecords[0])
|
|
89
|
+
base.getFields(CAA, ['flags', 'tag', 'value'])
|
|
90
|
+
base.getTypeId(CAA, 257)
|
|
91
|
+
|
|
92
|
+
base.toBind(CAA, validRecords)
|
|
93
|
+
base.toTinydns(CAA, validRecords)
|
|
94
|
+
|
|
95
|
+
base.fromBind(CAA, validRecords)
|
|
96
|
+
base.fromTinydns(CAA, validRecords)
|
|
97
|
+
|
|
98
|
+
for (const val of validRecords) {
|
|
99
|
+
it(`imports tinydns CAA (generic) record`, async function () {
|
|
100
|
+
const r = new CAA({ tinyline: val.testT })
|
|
101
|
+
if (process.env.DEBUG) console.dir(r)
|
|
102
|
+
for (const f of ['owner', 'flags', 'tag', 'value', 'ttl']) {
|
|
103
|
+
assert.deepStrictEqual(
|
|
104
|
+
r.get(f),
|
|
105
|
+
val[f],
|
|
106
|
+
`${f}: ${r.get(f)} !== ${val[f]}`,
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
})
|