@aoxborrow/registrar-client 0.9.0 → 0.9.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.
- package/README.md +29 -0
- package/dist/index.cjs +439 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -1
- package/dist/index.d.ts +35 -1
- package/dist/index.js +428 -16
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/dns-records.ts +544 -0
- package/src/index.ts +3 -0
- package/src/registrars/domain101.ts +23 -5
- package/src/registrars/namebright.ts +56 -15
package/README.md
CHANGED
|
@@ -425,6 +425,35 @@ sandbox (Name.com offers no domain deletion/reset there); it is excluded from th
|
|
|
425
425
|
default offline suite. Transfer completion and production behavior need separate
|
|
426
426
|
verification; sandbox transfers are not deterministic.
|
|
427
427
|
|
|
428
|
+
## DNS record syntax
|
|
429
|
+
|
|
430
|
+
Pure functions for checking DNS record syntax and putting records in one
|
|
431
|
+
canonical form, with no network calls, so a UI can validate what someone types
|
|
432
|
+
before it reaches a registrar.
|
|
433
|
+
|
|
434
|
+
```ts
|
|
435
|
+
import { findCnameConflicts, prepareEntry, toDnsEntry } from '@aoxborrow/registrar-client';
|
|
436
|
+
|
|
437
|
+
// throws DnsSyntaxError with a message safe to show a person
|
|
438
|
+
prepareEntry(toDnsEntry({ type: 'MX', name: '@', value: 'Mail.Example.net.', priority: 10 }));
|
|
439
|
+
// → { owner: '@', kind: 'MX', ttl: 3600, rdata: '10 mail.example.net' }
|
|
440
|
+
|
|
441
|
+
findCnameConflicts(records.map(r => toDnsEntry(r))); // [{ owner, kind, detail }]
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
- Names are folded to lowercase with one trailing dot removed, and are never
|
|
445
|
+
joined to a zone: `www` and `www.example.com` are different owners, and `@`
|
|
446
|
+
stays `@`. Underscore labels (`_dmarc`) and one leftmost `*` are allowed.
|
|
447
|
+
- Data syntax is checked for A, AAAA, CNAME, NS, MX (including null MX), TXT,
|
|
448
|
+
SRV, CAA and DS. Any other type is trimmed and passed through, so a
|
|
449
|
+
provider-specific type still round-trips.
|
|
450
|
+
- `findCnameConflicts` and `findRepeatedRecords` check a whole set of records.
|
|
451
|
+
- `toDnsEntry` adapts a `DnsRecord` (separate MX/SRV number fields) to the
|
|
452
|
+
entry shape the checks take.
|
|
453
|
+
|
|
454
|
+
These are DNS rules only. Each provider still applies its own limits, such as
|
|
455
|
+
a minimum TTL or the record types it accepts.
|
|
456
|
+
|
|
428
457
|
## Sandbox environments
|
|
429
458
|
|
|
430
459
|
Providers that offer a test environment accept `{ environment: 'sandbox' }` at
|
package/dist/index.cjs
CHANGED
|
@@ -2238,20 +2238,32 @@ function fromRecord(raw) {
|
|
|
2238
2238
|
if (raw.type === "MX") {
|
|
2239
2239
|
const match = /^(\d+)\s+(.+)$/.exec(raw.value);
|
|
2240
2240
|
if (!match) throw invalid("MX record");
|
|
2241
|
-
return { ...common, value: match[2], priority: Number(match[1]) };
|
|
2241
|
+
return { ...common, value: canonicalValue("MX", match[2]), priority: Number(match[1]) };
|
|
2242
2242
|
}
|
|
2243
2243
|
if (raw.type === "SRV") {
|
|
2244
2244
|
const match = /^(\d+)\s+(\d+)\s+(\d+)\s+(.+)$/.exec(raw.value);
|
|
2245
2245
|
if (!match) throw invalid("SRV record");
|
|
2246
2246
|
return {
|
|
2247
2247
|
...common,
|
|
2248
|
-
value: match[4],
|
|
2248
|
+
value: canonicalValue("SRV", match[4]),
|
|
2249
2249
|
priority: Number(match[1]),
|
|
2250
2250
|
weight: Number(match[2]),
|
|
2251
2251
|
port: Number(match[3])
|
|
2252
2252
|
};
|
|
2253
2253
|
}
|
|
2254
|
-
return { ...common, value: raw.value };
|
|
2254
|
+
return { ...common, value: canonicalValue(raw.type, raw.value) };
|
|
2255
|
+
}
|
|
2256
|
+
function canonicalValue(type, value) {
|
|
2257
|
+
const kind = type.toUpperCase();
|
|
2258
|
+
if (kind === "TXT") return unquoteTxt(value);
|
|
2259
|
+
if (["CNAME", "NS", "MX", "SRV"].includes(kind))
|
|
2260
|
+
return value === "." ? "." : value.replace(/\.$/, "");
|
|
2261
|
+
return value;
|
|
2262
|
+
}
|
|
2263
|
+
function unquoteTxt(value) {
|
|
2264
|
+
const chunk = /"((?:[^"\\]|\\.)*)"/g;
|
|
2265
|
+
if (!/^\s*"(?:[^"\\]|\\.)*"(?:\s+"(?:[^"\\]|\\.)*")*\s*$/.test(value)) return value;
|
|
2266
|
+
return [...value.matchAll(chunk)].map((m) => m[1].replace(/\\(.)/g, "$1")).join("");
|
|
2255
2267
|
}
|
|
2256
2268
|
function recordSpec(record, domain, strict = true) {
|
|
2257
2269
|
const type = record.type.toUpperCase();
|
|
@@ -2272,7 +2284,7 @@ function recordSpec(record, domain, strict = true) {
|
|
|
2272
2284
|
if (!Number.isInteger(record.priority) || record.priority < 0 || record.priority > 65535)
|
|
2273
2285
|
reject("MX/SRV priority is required");
|
|
2274
2286
|
result.priority = record.priority;
|
|
2275
|
-
result.target = record.value;
|
|
2287
|
+
result.target = canonicalValue(type, record.value);
|
|
2276
2288
|
if (type === "SRV") {
|
|
2277
2289
|
if (![record.weight, record.port].every((n) => Number.isInteger(n) && n >= 0 && n <= 65535))
|
|
2278
2290
|
reject("SRV weight and port are required");
|
|
@@ -2289,7 +2301,7 @@ function recordSpec(record, domain, strict = true) {
|
|
|
2289
2301
|
result.tag = match[2];
|
|
2290
2302
|
result.value = match[3];
|
|
2291
2303
|
}
|
|
2292
|
-
} else result.value = record.value;
|
|
2304
|
+
} else result.value = canonicalValue(type, record.value);
|
|
2293
2305
|
return result;
|
|
2294
2306
|
}
|
|
2295
2307
|
function recordSignature(record) {
|
|
@@ -4627,6 +4639,80 @@ function dnsFieldsDiffer(a, b) {
|
|
|
4627
4639
|
|
|
4628
4640
|
// src/registrars/namebright.ts
|
|
4629
4641
|
var TOKEN_URL = "https://api.namebright.com/auth/token";
|
|
4642
|
+
var NB_KEYS = new Map(
|
|
4643
|
+
[
|
|
4644
|
+
// domains
|
|
4645
|
+
"DomainName",
|
|
4646
|
+
"Status",
|
|
4647
|
+
"ExpirationDate",
|
|
4648
|
+
"RegistrationDate",
|
|
4649
|
+
"AutoRenew",
|
|
4650
|
+
"Locked",
|
|
4651
|
+
"WhoIsPrivacy",
|
|
4652
|
+
"AuthCode",
|
|
4653
|
+
"Category",
|
|
4654
|
+
"UpgradedDomain",
|
|
4655
|
+
"ResultsTotal",
|
|
4656
|
+
"CurrentPage",
|
|
4657
|
+
"Domains",
|
|
4658
|
+
"NameServers",
|
|
4659
|
+
// contacts
|
|
4660
|
+
"RegistrantContact",
|
|
4661
|
+
"AdministrativeContact",
|
|
4662
|
+
"TechnicalContact",
|
|
4663
|
+
"FirstName",
|
|
4664
|
+
"LastName",
|
|
4665
|
+
"Organization",
|
|
4666
|
+
"Department",
|
|
4667
|
+
"Email",
|
|
4668
|
+
"Address1",
|
|
4669
|
+
"Address2",
|
|
4670
|
+
"City",
|
|
4671
|
+
"Region",
|
|
4672
|
+
"Country",
|
|
4673
|
+
"PostalCode",
|
|
4674
|
+
"PhoneCountry",
|
|
4675
|
+
"Phone",
|
|
4676
|
+
"FaxCountry",
|
|
4677
|
+
"Fax",
|
|
4678
|
+
// host records
|
|
4679
|
+
"ARecords",
|
|
4680
|
+
"AAAARecords",
|
|
4681
|
+
"CNAMERecords",
|
|
4682
|
+
"MXRecords",
|
|
4683
|
+
"TXTRecords",
|
|
4684
|
+
"SRVRecords",
|
|
4685
|
+
"RecordId",
|
|
4686
|
+
"Subdomain",
|
|
4687
|
+
"IPV4Address",
|
|
4688
|
+
"IPV6Address",
|
|
4689
|
+
"RedirectDomain",
|
|
4690
|
+
"MailServer",
|
|
4691
|
+
"Priority",
|
|
4692
|
+
"TextRecord",
|
|
4693
|
+
"Service",
|
|
4694
|
+
"Protocol",
|
|
4695
|
+
"Weight",
|
|
4696
|
+
"Port",
|
|
4697
|
+
"Target",
|
|
4698
|
+
// availability + orders
|
|
4699
|
+
"ProductTypeName",
|
|
4700
|
+
"UnitPrice",
|
|
4701
|
+
"Promotion",
|
|
4702
|
+
"PromotionPrice",
|
|
4703
|
+
"Discount",
|
|
4704
|
+
"Description",
|
|
4705
|
+
"OrderId",
|
|
4706
|
+
"TotalPrice"
|
|
4707
|
+
].map((k) => [k.toLowerCase(), k])
|
|
4708
|
+
);
|
|
4709
|
+
function canonicalKeys(value) {
|
|
4710
|
+
if (Array.isArray(value)) return value.map(canonicalKeys);
|
|
4711
|
+
if (value === null || typeof value !== "object") return value;
|
|
4712
|
+
return Object.fromEntries(
|
|
4713
|
+
Object.entries(value).map(([k, v]) => [NB_KEYS.get(k.toLowerCase()) ?? k, canonicalKeys(v)])
|
|
4714
|
+
);
|
|
4715
|
+
}
|
|
4630
4716
|
var NameBrightRegistrar = class extends BaseRegistrar {
|
|
4631
4717
|
name = "namebright";
|
|
4632
4718
|
// a REST API: GET and HEAD never change state
|
|
@@ -4680,14 +4766,16 @@ var NameBrightRegistrar = class extends BaseRegistrar {
|
|
|
4680
4766
|
this.tokenExpiresAt = now + ttlMs - 6e4;
|
|
4681
4767
|
return this.token;
|
|
4682
4768
|
}
|
|
4683
|
-
// issue an authenticated REST request, attaching a fresh bearer token
|
|
4769
|
+
// issue an authenticated REST request, attaching a fresh bearer token. The
|
|
4770
|
+
// response's keys are normalized to PascalCase (see NB_KEYS).
|
|
4684
4771
|
async authed(config, opts) {
|
|
4685
4772
|
const token = await this.getToken(opts);
|
|
4686
|
-
|
|
4773
|
+
const res = await this.http.request({
|
|
4687
4774
|
...config,
|
|
4688
4775
|
headers: { Authorization: `Bearer ${token}` },
|
|
4689
4776
|
...opts
|
|
4690
4777
|
});
|
|
4778
|
+
return canonicalKeys(res);
|
|
4691
4779
|
}
|
|
4692
4780
|
async testConnection(opts) {
|
|
4693
4781
|
try {
|
|
@@ -4708,8 +4796,8 @@ var NameBrightRegistrar = class extends BaseRegistrar {
|
|
|
4708
4796
|
{ path: "account/domains", query: { page, domainsPerPage: perPage } },
|
|
4709
4797
|
reqOpts
|
|
4710
4798
|
);
|
|
4711
|
-
const list = Array.isArray(data) ? data : data?.Domains ??
|
|
4712
|
-
for (const d of list) domains.push(this.toDomain(d));
|
|
4799
|
+
const list = Array.isArray(data) ? data : data?.Domains ?? [];
|
|
4800
|
+
for (const d of list) if (d.DomainName) domains.push(this.toDomain(d));
|
|
4713
4801
|
hasMore = list.length === perPage;
|
|
4714
4802
|
page++;
|
|
4715
4803
|
}
|
|
@@ -4746,7 +4834,7 @@ var NameBrightRegistrar = class extends BaseRegistrar {
|
|
|
4746
4834
|
opts
|
|
4747
4835
|
);
|
|
4748
4836
|
if (Array.isArray(res)) return res.map(String);
|
|
4749
|
-
return (res.NameServers ??
|
|
4837
|
+
return (res.NameServers ?? []).map(String);
|
|
4750
4838
|
}
|
|
4751
4839
|
/**
|
|
4752
4840
|
* Read the registrant/admin/tech contacts via GET
|
|
@@ -4883,10 +4971,10 @@ var NameBrightRegistrar = class extends BaseRegistrar {
|
|
|
4883
4971
|
async setDomainFlag(domainName, change, successMessage, opts) {
|
|
4884
4972
|
try {
|
|
4885
4973
|
const path = `account/domains/${encodeURIComponent(domainName)}`;
|
|
4886
|
-
const current = await this.authed(
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4974
|
+
const current = await this.authed({ path }, opts);
|
|
4975
|
+
if (!current.DomainName) {
|
|
4976
|
+
return { success: false, message: `NameBright: could not read ${domainName} to update it` };
|
|
4977
|
+
}
|
|
4890
4978
|
const body = {
|
|
4891
4979
|
DomainName: current.DomainName,
|
|
4892
4980
|
Status: current.Status,
|
|
@@ -4963,7 +5051,7 @@ var NameBrightRegistrar = class extends BaseRegistrar {
|
|
|
4963
5051
|
// normalized Domain shape. Neither endpoint returns nameservers.
|
|
4964
5052
|
toDomain(d) {
|
|
4965
5053
|
return createDomain({
|
|
4966
|
-
domainName: d.DomainName
|
|
5054
|
+
domainName: d.DomainName,
|
|
4967
5055
|
registrar: this.name,
|
|
4968
5056
|
status: d.Status ?? "ok",
|
|
4969
5057
|
createdDate: d.RegistrationDate,
|
|
@@ -8006,6 +8094,330 @@ function createRegistrar(name, credentials, options) {
|
|
|
8006
8094
|
return new RegistrarClass(credentials, options);
|
|
8007
8095
|
}
|
|
8008
8096
|
|
|
8097
|
+
// src/dns-records.ts
|
|
8098
|
+
var DnsSyntaxError = class extends Error {
|
|
8099
|
+
constructor(message) {
|
|
8100
|
+
super(message);
|
|
8101
|
+
this.name = "DnsSyntaxError";
|
|
8102
|
+
}
|
|
8103
|
+
};
|
|
8104
|
+
var NAME_LIMIT = 253;
|
|
8105
|
+
var LABEL_LIMIT = 63;
|
|
8106
|
+
var TTL_LIMIT = 2147483647;
|
|
8107
|
+
var TEXT_OCTETS = 255;
|
|
8108
|
+
var TEXT_TOTAL_OCTETS = 65535 - Math.ceil(65535 / (TEXT_OCTETS + 1));
|
|
8109
|
+
var UINT16_MAX = 65535;
|
|
8110
|
+
var DIGEST_HEX_LENGTH = {
|
|
8111
|
+
1: 40,
|
|
8112
|
+
2: 64,
|
|
8113
|
+
3: 64,
|
|
8114
|
+
4: 96
|
|
8115
|
+
};
|
|
8116
|
+
var KINDS_ALLOWED_BESIDE_CNAME = /* @__PURE__ */ new Set(["CNAME", "RRSIG", "NSEC", "NSEC3"]);
|
|
8117
|
+
function mapAsciiLabel(label) {
|
|
8118
|
+
const folded = label.toLowerCase();
|
|
8119
|
+
if (folded.length < 1 || folded.length > LABEL_LIMIT) return null;
|
|
8120
|
+
if (!/^[a-z0-9_](?:[a-z0-9_-]{0,61}[a-z0-9_])?$/.test(folded)) return null;
|
|
8121
|
+
return folded;
|
|
8122
|
+
}
|
|
8123
|
+
var labelMapper = mapAsciiLabel;
|
|
8124
|
+
function useLabelMapper(mapper) {
|
|
8125
|
+
labelMapper = mapper;
|
|
8126
|
+
}
|
|
8127
|
+
function resetLabelMapper() {
|
|
8128
|
+
labelMapper = mapAsciiLabel;
|
|
8129
|
+
}
|
|
8130
|
+
function prepareName(input, role = "owner") {
|
|
8131
|
+
const trimmed = input.trim();
|
|
8132
|
+
if (!trimmed) throw new DnsSyntaxError("Name is empty");
|
|
8133
|
+
const bare = trimmed.endsWith(".") ? trimmed.slice(0, -1) : trimmed;
|
|
8134
|
+
if (!bare) throw new DnsSyntaxError("Name is empty");
|
|
8135
|
+
if (bare === "@") {
|
|
8136
|
+
if (role === "target") throw new DnsSyntaxError("Origin token @ cannot be a record target");
|
|
8137
|
+
return "@";
|
|
8138
|
+
}
|
|
8139
|
+
const labels = bare.split(".");
|
|
8140
|
+
if (labels.some((label) => label.length === 0)) {
|
|
8141
|
+
throw new DnsSyntaxError("Name contains an empty label");
|
|
8142
|
+
}
|
|
8143
|
+
const starLabels = labels.filter((label) => label.includes("*"));
|
|
8144
|
+
if (starLabels.length > 0) {
|
|
8145
|
+
if (role !== "owner") {
|
|
8146
|
+
throw new DnsSyntaxError("A wildcard is only valid as an owner name");
|
|
8147
|
+
}
|
|
8148
|
+
const starCount = starLabels.reduce(
|
|
8149
|
+
(count, label) => count + (label.match(/\*/g)?.length ?? 0),
|
|
8150
|
+
0
|
|
8151
|
+
);
|
|
8152
|
+
if (starCount > 1) throw new DnsSyntaxError("A name can contain only one wildcard");
|
|
8153
|
+
if (labels[0] !== "*") throw new DnsSyntaxError("A wildcard must be its own leftmost label");
|
|
8154
|
+
}
|
|
8155
|
+
const mapped = labels.map((label) => {
|
|
8156
|
+
if (label === "*") return "*";
|
|
8157
|
+
const ascii = labelMapper(label);
|
|
8158
|
+
if (!ascii) throw new DnsSyntaxError(`Label is not a valid DNS label: ${label}`);
|
|
8159
|
+
return ascii.toLowerCase();
|
|
8160
|
+
});
|
|
8161
|
+
const name = mapped.join(".");
|
|
8162
|
+
if (name.length > NAME_LIMIT) throw new DnsSyntaxError("Name is longer than 253 characters");
|
|
8163
|
+
return name;
|
|
8164
|
+
}
|
|
8165
|
+
function isIpv4(text2) {
|
|
8166
|
+
const octets = text2.split(".");
|
|
8167
|
+
if (octets.length !== 4) return false;
|
|
8168
|
+
return octets.every((octet) => {
|
|
8169
|
+
if (!/^\d{1,3}$/.test(octet)) return false;
|
|
8170
|
+
const value = Number(octet);
|
|
8171
|
+
return value >= 0 && value <= 255 && String(value) === octet;
|
|
8172
|
+
});
|
|
8173
|
+
}
|
|
8174
|
+
function isIpv6(text2) {
|
|
8175
|
+
let body = text2;
|
|
8176
|
+
const embedded = text2.match(/^(.+):(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/);
|
|
8177
|
+
if (embedded) {
|
|
8178
|
+
if (!isIpv4(embedded[2])) return false;
|
|
8179
|
+
body = `${embedded[1]}:0:0`;
|
|
8180
|
+
}
|
|
8181
|
+
const compressedAt = body.indexOf("::");
|
|
8182
|
+
if (compressedAt !== -1) {
|
|
8183
|
+
if (body.indexOf("::", compressedAt + 1) !== -1) return false;
|
|
8184
|
+
const groupsOf = (side) => side === "" ? [] : side.split(":");
|
|
8185
|
+
const head = groupsOf(body.slice(0, compressedAt));
|
|
8186
|
+
const tail = groupsOf(body.slice(compressedAt + 2));
|
|
8187
|
+
if (head.length + tail.length > 7) return false;
|
|
8188
|
+
return [...head, ...tail].every((group) => /^[0-9a-fA-F]{1,4}$/.test(group));
|
|
8189
|
+
}
|
|
8190
|
+
const groups = body.split(":");
|
|
8191
|
+
return groups.length === 8 && groups.every((group) => /^[0-9a-fA-F]{1,4}$/.test(group));
|
|
8192
|
+
}
|
|
8193
|
+
function utf8OctetCount(text2) {
|
|
8194
|
+
let count = 0;
|
|
8195
|
+
for (const char of text2) {
|
|
8196
|
+
const code = char.codePointAt(0) ?? 0;
|
|
8197
|
+
if (code <= 127) count += 1;
|
|
8198
|
+
else if (code <= 2047) count += 2;
|
|
8199
|
+
else if (code <= 65535) count += 3;
|
|
8200
|
+
else count += 4;
|
|
8201
|
+
}
|
|
8202
|
+
return count;
|
|
8203
|
+
}
|
|
8204
|
+
function decimalInRange(token, limit) {
|
|
8205
|
+
if (!/^(0|[1-9][0-9]*)$/.test(token)) return null;
|
|
8206
|
+
const value = Number(token);
|
|
8207
|
+
if (value > limit) return null;
|
|
8208
|
+
return value;
|
|
8209
|
+
}
|
|
8210
|
+
function prepareIpv4(text2) {
|
|
8211
|
+
if (!isIpv4(text2)) throw new DnsSyntaxError("IPv4 address is invalid");
|
|
8212
|
+
return text2;
|
|
8213
|
+
}
|
|
8214
|
+
function prepareIpv6(text2) {
|
|
8215
|
+
if (!isIpv6(text2)) throw new DnsSyntaxError("IPv6 address is invalid");
|
|
8216
|
+
return text2.toLowerCase();
|
|
8217
|
+
}
|
|
8218
|
+
function prepareHostTarget(text2, owner, rejectSelf) {
|
|
8219
|
+
const withoutDot = text2.endsWith(".") ? text2.slice(0, -1) : text2;
|
|
8220
|
+
if (isIpv4(withoutDot) || isIpv6(withoutDot)) {
|
|
8221
|
+
throw new DnsSyntaxError("Target must be a domain name, not an address");
|
|
8222
|
+
}
|
|
8223
|
+
const target = prepareName(text2, "target");
|
|
8224
|
+
if (rejectSelf && owner !== void 0 && prepareName(owner, "owner") === target) {
|
|
8225
|
+
throw new DnsSyntaxError("CNAME target is the same name as its owner");
|
|
8226
|
+
}
|
|
8227
|
+
return target;
|
|
8228
|
+
}
|
|
8229
|
+
function prepareMailExchange(text2) {
|
|
8230
|
+
const parts = text2.split(/\s+/);
|
|
8231
|
+
if (parts.length !== 2) {
|
|
8232
|
+
throw new DnsSyntaxError('MX data must be "<priority> <mail host>"');
|
|
8233
|
+
}
|
|
8234
|
+
const priority = decimalInRange(parts[0], UINT16_MAX);
|
|
8235
|
+
if (priority === null) throw new DnsSyntaxError("MX priority must be an integer from 0 to 65535");
|
|
8236
|
+
return `${priority} ${prepareServiceHost(parts[1])}`;
|
|
8237
|
+
}
|
|
8238
|
+
function prepareServiceHost(text2) {
|
|
8239
|
+
return text2 === "." ? "." : prepareHostTarget(text2, void 0, false);
|
|
8240
|
+
}
|
|
8241
|
+
function prepareService(text2) {
|
|
8242
|
+
const parts = text2.split(/\s+/);
|
|
8243
|
+
if (parts.length !== 4) {
|
|
8244
|
+
throw new DnsSyntaxError('SRV data must be "<priority> <weight> <port> <target>"');
|
|
8245
|
+
}
|
|
8246
|
+
const [priority, weight, port] = parts.slice(0, 3).map((part) => decimalInRange(part, UINT16_MAX));
|
|
8247
|
+
if (priority === null)
|
|
8248
|
+
throw new DnsSyntaxError("SRV priority must be an integer from 0 to 65535");
|
|
8249
|
+
if (weight === null) throw new DnsSyntaxError("SRV weight must be an integer from 0 to 65535");
|
|
8250
|
+
if (port === null) throw new DnsSyntaxError("SRV port must be an integer from 0 to 65535");
|
|
8251
|
+
return `${priority} ${weight} ${port} ${prepareServiceHost(parts[3])}`;
|
|
8252
|
+
}
|
|
8253
|
+
function prepareAuthorization(text2) {
|
|
8254
|
+
const match = /^(\S+)\s+(\S+)\s+([\s\S]+)$/.exec(text2);
|
|
8255
|
+
if (!match) throw new DnsSyntaxError('CAA data must be "<flags> <tag> <value>"');
|
|
8256
|
+
const flags = decimalInRange(match[1], 255);
|
|
8257
|
+
if (flags === null) throw new DnsSyntaxError("CAA flags must be an integer from 0 to 255");
|
|
8258
|
+
if (!/^[a-z0-9]{1,15}$/i.test(match[2])) {
|
|
8259
|
+
throw new DnsSyntaxError("CAA tag must be 1 to 15 letters or digits");
|
|
8260
|
+
}
|
|
8261
|
+
const quoted = /^"((?:[^"\\]|\\.)*)"$/.exec(match[3]);
|
|
8262
|
+
const value = quoted ? quoted[1] : match[3];
|
|
8263
|
+
if (!quoted && value.includes('"')) {
|
|
8264
|
+
throw new DnsSyntaxError("CAA value must be one quoted or unquoted string");
|
|
8265
|
+
}
|
|
8266
|
+
return `${flags} ${match[2].toLowerCase()} "${value}"`;
|
|
8267
|
+
}
|
|
8268
|
+
function prepareText(text2) {
|
|
8269
|
+
if (utf8OctetCount(text2) > TEXT_TOTAL_OCTETS) {
|
|
8270
|
+
throw new DnsSyntaxError(`TXT data is longer than ${TEXT_TOTAL_OCTETS} octets`);
|
|
8271
|
+
}
|
|
8272
|
+
return text2;
|
|
8273
|
+
}
|
|
8274
|
+
function readSignerFields(text2) {
|
|
8275
|
+
const parts = text2.trim().split(/\s+/);
|
|
8276
|
+
if (parts.length !== 4)
|
|
8277
|
+
throw new DnsSyntaxError('DS data must be "<key tag> <algorithm> <digest type> <digest>"');
|
|
8278
|
+
const keyTag = decimalInRange(parts[0], UINT16_MAX);
|
|
8279
|
+
const algorithm = decimalInRange(parts[1], 255);
|
|
8280
|
+
const digestType = decimalInRange(parts[2], 255);
|
|
8281
|
+
if (keyTag === null || algorithm === null || digestType === null || !parts[3]) {
|
|
8282
|
+
throw new DnsSyntaxError('DS data must be "<key tag> <algorithm> <digest type> <digest>"');
|
|
8283
|
+
}
|
|
8284
|
+
const digest = parts[3].toLowerCase();
|
|
8285
|
+
if (!/^[0-9a-f]+$/.test(digest)) throw new DnsSyntaxError("DS digest must be hexadecimal");
|
|
8286
|
+
const expectedLength = DIGEST_HEX_LENGTH[digestType];
|
|
8287
|
+
if (expectedLength !== void 0 && digest.length !== expectedLength) {
|
|
8288
|
+
throw new DnsSyntaxError(
|
|
8289
|
+
`DS digest must be ${expectedLength} hex characters for digest type ${digestType}`
|
|
8290
|
+
);
|
|
8291
|
+
}
|
|
8292
|
+
return { keyTag, algorithm, digestType, digest };
|
|
8293
|
+
}
|
|
8294
|
+
function readDelegationSigner(rdata) {
|
|
8295
|
+
return readSignerFields(rdata);
|
|
8296
|
+
}
|
|
8297
|
+
function prepareSignerText(text2) {
|
|
8298
|
+
const signer = readSignerFields(text2);
|
|
8299
|
+
return `${signer.keyTag} ${signer.algorithm} ${signer.digestType} ${signer.digest}`;
|
|
8300
|
+
}
|
|
8301
|
+
function prepareRdata(kind, rdata, owner) {
|
|
8302
|
+
const token = kind.trim().toUpperCase();
|
|
8303
|
+
const text2 = rdata.trim();
|
|
8304
|
+
if (!text2) throw new DnsSyntaxError("Record data is empty");
|
|
8305
|
+
switch (token) {
|
|
8306
|
+
case "A":
|
|
8307
|
+
return prepareIpv4(text2);
|
|
8308
|
+
case "AAAA":
|
|
8309
|
+
return prepareIpv6(text2);
|
|
8310
|
+
case "CNAME":
|
|
8311
|
+
return prepareHostTarget(text2, owner, true);
|
|
8312
|
+
case "NS":
|
|
8313
|
+
return prepareHostTarget(text2, void 0, false);
|
|
8314
|
+
case "MX":
|
|
8315
|
+
return prepareMailExchange(text2);
|
|
8316
|
+
case "TXT":
|
|
8317
|
+
return prepareText(text2);
|
|
8318
|
+
case "SRV":
|
|
8319
|
+
return prepareService(text2);
|
|
8320
|
+
case "CAA":
|
|
8321
|
+
return prepareAuthorization(text2);
|
|
8322
|
+
case "DS":
|
|
8323
|
+
return prepareSignerText(text2);
|
|
8324
|
+
default:
|
|
8325
|
+
return text2;
|
|
8326
|
+
}
|
|
8327
|
+
}
|
|
8328
|
+
function prepareEntry(entry) {
|
|
8329
|
+
if (!Number.isInteger(entry.ttl) || entry.ttl < 0 || entry.ttl > TTL_LIMIT) {
|
|
8330
|
+
throw new DnsSyntaxError("TTL must be an integer from 0 to 2147483647");
|
|
8331
|
+
}
|
|
8332
|
+
const kind = entry.kind.trim().toUpperCase();
|
|
8333
|
+
if (!kind) throw new DnsSyntaxError("Record type is empty");
|
|
8334
|
+
return {
|
|
8335
|
+
owner: prepareName(entry.owner, "owner"),
|
|
8336
|
+
kind,
|
|
8337
|
+
ttl: entry.ttl,
|
|
8338
|
+
rdata: prepareRdata(kind, entry.rdata, entry.owner)
|
|
8339
|
+
};
|
|
8340
|
+
}
|
|
8341
|
+
function syntaxClash(entry, error) {
|
|
8342
|
+
const detail = error instanceof Error ? error.message : "Invalid record";
|
|
8343
|
+
return { owner: entry.owner, kind: entry.kind, detail };
|
|
8344
|
+
}
|
|
8345
|
+
function findCnameConflicts(entries) {
|
|
8346
|
+
const clashes = [];
|
|
8347
|
+
const byOwner = /* @__PURE__ */ new Map();
|
|
8348
|
+
for (const entry of entries) {
|
|
8349
|
+
let owner;
|
|
8350
|
+
let kind;
|
|
8351
|
+
try {
|
|
8352
|
+
owner = prepareName(entry.owner, "owner");
|
|
8353
|
+
kind = entry.kind.trim().toUpperCase();
|
|
8354
|
+
if (!kind) throw new DnsSyntaxError("Record type is empty");
|
|
8355
|
+
} catch (error) {
|
|
8356
|
+
clashes.push(syntaxClash(entry, error));
|
|
8357
|
+
continue;
|
|
8358
|
+
}
|
|
8359
|
+
const group = byOwner.get(owner) ?? [];
|
|
8360
|
+
group.push({ ...entry, owner, kind });
|
|
8361
|
+
byOwner.set(owner, group);
|
|
8362
|
+
}
|
|
8363
|
+
for (const [owner, group] of byOwner) {
|
|
8364
|
+
const aliasCount = group.filter((entry) => entry.kind === "CNAME").length;
|
|
8365
|
+
if (aliasCount === 0) continue;
|
|
8366
|
+
if (aliasCount > 1) {
|
|
8367
|
+
clashes.push({
|
|
8368
|
+
owner,
|
|
8369
|
+
kind: "CNAME",
|
|
8370
|
+
detail: "A name can have only one CNAME target"
|
|
8371
|
+
});
|
|
8372
|
+
}
|
|
8373
|
+
const others = [...new Set(group.map((entry) => entry.kind))].filter(
|
|
8374
|
+
(kind) => !KINDS_ALLOWED_BESIDE_CNAME.has(kind)
|
|
8375
|
+
);
|
|
8376
|
+
if (others.length > 0) {
|
|
8377
|
+
clashes.push({
|
|
8378
|
+
owner,
|
|
8379
|
+
kind: "CNAME",
|
|
8380
|
+
detail: `A CNAME cannot share a name with ${others.join(", ")}`
|
|
8381
|
+
});
|
|
8382
|
+
}
|
|
8383
|
+
}
|
|
8384
|
+
return clashes;
|
|
8385
|
+
}
|
|
8386
|
+
function findRepeatedRecords(entries) {
|
|
8387
|
+
const clashes = [];
|
|
8388
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8389
|
+
for (const entry of entries) {
|
|
8390
|
+
let prepared;
|
|
8391
|
+
try {
|
|
8392
|
+
prepared = prepareEntry(entry);
|
|
8393
|
+
} catch (error) {
|
|
8394
|
+
clashes.push(syntaxClash(entry, error));
|
|
8395
|
+
continue;
|
|
8396
|
+
}
|
|
8397
|
+
if (prepared.kind === "CNAME") continue;
|
|
8398
|
+
const key = `${prepared.owner}\0${prepared.kind}\0${prepared.rdata}`;
|
|
8399
|
+
if (seen.has(key)) {
|
|
8400
|
+
clashes.push({
|
|
8401
|
+
owner: prepared.owner,
|
|
8402
|
+
kind: prepared.kind,
|
|
8403
|
+
detail: `Repeated ${prepared.kind} data at this name`
|
|
8404
|
+
});
|
|
8405
|
+
}
|
|
8406
|
+
seen.add(key);
|
|
8407
|
+
}
|
|
8408
|
+
return clashes;
|
|
8409
|
+
}
|
|
8410
|
+
function toDnsEntry(record, defaultTtl = 3600) {
|
|
8411
|
+
const kind = record.type.trim().toUpperCase();
|
|
8412
|
+
const numbers = kind === "MX" ? [record.priority] : kind === "SRV" ? [record.priority, record.weight, record.port] : [];
|
|
8413
|
+
return {
|
|
8414
|
+
owner: record.name,
|
|
8415
|
+
kind,
|
|
8416
|
+
ttl: record.ttl ?? defaultTtl,
|
|
8417
|
+
rdata: [...numbers.map((n) => n === void 0 ? "" : String(n)), record.value.trim()].join(" ")
|
|
8418
|
+
};
|
|
8419
|
+
}
|
|
8420
|
+
|
|
8009
8421
|
exports.ALL_FEATURES = ALL_FEATURES;
|
|
8010
8422
|
exports.AbortError = AbortError;
|
|
8011
8423
|
exports.AuthenticationError = AuthenticationError;
|
|
@@ -8017,6 +8429,7 @@ exports.ConfigurationError = ConfigurationError;
|
|
|
8017
8429
|
exports.ConnectionError = ConnectionError;
|
|
8018
8430
|
exports.ConsentRequiredError = ConsentRequiredError;
|
|
8019
8431
|
exports.DEFAULT_OPTIONS = DEFAULT_OPTIONS;
|
|
8432
|
+
exports.DnsSyntaxError = DnsSyntaxError;
|
|
8020
8433
|
exports.Domain101Registrar = Domain101Registrar;
|
|
8021
8434
|
exports.DynadotRegistrar = DynadotRegistrar;
|
|
8022
8435
|
exports.EXTENDED_FEATURES = EXTENDED_FEATURES;
|
|
@@ -8050,19 +8463,30 @@ exports.createDomain = createDomain;
|
|
|
8050
8463
|
exports.createRegistrar = createRegistrar;
|
|
8051
8464
|
exports.ensureArray = ensureArray;
|
|
8052
8465
|
exports.filterDomains = filterDomains;
|
|
8466
|
+
exports.findCnameConflicts = findCnameConflicts;
|
|
8467
|
+
exports.findRepeatedRecords = findRepeatedRecords;
|
|
8053
8468
|
exports.isCoreFeature = isCoreFeature;
|
|
8469
|
+
exports.isIpv4 = isIpv4;
|
|
8470
|
+
exports.isIpv6 = isIpv6;
|
|
8054
8471
|
exports.listPortfolio = listPortfolio;
|
|
8055
8472
|
exports.markNotSent = markNotSent;
|
|
8056
8473
|
exports.normalizeDomain = normalizeDomain;
|
|
8057
8474
|
exports.normalizeNameservers = normalizeNameservers;
|
|
8058
8475
|
exports.parseDate = parseDate;
|
|
8059
8476
|
exports.parseXml = parseXml;
|
|
8477
|
+
exports.prepareEntry = prepareEntry;
|
|
8478
|
+
exports.prepareName = prepareName;
|
|
8479
|
+
exports.prepareRdata = prepareRdata;
|
|
8480
|
+
exports.readDelegationSigner = readDelegationSigner;
|
|
8060
8481
|
exports.registrars = registrars;
|
|
8061
8482
|
exports.requireConsent = requireConsent;
|
|
8483
|
+
exports.resetLabelMapper = resetLabelMapper;
|
|
8062
8484
|
exports.selectBaseUrl = selectBaseUrl;
|
|
8063
8485
|
exports.settableForwards = settableForwards;
|
|
8064
8486
|
exports.sleep = sleep;
|
|
8487
|
+
exports.toDnsEntry = toDnsEntry;
|
|
8065
8488
|
exports.toRegistrarError = toRegistrarError;
|
|
8489
|
+
exports.useLabelMapper = useLabelMapper;
|
|
8066
8490
|
exports.wasNotSent = wasNotSent;
|
|
8067
8491
|
//# sourceMappingURL=index.cjs.map
|
|
8068
8492
|
//# sourceMappingURL=index.cjs.map
|