You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ol...@apache.org on 2022/03/31 21:21:22 UTC
[sling-org-apache-sling-clam] 02/02: SLING-10993 Update Sling Commons Messaging Mail to 2.0.0
This is an automated email from the ASF dual-hosted git repository.
olli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-clam.git
commit 2e50735793aafb15144be9fad60d7232149087be
Author: Oliver Lietz <ol...@apache.org>
AuthorDate: Thu Mar 31 23:21:06 2022 +0200
SLING-10993 Update Sling Commons Messaging Mail to 2.0.0
---
bnd.bnd | 14 +-
pom.xml | 16 +-
.../internal/MailSendingScanResultHandler.java | 2 +-
.../it/tests/MailSendingScanResultHandlerIT.java | 10 +-
.../org/commons/mail/util/MimeMessageParser.java | 451 +++++++++++++++++++++
5 files changed, 466 insertions(+), 27 deletions(-)
diff --git a/bnd.bnd b/bnd.bnd
index aecb1cd..70e1d1e 100644
--- a/bnd.bnd
+++ b/bnd.bnd
@@ -1,14 +1,8 @@
-DynamicImport-Package:\
- javax.mail.*,\
- org.apache.commons.lang3.*,\
- org.apache.sling.commons.messaging.*,\
- org.thymeleaf.*
-
Import-Package:\
- javax.mail.*;resolution:=optional,\
- org.apache.commons.lang3.*;resolution:=optional,\
- org.apache.sling.commons.messaging.*;resolution:=optional,\
- org.thymeleaf.*;resolution:=optional,\
+ jakarta.mail.*;resolution:=dynamic,\
+ org.apache.commons.lang3.*;resolution:=dynamic,\
+ org.apache.sling.commons.messaging.*;resolution:=dynamic,\
+ org.thymeleaf.*;resolution:=dynamic,\
*
-removeheaders:\
diff --git a/pom.xml b/pom.xml
index dc1f0ad..e5cb261 100644
--- a/pom.xml
+++ b/pom.xml
@@ -181,7 +181,7 @@
<dependency>
<groupId>jakarta.mail</groupId>
<artifactId>jakarta.mail-api</artifactId>
- <version>1.6.5</version>
+ <version>2.0.1</version>
<scope>provided</scope>
</dependency>
<!-- ok io/http -->
@@ -238,12 +238,6 @@
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
- <artifactId>commons-email</artifactId>
- <version>1.5</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
<scope>provided</scope>
@@ -308,7 +302,7 @@
<dependency>
<groupId>org.apache.sling</groupId>
<artifactId>org.apache.sling.commons.messaging.mail</artifactId>
- <version>1.0.0</version>
+ <version>2.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
@@ -338,14 +332,14 @@
<dependency>
<groupId>org.apache.sling</groupId>
<artifactId>org.apache.sling.testing.paxexam</artifactId>
- <version>3.1.1-SNAPSHOT</version>
+ <version>4.0.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<!-- Thymeleaf -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
- <version>3.0.12.RELEASE</version>
+ <version>3.0.15.RELEASE</version>
<scope>provided</scope>
</dependency>
<!-- nullability -->
@@ -387,7 +381,7 @@
<dependency>
<groupId>com.icegreen</groupId>
<artifactId>greenmail</artifactId>
- <version>1.6.5</version>
+ <version>2.0.0-alpha-2</version>
<scope>test</scope>
</dependency>
<dependency>
diff --git a/src/main/java/org/apache/sling/clam/result/internal/MailSendingScanResultHandler.java b/src/main/java/org/apache/sling/clam/result/internal/MailSendingScanResultHandler.java
index 0e6f198..a06423b 100644
--- a/src/main/java/org/apache/sling/clam/result/internal/MailSendingScanResultHandler.java
+++ b/src/main/java/org/apache/sling/clam/result/internal/MailSendingScanResultHandler.java
@@ -23,7 +23,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
-import javax.mail.internet.MimeMessage;
+import jakarta.mail.internet.MimeMessage;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.clam.result.JcrPropertyScanResultHandler;
diff --git a/src/test/java/org/apache/sling/clam/it/tests/MailSendingScanResultHandlerIT.java b/src/test/java/org/apache/sling/clam/it/tests/MailSendingScanResultHandlerIT.java
index f2855eb..3140b2f 100644
--- a/src/test/java/org/apache/sling/clam/it/tests/MailSendingScanResultHandlerIT.java
+++ b/src/test/java/org/apache/sling/clam/it/tests/MailSendingScanResultHandlerIT.java
@@ -22,7 +22,8 @@ import java.security.Security;
import java.util.Objects;
import javax.inject.Inject;
-import javax.mail.internet.MimeMessage;
+
+import jakarta.mail.internet.MimeMessage;
import com.icegreen.greenmail.util.DummySSLSocketFactory;
import com.icegreen.greenmail.util.GreenMail;
@@ -49,7 +50,6 @@ import static org.apache.sling.testing.paxexam.SlingOptions.slingCommonsMessagin
import static org.apache.sling.testing.paxexam.SlingOptions.slingResourcePresence;
import static org.apache.sling.testing.paxexam.SlingOptions.slingStarterContent;
import static org.apache.sling.testing.paxexam.SlingOptions.thymeleaf;
-import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
import static org.ops4j.pax.exam.CoreOptions.options;
import static org.ops4j.pax.exam.cm.ConfigurationAdminOptions.factoryConfiguration;
import static org.ops4j.pax.exam.cm.ConfigurationAdminOptions.newConfiguration;
@@ -96,6 +96,7 @@ public class MailSendingScanResultHandlerIT extends ClamTestSupport {
.put("host", "localhost")
.asOption(),
factoryConfiguration("org.apache.sling.commons.messaging.mail.internal.SimpleMailService")
+ .put("mail.smtps.ssl.checkserveridentity", false)
.put("mail.smtps.from", "envelope-from@example.org")
.put("mail.smtps.host", "localhost")
.put("mail.smtps.port", port)
@@ -104,7 +105,7 @@ public class MailSendingScanResultHandlerIT extends ClamTestSupport {
.asOption(),
slingCommonsMessagingMail(),
// Commons Crypto
- factoryConfiguration("org.apache.sling.commons.crypto.jasypt.internal.JasyptStandardPBEStringCryptoService")
+ factoryConfiguration("org.apache.sling.commons.crypto.jasypt.internal.JasyptStandardPbeStringCryptoService")
.put("algorithm", "PBEWITHHMACSHA512ANDAES_256")
.asOption(),
factoryConfiguration("org.apache.sling.commons.crypto.jasypt.internal.JasyptRandomIvGeneratorRegistrar")
@@ -116,8 +117,7 @@ public class MailSendingScanResultHandlerIT extends ClamTestSupport {
// Thymeleaf
thymeleaf(),
// testing – mail
- greenmail(),
- mavenBundle().groupId("org.apache.commons").artifactId("commons-email").versionAsInProject()
+ greenmail()
);
}
diff --git a/src/test/java/org/commons/mail/util/MimeMessageParser.java b/src/test/java/org/commons/mail/util/MimeMessageParser.java
new file mode 100644
index 0000000..c4d677c
--- /dev/null
+++ b/src/test/java/org/commons/mail/util/MimeMessageParser.java
@@ -0,0 +1,451 @@
+/*
+ * 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.commons.mail.util;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import jakarta.activation.DataHandler;
+import jakarta.activation.DataSource;
+import jakarta.mail.Message;
+import jakarta.mail.MessagingException;
+import jakarta.mail.Multipart;
+import jakarta.mail.Part;
+import jakarta.mail.internet.ContentType;
+import jakarta.mail.internet.InternetAddress;
+import jakarta.mail.internet.MimeBodyPart;
+import jakarta.mail.internet.MimeMessage;
+import jakarta.mail.internet.MimePart;
+import jakarta.mail.internet.MimeUtility;
+import jakarta.mail.internet.ParseException;
+import jakarta.mail.util.ByteArrayDataSource;
+
+/**
+ * Parses a MimeMessage and stores the individual parts such a plain text,
+ * HTML text and attachments.
+ *
+ * @since 1.3
+ */
+public class MimeMessageParser
+{
+ /** The MimeMessage to convert */
+ private final MimeMessage mimeMessage;
+
+ /** Plain mail content from MimeMessage */
+ private String plainContent;
+
+ /** Html mail content from MimeMessage */
+ private String htmlContent;
+
+ /** List of attachments of MimeMessage */
+ private final List<DataSource> attachmentList;
+
+ /** Attachments stored by their content-id */
+ private final Map<String, DataSource> cidMap;
+
+ /** Is this a Multipart email */
+ private boolean isMultiPart;
+
+ /**
+ * Constructs an instance with the MimeMessage to be extracted.
+ *
+ * @param message the message to parse
+ */
+ public MimeMessageParser(final MimeMessage message)
+ {
+ attachmentList = new ArrayList<>();
+ cidMap = new HashMap<>();
+ this.mimeMessage = message;
+ this.isMultiPart = false;
+ }
+
+ /**
+ * Does the actual extraction.
+ *
+ * @return this instance
+ * @throws Exception parsing the mime message failed
+ */
+ public MimeMessageParser parse() throws Exception
+ {
+ this.parse(null, mimeMessage);
+ return this;
+ }
+
+ /**
+ * @return the 'to' recipients of the message
+ * @throws Exception determining the recipients failed
+ */
+ public List<jakarta.mail.Address> getTo() throws Exception
+ {
+ final jakarta.mail.Address[] recipients = this.mimeMessage.getRecipients(Message.RecipientType.TO);
+ return recipients != null ? Arrays.asList(recipients) : new ArrayList<jakarta.mail.Address>();
+ }
+
+ /**
+ * @return the 'cc' recipients of the message
+ * @throws Exception determining the recipients failed
+ */
+ public List<jakarta.mail.Address> getCc() throws Exception
+ {
+ final jakarta.mail.Address[] recipients = this.mimeMessage.getRecipients(Message.RecipientType.CC);
+ return recipients != null ? Arrays.asList(recipients) : new ArrayList<jakarta.mail.Address>();
+ }
+
+ /**
+ * @return the 'bcc' recipients of the message
+ * @throws Exception determining the recipients failed
+ */
+ public List<jakarta.mail.Address> getBcc() throws Exception
+ {
+ final jakarta.mail.Address[] recipients = this.mimeMessage.getRecipients(Message.RecipientType.BCC);
+ return recipients != null ? Arrays.asList(recipients) : new ArrayList<jakarta.mail.Address>();
+ }
+
+ /**
+ * @return the 'from' field of the message
+ * @throws Exception parsing the mime message failed
+ */
+ public InternetAddress getFrom() throws Exception
+ {
+ final jakarta.mail.Address[] addresses = this.mimeMessage.getFrom();
+ if (addresses == null || addresses.length == 0)
+ {
+ return null;
+ }
+ return ((InternetAddress) addresses[0]);
+ }
+
+ /**
+ * @return the 'replyTo' address of the email
+ * @throws Exception parsing the mime message failed
+ */
+ public List<jakarta.mail.Address> getReplyTo() throws Exception
+ {
+ final jakarta.mail.Address[] addresses = this.mimeMessage.getReplyTo();
+ return addresses != null ? Arrays.asList(addresses) : new ArrayList<jakarta.mail.Address>();
+ }
+
+ /**
+ * @return the mail subject
+ * @throws Exception parsing the mime message failed
+ */
+ public String getSubject() throws Exception
+ {
+ return this.mimeMessage.getSubject();
+ }
+
+ /**
+ * Extracts the content of a MimeMessage recursively.
+ *
+ * @param parent the parent multi-part
+ * @param part the current MimePart
+ * @throws MessagingException parsing the MimeMessage failed
+ * @throws IOException parsing the MimeMessage failed
+ */
+ protected void parse(final Multipart parent, final MimePart part)
+ throws MessagingException, IOException
+ {
+ if (isMimeType(part, "text/plain") && plainContent == null
+ && !Part.ATTACHMENT.equalsIgnoreCase(part.getDisposition()))
+ {
+ plainContent = (String) part.getContent();
+ }
+ else
+ {
+ if (isMimeType(part, "text/html") && htmlContent == null
+ && !Part.ATTACHMENT.equalsIgnoreCase(part.getDisposition()))
+ {
+ htmlContent = (String) part.getContent();
+ }
+ else
+ {
+ if (isMimeType(part, "multipart/*"))
+ {
+ this.isMultiPart = true;
+ final Multipart mp = (Multipart) part.getContent();
+ final int count = mp.getCount();
+
+ // iterate over all MimeBodyPart
+
+ for (int i = 0; i < count; i++)
+ {
+ parse(mp, (MimeBodyPart) mp.getBodyPart(i));
+ }
+ }
+ else
+ {
+ final String cid = stripContentId(part.getContentID());
+ final DataSource ds = createDataSource(parent, part);
+ if (cid != null)
+ {
+ this.cidMap.put(cid, ds);
+ }
+ this.attachmentList.add(ds);
+ }
+ }
+ }
+ }
+
+ /**
+ * Strips the content id of any whitespace and angle brackets.
+ * @param contentId the string to strip
+ * @return a stripped version of the content id
+ */
+ private String stripContentId(final String contentId)
+ {
+ if (contentId == null)
+ {
+ return null;
+ }
+ return contentId.trim().replaceAll("[\\<\\>]", "");
+ }
+
+ /**
+ * Checks whether the MimePart contains an object of the given mime type.
+ *
+ * @param part the current MimePart
+ * @param mimeType the mime type to check
+ * @return {@code true} if the MimePart matches the given mime type, {@code false} otherwise
+ * @throws MessagingException parsing the MimeMessage failed
+ * @throws IOException parsing the MimeMessage failed
+ */
+ private boolean isMimeType(final MimePart part, final String mimeType)
+ throws MessagingException, IOException
+ {
+ // Do not use part.isMimeType(String) as it is broken for MimeBodyPart
+ // and does not really check the actual content type.
+
+ try
+ {
+ final ContentType ct = new ContentType(part.getDataHandler().getContentType());
+ return ct.match(mimeType);
+ }
+ catch (final ParseException ex)
+ {
+ return part.getContentType().equalsIgnoreCase(mimeType);
+ }
+ }
+
+ /**
+ * Parses the MimePart to create a DataSource.
+ *
+ * @param parent the parent multi-part
+ * @param part the current part to be processed
+ * @return the DataSource
+ * @throws MessagingException creating the DataSource failed
+ * @throws IOException creating the DataSource failed
+ */
+ protected DataSource createDataSource(final Multipart parent, final MimePart part)
+ throws MessagingException, IOException
+ {
+ final DataHandler dataHandler = part.getDataHandler();
+ final DataSource dataSource = dataHandler.getDataSource();
+ final String contentType = getBaseMimeType(dataSource.getContentType());
+ final byte[] content = this.getContent(dataSource.getInputStream());
+ final ByteArrayDataSource result = new ByteArrayDataSource(content, contentType);
+ final String dataSourceName = getDataSourceName(part, dataSource);
+
+ result.setName(dataSourceName);
+ return result;
+ }
+
+ /** @return Returns the mimeMessage. */
+ public MimeMessage getMimeMessage()
+ {
+ return mimeMessage;
+ }
+
+ /** @return Returns the isMultiPart. */
+ public boolean isMultipart()
+ {
+ return isMultiPart;
+ }
+
+ /** @return Returns the plainContent if any */
+ public String getPlainContent()
+ {
+ return plainContent;
+ }
+
+ /** @return Returns the attachmentList. */
+ public List<DataSource> getAttachmentList()
+ {
+ return attachmentList;
+ }
+
+ /**
+ * Returns a collection of all content-ids in the parsed message.
+ * <p>
+ * The content-ids are stripped of any angle brackets, i.e. "part1" instead
+ * of "<part1>".
+ *
+ * @return the collection of content ids.
+ * @since 1.3.4
+ */
+ public Collection<String> getContentIds()
+ {
+ return Collections.unmodifiableSet(cidMap.keySet());
+ }
+
+ /** @return Returns the htmlContent if any */
+ public String getHtmlContent()
+ {
+ return htmlContent;
+ }
+
+ /** @return true if a plain content is available */
+ public boolean hasPlainContent()
+ {
+ return this.plainContent != null;
+ }
+
+ /** @return true if HTML content is available */
+ public boolean hasHtmlContent()
+ {
+ return this.htmlContent != null;
+ }
+
+ /** @return true if attachments are available */
+ public boolean hasAttachments()
+ {
+ return this.attachmentList.size() > 0;
+ }
+
+ /**
+ * Find an attachment using its name.
+ *
+ * @param name the name of the attachment
+ * @return the corresponding datasource or null if nothing was found
+ */
+ public DataSource findAttachmentByName(final String name)
+ {
+ DataSource dataSource;
+
+ for (final DataSource element : getAttachmentList()) {
+ dataSource = element;
+ if (name.equalsIgnoreCase(dataSource.getName()))
+ {
+ return dataSource;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Find an attachment using its content-id.
+ * <p>
+ * The content-id must be stripped of any angle brackets,
+ * i.e. "part1" instead of "<part1>".
+ *
+ * @param cid the content-id of the attachment
+ * @return the corresponding datasource or null if nothing was found
+ * @since 1.3.4
+ */
+ public DataSource findAttachmentByCid(final String cid)
+ {
+ final DataSource dataSource = cidMap.get(cid);
+ return dataSource;
+ }
+
+ /**
+ * Determines the name of the data source if it is not already set.
+ *
+ * @param part the mail part
+ * @param dataSource the data source
+ * @return the name of the data source or {@code null} if no name can be determined
+ * @throws MessagingException accessing the part failed
+ * @throws UnsupportedEncodingException decoding the text failed
+ */
+ protected String getDataSourceName(final Part part, final DataSource dataSource)
+ throws MessagingException, UnsupportedEncodingException
+ {
+ String result = dataSource.getName();
+
+ if (result == null || result.length() == 0)
+ {
+ result = part.getFileName();
+ }
+
+ if (result != null && result.length() > 0)
+ {
+ result = MimeUtility.decodeText(result);
+ }
+ else
+ {
+ result = null;
+ }
+
+ return result;
+ }
+
+ /**
+ * Read the content of the input stream.
+ *
+ * @param is the input stream to process
+ * @return the content of the input stream
+ * @throws IOException reading the input stream failed
+ */
+ private byte[] getContent(final InputStream is)
+ throws IOException
+ {
+ int ch;
+ byte[] result;
+
+ final ByteArrayOutputStream os = new ByteArrayOutputStream();
+ final BufferedInputStream isReader = new BufferedInputStream(is);
+ final BufferedOutputStream osWriter = new BufferedOutputStream(os);
+
+ while ((ch = isReader.read()) != -1)
+ {
+ osWriter.write(ch);
+ }
+
+ osWriter.flush();
+ result = os.toByteArray();
+ osWriter.close();
+
+ return result;
+ }
+
+ /**
+ * Parses the mimeType.
+ *
+ * @param fullMimeType the mime type from the mail api
+ * @return the real mime type
+ */
+ private String getBaseMimeType(final String fullMimeType)
+ {
+ final int pos = fullMimeType.indexOf(';');
+ if (pos >= 0)
+ {
+ return fullMimeType.substring(0, pos);
+ }
+ return fullMimeType;
+ }
+}