@wdprlib/render 1.3.3 → 2.0.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/dist/index.cjs +116 -7
- package/dist/index.js +116 -7
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -356,10 +356,76 @@ function normalizeCssValue(value) {
|
|
|
356
356
|
result = result.replace(/[\s\u0000-\u001f\u007f-\u009f]/g, "");
|
|
357
357
|
return result.toLowerCase();
|
|
358
358
|
}
|
|
359
|
+
function isUrlAllowed(rawUrl) {
|
|
360
|
+
let url = rawUrl;
|
|
361
|
+
if (url.length >= 2) {
|
|
362
|
+
const first = url[0];
|
|
363
|
+
const last = url[url.length - 1];
|
|
364
|
+
if (first === '"' && last === '"' || first === "'" && last === "'") {
|
|
365
|
+
url = url.slice(1, -1);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
if (url === "")
|
|
369
|
+
return true;
|
|
370
|
+
if (url.startsWith("#"))
|
|
371
|
+
return true;
|
|
372
|
+
if (url.startsWith("./") || url.startsWith("../"))
|
|
373
|
+
return true;
|
|
374
|
+
if (url.startsWith("//"))
|
|
375
|
+
return true;
|
|
376
|
+
if (url.startsWith("/"))
|
|
377
|
+
return true;
|
|
378
|
+
if (url.startsWith("http://") || url.startsWith("https://"))
|
|
379
|
+
return true;
|
|
380
|
+
if (url.startsWith("data:image/")) {
|
|
381
|
+
const after = url.slice("data:image/".length);
|
|
382
|
+
const sep = Math.min(after.indexOf(";") === -1 ? after.length : after.indexOf(";"), after.indexOf(",") === -1 ? after.length : after.indexOf(","));
|
|
383
|
+
const mime = after.slice(0, sep);
|
|
384
|
+
if (mime === "png" || mime === "jpeg" || mime === "jpg" || mime === "gif" || mime === "webp") {
|
|
385
|
+
return true;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
function* iterateUrls(normalized) {
|
|
391
|
+
let searchPos = 0;
|
|
392
|
+
while (searchPos < normalized.length) {
|
|
393
|
+
const idx = normalized.indexOf("url(", searchPos);
|
|
394
|
+
if (idx === -1)
|
|
395
|
+
return;
|
|
396
|
+
let depth = 1;
|
|
397
|
+
let quoteChar = null;
|
|
398
|
+
let i = idx + 4;
|
|
399
|
+
while (i < normalized.length && depth > 0) {
|
|
400
|
+
const ch = normalized[i];
|
|
401
|
+
if (quoteChar !== null) {
|
|
402
|
+
if (ch === quoteChar)
|
|
403
|
+
quoteChar = null;
|
|
404
|
+
} else if (ch === '"' || ch === "'") {
|
|
405
|
+
quoteChar = ch;
|
|
406
|
+
} else if (ch === "(") {
|
|
407
|
+
depth++;
|
|
408
|
+
} else if (ch === ")") {
|
|
409
|
+
depth--;
|
|
410
|
+
}
|
|
411
|
+
i++;
|
|
412
|
+
}
|
|
413
|
+
if (depth > 0) {
|
|
414
|
+
yield { inner: normalized.slice(idx + 4), malformed: true };
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
yield { inner: normalized.slice(idx + 4, i - 1), malformed: false };
|
|
418
|
+
searchPos = i;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
359
421
|
function isDangerousCssValue(value) {
|
|
360
422
|
const normalized = normalizeCssValue(value);
|
|
361
|
-
|
|
362
|
-
|
|
423
|
+
for (const { inner, malformed } of iterateUrls(normalized)) {
|
|
424
|
+
if (malformed)
|
|
425
|
+
return true;
|
|
426
|
+
if (!isUrlAllowed(inner))
|
|
427
|
+
return true;
|
|
428
|
+
}
|
|
363
429
|
if (normalized.includes("expression("))
|
|
364
430
|
return true;
|
|
365
431
|
if (normalized.includes("-moz-binding"))
|
|
@@ -370,21 +436,61 @@ function isDangerousCssValue(value) {
|
|
|
370
436
|
return true;
|
|
371
437
|
return false;
|
|
372
438
|
}
|
|
439
|
+
function splitDeclarations(style) {
|
|
440
|
+
const out = [];
|
|
441
|
+
let buf = "";
|
|
442
|
+
let parenDepth = 0;
|
|
443
|
+
let quoteChar = null;
|
|
444
|
+
for (const ch of style) {
|
|
445
|
+
if (quoteChar !== null) {
|
|
446
|
+
buf += ch;
|
|
447
|
+
if (ch === quoteChar)
|
|
448
|
+
quoteChar = null;
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
if (ch === '"' || ch === "'") {
|
|
452
|
+
quoteChar = ch;
|
|
453
|
+
buf += ch;
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
if (ch === "(") {
|
|
457
|
+
parenDepth++;
|
|
458
|
+
buf += ch;
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
if (ch === ")") {
|
|
462
|
+
if (parenDepth > 0)
|
|
463
|
+
parenDepth--;
|
|
464
|
+
buf += ch;
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
if (ch === ";" && parenDepth === 0) {
|
|
468
|
+
out.push(buf);
|
|
469
|
+
buf = "";
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
buf += ch;
|
|
473
|
+
}
|
|
474
|
+
if (buf.length > 0)
|
|
475
|
+
out.push(buf);
|
|
476
|
+
return out;
|
|
477
|
+
}
|
|
373
478
|
function sanitizeStyleValue(style) {
|
|
374
479
|
const endsWithSemicolon = style.trimEnd().endsWith(";");
|
|
375
|
-
const declarations = style
|
|
480
|
+
const declarations = splitDeclarations(style).map((d) => d.trim()).filter(Boolean);
|
|
376
481
|
const safe = [];
|
|
377
482
|
for (const decl of declarations) {
|
|
378
483
|
const colonIdx = decl.indexOf(":");
|
|
379
484
|
if (colonIdx === -1)
|
|
380
485
|
continue;
|
|
381
|
-
const property = decl.slice(0, colonIdx).trim()
|
|
486
|
+
const property = decl.slice(0, colonIdx).trim();
|
|
382
487
|
const value = decl.slice(colonIdx + 1).trim();
|
|
383
488
|
if (isDangerousCssValue(value))
|
|
384
489
|
continue;
|
|
385
|
-
|
|
490
|
+
const normalisedProperty = normalizeCssValue(property);
|
|
491
|
+
if (normalisedProperty.startsWith("-moz-binding"))
|
|
386
492
|
continue;
|
|
387
|
-
if (
|
|
493
|
+
if (normalisedProperty === "behavior")
|
|
388
494
|
continue;
|
|
389
495
|
safe.push(decl);
|
|
390
496
|
}
|
|
@@ -833,7 +939,7 @@ function renderLink(ctx, data) {
|
|
|
833
939
|
const attrs = [`href="${escapeAttr(href)}"`];
|
|
834
940
|
if (data.type === "page" && typeof data.link === "object") {
|
|
835
941
|
const page = data.link.page;
|
|
836
|
-
const isSpecialPage = page.startsWith("//") || page.includes("
|
|
942
|
+
const isSpecialPage = page.startsWith("//") || page.includes("#/");
|
|
837
943
|
if (!isSpecialPage) {
|
|
838
944
|
const hashIdx = page.indexOf("#");
|
|
839
945
|
const pageToCheck = hashIdx !== -1 ? page.slice(0, hashIdx) : page;
|
|
@@ -4610,6 +4716,9 @@ function generateDefaultUrl(pageName, contents) {
|
|
|
4610
4716
|
return path;
|
|
4611
4717
|
}
|
|
4612
4718
|
function renderHtmlBlock(ctx, data) {
|
|
4719
|
+
if (ctx.settings.allowHtmlBlocks === false) {
|
|
4720
|
+
return;
|
|
4721
|
+
}
|
|
4613
4722
|
const index = ctx.nextHtmlBlockIndex();
|
|
4614
4723
|
const pageName = ctx.page?.pageName ?? "";
|
|
4615
4724
|
const callbackUrl = ctx.options.resolvers?.htmlBlockUrl?.(index);
|
package/dist/index.js
CHANGED
|
@@ -304,10 +304,76 @@ function normalizeCssValue(value) {
|
|
|
304
304
|
result = result.replace(/[\s\u0000-\u001f\u007f-\u009f]/g, "");
|
|
305
305
|
return result.toLowerCase();
|
|
306
306
|
}
|
|
307
|
+
function isUrlAllowed(rawUrl) {
|
|
308
|
+
let url = rawUrl;
|
|
309
|
+
if (url.length >= 2) {
|
|
310
|
+
const first = url[0];
|
|
311
|
+
const last = url[url.length - 1];
|
|
312
|
+
if (first === '"' && last === '"' || first === "'" && last === "'") {
|
|
313
|
+
url = url.slice(1, -1);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
if (url === "")
|
|
317
|
+
return true;
|
|
318
|
+
if (url.startsWith("#"))
|
|
319
|
+
return true;
|
|
320
|
+
if (url.startsWith("./") || url.startsWith("../"))
|
|
321
|
+
return true;
|
|
322
|
+
if (url.startsWith("//"))
|
|
323
|
+
return true;
|
|
324
|
+
if (url.startsWith("/"))
|
|
325
|
+
return true;
|
|
326
|
+
if (url.startsWith("http://") || url.startsWith("https://"))
|
|
327
|
+
return true;
|
|
328
|
+
if (url.startsWith("data:image/")) {
|
|
329
|
+
const after = url.slice("data:image/".length);
|
|
330
|
+
const sep = Math.min(after.indexOf(";") === -1 ? after.length : after.indexOf(";"), after.indexOf(",") === -1 ? after.length : after.indexOf(","));
|
|
331
|
+
const mime = after.slice(0, sep);
|
|
332
|
+
if (mime === "png" || mime === "jpeg" || mime === "jpg" || mime === "gif" || mime === "webp") {
|
|
333
|
+
return true;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
function* iterateUrls(normalized) {
|
|
339
|
+
let searchPos = 0;
|
|
340
|
+
while (searchPos < normalized.length) {
|
|
341
|
+
const idx = normalized.indexOf("url(", searchPos);
|
|
342
|
+
if (idx === -1)
|
|
343
|
+
return;
|
|
344
|
+
let depth = 1;
|
|
345
|
+
let quoteChar = null;
|
|
346
|
+
let i = idx + 4;
|
|
347
|
+
while (i < normalized.length && depth > 0) {
|
|
348
|
+
const ch = normalized[i];
|
|
349
|
+
if (quoteChar !== null) {
|
|
350
|
+
if (ch === quoteChar)
|
|
351
|
+
quoteChar = null;
|
|
352
|
+
} else if (ch === '"' || ch === "'") {
|
|
353
|
+
quoteChar = ch;
|
|
354
|
+
} else if (ch === "(") {
|
|
355
|
+
depth++;
|
|
356
|
+
} else if (ch === ")") {
|
|
357
|
+
depth--;
|
|
358
|
+
}
|
|
359
|
+
i++;
|
|
360
|
+
}
|
|
361
|
+
if (depth > 0) {
|
|
362
|
+
yield { inner: normalized.slice(idx + 4), malformed: true };
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
yield { inner: normalized.slice(idx + 4, i - 1), malformed: false };
|
|
366
|
+
searchPos = i;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
307
369
|
function isDangerousCssValue(value) {
|
|
308
370
|
const normalized = normalizeCssValue(value);
|
|
309
|
-
|
|
310
|
-
|
|
371
|
+
for (const { inner, malformed } of iterateUrls(normalized)) {
|
|
372
|
+
if (malformed)
|
|
373
|
+
return true;
|
|
374
|
+
if (!isUrlAllowed(inner))
|
|
375
|
+
return true;
|
|
376
|
+
}
|
|
311
377
|
if (normalized.includes("expression("))
|
|
312
378
|
return true;
|
|
313
379
|
if (normalized.includes("-moz-binding"))
|
|
@@ -318,21 +384,61 @@ function isDangerousCssValue(value) {
|
|
|
318
384
|
return true;
|
|
319
385
|
return false;
|
|
320
386
|
}
|
|
387
|
+
function splitDeclarations(style) {
|
|
388
|
+
const out = [];
|
|
389
|
+
let buf = "";
|
|
390
|
+
let parenDepth = 0;
|
|
391
|
+
let quoteChar = null;
|
|
392
|
+
for (const ch of style) {
|
|
393
|
+
if (quoteChar !== null) {
|
|
394
|
+
buf += ch;
|
|
395
|
+
if (ch === quoteChar)
|
|
396
|
+
quoteChar = null;
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
if (ch === '"' || ch === "'") {
|
|
400
|
+
quoteChar = ch;
|
|
401
|
+
buf += ch;
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
if (ch === "(") {
|
|
405
|
+
parenDepth++;
|
|
406
|
+
buf += ch;
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
if (ch === ")") {
|
|
410
|
+
if (parenDepth > 0)
|
|
411
|
+
parenDepth--;
|
|
412
|
+
buf += ch;
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
if (ch === ";" && parenDepth === 0) {
|
|
416
|
+
out.push(buf);
|
|
417
|
+
buf = "";
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
buf += ch;
|
|
421
|
+
}
|
|
422
|
+
if (buf.length > 0)
|
|
423
|
+
out.push(buf);
|
|
424
|
+
return out;
|
|
425
|
+
}
|
|
321
426
|
function sanitizeStyleValue(style) {
|
|
322
427
|
const endsWithSemicolon = style.trimEnd().endsWith(";");
|
|
323
|
-
const declarations = style
|
|
428
|
+
const declarations = splitDeclarations(style).map((d) => d.trim()).filter(Boolean);
|
|
324
429
|
const safe = [];
|
|
325
430
|
for (const decl of declarations) {
|
|
326
431
|
const colonIdx = decl.indexOf(":");
|
|
327
432
|
if (colonIdx === -1)
|
|
328
433
|
continue;
|
|
329
|
-
const property = decl.slice(0, colonIdx).trim()
|
|
434
|
+
const property = decl.slice(0, colonIdx).trim();
|
|
330
435
|
const value = decl.slice(colonIdx + 1).trim();
|
|
331
436
|
if (isDangerousCssValue(value))
|
|
332
437
|
continue;
|
|
333
|
-
|
|
438
|
+
const normalisedProperty = normalizeCssValue(property);
|
|
439
|
+
if (normalisedProperty.startsWith("-moz-binding"))
|
|
334
440
|
continue;
|
|
335
|
-
if (
|
|
441
|
+
if (normalisedProperty === "behavior")
|
|
336
442
|
continue;
|
|
337
443
|
safe.push(decl);
|
|
338
444
|
}
|
|
@@ -781,7 +887,7 @@ function renderLink(ctx, data) {
|
|
|
781
887
|
const attrs = [`href="${escapeAttr(href)}"`];
|
|
782
888
|
if (data.type === "page" && typeof data.link === "object") {
|
|
783
889
|
const page = data.link.page;
|
|
784
|
-
const isSpecialPage = page.startsWith("//") || page.includes("
|
|
890
|
+
const isSpecialPage = page.startsWith("//") || page.includes("#/");
|
|
785
891
|
if (!isSpecialPage) {
|
|
786
892
|
const hashIdx = page.indexOf("#");
|
|
787
893
|
const pageToCheck = hashIdx !== -1 ? page.slice(0, hashIdx) : page;
|
|
@@ -4558,6 +4664,9 @@ function generateDefaultUrl(pageName, contents) {
|
|
|
4558
4664
|
return path;
|
|
4559
4665
|
}
|
|
4560
4666
|
function renderHtmlBlock(ctx, data) {
|
|
4667
|
+
if (ctx.settings.allowHtmlBlocks === false) {
|
|
4668
|
+
return;
|
|
4669
|
+
}
|
|
4561
4670
|
const index = ctx.nextHtmlBlockIndex();
|
|
4562
4671
|
const pageName = ctx.page?.pageName ?? "";
|
|
4563
4672
|
const callbackUrl = ctx.options.resolvers?.htmlBlockUrl?.(index);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wdprlib/render",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "HTML renderer for Wikidot markup",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"html",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"access": "public"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@wdprlib/ast": "
|
|
42
|
+
"@wdprlib/ast": "2.0.0",
|
|
43
43
|
"domhandler": "^5.0.3",
|
|
44
44
|
"htmlparser2": "^10.0.0",
|
|
45
45
|
"sanitize-html": "^2.14.0",
|