@render-policy/core 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 +21 -0
- package/README.md +23 -0
- package/dist/binding.d.ts +28 -0
- package/dist/binding.d.ts.map +1 -0
- package/dist/binding.js +32 -0
- package/dist/binding.js.map +1 -0
- package/dist/data/sink-domains.d.ts +14 -0
- package/dist/data/sink-domains.d.ts.map +1 -0
- package/dist/data/sink-domains.js +91 -0
- package/dist/data/sink-domains.js.map +1 -0
- package/dist/decisions.d.ts +22 -0
- package/dist/decisions.d.ts.map +1 -0
- package/dist/decisions.js +2 -0
- package/dist/decisions.js.map +1 -0
- package/dist/dom.d.ts +13 -0
- package/dist/dom.d.ts.map +1 -0
- package/dist/dom.js +25 -0
- package/dist/dom.js.map +1 -0
- package/dist/errors.d.ts +3 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +5 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/internal.d.ts +12 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +10 -0
- package/dist/internal.js.map +1 -0
- package/dist/markdown.d.ts +15 -0
- package/dist/markdown.d.ts.map +1 -0
- package/dist/markdown.js +15 -0
- package/dist/markdown.js.map +1 -0
- package/dist/policy.d.ts +141 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +110 -0
- package/dist/policy.js.map +1 -0
- package/dist/render.d.ts +79 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +130 -0
- package/dist/render.js.map +1 -0
- package/dist/sanitize.d.ts +17 -0
- package/dist/sanitize.d.ts.map +1 -0
- package/dist/sanitize.js +321 -0
- package/dist/sanitize.js.map +1 -0
- package/dist/sinks.d.ts +16 -0
- package/dist/sinks.d.ts.map +1 -0
- package/dist/sinks.js +24 -0
- package/dist/sinks.js.map +1 -0
- package/dist/stream.d.ts +57 -0
- package/dist/stream.d.ts.map +1 -0
- package/dist/stream.js +180 -0
- package/dist/stream.js.map +1 -0
- package/dist/url.d.ts +48 -0
- package/dist/url.d.ts.map +1 -0
- package/dist/url.js +105 -0
- package/dist/url.js.map +1 -0
- package/dist/window.d.ts +15 -0
- package/dist/window.d.ts.map +1 -0
- package/dist/window.js +9 -0
- package/dist/window.js.map +1 -0
- package/package.json +62 -0
package/dist/sanitize.js
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import createDOMPurify, {} from 'dompurify';
|
|
2
|
+
import { messageOf } from './errors.js';
|
|
3
|
+
import { matchSink } from './sinks.js';
|
|
4
|
+
import { checkUrl, checkUrlHeuristics, hostMatches, usableBase } from './url.js';
|
|
5
|
+
/**
|
|
6
|
+
* Attributes whose value is fetched or navigated to. Some are also on the always-forbidden
|
|
7
|
+
* list below and are dropped before the URL check runs; they stay here on purpose, so that
|
|
8
|
+
* the URL check does not depend on the forbid list staying as it is.
|
|
9
|
+
*/
|
|
10
|
+
const URL_ATTRIBUTES = new Set([
|
|
11
|
+
'href', 'src', 'xlink:href', 'action', 'formaction', 'poster', 'background', 'cite',
|
|
12
|
+
'longdesc', 'usemap', 'data', 'ping', 'codebase', 'archive', 'profile', 'manifest', 'icon',
|
|
13
|
+
]);
|
|
14
|
+
const FORM_TAGS = ['form', 'input', 'button', 'select', 'textarea', 'option', 'optgroup', 'fieldset', 'legend', 'label', 'datalist', 'output'];
|
|
15
|
+
const MEDIA_TAGS = ['audio', 'video', 'source', 'track', 'picture'];
|
|
16
|
+
/** Forbidden regardless of policy. Most are already outside DOMPurify's HTML profile; listing them is belt and braces. */
|
|
17
|
+
const ALWAYS_FORBIDDEN_TAGS = [
|
|
18
|
+
'script', 'style', 'template', 'iframe', 'frame', 'frameset', 'object', 'embed', 'applet',
|
|
19
|
+
'base', 'meta', 'link', 'noscript', 'dialog', 'marquee', 'blink', 'math',
|
|
20
|
+
];
|
|
21
|
+
/** Attributes that fetch, submit, or hook into host behaviour without a click. */
|
|
22
|
+
const ALWAYS_FORBIDDEN_ATTRIBUTES = [
|
|
23
|
+
'srcset', 'sizes', 'ping', 'background', 'formaction', 'action', 'usemap', 'ismap',
|
|
24
|
+
'is', 'slot', 'part', 'exportparts', 'popover', 'popovertarget', 'inert', 'contenteditable', 'autofocus',
|
|
25
|
+
];
|
|
26
|
+
function buildConfig(policy) {
|
|
27
|
+
const { content } = policy;
|
|
28
|
+
const forbidTags = new Set([...ALWAYS_FORBIDDEN_TAGS, ...content.forbidTags]);
|
|
29
|
+
if (!content.allowForms)
|
|
30
|
+
for (const tag of FORM_TAGS)
|
|
31
|
+
forbidTags.add(tag);
|
|
32
|
+
if (!content.allowMedia)
|
|
33
|
+
for (const tag of MEDIA_TAGS)
|
|
34
|
+
forbidTags.add(tag);
|
|
35
|
+
if (!content.allowImages)
|
|
36
|
+
forbidTags.add('img');
|
|
37
|
+
if (!content.allowSvg)
|
|
38
|
+
forbidTags.add('svg');
|
|
39
|
+
const forbidAttributes = new Set([...ALWAYS_FORBIDDEN_ATTRIBUTES, ...content.forbidAttributes]);
|
|
40
|
+
if (!content.allowInlineStyles)
|
|
41
|
+
forbidAttributes.add('style');
|
|
42
|
+
if (!content.allowTargetBlank)
|
|
43
|
+
forbidAttributes.add('target');
|
|
44
|
+
// DOMPurify's own URI check is kept as a second layer, aligned with the policy's scheme allowlist
|
|
45
|
+
// (same shape as DOMPurify's default: listed schemes, or a relative URL).
|
|
46
|
+
const schemes = policy.urls.allowedSchemes.map((s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|');
|
|
47
|
+
const allowedUri = new RegExp(`^(?:(?:${schemes}):|[^a-z]|[a-z+.\\-]+(?:[^a-z+.\\-:]|$))`, 'i');
|
|
48
|
+
return {
|
|
49
|
+
USE_PROFILES: content.allowSvg ? { html: true, svg: true, svgFilters: true } : { html: true },
|
|
50
|
+
ALLOWED_URI_REGEXP: allowedUri,
|
|
51
|
+
FORBID_TAGS: [...forbidTags],
|
|
52
|
+
FORBID_ATTR: [...forbidAttributes],
|
|
53
|
+
ADD_ATTR: content.allowTargetBlank ? ['target'] : [],
|
|
54
|
+
ALLOW_DATA_ATTR: content.allowDataAttributes,
|
|
55
|
+
ALLOW_ARIA_ATTR: true,
|
|
56
|
+
ALLOW_UNKNOWN_PROTOCOLS: false,
|
|
57
|
+
SANITIZE_DOM: true,
|
|
58
|
+
SANITIZE_NAMED_PROPS: true,
|
|
59
|
+
KEEP_CONTENT: true,
|
|
60
|
+
WHOLE_DOCUMENT: false,
|
|
61
|
+
FORCE_BODY: false,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export function createSanitizer(win, policy, denylist) {
|
|
65
|
+
const purify = createDOMPurify(win);
|
|
66
|
+
const config = buildConfig(policy);
|
|
67
|
+
let state = null;
|
|
68
|
+
const pageOrigin = () => {
|
|
69
|
+
const origin = win.document.location?.origin;
|
|
70
|
+
return origin && origin !== 'null' ? origin : null;
|
|
71
|
+
};
|
|
72
|
+
const pageBase = () => usableBase(win.document.location?.href);
|
|
73
|
+
const record = (s, decision) => {
|
|
74
|
+
s.decisions.push(decision);
|
|
75
|
+
if (decision.attribute !== undefined) {
|
|
76
|
+
s.recorded.add(`${decision.tag ?? ''}|${decision.attribute}|${decision.value ?? ''}`);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
const applyImagePolicy = (parsed) => {
|
|
80
|
+
if (parsed.relative) {
|
|
81
|
+
// Resolves within the host page's own origin.
|
|
82
|
+
return { ok: true, value: parsed.normalized, rewritten: null };
|
|
83
|
+
}
|
|
84
|
+
const origin = pageOrigin();
|
|
85
|
+
const url = new URL(parsed.url.href);
|
|
86
|
+
if (origin !== null && url.origin === origin) {
|
|
87
|
+
return { ok: true, value: parsed.normalized, rewritten: null };
|
|
88
|
+
}
|
|
89
|
+
const images = policy.images;
|
|
90
|
+
if (images.hosts === 'none') {
|
|
91
|
+
return { ok: false, code: 'remote-images-disabled', reason: 'remote images are disabled by the policy' };
|
|
92
|
+
}
|
|
93
|
+
if (images.hosts !== 'any' && !hostMatches(url.host, images.hosts, images.allowWildcardHosts)) {
|
|
94
|
+
return { ok: false, code: 'image-host-not-allowed', reason: `image host "${url.host}" is not in the allowlist` };
|
|
95
|
+
}
|
|
96
|
+
let rewritten = null;
|
|
97
|
+
const hasQuery = url.search !== '' || url.hash !== '';
|
|
98
|
+
if (hasQuery && images.query === 'deny') {
|
|
99
|
+
return { ok: false, code: 'image-query-denied', reason: 'image URL carries a query string or fragment' };
|
|
100
|
+
}
|
|
101
|
+
if (hasQuery && images.query === 'strip') {
|
|
102
|
+
url.search = '';
|
|
103
|
+
url.hash = '';
|
|
104
|
+
rewritten = { code: 'image-query-stripped', reason: 'query string removed' };
|
|
105
|
+
}
|
|
106
|
+
if (policy.urls.heuristics) {
|
|
107
|
+
const problem = checkUrlHeuristics(url, policy.urls.heuristics);
|
|
108
|
+
if (problem)
|
|
109
|
+
return { ok: false, ...problem };
|
|
110
|
+
}
|
|
111
|
+
let value = rewritten ? url.href : parsed.normalized;
|
|
112
|
+
if (images.rewriteUrl) {
|
|
113
|
+
let out;
|
|
114
|
+
try {
|
|
115
|
+
out = images.rewriteUrl(url);
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
// Application code failed: the image is blocked and the render goes on.
|
|
119
|
+
return { ok: false, code: 'image-rewrite-failed', reason: `images.rewriteUrl threw: ${messageOf(error)}` };
|
|
120
|
+
}
|
|
121
|
+
if (out === null)
|
|
122
|
+
return { ok: false, code: 'image-rewrite-rejected', reason: 'rejected by images.rewriteUrl' };
|
|
123
|
+
const check = checkUrl(out, policy.urls, pageBase());
|
|
124
|
+
if (!check.ok)
|
|
125
|
+
return { ok: false, code: 'image-rewrite-invalid', reason: `images.rewriteUrl returned an invalid URL: ${check.reason}` };
|
|
126
|
+
value = out;
|
|
127
|
+
rewritten = { code: 'image-rewritten', reason: 'rewritten by images.rewriteUrl' };
|
|
128
|
+
}
|
|
129
|
+
return { ok: true, value, rewritten };
|
|
130
|
+
};
|
|
131
|
+
purify.addHook('uponSanitizeAttribute', (node, data) => {
|
|
132
|
+
const s = state;
|
|
133
|
+
if (!s)
|
|
134
|
+
return;
|
|
135
|
+
const name = data.attrName.toLowerCase();
|
|
136
|
+
const tag = node.nodeName.toLowerCase();
|
|
137
|
+
const original = data.attrValue;
|
|
138
|
+
const drop = (subject, code, reason) => {
|
|
139
|
+
data.keepAttr = false;
|
|
140
|
+
record(s, { kind: 'blocked', subject, code, reason, tag, attribute: name, value: original });
|
|
141
|
+
};
|
|
142
|
+
if (name.startsWith('on')) {
|
|
143
|
+
drop('attribute', 'event-handler', 'event handler attribute');
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (name === 'class') {
|
|
147
|
+
const tokens = original.split(/\s+/).filter(Boolean);
|
|
148
|
+
const kept = tokens.filter((token) => policy.content.allowedClassPatterns.some((pattern) => {
|
|
149
|
+
pattern.lastIndex = 0;
|
|
150
|
+
return pattern.test(token);
|
|
151
|
+
}));
|
|
152
|
+
if (kept.length === 0) {
|
|
153
|
+
if (tokens.length > 0)
|
|
154
|
+
drop('class', 'class-not-allowed', 'class names are not in the allowlist');
|
|
155
|
+
else
|
|
156
|
+
data.keepAttr = false;
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (kept.length !== tokens.length) {
|
|
160
|
+
data.attrValue = kept.join(' ');
|
|
161
|
+
record(s, { kind: 'rewritten', subject: 'class', code: 'class-filtered', reason: 'class names outside the allowlist removed', tag, attribute: name, value: original });
|
|
162
|
+
}
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (name === 'target') {
|
|
166
|
+
if (!policy.content.allowTargetBlank || original.trim().toLowerCase() !== '_blank') {
|
|
167
|
+
drop('attribute', 'target-not-allowed', 'only target="_blank" is allowed');
|
|
168
|
+
}
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (!URL_ATTRIBUTES.has(name))
|
|
172
|
+
return;
|
|
173
|
+
const isImage = tag === 'img' && name === 'src';
|
|
174
|
+
const subject = isImage ? 'image' : name === 'href' ? 'link' : 'url';
|
|
175
|
+
const verdict = checkUrl(original, policy.urls, pageBase());
|
|
176
|
+
if (!verdict.ok) {
|
|
177
|
+
drop(subject, verdict.code, verdict.reason);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
let parsed = verdict.parsed;
|
|
181
|
+
const blockImage = (reason) => {
|
|
182
|
+
if (isImage && policy.images.blocked === 'placeholder')
|
|
183
|
+
s.blockedImages.set(node, { src: parsed.url.href, reason });
|
|
184
|
+
};
|
|
185
|
+
if (!parsed.relative && policy.urls.sinkDenylist !== 'off') {
|
|
186
|
+
const explicitlyAllowed = isImage && Array.isArray(policy.images.hosts) && hostMatches(parsed.url.host, policy.images.hosts, policy.images.allowWildcardHosts);
|
|
187
|
+
const hit = explicitlyAllowed ? null : matchSink(parsed.url, denylist);
|
|
188
|
+
if (hit) {
|
|
189
|
+
const reason = `host matches the sink denylist (${hit.category}: ${hit.pattern}, list ${denylist.version})`;
|
|
190
|
+
if (policy.urls.sinkDenylist === 'block') {
|
|
191
|
+
drop(subject, 'sink-host', reason);
|
|
192
|
+
blockImage(reason);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
record(s, { kind: 'flagged', subject, code: 'sink-host', reason, tag, attribute: name, value: parsed.normalized });
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
// Application URL hook: the place a link policy lives (deny or redirect off-site links); it runs
|
|
199
|
+
// for images too, before the image-specific policy. If it throws, that one URL is dropped.
|
|
200
|
+
if (policy.urls.decide) {
|
|
201
|
+
let decision;
|
|
202
|
+
try {
|
|
203
|
+
decision = policy.urls.decide(new URL(parsed.url.href), { subject: subject, tag, attribute: name, relative: parsed.relative });
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
drop(subject, 'url-decider-failed', `urls.decide threw: ${messageOf(error)}`);
|
|
207
|
+
blockImage(`urls.decide threw: ${messageOf(error)}`);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (decision) {
|
|
211
|
+
if (decision.allow === false) {
|
|
212
|
+
const reason = decision.reason ?? 'denied by urls.decide';
|
|
213
|
+
drop(subject, 'url-denied', reason);
|
|
214
|
+
blockImage(reason);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (typeof decision.rewrite === 'string' && decision.rewrite !== parsed.normalized) {
|
|
218
|
+
const check = checkUrl(decision.rewrite, policy.urls, pageBase());
|
|
219
|
+
if (!check.ok) {
|
|
220
|
+
drop(subject, 'url-rewrite-invalid', `urls.decide returned an invalid URL: ${check.reason}`);
|
|
221
|
+
blockImage(`urls.decide returned an invalid URL: ${check.reason}`);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
parsed = check.parsed;
|
|
225
|
+
data.attrValue = decision.rewrite;
|
|
226
|
+
record(s, { kind: 'rewritten', subject, code: 'url-rewritten', reason: decision.reason ?? 'rewritten by urls.decide', tag, attribute: name, value: original });
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (!isImage)
|
|
231
|
+
return;
|
|
232
|
+
const outcome = applyImagePolicy(parsed);
|
|
233
|
+
if (!outcome.ok) {
|
|
234
|
+
drop('image', outcome.code, outcome.reason);
|
|
235
|
+
blockImage(outcome.reason);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (outcome.value !== original) {
|
|
239
|
+
data.attrValue = outcome.value;
|
|
240
|
+
if (outcome.rewritten) {
|
|
241
|
+
record(s, { kind: 'rewritten', subject: 'image', code: outcome.rewritten.code, reason: outcome.rewritten.reason, tag, attribute: name, value: original });
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
purify.addHook('afterSanitizeAttributes', (node) => {
|
|
246
|
+
const s = state;
|
|
247
|
+
if (!s)
|
|
248
|
+
return;
|
|
249
|
+
// Tripwire: DOMPurify never lets an event handler through; if one ever did, remove it and say so.
|
|
250
|
+
for (const attr of Array.from(node.attributes)) {
|
|
251
|
+
if (/^on/i.test(attr.name)) {
|
|
252
|
+
node.removeAttribute(attr.name);
|
|
253
|
+
record(s, { kind: 'blocked', subject: 'attribute', code: 'event-handler-survived', reason: 'event handler attribute survived sanitization', tag: node.nodeName.toLowerCase(), attribute: attr.name, value: attr.value });
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
if (node.hasAttribute('target')) {
|
|
257
|
+
node.setAttribute('rel', 'noopener noreferrer');
|
|
258
|
+
}
|
|
259
|
+
if (node.nodeName.toLowerCase() === 'img') {
|
|
260
|
+
const blocked = s.blockedImages.get(node);
|
|
261
|
+
if (blocked) {
|
|
262
|
+
s.blockedImages.delete(node);
|
|
263
|
+
// The placeholder goes back through the same sanitizer pass: its href and class obey the policy too.
|
|
264
|
+
node.replaceWith(makePlaceholder(node, blocked));
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (!node.hasAttribute('src')) {
|
|
268
|
+
node.remove();
|
|
269
|
+
record(s, { kind: 'blocked', subject: 'element', code: 'image-without-src', reason: 'image without an allowed src removed', tag: 'img' });
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
const collectRemoved = (s) => {
|
|
274
|
+
for (const removed of purify.removed) {
|
|
275
|
+
if ('element' in removed && removed.element) {
|
|
276
|
+
const el = removed.element;
|
|
277
|
+
record(s, { kind: 'blocked', subject: 'element', code: 'element-not-allowed', reason: 'element is not allowed', tag: (el.nodeName ?? '').toLowerCase() });
|
|
278
|
+
}
|
|
279
|
+
else if ('attribute' in removed && removed.attribute) {
|
|
280
|
+
const attr = removed.attribute;
|
|
281
|
+
const tag = (removed.from?.nodeName ?? '').toLowerCase();
|
|
282
|
+
if (s.recorded.has(`${tag}|${attr.name}|${attr.value}`))
|
|
283
|
+
continue;
|
|
284
|
+
record(s, { kind: 'blocked', subject: 'attribute', code: 'attribute-not-allowed', reason: 'attribute is not allowed', tag, attribute: attr.name, value: attr.value });
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
/** One sanitize call: fresh state for the hooks, the journal collected, the state cleared whatever happens. */
|
|
289
|
+
const run = (sanitize) => {
|
|
290
|
+
if (state !== null) {
|
|
291
|
+
throw new Error('@render-policy/core: the sanitizer was re-entered while a sanitize call was in progress');
|
|
292
|
+
}
|
|
293
|
+
const s = { decisions: [], blockedImages: new WeakMap(), recorded: new Set() };
|
|
294
|
+
state = s;
|
|
295
|
+
try {
|
|
296
|
+
const output = sanitize();
|
|
297
|
+
collectRemoved(s);
|
|
298
|
+
return { output, decisions: s.decisions };
|
|
299
|
+
}
|
|
300
|
+
finally {
|
|
301
|
+
state = null;
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
return {
|
|
305
|
+
toFragment: (html) => run(() => purify.sanitize(html, { ...config, RETURN_DOM_FRAGMENT: true })),
|
|
306
|
+
toTrustedHTML: (html) => run(() => purify.sanitize(html, { ...config, RETURN_TRUSTED_TYPE: true })),
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
function makePlaceholder(img, blocked) {
|
|
310
|
+
const doc = img.ownerDocument;
|
|
311
|
+
const link = doc.createElement('a');
|
|
312
|
+
link.setAttribute('class', 'rp-blocked-image');
|
|
313
|
+
link.setAttribute('href', blocked.src);
|
|
314
|
+
link.setAttribute('target', '_blank');
|
|
315
|
+
link.setAttribute('rel', 'noopener noreferrer');
|
|
316
|
+
link.setAttribute('title', blocked.reason);
|
|
317
|
+
const alt = img.getAttribute('alt')?.trim();
|
|
318
|
+
link.textContent = alt ? `[image blocked: ${alt}]` : '[image blocked]';
|
|
319
|
+
return link;
|
|
320
|
+
}
|
|
321
|
+
//# sourceMappingURL=sanitize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanitize.js","sourceRoot":"","sources":["../src/sanitize.ts"],"names":[],"mappings":"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"}
|
package/dist/sinks.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type SinkCategory = 'oast' | 'webhook' | 'tunnel' | 'serverless' | 'forms' | 'blob-storage';
|
|
2
|
+
export interface SinkEntry {
|
|
3
|
+
/** `host` matches the host and its subdomains; `host/path` additionally requires the path prefix. */
|
|
4
|
+
readonly pattern: string;
|
|
5
|
+
readonly category: SinkCategory;
|
|
6
|
+
}
|
|
7
|
+
export interface SinkDenylist {
|
|
8
|
+
/** Date of the list, so a stale list is visible in the journal and in bug reports. */
|
|
9
|
+
readonly version: string;
|
|
10
|
+
readonly entries: readonly SinkEntry[];
|
|
11
|
+
}
|
|
12
|
+
/** Match a URL against the denylist. Hosts match exactly or as a subdomain; an optional path prefix narrows the match. */
|
|
13
|
+
export declare function matchSink(url: URL, list: SinkDenylist): SinkEntry | null;
|
|
14
|
+
/** Merge lists; later entries win nothing, they are simply appended. */
|
|
15
|
+
export declare function mergeSinkDenylists(...lists: readonly SinkDenylist[]): SinkDenylist;
|
|
16
|
+
//# sourceMappingURL=sinks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sinks.d.ts","sourceRoot":"","sources":["../src/sinks.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,YAAY,GAAG,OAAO,GAAG,cAAc,CAAC;AAEnG,MAAM,WAAW,SAAS;IACxB,qGAAqG;IACrG,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;CACjC;AAED,MAAM,WAAW,YAAY;IAC3B,sFAAsF;IACtF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,SAAS,SAAS,EAAE,CAAC;CACxC;AAED,0HAA0H;AAC1H,wBAAgB,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,YAAY,GAAG,SAAS,GAAG,IAAI,CAYxE;AAED,wEAAwE;AACxE,wBAAgB,kBAAkB,CAAC,GAAG,KAAK,EAAE,SAAS,YAAY,EAAE,GAAG,YAAY,CAKlF"}
|
package/dist/sinks.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Match a URL against the denylist. Hosts match exactly or as a subdomain; an optional path prefix narrows the match. */
|
|
2
|
+
export function matchSink(url, list) {
|
|
3
|
+
const host = url.hostname.toLowerCase();
|
|
4
|
+
const path = url.pathname;
|
|
5
|
+
for (const entry of list.entries) {
|
|
6
|
+
const slash = entry.pattern.indexOf('/');
|
|
7
|
+
const hostPattern = (slash === -1 ? entry.pattern : entry.pattern.slice(0, slash)).toLowerCase();
|
|
8
|
+
const pathPattern = slash === -1 ? null : entry.pattern.slice(slash);
|
|
9
|
+
if (host !== hostPattern && !host.endsWith(`.${hostPattern}`))
|
|
10
|
+
continue;
|
|
11
|
+
if (pathPattern !== null && !path.startsWith(pathPattern))
|
|
12
|
+
continue;
|
|
13
|
+
return entry;
|
|
14
|
+
}
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
/** Merge lists; later entries win nothing, they are simply appended. */
|
|
18
|
+
export function mergeSinkDenylists(...lists) {
|
|
19
|
+
return {
|
|
20
|
+
version: lists.map((l) => l.version).sort().at(-1) ?? '0',
|
|
21
|
+
entries: lists.flatMap((l) => l.entries),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=sinks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sinks.js","sourceRoot":"","sources":["../src/sinks.ts"],"names":[],"mappings":"AAcA,0HAA0H;AAC1H,MAAM,UAAU,SAAS,CAAC,GAAQ,EAAE,IAAkB;IACpD,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;IACxC,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC;IAC1B,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QACjC,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACzC,MAAM,WAAW,GAAG,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;QACjG,MAAM,WAAW,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACrE,IAAI,IAAI,KAAK,WAAW,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,WAAW,EAAE,CAAC;YAAE,SAAS;QACxE,IAAI,WAAW,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC;YAAE,SAAS;QACpE,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,kBAAkB,CAAC,GAAG,KAA8B;IAClE,OAAO;QACL,OAAO,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG;QACzD,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;KACzC,CAAC;AACJ,CAAC"}
|
package/dist/stream.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { RenderOptions, RenderResult } from './render.js';
|
|
2
|
+
export type Scheduler = (render: () => void) => void;
|
|
3
|
+
export interface StreamOptions {
|
|
4
|
+
/** Treat pushed chunks as Markdown (default) or HTML. */
|
|
5
|
+
readonly mode?: 'markdown' | 'html';
|
|
6
|
+
/** Coalesce renders, for example one per animation frame. Default: render synchronously on every push. */
|
|
7
|
+
readonly schedule?: Scheduler;
|
|
8
|
+
/** Withhold a link, image or tag whose URL has not been closed yet. Default true. */
|
|
9
|
+
readonly holdIncompleteUrls?: boolean;
|
|
10
|
+
/** Close an unfinished ``` fence so a streaming code block renders as code, not as Markdown. Default true. */
|
|
11
|
+
readonly closeFences?: boolean;
|
|
12
|
+
/** Keep unchanged leading blocks in place and replace only from the first change. Default true. */
|
|
13
|
+
readonly patch?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface RenderStream {
|
|
16
|
+
/** Append a chunk and (re)render. */
|
|
17
|
+
push(chunk: string): void;
|
|
18
|
+
/** Replace the whole text and (re)render; for frameworks that hold the growing string. */
|
|
19
|
+
set(text: string): void;
|
|
20
|
+
/** Final render: nothing is withheld any more because nothing more is coming. Returns its decisions. */
|
|
21
|
+
end(): RenderResult;
|
|
22
|
+
/** Clear the buffer and the target. */
|
|
23
|
+
reset(): void;
|
|
24
|
+
readonly text: string;
|
|
25
|
+
readonly ended: boolean;
|
|
26
|
+
}
|
|
27
|
+
export interface StreamRenderOps {
|
|
28
|
+
markdown(target: ParentNode, markdown: string, options: RenderOptions): RenderResult;
|
|
29
|
+
html(target: ParentNode, html: string, options: RenderOptions): RenderResult;
|
|
30
|
+
}
|
|
31
|
+
export declare function createRenderStream(target: ParentNode, options: StreamOptions, ops: StreamRenderOps): RenderStream;
|
|
32
|
+
/**
|
|
33
|
+
* The scheduler an adapter should use unless told otherwise: one render per animation
|
|
34
|
+
* frame where the window has requestAnimationFrame, synchronous rendering elsewhere.
|
|
35
|
+
*/
|
|
36
|
+
export declare function defaultScheduler(win?: {
|
|
37
|
+
requestAnimationFrame?: (callback: () => void) => number;
|
|
38
|
+
} | null | undefined): Scheduler;
|
|
39
|
+
/** Coalesce renders to one per animation frame. */
|
|
40
|
+
export declare function frameScheduler(win: {
|
|
41
|
+
requestAnimationFrame(callback: () => void): number;
|
|
42
|
+
}): Scheduler;
|
|
43
|
+
/** Append the closing fence of an unfinished ``` or ~~~ block, and say whether one had to be closed. */
|
|
44
|
+
export declare function completeFences(markdown: string): {
|
|
45
|
+
readonly text: string;
|
|
46
|
+
readonly closed: boolean;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Cut the buffer before any link, image, autolink, reference definition or
|
|
50
|
+
* raw tag whose URL has not been closed yet. A half-received URL is never
|
|
51
|
+
* turned into a request; the construct appears once it is complete and
|
|
52
|
+
* has passed the policy.
|
|
53
|
+
*/
|
|
54
|
+
export declare function holdIncompleteMarkdown(markdown: string): string;
|
|
55
|
+
/** Cut the buffer before a raw tag that has not been closed with `>`. */
|
|
56
|
+
export declare function holdIncompleteHtml(html: string): string;
|
|
57
|
+
//# sourceMappingURL=stream.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../src/stream.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE/D,MAAM,MAAM,SAAS,GAAG,CAAC,MAAM,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;AAErD,MAAM,WAAW,aAAa;IAC5B,yDAAyD;IACzD,QAAQ,CAAC,IAAI,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC;IACpC,0GAA0G;IAC1G,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAC;IAC9B,qFAAqF;IACrF,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IACtC,8GAA8G;IAC9G,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAC/B,mGAAmG;IACnG,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,YAAY;IAC3B,qCAAqC;IACrC,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,0FAA0F;IAC1F,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,wGAAwG;IACxG,GAAG,IAAI,YAAY,CAAC;IACpB,uCAAuC;IACvC,KAAK,IAAI,IAAI,CAAC;IACd,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,YAAY,CAAC;IACrF,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,YAAY,CAAC;CAC9E;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,aAAa,EAAE,GAAG,EAAE,eAAe,GAAG,YAAY,CAyDjH;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,GAAE;IAAE,qBAAqB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,CAAA;CAAE,GAAG,IAAI,GAAG,SAA0B,GAAG,SAAS,CAGjJ;AAMD,mDAAmD;AACnD,wBAAgB,cAAc,CAAC,GAAG,EAAE;IAAE,qBAAqB,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,CAAA;CAAE,GAAG,SAAS,CActG;AAID,wGAAwG;AACxG,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CAmBpG;AAMD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAwB/D;AAED,yEAAyE;AACzE,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvD"}
|
package/dist/stream.js
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
export function createRenderStream(target, options, ops) {
|
|
2
|
+
const mode = options.mode ?? 'markdown';
|
|
3
|
+
const schedule = options.schedule ?? ((render) => render());
|
|
4
|
+
const hold = options.holdIncompleteUrls ?? true;
|
|
5
|
+
const closeFences = options.closeFences ?? true;
|
|
6
|
+
const patch = options.patch ?? true;
|
|
7
|
+
let text = '';
|
|
8
|
+
let ended = false;
|
|
9
|
+
const render = (final) => {
|
|
10
|
+
let source = text;
|
|
11
|
+
if (mode === 'markdown') {
|
|
12
|
+
if (!final && hold)
|
|
13
|
+
source = holdIncompleteMarkdown(source);
|
|
14
|
+
let openFenceAtEnd = false;
|
|
15
|
+
if (closeFences) {
|
|
16
|
+
const completed = completeFences(source);
|
|
17
|
+
source = completed.text;
|
|
18
|
+
openFenceAtEnd = completed.closed;
|
|
19
|
+
}
|
|
20
|
+
return ops.markdown(target, source, { patch, streaming: true, final, openFenceAtEnd });
|
|
21
|
+
}
|
|
22
|
+
if (!final && hold)
|
|
23
|
+
source = holdIncompleteHtml(source);
|
|
24
|
+
return ops.html(target, source, { patch, streaming: true, final, openFenceAtEnd: false });
|
|
25
|
+
};
|
|
26
|
+
return {
|
|
27
|
+
push(chunk) {
|
|
28
|
+
if (ended)
|
|
29
|
+
throw new Error('@render-policy/core: push() after end()');
|
|
30
|
+
text += chunk;
|
|
31
|
+
schedule(() => {
|
|
32
|
+
if (!ended)
|
|
33
|
+
render(false);
|
|
34
|
+
});
|
|
35
|
+
},
|
|
36
|
+
set(next) {
|
|
37
|
+
if (ended)
|
|
38
|
+
throw new Error('@render-policy/core: set() after end()');
|
|
39
|
+
text = next;
|
|
40
|
+
schedule(() => {
|
|
41
|
+
if (!ended)
|
|
42
|
+
render(false);
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
end() {
|
|
46
|
+
if (ended)
|
|
47
|
+
return { decisions: [] };
|
|
48
|
+
ended = true;
|
|
49
|
+
return render(true);
|
|
50
|
+
},
|
|
51
|
+
reset() {
|
|
52
|
+
text = '';
|
|
53
|
+
ended = false;
|
|
54
|
+
target.replaceChildren();
|
|
55
|
+
},
|
|
56
|
+
get text() {
|
|
57
|
+
return text;
|
|
58
|
+
},
|
|
59
|
+
get ended() {
|
|
60
|
+
return ended;
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The scheduler an adapter should use unless told otherwise: one render per animation
|
|
66
|
+
* frame where the window has requestAnimationFrame, synchronous rendering elsewhere.
|
|
67
|
+
*/
|
|
68
|
+
export function defaultScheduler(win = globalWindow()) {
|
|
69
|
+
if (win && typeof win.requestAnimationFrame === 'function')
|
|
70
|
+
return frameScheduler(win);
|
|
71
|
+
return (render) => render();
|
|
72
|
+
}
|
|
73
|
+
function globalWindow() {
|
|
74
|
+
return typeof window === 'undefined' ? undefined : window;
|
|
75
|
+
}
|
|
76
|
+
/** Coalesce renders to one per animation frame. */
|
|
77
|
+
export function frameScheduler(win) {
|
|
78
|
+
let pending = false;
|
|
79
|
+
let latest = null;
|
|
80
|
+
return (render) => {
|
|
81
|
+
latest = render;
|
|
82
|
+
if (pending)
|
|
83
|
+
return;
|
|
84
|
+
pending = true;
|
|
85
|
+
win.requestAnimationFrame(() => {
|
|
86
|
+
pending = false;
|
|
87
|
+
const run = latest;
|
|
88
|
+
latest = null;
|
|
89
|
+
run?.();
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
const FENCE_RE = /^ {0,3}(`{3,}|~{3,})(.*)$/;
|
|
94
|
+
/** Append the closing fence of an unfinished ``` or ~~~ block, and say whether one had to be closed. */
|
|
95
|
+
export function completeFences(markdown) {
|
|
96
|
+
let open = null;
|
|
97
|
+
for (const line of markdown.split('\n')) {
|
|
98
|
+
const match = FENCE_RE.exec(line);
|
|
99
|
+
if (!match)
|
|
100
|
+
continue;
|
|
101
|
+
const fence = match[1] ?? '';
|
|
102
|
+
const rest = match[2] ?? '';
|
|
103
|
+
const char = fence[0] ?? '`';
|
|
104
|
+
if (open === null) {
|
|
105
|
+
// A backtick fence's info string may not contain backticks.
|
|
106
|
+
if (char === '`' && rest.includes('`'))
|
|
107
|
+
continue;
|
|
108
|
+
open = { char, length: fence.length };
|
|
109
|
+
}
|
|
110
|
+
else if (char === open.char && fence.length >= open.length && rest.trim() === '') {
|
|
111
|
+
open = null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (open === null)
|
|
115
|
+
return { text: markdown, closed: false };
|
|
116
|
+
const newline = markdown.endsWith('\n') ? '' : '\n';
|
|
117
|
+
return { text: `${markdown}${newline}${open.char.repeat(open.length)}`, closed: true };
|
|
118
|
+
}
|
|
119
|
+
// A bare URL still being received. A URL preceded by `(` or `<` belongs to a link or autolink handled above.
|
|
120
|
+
const BARE_URL_TAIL_RE = /(?:^|\s)((?:https?:\/\/|www\.)\S*)$/i;
|
|
121
|
+
const REFERENCE_DEFINITION_TAIL_RE = /^ {0,3}\[[^\]]*\]:\s*\S*$/;
|
|
122
|
+
/**
|
|
123
|
+
* Cut the buffer before any link, image, autolink, reference definition or
|
|
124
|
+
* raw tag whose URL has not been closed yet. A half-received URL is never
|
|
125
|
+
* turned into a request; the construct appears once it is complete and
|
|
126
|
+
* has passed the policy.
|
|
127
|
+
*/
|
|
128
|
+
export function holdIncompleteMarkdown(markdown) {
|
|
129
|
+
let cut = markdown.length;
|
|
130
|
+
const lastDestination = markdown.lastIndexOf('](');
|
|
131
|
+
if (lastDestination !== -1 && !hasClosingParen(markdown, lastDestination + 2)) {
|
|
132
|
+
const bracket = markdown.lastIndexOf('[', lastDestination);
|
|
133
|
+
let start = bracket === -1 ? lastDestination : bracket;
|
|
134
|
+
if (start > 0 && markdown[start - 1] === '!')
|
|
135
|
+
start -= 1;
|
|
136
|
+
cut = Math.min(cut, start);
|
|
137
|
+
}
|
|
138
|
+
cut = Math.min(cut, incompleteTagStart(markdown));
|
|
139
|
+
const bare = BARE_URL_TAIL_RE.exec(markdown);
|
|
140
|
+
if (bare) {
|
|
141
|
+
cut = Math.min(cut, bare.index + bare[0].length - (bare[1] ?? '').length);
|
|
142
|
+
}
|
|
143
|
+
const lastLineStart = markdown.lastIndexOf('\n') + 1;
|
|
144
|
+
if (REFERENCE_DEFINITION_TAIL_RE.test(markdown.slice(lastLineStart))) {
|
|
145
|
+
cut = Math.min(cut, lastLineStart);
|
|
146
|
+
}
|
|
147
|
+
return markdown.slice(0, cut);
|
|
148
|
+
}
|
|
149
|
+
/** Cut the buffer before a raw tag that has not been closed with `>`. */
|
|
150
|
+
export function holdIncompleteHtml(html) {
|
|
151
|
+
return html.slice(0, incompleteTagStart(html));
|
|
152
|
+
}
|
|
153
|
+
function incompleteTagStart(text) {
|
|
154
|
+
const lastLt = text.lastIndexOf('<');
|
|
155
|
+
if (lastLt === -1)
|
|
156
|
+
return text.length;
|
|
157
|
+
const lastGt = text.lastIndexOf('>');
|
|
158
|
+
if (lastGt > lastLt)
|
|
159
|
+
return text.length;
|
|
160
|
+
return /^<[a-zA-Z!/?]/.test(text.slice(lastLt, lastLt + 2)) || lastLt === text.length - 1 ? lastLt : text.length;
|
|
161
|
+
}
|
|
162
|
+
function hasClosingParen(text, from) {
|
|
163
|
+
let depth = 0;
|
|
164
|
+
for (let i = from; i < text.length; i += 1) {
|
|
165
|
+
const ch = text[i];
|
|
166
|
+
if (ch === '\\') {
|
|
167
|
+
i += 1;
|
|
168
|
+
}
|
|
169
|
+
else if (ch === '(') {
|
|
170
|
+
depth += 1;
|
|
171
|
+
}
|
|
172
|
+
else if (ch === ')') {
|
|
173
|
+
if (depth === 0)
|
|
174
|
+
return true;
|
|
175
|
+
depth -= 1;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=stream.js.map
|