diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/Scoresheet/Default.cshtml b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/Scoresheet/Default.cshtml index 4b270ad47c..852d0e2e64 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/Scoresheet/Default.cshtml +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/Scoresheet/Default.cshtml @@ -1,6 +1,5 @@ @using Microsoft.AspNetCore.Mvc.Localization @using Unity.Flex.Localization; -@using Unity.Flex.Web.Views.Shared.Components.Scoresheet; @using Volo.Abp.Authorization.Permissions; @inject IHtmlLocalizer L @inject IPermissionChecker PermissionChecker @@ -99,7 +98,8 @@ data-maxlength="@question.GetMaxLength()" data-yesvalue="@question.GetYesValue()" data-novalue="@question.GetNoValue()" - data-questiondesc="@question.Description" + data-questionlabel="@question.Label" + data-questiondesc="@question.Description" data-definition="@question.Definition" data-rows="@question.GetRowsValue()" data-required="@question.GetIsRequiredValue()"> diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/Scoresheet/Scoresheet.js b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/Scoresheet/Scoresheet.js index 4143e200d7..5c90f25eb6 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/Scoresheet/Scoresheet.js +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/Scoresheet/Scoresheet.js @@ -1,3 +1,63 @@ +const _SANITIZE_ALLOWED_TAGS = new Set([ + 'a', 'b', 'blockquote', 'br', 'code', 'del', 'em', + 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', + 'li', 'ol', 'p', 'pre', 's', 'span', 'strong', 'u', 'ul' +]); +const _SANITIZE_ALLOWED_ATTRS = new Set(['href', 'rel', 'target', 'title']); +const _SANITIZE_ALLOWED_SCHEMES = new Set(['http:', 'https:', 'mailto:']); +const _SANITIZE_STRIP_WITH_CONTENT = new Set(['script', 'style', 'iframe', 'noscript', 'object', 'embed']); + +function _isSafeHref(href) { + try { + const url = new URL(href, location.href); + return _SANITIZE_ALLOWED_SCHEMES.has(url.protocol); + } catch (e) { + console.warn('sanitizeHtml: invalid href removed:', e); + return false; + } +} + +function _sanitizeElement(el) { + for (const attr of Array.from(el.attributes)) { + if (_SANITIZE_ALLOWED_ATTRS.has(attr.name)) { + if (attr.name === 'href' && !_isSafeHref(el.getAttribute('href'))) { + el.removeAttribute('href'); + } + } else { + el.removeAttribute(attr.name); + } + } +} + +function sanitizeHtml(html) { + if (!html) return ''; + const template = document.createElement('template'); + template.innerHTML = html; + // Process bottom-up so children are handled before their parent is unwrapped/removed + const elements = Array.from(template.content.querySelectorAll('*')).reverse(); + for (const el of elements) { + const tag = el.tagName.toLowerCase(); + if (_SANITIZE_STRIP_WITH_CONTENT.has(tag)) { + el.remove(); + } else if (_SANITIZE_ALLOWED_TAGS.has(tag)) { + _sanitizeElement(el); + } else { + el.replaceWith(...Array.from(el.childNodes)); + } + } + const wrapper = document.createElement('div'); + wrapper.appendChild(template.content); + return wrapper.innerHTML; +} + +function escapeHtml(text) { + return String(text) + .replaceAll('&', '&') + .replaceAll('<', '<') + .replaceAll('>', '>') + .replaceAll('"', '"'); +} + $(function () { function makeScoresheetsSortable() { @@ -149,7 +209,7 @@ $(function () {

@@ -163,7 +223,7 @@ $(function () {

@@ -219,7 +279,7 @@ $(function () { function buildTextAreaFieldPreview(item) { let req = item.dataset.required ? "required" : null; return ` -

${item.dataset.questiondesc}

+

${sanitizeHtml(item.dataset.questiondesc)}