Repository: camel Updated Branches: refs/heads/master 1757f959d -> dbbdc490e
CAMEL-11872: Handle MIME folded and MIME encoded email headers Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/87be1a32 Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/87be1a32 Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/87be1a32 Branch: refs/heads/master Commit: 87be1a32b90cd1ac9ebbef3c63a5966093e9b11e Parents: 1757f95 Author: Ion Savin <co...@gmx.net> Authored: Tue Oct 3 16:17:48 2017 +0300 Committer: Claus Ibsen <davscl...@apache.org> Committed: Tue Oct 3 19:23:31 2017 +0200 ---------------------------------------------------------------------- .../camel/component/mail/MailBinding.java | 9 +- .../camel/component/mail/MailConfiguration.java | 15 ++- .../mail/MailMimeDecodeHeadersTest.java | 108 +++++++++++++++++++ 3 files changed, 130 insertions(+), 2 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/87be1a32/components/camel-mail/src/main/java/org/apache/camel/component/mail/MailBinding.java ---------------------------------------------------------------------- diff --git a/components/camel-mail/src/main/java/org/apache/camel/component/mail/MailBinding.java b/components/camel-mail/src/main/java/org/apache/camel/component/mail/MailBinding.java index ec416e8..2220ac1 100644 --- a/components/camel-mail/src/main/java/org/apache/camel/component/mail/MailBinding.java +++ b/components/camel-mail/src/main/java/org/apache/camel/component/mail/MailBinding.java @@ -41,6 +41,7 @@ import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMultipart; +import javax.mail.internet.MimeUtility; import javax.mail.util.ByteArrayDataSource; import org.apache.camel.Attachment; @@ -598,15 +599,21 @@ public class MailBinding { Map<String, Object> answer = new TreeMap<String, Object>(String.CASE_INSENSITIVE_ORDER); Enumeration<?> names = mailMessage.getAllHeaders(); + MailConfiguration mailConfiguration = ((MailEndpoint) exchange.getFromEndpoint()).getConfiguration(); while (names.hasMoreElements()) { Header header = (Header) names.nextElement(); + String value = header.getValue(); + if (value != null && mailConfiguration.isMimeDecodeHeaders()) { + value = MimeUtility.decodeText(MimeUtility.unfold(value)); + } + if (headerFilterStrategy != null && !headerFilterStrategy.applyFilterToExternalHeaders(header.getName(), value, exchange)) { CollectionHelper.appendValue(answer, header.getName(), value); } } // if the message is a multipart message, do not set the content type to multipart/* - if (((MailEndpoint)exchange.getFromEndpoint()).getConfiguration().isMapMailMessage()) { + if (mailConfiguration.isMapMailMessage()) { Object content = mailMessage.getContent(); if (content instanceof MimeMultipart) { MimeMultipart multipart = (MimeMultipart)content; http://git-wip-us.apache.org/repos/asf/camel/blob/87be1a32/components/camel-mail/src/main/java/org/apache/camel/component/mail/MailConfiguration.java ---------------------------------------------------------------------- diff --git a/components/camel-mail/src/main/java/org/apache/camel/component/mail/MailConfiguration.java b/components/camel-mail/src/main/java/org/apache/camel/component/mail/MailConfiguration.java index 2ab4b26..f91c608 100644 --- a/components/camel-mail/src/main/java/org/apache/camel/component/mail/MailConfiguration.java +++ b/components/camel-mail/src/main/java/org/apache/camel/component/mail/MailConfiguration.java @@ -109,6 +109,8 @@ public class MailConfiguration implements Cloneable { private boolean skipFailedMessage; @UriParam @Metadata(label = "consumer") private boolean handleFailedMessage; + @UriParam(defaultValue = "false") @Metadata(label = "consumer") + private boolean mimeDecodeHeaders; @UriParam(label = "security") private SSLContextParameters sslContextParameters; @UriParam(label = "advanced", prefix = "mail.", multiValue = true) @@ -118,7 +120,7 @@ public class MailConfiguration implements Cloneable { public MailConfiguration() { } - + public MailConfiguration(CamelContext context) { this.applicationClassLoader = context.getApplicationContextClassLoader(); } @@ -748,4 +750,15 @@ public class MailConfiguration implements Cloneable { public void setAttachmentsContentTransferEncodingResolver(AttachmentsContentTransferEncodingResolver attachmentsContentTransferEncodingResolver) { this.attachmentsContentTransferEncodingResolver = attachmentsContentTransferEncodingResolver; } + + /** + * This option enables transparent MIME decoding and unfolding for mail headers. + */ + public void setMimeDecodeHeaders(boolean mimeDecodeHeaders) { + this.mimeDecodeHeaders = mimeDecodeHeaders; + } + + public boolean isMimeDecodeHeaders() { + return mimeDecodeHeaders; + } } http://git-wip-us.apache.org/repos/asf/camel/blob/87be1a32/components/camel-mail/src/test/java/org/apache/camel/component/mail/MailMimeDecodeHeadersTest.java ---------------------------------------------------------------------- diff --git a/components/camel-mail/src/test/java/org/apache/camel/component/mail/MailMimeDecodeHeadersTest.java b/components/camel-mail/src/test/java/org/apache/camel/component/mail/MailMimeDecodeHeadersTest.java new file mode 100644 index 0000000..cafe248 --- /dev/null +++ b/components/camel-mail/src/test/java/org/apache/camel/component/mail/MailMimeDecodeHeadersTest.java @@ -0,0 +1,108 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.mail; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; +import org.jvnet.mock_javamail.Mailbox; + +import javax.mail.internet.MimeUtility; + +/** + * Unit test for Mail header decoding/unfolding support. + */ +public class MailMimeDecodeHeadersTest extends CamelTestSupport { + private String nonAsciiSubject = "\uD83D\uDC2A rocks!"; + private String encodedNonAsciiSubject = "=?UTF-8?Q?=F0=9F=90=AA_rocks!?="; + + private String longSubject; + { + StringBuilder sb = new StringBuilder("Camel rocks!"); + + int mimeFoldingLimit = 76; + int headerLength = "subject: ".length(); + for (int i = 0; headerLength + sb.length() <= mimeFoldingLimit; i++) { + sb.insert(7, "o"); + } + longSubject = sb.toString(); + } + private String foldedLongSubject = MimeUtility.fold(9, longSubject); + + @Test + public void testLongMailSubject() throws Exception { + Mailbox.clearAll(); + + // The email subject is >76 chars and will get MIME folded. + template.sendBody("direct:longSubject", ""); + + // When mimeDecodeHeaders=true is used, expect the received subject to be MIME unfolded. + MockEndpoint mockDecoded = getMockEndpoint("mock:decoded"); + mockDecoded.expectedMessageCount(1); + mockDecoded.expectedHeaderReceived("subject", longSubject); + mockDecoded.setResultWaitTime(10000); + mockDecoded.assertIsSatisfied(); + + // When mimeDecodeHeaders=false or missing, expect the received subject to be MIME folded. + MockEndpoint mockPlain = getMockEndpoint("mock:plain"); + mockPlain.expectedMessageCount(1); + mockPlain.expectedHeaderReceived("subject", foldedLongSubject); + mockPlain.setResultWaitTime(10000); + mockPlain.assertIsSatisfied(); + } + + @Test + public void testNonAsciiMailSubject() throws Exception { + Mailbox.clearAll(); + + // The email subject contains non-ascii characters and will be encoded. + template.sendBody("direct:nonAsciiSubject", ""); + + // When mimeDecodeHeaders=true is used, expect the received subject to be MIME encoded. + MockEndpoint mockDecoded = getMockEndpoint("mock:decoded"); + mockDecoded.expectedMessageCount(1); + mockDecoded.expectedHeaderReceived("subject", nonAsciiSubject); + mockDecoded.assertIsSatisfied(); + + // When mimeDecodeHeaders=false or missing, expect the received subject to be MIME encoded. + MockEndpoint mockPlain = getMockEndpoint("mock:plain"); + mockPlain.expectedMessageCount(1); + mockPlain.expectedHeaderReceived("subject", encodedNonAsciiSubject); + mockPlain.assertIsSatisfied(); + } + + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + public void configure() throws Exception { + from("direct:longSubject") + .setHeader("subject", constant(longSubject)) + .to("smtp://plain@localhost", "smtp://decoded@localhost"); + + from("direct:nonAsciiSubject") + .setHeader("subject", constant(nonAsciiSubject)) + .to("smtp://plain@localhost", "smtp://decoded@localhost"); + + from("pop3://localhost?username=plain&password=secret&consumer.delay=1000") + .to("mock:plain"); + + from("pop3://localhost?username=decoded&password=secret&consumer.delay=1000&mimeDecodeHeaders=true") + .to("mock:decoded"); + } + }; + } +}