@nictool/dns-resource-record 1.1.7 → 1.2.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/.prettierrc.yml +3 -0
- package/CHANGELOG.md +17 -43
- package/README.md +45 -47
- package/index.js +77 -29
- package/lib/tinydns.js +68 -61
- package/package.json +1 -2
- package/rr/a.js +19 -20
- package/rr/aaaa.js +35 -31
- package/rr/caa.js +41 -39
- package/rr/cert.js +21 -19
- package/rr/cname.js +19 -20
- package/rr/dname.js +19 -19
- package/rr/dnskey.js +48 -41
- package/rr/ds.js +38 -36
- package/rr/hinfo.js +26 -25
- package/rr/ipseckey.js +53 -45
- package/rr/key.js +21 -21
- package/rr/loc.js +67 -54
- package/rr/mx.js +26 -24
- package/rr/naptr.js +51 -52
- package/rr/ns.js +25 -22
- package/rr/nsec.js +26 -20
- package/rr/nsec3.js +63 -44
- package/rr/nsec3param.js +29 -26
- package/rr/nxt.js +67 -0
- package/rr/openpgpkey.js +13 -14
- package/rr/ptr.js +20 -21
- package/rr/rrsig.js +27 -21
- package/rr/sig.js +24 -19
- package/rr/smimea.js +35 -28
- package/rr/soa.js +57 -42
- package/rr/spf.js +20 -17
- package/rr/srv.js +41 -41
- package/rr/sshfp.js +29 -30
- package/rr/tlsa.js +47 -40
- package/rr/tsig.js +48 -0
- package/rr/txt.js +40 -33
- package/rr/uri.js +31 -31
- package/rr/wks.js +44 -0
- package/rr.js +111 -77
- package/.codeclimate.yml +0 -25
- package/DEVELOP.md +0 -23
- package/test/a.js +0 -76
- package/test/aaaa.js +0 -79
- package/test/base.js +0 -138
- package/test/caa.js +0 -104
- package/test/cert.js +0 -48
- package/test/cname.js +0 -41
- package/test/dname.js +0 -53
- package/test/dnskey.js +0 -44
- package/test/ds.js +0 -44
- package/test/fake.js +0 -26
- package/test/hinfo.js +0 -75
- package/test/ipseckey.js +0 -115
- package/test/key.js +0 -37
- package/test/loc.js +0 -71
- package/test/mx.js +0 -75
- package/test/naptr.js +0 -40
- package/test/ns.js +0 -53
- package/test/nsec.js +0 -38
- package/test/nsec3.js +0 -40
- package/test/nsec3param.js +0 -26
- package/test/openpgpkey.js +0 -35
- package/test/ptr.js +0 -54
- package/test/rr.js +0 -196
- package/test/smimea.js +0 -45
- package/test/soa.js +0 -77
- package/test/spf.js +0 -48
- package/test/srv.js +0 -81
- package/test/sshfp.js +0 -62
- package/test/tinydns.js +0 -140
- package/test/tlsa.js +0 -54
- package/test/txt.js +0 -70
- package/test/uri.js +0 -61
package/lib/tinydns.js
CHANGED
|
@@ -1,83 +1,88 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
'
|
|
4
|
-
'
|
|
5
|
-
'
|
|
6
|
-
'
|
|
7
|
-
'
|
|
8
|
-
'
|
|
9
|
-
'
|
|
10
|
-
'
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
'
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
'S': [ 'SRV' ],
|
|
1
|
+
export const SPECIAL_CHARS = {
|
|
2
|
+
'+': ['A'],
|
|
3
|
+
'-': [undefined], // disabled RR
|
|
4
|
+
'%': ['location'],
|
|
5
|
+
'.': ['SOA', 'NS', 'A'],
|
|
6
|
+
'&': ['NS', 'A'],
|
|
7
|
+
'=': ['A', 'PTR'],
|
|
8
|
+
'@': ['MX', 'A'],
|
|
9
|
+
'#': ['comment'],
|
|
10
|
+
"'": ['TXT'],
|
|
11
|
+
'^': ['PTR'],
|
|
12
|
+
C: ['CNAME'],
|
|
13
|
+
Z: ['SOA'],
|
|
14
|
+
':': ['generic'],
|
|
15
|
+
3: ['AAAA'],
|
|
16
|
+
6: ['AAAA', 'PTR'],
|
|
17
|
+
S: ['SRV'],
|
|
19
18
|
}
|
|
20
19
|
|
|
21
20
|
const octalRe = new RegExp(/\\(?:[1-7][0-7]{0,2}|[0-7]{2,3})/, 'g')
|
|
22
21
|
|
|
23
|
-
export function escapeOctal
|
|
22
|
+
export function escapeOctal(re, str) {
|
|
24
23
|
let escaped = ''
|
|
25
|
-
str.split(/(.{1})/g).map(c => {
|
|
24
|
+
str.split(/(.{1})/g).map((c) => {
|
|
26
25
|
escaped += re.test(c) ? charToOctal(c) : c
|
|
27
26
|
})
|
|
28
27
|
return escaped
|
|
29
28
|
}
|
|
30
29
|
|
|
31
|
-
export function octalToChar
|
|
30
|
+
export function octalToChar(str) {
|
|
32
31
|
// relace instances of \NNN with ASCII
|
|
33
|
-
return str.replace(octalRe,
|
|
32
|
+
return str.replace(octalRe, (o) =>
|
|
33
|
+
String.fromCharCode(parseInt(o.substring(1), 8)),
|
|
34
|
+
)
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
export function octalToHex
|
|
37
|
+
export function octalToHex(str) {
|
|
37
38
|
// relace instances of \NNN with Hex
|
|
38
|
-
return str.replace(octalRe, o => {
|
|
39
|
+
return str.replace(octalRe, (o) => {
|
|
39
40
|
// parseInt(n, 8) -> from octal to decimal
|
|
40
41
|
// .toString(16) -> decimal to hex
|
|
41
42
|
return parseInt(o.substring(1), 8).toString(16).padStart(2, 0)
|
|
42
43
|
})
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
export function octalToUInt8
|
|
46
|
+
export function octalToUInt8(str) {
|
|
46
47
|
const b = Buffer.alloc(1)
|
|
47
|
-
b.writeUInt8(parseInt(str.substring(1,4), 8), 0)
|
|
48
|
+
b.writeUInt8(parseInt(str.substring(1, 4), 8), 0)
|
|
48
49
|
return b.readUInt8()
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
export function octalToUInt16
|
|
52
|
+
export function octalToUInt16(str) {
|
|
52
53
|
const b = Buffer.alloc(2)
|
|
53
|
-
b.writeUInt8(parseInt(str.substring(1,4), 8), 0)
|
|
54
|
-
b.writeUInt8(parseInt(str.substring(5,8), 8), 1)
|
|
54
|
+
b.writeUInt8(parseInt(str.substring(1, 4), 8), 0)
|
|
55
|
+
b.writeUInt8(parseInt(str.substring(5, 8), 8), 1)
|
|
55
56
|
return b.readUInt16BE()
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
export function octalToUInt32
|
|
59
|
+
export function octalToUInt32(str) {
|
|
59
60
|
const b = Buffer.alloc(4)
|
|
60
|
-
b.writeUInt8(parseInt(str.substring(1,4), 8), 0)
|
|
61
|
-
b.writeUInt8(parseInt(str.substring(5,8), 8), 1)
|
|
62
|
-
b.writeUInt8(parseInt(str.substring(9,12), 8), 2)
|
|
63
|
-
b.writeUInt8(parseInt(str.substring(13,16), 8), 3)
|
|
61
|
+
b.writeUInt8(parseInt(str.substring(1, 4), 8), 0)
|
|
62
|
+
b.writeUInt8(parseInt(str.substring(5, 8), 8), 1)
|
|
63
|
+
b.writeUInt8(parseInt(str.substring(9, 12), 8), 2)
|
|
64
|
+
b.writeUInt8(parseInt(str.substring(13, 16), 8), 3)
|
|
64
65
|
return b.readUInt32BE()
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
export function packString
|
|
68
|
-
return str
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
export function packString(str) {
|
|
69
|
+
return str
|
|
70
|
+
.match(/(.{1,255})/g)
|
|
71
|
+
.map((s) => {
|
|
72
|
+
const len = Buffer.alloc(1)
|
|
73
|
+
len.writeUInt8(s.length)
|
|
74
|
+
return `${UInt8toOctal(len.readUInt8(0))}${s}`
|
|
75
|
+
})
|
|
76
|
+
.join('')
|
|
73
77
|
}
|
|
74
78
|
|
|
75
|
-
export function unpackString
|
|
79
|
+
export function unpackString(str) {
|
|
76
80
|
const asBuf = Buffer.from(octalToChar(str.toString()))
|
|
77
81
|
const res = []
|
|
78
82
|
let pos = 0
|
|
79
83
|
let len
|
|
80
|
-
while ((len = asBuf.readUInt8(pos))) {
|
|
84
|
+
while ((len = asBuf.readUInt8(pos))) {
|
|
85
|
+
// encoded length byte
|
|
81
86
|
pos++
|
|
82
87
|
res.push(asBuf.slice(pos, pos + len).toString())
|
|
83
88
|
pos = +(pos + len)
|
|
@@ -86,13 +91,13 @@ export function unpackString (str) {
|
|
|
86
91
|
return res
|
|
87
92
|
}
|
|
88
93
|
|
|
89
|
-
export function packDomainName
|
|
94
|
+
export function packDomainName(fqdn) {
|
|
90
95
|
const labelRegEx = new RegExp(/[^A-Za-z0-9-.]/, 'g')
|
|
91
96
|
|
|
92
97
|
// RFC 1035, 3.3 Standard RRs
|
|
93
98
|
// The standard wire format for DNS names. (1 octet length + octets)
|
|
94
99
|
let packed = ''
|
|
95
|
-
fqdn.split('.').map(label => {
|
|
100
|
+
fqdn.split('.').map((label) => {
|
|
96
101
|
if (label === undefined || !label.length) return
|
|
97
102
|
|
|
98
103
|
const len = Buffer.alloc(1)
|
|
@@ -105,14 +110,14 @@ export function packDomainName (fqdn) {
|
|
|
105
110
|
return packed
|
|
106
111
|
}
|
|
107
112
|
|
|
108
|
-
export function unpackDomainName
|
|
109
|
-
|
|
113
|
+
export function unpackDomainName(fqdn) {
|
|
110
114
|
fqdn = Buffer.from(octalToChar(fqdn.toString()))
|
|
111
115
|
|
|
112
116
|
const labels = []
|
|
113
117
|
let pos = 0
|
|
114
118
|
let len
|
|
115
|
-
while ((len = fqdn.readUInt8(pos))) {
|
|
119
|
+
while ((len = fqdn.readUInt8(pos))) {
|
|
120
|
+
// encoded length byte
|
|
116
121
|
pos++
|
|
117
122
|
labels.push(fqdn.slice(pos, pos + len).toString())
|
|
118
123
|
pos = +(pos + len)
|
|
@@ -120,31 +125,31 @@ export function unpackDomainName (fqdn) {
|
|
|
120
125
|
const r = `${labels.join('.')}.`
|
|
121
126
|
// char position + length of last label + label length chars + null byte
|
|
122
127
|
const strLen = pos + len + labels.length * 4 + 1
|
|
123
|
-
return [
|
|
128
|
+
return [r, strLen]
|
|
124
129
|
}
|
|
125
130
|
|
|
126
|
-
export function packHex
|
|
131
|
+
export function packHex(str) {
|
|
127
132
|
let r = ''
|
|
128
|
-
for (let i = 0; i < str.length; i = i+2) {
|
|
133
|
+
for (let i = 0; i < str.length; i = i + 2) {
|
|
129
134
|
// nibble off 2 hex bytes, encode to octal
|
|
130
|
-
r += UInt8toOctal(parseInt(str.slice(i, i+2), 16))
|
|
135
|
+
r += UInt8toOctal(parseInt(str.slice(i, i + 2), 16))
|
|
131
136
|
}
|
|
132
137
|
return r
|
|
133
138
|
}
|
|
134
139
|
|
|
135
|
-
export function charToOctal
|
|
140
|
+
export function charToOctal(c) {
|
|
136
141
|
if (typeof c === 'number') return UInt8toOctal(c)
|
|
137
142
|
|
|
138
143
|
return UInt8toOctal(c.charCodeAt(0))
|
|
139
144
|
}
|
|
140
145
|
|
|
141
|
-
export function UInt8toOctal
|
|
146
|
+
export function UInt8toOctal(n) {
|
|
142
147
|
if (n > 255) throw new Error('UInt8toOctal does not work on numbers > 255')
|
|
143
148
|
|
|
144
149
|
return `\\${parseInt(n, 10).toString(8).padStart(3, 0)}`
|
|
145
150
|
}
|
|
146
151
|
|
|
147
|
-
export function UInt16toOctal
|
|
152
|
+
export function UInt16toOctal(n) {
|
|
148
153
|
let r = ''
|
|
149
154
|
const pri = Buffer.alloc(2)
|
|
150
155
|
pri.writeUInt16BE(n)
|
|
@@ -153,7 +158,7 @@ export function UInt16toOctal (n) {
|
|
|
153
158
|
return r
|
|
154
159
|
}
|
|
155
160
|
|
|
156
|
-
export function UInt32toOctal
|
|
161
|
+
export function UInt32toOctal(n) {
|
|
157
162
|
let r = ''
|
|
158
163
|
const pri = Buffer.alloc(4)
|
|
159
164
|
pri.writeUInt32BE(n)
|
|
@@ -163,24 +168,26 @@ export function UInt32toOctal (n) {
|
|
|
163
168
|
return r
|
|
164
169
|
}
|
|
165
170
|
|
|
166
|
-
export function ipv4toOctal
|
|
171
|
+
export function ipv4toOctal(ip) {
|
|
167
172
|
return UInt32toOctal(ip.split`.`.reduce((int, value) => int * 256 + +value))
|
|
168
173
|
}
|
|
169
174
|
|
|
170
|
-
export function octalToIPv4
|
|
175
|
+
export function octalToIPv4(str) {
|
|
171
176
|
const asInt = octalToUInt32(str)
|
|
172
|
-
return [
|
|
177
|
+
return [24, 16, 8, 0].map((n) => (asInt >> n) & 0xff).join('.')
|
|
173
178
|
}
|
|
174
179
|
|
|
175
|
-
export function base64toOctal
|
|
180
|
+
export function base64toOctal(str) {
|
|
176
181
|
const bytes = Buffer.from(str, 'base64')
|
|
177
182
|
let escaped = ''
|
|
178
183
|
for (const b of bytes) {
|
|
179
|
-
escaped += /[A-Za-z0-9\-.]/.test(String.fromCharCode(b))
|
|
184
|
+
escaped += /[A-Za-z0-9\-.]/.test(String.fromCharCode(b))
|
|
185
|
+
? String.fromCharCode(b)
|
|
186
|
+
: UInt8toOctal(b)
|
|
180
187
|
}
|
|
181
188
|
return escaped
|
|
182
189
|
}
|
|
183
190
|
|
|
184
|
-
export function octalToBase64
|
|
191
|
+
export function octalToBase64(str) {
|
|
185
192
|
return Buffer.from(octalToChar(str), 'binary').toString('base64')
|
|
186
193
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nictool/dns-resource-record",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "DNS Resource Records",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -39,7 +39,6 @@
|
|
|
39
39
|
},
|
|
40
40
|
"homepage": "https://github.com/NicTool/dns-resource-record#readme",
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"eslint": "^8.56.0",
|
|
43
42
|
"mocha": "^10.3.0"
|
|
44
43
|
}
|
|
45
44
|
}
|
package/rr/a.js
CHANGED
|
@@ -1,57 +1,56 @@
|
|
|
1
|
-
|
|
2
1
|
import net from 'net'
|
|
3
2
|
|
|
4
3
|
import RR from '../rr.js'
|
|
5
4
|
|
|
6
5
|
export default class A extends RR {
|
|
7
|
-
constructor
|
|
6
|
+
constructor(opts) {
|
|
8
7
|
super(opts)
|
|
9
8
|
}
|
|
10
9
|
|
|
11
10
|
/****** Resource record specific setters *******/
|
|
12
|
-
setAddress
|
|
11
|
+
setAddress(val) {
|
|
13
12
|
if (!val) throw new Error('A: address is required')
|
|
14
13
|
if (!net.isIPv4(val)) throw new Error('A address must be IPv4')
|
|
15
14
|
this.set('address', val)
|
|
16
15
|
}
|
|
17
16
|
|
|
18
|
-
getDescription
|
|
17
|
+
getDescription() {
|
|
19
18
|
return 'Address'
|
|
20
19
|
}
|
|
21
20
|
|
|
22
|
-
getRdataFields
|
|
23
|
-
return [
|
|
21
|
+
getRdataFields(arg) {
|
|
22
|
+
return ['address']
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
getRFCs
|
|
27
|
-
return [
|
|
25
|
+
getRFCs() {
|
|
26
|
+
return [1035]
|
|
28
27
|
}
|
|
29
28
|
|
|
30
|
-
getTypeId
|
|
29
|
+
getTypeId() {
|
|
31
30
|
return 1
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
/****** IMPORTERS *******/
|
|
35
|
-
fromTinydns
|
|
34
|
+
fromTinydns(opts) {
|
|
36
35
|
// +fqdn:ip:ttl:timestamp:lo
|
|
37
|
-
const [
|
|
36
|
+
const [owner, ip, ttl, ts, loc] = opts.tinyline.substring(1).split(':')
|
|
38
37
|
|
|
39
38
|
return new A({
|
|
40
|
-
owner
|
|
41
|
-
type
|
|
42
|
-
address
|
|
43
|
-
ttl
|
|
39
|
+
owner: this.fullyQualify(owner),
|
|
40
|
+
type: 'A',
|
|
41
|
+
address: ip,
|
|
42
|
+
ttl: parseInt(ttl, 10),
|
|
44
43
|
timestamp: ts,
|
|
45
|
-
location
|
|
44
|
+
location: loc !== '' && loc !== '\n' ? loc : '',
|
|
46
45
|
})
|
|
47
46
|
}
|
|
48
47
|
|
|
49
|
-
fromBind
|
|
48
|
+
fromBind(opts) {
|
|
50
49
|
// test.example.com 3600 IN A 192.0.2.127
|
|
51
|
-
const [
|
|
50
|
+
const [owner, ttl, c, type, address] = opts.bindline.split(/\s+/)
|
|
52
51
|
return new A({
|
|
53
52
|
owner,
|
|
54
|
-
ttl
|
|
53
|
+
ttl: parseInt(ttl, 10),
|
|
55
54
|
class: c,
|
|
56
55
|
type,
|
|
57
56
|
address,
|
|
@@ -59,7 +58,7 @@ export default class A extends RR {
|
|
|
59
58
|
}
|
|
60
59
|
|
|
61
60
|
/****** EXPORTERS *******/
|
|
62
|
-
toTinydns
|
|
61
|
+
toTinydns() {
|
|
63
62
|
return `+${this.getTinyFQDN('owner')}:${this.get('address')}:${this.getTinydnsPostamble()}\n`
|
|
64
63
|
}
|
|
65
64
|
}
|
package/rr/aaaa.js
CHANGED
|
@@ -1,86 +1,87 @@
|
|
|
1
|
-
|
|
2
1
|
import net from 'net'
|
|
3
2
|
|
|
4
3
|
import RR from '../rr.js'
|
|
5
4
|
import * as TINYDNS from '../lib/tinydns.js'
|
|
6
5
|
|
|
7
6
|
export default class AAAA extends RR {
|
|
8
|
-
constructor
|
|
7
|
+
constructor(opts) {
|
|
9
8
|
super(opts)
|
|
10
9
|
}
|
|
11
10
|
|
|
12
11
|
/****** Resource record specific setters *******/
|
|
13
|
-
setAddress
|
|
12
|
+
setAddress(val) {
|
|
14
13
|
if (!val) throw new Error('AAAA: address is required')
|
|
15
14
|
if (!net.isIPv6(val)) throw new Error(`AAAA: address must be IPv6 (${val})`)
|
|
16
15
|
|
|
17
16
|
this.set('address', this.expand(val.toLowerCase())) // lower case: RFC 5952
|
|
18
17
|
}
|
|
19
18
|
|
|
20
|
-
getCompressed
|
|
19
|
+
getCompressed(val) {
|
|
21
20
|
this.compress(val || this.get('address'))
|
|
22
21
|
}
|
|
23
22
|
|
|
24
|
-
getDescription
|
|
23
|
+
getDescription() {
|
|
25
24
|
return 'Address IPv6'
|
|
26
25
|
}
|
|
27
26
|
|
|
28
|
-
getRdataFields
|
|
29
|
-
return [
|
|
27
|
+
getRdataFields(arg) {
|
|
28
|
+
return ['address']
|
|
30
29
|
}
|
|
31
30
|
|
|
32
|
-
getRFCs
|
|
33
|
-
return [
|
|
31
|
+
getRFCs() {
|
|
32
|
+
return [3596]
|
|
34
33
|
}
|
|
35
34
|
|
|
36
|
-
getTypeId
|
|
35
|
+
getTypeId() {
|
|
37
36
|
return 28
|
|
38
37
|
}
|
|
39
38
|
|
|
40
39
|
/****** IMPORTERS *******/
|
|
41
|
-
fromTinydns
|
|
40
|
+
fromTinydns(opts) {
|
|
42
41
|
const str = opts.tinyline
|
|
43
42
|
let fqdn, ip, n, rdata, ttl, ts, loc
|
|
44
43
|
|
|
45
44
|
switch (str[0]) {
|
|
46
45
|
case ':':
|
|
47
46
|
// GENERIC => :fqdn:28:rdata:ttl:timestamp:lo
|
|
48
|
-
[
|
|
47
|
+
;[fqdn, n, rdata, ttl, ts, loc] = str.substring(1).split(':')
|
|
49
48
|
if (n != 28) throw new Error('AAAA fromTinydns, invalid n')
|
|
50
|
-
ip = TINYDNS.octalToHex(rdata)
|
|
49
|
+
ip = TINYDNS.octalToHex(rdata)
|
|
50
|
+
.match(/([0-9a-fA-F]{4})/g)
|
|
51
|
+
.join(':')
|
|
51
52
|
break
|
|
52
53
|
case '3':
|
|
53
54
|
case '6':
|
|
54
55
|
// AAAA => 3fqdn:ip:x:ttl:timestamp:lo
|
|
55
56
|
// AAAA,PTR => 6fqdn:ip:x:ttl:timestamp:lo
|
|
56
|
-
[
|
|
57
|
+
;[fqdn, rdata, ttl, ts, loc] = str.substring(1).split(':')
|
|
57
58
|
ip = rdata.match(/(.{4})/g).join(':')
|
|
58
59
|
break
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
return new AAAA({
|
|
62
|
-
owner
|
|
63
|
-
ttl
|
|
64
|
-
type
|
|
65
|
-
address
|
|
63
|
+
owner: this.fullyQualify(fqdn),
|
|
64
|
+
ttl: parseInt(ttl, 10),
|
|
65
|
+
type: 'AAAA',
|
|
66
|
+
address: ip,
|
|
66
67
|
timestamp: ts,
|
|
67
|
-
location
|
|
68
|
+
location: loc !== '' && loc !== '\n' ? loc : '',
|
|
68
69
|
})
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
fromBind
|
|
72
|
+
fromBind(opts) {
|
|
72
73
|
// test.example.com 3600 IN AAAA ...
|
|
73
|
-
const [
|
|
74
|
+
const [owner, ttl, c, type, ip] = opts.bindline.split(/\s+/)
|
|
74
75
|
return new AAAA({
|
|
75
76
|
owner,
|
|
76
|
-
ttl
|
|
77
|
-
class
|
|
77
|
+
ttl: parseInt(ttl, 10),
|
|
78
|
+
class: c,
|
|
78
79
|
type,
|
|
79
80
|
address: this.expand(ip),
|
|
80
81
|
})
|
|
81
82
|
}
|
|
82
83
|
|
|
83
|
-
compress
|
|
84
|
+
compress(val) {
|
|
84
85
|
/*
|
|
85
86
|
* RFC 5952
|
|
86
87
|
* 4.1. Leading zeros MUST be suppressed...A single 16-bit 0000 field MUST be represented as 0.
|
|
@@ -88,10 +89,10 @@ export default class AAAA extends RR {
|
|
|
88
89
|
* 4.2.2 The symbol "::" MUST NOT be used to shorten just one 16-bit 0 field.
|
|
89
90
|
* 4.2.3 When choosing placement of a "::", the longest run...MUST be shortened
|
|
90
91
|
* 4.3 The characters a-f in an IPv6 address MUST be represented in lowercase.
|
|
91
|
-
|
|
92
|
+
*/
|
|
92
93
|
let r = val
|
|
93
|
-
.replace(/0000/g, '0')
|
|
94
|
-
.replace(/:0+([1-9a-fA-F])/g, ':$1')
|
|
94
|
+
.replace(/0000/g, '0') // 4.1 0000 -> 0
|
|
95
|
+
.replace(/:0+([1-9a-fA-F])/g, ':$1') // 4.1 remove leading zeros
|
|
95
96
|
|
|
96
97
|
const mostConsecutiveZeros = [
|
|
97
98
|
new RegExp(/0?(?::0){6,}:0?/),
|
|
@@ -111,7 +112,7 @@ export default class AAAA extends RR {
|
|
|
111
112
|
return r
|
|
112
113
|
}
|
|
113
114
|
|
|
114
|
-
expand
|
|
115
|
+
expand(val, delimiter) {
|
|
115
116
|
if (delimiter === undefined) delimiter = ':'
|
|
116
117
|
|
|
117
118
|
const colons = val.match(/:/g)
|
|
@@ -121,15 +122,18 @@ export default class AAAA extends RR {
|
|
|
121
122
|
}
|
|
122
123
|
|
|
123
124
|
// restore compressed leading zeros
|
|
124
|
-
return val
|
|
125
|
+
return val
|
|
126
|
+
.split(':')
|
|
127
|
+
.map((s) => s.padStart(4, 0))
|
|
128
|
+
.join(delimiter)
|
|
125
129
|
}
|
|
126
130
|
|
|
127
131
|
/****** EXPORTERS *******/
|
|
128
|
-
toBind
|
|
132
|
+
toBind(zone_opts) {
|
|
129
133
|
return `${this.getPrefix(zone_opts)}\t${this.compress(this.get('address'))}\n`
|
|
130
134
|
}
|
|
131
135
|
|
|
132
|
-
toTinydns
|
|
136
|
+
toTinydns() {
|
|
133
137
|
// from AAAA notation (8 groups of 4 hex digits) to 16 escaped octals
|
|
134
138
|
const rdata = TINYDNS.packHex(this.expand(this.get('address'), ''))
|
|
135
139
|
return this.getTinydnsGeneric(rdata)
|
package/rr/caa.js
CHANGED
|
@@ -1,110 +1,112 @@
|
|
|
1
|
-
|
|
2
1
|
import RR from '../rr.js'
|
|
3
2
|
import * as TINYDNS from '../lib/tinydns.js'
|
|
4
3
|
|
|
5
4
|
export default class CAA extends RR {
|
|
6
|
-
constructor
|
|
5
|
+
constructor(opts) {
|
|
7
6
|
super(opts)
|
|
8
7
|
}
|
|
9
8
|
|
|
10
9
|
/****** Resource record specific setters *******/
|
|
11
|
-
setFlags
|
|
10
|
+
setFlags(val) {
|
|
12
11
|
this.is8bitInt('CAA', 'flags', val)
|
|
13
12
|
|
|
14
|
-
if (![
|
|
13
|
+
if (![0, 128].includes(val)) {
|
|
15
14
|
throw new Error(`CAA flags ${val} not recognized, ${this.citeRFC()}`)
|
|
16
15
|
}
|
|
17
16
|
|
|
18
17
|
this.set('flags', val)
|
|
19
18
|
}
|
|
20
19
|
|
|
21
|
-
setTag
|
|
22
|
-
if (typeof val !== 'string'
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
setTag(val) {
|
|
21
|
+
if (typeof val !== 'string' || val.length < 1 || /[^a-z0-9]/.test(val))
|
|
22
|
+
throw new Error(
|
|
23
|
+
`CAA tag must be a sequence of ASCII letters and numbers in lowercase, ${this.citeRFC()}`,
|
|
24
|
+
)
|
|
26
25
|
|
|
27
|
-
if (![
|
|
26
|
+
if (!['issue', 'issuewild', 'iodef'].includes(val)) {
|
|
28
27
|
throw new Error(`CAA tag ${val} not recognized: ${this.citeRFC()}`)
|
|
29
28
|
}
|
|
30
29
|
this.set('tag', val)
|
|
31
30
|
}
|
|
32
31
|
|
|
33
|
-
setValue
|
|
32
|
+
setValue(val) {
|
|
34
33
|
// either (2) a quoted string or
|
|
35
34
|
// (1) a contiguous set of characters without interior spaces
|
|
36
35
|
if (this.isQuoted(val)) {
|
|
37
36
|
val = val.replace(/^["']|["']$/g, '') // strip quotes
|
|
38
|
-
}
|
|
39
|
-
else {
|
|
37
|
+
} else {
|
|
40
38
|
// if (/\s/.test(val)) throw new Error(`CAA value may not have spaces unless quoted: RFC 8659`)
|
|
41
39
|
}
|
|
42
40
|
|
|
43
41
|
// check if val starts with one of iodefSchemes
|
|
44
42
|
if (this.get('tag') === 'iodef') {
|
|
45
|
-
const iodefSchemes = [
|
|
46
|
-
if (!iodefSchemes.filter(s => val.startsWith(s)).length) {
|
|
47
|
-
throw new Error(
|
|
43
|
+
const iodefSchemes = ['mailto:', 'http:', 'https:']
|
|
44
|
+
if (!iodefSchemes.filter((s) => val.startsWith(s)).length) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`CAA value must have valid iodefScheme prefix, ${this.citeRFC()}`,
|
|
47
|
+
)
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
this.set('value', val)
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
getDescription
|
|
54
|
+
getDescription() {
|
|
55
55
|
return 'Certification Authority Authorization'
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
getQuotedFields
|
|
59
|
-
return [
|
|
58
|
+
getQuotedFields() {
|
|
59
|
+
return ['value']
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
getRdataFields
|
|
63
|
-
return [
|
|
62
|
+
getRdataFields(arg) {
|
|
63
|
+
return ['flags', 'tag', 'value']
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
getRFCs
|
|
67
|
-
return [
|
|
66
|
+
getRFCs() {
|
|
67
|
+
return [6844, 8659]
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
getTypeId
|
|
70
|
+
getTypeId() {
|
|
71
71
|
return 257
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
/****** IMPORTERS *******/
|
|
75
|
-
fromTinydns
|
|
75
|
+
fromTinydns(opts) {
|
|
76
76
|
// CAA via generic, :fqdn:n:rdata:ttl:timestamp:lo
|
|
77
|
-
const [
|
|
77
|
+
const [fqdn, n, rdata, ttl, ts, loc] = opts.tinyline.substring(1).split(':')
|
|
78
78
|
if (n != 257) throw new Error('CAA fromTinydns, invalid n')
|
|
79
79
|
|
|
80
|
-
const flags
|
|
80
|
+
const flags = TINYDNS.octalToUInt8(rdata.substring(0, 4))
|
|
81
81
|
const taglen = TINYDNS.octalToUInt8(rdata.substring(4, 8))
|
|
82
82
|
|
|
83
|
-
const unescaped
|
|
84
|
-
const tag
|
|
83
|
+
const unescaped = TINYDNS.octalToChar(rdata.substring(8))
|
|
84
|
+
const tag = unescaped.substring(0, taglen)
|
|
85
85
|
const fingerprint = unescaped.substring(taglen)
|
|
86
86
|
|
|
87
87
|
return new CAA({
|
|
88
|
-
owner
|
|
89
|
-
ttl
|
|
90
|
-
type
|
|
88
|
+
owner: this.fullyQualify(fqdn),
|
|
89
|
+
ttl: parseInt(ttl, 10),
|
|
90
|
+
type: 'CAA',
|
|
91
91
|
flags,
|
|
92
92
|
tag,
|
|
93
|
-
value
|
|
93
|
+
value: fingerprint,
|
|
94
94
|
timestamp: ts,
|
|
95
|
-
location
|
|
95
|
+
location: loc !== '' && loc !== '\n' ? loc : '',
|
|
96
96
|
})
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
fromBind
|
|
99
|
+
fromBind(opts) {
|
|
100
100
|
// test.example.com 3600 IN CAA flags, tags, value
|
|
101
|
-
const fields = opts.bindline.match(
|
|
101
|
+
const fields = opts.bindline.match(
|
|
102
|
+
/^([^\s]+)\s+([0-9]+)\s+(\w+)\s+(\w+)\s+([0-9]+)\s+(\w+)\s+("[^"]+"|[^\s]+?)\s*$/i,
|
|
103
|
+
)
|
|
102
104
|
if (!fields) throw new Error(`unable to parse: ${opts.bindline}`)
|
|
103
105
|
|
|
104
|
-
const [
|
|
106
|
+
const [owner, ttl, c, type, flags, tag, value] = fields.slice(1)
|
|
105
107
|
return new CAA({
|
|
106
108
|
owner,
|
|
107
|
-
ttl
|
|
109
|
+
ttl: parseInt(ttl, 10),
|
|
108
110
|
class: c,
|
|
109
111
|
type,
|
|
110
112
|
flags: parseInt(flags, 10),
|
|
@@ -115,7 +117,7 @@ export default class CAA extends RR {
|
|
|
115
117
|
|
|
116
118
|
/****** EXPORTERS *******/
|
|
117
119
|
|
|
118
|
-
toTinydns
|
|
120
|
+
toTinydns() {
|
|
119
121
|
let rdata = ''
|
|
120
122
|
rdata += TINYDNS.UInt8toOctal(this.get('flags'))
|
|
121
123
|
|