replicant-packages_apps_Email/src/com/android/email/mail/internet/MimeUtility.java

322 lines
12 KiB
Java

/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.email.mail.internet;
import com.android.email.Email;
import com.android.email.mail.Body;
import com.android.email.mail.BodyPart;
import com.android.email.mail.Message;
import com.android.email.mail.MessagingException;
import com.android.email.mail.Multipart;
import com.android.email.mail.Part;
import org.apache.commons.io.IOUtils;
import org.apache.james.mime4j.decoder.Base64InputStream;
import org.apache.james.mime4j.decoder.DecoderUtil;
import org.apache.james.mime4j.decoder.QuotedPrintableInputStream;
import org.apache.james.mime4j.util.CharsetUtil;
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.regex.Pattern;
public class MimeUtility {
public static String unfold(String s) {
if (s == null) {
return null;
}
return s.replaceAll("\r|\n", "");
}
public static String decode(String s) {
if (s == null) {
return null;
}
return DecoderUtil.decodeEncodedWords(s);
}
public static String unfoldAndDecode(String s) {
return decode(unfold(s));
}
// TODO implement proper foldAndEncode
public static String foldAndEncode(String s) {
return s;
}
/**
* Returns the named parameter of a header field. If name is null the first
* parameter is returned, or if there are no additional parameters in the
* field the entire field is returned. Otherwise the named parameter is
* searched for in a case insensitive fashion and returned. If the parameter
* cannot be found the method returns null.
*
* @param header
* @param name
* @return
*/
public static String getHeaderParameter(String header, String name) {
if (header == null) {
return null;
}
header = header.replaceAll("\r|\n", "");
String[] parts = header.split(";");
if (name == null) {
return parts[0];
}
for (String part : parts) {
if (part.trim().toLowerCase().startsWith(name.toLowerCase())) {
String parameter = part.split("=", 2)[1].trim();
if (parameter.startsWith("\"") && parameter.endsWith("\"")) {
return parameter.substring(1, parameter.length() - 1);
}
else {
return parameter;
}
}
}
return null;
}
public static Part findFirstPartByMimeType(Part part, String mimeType)
throws MessagingException {
if (part.getBody() instanceof Multipart) {
Multipart multipart = (Multipart)part.getBody();
for (int i = 0, count = multipart.getCount(); i < count; i++) {
BodyPart bodyPart = multipart.getBodyPart(i);
Part ret = findFirstPartByMimeType(bodyPart, mimeType);
if (ret != null) {
return ret;
}
}
}
else if (part.getMimeType().equalsIgnoreCase(mimeType)) {
return part;
}
return null;
}
public static Part findPartByContentId(Part part, String contentId) throws Exception {
if (part.getBody() instanceof Multipart) {
Multipart multipart = (Multipart)part.getBody();
for (int i = 0, count = multipart.getCount(); i < count; i++) {
BodyPart bodyPart = multipart.getBodyPart(i);
Part ret = findPartByContentId(bodyPart, contentId);
if (ret != null) {
return ret;
}
}
}
String cid = part.getContentId();
if (contentId.equals(cid)) {
return part;
}
return null;
}
/**
* Reads the Part's body and returns a String based on any charset conversion that needed
* to be done.
* @param part
* @return
* @throws IOException
*/
public static String getTextFromPart(Part part) {
try {
if (part != null && part.getBody() != null) {
InputStream in = part.getBody().getInputStream();
String mimeType = part.getMimeType();
if (mimeType != null && MimeUtility.mimeTypeMatches(mimeType, "text/*")) {
/*
* Now we read the part into a buffer for further processing. Because
* the stream is now wrapped we'll remove any transfer encoding at this point.
*/
ByteArrayOutputStream out = new ByteArrayOutputStream();
IOUtils.copy(in, out);
byte[] bytes = out.toByteArray();
in.close();
out.close();
String charset = getHeaderParameter(part.getContentType(), "charset");
/*
* We've got a text part, so let's see if it needs to be processed further.
*/
if (charset != null) {
/*
* See if there is conversion from the MIME charset to the Java one.
*/
charset = CharsetUtil.toJavaCharset(charset);
}
if (charset != null) {
/*
* We've got a charset encoding, so decode using it.
*/
return new String(bytes, 0, bytes.length, charset);
}
else {
/*
* No encoding, so use us-ascii, which is the standard.
*/
return new String(bytes, 0, bytes.length, "ASCII");
}
}
}
}
catch (Exception e) {
/*
* If we are not able to process the body there's nothing we can do about it. Return
* null and let the upper layers handle the missing content.
*/
Log.e(Email.LOG_TAG, "Unable to getTextFromPart", e);
}
return null;
}
/**
* Returns true if the given mimeType matches the matchAgainst specification. The comparison
* ignores case and the matchAgainst string may include "*" for a wildcard (e.g. "image/*").
*
* @param mimeType A MIME type to check.
* @param matchAgainst A MIME type to check against. May include wildcards.
* @return true if the mimeType matches
*/
public static boolean mimeTypeMatches(String mimeType, String matchAgainst) {
Pattern p = Pattern.compile(matchAgainst.replaceAll("\\*", "\\.\\*"),
Pattern.CASE_INSENSITIVE);
return p.matcher(mimeType).matches();
}
/**
* Returns true if the given mimeType matches any of the matchAgainst specifications. The
* comparison ignores case and the matchAgainst strings may include "*" for a wildcard
* (e.g. "image/*").
*
* @param mimeType A MIME type to check.
* @param matchAgainst An array of MIME types to check against. May include wildcards.
* @return true if the mimeType matches any of the matchAgainst strings
*/
public static boolean mimeTypeMatches(String mimeType, String[] matchAgainst) {
for (String matchType : matchAgainst) {
if (mimeTypeMatches(mimeType, matchType)) {
return true;
}
}
return false;
}
/**
* Removes any content transfer encoding from the stream and returns a Body.
*/
public static Body decodeBody(InputStream in, String contentTransferEncoding)
throws IOException {
/*
* We'll remove any transfer encoding by wrapping the stream.
*/
if (contentTransferEncoding != null) {
contentTransferEncoding =
MimeUtility.getHeaderParameter(contentTransferEncoding, null);
if ("quoted-printable".equalsIgnoreCase(contentTransferEncoding)) {
in = new QuotedPrintableInputStream(in);
}
else if ("base64".equalsIgnoreCase(contentTransferEncoding)) {
in = new Base64InputStream(in);
}
}
BinaryTempFileBody tempBody = new BinaryTempFileBody();
OutputStream out = tempBody.getOutputStream();
IOUtils.copy(in, out);
out.close();
return tempBody;
}
/**
* An unfortunately named method that makes decisions about a Part (usually a Message)
* as to which of it's children will be "viewable" and which will be attachments.
* The method recursively sorts the viewables and attachments into seperate
* lists for further processing.
* @param part
* @param viewables
* @param attachments
* @throws MessagingException
*/
public static void collectParts(Part part, ArrayList<Part> viewables,
ArrayList<Part> attachments) throws MessagingException {
String disposition = part.getDisposition();
String dispositionType = null;
String dispositionFilename = null;
if (disposition != null) {
dispositionType = MimeUtility.getHeaderParameter(disposition, null);
dispositionFilename = MimeUtility.getHeaderParameter(disposition, "filename");
}
/*
* A best guess that this part is intended to be an attachment and not inline.
*/
boolean attachment = ("attachment".equalsIgnoreCase(dispositionType))
|| (dispositionFilename != null)
&& (!"inline".equalsIgnoreCase(dispositionType));
/*
* If the part is Multipart but not alternative it's either mixed or
* something we don't know about, which means we treat it as mixed
* per the spec. We just process it's pieces recursively.
*/
if (part.getBody() instanceof Multipart) {
Multipart mp = (Multipart)part.getBody();
for (int i = 0; i < mp.getCount(); i++) {
collectParts(mp.getBodyPart(i), viewables, attachments);
}
}
/*
* If the part is an embedded message we just continue to process
* it, pulling any viewables or attachments into the running list.
*/
else if (part.getBody() instanceof Message) {
Message message = (Message)part.getBody();
collectParts(message, viewables, attachments);
}
/*
* If the part is HTML and it got this far it's part of a mixed (et
* al) and should be rendered inline.
*/
else if ((!attachment) && (part.getMimeType().equalsIgnoreCase("text/html"))) {
viewables.add(part);
}
/*
* If the part is plain text and it got this far it's part of a
* mixed (et al) and should be rendered inline.
*/
else if ((!attachment) && (part.getMimeType().equalsIgnoreCase("text/plain"))) {
viewables.add(part);
}
/*
* Finally, if it's nothing else we will include it as an attachment.
*/
else {
attachments.add(part);
}
}
}