diff --git a/backend/src/Squidex.Web/Pipeline/FileCallbackResultExecutor.cs b/backend/src/Squidex.Web/Pipeline/FileCallbackResultExecutor.cs index 9333aef27..01a59cdc5 100644 --- a/backend/src/Squidex.Web/Pipeline/FileCallbackResultExecutor.cs +++ b/backend/src/Squidex.Web/Pipeline/FileCallbackResultExecutor.cs @@ -22,6 +22,11 @@ public sealed class FileCallbackResultExecutor : FileResultExecutorBase public async Task ExecuteAsync(ActionContext context, FileCallbackResult result) { + var response = context.HttpContext.Response; + + // Always block execution of scripts and inline scripts for file downloads. + response.Headers[HeaderNames.ContentSecurityPolicy] = "script-src 'none'"; + try { var (range, _, serveBody) = SetHeadersAndLog(context, result, result.FileSize, result.FileSize != null); @@ -32,14 +37,15 @@ public sealed class FileCallbackResultExecutor : FileResultExecutorBase headerValue.SetHttpFileName(result.FileDownloadName); - context.HttpContext.Response.Headers[HeaderNames.ContentDisposition] = headerValue.ToString(); + // This produces a nice file name without downloading it, but executes the file and shows images directly. + response.Headers[HeaderNames.ContentDisposition] = headerValue.ToString(); } if (serveBody) { var bytesRange = new BytesRange(range?.From, range?.To); - await result.Callback(context.HttpContext.Response.Body, bytesRange, context.HttpContext.RequestAborted); + await result.Callback(response.Body, bytesRange, context.HttpContext.RequestAborted); } } catch (OperationCanceledException) @@ -50,8 +56,8 @@ public sealed class FileCallbackResultExecutor : FileResultExecutorBase { if (!context.HttpContext.Response.HasStarted && result.ErrorAs404) { - context.HttpContext.Response.Headers.Clear(); - context.HttpContext.Response.StatusCode = 404; + response.Headers.Clear(); + response.StatusCode = 404; Logger.LogCritical(new EventId(99), e, "Failed to send result."); } @@ -61,4 +67,4 @@ public sealed class FileCallbackResultExecutor : FileResultExecutorBase } } } -} \ No newline at end of file +} diff --git a/backend/src/Squidex/wwwroot/scripts/editor-sdk.d.ts b/backend/src/Squidex/wwwroot/scripts/editor-sdk.d.ts index 3d2377e8f..06c712e87 100644 --- a/backend/src/Squidex/wwwroot/scripts/editor-sdk.d.ts +++ b/backend/src/Squidex/wwwroot/scripts/editor-sdk.d.ts @@ -1,4 +1,18 @@ +type PluginOptions = { + /** + * Defines the accepted origins for incoming messages. + */ + acceptedOrigins?: string[]; +} + declare class SquidexSidebar { + /** + * The constructor. + * + * @param options: The plugin options. + */ + constructor(options?: PluginOptions); + /** * Get the current context. */ @@ -30,6 +44,13 @@ declare class SquidexSidebar { } declare class SquidexWidget { + /** + * The constructor. + * + * @param options: The plugin options. + */ + constructor(options?: PluginOptions); + /** * Get the current context. */ @@ -41,6 +62,7 @@ declare class SquidexWidget { * @param callback: The callback to invoke. */ onInit(callback: () => void): void; + /** * Clean the editor SDK. */ @@ -49,6 +71,13 @@ declare class SquidexWidget { declare class SquidexFormField { + /** + * The constructor. + * + * @param options: The plugin options. + */ + constructor(options?: PluginOptions); + /** * Get the current value. */ @@ -152,6 +181,8 @@ declare class SquidexFormField { * * @param schemas: The list of schema names. * @param callback The callback to invoke when the dialog is completed or closed. +<<<<<<< HEAD +======= * @param query: The initial query that is used in the UI. * @param selectedIds: The selected ids to mark them as selected in the content selector dialog. */ @@ -159,6 +190,7 @@ declare class SquidexFormField { /** * Shows a dialog to pick a file. +>>>>>>> 8f149db852d984489e6ca1a07af988dac917ab56 */ pickFile(): void; diff --git a/backend/src/Squidex/wwwroot/scripts/editor-sdk.js b/backend/src/Squidex/wwwroot/scripts/editor-sdk.js index 6329cde8d..b0017a2d9 100644 --- a/backend/src/Squidex/wwwroot/scripts/editor-sdk.js +++ b/backend/src/Squidex/wwwroot/scripts/editor-sdk.js @@ -53,13 +53,19 @@ function isArrayOfStrings(value) { return true; } -function SquidexSidebar() { +/** + * Creates a new plugin for sidebars. + * + * @param {object} options with the accepted origins. + */ +function SquidexSidebar(options) { var initHandler; var initCalled = false; var contentHandler; var content; var context; var timer; + var acceptedOrigins = options && isArrayOfStrings(options.acceptedOrigins) ? options.acceptedOrigins : null; function raiseContentChanged() { if (contentHandler && content) { @@ -75,19 +81,28 @@ function SquidexSidebar() { } function eventListener(event) { - if (event.source !== window) { - var type = event.data.type; - - if (type === 'contentChanged') { - content = event.data.content; + if (acceptedOrigins && acceptedOrigins.indexOf(event.origin) < 0) { + console.log('Origin not accepted: ' + event.origin); + return; + } + + if (event.source === window) { + return; + } - raiseContentChanged(); - } else if (type === 'init') { - context = event.data.context; + var type = event.data.type; + + if (type === 'contentChanged') { + content = event.data.content; - raiseInit(); - } + raiseContentChanged(); + } else if (type === 'init') { + context = event.data.context; + + raiseInit(); } + + console.log('Received Message: ' + type); } window.addEventListener('message', eventListener, false); @@ -156,10 +171,17 @@ function SquidexSidebar() { return plugin; } -function SquidexWidget() { + +/** + * Creates a new plugin for widgets. + * + * @param {object} options with the accepted origins. + */ +function SquidexWidget(options) { var initHandler; var initCalled = false; var context; + var acceptedOrigins = options && isArrayOfStrings(options.acceptedOrigins) ? options.acceptedOrigins : null; document.body.style.margin = '0'; document.body.style.padding = '0'; @@ -172,15 +194,24 @@ function SquidexWidget() { } function eventListener(event) { - if (event.source !== window) { - var type = event.data.type; - - if (type === 'init') { - context = event.data.context; + if (acceptedOrigins && acceptedOrigins.indexOf(event.origin) < 0) { + console.log('Origin not accepted: ' + event.origin); + return; + } - raiseInit(); - } + if (event.source === window) { + return; + } + + var type = event.data.type; + + if (type === 'init') { + context = event.data.context; + + raiseInit(); } + + console.log('Received Message: ' + type); } window.addEventListener('message', eventListener, false); @@ -219,7 +250,12 @@ function SquidexWidget() { return plugin; } -function SquidexFormField() { +/** + * Creates a new plugin for form fields. + * + * @param {object} options with the accepted origins. + */ +function SquidexFormField(options) { var context; var currentConfirm; var currentPickAssets; @@ -241,6 +277,7 @@ function SquidexFormField() { var timer; var value; var valueHandler; + var acceptedOrigins = options && isArrayOfStrings(options.acceptedOrigins) ? options.acceptedOrigins : null; function raiseInit() { if (initHandler && !initCalled && context) { @@ -292,81 +329,88 @@ function SquidexFormField() { } function eventListener(event) { - if (event.source !== window) { - var type = event.data.type; + if (acceptedOrigins && acceptedOrigins.indexOf(event.origin) < 0) { + console.log('Origin not accepted: ' + event.origin); + return; + } - console.log('Received Message: ' + type); + if (event.source === window) { + return; + } + + var type = event.data.type; - if (type === 'disabled') { - var newDisabled = event.data.isDisabled; + if (type === 'disabled') { + var newDisabled = event.data.isDisabled; - if (disabled !== newDisabled) { - disabled = newDisabled; + if (disabled !== newDisabled) { + disabled = newDisabled; - raiseDisabled(); - } - } else if (type === 'moved') { - var newIndex = event.data.index; + raiseDisabled(); + } + } else if (type === 'moved') { + var newIndex = event.data.index; - if (index !== newIndex) { - index = newIndex; + if (index !== newIndex) { + index = newIndex; - raisedMoved(); - } - } else if (type === 'languageChanged') { - var newLanguage = event.data.language; + raisedMoved(); + } + } else if (type === 'languageChanged') { + var newLanguage = event.data.language; - if (language !== newLanguage) { - language = newLanguage; + if (language !== newLanguage) { + language = newLanguage; - raiseLanguageChanged(); - } - } else if (type === 'valueChanged') { - value = event.data.value; + raiseLanguageChanged(); + } + } else if (type === 'valueChanged') { + value = event.data.value; - raiseValueChanged(); - } else if (type === 'formValueChanged') { - formValue = event.data.formValue; + raiseValueChanged(); + } else if (type === 'formValueChanged') { + formValue = event.data.formValue; - raiseFormValueChanged(); - } else if (type === 'fullscreenChanged') { - fullscreen = event.data.fullscreen; + raiseFormValueChanged(); + } else if (type === 'fullscreenChanged') { + fullscreen = event.data.fullscreen; - raiseFullscreen(); - } else if (type === 'expandedChanged') { - expanded = event.data.expanded; + raiseFullscreen(); + } else if (type === 'expandedChanged') { + expanded = event.data.expanded; - raiseExpanded(); - } else if (type === 'init') { - context = event.data.context; + raiseExpanded(); + } else if (type === 'init') { + context = event.data.context; - raiseInit(); - } else if (type === 'confirmResult') { - var correlationId = event.data.correlationId; + raiseInit(); + } else if (type === 'confirmResult') { + var correlationId = event.data.correlationId; - if (currentConfirm && currentConfirm.correlationId === correlationId) { - if (typeof currentConfirm.callback === 'function') { - currentConfirm.callback(event.data.result); - } + if (currentConfirm && currentConfirm.correlationId === correlationId) { + if (typeof currentConfirm.callback === 'function') { + currentConfirm.callback(event.data.result); } - } else if (type === 'pickAssetsResult') { - var correlationId = event.data.correlationId; + } + } else if (type === 'pickAssetsResult') { + var correlationId = event.data.correlationId; - if (currentPickAssets && currentPickAssets.correlationId === correlationId) { - if (typeof currentPickAssets.callback === 'function') { - currentPickAssets.callback(event.data.result); - } + if (currentPickAssets && currentPickAssets.correlationId === correlationId) { + if (typeof currentPickAssets.callback === 'function') { + currentPickAssets.callback(event.data.result); } - } else if (type === 'pickContentsResult') { - var correlationId = event.data.correlationId; + } + } else if (type === 'pickContentsResult') { + var correlationId = event.data.correlationId; - if (currentPickContents && currentPickContents.correlationId === correlationId) { - if (typeof currentPickContents.callback === 'function') { - currentPickContents.callback(event.data.result); - } + if (currentPickContents && currentPickContents.correlationId === correlationId) { + if (typeof currentPickContents.callback === 'function') { + currentPickContents.callback(event.data.result); } } } + + console.log('Received Message: ' + type); } window.addEventListener('message', eventListener, false);