1345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein/* 2345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Copyright (C) 2008 The Android Open Source Project 3345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * 4345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Licensed under the Apache License, Version 2.0 (the "License"); 5345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * you may not use this file except in compliance with the License. 6345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * You may obtain a copy of the License at 7345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * 8345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * http://www.apache.org/licenses/LICENSE-2.0 9345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * 10345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Unless required by applicable law or agreed to in writing, software 11345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * distributed under the License is distributed on an "AS IS" BASIS, 12345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * See the License for the specific language governing permissions and 14345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * limitations under the License. 15345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 16345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 17345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinpackage com.android.emailcommon.internet; 18345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 19345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport android.text.TextUtils; 20345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport android.util.Base64; 21345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport android.util.Base64DataException; 22345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport android.util.Base64InputStream; 23345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport android.util.Log; 24345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 25345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport com.android.emailcommon.mail.Body; 26345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport com.android.emailcommon.mail.BodyPart; 27345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport com.android.emailcommon.mail.Message; 28345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport com.android.emailcommon.mail.MessagingException; 29345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport com.android.emailcommon.mail.Multipart; 30345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport com.android.emailcommon.mail.Part; 31345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 32345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport org.apache.commons.io.IOUtils; 33345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport org.apache.james.mime4j.codec.EncoderUtil; 34345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport org.apache.james.mime4j.decoder.DecoderUtil; 35345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport org.apache.james.mime4j.decoder.QuotedPrintableInputStream; 36345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport org.apache.james.mime4j.util.CharsetUtil; 37345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 38345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport java.io.ByteArrayOutputStream; 39345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport java.io.IOException; 40345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport java.io.InputStream; 41345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport java.io.OutputStream; 42345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport java.util.ArrayList; 43345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport java.util.regex.Matcher; 44345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport java.util.regex.Pattern; 45345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 46345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinpublic class MimeUtility { 47345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein private static final String LOG_TAG = "Email"; 48345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 49345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein public static final String MIME_TYPE_RFC822 = "message/rfc822"; 50345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein private final static Pattern PATTERN_CR_OR_LF = Pattern.compile("\r|\n"); 51345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 52345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein /** 53345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Replace sequences of CRLF+WSP with WSP. Tries to preserve original string 54345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * object whenever possible. 55345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 56345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein public static String unfold(String s) { 57345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (s == null) { 58345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return null; 59345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 60345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein Matcher patternMatcher = PATTERN_CR_OR_LF.matcher(s); 61345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (patternMatcher.find()) { 62345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein patternMatcher.reset(); 63345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein s = patternMatcher.replaceAll(""); 64345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 65345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return s; 66345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 67345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 68345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein public static String decode(String s) { 69345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (s == null) { 70345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return null; 71345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 72345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return DecoderUtil.decodeEncodedWords(s); 73345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 74345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 75345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein public static String unfoldAndDecode(String s) { 76345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return decode(unfold(s)); 77345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 78345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 79345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein // TODO implement proper foldAndEncode 80345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein // NOTE: When this really works, we *must* remove all calls to foldAndEncode2() to prevent 81345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein // duplication of encoding. 82345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein public static String foldAndEncode(String s) { 83345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return s; 84345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 85345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 86345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein /** 87345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * INTERIM version of foldAndEncode that will be used only by Subject: headers. 88345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * This is safer than implementing foldAndEncode() (see above) and risking unknown damage 89345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * to other headers. 90345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * 91345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * TODO: Copy this code to foldAndEncode(), get rid of this function, confirm all working OK. 92345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * 93345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * @param s original string to encode and fold 94345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * @param usedCharacters number of characters already used up by header name 95345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 96345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * @return the String ready to be transmitted 97345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 98345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein public static String foldAndEncode2(String s, int usedCharacters) { 99345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein // james.mime4j.codec.EncoderUtil.java 100345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein // encode: encodeIfNecessary(text, usage, numUsedInHeaderName) 101345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein // Usage.TEXT_TOKENlooks like the right thing for subjects 102345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein // use WORD_ENTITY for address/names 103345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 104345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein String encoded = EncoderUtil.encodeIfNecessary(s, EncoderUtil.Usage.TEXT_TOKEN, 105345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein usedCharacters); 106345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 107345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return fold(encoded, usedCharacters); 108345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 109345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 110345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein /** 111345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * INTERIM: From newer version of org.apache.james (but we don't want to import 112345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * the entire MimeUtil class). 113345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * 114345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Splits the specified string into a multiple-line representation with 115345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * lines no longer than 76 characters (because the line might contain 116345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * encoded words; see <a href='http://www.faqs.org/rfcs/rfc2047.html'>RFC 117345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * 2047</a> section 2). If the string contains non-whitespace sequences 118345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * longer than 76 characters a line break is inserted at the whitespace 119345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * character following the sequence resulting in a line longer than 76 120345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * characters. 121345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * 122345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * @param s 123345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * string to split. 124345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * @param usedCharacters 125345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * number of characters already used up. Usually the number of 126345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * characters for header field name plus colon and one space. 127345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * @return a multiple-line representation of the given string. 128345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 129345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein public static String fold(String s, int usedCharacters) { 130345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein final int maxCharacters = 76; 131345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 132345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein final int length = s.length(); 133345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (usedCharacters + length <= maxCharacters) 134345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return s; 135345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 136345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein StringBuilder sb = new StringBuilder(); 137345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 138345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein int lastLineBreak = -usedCharacters; 139345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein int wspIdx = indexOfWsp(s, 0); 140345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein while (true) { 141345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (wspIdx == length) { 142345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein sb.append(s.substring(Math.max(0, lastLineBreak))); 143345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return sb.toString(); 144345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 145345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 146345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein int nextWspIdx = indexOfWsp(s, wspIdx + 1); 147345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 148345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (nextWspIdx - lastLineBreak > maxCharacters) { 149345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein sb.append(s.substring(Math.max(0, lastLineBreak), wspIdx)); 150345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein sb.append("\r\n"); 151345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein lastLineBreak = wspIdx; 152345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 153345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 154345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein wspIdx = nextWspIdx; 155345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 156345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 157345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 158345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein /** 159345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * INTERIM: From newer version of org.apache.james (but we don't want to import 160345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * the entire MimeUtil class). 161345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * 162345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Search for whitespace. 163345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 164345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein private static int indexOfWsp(String s, int fromIndex) { 165345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein final int len = s.length(); 166345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein for (int index = fromIndex; index < len; index++) { 167345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein char c = s.charAt(index); 168345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (c == ' ' || c == '\t') 169345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return index; 170345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 171345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return len; 172345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 173345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 174345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein /** 175345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Returns the named parameter of a header field. If name is null the first 176345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * parameter is returned, or if there are no additional parameters in the 177345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * field the entire field is returned. Otherwise the named parameter is 178345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * searched for in a case insensitive fashion and returned. If the parameter 179345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * cannot be found the method returns null. 180345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * 181345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * TODO: quite inefficient with the inner trimming & splitting. 182345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * TODO: Also has a latent bug: uses "startsWith" to match the name, which can false-positive. 183345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * TODO: The doc says that for a null name you get the first param, but you get the header. 184345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Should probably just fix the doc, but if other code assumes that behavior, fix the code. 185345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * TODO: Need to decode %-escaped strings, as in: filename="ab%22d". 186345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * ('+' -> ' ' conversion too? check RFC) 187345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * 188345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * @param header 189345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * @param name 190345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * @return the entire header (if name=null), the found parameter, or null 191345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 192345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein public static String getHeaderParameter(String header, String name) { 193345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (header == null) { 194345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return null; 195345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 196345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein String[] parts = unfold(header).split(";"); 197345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (name == null) { 198345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return parts[0].trim(); 199345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 200345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein String lowerCaseName = name.toLowerCase(); 201345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein for (String part : parts) { 202345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (part.trim().toLowerCase().startsWith(lowerCaseName)) { 203345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein String[] parameterParts = part.split("=", 2); 204345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (parameterParts.length < 2) { 205345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return null; 206345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 207345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein String parameter = parameterParts[1].trim(); 208345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (parameter.startsWith("\"") && parameter.endsWith("\"")) { 209345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return parameter.substring(1, parameter.length() - 1); 210345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } else { 211345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return parameter; 212345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 213345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 214345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 215345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return null; 216345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 217345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 218345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein /** 219345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Reads the Part's body and returns a String based on any charset conversion that needed 220345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * to be done. 221345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * @param part The part containing a body 222345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * @return a String containing the converted text in the body, or null if there was no text 223345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * or an error during conversion. 224345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 225345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein public static String getTextFromPart(Part part) { 226345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein try { 227345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (part != null && part.getBody() != null) { 228345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein InputStream in = part.getBody().getInputStream(); 229345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein String mimeType = part.getMimeType(); 230345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (mimeType != null && MimeUtility.mimeTypeMatches(mimeType, "text/*")) { 231345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein /* 232345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Now we read the part into a buffer for further processing. Because 233345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * the stream is now wrapped we'll remove any transfer encoding at this point. 234345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 235345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein ByteArrayOutputStream out = new ByteArrayOutputStream(); 236345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein IOUtils.copy(in, out); 237345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein in.close(); 238345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein in = null; // we want all of our memory back, and close might not release 239345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 240345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein /* 241345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * We've got a text part, so let's see if it needs to be processed further. 242345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 243345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein String charset = getHeaderParameter(part.getContentType(), "charset"); 244345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (charset != null) { 245345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein /* 246345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * See if there is conversion from the MIME charset to the Java one. 247345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 248345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein charset = CharsetUtil.toJavaCharset(charset); 249345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 250345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein /* 251345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * No encoding, so use us-ascii, which is the standard. 252345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 253345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (charset == null) { 254345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein charset = "ASCII"; 255345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 256345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein /* 257345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Convert and return as new String 258345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 259345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein String result = out.toString(charset); 260345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein out.close(); 261345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return result; 262345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 263345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 264345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 265345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 266345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein catch (OutOfMemoryError oom) { 267345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein /* 268345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * If we are not able to process the body there's nothing we can do about it. Return 269345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * null and let the upper layers handle the missing content. 270345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 271345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein Log.e(LOG_TAG, "Unable to getTextFromPart " + oom.toString()); 272345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 273345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein catch (Exception e) { 274345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein /* 275345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * If we are not able to process the body there's nothing we can do about it. Return 276345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * null and let the upper layers handle the missing content. 277345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 278345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein Log.e(LOG_TAG, "Unable to getTextFromPart " + e.toString()); 279345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 280345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return null; 281345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 282345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 283345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein /** 284345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Returns true if the given mimeType matches the matchAgainst specification. The comparison 285345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * ignores case and the matchAgainst string may include "*" for a wildcard (e.g. "image/*"). 286345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * 287345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * @param mimeType A MIME type to check. 288345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * @param matchAgainst A MIME type to check against. May include wildcards. 289345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * @return true if the mimeType matches 290345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 291345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein public static boolean mimeTypeMatches(String mimeType, String matchAgainst) { 292345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein Pattern p = Pattern.compile(matchAgainst.replaceAll("\\*", "\\.\\*"), 293345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein Pattern.CASE_INSENSITIVE); 294345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return p.matcher(mimeType).matches(); 295345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 296345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 297345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein /** 298345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Returns true if the given mimeType matches any of the matchAgainst specifications. The 299345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * comparison ignores case and the matchAgainst strings may include "*" for a wildcard 300345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * (e.g. "image/*"). 301345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * 302345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * @param mimeType A MIME type to check. 303345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * @param matchAgainst An array of MIME types to check against. May include wildcards. 304345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * @return true if the mimeType matches any of the matchAgainst strings 305345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 306345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein public static boolean mimeTypeMatches(String mimeType, String[] matchAgainst) { 307345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein for (String matchType : matchAgainst) { 308345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (mimeTypeMatches(mimeType, matchType)) { 309345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return true; 310345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 311345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 312345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return false; 313345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 314345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 315345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein /** 316345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Given an input stream and a transfer encoding, return a wrapped input stream for that 317345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * encoding (or the original if none is required) 318345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * @param in the input stream 319345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * @param contentTransferEncoding the content transfer encoding 320345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * @return a properly wrapped stream 321345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 322345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein public static InputStream getInputStreamForContentTransferEncoding(InputStream in, 323345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein String contentTransferEncoding) { 324345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (contentTransferEncoding != null) { 325345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein contentTransferEncoding = 326345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein MimeUtility.getHeaderParameter(contentTransferEncoding, null); 327345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if ("quoted-printable".equalsIgnoreCase(contentTransferEncoding)) { 328345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein in = new QuotedPrintableInputStream(in); 329345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 330345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein else if ("base64".equalsIgnoreCase(contentTransferEncoding)) { 331345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein in = new Base64InputStream(in, Base64.DEFAULT); 332345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 333345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 334345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return in; 335345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 336345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 337345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein /** 338345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Removes any content transfer encoding from the stream and returns a Body. 339345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 340345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein public static Body decodeBody(InputStream in, String contentTransferEncoding) 341345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein throws IOException { 342345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein /* 343345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * We'll remove any transfer encoding by wrapping the stream. 344345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 345345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein in = getInputStreamForContentTransferEncoding(in, contentTransferEncoding); 346345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein BinaryTempFileBody tempBody = new BinaryTempFileBody(); 347345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein OutputStream out = tempBody.getOutputStream(); 348345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein try { 349345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein IOUtils.copy(in, out); 350345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } catch (Base64DataException bde) { 351345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein // TODO Need to fix this somehow 352345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein //String warning = "\n\n" + Email.getMessageDecodeErrorString(); 353345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein //out.write(warning.getBytes()); 354345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } finally { 355345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein out.close(); 356345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 357345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return tempBody; 358345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 359345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 360345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein /** 361345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Recursively scan a Part (usually a Message) and sort out which of its children will be 362345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * "viewable" and which will be attachments. 363345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * 364345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * @param part The part to be broken down 365345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * @param viewables This arraylist will be populated with all parts that appear to be 366345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * the "message" (e.g. text/plain & text/html) 367345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * @param attachments This arraylist will be populated with all parts that appear to be 368345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * attachments (including inlines) 369345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * @throws MessagingException 370345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 371345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein public static void collectParts(Part part, ArrayList<Part> viewables, 372345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein ArrayList<Part> attachments) throws MessagingException { 373345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein String disposition = part.getDisposition(); 374345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein String dispositionType = MimeUtility.getHeaderParameter(disposition, null); 375345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein // If a disposition is not specified, default to "inline" 376345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein boolean inline = 377345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein TextUtils.isEmpty(dispositionType) || "inline".equalsIgnoreCase(dispositionType); 378345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein // The lower-case mime type 379345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein String mimeType = part.getMimeType().toLowerCase(); 380345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 381345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (part.getBody() instanceof Multipart) { 382345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein // If the part is Multipart but not alternative it's either mixed or 383345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein // something we don't know about, which means we treat it as mixed 384345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein // per the spec. We just process its pieces recursively. 385345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein MimeMultipart mp = (MimeMultipart)part.getBody(); 386345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein boolean foundHtml = false; 387345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (mp.getSubTypeForTest().equals("alternative")) { 388345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein for (int i = 0; i < mp.getCount(); i++) { 389345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (mp.getBodyPart(i).isMimeType("text/html")) { 390345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein foundHtml = true; 391345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein break; 392345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 393345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 394345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 395345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein for (int i = 0; i < mp.getCount(); i++) { 396345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein // See if we have text and html 397345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein BodyPart bp = mp.getBodyPart(i); 398345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein // If there's html, don't bother loading text 399345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (foundHtml && bp.isMimeType("text/plain")) { 400345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein continue; 401345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 402345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein collectParts(bp, viewables, attachments); 403345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 404345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } else if (part.getBody() instanceof Message) { 405345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein // If the part is an embedded message we just continue to process 406345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein // it, pulling any viewables or attachments into the running list. 407345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein Message message = (Message)part.getBody(); 408345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein collectParts(message, viewables, attachments); 409345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } else if (inline && (mimeType.startsWith("text") || (mimeType.startsWith("image")))) { 410345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein // We'll treat text and images as viewables 411345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein viewables.add(part); 412345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } else { 413345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein // Everything else is an attachment. 414345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein attachments.add(part); 415345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 416345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 417345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein} 418