@nictool/dns-resource-record 1.4.0 → 1.6.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/rr/txt.js CHANGED
@@ -16,25 +16,39 @@ export default class TXT extends RR {
16
16
  return 'Text'
17
17
  }
18
18
 
19
+ getTags() {
20
+ return ['common']
21
+ }
22
+
19
23
  getRdataFields(arg) {
20
24
  return ['data']
21
25
  }
22
26
 
23
27
  getRFCs() {
24
- return [1035]
28
+ return [1035, 4408, 7208, 6376]
25
29
  }
26
30
 
27
31
  getTypeId() {
28
32
  return 16
29
33
  }
30
34
 
35
+ getCanonical() {
36
+ return {
37
+ owner: 'example.com.',
38
+ ttl: 3600,
39
+ class: 'IN',
40
+ type: 'TXT',
41
+ data: 'v=spf1 mx -all',
42
+ }
43
+ }
44
+
31
45
  /****** IMPORTERS *******/
32
- fromTinydns(opts) {
33
- const str = opts.tinyline
46
+ fromTinydns({ tinyline }) {
47
+ const str = tinyline
34
48
  let fqdn, rdata, s, ttl, ts, loc
35
49
  // 'fqdn:s:ttl:timestamp:lo
36
50
  if (str[0] === "'") {
37
- ;[fqdn, s, ttl, ts, loc] = str.substring(1).split(':')
51
+ ;[fqdn, s, ttl, ts, loc] = str.slice(1).split(':')
38
52
  rdata = TINYDNS.octalToChar(s)
39
53
  } else {
40
54
  ;[fqdn, rdata, ttl, ts, loc] = this.fromTinydnsGeneric(str)
@@ -46,14 +60,14 @@ export default class TXT extends RR {
46
60
  type: 'TXT',
47
61
  data: rdata,
48
62
  timestamp: ts,
49
- location: loc !== '' && loc !== '\n' ? loc : '',
63
+ location: loc?.trim() || '',
50
64
  })
51
65
  }
52
66
 
53
67
  fromTinydnsGeneric(str) {
54
68
  // generic: :fqdn:n:rdata:ttl:timestamp:location
55
69
  // eslint-disable-next-line prefer-const
56
- let [fqdn, n, rdata, ttl, ts, loc] = str.substring(1).split(':')
70
+ let [fqdn, n, rdata, ttl, ts, loc] = str.slice(1).split(':')
57
71
  if (n != 16) this.throwHelp('TXT fromTinydns, invalid n')
58
72
 
59
73
  rdata = TINYDNS.octalToChar(rdata)
@@ -61,19 +75,18 @@ export default class TXT extends RR {
61
75
  let len = rdata[0].charCodeAt(0)
62
76
  let pos = 1
63
77
  while (pos < rdata.length) {
64
- s += rdata.substring(pos, +(len + pos))
78
+ s += rdata.slice(pos, +(len + pos))
65
79
  pos = len + pos
66
80
  len = rdata.charCodeAt(pos + 1)
67
81
  }
68
82
  return [fqdn, s, ttl, ts, loc]
69
83
  }
70
84
 
71
- fromBind(opts) {
85
+ fromBind({ bindline }) {
72
86
  // test.example.com 3600 IN TXT "..."
73
- const regex =
74
- /^(?<owner>[\S]{1,255})\s+(?<ttl>\d{1,10})\s+(?<cls>IN)\s+(?<type>\w{3})\s+?\s*(?<rdata>.+?)$/i
75
- const match = opts.bindline.trim().match(regex)
76
- if (!match) this.throwHelp(`unable to parse TXT: ${opts.bindline}`)
87
+ const regex = /^(?<owner>\S{1,255})\s+(?<ttl>\d{1,10})\s+(?<cls>IN)\s+(?<type>\w{3})\s+(?<rdata>\S.*)$/i
88
+ const match = bindline.trim().match(regex)
89
+ if (!match) this.throwHelp(`unable to parse TXT: ${bindline}`)
77
90
 
78
91
  const { owner, ttl, cls, type, rdata } = match.groups
79
92
 
@@ -99,6 +112,12 @@ export default class TXT extends RR {
99
112
  return `${this.get('owner')}\t+${this.get('ttl')}\t${this.get('type')}\t'${data}' ~\n`
100
113
  }
101
114
 
115
+ getWireRdata() {
116
+ let data = this.get('data')
117
+ if (Array.isArray(data)) data = data.join('')
118
+ return packStringWire(data)
119
+ }
120
+
102
121
  toTinydns() {
103
122
  let data = this.get('data')
104
123
  if (Array.isArray(data)) data = data.join('')
@@ -128,3 +147,18 @@ function asQuotedStrings(data) {
128
147
 
129
148
  return data
130
149
  }
150
+
151
+ function packStringWire(str) {
152
+ const parts = str.match(/(.{1,255})/g)
153
+ let len = 0
154
+ for (const part of parts) len += part.length + 1
155
+
156
+ const buf = Buffer.allocUnsafe(len)
157
+ let offset = 0
158
+ for (const part of parts) {
159
+ buf.writeUInt8(part.length, offset++)
160
+ buf.write(part, offset, 'ascii')
161
+ offset += part.length
162
+ }
163
+ return buf
164
+ }
package/rr/uri.js CHANGED
@@ -27,26 +27,26 @@ export default class URI extends RR {
27
27
  }
28
28
 
29
29
  /****** IMPORTERS *******/
30
- fromTinydns(opts) {
30
+ fromTinydns({ tinyline }) {
31
31
  // URI via generic, :fqdn:n:rdata:ttl:timestamp:lo
32
- const [fqdn, n, rdata, ttl, ts, loc] = opts.tinyline.substring(1).split(':')
32
+ const [fqdn, n, rdata, ttl, ts, loc] = tinyline.slice(1).split(':')
33
33
  if (n != 256) this.throwHelp('URI fromTinydns, invalid n')
34
34
 
35
35
  return new URI({
36
36
  type: 'URI',
37
37
  owner: this.fullyQualify(fqdn),
38
- priority: TINYDNS.octalToUInt16(rdata.substring(0, 8)),
39
- weight: TINYDNS.octalToUInt16(rdata.substring(8, 16)),
40
- target: TINYDNS.octalToChar(rdata.substring(16)),
38
+ priority: TINYDNS.octalToUInt16(rdata.slice(0, 8)),
39
+ weight: TINYDNS.octalToUInt16(rdata.slice(8, 16)),
40
+ target: TINYDNS.octalToChar(rdata.slice(16)),
41
41
  ttl: parseInt(ttl, 10),
42
42
  timestamp: ts,
43
- location: loc.trim() || '',
43
+ location: loc?.trim() ?? '',
44
44
  })
45
45
  }
46
46
 
47
- fromBind(opts) {
47
+ fromBind({ bindline }) {
48
48
  // test.example.com 3600 IN URI priority, weight, target
49
- const [owner, ttl, c, type, priority, weight, target] = opts.bindline.split(/\s+/)
49
+ const [owner, ttl, c, type, priority, weight, target] = bindline.split(/\s+/)
50
50
  return new URI({
51
51
  class: c,
52
52
  type: type,
@@ -75,6 +75,18 @@ export default class URI extends RR {
75
75
  return 256
76
76
  }
77
77
 
78
+ getCanonical() {
79
+ return {
80
+ owner: 'www.example.com.',
81
+ ttl: 3600,
82
+ class: 'IN',
83
+ type: 'URI',
84
+ priority: 10,
85
+ weight: 10,
86
+ target: 'http://www.example.com/',
87
+ }
88
+ }
89
+
78
90
  getQuotedFields() {
79
91
  return ['target']
80
92
  }
package/rr/wks.js CHANGED
@@ -29,6 +29,10 @@ export default class WKS extends RR {
29
29
  return 'Well Known Service'
30
30
  }
31
31
 
32
+ getTags() {
33
+ return ['obsolete']
34
+ }
35
+
32
36
  getRdataFields(arg) {
33
37
  return ['address', 'protocol', 'bit map']
34
38
  }
@@ -41,11 +45,23 @@ export default class WKS extends RR {
41
45
  return 11
42
46
  }
43
47
 
48
+ getCanonical() {
49
+ return {
50
+ owner: 'host.example.com.',
51
+ ttl: 3600,
52
+ class: 'IN',
53
+ type: 'WKS',
54
+ address: '192.0.2.1',
55
+ protocol: 'TCP',
56
+ 'bit map': 'ftp smtp',
57
+ }
58
+ }
59
+
44
60
  /****** IMPORTERS *******/
45
61
 
46
- fromBind(opts) {
62
+ fromBind({ bindline }) {
47
63
  // test.example.com 3600 IN WKS 192.168.1.1 TCP ftp smtp
48
- const parts = opts.bindline.split(/\s+/)
64
+ const parts = bindline.split(/\s+/)
49
65
  const [owner, ttl, c, type, address, protocol] = parts
50
66
  return new WKS({
51
67
  owner,
@@ -58,6 +74,30 @@ export default class WKS extends RR {
58
74
  })
59
75
  }
60
76
 
77
+ fromTinydns({ tinyline }) {
78
+ const [owner, _typeId, rdata, ttl, ts, loc] = tinyline.slice(1).split(':')
79
+
80
+ const binary = Buffer.from(TINYDNS.octalToChar(rdata), 'binary')
81
+ const address = [binary.readUInt8(0), binary.readUInt8(1), binary.readUInt8(2), binary.readUInt8(3)].join(
82
+ '.',
83
+ )
84
+ const protoNum = binary.readUInt8(4)
85
+ const protoMap = { 6: 'TCP', 17: 'UDP' }
86
+ const protocol = protoMap[protoNum] ?? protoNum
87
+ const bitmap = binary.slice(5).toString()
88
+
89
+ return new WKS({
90
+ owner: this.fullyQualify(owner),
91
+ ttl: parseInt(ttl, 10),
92
+ type: 'WKS',
93
+ address,
94
+ protocol,
95
+ 'bit map': bitmap,
96
+ timestamp: ts,
97
+ location: loc?.trim() ?? '',
98
+ })
99
+ }
100
+
61
101
  /****** EXPORTERS *******/
62
102
 
63
103
  toTinydns() {
package/rr.js CHANGED
@@ -1,13 +1,15 @@
1
+ import * as TINYDNS from './lib/tinydns.js'
2
+
1
3
  export default class RR extends Map {
2
4
  constructor(opts) {
3
5
  super()
4
6
 
5
7
  if (opts === null) return
6
8
 
7
- if (opts.default) this.default = opts.default
9
+ if (opts?.default) this.default = opts.default
8
10
 
9
- if (opts.bindline) return this.fromBind(opts)
10
- if (opts.tinyline) return this.fromTinydns(opts)
11
+ if (opts?.bindline) return this.fromBind(opts)
12
+ if (opts?.tinyline) return this.fromTinydns(opts)
11
13
 
12
14
  // tinydns specific
13
15
  this.setLocation(opts?.location)
@@ -21,10 +23,10 @@ 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) this.throwHelp(`Missing ${fnName} in class ${this.get('type')}`)
24
- this[fnName](opts[f])
26
+ this[fnName](opts?.[f])
25
27
  }
26
28
 
27
- if (opts.comment) this.set('comment', opts.comment)
29
+ if (opts?.comment) this.set('comment', opts.comment)
28
30
  }
29
31
 
30
32
  ucFirst(str) {
@@ -91,7 +93,7 @@ export default class RR extends Map {
91
93
  }
92
94
 
93
95
  setTtl(t) {
94
- if (t === undefined) t = this?.default?.ttl
96
+ t = t ?? this.default?.ttl
95
97
  if (t === undefined) {
96
98
  if (['SOA', 'SSHPF'].includes(this.get('type'))) return
97
99
  this.throwHelp('TTL is required, no default available')
@@ -157,7 +159,7 @@ export default class RR extends Map {
157
159
  }
158
160
 
159
161
  getEmpty(prop) {
160
- return this.get(prop) === undefined ? '' : this.get(prop)
162
+ return this.get(prop) ?? ''
161
163
  }
162
164
 
163
165
  getComment(prop) {
@@ -184,6 +186,10 @@ export default class RR extends Map {
184
186
  return []
185
187
  }
186
188
 
189
+ getTags() {
190
+ return []
191
+ }
192
+
187
193
  getFields(arg) {
188
194
  const commonFields = ['owner', 'ttl', 'class', 'type']
189
195
  Object.freeze(commonFields)
@@ -307,6 +313,80 @@ export default class RR extends Map {
307
313
  )
308
314
  }
309
315
 
316
+ compressIPv6(val) {
317
+ // * RFC 5952
318
+ // * 4.1. Leading zeros MUST be suppressed...A single 16-bit 0000 field MUST be represented as 0.
319
+ // * 4.2.1 The use of the symbol "::" MUST be used to its maximum capability.
320
+ // * 4.2.2 The symbol "::" MUST NOT be used to shorten just one 16-bit 0 field.
321
+ // * 4.2.3 When choosing placement of a "::", the longest run...MUST be shortened
322
+ // * 4.3 The characters a-f in an IPv6 address MUST be represented in lowercase.
323
+
324
+ // 4.3 Lowercase and 4.1 remove leading zeros per segment
325
+ const segments = val
326
+ .toLowerCase()
327
+ .split(':')
328
+ .map((s) => s.replace(/^0+/, '') || '0')
329
+
330
+ let bestStart = -1
331
+ let bestLen = 0
332
+ let curStart = -1
333
+ let curLen = 0
334
+
335
+ // 4.2.1 & 4.2.3 Find the longest consecutive run of '0'
336
+ for (let i = 0; i < segments.length; i++) {
337
+ if (segments[i] === '0') {
338
+ if (curStart === -1) curStart = i
339
+ curLen++
340
+ if (curLen > bestLen) {
341
+ bestLen = curLen
342
+ bestStart = curStart
343
+ }
344
+ } else {
345
+ curStart = -1
346
+ curLen = 0
347
+ }
348
+ }
349
+
350
+ // 4.2.2 Don't shorten a single 16-bit 0 field
351
+ if (bestLen < 2) {
352
+ return segments.join(':')
353
+ }
354
+
355
+ const head = segments.slice(0, bestStart).join(':')
356
+ const tail = segments.slice(bestStart + bestLen).join(':')
357
+
358
+ return `${head}::${tail}`
359
+ }
360
+
361
+ octalToBuffer(octalStr) {
362
+ return Buffer.from(TINYDNS.octalToChar(octalStr), 'binary')
363
+ }
364
+
365
+ wirePackDomain(fqdn) {
366
+ return packDomainNameWire(fqdn)
367
+ }
368
+
369
+ getWireRdata() {
370
+ const line = this.toTinydns()
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 ?? '')
376
+ }
377
+
378
+ toWire() {
379
+ const rdata = this.getWireRdata()
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])
388
+ }
389
+
310
390
  toBind(zone_opts) {
311
391
  return `${this.getPrefix(zone_opts)}\t${this.getRdataFields()
312
392
  .map((f) => this.getQuoted(f))
@@ -331,3 +411,25 @@ export default class RR extends Map {
331
411
  .join(' ')}' ~\n`
332
412
  }
333
413
  }
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
+ }