@kernhq/module-quire 0.14.0 → 0.16.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.
Files changed (74) hide show
  1. package/dist/contract/models.d.ts +209 -0
  2. package/dist/contract/models.d.ts.map +1 -1
  3. package/dist/contract/models.js +151 -0
  4. package/dist/contract/models.js.map +1 -1
  5. package/dist/contract/permissions.d.ts.map +1 -1
  6. package/dist/contract/permissions.js +32 -0
  7. package/dist/contract/permissions.js.map +1 -1
  8. package/dist/contract/properties.d.ts +3 -3
  9. package/dist/contract/router.d.ts +509 -8
  10. package/dist/contract/router.d.ts.map +1 -1
  11. package/dist/contract/router.js +211 -1
  12. package/dist/contract/router.js.map +1 -1
  13. package/dist/server/_impl.d.ts +573 -1269
  14. package/dist/server/_impl.d.ts.map +1 -1
  15. package/dist/server/_impl.js +180 -0
  16. package/dist/server/_impl.js.map +1 -1
  17. package/dist/server/export/markdown.d.ts.map +1 -1
  18. package/dist/server/export/markdown.js +84 -1
  19. package/dist/server/export/markdown.js.map +1 -1
  20. package/dist/server/render.d.ts +190 -1
  21. package/dist/server/render.d.ts.map +1 -1
  22. package/dist/server/render.js +413 -0
  23. package/dist/server/render.js.map +1 -1
  24. package/dist/server/schema.d.ts +260 -1
  25. package/dist/server/schema.d.ts.map +1 -1
  26. package/dist/server/schema.js +108 -1
  27. package/dist/server/schema.js.map +1 -1
  28. package/dist/server/services/databases.d.ts +5 -5
  29. package/dist/server/services/index.d.ts +6 -0
  30. package/dist/server/services/index.d.ts.map +1 -1
  31. package/dist/server/services/index.js +27 -3
  32. package/dist/server/services/index.js.map +1 -1
  33. package/dist/server/services/macros.d.ts +83 -0
  34. package/dist/server/services/macros.d.ts.map +1 -0
  35. package/dist/server/services/macros.js +488 -0
  36. package/dist/server/services/macros.js.map +1 -0
  37. package/dist/server/services/objects.d.ts +61 -0
  38. package/dist/server/services/objects.d.ts.map +1 -0
  39. package/dist/server/services/objects.js +110 -0
  40. package/dist/server/services/objects.js.map +1 -0
  41. package/dist/server/services/publications.d.ts +2 -1
  42. package/dist/server/services/publications.d.ts.map +1 -1
  43. package/dist/server/services/publications.js +43 -4
  44. package/dist/server/services/publications.js.map +1 -1
  45. package/dist/server/services/templates.d.ts +135 -0
  46. package/dist/server/services/templates.d.ts.map +1 -0
  47. package/dist/server/services/templates.js +897 -0
  48. package/dist/server/services/templates.js.map +1 -0
  49. package/dist/server/services/unfurl.d.ts +154 -0
  50. package/dist/server/services/unfurl.d.ts.map +1 -0
  51. package/dist/server/services/unfurl.js +593 -0
  52. package/dist/server/services/unfurl.js.map +1 -0
  53. package/dist/server/services/versions.d.ts +12 -0
  54. package/dist/server/services/versions.d.ts.map +1 -1
  55. package/dist/server/services/versions.js +3 -1
  56. package/dist/server/services/versions.js.map +1 -1
  57. package/migrations/0011_templates.sql +157 -0
  58. package/migrations/meta/_journal.json +7 -0
  59. package/package.json +5 -5
  60. package/src/client/components/NewSpaceDialog.svelte +77 -8
  61. package/src/client/components/PageEditor.svelte +80 -0
  62. package/src/client/components/PagePicker.svelte +264 -0
  63. package/src/client/components/SaveAsTemplateDialog.svelte +502 -0
  64. package/src/client/components/SidebarSpaces.svelte +33 -1
  65. package/src/client/components/TemplatePicker.svelte +437 -0
  66. package/src/client/i18n.ts +327 -0
  67. package/src/client/index.ts +19 -0
  68. package/src/client/mock.ts +274 -0
  69. package/src/client/pages/PageView.svelte +50 -0
  70. package/src/client/pages/SpacePage.svelte +20 -4
  71. package/src/client/query.ts +17 -0
  72. package/src/contract/models.ts +191 -0
  73. package/src/contract/permissions.ts +33 -0
  74. package/src/contract/router.ts +228 -0
@@ -0,0 +1,593 @@
1
+ /**
2
+ * The server fetching a URL somebody typed into a page.
3
+ *
4
+ * This is the most dangerous thing in the module, and it is worth saying why before anything else.
5
+ * Core listens on :4000 and Postgres on :5432 **on the same host as this process**, and a cloud
6
+ * instance sits behind a metadata service at 169.254.169.254 that hands out credentials to anyone
7
+ * who asks. A fetcher that will retrieve any address a user supplies is a way to make Kern read all
8
+ * of that on the attacker's behalf and hand back what it found — which is what "unfurl this link"
9
+ * would be, written the obvious way.
10
+ *
11
+ * So the defences come first and the feature second. In order, and every one of them is load-bearing:
12
+ *
13
+ * 1. **Scheme.** `http:` and `https:` only. `file:`, `gopher:` and `data:` are not addresses on
14
+ * the web, and a redirect to one is a redirect out of this function's assumptions.
15
+ * 2. **No credentials in the URL.** `http://user:pass@host/` sends those to the host, and a URL
16
+ * with them in is either an accident or a way to reach something that asked for them.
17
+ * 3. **Resolve, then judge.** The hostname is resolved and *every* address it answers with has to
18
+ * be public. `evil.example` resolving to 127.0.0.1 is the ordinary way past a name-based check,
19
+ * and a name-based check is the only kind that can run before DNS.
20
+ * 4. **Then the allow-list**, on the hostname, *after* the address check rather than instead of
21
+ * it. Order matters: an allow-list entry must never be a way *past* the address rule, so a host
22
+ * an administrator allowed which today resolves into private space is still refused.
23
+ * 5. **The connection is pinned to the addresses that were checked.** The guard is installed as
24
+ * the socket's own `lookup`, so there is no second resolution between the check and the
25
+ * connection for a DNS rebind to win — which is why this uses `node:http` rather than `fetch`,
26
+ * whose dispatcher cannot be given one without pulling undici in.
27
+ * 6. **Every redirect is a new request and gets the whole check again.** The first response is
28
+ * public and the second one is the loopback interface: that is the entire trick, and it is why
29
+ * redirects are followed here by hand rather than by the HTTP client.
30
+ * 7. **A ceiling on the body, the time and the number of hops**, because a fetcher with none is a
31
+ * way to hold a worker open on a socket that trickles bytes for ever.
32
+ *
33
+ * **The allow-list is empty by default, and every unfurl is refused until an operator sets one.**
34
+ * That is deliberate rather than unfinished. Fetching arbitrary addresses on a user's say-so is a
35
+ * capability an instance's operator should have to grant on purpose, and a self-hosted Kern sitting
36
+ * inside somebody's private network is exactly the deployment where granting it by default would be
37
+ * worst. `QUIRE_EMBED_HOSTS` is the switch; the procedure says so when it refuses, so an
38
+ * administrator reads a sentence about a setting rather than "something went wrong".
39
+ *
40
+ * What this file is **not** for: Kern's own objects. A page, an issue or a channel is named by
41
+ * reference and resolved through `objectTypes`/`resolvers` against whoever is reading — see
42
+ * `objects.ts`. Unfurling our own URL would point this fetcher at core, and would freeze a
43
+ * permission question into a stored answer.
44
+ */
45
+ import { lookup as dnsLookup } from 'node:dns';
46
+ import { request as httpRequest } from 'node:http';
47
+ import { request as httpsRequest } from 'node:https';
48
+ import { isIP } from 'node:net';
49
+ import { KernError } from '@kernhq/kernel';
50
+ import { PAGE_EMBED_MAX_DESCRIPTION, PAGE_EMBED_MAX_SITE, PAGE_EMBED_MAX_TITLE, PAGE_EMBED_MAX_URL, } from '@kernhq/ui/editor/page-doc';
51
+ /* ---------------------------------------------------------------------------------------------- */
52
+ /* Refusals */
53
+ /* ---------------------------------------------------------------------------------------------- */
54
+ /**
55
+ * Why an address was not fetched.
56
+ *
57
+ * Named rather than free text, because the caller has to be able to tell an operator's problem
58
+ * ("nobody has allowed any host") from a writer's ("that address is not one of them") from the
59
+ * site's ("it never answered"), and because the tests assert on them: a refusal for the wrong
60
+ * reason is a check that happened to catch something the *next* URL will walk past.
61
+ */
62
+ export const UNFURL_REFUSALS = [
63
+ 'no_allowlist',
64
+ 'not_allowed',
65
+ 'private_address',
66
+ 'unresolvable',
67
+ 'bad_url',
68
+ 'too_many_redirects',
69
+ 'unreachable',
70
+ 'not_readable',
71
+ ];
72
+ export class UnfurlRefused extends Error {
73
+ refusal;
74
+ constructor(refusal) {
75
+ super(refusal);
76
+ this.refusal = refusal;
77
+ this.name = 'UnfurlRefused';
78
+ }
79
+ }
80
+ /* ---------------------------------------------------------------------------------------------- */
81
+ /* Address space */
82
+ /* ---------------------------------------------------------------------------------------------- */
83
+ function ipv4Parts(ip) {
84
+ const parts = ip.split('.');
85
+ if (parts.length !== 4)
86
+ return null;
87
+ const out = [];
88
+ for (const part of parts) {
89
+ if (!/^\d{1,3}$/.test(part))
90
+ return null;
91
+ const value = Number(part);
92
+ if (value > 255)
93
+ return null;
94
+ out.push(value);
95
+ }
96
+ return out;
97
+ }
98
+ /**
99
+ * Everything IPv4 reserves, refused.
100
+ *
101
+ * Written out rather than reduced to "not 10/8, 172.16/12, 192.168/16", because the ones people
102
+ * forget are the ones that matter here: `169.254.169.254` is a cloud metadata service, `0.0.0.0` is
103
+ * *this host* on Linux, and `100.64/10` is a carrier's inside. A decimal or hex-encoded address —
104
+ * `http://2130706433/` — never reaches this function looking like one: the WHATWG URL parser
105
+ * normalises a special-scheme host to dotted quad before anything here sees it.
106
+ */
107
+ function publicV4(parts) {
108
+ const [a = 0, b = 0, c = 0] = parts;
109
+ if (a === 0)
110
+ return false; // 0.0.0.0/8 — "this network", and 0.0.0.0 is every local interface
111
+ if (a === 10)
112
+ return false;
113
+ if (a === 127)
114
+ return false; // loopback
115
+ if (a === 100 && b >= 64 && b <= 127)
116
+ return false; // 100.64/10, carrier-grade NAT
117
+ if (a === 169 && b === 254)
118
+ return false; // link-local, which is where the metadata service lives
119
+ if (a === 172 && b >= 16 && b <= 31)
120
+ return false;
121
+ if (a === 192 && b === 0 && (c === 0 || c === 2))
122
+ return false; // IETF protocol assignments, TEST-NET-1
123
+ if (a === 192 && b === 168)
124
+ return false;
125
+ if (a === 198 && (b === 18 || b === 19))
126
+ return false; // benchmarking
127
+ if (a === 198 && b === 51 && c === 100)
128
+ return false; // TEST-NET-2
129
+ if (a === 203 && b === 0 && c === 113)
130
+ return false; // TEST-NET-3
131
+ if (a >= 224)
132
+ return false; // multicast, reserved, and 255.255.255.255
133
+ return true;
134
+ }
135
+ /**
136
+ * The same, for IPv6, and the four traps that make it harder.
137
+ *
138
+ * `::1` is loopback and everybody checks it. What gets missed is that IPv6 has three separate ways
139
+ * to *carry* an IPv4 address — `::ffff:127.0.0.1` (mapped), `64:ff9b::7f00:1` (NAT64) and
140
+ * `2002::/16` (6to4) — and a check that only refuses `fc00::/7` and `fe80::/10` lets every one of
141
+ * them through to the loopback interface. So an embedded address is unwrapped and judged as IPv4.
142
+ */
143
+ function publicV6(groups) {
144
+ const [g0 = 0, g1 = 0, g2 = 0, g3 = 0, g4 = 0, g5 = 0, g6 = 0, g7 = 0] = groups;
145
+ const embedded = (high, low) => [high >> 8, high & 0xff, low >> 8, low & 0xff];
146
+ // `::`, `::1`, and the deprecated `::a.b.c.d` — the first six groups zero means an embedded v4.
147
+ if (g0 === 0 && g1 === 0 && g2 === 0 && g3 === 0 && g4 === 0) {
148
+ if (g5 === 0xffff)
149
+ return publicV4(embedded(g6, g7)); // IPv4-mapped
150
+ if (g5 === 0) {
151
+ if (g6 === 0 && g7 <= 1)
152
+ return false; // :: and ::1
153
+ return publicV4(embedded(g6, g7)); // IPv4-compatible
154
+ }
155
+ }
156
+ if (g0 === 0x0064 && g1 === 0xff9b)
157
+ return publicV4(embedded(g6, g7)); // NAT64 well-known prefix
158
+ if (g0 === 0x2002)
159
+ return publicV4(embedded(g1, g2)); // 6to4
160
+ if (g0 === 0x0100 && g1 === 0 && g2 === 0 && g3 === 0)
161
+ return false; // discard-only
162
+ if ((g0 & 0xfe00) === 0xfc00)
163
+ return false; // unique local
164
+ if ((g0 & 0xffc0) === 0xfe80)
165
+ return false; // link-local
166
+ if ((g0 & 0xff00) === 0xff00)
167
+ return false; // multicast
168
+ if (g0 === 0x2001 && g1 === 0x0db8)
169
+ return false; // documentation
170
+ return true;
171
+ }
172
+ function ipv6Groups(ip) {
173
+ let text = (ip.split('%')[0] ?? '').toLowerCase();
174
+ if (text.startsWith('[') && text.endsWith(']'))
175
+ text = text.slice(1, -1);
176
+ if (!text.includes(':'))
177
+ return null;
178
+ // A trailing dotted quad is two groups written the other way round.
179
+ const dotted = /(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/.exec(text);
180
+ if (dotted) {
181
+ const quad = ipv4Parts(dotted[1]);
182
+ if (!quad)
183
+ return null;
184
+ const high = ((quad[0] << 8) | quad[1]).toString(16);
185
+ const low = ((quad[2] << 8) | quad[3]).toString(16);
186
+ text = `${text.slice(0, dotted.index)}${high}:${low}`;
187
+ }
188
+ const halves = text.split('::');
189
+ if (halves.length > 2)
190
+ return null;
191
+ const head = halves[0] ? halves[0].split(':') : [];
192
+ const tail = halves.length === 2 && halves[1] ? halves[1].split(':') : [];
193
+ let parts;
194
+ if (halves.length === 1) {
195
+ if (head.length !== 8)
196
+ return null;
197
+ parts = head;
198
+ }
199
+ else {
200
+ const missing = 8 - head.length - tail.length;
201
+ if (missing < 0)
202
+ return null;
203
+ parts = [...head, ...Array.from({ length: missing }, () => '0'), ...tail];
204
+ }
205
+ const groups = [];
206
+ for (const part of parts) {
207
+ if (!/^[0-9a-f]{1,4}$/.test(part))
208
+ return null;
209
+ groups.push(Number.parseInt(part, 16));
210
+ }
211
+ return groups;
212
+ }
213
+ /**
214
+ * Is this address one the public internet can route to?
215
+ *
216
+ * **Anything this function cannot parse is not public.** That is the direction the default has to
217
+ * fall: an address shape nobody here anticipated is exactly the one an attacker went looking for.
218
+ */
219
+ export function isPublicAddress(ip) {
220
+ const trimmed = ip.trim();
221
+ const v4 = ipv4Parts(trimmed);
222
+ if (v4)
223
+ return publicV4(v4);
224
+ const v6 = ipv6Groups(trimmed);
225
+ if (v6)
226
+ return publicV6(v6);
227
+ return false;
228
+ }
229
+ /* ---------------------------------------------------------------------------------------------- */
230
+ /* The allow-list */
231
+ /* ---------------------------------------------------------------------------------------------- */
232
+ /**
233
+ * `QUIRE_EMBED_HOSTS`, as a list.
234
+ *
235
+ * `example.com` allows exactly that host. `.example.com` allows it and everything under it — spelled
236
+ * with a leading dot rather than inferred, because "allow github.com" and "allow every host anybody
237
+ * can create under github.io" are different decisions and an operator should have to make the second
238
+ * one on purpose.
239
+ */
240
+ export function parseHostAllowlist(raw) {
241
+ return (raw ?? '')
242
+ .split(/[\s,]+/)
243
+ .map((entry) => entry.trim().toLowerCase().replace(/\.$/, ''))
244
+ .filter((entry) => entry.length > 0 && /^\.?[a-z0-9.-]+$/.test(entry));
245
+ }
246
+ export function hostAllowed(hostname, allowlist) {
247
+ const host = hostname.trim().toLowerCase().replace(/\.$/, '');
248
+ if (!host)
249
+ return false;
250
+ for (const entry of allowlist) {
251
+ if (entry.startsWith('.')) {
252
+ if (host === entry.slice(1) || host.endsWith(entry))
253
+ return true;
254
+ }
255
+ else if (host === entry)
256
+ return true;
257
+ }
258
+ return false;
259
+ }
260
+ const DEFAULT_REDIRECTS = 3;
261
+ const DEFAULT_TIMEOUT_MS = 5_000;
262
+ const DEFAULT_MAX_BYTES = 128 * 1024;
263
+ const resolveWithDns = (hostname) => new Promise((resolve, reject) => {
264
+ dnsLookup(hostname, { all: true, verbatim: true }, (err, addresses) => {
265
+ if (err)
266
+ reject(new UnfurlRefused('unresolvable'));
267
+ else
268
+ resolve(addresses.map((a) => a.address));
269
+ });
270
+ });
271
+ /**
272
+ * Everything that has to be true before a request is made, in the order it has to be true in.
273
+ *
274
+ * Returns the addresses, because the connection is then pinned to exactly these — checking an
275
+ * address and then letting the socket resolve the name again is checking a different address from
276
+ * the one that gets connected to.
277
+ */
278
+ export async function checkTarget(url, policy) {
279
+ if (url.protocol !== 'http:' && url.protocol !== 'https:')
280
+ throw new UnfurlRefused('bad_url');
281
+ if (url.username || url.password)
282
+ throw new UnfurlRefused('bad_url');
283
+ if (url.href.length > PAGE_EMBED_MAX_URL)
284
+ throw new UnfurlRefused('bad_url');
285
+ if (policy.allowHosts.length === 0)
286
+ throw new UnfurlRefused('no_allowlist');
287
+ const hostname = url.hostname.replace(/^\[|\]$/g, '');
288
+ /*
289
+ * A literal address is its own resolution, so there is nothing to look up — and it is checked
290
+ * before the allow-list for the reason given at the top of this file: the allow-list must never
291
+ * be a way past the address rule.
292
+ */
293
+ const addresses = isIP(hostname) ? [hostname] : await (policy.resolve ?? resolveWithDns)(hostname);
294
+ if (addresses.length === 0)
295
+ throw new UnfurlRefused('unresolvable');
296
+ for (const address of addresses)
297
+ if (!isPublicAddress(address))
298
+ throw new UnfurlRefused('private_address');
299
+ if (!hostAllowed(hostname, policy.allowHosts))
300
+ throw new UnfurlRefused('not_allowed');
301
+ return addresses;
302
+ }
303
+ /* ---------------------------------------------------------------------------------------------- */
304
+ /* One request */
305
+ /* ---------------------------------------------------------------------------------------------- */
306
+ /**
307
+ * One HTTP request that does not follow redirects, capped in bytes and in time.
308
+ *
309
+ * `node:http` rather than `fetch`, and the reason is the `lookup` below: it is the only place a
310
+ * resolution can be pinned to addresses that have already been judged, and Node's `fetch` gives no
311
+ * way to install one without adding undici as a dependency. `agent: false` because a pooled socket
312
+ * is keyed on host and port and *not* on the lookup, so pooling would happily hand back a connection
313
+ * that was opened under someone else's guard.
314
+ */
315
+ export async function httpHop(url, addresses, policy) {
316
+ const send = url.protocol === 'https:' ? httpsRequest : httpRequest;
317
+ return new Promise((resolve, reject) => {
318
+ let settled = false;
319
+ const done = (fn) => {
320
+ if (settled)
321
+ return;
322
+ settled = true;
323
+ fn();
324
+ };
325
+ const req = send(url, {
326
+ method: 'GET',
327
+ agent: false,
328
+ // Pinned. The socket connects to an address this request has already had judged, and there
329
+ // is no second resolution between the check and the connection.
330
+ lookup: (_hostname, options, callback) => {
331
+ const chosen = addresses.map((address) => ({
332
+ address,
333
+ family: isIP(address) === 6 ? 6 : 4,
334
+ }));
335
+ if (typeof options === 'object' && options?.all)
336
+ callback(null, chosen);
337
+ else {
338
+ const first = chosen[0];
339
+ callback(null, first.address, first.family);
340
+ }
341
+ },
342
+ headers: {
343
+ accept: 'text/html,application/xhtml+xml;q=0.9,*/*;q=0.1',
344
+ 'accept-language': 'en',
345
+ 'user-agent': 'Kern (+https://kernaio.com)',
346
+ },
347
+ }, (res) => {
348
+ const status = res.statusCode ?? 0;
349
+ const contentType = (res.headers['content-type'] ?? null);
350
+ const location = (res.headers.location ?? null);
351
+ /*
352
+ * A redirect and a non-HTML body are both answered without reading anything: there is no
353
+ * metadata in a 3 GB video, and reading one is how a fetcher becomes a way to make the
354
+ * server download things.
355
+ */
356
+ if (status >= 300 && status < 400) {
357
+ res.destroy();
358
+ return done(() => resolve({ status, location, contentType, body: '' }));
359
+ }
360
+ if (contentType && !/^\s*(text\/html|application\/xhtml\+xml|text\/plain)/i.test(contentType)) {
361
+ res.destroy();
362
+ return done(() => resolve({ status, location: null, contentType, body: '' }));
363
+ }
364
+ let size = 0;
365
+ const chunks = [];
366
+ res.on('data', (chunk) => {
367
+ size += chunk.length;
368
+ chunks.push(chunk);
369
+ // Enough to hold a `<head>`; the rest of the page is not metadata and is not read.
370
+ if (size >= policy.maxBytes)
371
+ res.destroy();
372
+ });
373
+ const finish = () => done(() => resolve({
374
+ status,
375
+ location: null,
376
+ contentType,
377
+ body: Buffer.concat(chunks).subarray(0, policy.maxBytes).toString('utf8'),
378
+ }));
379
+ res.on('end', finish);
380
+ // A destroyed response is the cap doing its job, not a failure: keep what was read.
381
+ res.on('close', finish);
382
+ res.on('error', finish);
383
+ });
384
+ const timer = setTimeout(() => {
385
+ req.destroy();
386
+ done(() => reject(new UnfurlRefused('unreachable')));
387
+ }, policy.timeoutMs);
388
+ timer.unref?.();
389
+ req.on('error', () => done(() => reject(new UnfurlRefused('unreachable'))));
390
+ req.on('close', () => clearTimeout(timer));
391
+ req.end();
392
+ });
393
+ }
394
+ /* ---------------------------------------------------------------------------------------------- */
395
+ /* Reading the metadata */
396
+ /* ---------------------------------------------------------------------------------------------- */
397
+ const ENTITIES = {
398
+ amp: '&',
399
+ lt: '<',
400
+ gt: '>',
401
+ quot: '"',
402
+ apos: "'",
403
+ nbsp: ' ',
404
+ '#39': "'",
405
+ '#x27': "'",
406
+ };
407
+ /**
408
+ * Enough entity decoding to read a headline, and no more.
409
+ *
410
+ * The result is escaped again by the renderer before it reaches a page, so this is about the words
411
+ * being right rather than about safety — `AT&amp;T` should be stored as `AT&T` and not as five
412
+ * characters of markup somebody has to read past.
413
+ */
414
+ function decodeEntities(text) {
415
+ return text.replace(/&(#x?[0-9a-f]+|[a-z]+);/gi, (whole, name) => {
416
+ const key = name.toLowerCase();
417
+ const known = ENTITIES[key];
418
+ if (known)
419
+ return known;
420
+ if (key.startsWith('#x')) {
421
+ const code = Number.parseInt(key.slice(2), 16);
422
+ return Number.isFinite(code) && code > 0 && code < 0x110000 ? String.fromCodePoint(code) : whole;
423
+ }
424
+ if (key.startsWith('#')) {
425
+ const code = Number.parseInt(key.slice(1), 10);
426
+ return Number.isFinite(code) && code > 0 && code < 0x110000 ? String.fromCodePoint(code) : whole;
427
+ }
428
+ return whole;
429
+ });
430
+ }
431
+ const clean = (text, max) => {
432
+ if (!text)
433
+ return null;
434
+ const out = decodeEntities(text).replace(/\s+/g, ' ').trim().slice(0, max);
435
+ return out || null;
436
+ };
437
+ /** `<meta property="og:title" content="…">`, either attribute order, either quote. */
438
+ function metaContent(html, key) {
439
+ const named = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
440
+ const patterns = [
441
+ new RegExp(`<meta[^>]+(?:property|name)\\s*=\\s*["']${named}["'][^>]*?content\\s*=\\s*["']([^"']*)["']`, 'i'),
442
+ new RegExp(`<meta[^>]+content\\s*=\\s*["']([^"']*)["'][^>]*?(?:property|name)\\s*=\\s*["']${named}["']`, 'i'),
443
+ ];
444
+ for (const pattern of patterns) {
445
+ const found = pattern.exec(html);
446
+ if (found?.[1])
447
+ return found[1];
448
+ }
449
+ return null;
450
+ }
451
+ /**
452
+ * What an HTML page says about itself.
453
+ *
454
+ * Open Graph first because it is what a page puts there deliberately, `<title>` and the description
455
+ * meta as the fallback, and the hostname as the site of last resort — a card with an address and
456
+ * nothing else is still a card, and it is what the renderer draws when there is no title at all.
457
+ */
458
+ export function metadataFrom(html, url) {
459
+ const head = html.split(/<\/head\s*>/i)[0] ?? html;
460
+ const titleTag = /<title[^>]*>([\s\S]{0,400}?)<\/title\s*>/i.exec(head)?.[1] ?? null;
461
+ return {
462
+ url: url.href,
463
+ title: clean(metaContent(head, 'og:title'), PAGE_EMBED_MAX_TITLE) ??
464
+ clean(metaContent(head, 'twitter:title'), PAGE_EMBED_MAX_TITLE) ??
465
+ clean(titleTag?.replace(/<[^>]*>/g, ''), PAGE_EMBED_MAX_TITLE),
466
+ description: clean(metaContent(head, 'og:description'), PAGE_EMBED_MAX_DESCRIPTION) ??
467
+ clean(metaContent(head, 'description'), PAGE_EMBED_MAX_DESCRIPTION),
468
+ siteName: clean(metaContent(head, 'og:site_name'), PAGE_EMBED_MAX_SITE) ??
469
+ clean(url.hostname, PAGE_EMBED_MAX_SITE),
470
+ };
471
+ }
472
+ /* ---------------------------------------------------------------------------------------------- */
473
+ /* The whole thing */
474
+ /* ---------------------------------------------------------------------------------------------- */
475
+ /**
476
+ * Fetch a URL and read what it says about itself, or refuse and say which rule refused it.
477
+ *
478
+ * The redirect loop is the part worth reading. Every hop — including the first — goes through
479
+ * `checkTarget` *before* it is requested, so a 302 from a public site to `http://127.0.0.1:4000` is
480
+ * refused at the second check, with the second request never made. That is the only arrangement
481
+ * that works: an HTTP client following redirects on its own has already made the request by the
482
+ * time anything here could look at where it went.
483
+ */
484
+ export async function unfurl(rawUrl, policy) {
485
+ const full = {
486
+ allowHosts: policy.allowHosts,
487
+ maxRedirects: policy.maxRedirects ?? DEFAULT_REDIRECTS,
488
+ timeoutMs: policy.timeoutMs ?? DEFAULT_TIMEOUT_MS,
489
+ maxBytes: policy.maxBytes ?? DEFAULT_MAX_BYTES,
490
+ resolve: policy.resolve ?? resolveWithDns,
491
+ hop: policy.hop ?? httpHop,
492
+ };
493
+ let url;
494
+ try {
495
+ url = new URL(rawUrl);
496
+ }
497
+ catch {
498
+ throw new UnfurlRefused('bad_url');
499
+ }
500
+ const deadline = Date.now() + full.timeoutMs;
501
+ for (let redirects = 0; redirects <= full.maxRedirects; redirects++) {
502
+ const remaining = deadline - Date.now();
503
+ if (remaining <= 0)
504
+ throw new UnfurlRefused('unreachable');
505
+ const addresses = await checkTarget(url, full);
506
+ const answer = await full.hop(url, addresses, { ...full, timeoutMs: remaining });
507
+ if (answer.status >= 300 && answer.status < 400 && answer.location) {
508
+ let next;
509
+ try {
510
+ next = new URL(answer.location, url);
511
+ }
512
+ catch {
513
+ throw new UnfurlRefused('bad_url');
514
+ }
515
+ url = next;
516
+ continue;
517
+ }
518
+ if (answer.status < 200 || answer.status >= 300)
519
+ throw new UnfurlRefused('not_readable');
520
+ if (!answer.body) {
521
+ // Nothing to read, but the address answered: a card with the URL on it is still honest.
522
+ return { url: url.href, title: null, description: null, siteName: url.hostname };
523
+ }
524
+ return metadataFrom(answer.body, url);
525
+ }
526
+ throw new UnfurlRefused('too_many_redirects');
527
+ }
528
+ /* ---------------------------------------------------------------------------------------------- */
529
+ /* The service */
530
+ /* ---------------------------------------------------------------------------------------------- */
531
+ /** How long an answer is kept. A headline does not change in ten minutes, and a page render is hot. */
532
+ const CACHE_TTL_MS = 10 * 60_000;
533
+ const CACHE_MAX = 500;
534
+ /**
535
+ * The words a refusal is reported with.
536
+ *
537
+ * Two of the eight are an *operator's* problem rather than the writer's, and they get their own
538
+ * sentence: somebody pasting a link into a page cannot be expected to work out that an environment
539
+ * variable is empty, and "that address is not allowed" would send them looking for a typo.
540
+ */
541
+ const REFUSAL_MESSAGES = {
542
+ no_allowlist: 'No sites have been allowed for embedding on this instance. An administrator sets QUIRE_EMBED_HOSTS.',
543
+ not_allowed: 'This site is not one of the sites allowed for embedding on this instance.',
544
+ private_address: 'That address is on a private network, so it will not be fetched.',
545
+ unresolvable: 'That address could not be resolved.',
546
+ bad_url: 'That is not a web address that can be embedded.',
547
+ too_many_redirects: 'That address redirected too many times.',
548
+ unreachable: 'That site did not answer.',
549
+ not_readable: 'That page could not be read.',
550
+ };
551
+ export function quireUnfurl(kernel) {
552
+ const cache = new Map();
553
+ /*
554
+ * Read on every call rather than at boot, so an operator who sets the variable and restarts one
555
+ * process does not have to wonder which of them picked it up — and so a test can set it.
556
+ */
557
+ const allowHosts = () => parseHostAllowlist(process.env.QUIRE_EMBED_HOSTS);
558
+ return {
559
+ allowHosts,
560
+ /**
561
+ * Unfurl one address, refusing with a sentence rather than a stack trace.
562
+ *
563
+ * Cached by the URL the writer typed. The cache is per process and small: it exists so that a
564
+ * page whose writer pastes the same link twice is one request, not so that an unfurl is free.
565
+ */
566
+ async get(rawUrl) {
567
+ const key = rawUrl.trim();
568
+ const hit = cache.get(key);
569
+ if (hit && Date.now() - hit.at < CACHE_TTL_MS)
570
+ return hit.value;
571
+ try {
572
+ const value = await unfurl(key, { allowHosts: allowHosts() });
573
+ if (cache.size >= CACHE_MAX)
574
+ cache.clear();
575
+ cache.set(key, { at: Date.now(), value });
576
+ return value;
577
+ }
578
+ catch (err) {
579
+ if (err instanceof UnfurlRefused) {
580
+ /*
581
+ * Logged at info rather than warn: a refusal is this working, and a workspace with a
582
+ * strict allow-list would otherwise fill an operator's log with warnings about the
583
+ * feature behaving exactly as configured.
584
+ */
585
+ kernel.log.info({ refusal: err.refusal }, 'quire: refused to unfurl an address');
586
+ throw new KernError('BAD_REQUEST', REFUSAL_MESSAGES[err.refusal]);
587
+ }
588
+ throw err;
589
+ }
590
+ },
591
+ };
592
+ }
593
+ //# sourceMappingURL=unfurl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unfurl.js","sourceRoot":"","sources":["../../../src/server/services/unfurl.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import type { Principal } from '@kernhq/contracts';
2
2
  import { type Kernel, type Tx } from '@kernhq/kernel';
3
3
  import type { PageVersion } from '../../contract/index.js';
4
+ import { type MacroResolver, type MacroStrings } from '../render.js';
4
5
  import { pageVersions } from '../schema.js';
5
6
  import type { QuireAccess } from './access.js';
6
7
  type VersionRow = typeof pageVersions.$inferSelect;
@@ -168,6 +169,17 @@ export declare function quireVersions(kernel: Kernel, access: QuireAccess): {
168
169
  */
169
170
  html(tx: Tx, workspaceId: string, state: Buffer | Uint8Array | null, opts?: {
170
171
  pictures?: "signed" | "referenced";
172
+ /**
173
+ * What the macros that read other pages were told, for whoever this render is for.
174
+ *
175
+ * Absent — which is what the publish-time render passes — draws every reading macro as an
176
+ * empty frame. That is not an oversight to fix later: this output is **stored** in
177
+ * `page_versions.html`, and a resolved macro in a stored row is a set of page titles frozen
178
+ * at publish time and served for ever, including after those pages are unpublished. The
179
+ * public read re-resolves them per read instead. See `publications.html`.
180
+ */
181
+ macros?: MacroResolver;
182
+ macroStrings?: MacroStrings;
171
183
  }): Promise<string>;
172
184
  };
173
185
  export type QuireVersions = ReturnType<typeof quireVersions>;
@@ -1 +1 @@
1
- {"version":3,"file":"versions.d.ts","sourceRoot":"","sources":["../../../src/server/services/versions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAuB,SAAS,EAAE,MAAM,mBAAmB,CAAA;AACvE,OAAO,EAAa,KAAK,MAAM,EAAE,KAAK,EAAE,EAAU,MAAM,gBAAgB,CAAA;AAExE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAG1D,OAAO,EAAS,YAAY,EAAU,MAAM,cAAc,CAAA;AAC1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAG9C,KAAK,UAAU,GAAG,OAAO,YAAY,CAAC,YAAY,CAAA;AAKlD;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB,oBAAoB,CAAA;AACvD,eAAO,MAAM,cAAc,GAAI,QAAQ,MAAM,KAAG,MAA8C,CAAA;AAE9F,wBAAgB,SAAS,CAAC,GAAG,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,WAAW,CAalF;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW;IAS7D;;;;;;OAMG;gBAEG,EAAE,eACO,MAAM,UACX,MAAM,QACR;QAAE,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GACnF,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;aA0Cd,EAAE,eAAe,MAAM,UAAU,MAAM,SAAS,MAAM,UAAU,MAAM,GAAG,IAAI;;;;;;;;;;;;;;;YAkB9E,EAAE,eAAe,MAAM,aAAa,MAAM;;;;;;;;;;;;;;IAUxD;;;;;;;OAOG;gBACe,EAAE,aAAa,SAAS,eAAe,MAAM,aAAa,MAAM;;;;;;;;;;;;;;IAsBlF,kEAAkE;gBAChD,EAAE,aAAa,SAAS,eAAe,MAAM,UAAU,MAAM,SAAS,MAAM,GAAG,IAAI;;;;;;;;;;;;;;;;;;;;;;;;IAyBrG,wEAAwE;eACvD,EAAE,aAAa,SAAS,eAAe,MAAM,UAAU,MAAM;;;;;;;;;;;;;;;;;;;;;;;;IAyB9E,2FAA2F;uBAClE,EAAE,eAAe,MAAM,UAAU,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAUvF,wFAAwF;+BAC7D,MAAM,UAAU,MAAM;;;;;;IAMjD;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;aAEG,EAAE,eACO,MAAM,SACZ,MAAM,GAAG,UAAU,GAAG,IAAI,SAC3B;QAAE,QAAQ,CAAC,EAAE,QAAQ,GAAG,YAAY,CAAA;KAAE,GAC3C,OAAO,CAAC,MAAM,CAAC;EAgDrB;AACD,MAAM,MAAM,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAA"}
1
+ {"version":3,"file":"versions.d.ts","sourceRoot":"","sources":["../../../src/server/services/versions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAuB,SAAS,EAAE,MAAM,mBAAmB,CAAA;AACvE,OAAO,EAAa,KAAK,MAAM,EAAE,KAAK,EAAE,EAAU,MAAM,gBAAgB,CAAA;AAExE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAE1D,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,YAAY,EAIlB,MAAM,cAAc,CAAA;AACrB,OAAO,EAAS,YAAY,EAAU,MAAM,cAAc,CAAA;AAC1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAG9C,KAAK,UAAU,GAAG,OAAO,YAAY,CAAC,YAAY,CAAA;AAKlD;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB,oBAAoB,CAAA;AACvD,eAAO,MAAM,cAAc,GAAI,QAAQ,MAAM,KAAG,MAA8C,CAAA;AAE9F,wBAAgB,SAAS,CAAC,GAAG,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,WAAW,CAalF;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW;IAS7D;;;;;;OAMG;gBAEG,EAAE,eACO,MAAM,UACX,MAAM,QACR;QAAE,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GACnF,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;aA0Cd,EAAE,eAAe,MAAM,UAAU,MAAM,SAAS,MAAM,UAAU,MAAM,GAAG,IAAI;;;;;;;;;;;;;;;YAkB9E,EAAE,eAAe,MAAM,aAAa,MAAM;;;;;;;;;;;;;;IAUxD;;;;;;;OAOG;gBACe,EAAE,aAAa,SAAS,eAAe,MAAM,aAAa,MAAM;;;;;;;;;;;;;;IAsBlF,kEAAkE;gBAChD,EAAE,aAAa,SAAS,eAAe,MAAM,UAAU,MAAM,SAAS,MAAM,GAAG,IAAI;;;;;;;;;;;;;;;;;;;;;;;;IAyBrG,wEAAwE;eACvD,EAAE,aAAa,SAAS,eAAe,MAAM,UAAU,MAAM;;;;;;;;;;;;;;;;;;;;;;;;IAyB9E,2FAA2F;uBAClE,EAAE,eAAe,MAAM,UAAU,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAUvF,wFAAwF;+BAC7D,MAAM,UAAU,MAAM;;;;;;IAMjD;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;aAEG,EAAE,eACO,MAAM,SACZ,MAAM,GAAG,UAAU,GAAG,IAAI,SAC3B;QACJ,QAAQ,CAAC,EAAE,QAAQ,GAAG,YAAY,CAAA;QAClC;;;;;;;;WAQG;QACH,MAAM,CAAC,EAAE,aAAa,CAAA;QACtB,YAAY,CAAC,EAAE,YAAY,CAAA;KAC5B,GACA,OAAO,CAAC,MAAM,CAAC;EAkDrB;AACD,MAAM,MAAM,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAA"}