Add HTML sanitization when injecting code in pages in the UI
This commit is contained in:
parent
4d50026744
commit
ece5ce1cdf
|
@ -6,7 +6,8 @@ paths:
|
|||
- src/ui
|
||||
- src/common
|
||||
paths-ignore:
|
||||
- src/ui/static/tsparticles.bundle.min.js
|
||||
- src/ui/static/js/utils/flatpickr.js
|
||||
- src/ui/static/js/tsparticles.bundle.min.js
|
||||
- src/ui/static/js/editor
|
||||
- src/ui/static/js/utils/flatpickr.js
|
||||
- src/ui/static/js/utils/purify
|
||||
- src/common/core/modsecurity/files
|
||||
|
|
|
@ -13,6 +13,7 @@ SECURITY.md
|
|||
tsparticles.bundle.min.js
|
||||
flatpickr.*
|
||||
src/ui/static/js/editor/*
|
||||
src/ui/static/js/utils/purify/*
|
||||
src/ui/templates/*
|
||||
datepicker-foundation.css
|
||||
examples/*
|
||||
|
|
|
@ -72,12 +72,13 @@ class News {
|
|||
excerpt,
|
||||
tags,
|
||||
date,
|
||||
lastUpdate,
|
||||
lastUpdate
|
||||
);
|
||||
let cleanHTML = DOMPurify.sanitize(cardHTML);
|
||||
//add to DOM
|
||||
document
|
||||
.querySelector("[data-news-container]")
|
||||
.insertAdjacentHTML("afterbegin", cardHTML);
|
||||
.insertAdjacentHTML("afterbegin", cleanHTML);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -190,7 +191,7 @@ class darkMode {
|
|||
};
|
||||
const send = await fetch(
|
||||
`${location.href.split("/").slice(0, -1).join("/")}/darkmode`,
|
||||
data,
|
||||
data
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -230,7 +231,7 @@ class FlashMsg {
|
|||
flashEl.remove();
|
||||
//update count
|
||||
this.flashCount.textContent = document.querySelectorAll(
|
||||
"[data-flash-message]",
|
||||
"[data-flash-message]"
|
||||
).length;
|
||||
}
|
||||
} catch (err) {}
|
||||
|
@ -299,7 +300,7 @@ const setMenu = new Menu();
|
|||
const setNewsSidebar = new Sidebar(
|
||||
"[data-sidebar-info]",
|
||||
"[data-sidebar-info-open]",
|
||||
"[data-sidebar-info-close]",
|
||||
"[data-sidebar-info-close]"
|
||||
);
|
||||
|
||||
const setCheckbox = new Checkbox();
|
||||
|
@ -310,7 +311,7 @@ const setDisabledPop = new DisabledPop();
|
|||
const setFlashSidebar = new Sidebar(
|
||||
"[data-flash-sidebar]",
|
||||
"[data-flash-sidebar-open]",
|
||||
"[data-flash-sidebar-close]",
|
||||
"[data-flash-sidebar-close]"
|
||||
);
|
||||
const setNews = new News();
|
||||
const setDarkM = new darkMode();
|
||||
|
|
|
@ -36,7 +36,7 @@ class Dropdown {
|
|||
const btn = e.target.closest("button");
|
||||
const btnValue = btn.getAttribute("value");
|
||||
const btnSetting = btn.getAttribute(
|
||||
`data-${this.prefix}-setting-select-dropdown-btn`,
|
||||
`data-${this.prefix}-setting-select-dropdown-btn`
|
||||
);
|
||||
//stop if same value to avoid new fetching
|
||||
const isSameVal = this.isSameValue(btnSetting, btnValue);
|
||||
|
@ -57,7 +57,7 @@ class Dropdown {
|
|||
|
||||
closeAllDrop() {
|
||||
const drops = document.querySelectorAll(
|
||||
`[data-${this.prefix}-setting-select-dropdown]`,
|
||||
`[data-${this.prefix}-setting-select-dropdown]`
|
||||
);
|
||||
drops.forEach((drop) => {
|
||||
drop.classList.add("hidden");
|
||||
|
@ -65,8 +65,8 @@ class Dropdown {
|
|||
document
|
||||
.querySelector(
|
||||
`svg[data-${this.prefix}-setting-select="${drop.getAttribute(
|
||||
`data-${this.prefix}-setting-select-dropdown`,
|
||||
)}"]`,
|
||||
`data-${this.prefix}-setting-select-dropdown`
|
||||
)}"]`
|
||||
)
|
||||
.classList.remove("rotate-180");
|
||||
});
|
||||
|
@ -74,7 +74,7 @@ class Dropdown {
|
|||
|
||||
isSameValue(btnSetting, value) {
|
||||
const selectCustom = document.querySelector(
|
||||
`[data-${this.prefix}-setting-select-text="${btnSetting}"]`,
|
||||
`[data-${this.prefix}-setting-select-text="${btnSetting}"]`
|
||||
);
|
||||
const currVal = selectCustom.textContent;
|
||||
return currVal === value ? true : false;
|
||||
|
@ -82,30 +82,30 @@ class Dropdown {
|
|||
|
||||
setSelectNewValue(btnSetting, value) {
|
||||
const selectCustom = document.querySelector(
|
||||
`[data-${this.prefix}-setting-select="${btnSetting}"]`,
|
||||
`[data-${this.prefix}-setting-select="${btnSetting}"]`
|
||||
);
|
||||
selectCustom.querySelector(
|
||||
`[data-${this.prefix}-setting-select-text]`,
|
||||
`[data-${this.prefix}-setting-select-text]`
|
||||
).textContent = value;
|
||||
}
|
||||
|
||||
hideDropdown(btnSetting) {
|
||||
//hide dropdown
|
||||
const dropdownEl = document.querySelector(
|
||||
`[data-${this.prefix}-setting-select-dropdown="${btnSetting}"]`,
|
||||
`[data-${this.prefix}-setting-select-dropdown="${btnSetting}"]`
|
||||
);
|
||||
dropdownEl.classList.add("hidden");
|
||||
dropdownEl.classList.remove("flex");
|
||||
//svg effect
|
||||
const dropdownChevron = document.querySelector(
|
||||
`svg[data-${this.prefix}-setting-select="${btnSetting}"]`,
|
||||
`svg[data-${this.prefix}-setting-select="${btnSetting}"]`
|
||||
);
|
||||
dropdownChevron.classList.remove("rotate-180");
|
||||
}
|
||||
|
||||
changeDropBtnStyle(btnSetting, selectedBtn) {
|
||||
const dropdownEl = document.querySelector(
|
||||
`[data-${this.prefix}-setting-select-dropdown="${btnSetting}"]`,
|
||||
`[data-${this.prefix}-setting-select-dropdown="${btnSetting}"]`
|
||||
);
|
||||
//reset dropdown btns
|
||||
const btnEls = dropdownEl.querySelectorAll("button");
|
||||
|
@ -116,7 +116,7 @@ class Dropdown {
|
|||
"bg-primary",
|
||||
"bg-primary",
|
||||
"text-gray-300",
|
||||
"text-gray-300",
|
||||
"text-gray-300"
|
||||
);
|
||||
btn.classList.add("bg-white", "dark:bg-slate-700", "text-gray-700");
|
||||
});
|
||||
|
@ -124,7 +124,7 @@ class Dropdown {
|
|||
selectedBtn.classList.remove(
|
||||
"bg-white",
|
||||
"dark:bg-slate-700",
|
||||
"text-gray-700",
|
||||
"text-gray-700"
|
||||
);
|
||||
selectedBtn.classList.add("dark:bg-primary", "bg-primary", "text-gray-300");
|
||||
}
|
||||
|
@ -135,10 +135,10 @@ class Dropdown {
|
|||
.getAttribute(`data-${this.prefix}-setting-select`);
|
||||
//toggle dropdown
|
||||
const dropdownEl = document.querySelector(
|
||||
`[data-${this.prefix}-setting-select-dropdown="${attribut}"]`,
|
||||
`[data-${this.prefix}-setting-select-dropdown="${attribut}"]`
|
||||
);
|
||||
const dropdownChevron = document.querySelector(
|
||||
`svg[data-${this.prefix}-setting-select="${attribut}"]`,
|
||||
`svg[data-${this.prefix}-setting-select="${attribut}"]`
|
||||
);
|
||||
dropdownEl.classList.toggle("hidden");
|
||||
dropdownEl.classList.toggle("flex");
|
||||
|
@ -300,7 +300,7 @@ class Upload {
|
|||
this.dropZoneElement.classList.remove(
|
||||
"border-solid",
|
||||
"bg-gray-100",
|
||||
"dark:bg-slate-700/50",
|
||||
"dark:bg-slate-700/50"
|
||||
);
|
||||
this.dropZoneElement.classList.add("border-dashed");
|
||||
}
|
||||
|
@ -309,7 +309,7 @@ class Upload {
|
|||
this.dropZoneElement.classList.add(
|
||||
"border-solid",
|
||||
"bg-gray-100",
|
||||
"dark:bg-slate-700/50",
|
||||
"dark:bg-slate-700/50"
|
||||
);
|
||||
this.dropZoneElement.classList.remove("border-dashed");
|
||||
}
|
||||
|
@ -345,13 +345,13 @@ class Upload {
|
|||
if (xhr.status == 201) {
|
||||
this.uploadedArea.insertAdjacentHTML(
|
||||
"afterbegin",
|
||||
this.fileSuccess(name, fileSize),
|
||||
this.fileSuccess(name, fileSize)
|
||||
);
|
||||
this.allowReload();
|
||||
} else {
|
||||
this.uploadedArea.insertAdjacentHTML(
|
||||
"afterbegin",
|
||||
this.fileFail(name, fileSize),
|
||||
this.fileFail(name, fileSize)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -408,7 +408,8 @@ class Upload {
|
|||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
return str;
|
||||
let cleanHTML = DOMPurify.sanitize(str);
|
||||
return cleanHTML;
|
||||
}
|
||||
|
||||
fileFail(name, fileSize) {
|
||||
|
@ -435,7 +436,8 @@ class Upload {
|
|||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
return str;
|
||||
let cleanHTML = DOMPurify.sanitize(str);
|
||||
return cleanHTML;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -448,10 +450,10 @@ class Modal {
|
|||
this.modalExtInp = this.modal.querySelector("input#external");
|
||||
|
||||
this.modalTitle = this.modal.querySelector(
|
||||
`[data-${this.prefix}-modal-title]`,
|
||||
`[data-${this.prefix}-modal-title]`
|
||||
);
|
||||
this.modalTxt = this.modal.querySelector(
|
||||
`[data-${this.prefix}-modal-text]`,
|
||||
`[data-${this.prefix}-modal-text]`
|
||||
);
|
||||
this.init();
|
||||
}
|
||||
|
|
|
@ -52,18 +52,18 @@ class Select {
|
|||
try {
|
||||
if (!e.target.closest("button")) {
|
||||
const selectEls = document.querySelectorAll(
|
||||
"div[data-setting-select-dropdown]",
|
||||
"div[data-setting-select-dropdown]"
|
||||
);
|
||||
selectEls.forEach((select) => {
|
||||
select.classList.add("hidden");
|
||||
select.classList.remove("flex");
|
||||
});
|
||||
const btnEls = document.querySelectorAll(
|
||||
"button[data-setting-select]",
|
||||
"button[data-setting-select]"
|
||||
);
|
||||
btnEls.forEach((btn) => {
|
||||
const dropdownChevron = btn.querySelector(
|
||||
`svg[data-setting-select]`,
|
||||
`svg[data-setting-select]`
|
||||
);
|
||||
dropdownChevron.classList.remove("rotate-180");
|
||||
});
|
||||
|
@ -87,7 +87,7 @@ class Select {
|
|||
.hasAttribute(`data-setting-select-dropdown-btn`)
|
||||
) {
|
||||
const btn = e.target.closest(
|
||||
`button[data-setting-select-dropdown-btn]`,
|
||||
`button[data-setting-select-dropdown-btn]`
|
||||
);
|
||||
const btnValue = btn.getAttribute("value");
|
||||
|
||||
|
@ -116,7 +116,7 @@ class Select {
|
|||
|
||||
//close dropdown
|
||||
const dropdownChevron = selectCustom.querySelector(
|
||||
`svg[data-setting-select]`,
|
||||
`svg[data-setting-select]`
|
||||
);
|
||||
dropdownChevron.classList.remove("rotate-180");
|
||||
|
||||
|
@ -139,7 +139,7 @@ class Select {
|
|||
});
|
||||
//select new one
|
||||
const newOption = selectEl.querySelector(
|
||||
`option[value="${selectedValue}"]`,
|
||||
`option[value="${selectedValue}"]`
|
||||
);
|
||||
newOption.selected = true;
|
||||
newOption.setAttribute("selected", "");
|
||||
|
@ -255,7 +255,8 @@ class DisabledPop {
|
|||
} bg-blue-500 absolute right-2 rounded-lg px-2 py-1 z-20 dark:brightness-90">
|
||||
<p class="m-0 text-xs text-white dark:text-gray-100">disabled by ${method}</p>
|
||||
</div>`;
|
||||
el.insertAdjacentHTML("beforebegin", popupHTML);
|
||||
let cleanHTML = DOMPurify.sanitize(popupHTML);
|
||||
el.insertAdjacentHTML("beforebegin", cleanHTML);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,362 @@
|
|||
import { freeze } from './utils.js';
|
||||
|
||||
export const html = freeze([
|
||||
'accept',
|
||||
'action',
|
||||
'align',
|
||||
'alt',
|
||||
'autocapitalize',
|
||||
'autocomplete',
|
||||
'autopictureinpicture',
|
||||
'autoplay',
|
||||
'background',
|
||||
'bgcolor',
|
||||
'border',
|
||||
'capture',
|
||||
'cellpadding',
|
||||
'cellspacing',
|
||||
'checked',
|
||||
'cite',
|
||||
'class',
|
||||
'clear',
|
||||
'color',
|
||||
'cols',
|
||||
'colspan',
|
||||
'controls',
|
||||
'controlslist',
|
||||
'coords',
|
||||
'crossorigin',
|
||||
'datetime',
|
||||
'decoding',
|
||||
'default',
|
||||
'dir',
|
||||
'disabled',
|
||||
'disablepictureinpicture',
|
||||
'disableremoteplayback',
|
||||
'download',
|
||||
'draggable',
|
||||
'enctype',
|
||||
'enterkeyhint',
|
||||
'face',
|
||||
'for',
|
||||
'headers',
|
||||
'height',
|
||||
'hidden',
|
||||
'high',
|
||||
'href',
|
||||
'hreflang',
|
||||
'id',
|
||||
'inputmode',
|
||||
'integrity',
|
||||
'ismap',
|
||||
'kind',
|
||||
'label',
|
||||
'lang',
|
||||
'list',
|
||||
'loading',
|
||||
'loop',
|
||||
'low',
|
||||
'max',
|
||||
'maxlength',
|
||||
'media',
|
||||
'method',
|
||||
'min',
|
||||
'minlength',
|
||||
'multiple',
|
||||
'muted',
|
||||
'name',
|
||||
'nonce',
|
||||
'noshade',
|
||||
'novalidate',
|
||||
'nowrap',
|
||||
'open',
|
||||
'optimum',
|
||||
'pattern',
|
||||
'placeholder',
|
||||
'playsinline',
|
||||
'poster',
|
||||
'preload',
|
||||
'pubdate',
|
||||
'radiogroup',
|
||||
'readonly',
|
||||
'rel',
|
||||
'required',
|
||||
'rev',
|
||||
'reversed',
|
||||
'role',
|
||||
'rows',
|
||||
'rowspan',
|
||||
'spellcheck',
|
||||
'scope',
|
||||
'selected',
|
||||
'shape',
|
||||
'size',
|
||||
'sizes',
|
||||
'span',
|
||||
'srclang',
|
||||
'start',
|
||||
'src',
|
||||
'srcset',
|
||||
'step',
|
||||
'style',
|
||||
'summary',
|
||||
'tabindex',
|
||||
'title',
|
||||
'translate',
|
||||
'type',
|
||||
'usemap',
|
||||
'valign',
|
||||
'value',
|
||||
'width',
|
||||
'xmlns',
|
||||
'slot',
|
||||
]);
|
||||
|
||||
export const svg = freeze([
|
||||
'accent-height',
|
||||
'accumulate',
|
||||
'additive',
|
||||
'alignment-baseline',
|
||||
'ascent',
|
||||
'attributename',
|
||||
'attributetype',
|
||||
'azimuth',
|
||||
'basefrequency',
|
||||
'baseline-shift',
|
||||
'begin',
|
||||
'bias',
|
||||
'by',
|
||||
'class',
|
||||
'clip',
|
||||
'clippathunits',
|
||||
'clip-path',
|
||||
'clip-rule',
|
||||
'color',
|
||||
'color-interpolation',
|
||||
'color-interpolation-filters',
|
||||
'color-profile',
|
||||
'color-rendering',
|
||||
'cx',
|
||||
'cy',
|
||||
'd',
|
||||
'dx',
|
||||
'dy',
|
||||
'diffuseconstant',
|
||||
'direction',
|
||||
'display',
|
||||
'divisor',
|
||||
'dur',
|
||||
'edgemode',
|
||||
'elevation',
|
||||
'end',
|
||||
'fill',
|
||||
'fill-opacity',
|
||||
'fill-rule',
|
||||
'filter',
|
||||
'filterunits',
|
||||
'flood-color',
|
||||
'flood-opacity',
|
||||
'font-family',
|
||||
'font-size',
|
||||
'font-size-adjust',
|
||||
'font-stretch',
|
||||
'font-style',
|
||||
'font-variant',
|
||||
'font-weight',
|
||||
'fx',
|
||||
'fy',
|
||||
'g1',
|
||||
'g2',
|
||||
'glyph-name',
|
||||
'glyphref',
|
||||
'gradientunits',
|
||||
'gradienttransform',
|
||||
'height',
|
||||
'href',
|
||||
'id',
|
||||
'image-rendering',
|
||||
'in',
|
||||
'in2',
|
||||
'k',
|
||||
'k1',
|
||||
'k2',
|
||||
'k3',
|
||||
'k4',
|
||||
'kerning',
|
||||
'keypoints',
|
||||
'keysplines',
|
||||
'keytimes',
|
||||
'lang',
|
||||
'lengthadjust',
|
||||
'letter-spacing',
|
||||
'kernelmatrix',
|
||||
'kernelunitlength',
|
||||
'lighting-color',
|
||||
'local',
|
||||
'marker-end',
|
||||
'marker-mid',
|
||||
'marker-start',
|
||||
'markerheight',
|
||||
'markerunits',
|
||||
'markerwidth',
|
||||
'maskcontentunits',
|
||||
'maskunits',
|
||||
'max',
|
||||
'mask',
|
||||
'media',
|
||||
'method',
|
||||
'mode',
|
||||
'min',
|
||||
'name',
|
||||
'numoctaves',
|
||||
'offset',
|
||||
'operator',
|
||||
'opacity',
|
||||
'order',
|
||||
'orient',
|
||||
'orientation',
|
||||
'origin',
|
||||
'overflow',
|
||||
'paint-order',
|
||||
'path',
|
||||
'pathlength',
|
||||
'patterncontentunits',
|
||||
'patterntransform',
|
||||
'patternunits',
|
||||
'points',
|
||||
'preservealpha',
|
||||
'preserveaspectratio',
|
||||
'primitiveunits',
|
||||
'r',
|
||||
'rx',
|
||||
'ry',
|
||||
'radius',
|
||||
'refx',
|
||||
'refy',
|
||||
'repeatcount',
|
||||
'repeatdur',
|
||||
'restart',
|
||||
'result',
|
||||
'rotate',
|
||||
'scale',
|
||||
'seed',
|
||||
'shape-rendering',
|
||||
'specularconstant',
|
||||
'specularexponent',
|
||||
'spreadmethod',
|
||||
'startoffset',
|
||||
'stddeviation',
|
||||
'stitchtiles',
|
||||
'stop-color',
|
||||
'stop-opacity',
|
||||
'stroke-dasharray',
|
||||
'stroke-dashoffset',
|
||||
'stroke-linecap',
|
||||
'stroke-linejoin',
|
||||
'stroke-miterlimit',
|
||||
'stroke-opacity',
|
||||
'stroke',
|
||||
'stroke-width',
|
||||
'style',
|
||||
'surfacescale',
|
||||
'systemlanguage',
|
||||
'tabindex',
|
||||
'targetx',
|
||||
'targety',
|
||||
'transform',
|
||||
'transform-origin',
|
||||
'text-anchor',
|
||||
'text-decoration',
|
||||
'text-rendering',
|
||||
'textlength',
|
||||
'type',
|
||||
'u1',
|
||||
'u2',
|
||||
'unicode',
|
||||
'values',
|
||||
'viewbox',
|
||||
'visibility',
|
||||
'version',
|
||||
'vert-adv-y',
|
||||
'vert-origin-x',
|
||||
'vert-origin-y',
|
||||
'width',
|
||||
'word-spacing',
|
||||
'wrap',
|
||||
'writing-mode',
|
||||
'xchannelselector',
|
||||
'ychannelselector',
|
||||
'x',
|
||||
'x1',
|
||||
'x2',
|
||||
'xmlns',
|
||||
'y',
|
||||
'y1',
|
||||
'y2',
|
||||
'z',
|
||||
'zoomandpan',
|
||||
]);
|
||||
|
||||
export const mathMl = freeze([
|
||||
'accent',
|
||||
'accentunder',
|
||||
'align',
|
||||
'bevelled',
|
||||
'close',
|
||||
'columnsalign',
|
||||
'columnlines',
|
||||
'columnspan',
|
||||
'denomalign',
|
||||
'depth',
|
||||
'dir',
|
||||
'display',
|
||||
'displaystyle',
|
||||
'encoding',
|
||||
'fence',
|
||||
'frame',
|
||||
'height',
|
||||
'href',
|
||||
'id',
|
||||
'largeop',
|
||||
'length',
|
||||
'linethickness',
|
||||
'lspace',
|
||||
'lquote',
|
||||
'mathbackground',
|
||||
'mathcolor',
|
||||
'mathsize',
|
||||
'mathvariant',
|
||||
'maxsize',
|
||||
'minsize',
|
||||
'movablelimits',
|
||||
'notation',
|
||||
'numalign',
|
||||
'open',
|
||||
'rowalign',
|
||||
'rowlines',
|
||||
'rowspacing',
|
||||
'rowspan',
|
||||
'rspace',
|
||||
'rquote',
|
||||
'scriptlevel',
|
||||
'scriptminsize',
|
||||
'scriptsizemultiplier',
|
||||
'selection',
|
||||
'separator',
|
||||
'separators',
|
||||
'stretchy',
|
||||
'subscriptshift',
|
||||
'supscriptshift',
|
||||
'symmetric',
|
||||
'voffset',
|
||||
'width',
|
||||
'xmlns',
|
||||
]);
|
||||
|
||||
export const xml = freeze([
|
||||
'xlink:href',
|
||||
'xml:id',
|
||||
'xlink:title',
|
||||
'xml:space',
|
||||
'xmlns:xlink',
|
||||
]);
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,16 @@
|
|||
import { seal } from './utils.js';
|
||||
|
||||
// eslint-disable-next-line unicorn/better-regex
|
||||
export const MUSTACHE_EXPR = seal(/\{\{[\w\W]*|[\w\W]*\}\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode
|
||||
export const ERB_EXPR = seal(/<%[\w\W]*|[\w\W]*%>/gm);
|
||||
export const TMPLIT_EXPR = seal(/\${[\w\W]*}/gm);
|
||||
export const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]/); // eslint-disable-line no-useless-escape
|
||||
export const ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape
|
||||
export const IS_ALLOWED_URI = seal(
|
||||
/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i // eslint-disable-line no-useless-escape
|
||||
);
|
||||
export const IS_SCRIPT_OR_DATA = seal(/^(?:\w+script|data):/i);
|
||||
export const ATTR_WHITESPACE = seal(
|
||||
/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g // eslint-disable-line no-control-regex
|
||||
);
|
||||
export const DOCTYPE_NAME = seal(/^html$/i);
|
|
@ -0,0 +1,280 @@
|
|||
import { freeze } from './utils.js';
|
||||
|
||||
export const html = freeze([
|
||||
'a',
|
||||
'abbr',
|
||||
'acronym',
|
||||
'address',
|
||||
'area',
|
||||
'article',
|
||||
'aside',
|
||||
'audio',
|
||||
'b',
|
||||
'bdi',
|
||||
'bdo',
|
||||
'big',
|
||||
'blink',
|
||||
'blockquote',
|
||||
'body',
|
||||
'br',
|
||||
'button',
|
||||
'canvas',
|
||||
'caption',
|
||||
'center',
|
||||
'cite',
|
||||
'code',
|
||||
'col',
|
||||
'colgroup',
|
||||
'content',
|
||||
'data',
|
||||
'datalist',
|
||||
'dd',
|
||||
'decorator',
|
||||
'del',
|
||||
'details',
|
||||
'dfn',
|
||||
'dialog',
|
||||
'dir',
|
||||
'div',
|
||||
'dl',
|
||||
'dt',
|
||||
'element',
|
||||
'em',
|
||||
'fieldset',
|
||||
'figcaption',
|
||||
'figure',
|
||||
'font',
|
||||
'footer',
|
||||
'form',
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'head',
|
||||
'header',
|
||||
'hgroup',
|
||||
'hr',
|
||||
'html',
|
||||
'i',
|
||||
'img',
|
||||
'input',
|
||||
'ins',
|
||||
'kbd',
|
||||
'label',
|
||||
'legend',
|
||||
'li',
|
||||
'main',
|
||||
'map',
|
||||
'mark',
|
||||
'marquee',
|
||||
'menu',
|
||||
'menuitem',
|
||||
'meter',
|
||||
'nav',
|
||||
'nobr',
|
||||
'ol',
|
||||
'optgroup',
|
||||
'option',
|
||||
'output',
|
||||
'p',
|
||||
'picture',
|
||||
'pre',
|
||||
'progress',
|
||||
'q',
|
||||
'rp',
|
||||
'rt',
|
||||
'ruby',
|
||||
's',
|
||||
'samp',
|
||||
'section',
|
||||
'select',
|
||||
'shadow',
|
||||
'small',
|
||||
'source',
|
||||
'spacer',
|
||||
'span',
|
||||
'strike',
|
||||
'strong',
|
||||
'style',
|
||||
'sub',
|
||||
'summary',
|
||||
'sup',
|
||||
'table',
|
||||
'tbody',
|
||||
'td',
|
||||
'template',
|
||||
'textarea',
|
||||
'tfoot',
|
||||
'th',
|
||||
'thead',
|
||||
'time',
|
||||
'tr',
|
||||
'track',
|
||||
'tt',
|
||||
'u',
|
||||
'ul',
|
||||
'var',
|
||||
'video',
|
||||
'wbr',
|
||||
]);
|
||||
|
||||
// SVG
|
||||
export const svg = freeze([
|
||||
'svg',
|
||||
'a',
|
||||
'altglyph',
|
||||
'altglyphdef',
|
||||
'altglyphitem',
|
||||
'animatecolor',
|
||||
'animatemotion',
|
||||
'animatetransform',
|
||||
'circle',
|
||||
'clippath',
|
||||
'defs',
|
||||
'desc',
|
||||
'ellipse',
|
||||
'filter',
|
||||
'font',
|
||||
'g',
|
||||
'glyph',
|
||||
'glyphref',
|
||||
'hkern',
|
||||
'image',
|
||||
'line',
|
||||
'lineargradient',
|
||||
'marker',
|
||||
'mask',
|
||||
'metadata',
|
||||
'mpath',
|
||||
'path',
|
||||
'pattern',
|
||||
'polygon',
|
||||
'polyline',
|
||||
'radialgradient',
|
||||
'rect',
|
||||
'stop',
|
||||
'style',
|
||||
'switch',
|
||||
'symbol',
|
||||
'text',
|
||||
'textpath',
|
||||
'title',
|
||||
'tref',
|
||||
'tspan',
|
||||
'view',
|
||||
'vkern',
|
||||
]);
|
||||
|
||||
export const svgFilters = freeze([
|
||||
'feBlend',
|
||||
'feColorMatrix',
|
||||
'feComponentTransfer',
|
||||
'feComposite',
|
||||
'feConvolveMatrix',
|
||||
'feDiffuseLighting',
|
||||
'feDisplacementMap',
|
||||
'feDistantLight',
|
||||
'feDropShadow',
|
||||
'feFlood',
|
||||
'feFuncA',
|
||||
'feFuncB',
|
||||
'feFuncG',
|
||||
'feFuncR',
|
||||
'feGaussianBlur',
|
||||
'feImage',
|
||||
'feMerge',
|
||||
'feMergeNode',
|
||||
'feMorphology',
|
||||
'feOffset',
|
||||
'fePointLight',
|
||||
'feSpecularLighting',
|
||||
'feSpotLight',
|
||||
'feTile',
|
||||
'feTurbulence',
|
||||
]);
|
||||
|
||||
// List of SVG elements that are disallowed by default.
|
||||
// We still need to know them so that we can do namespace
|
||||
// checks properly in case one wants to add them to
|
||||
// allow-list.
|
||||
export const svgDisallowed = freeze([
|
||||
'animate',
|
||||
'color-profile',
|
||||
'cursor',
|
||||
'discard',
|
||||
'font-face',
|
||||
'font-face-format',
|
||||
'font-face-name',
|
||||
'font-face-src',
|
||||
'font-face-uri',
|
||||
'foreignobject',
|
||||
'hatch',
|
||||
'hatchpath',
|
||||
'mesh',
|
||||
'meshgradient',
|
||||
'meshpatch',
|
||||
'meshrow',
|
||||
'missing-glyph',
|
||||
'script',
|
||||
'set',
|
||||
'solidcolor',
|
||||
'unknown',
|
||||
'use',
|
||||
]);
|
||||
|
||||
export const mathMl = freeze([
|
||||
'math',
|
||||
'menclose',
|
||||
'merror',
|
||||
'mfenced',
|
||||
'mfrac',
|
||||
'mglyph',
|
||||
'mi',
|
||||
'mlabeledtr',
|
||||
'mmultiscripts',
|
||||
'mn',
|
||||
'mo',
|
||||
'mover',
|
||||
'mpadded',
|
||||
'mphantom',
|
||||
'mroot',
|
||||
'mrow',
|
||||
'ms',
|
||||
'mspace',
|
||||
'msqrt',
|
||||
'mstyle',
|
||||
'msub',
|
||||
'msup',
|
||||
'msubsup',
|
||||
'mtable',
|
||||
'mtd',
|
||||
'mtext',
|
||||
'mtr',
|
||||
'munder',
|
||||
'munderover',
|
||||
'mprescripts',
|
||||
]);
|
||||
|
||||
// Similarly to SVG, we want to know all MathML elements,
|
||||
// even those that we disallow by default.
|
||||
export const mathMlDisallowed = freeze([
|
||||
'maction',
|
||||
'maligngroup',
|
||||
'malignmark',
|
||||
'mlongdiv',
|
||||
'mscarries',
|
||||
'mscarry',
|
||||
'msgroup',
|
||||
'mstack',
|
||||
'msline',
|
||||
'msrow',
|
||||
'semantics',
|
||||
'annotation',
|
||||
'annotation-xml',
|
||||
'mprescripts',
|
||||
'none',
|
||||
]);
|
||||
|
||||
export const text = freeze(['#text']);
|
|
@ -0,0 +1,193 @@
|
|||
const {
|
||||
entries,
|
||||
setPrototypeOf,
|
||||
isFrozen,
|
||||
getPrototypeOf,
|
||||
getOwnPropertyDescriptor,
|
||||
} = Object;
|
||||
|
||||
let { freeze, seal, create } = Object; // eslint-disable-line import/no-mutable-exports
|
||||
let { apply, construct } = typeof Reflect !== 'undefined' && Reflect;
|
||||
|
||||
if (!freeze) {
|
||||
freeze = function (x) {
|
||||
return x;
|
||||
};
|
||||
}
|
||||
|
||||
if (!seal) {
|
||||
seal = function (x) {
|
||||
return x;
|
||||
};
|
||||
}
|
||||
|
||||
if (!apply) {
|
||||
apply = function (fun, thisValue, args) {
|
||||
return fun.apply(thisValue, args);
|
||||
};
|
||||
}
|
||||
|
||||
if (!construct) {
|
||||
construct = function (Func, args) {
|
||||
return new Func(...args);
|
||||
};
|
||||
}
|
||||
|
||||
const arrayForEach = unapply(Array.prototype.forEach);
|
||||
const arrayIndexOf = unapply(Array.prototype.indexOf);
|
||||
const arrayPop = unapply(Array.prototype.pop);
|
||||
const arrayPush = unapply(Array.prototype.push);
|
||||
const arraySlice = unapply(Array.prototype.slice);
|
||||
|
||||
const stringToLowerCase = unapply(String.prototype.toLowerCase);
|
||||
const stringToString = unapply(String.prototype.toString);
|
||||
const stringMatch = unapply(String.prototype.match);
|
||||
const stringReplace = unapply(String.prototype.replace);
|
||||
const stringIndexOf = unapply(String.prototype.indexOf);
|
||||
const stringTrim = unapply(String.prototype.trim);
|
||||
|
||||
const regExpTest = unapply(RegExp.prototype.test);
|
||||
|
||||
const typeErrorCreate = unconstruct(TypeError);
|
||||
|
||||
/**
|
||||
* Creates a new function that calls the given function with a specified thisArg and arguments.
|
||||
*
|
||||
* @param {Function} func - The function to be wrapped and called.
|
||||
* @returns {Function} A new function that calls the given function with a specified thisArg and arguments.
|
||||
*/
|
||||
function unapply(func) {
|
||||
return (thisArg, ...args) => apply(func, thisArg, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new function that constructs an instance of the given constructor function with the provided arguments.
|
||||
*
|
||||
* @param {Function} func - The constructor function to be wrapped and called.
|
||||
* @returns {Function} A new function that constructs an instance of the given constructor function with the provided arguments.
|
||||
*/
|
||||
function unconstruct(func) {
|
||||
return (...args) => construct(func, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add properties to a lookup table
|
||||
*
|
||||
* @param {Object} set - The set to which elements will be added.
|
||||
* @param {Array} array - The array containing elements to be added to the set.
|
||||
* @param {Function} transformCaseFunc - An optional function to transform the case of each element before adding to the set.
|
||||
* @returns {Object} The modified set with added elements.
|
||||
*/
|
||||
function addToSet(set, array, transformCaseFunc = stringToLowerCase) {
|
||||
if (setPrototypeOf) {
|
||||
// Make 'in' and truthy checks like Boolean(set.constructor)
|
||||
// independent of any properties defined on Object.prototype.
|
||||
// Prevent prototype setters from intercepting set as a this value.
|
||||
setPrototypeOf(set, null);
|
||||
}
|
||||
|
||||
let l = array.length;
|
||||
while (l--) {
|
||||
let element = array[l];
|
||||
if (typeof element === 'string') {
|
||||
const lcElement = transformCaseFunc(element);
|
||||
if (lcElement !== element) {
|
||||
// Config presets (e.g. tags.js, attrs.js) are immutable.
|
||||
if (!isFrozen(array)) {
|
||||
array[l] = lcElement;
|
||||
}
|
||||
|
||||
element = lcElement;
|
||||
}
|
||||
}
|
||||
|
||||
set[element] = true;
|
||||
}
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shallow clone an object
|
||||
*
|
||||
* @param {Object} object - The object to be cloned.
|
||||
* @returns {Object} A new object that copies the original.
|
||||
*/
|
||||
export function clone(object) {
|
||||
const newObject = create(null);
|
||||
|
||||
for (const [property, value] of entries(object)) {
|
||||
if (getOwnPropertyDescriptor(object, property) !== undefined) {
|
||||
newObject[property] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return newObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method automatically checks if the prop is function or getter and behaves accordingly.
|
||||
*
|
||||
* @param {Object} object - The object to look up the getter function in its prototype chain.
|
||||
* @param {String} prop - The property name for which to find the getter function.
|
||||
* @returns {Function} The getter function found in the prototype chain or a fallback function.
|
||||
*/
|
||||
function lookupGetter(object, prop) {
|
||||
while (object !== null) {
|
||||
const desc = getOwnPropertyDescriptor(object, prop);
|
||||
|
||||
if (desc) {
|
||||
if (desc.get) {
|
||||
return unapply(desc.get);
|
||||
}
|
||||
|
||||
if (typeof desc.value === 'function') {
|
||||
return unapply(desc.value);
|
||||
}
|
||||
}
|
||||
|
||||
object = getPrototypeOf(object);
|
||||
}
|
||||
|
||||
function fallbackValue(element) {
|
||||
console.warn('fallback value for', element);
|
||||
return null;
|
||||
}
|
||||
|
||||
return fallbackValue;
|
||||
}
|
||||
|
||||
export {
|
||||
// Array
|
||||
arrayForEach,
|
||||
arrayIndexOf,
|
||||
arrayPop,
|
||||
arrayPush,
|
||||
arraySlice,
|
||||
// Object
|
||||
entries,
|
||||
freeze,
|
||||
getPrototypeOf,
|
||||
getOwnPropertyDescriptor,
|
||||
isFrozen,
|
||||
setPrototypeOf,
|
||||
seal,
|
||||
create,
|
||||
// RegExp
|
||||
regExpTest,
|
||||
// String
|
||||
stringIndexOf,
|
||||
stringMatch,
|
||||
stringReplace,
|
||||
stringToLowerCase,
|
||||
stringToString,
|
||||
stringTrim,
|
||||
// Errors
|
||||
typeErrorCreate,
|
||||
// Other
|
||||
lookupGetter,
|
||||
addToSet,
|
||||
// Reflect
|
||||
unapply,
|
||||
unconstruct,
|
||||
};
|
|
@ -18,7 +18,8 @@
|
|||
|
||||
<script type="module" src="./js/global.js"></script>
|
||||
|
||||
<script src="./js/editor/ace.js"></script>
|
||||
<script async src="./js/utils/purify/purify.min.js"></script>
|
||||
<script async src="./js/editor/ace.js"></script>
|
||||
|
||||
{% if current_endpoint == "global_config" %}
|
||||
<script type="module" src="./js/global_config.js"></script>
|
||||
|
@ -33,7 +34,7 @@
|
|||
{% elif current_endpoint == "logs" %}
|
||||
<link rel="stylesheet" type="text/css" href="./css/flatpickr.css" />
|
||||
<link rel="stylesheet" type="text/css" href="./css/flatpickr.dark.css" />
|
||||
<script type="module" src="./js/utils/flatpickr.js"></script>
|
||||
<script async type="module" src="./js/utils/flatpickr.js"></script>
|
||||
<script type="module" src="./js/logs.js"></script>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
|
|
Loading…
Reference in New Issue