@zeropress/build-core 0.6.10 → 0.6.11
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/package.json
CHANGED
|
@@ -336,14 +336,14 @@ function sanitizeHtml(html) {
|
|
|
336
336
|
]);
|
|
337
337
|
|
|
338
338
|
const allowedAttributes = {
|
|
339
|
-
a: new Set(['href', 'title', 'class', 'id']),
|
|
339
|
+
a: new Set(['href', 'title', 'class', 'id', 'target', 'rel']),
|
|
340
340
|
aside: new Set(['role', 'class', 'id']),
|
|
341
341
|
img: new Set(['src', 'srcset', 'sizes', 'alt', 'title', 'class', 'id', 'width', 'height', 'loading', 'decoding']),
|
|
342
342
|
iframe: new Set(['src', 'width', 'height', 'frameborder', 'allowfullscreen', 'class', 'title']),
|
|
343
343
|
input: new Set(['type', 'checked', 'disabled', 'class', 'id', 'aria-label']),
|
|
344
344
|
source: new Set(['src', 'srcset', 'sizes', 'type', 'media', 'width', 'height', 'class', 'id']),
|
|
345
|
-
video: new Set(['src', 'controls', 'autoplay', 'loop', 'muted', 'playsinline', 'poster', 'preload', 'width', 'height', 'class', 'id', 'title']),
|
|
346
|
-
audio: new Set(['src', 'controls', 'autoplay', 'loop', 'muted', 'preload', 'class', 'id', 'title']),
|
|
345
|
+
video: new Set(['src', 'controls', 'controlslist', 'autoplay', 'loop', 'muted', 'playsinline', 'poster', 'preload', 'width', 'height', 'class', 'id', 'title']),
|
|
346
|
+
audio: new Set(['src', 'controls', 'controlslist', 'autoplay', 'loop', 'muted', 'preload', 'class', 'id', 'title']),
|
|
347
347
|
track: new Set(['src', 'kind', 'srclang', 'label', 'default', 'class', 'id']),
|
|
348
348
|
'*': new Set(['class', 'id']),
|
|
349
349
|
};
|
|
@@ -368,6 +368,8 @@ function sanitizeHtml(html) {
|
|
|
368
368
|
const tagAllowedAttributes = allowedAttributes[normalizedTag] || new Set();
|
|
369
369
|
const globalAllowedAttributes = allowedAttributes['*'];
|
|
370
370
|
const filteredAttributes = [];
|
|
371
|
+
let anchorTarget = '';
|
|
372
|
+
let anchorRel = '';
|
|
371
373
|
|
|
372
374
|
if (attributeString) {
|
|
373
375
|
const attributePattern = /([a-zA-Z][a-zA-Z0-9-]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|(\S+)))?/g;
|
|
@@ -385,10 +387,37 @@ function sanitizeHtml(html) {
|
|
|
385
387
|
continue;
|
|
386
388
|
}
|
|
387
389
|
|
|
390
|
+
if (normalizedTag === 'a' && attributeName === 'target') {
|
|
391
|
+
const target = sanitizeAnchorTarget(attributeValue);
|
|
392
|
+
if (!target) {
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
anchorTarget = target;
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (normalizedTag === 'a' && attributeName === 'rel') {
|
|
400
|
+
const rel = sanitizeRelList(attributeValue);
|
|
401
|
+
if (!rel) {
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
anchorRel = rel;
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
|
|
388
408
|
if (attributeName === 'srcset' && !isSafeSrcset(attributeValue, safeUriPattern)) {
|
|
389
409
|
continue;
|
|
390
410
|
}
|
|
391
411
|
|
|
412
|
+
if (attributeName === 'controlslist') {
|
|
413
|
+
const controlsList = sanitizeControlsList(attributeValue);
|
|
414
|
+
if (!controlsList) {
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
filteredAttributes.push(`${attributeName}="${controlsList}"`);
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
|
|
392
421
|
if (normalizedTag === 'input' && attributeName === 'type' && attributeValue !== 'checkbox') {
|
|
393
422
|
continue;
|
|
394
423
|
}
|
|
@@ -397,6 +426,18 @@ function sanitizeHtml(html) {
|
|
|
397
426
|
}
|
|
398
427
|
}
|
|
399
428
|
|
|
429
|
+
if (normalizedTag === 'a') {
|
|
430
|
+
if (anchorTarget) {
|
|
431
|
+
filteredAttributes.push(`target="${anchorTarget}"`);
|
|
432
|
+
}
|
|
433
|
+
if (anchorTarget === '_blank') {
|
|
434
|
+
anchorRel = ensureBlankTargetRel(anchorRel);
|
|
435
|
+
}
|
|
436
|
+
if (anchorRel) {
|
|
437
|
+
filteredAttributes.push(`rel="${anchorRel}"`);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
400
441
|
const isSelfClosing = match.endsWith('/>') || normalizedTag === 'br' || normalizedTag === 'hr' || normalizedTag === 'img' || normalizedTag === 'input' || normalizedTag === 'source' || normalizedTag === 'track';
|
|
401
442
|
const attributeSuffix = filteredAttributes.length > 0 ? ` ${filteredAttributes.join(' ')}` : '';
|
|
402
443
|
return isSelfClosing ? `<${normalizedTag}${attributeSuffix} />` : `<${normalizedTag}${attributeSuffix}>`;
|
|
@@ -414,6 +455,52 @@ function sanitizeHtml(html) {
|
|
|
414
455
|
.join('');
|
|
415
456
|
}
|
|
416
457
|
|
|
458
|
+
function sanitizeAnchorTarget(value) {
|
|
459
|
+
const normalized = String(value).trim().toLowerCase();
|
|
460
|
+
return normalized === '_blank' ? '_blank' : '';
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function sanitizeRelList(value) {
|
|
464
|
+
const allowedTokens = new Set(['noopener', 'noreferrer', 'nofollow', 'ugc', 'sponsored', 'external']);
|
|
465
|
+
const tokens = String(value)
|
|
466
|
+
.toLowerCase()
|
|
467
|
+
.split(/\s+/)
|
|
468
|
+
.filter((token, index, allTokens) => (
|
|
469
|
+
allowedTokens.has(token)
|
|
470
|
+
&& allTokens.indexOf(token) === index
|
|
471
|
+
));
|
|
472
|
+
|
|
473
|
+
return tokens.join(' ');
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function ensureBlankTargetRel(value) {
|
|
477
|
+
const tokens = String(value)
|
|
478
|
+
.toLowerCase()
|
|
479
|
+
.split(/\s+/)
|
|
480
|
+
.filter(Boolean);
|
|
481
|
+
|
|
482
|
+
for (const requiredToken of ['noopener', 'noreferrer']) {
|
|
483
|
+
if (!tokens.includes(requiredToken)) {
|
|
484
|
+
tokens.push(requiredToken);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return tokens.join(' ');
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function sanitizeControlsList(value) {
|
|
492
|
+
const allowedTokens = new Set(['nodownload', 'nofullscreen', 'noremoteplayback']);
|
|
493
|
+
const tokens = String(value)
|
|
494
|
+
.toLowerCase()
|
|
495
|
+
.split(/\s+/)
|
|
496
|
+
.filter((token, index, allTokens) => (
|
|
497
|
+
allowedTokens.has(token)
|
|
498
|
+
&& allTokens.indexOf(token) === index
|
|
499
|
+
));
|
|
500
|
+
|
|
501
|
+
return tokens.join(' ');
|
|
502
|
+
}
|
|
503
|
+
|
|
417
504
|
function isSafeSrcset(value, safeUriPattern) {
|
|
418
505
|
const candidates = String(value)
|
|
419
506
|
.split(',')
|