@capillarytech/creatives-library 8.0.250-alpha.0 → 8.0.250-alpha.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
CHANGED
|
@@ -15,13 +15,13 @@
|
|
|
15
15
|
* - Filters dangerous protocols (javascript:, data:, vbscript:)
|
|
16
16
|
* - Variant-aware sanitization (EMAIL vs INAPP with different security levels)
|
|
17
17
|
* - Iframe sandbox uses "allow-scripts" only (no allow-same-origin for security)
|
|
18
|
-
* - Content delivered via
|
|
18
|
+
* - Content delivered via srcdoc attribute for CSP compliance (replaces blob URLs)
|
|
19
19
|
*
|
|
20
20
|
* Previous Security Issues (FIXED):
|
|
21
21
|
* - Removed insecure manual sanitizeJavaScript function that only escaped </script> tags
|
|
22
22
|
* - Replaced with robust DOMPurify-based sanitization for full XSS protection
|
|
23
23
|
* - Removed "allow-same-origin" from iframe sandbox to prevent same-origin access
|
|
24
|
-
* -
|
|
24
|
+
* - Replaced blob URLs with srcdoc to comply with CSP frame-src directives
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
27
|
import React, { useMemo, useEffect, useRef } from 'react';
|
|
@@ -103,34 +103,25 @@ const PreviewPane = ({
|
|
|
103
103
|
return sanitizationResult.sanitized;
|
|
104
104
|
}, [contentValue, variant]);
|
|
105
105
|
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
URL.revokeObjectURL(blobUrlRef.current);
|
|
113
|
-
blobUrlRef.current = null;
|
|
106
|
+
// Use srcdoc instead of blob URLs for CSP compliance
|
|
107
|
+
// srcdoc allows embedding HTML directly in iframe without violating frame-src CSP directives
|
|
108
|
+
// This is more secure and CSP-compliant than blob URLs
|
|
109
|
+
const iframeContent = useMemo(() => {
|
|
110
|
+
if (!combinedContent) {
|
|
111
|
+
return null;
|
|
114
112
|
}
|
|
115
113
|
|
|
116
|
-
//
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
114
|
+
// srcdoc has a size limit (~2MB in some browsers), but for HTML preview content this should be sufficient
|
|
115
|
+
// If content is too large, we'll fall back to about:blank and log a warning
|
|
116
|
+
const MAX_SRCDOC_SIZE = 2000000; // 2MB limit
|
|
117
|
+
if (combinedContent.length > MAX_SRCDOC_SIZE) {
|
|
118
|
+
console.warn('PreviewPane: Content too large for srcdoc, using about:blank');
|
|
119
|
+
return null;
|
|
121
120
|
}
|
|
122
121
|
|
|
123
|
-
return
|
|
122
|
+
return combinedContent;
|
|
124
123
|
}, [combinedContent]);
|
|
125
124
|
|
|
126
|
-
// Cleanup blob URL on unmount
|
|
127
|
-
useEffect(() => () => {
|
|
128
|
-
if (blobUrlRef.current) {
|
|
129
|
-
URL.revokeObjectURL(blobUrlRef.current);
|
|
130
|
-
blobUrlRef.current = null;
|
|
131
|
-
}
|
|
132
|
-
}, []);
|
|
133
|
-
|
|
134
125
|
// Generate CSS classes based on view mode
|
|
135
126
|
const getIframeClasses = () => {
|
|
136
127
|
const baseClass = 'preview-pane__iframe';
|
|
@@ -159,7 +150,8 @@ const PreviewPane = ({
|
|
|
159
150
|
|
|
160
151
|
const renderDeviceFrame = () => (
|
|
161
152
|
<iframe
|
|
162
|
-
src={
|
|
153
|
+
src={iframeContent ? undefined : 'about:blank'}
|
|
154
|
+
srcDoc={iframeContent || undefined}
|
|
163
155
|
title={intl.formatMessage(messages.htmlPreview)}
|
|
164
156
|
className={getIframeClasses()}
|
|
165
157
|
style={getPreviewStyles()}
|
|
@@ -210,7 +202,7 @@ const PreviewPane = ({
|
|
|
210
202
|
</CapRow>
|
|
211
203
|
|
|
212
204
|
<CapRow className="preview-pane__content">
|
|
213
|
-
{
|
|
205
|
+
{iframeContent ? renderDeviceFrame() : renderEmptyState()}
|
|
214
206
|
</CapRow>
|
|
215
207
|
</div>
|
|
216
208
|
);
|
|
@@ -14,7 +14,6 @@ import { FormattedMessage, injectIntl, intlShape } from 'react-intl';
|
|
|
14
14
|
// Cap UI Library
|
|
15
15
|
import CapRow from '@capillarytech/cap-ui-library/CapRow';
|
|
16
16
|
import CapIcon from '@capillarytech/cap-ui-library/CapIcon';
|
|
17
|
-
import CapTab from '@capillarytech/cap-ui-library/CapTab';
|
|
18
17
|
import CapTooltip from '@capillarytech/cap-ui-library/CapTooltip';
|
|
19
18
|
|
|
20
19
|
// Messages
|
|
@@ -23,6 +22,7 @@ import { BLOCKING_ERROR_RULE_IDS } from '../../constants';
|
|
|
23
22
|
|
|
24
23
|
// Styles
|
|
25
24
|
import './_validationTabs.scss';
|
|
25
|
+
import {StyledCapTab} from '../../../../v2Containers/MobilePushNew/style';
|
|
26
26
|
|
|
27
27
|
// Constants for issue sources
|
|
28
28
|
const ISSUE_SOURCES = {
|
|
@@ -346,11 +346,11 @@ const ValidationTabs = ({
|
|
|
346
346
|
return (
|
|
347
347
|
<div className={`validation-tabs ${className || ''}`}>
|
|
348
348
|
<CapRow className="validation-tabs__header">
|
|
349
|
-
<
|
|
349
|
+
<StyledCapTab
|
|
350
|
+
className="validation-tabs__tabs"
|
|
350
351
|
activeKey={activeKey || (tabPanes[0]?.key)}
|
|
351
352
|
onChange={setActiveKey}
|
|
352
353
|
panes={tabPanes}
|
|
353
|
-
className="validation-tabs__tabs"
|
|
354
354
|
/>
|
|
355
355
|
<CapRow className="validation-tabs__actions">
|
|
356
356
|
<CapTooltip title={intl.formatMessage(messages.closePanel)}>
|
|
@@ -883,8 +883,18 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
|
|
|
883
883
|
const msgString = JSON.stringify(msg);
|
|
884
884
|
const langId = formData[this.state.currentTab - 1].activeTab;
|
|
885
885
|
const langIndex = formData[this.state.currentTab - 1].selectedLanguages.indexOf(langId);
|
|
886
|
-
const
|
|
887
|
-
|
|
886
|
+
const elementToSelect = document.getElementById(`edmeditor${(langIndex + 1) > 1 ? (langIndex + 1) : ''}`);
|
|
887
|
+
if (elementToSelect) {
|
|
888
|
+
try {
|
|
889
|
+
const win = elementToSelect.contentWindow;
|
|
890
|
+
if (win) {
|
|
891
|
+
win.postMessage(msgString, '*');
|
|
892
|
+
}
|
|
893
|
+
} catch (error) {
|
|
894
|
+
// Handle cross-origin frame access errors
|
|
895
|
+
console.warn('Failed to access iframe contentWindow (cross-origin restriction):', error);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
888
898
|
} else if (data === "unsubscribe" && this.state.editorInstanse) {
|
|
889
899
|
const anchor = `<a href='{{${data}}}'>${data}</a>`;
|
|
890
900
|
this.state.editorInstanse.insertHtml(`${anchor}`);
|