diff --git a/packages/nodes-base/nodes/Form/formNodeUtils.ts b/packages/nodes-base/nodes/Form/formNodeUtils.ts index 4d33b1365d..ac8e1d5655 100644 --- a/packages/nodes-base/nodes/Form/formNodeUtils.ts +++ b/packages/nodes-base/nodes/Form/formNodeUtils.ts @@ -6,7 +6,7 @@ import { type IWebhookResponseData, } from 'n8n-workflow'; -import { renderForm } from './utils'; +import { renderForm, sanitizeHtml } from './utils'; export const renderFormNode = async ( context: IWebhookFunctions, @@ -42,6 +42,12 @@ export const renderFormNode = async ( ) as string) || 'Submit'; } + for (const field of fields) { + if (field.fieldType === 'html') { + field.html = sanitizeHtml(field.html as string); + } + } + const appendAttribution = context.evaluateExpression( `{{ $('${trigger?.name}').params.options?.appendAttribution === false ? false : true }}`, ) as boolean; diff --git a/packages/nodes-base/nodes/Form/test/formNodeUtils.test.ts b/packages/nodes-base/nodes/Form/test/formNodeUtils.test.ts new file mode 100644 index 0000000000..a9bbf936f1 --- /dev/null +++ b/packages/nodes-base/nodes/Form/test/formNodeUtils.test.ts @@ -0,0 +1,113 @@ +import { type Response } from 'express'; +import { mock } from 'jest-mock-extended'; +import { + type FormFieldsParameter, + type IWebhookFunctions, + type NodeTypeAndVersion, +} from 'n8n-workflow'; + +import { renderFormNode } from '../formNodeUtils'; + +describe('formNodeUtils', () => { + it('should sanitize custom html', async () => { + const executeFunctions = mock(); + executeFunctions.getNode.mockReturnValue({ typeVersion: 2.1 } as any); + executeFunctions.getNodeParameter.calledWith('options').mockReturnValue({ + formTitle: 'Test Title', + formDescription: 'Test Description', + buttonLabel: 'Test Button Label', + }); + + const mockRender = jest.fn(); + + const formFields: FormFieldsParameter = [ + { + fieldLabel: 'Custom HTML', + fieldType: 'html', + html: '
Test HTML
', + requiredField: false, + }, + { + fieldLabel: 'Custom HTML', + fieldType: 'html', + html: '', + requiredField: false, + }, + { + fieldLabel: 'Custom HTML', + fieldType: 'html', + html: '', + requiredField: false, + }, + { + fieldLabel: 'Custom HTML', + fieldType: 'html', + html: '
hihihi
', + requiredField: false, + }, + ]; + + executeFunctions.getNodeParameter.calledWith('formFields.values').mockReturnValue(formFields); + + const responseMock = mock({ render: mockRender } as any); + const triggerMock = mock({ name: 'triggerName' } as any); + + await renderFormNode(executeFunctions, responseMock, triggerMock, formFields, 'test'); + + expect(mockRender).toHaveBeenCalledWith('form-trigger', { + appendAttribution: true, + buttonLabel: 'Test Button Label', + formDescription: 'Test Description', + formDescriptionMetadata: 'Test Description', + formFields: [ + { + defaultValue: '', + errorId: 'error-field-0', + html: '
Test HTML
', + id: 'field-0', + inputRequired: '', + isHtml: true, + label: 'Custom HTML', + placeholder: undefined, + }, + { + defaultValue: '', + errorId: 'error-field-1', + html: '', + id: 'field-1', + inputRequired: '', + isHtml: true, + label: 'Custom HTML', + placeholder: undefined, + }, + { + defaultValue: '', + errorId: 'error-field-2', + html: '', + id: 'field-2', + inputRequired: '', + isHtml: true, + label: 'Custom HTML', + placeholder: undefined, + }, + { + defaultValue: '', + errorId: 'error-field-3', + html: '
hihihi
', + id: 'field-3', + inputRequired: '', + isHtml: true, + label: 'Custom HTML', + placeholder: undefined, + }, + ], + formSubmittedHeader: undefined, + formSubmittedText: 'Your response has been recorded', + formTitle: 'Test Title', + n8nWebsiteLink: 'https://n8n.io/?utm_source=n8n-internal&utm_medium=form-trigger', + testRun: true, + useResponseData: true, + validForm: true, + }); + }); +});