Fix managed fonts and quoted styles in slide previews
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile, web, pulse-signage-web) (push) Successful in 1m20s
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile.player, player, pulse-signage-player) (push) Successful in 35s

This commit is contained in:
lzstealth committed 2026-10-26 01:52:55 +00:00
1 parent 5b5af1a7b9
commit ccd3535e7c
4 files changed
+61 -2

No files matched your search

+1
View File
@@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file.
- Fixed playlist schedule summaries showing weekday text and omitting boundary times when dates are returned as `Date` objects.
- Preserved saved API region items-path overrides when reopening the slide editor instead of replacing them with the source default.
- Fixed managed-font loading in thumbnail popup previews and preserved entity-encoded font-family quotes across preview sanitization.
## 2.20.4 - 2026-10-25
+8 -2
View File
@@ -14,6 +14,12 @@
.replace(/'/g, ''');
}
function decodeAttributeValue(value) {
var decoder = document.createElement('textarea');
decoder.innerHTML = String(value || '').replace(/</g, '&lt;');
return decoder.value;
}
function sanitizePreviewHtml(html) {
var output = String(html || '');
output = output.replace(/<script[\s\S]*?<\/script>/gi, '');
@@ -123,7 +129,7 @@
return '';
}
var value = doubleQuoted !== undefined ? doubleQuoted : singleQuoted !== undefined ? singleQuoted : bareValue !== undefined ? bareValue : '';
var value = decodeAttributeValue(doubleQuoted !== undefined ? doubleQuoted : singleQuoted !== undefined ? singleQuoted : bareValue !== undefined ? bareValue : '');
if (lowerKey === 'href' && /^(?:\s*javascript:|\s*data:)/i.test(String(value || ''))) {
return '';
}
@@ -185,7 +191,7 @@
return '';
}
var value = doubleQuoted !== undefined ? doubleQuoted : singleQuoted !== undefined ? singleQuoted : bareValue !== undefined ? bareValue : '';
var value = decodeAttributeValue(doubleQuoted !== undefined ? doubleQuoted : singleQuoted !== undefined ? singleQuoted : bareValue !== undefined ? bareValue : '');
if (lowerKey === 'href' && /^(?:\s*javascript:|\s*data:)/i.test(String(value || ''))) {
return '';
}
+1
View File
@@ -102,6 +102,7 @@ function registerAuthAndAccountRoutes(app, deps) {
pool: deps.pool,
common: deps.common,
pages: deps.pages,
mediaDir: deps.mediaDir,
requirePermission: deps.requirePermission,
buildDashboardState: deps.buildDashboardState,
fetchScreensByPlaylistId: deps.fetchScreensByPlaylistId,
+51
View File
@@ -35,6 +35,57 @@ function loadEditorTimeDateModule() {
return modules.get('time-date');
}
test('preview sanitizers preserve entity-quoted font families without double escaping', () => {
const entities = { apos: "'", quot: '"', amp: '&', lt: '<', gt: '>' };
const sandbox = {
window: {},
document: {
createElement(tag) {
assert.equal(tag, 'textarea');
return {
set innerHTML(value) {
this.value = value.replace(/&(#x[\da-f]+|#\d+|apos|quot|amp|lt|gt);/gi, (_match, entity) => {
if (entity[0] !== '#') return entities[entity];
return String.fromCodePoint(entity[1].toLowerCase() === 'x' ? parseInt(entity.slice(2), 16) : Number(entity.slice(1)));
});
}
};
}
}
};
vm.runInNewContext(fs.readFileSync(require.resolve('../src/web/public/js/regions/region-utils'), 'utf8'), sandbox);
for (const sanitize of [sandbox.window.pulseRegionUtils.sanitizePreviewHtml, sandbox.window.pulseRegionUtils.sanitizeRichText]) {
for (const quotes of ["'Ubuntu Regular'", '&#39;Ubuntu Regular&#39;', '&quot;Ubuntu Regular&quot;']) {
const markup = sanitize('<span style="font-family: ' + quotes + ';">Player</span>');
assert.doesNotMatch(markup, /&amp;#39;|&amp;quot;/);
assert.equal(sanitize(markup), markup);
assert.match(markup, /font-family: (?:&#39;Ubuntu Regular&#39;|&quot;Ubuntu Regular&quot;);/);
}
const unsafe = sanitize('<a href="java&#115;cript:alert(1)" style="background:u&#114;l(test)">Player</a>');
assert.doesNotMatch(unsafe, /href=|style=/);
}
});
test('page route wiring preserves the configured media directory for popup font stylesheets', () => {
let pageDependencies;
const sandbox = {
module: { exports: {} },
require(name) {
if (name === '#src/rbac') return { PERMISSIONS: [], normalizePermissionKeys: value => value };
return function (_app, dependencies) {
if (name === '#src/web/routes/admin/pages') pageDependencies = dependencies;
};
}
};
vm.runInNewContext(fs.readFileSync(require.resolve('../src/web/routes/register'), 'utf8'), sandbox);
sandbox.module.exports.registerRoutes({}, {
mediaDir: '/configured/signage-media',
playerActionService: {}
});
assert.equal(pageDependencies.mediaDir, '/configured/signage-media');
});
test('thumbnail payload passes saved region geometry and content to the popup renderer', () => {
const payload = buildThumbnailPreviewPayload({
template: {