@limetech/lime-elements 39.0.3 → 39.1.0
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/CHANGELOG.md +19 -0
- package/dist/cjs/index-nxo2UO7I.js +20355 -0
- package/dist/cjs/lime-elements.cjs.js +1 -1
- package/dist/cjs/limel-action-bar_2.cjs.entry.js +4 -4
- package/dist/cjs/limel-ai-avatar.cjs.entry.js +1 -1
- package/dist/cjs/limel-breadcrumbs_7.cjs.entry.js +6 -6
- package/dist/cjs/limel-callout.cjs.entry.js +1 -1
- package/dist/cjs/limel-chart.cjs.entry.js +1 -1
- package/dist/cjs/limel-chip_2.cjs.entry.js +1 -1
- package/dist/cjs/limel-code-editor.cjs.entry.js +1 -1
- package/dist/cjs/limel-collapsible-section.cjs.entry.js +2 -2
- package/dist/cjs/limel-drag-handle.cjs.entry.js +1 -1
- package/dist/cjs/limel-email-viewer.cjs.entry.js +351 -0
- package/dist/cjs/limel-file-dropzone_2.cjs.entry.js +2 -2
- package/dist/cjs/limel-file-viewer.cjs.entry.js +4967 -26
- package/dist/cjs/limel-file.cjs.entry.js +2 -2
- package/dist/cjs/limel-flatpickr-adapter.cjs.entry.js +1 -1
- package/dist/cjs/limel-flex-container.cjs.entry.js +1 -1
- package/dist/cjs/limel-form.cjs.entry.js +1 -1
- package/dist/cjs/limel-grid.cjs.entry.js +1 -1
- package/dist/cjs/limel-header.cjs.entry.js +1 -1
- package/dist/cjs/limel-help-content.cjs.entry.js +1 -1
- package/dist/cjs/limel-help.cjs.entry.js +2 -2
- package/dist/cjs/limel-helper-line_2.cjs.entry.js +3 -3
- package/dist/cjs/limel-icon-button.cjs.entry.js +1 -1
- package/dist/cjs/limel-icon.cjs.entry.js +1 -1
- package/dist/cjs/limel-info-tile.cjs.entry.js +2 -2
- package/dist/cjs/limel-linear-progress.cjs.entry.js +1 -1
- package/dist/cjs/limel-list-item.cjs.entry.js +3 -3
- package/dist/cjs/limel-markdown.cjs.entry.js +3 -2
- package/dist/cjs/limel-menu-item-meta.cjs.entry.js +1 -1
- package/dist/cjs/limel-picker.cjs.entry.js +1 -1
- package/dist/cjs/limel-popover_2.cjs.entry.js +2 -2
- package/dist/cjs/limel-portal_3.cjs.entry.js +4 -4
- package/dist/cjs/limel-profile-picture.cjs.entry.js +1 -1
- package/dist/cjs/limel-prosemirror-adapter.cjs.entry.js +15 -14
- package/dist/cjs/limel-radio-button-group.cjs.entry.js +1 -1
- package/dist/cjs/limel-radio-button.cjs.entry.js +2 -2
- package/dist/cjs/limel-select.cjs.entry.js +1 -1
- package/dist/cjs/limel-shortcut.cjs.entry.js +1 -1
- package/dist/cjs/limel-slider.cjs.entry.js +1 -1
- package/dist/cjs/limel-snackbar.cjs.entry.js +3 -3
- package/dist/cjs/limel-split-button.cjs.entry.js +2 -2
- package/dist/cjs/limel-tab-bar.cjs.entry.js +2 -2
- package/dist/cjs/limel-tab-panel.cjs.entry.js +1 -1
- package/dist/cjs/limel-table.cjs.entry.js +4 -4
- package/dist/cjs/limel-text-editor.cjs.entry.js +1 -1
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/cjs/{markdown-parser-_1bsy-5h.js → markdown-parser-B66l_Zvo.js} +20342 -40680
- package/dist/cjs/{translations-BIHr3B9O.js → translations-B0hzD08N.js} +112 -0
- package/dist/collection/collection-manifest.json +1 -0
- package/dist/collection/components/collapsible-section/collapsible-section.css +1 -0
- package/dist/collection/components/email-viewer/email-loader.js +206 -0
- package/dist/collection/components/email-viewer/email-viewer.css +293 -0
- package/dist/collection/components/email-viewer/email-viewer.js +294 -0
- package/dist/collection/components/email-viewer/email-viewer.types.js +1 -0
- package/dist/collection/components/email-viewer/remote-images.js +50 -0
- package/dist/collection/components/email-viewer/sanitize-email-html.js +245 -0
- package/dist/collection/components/email-viewer/split-email-address-list.js +106 -0
- package/dist/collection/components/file/file.js +1 -1
- package/dist/collection/components/file-dropzone/file-dropzone.js +1 -1
- package/dist/collection/components/file-input/file-input.js +1 -1
- package/dist/collection/components/file-viewer/extension-mapping.js +1 -0
- package/dist/collection/components/file-viewer/file-viewer.js +54 -7
- package/dist/collection/components/flex-container/flex-container.js +1 -1
- package/dist/collection/components/form/form.js +1 -1
- package/dist/collection/components/grid/grid.js +1 -1
- package/dist/collection/components/header/header.js +1 -1
- package/dist/collection/components/help/help-content.js +1 -1
- package/dist/collection/components/help/help.js +2 -2
- package/dist/collection/components/helper-line/helper-line.js +2 -2
- package/dist/collection/components/icon/icon.js +1 -1
- package/dist/collection/components/icon-button/icon-button.js +1 -1
- package/dist/collection/components/info-tile/info-tile.js +2 -2
- package/dist/collection/components/input-field/input-field.js +1 -1
- package/dist/collection/components/list/list.js +1 -1
- package/dist/collection/components/list-item/list-item.js +2 -2
- package/dist/collection/components/list-item/menu-item-meta/menu-item-meta.js +1 -1
- package/dist/collection/components/markdown/markdown.js +1 -1
- package/dist/collection/components/menu/menu.js +1 -1
- package/dist/collection/components/menu-list/menu-list.js +1 -1
- package/dist/collection/components/menu-surface/menu-surface.js +1 -1
- package/dist/collection/components/notched-outline/notched-outline.js +1 -1
- package/dist/collection/components/picker/picker.js +1 -1
- package/dist/collection/components/popover/popover.js +1 -1
- package/dist/collection/components/popover-surface/popover-surface.js +1 -1
- package/dist/collection/components/portal/portal.js +1 -1
- package/dist/collection/components/radio-button-group/radio-button-group.js +1 -1
- package/dist/collection/components/radio-button-group/radio-button.js +2 -2
- package/dist/collection/components/select/select.js +1 -1
- package/dist/collection/components/shortcut/shortcut.js +1 -1
- package/dist/collection/components/slider/slider.js +1 -1
- package/dist/collection/components/snackbar/snackbar.js +2 -2
- package/dist/collection/components/spinner/spinner.js +1 -1
- package/dist/collection/components/split-button/split-button.js +2 -2
- package/dist/collection/components/tab-bar/tab-bar.js +2 -2
- package/dist/collection/components/tab-panel/tab-panel.js +1 -1
- package/dist/collection/components/table/table.js +3 -3
- package/dist/collection/components/text-editor/link-menu/editor-link-menu.js +3 -3
- package/dist/collection/components/text-editor/prosemirror-adapter/prosemirror-adapter.js +1 -1
- package/dist/collection/components/text-editor/text-editor.js +1 -1
- package/dist/collection/components/tooltip/tooltip-content.js +1 -1
- package/dist/collection/components/tooltip/tooltip.js +2 -2
- package/dist/collection/interface.js +1 -0
- package/dist/collection/translations/da.js +14 -0
- package/dist/collection/translations/de.js +14 -0
- package/dist/collection/translations/en.js +14 -0
- package/dist/collection/translations/fi.js +14 -0
- package/dist/collection/translations/fr.js +14 -0
- package/dist/collection/translations/nl.js +14 -0
- package/dist/collection/translations/no.js +14 -0
- package/dist/collection/translations/sv.js +14 -0
- package/dist/collection/util/format-bytes.js +38 -0
- package/dist/esm/{file-metadata-D5joHaqk.js → file-metadata-BwF9vTXN.js} +1 -1
- package/dist/esm/{files-CkgibGPZ.js → files-P324wLau.js} +1 -1
- package/dist/esm/{get-icon-props-COzG_Mhw.js → get-icon-props-CgNJbSP4.js} +1 -1
- package/dist/esm/index-CJ0GYrWG.js +20341 -0
- package/dist/esm/lime-elements.js +1 -1
- package/dist/esm/limel-action-bar-item_2.entry.js +1 -1
- package/dist/esm/limel-action-bar_2.entry.js +4 -4
- package/dist/esm/limel-ai-avatar.entry.js +1 -1
- package/dist/esm/limel-breadcrumbs_7.entry.js +7 -7
- package/dist/esm/limel-button.entry.js +1 -1
- package/dist/esm/limel-callout.entry.js +1 -1
- package/dist/esm/limel-card.entry.js +1 -1
- package/dist/esm/limel-chart.entry.js +1 -1
- package/dist/esm/limel-chip_2.entry.js +2 -2
- package/dist/esm/limel-code-editor.entry.js +1 -1
- package/dist/esm/limel-collapsible-section.entry.js +3 -3
- package/dist/esm/limel-drag-handle.entry.js +1 -1
- package/dist/esm/limel-dynamic-label.entry.js +1 -1
- package/dist/esm/limel-email-viewer.entry.js +349 -0
- package/dist/esm/limel-file-dropzone_2.entry.js +5 -5
- package/dist/esm/limel-file-viewer.entry.js +4948 -7
- package/dist/esm/limel-file.entry.js +4 -4
- package/dist/esm/limel-flatpickr-adapter.entry.js +1 -1
- package/dist/esm/limel-flex-container.entry.js +1 -1
- package/dist/esm/limel-form.entry.js +1 -1
- package/dist/esm/limel-grid.entry.js +1 -1
- package/dist/esm/limel-header.entry.js +2 -2
- package/dist/esm/limel-help-content.entry.js +1 -1
- package/dist/esm/limel-help.entry.js +2 -2
- package/dist/esm/limel-helper-line_2.entry.js +3 -3
- package/dist/esm/limel-icon-button.entry.js +2 -2
- package/dist/esm/limel-icon.entry.js +1 -1
- package/dist/esm/limel-info-tile.entry.js +2 -2
- package/dist/esm/limel-linear-progress.entry.js +1 -1
- package/dist/esm/limel-list-item.entry.js +4 -4
- package/dist/esm/limel-markdown.entry.js +3 -2
- package/dist/esm/limel-menu-item-meta.entry.js +1 -1
- package/dist/esm/limel-picker.entry.js +2 -2
- package/dist/esm/limel-popover_2.entry.js +2 -2
- package/dist/esm/limel-portal_3.entry.js +4 -4
- package/dist/esm/limel-profile-picture.entry.js +4 -4
- package/dist/esm/limel-progress-flow-item.entry.js +1 -1
- package/dist/esm/limel-progress-flow.entry.js +1 -1
- package/dist/esm/limel-prosemirror-adapter.entry.js +7 -6
- package/dist/esm/limel-radio-button-group.entry.js +1 -1
- package/dist/esm/limel-radio-button.entry.js +2 -2
- package/dist/esm/limel-select.entry.js +2 -2
- package/dist/esm/limel-shortcut.entry.js +1 -1
- package/dist/esm/limel-slider.entry.js +1 -1
- package/dist/esm/limel-snackbar.entry.js +3 -3
- package/dist/esm/limel-split-button.entry.js +2 -2
- package/dist/esm/limel-tab-bar.entry.js +3 -3
- package/dist/esm/limel-tab-panel.entry.js +1 -1
- package/dist/esm/limel-table.entry.js +4 -4
- package/dist/esm/limel-text-editor.entry.js +1 -1
- package/dist/esm/loader.js +1 -1
- package/dist/esm/{markdown-parser-DkmQCwAi.js → markdown-parser-DJi1w622.js} +20287 -40624
- package/dist/esm/{translations-6rJPElLH.js → translations-Cdx3I2X8.js} +112 -0
- package/dist/lime-elements/lime-elements.esm.js +1 -1
- package/dist/lime-elements/{p-927622ec.entry.js → p-00fdb26c.entry.js} +1 -1
- package/dist/lime-elements/{p-880b9683.entry.js → p-1244d687.entry.js} +2 -2
- package/dist/lime-elements/p-1d71e9c8.entry.js +1 -0
- package/dist/lime-elements/p-21aef7f4.entry.js +1 -0
- package/dist/lime-elements/{p-68f49d6f.entry.js → p-278356b3.entry.js} +1 -1
- package/dist/lime-elements/{p-a6fae24d.entry.js → p-28dae22e.entry.js} +1 -1
- package/dist/lime-elements/{p-6951136b.entry.js → p-2d31cf0d.entry.js} +1 -1
- package/dist/lime-elements/{p-6e3666e5.entry.js → p-2ef38dac.entry.js} +1 -1
- package/dist/lime-elements/{p-dd36d57b.entry.js → p-2f788e92.entry.js} +7 -7
- package/dist/lime-elements/p-2kcqdtMr.js +1 -0
- package/dist/lime-elements/{p-11f716f5.entry.js → p-3000785f.entry.js} +1 -1
- package/dist/lime-elements/{p-2a5b259e.entry.js → p-3130d348.entry.js} +1 -1
- package/dist/lime-elements/{p-feeae1e4.entry.js → p-38097fa1.entry.js} +1 -1
- package/dist/lime-elements/p-3b299849.entry.js +1 -0
- package/dist/lime-elements/{p-00859fac.entry.js → p-3e6ce4e1.entry.js} +2 -2
- package/dist/lime-elements/{p-23f7956e.entry.js → p-3ec31835.entry.js} +1 -1
- package/dist/lime-elements/{p-52edfe86.entry.js → p-44396c0d.entry.js} +1 -1
- package/dist/lime-elements/{p-ec42a4aa.entry.js → p-494f880b.entry.js} +1 -1
- package/dist/lime-elements/{p-d1fa6da3.entry.js → p-4f6e3057.entry.js} +3 -3
- package/dist/lime-elements/{p-e6d74062.entry.js → p-504ae59f.entry.js} +1 -1
- package/dist/lime-elements/{p-7d5bd4a2.entry.js → p-51565372.entry.js} +1 -1
- package/dist/lime-elements/{p-5ddeb498.entry.js → p-52098c47.entry.js} +1 -1
- package/dist/lime-elements/{p-d2123236.entry.js → p-521a0204.entry.js} +1 -1
- package/dist/lime-elements/{p-38fb97fe.entry.js → p-54d85ae4.entry.js} +1 -1
- package/dist/lime-elements/{p-88f503eb.entry.js → p-59a522ee.entry.js} +1 -1
- package/dist/lime-elements/{p-2c1538f0.entry.js → p-5af72e1b.entry.js} +1 -1
- package/dist/lime-elements/p-64cc5094.entry.js +1 -0
- package/dist/lime-elements/{p-045c6027.entry.js → p-67a2c7f5.entry.js} +1 -1
- package/dist/lime-elements/{p-8db6b7d9.entry.js → p-68b1605f.entry.js} +1 -1
- package/dist/lime-elements/{p-7457bc07.entry.js → p-770981e6.entry.js} +1 -1
- package/dist/lime-elements/{p-9ea564fe.entry.js → p-7ed97446.entry.js} +1 -1
- package/dist/lime-elements/{p-cbb7d624.entry.js → p-8053727c.entry.js} +1 -1
- package/dist/lime-elements/{p-fc3209db.entry.js → p-80f9f2d3.entry.js} +1 -1
- package/dist/lime-elements/{p-34ef71f2.entry.js → p-95fb9ef8.entry.js} +1 -1
- package/dist/lime-elements/{p-6275668f.entry.js → p-961dff13.entry.js} +1 -1
- package/dist/lime-elements/{p-abef62d7.entry.js → p-97952658.entry.js} +1 -1
- package/dist/lime-elements/{p-d6270e4a.entry.js → p-982b9f50.entry.js} +1 -1
- package/dist/lime-elements/p-9c4156c6.entry.js +1 -0
- package/dist/lime-elements/p-BRCcjfVu.js +7 -0
- package/dist/lime-elements/{p-DSjFzQmB.js → p-CWuGCKo1.js} +1 -1
- package/dist/lime-elements/p-Cdx3I2X8.js +1 -0
- package/dist/lime-elements/{p-COzG_Mhw.js → p-CgNJbSP4.js} +1 -1
- package/dist/lime-elements/{p-BQY2kAWs.js → p-DlJXKdhK.js} +1 -1
- package/dist/lime-elements/{p-bbaf35ce.entry.js → p-a9f3f90c.entry.js} +1 -1
- package/dist/lime-elements/{p-c9e934af.entry.js → p-af5f2052.entry.js} +1 -1
- package/dist/lime-elements/{p-e11c9b40.entry.js → p-c2478225.entry.js} +1 -1
- package/dist/lime-elements/{p-6aebcf60.entry.js → p-c7f2e189.entry.js} +1 -1
- package/dist/lime-elements/{p-d424688d.entry.js → p-cb2cc243.entry.js} +1 -1
- package/dist/lime-elements/{p-c118eac0.entry.js → p-cce53162.entry.js} +1 -1
- package/dist/lime-elements/{p-6896d5c8.entry.js → p-d4fea438.entry.js} +1 -1
- package/dist/lime-elements/{p-534fdf9b.entry.js → p-df1fa930.entry.js} +1 -1
- package/dist/lime-elements/{p-52bb74b9.entry.js → p-e05ad4f8.entry.js} +1 -1
- package/dist/lime-elements/{p-303d01e5.entry.js → p-e6b0e0a2.entry.js} +1 -1
- package/dist/lime-elements/{p-80d35f8f.entry.js → p-e975cc29.entry.js} +1 -1
- package/dist/lime-elements/{p-9ed578ec.entry.js → p-f532b60f.entry.js} +1 -1
- package/dist/lime-elements/{p-4be18a57.entry.js → p-ff845f5c.entry.js} +1 -1
- package/dist/types/components/email-viewer/email-loader.d.ts +19 -0
- package/dist/types/components/email-viewer/email-viewer.d.ts +69 -0
- package/dist/types/components/email-viewer/email-viewer.types.d.ts +70 -0
- package/dist/types/components/email-viewer/remote-images.d.ts +22 -0
- package/dist/types/components/email-viewer/sanitize-email-html.d.ts +14 -0
- package/dist/types/components/email-viewer/split-email-address-list.d.ts +25 -0
- package/dist/types/components/file-viewer/file-viewer.d.ts +9 -0
- package/dist/types/components/file-viewer/file-viewer.types.d.ts +1 -1
- package/dist/types/components.d.ts +116 -0
- package/dist/types/interface.d.ts +1 -0
- package/dist/types/translations/da.d.ts +10 -0
- package/dist/types/translations/de.d.ts +10 -0
- package/dist/types/translations/en.d.ts +10 -0
- package/dist/types/translations/fi.d.ts +10 -0
- package/dist/types/translations/fr.d.ts +10 -0
- package/dist/types/translations/nl.d.ts +10 -0
- package/dist/types/translations/no.d.ts +10 -0
- package/dist/types/translations/sv.d.ts +10 -0
- package/dist/types/util/format-bytes.d.ts +21 -0
- package/package.json +4 -5
- package/dist/lime-elements/p-017dd326.entry.js +0 -1
- package/dist/lime-elements/p-4beeec39.entry.js +0 -1
- package/dist/lime-elements/p-55596d9a.entry.js +0 -1
- package/dist/lime-elements/p-6rJPElLH.js +0 -1
- package/dist/lime-elements/p-Df0HAtSs.js +0 -7
- package/dist/lime-elements/p-f395fbe3.entry.js +0 -1
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { h, Host, } from "@stencil/core";
|
|
2
|
+
import translate from "../../global/translations";
|
|
3
|
+
import { applyRemoteImagesPolicy, containsRemoteImages } from "./remote-images";
|
|
4
|
+
import { splitEmailAddressList } from "./split-email-address-list";
|
|
5
|
+
import { formatBytes } from "../../util/format-bytes";
|
|
6
|
+
/**
|
|
7
|
+
* This is a private component, used to render `.eml` files inside
|
|
8
|
+
* `limel-file-viewer`.
|
|
9
|
+
*
|
|
10
|
+
* :::note
|
|
11
|
+
* If `bodyHtml` is provided, it will be rendered using `innerHTML`.
|
|
12
|
+
* Consumers should pre-sanitize `bodyHtml` before passing it to the component.
|
|
13
|
+
* :::
|
|
14
|
+
*
|
|
15
|
+
* @exampleComponent limel-example-email-viewer-plain-text
|
|
16
|
+
* @exampleComponent limel-example-email-viewer-inline-image
|
|
17
|
+
* @exampleComponent limel-example-email-viewer-remote-image-policy
|
|
18
|
+
*
|
|
19
|
+
* @private
|
|
20
|
+
*/
|
|
21
|
+
export class EmailViewer {
|
|
22
|
+
constructor() {
|
|
23
|
+
/**
|
|
24
|
+
* Defines the localization for translations.
|
|
25
|
+
*/
|
|
26
|
+
this.language = 'en';
|
|
27
|
+
this.allowRemoteImagesState = false;
|
|
28
|
+
this.renderAttachment = (attachment, index) => {
|
|
29
|
+
var _a, _b;
|
|
30
|
+
const filename = ((_a = attachment.filename) === null || _a === void 0 ? void 0 : _a.trim()) ||
|
|
31
|
+
this.getTranslation('file-viewer.email.attachment.unnamed');
|
|
32
|
+
const mimeType = ((_b = attachment.mimeType) === null || _b === void 0 ? void 0 : _b.trim()) || '';
|
|
33
|
+
return (h("li", { key: `attachment-${index}` }, h("span", { class: "attachment-filename" }, filename), h("span", { class: "attachment-mime-type" }, " ", mimeType), this.renderSizeBadge(attachment.size)));
|
|
34
|
+
};
|
|
35
|
+
this.renderSizeBadge = (size) => {
|
|
36
|
+
if (typeof size !== 'number') {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
return (h("limel-badge", { class: "attachment-size", label: formatBytes(size) }));
|
|
40
|
+
};
|
|
41
|
+
this.onEnableRemoteImagesClick = (event) => {
|
|
42
|
+
var _a;
|
|
43
|
+
(_a = event === null || event === void 0 ? void 0 : event.stopPropagation) === null || _a === void 0 ? void 0 : _a.call(event);
|
|
44
|
+
this.enableRemoteImages();
|
|
45
|
+
};
|
|
46
|
+
this.enableRemoteImages = () => {
|
|
47
|
+
if (this.allowRemoteImages !== undefined) {
|
|
48
|
+
this.allowRemoteImagesChange.emit(true);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
this.allowRemoteImagesState = true;
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
resetAllowRemoteImages(newEmail, oldEmail) {
|
|
55
|
+
if (!newEmail) {
|
|
56
|
+
this.allowRemoteImagesState = false;
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (newEmail.from !== (oldEmail === null || oldEmail === void 0 ? void 0 : oldEmail.from)) {
|
|
60
|
+
this.allowRemoteImagesState = false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
render() {
|
|
64
|
+
return (h(Host, { key: 'a004af12989a477f4a9404ee86e0429bc5c2d427' }, h("div", { key: 'fc3bdeb81d823ff1cd39b4f6d52b4a88d9326392', class: "email", part: "email" }, this.renderHeaders(), this.renderRemoteImageBanner(), h("section", { key: '6fdd1cfe50cc4b41f7e70413541dc5950d7c44fa' }, this.renderAttachments(), this.renderBody()))));
|
|
65
|
+
}
|
|
66
|
+
renderHeaders() {
|
|
67
|
+
const headerFields = [
|
|
68
|
+
'subject',
|
|
69
|
+
'from',
|
|
70
|
+
'to',
|
|
71
|
+
'cc',
|
|
72
|
+
'date',
|
|
73
|
+
];
|
|
74
|
+
return (h("div", { class: "email-headers", part: "email-headers" }, headerFields.map((type) => {
|
|
75
|
+
var _a;
|
|
76
|
+
return this.renderEmailHeader(type, this.getTranslation(`file-viewer.email.${type}`), (_a = this.email) === null || _a === void 0 ? void 0 : _a[type]);
|
|
77
|
+
})));
|
|
78
|
+
}
|
|
79
|
+
renderBody() {
|
|
80
|
+
return (this.renderBodyHtml() ||
|
|
81
|
+
this.renderBodyText() ||
|
|
82
|
+
this.renderFallbackUrl() || h("slot", { name: "fallback" }));
|
|
83
|
+
}
|
|
84
|
+
renderBodyHtml() {
|
|
85
|
+
var _a;
|
|
86
|
+
const bodyHtml = (_a = this.email) === null || _a === void 0 ? void 0 : _a.bodyHtml;
|
|
87
|
+
if (!bodyHtml) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const innerHtml = applyRemoteImagesPolicy(bodyHtml, this.getAllowRemoteImages());
|
|
91
|
+
return h("div", { class: "body", innerHTML: innerHtml, part: "email-body" });
|
|
92
|
+
}
|
|
93
|
+
renderBodyText() {
|
|
94
|
+
var _a;
|
|
95
|
+
const bodyText = (_a = this.email) === null || _a === void 0 ? void 0 : _a.bodyText;
|
|
96
|
+
if (!bodyText) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
return (h("pre", { class: "body plain-text", part: "email-body" }, bodyText));
|
|
100
|
+
}
|
|
101
|
+
renderFallbackUrl() {
|
|
102
|
+
if (!this.fallbackUrl) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
return (h("object", { data: this.fallbackUrl, type: "text/plain" }, h("slot", { name: "fallback" })));
|
|
106
|
+
}
|
|
107
|
+
renderEmailHeader(type, label, value) {
|
|
108
|
+
if (!value) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const values = this.getHeaderValues(type, value);
|
|
112
|
+
return (h("dl", { class: `headers ${type}` }, h("dt", null, label), values.map((headerValue, index) => (h("dd", { key: `${type}-${index}` }, headerValue)))));
|
|
113
|
+
}
|
|
114
|
+
getHeaderValues(type, value) {
|
|
115
|
+
if (type === 'to' || type === 'cc') {
|
|
116
|
+
return splitEmailAddressList(value);
|
|
117
|
+
}
|
|
118
|
+
return [value];
|
|
119
|
+
}
|
|
120
|
+
renderAttachments() {
|
|
121
|
+
var _a;
|
|
122
|
+
const attachments = (_a = this.email) === null || _a === void 0 ? void 0 : _a.attachments;
|
|
123
|
+
if (!(attachments === null || attachments === void 0 ? void 0 : attachments.length)) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const label = this.getTranslation('file-viewer.email.attachments');
|
|
127
|
+
return (h("div", { class: "attachments" }, h("span", null, label), h("ul", null, attachments.map((attachment, index) => this.renderAttachment(attachment, index)))));
|
|
128
|
+
}
|
|
129
|
+
getTranslation(key) {
|
|
130
|
+
return translate.get(key, this.language);
|
|
131
|
+
}
|
|
132
|
+
shouldShowRemoteImagesBanner() {
|
|
133
|
+
var _a;
|
|
134
|
+
const bodyHtml = (_a = this.email) === null || _a === void 0 ? void 0 : _a.bodyHtml;
|
|
135
|
+
if (!bodyHtml || this.getAllowRemoteImages()) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
return containsRemoteImages(bodyHtml);
|
|
139
|
+
}
|
|
140
|
+
renderRemoteImageBanner() {
|
|
141
|
+
if (!this.shouldShowRemoteImagesBanner()) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const icon = {
|
|
145
|
+
name: 'warning_shield',
|
|
146
|
+
color: 'rgb(var(--color-orange-default))',
|
|
147
|
+
};
|
|
148
|
+
const heading = this.getTranslation('file-viewer.email.remote-images.warning');
|
|
149
|
+
const description = this.getTranslation('file-viewer.email.remote-images.warning.description');
|
|
150
|
+
const buttonLabel = this.getTranslation('file-viewer.email.remote-images.load');
|
|
151
|
+
return (h("limel-collapsible-section", { header: heading, icon: icon, language: this.language }, h("button", { type: "button", class: "load-images", slot: "header", onClick: this.onEnableRemoteImagesClick }, buttonLabel), h("limel-markdown", { value: description })));
|
|
152
|
+
}
|
|
153
|
+
getAllowRemoteImages() {
|
|
154
|
+
var _a;
|
|
155
|
+
return (_a = this.allowRemoteImages) !== null && _a !== void 0 ? _a : this.allowRemoteImagesState;
|
|
156
|
+
}
|
|
157
|
+
static get is() { return "limel-email-viewer"; }
|
|
158
|
+
static get encapsulation() { return "shadow"; }
|
|
159
|
+
static get originalStyleUrls() {
|
|
160
|
+
return {
|
|
161
|
+
"$": ["email-viewer.scss"]
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
static get styleUrls() {
|
|
165
|
+
return {
|
|
166
|
+
"$": ["email-viewer.css"]
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
static get properties() {
|
|
170
|
+
return {
|
|
171
|
+
"email": {
|
|
172
|
+
"type": "unknown",
|
|
173
|
+
"mutable": false,
|
|
174
|
+
"complexType": {
|
|
175
|
+
"original": "Email",
|
|
176
|
+
"resolved": "Email",
|
|
177
|
+
"references": {
|
|
178
|
+
"Email": {
|
|
179
|
+
"location": "import",
|
|
180
|
+
"path": "./email-viewer.types",
|
|
181
|
+
"id": "src/components/email-viewer/email-viewer.types.ts::Email",
|
|
182
|
+
"referenceLocation": "Email"
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
"required": false,
|
|
187
|
+
"optional": true,
|
|
188
|
+
"docs": {
|
|
189
|
+
"tags": [],
|
|
190
|
+
"text": "The email message to display.\n\nIf `email.bodyHtml` is set directly, consumers must provide sanitized\nHTML."
|
|
191
|
+
},
|
|
192
|
+
"getter": false,
|
|
193
|
+
"setter": false
|
|
194
|
+
},
|
|
195
|
+
"fallbackUrl": {
|
|
196
|
+
"type": "string",
|
|
197
|
+
"mutable": false,
|
|
198
|
+
"complexType": {
|
|
199
|
+
"original": "string",
|
|
200
|
+
"resolved": "string",
|
|
201
|
+
"references": {}
|
|
202
|
+
},
|
|
203
|
+
"required": false,
|
|
204
|
+
"optional": true,
|
|
205
|
+
"docs": {
|
|
206
|
+
"tags": [],
|
|
207
|
+
"text": "Optional URL to render as a final fallback using an `<object type=\"text/plain\">`."
|
|
208
|
+
},
|
|
209
|
+
"getter": false,
|
|
210
|
+
"setter": false,
|
|
211
|
+
"reflect": true,
|
|
212
|
+
"attribute": "fallback-url"
|
|
213
|
+
},
|
|
214
|
+
"language": {
|
|
215
|
+
"type": "string",
|
|
216
|
+
"mutable": false,
|
|
217
|
+
"complexType": {
|
|
218
|
+
"original": "Languages",
|
|
219
|
+
"resolved": "\"da\" | \"de\" | \"en\" | \"fi\" | \"fr\" | \"nb\" | \"nl\" | \"no\" | \"sv\"",
|
|
220
|
+
"references": {
|
|
221
|
+
"Languages": {
|
|
222
|
+
"location": "import",
|
|
223
|
+
"path": "../date-picker/date.types",
|
|
224
|
+
"id": "src/components/date-picker/date.types.ts::Languages",
|
|
225
|
+
"referenceLocation": "Languages"
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
"required": false,
|
|
230
|
+
"optional": false,
|
|
231
|
+
"docs": {
|
|
232
|
+
"tags": [],
|
|
233
|
+
"text": "Defines the localization for translations."
|
|
234
|
+
},
|
|
235
|
+
"getter": false,
|
|
236
|
+
"setter": false,
|
|
237
|
+
"reflect": true,
|
|
238
|
+
"attribute": "language",
|
|
239
|
+
"defaultValue": "'en'"
|
|
240
|
+
},
|
|
241
|
+
"allowRemoteImages": {
|
|
242
|
+
"type": "boolean",
|
|
243
|
+
"mutable": false,
|
|
244
|
+
"complexType": {
|
|
245
|
+
"original": "boolean",
|
|
246
|
+
"resolved": "boolean",
|
|
247
|
+
"references": {}
|
|
248
|
+
},
|
|
249
|
+
"required": false,
|
|
250
|
+
"optional": true,
|
|
251
|
+
"docs": {
|
|
252
|
+
"tags": [],
|
|
253
|
+
"text": "Controls whether remote images (http/https) are loaded.\n\nIf omitted, the component treats this as a per-email setting.\nConsumers that want to remember the choice (per session/global) can\nprovide this prop and listen for `allowRemoteImagesChange`."
|
|
254
|
+
},
|
|
255
|
+
"getter": false,
|
|
256
|
+
"setter": false,
|
|
257
|
+
"reflect": false,
|
|
258
|
+
"attribute": "allow-remote-images"
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
static get states() {
|
|
263
|
+
return {
|
|
264
|
+
"allowRemoteImagesState": {}
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
static get events() {
|
|
268
|
+
return [{
|
|
269
|
+
"method": "allowRemoteImagesChange",
|
|
270
|
+
"name": "allowRemoteImagesChange",
|
|
271
|
+
"bubbles": true,
|
|
272
|
+
"cancelable": true,
|
|
273
|
+
"composed": true,
|
|
274
|
+
"docs": {
|
|
275
|
+
"tags": [{
|
|
276
|
+
"name": "internal",
|
|
277
|
+
"text": undefined
|
|
278
|
+
}],
|
|
279
|
+
"text": "Emitted when the user requests remote images to be loaded."
|
|
280
|
+
},
|
|
281
|
+
"complexType": {
|
|
282
|
+
"original": "boolean",
|
|
283
|
+
"resolved": "boolean",
|
|
284
|
+
"references": {}
|
|
285
|
+
}
|
|
286
|
+
}];
|
|
287
|
+
}
|
|
288
|
+
static get watchers() {
|
|
289
|
+
return [{
|
|
290
|
+
"propName": "email",
|
|
291
|
+
"methodName": "resetAllowRemoteImages"
|
|
292
|
+
}];
|
|
293
|
+
}
|
|
294
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks whether the given HTML contains any images with a `data-remote-src`
|
|
3
|
+
* attribute, indicating they reference external (remote) resources.
|
|
4
|
+
*
|
|
5
|
+
* @param html - The HTML string to inspect.
|
|
6
|
+
*/
|
|
7
|
+
export function containsRemoteImages(html) {
|
|
8
|
+
const parser = new DOMParser();
|
|
9
|
+
const document = parser.parseFromString(html, 'text/html');
|
|
10
|
+
return document.querySelector('img[data-remote-src]') !== null;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* If `allowRemoteImages` is `true`, replaces the `src` attribute of every
|
|
14
|
+
* `<img data-remote-src="...">` element with the value of `data-remote-src`
|
|
15
|
+
* (provided it points to an http(s) URL) and removes the data attribute.
|
|
16
|
+
*
|
|
17
|
+
* When `allowRemoteImages` is `false` the HTML is returned unchanged.
|
|
18
|
+
*
|
|
19
|
+
* Returns an HTML fragment that is safe to assign to `innerHTML` on a regular
|
|
20
|
+
* container element (not a full `<html>/<head>/<body>` document string).
|
|
21
|
+
*
|
|
22
|
+
* @param html - The HTML string to process.
|
|
23
|
+
* @param allowRemoteImages - Whether to restore remote image sources.
|
|
24
|
+
*/
|
|
25
|
+
export function applyRemoteImagesPolicy(html, allowRemoteImages) {
|
|
26
|
+
if (!allowRemoteImages) {
|
|
27
|
+
return html;
|
|
28
|
+
}
|
|
29
|
+
const parser = new DOMParser();
|
|
30
|
+
const document = parser.parseFromString(html, 'text/html');
|
|
31
|
+
const images = document.querySelectorAll('img[data-remote-src]');
|
|
32
|
+
for (const image of images) {
|
|
33
|
+
const remoteSrc = image.dataset.remoteSrc;
|
|
34
|
+
if (!remoteSrc || !isAllowedRemoteImageUrl(remoteSrc)) {
|
|
35
|
+
delete image.dataset.remoteSrc;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
image.setAttribute('src', remoteSrc);
|
|
39
|
+
delete image.dataset.remoteSrc;
|
|
40
|
+
}
|
|
41
|
+
const headStyles = [...document.head.querySelectorAll('style')]
|
|
42
|
+
.map((style) => style.outerHTML)
|
|
43
|
+
.join('');
|
|
44
|
+
return `${headStyles}${document.body.innerHTML}`;
|
|
45
|
+
}
|
|
46
|
+
function isAllowedRemoteImageUrl(url) {
|
|
47
|
+
const trimmed = url.trim();
|
|
48
|
+
const lower = trimmed.toLowerCase();
|
|
49
|
+
return lower.startsWith('https://') || lower.startsWith('http://');
|
|
50
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { unified } from "unified";
|
|
2
|
+
import rehypeParse from "rehype-parse";
|
|
3
|
+
import rehypeSanitize from "rehype-sanitize";
|
|
4
|
+
import rehypeStringify from "rehype-stringify";
|
|
5
|
+
import { visit } from "unist-util-visit";
|
|
6
|
+
import { defaultSchema } from "rehype-sanitize";
|
|
7
|
+
const allowedMimeTypes = new Set([
|
|
8
|
+
'image/png',
|
|
9
|
+
'image/jpeg',
|
|
10
|
+
'image/jpg',
|
|
11
|
+
'image/gif',
|
|
12
|
+
'image/webp',
|
|
13
|
+
'image/bmp',
|
|
14
|
+
'image/x-icon',
|
|
15
|
+
'image/vnd.microsoft.icon',
|
|
16
|
+
]);
|
|
17
|
+
/**
|
|
18
|
+
* Sanitizes email HTML to prevent XSS and other security issues while
|
|
19
|
+
* preserving the original visual appearance (layout, colors, fonts, etc.).
|
|
20
|
+
*
|
|
21
|
+
* This differs from the markdown sanitizer (`sanitizeHTML`) in that:
|
|
22
|
+
* - **All inline CSS is preserved** (no style property filtering).
|
|
23
|
+
* - Dangerous CSS properties like `behavior`, `expression`, `-moz-binding` are removed.
|
|
24
|
+
* - Standard dangerous tags/attributes are blocked (script, event handlers, javascript: URLs).
|
|
25
|
+
*
|
|
26
|
+
* @param html - The HTML string to sanitize (typically an email body).
|
|
27
|
+
* @returns The sanitized HTML string.
|
|
28
|
+
*/
|
|
29
|
+
export async function sanitizeEmailHTML(html) {
|
|
30
|
+
const file = await unified()
|
|
31
|
+
.use(rehypeParse)
|
|
32
|
+
.use(rehypeSanitize, getEmailSanitizationSchema())
|
|
33
|
+
.use(() => {
|
|
34
|
+
return (tree) => {
|
|
35
|
+
visit(tree, 'element', (node) => {
|
|
36
|
+
sanitizeDangerousCss(node);
|
|
37
|
+
sanitizeDangerousUrls(node);
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
})
|
|
41
|
+
.use(rehypeStringify)
|
|
42
|
+
.process(html);
|
|
43
|
+
return file.toString();
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Returns a rehype-sanitize schema that allows all standard HTML elements
|
|
47
|
+
* and attributes needed for rich email rendering, including `style`.
|
|
48
|
+
*/
|
|
49
|
+
function getEmailSanitizationSchema() {
|
|
50
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
51
|
+
const defaultSrcProtocols = (_b = (_a = defaultSchema.protocols) === null || _a === void 0 ? void 0 : _a.src) !== null && _b !== void 0 ? _b : [];
|
|
52
|
+
const schema = Object.assign(Object.assign({}, defaultSchema), { protocols: Object.assign(Object.assign({}, defaultSchema.protocols), {
|
|
53
|
+
// Email bodies often embed images as data URLs. We allow `data:` here,
|
|
54
|
+
// but still validate the MIME type in `sanitizeDangerousUrls`.
|
|
55
|
+
src: [...defaultSrcProtocols, 'http', 'https', 'data']
|
|
56
|
+
}), attributes: Object.assign(Object.assign({}, defaultSchema.attributes), { table: [
|
|
57
|
+
...((_c = defaultSchema.attributes.table) !== null && _c !== void 0 ? _c : []),
|
|
58
|
+
// Email HTML often relies on these legacy attributes.
|
|
59
|
+
'cellpadding',
|
|
60
|
+
'cellPadding',
|
|
61
|
+
'cellspacing',
|
|
62
|
+
'cellSpacing',
|
|
63
|
+
'border',
|
|
64
|
+
'dir',
|
|
65
|
+
'width',
|
|
66
|
+
'height',
|
|
67
|
+
], colgroup: [...((_d = defaultSchema.attributes.colgroup) !== null && _d !== void 0 ? _d : []), 'span'], col: [...((_e = defaultSchema.attributes.col) !== null && _e !== void 0 ? _e : []), 'width', 'span'], '*': [
|
|
68
|
+
...((_f = defaultSchema.attributes['*']) !== null && _f !== void 0 ? _f : []),
|
|
69
|
+
'style', // Allow inline styles on all elements
|
|
70
|
+
// NOTE: rehype/parse maps `class` to the HAST property name
|
|
71
|
+
// `className`, which is what rehype-sanitize checks.
|
|
72
|
+
'className',
|
|
73
|
+
'class', // Keep for completeness
|
|
74
|
+
'id', // Allow id for anchors/internal navigation
|
|
75
|
+
// Used to store remote image URLs without loading them immediately.
|
|
76
|
+
'data-remote-src',
|
|
77
|
+
'dataRemoteSrc',
|
|
78
|
+
] }),
|
|
79
|
+
// Allow common email-specific tags
|
|
80
|
+
tagNames: [
|
|
81
|
+
...((_g = defaultSchema.tagNames) !== null && _g !== void 0 ? _g : []),
|
|
82
|
+
// Allow full-document HTML emails. These tags won't render as text,
|
|
83
|
+
// but keeping them avoids their contents being surfaced as plain text.
|
|
84
|
+
'html',
|
|
85
|
+
'head',
|
|
86
|
+
'body',
|
|
87
|
+
'title',
|
|
88
|
+
'meta',
|
|
89
|
+
// Preserve embedded email CSS.
|
|
90
|
+
'style',
|
|
91
|
+
// Preserve table column sizing when using <colgroup>/<col>.
|
|
92
|
+
'colgroup',
|
|
93
|
+
'col',
|
|
94
|
+
'center', // Deprecated but widely used in email
|
|
95
|
+
'font', // Deprecated but widely used in email
|
|
96
|
+
] });
|
|
97
|
+
return schema;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Validates and normalizes potentially dangerous URL attributes.
|
|
101
|
+
*
|
|
102
|
+
* Currently only handles `<img src>` and allows safe embedded `data:image/*`.
|
|
103
|
+
*
|
|
104
|
+
* @param node - The HTML element node to sanitize.
|
|
105
|
+
*/
|
|
106
|
+
function sanitizeDangerousUrls(node) {
|
|
107
|
+
var _a;
|
|
108
|
+
if (!(node === null || node === void 0 ? void 0 : node.tagName) || node.tagName !== 'img') {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const src = (_a = node.properties) === null || _a === void 0 ? void 0 : _a.src;
|
|
112
|
+
if (typeof src !== 'string') {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const safeSrc = getSafeEmailImageSrc(src);
|
|
116
|
+
if (safeSrc) {
|
|
117
|
+
node.properties.src = safeSrc;
|
|
118
|
+
delete node.properties.dataRemoteSrc;
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const remoteSrc = getRemoteEmailImageSrc(src);
|
|
122
|
+
if (remoteSrc) {
|
|
123
|
+
// Avoid loading remote images by default. Store the URL so the viewer can
|
|
124
|
+
// opt-in and restore it later.
|
|
125
|
+
node.properties.dataRemoteSrc = remoteSrc;
|
|
126
|
+
delete node.properties.src;
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
// Keep the <img> but strip the source.
|
|
130
|
+
delete node.properties.src;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Returns a safe image `src` for email rendering.
|
|
134
|
+
*
|
|
135
|
+
* Only permits embedded `data:` URLs with an allow-listed image MIME type.
|
|
136
|
+
*
|
|
137
|
+
* @param src - The raw `src` attribute value.
|
|
138
|
+
* @returns A safe `src` to keep, or `undefined` to strip it.
|
|
139
|
+
*/
|
|
140
|
+
function getSafeEmailImageSrc(src) {
|
|
141
|
+
const trimmedSrc = src.trim();
|
|
142
|
+
if (!trimmedSrc) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
// Only allow embedded images. Loading remote images has privacy implications
|
|
146
|
+
// (tracking pixels) and may leak network details.
|
|
147
|
+
if (!trimmedSrc.toLowerCase().startsWith('data:')) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const mimeType = getDataUrlMimeType(trimmedSrc);
|
|
151
|
+
if (!mimeType) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (!allowedMimeTypes.has(mimeType)) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
return trimmedSrc;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Returns a safe remote image URL to keep for later opt-in loading.
|
|
161
|
+
*
|
|
162
|
+
* @param src - The raw `src` attribute value.
|
|
163
|
+
* @returns The remote URL if it is http/https.
|
|
164
|
+
*/
|
|
165
|
+
function getRemoteEmailImageSrc(src) {
|
|
166
|
+
const trimmedSrc = src.trim();
|
|
167
|
+
const lower = trimmedSrc.toLowerCase();
|
|
168
|
+
if (lower.startsWith('http://') || lower.startsWith('https://')) {
|
|
169
|
+
return trimmedSrc;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Extracts the MIME type from a `data:` URL.
|
|
174
|
+
*
|
|
175
|
+
* @param dataUrl - A `data:` URL string.
|
|
176
|
+
* @returns The MIME type if present.
|
|
177
|
+
*/
|
|
178
|
+
function getDataUrlMimeType(dataUrl) {
|
|
179
|
+
var _a;
|
|
180
|
+
// data:[<mime type>][;charset=<charset>][;base64],<data>
|
|
181
|
+
const match = /^data:([^;,]+)(?:;charset=[^;,]+)?(?:;base64)?,/i.exec(dataUrl);
|
|
182
|
+
const mimeType = (_a = match === null || match === void 0 ? void 0 : match[1]) === null || _a === void 0 ? void 0 : _a.toLowerCase();
|
|
183
|
+
return mimeType || undefined;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Removes dangerous constructs from inline CSS (style attributes and `<style>` tags).
|
|
187
|
+
*
|
|
188
|
+
* @param node - The HTML element node to sanitize.
|
|
189
|
+
*/
|
|
190
|
+
function sanitizeDangerousCss(node) {
|
|
191
|
+
var _a;
|
|
192
|
+
if (!(node === null || node === void 0 ? void 0 : node.tagName)) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (((_a = node.properties) === null || _a === void 0 ? void 0 : _a.style) && typeof node.properties.style === 'string') {
|
|
196
|
+
node.properties.style = stripDangerousCss(node.properties.style);
|
|
197
|
+
}
|
|
198
|
+
if (node.tagName === 'style' && Array.isArray(node.children)) {
|
|
199
|
+
for (const child of node.children) {
|
|
200
|
+
if ((child === null || child === void 0 ? void 0 : child.type) === 'text' && typeof child.value === 'string') {
|
|
201
|
+
child.value = stripDangerousCss(child.value);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Removes common script-capable CSS constructs.
|
|
208
|
+
*
|
|
209
|
+
* @param css - A CSS string from a style attribute or `<style>` tag.
|
|
210
|
+
* @returns The CSS with dangerous constructs removed.
|
|
211
|
+
*/
|
|
212
|
+
function stripDangerousCss(css) {
|
|
213
|
+
// Minimal defensive filtering. We preserve styling for fidelity, but drop
|
|
214
|
+
// well-known script-capable constructs (mostly relevant in legacy engines).
|
|
215
|
+
const dangerousPatterns = [
|
|
216
|
+
/behavior\s*:/gi,
|
|
217
|
+
/expression\s*\(/gi,
|
|
218
|
+
/-moz-binding\s*:/gi,
|
|
219
|
+
/vbscript\s*:/gi,
|
|
220
|
+
/javascript\s*:/gi,
|
|
221
|
+
];
|
|
222
|
+
const importPattern = /@import\s+(?:url\([^)]{0,2000}\)|[^;]{0,2000});?/gi;
|
|
223
|
+
const urlPattern = /url\(\s{0,50}(?:"([^"]{0,2000})"|'([^']{0,2000})'|([^"')\s]{1,2000}))\s{0,50}\)/gi;
|
|
224
|
+
const isSafeCssUrl = (value) => {
|
|
225
|
+
const normalized = value.trim().toLowerCase();
|
|
226
|
+
if (normalized.startsWith('data:') ||
|
|
227
|
+
normalized.startsWith('cid:') ||
|
|
228
|
+
normalized.startsWith('#')) {
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
return (!normalized.startsWith('//') &&
|
|
232
|
+
!/^[a-z][a-z0-9+.-]*:/i.test(normalized));
|
|
233
|
+
};
|
|
234
|
+
let cleaned = css;
|
|
235
|
+
for (const pattern of dangerousPatterns) {
|
|
236
|
+
cleaned = cleaned.replaceAll(pattern, '');
|
|
237
|
+
}
|
|
238
|
+
cleaned = cleaned.replaceAll(importPattern, '');
|
|
239
|
+
cleaned = cleaned.replaceAll(urlPattern, (match, first, second, third) => {
|
|
240
|
+
var _a, _b;
|
|
241
|
+
const value = ((_b = (_a = first !== null && first !== void 0 ? first : second) !== null && _a !== void 0 ? _a : third) !== null && _b !== void 0 ? _b : '').trim();
|
|
242
|
+
return isSafeCssUrl(value) ? match : 'url("")';
|
|
243
|
+
});
|
|
244
|
+
return cleaned;
|
|
245
|
+
}
|