@nictool/dns-resource-record 1.6.0 → 1.7.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 +45 -6
- package/README.md +328 -198
- package/dist/dns-rr.min.js +26 -0
- package/dist/dns-rr.min.js.map +7 -0
- package/index.js +12 -11
- package/lib/binary.js +108 -0
- package/lib/bind.js +94 -0
- package/lib/dns-query.js +67 -0
- package/lib/tinydns.js +128 -44
- package/lib/wire.js +519 -0
- package/package.json +29 -10
- package/rr/TEMPLATE.js +124 -0
- package/rr/a.js +8 -48
- package/rr/aaaa.js +13 -80
- package/rr/apl.js +123 -27
- package/rr/caa.js +27 -50
- package/rr/cert.js +58 -87
- package/rr/cname.js +7 -63
- package/rr/dhcid.js +20 -30
- package/rr/dname.js +9 -36
- package/rr/dnskey.js +38 -32
- package/rr/ds.js +43 -57
- package/rr/hinfo.js +21 -40
- package/rr/hip.js +88 -26
- package/rr/https.js +28 -40
- package/rr/ipseckey.js +97 -18
- package/rr/key.js +39 -29
- package/rr/kx.js +16 -27
- package/rr/loc.js +42 -12
- package/rr/mx.js +16 -51
- package/rr/naptr.js +58 -33
- package/rr/ns.js +8 -36
- package/rr/nsec.js +89 -18
- package/rr/nsec3.js +62 -26
- package/rr/nsec3param.js +56 -53
- package/rr/nxt.js +57 -18
- package/rr/openpgpkey.js +24 -30
- package/rr/ptr.js +7 -47
- package/rr/rp.js +17 -32
- package/rr/rrsig.js +80 -85
- package/rr/sig.js +70 -45
- package/rr/smimea.js +33 -33
- package/rr/soa.js +31 -49
- package/rr/spf.js +10 -17
- package/rr/srv.js +22 -62
- package/rr/sshfp.js +25 -53
- package/rr/svcb.js +27 -40
- package/rr/tlsa.js +34 -34
- package/rr/tsig.js +87 -24
- package/rr/txt.js +40 -59
- package/rr/uri.js +19 -29
- package/rr/wks.js +154 -22
- package/rr.js +290 -109
- package/lib/readme.js +0 -84
package/rr.js
CHANGED
|
@@ -1,16 +1,31 @@
|
|
|
1
1
|
import * as TINYDNS from './lib/tinydns.js'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
const customInspect = Symbol.for('nodejs.util.inspect.custom')
|
|
4
|
+
import {
|
|
5
|
+
wireUnpackDomain,
|
|
6
|
+
wirePackDomain,
|
|
7
|
+
getWireRdata as wireGetRdata,
|
|
8
|
+
toWire as wireToWire,
|
|
9
|
+
fromWireBytes,
|
|
10
|
+
fromWireGeneric,
|
|
11
|
+
} from './lib/wire.js'
|
|
12
|
+
import {
|
|
13
|
+
parseBindLine as bindParseLine,
|
|
14
|
+
fromBind as bindFromGeneric,
|
|
15
|
+
toBind as bindToGeneric,
|
|
16
|
+
} from './lib/bind.js'
|
|
17
|
+
|
|
18
|
+
export default class RR {
|
|
19
|
+
static CLASSES = { IN: 1, CS: 2, CH: 3, HS: 4, NONE: 254, ANY: 255 }
|
|
20
|
+
static typeId
|
|
21
|
+
static RFCs = []
|
|
22
|
+
static tags = []
|
|
6
23
|
|
|
24
|
+
constructor(opts) {
|
|
7
25
|
if (opts === null) return
|
|
8
26
|
|
|
9
27
|
if (opts?.default) this.default = opts.default
|
|
10
28
|
|
|
11
|
-
if (opts?.bindline) return this.fromBind(opts)
|
|
12
|
-
if (opts?.tinyline) return this.fromTinydns(opts)
|
|
13
|
-
|
|
14
29
|
// tinydns specific
|
|
15
30
|
this.setLocation(opts?.location)
|
|
16
31
|
this.setTimestamp(opts?.timestamp)
|
|
@@ -20,16 +35,86 @@ export default class RR extends Map {
|
|
|
20
35
|
this.setTtl(opts?.ttl)
|
|
21
36
|
this.setClass(opts?.class)
|
|
22
37
|
|
|
23
|
-
for (const
|
|
38
|
+
for (const entry of this.constructor.rdataFields ?? []) {
|
|
39
|
+
const f = RR.fieldName(entry)
|
|
40
|
+
const fieldType = Array.isArray(entry) ? entry[1] : null
|
|
24
41
|
const fnName = `set${this.ucFirst(f)}`
|
|
25
|
-
if (this[fnName] ===
|
|
26
|
-
|
|
42
|
+
if (typeof this[fnName] === 'function') {
|
|
43
|
+
this[fnName](opts?.[f])
|
|
44
|
+
} else if (fieldType) {
|
|
45
|
+
this.setTypedValue(fieldType, f, opts?.[f])
|
|
46
|
+
} else {
|
|
47
|
+
this.set(f, opts?.[f])
|
|
48
|
+
}
|
|
27
49
|
}
|
|
28
50
|
|
|
29
51
|
if (opts?.comment) this.set('comment', opts.comment)
|
|
30
52
|
}
|
|
31
53
|
|
|
54
|
+
static fromBind(line, opts = {}) {
|
|
55
|
+
const instance = new this(null)
|
|
56
|
+
if (opts.default !== undefined) instance.default = opts.default
|
|
57
|
+
const parsed = this.parseBindLine(line)
|
|
58
|
+
if (!parsed) return null
|
|
59
|
+
return instance.fromBind({ ...opts, ...parsed, bindline: line })
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
fromBind(opts) {
|
|
63
|
+
return bindFromGeneric(this, opts)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
static parseBindLine(line) {
|
|
67
|
+
return bindParseLine(line, RR.CLASSES)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
static fromTinydns(line, opts = {}) {
|
|
71
|
+
const instance = new this(null)
|
|
72
|
+
if (opts.default !== undefined) instance.default = opts.default
|
|
73
|
+
return instance.fromTinydns({ ...opts, tinyline: line })
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
fromTinydns(opts) {
|
|
77
|
+
return TINYDNS.fromGeneric(this, opts)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
static fromWire(wireBytes) {
|
|
81
|
+
return fromWireBytes(this, wireBytes, wireUnpackDomain)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
fromWire(opts) {
|
|
85
|
+
return fromWireGeneric(this, opts)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
static #reserved = ['__proto__', 'constructor', 'prototype']
|
|
89
|
+
|
|
90
|
+
get(key) {
|
|
91
|
+
if (RR.#reserved.includes(key)) throw new Error(`Invalid field name: ${key}`)
|
|
92
|
+
return this[key]
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
set(key, value) {
|
|
96
|
+
if (RR.#reserved.includes(key)) throw new Error(`Invalid field name: ${key}`)
|
|
97
|
+
this[key] = value
|
|
98
|
+
return this
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
toJSON() {
|
|
102
|
+
const fields = [...this.getFields(), 'location', 'timestamp', 'comment']
|
|
103
|
+
const obj = {}
|
|
104
|
+
for (const f of fields) {
|
|
105
|
+
const v = this.get(f)
|
|
106
|
+
if (v !== undefined) obj[f] = v
|
|
107
|
+
}
|
|
108
|
+
return obj
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
[customInspect](depth, options, nextInspect) {
|
|
112
|
+
// Returns a formatted string that looks like: A { ... }
|
|
113
|
+
return `${this.type} ${nextInspect(this.toJSON(), options)}`
|
|
114
|
+
}
|
|
115
|
+
|
|
32
116
|
ucFirst(str) {
|
|
117
|
+
if (!str) return str
|
|
33
118
|
return str
|
|
34
119
|
.split(/\s/)
|
|
35
120
|
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
@@ -37,23 +122,15 @@ export default class RR extends Map {
|
|
|
37
122
|
}
|
|
38
123
|
|
|
39
124
|
setClass(c) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
case 'CS': // 2
|
|
48
|
-
case 'CH': // 3
|
|
49
|
-
case 'HS': // 4
|
|
50
|
-
case 'NONE': // 254
|
|
51
|
-
case 'ANY': // 255
|
|
52
|
-
this.set('class', c)
|
|
53
|
-
break
|
|
54
|
-
default:
|
|
55
|
-
this.throwHelp(`invalid class ${c}`)
|
|
125
|
+
if ([undefined, null, ''].includes(c)) {
|
|
126
|
+
this.set('class', 'IN')
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
if (RR.CLASSES[c.toUpperCase()]) {
|
|
130
|
+
this.set('class', c.toUpperCase())
|
|
131
|
+
return
|
|
56
132
|
}
|
|
133
|
+
this.throwHelp(`invalid class ${c}`)
|
|
57
134
|
}
|
|
58
135
|
|
|
59
136
|
setLocation(l) {
|
|
@@ -80,7 +157,7 @@ export default class RR extends Map {
|
|
|
80
157
|
if (n.length < 1 || n.length > 255)
|
|
81
158
|
this.throwHelp('Domain names must have 1-255 octets (characters): RFC 2181')
|
|
82
159
|
|
|
83
|
-
this.isFullyQualified(this.constructor.name, 'owner', n)
|
|
160
|
+
this.isFullyQualified(this.constructor.typeName ?? this.constructor.name, 'owner', n)
|
|
84
161
|
this.hasValidLabels(n)
|
|
85
162
|
|
|
86
163
|
// wildcard records: RFC 1034, 4592
|
|
@@ -95,7 +172,7 @@ export default class RR extends Map {
|
|
|
95
172
|
setTtl(t) {
|
|
96
173
|
t = t ?? this.default?.ttl
|
|
97
174
|
if (t === undefined) {
|
|
98
|
-
if (['SOA', '
|
|
175
|
+
if (['SOA', 'SSHFP', 'RRSIG'].includes(this.get('type'))) return
|
|
99
176
|
this.throwHelp('TTL is required, no default available')
|
|
100
177
|
}
|
|
101
178
|
|
|
@@ -108,24 +185,23 @@ export default class RR extends Map {
|
|
|
108
185
|
}
|
|
109
186
|
|
|
110
187
|
setType(t) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
this.throwHelp(`type is required`)
|
|
115
|
-
}
|
|
188
|
+
if ([undefined, ''].includes(t)) t = this.constructor.typeName
|
|
189
|
+
|
|
190
|
+
if (t === undefined) this.throwHelp(`type is required`)
|
|
116
191
|
|
|
117
|
-
if (t.toUpperCase() !== this.constructor.
|
|
118
|
-
this.throwHelp(`type ${t} doesn't match ${this.constructor.
|
|
192
|
+
if (t.toUpperCase() !== this.constructor.typeName)
|
|
193
|
+
this.throwHelp(`type ${t} doesn't match ${this.constructor.typeName}`)
|
|
119
194
|
|
|
120
195
|
this.set('type', t.toUpperCase())
|
|
121
196
|
}
|
|
122
197
|
|
|
123
198
|
throwHelp(e) {
|
|
124
|
-
if (this.constructor.
|
|
199
|
+
if (!this.constructor.typeName) throw new Error(e)
|
|
125
200
|
|
|
201
|
+
const typeName = this.constructor.typeName
|
|
126
202
|
const example = this.getCanonical
|
|
127
|
-
? `Example ${
|
|
128
|
-
: `${
|
|
203
|
+
? `Example ${typeName}:\n${JSON.stringify(this.getCanonical(), null, '\t')}\n\n`
|
|
204
|
+
: `${typeName} records have the fields: ${this.getFields().join(', ')}\n\n`
|
|
129
205
|
|
|
130
206
|
throw new Error(`${e}\n\n${example}${this.citeRFC()}\n`)
|
|
131
207
|
}
|
|
@@ -169,8 +245,8 @@ export default class RR extends Map {
|
|
|
169
245
|
}
|
|
170
246
|
|
|
171
247
|
getQuoted(prop) {
|
|
172
|
-
// if prop is not
|
|
173
|
-
if (!this.
|
|
248
|
+
// if prop is not a quoted string field, return bare
|
|
249
|
+
if (!this.isQuotedField(prop)) return this.get(prop)
|
|
174
250
|
|
|
175
251
|
// if it's already quoted, return as-is
|
|
176
252
|
if (/['"]/.test(this.get(prop)[0])) return this.get(prop)
|
|
@@ -178,29 +254,49 @@ export default class RR extends Map {
|
|
|
178
254
|
return `"${this.get(prop)}"` // add double quotes
|
|
179
255
|
}
|
|
180
256
|
|
|
181
|
-
|
|
182
|
-
return []
|
|
257
|
+
static fieldName(entry) {
|
|
258
|
+
return Array.isArray(entry) ? entry[0] : entry
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
static fieldType(entry) {
|
|
262
|
+
return Array.isArray(entry) ? entry[1] : null
|
|
183
263
|
}
|
|
184
264
|
|
|
185
265
|
getRdataFields() {
|
|
186
|
-
return []
|
|
266
|
+
return (this.constructor.rdataFields ?? []).map((e) => RR.fieldName(e))
|
|
187
267
|
}
|
|
188
268
|
|
|
189
269
|
getTags() {
|
|
190
|
-
return []
|
|
270
|
+
return this.constructor.tags ?? []
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
getRFCs() {
|
|
274
|
+
return this.constructor.RFCs ?? []
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
getTypeId() {
|
|
278
|
+
const typeId = this.constructor.typeId
|
|
279
|
+
if (typeId === undefined) this.throwHelp(`${this.constructor.typeName}: missing static typeId`)
|
|
280
|
+
return typeId
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
static getTypeId() {
|
|
284
|
+
return this.typeId
|
|
191
285
|
}
|
|
192
286
|
|
|
193
287
|
getFields(arg) {
|
|
194
288
|
const commonFields = ['owner', 'ttl', 'class', 'type']
|
|
195
289
|
Object.freeze(commonFields)
|
|
196
290
|
|
|
291
|
+
const rdataFields = this.getRdataFields()
|
|
292
|
+
|
|
197
293
|
switch (arg) {
|
|
198
294
|
case 'common':
|
|
199
295
|
return commonFields
|
|
200
296
|
case 'rdata':
|
|
201
|
-
return
|
|
297
|
+
return rdataFields
|
|
202
298
|
default:
|
|
203
|
-
return commonFields.concat(
|
|
299
|
+
return commonFields.concat(rdataFields)
|
|
204
300
|
}
|
|
205
301
|
}
|
|
206
302
|
|
|
@@ -249,45 +345,132 @@ export default class RR extends Map {
|
|
|
249
345
|
}
|
|
250
346
|
|
|
251
347
|
is8bitInt(type, field, value) {
|
|
252
|
-
if (
|
|
253
|
-
typeof value === 'number' &&
|
|
254
|
-
parseInt(value, 10) === value && // assure integer
|
|
255
|
-
value >= 0 &&
|
|
256
|
-
value <= 255
|
|
257
|
-
)
|
|
258
|
-
return true
|
|
348
|
+
if (Number.isInteger(value) && value >= 0 && value <= 255) return true
|
|
259
349
|
|
|
260
350
|
this.throwHelp(`${type} ${field} must be a 8-bit integer (in the range 0-255)`)
|
|
261
351
|
}
|
|
262
352
|
|
|
263
353
|
is16bitInt(type, field, value) {
|
|
264
|
-
if (
|
|
265
|
-
typeof value === 'number' &&
|
|
266
|
-
parseInt(value, 10) === value && // assure integer
|
|
267
|
-
value >= 0 &&
|
|
268
|
-
value <= 65535
|
|
269
|
-
)
|
|
270
|
-
return true
|
|
354
|
+
if (Number.isInteger(value) && value >= 0 && value <= 65535) return true
|
|
271
355
|
|
|
272
356
|
this.throwHelp(`${type} ${field} must be a 16-bit integer (in the range 0-65535)`)
|
|
273
357
|
}
|
|
274
358
|
|
|
275
359
|
is32bitInt(type, field, value) {
|
|
360
|
+
if (Number.isInteger(value) && value >= 0 && value <= 4294967295) return true
|
|
361
|
+
|
|
362
|
+
this.throwHelp(`${type} ${field} must be a 32-bit integer (in the range 0-4294967295)`)
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
isBase64(type, field, value) {
|
|
276
366
|
if (
|
|
277
|
-
typeof value === '
|
|
278
|
-
|
|
279
|
-
value
|
|
280
|
-
value
|
|
367
|
+
typeof value === 'string' &&
|
|
368
|
+
value.length > 0 &&
|
|
369
|
+
value.length % 4 === 0 &&
|
|
370
|
+
/^[A-Za-z0-9+/]*={0,2}$/.test(value)
|
|
281
371
|
)
|
|
282
372
|
return true
|
|
283
373
|
|
|
284
|
-
this.throwHelp(`${type} ${field} must be a
|
|
374
|
+
this.throwHelp(`${type} ${field} must be a valid base64 string`)
|
|
285
375
|
}
|
|
286
376
|
|
|
287
377
|
isQuoted(val) {
|
|
288
378
|
return /^["']/.test(val) && /["']$/.test(val)
|
|
289
379
|
}
|
|
290
380
|
|
|
381
|
+
setFqdnValue(typeName, fieldName, val) {
|
|
382
|
+
if (!val) this.throwHelp(`${typeName}: ${fieldName} is required`)
|
|
383
|
+
if (this.isIPv4(val) || this.isIPv6(val))
|
|
384
|
+
this.throwHelp(`${typeName}: ${fieldName} must be a domain name`)
|
|
385
|
+
this.isFullyQualified(typeName, fieldName, val)
|
|
386
|
+
this.isValidHostname(typeName, fieldName, val)
|
|
387
|
+
this.set(fieldName, val.toLowerCase())
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
setTypedValue(type, fieldName, val) {
|
|
391
|
+
const typeName = this.constructor.typeName
|
|
392
|
+
switch (type) {
|
|
393
|
+
case 'u8':
|
|
394
|
+
this.is8bitInt(typeName, fieldName, val)
|
|
395
|
+
this.set(fieldName, parseInt(val, 10))
|
|
396
|
+
break
|
|
397
|
+
case 'u16':
|
|
398
|
+
this.is16bitInt(typeName, fieldName, val)
|
|
399
|
+
this.set(fieldName, parseInt(val, 10))
|
|
400
|
+
break
|
|
401
|
+
case 'certtype': {
|
|
402
|
+
if (val === undefined || val === null || val === '')
|
|
403
|
+
this.throwHelp(`${typeName}: ${fieldName} is required`)
|
|
404
|
+
if (typeof val === 'string' && !/^[0-9]+$/.test(val)) {
|
|
405
|
+
const certTypes = this.constructor.CERT_TYPES
|
|
406
|
+
if (!certTypes || !Object.hasOwn(certTypes, val)) {
|
|
407
|
+
this.throwHelp(`${typeName}: unknown cert type mnemonic: ${val}`)
|
|
408
|
+
}
|
|
409
|
+
this.set(fieldName, val)
|
|
410
|
+
break
|
|
411
|
+
}
|
|
412
|
+
this.is16bitInt(typeName, fieldName, val)
|
|
413
|
+
this.set(fieldName, parseInt(val, 10))
|
|
414
|
+
break
|
|
415
|
+
}
|
|
416
|
+
case 'u32':
|
|
417
|
+
this.is32bitInt(typeName, fieldName, val)
|
|
418
|
+
this.set(fieldName, parseInt(val, 10))
|
|
419
|
+
break
|
|
420
|
+
case 'fqdn':
|
|
421
|
+
this.setFqdnValue(typeName, fieldName, val)
|
|
422
|
+
break
|
|
423
|
+
case 'base64':
|
|
424
|
+
this.isBase64(typeName, fieldName, val)
|
|
425
|
+
this.set(fieldName, val)
|
|
426
|
+
break
|
|
427
|
+
case 'hex':
|
|
428
|
+
if (!/^[0-9a-fA-F]*$/.test(val)) this.throwHelp(`${typeName}: ${fieldName} must be hexadecimal`)
|
|
429
|
+
this.set(fieldName, val)
|
|
430
|
+
break
|
|
431
|
+
case 'str':
|
|
432
|
+
if (!val) this.throwHelp(`${typeName}: ${fieldName} is required`)
|
|
433
|
+
this.set(fieldName, val)
|
|
434
|
+
break
|
|
435
|
+
case 'qstr':
|
|
436
|
+
if (val === undefined || val === null) this.throwHelp(`${typeName}: ${fieldName} is required`)
|
|
437
|
+
this.set(fieldName, val)
|
|
438
|
+
break
|
|
439
|
+
case 'charstr': {
|
|
440
|
+
if (val === undefined || val === null) this.throwHelp(`${typeName}: ${fieldName} is required`)
|
|
441
|
+
const value = String(val)
|
|
442
|
+
const byteLen = new TextEncoder().encode(value).length
|
|
443
|
+
if (byteLen > 255) this.throwHelp(`${typeName}: ${fieldName} must be <=255 bytes`)
|
|
444
|
+
this.set(fieldName, value)
|
|
445
|
+
break
|
|
446
|
+
}
|
|
447
|
+
case 'qcharstr': {
|
|
448
|
+
if (val === undefined || val === null) this.throwHelp(`${typeName}: ${fieldName} is required`)
|
|
449
|
+
const value = String(val)
|
|
450
|
+
const byteLen = new TextEncoder().encode(value).length
|
|
451
|
+
if (byteLen > 255) this.throwHelp(`${typeName}: ${fieldName} must be <=255 bytes`)
|
|
452
|
+
this.set(fieldName, value)
|
|
453
|
+
break
|
|
454
|
+
}
|
|
455
|
+
case 'charstrs':
|
|
456
|
+
if (val === undefined || val === null) this.throwHelp(`${typeName}: ${fieldName} is required`)
|
|
457
|
+
this.set(fieldName, val)
|
|
458
|
+
break
|
|
459
|
+
case 'svcparams':
|
|
460
|
+
if (val === undefined || val === null) this.throwHelp(`${typeName}: ${fieldName} is required`)
|
|
461
|
+
this.set(fieldName, val)
|
|
462
|
+
break
|
|
463
|
+
case 'ipv4':
|
|
464
|
+
if (!this.isIPv4(val)) this.throwHelp(`${typeName}: ${fieldName} must be a valid IPv4 address`)
|
|
465
|
+
this.set(fieldName, val)
|
|
466
|
+
break
|
|
467
|
+
case 'ipv6':
|
|
468
|
+
if (!this.isIPv6(val)) this.throwHelp(`${typeName}: ${fieldName} must be a valid IPv6 address`)
|
|
469
|
+
this.set(fieldName, this.expandIPv6(val.toLowerCase())) // lower case: RFC 5952
|
|
470
|
+
break
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
291
474
|
isFullyQualified(type, field, hostname) {
|
|
292
475
|
if (hostname.endsWith('.')) return true
|
|
293
476
|
|
|
@@ -313,6 +496,10 @@ export default class RR extends Map {
|
|
|
313
496
|
)
|
|
314
497
|
}
|
|
315
498
|
|
|
499
|
+
expandIPv6(val, delimiter) {
|
|
500
|
+
return TINYDNS.expandIPv6(val, delimiter)
|
|
501
|
+
}
|
|
502
|
+
|
|
316
503
|
compressIPv6(val) {
|
|
317
504
|
// * RFC 5952
|
|
318
505
|
// * 4.1. Leading zeros MUST be suppressed...A single 16-bit 0000 field MUST be represented as 0.
|
|
@@ -358,39 +545,55 @@ export default class RR extends Map {
|
|
|
358
545
|
return `${head}::${tail}`
|
|
359
546
|
}
|
|
360
547
|
|
|
361
|
-
|
|
362
|
-
|
|
548
|
+
octalToUint8Array(octalStr) {
|
|
549
|
+
const str = TINYDNS.octalToChar(octalStr)
|
|
550
|
+
return Uint8Array.from(str, (c) => c.charCodeAt(0))
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
wireUnpackDomain(bytes, offset = 0) {
|
|
554
|
+
return wireUnpackDomain(bytes, offset)
|
|
363
555
|
}
|
|
364
556
|
|
|
365
557
|
wirePackDomain(fqdn) {
|
|
366
|
-
return
|
|
558
|
+
return wirePackDomain(fqdn)
|
|
367
559
|
}
|
|
368
560
|
|
|
369
561
|
getWireRdata() {
|
|
370
|
-
|
|
371
|
-
if (!line.startsWith(':'))
|
|
372
|
-
throw new Error(`${this.get('type')}: override getWireRdata() — non-generic tinydns format`)
|
|
373
|
-
// line: :fqdn:typeId:rdata:ttl:ts:loc\n
|
|
374
|
-
const rdata = line.split(':')[3]
|
|
375
|
-
return this.octalToBuffer(rdata ?? '')
|
|
562
|
+
return wireGetRdata(this)
|
|
376
563
|
}
|
|
377
564
|
|
|
378
565
|
toWire() {
|
|
379
|
-
|
|
380
|
-
const owner = this.wirePackDomain(this.get('owner'))
|
|
381
|
-
const classMap = { IN: 1, CS: 2, CH: 3, HS: 4, NONE: 254, ANY: 255 }
|
|
382
|
-
const meta = Buffer.alloc(10)
|
|
383
|
-
meta.writeUInt16BE(this.getTypeId(), 0)
|
|
384
|
-
meta.writeUInt16BE(classMap[this.get('class')] ?? 1, 2)
|
|
385
|
-
meta.writeUInt32BE(this.get('ttl'), 4)
|
|
386
|
-
meta.writeUInt16BE(rdata.length, 8)
|
|
387
|
-
return Buffer.concat([owner, meta, rdata])
|
|
566
|
+
return wireToWire(this, RR.CLASSES)
|
|
388
567
|
}
|
|
389
568
|
|
|
390
569
|
toBind(zone_opts) {
|
|
391
|
-
return
|
|
392
|
-
|
|
393
|
-
|
|
570
|
+
return bindToGeneric(this, zone_opts)
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
parseTinydnsLine(tinyline) {
|
|
574
|
+
const parsed = TINYDNS.parseGenericLine(tinyline)
|
|
575
|
+
return { ...parsed, owner: this.fullyQualify(parsed.owner) }
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
toTinydns() {
|
|
579
|
+
return TINYDNS.to(this)
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
isFqdnField(field) {
|
|
583
|
+
return (
|
|
584
|
+
(this.constructor.rdataFields ?? []).some(
|
|
585
|
+
(entry) => RR.fieldName(entry) === field && RR.fieldType(entry) === 'fqdn',
|
|
586
|
+
) || false
|
|
587
|
+
)
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
isQuotedField(field) {
|
|
591
|
+
const quotedTypes = new Set(['qstr', 'qcharstr', 'charstrs'])
|
|
592
|
+
return (
|
|
593
|
+
(this.constructor.rdataFields ?? []).some(
|
|
594
|
+
(entry) => RR.fieldName(entry) === field && quotedTypes.has(RR.fieldType(entry)),
|
|
595
|
+
) || false
|
|
596
|
+
)
|
|
394
597
|
}
|
|
395
598
|
|
|
396
599
|
toMaraDNS() {
|
|
@@ -399,37 +602,15 @@ export default class RR extends Map {
|
|
|
399
602
|
/\s+/g,
|
|
400
603
|
)
|
|
401
604
|
if (!supportedTypes.includes(type)) return this.toMaraGeneric()
|
|
402
|
-
return `${this.get('owner')}\t+${this.get('ttl')}\t${type}\t${this.
|
|
605
|
+
return `${this.get('owner')}\t+${this.get('ttl')}\t${type}\t${this.getFields('rdata')
|
|
403
606
|
.map((f) => this.getQuoted(f))
|
|
404
607
|
.join('\t')} ~\n`
|
|
405
608
|
}
|
|
406
609
|
|
|
407
610
|
toMaraGeneric() {
|
|
408
611
|
// this.throwHelp(`\nMaraDNS does not support ${type} records yet and this package does not support MaraDNS generic records. Yet.\n`)
|
|
409
|
-
return `${this.get('owner')}\t+${this.get('ttl')}\tRAW ${this.getTypeId()}\t'${this.
|
|
612
|
+
return `${this.get('owner')}\t+${this.get('ttl')}\tRAW ${this.getTypeId()}\t'${this.getFields('rdata')
|
|
410
613
|
.map((f) => this.getQuoted(f))
|
|
411
614
|
.join(' ')}' ~\n`
|
|
412
615
|
}
|
|
413
616
|
}
|
|
414
|
-
|
|
415
|
-
function packDomainNameWire(fqdn) {
|
|
416
|
-
if (fqdn === '.') return Buffer.from([0])
|
|
417
|
-
const parts = fqdn.split('.')
|
|
418
|
-
let len = 0
|
|
419
|
-
for (const part of parts) {
|
|
420
|
-
if (part.length > 0) len += part.length + 1
|
|
421
|
-
}
|
|
422
|
-
len += 1 // for the final \0
|
|
423
|
-
|
|
424
|
-
const buf = Buffer.allocUnsafe(len)
|
|
425
|
-
let offset = 0
|
|
426
|
-
for (const part of parts) {
|
|
427
|
-
if (part.length > 0) {
|
|
428
|
-
buf.writeUInt8(part.length, offset++)
|
|
429
|
-
buf.write(part, offset, 'ascii')
|
|
430
|
-
offset += part.length
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
buf.writeUInt8(0, offset)
|
|
434
|
-
return buf
|
|
435
|
-
}
|
package/lib/readme.js
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Regenerates the Supported Records table in README.md using getTags() from each RR class.
|
|
3
|
-
|
|
4
|
-
import { readFileSync, writeFileSync } from 'fs'
|
|
5
|
-
import { fileURLToPath } from 'url'
|
|
6
|
-
import { dirname, join } from 'path'
|
|
7
|
-
|
|
8
|
-
import * as RR from '../index.js'
|
|
9
|
-
|
|
10
|
-
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
11
|
-
|
|
12
|
-
const tagToGroup = {
|
|
13
|
-
common: 'Common',
|
|
14
|
-
security: 'Security',
|
|
15
|
-
dnssec: 'DNSSEC',
|
|
16
|
-
deprecated: 'Deprecated',
|
|
17
|
-
obsolete: 'Obsolete',
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const groupOrder = ['Common', 'Less Common', 'Security', 'DNSSEC', 'Deprecated', 'Obsolete']
|
|
21
|
-
|
|
22
|
-
function getGroup(instance) {
|
|
23
|
-
const tags = instance.getTags()
|
|
24
|
-
for (const [tag, group] of Object.entries(tagToGroup)) {
|
|
25
|
-
if (tags.includes(tag)) return group
|
|
26
|
-
}
|
|
27
|
-
return 'Less Common'
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const groups = {}
|
|
31
|
-
for (const name of groupOrder) groups[name] = []
|
|
32
|
-
|
|
33
|
-
for (const [name, cls] of Object.entries(RR)) {
|
|
34
|
-
if (name === 'typeMap' || name === 'default') continue
|
|
35
|
-
if (typeof cls !== 'function') continue
|
|
36
|
-
const instance = new cls(null)
|
|
37
|
-
const group = getGroup(instance)
|
|
38
|
-
groups[group].push(name)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const check = ':white_check_mark:'
|
|
42
|
-
const rows = []
|
|
43
|
-
for (const groupName of groupOrder) {
|
|
44
|
-
const names = groups[groupName]
|
|
45
|
-
if (!names.length) continue
|
|
46
|
-
rows.push(`| *${groupName}* |`)
|
|
47
|
-
for (const name of names.sort()) {
|
|
48
|
-
const cls = RR[name]
|
|
49
|
-
const hasToBind = typeof cls.prototype.toBind === 'function'
|
|
50
|
-
const hasFromBind = typeof cls.prototype.fromBind === 'function'
|
|
51
|
-
const hasToTinydns = typeof cls.prototype.toTinydns === 'function'
|
|
52
|
-
const hasFromTinydns = typeof cls.prototype.fromTinydns === 'function'
|
|
53
|
-
|
|
54
|
-
const bind = hasToBind && hasFromBind ? check : ''
|
|
55
|
-
const tinydns = hasToTinydns && hasFromTinydns ? check : ''
|
|
56
|
-
|
|
57
|
-
rows.push(`| **${name}** | ${bind} | ${tinydns} |`)
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const table = [
|
|
62
|
-
`| **RR** | **BIND / RFC 1035** | **Tinydns** |`,
|
|
63
|
-
`| :------------: | :-------------------: | :-----------------: |`,
|
|
64
|
-
...rows,
|
|
65
|
-
].join('\n')
|
|
66
|
-
|
|
67
|
-
const readmePath = join(__dirname, '..', 'README.md')
|
|
68
|
-
const readme = readFileSync(readmePath, 'utf-8')
|
|
69
|
-
|
|
70
|
-
// Replace from the table header line up to (but not including) the ## TIPS section
|
|
71
|
-
const tableHeaderMarker = '\n| **RR** |'
|
|
72
|
-
const tipsSectionMarker = '\n\n## TIPS'
|
|
73
|
-
|
|
74
|
-
const tableStart = readme.indexOf(tableHeaderMarker)
|
|
75
|
-
const tipsStart = readme.indexOf(tipsSectionMarker)
|
|
76
|
-
|
|
77
|
-
if (tableStart === -1 || tipsStart === -1) {
|
|
78
|
-
console.error('Could not locate table boundaries in README.md')
|
|
79
|
-
process.exit(1)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const newReadme = readme.slice(0, tableStart + 1) + table + '\n' + readme.slice(tipsStart)
|
|
83
|
-
writeFileSync(readmePath, newReadme)
|
|
84
|
-
console.log('README.md updated successfully')
|