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