polite-crawler 0.1.1 → 0.1.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 +557 -10
- package/build/browser-CKRNbIDT.mjs +62 -0
- package/build/browser-CKRNbIDT.mjs.map +1 -0
- package/build/cli.mjs +795 -121
- package/build/cli.mjs.map +1 -1
- package/build/diff-Dk2eSvi-.mjs +648 -0
- package/build/diff-Dk2eSvi-.mjs.map +1 -0
- package/build/duration-iIuMMofi.mjs +38 -0
- package/build/duration-iIuMMofi.mjs.map +1 -0
- package/build/extract-DcwNrTK7.mjs +1793 -0
- package/build/extract-DcwNrTK7.mjs.map +1 -0
- package/build/{html-nWYFVsRU.d.mts → html-BACljlkf.d.mts} +8 -9
- package/build/{html-oNpGAHwi.mjs → html-DkQUAE_d.mjs} +18 -39
- package/build/html-DkQUAE_d.mjs.map +1 -0
- package/build/html.d.mts +2 -1
- package/build/html.mjs +3 -1
- package/build/index.d.mts +94 -4
- package/build/index.mjs +13 -5
- package/build/log-Dx-wXjJl.mjs +342 -0
- package/build/log-Dx-wXjJl.mjs.map +1 -0
- package/build/mcp.d.mts +7 -4
- package/build/mcp.mjs +556 -32
- package/build/mcp.mjs.map +1 -1
- package/build/open-Ca1lcWR3.d.mts +2875 -0
- package/build/open-DnrWgkC0.mjs +3161 -0
- package/build/open-DnrWgkC0.mjs.map +1 -0
- package/build/{browser-gJ_h4AhB.mjs → render-C1CUraWF.mjs} +31 -74
- package/build/render-C1CUraWF.mjs.map +1 -0
- package/build/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/build/{open-DYZ7KL2A.mjs → runtime-k8mo1_7U.mjs} +986 -1657
- package/build/runtime-k8mo1_7U.mjs.map +1 -0
- package/build/urls-BHFeSyEK.mjs +62 -0
- package/build/urls-BHFeSyEK.mjs.map +1 -0
- package/build/urls-BTDSgDLC.d.mts +16 -0
- package/build/wacz-sfOLT8e9.mjs +275 -0
- package/build/wacz-sfOLT8e9.mjs.map +1 -0
- package/build/warc-VIPscWxY.mjs +728 -0
- package/build/warc-VIPscWxY.mjs.map +1 -0
- package/build/webbotauth-CjJdTAfE.mjs +568 -0
- package/build/webbotauth-CjJdTAfE.mjs.map +1 -0
- package/package.json +1 -1
- package/record.schema.json +15 -1
- package/build/browser-gJ_h4AhB.mjs.map +0 -1
- package/build/html-oNpGAHwi.mjs.map +0 -1
- package/build/open-DYZ7KL2A.mjs.map +0 -1
- package/build/open-eHcriTds.d.mts +0 -1499
- package/build/version-no12qgos.mjs +0 -11
- package/build/version-no12qgos.mjs.map +0 -1
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
+
import { createHash, createPrivateKey, createPublicKey, randomBytes, sign, verify } from "node:crypto";
|
|
3
|
+
//#region src/webbotauth.ts
|
|
4
|
+
var webbotauth_exports = /* @__PURE__ */ __exportAll({
|
|
5
|
+
KEY_DIRECTORY_PATH: () => KEY_DIRECTORY_PATH,
|
|
6
|
+
KEY_DIRECTORY_TYPE: () => KEY_DIRECTORY_TYPE,
|
|
7
|
+
WEB_BOT_AUTH_TAG: () => WEB_BOT_AUTH_TAG,
|
|
8
|
+
createWebBotAuthSigner: () => createWebBotAuthSigner,
|
|
9
|
+
isRequestSigner: () => isRequestSigner,
|
|
10
|
+
keyDirectory: () => keyDirectory,
|
|
11
|
+
signParts: () => signParts,
|
|
12
|
+
signatureBase: () => signatureBase,
|
|
13
|
+
signerOf: () => signerOf,
|
|
14
|
+
verifySignature: () => verifySignature
|
|
15
|
+
});
|
|
16
|
+
/**
|
|
17
|
+
* Signed requests: HTTP Message Signatures (RFC 9421), as the IETF Web Bot Auth profile tells
|
|
18
|
+
* (`draft-ietf-webbotauth-httpsig-protocol-00`, 1 September 2026). A signature lets a site verify that
|
|
19
|
+
* a request comes from the holder of a key that the operator publishes.
|
|
20
|
+
*
|
|
21
|
+
* - Each request gets three headers: `Signature-Agent`, `Signature-Input` and `Signature`.
|
|
22
|
+
* - `Signature-Agent` is a dictionary. Its member names the key directory of the operator.
|
|
23
|
+
* - The signature covers `@authority` and that member of `Signature-Agent`.
|
|
24
|
+
* - The parameters are `created`, `keyid` (the JWK thumbprint of the key), `alg="ed25519"`,
|
|
25
|
+
* `expires`, a random `nonce` and `tag="web-bot-auth"`.
|
|
26
|
+
* - The key is an Ed25519 key, and `node:crypto` signs. The package adds no dependency.
|
|
27
|
+
*
|
|
28
|
+
* A signature adds an identity. It does not replace the `User-Agent` header, which stays honest.
|
|
29
|
+
*/
|
|
30
|
+
/** The path of the key directory on the host of the operator (the well-known URI of the draft). */
|
|
31
|
+
const KEY_DIRECTORY_PATH = "/.well-known/http-message-signatures-directory";
|
|
32
|
+
/** The media type of the key directory. The host of the directory sends it as `Content-Type`. */
|
|
33
|
+
const KEY_DIRECTORY_TYPE = "application/http-message-signatures-directory+json";
|
|
34
|
+
/** The value of the `tag` parameter of a Web Bot Auth signature. */
|
|
35
|
+
const WEB_BOT_AUTH_TAG = "web-bot-auth";
|
|
36
|
+
/** A token of a structured field, for example `ed25519` in `alg=ed25519`. */
|
|
37
|
+
var Token = class {
|
|
38
|
+
value;
|
|
39
|
+
constructor(value) {
|
|
40
|
+
this.value = value;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
const isInnerList = (member) => "items" in member;
|
|
44
|
+
/** This error tells that a structured field does not parse. */
|
|
45
|
+
var FieldError = class extends Error {};
|
|
46
|
+
const KEY_START = /[a-z*]/;
|
|
47
|
+
const KEY_CHAR = /[a-z0-9_\-.*]/;
|
|
48
|
+
const TOKEN_START = /[A-Za-z*]/;
|
|
49
|
+
const TOKEN_CHAR = /[!#$%&'*+\-.^_`|~0-9A-Za-z:/]/;
|
|
50
|
+
const BASE64 = /^[A-Za-z0-9+/=]*$/;
|
|
51
|
+
/** This class reads one structured field from its text. */
|
|
52
|
+
var FieldParser = class {
|
|
53
|
+
text;
|
|
54
|
+
at = 0;
|
|
55
|
+
constructor(text) {
|
|
56
|
+
this.text = text;
|
|
57
|
+
}
|
|
58
|
+
peek() {
|
|
59
|
+
return this.text[this.at] ?? "";
|
|
60
|
+
}
|
|
61
|
+
spaces() {
|
|
62
|
+
while (this.peek() === " ") this.at += 1;
|
|
63
|
+
}
|
|
64
|
+
ows() {
|
|
65
|
+
while (this.peek() === " " || this.peek() === " ") this.at += 1;
|
|
66
|
+
}
|
|
67
|
+
dictionary() {
|
|
68
|
+
const out = /* @__PURE__ */ new Map();
|
|
69
|
+
this.spaces();
|
|
70
|
+
while (this.at < this.text.length) {
|
|
71
|
+
const key = this.key();
|
|
72
|
+
let member;
|
|
73
|
+
if (this.peek() === "=") {
|
|
74
|
+
this.at += 1;
|
|
75
|
+
member = this.peek() === "(" ? this.innerList() : this.item();
|
|
76
|
+
} else member = {
|
|
77
|
+
value: true,
|
|
78
|
+
params: this.params()
|
|
79
|
+
};
|
|
80
|
+
out.set(key, member);
|
|
81
|
+
this.ows();
|
|
82
|
+
if (this.at >= this.text.length) break;
|
|
83
|
+
if (this.peek() !== ",") throw new FieldError(`a "," is necessary at ${String(this.at)}`);
|
|
84
|
+
this.at += 1;
|
|
85
|
+
this.ows();
|
|
86
|
+
if (this.at >= this.text.length) throw new FieldError("the field ends with a comma");
|
|
87
|
+
}
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
/** This method reads one item and checks that the field has nothing after it. */
|
|
91
|
+
wholeItem() {
|
|
92
|
+
this.spaces();
|
|
93
|
+
const item = this.item();
|
|
94
|
+
this.spaces();
|
|
95
|
+
if (this.at < this.text.length) throw new FieldError("text after the item");
|
|
96
|
+
return item;
|
|
97
|
+
}
|
|
98
|
+
key() {
|
|
99
|
+
if (!KEY_START.test(this.peek())) throw new FieldError(`no key at ${String(this.at)}`);
|
|
100
|
+
const start = this.at;
|
|
101
|
+
while (KEY_CHAR.test(this.peek()) && this.at < this.text.length) this.at += 1;
|
|
102
|
+
return this.text.slice(start, this.at);
|
|
103
|
+
}
|
|
104
|
+
innerList() {
|
|
105
|
+
this.at += 1;
|
|
106
|
+
const items = [];
|
|
107
|
+
for (;;) {
|
|
108
|
+
this.spaces();
|
|
109
|
+
if (this.peek() === ")") {
|
|
110
|
+
this.at += 1;
|
|
111
|
+
return {
|
|
112
|
+
items,
|
|
113
|
+
params: this.params()
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
if (this.at >= this.text.length) throw new FieldError("an inner list has no end");
|
|
117
|
+
items.push(this.item());
|
|
118
|
+
if (this.peek() !== " " && this.peek() !== ")") throw new FieldError(`a space or ")" is necessary at ${String(this.at)}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
item() {
|
|
122
|
+
return {
|
|
123
|
+
value: this.bare(),
|
|
124
|
+
params: this.params()
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
params() {
|
|
128
|
+
const out = [];
|
|
129
|
+
while (this.peek() === ";") {
|
|
130
|
+
this.at += 1;
|
|
131
|
+
this.spaces();
|
|
132
|
+
const key = this.key();
|
|
133
|
+
let value = true;
|
|
134
|
+
if (this.peek() === "=") {
|
|
135
|
+
this.at += 1;
|
|
136
|
+
value = this.bare();
|
|
137
|
+
}
|
|
138
|
+
const known = out.findIndex(([name]) => name === key);
|
|
139
|
+
if (known === -1) out.push([key, value]);
|
|
140
|
+
else out[known] = [key, value];
|
|
141
|
+
}
|
|
142
|
+
return out;
|
|
143
|
+
}
|
|
144
|
+
bare() {
|
|
145
|
+
const char = this.peek();
|
|
146
|
+
if (char === "-" || /[0-9]/.test(char)) return this.number();
|
|
147
|
+
if (char === "\"") return this.string();
|
|
148
|
+
if (char === ":") return this.bytes();
|
|
149
|
+
if (char === "?") return this.boolean();
|
|
150
|
+
if (TOKEN_START.test(char)) return this.token();
|
|
151
|
+
throw new FieldError(`no item at ${String(this.at)}`);
|
|
152
|
+
}
|
|
153
|
+
number() {
|
|
154
|
+
const match = /^-?(\d{1,15})(\.\d{1,3})?/.exec(this.text.slice(this.at));
|
|
155
|
+
if (match === null || match[2] !== void 0 && match[1].length > 12) throw new FieldError(`not a number at ${String(this.at)}`);
|
|
156
|
+
this.at += match[0].length;
|
|
157
|
+
return Number(match[0]);
|
|
158
|
+
}
|
|
159
|
+
string() {
|
|
160
|
+
this.at += 1;
|
|
161
|
+
let out = "";
|
|
162
|
+
for (;;) {
|
|
163
|
+
const char = this.peek();
|
|
164
|
+
if (char === "") throw new FieldError("a string has no end");
|
|
165
|
+
this.at += 1;
|
|
166
|
+
if (char === "\\") {
|
|
167
|
+
const next = this.peek();
|
|
168
|
+
if (next !== "\"" && next !== "\\") throw new FieldError("a bad escape in a string");
|
|
169
|
+
out += next;
|
|
170
|
+
this.at += 1;
|
|
171
|
+
} else if (char === "\"") return out;
|
|
172
|
+
else {
|
|
173
|
+
const code = char.charCodeAt(0);
|
|
174
|
+
if (code < 32 || code > 126) throw new FieldError("a string holds a character that is not printable ASCII");
|
|
175
|
+
out += char;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
bytes() {
|
|
180
|
+
this.at += 1;
|
|
181
|
+
const end = this.text.indexOf(":", this.at);
|
|
182
|
+
if (end === -1) throw new FieldError("a byte sequence has no end");
|
|
183
|
+
const text = this.text.slice(this.at, end);
|
|
184
|
+
if (!BASE64.test(text)) throw new FieldError("a byte sequence is not base64");
|
|
185
|
+
this.at = end + 1;
|
|
186
|
+
return new Uint8Array(Buffer.from(text, "base64"));
|
|
187
|
+
}
|
|
188
|
+
boolean() {
|
|
189
|
+
const value = this.text.slice(this.at, this.at + 2);
|
|
190
|
+
if (value !== "?1" && value !== "?0") throw new FieldError("not a boolean");
|
|
191
|
+
this.at += 2;
|
|
192
|
+
return value === "?1";
|
|
193
|
+
}
|
|
194
|
+
token() {
|
|
195
|
+
const start = this.at;
|
|
196
|
+
this.at += 1;
|
|
197
|
+
while (TOKEN_CHAR.test(this.peek()) && this.at < this.text.length) this.at += 1;
|
|
198
|
+
return new Token(this.text.slice(start, this.at));
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
const serializeBare = (value) => {
|
|
202
|
+
if (value instanceof Token) return value.value;
|
|
203
|
+
if (value instanceof Uint8Array) return `:${Buffer.from(value).toString("base64")}:`;
|
|
204
|
+
if (typeof value === "boolean") return value ? "?1" : "?0";
|
|
205
|
+
if (typeof value === "number") return Number.isInteger(value) ? String(value) : String(Math.round(value * 1e3) / 1e3);
|
|
206
|
+
return `"${value.replace(/["\\]/g, (char) => `\\${char}`)}"`;
|
|
207
|
+
};
|
|
208
|
+
const serializeParams = (params) => params.map(([key, value]) => value === true ? `;${key}` : `;${key}=${serializeBare(value)}`).join("");
|
|
209
|
+
const serializeItem = (item) => `${serializeBare(item.value)}${serializeParams(item.params)}`;
|
|
210
|
+
const serializeMember = (member) => isInnerList(member) ? `(${member.items.map(serializeItem).join(" ")})${serializeParams(member.params)}` : serializeItem(member);
|
|
211
|
+
/** This function tells if a text is a key of a structured field: a label of a signature, for example. */
|
|
212
|
+
const isKey = (text) => /^[a-z*][a-z0-9_\-.*]*$/.test(text);
|
|
213
|
+
const base64url = (bytes) => Buffer.from(bytes).toString("base64url");
|
|
214
|
+
const isKeyObject = (key) => typeof key === "object" && key !== null && "asymmetricKeyType" in key && "export" in key;
|
|
215
|
+
/** This function reads a private key. Its errors do not hold the key. */
|
|
216
|
+
const privateKeyOf = (input) => {
|
|
217
|
+
let key;
|
|
218
|
+
try {
|
|
219
|
+
if (isKeyObject(input)) key = input;
|
|
220
|
+
else if (typeof input === "string") key = input.trim().startsWith("{") ? createPrivateKey({
|
|
221
|
+
key: JSON.parse(input),
|
|
222
|
+
format: "jwk"
|
|
223
|
+
}) : createPrivateKey(input);
|
|
224
|
+
else if (input instanceof Uint8Array) key = createPrivateKey({
|
|
225
|
+
key: Buffer.from(input),
|
|
226
|
+
format: "der",
|
|
227
|
+
type: "pkcs8"
|
|
228
|
+
});
|
|
229
|
+
else key = createPrivateKey({
|
|
230
|
+
key: input,
|
|
231
|
+
format: "jwk"
|
|
232
|
+
});
|
|
233
|
+
} catch (cause) {
|
|
234
|
+
throw new Error("the private key of the signer does not parse: give an Ed25519 key as PKCS#8 (PEM or DER) or as a JWK", { cause });
|
|
235
|
+
}
|
|
236
|
+
if (key.type !== "private") throw new Error("the key of the signer is not a private key: give the Ed25519 private key");
|
|
237
|
+
if (key.asymmetricKeyType !== "ed25519") throw new Error(`the key of the signer is ${key.asymmetricKeyType ?? "not an asymmetric key"}, not ed25519: Web Bot Auth here uses Ed25519 keys`);
|
|
238
|
+
return key;
|
|
239
|
+
};
|
|
240
|
+
/** This function reads a public key, or the public part of a private key. Its errors do not hold the key. */
|
|
241
|
+
const publicKeyOf = (input) => {
|
|
242
|
+
let key;
|
|
243
|
+
try {
|
|
244
|
+
if (isKeyObject(input)) key = input.type === "private" ? createPublicKey(input) : input;
|
|
245
|
+
else if (typeof input === "string") key = input.trim().startsWith("{") ? createPublicKey({
|
|
246
|
+
key: JSON.parse(input),
|
|
247
|
+
format: "jwk"
|
|
248
|
+
}) : createPublicKey(input);
|
|
249
|
+
else if (input instanceof Uint8Array) {
|
|
250
|
+
const bytes = Buffer.from(input);
|
|
251
|
+
try {
|
|
252
|
+
key = createPublicKey({
|
|
253
|
+
key: bytes,
|
|
254
|
+
format: "der",
|
|
255
|
+
type: "spki"
|
|
256
|
+
});
|
|
257
|
+
} catch {
|
|
258
|
+
key = createPublicKey(createPrivateKey({
|
|
259
|
+
key: bytes,
|
|
260
|
+
format: "der",
|
|
261
|
+
type: "pkcs8"
|
|
262
|
+
}));
|
|
263
|
+
}
|
|
264
|
+
} else key = createPublicKey({
|
|
265
|
+
key: input,
|
|
266
|
+
format: "jwk"
|
|
267
|
+
});
|
|
268
|
+
} catch (cause) {
|
|
269
|
+
throw new Error("a key of the directory does not parse: give an Ed25519 key", { cause });
|
|
270
|
+
}
|
|
271
|
+
if (key.asymmetricKeyType !== "ed25519") throw new Error(`a key of the directory is ${key.asymmetricKeyType ?? "not an asymmetric key"}, not ed25519`);
|
|
272
|
+
return key;
|
|
273
|
+
};
|
|
274
|
+
/** This function gives the JWK thumbprint of an Ed25519 public key (RFC 7638, RFC 8037 appendix A.3). */
|
|
275
|
+
const thumbprintOf = (x) => base64url(createHash("sha256").update(JSON.stringify({
|
|
276
|
+
crv: "Ed25519",
|
|
277
|
+
kty: "OKP",
|
|
278
|
+
x
|
|
279
|
+
}), "utf8").digest());
|
|
280
|
+
const directoryKeyOf = (key) => {
|
|
281
|
+
const jwk = key.export({ format: "jwk" });
|
|
282
|
+
const x = typeof jwk.x === "string" ? jwk.x : "";
|
|
283
|
+
return {
|
|
284
|
+
kty: "OKP",
|
|
285
|
+
crv: "Ed25519",
|
|
286
|
+
kid: thumbprintOf(x),
|
|
287
|
+
x,
|
|
288
|
+
use: "sig"
|
|
289
|
+
};
|
|
290
|
+
};
|
|
291
|
+
/**
|
|
292
|
+
* This function makes the key directory for the operator to host: a JSON Web Key Set with each
|
|
293
|
+
* public key and its thumbprint as `kid`. It takes public keys, or private keys, and it writes only
|
|
294
|
+
* their public parts. Serve the JSON at `KEY_DIRECTORY_PATH` with the type `KEY_DIRECTORY_TYPE`.
|
|
295
|
+
*/
|
|
296
|
+
const keyDirectory = (publicKeys) => {
|
|
297
|
+
const keys = /* @__PURE__ */ new Map();
|
|
298
|
+
for (const input of publicKeys) {
|
|
299
|
+
const key = directoryKeyOf(publicKeyOf(input));
|
|
300
|
+
keys.set(key.kid, key);
|
|
301
|
+
}
|
|
302
|
+
return { keys: [...keys.values()] };
|
|
303
|
+
};
|
|
304
|
+
/** This function gives the `@signature-params` value: the covered components and the parameters, in the order of the draft. */
|
|
305
|
+
const signatureParams = (parts) => `("@authority" "signature-agent";key=${serializeBare(parts.agentKey)});created=${String(parts.created)};keyid=${serializeBare(parts.keyId)};alg="ed25519";expires=${String(parts.expires)}${parts.nonce === void 0 ? "" : `;nonce=${serializeBare(parts.nonce)}`};tag="${WEB_BOT_AUTH_TAG}"`;
|
|
306
|
+
/** This function gives the signature base of RFC 9421, section 2.5, for the components of Web Bot Auth. */
|
|
307
|
+
const signatureBase = (parts) => [
|
|
308
|
+
`"@authority": ${parts.authority}`,
|
|
309
|
+
`"signature-agent";key=${serializeBare(parts.agentKey)}: ${parts.agentValue}`,
|
|
310
|
+
`"@signature-params": ${signatureParams(parts)}`
|
|
311
|
+
].join("\n");
|
|
312
|
+
/** This function signs the parts with an Ed25519 key, and gives the three headers. */
|
|
313
|
+
const signParts = (parts, key) => {
|
|
314
|
+
const signature = sign(null, Buffer.from(signatureBase(parts), "utf8"), key);
|
|
315
|
+
return {
|
|
316
|
+
"signature-agent": `${parts.agentKey}=${parts.agentValue}`,
|
|
317
|
+
"signature-input": `${parts.label}=${signatureParams(parts)}`,
|
|
318
|
+
signature: `${parts.label}=:${signature.toString("base64")}:`
|
|
319
|
+
};
|
|
320
|
+
};
|
|
321
|
+
const DEFAULT_LIFETIME_MS = 3e5;
|
|
322
|
+
const MAX_LIFETIME_MS = 864e5;
|
|
323
|
+
/** This function checks the URL of `Signature-Agent`, and gives the URL and the serialized value of the member. */
|
|
324
|
+
const agentOf = (url, type) => {
|
|
325
|
+
let target;
|
|
326
|
+
try {
|
|
327
|
+
target = new URL(url);
|
|
328
|
+
} catch {
|
|
329
|
+
throw new Error(`signatureAgent "${url}" is not a URL: give the https URL of the key directory`);
|
|
330
|
+
}
|
|
331
|
+
if (target.protocol !== "https:") throw new Error(`signatureAgent "${url}" is not an https URL: the draft permits only https`);
|
|
332
|
+
if (target.username !== "" || target.password !== "" || target.hash !== "") throw new Error(`signatureAgent "${url}" has a user name, a password or a fragment`);
|
|
333
|
+
if (type === "directory") {
|
|
334
|
+
if (target.pathname !== "/" || target.search !== "") throw new Error(`signatureAgent "${url}" is not an origin: the type directory takes only an origin, such as https://bot.example.com. For a key set on a path, set agentType to jwks_uri`);
|
|
335
|
+
return {
|
|
336
|
+
url: target.origin,
|
|
337
|
+
value: serializeBare(target.origin)
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
url: target.href,
|
|
342
|
+
value: `${serializeBare(target.href)};type=${type}`
|
|
343
|
+
};
|
|
344
|
+
};
|
|
345
|
+
/**
|
|
346
|
+
* This function makes a signer for Web Bot Auth with an Ed25519 key. Each signature covers
|
|
347
|
+
* `@authority` and the member of `Signature-Agent`. It has a new random nonce, and it expires after
|
|
348
|
+
* `lifetimeMs`. The signer keeps the private key in a closure: no property of the signer holds it.
|
|
349
|
+
*/
|
|
350
|
+
const createWebBotAuthSigner = (options) => {
|
|
351
|
+
const key = privateKeyOf(options.privateKey);
|
|
352
|
+
const publicKey = directoryKeyOf(createPublicKey(key));
|
|
353
|
+
if (options.keyId !== void 0 && options.keyId !== publicKey.kid) throw new Error(`keyId "${options.keyId}" is not the JWK thumbprint of the key (${publicKey.kid}): Web Bot Auth uses the thumbprint as keyid`);
|
|
354
|
+
const agent = agentOf(options.signatureAgent, options.agentType ?? "directory");
|
|
355
|
+
const label = options.label ?? "sig1";
|
|
356
|
+
if (!isKey(label)) throw new Error(`label "${label}" is not a key of a structured field: use lowercase letters, digits, "_", "-", "." and "*"`);
|
|
357
|
+
const lifetimeMs = options.lifetimeMs ?? DEFAULT_LIFETIME_MS;
|
|
358
|
+
if (!Number.isFinite(lifetimeMs) || lifetimeMs < 1e3 || lifetimeMs > MAX_LIFETIME_MS) throw new RangeError(`lifetimeMs must be from 1000 (1 second) to ${String(MAX_LIFETIME_MS)} (24 hours), not ${String(lifetimeMs)}`);
|
|
359
|
+
const { kty, crv, x } = publicKey;
|
|
360
|
+
return {
|
|
361
|
+
keyId: publicKey.kid,
|
|
362
|
+
signatureAgent: agent.url,
|
|
363
|
+
publicKey: {
|
|
364
|
+
kty,
|
|
365
|
+
crv,
|
|
366
|
+
x
|
|
367
|
+
},
|
|
368
|
+
sign(url, now) {
|
|
369
|
+
const created = Math.floor(now / 1e3);
|
|
370
|
+
return signParts({
|
|
371
|
+
authority: url.host.toLowerCase(),
|
|
372
|
+
label,
|
|
373
|
+
agentKey: label,
|
|
374
|
+
agentValue: agent.value,
|
|
375
|
+
keyId: publicKey.kid,
|
|
376
|
+
created,
|
|
377
|
+
expires: created + Math.ceil(lifetimeMs / 1e3),
|
|
378
|
+
nonce: randomBytes(32).toString("base64")
|
|
379
|
+
}, key);
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
};
|
|
383
|
+
/** This function tells if a value is a request signer, not the options of one. */
|
|
384
|
+
const isRequestSigner = (value) => typeof value.sign === "function";
|
|
385
|
+
/** This function gives a request signer: the signer itself, or a new signer from its options. */
|
|
386
|
+
const signerOf = (value) => value === void 0 ? void 0 : isRequestSigner(value) ? value : createWebBotAuthSigner(value);
|
|
387
|
+
/** A signature can start this long before the clock of the verifier, for a small difference of the clocks. */
|
|
388
|
+
const CLOCK_SKEW_S = 60;
|
|
389
|
+
const headerLines = (headers, name) => {
|
|
390
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
391
|
+
if (key.toLowerCase() !== name || value === void 0) continue;
|
|
392
|
+
return typeof value === "string" ? [value] : value;
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
/** This function gives the value of a header as RFC 9421 section 2.1 combines it: each line without its outer spaces, with ", " between. */
|
|
396
|
+
const fieldValue = (headers, name) => headerLines(headers, name)?.map((line) => line.replace(/\r?\n[ \t]+/g, " ").trim()).join(", ");
|
|
397
|
+
/** This function gives the value of one component of a request, or an error text. */
|
|
398
|
+
const componentValue = (component, request, target) => {
|
|
399
|
+
const name = component.value;
|
|
400
|
+
if (typeof name !== "string") return { error: "a component is not a string" };
|
|
401
|
+
const params = new Map(component.params);
|
|
402
|
+
if (name.startsWith("@")) {
|
|
403
|
+
if (params.size > 0) return { error: `the parameters of ${name} are not supported` };
|
|
404
|
+
switch (name) {
|
|
405
|
+
case "@method": return { value: (request.method ?? "GET").toUpperCase() };
|
|
406
|
+
case "@authority": return { value: target.host.toLowerCase() };
|
|
407
|
+
case "@scheme": return { value: target.protocol.slice(0, -1).toLowerCase() };
|
|
408
|
+
case "@target-uri": return { value: target.href };
|
|
409
|
+
case "@path": return { value: target.pathname === "" ? "/" : target.pathname };
|
|
410
|
+
case "@query": return { value: target.search === "" ? "?" : target.search };
|
|
411
|
+
case "@request-target": return { value: `${target.pathname}${target.search}` };
|
|
412
|
+
default: return { error: `the component ${name} is not supported` };
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
const value = fieldValue(request.headers, name);
|
|
416
|
+
if (value === void 0) return { error: `the request has no ${name} header` };
|
|
417
|
+
if ([...params.keys()].some((param) => param !== "key")) return { error: `the parameters of ${name} are not supported` };
|
|
418
|
+
const key = params.get("key");
|
|
419
|
+
if (key === void 0) return { value };
|
|
420
|
+
if (typeof key !== "string") return { error: `the key of ${name} is not a string` };
|
|
421
|
+
let member;
|
|
422
|
+
try {
|
|
423
|
+
member = new FieldParser(value).dictionary().get(key);
|
|
424
|
+
} catch (error) {
|
|
425
|
+
return { error: `the ${name} header is not a dictionary: ${error.message}` };
|
|
426
|
+
}
|
|
427
|
+
if (member === void 0) return { error: `the ${name} header has no member ${key}` };
|
|
428
|
+
return { value: serializeMember(member) };
|
|
429
|
+
};
|
|
430
|
+
const failed = (reason, facts = {}) => ({
|
|
431
|
+
verified: false,
|
|
432
|
+
label: facts.label ?? null,
|
|
433
|
+
keyId: facts.keyId ?? null,
|
|
434
|
+
agent: facts.agent ?? null,
|
|
435
|
+
reason
|
|
436
|
+
});
|
|
437
|
+
/**
|
|
438
|
+
* This function verifies the Web Bot Auth signatures of a request with the keys of a directory. It
|
|
439
|
+
* applies RFC 9421 section 3.2 and the profile of the draft. The profile asks for the tag
|
|
440
|
+
* `web-bot-auth`, `created`, `expires` and `@authority`. It also asks for the member of
|
|
441
|
+
* `Signature-Agent`, and a key with the thumbprint of `keyid`. The tests use it, and an operator can check a setup with it. It sends no request.
|
|
442
|
+
*/
|
|
443
|
+
const verifySignature = (request, directory, options = {}) => {
|
|
444
|
+
let target;
|
|
445
|
+
try {
|
|
446
|
+
target = new URL(request.url);
|
|
447
|
+
} catch {
|
|
448
|
+
return failed("the URL of the request is not a URL");
|
|
449
|
+
}
|
|
450
|
+
const inputText = fieldValue(request.headers, "signature-input");
|
|
451
|
+
const signatureText = fieldValue(request.headers, "signature");
|
|
452
|
+
if (inputText === void 0 || signatureText === void 0) return failed("the request has no Signature-Input or no Signature header");
|
|
453
|
+
let inputs;
|
|
454
|
+
let signatures;
|
|
455
|
+
try {
|
|
456
|
+
inputs = new FieldParser(inputText).dictionary();
|
|
457
|
+
signatures = new FieldParser(signatureText).dictionary();
|
|
458
|
+
} catch (error) {
|
|
459
|
+
return failed(`a signature header does not parse: ${error.message}`);
|
|
460
|
+
}
|
|
461
|
+
const now = Math.floor((options.now ?? Date.now()) / 1e3);
|
|
462
|
+
const keys = /* @__PURE__ */ new Map();
|
|
463
|
+
for (const entry of directory.keys) try {
|
|
464
|
+
const key = createPublicKey({
|
|
465
|
+
key: {
|
|
466
|
+
kty: entry.kty,
|
|
467
|
+
crv: entry.crv,
|
|
468
|
+
x: entry.x
|
|
469
|
+
},
|
|
470
|
+
format: "jwk"
|
|
471
|
+
});
|
|
472
|
+
keys.set(thumbprintOf(entry.x), key);
|
|
473
|
+
} catch {}
|
|
474
|
+
let last = failed("the request has no Web Bot Auth signature");
|
|
475
|
+
for (const [label, input] of inputs) {
|
|
476
|
+
if (!isInnerList(input)) continue;
|
|
477
|
+
const params = new Map(input.params);
|
|
478
|
+
if (params.get("tag") !== "web-bot-auth") continue;
|
|
479
|
+
const keyId = typeof params.get("keyid") === "string" ? params.get("keyid") : null;
|
|
480
|
+
const covered = input.items;
|
|
481
|
+
const names = covered.map((item) => serializeItem(item));
|
|
482
|
+
const agentItem = covered.find((item) => item.value === "signature-agent");
|
|
483
|
+
let agent = null;
|
|
484
|
+
if (agentItem !== void 0) {
|
|
485
|
+
const value = componentValue(agentItem, request, target);
|
|
486
|
+
if ("value" in value) try {
|
|
487
|
+
const member = new FieldParser(value.value).wholeItem().value;
|
|
488
|
+
agent = typeof member === "string" ? member : null;
|
|
489
|
+
} catch {
|
|
490
|
+
agent = null;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
const facts = {
|
|
494
|
+
label,
|
|
495
|
+
keyId,
|
|
496
|
+
agent
|
|
497
|
+
};
|
|
498
|
+
if (new Set(names).size !== names.length) {
|
|
499
|
+
last = failed("a component is covered two times", facts);
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
if (!names.includes("\"@authority\"") && !names.includes("\"@target-uri\"")) {
|
|
503
|
+
last = failed("the signature does not cover @authority or @target-uri", facts);
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
if (agentItem === void 0) {
|
|
507
|
+
last = failed("the signature does not cover Signature-Agent", facts);
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
const created = params.get("created");
|
|
511
|
+
const expires = params.get("expires");
|
|
512
|
+
if (typeof created !== "number" || typeof expires !== "number") {
|
|
513
|
+
last = failed("the signature has no created or no expires", facts);
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
516
|
+
if (expires <= now) {
|
|
517
|
+
last = failed("the signature expired", facts);
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
if (created > now + CLOCK_SKEW_S) {
|
|
521
|
+
last = failed("the signature was created in the future", facts);
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
const alg = params.get("alg");
|
|
525
|
+
if (alg !== void 0 && alg !== "ed25519") {
|
|
526
|
+
last = failed(`the algorithm ${serializeBare(alg)} is not ed25519`, facts);
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
const key = keyId === null ? void 0 : keys.get(keyId);
|
|
530
|
+
if (key === void 0) {
|
|
531
|
+
last = failed("no key of the directory has the thumbprint of keyid", facts);
|
|
532
|
+
continue;
|
|
533
|
+
}
|
|
534
|
+
const lines = [];
|
|
535
|
+
let error;
|
|
536
|
+
for (const item of covered) {
|
|
537
|
+
const value = componentValue(item, request, target);
|
|
538
|
+
if ("error" in value) {
|
|
539
|
+
error = value.error;
|
|
540
|
+
break;
|
|
541
|
+
}
|
|
542
|
+
lines.push(`${serializeItem(item)}: ${value.value}`);
|
|
543
|
+
}
|
|
544
|
+
if (error !== void 0) {
|
|
545
|
+
last = failed(error, facts);
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
lines.push(`"@signature-params": ${serializeMember(input)}`);
|
|
549
|
+
const signature = signatures.get(label);
|
|
550
|
+
if (signature === void 0 || isInnerList(signature) || !(signature.value instanceof Uint8Array)) {
|
|
551
|
+
last = failed(`the Signature header has no byte sequence for ${label}`, facts);
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
if (verify(null, Buffer.from(lines.join("\n"), "utf8"), key, signature.value)) return {
|
|
555
|
+
verified: true,
|
|
556
|
+
label,
|
|
557
|
+
keyId,
|
|
558
|
+
agent,
|
|
559
|
+
reason: null
|
|
560
|
+
};
|
|
561
|
+
last = failed("the signature does not match the signature base", facts);
|
|
562
|
+
}
|
|
563
|
+
return last;
|
|
564
|
+
};
|
|
565
|
+
//#endregion
|
|
566
|
+
export { signerOf as a, keyDirectory as i, KEY_DIRECTORY_TYPE as n, verifySignature as o, createWebBotAuthSigner as r, webbotauth_exports as s, KEY_DIRECTORY_PATH as t };
|
|
567
|
+
|
|
568
|
+
//# sourceMappingURL=webbotauth-CjJdTAfE.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webbotauth-CjJdTAfE.mjs","names":["signBytes","verifyBytes"],"sources":["../src/webbotauth.ts"],"sourcesContent":["import type { JsonWebKey, KeyObject } from \"node:crypto\";\nimport {\n createHash,\n createPrivateKey,\n createPublicKey,\n randomBytes,\n sign as signBytes,\n verify as verifyBytes,\n} from \"node:crypto\";\n\n/**\n * Signed requests: HTTP Message Signatures (RFC 9421), as the IETF Web Bot Auth profile tells\n * (`draft-ietf-webbotauth-httpsig-protocol-00`, 1 September 2026). A signature lets a site verify that\n * a request comes from the holder of a key that the operator publishes.\n *\n * - Each request gets three headers: `Signature-Agent`, `Signature-Input` and `Signature`.\n * - `Signature-Agent` is a dictionary. Its member names the key directory of the operator.\n * - The signature covers `@authority` and that member of `Signature-Agent`.\n * - The parameters are `created`, `keyid` (the JWK thumbprint of the key), `alg=\"ed25519\"`,\n * `expires`, a random `nonce` and `tag=\"web-bot-auth\"`.\n * - The key is an Ed25519 key, and `node:crypto` signs. The package adds no dependency.\n *\n * A signature adds an identity. It does not replace the `User-Agent` header, which stays honest.\n */\n\n/** The path of the key directory on the host of the operator (the well-known URI of the draft). */\nexport const KEY_DIRECTORY_PATH = \"/.well-known/http-message-signatures-directory\";\n\n/** The media type of the key directory. The host of the directory sends it as `Content-Type`. */\nexport const KEY_DIRECTORY_TYPE = \"application/http-message-signatures-directory+json\";\n\n/** The value of the `tag` parameter of a Web Bot Auth signature. */\nexport const WEB_BOT_AUTH_TAG = \"web-bot-auth\";\n\n/**\n * This value tells how a site finds the keys from the URL of `Signature-Agent`:\n *\n * - `directory` (the default): the URL is an origin, and the keys are at `KEY_DIRECTORY_PATH` on it.\n * - `jwks_uri`: the URL is the address of a JSON Web Key Set, on any path.\n */\nexport type SignatureAgentType = \"directory\" | \"jwks_uri\";\n\n/** A key in one of the forms that `node:crypto` reads: a `KeyObject`, PEM text, DER bytes (PKCS#8 or SPKI) or a JWK. */\nexport type KeyInput = KeyObject | string | Uint8Array | JsonWebKey;\n\n/** These options make a signer for Web Bot Auth. */\nexport interface WebBotAuthOptions {\n /** This key signs. It is an Ed25519 private key: a `KeyObject`, PKCS#8 (PEM text or DER bytes) or a JWK with `d`. */\n readonly privateKey: KeyInput;\n /**\n * This text is the `keyid` of each signature: the JWK thumbprint of the key (RFC 8037). The signer\n * calculates it. A value that is not the thumbprint of the key throws an error.\n */\n readonly keyId?: string | undefined;\n /**\n * This URL tells a site where the keys are. For the type `directory`, it is an https origin, for\n * example `https://bot.example.com`. For `jwks_uri`, it is the https URL of a JSON Web Key Set.\n */\n readonly signatureAgent: string;\n /** This value tells how a site finds the keys from `signatureAgent`. The default is `directory`. */\n readonly agentType?: SignatureAgentType | undefined;\n /** This value is the time that a signature stays valid, in milliseconds. The default is 5 minutes, and the maximum is 24 hours. */\n readonly lifetimeMs?: number | undefined;\n /** This text is the label of the signature, and the key of the member of `Signature-Agent`. The default is `sig1`. */\n readonly label?: string | undefined;\n}\n\n/**\n * A request signer gives the signature headers of one request. The crawler uses it for each\n * request, robots.txt too. `createWebBotAuthSigner` makes one. A signer of your own can sign with a\n * key in a hardware module, for example.\n */\nexport interface RequestSigner {\n /** This text is the `keyid` of the signatures. */\n readonly keyId: string;\n /** This URL is the value of the member of `Signature-Agent`: where a site finds the keys. */\n readonly signatureAgent: string;\n /** This method gives the headers that sign a request to `url` at the time `now` (milliseconds since the epoch). */\n sign(url: URL, now: number): Readonly<Record<string, string>>;\n}\n\n/** The signer that `createWebBotAuthSigner` makes. It also gives its public key for the key directory. */\nexport interface WebBotAuthSigner extends RequestSigner {\n /** This JWK is the public key of the signer, with no private part. Give it to `keyDirectory`. */\n readonly publicKey: JsonWebKey;\n}\n\n/** One key of a key directory: an Ed25519 public key as a JWK, with its thumbprint as `kid`. */\nexport interface DirectoryKey {\n readonly kty: \"OKP\";\n readonly crv: \"Ed25519\";\n readonly kid: string;\n readonly x: string;\n readonly use: \"sig\";\n}\n\n/** A key directory: a JSON Web Key Set. The operator serves it as JSON at `KEY_DIRECTORY_PATH`, with the type `KEY_DIRECTORY_TYPE`. */\nexport interface KeyDirectory {\n readonly keys: readonly DirectoryKey[];\n}\n\n/** The parts of a request that `verifySignature` reads. */\nexport interface SignedRequest {\n readonly url: string;\n /** The default is `GET`. */\n readonly method?: string | undefined;\n /** These are the headers of the request. A name can be in any case, and a header can have more than one line. */\n readonly headers: Readonly<Record<string, string | readonly string[] | undefined>>;\n}\n\n/** The result of `verifySignature`. */\nexport interface SignatureCheck {\n /** This value is true when a Web Bot Auth signature of the request verifies with a key of the directory. */\n readonly verified: boolean;\n /** This text is the label of the signature that the check examined last, or null. */\n readonly label: string | null;\n readonly keyId: string | null;\n /** This URL is the member of `Signature-Agent` that the signature covers, or null. */\n readonly agent: string | null;\n /** This text tells why the request does not verify. It is null when it verifies. */\n readonly reason: string | null;\n}\n\n// ── structured fields (RFC 9651): the parts that the signature headers use ──\n\n/** A token of a structured field, for example `ed25519` in `alg=ed25519`. */\nclass Token {\n constructor(readonly value: string) {}\n}\n\ntype BareItem = string | number | boolean | Uint8Array | Token;\ntype Params = ReadonlyArray<readonly [string, BareItem]>;\n\ninterface Item {\n readonly value: BareItem;\n readonly params: Params;\n}\n\ninterface InnerList {\n readonly items: readonly Item[];\n readonly params: Params;\n}\n\ntype Member = Item | InnerList;\n\nconst isInnerList = (member: Member): member is InnerList => \"items\" in member;\n\n/** This error tells that a structured field does not parse. */\nclass FieldError extends Error {}\n\nconst KEY_START = /[a-z*]/;\nconst KEY_CHAR = /[a-z0-9_\\-.*]/;\nconst TOKEN_START = /[A-Za-z*]/;\nconst TOKEN_CHAR = /[!#$%&'*+\\-.^_`|~0-9A-Za-z:/]/;\nconst BASE64 = /^[A-Za-z0-9+/=]*$/;\n\n/** This class reads one structured field from its text. */\nclass FieldParser {\n private at = 0;\n constructor(private readonly text: string) {}\n\n private peek(): string {\n return this.text[this.at] ?? \"\";\n }\n\n private spaces(): void {\n while (this.peek() === \" \") this.at += 1;\n }\n\n private ows(): void {\n while (this.peek() === \" \" || this.peek() === \"\\t\") this.at += 1;\n }\n\n dictionary(): Map<string, Member> {\n const out = new Map<string, Member>();\n this.spaces();\n while (this.at < this.text.length) {\n const key = this.key();\n let member: Member;\n if (this.peek() === \"=\") {\n this.at += 1;\n member = this.peek() === \"(\" ? this.innerList() : this.item();\n } else {\n member = { value: true, params: this.params() };\n }\n out.set(key, member);\n this.ows();\n if (this.at >= this.text.length) break;\n if (this.peek() !== \",\") throw new FieldError(`a \",\" is necessary at ${String(this.at)}`);\n this.at += 1;\n this.ows();\n if (this.at >= this.text.length) throw new FieldError(\"the field ends with a comma\");\n }\n return out;\n }\n\n /** This method reads one item and checks that the field has nothing after it. */\n wholeItem(): Item {\n this.spaces();\n const item = this.item();\n this.spaces();\n if (this.at < this.text.length) throw new FieldError(\"text after the item\");\n return item;\n }\n\n private key(): string {\n if (!KEY_START.test(this.peek())) throw new FieldError(`no key at ${String(this.at)}`);\n const start = this.at;\n while (KEY_CHAR.test(this.peek()) && this.at < this.text.length) this.at += 1;\n return this.text.slice(start, this.at);\n }\n\n private innerList(): InnerList {\n this.at += 1;\n const items: Item[] = [];\n for (;;) {\n this.spaces();\n if (this.peek() === \")\") {\n this.at += 1;\n return { items, params: this.params() };\n }\n if (this.at >= this.text.length) throw new FieldError(\"an inner list has no end\");\n items.push(this.item());\n if (this.peek() !== \" \" && this.peek() !== \")\")\n throw new FieldError(`a space or \")\" is necessary at ${String(this.at)}`);\n }\n }\n\n private item(): Item {\n const value = this.bare();\n return { value, params: this.params() };\n }\n\n private params(): Params {\n const out: Array<readonly [string, BareItem]> = [];\n while (this.peek() === \";\") {\n this.at += 1;\n this.spaces();\n const key = this.key();\n let value: BareItem = true;\n if (this.peek() === \"=\") {\n this.at += 1;\n value = this.bare();\n }\n const known = out.findIndex(([name]) => name === key);\n if (known === -1) out.push([key, value]);\n else out[known] = [key, value];\n }\n return out;\n }\n\n private bare(): BareItem {\n const char = this.peek();\n if (char === \"-\" || /[0-9]/.test(char)) return this.number();\n if (char === '\"') return this.string();\n if (char === \":\") return this.bytes();\n if (char === \"?\") return this.boolean();\n if (TOKEN_START.test(char)) return this.token();\n throw new FieldError(`no item at ${String(this.at)}`);\n }\n\n private number(): number {\n const match = /^-?(\\d{1,15})(\\.\\d{1,3})?/.exec(this.text.slice(this.at));\n if (match === null || (match[2] !== undefined && match[1]!.length > 12))\n throw new FieldError(`not a number at ${String(this.at)}`);\n this.at += match[0].length;\n return Number(match[0]);\n }\n\n private string(): string {\n this.at += 1;\n let out = \"\";\n for (;;) {\n const char = this.peek();\n if (char === \"\") throw new FieldError(\"a string has no end\");\n this.at += 1;\n if (char === \"\\\\\") {\n const next = this.peek();\n if (next !== '\"' && next !== \"\\\\\") throw new FieldError(\"a bad escape in a string\");\n out += next;\n this.at += 1;\n } else if (char === '\"') {\n return out;\n } else {\n const code = char.charCodeAt(0);\n if (code < 0x20 || code > 0x7e)\n throw new FieldError(\"a string holds a character that is not printable ASCII\");\n out += char;\n }\n }\n }\n\n private bytes(): Uint8Array {\n this.at += 1;\n const end = this.text.indexOf(\":\", this.at);\n if (end === -1) throw new FieldError(\"a byte sequence has no end\");\n const text = this.text.slice(this.at, end);\n if (!BASE64.test(text)) throw new FieldError(\"a byte sequence is not base64\");\n this.at = end + 1;\n return new Uint8Array(Buffer.from(text, \"base64\"));\n }\n\n private boolean(): boolean {\n const value = this.text.slice(this.at, this.at + 2);\n if (value !== \"?1\" && value !== \"?0\") throw new FieldError(\"not a boolean\");\n this.at += 2;\n return value === \"?1\";\n }\n\n private token(): Token {\n const start = this.at;\n this.at += 1;\n while (TOKEN_CHAR.test(this.peek()) && this.at < this.text.length) this.at += 1;\n return new Token(this.text.slice(start, this.at));\n }\n}\n\nconst serializeBare = (value: BareItem): string => {\n if (value instanceof Token) return value.value;\n if (value instanceof Uint8Array) return `:${Buffer.from(value).toString(\"base64\")}:`;\n if (typeof value === \"boolean\") return value ? \"?1\" : \"?0\";\n if (typeof value === \"number\")\n return Number.isInteger(value) ? String(value) : String(Math.round(value * 1000) / 1000);\n return `\"${value.replace(/[\"\\\\]/g, (char) => `\\\\${char}`)}\"`;\n};\n\nconst serializeParams = (params: Params): string =>\n params\n .map(([key, value]) => (value === true ? `;${key}` : `;${key}=${serializeBare(value)}`))\n .join(\"\");\n\nconst serializeItem = (item: Item): string =>\n `${serializeBare(item.value)}${serializeParams(item.params)}`;\n\nconst serializeMember = (member: Member): string =>\n isInnerList(member)\n ? `(${member.items.map(serializeItem).join(\" \")})${serializeParams(member.params)}`\n : serializeItem(member);\n\n/** This function tells if a text is a key of a structured field: a label of a signature, for example. */\nconst isKey = (text: string): boolean => /^[a-z*][a-z0-9_\\-.*]*$/.test(text);\n\n// ── keys ──\n\nconst base64url = (bytes: Uint8Array): string => Buffer.from(bytes).toString(\"base64url\");\n\nconst isKeyObject = (key: KeyInput): key is KeyObject =>\n typeof key === \"object\" && key !== null && \"asymmetricKeyType\" in key && \"export\" in key;\n\n/** This function reads a private key. Its errors do not hold the key. */\nconst privateKeyOf = (input: KeyInput): KeyObject => {\n let key: KeyObject;\n try {\n if (isKeyObject(input)) key = input;\n else if (typeof input === \"string\")\n key = input.trim().startsWith(\"{\")\n ? createPrivateKey({ key: JSON.parse(input) as JsonWebKey, format: \"jwk\" })\n : createPrivateKey(input);\n else if (input instanceof Uint8Array)\n key = createPrivateKey({ key: Buffer.from(input), format: \"der\", type: \"pkcs8\" });\n else key = createPrivateKey({ key: input, format: \"jwk\" });\n } catch (cause) {\n throw new Error(\n \"the private key of the signer does not parse: give an Ed25519 key as PKCS#8 (PEM or DER) or as a JWK\",\n { cause },\n );\n }\n if (key.type !== \"private\")\n throw new Error(\"the key of the signer is not a private key: give the Ed25519 private key\");\n if (key.asymmetricKeyType !== \"ed25519\")\n throw new Error(\n `the key of the signer is ${key.asymmetricKeyType ?? \"not an asymmetric key\"}, not ed25519: Web Bot Auth here uses Ed25519 keys`,\n );\n return key;\n};\n\n/** This function reads a public key, or the public part of a private key. Its errors do not hold the key. */\nconst publicKeyOf = (input: KeyInput): KeyObject => {\n let key: KeyObject;\n try {\n if (isKeyObject(input)) key = input.type === \"private\" ? createPublicKey(input) : input;\n else if (typeof input === \"string\")\n key = input.trim().startsWith(\"{\")\n ? createPublicKey({ key: JSON.parse(input) as JsonWebKey, format: \"jwk\" })\n : createPublicKey(input);\n else if (input instanceof Uint8Array) {\n const bytes = Buffer.from(input);\n try {\n key = createPublicKey({ key: bytes, format: \"der\", type: \"spki\" });\n } catch {\n key = createPublicKey(createPrivateKey({ key: bytes, format: \"der\", type: \"pkcs8\" }));\n }\n } else key = createPublicKey({ key: input, format: \"jwk\" });\n } catch (cause) {\n throw new Error(\"a key of the directory does not parse: give an Ed25519 key\", { cause });\n }\n if (key.asymmetricKeyType !== \"ed25519\")\n throw new Error(\n `a key of the directory is ${key.asymmetricKeyType ?? \"not an asymmetric key\"}, not ed25519`,\n );\n return key;\n};\n\n/** This function gives the JWK thumbprint of an Ed25519 public key (RFC 7638, RFC 8037 appendix A.3). */\nconst thumbprintOf = (x: string): string =>\n base64url(\n createHash(\"sha256\")\n .update(JSON.stringify({ crv: \"Ed25519\", kty: \"OKP\", x }), \"utf8\")\n .digest(),\n );\n\nconst directoryKeyOf = (key: KeyObject): DirectoryKey => {\n const jwk = key.export({ format: \"jwk\" });\n const x = typeof jwk.x === \"string\" ? jwk.x : \"\";\n return { kty: \"OKP\", crv: \"Ed25519\", kid: thumbprintOf(x), x, use: \"sig\" };\n};\n\n/**\n * This function makes the key directory for the operator to host: a JSON Web Key Set with each\n * public key and its thumbprint as `kid`. It takes public keys, or private keys, and it writes only\n * their public parts. Serve the JSON at `KEY_DIRECTORY_PATH` with the type `KEY_DIRECTORY_TYPE`.\n */\nexport const keyDirectory = (publicKeys: readonly KeyInput[]): KeyDirectory => {\n const keys = new Map<string, DirectoryKey>();\n for (const input of publicKeys) {\n const key = directoryKeyOf(publicKeyOf(input));\n keys.set(key.kid, key);\n }\n return { keys: [...keys.values()] };\n};\n\n// ── signing ──\n\n/** The parts of one signature. The signer and the tests use this function. */\nexport interface SignatureParts {\n /** The value of `@authority`: the host and a port that is not the default port, in lowercase. */\n readonly authority: string;\n readonly label: string;\n /** The key of the member of `Signature-Agent`. The draft uses the label of the signature. */\n readonly agentKey: string;\n /** The serialized value of the member: a string, and its parameters. */\n readonly agentValue: string;\n readonly keyId: string;\n readonly created: number;\n readonly expires: number;\n readonly nonce?: string | undefined;\n}\n\n/** This function gives the `@signature-params` value: the covered components and the parameters, in the order of the draft. */\nconst signatureParams = (parts: SignatureParts): string =>\n `(\"@authority\" \"signature-agent\";key=${serializeBare(parts.agentKey)});created=${String(parts.created)};keyid=${serializeBare(parts.keyId)};alg=\"ed25519\";expires=${String(parts.expires)}${parts.nonce === undefined ? \"\" : `;nonce=${serializeBare(parts.nonce)}`};tag=\"${WEB_BOT_AUTH_TAG}\"`;\n\n/** This function gives the signature base of RFC 9421, section 2.5, for the components of Web Bot Auth. */\nexport const signatureBase = (parts: SignatureParts): string =>\n [\n `\"@authority\": ${parts.authority}`,\n `\"signature-agent\";key=${serializeBare(parts.agentKey)}: ${parts.agentValue}`,\n `\"@signature-params\": ${signatureParams(parts)}`,\n ].join(\"\\n\");\n\n/** This function signs the parts with an Ed25519 key, and gives the three headers. */\nexport const signParts = (\n parts: SignatureParts,\n key: KeyObject,\n): {\n readonly \"signature-agent\": string;\n readonly \"signature-input\": string;\n readonly signature: string;\n} => {\n const signature = signBytes(null, Buffer.from(signatureBase(parts), \"utf8\"), key);\n return {\n \"signature-agent\": `${parts.agentKey}=${parts.agentValue}`,\n \"signature-input\": `${parts.label}=${signatureParams(parts)}`,\n signature: `${parts.label}=:${signature.toString(\"base64\")}:`,\n };\n};\n\nconst DEFAULT_LIFETIME_MS = 5 * 60_000;\nconst MAX_LIFETIME_MS = 24 * 60 * 60_000;\n\n/** This function checks the URL of `Signature-Agent`, and gives the URL and the serialized value of the member. */\nconst agentOf = (\n url: string,\n type: SignatureAgentType,\n): { readonly url: string; readonly value: string } => {\n let target: URL;\n try {\n target = new URL(url);\n } catch {\n throw new Error(\n `signatureAgent \"${url}\" is not a URL: give the https URL of the key directory`,\n );\n }\n if (target.protocol !== \"https:\")\n throw new Error(`signatureAgent \"${url}\" is not an https URL: the draft permits only https`);\n if (target.username !== \"\" || target.password !== \"\" || target.hash !== \"\")\n throw new Error(`signatureAgent \"${url}\" has a user name, a password or a fragment`);\n if (type === \"directory\") {\n if (target.pathname !== \"/\" || target.search !== \"\")\n throw new Error(\n `signatureAgent \"${url}\" is not an origin: the type directory takes only an origin, such as https://bot.example.com. For a key set on a path, set agentType to jwks_uri`,\n );\n return { url: target.origin, value: serializeBare(target.origin) };\n }\n return { url: target.href, value: `${serializeBare(target.href)};type=${type}` };\n};\n\n/**\n * This function makes a signer for Web Bot Auth with an Ed25519 key. Each signature covers\n * `@authority` and the member of `Signature-Agent`. It has a new random nonce, and it expires after\n * `lifetimeMs`. The signer keeps the private key in a closure: no property of the signer holds it.\n */\nexport const createWebBotAuthSigner = (options: WebBotAuthOptions): WebBotAuthSigner => {\n const key = privateKeyOf(options.privateKey);\n const publicKey = directoryKeyOf(createPublicKey(key));\n if (options.keyId !== undefined && options.keyId !== publicKey.kid)\n throw new Error(\n `keyId \"${options.keyId}\" is not the JWK thumbprint of the key (${publicKey.kid}): Web Bot Auth uses the thumbprint as keyid`,\n );\n const agent = agentOf(options.signatureAgent, options.agentType ?? \"directory\");\n const label = options.label ?? \"sig1\";\n if (!isKey(label))\n throw new Error(\n `label \"${label}\" is not a key of a structured field: use lowercase letters, digits, \"_\", \"-\", \".\" and \"*\"`,\n );\n const lifetimeMs = options.lifetimeMs ?? DEFAULT_LIFETIME_MS;\n if (!Number.isFinite(lifetimeMs) || lifetimeMs < 1000 || lifetimeMs > MAX_LIFETIME_MS)\n throw new RangeError(\n `lifetimeMs must be from 1000 (1 second) to ${String(MAX_LIFETIME_MS)} (24 hours), not ${String(lifetimeMs)}`,\n );\n const { kty, crv, x } = publicKey;\n return {\n keyId: publicKey.kid,\n signatureAgent: agent.url,\n publicKey: { kty, crv, x },\n sign(url, now) {\n const created = Math.floor(now / 1000);\n return signParts(\n {\n authority: url.host.toLowerCase(),\n label,\n agentKey: label,\n agentValue: agent.value,\n keyId: publicKey.kid,\n created,\n expires: created + Math.ceil(lifetimeMs / 1000),\n nonce: randomBytes(32).toString(\"base64\"),\n },\n key,\n );\n },\n };\n};\n\n/** This function tells if a value is a request signer, not the options of one. */\nexport const isRequestSigner = (value: RequestSigner | WebBotAuthOptions): value is RequestSigner =>\n typeof (value as Partial<RequestSigner>).sign === \"function\";\n\n/** This function gives a request signer: the signer itself, or a new signer from its options. */\nexport const signerOf = (\n value: RequestSigner | WebBotAuthOptions | undefined,\n): RequestSigner | undefined =>\n value === undefined ? undefined : isRequestSigner(value) ? value : createWebBotAuthSigner(value);\n\n// ── verification ──\n\n/** A signature can start this long before the clock of the verifier, for a small difference of the clocks. */\nconst CLOCK_SKEW_S = 60;\n\nconst headerLines = (\n headers: SignedRequest[\"headers\"],\n name: string,\n): readonly string[] | undefined => {\n for (const [key, value] of Object.entries(headers)) {\n if (key.toLowerCase() !== name || value === undefined) continue;\n return typeof value === \"string\" ? [value] : value;\n }\n return undefined;\n};\n\n/** This function gives the value of a header as RFC 9421 section 2.1 combines it: each line without its outer spaces, with \", \" between. */\nconst fieldValue = (headers: SignedRequest[\"headers\"], name: string): string | undefined =>\n headerLines(headers, name)\n ?.map((line) => line.replace(/\\r?\\n[ \\t]+/g, \" \").trim())\n .join(\", \");\n\n/** This function gives the value of one component of a request, or an error text. */\nconst componentValue = (\n component: Item,\n request: SignedRequest,\n target: URL,\n): { readonly value: string } | { readonly error: string } => {\n const name = component.value;\n if (typeof name !== \"string\") return { error: \"a component is not a string\" };\n const params = new Map(component.params);\n if (name.startsWith(\"@\")) {\n if (params.size > 0) return { error: `the parameters of ${name} are not supported` };\n switch (name) {\n case \"@method\":\n return { value: (request.method ?? \"GET\").toUpperCase() };\n case \"@authority\":\n return { value: target.host.toLowerCase() };\n case \"@scheme\":\n return { value: target.protocol.slice(0, -1).toLowerCase() };\n case \"@target-uri\":\n return { value: target.href };\n case \"@path\":\n return { value: target.pathname === \"\" ? \"/\" : target.pathname };\n case \"@query\":\n return { value: target.search === \"\" ? \"?\" : target.search };\n case \"@request-target\":\n return { value: `${target.pathname}${target.search}` };\n default:\n return { error: `the component ${name} is not supported` };\n }\n }\n const value = fieldValue(request.headers, name);\n if (value === undefined) return { error: `the request has no ${name} header` };\n if ([...params.keys()].some((param) => param !== \"key\"))\n return { error: `the parameters of ${name} are not supported` };\n const key = params.get(\"key\");\n if (key === undefined) return { value };\n if (typeof key !== \"string\") return { error: `the key of ${name} is not a string` };\n let member: Member | undefined;\n try {\n member = new FieldParser(value).dictionary().get(key);\n } catch (error) {\n return { error: `the ${name} header is not a dictionary: ${(error as Error).message}` };\n }\n if (member === undefined) return { error: `the ${name} header has no member ${key}` };\n return { value: serializeMember(member) };\n};\n\nconst failed = (\n reason: string,\n facts: { label?: string | null; keyId?: string | null; agent?: string | null } = {},\n): SignatureCheck => ({\n verified: false,\n label: facts.label ?? null,\n keyId: facts.keyId ?? null,\n agent: facts.agent ?? null,\n reason,\n});\n\n/**\n * This function verifies the Web Bot Auth signatures of a request with the keys of a directory. It\n * applies RFC 9421 section 3.2 and the profile of the draft. The profile asks for the tag\n * `web-bot-auth`, `created`, `expires` and `@authority`. It also asks for the member of\n * `Signature-Agent`, and a key with the thumbprint of `keyid`. The tests use it, and an operator can check a setup with it. It sends no request.\n */\nexport const verifySignature = (\n request: SignedRequest,\n directory: KeyDirectory,\n options: { readonly now?: number | undefined } = {},\n): SignatureCheck => {\n let target: URL;\n try {\n target = new URL(request.url);\n } catch {\n return failed(\"the URL of the request is not a URL\");\n }\n const inputText = fieldValue(request.headers, \"signature-input\");\n const signatureText = fieldValue(request.headers, \"signature\");\n if (inputText === undefined || signatureText === undefined)\n return failed(\"the request has no Signature-Input or no Signature header\");\n let inputs: Map<string, Member>;\n let signatures: Map<string, Member>;\n try {\n inputs = new FieldParser(inputText).dictionary();\n signatures = new FieldParser(signatureText).dictionary();\n } catch (error) {\n return failed(`a signature header does not parse: ${(error as Error).message}`);\n }\n const now = Math.floor((options.now ?? Date.now()) / 1000);\n const keys = new Map<string, KeyObject>();\n for (const entry of directory.keys) {\n try {\n const key = createPublicKey({\n key: { kty: entry.kty, crv: entry.crv, x: entry.x },\n format: \"jwk\",\n });\n keys.set(thumbprintOf(entry.x), key);\n } catch {\n // a key that does not parse verifies nothing\n }\n }\n let last: SignatureCheck = failed(\"the request has no Web Bot Auth signature\");\n for (const [label, input] of inputs) {\n if (!isInnerList(input)) continue;\n const params = new Map(input.params);\n if (params.get(\"tag\") !== WEB_BOT_AUTH_TAG) continue;\n const keyId = typeof params.get(\"keyid\") === \"string\" ? (params.get(\"keyid\") as string) : null;\n const covered = input.items;\n const names = covered.map((item) => serializeItem(item));\n const agentItem = covered.find((item) => item.value === \"signature-agent\");\n let agent: string | null = null;\n if (agentItem !== undefined) {\n const value = componentValue(agentItem, request, target);\n if (\"value\" in value) {\n try {\n const member = new FieldParser(value.value).wholeItem().value;\n agent = typeof member === \"string\" ? member : null;\n } catch {\n agent = null;\n }\n }\n }\n const facts = { label, keyId, agent };\n if (new Set(names).size !== names.length) {\n last = failed(\"a component is covered two times\", facts);\n continue;\n }\n if (!names.includes('\"@authority\"') && !names.includes('\"@target-uri\"')) {\n last = failed(\"the signature does not cover @authority or @target-uri\", facts);\n continue;\n }\n if (agentItem === undefined) {\n last = failed(\"the signature does not cover Signature-Agent\", facts);\n continue;\n }\n const created = params.get(\"created\");\n const expires = params.get(\"expires\");\n if (typeof created !== \"number\" || typeof expires !== \"number\") {\n last = failed(\"the signature has no created or no expires\", facts);\n continue;\n }\n if (expires <= now) {\n last = failed(\"the signature expired\", facts);\n continue;\n }\n if (created > now + CLOCK_SKEW_S) {\n last = failed(\"the signature was created in the future\", facts);\n continue;\n }\n const alg = params.get(\"alg\");\n if (alg !== undefined && alg !== \"ed25519\") {\n last = failed(`the algorithm ${serializeBare(alg)} is not ed25519`, facts);\n continue;\n }\n const key = keyId === null ? undefined : keys.get(keyId);\n if (key === undefined) {\n last = failed(\"no key of the directory has the thumbprint of keyid\", facts);\n continue;\n }\n const lines: string[] = [];\n let error: string | undefined;\n for (const item of covered) {\n const value = componentValue(item, request, target);\n if (\"error\" in value) {\n error = value.error;\n break;\n }\n lines.push(`${serializeItem(item)}: ${value.value}`);\n }\n if (error !== undefined) {\n last = failed(error, facts);\n continue;\n }\n lines.push(`\"@signature-params\": ${serializeMember(input)}`);\n const signature = signatures.get(label);\n if (\n signature === undefined ||\n isInnerList(signature) ||\n !(signature.value instanceof Uint8Array)\n ) {\n last = failed(`the Signature header has no byte sequence for ${label}`, facts);\n continue;\n }\n const good = verifyBytes(null, Buffer.from(lines.join(\"\\n\"), \"utf8\"), key, signature.value);\n if (good) return { verified: true, label, keyId, agent, reason: null };\n last = failed(\"the signature does not match the signature base\", facts);\n }\n return last;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,MAAa,qBAAqB;;AAGlC,MAAa,qBAAqB;;AAGlC,MAAa,mBAAmB;;AA8FhC,IAAM,QAAN,MAAY;CACW;CAArB,YAAY,OAAwB;EAAf,KAAA,QAAA;CAAgB;AACvC;AAiBA,MAAM,eAAe,WAAwC,WAAW;;AAGxE,IAAM,aAAN,cAAyB,MAAM,CAAC;AAEhC,MAAM,YAAY;AAClB,MAAM,WAAW;AACjB,MAAM,cAAc;AACpB,MAAM,aAAa;AACnB,MAAM,SAAS;;AAGf,IAAM,cAAN,MAAkB;CAEa;CAD7B,KAAa;CACb,YAAY,MAA+B;EAAd,KAAA,OAAA;CAAe;CAE5C,OAAuB;EACrB,OAAO,KAAK,KAAK,KAAK,OAAO;CAC/B;CAEA,SAAuB;EACrB,OAAO,KAAK,KAAK,MAAM,KAAK,KAAK,MAAM;CACzC;CAEA,MAAoB;EAClB,OAAO,KAAK,KAAK,MAAM,OAAO,KAAK,KAAK,MAAM,KAAM,KAAK,MAAM;CACjE;CAEA,aAAkC;EAChC,MAAM,sBAAM,IAAI,IAAoB;EACpC,KAAK,OAAO;EACZ,OAAO,KAAK,KAAK,KAAK,KAAK,QAAQ;GACjC,MAAM,MAAM,KAAK,IAAI;GACrB,IAAI;GACJ,IAAI,KAAK,KAAK,MAAM,KAAK;IACvB,KAAK,MAAM;IACX,SAAS,KAAK,KAAK,MAAM,MAAM,KAAK,UAAU,IAAI,KAAK,KAAK;GAC9D,OACE,SAAS;IAAE,OAAO;IAAM,QAAQ,KAAK,OAAO;GAAE;GAEhD,IAAI,IAAI,KAAK,MAAM;GACnB,KAAK,IAAI;GACT,IAAI,KAAK,MAAM,KAAK,KAAK,QAAQ;GACjC,IAAI,KAAK,KAAK,MAAM,KAAK,MAAM,IAAI,WAAW,yBAAyB,OAAO,KAAK,EAAE,GAAG;GACxF,KAAK,MAAM;GACX,KAAK,IAAI;GACT,IAAI,KAAK,MAAM,KAAK,KAAK,QAAQ,MAAM,IAAI,WAAW,6BAA6B;EACrF;EACA,OAAO;CACT;;CAGA,YAAkB;EAChB,KAAK,OAAO;EACZ,MAAM,OAAO,KAAK,KAAK;EACvB,KAAK,OAAO;EACZ,IAAI,KAAK,KAAK,KAAK,KAAK,QAAQ,MAAM,IAAI,WAAW,qBAAqB;EAC1E,OAAO;CACT;CAEA,MAAsB;EACpB,IAAI,CAAC,UAAU,KAAK,KAAK,KAAK,CAAC,GAAG,MAAM,IAAI,WAAW,aAAa,OAAO,KAAK,EAAE,GAAG;EACrF,MAAM,QAAQ,KAAK;EACnB,OAAO,SAAS,KAAK,KAAK,KAAK,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,QAAQ,KAAK,MAAM;EAC5E,OAAO,KAAK,KAAK,MAAM,OAAO,KAAK,EAAE;CACvC;CAEA,YAA+B;EAC7B,KAAK,MAAM;EACX,MAAM,QAAgB,CAAC;EACvB,SAAS;GACP,KAAK,OAAO;GACZ,IAAI,KAAK,KAAK,MAAM,KAAK;IACvB,KAAK,MAAM;IACX,OAAO;KAAE;KAAO,QAAQ,KAAK,OAAO;IAAE;GACxC;GACA,IAAI,KAAK,MAAM,KAAK,KAAK,QAAQ,MAAM,IAAI,WAAW,0BAA0B;GAChF,MAAM,KAAK,KAAK,KAAK,CAAC;GACtB,IAAI,KAAK,KAAK,MAAM,OAAO,KAAK,KAAK,MAAM,KACzC,MAAM,IAAI,WAAW,kCAAkC,OAAO,KAAK,EAAE,GAAG;EAC5E;CACF;CAEA,OAAqB;EAEnB,OAAO;GAAE,OADK,KAAK,KACN;GAAG,QAAQ,KAAK,OAAO;EAAE;CACxC;CAEA,SAAyB;EACvB,MAAM,MAA0C,CAAC;EACjD,OAAO,KAAK,KAAK,MAAM,KAAK;GAC1B,KAAK,MAAM;GACX,KAAK,OAAO;GACZ,MAAM,MAAM,KAAK,IAAI;GACrB,IAAI,QAAkB;GACtB,IAAI,KAAK,KAAK,MAAM,KAAK;IACvB,KAAK,MAAM;IACX,QAAQ,KAAK,KAAK;GACpB;GACA,MAAM,QAAQ,IAAI,WAAW,CAAC,UAAU,SAAS,GAAG;GACpD,IAAI,UAAU,IAAI,IAAI,KAAK,CAAC,KAAK,KAAK,CAAC;QAClC,IAAI,SAAS,CAAC,KAAK,KAAK;EAC/B;EACA,OAAO;CACT;CAEA,OAAyB;EACvB,MAAM,OAAO,KAAK,KAAK;EACvB,IAAI,SAAS,OAAO,QAAQ,KAAK,IAAI,GAAG,OAAO,KAAK,OAAO;EAC3D,IAAI,SAAS,MAAK,OAAO,KAAK,OAAO;EACrC,IAAI,SAAS,KAAK,OAAO,KAAK,MAAM;EACpC,IAAI,SAAS,KAAK,OAAO,KAAK,QAAQ;EACtC,IAAI,YAAY,KAAK,IAAI,GAAG,OAAO,KAAK,MAAM;EAC9C,MAAM,IAAI,WAAW,cAAc,OAAO,KAAK,EAAE,GAAG;CACtD;CAEA,SAAyB;EACvB,MAAM,QAAQ,4BAA4B,KAAK,KAAK,KAAK,MAAM,KAAK,EAAE,CAAC;EACvE,IAAI,UAAU,QAAS,MAAM,OAAO,KAAA,KAAa,MAAM,EAAE,CAAE,SAAS,IAClE,MAAM,IAAI,WAAW,mBAAmB,OAAO,KAAK,EAAE,GAAG;EAC3D,KAAK,MAAM,MAAM,EAAE,CAAC;EACpB,OAAO,OAAO,MAAM,EAAE;CACxB;CAEA,SAAyB;EACvB,KAAK,MAAM;EACX,IAAI,MAAM;EACV,SAAS;GACP,MAAM,OAAO,KAAK,KAAK;GACvB,IAAI,SAAS,IAAI,MAAM,IAAI,WAAW,qBAAqB;GAC3D,KAAK,MAAM;GACX,IAAI,SAAS,MAAM;IACjB,MAAM,OAAO,KAAK,KAAK;IACvB,IAAI,SAAS,QAAO,SAAS,MAAM,MAAM,IAAI,WAAW,0BAA0B;IAClF,OAAO;IACP,KAAK,MAAM;GACb,OAAO,IAAI,SAAS,MAClB,OAAO;QACF;IACL,MAAM,OAAO,KAAK,WAAW,CAAC;IAC9B,IAAI,OAAO,MAAQ,OAAO,KACxB,MAAM,IAAI,WAAW,wDAAwD;IAC/E,OAAO;GACT;EACF;CACF;CAEA,QAA4B;EAC1B,KAAK,MAAM;EACX,MAAM,MAAM,KAAK,KAAK,QAAQ,KAAK,KAAK,EAAE;EAC1C,IAAI,QAAQ,IAAI,MAAM,IAAI,WAAW,4BAA4B;EACjE,MAAM,OAAO,KAAK,KAAK,MAAM,KAAK,IAAI,GAAG;EACzC,IAAI,CAAC,OAAO,KAAK,IAAI,GAAG,MAAM,IAAI,WAAW,+BAA+B;EAC5E,KAAK,KAAK,MAAM;EAChB,OAAO,IAAI,WAAW,OAAO,KAAK,MAAM,QAAQ,CAAC;CACnD;CAEA,UAA2B;EACzB,MAAM,QAAQ,KAAK,KAAK,MAAM,KAAK,IAAI,KAAK,KAAK,CAAC;EAClD,IAAI,UAAU,QAAQ,UAAU,MAAM,MAAM,IAAI,WAAW,eAAe;EAC1E,KAAK,MAAM;EACX,OAAO,UAAU;CACnB;CAEA,QAAuB;EACrB,MAAM,QAAQ,KAAK;EACnB,KAAK,MAAM;EACX,OAAO,WAAW,KAAK,KAAK,KAAK,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,QAAQ,KAAK,MAAM;EAC9E,OAAO,IAAI,MAAM,KAAK,KAAK,MAAM,OAAO,KAAK,EAAE,CAAC;CAClD;AACF;AAEA,MAAM,iBAAiB,UAA4B;CACjD,IAAI,iBAAiB,OAAO,OAAO,MAAM;CACzC,IAAI,iBAAiB,YAAY,OAAO,IAAI,OAAO,KAAK,KAAK,CAAC,CAAC,SAAS,QAAQ,EAAE;CAClF,IAAI,OAAO,UAAU,WAAW,OAAO,QAAQ,OAAO;CACtD,IAAI,OAAO,UAAU,UACnB,OAAO,OAAO,UAAU,KAAK,IAAI,OAAO,KAAK,IAAI,OAAO,KAAK,MAAM,QAAQ,GAAI,IAAI,GAAI;CACzF,OAAO,IAAI,MAAM,QAAQ,WAAW,SAAS,KAAK,MAAM,EAAE;AAC5D;AAEA,MAAM,mBAAmB,WACvB,OACG,KAAK,CAAC,KAAK,WAAY,UAAU,OAAO,IAAI,QAAQ,IAAI,IAAI,GAAG,cAAc,KAAK,GAAI,CAAC,CACvF,KAAK,EAAE;AAEZ,MAAM,iBAAiB,SACrB,GAAG,cAAc,KAAK,KAAK,IAAI,gBAAgB,KAAK,MAAM;AAE5D,MAAM,mBAAmB,WACvB,YAAY,MAAM,IACd,IAAI,OAAO,MAAM,IAAI,aAAa,CAAC,CAAC,KAAK,GAAG,EAAE,GAAG,gBAAgB,OAAO,MAAM,MAC9E,cAAc,MAAM;;AAG1B,MAAM,SAAS,SAA0B,yBAAyB,KAAK,IAAI;AAI3E,MAAM,aAAa,UAA8B,OAAO,KAAK,KAAK,CAAC,CAAC,SAAS,WAAW;AAExF,MAAM,eAAe,QACnB,OAAO,QAAQ,YAAY,QAAQ,QAAQ,uBAAuB,OAAO,YAAY;;AAGvF,MAAM,gBAAgB,UAA+B;CACnD,IAAI;CACJ,IAAI;EACF,IAAI,YAAY,KAAK,GAAG,MAAM;OACzB,IAAI,OAAO,UAAU,UACxB,MAAM,MAAM,KAAK,CAAC,CAAC,WAAW,GAAG,IAC7B,iBAAiB;GAAE,KAAK,KAAK,MAAM,KAAK;GAAiB,QAAQ;EAAM,CAAC,IACxE,iBAAiB,KAAK;OACvB,IAAI,iBAAiB,YACxB,MAAM,iBAAiB;GAAE,KAAK,OAAO,KAAK,KAAK;GAAG,QAAQ;GAAO,MAAM;EAAQ,CAAC;OAC7E,MAAM,iBAAiB;GAAE,KAAK;GAAO,QAAQ;EAAM,CAAC;CAC3D,SAAS,OAAO;EACd,MAAM,IAAI,MACR,wGACA,EAAE,MAAM,CACV;CACF;CACA,IAAI,IAAI,SAAS,WACf,MAAM,IAAI,MAAM,0EAA0E;CAC5F,IAAI,IAAI,sBAAsB,WAC5B,MAAM,IAAI,MACR,4BAA4B,IAAI,qBAAqB,wBAAwB,mDAC/E;CACF,OAAO;AACT;;AAGA,MAAM,eAAe,UAA+B;CAClD,IAAI;CACJ,IAAI;EACF,IAAI,YAAY,KAAK,GAAG,MAAM,MAAM,SAAS,YAAY,gBAAgB,KAAK,IAAI;OAC7E,IAAI,OAAO,UAAU,UACxB,MAAM,MAAM,KAAK,CAAC,CAAC,WAAW,GAAG,IAC7B,gBAAgB;GAAE,KAAK,KAAK,MAAM,KAAK;GAAiB,QAAQ;EAAM,CAAC,IACvE,gBAAgB,KAAK;OACtB,IAAI,iBAAiB,YAAY;GACpC,MAAM,QAAQ,OAAO,KAAK,KAAK;GAC/B,IAAI;IACF,MAAM,gBAAgB;KAAE,KAAK;KAAO,QAAQ;KAAO,MAAM;IAAO,CAAC;GACnE,QAAQ;IACN,MAAM,gBAAgB,iBAAiB;KAAE,KAAK;KAAO,QAAQ;KAAO,MAAM;IAAQ,CAAC,CAAC;GACtF;EACF,OAAO,MAAM,gBAAgB;GAAE,KAAK;GAAO,QAAQ;EAAM,CAAC;CAC5D,SAAS,OAAO;EACd,MAAM,IAAI,MAAM,8DAA8D,EAAE,MAAM,CAAC;CACzF;CACA,IAAI,IAAI,sBAAsB,WAC5B,MAAM,IAAI,MACR,6BAA6B,IAAI,qBAAqB,wBAAwB,cAChF;CACF,OAAO;AACT;;AAGA,MAAM,gBAAgB,MACpB,UACE,WAAW,QAAQ,CAAC,CACjB,OAAO,KAAK,UAAU;CAAE,KAAK;CAAW,KAAK;CAAO;AAAE,CAAC,GAAG,MAAM,CAAC,CACjE,OAAO,CACZ;AAEF,MAAM,kBAAkB,QAAiC;CACvD,MAAM,MAAM,IAAI,OAAO,EAAE,QAAQ,MAAM,CAAC;CACxC,MAAM,IAAI,OAAO,IAAI,MAAM,WAAW,IAAI,IAAI;CAC9C,OAAO;EAAE,KAAK;EAAO,KAAK;EAAW,KAAK,aAAa,CAAC;EAAG;EAAG,KAAK;CAAM;AAC3E;;;;;;AAOA,MAAa,gBAAgB,eAAkD;CAC7E,MAAM,uBAAO,IAAI,IAA0B;CAC3C,KAAK,MAAM,SAAS,YAAY;EAC9B,MAAM,MAAM,eAAe,YAAY,KAAK,CAAC;EAC7C,KAAK,IAAI,IAAI,KAAK,GAAG;CACvB;CACA,OAAO,EAAE,MAAM,CAAC,GAAG,KAAK,OAAO,CAAC,EAAE;AACpC;;AAoBA,MAAM,mBAAmB,UACvB,uCAAuC,cAAc,MAAM,QAAQ,EAAE,YAAY,OAAO,MAAM,OAAO,EAAE,SAAS,cAAc,MAAM,KAAK,EAAE,yBAAyB,OAAO,MAAM,OAAO,IAAI,MAAM,UAAU,KAAA,IAAY,KAAK,UAAU,cAAc,MAAM,KAAK,IAAI,QAAQ,iBAAiB;;AAG/R,MAAa,iBAAiB,UAC5B;CACE,iBAAiB,MAAM;CACvB,yBAAyB,cAAc,MAAM,QAAQ,EAAE,IAAI,MAAM;CACjE,wBAAwB,gBAAgB,KAAK;AAC/C,CAAC,CAAC,KAAK,IAAI;;AAGb,MAAa,aACX,OACA,QAKG;CACH,MAAM,YAAYA,KAAU,MAAM,OAAO,KAAK,cAAc,KAAK,GAAG,MAAM,GAAG,GAAG;CAChF,OAAO;EACL,mBAAmB,GAAG,MAAM,SAAS,GAAG,MAAM;EAC9C,mBAAmB,GAAG,MAAM,MAAM,GAAG,gBAAgB,KAAK;EAC1D,WAAW,GAAG,MAAM,MAAM,IAAI,UAAU,SAAS,QAAQ,EAAE;CAC7D;AACF;AAEA,MAAM,sBAAsB;AAC5B,MAAM,kBAAkB;;AAGxB,MAAM,WACJ,KACA,SACqD;CACrD,IAAI;CACJ,IAAI;EACF,SAAS,IAAI,IAAI,GAAG;CACtB,QAAQ;EACN,MAAM,IAAI,MACR,mBAAmB,IAAI,wDACzB;CACF;CACA,IAAI,OAAO,aAAa,UACtB,MAAM,IAAI,MAAM,mBAAmB,IAAI,oDAAoD;CAC7F,IAAI,OAAO,aAAa,MAAM,OAAO,aAAa,MAAM,OAAO,SAAS,IACtE,MAAM,IAAI,MAAM,mBAAmB,IAAI,4CAA4C;CACrF,IAAI,SAAS,aAAa;EACxB,IAAI,OAAO,aAAa,OAAO,OAAO,WAAW,IAC/C,MAAM,IAAI,MACR,mBAAmB,IAAI,iJACzB;EACF,OAAO;GAAE,KAAK,OAAO;GAAQ,OAAO,cAAc,OAAO,MAAM;EAAE;CACnE;CACA,OAAO;EAAE,KAAK,OAAO;EAAM,OAAO,GAAG,cAAc,OAAO,IAAI,EAAE,QAAQ;CAAO;AACjF;;;;;;AAOA,MAAa,0BAA0B,YAAiD;CACtF,MAAM,MAAM,aAAa,QAAQ,UAAU;CAC3C,MAAM,YAAY,eAAe,gBAAgB,GAAG,CAAC;CACrD,IAAI,QAAQ,UAAU,KAAA,KAAa,QAAQ,UAAU,UAAU,KAC7D,MAAM,IAAI,MACR,UAAU,QAAQ,MAAM,0CAA0C,UAAU,IAAI,6CAClF;CACF,MAAM,QAAQ,QAAQ,QAAQ,gBAAgB,QAAQ,aAAa,WAAW;CAC9E,MAAM,QAAQ,QAAQ,SAAS;CAC/B,IAAI,CAAC,MAAM,KAAK,GACd,MAAM,IAAI,MACR,UAAU,MAAM,2FAClB;CACF,MAAM,aAAa,QAAQ,cAAc;CACzC,IAAI,CAAC,OAAO,SAAS,UAAU,KAAK,aAAa,OAAQ,aAAa,iBACpE,MAAM,IAAI,WACR,8CAA8C,OAAO,eAAe,EAAE,mBAAmB,OAAO,UAAU,GAC5G;CACF,MAAM,EAAE,KAAK,KAAK,MAAM;CACxB,OAAO;EACL,OAAO,UAAU;EACjB,gBAAgB,MAAM;EACtB,WAAW;GAAE;GAAK;GAAK;EAAE;EACzB,KAAK,KAAK,KAAK;GACb,MAAM,UAAU,KAAK,MAAM,MAAM,GAAI;GACrC,OAAO,UACL;IACE,WAAW,IAAI,KAAK,YAAY;IAChC;IACA,UAAU;IACV,YAAY,MAAM;IAClB,OAAO,UAAU;IACjB;IACA,SAAS,UAAU,KAAK,KAAK,aAAa,GAAI;IAC9C,OAAO,YAAY,EAAE,CAAC,CAAC,SAAS,QAAQ;GAC1C,GACA,GACF;EACF;CACF;AACF;;AAGA,MAAa,mBAAmB,UAC9B,OAAQ,MAAiC,SAAS;;AAGpD,MAAa,YACX,UAEA,UAAU,KAAA,IAAY,KAAA,IAAY,gBAAgB,KAAK,IAAI,QAAQ,uBAAuB,KAAK;;AAKjG,MAAM,eAAe;AAErB,MAAM,eACJ,SACA,SACkC;CAClC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,GAAG;EAClD,IAAI,IAAI,YAAY,MAAM,QAAQ,UAAU,KAAA,GAAW;EACvD,OAAO,OAAO,UAAU,WAAW,CAAC,KAAK,IAAI;CAC/C;AAEF;;AAGA,MAAM,cAAc,SAAmC,SACrD,YAAY,SAAS,IAAI,CAAC,EACtB,KAAK,SAAS,KAAK,QAAQ,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CACxD,KAAK,IAAI;;AAGd,MAAM,kBACJ,WACA,SACA,WAC4D;CAC5D,MAAM,OAAO,UAAU;CACvB,IAAI,OAAO,SAAS,UAAU,OAAO,EAAE,OAAO,8BAA8B;CAC5E,MAAM,SAAS,IAAI,IAAI,UAAU,MAAM;CACvC,IAAI,KAAK,WAAW,GAAG,GAAG;EACxB,IAAI,OAAO,OAAO,GAAG,OAAO,EAAE,OAAO,qBAAqB,KAAK,oBAAoB;EACnF,QAAQ,MAAR;GACE,KAAK,WACH,OAAO,EAAE,QAAQ,QAAQ,UAAU,MAAA,CAAO,YAAY,EAAE;GAC1D,KAAK,cACH,OAAO,EAAE,OAAO,OAAO,KAAK,YAAY,EAAE;GAC5C,KAAK,WACH,OAAO,EAAE,OAAO,OAAO,SAAS,MAAM,GAAG,EAAE,CAAC,CAAC,YAAY,EAAE;GAC7D,KAAK,eACH,OAAO,EAAE,OAAO,OAAO,KAAK;GAC9B,KAAK,SACH,OAAO,EAAE,OAAO,OAAO,aAAa,KAAK,MAAM,OAAO,SAAS;GACjE,KAAK,UACH,OAAO,EAAE,OAAO,OAAO,WAAW,KAAK,MAAM,OAAO,OAAO;GAC7D,KAAK,mBACH,OAAO,EAAE,OAAO,GAAG,OAAO,WAAW,OAAO,SAAS;GACvD,SACE,OAAO,EAAE,OAAO,iBAAiB,KAAK,mBAAmB;EAC7D;CACF;CACA,MAAM,QAAQ,WAAW,QAAQ,SAAS,IAAI;CAC9C,IAAI,UAAU,KAAA,GAAW,OAAO,EAAE,OAAO,sBAAsB,KAAK,SAAS;CAC7E,IAAI,CAAC,GAAG,OAAO,KAAK,CAAC,CAAC,CAAC,MAAM,UAAU,UAAU,KAAK,GACpD,OAAO,EAAE,OAAO,qBAAqB,KAAK,oBAAoB;CAChE,MAAM,MAAM,OAAO,IAAI,KAAK;CAC5B,IAAI,QAAQ,KAAA,GAAW,OAAO,EAAE,MAAM;CACtC,IAAI,OAAO,QAAQ,UAAU,OAAO,EAAE,OAAO,cAAc,KAAK,kBAAkB;CAClF,IAAI;CACJ,IAAI;EACF,SAAS,IAAI,YAAY,KAAK,CAAC,CAAC,WAAW,CAAC,CAAC,IAAI,GAAG;CACtD,SAAS,OAAO;EACd,OAAO,EAAE,OAAO,OAAO,KAAK,+BAAgC,MAAgB,UAAU;CACxF;CACA,IAAI,WAAW,KAAA,GAAW,OAAO,EAAE,OAAO,OAAO,KAAK,wBAAwB,MAAM;CACpF,OAAO,EAAE,OAAO,gBAAgB,MAAM,EAAE;AAC1C;AAEA,MAAM,UACJ,QACA,QAAiF,CAAC,OAC9D;CACpB,UAAU;CACV,OAAO,MAAM,SAAS;CACtB,OAAO,MAAM,SAAS;CACtB,OAAO,MAAM,SAAS;CACtB;AACF;;;;;;;AAQA,MAAa,mBACX,SACA,WACA,UAAiD,CAAC,MAC/B;CACnB,IAAI;CACJ,IAAI;EACF,SAAS,IAAI,IAAI,QAAQ,GAAG;CAC9B,QAAQ;EACN,OAAO,OAAO,qCAAqC;CACrD;CACA,MAAM,YAAY,WAAW,QAAQ,SAAS,iBAAiB;CAC/D,MAAM,gBAAgB,WAAW,QAAQ,SAAS,WAAW;CAC7D,IAAI,cAAc,KAAA,KAAa,kBAAkB,KAAA,GAC/C,OAAO,OAAO,2DAA2D;CAC3E,IAAI;CACJ,IAAI;CACJ,IAAI;EACF,SAAS,IAAI,YAAY,SAAS,CAAC,CAAC,WAAW;EAC/C,aAAa,IAAI,YAAY,aAAa,CAAC,CAAC,WAAW;CACzD,SAAS,OAAO;EACd,OAAO,OAAO,sCAAuC,MAAgB,SAAS;CAChF;CACA,MAAM,MAAM,KAAK,OAAO,QAAQ,OAAO,KAAK,IAAI,KAAK,GAAI;CACzD,MAAM,uBAAO,IAAI,IAAuB;CACxC,KAAK,MAAM,SAAS,UAAU,MAC5B,IAAI;EACF,MAAM,MAAM,gBAAgB;GAC1B,KAAK;IAAE,KAAK,MAAM;IAAK,KAAK,MAAM;IAAK,GAAG,MAAM;GAAE;GAClD,QAAQ;EACV,CAAC;EACD,KAAK,IAAI,aAAa,MAAM,CAAC,GAAG,GAAG;CACrC,QAAQ,CAER;CAEF,IAAI,OAAuB,OAAO,2CAA2C;CAC7E,KAAK,MAAM,CAAC,OAAO,UAAU,QAAQ;EACnC,IAAI,CAAC,YAAY,KAAK,GAAG;EACzB,MAAM,SAAS,IAAI,IAAI,MAAM,MAAM;EACnC,IAAI,OAAO,IAAI,KAAK,MAAA,gBAAwB;EAC5C,MAAM,QAAQ,OAAO,OAAO,IAAI,OAAO,MAAM,WAAY,OAAO,IAAI,OAAO,IAAe;EAC1F,MAAM,UAAU,MAAM;EACtB,MAAM,QAAQ,QAAQ,KAAK,SAAS,cAAc,IAAI,CAAC;EACvD,MAAM,YAAY,QAAQ,MAAM,SAAS,KAAK,UAAU,iBAAiB;EACzE,IAAI,QAAuB;EAC3B,IAAI,cAAc,KAAA,GAAW;GAC3B,MAAM,QAAQ,eAAe,WAAW,SAAS,MAAM;GACvD,IAAI,WAAW,OACb,IAAI;IACF,MAAM,SAAS,IAAI,YAAY,MAAM,KAAK,CAAC,CAAC,UAAU,CAAC,CAAC;IACxD,QAAQ,OAAO,WAAW,WAAW,SAAS;GAChD,QAAQ;IACN,QAAQ;GACV;EAEJ;EACA,MAAM,QAAQ;GAAE;GAAO;GAAO;EAAM;EACpC,IAAI,IAAI,IAAI,KAAK,CAAC,CAAC,SAAS,MAAM,QAAQ;GACxC,OAAO,OAAO,oCAAoC,KAAK;GACvD;EACF;EACA,IAAI,CAAC,MAAM,SAAS,gBAAc,KAAK,CAAC,MAAM,SAAS,iBAAe,GAAG;GACvE,OAAO,OAAO,0DAA0D,KAAK;GAC7E;EACF;EACA,IAAI,cAAc,KAAA,GAAW;GAC3B,OAAO,OAAO,gDAAgD,KAAK;GACnE;EACF;EACA,MAAM,UAAU,OAAO,IAAI,SAAS;EACpC,MAAM,UAAU,OAAO,IAAI,SAAS;EACpC,IAAI,OAAO,YAAY,YAAY,OAAO,YAAY,UAAU;GAC9D,OAAO,OAAO,8CAA8C,KAAK;GACjE;EACF;EACA,IAAI,WAAW,KAAK;GAClB,OAAO,OAAO,yBAAyB,KAAK;GAC5C;EACF;EACA,IAAI,UAAU,MAAM,cAAc;GAChC,OAAO,OAAO,2CAA2C,KAAK;GAC9D;EACF;EACA,MAAM,MAAM,OAAO,IAAI,KAAK;EAC5B,IAAI,QAAQ,KAAA,KAAa,QAAQ,WAAW;GAC1C,OAAO,OAAO,iBAAiB,cAAc,GAAG,EAAE,kBAAkB,KAAK;GACzE;EACF;EACA,MAAM,MAAM,UAAU,OAAO,KAAA,IAAY,KAAK,IAAI,KAAK;EACvD,IAAI,QAAQ,KAAA,GAAW;GACrB,OAAO,OAAO,uDAAuD,KAAK;GAC1E;EACF;EACA,MAAM,QAAkB,CAAC;EACzB,IAAI;EACJ,KAAK,MAAM,QAAQ,SAAS;GAC1B,MAAM,QAAQ,eAAe,MAAM,SAAS,MAAM;GAClD,IAAI,WAAW,OAAO;IACpB,QAAQ,MAAM;IACd;GACF;GACA,MAAM,KAAK,GAAG,cAAc,IAAI,EAAE,IAAI,MAAM,OAAO;EACrD;EACA,IAAI,UAAU,KAAA,GAAW;GACvB,OAAO,OAAO,OAAO,KAAK;GAC1B;EACF;EACA,MAAM,KAAK,wBAAwB,gBAAgB,KAAK,GAAG;EAC3D,MAAM,YAAY,WAAW,IAAI,KAAK;EACtC,IACE,cAAc,KAAA,KACd,YAAY,SAAS,KACrB,EAAE,UAAU,iBAAiB,aAC7B;GACA,OAAO,OAAO,iDAAiD,SAAS,KAAK;GAC7E;EACF;EAEA,IADaC,OAAY,MAAM,OAAO,KAAK,MAAM,KAAK,IAAI,GAAG,MAAM,GAAG,KAAK,UAAU,KAC9E,GAAG,OAAO;GAAE,UAAU;GAAM;GAAO;GAAO;GAAO,QAAQ;EAAK;EACrE,OAAO,OAAO,mDAAmD,KAAK;CACxE;CACA,OAAO;AACT"}
|