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 "&lt;part1&gt;".
+     *
+     * @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 "&lt;part1&gt;".
+     *
+     * @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;
+    }
+}