@instructure/platform-sanitize 0.4.0 → 0.4.1
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/dist/index.js +95 -74
- package/dist/sanitizeHtml.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
const
|
|
1
|
+
import f from "dompurify";
|
|
2
|
+
const w = /* @__PURE__ */ new Set([
|
|
3
3
|
// layout
|
|
4
4
|
"display",
|
|
5
5
|
"float",
|
|
@@ -241,7 +241,7 @@ const m = /* @__PURE__ */ new Set([
|
|
|
241
241
|
"caret-color",
|
|
242
242
|
"accent-color",
|
|
243
243
|
"appearance"
|
|
244
|
-
]),
|
|
244
|
+
]), h = /* @__PURE__ */ new Set([
|
|
245
245
|
"src",
|
|
246
246
|
"href",
|
|
247
247
|
"action",
|
|
@@ -252,7 +252,7 @@ const m = /* @__PURE__ */ new Set([
|
|
|
252
252
|
"cite",
|
|
253
253
|
"longdesc",
|
|
254
254
|
"xlink:href"
|
|
255
|
-
]),
|
|
255
|
+
]), y = ["content"], A = /url\s*\(\s*['"]?(?:[a-z][a-z0-9+\-.]*:|\/\/)/i, k = /* @__PURE__ */ new Set([
|
|
256
256
|
"allow-downloads",
|
|
257
257
|
"allow-forms",
|
|
258
258
|
"allow-modals",
|
|
@@ -265,8 +265,22 @@ const m = /* @__PURE__ */ new Set([
|
|
|
265
265
|
"allow-scripts",
|
|
266
266
|
"allow-storage-access-by-user-activation",
|
|
267
267
|
"allow-top-navigation-by-user-activation"
|
|
268
|
-
]),
|
|
269
|
-
|
|
268
|
+
]), p = {
|
|
269
|
+
// semantics and annotation are re-allowed because DOMPurify blocks all of
|
|
270
|
+
// mathMlDisallowed by default. semantics is a safe structural wrapper;
|
|
271
|
+
// annotation carries LaTeX source text (e.g. \frac{1}{2}); Canvas emits
|
|
272
|
+
// text-only content here, and any child elements would be hoisted out of
|
|
273
|
+
// the MathML context and sanitized as ordinary HTML.
|
|
274
|
+
//
|
|
275
|
+
// annotation-xml is re-added here and filtered by an afterSanitizeElements
|
|
276
|
+
// hook below: it is stripped when its encoding makes it an HTML integration
|
|
277
|
+
// point ("text/html" or "application/xhtml+xml" per the WHATWG spec), which
|
|
278
|
+
// is the mXSS namespace-confusion vector. Semantic encodings used by external
|
|
279
|
+
// tools (MathML-Content, MathML-Presentation from MathType/Wolfram Alpha,
|
|
280
|
+
// application/x-tex, …) are not integration points and are preserved, so this
|
|
281
|
+
// layer no longer strips the benign annotation-xml the backend and TinyMCE
|
|
282
|
+
// already allow.
|
|
283
|
+
ADD_TAGS: ["iframe", "semantics", "annotation", "annotation-xml"],
|
|
270
284
|
ADD_ATTR: [
|
|
271
285
|
"allowfullscreen",
|
|
272
286
|
"allow",
|
|
@@ -310,17 +324,18 @@ const m = /* @__PURE__ */ new Set([
|
|
|
310
324
|
// confusion attacks where fragments like <svg> could influence parse context.
|
|
311
325
|
FORCE_BODY: !0
|
|
312
326
|
};
|
|
313
|
-
let
|
|
314
|
-
function
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
327
|
+
let s = null;
|
|
328
|
+
function E() {
|
|
329
|
+
if (s) return s;
|
|
330
|
+
s = typeof f == "function" ? f(window) : f, s.addHook("afterSanitizeAttributes", (t) => {
|
|
331
|
+
if (!(t instanceof Element) || !t.hasAttribute("style")) return;
|
|
332
|
+
const e = t.style, r = [];
|
|
333
|
+
for (let i = 0; i < e.length; i++) {
|
|
334
|
+
const l = e.item(i);
|
|
335
|
+
w.has(l) || r.push(l);
|
|
321
336
|
}
|
|
322
|
-
for (const
|
|
323
|
-
const
|
|
337
|
+
for (const i of r) e.removeProperty(i);
|
|
338
|
+
const a = /* @__PURE__ */ new Set([
|
|
324
339
|
"static",
|
|
325
340
|
"relative",
|
|
326
341
|
"absolute",
|
|
@@ -329,53 +344,59 @@ function k() {
|
|
|
329
344
|
"unset",
|
|
330
345
|
"revert",
|
|
331
346
|
"revert-layer"
|
|
332
|
-
]),
|
|
333
|
-
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
for (const
|
|
337
|
-
const
|
|
338
|
-
|
|
347
|
+
]), c = e.getPropertyValue("position").trim().toLowerCase();
|
|
348
|
+
c && !a.has(c) && e.removeProperty("position");
|
|
349
|
+
const d = /* @__PURE__ */ new Set(["initial", "inherit", "unset", "revert", "revert-layer"]), o = e.getPropertyValue("opacity").trim().toLowerCase();
|
|
350
|
+
o && !d.has(o) && (o.endsWith("%") ? parseFloat(o) / 100 : parseFloat(o)) < 0.05 && e.removeProperty("opacity");
|
|
351
|
+
for (const i of y) {
|
|
352
|
+
const l = e.getPropertyValue(i);
|
|
353
|
+
l && A.test(l) && e.removeProperty(i);
|
|
339
354
|
}
|
|
340
|
-
|
|
341
|
-
}),
|
|
342
|
-
if (!
|
|
343
|
-
const
|
|
344
|
-
/^\s*\/\//.test(
|
|
345
|
-
}),
|
|
346
|
-
if (!(
|
|
347
|
-
const
|
|
348
|
-
if (
|
|
349
|
-
|
|
355
|
+
e.length === 0 && t.removeAttribute("style");
|
|
356
|
+
}), s.addHook("uponSanitizeAttribute", (t, e) => {
|
|
357
|
+
if (!h.has(e.attrName)) return;
|
|
358
|
+
const r = e.attrValue;
|
|
359
|
+
/^\s*\/\//.test(r) ? (e.attrValue = r.trimStart().replace(/^\/\//, "https://"), e.keepAttr = !0) : /^\s*\\/.test(r) && (e.keepAttr = !1);
|
|
360
|
+
}), s.addHook("afterSanitizeAttributes", (t) => {
|
|
361
|
+
if (!(t instanceof Element) || !t.hasAttribute("srcset")) return;
|
|
362
|
+
const r = (t.getAttribute("srcset") ?? "").split(","), a = (o) => o.trim().split(/\s+/)[0];
|
|
363
|
+
if (r.some((o) => /^\s*\\/.test(a(o)))) {
|
|
364
|
+
t.removeAttribute("srcset");
|
|
350
365
|
return;
|
|
351
366
|
}
|
|
352
|
-
let
|
|
353
|
-
const
|
|
354
|
-
const
|
|
355
|
-
if (!
|
|
356
|
-
|
|
357
|
-
const
|
|
358
|
-
return
|
|
367
|
+
let c = !1;
|
|
368
|
+
const d = r.map((o) => {
|
|
369
|
+
const i = a(o);
|
|
370
|
+
if (!i.startsWith("//")) return o;
|
|
371
|
+
c = !0;
|
|
372
|
+
const l = o.indexOf(i);
|
|
373
|
+
return o.slice(0, l) + "https://" + i.slice(2) + o.slice(l + i.length);
|
|
359
374
|
});
|
|
360
|
-
|
|
361
|
-
}),
|
|
362
|
-
var
|
|
363
|
-
if (!(
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
}),
|
|
367
|
-
if (!(
|
|
368
|
-
const
|
|
369
|
-
|
|
370
|
-
})
|
|
375
|
+
c && t.setAttribute("srcset", d.join(","));
|
|
376
|
+
}), s.addHook("afterSanitizeAttributes", (t) => {
|
|
377
|
+
var a;
|
|
378
|
+
if (!(t instanceof Element) || t.tagName !== "A" && t.tagName !== "AREA" || ((a = t.getAttribute("target")) == null ? void 0 : a.toLowerCase()) !== "_blank") return;
|
|
379
|
+
const e = t.getAttribute("rel") ?? "", r = new Set(e.split(/\s+/).filter(Boolean));
|
|
380
|
+
r.add("noopener"), t.setAttribute("rel", [...r].join(" "));
|
|
381
|
+
}), s.addHook("afterSanitizeAttributes", (t) => {
|
|
382
|
+
if (!(t instanceof Element) || t.tagName !== "IFRAME" || !t.hasAttribute("sandbox")) return;
|
|
383
|
+
const r = (t.getAttribute("sandbox") ?? "").toLowerCase().split(/\s+/).filter(Boolean), a = r.filter((c) => k.has(c));
|
|
384
|
+
a.length !== r.length && t.setAttribute("sandbox", a.join(" "));
|
|
385
|
+
});
|
|
386
|
+
const n = /* @__PURE__ */ new Set(["text/html", "application/xhtml+xml"]);
|
|
387
|
+
return s.addHook("afterSanitizeElements", (t) => {
|
|
388
|
+
if (!(t instanceof Element) || t.tagName.toLowerCase() !== "annotation-xml") return;
|
|
389
|
+
const e = (t.getAttribute("encoding") ?? "").toLowerCase().trim();
|
|
390
|
+
n.has(e) && t.remove();
|
|
391
|
+
}), s;
|
|
371
392
|
}
|
|
372
|
-
function
|
|
393
|
+
function _(n, t) {
|
|
373
394
|
if (typeof window > "u")
|
|
374
395
|
throw new Error("sanitizeHtml requires a DOM environment (window is not defined)");
|
|
375
|
-
const e = t != null && t.allowFormAttributeNames ? { ...
|
|
376
|
-
return
|
|
396
|
+
const e = t != null && t.allowFormAttributeNames ? { ...p, SANITIZE_DOM: !1 } : p;
|
|
397
|
+
return E().sanitize(n ?? "", e);
|
|
377
398
|
}
|
|
378
|
-
const
|
|
399
|
+
const m = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]), u = "http://platform-sanitize.invalid/", g = /^\s*\/\//, b = (
|
|
379
400
|
// oxlint-disable-next-line no-control-regex -- intentional security guard
|
|
380
401
|
/^[\u0000-\u0020\u007F-\u00A0\u2000-\u200F\u2028\u2029\u202F\u205F\u2060\u3000\uFEFF]*(?:javascript|data|vbscript|file):/i
|
|
381
402
|
), S = {
|
|
@@ -391,49 +412,49 @@ const p = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]), c = "
|
|
|
391
412
|
`
|
|
392
413
|
// java
script:
|
|
393
414
|
};
|
|
394
|
-
function
|
|
395
|
-
return
|
|
396
|
-
const
|
|
397
|
-
if (!Number.isFinite(
|
|
415
|
+
function x(n) {
|
|
416
|
+
return n.replace(/&#(\d+);/g, (t, e) => {
|
|
417
|
+
const r = Number(e);
|
|
418
|
+
if (!Number.isFinite(r) || r < 0 || r > 1114111) return "";
|
|
398
419
|
try {
|
|
399
|
-
return String.fromCodePoint(
|
|
420
|
+
return String.fromCodePoint(r);
|
|
400
421
|
} catch {
|
|
401
422
|
return "";
|
|
402
423
|
}
|
|
403
424
|
}).replace(/&#x([\da-f]+);/gi, (t, e) => {
|
|
404
|
-
const
|
|
405
|
-
if (!Number.isFinite(
|
|
425
|
+
const r = parseInt(e, 16);
|
|
426
|
+
if (!Number.isFinite(r) || r < 0 || r > 1114111) return "";
|
|
406
427
|
try {
|
|
407
|
-
return String.fromCodePoint(
|
|
428
|
+
return String.fromCodePoint(r);
|
|
408
429
|
} catch {
|
|
409
430
|
return "";
|
|
410
431
|
}
|
|
411
432
|
}).replace(/&([A-Za-z][A-Za-z0-9]*);/g, (t, e) => S[e] ?? t);
|
|
412
433
|
}
|
|
413
|
-
function R(
|
|
414
|
-
if (!
|
|
415
|
-
const t =
|
|
416
|
-
if (
|
|
434
|
+
function R(n) {
|
|
435
|
+
if (!n || !n.trim()) return "about:blank";
|
|
436
|
+
const t = n.replace(/\\/g, "/");
|
|
437
|
+
if (g.test(t) || b.test(t)) return "about:blank";
|
|
417
438
|
if (/&[#A-Za-z]/.test(t)) {
|
|
418
|
-
const e =
|
|
419
|
-
if (
|
|
439
|
+
const e = x(t);
|
|
440
|
+
if (g.test(e) || b.test(e))
|
|
420
441
|
return "about:blank";
|
|
421
442
|
try {
|
|
422
|
-
const
|
|
423
|
-
if (!
|
|
443
|
+
const r = new URL(e, u);
|
|
444
|
+
if (!r.href.startsWith(u) && !m.has(r.protocol))
|
|
424
445
|
return "about:blank";
|
|
425
446
|
} catch {
|
|
426
447
|
return "about:blank";
|
|
427
448
|
}
|
|
428
449
|
}
|
|
429
450
|
try {
|
|
430
|
-
const e = new URL(t,
|
|
431
|
-
return !
|
|
451
|
+
const e = new URL(t, u);
|
|
452
|
+
return !m.has(e.protocol) || (e.protocol === "http:" || e.protocol === "https:") && (e.username || e.password) ? "about:blank" : e.href.startsWith(u) ? n : t.replace(/[\x00-\x1F\u2028\u2029]/g, "").replace(/%250[9ad]/gi, "").replace(/%0[9ad]/gi, "");
|
|
432
453
|
} catch {
|
|
433
454
|
return "about:blank";
|
|
434
455
|
}
|
|
435
456
|
}
|
|
436
457
|
export {
|
|
437
|
-
|
|
458
|
+
_ as sanitizeHtml,
|
|
438
459
|
R as sanitizeUrl
|
|
439
460
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sanitizeHtml.d.ts","sourceRoot":"","sources":["../src/sanitizeHtml.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sanitizeHtml.d.ts","sourceRoot":"","sources":["../src/sanitizeHtml.ts"],"names":[],"mappings":"AA8fA,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAC/B,OAAO,CAAC,EAAE;IAAE,uBAAuB,CAAC,EAAE,OAAO,CAAA;CAAE,GAC9C,MAAM,CASR"}
|