Search code examples
javajakarta-mailmimesmime

Create multipart using JavaMail


I would like to create a MimeMessage which must have two parts as shown below picture (Part_0 and Part_2)

example s/mime

I'm trying to use below code to generated s/mime

public static void main(String[] a) throws Exception {

    // create some properties and get the default Session
    Properties props = new Properties();
    // props.put("mail.smtp.host", host);

    Session session = Session.getInstance(props, null);
    // session.setDebug(debug);
    MimeMessage msg = new MimeMessage(session);
    try {

        msg.addHeader("Content-Type", "multipart/signed; protocol=\"application/pkcs7-signature;");

        msg.setFrom(new InternetAddress(FROM_EMAIL));
        InternetAddress[] address = {new InternetAddress(
            TO_EMAIL)};
        msg.setRecipients(Message.RecipientType.TO, address);
        msg.setSubject("Test Subject");
        msg.setSentDate(new Date());

        // create and fill the first message part
        MimeBodyPart bodyPart1 = new MimeBodyPart();

        bodyPart1.setHeader("Content-Type", "text/html; charset=utf-8");
        bodyPart1.setContent("<b>Hello World</b>", "text/html");

        Multipart multiPart = new MimeMultipart();
        multiPart.addBodyPart(bodyPart1, 0);

        try (OutputStream str = Files.newOutputStream(Paths
                .get(UNSIGNED_MIME))) {
            bodyPart1.writeTo(str);
        }

        signMime();

        MimeBodyPart attachPart = new MimeBodyPart();

        String filename = SIGNED_VALUE;

        DataSource source = new FileDataSource(filename);

        attachPart.setDataHandler(new DataHandler(source));
        attachPart.setFileName("smime.p7s");
        attachPart.addHeader("Content-Type", "application/pkcs7-signature; name=smime.p7s;");
        attachPart.addHeader("Content-Transfer-Encoding", "base64");
        attachPart.addHeader("Content-Description", "S/MIME Cryptographic Signature");

        multiPart.addBodyPart(attachPart);

        msg.setContent(multiPart, "multipart/signed; protocol=\"application/pkcs7-signature\"; ");

        msg.saveChanges();
        try (OutputStream str = Files.newOutputStream(Paths
                .get(SIGNED_MIME))) {
            msg.writeTo(str);
        }

    } catch (MessagingException mex) {
        mex.printStackTrace();
        Exception ex = null;
        if ((ex = mex.getNextException()) != null) {
            ex.printStackTrace();
        }
    }

I used two MimeBodyPart however I always got one Part_0 and generated eml file shown below.

enter image description here


Solution

  • I haven't tried to compile it, but what you want is something like this. The inner multipart is a body part of the outer multipart.

        msg.setFrom(new InternetAddress(FROM_EMAIL));
        InternetAddress[] address = {new InternetAddress(
            TO_EMAIL)};
        msg.setRecipients(Message.RecipientType.TO, address);
        msg.setSubject("Test Subject");
        msg.setSentDate(new Date());
    
        MultipartSigned multiSigned = new MultipartSigned();
    
        // create and fill the first message part
        MimeBodyPart bodyPart1 = new MimeBodyPart();
    
        bodyPart1.setText("<b>Hello World</b>", "utf-8", "html");
    
        Multipart multiPart = new MimeMultipart();
        multiPart.addBodyPart(bodyPart1);
    
        // add other content to the inner multipart here
    
        MimeBodyPart body = new MimeBodyPart();
        body.setContent(multiPart);
        multiSigned.addBodyPart(body);
    
        try (OutputStream str = Files.newOutputStream(Paths
                .get(UNSIGNED_MIME))) {
            body.writeTo(str);
        }
    
        signMime();
    
        MimeBodyPart attachPart = new MimeBodyPart();
    
        String filename = SIGNED_VALUE;
    
        attachPart.attachFile(filename,
                "application/pkcs7-signature; name=smime.p7s", "base64");
        attachPart.setFileName("smime.p7s");
        attachPart.addHeader("Content-Description",
                "S/MIME Cryptographic Signature");
    
        multiSigned.addBodyPart(attachPart);
        msg.setContent(multiSigned);
    
        msg.saveChanges();
    

    And you'll need this:

    public class MultipartSigned extends MimeMultipart {
        public MultipartSigned() {
            super("signed");
            ContentType ct = new ContentType(contentType);
            ct.setParameter("protocol", "application/pkcs7-signature");
            contentType = ct.toString();
        }
    }
    

    You could make this much cleaner by moving more of the functionality into the MultipartSigned class.