@jini-ai/platform 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/dist/asset-cache.d.ts +80 -0
- package/dist/asset-cache.d.ts.map +1 -0
- package/dist/asset-cache.js +423 -0
- package/dist/asset-cache.js.map +1 -0
- package/dist/aws-sigv4.d.ts +51 -0
- package/dist/aws-sigv4.d.ts.map +1 -0
- package/dist/aws-sigv4.js +104 -0
- package/dist/aws-sigv4.js.map +1 -0
- package/dist/blob-storage.d.ts +89 -0
- package/dist/blob-storage.d.ts.map +1 -0
- package/dist/blob-storage.js +315 -0
- package/dist/blob-storage.js.map +1 -0
- package/dist/command.d.ts +32 -0
- package/dist/command.d.ts.map +1 -0
- package/dist/command.js +101 -0
- package/dist/command.js.map +1 -0
- package/dist/download.d.ts +92 -0
- package/dist/download.d.ts.map +1 -0
- package/dist/download.js +856 -0
- package/dist/download.js.map +1 -0
- package/dist/fs.d.ts +55 -0
- package/dist/fs.d.ts.map +1 -0
- package/dist/fs.js +124 -0
- package/dist/fs.js.map +1 -0
- package/dist/home-expansion.d.ts +18 -0
- package/dist/home-expansion.d.ts.map +1 -0
- package/dist/home-expansion.js +58 -0
- package/dist/home-expansion.js.map +1 -0
- package/dist/http.d.ts +14 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +39 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +45 -0
- package/dist/index.js.map +1 -0
- package/dist/process.d.ts +159 -0
- package/dist/process.d.ts.map +1 -0
- package/dist/process.js +362 -0
- package/dist/process.js.map +1 -0
- package/dist/proxy-env.d.ts +50 -0
- package/dist/proxy-env.d.ts.map +1 -0
- package/dist/proxy-env.js +373 -0
- package/dist/proxy-env.js.map +1 -0
- package/dist/resource-paths.d.ts +68 -0
- package/dist/resource-paths.d.ts.map +1 -0
- package/dist/resource-paths.js +137 -0
- package/dist/resource-paths.js.map +1 -0
- package/dist/sandbox-env.d.ts +86 -0
- package/dist/sandbox-env.d.ts.map +1 -0
- package/dist/sandbox-env.js +215 -0
- package/dist/sandbox-env.js.map +1 -0
- package/dist/shell.d.ts +46 -0
- package/dist/shell.d.ts.map +1 -0
- package/dist/shell.js +78 -0
- package/dist/shell.js.map +1 -0
- package/dist/terminal.d.ts +137 -0
- package/dist/terminal.d.ts.map +1 -0
- package/dist/terminal.js +398 -0
- package/dist/terminal.js.map +1 -0
- package/dist/toolchain.d.ts +17 -0
- package/dist/toolchain.d.ts.map +1 -0
- package/dist/toolchain.js +251 -0
- package/dist/toolchain.js.map +1 -0
- package/package.json +47 -0
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
// Local disk cache + same-origin proxy for external images/videos referenced
|
|
2
|
+
// by sandboxed HTML (an iframe preview, an embedded document, etc.) that a
|
|
3
|
+
// host application renders on cross-origin CDNs.
|
|
4
|
+
//
|
|
5
|
+
// Two problems this solves for that sandboxed content:
|
|
6
|
+
// 1. A CSP like `img-src 'self' data: blob:; media-src 'self' ...` blocks
|
|
7
|
+
// absolute third-party URLs outright inside the sandbox.
|
|
8
|
+
// 2. Even when reachable, those hosts have no nearby edge for many users,
|
|
9
|
+
// so each render re-pays multi-second cross-border latency per asset.
|
|
10
|
+
//
|
|
11
|
+
// A caller rewrites matching URLs (via `assetCacheRewriteUrl`) to a
|
|
12
|
+
// same-origin route it serves from this cache: fetched once, validated,
|
|
13
|
+
// stored content-addressably on disk, and replayed instantly afterwards.
|
|
14
|
+
//
|
|
15
|
+
// SSRF is the load-bearing risk here: the route fetches a caller-supplied URL.
|
|
16
|
+
// `assertSafePublicUrl` rejects non-http(s) schemes, embedded credentials,
|
|
17
|
+
// localhost, and literal private IPs up-front. The authoritative guard against
|
|
18
|
+
// DNS rebinding / TOCTOU is `createValidatingLookup`: it is installed as the
|
|
19
|
+
// undici Agent's connection-time `lookup`, so the address that is *validated*
|
|
20
|
+
// is the exact address the socket *connects to* — there is no separate
|
|
21
|
+
// validation lookup a rebinding resolver could diverge from.
|
|
22
|
+
import { createHash } from 'node:crypto';
|
|
23
|
+
import { lookup as dnsLookupCb } from 'node:dns';
|
|
24
|
+
import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
25
|
+
import { isIP } from 'node:net';
|
|
26
|
+
import path from 'node:path';
|
|
27
|
+
import { Agent } from 'undici';
|
|
28
|
+
/** Media extensions we are willing to cache + proxy. Anything else (HTML,
|
|
29
|
+
* CSS, JS, fonts) is intentionally left alone so this surface never becomes a
|
|
30
|
+
* general-purpose open proxy. */
|
|
31
|
+
export const CACHEABLE_MEDIA_EXT = /\.(?:png|jpe?g|webp|gif|avif|svg|mp4|webm|mov|m4v|ogg|mp3|wav)(?:$|[?#])/i;
|
|
32
|
+
const EXT_TO_MIME = {
|
|
33
|
+
'.png': 'image/png',
|
|
34
|
+
'.jpg': 'image/jpeg',
|
|
35
|
+
'.jpeg': 'image/jpeg',
|
|
36
|
+
'.webp': 'image/webp',
|
|
37
|
+
'.gif': 'image/gif',
|
|
38
|
+
'.avif': 'image/avif',
|
|
39
|
+
'.svg': 'image/svg+xml',
|
|
40
|
+
'.mp4': 'video/mp4',
|
|
41
|
+
'.webm': 'video/webm',
|
|
42
|
+
'.mov': 'video/quicktime',
|
|
43
|
+
'.m4v': 'video/x-m4v',
|
|
44
|
+
'.ogg': 'audio/ogg',
|
|
45
|
+
'.mp3': 'audio/mpeg',
|
|
46
|
+
'.wav': 'audio/wav',
|
|
47
|
+
};
|
|
48
|
+
/** True when `raw` is an absolute http(s) URL pointing at a media file we are
|
|
49
|
+
* willing to route through the cache. Pure + side-effect free so the URL
|
|
50
|
+
* rewriter and tests can share the exact predicate. */
|
|
51
|
+
export function isCacheableExternalUrl(raw) {
|
|
52
|
+
if (typeof raw !== 'string')
|
|
53
|
+
return false;
|
|
54
|
+
const value = raw.trim();
|
|
55
|
+
if (!/^https?:\/\//i.test(value))
|
|
56
|
+
return false;
|
|
57
|
+
// `String#split` always returns a non-empty array (even `''.split(x)` is
|
|
58
|
+
// `['']`), so index 0 is never `undefined` — the `!` narrows a
|
|
59
|
+
// `noUncheckedIndexedAccess` false positive, not a real fallback path.
|
|
60
|
+
const pathOnly = value.split(/[?#]/)[0];
|
|
61
|
+
return CACHEABLE_MEDIA_EXT.test(pathOnly) || CACHEABLE_MEDIA_EXT.test(value);
|
|
62
|
+
}
|
|
63
|
+
/** Map a cacheable external URL to its same-origin proxy URL. `routePath`
|
|
64
|
+
* defaults to `/api/asset-cache` but is caller-configurable since the route
|
|
65
|
+
* is mounted by the host application, not this package. */
|
|
66
|
+
export function assetCacheRewriteUrl(raw, routePath = '/api/asset-cache') {
|
|
67
|
+
return `${routePath}?url=${encodeURIComponent(raw.trim())}`;
|
|
68
|
+
}
|
|
69
|
+
/** Error carrying the HTTP status the route should surface. */
|
|
70
|
+
export class AssetCacheError extends Error {
|
|
71
|
+
status;
|
|
72
|
+
constructor(status, message) {
|
|
73
|
+
super(message);
|
|
74
|
+
this.name = 'AssetCacheError';
|
|
75
|
+
this.status = status;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/** Expand an IPv6 literal (with `::` compression and/or an embedded dotted
|
|
79
|
+
* IPv4 tail) into its eight 16-bit groups, or null if it does not parse.
|
|
80
|
+
* Node's URL parser normalizes mapped literals to hex (`::ffff:127.0.0.1` →
|
|
81
|
+
* `::ffff:7f00:1`), so a string regex can't reliably spot the mapped range —
|
|
82
|
+
* we have to canonicalize.
|
|
83
|
+
*
|
|
84
|
+
* Exported (previously module-private) so its parse-failure guards are
|
|
85
|
+
* directly unit-testable: `isPrivateAddress`'s only real call site gates
|
|
86
|
+
* every call behind `net.isIP(addr) === 6`, and Node's `isIP` already fully
|
|
87
|
+
* validates IPv6 syntax (including embedded-IPv4-tail octet ranges) before
|
|
88
|
+
* returning 6 — empirically re-verified this session by fuzzing ~10k
|
|
89
|
+
* `isIP`-accepted v6 literals through this function's guards with zero
|
|
90
|
+
* hits — so none of these guards are reachable through that path. They stay
|
|
91
|
+
* as real defense-in-depth (fail closed with `null` rather than trust a
|
|
92
|
+
* platform primitive never changing behavior), matching this file's
|
|
93
|
+
* "exported for direct unit testing" precedent (see `readBodyCapped`'s
|
|
94
|
+
* abort parameter). */
|
|
95
|
+
export function expandIpv6(addr) {
|
|
96
|
+
// `String#split` always returns a non-empty array, so index 0 is never
|
|
97
|
+
// `undefined` — the `!` narrows a `noUncheckedIndexedAccess` false
|
|
98
|
+
// positive, not a real fallback path.
|
|
99
|
+
let s = addr.toLowerCase().split('%')[0];
|
|
100
|
+
if (!s.includes(':'))
|
|
101
|
+
return null;
|
|
102
|
+
// Fold a trailing dotted IPv4 (`…:1.2.3.4`) into two hex groups.
|
|
103
|
+
const v4 = /^(.*:)(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/.exec(s);
|
|
104
|
+
if (v4) {
|
|
105
|
+
// `v4[2]` is the regex's second (mandatory, non-optional) capture group
|
|
106
|
+
// of a successful match — always defined.
|
|
107
|
+
const o = v4[2].split('.').map((n) => Number(n));
|
|
108
|
+
if (o.length !== 4 || o.some((n) => !Number.isInteger(n) || n < 0 || n > 255))
|
|
109
|
+
return null;
|
|
110
|
+
// `o` is now a validated 4-element array — indices 0-3 always defined.
|
|
111
|
+
const hi = (((o[0] << 8) | o[1])).toString(16);
|
|
112
|
+
const lo = (((o[2] << 8) | o[3])).toString(16);
|
|
113
|
+
s = `${v4[1]}${hi}:${lo}`;
|
|
114
|
+
}
|
|
115
|
+
const halves = s.split('::');
|
|
116
|
+
if (halves.length > 2)
|
|
117
|
+
return null;
|
|
118
|
+
const parse = (p) => p === '' ? [] : p.split(':').map((g) => (/^[0-9a-f]{1,4}$/.test(g) ? parseInt(g, 16) : Number.NaN));
|
|
119
|
+
// `halves` always has at least one element (split guarantee).
|
|
120
|
+
const left = parse(halves[0]);
|
|
121
|
+
let groups;
|
|
122
|
+
if (halves.length === 1) {
|
|
123
|
+
groups = left;
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
// `halves.length === 2` here (the `> 2` case already returned above).
|
|
127
|
+
const right = parse(halves[1]);
|
|
128
|
+
const fill = 8 - left.length - right.length;
|
|
129
|
+
if (fill < 1)
|
|
130
|
+
return null;
|
|
131
|
+
groups = [...left, ...new Array(fill).fill(0), ...right];
|
|
132
|
+
}
|
|
133
|
+
if (groups.length !== 8 || groups.some((g) => Number.isNaN(g) || g < 0 || g > 0xffff))
|
|
134
|
+
return null;
|
|
135
|
+
return groups;
|
|
136
|
+
}
|
|
137
|
+
/** True for any address an external CDN must never resolve to: loopback,
|
|
138
|
+
* link-local, private, CGNAT, ULA, multicast, or unspecified. An unparseable
|
|
139
|
+
* input is treated as unsafe. */
|
|
140
|
+
export function isPrivateAddress(addr) {
|
|
141
|
+
const fam = isIP(addr);
|
|
142
|
+
if (fam === 4) {
|
|
143
|
+
// `net.isIP(addr) === 4` already guarantees `addr` is a syntactically
|
|
144
|
+
// valid dotted-quad with four 0-255 decimal octets (no leading-zero
|
|
145
|
+
// octal ambiguity, no out-of-range component) — empirically re-verified
|
|
146
|
+
// this session by fuzzing 500k+ candidate strings through `isIP` and
|
|
147
|
+
// this same re-derivation with zero divergences. `o` is therefore always
|
|
148
|
+
// a validated 4-element array.
|
|
149
|
+
const o = addr.split('.').map((n) => Number(n));
|
|
150
|
+
const a = o[0];
|
|
151
|
+
const b = o[1];
|
|
152
|
+
if (a === 0)
|
|
153
|
+
return true; // "this" network / unspecified
|
|
154
|
+
if (a === 10)
|
|
155
|
+
return true; // private
|
|
156
|
+
if (a === 127)
|
|
157
|
+
return true; // loopback
|
|
158
|
+
if (a === 169 && b === 254)
|
|
159
|
+
return true; // link-local
|
|
160
|
+
if (a === 172 && b >= 16 && b <= 31)
|
|
161
|
+
return true; // private
|
|
162
|
+
if (a === 192 && b === 168)
|
|
163
|
+
return true; // private
|
|
164
|
+
if (a === 100 && b >= 64 && b <= 127)
|
|
165
|
+
return true; // CGNAT
|
|
166
|
+
if (a >= 224)
|
|
167
|
+
return true; // multicast + reserved
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
if (fam === 6) {
|
|
171
|
+
const groups = expandIpv6(addr);
|
|
172
|
+
// `net.isIP(addr) === 6` already guarantees `addr` is syntactically valid
|
|
173
|
+
// IPv6 (Node validates this before classifying the family), and
|
|
174
|
+
// `expandIpv6` implements the same canonicalization Node's own parser
|
|
175
|
+
// does, so it never returns `null` for an `isIP`-accepted literal —
|
|
176
|
+
// empirically re-verified this session by fuzzing ~10k `isIP`-accepted
|
|
177
|
+
// v6 literals through `expandIpv6` with zero `null` results (see
|
|
178
|
+
// `expandIpv6`'s doc comment and `source-map.md`'s 2026-07-22 entry).
|
|
179
|
+
// Kept as a real fail-closed guard (defense-in-depth against a future
|
|
180
|
+
// change to either parser) rather than asserted away, so it stays
|
|
181
|
+
// provably-unreachable-but-intentional instead of a forced test.
|
|
182
|
+
if (!groups)
|
|
183
|
+
return true; // can't canonicalize a "valid" v6 → refuse
|
|
184
|
+
// IPv4-mapped (`::ffff:0:0/96`) and the deprecated IPv4-compatible
|
|
185
|
+
// (`::0:0/96`, excluding :: / ::1) carry an embedded IPv4 — classify it by
|
|
186
|
+
// its real v4 range so `::ffff:7f00:1` (== 127.0.0.1) is caught.
|
|
187
|
+
const topZero = groups.slice(0, 5).every((g) => g === 0);
|
|
188
|
+
if (topZero && (groups[5] === 0xffff || groups[5] === 0)) {
|
|
189
|
+
// `groups` is a validated 8-element array once non-null — indices 6-7
|
|
190
|
+
// always defined.
|
|
191
|
+
const a = groups[6] >> 8;
|
|
192
|
+
const b = groups[6] & 0xff;
|
|
193
|
+
const c = groups[7] >> 8;
|
|
194
|
+
const d = groups[7] & 0xff;
|
|
195
|
+
if (groups[5] === 0xffff || a !== 0 || b !== 0) {
|
|
196
|
+
return isPrivateAddress(`${a}.${b}.${c}.${d}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (groups.every((g) => g === 0))
|
|
200
|
+
return true; // :: (unspecified)
|
|
201
|
+
if (groups.slice(0, 7).every((g) => g === 0) && groups[7] === 1)
|
|
202
|
+
return true; // ::1 loopback
|
|
203
|
+
const first = groups[0];
|
|
204
|
+
if ((first & 0xffc0) === 0xfe80)
|
|
205
|
+
return true; // link-local fe80::/10 (fe80–febf)
|
|
206
|
+
if ((first & 0xfe00) === 0xfc00)
|
|
207
|
+
return true; // unique-local fc00::/7
|
|
208
|
+
if ((first & 0xff00) === 0xff00)
|
|
209
|
+
return true; // multicast ff00::/8
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
return true; // not a literal IP — caller must resolve before trusting it
|
|
213
|
+
}
|
|
214
|
+
/** Cheap up-front rejection: enforce http(s), no embedded credentials, no
|
|
215
|
+
* localhost, and no *literal* private IP. This is NOT the DNS-rebinding guard
|
|
216
|
+
* (that is `createValidatingLookup`, applied at connection time) — it only
|
|
217
|
+
* fast-fails the obvious cases before any socket is opened. */
|
|
218
|
+
export function assertSafePublicUrl(raw) {
|
|
219
|
+
let url;
|
|
220
|
+
try {
|
|
221
|
+
url = new URL(raw);
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
throw new AssetCacheError(400, 'invalid url');
|
|
225
|
+
}
|
|
226
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
227
|
+
throw new AssetCacheError(400, 'unsupported url scheme');
|
|
228
|
+
}
|
|
229
|
+
if (url.username || url.password) {
|
|
230
|
+
throw new AssetCacheError(400, 'url credentials are not allowed');
|
|
231
|
+
}
|
|
232
|
+
const host = url.hostname.replace(/^\[|\]$/g, '');
|
|
233
|
+
const lowerHost = host.toLowerCase();
|
|
234
|
+
if (lowerHost === 'localhost' || lowerHost.endsWith('.localhost')) {
|
|
235
|
+
throw new AssetCacheError(400, 'localhost is not allowed');
|
|
236
|
+
}
|
|
237
|
+
if (isIP(host) && isPrivateAddress(host)) {
|
|
238
|
+
throw new AssetCacheError(400, 'url points at a private address');
|
|
239
|
+
}
|
|
240
|
+
return url;
|
|
241
|
+
}
|
|
242
|
+
/** Wrap a `dns.lookup`-shaped resolver so the resolved address is rejected when
|
|
243
|
+
* it is private. Installed as the undici Agent's connection-time `lookup`, so
|
|
244
|
+
* the validated address IS the one the socket connects to — closing the
|
|
245
|
+
* DNS-rebinding / TOCTOU gap a separate pre-validation lookup would leave open.
|
|
246
|
+
* Exported so the guard can be unit-tested without standing up a server. */
|
|
247
|
+
export function createValidatingLookup(lookupImpl = dnsLookupCb) {
|
|
248
|
+
return function validatingLookup(hostname, options, callback) {
|
|
249
|
+
const cb = (typeof options === 'function' ? options : callback);
|
|
250
|
+
const opts = (typeof options === 'function' ? {} : (options ?? {}));
|
|
251
|
+
lookupImpl(hostname, opts, (err, address, family) => {
|
|
252
|
+
if (err)
|
|
253
|
+
return cb(err);
|
|
254
|
+
const list = Array.isArray(address) ? address : [{ address, family }];
|
|
255
|
+
for (const entry of list) {
|
|
256
|
+
const addr = typeof entry === 'string' ? entry : entry.address;
|
|
257
|
+
if (isPrivateAddress(addr)) {
|
|
258
|
+
return cb(new AssetCacheError(400, 'host resolves to a private address'));
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return cb(null, address, family);
|
|
262
|
+
});
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
/** Build a disk-backed cache for external sandboxed-content media. Concurrent
|
|
266
|
+
* requests for the same URL share one in-flight fetch; completed fetches
|
|
267
|
+
* persist to `<cacheDir>/<sha256>` (+ `.json` sidecar) and are replayed from
|
|
268
|
+
* disk. */
|
|
269
|
+
export function createAssetCache(opts) {
|
|
270
|
+
const cacheDir = opts.cacheDir;
|
|
271
|
+
const maxBytes = opts.maxBytes ?? 64 * 1024 * 1024;
|
|
272
|
+
const timeoutMs = opts.fetchTimeoutMs ?? 15_000;
|
|
273
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
274
|
+
// Pin SSRF validation to the actual outbound connection: the Agent resolves
|
|
275
|
+
// the host once, and `validatingLookup` rejects the connection if that exact
|
|
276
|
+
// address is private. No separate pre-validation lookup to rebind around.
|
|
277
|
+
const dispatcher = new Agent({
|
|
278
|
+
connect: { lookup: createValidatingLookup(opts.lookupImpl) },
|
|
279
|
+
});
|
|
280
|
+
const inflight = new Map();
|
|
281
|
+
function keyFor(rawUrl) {
|
|
282
|
+
return createHash('sha256').update(rawUrl).digest('hex');
|
|
283
|
+
}
|
|
284
|
+
async function readFromDisk(key) {
|
|
285
|
+
const blobPath = path.join(cacheDir, key);
|
|
286
|
+
const metaPath = `${blobPath}.json`;
|
|
287
|
+
try {
|
|
288
|
+
const [buf, metaRaw] = await Promise.all([readFile(blobPath), readFile(metaPath, 'utf8')]);
|
|
289
|
+
const meta = JSON.parse(metaRaw);
|
|
290
|
+
const contentType = typeof meta.contentType === 'string' && meta.contentType ? meta.contentType : 'application/octet-stream';
|
|
291
|
+
return { buf, contentType };
|
|
292
|
+
}
|
|
293
|
+
catch {
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
async function writeToDisk(key, result) {
|
|
298
|
+
await mkdir(cacheDir, { recursive: true });
|
|
299
|
+
const blobPath = path.join(cacheDir, key);
|
|
300
|
+
const tmpPath = `${blobPath}.${process.pid}.tmp`;
|
|
301
|
+
await writeFile(tmpPath, result.buf);
|
|
302
|
+
await rename(tmpPath, blobPath);
|
|
303
|
+
await writeFile(`${blobPath}.json`, JSON.stringify({ contentType: result.contentType }));
|
|
304
|
+
}
|
|
305
|
+
function resolveContentType(headerValue, url) {
|
|
306
|
+
// `(headerValue ?? '')` is reachable (no content-type header at all). The
|
|
307
|
+
// trailing `?? ''` after the optional-chained `.split(';')[0]?.trim()` is
|
|
308
|
+
// not: `String#split` always returns a non-empty array, so `[0]` is never
|
|
309
|
+
// `undefined` and `?.` never short-circuits — kept for
|
|
310
|
+
// `noUncheckedIndexedAccess`, not a real fallback path.
|
|
311
|
+
const header = (headerValue ?? '').split(';')[0].trim().toLowerCase();
|
|
312
|
+
if (/^(?:image|video|audio)\//.test(header))
|
|
313
|
+
return header;
|
|
314
|
+
const ext = path.posix.extname(url.pathname).toLowerCase();
|
|
315
|
+
const guessed = EXT_TO_MIME[ext];
|
|
316
|
+
if (guessed)
|
|
317
|
+
return guessed;
|
|
318
|
+
throw new AssetCacheError(415, `unsupported content-type: ${header || 'unknown'}`);
|
|
319
|
+
}
|
|
320
|
+
/** Drain the body, aborting the moment the accumulated size exceeds
|
|
321
|
+
* `maxBytes`. A response without a trustworthy Content-Length must never be
|
|
322
|
+
* fully buffered first — that would turn this caller-supplied proxy into a
|
|
323
|
+
* memory-exhaustion path. */
|
|
324
|
+
async function readBodyCapped(response, controller) {
|
|
325
|
+
const body = response.body;
|
|
326
|
+
if (!body) {
|
|
327
|
+
const ab = await response.arrayBuffer();
|
|
328
|
+
if (ab.byteLength > maxBytes)
|
|
329
|
+
throw new AssetCacheError(413, 'asset exceeds size limit');
|
|
330
|
+
return Buffer.from(ab);
|
|
331
|
+
}
|
|
332
|
+
const reader = body.getReader();
|
|
333
|
+
const chunks = [];
|
|
334
|
+
let total = 0;
|
|
335
|
+
try {
|
|
336
|
+
for (;;) {
|
|
337
|
+
const { done, value } = await reader.read();
|
|
338
|
+
if (done)
|
|
339
|
+
break;
|
|
340
|
+
if (!value)
|
|
341
|
+
continue;
|
|
342
|
+
total += value.byteLength;
|
|
343
|
+
if (total > maxBytes) {
|
|
344
|
+
controller.abort(); // stop pulling more bytes from upstream
|
|
345
|
+
throw new AssetCacheError(413, 'asset exceeds size limit');
|
|
346
|
+
}
|
|
347
|
+
chunks.push(value);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
finally {
|
|
351
|
+
try {
|
|
352
|
+
await reader.cancel();
|
|
353
|
+
}
|
|
354
|
+
catch {
|
|
355
|
+
// reader already closed / errored — nothing to release
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return Buffer.concat(chunks);
|
|
359
|
+
}
|
|
360
|
+
async function fetchAndStore(rawUrl, key) {
|
|
361
|
+
const url = assertSafePublicUrl(rawUrl);
|
|
362
|
+
const controller = new AbortController();
|
|
363
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
364
|
+
try {
|
|
365
|
+
let response;
|
|
366
|
+
try {
|
|
367
|
+
// `dispatcher` carries the connection-time SSRF guard. It is an undici
|
|
368
|
+
// extension of RequestInit; attach it at runtime to avoid the
|
|
369
|
+
// undici-types (bundled with @types/node) vs undici@7 Dispatcher version
|
|
370
|
+
// skew that a typed field would trip over.
|
|
371
|
+
const init = { redirect: 'error', signal: controller.signal };
|
|
372
|
+
init.dispatcher = dispatcher;
|
|
373
|
+
response = await fetchImpl(url, init);
|
|
374
|
+
}
|
|
375
|
+
catch (err) {
|
|
376
|
+
throw new AssetCacheError(502, `fetch failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
377
|
+
}
|
|
378
|
+
if (!response.ok) {
|
|
379
|
+
throw new AssetCacheError(502, `upstream responded ${response.status}`);
|
|
380
|
+
}
|
|
381
|
+
const declaredLength = Number(response.headers.get('content-length') ?? '');
|
|
382
|
+
if (Number.isFinite(declaredLength) && declaredLength > maxBytes) {
|
|
383
|
+
throw new AssetCacheError(413, 'asset exceeds size limit');
|
|
384
|
+
}
|
|
385
|
+
const contentType = resolveContentType(response.headers.get('content-type'), url);
|
|
386
|
+
const buf = await readBodyCapped(response, controller);
|
|
387
|
+
const result = { buf, contentType };
|
|
388
|
+
try {
|
|
389
|
+
await writeToDisk(key, result);
|
|
390
|
+
}
|
|
391
|
+
catch {
|
|
392
|
+
// A cache-write failure must not fail the request; serve from memory.
|
|
393
|
+
}
|
|
394
|
+
return result;
|
|
395
|
+
}
|
|
396
|
+
finally {
|
|
397
|
+
clearTimeout(timer);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
async function get(rawUrl) {
|
|
401
|
+
if (!isCacheableExternalUrl(rawUrl)) {
|
|
402
|
+
throw new AssetCacheError(400, 'url is not a cacheable external media url');
|
|
403
|
+
}
|
|
404
|
+
const key = keyFor(rawUrl.trim());
|
|
405
|
+
const cached = await readFromDisk(key);
|
|
406
|
+
if (cached)
|
|
407
|
+
return cached;
|
|
408
|
+
const existing = inflight.get(key);
|
|
409
|
+
if (existing)
|
|
410
|
+
return existing;
|
|
411
|
+
const pending = fetchAndStore(rawUrl.trim(), key).finally(() => {
|
|
412
|
+
inflight.delete(key);
|
|
413
|
+
});
|
|
414
|
+
inflight.set(key, pending);
|
|
415
|
+
return pending;
|
|
416
|
+
}
|
|
417
|
+
return { get };
|
|
418
|
+
}
|
|
419
|
+
/** Exposed for tests that need to assert the on-disk key layout. */
|
|
420
|
+
export function assetCacheKey(rawUrl) {
|
|
421
|
+
return createHash('sha256').update(rawUrl.trim()).digest('hex');
|
|
422
|
+
}
|
|
423
|
+
//# sourceMappingURL=asset-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asset-cache.js","sourceRoot":"","sources":["../src/asset-cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** AWS credentials used to derive a SigV4 signature. */
|
|
2
|
+
export interface SigV4Credentials {
|
|
3
|
+
accessKeyId: string;
|
|
4
|
+
secretAccessKey: string;
|
|
5
|
+
sessionToken?: string;
|
|
6
|
+
}
|
|
7
|
+
/** Input to {@link signSigV4}. */
|
|
8
|
+
export interface SignSigV4Input {
|
|
9
|
+
method: string;
|
|
10
|
+
/**
|
|
11
|
+
* Canonical path (URI-encoded but with `/` preserved between segments).
|
|
12
|
+
* For S3 object keys, the caller must double-encode RFC-3986-reserved
|
|
13
|
+
* chars beyond the canonical pass.
|
|
14
|
+
*/
|
|
15
|
+
path: string;
|
|
16
|
+
/** Already-stringified canonical query (sorted `key=value` join). Pass `''` when the request has no query. */
|
|
17
|
+
query: string;
|
|
18
|
+
/** Mutable lower-case header map. The signer adds the four amz headers and the Authorization header. */
|
|
19
|
+
headers: Record<string, string>;
|
|
20
|
+
/** Raw body bytes; pass an empty Buffer for no-body requests. */
|
|
21
|
+
body: Buffer;
|
|
22
|
+
region: string;
|
|
23
|
+
service: string;
|
|
24
|
+
credentials: SigV4Credentials;
|
|
25
|
+
/** ISO-8601 UTC datetime used for the signature. Defaults to `new Date()`. */
|
|
26
|
+
now?: Date;
|
|
27
|
+
}
|
|
28
|
+
/** Result of {@link signSigV4} — the same values written into `input.headers`, surfaced directly for convenience. */
|
|
29
|
+
export interface SignSigV4Result {
|
|
30
|
+
authorization: string;
|
|
31
|
+
amzDate: string;
|
|
32
|
+
contentSha256: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Signs an HTTP request per the AWS Signature Version 4 recipe, mutating
|
|
36
|
+
* `input.headers` in place and returning the same signature fields.
|
|
37
|
+
*
|
|
38
|
+
* @param input - Method/path/query/headers/body plus region, service, and credentials.
|
|
39
|
+
* @returns The `authorization` header value, the `x-amz-date` used, and the body's SHA-256 hex digest.
|
|
40
|
+
*/
|
|
41
|
+
export declare function signSigV4(input: SignSigV4Input): SignSigV4Result;
|
|
42
|
+
/**
|
|
43
|
+
* URI-encodes a path segment per RFC 3986, with the AWS twist that reserved
|
|
44
|
+
* chars outside the unreserved set (`!'()*`) get `%HH`-encoded even though
|
|
45
|
+
* `encodeURIComponent` leaves them alone.
|
|
46
|
+
*
|
|
47
|
+
* @param seg - A single path segment (no `/`).
|
|
48
|
+
* @returns The RFC-3986/AWS-encoded segment.
|
|
49
|
+
*/
|
|
50
|
+
export declare function encodeS3PathSegment(seg: string): string;
|
|
51
|
+
//# sourceMappingURL=aws-sigv4.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aws-sigv4.d.ts","sourceRoot":"","sources":["../src/aws-sigv4.ts"],"names":[],"mappings":"AA2BA,wDAAwD;AACxD,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,kCAAkC;AAClC,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,8GAA8G;IAC9G,KAAK,EAAE,MAAM,CAAC;IACd,wGAAwG;IACxG,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,iEAAiE;IACjE,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,gBAAgB,CAAC;IAC9B,8EAA8E;IAC9E,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ;AAED,qHAAqH;AACrH,MAAM,WAAW,eAAe;IAC9B,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,eAAe,CAmDhE;AAoBD;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEvD"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module aws-sigv4
|
|
3
|
+
*
|
|
4
|
+
* Minimal AWS Signature V4 signer, implementing the spec'd canonical-request /
|
|
5
|
+
* string-to-sign / signing-key / authorization recipe with only `node:crypto`.
|
|
6
|
+
* No `@aws-sdk/*` dependency is pulled in — a host daemon that only needs to
|
|
7
|
+
* sign a handful of S3-compatible requests stays free of an extra 60+ MB of
|
|
8
|
+
* SDK code on disk.
|
|
9
|
+
*
|
|
10
|
+
* References:
|
|
11
|
+
* https://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html
|
|
12
|
+
* https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html
|
|
13
|
+
*
|
|
14
|
+
* Caller passes the HTTP method + path + query + headers + body, the region +
|
|
15
|
+
* service (e.g. `'s3'`) + AWS access key + secret key, an optional
|
|
16
|
+
* `sessionToken` (for STS-vended creds), and an optional `now` override so
|
|
17
|
+
* tests can pin deterministic signatures.
|
|
18
|
+
*
|
|
19
|
+
* The signer mutates the supplied headers map by adding `authorization`,
|
|
20
|
+
* `x-amz-date`, `x-amz-content-sha256`, and (when supplied)
|
|
21
|
+
* `x-amz-security-token`. The caller is responsible for setting `host`.
|
|
22
|
+
*
|
|
23
|
+
* Path-style vs virtual-host-style: this signer does not care; the caller
|
|
24
|
+
* chooses by setting `host` + path appropriately.
|
|
25
|
+
*/
|
|
26
|
+
import { createHash, createHmac } from 'node:crypto';
|
|
27
|
+
/**
|
|
28
|
+
* Signs an HTTP request per the AWS Signature Version 4 recipe, mutating
|
|
29
|
+
* `input.headers` in place and returning the same signature fields.
|
|
30
|
+
*
|
|
31
|
+
* @param input - Method/path/query/headers/body plus region, service, and credentials.
|
|
32
|
+
* @returns The `authorization` header value, the `x-amz-date` used, and the body's SHA-256 hex digest.
|
|
33
|
+
*/
|
|
34
|
+
export function signSigV4(input) {
|
|
35
|
+
const now = input.now ?? new Date();
|
|
36
|
+
const amzDate = formatAmzDate(now);
|
|
37
|
+
const dateStamp = amzDate.slice(0, 8);
|
|
38
|
+
const contentSha256 = sha256Hex(input.body);
|
|
39
|
+
// Add the required amz headers BEFORE composing the canonical request, since they're part of
|
|
40
|
+
// the signed-headers list.
|
|
41
|
+
input.headers['x-amz-date'] = amzDate;
|
|
42
|
+
input.headers['x-amz-content-sha256'] = contentSha256;
|
|
43
|
+
if (input.credentials.sessionToken) {
|
|
44
|
+
input.headers['x-amz-security-token'] = input.credentials.sessionToken;
|
|
45
|
+
}
|
|
46
|
+
// Canonical headers: lower-case keys + trim values; sort by key.
|
|
47
|
+
const headerKeys = Object.keys(input.headers)
|
|
48
|
+
.map((k) => k.toLowerCase())
|
|
49
|
+
.sort();
|
|
50
|
+
const canonicalHeaders = headerKeys
|
|
51
|
+
.map((k) => `${k}:${trimValue(input.headers[k] ?? input.headers[k.toLowerCase()] ?? '')}\n`)
|
|
52
|
+
.join('');
|
|
53
|
+
const signedHeaders = headerKeys.join(';');
|
|
54
|
+
const canonicalRequest = [
|
|
55
|
+
input.method.toUpperCase(),
|
|
56
|
+
input.path,
|
|
57
|
+
input.query,
|
|
58
|
+
canonicalHeaders,
|
|
59
|
+
signedHeaders,
|
|
60
|
+
contentSha256,
|
|
61
|
+
].join('\n');
|
|
62
|
+
const credentialScope = `${dateStamp}/${input.region}/${input.service}/aws4_request`;
|
|
63
|
+
const stringToSign = [
|
|
64
|
+
'AWS4-HMAC-SHA256',
|
|
65
|
+
amzDate,
|
|
66
|
+
credentialScope,
|
|
67
|
+
sha256Hex(Buffer.from(canonicalRequest, 'utf8')),
|
|
68
|
+
].join('\n');
|
|
69
|
+
// Derive the signing key.
|
|
70
|
+
const kDate = hmac(`AWS4${input.credentials.secretAccessKey}`, dateStamp);
|
|
71
|
+
const kRegion = hmac(kDate, input.region);
|
|
72
|
+
const kService = hmac(kRegion, input.service);
|
|
73
|
+
const kSigning = hmac(kService, 'aws4_request');
|
|
74
|
+
const signature = createHmac('sha256', kSigning).update(stringToSign).digest('hex');
|
|
75
|
+
const authorization = `AWS4-HMAC-SHA256 Credential=${input.credentials.accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
|
|
76
|
+
input.headers['authorization'] = authorization;
|
|
77
|
+
return { authorization, amzDate, contentSha256 };
|
|
78
|
+
}
|
|
79
|
+
function sha256Hex(body) {
|
|
80
|
+
return createHash('sha256').update(body).digest('hex');
|
|
81
|
+
}
|
|
82
|
+
function hmac(key, value) {
|
|
83
|
+
return createHmac('sha256', key).update(value).digest();
|
|
84
|
+
}
|
|
85
|
+
function trimValue(v) {
|
|
86
|
+
return v.trim().replace(/\s+/g, ' ');
|
|
87
|
+
}
|
|
88
|
+
function formatAmzDate(d) {
|
|
89
|
+
const iso = d.toISOString().replace(/[-:]/g, '');
|
|
90
|
+
// ISO is YYYYMMDDTHHmmss.sssZ; AWS expects YYYYMMDDTHHMMSSZ.
|
|
91
|
+
return `${iso.slice(0, 15)}Z`;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* URI-encodes a path segment per RFC 3986, with the AWS twist that reserved
|
|
95
|
+
* chars outside the unreserved set (`!'()*`) get `%HH`-encoded even though
|
|
96
|
+
* `encodeURIComponent` leaves them alone.
|
|
97
|
+
*
|
|
98
|
+
* @param seg - A single path segment (no `/`).
|
|
99
|
+
* @returns The RFC-3986/AWS-encoded segment.
|
|
100
|
+
*/
|
|
101
|
+
export function encodeS3PathSegment(seg) {
|
|
102
|
+
return encodeURIComponent(seg).replace(/[!'()*]/g, (c) => '%' + c.charCodeAt(0).toString(16).toUpperCase());
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=aws-sigv4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aws-sigv4.js","sourceRoot":"","sources":["../src/aws-sigv4.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAsCrD;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAC,KAAqB;IAC7C,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC;IACpC,MAAM,OAAO,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;IACnC,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACtC,MAAM,aAAa,GAAG,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAE5C,6FAA6F;IAC7F,2BAA2B;IAC3B,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,GAAG,OAAO,CAAC;IACtC,KAAK,CAAC,OAAO,CAAC,sBAAsB,CAAC,GAAG,aAAa,CAAC;IACtD,IAAI,KAAK,CAAC,WAAW,CAAC,YAAY,EAAE,CAAC;QACnC,KAAK,CAAC,OAAO,CAAC,sBAAsB,CAAC,GAAG,KAAK,CAAC,WAAW,CAAC,YAAY,CAAC;IACzE,CAAC;IAED,iEAAiE;IACjE,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC;SAC1C,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;SAC3B,IAAI,EAAE,CAAC;IACV,MAAM,gBAAgB,GAAG,UAAU;SAChC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC;SAC3F,IAAI,CAAC,EAAE,CAAC,CAAC;IACZ,MAAM,aAAa,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAE3C,MAAM,gBAAgB,GAAG;QACvB,KAAK,CAAC,MAAM,CAAC,WAAW,EAAE;QAC1B,KAAK,CAAC,IAAI;QACV,KAAK,CAAC,KAAK;QACX,gBAAgB;QAChB,aAAa;QACb,aAAa;KACd,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEb,MAAM,eAAe,GAAG,GAAG,SAAS,IAAI,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,OAAO,eAAe,CAAC;IACrF,MAAM,YAAY,GAAG;QACnB,kBAAkB;QAClB,OAAO;QACP,eAAe;QACf,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC;KACjD,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEb,0BAA0B;IAC1B,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,KAAK,CAAC,WAAW,CAAC,eAAe,EAAE,EAAE,SAAS,CAAC,CAAC;IAC1E,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC1C,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IAC9C,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;IAEhD,MAAM,SAAS,GAAG,UAAU,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACpF,MAAM,aAAa,GAAG,+BAA+B,KAAK,CAAC,WAAW,CAAC,WAAW,IAAI,eAAe,mBAAmB,aAAa,eAAe,SAAS,EAAE,CAAC;IAChK,KAAK,CAAC,OAAO,CAAC,eAAe,CAAC,GAAG,aAAa,CAAC;IAE/C,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC;AACnD,CAAC;AAED,SAAS,SAAS,CAAC,IAAY;IAC7B,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACzD,CAAC;AAED,SAAS,IAAI,CAAC,GAAoB,EAAE,KAAa;IAC/C,OAAO,UAAU,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC;AAC1D,CAAC;AAED,SAAS,SAAS,CAAC,CAAS;IAC1B,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AACvC,CAAC;AAED,SAAS,aAAa,CAAC,CAAO;IAC5B,MAAM,GAAG,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IACjD,6DAA6D;IAC7D,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC;AAChC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAAW;IAC7C,OAAO,kBAAkB,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;AAC9G,CAAC"}
|