Rfc822Output.java revision f2dded3a2fba83dd3f0d14cce6abe467a4ab66eb
1a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler/*
2a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * Copyright (C) 2009 The Android Open Source Project
3a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler *
4a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * Licensed under the Apache License, Version 2.0 (the "License");
5a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * you may not use this file except in compliance with the License.
6a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * You may obtain a copy of the License at
7a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler *
8a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler *      http://www.apache.org/licenses/LICENSE-2.0
9a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler *
10a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * Unless required by applicable law or agreed to in writing, software
11a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * distributed under the License is distributed on an "AS IS" BASIS,
12a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * See the License for the specific language governing permissions and
14a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * limitations under the License.
15a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler */
16a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
17a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadlerpackage com.android.email.mail.transport;
18a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
19f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blankimport com.android.email.R;
20a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadlerimport com.android.email.codec.binary.Base64;
21c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadlerimport com.android.email.codec.binary.Base64OutputStream;
22a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadlerimport com.android.email.mail.Address;
23c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadlerimport com.android.email.mail.MessagingException;
24a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadlerimport com.android.email.mail.internet.MimeUtility;
25c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadlerimport com.android.email.provider.EmailContent.Attachment;
26a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadlerimport com.android.email.provider.EmailContent.Body;
27a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadlerimport com.android.email.provider.EmailContent.Message;
28a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
29c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadlerimport org.apache.commons.io.IOUtils;
30c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler
31c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadlerimport android.content.ContentUris;
32a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadlerimport android.content.Context;
33c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadlerimport android.database.Cursor;
34c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadlerimport android.net.Uri;
35a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
36a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadlerimport java.io.BufferedOutputStream;
37c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadlerimport java.io.FileNotFoundException;
38a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadlerimport java.io.IOException;
39c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadlerimport java.io.InputStream;
40a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadlerimport java.io.OutputStream;
41a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadlerimport java.io.OutputStreamWriter;
42a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadlerimport java.io.Writer;
43a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadlerimport java.text.SimpleDateFormat;
44a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadlerimport java.util.Date;
45a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadlerimport java.util.Locale;
46f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blankimport java.util.regex.Matcher;
47f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blankimport java.util.regex.Pattern;
48a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
49a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler/**
50a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * Utility class to output RFC 822 messages from provider email messages
51a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler */
52a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadlerpublic class Rfc822Output {
53a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
54f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank    private static final Pattern PATTERN_START_OF_LINE = Pattern.compile("(?m)^");
55f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank    private static final Pattern PATTERN_ENDLINE_CRLF = Pattern.compile("\r\n");
56f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank
57a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler    // In MIME, en_US-like date format should be used. In other words "MMM" should be encoded to
58a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler    // "Jan", not the other localized format like "Ene" (meaning January in locale es).
59a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler    static final SimpleDateFormat mDateFormat =
60a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.US);
61a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
62f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank    /*package*/ static String buildBodyText(Context context, Message message) {
63f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank        int flags = message.mFlags;
64f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank        Body body = Body.restoreBodyWithMessageId(context, message.mId);
65f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank        String text = body.mTextContent;
66f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank
67f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank        String quotedText = body.mTextReply;
68f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank        if (quotedText != null) {
69f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank            // fix CR-LF line endings to LF-only needed by EditText.
70f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank            Matcher matcher = PATTERN_ENDLINE_CRLF.matcher(quotedText);
71f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank            quotedText = matcher.replaceAll("\n");
72f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank        }
73f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank        String fromAsString = Address.unpackToString(message.mFrom);
74f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank        if ((flags & Message.FLAG_TYPE_REPLY) != 0) {
75f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank            text += context.getString(R.string.message_compose_reply_header_fmt, fromAsString);
76f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank            if (quotedText != null) {
77f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank                Matcher matcher = PATTERN_START_OF_LINE.matcher(quotedText);
78f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank                text += matcher.replaceAll(">");
79f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank            }
80f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank        } else if ((flags & Message.FLAG_TYPE_FORWARD) != 0) {
81f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank            String subject = message.mSubject;
82f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank            String to = Address.unpackToString(message.mTo);
83f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank            String cc = Address.unpackToString(message.mCc);
84f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank            text += context.getString(R.string.message_compose_fwd_header_fmt, subject,
85f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank                    fromAsString, to != null ? to : "", cc != null ? cc : "");
86f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank            if (quotedText != null) {
87f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank                text += quotedText;
88f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank            }
89f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank        }
90f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank        return text;
91f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank    }
92f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank
93a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler    /**
94a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * Write the entire message to an output stream.  This method provides buffering, so it is
95a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * not necessary to pass in a buffered output stream here.
96a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     *
97a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * @param context system context for accessing the provider
98a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * @param messageId the message to write out
99a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * @param out the output stream to write the message to
100a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     *
101a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * TODO is there anything in the flags fields we need to look at?
102a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * TODO alternative parts (e.g. text+html) are not supported here.
103a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     */
104a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler    public static void writeTo(Context context, long messageId, OutputStream out)
105c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler            throws IOException, MessagingException {
106a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        Message message = Message.restoreMessageWithId(context, messageId);
107a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        if (message == null) {
108a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler            // throw something?
109a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler            return;
110a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        }
111a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
112a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        OutputStream stream = new BufferedOutputStream(out, 1024);
113a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        Writer writer = new OutputStreamWriter(stream);
114a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
115a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        // Write the fixed headers.  Ordering is arbitrary (the legacy code iterated through a
116a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        // hashmap here).
117a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
118a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        String date = mDateFormat.format(new Date(message.mTimeStamp));
119a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        writeHeader(writer, "Date", date);
120a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
121a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        writeEncodedHeader(writer, "Subject", message.mSubject);
122a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
123a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        writeHeader(writer, "Message-ID", message.mMessageId);
124a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
125a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        // Address fields.  Note, obviously, we skip bcc here
126a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        writeAddressHeader(writer, "From", message.mFrom);
127a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        writeAddressHeader(writer, "To", message.mTo);
128a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        writeAddressHeader(writer, "Cc", message.mCc);
129a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        writeAddressHeader(writer, "Reply-To", message.mReplyTo);
130a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
131a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        // Analyze message and determine if we have multiparts
132f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank        String text = buildBodyText(context, message);
133c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler
134c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler        Uri uri = ContentUris.withAppendedId(Attachment.MESSAGE_ID_URI, messageId);
135c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler        Cursor attachmentsCursor = context.getContentResolver().query(uri,
136c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler                Attachment.CONTENT_PROJECTION, null, null, null);
137c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler
138c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler        try {
139c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler            boolean mixedParts = attachmentsCursor.getCount() > 0;
140c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler            String mixedBoundary = null;
141a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
142c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler            // Simplified case for no multipart - just emit text and be done.
143c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler            if (!mixedParts) {
144c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler                if (text != null) {
145c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler                    writeTextWithHeaders(writer, stream, text);
146c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler                } else {
147c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler                    writer.write("\r\n");       // a truly empty message
148c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler                }
149c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler            } else {
150c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler                // continue with multipart headers, then into multipart body
151c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler                writeHeader(writer, "MIME-Version", "1.0");
152a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
153c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler                mixedBoundary = "--_com.android.email_" + System.nanoTime();
154c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler                writeHeader(writer, "Content-Type",
155c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler                        "multipart/mixed; boundary=\"" + mixedBoundary + "\"");
156a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
157c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler                // Finish headers and prepare for body section(s)
158c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler                writer.write("\r\n");
159a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
160c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler                // first multipart element is the body
161c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler                if (text != null) {
162c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler                    writeBoundary(writer, mixedBoundary, false);
163c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler                    writeTextWithHeaders(writer, stream, text);
164c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler                }
165a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
166c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler                // Write out the attachments
167c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler                while (attachmentsCursor.moveToNext()) {
168c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler                    writeBoundary(writer, mixedBoundary, false);
169c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler                    Attachment attachment =
170c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler                        Attachment.getContent(attachmentsCursor, Attachment.class);
171c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler                    writeOneAttachment(context, writer, stream, attachment);
172c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler                    writer.write("\r\n");
173c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler                }
174a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
175c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler                // end of multipart section
176c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler                writeBoundary(writer, mixedBoundary, true);
177c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler            }
178c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler        } finally {
179c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler            attachmentsCursor.close();
180a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        }
181a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
182a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        writer.flush();
183a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        out.flush();
184a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler    }
185a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
186a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler    /**
187c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler     * Write a single attachment and its payload
188c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler     */
189c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler    private static void writeOneAttachment(Context context, Writer writer, OutputStream out,
190c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler            Attachment attachment) throws IOException, MessagingException {
191c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler        writeHeader(writer, "Content-Type",
192c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler                attachment.mMimeType + ";\n name=\"" + attachment.mFileName + "\"");
193c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler        writeHeader(writer, "Content-Transfer-Encoding", "base64");
194c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler        writeHeader(writer, "Content-Disposition",
195c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler                "attachment;"
196c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler                + "\n filename=\"" + attachment.mFileName + "\";"
197c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler                + "\n size=" + Long.toString(attachment.mSize));
198c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler        writeHeader(writer, "Content-ID", attachment.mContentId);
199c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler        writer.append("\r\n");
200c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler
201c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler        // Set up input stream and write it out via base64
202c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler        InputStream inStream = null;
203c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler        try {
204c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler            // try to open the file
205c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler            Uri fileUri = Uri.parse(attachment.mContentUri);
206c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler            inStream = context.getContentResolver().openInputStream(fileUri);
207c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler            // switch to output stream for base64 text output
208c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler            writer.flush();
209c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler            Base64OutputStream base64Out = new Base64OutputStream(out);
210c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler            // copy base64 data and close up
211c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler            IOUtils.copy(inStream, base64Out);
212c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler            base64Out.close();
213c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler        }
214c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler        catch (FileNotFoundException fnfe) {
215c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler            // Ignore this - empty file is OK
216c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler        }
217c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler        catch (IOException ioe) {
218c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler            throw new MessagingException("Invalid attachment.", ioe);
219c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler        }
220c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler    }
221c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler
222c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler    /**
223a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * Write a single header with no wrapping or encoding
224a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     *
225a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * @param writer the output writer
226a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * @param name the header name
227a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * @param value the header value
228a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     */
229a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler    private static void writeHeader(Writer writer, String name, String value) throws IOException {
230a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        if (value != null && value.length() > 0) {
231a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler            writer.append(name);
232a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler            writer.append(": ");
233a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler            writer.append(value);
234a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler            writer.append("\r\n");
235a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        }
236a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler    }
237a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
238a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler    /**
239a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * Write a single header using appropriate folding & encoding
240a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     *
241a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * @param writer the output writer
242a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * @param name the header name
243a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * @param value the header value
244a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     */
245a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler    private static void writeEncodedHeader(Writer writer, String name, String value)
246a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler            throws IOException {
247a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        if (value != null && value.length() > 0) {
248a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler            writer.append(name);
249a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler            writer.append(": ");
250a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler            writer.append(MimeUtility.foldAndEncode2(value, name.length() + 2));
251a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler            writer.append("\r\n");
252a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        }
253a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler    }
254a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
255a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler    /**
256a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * Unpack, encode, and fold address(es) into a header
257a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     *
258a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * @param writer the output writer
259a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * @param name the header name
260a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * @param value the header value (a packed list of addresses)
261a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     */
262a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler    private static void writeAddressHeader(Writer writer, String name, String value)
263a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler            throws IOException {
264a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        if (value != null && value.length() > 0) {
265a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler            writer.append(name);
266a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler            writer.append(": ");
267a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler            writer.append(MimeUtility.fold(Address.packedToHeader(value), name.length() + 2));
268a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler            writer.append("\r\n");
269a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        }
270a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler    }
271a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
272a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler    /**
273a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * Write a multipart boundary
274a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     *
275a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * @param writer the output writer
276a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * @param boundary the boundary string
277a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * @param end false if inner boundary, true if final boundary
278a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     */
279a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler    private static void writeBoundary(Writer writer, String boundary, boolean end)
280a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler            throws IOException {
281a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        writer.append("--");
282a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        writer.append(boundary);
283a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        if (end) {
284a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler            writer.append("--");
285a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        }
286a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        writer.append("\r\n");
287a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler    }
288a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
289a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler    /**
290a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * Write text (either as main body or inside a multipart), preceded by appropriate headers.
291a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     *
292a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * Note this always uses base64, even when not required.  Slightly less efficient for
293a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * US-ASCII text, but handles all formats even when non-ascii chars are involved.  A small
294a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * optimization might be to prescan the string for safety and send raw if possible.
295a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     *
296a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * @param writer the output writer
297a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * @param out the output stream inside the writer (used for byte[] access)
298a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * @param text The original text of the message
299a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     */
300a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler    private static void writeTextWithHeaders(Writer writer, OutputStream out, String text)
301a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler            throws IOException {
302a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        writeHeader(writer, "Content-Type", "text/plain; charset=utf-8");
303a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        writeHeader(writer, "Content-Transfer-Encoding", "base64");
304a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        writer.write("\r\n");
305a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        byte[] bytes = text.getBytes("UTF-8");
306a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        writer.flush();
307a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        out.write(Base64.encodeBase64Chunked(bytes));
308a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler    }
309a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler}
310