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
1731d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blankpackage com.android.emailcommon.internet;
18a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
19bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrookimport android.content.ContentUris;
20f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blankimport android.content.Context;
21bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrookimport android.database.Cursor;
22f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blankimport android.net.Uri;
23bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrookimport android.text.Html;
24bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrookimport android.text.TextUtils;
25f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blankimport android.util.Base64;
26f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blankimport android.util.Base64OutputStream;
27f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank
282193962ca2b3157e79f731736afa2a0c972e778aMarc Blankimport com.android.emailcommon.mail.Address;
292193962ca2b3157e79f731736afa2a0c972e778aMarc Blankimport com.android.emailcommon.mail.MessagingException;
30a7bc0319a75184ad706bb35c049af107ac3688e6Marc Blankimport com.android.emailcommon.provider.EmailContent.Attachment;
31a7bc0319a75184ad706bb35c049af107ac3688e6Marc Blankimport com.android.emailcommon.provider.EmailContent.Body;
32a7bc0319a75184ad706bb35c049af107ac3688e6Marc Blankimport com.android.emailcommon.provider.EmailContent.Message;
33a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
34c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadlerimport org.apache.commons.io.IOUtils;
35c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler
36a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadlerimport java.io.BufferedOutputStream;
373aaba9eb87db34ea0861d70d5c08f84d7ca97ab0Andrew Stadlerimport java.io.ByteArrayInputStream;
38c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadlerimport java.io.FileNotFoundException;
39a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadlerimport java.io.IOException;
40c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadlerimport java.io.InputStream;
41a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadlerimport java.io.OutputStream;
42a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadlerimport java.io.OutputStreamWriter;
43a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadlerimport java.io.Writer;
44a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadlerimport java.text.SimpleDateFormat;
45a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadlerimport java.util.Date;
46a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadlerimport java.util.Locale;
47f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blankimport java.util.regex.Matcher;
48f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blankimport java.util.regex.Pattern;
49a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
50a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler/**
51a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * Utility class to output RFC 822 messages from provider email messages
52a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler */
53a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadlerpublic class Rfc822Output {
54a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
55bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    private static final Pattern PATTERN_START_OF_LINE = Pattern.compile("(?m)^");
56bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    private static final Pattern PATTERN_ENDLINE_CRLF = Pattern.compile("\r\n");
57bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
58a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler    // In MIME, en_US-like date format should be used. In other words "MMM" should be encoded to
59a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler    // "Jan", not the other localized format like "Ene" (meaning January in locale es).
60b3f7dd0169a35221184b9327c8ce337b09dc6d1fMakoto Onuki    private static final SimpleDateFormat DATE_FORMAT =
61a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.US);
62a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
63bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    private static final String WHERE_NOT_SMART_FORWARD = "(" + Attachment.FLAGS + "&" +
64bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        Attachment.FLAG_SMART_FORWARD + ")=0";
65bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
669cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy    /** A less-than-perfect pattern to pull out <body> content */
679cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy    private static final Pattern BODY_PATTERN = Pattern.compile(
689cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy                "(?:<\\s*body[^>]*>)(.*)(?:<\\s*/\\s*body\\s*>)",
699cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy                Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
709cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy    /** Match group in {@code BODDY_PATTERN} for the body HTML */
719cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy    private static final int BODY_PATTERN_GROUP = 1;
72bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    /** Pattern to find both dos and unix newlines */
73bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    private static final Pattern NEWLINE_PATTERN =
74bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        Pattern.compile("\\r?\\n");
75bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    /** HTML string to use when replacing text newlines */
76bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    private static final String NEWLINE_HTML = "<br>";
779cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy    /** Index of the plain text version of the message body */
789cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy    private final static int INDEX_BODY_TEXT = 0;
799cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy    /** Index of the HTML version of the message body */
809cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy    private final static int INDEX_BODY_HTML = 1;
819cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy    /** Single digit [0-9] to ensure uniqueness of the MIME boundary */
829cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy    /*package*/ static byte sBoundaryDigit;
839cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy
849cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy    /**
859cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy     * Returns just the content between the <body></body> tags. This is not perfect and breaks
869cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy     * with malformed HTML or if there happens to be special characters in the attributes of
879cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy     * the <body> tag (e.g. a '>' in a java script block).
889cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy     */
899cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy    /*package*/ static String getHtmlBody(String html) {
909cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy        Matcher match = BODY_PATTERN.matcher(html);
919cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy        if (match.find()) {
929cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy            return match.group(BODY_PATTERN_GROUP);    // Found body; return
939cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy        } else {
949cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy            return html;              // Body not found; return the full HTML and hope for the best
959cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy        }
969cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy    }
979cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy
989cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy    /**
99bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     * Returns an HTML encoded message alternate
100bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     */
101bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    /*package*/ static String getHtmlAlternate(Body body, boolean useSmartReply) {
102bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        if (body.mHtmlReply == null) {
103bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            return null;
104bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        }
105bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        StringBuffer altMessage = new StringBuffer();
106bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        String htmlContent = TextUtils.htmlEncode(body.mTextContent); // Escape HTML reserved chars
107bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        htmlContent = NEWLINE_PATTERN.matcher(htmlContent).replaceAll(NEWLINE_HTML);
108bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        altMessage.append(htmlContent);
109bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        if (body.mIntroText != null) {
110bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            String htmlIntro = TextUtils.htmlEncode(body.mIntroText);
111bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            htmlIntro = NEWLINE_PATTERN.matcher(htmlIntro).replaceAll(NEWLINE_HTML);
112bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            altMessage.append(htmlIntro);
113bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        }
114bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        if (!useSmartReply) {
115bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            String htmlBody = getHtmlBody(body.mHtmlReply);
116bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            altMessage.append(htmlBody);
117bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        }
118bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        return altMessage.toString();
119bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    }
120bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
121bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    /**
1229cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy     * Gets both the plain text and HTML versions of the message body.
1239cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy     */
1249cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy    /*package*/ static String[] buildBodyText(Body body, int flags, boolean useSmartReply) {
125bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        String[] messageBody = new String[] { null, null };
1269cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy        if (body == null) {
127bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            return messageBody;
128bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        }
129bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        String text = body.mTextContent;
130bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        boolean isReply = (flags & Message.FLAG_TYPE_REPLY) != 0;
131bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        boolean isForward = (flags & Message.FLAG_TYPE_FORWARD) != 0;
132bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        // For all forwards/replies, we add the intro text
133bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        if (isReply || isForward) {
134bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            String intro = body.mIntroText == null ? "" : body.mIntroText;
135bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            text += intro;
13680ee607a7cc784993a48741d0dfbd132cd546985Marc Blank        }
137bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        if (useSmartReply) {
138bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            // useSmartReply is set to true for use by SmartReply/SmartForward in EAS.
139bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            // SmartForward doesn't put a break between the original and new text, so we add an LF
140bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            if (isForward) {
141bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                text += "\n";
142bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            }
143bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        } else {
144bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            String quotedText = body.mTextReply;
145bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            // If there is no plain-text body, use de-tagified HTML as the text body
146bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            if (quotedText == null && body.mHtmlReply != null) {
147bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                quotedText = Html.fromHtml(body.mHtmlReply).toString();
148bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            }
149bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            if (quotedText != null) {
150bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                // fix CR-LF line endings to LF-only needed by EditText.
151bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                Matcher matcher = PATTERN_ENDLINE_CRLF.matcher(quotedText);
152bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                quotedText = matcher.replaceAll("\n");
1539cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy            }
154bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            if (isReply) {
155bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                if (quotedText != null) {
156bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                    Matcher matcher = PATTERN_START_OF_LINE.matcher(quotedText);
157bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                    text += matcher.replaceAll(">");
158bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                }
159bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            } else if (isForward) {
160bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                if (quotedText != null) {
161bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                    text += quotedText;
162bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                }
163bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            }
164bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        }
165bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        messageBody[INDEX_BODY_TEXT] = text;
166bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        // Exchange 2003 doesn't seem to support multipart w/SmartReply and SmartForward, so
167bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        // we'll skip this.  Really, it would only matter if we could compose HTML replies
168bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        if (!useSmartReply) {
169bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            messageBody[INDEX_BODY_HTML] = getHtmlAlternate(body, useSmartReply);
17009a071a87938e0c1ed80962d54b6025618dca120Marc Blank        }
1719cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy        return messageBody;
172f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank    }
173f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank
174a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler    /**
175a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * Write the entire message to an output stream.  This method provides buffering, so it is
176a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * not necessary to pass in a buffered output stream here.
177a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     *
178a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * @param context system context for accessing the provider
179a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * @param messageId the message to write out
180a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * @param out the output stream to write the message to
1817c87bfc33aaf94e50e4be9ff0b838e4b3a3812f4Todd Kennedy     * @param useSmartReply whether or not quoted text is appended to a reply/forward
182a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     */
1831d98989222f2d023ddb08a70d5abb850029f95dcMarc Blank    public static void writeTo(Context context, long messageId, OutputStream out,
1847c87bfc33aaf94e50e4be9ff0b838e4b3a3812f4Todd Kennedy            boolean useSmartReply, boolean sendBcc) throws IOException, MessagingException {
185a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        Message message = Message.restoreMessageWithId(context, messageId);
186a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        if (message == null) {
187a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler            // throw something?
188a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler            return;
189a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        }
190a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
191a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        OutputStream stream = new BufferedOutputStream(out, 1024);
192a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        Writer writer = new OutputStreamWriter(stream);
193a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
194a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        // Write the fixed headers.  Ordering is arbitrary (the legacy code iterated through a
195a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        // hashmap here).
196a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
197b3f7dd0169a35221184b9327c8ce337b09dc6d1fMakoto Onuki        String date = DATE_FORMAT.format(new Date(message.mTimeStamp));
198a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        writeHeader(writer, "Date", date);
199a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
200a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        writeEncodedHeader(writer, "Subject", message.mSubject);
201a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
202a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        writeHeader(writer, "Message-ID", message.mMessageId);
203a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
204a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        writeAddressHeader(writer, "From", message.mFrom);
205a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        writeAddressHeader(writer, "To", message.mTo);
206a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        writeAddressHeader(writer, "Cc", message.mCc);
2072d5691cac1874eb3491353ab608a84c2a75e2b62Marc Blank        // Address fields.  Note that we skip bcc unless the sendBcc argument is true
2082d5691cac1874eb3491353ab608a84c2a75e2b62Marc Blank        // SMTP should NOT send bcc headers, but EAS must send it!
2092d5691cac1874eb3491353ab608a84c2a75e2b62Marc Blank        if (sendBcc) {
2102d5691cac1874eb3491353ab608a84c2a75e2b62Marc Blank            writeAddressHeader(writer, "Bcc", message.mBcc);
2112d5691cac1874eb3491353ab608a84c2a75e2b62Marc Blank        }
212a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        writeAddressHeader(writer, "Reply-To", message.mReplyTo);
2136bcccf628413d40696980d0d86c7ab2b4f831952Andrew Stadler        writeHeader(writer, "MIME-Version", "1.0");
214a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
215a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        // Analyze message and determine if we have multiparts
2169cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy        Body body = Body.restoreBodyWithMessageId(context, message.mId);
2179cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy        String[] bodyText = buildBodyText(body, message.mFlags, useSmartReply);
218c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler
219bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        Uri uri = ContentUris.withAppendedId(Attachment.MESSAGE_ID_URI, messageId);
220bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        Cursor attachmentsCursor = context.getContentResolver().query(uri,
221bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                Attachment.CONTENT_PROJECTION, WHERE_NOT_SMART_FORWARD, null, null);
222c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler
223bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        try {
224bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            int attachmentCount = attachmentsCursor.getCount();
225bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            boolean multipart = attachmentCount > 0;
226bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            String multipartBoundary = null;
227bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            String multipartType = "mixed";
228a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
229bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            // Simplified case for no multipart - just emit text and be done.
230bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            if (!multipart) {
231bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                writeTextWithHeaders(writer, stream, bodyText);
232bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            } else {
233bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                // continue with multipart headers, then into multipart body
234bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                multipartBoundary = getNextBoundary();
235bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
236bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                // Move to the first attachment; this must succeed because multipart is true
237bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                attachmentsCursor.moveToFirst();
238bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                if (attachmentCount == 1) {
239bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                    // If we've got one attachment and it's an ics "attachment", we want to send
240bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                    // this as multipart/alternative instead of multipart/mixed
241bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                    int flags = attachmentsCursor.getInt(Attachment.CONTENT_FLAGS_COLUMN);
242bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                    if ((flags & Attachment.FLAG_ICS_ALTERNATIVE_PART) != 0) {
243bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                        multipartType = "alternative";
244bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                    }
2450ed690bfb4fdfa86cb6f75c19e24b8ff39a1756cMarc Blank                }
246a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
247bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                writeHeader(writer, "Content-Type",
248bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                        "multipart/" + multipartType + "; boundary=\"" + multipartBoundary + "\"");
249bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                // Finish headers and prepare for body section(s)
250bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                writer.write("\r\n");
251a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
252bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                // first multipart element is the body
253bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                if (bodyText[INDEX_BODY_TEXT] != null) {
254bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                    writeBoundary(writer, multipartBoundary, false);
255bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                    writeTextWithHeaders(writer, stream, bodyText);
256bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                }
257a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
258bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                // Write out the attachments until we run out
259bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                do {
260bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                    writeBoundary(writer, multipartBoundary, false);
261bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                    Attachment attachment =
262bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                        Attachment.getContent(attachmentsCursor, Attachment.class);
263bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                    writeOneAttachment(context, writer, stream, attachment);
264bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                    writer.write("\r\n");
265bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                } while (attachmentsCursor.moveToNext());
266bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
267bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                // end of multipart section
268bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                writeBoundary(writer, multipartBoundary, true);
269c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler            }
270bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        } finally {
271bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            attachmentsCursor.close();
272a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        }
273a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
274a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        writer.flush();
275a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        out.flush();
276a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler    }
277a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
278a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler    /**
279c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler     * Write a single attachment and its payload
280c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler     */
281c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler    private static void writeOneAttachment(Context context, Writer writer, OutputStream out,
282c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler            Attachment attachment) throws IOException, MessagingException {
283c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler        writeHeader(writer, "Content-Type",
284c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler                attachment.mMimeType + ";\n name=\"" + attachment.mFileName + "\"");
285c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler        writeHeader(writer, "Content-Transfer-Encoding", "base64");
2863aaba9eb87db34ea0861d70d5c08f84d7ca97ab0Andrew Stadler        // Most attachments (real files) will send Content-Disposition.  The suppression option
2873aaba9eb87db34ea0861d70d5c08f84d7ca97ab0Andrew Stadler        // is used when sending calendar invites.
2880ed690bfb4fdfa86cb6f75c19e24b8ff39a1756cMarc Blank        if ((attachment.mFlags & Attachment.FLAG_ICS_ALTERNATIVE_PART) == 0) {
2893aaba9eb87db34ea0861d70d5c08f84d7ca97ab0Andrew Stadler            writeHeader(writer, "Content-Disposition",
2903aaba9eb87db34ea0861d70d5c08f84d7ca97ab0Andrew Stadler                    "attachment;"
2913aaba9eb87db34ea0861d70d5c08f84d7ca97ab0Andrew Stadler                    + "\n filename=\"" + attachment.mFileName + "\";"
2923aaba9eb87db34ea0861d70d5c08f84d7ca97ab0Andrew Stadler                    + "\n size=" + Long.toString(attachment.mSize));
2933aaba9eb87db34ea0861d70d5c08f84d7ca97ab0Andrew Stadler        }
2949cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy        if (attachment.mContentId != null) {
2959cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy            writeHeader(writer, "Content-ID", attachment.mContentId);
2969cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy        }
297c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler        writer.append("\r\n");
298c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler
299c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler        // Set up input stream and write it out via base64
300c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler        InputStream inStream = null;
301c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler        try {
3023aaba9eb87db34ea0861d70d5c08f84d7ca97ab0Andrew Stadler            // Use content, if provided; otherwise, use the contentUri
30320225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki            if (attachment.mContentBytes != null) {
30420225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki                inStream = new ByteArrayInputStream(attachment.mContentBytes);
3053aaba9eb87db34ea0861d70d5c08f84d7ca97ab0Andrew Stadler            } else {
3063aaba9eb87db34ea0861d70d5c08f84d7ca97ab0Andrew Stadler                // try to open the file
307bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                Uri fileUri = Uri.parse(attachment.mContentUri);
3083aaba9eb87db34ea0861d70d5c08f84d7ca97ab0Andrew Stadler                inStream = context.getContentResolver().openInputStream(fileUri);
3093aaba9eb87db34ea0861d70d5c08f84d7ca97ab0Andrew Stadler            }
310c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler            // switch to output stream for base64 text output
311c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler            writer.flush();
312ba714999f24697161f1ecd68199c3b330ea64ab9Doug Zongker            Base64OutputStream base64Out = new Base64OutputStream(
313ba714999f24697161f1ecd68199c3b330ea64ab9Doug Zongker                out, Base64.CRLF | Base64.NO_CLOSE);
314c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler            // copy base64 data and close up
315c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler            IOUtils.copy(inStream, base64Out);
316c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler            base64Out.close();
317ba714999f24697161f1ecd68199c3b330ea64ab9Doug Zongker
318ba714999f24697161f1ecd68199c3b330ea64ab9Doug Zongker            // The old Base64OutputStream wrote an extra CRLF after
319ba714999f24697161f1ecd68199c3b330ea64ab9Doug Zongker            // the output.  It's not required by the base-64 spec; not
320ba714999f24697161f1ecd68199c3b330ea64ab9Doug Zongker            // sure if it's required by RFC 822 or not.
321ba714999f24697161f1ecd68199c3b330ea64ab9Doug Zongker            out.write('\r');
322ba714999f24697161f1ecd68199c3b330ea64ab9Doug Zongker            out.write('\n');
323ba714999f24697161f1ecd68199c3b330ea64ab9Doug Zongker            out.flush();
324c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler        }
325c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler        catch (FileNotFoundException fnfe) {
326c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler            // Ignore this - empty file is OK
327c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler        }
328c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler        catch (IOException ioe) {
329c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler            throw new MessagingException("Invalid attachment.", ioe);
330c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler        }
331c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler    }
332c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler
333c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler    /**
334a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * Write a single header with no wrapping or encoding
335a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     *
336a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * @param writer the output writer
337a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * @param name the header name
338a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * @param value the header value
339a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     */
340a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler    private static void writeHeader(Writer writer, String name, String value) throws IOException {
341a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        if (value != null && value.length() > 0) {
342a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler            writer.append(name);
343a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler            writer.append(": ");
344a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler            writer.append(value);
345a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler            writer.append("\r\n");
346a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        }
347a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler    }
348a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
349a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler    /**
350a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * Write a single header using appropriate folding & encoding
351a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     *
352a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * @param writer the output writer
353a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * @param name the header name
354a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * @param value the header value
355a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     */
356a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler    private static void writeEncodedHeader(Writer writer, String name, String value)
357a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler            throws IOException {
358a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        if (value != null && value.length() > 0) {
359a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler            writer.append(name);
360a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler            writer.append(": ");
361a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler            writer.append(MimeUtility.foldAndEncode2(value, name.length() + 2));
362a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler            writer.append("\r\n");
363a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        }
364a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler    }
365a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
366a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler    /**
367a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * Unpack, encode, and fold address(es) into a header
368a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     *
369a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * @param writer the output writer
370a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * @param name the header name
371a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * @param value the header value (a packed list of addresses)
372a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     */
373a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler    private static void writeAddressHeader(Writer writer, String name, String value)
374a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler            throws IOException {
375a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        if (value != null && value.length() > 0) {
376a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler            writer.append(name);
377a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler            writer.append(": ");
378a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler            writer.append(MimeUtility.fold(Address.packedToHeader(value), name.length() + 2));
379a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler            writer.append("\r\n");
380a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        }
381a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler    }
382a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
383a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler    /**
384a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * Write a multipart boundary
385a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     *
386a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * @param writer the output writer
387a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * @param boundary the boundary string
388a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * @param end false if inner boundary, true if final boundary
389a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     */
390a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler    private static void writeBoundary(Writer writer, String boundary, boolean end)
391a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler            throws IOException {
392a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        writer.append("--");
393a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        writer.append(boundary);
394a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        if (end) {
395a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler            writer.append("--");
396a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        }
397a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler        writer.append("\r\n");
398a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler    }
399a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler
400a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler    /**
401bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     * Write the body text. If only one version of the body is specified (either plain text
402bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     * or HTML), the text is written directly. Otherwise, the plain text and HTML bodies
403bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     * are both written with the appropriate headers.
404a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     *
405a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * Note this always uses base64, even when not required.  Slightly less efficient for
406a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * US-ASCII text, but handles all formats even when non-ascii chars are involved.  A small
407a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * optimization might be to prescan the string for safety and send raw if possible.
408a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     *
409a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * @param writer the output writer
410a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     * @param out the output stream inside the writer (used for byte[] access)
4119cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy     * @param bodyText Plain text and HTML versions of the original text of the message
412a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler     */
4139cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy    private static void writeTextWithHeaders(Writer writer, OutputStream out, String[] bodyText)
414a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler            throws IOException {
4159cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy        String text = bodyText[INDEX_BODY_TEXT];
416bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        String html = bodyText[INDEX_BODY_HTML];
417bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
4189cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy        if (text == null) {
4199cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy            writer.write("\r\n");       // a truly empty message
4209cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy        } else {
421bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            String multipartBoundary = null;
422bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            boolean multipart = html != null;
423bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
424bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            // Simplified case for no multipart - just emit text and be done.
425bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            if (multipart) {
426bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                // continue with multipart headers, then into multipart body
427bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                multipartBoundary = getNextBoundary();
428bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
429bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                writeHeader(writer, "Content-Type",
430bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                        "multipart/alternative; boundary=\"" + multipartBoundary + "\"");
431bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                // Finish headers and prepare for body section(s)
432bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                writer.write("\r\n");
433bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                writeBoundary(writer, multipartBoundary, false);
434bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            }
435bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
4369cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy            // first multipart element is the body
437bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            writeHeader(writer, "Content-Type", "text/plain; charset=utf-8");
4389cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy            writeHeader(writer, "Content-Transfer-Encoding", "base64");
4399cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy            writer.write("\r\n");
4409cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy            byte[] textBytes = text.getBytes("UTF-8");
4419cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy            writer.flush();
4429cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy            out.write(Base64.encode(textBytes, Base64.CRLF));
443bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
444bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            if (multipart) {
445bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                // next multipart section
446bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                writeBoundary(writer, multipartBoundary, false);
447bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
448bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                writeHeader(writer, "Content-Type", "text/html; charset=utf-8");
449bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                writeHeader(writer, "Content-Transfer-Encoding", "base64");
450bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                writer.write("\r\n");
451bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                byte[] htmlBytes = html.getBytes("UTF-8");
452bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                writer.flush();
453bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                out.write(Base64.encode(htmlBytes, Base64.CRLF));
454bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
455bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                // end of multipart section
456bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                writeBoundary(writer, multipartBoundary, true);
457bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            }
4589cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy        }
4599cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy    }
4609cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy
4619cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy    /**
4629cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy     * Returns a unique boundary string.
4639cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy     */
4649cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy    /*package*/ static String getNextBoundary() {
4659cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy        StringBuilder boundary = new StringBuilder();
4669cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy        boundary.append("--_com.android.email_").append(System.nanoTime());
4679cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy        synchronized (Rfc822Output.class) {
4689cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy            boundary = boundary.append(sBoundaryDigit);
4699cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy            sBoundaryDigit = (byte)((sBoundaryDigit + 1) % 10);
4709cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy        }
4719cc51b72c6902b95f65857af64eb38063aa4a42bTodd Kennedy        return boundary.toString();
472a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler    }
473a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler}
474