Rfc822Output.java revision 80ee607a7cc784993a48741d0dfbd132cd546985
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 19a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadlerimport com.android.email.mail.Address; 20c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadlerimport com.android.email.mail.MessagingException; 21a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadlerimport com.android.email.mail.internet.MimeUtility; 22c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadlerimport com.android.email.provider.EmailContent.Attachment; 23a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadlerimport com.android.email.provider.EmailContent.Body; 24a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadlerimport com.android.email.provider.EmailContent.Message; 25a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler 26c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadlerimport org.apache.commons.io.IOUtils; 27c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler 28c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadlerimport android.content.ContentUris; 29a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadlerimport android.content.Context; 30c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadlerimport android.database.Cursor; 31c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadlerimport android.net.Uri; 326cec1104fe8863fce2ee86ff5145076e6c436a00Doug Zongkerimport android.util.Base64; 336cec1104fe8863fce2ee86ff5145076e6c436a00Doug Zongkerimport android.util.Base64OutputStream; 34a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler 35a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadlerimport java.io.BufferedOutputStream; 363aaba9eb87db34ea0861d70d5c08f84d7ca97ab0Andrew Stadlerimport java.io.ByteArrayInputStream; 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). 59b3f7dd0169a35221184b9327c8ce337b09dc6d1fMakoto Onuki private static final SimpleDateFormat DATE_FORMAT = 60a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.US); 61a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler 621d98989222f2d023ddb08a70d5abb850029f95dcMarc Blank /*package*/ static String buildBodyText(Context context, Message message, 631d98989222f2d023ddb08a70d5abb850029f95dcMarc Blank boolean appendQuotedText) { 64f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank Body body = Body.restoreBodyWithMessageId(context, message.mId); 65ffc681a4da85c82c80d819ab8afb4442577c52a0Marc Blank if (body == null) { 66ffc681a4da85c82c80d819ab8afb4442577c52a0Marc Blank return null; 67ffc681a4da85c82c80d819ab8afb4442577c52a0Marc Blank } 682f99314326de0ee3bab9fbf18d511b24c0574ee1Marc Blank 69f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank String text = body.mTextContent; 702f99314326de0ee3bab9fbf18d511b24c0574ee1Marc Blank int flags = message.mFlags; 712b4b5e2f26b2fc9e0875a2a1974d8010794f3642Marc Blank boolean isReply = (flags & Message.FLAG_TYPE_REPLY) != 0; 722b4b5e2f26b2fc9e0875a2a1974d8010794f3642Marc Blank boolean isForward = (flags & Message.FLAG_TYPE_FORWARD) != 0; 7380ee607a7cc784993a48741d0dfbd132cd546985Marc Blank // For all forwards/replies, we add the intro text 7480ee607a7cc784993a48741d0dfbd132cd546985Marc Blank if (isReply || isForward) { 7580ee607a7cc784993a48741d0dfbd132cd546985Marc Blank String intro = body.mIntroText == null ? "" : body.mIntroText; 7680ee607a7cc784993a48741d0dfbd132cd546985Marc Blank text += intro; 7780ee607a7cc784993a48741d0dfbd132cd546985Marc Blank } 781d98989222f2d023ddb08a70d5abb850029f95dcMarc Blank if (!appendQuotedText) { 792f99314326de0ee3bab9fbf18d511b24c0574ee1Marc Blank // appendQuotedText is set to false for use by SmartReply/SmartForward in EAS. 8080ee607a7cc784993a48741d0dfbd132cd546985Marc Blank // SmartForward doesn't put a break between the original and new text, so we add an LF 8180ee607a7cc784993a48741d0dfbd132cd546985Marc Blank if (isForward) { 8280ee607a7cc784993a48741d0dfbd132cd546985Marc Blank text += "\n"; 832f99314326de0ee3bab9fbf18d511b24c0574ee1Marc Blank } 841d98989222f2d023ddb08a70d5abb850029f95dcMarc Blank return text; 851d98989222f2d023ddb08a70d5abb850029f95dcMarc Blank } 861d98989222f2d023ddb08a70d5abb850029f95dcMarc Blank 87f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank String quotedText = body.mTextReply; 88f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank if (quotedText != null) { 89f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank // fix CR-LF line endings to LF-only needed by EditText. 90f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank Matcher matcher = PATTERN_ENDLINE_CRLF.matcher(quotedText); 91f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank quotedText = matcher.replaceAll("\n"); 92f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank } 932b4b5e2f26b2fc9e0875a2a1974d8010794f3642Marc Blank if (isReply) { 94f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank if (quotedText != null) { 95f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank Matcher matcher = PATTERN_START_OF_LINE.matcher(quotedText); 96f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank text += matcher.replaceAll(">"); 97f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank } 982b4b5e2f26b2fc9e0875a2a1974d8010794f3642Marc Blank } else if (isForward) { 99f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank if (quotedText != null) { 100f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank text += quotedText; 101f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank } 102f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank } 103f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank return text; 104f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank } 105f2dded3a2fba83dd3f0d14cce6abe467a4ab66ebMarc Blank 106a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler /** 107a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * Write the entire message to an output stream. This method provides buffering, so it is 108a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * not necessary to pass in a buffered output stream here. 109a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * 110a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * @param context system context for accessing the provider 111a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * @param messageId the message to write out 112a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * @param out the output stream to write the message to 1131d98989222f2d023ddb08a70d5abb850029f95dcMarc Blank * @param appendQuotedText whether or not to append quoted text if this is a reply/forward 114a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * 115a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * TODO alternative parts (e.g. text+html) are not supported here. 116a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler */ 1171d98989222f2d023ddb08a70d5abb850029f95dcMarc Blank public static void writeTo(Context context, long messageId, OutputStream out, 1182d5691cac1874eb3491353ab608a84c2a75e2b62Marc Blank boolean appendQuotedText, boolean sendBcc) throws IOException, MessagingException { 119a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler Message message = Message.restoreMessageWithId(context, messageId); 120a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler if (message == null) { 121a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler // throw something? 122a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler return; 123a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler } 124a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler 125a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler OutputStream stream = new BufferedOutputStream(out, 1024); 126a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler Writer writer = new OutputStreamWriter(stream); 127a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler 128a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler // Write the fixed headers. Ordering is arbitrary (the legacy code iterated through a 129a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler // hashmap here). 130a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler 131b3f7dd0169a35221184b9327c8ce337b09dc6d1fMakoto Onuki String date = DATE_FORMAT.format(new Date(message.mTimeStamp)); 132a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler writeHeader(writer, "Date", date); 133a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler 134a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler writeEncodedHeader(writer, "Subject", message.mSubject); 135a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler 136a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler writeHeader(writer, "Message-ID", message.mMessageId); 137a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler 138a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler writeAddressHeader(writer, "From", message.mFrom); 139a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler writeAddressHeader(writer, "To", message.mTo); 140a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler writeAddressHeader(writer, "Cc", message.mCc); 1412d5691cac1874eb3491353ab608a84c2a75e2b62Marc Blank // Address fields. Note that we skip bcc unless the sendBcc argument is true 1422d5691cac1874eb3491353ab608a84c2a75e2b62Marc Blank // SMTP should NOT send bcc headers, but EAS must send it! 1432d5691cac1874eb3491353ab608a84c2a75e2b62Marc Blank if (sendBcc) { 1442d5691cac1874eb3491353ab608a84c2a75e2b62Marc Blank writeAddressHeader(writer, "Bcc", message.mBcc); 1452d5691cac1874eb3491353ab608a84c2a75e2b62Marc Blank } 146a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler writeAddressHeader(writer, "Reply-To", message.mReplyTo); 1476bcccf628413d40696980d0d86c7ab2b4f831952Andrew Stadler writeHeader(writer, "MIME-Version", "1.0"); 148a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler 149a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler // Analyze message and determine if we have multiparts 1501d98989222f2d023ddb08a70d5abb850029f95dcMarc Blank String text = buildBodyText(context, message, appendQuotedText); 151c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler 152c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler Uri uri = ContentUris.withAppendedId(Attachment.MESSAGE_ID_URI, messageId); 153c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler Cursor attachmentsCursor = context.getContentResolver().query(uri, 154c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler Attachment.CONTENT_PROJECTION, null, null, null); 155c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler 156c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler try { 1570ed690bfb4fdfa86cb6f75c19e24b8ff39a1756cMarc Blank int attachmentCount = attachmentsCursor.getCount(); 1580ed690bfb4fdfa86cb6f75c19e24b8ff39a1756cMarc Blank boolean multipart = attachmentCount > 0; 1590ed690bfb4fdfa86cb6f75c19e24b8ff39a1756cMarc Blank String multipartBoundary = null; 1600ed690bfb4fdfa86cb6f75c19e24b8ff39a1756cMarc Blank String multipartType = "mixed"; 161a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler 162c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler // Simplified case for no multipart - just emit text and be done. 1630ed690bfb4fdfa86cb6f75c19e24b8ff39a1756cMarc Blank if (!multipart) { 164c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler if (text != null) { 165c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler writeTextWithHeaders(writer, stream, text); 166c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler } else { 167c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler writer.write("\r\n"); // a truly empty message 168c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler } 169c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler } else { 170c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler // continue with multipart headers, then into multipart body 1710ed690bfb4fdfa86cb6f75c19e24b8ff39a1756cMarc Blank multipartBoundary = "--_com.android.email_" + System.nanoTime(); 1720ed690bfb4fdfa86cb6f75c19e24b8ff39a1756cMarc Blank 1730ed690bfb4fdfa86cb6f75c19e24b8ff39a1756cMarc Blank // Move to the first attachment; this must succeed because multipart is true 1740ed690bfb4fdfa86cb6f75c19e24b8ff39a1756cMarc Blank attachmentsCursor.moveToFirst(); 1750ed690bfb4fdfa86cb6f75c19e24b8ff39a1756cMarc Blank if (attachmentCount == 1) { 1760ed690bfb4fdfa86cb6f75c19e24b8ff39a1756cMarc Blank // If we've got one attachment and it's an ics "attachment", we want to send 1770ed690bfb4fdfa86cb6f75c19e24b8ff39a1756cMarc Blank // this as multipart/alternative instead of multipart/mixed 1780ed690bfb4fdfa86cb6f75c19e24b8ff39a1756cMarc Blank int flags = attachmentsCursor.getInt(Attachment.CONTENT_FLAGS_COLUMN); 1790ed690bfb4fdfa86cb6f75c19e24b8ff39a1756cMarc Blank if ((flags & Attachment.FLAG_ICS_ALTERNATIVE_PART) != 0) { 1800ed690bfb4fdfa86cb6f75c19e24b8ff39a1756cMarc Blank multipartType = "alternative"; 1810ed690bfb4fdfa86cb6f75c19e24b8ff39a1756cMarc Blank } 1820ed690bfb4fdfa86cb6f75c19e24b8ff39a1756cMarc Blank } 183a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler 184c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler writeHeader(writer, "Content-Type", 1850ed690bfb4fdfa86cb6f75c19e24b8ff39a1756cMarc Blank "multipart/" + multipartType + "; boundary=\"" + multipartBoundary + "\""); 186c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler // Finish headers and prepare for body section(s) 187c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler writer.write("\r\n"); 188a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler 189c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler // first multipart element is the body 190c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler if (text != null) { 1910ed690bfb4fdfa86cb6f75c19e24b8ff39a1756cMarc Blank writeBoundary(writer, multipartBoundary, false); 192c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler writeTextWithHeaders(writer, stream, text); 193c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler } 194a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler 1950ed690bfb4fdfa86cb6f75c19e24b8ff39a1756cMarc Blank // Write out the attachments until we run out 1960ed690bfb4fdfa86cb6f75c19e24b8ff39a1756cMarc Blank do { 1970ed690bfb4fdfa86cb6f75c19e24b8ff39a1756cMarc Blank writeBoundary(writer, multipartBoundary, false); 198c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler Attachment attachment = 199c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler Attachment.getContent(attachmentsCursor, Attachment.class); 200c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler writeOneAttachment(context, writer, stream, attachment); 201c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler writer.write("\r\n"); 2020ed690bfb4fdfa86cb6f75c19e24b8ff39a1756cMarc Blank } while (attachmentsCursor.moveToNext()); 203a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler 204c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler // end of multipart section 2050ed690bfb4fdfa86cb6f75c19e24b8ff39a1756cMarc Blank writeBoundary(writer, multipartBoundary, true); 206c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler } 207c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler } finally { 208c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler attachmentsCursor.close(); 209a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler } 210a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler 211a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler writer.flush(); 212a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler out.flush(); 213a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler } 214a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler 215a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler /** 216c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler * Write a single attachment and its payload 217c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler */ 218c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler private static void writeOneAttachment(Context context, Writer writer, OutputStream out, 219c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler Attachment attachment) throws IOException, MessagingException { 220c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler writeHeader(writer, "Content-Type", 221c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler attachment.mMimeType + ";\n name=\"" + attachment.mFileName + "\""); 222c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler writeHeader(writer, "Content-Transfer-Encoding", "base64"); 2233aaba9eb87db34ea0861d70d5c08f84d7ca97ab0Andrew Stadler // Most attachments (real files) will send Content-Disposition. The suppression option 2243aaba9eb87db34ea0861d70d5c08f84d7ca97ab0Andrew Stadler // is used when sending calendar invites. 2250ed690bfb4fdfa86cb6f75c19e24b8ff39a1756cMarc Blank if ((attachment.mFlags & Attachment.FLAG_ICS_ALTERNATIVE_PART) == 0) { 2263aaba9eb87db34ea0861d70d5c08f84d7ca97ab0Andrew Stadler writeHeader(writer, "Content-Disposition", 2273aaba9eb87db34ea0861d70d5c08f84d7ca97ab0Andrew Stadler "attachment;" 2283aaba9eb87db34ea0861d70d5c08f84d7ca97ab0Andrew Stadler + "\n filename=\"" + attachment.mFileName + "\";" 2293aaba9eb87db34ea0861d70d5c08f84d7ca97ab0Andrew Stadler + "\n size=" + Long.toString(attachment.mSize)); 2303aaba9eb87db34ea0861d70d5c08f84d7ca97ab0Andrew Stadler } 231c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler writeHeader(writer, "Content-ID", attachment.mContentId); 232c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler writer.append("\r\n"); 233c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler 234c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler // Set up input stream and write it out via base64 235c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler InputStream inStream = null; 236c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler try { 2373aaba9eb87db34ea0861d70d5c08f84d7ca97ab0Andrew Stadler // Use content, if provided; otherwise, use the contentUri 23820225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki if (attachment.mContentBytes != null) { 23920225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki inStream = new ByteArrayInputStream(attachment.mContentBytes); 2403aaba9eb87db34ea0861d70d5c08f84d7ca97ab0Andrew Stadler } else { 2413aaba9eb87db34ea0861d70d5c08f84d7ca97ab0Andrew Stadler // try to open the file 2423aaba9eb87db34ea0861d70d5c08f84d7ca97ab0Andrew Stadler Uri fileUri = Uri.parse(attachment.mContentUri); 2433aaba9eb87db34ea0861d70d5c08f84d7ca97ab0Andrew Stadler inStream = context.getContentResolver().openInputStream(fileUri); 2443aaba9eb87db34ea0861d70d5c08f84d7ca97ab0Andrew Stadler } 245c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler // switch to output stream for base64 text output 246c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler writer.flush(); 247ba714999f24697161f1ecd68199c3b330ea64ab9Doug Zongker Base64OutputStream base64Out = new Base64OutputStream( 248ba714999f24697161f1ecd68199c3b330ea64ab9Doug Zongker out, Base64.CRLF | Base64.NO_CLOSE); 249c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler // copy base64 data and close up 250c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler IOUtils.copy(inStream, base64Out); 251c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler base64Out.close(); 252ba714999f24697161f1ecd68199c3b330ea64ab9Doug Zongker 253ba714999f24697161f1ecd68199c3b330ea64ab9Doug Zongker // The old Base64OutputStream wrote an extra CRLF after 254ba714999f24697161f1ecd68199c3b330ea64ab9Doug Zongker // the output. It's not required by the base-64 spec; not 255ba714999f24697161f1ecd68199c3b330ea64ab9Doug Zongker // sure if it's required by RFC 822 or not. 256ba714999f24697161f1ecd68199c3b330ea64ab9Doug Zongker out.write('\r'); 257ba714999f24697161f1ecd68199c3b330ea64ab9Doug Zongker out.write('\n'); 258ba714999f24697161f1ecd68199c3b330ea64ab9Doug Zongker out.flush(); 259c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler } 260c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler catch (FileNotFoundException fnfe) { 261c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler // Ignore this - empty file is OK 262c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler } 263c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler catch (IOException ioe) { 264c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler throw new MessagingException("Invalid attachment.", ioe); 265c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler } 266c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler } 267c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler 268c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler /** 269a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * Write a single header with no wrapping or encoding 270a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * 271a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * @param writer the output writer 272a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * @param name the header name 273a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * @param value the header value 274a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler */ 275a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler private static void writeHeader(Writer writer, String name, String value) throws IOException { 276a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler if (value != null && value.length() > 0) { 277a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler writer.append(name); 278a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler writer.append(": "); 279a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler writer.append(value); 280a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler writer.append("\r\n"); 281a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler } 282a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler } 283a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler 284a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler /** 285a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * Write a single header using appropriate folding & encoding 286a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * 287a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * @param writer the output writer 288a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * @param name the header name 289a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * @param value the header value 290a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler */ 291a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler private static void writeEncodedHeader(Writer writer, String name, String value) 292a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler throws IOException { 293a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler if (value != null && value.length() > 0) { 294a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler writer.append(name); 295a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler writer.append(": "); 296a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler writer.append(MimeUtility.foldAndEncode2(value, name.length() + 2)); 297a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler writer.append("\r\n"); 298a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler } 299a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler } 300a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler 301a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler /** 302a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * Unpack, encode, and fold address(es) into a header 303a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * 304a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * @param writer the output writer 305a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * @param name the header name 306a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * @param value the header value (a packed list of addresses) 307a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler */ 308a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler private static void writeAddressHeader(Writer writer, String name, String value) 309a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler throws IOException { 310a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler if (value != null && value.length() > 0) { 311a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler writer.append(name); 312a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler writer.append(": "); 313a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler writer.append(MimeUtility.fold(Address.packedToHeader(value), name.length() + 2)); 314a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler writer.append("\r\n"); 315a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler } 316a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler } 317a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler 318a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler /** 319a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * Write a multipart boundary 320a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * 321a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * @param writer the output writer 322a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * @param boundary the boundary string 323a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * @param end false if inner boundary, true if final boundary 324a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler */ 325a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler private static void writeBoundary(Writer writer, String boundary, boolean end) 326a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler throws IOException { 327a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler writer.append("--"); 328a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler writer.append(boundary); 329a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler if (end) { 330a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler writer.append("--"); 331a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler } 332a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler writer.append("\r\n"); 333a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler } 334a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler 335a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler /** 336a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * Write text (either as main body or inside a multipart), preceded by appropriate headers. 337a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * 338a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * Note this always uses base64, even when not required. Slightly less efficient for 339a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * US-ASCII text, but handles all formats even when non-ascii chars are involved. A small 340a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * optimization might be to prescan the string for safety and send raw if possible. 341a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * 342a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * @param writer the output writer 343a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * @param out the output stream inside the writer (used for byte[] access) 344a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler * @param text The original text of the message 345a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler */ 346a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler private static void writeTextWithHeaders(Writer writer, OutputStream out, String text) 347a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler throws IOException { 348a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler writeHeader(writer, "Content-Type", "text/plain; charset=utf-8"); 349a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler writeHeader(writer, "Content-Transfer-Encoding", "base64"); 350a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler writer.write("\r\n"); 351a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler byte[] bytes = text.getBytes("UTF-8"); 352a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler writer.flush(); 353f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker out.write(Base64.encode(bytes, Base64.CRLF)); 354a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler } 355a9a0e57436bce15e186bef216564e996ec20ae82Andrew Stadler} 356