diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/mail/EmailServiceTaskTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/mail/EmailServiceTaskTest.java index 2f772654336..c5a8659a831 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/mail/EmailServiceTaskTest.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/mail/EmailServiceTaskTest.java @@ -502,6 +502,52 @@ public void testTextMailWithDataSourceAttachment() throws Exception { assertThat(attachmentFileName).isEqualTo(fileName); } + @Test + @Deployment(resources = "org/flowable/engine/test/bpmn/mail/EmailServiceTaskTest.testTextMailWithDataSourceAttachment.bpmn20.xml") + public void testTextMailWithDataSourceAttachmentLongNonAsciiFileName() throws Exception { + String fileName = "Précis of Evidence VA23-5-1034 Lorge Chocolatier PN 1136882 Retail Henry Street Kenmare Co.pdf"; + String fileContent = "This is the file content"; + HashMap vars = new HashMap<>(); + vars.put("attachmentsBean", new AttachmentsBean()); + vars.put("fileContent", fileContent); + vars.put("fileName", fileName); + runtimeService.startProcessInstanceByKey("textMailWithDataSourceAttachment", vars); + + List messages = wiser.getMessages(); + assertThat(messages).hasSize(1); + WiserMessage message = messages.get(0); + MimeMultipart mm = (MimeMultipart) message.getMimeMessage().getContent(); + assertThat(mm.getCount()).isEqualTo(2); + String attachmentFileName = mm.getBodyPart(1).getDataHandler().getName(); + assertThat(attachmentFileName).isEqualTo(fileName); + } + + @Test + @Deployment(resources = "org/flowable/engine/test/bpmn/mail/EmailServiceTaskTest.testTextMailWithDataSourceAttachment.bpmn20.xml") + public void testTextMailWithDataSourceAttachmentUsesConfiguredCharset() throws Exception { + processEngineConfiguration.setMailServerDefaultCharset(StandardCharsets.ISO_8859_1); + reinitilizeMailClients(); + + String fileName = "café.pdf"; + String fileContent = "This is the file content"; + HashMap vars = new HashMap<>(); + vars.put("attachmentsBean", new AttachmentsBean()); + vars.put("fileContent", fileContent); + vars.put("fileName", fileName); + runtimeService.startProcessInstanceByKey("textMailWithDataSourceAttachment", vars); + + List messages = wiser.getMessages(); + assertThat(messages).hasSize(1); + WiserMessage message = messages.get(0); + MimeMultipart mm = (MimeMultipart) message.getMimeMessage().getContent(); + String contentDisposition = mm.getBodyPart(1).getHeader("Content-Disposition")[0]; + + // 'é' is a single byte (0xE9) in ISO-8859-1 but two bytes (0xC3 0xA9) in UTF-8: this string can only + // appear if the configured ISO-8859-1 charset was actually used to encode the attachment file name. + assertThat(contentDisposition).contains("ISO-8859-1''caf%E9.pdf"); + assertThat(mm.getBodyPart(1).getDataHandler().getName()).isEqualTo(fileName); + } + @Test @Deployment public void testTextMailWithNotExistingFileAttachment() throws Exception { diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/mail/EmailTestCase.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/mail/EmailTestCase.java index fdb8aac573c..af5768df8fe 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/mail/EmailTestCase.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/mail/EmailTestCase.java @@ -13,6 +13,7 @@ package org.flowable.engine.test.bpmn.mail; +import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; @@ -32,6 +33,7 @@ public abstract class EmailTestCase extends PluggableFlowableTestCase { protected Wiser wiser; private String initialForceTo; + private Charset initialDefaultCharset; private MailClientProvider initialMailClientProvider; private Map initialMailServers; @@ -39,6 +41,7 @@ public abstract class EmailTestCase extends PluggableFlowableTestCase { protected void setUp() throws Exception { initialForceTo = processEngineConfiguration.getMailServerForceTo(); + initialDefaultCharset = processEngineConfiguration.getMailServerDefaultCharset(); initialMailClientProvider = processEngineConfiguration.getMailClientProvider(); reinitilizeMailClients(); Map mailServers = processEngineConfiguration.getMailServers(); @@ -66,6 +69,7 @@ protected void tearDown() throws Exception { Thread.sleep(250L); processEngineConfiguration.setMailServerForceTo(initialForceTo); + processEngineConfiguration.setMailServerDefaultCharset(initialDefaultCharset); processEngineConfiguration.setMailServers(initialMailServers); processEngineConfiguration.setMailClientProvider(initialMailClientProvider); } diff --git a/modules/flowable-mail/src/main/java/org/flowable/mail/common/impl/jakarta/mail/JakartaMailFlowableMailClient.java b/modules/flowable-mail/src/main/java/org/flowable/mail/common/impl/jakarta/mail/JakartaMailFlowableMailClient.java index dc19341f3b7..9b73277bef9 100644 --- a/modules/flowable-mail/src/main/java/org/flowable/mail/common/impl/jakarta/mail/JakartaMailFlowableMailClient.java +++ b/modules/flowable-mail/src/main/java/org/flowable/mail/common/impl/jakarta/mail/JakartaMailFlowableMailClient.java @@ -36,11 +36,13 @@ import jakarta.mail.Session; import jakarta.mail.Transport; import jakarta.mail.internet.AddressException; +import jakarta.mail.internet.ContentDisposition; import jakarta.mail.internet.InternetAddress; import jakarta.mail.internet.MimeBodyPart; import jakarta.mail.internet.MimeMessage; import jakarta.mail.internet.MimeMultipart; import jakarta.mail.internet.MimeUtility; +import jakarta.mail.internet.ParameterList; import org.apache.commons.lang3.StringUtils; import org.flowable.common.engine.api.FlowableException; @@ -323,12 +325,7 @@ protected MimeMultipart createMultiPartContent(String text, String html, String if (attachmentsExists) { for (DataSource attachment : attachments) { BodyPart bodyPart = new MimeBodyPart(); - bodyPart.setDisposition(Part.ATTACHMENT); - try { - bodyPart.setFileName(MimeUtility.encodeText(attachment.getName(), charset, null)); - } catch (UnsupportedEncodingException e) { - throw new FlowableMailException("Could not encode attachment file name", e); - } + setAttachmentFileName(bodyPart, attachment.getName(), charset); bodyPart.setDataHandler(new DataHandler(attachment)); rootContainer.addBodyPart(bodyPart); } @@ -337,6 +334,21 @@ protected MimeMultipart createMultiPartContent(String text, String html, String return rootContainer; } + protected void setAttachmentFileName(BodyPart bodyPart, String fileName, String charset) throws MessagingException { + // Build the Content-Disposition header ourselves instead of using BodyPart.setFileName(): that method + // ignores the charset argument entirely and RFC 2231 encodes with MimeUtility.getDefaultMIMECharset() + // (the JVM's file.encoding, unless mail.mime.charset is set), so it silently diverges from the charset + // used for the rest of the message. ParameterList.set(name, value, charset) does the same RFC 2231 + // encoding and continuation folding, but with the charset we actually pass it, and leaves ASCII names + // untouched. When no charset is configured, fall back to the same default MimeBodyPart.setText() falls + // back to for the message body, so the file name and body stay consistent with each other. + String mimeCharset = charset != null ? charset : MimeUtility.mimeCharset(MimeUtility.getDefaultJavaCharset()); + ParameterList parameters = new ParameterList(); + parameters.set("filename", fileName, mimeCharset); + ContentDisposition disposition = new ContentDisposition(Part.ATTACHMENT, parameters); + bodyPart.setHeader("Content-Disposition", disposition.toString()); + } + protected Session createSession() { if (serverConfiguration instanceof MailJndiServerConfiguration jndiServerConfiguration) { return createSession(jndiServerConfiguration);