org.bouncycastle.mail.smime.SMIMEUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.bouncycastle.mail.smime.SMIMEUtil.java

Source

package org.bouncycastle.mail.smime;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.Enumeration;

import javax.mail.BodyPart;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Part;
import javax.mail.internet.ContentType;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMultipart;

import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
import org.bouncycastle.cms.CMSTypedStream;
import org.bouncycastle.mail.smime.util.CRLFOutputStream;
import org.bouncycastle.mail.smime.util.FileBackedMimeBodyPart;
import org.bouncycastle.util.Strings;

public class SMIMEUtil {
    private static final String MULTIPART = "multipart";
    private static final int BUF_SIZE = 32760;

    public static boolean isMultipartContent(Part part) throws MessagingException {
        String partType = Strings.toLowerCase(part.getContentType());

        return partType.startsWith(MULTIPART);
    }

    static boolean isCanonicalisationRequired(MimeBodyPart bodyPart, String defaultContentTransferEncoding)
            throws MessagingException {
        String[] cte = bodyPart.getHeader("Content-Transfer-Encoding");
        String contentTransferEncoding;

        if (cte == null) {
            contentTransferEncoding = defaultContentTransferEncoding;
        } else {
            contentTransferEncoding = cte[0];
        }

        return !contentTransferEncoding.equalsIgnoreCase("binary");
    }

    static class LineOutputStream extends FilterOutputStream {
        private static byte newline[];

        public LineOutputStream(OutputStream outputstream) {
            super(outputstream);
        }

        public void writeln(String s) throws MessagingException {
            try {
                byte abyte0[] = getBytes(s);
                super.out.write(abyte0);
                super.out.write(newline);
            } catch (Exception exception) {
                throw new MessagingException("IOException", exception);
            }
        }

        public void writeln() throws MessagingException {
            try {
                super.out.write(newline);
            } catch (Exception exception) {
                throw new MessagingException("IOException", exception);
            }
        }

        static {
            newline = new byte[2];
            newline[0] = 13;
            newline[1] = 10;
        }

        private static byte[] getBytes(String s) {
            char ac[] = s.toCharArray();
            int i = ac.length;
            byte abyte0[] = new byte[i];
            int j = 0;

            while (j < i) {
                abyte0[j] = (byte) ac[j++];
            }

            return abyte0;
        }
    }

    /**
     * internal preamble is generally included in signatures, while this is technically wrong,
     * if we find internal preamble we include it by default.
     */
    static void outputPreamble(LineOutputStream lOut, MimeBodyPart part, String boundary)
            throws MessagingException, IOException {
        InputStream in;

        try {
            in = part.getRawInputStream();
        } catch (MessagingException e) {
            return; // no underlying content rely on default generation
        }

        String line;

        while ((line = readLine(in)) != null) {
            if (line.equals(boundary)) {
                break;
            }

            lOut.writeln(line);
        }

        in.close();

        if (line == null) {
            throw new MessagingException("no boundary found");
        }
    }

    /**
     * internal postamble is generally included in signatures, while this is technically wrong,
     * if we find internal postamble we include it by default.
     */
    static void outputPostamble(LineOutputStream lOut, MimeBodyPart part, int count, String boundary)
            throws MessagingException, IOException {
        InputStream in;

        try {
            in = part.getRawInputStream();
        } catch (MessagingException e) {
            return; // no underlying content rely on default generation
        }

        String line;
        int boundaries = count + 1;

        while ((line = readLine(in)) != null) {
            if (line.startsWith(boundary)) {
                boundaries--;

                if (boundaries == 0) {
                    break;
                }
            }
        }

        while ((line = readLine(in)) != null) {
            lOut.writeln(line);
        }

        in.close();

        if (boundaries != 0) {
            throw new MessagingException("all boundaries not found for: " + boundary);
        }
    }

    static void outputPostamble(LineOutputStream lOut, BodyPart parent, String parentBoundary, BodyPart part)
            throws MessagingException, IOException {
        InputStream in;

        try {
            in = ((MimeBodyPart) parent).getRawInputStream();
        } catch (MessagingException e) {
            return; // no underlying content rely on default generation
        }

        MimeMultipart multipart = (MimeMultipart) part.getContent();
        ContentType contentType = new ContentType(multipart.getContentType());
        String boundary = "--" + contentType.getParameter("boundary");
        int count = multipart.getCount() + 1;
        String line;
        while (count != 0 && (line = readLine(in)) != null) {
            if (line.startsWith(boundary)) {
                count--;
            }
        }

        while ((line = readLine(in)) != null) {
            if (line.startsWith(parentBoundary)) {
                break;
            }
            lOut.writeln(line);
        }

        in.close();
    }

    /*
     * read a line of input stripping of the tailing \r\n
     */
    private static String readLine(InputStream in) throws IOException {
        StringBuffer b = new StringBuffer();

        int ch;
        while ((ch = in.read()) >= 0 && ch != '\n') {
            if (ch != '\r') {
                b.append((char) ch);
            }
        }

        if (ch < 0 && b.length() == 0) {
            return null;
        }

        return b.toString();
    }

    static void outputBodyPart(OutputStream out, boolean topLevel, BodyPart bodyPart,
            String defaultContentTransferEncoding) throws MessagingException, IOException {
        if (bodyPart instanceof MimeBodyPart) {
            MimeBodyPart mimePart = (MimeBodyPart) bodyPart;
            String[] cte = mimePart.getHeader("Content-Transfer-Encoding");
            String contentTransferEncoding;

            if (isMultipartContent(mimePart)) {
                Object content = bodyPart.getContent();
                Multipart mp;
                if (content instanceof Multipart) {
                    mp = (Multipart) content;
                } else {
                    mp = new MimeMultipart(bodyPart.getDataHandler().getDataSource());
                }

                ContentType contentType = new ContentType(mp.getContentType());
                String boundary = "--" + contentType.getParameter("boundary");

                SMIMEUtil.LineOutputStream lOut = new SMIMEUtil.LineOutputStream(out);

                Enumeration headers = mimePart.getAllHeaderLines();
                while (headers.hasMoreElements()) {
                    String header = (String) headers.nextElement();
                    lOut.writeln(header);
                }

                lOut.writeln(); // CRLF separator

                outputPreamble(lOut, mimePart, boundary);

                for (int i = 0; i < mp.getCount(); i++) {
                    lOut.writeln(boundary);
                    BodyPart part = mp.getBodyPart(i);
                    outputBodyPart(out, false, part, defaultContentTransferEncoding);
                    if (!isMultipartContent(part)) {
                        lOut.writeln(); // CRLF terminator needed
                    } else { // output nested preamble
                        outputPostamble(lOut, mimePart, boundary, part);
                    }
                }

                lOut.writeln(boundary + "--");

                if (topLevel) {
                    outputPostamble(lOut, mimePart, mp.getCount(), boundary);
                }

                return;
            }

            if (cte == null) {
                contentTransferEncoding = defaultContentTransferEncoding;
            } else {
                contentTransferEncoding = cte[0];
            }

            if (!contentTransferEncoding.equalsIgnoreCase("base64")
                    && !contentTransferEncoding.equalsIgnoreCase("quoted-printable")) {
                if (!contentTransferEncoding.equalsIgnoreCase("binary")) {
                    out = new CRLFOutputStream(out);
                }
                bodyPart.writeTo(out);
                out.flush();
                return;
            }

            boolean base64 = contentTransferEncoding.equalsIgnoreCase("base64");

            //
            // Write raw content, performing canonicalization
            //
            InputStream inRaw;

            try {
                inRaw = mimePart.getRawInputStream();
            } catch (MessagingException e) {
                // this is less than ideal, but if the raw output stream is unavailable it's the
                // best option we've got.
                out = new CRLFOutputStream(out);
                bodyPart.writeTo(out);
                out.flush();
                return;
            }

            //
            // Write headers
            //
            LineOutputStream outLine = new LineOutputStream(out);
            for (Enumeration e = mimePart.getAllHeaderLines(); e.hasMoreElements();) {
                String header = (String) e.nextElement();

                outLine.writeln(header);
            }

            outLine.writeln();
            outLine.flush();

            OutputStream outCRLF;

            if (base64) {
                outCRLF = new Base64CRLFOutputStream(out);
            } else {
                outCRLF = new CRLFOutputStream(out);
            }

            byte[] buf = new byte[BUF_SIZE];

            int len;
            while ((len = inRaw.read(buf, 0, buf.length)) > 0) {

                outCRLF.write(buf, 0, len);
            }

            inRaw.close();

            outCRLF.flush();
        } else {
            if (!defaultContentTransferEncoding.equalsIgnoreCase("binary")) {
                out = new CRLFOutputStream(out);
            }

            bodyPart.writeTo(out);

            out.flush();
        }
    }

    /**
     * return the MimeBodyPart described in the raw bytes provided in content
     */
    public static MimeBodyPart toMimeBodyPart(byte[] content) throws SMIMEException {
        return toMimeBodyPart(new ByteArrayInputStream(content));
    }

    /**
     * return the MimeBodyPart described in the input stream content
     */
    public static MimeBodyPart toMimeBodyPart(InputStream content) throws SMIMEException {
        try {
            return new MimeBodyPart(content);
        } catch (MessagingException e) {
            throw new SMIMEException("exception creating body part.", e);
        }
    }

    static FileBackedMimeBodyPart toWriteOnceBodyPart(CMSTypedStream content) throws SMIMEException {
        try {
            return new WriteOnceFileBackedMimeBodyPart(content.getContentStream(),
                    File.createTempFile("bcMail", ".mime"));
        } catch (IOException e) {
            throw new SMIMEException("IOException creating tmp file:" + e.getMessage(), e);
        } catch (MessagingException e) {
            throw new SMIMEException("can't create part: " + e, e);
        }
    }

    /**
     * return a file backed MimeBodyPart described in {@link CMSTypedStream} content.
     */
    public static FileBackedMimeBodyPart toMimeBodyPart(CMSTypedStream content) throws SMIMEException {
        try {
            return toMimeBodyPart(content, File.createTempFile("bcMail", ".mime"));
        } catch (IOException e) {
            throw new SMIMEException("IOException creating tmp file:" + e.getMessage(), e);
        }
    }

    /**
     * Return a file based MimeBodyPart represented by content and backed
     * by the file represented by file.
     * 
     * @param content content stream containing body part.
     * @param file file to store the decoded body part in.
     * @return the decoded body part.
     * @throws SMIMEException
     */
    public static FileBackedMimeBodyPart toMimeBodyPart(CMSTypedStream content, File file) throws SMIMEException {
        try {
            return new FileBackedMimeBodyPart(content.getContentStream(), file);
        } catch (IOException e) {
            throw new SMIMEException("can't save content to file: " + e, e);
        } catch (MessagingException e) {
            throw new SMIMEException("can't create part: " + e, e);
        }
    }

    /**
     * Return a CMS IssuerAndSerialNumber structure for the passed in X.509 certificate.
     * 
     * @param cert the X.509 certificate to get the issuer and serial number for.
     * @return an IssuerAndSerialNumber structure representing the certificate.
     */
    public static IssuerAndSerialNumber createIssuerAndSerialNumberFor(X509Certificate cert)
            throws CertificateParsingException {
        try {
            return new IssuerAndSerialNumber(new JcaX509CertificateHolder(cert).getIssuer(),
                    cert.getSerialNumber());
        } catch (Exception e) {
            throw new CertificateParsingException("exception extracting issuer and serial number: " + e);
        }
    }

    private static class WriteOnceFileBackedMimeBodyPart extends FileBackedMimeBodyPart {
        public WriteOnceFileBackedMimeBodyPart(InputStream content, File file)
                throws MessagingException, IOException {
            super(content, file);
        }

        public void writeTo(OutputStream out) throws MessagingException, IOException {
            super.writeTo(out);

            this.dispose();
        }
    }

    static class Base64CRLFOutputStream extends FilterOutputStream {
        protected int lastb;
        protected static byte newline[];
        private boolean isCrlfStream;

        public Base64CRLFOutputStream(OutputStream outputstream) {
            super(outputstream);
            lastb = -1;
        }

        public void write(int i) throws IOException {
            if (i == '\r') {
                out.write(newline);
            } else if (i == '\n') {
                if (lastb != '\r') { // imagine my joy...
                    if (!(isCrlfStream && lastb == '\n')) {
                        out.write(newline);
                    }
                } else {
                    isCrlfStream = true;
                }
            } else {
                out.write(i);
            }

            lastb = i;
        }

        public void write(byte[] buf) throws IOException {
            this.write(buf, 0, buf.length);
        }

        public void write(byte buf[], int off, int len) throws IOException {
            for (int i = off; i != off + len; i++) {
                this.write(buf[i]);
            }
        }

        public void writeln() throws IOException {
            super.out.write(newline);
        }

        static {
            newline = new byte[2];
            newline[0] = '\r';
            newline[1] = '\n';
        }
    }
}