bbottema / simple-java-mail

Simple API, Complex Emails (Jakarta Mail smtp wrapper)
http://www.simplejavamail.org
Apache License 2.0
1.22k stars 266 forks source link

Trying to add a application/x-xz; charset=binary (tar.xz) attachment ... #536

Closed alan-vos closed 3 months ago

alan-vos commented 3 months ago

I'm unable to attach a x-gzip attachment. You'd think a zip file, gzip file, essentially a compressed file attachment is a relatively common use-case.

I've looked at TestEmail and have tried my combinations. Here are a couple of examples that do not work.

                //.withAttachment("Log file", new FileDataSource("log.gz"), "Attached log", ContentTransferEncoding.BASE64)
                //.withAttachment("Log file", new FileDataSource("log.gz"), "Attached log", ContentTransferEncoding.BINARY)
                //.withAttachment("Log file",  readBytesFromFile("log.gz"), "application/x-gzip")
                .buildEmail();

I've verified that log.gz does in fact exist in the current directory and is in fact a gzip.

The error I get back is:

jakarta.mail.MessagingException: IOException while sending message;
  nested exception is:
    jakarta.activation.UnsupportedDataTypeException: no object DCH for MIME type multipart/mixed;
    boundary="----=_Part_0_1138193439.1718248442284"

Reading pass issues, this generally means a missing (or unable) to read a mailcap. However, the mailcap exists. Now, I do not see a mailcap for x-gzip MIME.

Can anyone provide a "simple" working example, please?

bbottema commented 3 months ago

Yes, not all types are recognized by default in Jakarta Activation. For example when applying S/MIME, I have to add a bunch of types as well, same for text/calendar when parsing MimeMessage files.

In your case, the x-gzip is missing. Luckily, it's easy to add:

MailcapCommandMap mc = (MailcapCommandMap) CommandMap.getDefaultCommandMap();
mc.addMailcap("application/x-gzip;; x-java-content-handler=com.sun.mail.handlers.multipart_mixed");
CommandMap.setDefaultCommandMap(mc);

Let me know if this helped you. In the meantime, I'll think about adding it in Simple Java Mail as well.

I should note that x-gzip seems to be an odd occurrence from what I read (ranging from experimental to obsolete). Are you using an older archiving program?

alan-vos commented 3 months ago

Thank you for the quick reply. I like the implementation (using fluent API) and really wanted to use your library.

I believe attaching compressed archives (zip, gzip, tar, etc ..) is a fairly common use case among the use cases you've already implemented.

I will try out this code now and report back.

alan-vos commented 3 months ago

Ok, unfortunately -- no joy.

I'm using the gzip that has worked with all Unix platforms for years. It's nothing special -- pretty vanilla.

        final MailcapCommandMap mc = (MailcapCommandMap) CommandMap.getDefaultCommandMap();
        mc.addMailcap("application/x-gzip; x-java-content-handler=com.sun.mail.handlers.multipart_mixed");
        CommandMap.setDefaultCommandMap(mc);

        final byte[] bytes = readBytesFromFile("log.tar.gz");
        final Email email = EmailBuilder.startingBlank()    
                .from(...)
                .to(...)
                .withSubject("Test email gzip attachment")
                .withPlainText("See attached gzip")
                .withAttachment("log", bytes, "application/x-gzip")
                .buildEmail();

with same error:

jakarta.mail.MessagingException: IOException while sending message; nested exception is: jakarta.activation.UnsupportedDataTypeException: no object DCH for MIME type multipart/mixed; boundary="----=_Part_0_852445367.1718255155787"

alan-vos commented 3 months ago

Oh, I believe, I've made a big mistake! What I thought was a gzip file is actually a "application/x-xz; charset=binary" log.tar.xz file!

alan-vos commented 3 months ago

Ok, now that I've corrected my goof-up ...

        final MailcapCommandMap mc = (MailcapCommandMap) CommandMap.getDefaultCommandMap();
        //mc.addMailcap("application/x-gzip; x-java-content-handler=com.sun.mail.handlers.multipart_mixed");
        mc.addMailcap("application/x-xz; charset=binary; x-java-content-handler=com.sun.mail.handlers.multipart_mixed");
        CommandMap.setDefaultCommandMap(mc);

        final byte[] bytes = readBytesFromFile("log.tar.xz");
        final Email email = EmailBuilder.startingBlank()
                .from(...)
                .to(...)
                .withSubject("Test Email")
                .withPlainText("log.tar.xz")
                .withAttachment("log.tar.xz", bytes, "application/x-xz; charset=binary")
                .buildEmail();

However, the error is still the same ...

jakarta.mail.MessagingException: IOException while sending message; nested exception is: jakarta.activation.UnsupportedDataTypeException: no object DCH for MIME type multipart/mixed; boundary="----=_Part_0_852445367.1718256585518"

So, just in case ... The following code is currently working using the log.tar.xz file.

            final MimeBodyPart attachmentBodyPart = new MimeBodyPart();
            attachmentBodyPart.attachFile(new File("logs.tar.xz"));
            multipart.addBodyPart(attachmentBodyPart);         
            message.setContent(multipart);
            Transport.send(message);
bbottema commented 3 months ago

Can you give this a go?

final MailcapCommandMap mc = (MailcapCommandMap) CommandMap.getDefaultCommandMap();
mc.addMailcap("application/x-xz;; x-java-content-handler=com.sun.mail.handlers.message_rfc822");
CommandMap.setDefaultCommandMap(mc);

It uses a more generic data handler that can handle binary data. If still no luck, please provide a test archive so I can reproduce the issue.

alan-vos commented 3 months ago

Still same error

        final MailcapCommandMap mc = (MailcapCommandMap) CommandMap.getDefaultCommandMap();
        mc.addMailcap("application/x-xz;; x-java-content-handler=com.sun.mail.handlers.message_rfc822");
        CommandMap.setDefaultCommandMap(mc);

        final byte[] bytes = readBytesFromFile("log.tar.xz");
        final Email email = EmailBuilder.startingBlank()
                .from(...)
                .to(...)
                .withSubject("Test Email from Gmail")
                .withPlainText("Helloworld")               
                .withAttachment("log", bytes, "application/x-xz")
                .buildEmail();

jakarta.mail.MessagingException: IOException while sending message; nested exception is: jakarta.activation.UnsupportedDataTypeException: no object DCH for MIME type multipart/mixed; boundary="----=_Part_0_1935122449.1718281067735

bbottema commented 3 months ago

So can you provide a test case for me? That would help me a lot.

alan-vos commented 3 months ago

I believe I understand the problem a bit more. I'm using maven with the shade jar plugin. I suspect it might be causing the headaches. In the use-case, I purposefully used an all inclusive maven-assembly-plugin with jar-with-dependencies. Which includes every project dependency.

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>${maven-assembly-plugin.version}</version>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <mainClass>${entrypoint.class}</mainClass>
                        </manifest>
                    </archive>
                </configuration>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

So, this worked! However, how do I force the maven shaded plugin to include the jakarta activation framework? This currently is puzzling me. Any ideas how to do that?

alan-vos commented 3 months ago

After some further investigation and I hope it helps someone, the following dependencies are required. In my case, jarkarta along with the contents in META-INF/ that pertain to jakarta mail/activation.

screenshot_504

So, the following code worked as long as the assembly plugin was used with jar-with dependencies.

public final class EntryPoint {

    public static void main(String[] args) throws IOException {
        final String filename = "log";
        final String directory = "tmp-test";
        final Path outputFilenamePath = Paths.get(filename + ".tar.xz");

        final String user = "";
        final String passwd = "";

        // Generate dummy archive
        generateTestArchive(filename, directory);

        // Simple Java Mail code
        final MailcapCommandMap mc = (MailcapCommandMap) CommandMap.getDefaultCommandMap();
        mc.addMailcap("application/x-xz;; x-java-content-handler=com.sun.mail.handlers.message_rfc822");
        CommandMap.setDefaultCommandMap(mc);

        // Read bytes from archive file tar.xz
        final byte[] bytes = Files.readAllBytes(outputFilenamePath);

        // The email content
        final Email email = EmailBuilder.startingBlank()
                // Might be a good idea to get real email addresses, othewise gmail complains.
                .from("From Test User", "testuser@gmail.com")
                .to("To Test User", "testuser@gmail.com")
                .withSubject("Test Email from Gmail")
                .withPlainText("Test attachment")
                .withAttachment("log", bytes, "application/x-xz")
                .buildEmail();
        // Send mail configuration
        try (final Mailer mailer = MailerBuilder
                .withSMTPServer("smtp.gmail.com", 587, user, passwd)
                .withTransportStrategy(TransportStrategy.SMTP_TLS)
                .buildMailer()) {
            mailer.sendMail(email);
        } catch (Exception e) {
            Logger.error(e);
        } finally {
            FileUtils.delete(outputFilenamePath.toFile());
        }
    }
}
alan-vos commented 3 months ago

Just in case someone uses the maven-shade-plugin -- this worked for me (cleanly). Keep in mind, I'm using the latest JDK8 to date.

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>${maven.shade.version}</version>
                <executions>
                    <execution>
                        <id>second-shade</id>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <minimizeJar>true</minimizeJar>
                            <createDependencyReducedPom>false</createDependencyReducedPom>
                            <outputFile>${project.build.directory}/${executable.name}.jar</outputFile>
                            <filters>
                                <filter>
                                    <artifact>org.eclipse.angus:*</artifact>
                                    <includes>
                                        <include>**</include>
                                    </includes>
                                </filter>
                                <filter>
                                    <artifact>jakarta.mail:*</artifact>
                                    <includes>
                                        <include>**</include>
                                    </includes>
                                </filter>
                                <filter>
                                    <artifact>jakarta.activation:*</artifact>
                                    <includes>
                                        <include>**</include>
                                    </includes>
                                </filter>
                                <filter>
                                    <artifact>org.simplejavamail:*</artifact>
                                    <includes>
                                        <include>**</include>
                                    </includes>
                                </filter>
                                <filter>
                                    <artifact>org.tinylog:*</artifact>
                                    <includes>
                                        <include>**</include>
                                    </includes>
                                </filter>
                                <filter>
                                    <artifact>*:*</artifact>
                                    <excludes>
                                        <exclude>**/module-info.class</exclude>
                                    </excludes>
                                </filter>
                                <filter>
                                    <artifact>*:*</artifact>
                                    <excludes>
                                        <exclude>META-INF/MANIFEST.MF</exclude>
                                        <exclude>META-INF/LICENSE*</exclude>
                                        <exclude>META-INF/license/**</exclude>
                                        <exclude>META-INF/maven/**</exclude>
                                        <exclude>LICENSE*</exclude>
                                        <exclude>RELEASE*</exclude>
                                        <exclude>NOTICE*</exclude>
                                        <exclude>/*.txt</exclude>
                                        <exclude>build.properties</exclude>
                                    </excludes>
                                </filter>
                            </filters>
                            <transformers>
                                <transformer
                                        implementation="org.apache.maven.plugins.shade.resource.ApacheNoticeResourceTransformer">
                                    <addHeader>true</addHeader>
                                </transformer>
                                <transformer
                                        implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <manifestEntries>
                                        <Main-Class>${entrypoint.class}</Main-Class>
                                        <Build-Number>${project.version}</Build-Number>
                                    </manifestEntries>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
alan-vos commented 3 months ago

Everything is working as designed as per my issue.