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:20 UTC

[sling-org-apache-sling-clam] branch master updated (fb0e682 -> 2e50735)

This is an automated email from the ASF dual-hosted git repository.

olli pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-clam.git.


    from fb0e682  SLING-11226 Use Commons Threads 3.2.22
     new 0dce4ea  update test dependencies
     new 2e50735  SLING-10993 Update Sling Commons Messaging Mail to 2.0.0

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 bnd.bnd                                            |  14 +-
 pom.xml                                            |  22 +-
 .../internal/MailSendingScanResultHandler.java     |   2 +-
 .../it/tests/MailSendingScanResultHandlerIT.java   |  10 +-
 .../org/commons/mail/util/MimeMessageParser.java   | 451 +++++++++++++++++++++
 5 files changed, 469 insertions(+), 30 deletions(-)
 create mode 100644 src/test/java/org/commons/mail/util/MimeMessageParser.java

[sling-org-apache-sling-clam] 01/02: update test dependencies

Posted by ol...@apache.org.
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 0dce4ea9ce0abb89d793a62d894f4acccd4cfbac
Author: Oliver Lietz <ol...@apache.org>
AuthorDate: Thu Mar 31 23:18:48 2022 +0200

    update test dependencies
---
 pom.xml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/pom.xml b/pom.xml
index 55574d9..dc1f0ad 100644
--- a/pom.xml
+++ b/pom.xml
@@ -252,7 +252,7 @@
     <dependency>
       <groupId>org.apache.felix</groupId>
       <artifactId>org.apache.felix.framework</artifactId>
-      <version>7.0.1</version>
+      <version>7.0.3</version>
       <scope>test</scope>
     </dependency>
     <!-- Apache Jackrabbit -->
@@ -381,7 +381,7 @@
     <dependency>
       <groupId>org.testcontainers</groupId>
       <artifactId>testcontainers</artifactId>
-      <version>1.16.2</version>
+      <version>1.16.3</version>
       <scope>test</scope>
     </dependency>
     <dependency>
@@ -436,7 +436,7 @@
     <dependency>
       <groupId>com.google.guava</groupId>
       <artifactId>guava</artifactId>
-      <version>31.0.1-jre</version>
+      <version>31.1-jre</version>
       <scope>test</scope>
     </dependency>
     <dependency>

[sling-org-apache-sling-clam] 02/02: SLING-10993 Update Sling Commons Messaging Mail to 2.0.0

Posted by ol...@apache.org.
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;
+    }
+}