@nictool/dns-resource-record 1.3.1 → 1.5.0

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/CHANGELOG.md CHANGED
@@ -4,6 +4,32 @@ Notable changes to this project are documented in this file.
4
4
 
5
5
  #### Unreleased
6
6
 
7
+ ### [1.5.0] - 2026-03-22
8
+
9
+ - feat(RRSIG, SIG, TSIG): added toBind, toTinydns, fromTinydns
10
+ - feat(TSIG): added missing setters
11
+ - feat(fromTinydns): added to APL, DHCID, HIP, KX, RRSIG, RP, WKS
12
+ - feat(CERT): validate mnemonic values
13
+ - fix: hardened polynomial regexes #56
14
+ - fix(NAPTR): replace polynomial regexp
15
+ - test: coverage is at 98% 🎉
16
+ - test(NSEC3PARAM): added test cases, better binary parser, better salt
17
+ - test(AAAA, APL, CERT, KEY, NAPTR, NSEC3, NSEC3PARAM, RRSIG, SIG, SSHFP, TSIG, URL): added test coverage
18
+ - doc(README): updated RR table completion status
19
+ - style(es6): use ?. and ?? operators (nullish coalescing) #57
20
+ - style(es6): use argument expansion / destructured parameters
21
+ - style(es6): substring -> slice #55
22
+ - style(es6): use more trim() #55
23
+
24
+ ### [1.4.0] - 2026-03-21
25
+
26
+ - feat(toBind): added to nsec3param, rrsig
27
+ - feat(toTinydns): added to key, nsec3param, nxt, openpgpkey
28
+ - feat(fromTinydns): added to cert, https, key, nsec, nsec3param, nxt, openpgpkey, smimea, svcb
29
+ - fix(naptr): fixed flags calc in fromTinydns
30
+ - fix(tinydns): more robust unpackDomainName
31
+ - test: switch test runner to node --test
32
+
7
33
  ### [1.3.1] - 2026-03-13
8
34
 
9
35
  - ci: update workflow triggers
@@ -338,3 +364,5 @@ Notable changes to this project are documented in this file.
338
364
  [1.2.4]: https://github.com/NicTool/dns-resource-record/releases/tag/1.2.4
339
365
  [1.3.0]: https://github.com/NicTool/dns-resource-record/releases/tag/v1.3.0
340
366
  [1.3.1]: https://github.com/NicTool/dns-resource-record/releases/tag/v1.3.1
367
+ [1.4.0]: https://github.com/NicTool/dns-resource-record/releases/tag/v1.4.0
368
+ [1.5.0]: https://github.com/NicTool/dns-resource-record/releases/tag/v1.5.0
package/README.md CHANGED
@@ -19,6 +19,7 @@ This module is used to:
19
19
  | **[Tinydns](https://cr.yp.to/djbdns/tinydns-data.html)** | :white_check_mark: | :white_check_mark: |
20
20
  | **MaraDNS** | | :white_check_mark: |
21
21
  | **JS** | :white_check_mark: | :white_check_mark: |
22
+ | **PowerDNS** | | |
22
23
 
23
24
  This package intends to import and export RFC compliant DNS resource records. Please [raise an issue](https://github.com/NicTool/dns-resource-record/issues) if a valid resource record fails to pass or an invalid resource record passes.
24
25
 
@@ -189,44 +190,44 @@ PRs are welcome, especially PRs with tests.
189
190
  | :------------: | :----------------: | :----------------: | :----------------: | :----------------: |
190
191
  | **A** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
191
192
  | **AAAA** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
192
- | **APL** | :white_check_mark: | :white_check_mark: | :white_check_mark: | |
193
+ | **APL** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
193
194
  | **CAA** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
194
- | **CERT** | :white_check_mark: | :white_check_mark: | :white_check_mark: | |
195
+ | **CERT** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
195
196
  | **CNAME** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
196
- | **DHCID** | :white_check_mark: | :white_check_mark: | :white_check_mark: | |
197
+ | **DHCID** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
197
198
  | **DNAME** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
198
199
  | **DNSKEY** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
199
200
  | **DS** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
200
201
  | **HINFO** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
201
- | **HIP** | :white_check_mark: | :white_check_mark: | :white_check_mark: | |
202
- | **HTTPS** | :white_check_mark: | :white_check_mark: | :white_check_mark: | |
202
+ | **HIP** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
203
+ | **HTTPS** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
203
204
  | **IPSECKEY** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
204
- | **KEY** | | | | |
205
- | **KX** | :white_check_mark: | :white_check_mark: | :white_check_mark: | |
205
+ | **KEY** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
206
+ | **KX** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
206
207
  | **LOC** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
207
208
  | **MX** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
208
209
  | **NAPTR** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
209
210
  | **NS** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
210
- | **NSEC** | :white_check_mark: | :white_check_mark: | :white_check_mark: | |
211
- | **NSEC3** | :white_check_mark: | :white_check_mark: | :white_check_mark: | |
212
- | **NSEC3PARAM** | | | | |
213
- | **NXT** | | | | |
214
- | **OPENPGPKEY** | | | | |
211
+ | **NSEC** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
212
+ | **NSEC3** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
213
+ | **NSEC3PARAM** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
214
+ | **NXT** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
215
+ | **OPENPGPKEY** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
215
216
  | **PTR** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
216
- | **RP** | :white_check_mark: | :white_check_mark: | :white_check_mark: | |
217
- | **RRSIG** | | | | |
218
- | **SIG** | | | | |
219
- | **SMIMEA** | :white_check_mark: | :white_check_mark: | :white_check_mark: | |
217
+ | **RP** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
218
+ | **RRSIG** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
219
+ | **SIG** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
220
+ | **SMIMEA** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
220
221
  | **SOA** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
221
222
  | **SPF** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
222
223
  | **SRV** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
223
224
  | **SSHFP** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
224
- | **SVCB** | :white_check_mark: | :white_check_mark: | :white_check_mark: | |
225
+ | **SVCB** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
225
226
  | **TLSA** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
226
- | **TSIG** | | | | |
227
+ | **TSIG** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
227
228
  | **TXT** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
228
229
  | **URI** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
229
- | **WKS** | :white_check_mark: | :white_check_mark: | :white_check_mark: | |
230
+ | **WKS** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
230
231
 
231
232
  ## TIPS
232
233
 
package/lib/tinydns.js CHANGED
@@ -27,9 +27,13 @@ export function escapeOctal(re, str) {
27
27
  return escaped
28
28
  }
29
29
 
30
+ export function unescapeOctal(str) {
31
+ return this.octalToChar(str)
32
+ }
33
+
30
34
  export function octalToChar(str) {
31
35
  // relace instances of \NNN with ASCII
32
- return str.replace(octalRe, (o) => String.fromCharCode(parseInt(o.substring(1), 8)))
36
+ return str.replace(octalRe, (o) => String.fromCharCode(parseInt(o.slice(1), 8)))
33
37
  }
34
38
 
35
39
  export function octalToHex(str) {
@@ -37,29 +41,29 @@ export function octalToHex(str) {
37
41
  return str.replace(octalRe, (o) => {
38
42
  // parseInt(n, 8) -> from octal to decimal
39
43
  // .toString(16) -> decimal to hex
40
- return parseInt(o.substring(1), 8).toString(16).padStart(2, 0)
44
+ return parseInt(o.slice(1), 8).toString(16).padStart(2, 0)
41
45
  })
42
46
  }
43
47
 
44
48
  export function octalToUInt8(str) {
45
49
  const b = Buffer.alloc(1)
46
- b.writeUInt8(parseInt(str.substring(1, 4), 8), 0)
50
+ b.writeUInt8(parseInt(str.slice(1, 4), 8), 0)
47
51
  return b.readUInt8()
48
52
  }
49
53
 
50
54
  export function octalToUInt16(str) {
51
55
  const b = Buffer.alloc(2)
52
- b.writeUInt8(parseInt(str.substring(1, 4), 8), 0)
53
- b.writeUInt8(parseInt(str.substring(5, 8), 8), 1)
56
+ b.writeUInt8(parseInt(str.slice(1, 4), 8), 0)
57
+ b.writeUInt8(parseInt(str.slice(5, 8), 8), 1)
54
58
  return b.readUInt16BE()
55
59
  }
56
60
 
57
61
  export function octalToUInt32(str) {
58
62
  const b = Buffer.alloc(4)
59
- b.writeUInt8(parseInt(str.substring(1, 4), 8), 0)
60
- b.writeUInt8(parseInt(str.substring(5, 8), 8), 1)
61
- b.writeUInt8(parseInt(str.substring(9, 12), 8), 2)
62
- b.writeUInt8(parseInt(str.substring(13, 16), 8), 3)
63
+ b.writeUInt8(parseInt(str.slice(1, 4), 8), 0)
64
+ b.writeUInt8(parseInt(str.slice(5, 8), 8), 1)
65
+ b.writeUInt8(parseInt(str.slice(9, 12), 8), 2)
66
+ b.writeUInt8(parseInt(str.slice(13, 16), 8), 3)
63
67
  return b.readUInt32BE()
64
68
  }
65
69
 
@@ -95,7 +99,7 @@ export function packDomainName(fqdn) {
95
99
  // RFC 1035, 3.3 Standard RRs
96
100
  // The standard wire format for DNS names. (1 octet length + octets)
97
101
  let packed = ''
98
- fqdn.split('.').map((label) => {
102
+ fqdn.split('.').forEach((label) => {
99
103
  if (label === undefined || !label.length) return
100
104
 
101
105
  const len = Buffer.alloc(1)
@@ -108,22 +112,39 @@ export function packDomainName(fqdn) {
108
112
  return packed
109
113
  }
110
114
 
111
- export function unpackDomainName(fqdn) {
112
- fqdn = Buffer.from(octalToChar(fqdn.toString()))
113
-
114
- const labels = []
115
+ export function unpackDomainName(escaped) {
115
116
  let pos = 0
116
- let len
117
- while ((len = fqdn.readUInt8(pos))) {
118
- // encoded length byte
119
- pos++
120
- labels.push(fqdn.slice(pos, pos + len).toString())
121
- pos = +(pos + len)
117
+ let binaryLen = 0
118
+ const labels = []
119
+
120
+ // consume the next logical "byte" (char or octal escape)
121
+ const getNextByte = () => {
122
+ if (pos >= escaped.length) return null
123
+
124
+ let value
125
+ if (escaped[pos] === '\\') {
126
+ value = parseInt(escaped.slice(pos + 1, pos + 4), 8)
127
+ pos += 4
128
+ } else {
129
+ value = escaped.charCodeAt(pos++)
130
+ }
131
+
132
+ binaryLen++
133
+ return value
122
134
  }
123
- const r = `${labels.join('.')}.`
124
- // char position + length of last label + label length chars + null byte
125
- const strLen = pos + len + labels.length * 4 + 1
126
- return [r, strLen]
135
+
136
+ let lengthByte
137
+ while ((lengthByte = getNextByte()) && lengthByte !== 0) {
138
+ let label = ''
139
+ for (let i = 0; i < lengthByte; i++) {
140
+ const char = getNextByte()
141
+ if (char === null) break
142
+ label += String.fromCharCode(char)
143
+ }
144
+ labels.push(label)
145
+ }
146
+
147
+ return [`${labels.join('.')}.`, pos, binaryLen]
127
148
  }
128
149
 
129
150
  export function packHex(str) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nictool/dns-resource-record",
3
- "version": "1.3.1",
3
+ "version": "1.5.0",
4
4
  "description": "DNS Resource Records",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -12,15 +12,16 @@
12
12
  "type": "module",
13
13
  "scripts": {
14
14
  "format:check": "npm run prettier; npm run lint",
15
- "format": "npm run prettier -- --write && npm run lint --fix",
15
+ "format": "npm run prettier -- --write && npm run lint:fix",
16
16
  "lint": "npx eslint **/*.js",
17
17
  "lint:fix": "npm run lint -- --fix",
18
18
  "prettier": "npx prettier --ignore-path .gitignore --check .",
19
19
  "prettier:fix": "npx prettier --ignore-path .gitignore --write .",
20
- "test": "npx mocha",
21
- "versions": "npx dependency-version-checker check",
22
- "watch": "npm run test -- --watch",
23
- "versions:fix": "npx dependency-version-checker update"
20
+ "test": "node --test test/*.js",
21
+ "test:coverage": "npx c8 --reporter=text --reporter=text-summary npm test",
22
+ "versions": "npx npm-dep-mgr check",
23
+ "versions:fix": "npx npm-dep-mgr update",
24
+ "watch": "node --test --watch test/*.js"
24
25
  },
25
26
  "repository": {
26
27
  "type": "git",
@@ -47,9 +48,8 @@
47
48
  "homepage": "https://github.com/NicTool/dns-resource-record#readme",
48
49
  "devDependencies": {
49
50
  "@eslint/js": "^10.0.1",
50
- "eslint": "^10.0.3",
51
- "globals": "^17.4.0",
52
- "mocha": "^11.7.5"
51
+ "eslint": "^10.1.0",
52
+ "globals": "^17.4.0"
53
53
  },
54
54
  "prettier": {
55
55
  "printWidth": 110,
package/rr/a.js CHANGED
@@ -39,9 +39,9 @@ export default class A extends RR {
39
39
  }
40
40
 
41
41
  /****** IMPORTERS *******/
42
- fromTinydns(opts) {
42
+ fromTinydns({ tinyline }) {
43
43
  // +fqdn:ip:ttl:timestamp:lo
44
- const [owner, ip, ttl, ts, loc] = opts.tinyline.substring(1).split(':')
44
+ const [owner, ip, ttl, ts, loc] = tinyline.slice(1).split(':')
45
45
 
46
46
  return new A({
47
47
  owner: this.fullyQualify(owner),
@@ -49,13 +49,13 @@ export default class A extends RR {
49
49
  address: ip,
50
50
  ttl: parseInt(ttl, 10),
51
51
  timestamp: ts,
52
- location: loc !== '' && loc !== '\n' ? loc : '',
52
+ location: loc?.trim() ?? '',
53
53
  })
54
54
  }
55
55
 
56
- fromBind(opts) {
56
+ fromBind({ bindline }) {
57
57
  // test.example.com 3600 IN A 192.0.2.127
58
- const [owner, ttl, c, type, address] = opts.bindline.split(/\s+/)
58
+ const [owner, ttl, c, type, address] = bindline.split(/\s+/)
59
59
  return new A({
60
60
  owner,
61
61
  ttl: parseInt(ttl, 10),
package/rr/aaaa.js CHANGED
@@ -15,7 +15,7 @@ export default class AAAA extends RR {
15
15
  }
16
16
 
17
17
  getCompressed(val) {
18
- return this.compress(val || this.get('address'))
18
+ return this.compress(val ?? this.get('address'))
19
19
  }
20
20
 
21
21
  getDescription() {
@@ -45,14 +45,14 @@ export default class AAAA extends RR {
45
45
  }
46
46
 
47
47
  /****** IMPORTERS *******/
48
- fromTinydns(opts) {
49
- const str = opts.tinyline
48
+ fromTinydns({ tinyline }) {
49
+ const str = tinyline
50
50
  let fqdn, ip, n, rdata, ttl, ts, loc
51
51
 
52
52
  switch (str[0]) {
53
53
  case ':':
54
54
  // GENERIC => :fqdn:28:rdata:ttl:timestamp:lo
55
- ;[fqdn, n, rdata, ttl, ts, loc] = str.substring(1).split(':')
55
+ ;[fqdn, n, rdata, ttl, ts, loc] = str.slice(1).split(':')
56
56
  if (n != 28) this.throwHelp('AAAA fromTinydns, invalid n')
57
57
  ip = TINYDNS.octalToHex(rdata)
58
58
  .match(/([0-9a-fA-F]{4})/g)
@@ -62,7 +62,7 @@ export default class AAAA extends RR {
62
62
  case '6':
63
63
  // AAAA => 3fqdn:ip:x:ttl:timestamp:lo
64
64
  // AAAA,PTR => 6fqdn:ip:x:ttl:timestamp:lo
65
- ;[fqdn, rdata, ttl, ts, loc] = str.substring(1).split(':')
65
+ ;[fqdn, rdata, ttl, ts, loc] = str.slice(1).split(':')
66
66
  ip = rdata.match(/(.{4})/g).join(':')
67
67
  break
68
68
  }
@@ -73,13 +73,13 @@ export default class AAAA extends RR {
73
73
  type: 'AAAA',
74
74
  address: ip,
75
75
  timestamp: ts,
76
- location: loc !== '' && loc !== '\n' ? loc : '',
76
+ location: loc?.trim() ?? '',
77
77
  })
78
78
  }
79
79
 
80
- fromBind(opts) {
80
+ fromBind({ bindline }) {
81
81
  // test.example.com 3600 IN AAAA ...
82
- const [owner, ttl, c, type, ip] = opts.bindline.split(/\s+/)
82
+ const [owner, ttl, c, type, ip] = bindline.split(/\s+/)
83
83
  return new AAAA({
84
84
  owner,
85
85
  ttl: parseInt(ttl, 10),
@@ -124,7 +124,7 @@ export default class AAAA extends RR {
124
124
  if (delimiter === undefined) delimiter = ':'
125
125
 
126
126
  const colons = val.match(/:/g)
127
- if (colons && colons.length < 7) {
127
+ if (colons?.length < 7) {
128
128
  // console.log(`AAAA: restoring compressed colons`)
129
129
  val = val.replace(/::/, ':'.repeat(9 - colons.length))
130
130
  }
package/rr/apl.js CHANGED
@@ -42,9 +42,56 @@ export default class APL extends RR {
42
42
  }
43
43
 
44
44
  /****** IMPORTERS *******/
45
- fromBind(opts) {
45
+ fromTinydns({ tinyline }) {
46
+ // APL via generic, :fqdn:42:rdata:ttl:timestamp:lo
47
+ const [fqdn, n, rdata, ttl, ts, loc] = tinyline.slice(1).split(':')
48
+ if (n != 42) this.throwHelp('APL fromTinydns, invalid n')
49
+
50
+ const bytes = Buffer.from(TINYDNS.octalToChar(rdata), 'binary')
51
+ const items = []
52
+ let pos = 0
53
+
54
+ while (pos < bytes.length) {
55
+ const afi = bytes.readUInt16BE(pos)
56
+ pos += 2
57
+ const prefix = bytes.readUInt8(pos)
58
+ pos++
59
+ const adfLenByte = bytes.readUInt8(pos)
60
+ pos++
61
+ const neg = (adfLenByte & 0x80) !== 0
62
+ const addrLen = adfLenByte & 0x7f
63
+ const addrBytes = bytes.slice(pos, pos + addrLen)
64
+ pos += addrLen
65
+
66
+ let addr
67
+ if (afi === 1) {
68
+ const padded = Buffer.alloc(4)
69
+ addrBytes.copy(padded)
70
+ addr = [...padded].join('.')
71
+ } else {
72
+ const padded = Buffer.alloc(16)
73
+ addrBytes.copy(padded)
74
+ const groups = []
75
+ for (let i = 0; i < 16; i += 2) groups.push(padded.readUInt16BE(i).toString(16).padStart(4, '0'))
76
+ addr = this.compressIPv6(groups.join(':'))
77
+ }
78
+
79
+ items.push(`${neg ? '!' : ''}${afi}:${addr}/${prefix}`)
80
+ }
81
+
82
+ return new APL({
83
+ owner: this.fullyQualify(fqdn),
84
+ ttl: parseInt(ttl, 10),
85
+ type: 'APL',
86
+ 'apl rdata': items.join(' '),
87
+ timestamp: ts,
88
+ location: loc?.trim() ?? '',
89
+ })
90
+ }
91
+
92
+ fromBind({ bindline }) {
46
93
  // test.example.com 3600 IN APL {[!]afi:address/prefix}*
47
- const parts = opts.bindline.split(/\s+/)
94
+ const parts = bindline.split(/\s+/)
48
95
  const [owner, ttl, c, type] = parts
49
96
  return new APL({
50
97
  owner,
package/rr/caa.js CHANGED
@@ -91,17 +91,17 @@ export default class CAA extends RR {
91
91
  }
92
92
 
93
93
  /****** IMPORTERS *******/
94
- fromTinydns(opts) {
94
+ fromTinydns({ tinyline }) {
95
95
  // CAA via generic, :fqdn:n:rdata:ttl:timestamp:lo
96
- const [fqdn, n, rdata, ttl, ts, loc] = opts.tinyline.substring(1).split(':')
96
+ const [fqdn, n, rdata, ttl, ts, loc] = tinyline.slice(1).split(':')
97
97
  if (n != 257) this.throwHelp('CAA fromTinydns, invalid n')
98
98
 
99
- const flags = TINYDNS.octalToUInt8(rdata.substring(0, 4))
100
- const taglen = TINYDNS.octalToUInt8(rdata.substring(4, 8))
99
+ const flags = TINYDNS.octalToUInt8(rdata.slice(0, 4))
100
+ const taglen = TINYDNS.octalToUInt8(rdata.slice(4, 8))
101
101
 
102
- const unescaped = TINYDNS.octalToChar(rdata.substring(8))
103
- const tag = unescaped.substring(0, taglen)
104
- const fingerprint = unescaped.substring(taglen)
102
+ const unescaped = TINYDNS.octalToChar(rdata.slice(8))
103
+ const tag = unescaped.slice(0, taglen)
104
+ const fingerprint = unescaped.slice(taglen)
105
105
 
106
106
  return new CAA({
107
107
  owner: this.fullyQualify(fqdn),
@@ -111,17 +111,23 @@ export default class CAA extends RR {
111
111
  tag,
112
112
  value: fingerprint,
113
113
  timestamp: ts,
114
- location: loc !== '' && loc !== '\n' ? loc : '',
114
+ location: loc?.trim() ?? '',
115
115
  })
116
116
  }
117
117
 
118
- fromBind(opts) {
118
+ fromBind({ bindline }) {
119
119
  // test.example.com 3600 IN CAA flags, tags, value
120
- const regex = /^([\S]+)\s+([0-9]{1,10})\s+(IN)\s+(CAA)\s+([0-9]+)\s+(\w+)\s+("[^"]+"|[\S]+?)$/i
121
- const fields = opts.bindline.trim().match(regex)
122
- if (!fields) this.throwHelp(`unable to parse: ${opts.bindline}`)
120
+ const regex =
121
+ /^(?<owner>\S+)\s+(?<ttl>\d{1,10})\s+(?<class>IN)\s+(?<type>CAA)\s+(?<flags>\d+)\s+(?<tag>\w+)\s+(?:"(?<quotedValue>[^"]+)"|(?<unquotedValue>\S+))$/i
122
+
123
+ const match = bindline.trim().match(regex)
124
+
125
+ if (!match) {
126
+ this.throwHelp(`unable to parse CAA: ${bindline}`)
127
+ }
128
+
129
+ const { owner, ttl, class: c, type, flags, tag, quotedValue, unquotedValue } = match.groups
123
130
 
124
- const [owner, ttl, c, type, flags, tag, value] = fields.slice(1)
125
131
  return new CAA({
126
132
  owner,
127
133
  ttl: parseInt(ttl, 10),
@@ -129,21 +135,18 @@ export default class CAA extends RR {
129
135
  type,
130
136
  flags: parseInt(flags, 10),
131
137
  tag,
132
- value,
138
+ value: quotedValue ?? unquotedValue,
133
139
  })
134
140
  }
135
141
 
136
142
  /****** EXPORTERS *******/
137
143
 
138
144
  toTinydns() {
139
- let rdata = ''
140
- rdata += TINYDNS.UInt8toOctal(this.get('flags'))
141
-
142
- rdata += TINYDNS.UInt8toOctal(this.get('tag').length)
143
- rdata += TINYDNS.escapeOctal(/[\r\n\t:\\/]/, this.get('tag'))
144
-
145
- rdata += TINYDNS.escapeOctal(/[\r\n\t:\\/]/, this.getQuoted('value'))
146
-
147
- return this.getTinydnsGeneric(rdata)
145
+ return this.getTinydnsGeneric(
146
+ TINYDNS.UInt8toOctal(this.get('flags')) +
147
+ TINYDNS.UInt8toOctal(this.get('tag').length) +
148
+ TINYDNS.escapeOctal(/[\r\n\t:\\/]/, this.get('tag')) +
149
+ TINYDNS.escapeOctal(/[\r\n\t:\\/]/, this.getQuoted('value')),
150
+ )
148
151
  }
149
152
  }
package/rr/cert.js CHANGED
@@ -11,8 +11,29 @@ export default class CERT extends RR {
11
11
  setCertType(val) {
12
12
  // The type field is the certificate type
13
13
  // the type field as an unsigned decimal integer or as a mnemonic symbol
14
- // this.is16bitInt('CERT', 'type', val)
15
-
14
+ if (val === undefined || val === null || val === '') {
15
+ this.throwHelp('cert type is required')
16
+ }
17
+ // Accept both mnemonic and numeric, but validate mnemonic
18
+ if (typeof val === 'string') {
19
+ const types = {
20
+ PKIX: 1,
21
+ SPKI: 2,
22
+ PGP: 3,
23
+ IPKIX: 4,
24
+ ISPKI: 5,
25
+ IPGP: 6,
26
+ ACPKIX: 7,
27
+ IACPKIX: 8,
28
+ URI: 253,
29
+ OID: 254,
30
+ }
31
+ if (!Object.hasOwn(types, val)) {
32
+ this.throwHelp(`CERT: unknown cert type mnemonic: ${val}`)
33
+ }
34
+ } else {
35
+ this.is16bitInt('CERT', 'cert type', val)
36
+ }
16
37
  this.set('cert type', val)
17
38
  }
18
39
 
@@ -54,6 +75,9 @@ export default class CERT extends RR {
54
75
  setCertificate(val) {
55
76
  // certificate/CRL portion is represented in base 64 [16] and may be
56
77
  // divided into any number of white-space-separated substrings
78
+ if (val === undefined || val === null || val === '') {
79
+ this.throwHelp('certificate is required and cannot be empty')
80
+ }
57
81
  this.set('certificate', val)
58
82
  }
59
83
 
@@ -75,9 +99,44 @@ export default class CERT extends RR {
75
99
 
76
100
  /****** IMPORTERS *******/
77
101
 
78
- fromBind(opts) {
102
+ fromTinydns({ tinyline }) {
103
+ const [owner, n, rdata, ttl, ts, loc] = tinyline.slice(1).split(':')
104
+ if (n != 37) this.throwHelp('CERT fromTinydns, invalid n')
105
+
106
+ const bytes = Buffer.from(TINYDNS.octalToChar(rdata), 'binary')
107
+ const typeNum = bytes.readUInt16BE(0)
108
+ let certType = typeNum
109
+
110
+ const types = {
111
+ 1: 'PKIX',
112
+ 2: 'SPKI',
113
+ 3: 'PGP',
114
+ 4: 'IPKIX',
115
+ 5: 'ISPKI',
116
+ 6: 'IPGP',
117
+ 7: 'ACPKIX',
118
+ 8: 'IACPKIX',
119
+ 253: 'URI',
120
+ 254: 'OID',
121
+ }
122
+ if (types[typeNum]) certType = types[typeNum]
123
+
124
+ return new CERT({
125
+ owner: this.fullyQualify(owner),
126
+ ttl: parseInt(ttl, 10),
127
+ type: 'CERT',
128
+ 'cert type': certType,
129
+ 'key tag': bytes.readUInt16BE(2),
130
+ algorithm: bytes.readUInt8(4),
131
+ certificate: bytes.slice(5).toString(),
132
+ timestamp: ts,
133
+ location: loc?.trim() ?? '',
134
+ })
135
+ }
136
+
137
+ fromBind({ bindline }) {
79
138
  // test.example.com 3600 IN CERT certtype, keytag, algo, cert
80
- const [owner, ttl, c, type, certtype, keytag, algo, certificate] = opts.bindline.split(/\s+/)
139
+ const [owner, ttl, c, type, certtype, keytag, algo, certificate] = bindline.split(/\s+/)
81
140
  return new CERT({
82
141
  owner,
83
142
  ttl: parseInt(ttl, 10),
package/rr/cname.js CHANGED
@@ -38,9 +38,9 @@ export default class CNAME extends RR {
38
38
  }
39
39
 
40
40
  /****** IMPORTERS *******/
41
- fromTinydns(opts) {
41
+ fromTinydns({ tinyline }) {
42
42
  // Cfqdn:p:ttl:timestamp:lo
43
- const [fqdn, p, ttl, ts, loc] = opts.tinyline.substring(1).split(':')
43
+ const [fqdn, p, ttl, ts, loc] = tinyline.slice(1).split(':')
44
44
 
45
45
  return new CNAME({
46
46
  owner: this.fullyQualify(fqdn),
@@ -48,13 +48,13 @@ export default class CNAME extends RR {
48
48
  type: 'CNAME',
49
49
  cname: this.fullyQualify(p),
50
50
  timestamp: ts,
51
- location: loc !== '' && loc !== '\n' ? loc : '',
51
+ location: loc?.trim() ?? '',
52
52
  })
53
53
  }
54
54
 
55
- fromBind(opts) {
55
+ fromBind({ bindline }) {
56
56
  // test.example.com 3600 IN CNAME ...
57
- const [owner, ttl, c, type, cname] = opts.bindline.split(/\s+/)
57
+ const [owner, ttl, c, type, cname] = bindline.split(/\s+/)
58
58
  return new CNAME({
59
59
  owner,
60
60
  ttl: parseInt(ttl, 10),
package/rr/dhcid.js CHANGED
@@ -40,9 +40,24 @@ export default class DHCID extends RR {
40
40
  }
41
41
 
42
42
  /****** IMPORTERS *******/
43
- fromBind(opts) {
43
+ fromTinydns({ tinyline }) {
44
+ // DHCID via generic, :fqdn:49:rdata:ttl:timestamp:lo
45
+ const [fqdn, n, rdata, ttl, ts, loc] = tinyline.slice(1).split(':')
46
+ if (n != 49) this.throwHelp('DHCID fromTinydns, invalid n')
47
+
48
+ return new DHCID({
49
+ owner: this.fullyQualify(fqdn),
50
+ ttl: parseInt(ttl, 10),
51
+ type: 'DHCID',
52
+ data: TINYDNS.octalToBase64(rdata),
53
+ timestamp: ts,
54
+ location: loc?.trim() ?? '',
55
+ })
56
+ }
57
+
58
+ fromBind({ bindline }) {
44
59
  // host.example.com 3600 IN DHCID <base64data>
45
- const parts = opts.bindline.split(/\s+/)
60
+ const parts = bindline.split(/\s+/)
46
61
  const [owner, ttl, c, type] = parts
47
62
  return new DHCID({
48
63
  owner,