1d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian/*
2d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian * Copyright (C) 2015 The Android Open Source Project
3d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian *
4d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian * Licensed under the Apache License, Version 2.0 (the "License");
5d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian * you may not use this file except in compliance with the License.
6d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian * You may obtain a copy of the License at
7d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian *
8d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian *      http://www.apache.org/licenses/LICENSE-2.0
9d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian *
10d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian * Unless required by applicable law or agreed to in writing, software
11d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian * distributed under the License is distributed on an "AS IS" BASIS,
12d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian * See the License for the specific language governing permissions and
14d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian * limitations under the License.
15d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian */
16d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianpackage com.android.voicemail.impl.mail.internet;
17d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
18d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport android.text.TextUtils;
19d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport android.util.Base64;
20d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport android.util.Base64DataException;
21d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport android.util.Base64InputStream;
22d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport com.android.voicemail.impl.VvmLog;
23d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport com.android.voicemail.impl.mail.Body;
24d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport com.android.voicemail.impl.mail.BodyPart;
25d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport com.android.voicemail.impl.mail.Message;
26d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport com.android.voicemail.impl.mail.MessagingException;
27d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport com.android.voicemail.impl.mail.Multipart;
28d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport com.android.voicemail.impl.mail.Part;
29d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport java.io.ByteArrayOutputStream;
30d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport java.io.IOException;
31d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport java.io.InputStream;
32d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport java.io.OutputStream;
33d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport java.util.ArrayList;
34d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport java.util.regex.Matcher;
35d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport java.util.regex.Pattern;
36d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport org.apache.commons.io.IOUtils;
378369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanianimport org.apache.james.mime4j.codec.DecodeMonitor;
388369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanianimport org.apache.james.mime4j.codec.DecoderUtil;
39d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport org.apache.james.mime4j.codec.EncoderUtil;
408369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanianimport org.apache.james.mime4j.codec.QuotedPrintableInputStream;
41d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport org.apache.james.mime4j.util.CharsetUtil;
42d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
43d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianpublic class MimeUtility {
44d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  private static final String LOG_TAG = "Email";
45d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
46d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  public static final String MIME_TYPE_RFC822 = "message/rfc822";
47d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  private static final Pattern PATTERN_CR_OR_LF = Pattern.compile("\r|\n");
48d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
49d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  /**
50d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * Replace sequences of CRLF+WSP with WSP. Tries to preserve original string object whenever
51d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * possible.
52d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   */
53d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  public static String unfold(String s) {
54d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    if (s == null) {
55d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      return null;
56d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    }
57d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    Matcher patternMatcher = PATTERN_CR_OR_LF.matcher(s);
58d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    if (patternMatcher.find()) {
59d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      patternMatcher.reset();
60d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      s = patternMatcher.replaceAll("");
61d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    }
62d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    return s;
63d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  }
64d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
65d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  public static String decode(String s) {
66d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    if (s == null) {
67d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      return null;
68d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    }
698369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian    return DecoderUtil.decodeEncodedWords(s, DecodeMonitor.STRICT);
70d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  }
71d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
72d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  public static String unfoldAndDecode(String s) {
73d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    return decode(unfold(s));
74d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  }
75d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
76d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  // TODO implement proper foldAndEncode
77d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  // NOTE: When this really works, we *must* remove all calls to foldAndEncode2() to prevent
78d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  // duplication of encoding.
79d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  public static String foldAndEncode(String s) {
80d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    return s;
81d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  }
82d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
83d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  /**
84d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * INTERIM version of foldAndEncode that will be used only by Subject: headers. This is safer than
85d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * implementing foldAndEncode() (see above) and risking unknown damage to other headers.
86d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   *
87d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * <p>TODO: Copy this code to foldAndEncode(), get rid of this function, confirm all working OK.
88d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   *
89d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * @param s original string to encode and fold
90d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * @param usedCharacters number of characters already used up by header name
91d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * @return the String ready to be transmitted
92d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   */
93d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  public static String foldAndEncode2(String s, int usedCharacters) {
94d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    // james.mime4j.codec.EncoderUtil.java
95d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    // encode:  encodeIfNecessary(text, usage, numUsedInHeaderName)
96d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    // Usage.TEXT_TOKENlooks like the right thing for subjects
97d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    // use WORD_ENTITY for address/names
98d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
99d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    String encoded = EncoderUtil.encodeIfNecessary(s, EncoderUtil.Usage.TEXT_TOKEN, usedCharacters);
100d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
101d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    return fold(encoded, usedCharacters);
102d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  }
103d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
104d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  /**
105d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * INTERIM: From newer version of org.apache.james (but we don't want to import the entire
106d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * MimeUtil class).
107d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   *
108d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * <p>Splits the specified string into a multiple-line representation with lines no longer than 76
109d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * characters (because the line might contain encoded words; see <a
110d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * href='http://www.faqs.org/rfcs/rfc2047.html'>RFC 2047</a> section 2). If the string contains
111d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * non-whitespace sequences longer than 76 characters a line break is inserted at the whitespace
112d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * character following the sequence resulting in a line longer than 76 characters.
113d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   *
114d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * @param s string to split.
115d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * @param usedCharacters number of characters already used up. Usually the number of characters
116d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   *     for header field name plus colon and one space.
117d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * @return a multiple-line representation of the given string.
118d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   */
119d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  public static String fold(String s, int usedCharacters) {
120d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    final int maxCharacters = 76;
121d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
122d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    final int length = s.length();
123d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    if (usedCharacters + length <= maxCharacters) {
124d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      return s;
125d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    }
126d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
127d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    StringBuilder sb = new StringBuilder();
128d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
129d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    int lastLineBreak = -usedCharacters;
130d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    int wspIdx = indexOfWsp(s, 0);
131d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    while (true) {
132d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      if (wspIdx == length) {
133d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        sb.append(s.substring(Math.max(0, lastLineBreak)));
134d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        return sb.toString();
135d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      }
136d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
137d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      int nextWspIdx = indexOfWsp(s, wspIdx + 1);
138d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
139d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      if (nextWspIdx - lastLineBreak > maxCharacters) {
140d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        sb.append(s.substring(Math.max(0, lastLineBreak), wspIdx));
141d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        sb.append("\r\n");
142d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        lastLineBreak = wspIdx;
143d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      }
144d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
145d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      wspIdx = nextWspIdx;
146d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    }
147d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  }
148d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
149d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  /**
150d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * INTERIM: From newer version of org.apache.james (but we don't want to import the entire
151d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * MimeUtil class).
152d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   *
153d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * <p>Search for whitespace.
154d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   */
155d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  private static int indexOfWsp(String s, int fromIndex) {
156d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    final int len = s.length();
157d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    for (int index = fromIndex; index < len; index++) {
158d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      char c = s.charAt(index);
159d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      if (c == ' ' || c == '\t') {
160d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        return index;
161d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      }
162d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    }
163d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    return len;
164d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  }
165d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
166d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  /**
167d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * Returns the named parameter of a header field. If name is null the first parameter is returned,
168d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * or if there are no additional parameters in the field the entire field is returned. Otherwise
169d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * the named parameter is searched for in a case insensitive fashion and returned. If the
170d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * parameter cannot be found the method returns null.
171d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   *
172d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * <p>TODO: quite inefficient with the inner trimming & splitting. TODO: Also has a latent bug:
173d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * uses "startsWith" to match the name, which can false-positive. TODO: The doc says that for a
174d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * null name you get the first param, but you get the header. Should probably just fix the doc,
175d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * but if other code assumes that behavior, fix the code. TODO: Need to decode %-escaped strings,
176d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * as in: filename="ab%22d". ('+' -> ' ' conversion too? check RFC)
177d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   *
178d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * @param header
179d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * @param name
180d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * @return the entire header (if name=null), the found parameter, or null
181d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   */
182d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  public static String getHeaderParameter(String header, String name) {
183d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    if (header == null) {
184d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      return null;
185d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    }
186d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    String[] parts = unfold(header).split(";");
187d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    if (name == null) {
188d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      return parts[0].trim();
189d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    }
190d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    String lowerCaseName = name.toLowerCase();
191d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    for (String part : parts) {
192d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      if (part.trim().toLowerCase().startsWith(lowerCaseName)) {
193d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        String[] parameterParts = part.split("=", 2);
194d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        if (parameterParts.length < 2) {
195d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          return null;
196d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        }
197d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        String parameter = parameterParts[1].trim();
198d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        if (parameter.startsWith("\"") && parameter.endsWith("\"")) {
199d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          return parameter.substring(1, parameter.length() - 1);
200d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        } else {
201d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          return parameter;
202d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        }
203d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      }
204d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    }
205d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    return null;
206d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  }
207d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
208d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  /**
209d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * Reads the Part's body and returns a String based on any charset conversion that needed to be
210d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * done.
211d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   *
212d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * @param part The part containing a body
213d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * @return a String containing the converted text in the body, or null if there was no text or an
214d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   *     error during conversion.
215d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   */
216d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  public static String getTextFromPart(Part part) {
217d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    try {
218d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      if (part != null && part.getBody() != null) {
219d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        InputStream in = part.getBody().getInputStream();
220d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        String mimeType = part.getMimeType();
221d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        if (mimeType != null && MimeUtility.mimeTypeMatches(mimeType, "text/*")) {
222d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          /*
223d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian           * Now we read the part into a buffer for further processing. Because
224d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian           * the stream is now wrapped we'll remove any transfer encoding at this point.
225d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian           */
226d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          ByteArrayOutputStream out = new ByteArrayOutputStream();
227d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          IOUtils.copy(in, out);
228d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          in.close();
229d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          in = null; // we want all of our memory back, and close might not release
230d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
231d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          /*
232d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian           * We've got a text part, so let's see if it needs to be processed further.
233d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian           */
234d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          String charset = getHeaderParameter(part.getContentType(), "charset");
235d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          if (charset != null) {
236d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian            /*
237d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian             * See if there is conversion from the MIME charset to the Java one.
238d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian             */
2398369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian            charset = CharsetUtil.lookup(charset).name();
240d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          }
241d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          /*
242d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian           * No encoding, so use us-ascii, which is the standard.
243d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian           */
244d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          if (charset == null) {
245d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian            charset = "ASCII";
246d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          }
247d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          /*
248d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian           * Convert and return as new String
249d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian           */
250d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          String result = out.toString(charset);
251d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          out.close();
252d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          return result;
253d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        }
254d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      }
255d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
256d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    } catch (OutOfMemoryError oom) {
257d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      /*
258d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian       * If we are not able to process the body there's nothing we can do about it. Return
259d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian       * null and let the upper layers handle the missing content.
260d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian       */
261d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      VvmLog.e(LOG_TAG, "Unable to getTextFromPart " + oom.toString());
262d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    } catch (Exception e) {
263d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      /*
264d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian       * If we are not able to process the body there's nothing we can do about it. Return
265d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian       * null and let the upper layers handle the missing content.
266d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian       */
267d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      VvmLog.e(LOG_TAG, "Unable to getTextFromPart " + e.toString());
268d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    }
269d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    return null;
270d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  }
271d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
272d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  /**
273d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * Returns true if the given mimeType matches the matchAgainst specification. The comparison
274d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * ignores case and the matchAgainst string may include "*" for a wildcard (e.g. "image/*").
275d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   *
276d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * @param mimeType A MIME type to check.
277d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * @param matchAgainst A MIME type to check against. May include wildcards.
278d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * @return true if the mimeType matches
279d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   */
280d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  public static boolean mimeTypeMatches(String mimeType, String matchAgainst) {
281d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    Pattern p = Pattern.compile(matchAgainst.replaceAll("\\*", "\\.\\*"), Pattern.CASE_INSENSITIVE);
282d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    return p.matcher(mimeType).matches();
283d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  }
284d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
285d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  /**
286d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * Returns true if the given mimeType matches any of the matchAgainst specifications. The
287d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * comparison ignores case and the matchAgainst strings may include "*" for a wildcard (e.g.
288d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * "image/*").
289d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   *
290d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * @param mimeType A MIME type to check.
291d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * @param matchAgainst An array of MIME types to check against. May include wildcards.
292d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * @return true if the mimeType matches any of the matchAgainst strings
293d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   */
294d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  public static boolean mimeTypeMatches(String mimeType, String[] matchAgainst) {
295d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    for (String matchType : matchAgainst) {
296d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      if (mimeTypeMatches(mimeType, matchType)) {
297d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        return true;
298d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      }
299d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    }
300d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    return false;
301d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  }
302d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
303d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  /**
304d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * Given an input stream and a transfer encoding, return a wrapped input stream for that encoding
305d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * (or the original if none is required)
306d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   *
307d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * @param in the input stream
308d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * @param contentTransferEncoding the content transfer encoding
309d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * @return a properly wrapped stream
310d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   */
311d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  public static InputStream getInputStreamForContentTransferEncoding(
312d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      InputStream in, String contentTransferEncoding) {
313d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    if (contentTransferEncoding != null) {
314d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      contentTransferEncoding = MimeUtility.getHeaderParameter(contentTransferEncoding, null);
315d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      if ("quoted-printable".equalsIgnoreCase(contentTransferEncoding)) {
316d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        in = new QuotedPrintableInputStream(in);
317d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      } else if ("base64".equalsIgnoreCase(contentTransferEncoding)) {
318d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        in = new Base64InputStream(in, Base64.DEFAULT);
319d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      }
320d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    }
321d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    return in;
322d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  }
323d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
324d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  /** Removes any content transfer encoding from the stream and returns a Body. */
325d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  public static Body decodeBody(InputStream in, String contentTransferEncoding) throws IOException {
326d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    /*
327d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian     * We'll remove any transfer encoding by wrapping the stream.
328d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian     */
329d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    in = getInputStreamForContentTransferEncoding(in, contentTransferEncoding);
330d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    BinaryTempFileBody tempBody = new BinaryTempFileBody();
331d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    OutputStream out = tempBody.getOutputStream();
332d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    try {
333d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      IOUtils.copy(in, out);
334d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    } catch (Base64DataException bde) {
335d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      // TODO Need to fix this somehow
336d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      //String warning = "\n\n" + Email.getMessageDecodeErrorString();
337d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      //out.write(warning.getBytes());
338d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    } finally {
339d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      out.close();
340d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    }
341d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    return tempBody;
342d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  }
343d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
344d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  /**
345d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * Recursively scan a Part (usually a Message) and sort out which of its children will be
346d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * "viewable" and which will be attachments.
347d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   *
348d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * @param part The part to be broken down
349d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * @param viewables This arraylist will be populated with all parts that appear to be the
350d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   *     "message" (e.g. text/plain & text/html)
351d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * @param attachments This arraylist will be populated with all parts that appear to be
352d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   *     attachments (including inlines)
353d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * @throws MessagingException
354d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   */
355d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  public static void collectParts(Part part, ArrayList<Part> viewables, ArrayList<Part> attachments)
356d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      throws MessagingException {
357d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    String disposition = part.getDisposition();
358d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    String dispositionType = MimeUtility.getHeaderParameter(disposition, null);
359d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    // If a disposition is not specified, default to "inline"
360d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    boolean inline =
361d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        TextUtils.isEmpty(dispositionType) || "inline".equalsIgnoreCase(dispositionType);
362d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    // The lower-case mime type
363d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    String mimeType = part.getMimeType().toLowerCase();
364d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
365d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    if (part.getBody() instanceof Multipart) {
366d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      // If the part is Multipart but not alternative it's either mixed or
367d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      // something we don't know about, which means we treat it as mixed
368d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      // per the spec. We just process its pieces recursively.
369d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      MimeMultipart mp = (MimeMultipart) part.getBody();
370d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      boolean foundHtml = false;
371d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      if (mp.getSubTypeForTest().equals("alternative")) {
372d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        for (int i = 0; i < mp.getCount(); i++) {
373d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          if (mp.getBodyPart(i).isMimeType("text/html")) {
374d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian            foundHtml = true;
375d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian            break;
376d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          }
377d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        }
378d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      }
379d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      for (int i = 0; i < mp.getCount(); i++) {
380d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        // See if we have text and html
381d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        BodyPart bp = mp.getBodyPart(i);
382d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        // If there's html, don't bother loading text
383d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        if (foundHtml && bp.isMimeType("text/plain")) {
384d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          continue;
385d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        }
386d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        collectParts(bp, viewables, attachments);
387d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      }
388d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    } else if (part.getBody() instanceof Message) {
389d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      // If the part is an embedded message we just continue to process
390d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      // it, pulling any viewables or attachments into the running list.
391d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      Message message = (Message) part.getBody();
392d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      collectParts(message, viewables, attachments);
393d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    } else if (inline && (mimeType.startsWith("text") || (mimeType.startsWith("image")))) {
394d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      // We'll treat text and images as viewables
395d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      viewables.add(part);
396d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    } else {
397d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      // Everything else is an attachment.
398d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      attachments.add(part);
399d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    }
400d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  }
401d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian}
402