@wdprlib/render 1.0.0-rc.0 → 1.0.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.cjs CHANGED
@@ -4209,8 +4209,8 @@ function renderGitlabSnippet(ctx, snippetId) {
4209
4209
  }
4210
4210
 
4211
4211
  // packages/render/src/elements/embed-block.ts
4212
- var import_dompurify = __toESM(require("dompurify"));
4213
- var import_jsdom = require("jsdom");
4212
+ var import_htmlparser2 = require("htmlparser2");
4213
+ var import_sanitize_html = __toESM(require("sanitize-html"));
4214
4214
  var BOOLEAN_ATTRIBUTES = [
4215
4215
  "allowfullscreen",
4216
4216
  "async",
@@ -4244,21 +4244,42 @@ var DEFAULT_EMBED_ALLOWLIST = [
4244
4244
  { host: "w.soundcloud.com", pathPrefix: "/player/" },
4245
4245
  { host: "codepen.io" }
4246
4246
  ];
4247
- var window = new import_jsdom.JSDOM("").window;
4248
- var purify = import_dompurify.default(window);
4249
- purify.addHook("uponSanitizeAttribute", (_node, data) => {
4250
- if (data.attrName === "src" && data.attrValue) {
4251
- if (!data.attrValue.toLowerCase().startsWith("https://")) {
4252
- data.attrValue = "";
4253
- data.forceKeepAttr = false;
4247
+ var SANITIZE_CONFIG = {
4248
+ allowedTags: ["iframe"],
4249
+ allowedAttributes: {
4250
+ iframe: [
4251
+ "src",
4252
+ "allow",
4253
+ "allowfullscreen",
4254
+ "frameborder",
4255
+ "height",
4256
+ "loading",
4257
+ "referrerpolicy",
4258
+ "sandbox",
4259
+ "title",
4260
+ "width"
4261
+ ]
4262
+ },
4263
+ allowedSchemes: ["https"]
4264
+ };
4265
+ function findIframes(html) {
4266
+ const doc = import_htmlparser2.parseDocument(html);
4267
+ const iframes = [];
4268
+ function walk(nodes) {
4269
+ for (const node of nodes) {
4270
+ if (node.type === "tag") {
4271
+ if (node.name === "iframe") {
4272
+ iframes.push(node);
4273
+ }
4274
+ if (node.children) {
4275
+ walk(node.children);
4276
+ }
4277
+ }
4254
4278
  }
4255
4279
  }
4256
- });
4257
- var DOMPURIFY_CONFIG = {
4258
- ALLOWED_TAGS: ["iframe"],
4259
- ADD_ATTR: ["allow", "allowfullscreen", "frameborder", "loading", "referrerpolicy", "sandbox"],
4260
- FORBID_ATTR: ["srcdoc", "onload", "onerror", "onclick"]
4261
- };
4280
+ walk(doc.children);
4281
+ return iframes;
4282
+ }
4262
4283
  function matchesHostPattern(hostname, pattern) {
4263
4284
  const lowerHostname = hostname.toLowerCase();
4264
4285
  const lowerPattern = pattern.toLowerCase();
@@ -4288,20 +4309,16 @@ function matchesAllowlistEntry(url, entry) {
4288
4309
  return true;
4289
4310
  }
4290
4311
  function validateAndSanitizeEmbed(content, allowlist) {
4291
- const sanitized = purify.sanitize(content.trim(), {
4292
- ...DOMPURIFY_CONFIG,
4293
- RETURN_TRUSTED_TYPE: false
4294
- });
4312
+ const sanitized = import_sanitize_html.default(content.trim(), SANITIZE_CONFIG);
4295
4313
  if (!sanitized.trim()) {
4296
4314
  return null;
4297
4315
  }
4298
- const dom = new import_jsdom.JSDOM(sanitized);
4299
- const iframes = dom.window.document.querySelectorAll("iframe");
4316
+ const iframes = findIframes(sanitized);
4300
4317
  if (iframes.length !== 1) {
4301
4318
  return null;
4302
4319
  }
4303
4320
  const iframe = iframes[0];
4304
- const src = iframe.getAttribute("src")?.trim();
4321
+ const src = iframe.attribs.src?.trim();
4305
4322
  if (!src) {
4306
4323
  return null;
4307
4324
  }
package/dist/index.d.cts CHANGED
@@ -15,7 +15,7 @@ interface EmbedAllowlistEntry {
15
15
  * Only iframes with src matching these host+path patterns will be rendered.
16
16
  *
17
17
  * Note: Set to null to allow any HTTPS iframe (Wikidot's 'anyiframe' behavior).
18
- * DOMPurify still enforces HTTPS-only and blocks dangerous attributes.
18
+ * sanitize-html still enforces HTTPS-only and blocks dangerous attributes.
19
19
  */
20
20
  declare const DEFAULT_EMBED_ALLOWLIST: EmbedAllowlistEntry[] | null;
21
21
  /**
package/dist/index.d.ts CHANGED
@@ -15,7 +15,7 @@ interface EmbedAllowlistEntry {
15
15
  * Only iframes with src matching these host+path patterns will be rendered.
16
16
  *
17
17
  * Note: Set to null to allow any HTTPS iframe (Wikidot's 'anyiframe' behavior).
18
- * DOMPurify still enforces HTTPS-only and blocks dangerous attributes.
18
+ * sanitize-html still enforces HTTPS-only and blocks dangerous attributes.
19
19
  */
20
20
  declare const DEFAULT_EMBED_ALLOWLIST: EmbedAllowlistEntry[] | null;
21
21
  /**
package/dist/index.js CHANGED
@@ -4159,8 +4159,8 @@ function renderGitlabSnippet(ctx, snippetId) {
4159
4159
  }
4160
4160
 
4161
4161
  // packages/render/src/elements/embed-block.ts
4162
- import DOMPurify from "dompurify";
4163
- import { JSDOM } from "jsdom";
4162
+ import { parseDocument } from "htmlparser2";
4163
+ import sanitizeHtml from "sanitize-html";
4164
4164
  var BOOLEAN_ATTRIBUTES = [
4165
4165
  "allowfullscreen",
4166
4166
  "async",
@@ -4194,21 +4194,42 @@ var DEFAULT_EMBED_ALLOWLIST = [
4194
4194
  { host: "w.soundcloud.com", pathPrefix: "/player/" },
4195
4195
  { host: "codepen.io" }
4196
4196
  ];
4197
- var window = new JSDOM("").window;
4198
- var purify = DOMPurify(window);
4199
- purify.addHook("uponSanitizeAttribute", (_node, data) => {
4200
- if (data.attrName === "src" && data.attrValue) {
4201
- if (!data.attrValue.toLowerCase().startsWith("https://")) {
4202
- data.attrValue = "";
4203
- data.forceKeepAttr = false;
4197
+ var SANITIZE_CONFIG = {
4198
+ allowedTags: ["iframe"],
4199
+ allowedAttributes: {
4200
+ iframe: [
4201
+ "src",
4202
+ "allow",
4203
+ "allowfullscreen",
4204
+ "frameborder",
4205
+ "height",
4206
+ "loading",
4207
+ "referrerpolicy",
4208
+ "sandbox",
4209
+ "title",
4210
+ "width"
4211
+ ]
4212
+ },
4213
+ allowedSchemes: ["https"]
4214
+ };
4215
+ function findIframes(html) {
4216
+ const doc = parseDocument(html);
4217
+ const iframes = [];
4218
+ function walk(nodes) {
4219
+ for (const node of nodes) {
4220
+ if (node.type === "tag") {
4221
+ if (node.name === "iframe") {
4222
+ iframes.push(node);
4223
+ }
4224
+ if (node.children) {
4225
+ walk(node.children);
4226
+ }
4227
+ }
4204
4228
  }
4205
4229
  }
4206
- });
4207
- var DOMPURIFY_CONFIG = {
4208
- ALLOWED_TAGS: ["iframe"],
4209
- ADD_ATTR: ["allow", "allowfullscreen", "frameborder", "loading", "referrerpolicy", "sandbox"],
4210
- FORBID_ATTR: ["srcdoc", "onload", "onerror", "onclick"]
4211
- };
4230
+ walk(doc.children);
4231
+ return iframes;
4232
+ }
4212
4233
  function matchesHostPattern(hostname, pattern) {
4213
4234
  const lowerHostname = hostname.toLowerCase();
4214
4235
  const lowerPattern = pattern.toLowerCase();
@@ -4238,20 +4259,16 @@ function matchesAllowlistEntry(url, entry) {
4238
4259
  return true;
4239
4260
  }
4240
4261
  function validateAndSanitizeEmbed(content, allowlist) {
4241
- const sanitized = purify.sanitize(content.trim(), {
4242
- ...DOMPURIFY_CONFIG,
4243
- RETURN_TRUSTED_TYPE: false
4244
- });
4262
+ const sanitized = sanitizeHtml(content.trim(), SANITIZE_CONFIG);
4245
4263
  if (!sanitized.trim()) {
4246
4264
  return null;
4247
4265
  }
4248
- const dom = new JSDOM(sanitized);
4249
- const iframes = dom.window.document.querySelectorAll("iframe");
4266
+ const iframes = findIframes(sanitized);
4250
4267
  if (iframes.length !== 1) {
4251
4268
  return null;
4252
4269
  }
4253
4270
  const iframe = iframes[0];
4254
- const src = iframe.getAttribute("src")?.trim();
4271
+ const src = iframe.attribs.src?.trim();
4255
4272
  if (!src) {
4256
4273
  return null;
4257
4274
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wdprlib/render",
3
- "version": "1.0.0-rc.0",
3
+ "version": "1.0.1",
4
4
  "description": "HTML renderer for Wikidot markup",
5
5
  "keywords": [
6
6
  "html",
@@ -39,13 +39,13 @@
39
39
  "access": "public"
40
40
  },
41
41
  "dependencies": {
42
- "@wdprlib/ast": "1.0.0-rc.0",
43
- "dompurify": "^3.3.1",
44
- "jsdom": "^28.0.0",
42
+ "@wdprlib/ast": "1.0.0",
43
+ "domhandler": "^5.0.3",
44
+ "htmlparser2": "^10.0.0",
45
+ "sanitize-html": "^2.14.0",
45
46
  "temml": "^0.13.1"
46
47
  },
47
48
  "devDependencies": {
48
- "@types/dompurify": "^3.2.0",
49
- "@types/jsdom": "^27.0.0"
49
+ "@types/sanitize-html": "^2.13.0"
50
50
  }
51
51
  }