diff --git a/packages/nodes-base/nodes/Aws/SES/AwsSes.node.ts b/packages/nodes-base/nodes/Aws/SES/AwsSes.node.ts
index 54d680f12b8..de9d1eab38b 100644
--- a/packages/nodes-base/nodes/Aws/SES/AwsSes.node.ts
+++ b/packages/nodes-base/nodes/Aws/SES/AwsSes.node.ts
@@ -867,7 +867,7 @@ export class AwsSes implements INodeType {
const params = [
'Action=DeleteCustomVerificationEmailTemplate',
- `TemplateName=${templateName}`,
+ `TemplateName=${encodeURIComponent(templateName)}`,
];
responseData = await awsApiRequestSOAP.call(
@@ -884,7 +884,7 @@ export class AwsSes implements INodeType {
if (operation === 'get') {
const templateName = this.getNodeParameter('templateName', i) as string;
- const params = [`TemplateName=${templateName}`];
+ const params = [`TemplateName=${encodeURIComponent(templateName)}`];
responseData = await awsApiRequestSOAP.call(
this,
@@ -925,7 +925,7 @@ export class AwsSes implements INodeType {
}
if (operation === 'send') {
- const email = this.getNodeParameter('email', i) as string[];
+ const email = this.getNodeParameter('email', i) as string;
const templateName = this.getNodeParameter('templateName', i) as string;
@@ -933,12 +933,14 @@ export class AwsSes implements INodeType {
const params = [
'Action=SendCustomVerificationEmail',
- `TemplateName=${templateName}`,
- `EmailAddress=${email}`,
+ `TemplateName=${encodeURIComponent(templateName)}`,
+ `EmailAddress=${encodeURIComponent(email)}`,
];
if (additionalFields.configurationSetName) {
- params.push(`ConfigurationSetName=${additionalFields.configurationSetName}`);
+ params.push(
+ `ConfigurationSetName=${encodeURIComponent(additionalFields.configurationSetName as string)}`,
+ );
}
responseData = await awsApiRequestSOAP.call(
@@ -959,27 +961,35 @@ export class AwsSes implements INodeType {
const params = [
'Action=UpdateCustomVerificationEmailTemplate',
- `TemplateName=${templateName}`,
+ `TemplateName=${encodeURIComponent(templateName)}`,
];
if (updateFields.FailureRedirectionURL) {
- params.push(`FailureRedirectionURL=${updateFields.FailureRedirectionURL}`);
+ params.push(
+ `FailureRedirectionURL=${encodeURIComponent(updateFields.FailureRedirectionURL as string)}`,
+ );
}
if (updateFields.email) {
- params.push(`FromEmailAddress=${updateFields.email}`);
+ params.push(`FromEmailAddress=${encodeURIComponent(updateFields.email as string)}`);
}
if (updateFields.successRedirectionURL) {
- params.push(`SuccessRedirectionURL=${updateFields.successRedirectionURL}`);
+ params.push(
+ `SuccessRedirectionURL=${encodeURIComponent(updateFields.successRedirectionURL as string)}`,
+ );
}
if (updateFields.templateContent) {
- params.push(`TemplateContent=${updateFields.templateContent}`);
+ params.push(
+ `TemplateContent=${encodeURIComponent(updateFields.templateContent as string)}`,
+ );
}
if (updateFields.templateSubject) {
- params.push(`TemplateSubject=${updateFields.templateSubject}`);
+ params.push(
+ `TemplateSubject=${encodeURIComponent(updateFields.templateSubject as string)}`,
+ );
}
responseData = await awsApiRequestSOAP.call(
@@ -1031,19 +1041,25 @@ export class AwsSes implements INodeType {
}
if (additionalFields.configurationSetName) {
- params.push(`ConfigurationSetName=${additionalFields.configurationSetName}`);
+ params.push(
+ `ConfigurationSetName=${encodeURIComponent(additionalFields.configurationSetName as string)}`,
+ );
}
if (additionalFields.returnPath) {
- params.push(`ReturnPath=${additionalFields.returnPath}`);
+ params.push(
+ `ReturnPath=${encodeURIComponent(additionalFields.returnPath as string)}`,
+ );
}
if (additionalFields.returnPathArn) {
- params.push(`ReturnPathArn=${additionalFields.returnPathArn}`);
+ params.push(
+ `ReturnPathArn=${encodeURIComponent(additionalFields.returnPathArn as string)}`,
+ );
}
if (additionalFields.sourceArn) {
- params.push(`SourceArn=${additionalFields.sourceArn}`);
+ params.push(`SourceArn=${encodeURIComponent(additionalFields.sourceArn as string)}`);
}
if (additionalFields.replyToAddresses) {
@@ -1083,7 +1099,10 @@ export class AwsSes implements INodeType {
const fromEmail = this.getNodeParameter('fromEmail', i) as string;
const additionalFields = this.getNodeParameter('additionalFields', i);
const templateDataUi = this.getNodeParameter('templateDataUi', i) as IDataObject;
- const params = [`Template=${template}`, `Source=${encodeURIComponent(fromEmail)}`];
+ const params = [
+ `Template=${encodeURIComponent(template)}`,
+ `Source=${encodeURIComponent(fromEmail)}`,
+ ];
if (toAddresses.length) {
setParameter(params, 'Destination.ToAddresses.member', toAddresses);
@@ -1096,19 +1115,25 @@ export class AwsSes implements INodeType {
}
if (additionalFields.configurationSetName) {
- params.push(`ConfigurationSetName=${additionalFields.configurationSetName}`);
+ params.push(
+ `ConfigurationSetName=${encodeURIComponent(additionalFields.configurationSetName as string)}`,
+ );
}
if (additionalFields.returnPath) {
- params.push(`ReturnPath=${additionalFields.returnPath}`);
+ params.push(
+ `ReturnPath=${encodeURIComponent(additionalFields.returnPath as string)}`,
+ );
}
if (additionalFields.returnPathArn) {
- params.push(`ReturnPathArn=${additionalFields.returnPathArn}`);
+ params.push(
+ `ReturnPathArn=${encodeURIComponent(additionalFields.returnPathArn as string)}`,
+ );
}
if (additionalFields.sourceArn) {
- params.push(`SourceArn=${additionalFields.sourceArn}`);
+ params.push(`SourceArn=${encodeURIComponent(additionalFields.sourceArn as string)}`);
}
if (additionalFields.replyToAddresses) {
@@ -1193,7 +1218,7 @@ export class AwsSes implements INodeType {
if (operation === 'delete') {
const templateName = this.getNodeParameter('templateName', i) as string;
- const params = [`TemplateName=${templateName}`];
+ const params = [`TemplateName=${encodeURIComponent(templateName)}`];
responseData = await awsApiRequestSOAP.call(
this,
@@ -1208,7 +1233,7 @@ export class AwsSes implements INodeType {
if (operation === 'get') {
const templateName = this.getNodeParameter('templateName', i) as string;
- const params = [`TemplateName=${templateName}`];
+ const params = [`TemplateName=${encodeURIComponent(templateName)}`];
responseData = await awsApiRequestSOAP.call(
this,
diff --git a/packages/nodes-base/nodes/Aws/SES/test/AwsSes.node.test.ts b/packages/nodes-base/nodes/Aws/SES/test/AwsSes.node.test.ts
index 9f4289dca4b..693be44cf7a 100644
--- a/packages/nodes-base/nodes/Aws/SES/test/AwsSes.node.test.ts
+++ b/packages/nodes-base/nodes/Aws/SES/test/AwsSes.node.test.ts
@@ -1,5 +1,5 @@
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
-import { NodeConnectionTypes, type WorkflowTestData } from 'n8n-workflow';
+import { NodeConnectionTypes, type INodeParameters, type WorkflowTestData } from 'n8n-workflow';
import assert from 'node:assert';
import qs from 'node:querystring';
@@ -8,9 +8,55 @@ import { credentials } from '../../__tests__/credentials';
describe('AwsSes Node', () => {
const testHarness = new NodeTestHarness();
const email = 'test+user@example.com';
+ const configurationSetName = 'config&segment=value';
+ const returnPath = 'bounce+tag@example.com';
+ const returnPathArn = 'arn:aws:ses:eu-central-1:123456789012:identity/bounce+tag@example.com';
+ const sourceArn = 'arn:aws:ses:eu-central-1:123456789012:identity/example.com&segment=value';
+ const templateName = 'Template&segment=value';
const templateData = {
Name: 'Special. Characters @#$%^&*()_-',
};
+ const createWorkflowData = (
+ parameters: INodeParameters,
+ ): WorkflowTestData['input']['workflowData'] => ({
+ nodes: [
+ {
+ parameters: {},
+ id: 'b30ae9d4-6a92-4b62-92f4-5810b4718c66',
+ name: 'When clicking ‘Execute workflow’',
+ type: 'n8n-nodes-base.manualTrigger',
+ typeVersion: 1,
+ position: [720, 380],
+ },
+ {
+ parameters,
+ id: '07955ca4-e9c9-415a-8175-dda8ad3204fd',
+ name: 'AWS SES',
+ type: 'n8n-nodes-base.awsSes',
+ typeVersion: 1,
+ position: [940, 380],
+ credentials: {
+ aws: {
+ id: '1',
+ name: 'AWS',
+ },
+ },
+ },
+ ],
+ connections: {
+ 'When clicking ‘Execute workflow’': {
+ main: [
+ [
+ {
+ node: 'AWS SES',
+ type: NodeConnectionTypes.Main,
+ index: 0,
+ },
+ ],
+ ],
+ },
+ },
+ });
const tests: WorkflowTestData[] = [
{
description: 'should create customVerificationEmail',
@@ -94,61 +140,28 @@ describe('AwsSes Node', () => {
},
},
{
- description: 'should URIencode params for sending email with template',
+ description: 'should preserve reserved characters when sending email with a template',
input: {
- workflowData: {
- nodes: [
- {
- parameters: {},
- type: 'n8n-nodes-base.manualTrigger',
- typeVersion: 1,
- position: [-180, 520],
- id: '363e874a-9054-4a64-bc3f-786719dde626',
- name: 'When clicking ‘Execute workflow’',
- },
- {
- parameters: {
- operation: 'sendTemplate',
- templateName: '=Template11',
- fromEmail: 'test+user@example.com',
- toAddresses: ['test+user@example.com'],
- templateDataUi: {
- templateDataValues: [
- {
- key: 'Name',
- value: '=Special. Characters @#$%^&*()_-',
- },
- ],
- },
- additionalFields: {},
+ workflowData: createWorkflowData({
+ operation: 'sendTemplate',
+ templateName,
+ fromEmail: email,
+ toAddresses: [email],
+ templateDataUi: {
+ templateDataValues: [
+ {
+ key: 'Name',
+ value: templateData.Name,
},
- type: 'n8n-nodes-base.awsSes',
- typeVersion: 1,
- position: [60, 520],
- id: '13bbf4ef-8320-45d1-9210-61b62794a108',
- name: 'AWS SES',
- credentials: {
- aws: {
- id: 'Nz0QZhzu3MvfK4TQ',
- name: 'AWS account',
- },
- },
- },
- ],
- connections: {
- 'When clicking ‘Execute workflow’': {
- main: [
- [
- {
- node: 'AWS SES',
- type: NodeConnectionTypes.Main,
- index: 0,
- },
- ],
- ],
- },
+ ],
},
- },
+ additionalFields: {
+ configurationSetName,
+ returnPath,
+ returnPathArn,
+ sourceArn,
+ },
+ }),
},
output: {
nodeData: { 'AWS SES': [[{ json: { success: 'true' } }]] },
@@ -158,7 +171,7 @@ describe('AwsSes Node', () => {
mocks: [
{
method: 'post',
- path: `/?Action=SendTemplatedEmail&Template=Template11&Source=${encodeURIComponent(email)}&Destination.ToAddresses.member.1=${encodeURIComponent(email)}&TemplateData=${encodeURIComponent(JSON.stringify(templateData))}`,
+ path: `/?Action=SendTemplatedEmail&Template=${encodeURIComponent(templateName)}&Source=${encodeURIComponent(email)}&Destination.ToAddresses.member.1=${encodeURIComponent(email)}&ConfigurationSetName=${encodeURIComponent(configurationSetName)}&ReturnPath=${encodeURIComponent(returnPath)}&ReturnPathArn=${encodeURIComponent(returnPathArn)}&SourceArn=${encodeURIComponent(sourceArn)}&TemplateData=${encodeURIComponent(JSON.stringify(templateData))}`,
statusCode: 200,
responseBody:
'true',
@@ -166,6 +179,104 @@ describe('AwsSes Node', () => {
],
},
},
+ {
+ description: 'should preserve reserved characters when sending an email',
+ input: {
+ workflowData: createWorkflowData({
+ resource: 'email',
+ operation: 'send',
+ fromEmail: email,
+ toAddresses: [email],
+ subject: 'Test subject',
+ body: 'Test body',
+ isBodyHtml: false,
+ additionalFields: {
+ configurationSetName,
+ returnPath,
+ returnPathArn,
+ sourceArn,
+ },
+ }),
+ },
+ output: {
+ nodeData: {
+ 'AWS SES': [[{ json: { SendEmailResponse: { success: 'true' } } }]],
+ },
+ },
+ nock: {
+ baseUrl: 'https://email.eu-central-1.amazonaws.com',
+ mocks: [
+ {
+ method: 'post',
+ path: `/?Action=SendEmail&Message.Subject.Data=Test%20subject&Source=${encodeURIComponent(email)}&Message.Body.Text.Data=Test%20body&Destination.ToAddresses.member.1=${encodeURIComponent(email)}&ConfigurationSetName=${encodeURIComponent(configurationSetName)}&ReturnPath=${encodeURIComponent(returnPath)}&ReturnPathArn=${encodeURIComponent(returnPathArn)}&SourceArn=${encodeURIComponent(sourceArn)}`,
+ statusCode: 200,
+ responseBody: 'true',
+ },
+ ],
+ },
+ },
+ {
+ description: 'should preserve reserved characters when getting a template',
+ input: {
+ workflowData: createWorkflowData({
+ resource: 'template',
+ operation: 'get',
+ templateName,
+ }),
+ },
+ output: {
+ nodeData: { 'AWS SES': [[{ json: { success: 'true' } }]] },
+ },
+ nock: {
+ baseUrl: 'https://email.eu-central-1.amazonaws.com',
+ mocks: [
+ {
+ method: 'post',
+ path: `/?Action=GetTemplate&TemplateName=${encodeURIComponent(templateName)}`,
+ statusCode: 200,
+ responseBody: 'true',
+ },
+ ],
+ },
+ },
+ {
+ description: 'should preserve reserved characters when sending a custom verification email',
+ input: {
+ workflowData: createWorkflowData({
+ resource: 'customVerificationEmail',
+ operation: 'send',
+ email,
+ templateName,
+ additionalFields: {
+ configurationSetName,
+ },
+ }),
+ },
+ output: {
+ nodeData: { 'AWS SES': [[{ json: { success: 'true' } }]] },
+ },
+ nock: {
+ baseUrl: 'https://email.eu-central-1.amazonaws.com',
+ mocks: [
+ {
+ method: 'post',
+ path: '/',
+ requestBody: (body: string) => {
+ assert.deepEqual(qs.parse(body), {
+ Action: 'SendCustomVerificationEmail',
+ TemplateName: templateName,
+ EmailAddress: email,
+ ConfigurationSetName: configurationSetName,
+ });
+ return true;
+ },
+ statusCode: 200,
+ responseBody:
+ 'true',
+ },
+ ],
+ },
+ },
];
for (const testData of tests) {
diff --git a/packages/nodes-base/nodes/Aws/SQS/AwsSqs.node.ts b/packages/nodes-base/nodes/Aws/SQS/AwsSqs.node.ts
index de6331c226c..917d27b5a74 100644
--- a/packages/nodes-base/nodes/Aws/SQS/AwsSqs.node.ts
+++ b/packages/nodes-base/nodes/Aws/SQS/AwsSqs.node.ts
@@ -316,12 +316,12 @@ export class AwsSqs implements INodeType {
'',
) as string;
if (messageDeduplicationId) {
- params.push(`MessageDeduplicationId=${messageDeduplicationId}`);
+ params.push(`MessageDeduplicationId=${encodeURIComponent(messageDeduplicationId)}`);
}
const messageGroupId = this.getNodeParameter('messageGroupId', i) as string;
if (messageGroupId) {
- params.push(`MessageGroupId=${messageGroupId}`);
+ params.push(`MessageGroupId=${encodeURIComponent(messageGroupId)}`);
}
}
@@ -331,8 +331,12 @@ export class AwsSqs implements INodeType {
this.getNodeParameter('options.messageAttributes.string', i, []) as INodeParameters[]
).forEach((attribute) => {
attributeCount++;
- params.push(`MessageAttribute.${attributeCount}.Name=${attribute.name}`);
- params.push(`MessageAttribute.${attributeCount}.Value.StringValue=${attribute.value}`);
+ params.push(
+ `MessageAttribute.${attributeCount}.Name=${encodeURIComponent(attribute.name as string)}`,
+ );
+ params.push(
+ `MessageAttribute.${attributeCount}.Value.StringValue=${encodeURIComponent(attribute.value as string)}`,
+ );
params.push(`MessageAttribute.${attributeCount}.Value.DataType=String`);
});
@@ -345,8 +349,12 @@ export class AwsSqs implements INodeType {
const dataPropertyName = attribute.dataPropertyName as string;
const binaryData = this.helpers.assertBinaryData(i, dataPropertyName);
- params.push(`MessageAttribute.${attributeCount}.Name=${attribute.name}`);
- params.push(`MessageAttribute.${attributeCount}.Value.BinaryValue=${binaryData.data}`);
+ params.push(
+ `MessageAttribute.${attributeCount}.Name=${encodeURIComponent(attribute.name as string)}`,
+ );
+ params.push(
+ `MessageAttribute.${attributeCount}.Value.BinaryValue=${encodeURIComponent(binaryData.data)}`,
+ );
params.push(`MessageAttribute.${attributeCount}.Value.DataType=Binary`);
});
@@ -355,8 +363,12 @@ export class AwsSqs implements INodeType {
this.getNodeParameter('options.messageAttributes.number', i, []) as INodeParameters[]
).forEach((attribute) => {
attributeCount++;
- params.push(`MessageAttribute.${attributeCount}.Name=${attribute.name}`);
- params.push(`MessageAttribute.${attributeCount}.Value.StringValue=${attribute.value}`);
+ params.push(
+ `MessageAttribute.${attributeCount}.Name=${encodeURIComponent(attribute.name as string)}`,
+ );
+ params.push(
+ `MessageAttribute.${attributeCount}.Value.StringValue=${encodeURIComponent(attribute.value as number)}`,
+ );
params.push(`MessageAttribute.${attributeCount}.Value.DataType=Number`);
});
diff --git a/packages/nodes-base/nodes/Aws/SQS/test/AwsSqs.node.test.ts b/packages/nodes-base/nodes/Aws/SQS/test/AwsSqs.node.test.ts
new file mode 100644
index 00000000000..ea1314402ea
--- /dev/null
+++ b/packages/nodes-base/nodes/Aws/SQS/test/AwsSqs.node.test.ts
@@ -0,0 +1,93 @@
+import { NodeTestHarness } from '@nodes-testing/node-test-harness';
+import { NodeConnectionTypes, type WorkflowTestData } from 'n8n-workflow';
+
+import { credentials } from '../../__tests__/credentials';
+
+describe('AwsSqs Node', () => {
+ const messageAttributeName = 'label&kind=primary';
+ const messageAttributeValue = 'left&segment=value';
+ const messageDeduplicationId = 'dedup&segment=value';
+ const messageGroupId = 'group&segment=value';
+ const queuePath = '/123456789012/n8n-node-test-fifo.fifo';
+
+ const testData: WorkflowTestData = {
+ description: 'should preserve reserved characters in request parameter values',
+ input: {
+ workflowData: {
+ nodes: [
+ {
+ parameters: {},
+ id: '5d4c45ed-1368-4ea3-a97c-2d46664f4656',
+ name: 'When clicking ‘Execute workflow’',
+ type: 'n8n-nodes-base.manualTrigger',
+ typeVersion: 1,
+ position: [720, 380],
+ },
+ {
+ parameters: {
+ queue: `https://sqs.eu-central-1.amazonaws.com${queuePath}`,
+ queueType: 'fifo',
+ sendInputData: false,
+ message: 'test message',
+ messageGroupId,
+ options: {
+ messageDeduplicationId,
+ messageAttributes: {
+ string: [
+ {
+ name: messageAttributeName,
+ value: messageAttributeValue,
+ },
+ ],
+ },
+ },
+ },
+ id: 'bf664a84-bd26-413b-94d3-7f2935883ce3',
+ name: 'AWS SQS',
+ type: 'n8n-nodes-base.awsSqs',
+ typeVersion: 1,
+ position: [940, 380],
+ credentials: {
+ aws: {
+ id: '1',
+ name: 'AWS',
+ },
+ },
+ },
+ ],
+ connections: {
+ 'When clicking ‘Execute workflow’': {
+ main: [
+ [
+ {
+ node: 'AWS SQS',
+ type: NodeConnectionTypes.Main,
+ index: 0,
+ },
+ ],
+ ],
+ },
+ },
+ },
+ },
+ output: {
+ nodeData: {
+ 'AWS SQS': [[{ json: { MessageId: 'message-id' } }]],
+ },
+ },
+ nock: {
+ baseUrl: 'https://sqs.eu-central-1.amazonaws.com',
+ mocks: [
+ {
+ method: 'get',
+ path: `${queuePath}?Version=2012-11-05&Action=SendMessage&MessageBody=test%20message&MessageDeduplicationId=${encodeURIComponent(messageDeduplicationId)}&MessageGroupId=${encodeURIComponent(messageGroupId)}&MessageAttribute.1.Name=${encodeURIComponent(messageAttributeName)}&MessageAttribute.1.Value.StringValue=${encodeURIComponent(messageAttributeValue)}&MessageAttribute.1.Value.DataType=String`,
+ statusCode: 200,
+ responseBody:
+ 'message-id',
+ },
+ ],
+ },
+ };
+
+ new NodeTestHarness().setupTest(testData, { credentials });
+});