@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.
Files changed (81) hide show
  1. package/.codeclimate.yml +25 -0
  2. package/.github/FUNDING.yml +3 -0
  3. package/.github/workflows/ci.yml +43 -0
  4. package/.github/workflows/codeql.yml +14 -0
  5. package/.github/workflows/publish.yml +16 -0
  6. package/.gitmodules +3 -0
  7. package/CHANGELOG.md +18 -0
  8. package/DEVELOP.md +9 -0
  9. package/README.md +6 -2
  10. package/eslint.config.mjs +41 -0
  11. package/index.js +6 -0
  12. package/package.json +12 -3
  13. package/rr/a.js +13 -3
  14. package/rr/aaaa.js +14 -4
  15. package/rr/caa.js +25 -13
  16. package/rr/cname.js +3 -3
  17. package/rr/dname.js +4 -4
  18. package/rr/dnskey.js +6 -11
  19. package/rr/ds.js +6 -8
  20. package/rr/hinfo.js +8 -6
  21. package/rr/https.js +62 -0
  22. package/rr/ipseckey.js +20 -9
  23. package/rr/key.js +2 -2
  24. package/rr/loc.js +4 -4
  25. package/rr/mx.js +15 -3
  26. package/rr/naptr.js +2 -2
  27. package/rr/ns.js +1 -1
  28. package/rr/nsec.js +2 -4
  29. package/rr/nsec3.js +6 -12
  30. package/rr/nsec3param.js +4 -9
  31. package/rr/nxt.js +2 -4
  32. package/rr/openpgpkey.js +7 -3
  33. package/rr/rrsig.js +3 -4
  34. package/rr/sig.js +1 -1
  35. package/rr/smimea.js +3 -5
  36. package/rr/soa.js +1 -2
  37. package/rr/spf.js +1 -1
  38. package/rr/srv.js +4 -4
  39. package/rr/sshfp.js +1 -1
  40. package/rr/svcb.js +64 -0
  41. package/rr/tlsa.js +10 -10
  42. package/rr/tsig.js +10 -2
  43. package/rr/txt.js +5 -5
  44. package/rr/uri.js +2 -2
  45. package/rr/wks.js +4 -3
  46. package/rr.js +32 -20
  47. package/test/a.js +75 -0
  48. package/test/aaaa.js +86 -0
  49. package/test/base.js +148 -0
  50. package/test/caa.js +111 -0
  51. package/test/cert.js +48 -0
  52. package/test/cname.js +40 -0
  53. package/test/dname.js +57 -0
  54. package/test/dnskey.js +45 -0
  55. package/test/ds.js +45 -0
  56. package/test/fake.js +34 -0
  57. package/test/hinfo.js +80 -0
  58. package/test/https.js +56 -0
  59. package/test/ipseckey.js +141 -0
  60. package/test/key.js +36 -0
  61. package/test/loc.js +75 -0
  62. package/test/mx.js +78 -0
  63. package/test/naptr.js +47 -0
  64. package/test/ns.js +56 -0
  65. package/test/nsec.js +37 -0
  66. package/test/nsec3.js +47 -0
  67. package/test/nsec3param.js +23 -0
  68. package/test/nxt.js +37 -0
  69. package/test/openpgpkey.js +85 -0
  70. package/test/ptr.js +56 -0
  71. package/test/rr.js +212 -0
  72. package/test/smimea.js +51 -0
  73. package/test/soa.js +99 -0
  74. package/test/spf.js +51 -0
  75. package/test/srv.js +101 -0
  76. package/test/sshfp.js +69 -0
  77. package/test/svcb.js +56 -0
  78. package/test/tinydns.js +163 -0
  79. package/test/tlsa.js +63 -0
  80. package/test/txt.js +82 -0
  81. 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 ['algorithm name', 'time signed', 'fudge', 'mac', 'original id', 'error', 'other']
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
- 'algorithm': algorithm,
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) throw new Error('TXT fromTinydns, invalid n')
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.split(
74
- /^([^\s]+)\s+([0-9]+)\s+(\w+)\s+(\w+)\s+?\s*(.*?)\s*$/,
75
- )
76
- if (!match) throw new Error(`unable to parse TXT: ${opts.bindline}`)
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) throw new Error(`URI: target is required, ${this.citeRFC()}`)
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) throw new Error('URI fromTinydns, invalid n')
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] = opts.bindline.split(/\s+/)
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, // 32-bit int
38
+ address, // 32-bit int
38
39
  protocol, // 8-bit int, 6=TCP, 17=UDP
39
- bitmap, // var len bit map, must be multiple of 8
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
- throw new Error(`Missing ${fnName} in class ${this.get('type')}`)
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
- throw new Error(`invalid class ${c}`)
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) throw new Error(`owner is required`)
79
+ if (n === undefined) this.throwHelp(`owner is required`)
78
80
 
79
81
  if (n.length < 1 || n.length > 255)
80
- throw new Error(
82
+ this.throwHelp(
81
83
  'Domain names must have 1-255 octets (characters): RFC 2181',
82
84
  )
83
85
 
84
- this.isFullyQualified('', 'owner', n)
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
- throw new Error('only *.something or * (by itself) is a valid wildcard')
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
- throw new Error('TTL is required, no default available')
102
+ this.throwHelp('TTL is required, no default available')
101
103
  }
102
104
 
103
105
  if (typeof t !== 'number')
104
- throw new Error(`TTL must be numeric (${typeof t})`)
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
- throw new Error(`type is required`)
118
+ this.throwHelp(`type is required`)
117
119
  }
118
120
 
119
121
  if (t.toUpperCase() !== this.constructor.name)
120
- throw new Error(`type ${t} doesn't match ${this.constructor.name}`)
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) throw new Error(`empty value for field ${field}`)
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
- throw new Error('Labels must have 1-63 octets (characters), RFC 2181')
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
- throw new Error(
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
- throw new Error(
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
- throw new Error(
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, blah, hostname) {
300
+ isFullyQualified(type, field, hostname) {
289
301
  if (hostname.endsWith('.')) return true
290
302
 
291
- throw new Error(`${type}: ${blah} must be fully qualified`)
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
- throw new Error(
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
- // throw new Error(`\nMaraDNS does not support ${type} records yet and this package does not support MaraDNS generic records. Yet.\n`)
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
+ })