@schukai/monster 4.135.0 → 4.136.1
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 +1 -1
- package/source/components/content/stylesheet/viewer.mjs +29 -0
- package/source/components/content/viewer/style/html.pcss +2 -0
- package/source/components/content/viewer/stylesheet/html.mjs +1 -1
- package/source/components/content/viewer.mjs +98 -15
- package/source/data/datasource/server/restapi.mjs +3 -0
- package/source/data/datasource/server/webconnect.mjs +3 -0
- package/source/data/datasource/server.mjs +3 -0
- package/source/data/datasource/storage.mjs +3 -0
- package/source/i18n/formatter.mjs +3 -0
- package/source/i18n/provider.mjs +3 -0
- package/source/logging/handler.mjs +3 -0
- package/source/logging/logger.mjs +3 -0
- package/source/net/webconnect/message.mjs +3 -0
- package/source/net/webconnect.mjs +3 -0
- package/test/cases/components/content/viewer.mjs +99 -0
- package/test/web/import.js +1 -0
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.6"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"type":"module","version":"4.
|
|
1
|
+
{"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.6"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"type":"module","version":"4.136.1"}
|
|
@@ -29,6 +29,35 @@ try {
|
|
|
29
29
|
}`,
|
|
30
30
|
0,
|
|
31
31
|
);
|
|
32
|
+
ViewerStyleSheet.insertRule(
|
|
33
|
+
`
|
|
34
|
+
@layer viewer {
|
|
35
|
+
:host {
|
|
36
|
+
max-width: 100%;
|
|
37
|
+
min-width: 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
[data-monster-role="viewer"] {
|
|
41
|
+
max-width: 100%;
|
|
42
|
+
min-height: 0;
|
|
43
|
+
min-width: 0;
|
|
44
|
+
overflow: auto;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
[data-monster-role="viewer"] :is(img, video, audio, object, embed, table, pre) {
|
|
48
|
+
max-width: 100%;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
[data-monster-role="viewer"] [part="text"] {
|
|
52
|
+
overflow: hidden;
|
|
53
|
+
overflow-wrap: anywhere;
|
|
54
|
+
white-space: pre-wrap;
|
|
55
|
+
width: 100%;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
`,
|
|
59
|
+
1,
|
|
60
|
+
);
|
|
32
61
|
} catch (e) {
|
|
33
62
|
addAttributeToken(
|
|
34
63
|
document.getRootNode().querySelector("html"),
|
|
@@ -25,7 +25,7 @@ try {
|
|
|
25
25
|
HtmlStyleSheet.insertRule(
|
|
26
26
|
`
|
|
27
27
|
@layer html {
|
|
28
|
-
div,img{transition:opacity .9s ease,transform .3s ease,filter .3s ease,box-shadow .3s ease,background-color .3s ease}div[data-monster-role=content-container]{display:flex;flex-direction:column;height:100%;overflow:auto;position:relative;width:100%}.privacyImage{-webkit-mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath d='M5.338 1.59a61 61 0 0 0-2.837.856.48.48 0 0 0-.328.39c-.554 4.157.726 7.19 2.253 9.188a10.7 10.7 0 0 0 2.287 2.233c.346.244.652.42.893.533q.18.085.293.118a1 1 0 0 0 .101.025 1 1 0 0 0 .1-.025q.114-.034.294-.118c.24-.113.547-.29.893-.533a10.7 10.7 0 0 0 2.287-2.233c1.527-1.997 2.807-5.031 2.253-9.188a.48.48 0 0 0-.328-.39c-.651-.213-1.75-.56-2.837-.855C9.552 1.29 8.531 1.067 8 1.067c-.53 0-1.552.223-2.662.524zM5.072.56C6.157.265 7.31 0 8 0s1.843.265 2.928.56c1.11.3 2.229.655 2.887.87a1.54 1.54 0 0 1 1.044 1.262c.596 4.477-.787 7.795-2.465 9.99a11.8 11.8 0 0 1-2.517 2.453 7 7 0 0 1-1.048.625c-.28.132-.581.24-.829.24s-.548-.108-.829-.24a7 7 0 0 1-1.048-.625 11.8 11.8 0 0 1-2.517-2.453C1.928 10.487.545 7.169 1.141 2.692A1.54 1.54 0 0 1 2.185 1.43 63 63 0 0 1 5.072.56'/%3E%3Cpath d='M7.001 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0M7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.553.553 0 0 1-1.1 0z'/%3E%3C/svg%3E\");mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath d='M5.338 1.59a61 61 0 0 0-2.837.856.48.48 0 0 0-.328.39c-.554 4.157.726 7.19 2.253 9.188a10.7 10.7 0 0 0 2.287 2.233c.346.244.652.42.893.533q.18.085.293.118a1 1 0 0 0 .101.025 1 1 0 0 0 .1-.025q.114-.034.294-.118c.24-.113.547-.29.893-.533a10.7 10.7 0 0 0 2.287-2.233c1.527-1.997 2.807-5.031 2.253-9.188a.48.48 0 0 0-.328-.39c-.651-.213-1.75-.56-2.837-.855C9.552 1.29 8.531 1.067 8 1.067c-.53 0-1.552.223-2.662.524zM5.072.56C6.157.265 7.31 0 8 0s1.843.265 2.928.56c1.11.3 2.229.655 2.887.87a1.54 1.54 0 0 1 1.044 1.262c.596 4.477-.787 7.795-2.465 9.99a11.8 11.8 0 0 1-2.517 2.453 7 7 0 0 1-1.048.625c-.28.132-.581.24-.829.24s-.548-.108-.829-.24a7 7 0 0 1-1.048-.625 11.8 11.8 0 0 1-2.517-2.453C1.928 10.487.545 7.169 1.141 2.692A1.54 1.54 0 0 1 2.185 1.43 63 63 0 0 1 5.072.56'/%3E%3Cpath d='M7.001 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0M7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.553.553 0 0 1-1.1 0z'/%3E%3C/svg%3E\")}.notFoundImage,.privacyImage{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:var(--monster-bg-color-primary-4);-webkit-mask-position:center center;mask-position:center center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:50%;mask-size:50%;-o-object-fit:contain;object-fit:contain}.notFoundImage{-webkit-mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M3.112 5.112a3 3 0 0 0-.17.613C1.266 6.095 0 7.555 0 9.318 0 11.366 1.708 13 3.781 13H11l-1-1H3.781C2.231 12 1 10.785 1 9.318c0-1.365 1.064-2.513 2.46-2.666l.446-.05v-.447q0-.113.018-.231zm2.55-1.45-.725-.725A5.5 5.5 0 0 1 8 2c2.69 0 4.923 2 5.166 4.579C14.758 6.804 16 8.137 16 9.773a3.2 3.2 0 0 1-1.516 2.711l-.733-.733C14.498 11.378 15 10.626 15 9.773c0-1.216-1.02-2.228-2.313-2.228h-.5v-.5C12.188 4.825 10.328 3 8 3c-.875 0-1.678.26-2.339.661z'/%3E%3Cpath d='m13.646 14.354-12-12 .708-.708 12 12z'/%3E%3C/svg%3E\");mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M3.112 5.112a3 3 0 0 0-.17.613C1.266 6.095 0 7.555 0 9.318 0 11.366 1.708 13 3.781 13H11l-1-1H3.781C2.231 12 1 10.785 1 9.318c0-1.365 1.064-2.513 2.46-2.666l.446-.05v-.447q0-.113.018-.231zm2.55-1.45-.725-.725A5.5 5.5 0 0 1 8 2c2.69 0 4.923 2 5.166 4.579C14.758 6.804 16 8.137 16 9.773a3.2 3.2 0 0 1-1.516 2.711l-.733-.733C14.498 11.378 15 10.626 15 9.773c0-1.216-1.02-2.228-2.313-2.228h-.5v-.5C12.188 4.825 10.328 3 8 3c-.875 0-1.678.26-2.339.661z'/%3E%3Cpath d='m13.646 14.354-12-12 .708-.708 12 12z'/%3E%3C/svg%3E\")}
|
|
28
|
+
div,img{transition:opacity .9s ease,transform .3s ease,filter .3s ease,box-shadow .3s ease,background-color .3s ease}div[data-monster-role=content-container]{display:flex;flex-direction:column;height:100%;max-width:100%;min-width:0;overflow:auto;position:relative;width:100%}.privacyImage{-webkit-mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath d='M5.338 1.59a61 61 0 0 0-2.837.856.48.48 0 0 0-.328.39c-.554 4.157.726 7.19 2.253 9.188a10.7 10.7 0 0 0 2.287 2.233c.346.244.652.42.893.533q.18.085.293.118a1 1 0 0 0 .101.025 1 1 0 0 0 .1-.025q.114-.034.294-.118c.24-.113.547-.29.893-.533a10.7 10.7 0 0 0 2.287-2.233c1.527-1.997 2.807-5.031 2.253-9.188a.48.48 0 0 0-.328-.39c-.651-.213-1.75-.56-2.837-.855C9.552 1.29 8.531 1.067 8 1.067c-.53 0-1.552.223-2.662.524zM5.072.56C6.157.265 7.31 0 8 0s1.843.265 2.928.56c1.11.3 2.229.655 2.887.87a1.54 1.54 0 0 1 1.044 1.262c.596 4.477-.787 7.795-2.465 9.99a11.8 11.8 0 0 1-2.517 2.453 7 7 0 0 1-1.048.625c-.28.132-.581.24-.829.24s-.548-.108-.829-.24a7 7 0 0 1-1.048-.625 11.8 11.8 0 0 1-2.517-2.453C1.928 10.487.545 7.169 1.141 2.692A1.54 1.54 0 0 1 2.185 1.43 63 63 0 0 1 5.072.56'/%3E%3Cpath d='M7.001 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0M7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.553.553 0 0 1-1.1 0z'/%3E%3C/svg%3E\");mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath d='M5.338 1.59a61 61 0 0 0-2.837.856.48.48 0 0 0-.328.39c-.554 4.157.726 7.19 2.253 9.188a10.7 10.7 0 0 0 2.287 2.233c.346.244.652.42.893.533q.18.085.293.118a1 1 0 0 0 .101.025 1 1 0 0 0 .1-.025q.114-.034.294-.118c.24-.113.547-.29.893-.533a10.7 10.7 0 0 0 2.287-2.233c1.527-1.997 2.807-5.031 2.253-9.188a.48.48 0 0 0-.328-.39c-.651-.213-1.75-.56-2.837-.855C9.552 1.29 8.531 1.067 8 1.067c-.53 0-1.552.223-2.662.524zM5.072.56C6.157.265 7.31 0 8 0s1.843.265 2.928.56c1.11.3 2.229.655 2.887.87a1.54 1.54 0 0 1 1.044 1.262c.596 4.477-.787 7.795-2.465 9.99a11.8 11.8 0 0 1-2.517 2.453 7 7 0 0 1-1.048.625c-.28.132-.581.24-.829.24s-.548-.108-.829-.24a7 7 0 0 1-1.048-.625 11.8 11.8 0 0 1-2.517-2.453C1.928 10.487.545 7.169 1.141 2.692A1.54 1.54 0 0 1 2.185 1.43 63 63 0 0 1 5.072.56'/%3E%3Cpath d='M7.001 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0M7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.553.553 0 0 1-1.1 0z'/%3E%3C/svg%3E\")}.notFoundImage,.privacyImage{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:var(--monster-bg-color-primary-4);-webkit-mask-position:center center;mask-position:center center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:50%;mask-size:50%;-o-object-fit:contain;object-fit:contain}.notFoundImage{-webkit-mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M3.112 5.112a3 3 0 0 0-.17.613C1.266 6.095 0 7.555 0 9.318 0 11.366 1.708 13 3.781 13H11l-1-1H3.781C2.231 12 1 10.785 1 9.318c0-1.365 1.064-2.513 2.46-2.666l.446-.05v-.447q0-.113.018-.231zm2.55-1.45-.725-.725A5.5 5.5 0 0 1 8 2c2.69 0 4.923 2 5.166 4.579C14.758 6.804 16 8.137 16 9.773a3.2 3.2 0 0 1-1.516 2.711l-.733-.733C14.498 11.378 15 10.626 15 9.773c0-1.216-1.02-2.228-2.313-2.228h-.5v-.5C12.188 4.825 10.328 3 8 3c-.875 0-1.678.26-2.339.661z'/%3E%3Cpath d='m13.646 14.354-12-12 .708-.708 12 12z'/%3E%3C/svg%3E\");mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M3.112 5.112a3 3 0 0 0-.17.613C1.266 6.095 0 7.555 0 9.318 0 11.366 1.708 13 3.781 13H11l-1-1H3.781C2.231 12 1 10.785 1 9.318c0-1.365 1.064-2.513 2.46-2.666l.446-.05v-.447q0-.113.018-.231zm2.55-1.45-.725-.725A5.5 5.5 0 0 1 8 2c2.69 0 4.923 2 5.166 4.579C14.758 6.804 16 8.137 16 9.773a3.2 3.2 0 0 1-1.516 2.711l-.733-.733C14.498 11.378 15 10.626 15 9.773c0-1.216-1.02-2.228-2.313-2.228h-.5v-.5C12.188 4.825 10.328 3 8 3c-.875 0-1.678.26-2.339.661z'/%3E%3Cpath d='m13.646 14.354-12-12 .708-.708 12 12z'/%3E%3C/svg%3E\")}
|
|
29
29
|
}`,
|
|
30
30
|
0,
|
|
31
31
|
);
|
|
@@ -29,9 +29,13 @@ import "./viewer/message.mjs";
|
|
|
29
29
|
import { getLocaleOfDocument } from "../../dom/locale.mjs";
|
|
30
30
|
import { Button } from "../form/button.mjs";
|
|
31
31
|
import { findTargetElementFromEvent } from "../../dom/events.mjs";
|
|
32
|
+
import { sanitizeHtml } from "../../dom/sanitize-html.mjs";
|
|
32
33
|
|
|
33
34
|
export { Viewer };
|
|
34
35
|
|
|
36
|
+
const BLOCKED_HTML_RESOURCE_DATA_URL =
|
|
37
|
+
"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
|
|
38
|
+
|
|
35
39
|
/**
|
|
36
40
|
* @private
|
|
37
41
|
* @type {symbol}
|
|
@@ -732,7 +736,7 @@ class Viewer extends CustomElement {
|
|
|
732
736
|
if (data instanceof Blob) {
|
|
733
737
|
blobToText(data)
|
|
734
738
|
.then((html) => {
|
|
735
|
-
this.setOption("content", html);
|
|
739
|
+
this.setOption("content", sanitizeViewerHTMLContent(html));
|
|
736
740
|
})
|
|
737
741
|
.catch((error) => {
|
|
738
742
|
this.dispatchEvent(
|
|
@@ -754,7 +758,7 @@ class Viewer extends CustomElement {
|
|
|
754
758
|
return response.text();
|
|
755
759
|
})
|
|
756
760
|
.then((html) => {
|
|
757
|
-
this.setOption("content", html);
|
|
761
|
+
this.setOption("content", sanitizeViewerHTMLContent(html));
|
|
758
762
|
})
|
|
759
763
|
.catch((error) => {
|
|
760
764
|
this.dispatchEvent(
|
|
@@ -771,7 +775,7 @@ class Viewer extends CustomElement {
|
|
|
771
775
|
throw new Error("HTMLElement or string expected");
|
|
772
776
|
}
|
|
773
777
|
|
|
774
|
-
this.setOption("content", data);
|
|
778
|
+
this.setOption("content", sanitizeViewerHTMLContent(data));
|
|
775
779
|
}
|
|
776
780
|
|
|
777
781
|
/**
|
|
@@ -787,7 +791,7 @@ class Viewer extends CustomElement {
|
|
|
787
791
|
setPlainText(data) {
|
|
788
792
|
const mkPreSpan = (text) => {
|
|
789
793
|
const pre = document.createElement("pre");
|
|
790
|
-
pre.
|
|
794
|
+
pre.textContent = text;
|
|
791
795
|
pre.setAttribute("part", "text");
|
|
792
796
|
return pre.outerHTML;
|
|
793
797
|
};
|
|
@@ -795,10 +799,6 @@ class Viewer extends CustomElement {
|
|
|
795
799
|
if (data instanceof Blob) {
|
|
796
800
|
blobToText(data)
|
|
797
801
|
.then((text) => {
|
|
798
|
-
const div = document.createElement("div");
|
|
799
|
-
div.innerHTML = text;
|
|
800
|
-
text = div.innerText;
|
|
801
|
-
|
|
802
802
|
this.setOption("content", mkPreSpan(text));
|
|
803
803
|
})
|
|
804
804
|
.catch((error) => {
|
|
@@ -812,9 +812,7 @@ class Viewer extends CustomElement {
|
|
|
812
812
|
} else if (data instanceof HTMLElement) {
|
|
813
813
|
data = data.outerText;
|
|
814
814
|
} else if (isString(data)) {
|
|
815
|
-
|
|
816
|
-
div.innerHTML = data;
|
|
817
|
-
data = div.innerText;
|
|
815
|
+
// text/plain should never be interpreted as HTML
|
|
818
816
|
} else if (isURL(data)) {
|
|
819
817
|
getGlobal()
|
|
820
818
|
.fetch(data)
|
|
@@ -822,10 +820,6 @@ class Viewer extends CustomElement {
|
|
|
822
820
|
return response.text();
|
|
823
821
|
})
|
|
824
822
|
.then((text) => {
|
|
825
|
-
const div = document.createElement("div");
|
|
826
|
-
div.innerHTML = text;
|
|
827
|
-
text = div.innerText;
|
|
828
|
-
|
|
829
823
|
this.setOption("content", mkPreSpan(text));
|
|
830
824
|
})
|
|
831
825
|
.catch((error) => {
|
|
@@ -910,6 +904,95 @@ function blobToText(blob) {
|
|
|
910
904
|
});
|
|
911
905
|
}
|
|
912
906
|
|
|
907
|
+
function sanitizeViewerHTMLContent(html) {
|
|
908
|
+
const sanitizedHTML = sanitizeHtml(html);
|
|
909
|
+
const parser = new DOMParser();
|
|
910
|
+
const doc = parser.parseFromString(sanitizedHTML, "text/html");
|
|
911
|
+
|
|
912
|
+
doc.querySelectorAll("*").forEach((element) => {
|
|
913
|
+
removeCrossOriginResourceAttribute(element, "src");
|
|
914
|
+
removeCrossOriginResourceAttribute(element, "srcset");
|
|
915
|
+
removeCrossOriginResourceAttribute(element, "poster");
|
|
916
|
+
|
|
917
|
+
if (element.hasAttribute("style")) {
|
|
918
|
+
const safeStyle = sanitizeStyleAttribute(element.getAttribute("style"));
|
|
919
|
+
if (safeStyle) {
|
|
920
|
+
element.setAttribute("style", safeStyle);
|
|
921
|
+
} else {
|
|
922
|
+
element.removeAttribute("style");
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
});
|
|
926
|
+
|
|
927
|
+
return doc.body.innerHTML;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
function removeCrossOriginResourceAttribute(element, attributeName) {
|
|
931
|
+
const value = element.getAttribute(attributeName);
|
|
932
|
+
if (!value) {
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
if (attributeName === "srcset") {
|
|
937
|
+
const sameOriginEntries = value
|
|
938
|
+
.split(",")
|
|
939
|
+
.map((entry) => entry.trim())
|
|
940
|
+
.filter(Boolean)
|
|
941
|
+
.filter((entry) => {
|
|
942
|
+
const [candidateUrl] = entry.split(/\s+/, 1);
|
|
943
|
+
return isSameOriginResourceUrl(candidateUrl);
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
if (sameOriginEntries.length > 0) {
|
|
947
|
+
element.setAttribute(attributeName, sameOriginEntries.join(", "));
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
} else if (isSameOriginResourceUrl(value)) {
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
if (
|
|
955
|
+
attributeName === "src" &&
|
|
956
|
+
element.tagName.toLowerCase() === "img" &&
|
|
957
|
+
!element.classList.contains("privacyImage")
|
|
958
|
+
) {
|
|
959
|
+
element.setAttribute("src", BLOCKED_HTML_RESOURCE_DATA_URL);
|
|
960
|
+
element.classList.add("privacyImage");
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
element.removeAttribute(attributeName);
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
function sanitizeStyleAttribute(styleValue) {
|
|
968
|
+
return styleValue.replace(/url\(([^)]+)\)/gi, (match, rawUrl) => {
|
|
969
|
+
const cleanedUrl = rawUrl.trim().replace(/^['"]|['"]$/g, "");
|
|
970
|
+
return isSameOriginResourceUrl(cleanedUrl) ? match : "";
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
function isSameOriginResourceUrl(value) {
|
|
975
|
+
if (!value) {
|
|
976
|
+
return true;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
const trimmedValue = value.trim();
|
|
980
|
+
if (!trimmedValue || trimmedValue.startsWith("#")) {
|
|
981
|
+
return true;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
try {
|
|
985
|
+
const url = new URL(trimmedValue, document.location.href);
|
|
986
|
+
return (
|
|
987
|
+
url.origin === document.location.origin ||
|
|
988
|
+
url.protocol === "data:" ||
|
|
989
|
+
url.protocol === "blob:"
|
|
990
|
+
);
|
|
991
|
+
} catch {
|
|
992
|
+
return true;
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
|
|
913
996
|
/**
|
|
914
997
|
* @private
|
|
915
998
|
* @return {Select}
|
|
@@ -39,6 +39,9 @@ const rawDataSymbol = Symbol.for(
|
|
|
39
39
|
*
|
|
40
40
|
* @externalExample ../../../../example/data/datasource/server/restapi.mjs
|
|
41
41
|
* @example /examples/libraries/data/datasource/server/restapi/simple/ Configure read and write endpoints
|
|
42
|
+
* @example /examples/libraries/data/datasource/server/restapi/response-callback/ Replace the default read callback with a custom assignment flow
|
|
43
|
+
* @example /examples/libraries/data/datasource/server/restapi/partial-write/ Patch only changed fields before sending a write request
|
|
44
|
+
* @example /examples/libraries/data/datasource/server/restapi/report-path/ Read validation reports from a nested response path
|
|
42
45
|
* @license AGPLv3
|
|
43
46
|
* @since 1.22.0
|
|
44
47
|
* @copyright Volker Schukai
|
|
@@ -35,6 +35,9 @@ const webConnectSymbol = Symbol("connection");
|
|
|
35
35
|
*
|
|
36
36
|
* @externalExample ../../../../example/data/datasource/server/webconnect.mjs
|
|
37
37
|
* @example /examples/libraries/data/datasource/server/webconnect/simple/ Configure a realtime datasource bridge
|
|
38
|
+
* @example /examples/libraries/data/datasource/server/webconnect/message-queue/ Read queued realtime messages through the datasource API
|
|
39
|
+
* @example /examples/libraries/data/datasource/server/webconnect/transformed-read/ Apply read mapping to incoming socket payloads
|
|
40
|
+
* @example /examples/libraries/data/datasource/server/webconnect/write-envelope/ Prepare outgoing socket writes with sheathing options
|
|
38
41
|
* @license AGPLv3
|
|
39
42
|
* @since 3.1.0
|
|
40
43
|
* @copyright Volker Schukai
|
|
@@ -33,6 +33,9 @@ const serverVersionSymbol = Symbol("serverVersion");
|
|
|
33
33
|
* @fragments /fragments/libraries/data/datasource/server/
|
|
34
34
|
*
|
|
35
35
|
* @example /examples/libraries/data/datasource/server/simple/ Transform and prepare server payloads
|
|
36
|
+
* @example /examples/libraries/data/datasource/server/transformer-callbacks/ Combine transformer expressions with mapping callbacks
|
|
37
|
+
* @example /examples/libraries/data/datasource/server/partial-diff/ Reduce write payloads through the partial diff callback
|
|
38
|
+
* @example /examples/libraries/data/datasource/server/sheathing-object/ Wrap outgoing payloads into a server-side envelope
|
|
36
39
|
* @license AGPLv3
|
|
37
40
|
* @since 3.4.0
|
|
38
41
|
* @copyright Volker Schukai
|
|
@@ -32,6 +32,9 @@ const storageObjectSymbol = Symbol.for(
|
|
|
32
32
|
* @fragments /fragments/libraries/data/datasource/storage/
|
|
33
33
|
*
|
|
34
34
|
* @example /examples/libraries/data/datasource/storage/simple/ Read and write JSON through Web Storage
|
|
35
|
+
* @example /examples/libraries/data/datasource/storage/session-draft/ Persist draft state in session storage
|
|
36
|
+
* @example /examples/libraries/data/datasource/storage/local-preferences/ Persist user preferences in local storage
|
|
37
|
+
* @example /examples/libraries/data/datasource/storage/remove-on-undefined/ Remove the storage entry when the datasource becomes undefined
|
|
35
38
|
* @license AGPLv3
|
|
36
39
|
* @since 1.22.0
|
|
37
40
|
* @copyright Volker Schukai
|
|
@@ -31,6 +31,9 @@ const internalTranslationSymbol = Symbol("internalTranslation");
|
|
|
31
31
|
* The Formatter extends the Text.Formatter with the possibility to replace the key by a translation.
|
|
32
32
|
*
|
|
33
33
|
* @fragments /fragments/libraries/i18n/formatter/
|
|
34
|
+
* @example /examples/libraries/i18n/formatter/basic-translation/ Resolve one translation key through the i18n marker
|
|
35
|
+
* @example /examples/libraries/i18n/formatter/parameterized-text/ Combine translated text with formatter parameters
|
|
36
|
+
* @example /examples/libraries/i18n/formatter/custom-markers/ Configure custom formatter markers for translated strings
|
|
34
37
|
*
|
|
35
38
|
* @license AGPLv3
|
|
36
39
|
* @since 1.26.0
|
package/source/i18n/provider.mjs
CHANGED
|
@@ -41,6 +41,9 @@ const translationsLinkSymbol = Symbol.for(
|
|
|
41
41
|
* @fragments /fragments/libraries/i18n/provider/
|
|
42
42
|
*
|
|
43
43
|
* @example /examples/libraries/i18n/provider/simple/ Assign translations to a document
|
|
44
|
+
* @example /examples/libraries/i18n/provider/merge-existing/ Merge a second translation payload into an already linked element
|
|
45
|
+
* @example /examples/libraries/i18n/provider/subtree-assignment/ Attach translations to a dedicated subtree instead of the whole document
|
|
46
|
+
* @example /examples/libraries/i18n/provider/locale-switch/ Resolve different translation objects for different locales
|
|
44
47
|
* @license AGPLv3
|
|
45
48
|
* @since 1.13.0
|
|
46
49
|
* @copyright Volker Schukai
|
|
@@ -25,6 +25,9 @@ export { Handler };
|
|
|
25
25
|
* @fragments /fragments/libraries/logging/handler/
|
|
26
26
|
*
|
|
27
27
|
* @example /examples/libraries/logging/handler/simple/ Filter log entries before forwarding them
|
|
28
|
+
* @example /examples/libraries/logging/handler/level-switch/ Switch handler thresholds with the convenience methods
|
|
29
|
+
* @example /examples/libraries/logging/handler/structured-output/ Forward structured log entry data into a custom buffer
|
|
30
|
+
* @example /examples/libraries/logging/handler/off-state/ Disable a handler completely with the off level
|
|
28
31
|
* @license AGPLv3
|
|
29
32
|
* @since 1.5.0
|
|
30
33
|
* @copyright Volker Schukai
|
|
@@ -71,6 +71,9 @@ const OFF = 0;
|
|
|
71
71
|
* @fragments /fragments/libraries/logging/logger/
|
|
72
72
|
*
|
|
73
73
|
* @example /examples/libraries/logging/logger/simple/ Write entries through a console handler
|
|
74
|
+
* @example /examples/libraries/logging/logger/multiple-handlers/ Route the same log entry through multiple handlers
|
|
75
|
+
* @example /examples/libraries/logging/logger/level-filtering/ Filter log output through handler log levels
|
|
76
|
+
* @example /examples/libraries/logging/logger/remove-handler/ Remove a handler and verify that it no longer receives entries
|
|
74
77
|
* @license AGPLv3
|
|
75
78
|
* @since 1.5.0
|
|
76
79
|
* @copyright Volker Schukai
|
|
@@ -25,6 +25,9 @@ const dataSymbol = Symbol("@@data");
|
|
|
25
25
|
* @fragments /fragments/libraries/net/webconnect/message/
|
|
26
26
|
*
|
|
27
27
|
* @example /examples/libraries/net/webconnect/message/simple/ Serialize and restore a structured message
|
|
28
|
+
* @example /examples/libraries/net/webconnect/message/nested-payload/ Keep nested payload data intact through serialization
|
|
29
|
+
* @example /examples/libraries/net/webconnect/message/queue-ready-shape/ Use a message shape that is ready for queue inspection and logging
|
|
30
|
+
* @example /examples/libraries/net/webconnect/message/object-access/ Access raw message data through getData and toJSON
|
|
28
31
|
* @license AGPLv3
|
|
29
32
|
* @since 3.4.0
|
|
30
33
|
* @copyright Volker Schukai
|
|
@@ -213,6 +213,9 @@ function connectServer(resolve, reject) {
|
|
|
213
213
|
*
|
|
214
214
|
* @externalExample ../../example/net/webconnect.mjs
|
|
215
215
|
* @example /examples/libraries/net/webconnect/simple/ Connect and consume structured messages
|
|
216
|
+
* @example /examples/libraries/net/webconnect/send-and-close/ Send structured messages and close the connection manually
|
|
217
|
+
* @example /examples/libraries/net/webconnect/observer-queue/ Observe incoming queue updates through the receive queue observer API
|
|
218
|
+
* @example /examples/libraries/net/webconnect/reconnect-options/ Configure reconnect and timeout options for resilient connections
|
|
216
219
|
* @license AGPLv3
|
|
217
220
|
* @since 3.1.0
|
|
218
221
|
* @copyright Volker Schukai
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import * as chai from "chai";
|
|
2
|
+
import { chaiDom } from "../../../util/chai-dom.mjs";
|
|
3
|
+
import { initJSDOM } from "../../../util/jsdom.mjs";
|
|
4
|
+
|
|
5
|
+
chai.use(chaiDom);
|
|
6
|
+
const expect = chai.expect;
|
|
7
|
+
|
|
8
|
+
let Viewer;
|
|
9
|
+
|
|
10
|
+
describe("Viewer", function () {
|
|
11
|
+
before(function (done) {
|
|
12
|
+
initJSDOM()
|
|
13
|
+
.then(() => import("../../../../source/components/content/viewer.mjs"))
|
|
14
|
+
.then((module) => {
|
|
15
|
+
Viewer = module.Viewer;
|
|
16
|
+
done();
|
|
17
|
+
})
|
|
18
|
+
.catch((error) => done(error));
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
const mocks = document.getElementById("mocks");
|
|
23
|
+
mocks.innerHTML = "";
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("renders text/plain without interpreting html", function (done) {
|
|
27
|
+
const mocks = document.getElementById("mocks");
|
|
28
|
+
mocks.innerHTML = `<monster-viewer id="viewer"></monster-viewer>`;
|
|
29
|
+
|
|
30
|
+
setTimeout(() => {
|
|
31
|
+
try {
|
|
32
|
+
const viewer = document.getElementById("viewer");
|
|
33
|
+
expect(viewer).instanceof(Viewer);
|
|
34
|
+
|
|
35
|
+
viewer.setContent("<strong>raw</strong>\n<script>alert(1)</script>");
|
|
36
|
+
|
|
37
|
+
setTimeout(() => {
|
|
38
|
+
try {
|
|
39
|
+
const textNode = viewer.shadowRoot.querySelector('[part="text"]');
|
|
40
|
+
expect(textNode).to.not.equal(null);
|
|
41
|
+
expect(textNode.textContent).to.equal(
|
|
42
|
+
"<strong>raw</strong>\n<script>alert(1)</script>",
|
|
43
|
+
);
|
|
44
|
+
expect(textNode.innerHTML).to.contain("<strong>raw</strong>");
|
|
45
|
+
done();
|
|
46
|
+
} catch (error) {
|
|
47
|
+
done(error);
|
|
48
|
+
}
|
|
49
|
+
}, 0);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
done(error);
|
|
52
|
+
}
|
|
53
|
+
}, 0);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("blocks cross-origin resources in html view", function (done) {
|
|
57
|
+
const mocks = document.getElementById("mocks");
|
|
58
|
+
mocks.innerHTML = `<monster-viewer id="viewer"></monster-viewer>`;
|
|
59
|
+
|
|
60
|
+
setTimeout(() => {
|
|
61
|
+
try {
|
|
62
|
+
const viewer = document.getElementById("viewer");
|
|
63
|
+
expect(viewer).instanceof(Viewer);
|
|
64
|
+
|
|
65
|
+
viewer.setContent(
|
|
66
|
+
`
|
|
67
|
+
<link rel="stylesheet" href="https://evil.example/tracker.css">
|
|
68
|
+
<script src="https://evil.example/tracker.js"></script>
|
|
69
|
+
<img id="external" src="https://evil.example/pixel.png">
|
|
70
|
+
<img id="local" src="/image.png">
|
|
71
|
+
`,
|
|
72
|
+
"text/html",
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
setTimeout(() => {
|
|
76
|
+
try {
|
|
77
|
+
const root = viewer.shadowRoot;
|
|
78
|
+
expect(root.querySelector("link")).to.equal(null);
|
|
79
|
+
expect(root.querySelector("script")).to.equal(null);
|
|
80
|
+
|
|
81
|
+
const externalImage = root.querySelector("#external");
|
|
82
|
+
expect(externalImage).to.not.equal(null);
|
|
83
|
+
expect(externalImage.classList.contains("privacyImage")).to.equal(true);
|
|
84
|
+
expect(externalImage.getAttribute("src")).to.match(/^data:image\/gif;base64,/);
|
|
85
|
+
|
|
86
|
+
const localImage = root.querySelector("#local");
|
|
87
|
+
expect(localImage).to.not.equal(null);
|
|
88
|
+
expect(localImage.getAttribute("src")).to.equal("/image.png");
|
|
89
|
+
done();
|
|
90
|
+
} catch (error) {
|
|
91
|
+
done(error);
|
|
92
|
+
}
|
|
93
|
+
}, 0);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
done(error);
|
|
96
|
+
}
|
|
97
|
+
}, 0);
|
|
98
|
+
});
|
|
99
|
+
});
|
package/test/web/import.js
CHANGED
|
@@ -3,6 +3,7 @@ import "./prepare.js";
|
|
|
3
3
|
import "../cases/components/layout/tabs.mjs";
|
|
4
4
|
import "../cases/components/layout/slit-panel.mjs";
|
|
5
5
|
import "../cases/components/layout/panel.mjs";
|
|
6
|
+
import "../cases/components/content/viewer.mjs";
|
|
6
7
|
import "../cases/components/content/image-editor.mjs";
|
|
7
8
|
import "../cases/components/form/buy-box.mjs";
|
|
8
9
|
import "../cases/components/form/message-state-button.mjs";
|