Moved code out of emailcommon to UnifedEmail.
This CL is mostly just deletion since the new code is in a new project. Change-Id: I4aee519187a6ed7514c97d2a7a85ed29ea29d25f
This commit is contained in:
parent
f5ed3d2c9d
commit
91973256c2
@ -25,9 +25,6 @@ unified_email_src_dir := ../../UnifiedEmail/src
|
||||
apache_src_dir := ../../UnifiedEmail/src/org
|
||||
|
||||
imported_unified_email_files := \
|
||||
$(unified_email_unified_src_dir)/com/android/mail/utils/LogTag.java \
|
||||
$(unified_email_src_dir)/com/android/mail/preferences/BasePreferenceMigrator.java \
|
||||
$(unified_email_unified_src_dir)/com/android/mail/preferences/PreferenceMigrator.java \
|
||||
$(unified_email_src_dir)/com/android/mail/utils/LogUtils.java \
|
||||
$(unified_email_src_dir)/com/android/mail/providers/UIProvider.java
|
||||
|
||||
@ -41,6 +38,7 @@ LOCAL_SRC_FILES += \
|
||||
src/com/android/emailcommon/service/IAccountService.aidl
|
||||
LOCAL_SRC_FILES += $(call all-java-files-under, $(apache_src_dir))
|
||||
LOCAL_SRC_FILES += $(imported_unified_email_files)
|
||||
LOCAL_SRC_FILES += $(call all-java-files-under, $(unified_email_src_dir)/com/android/emailcommon)
|
||||
|
||||
LOCAL_SDK_VERSION := 14
|
||||
|
||||
|
@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 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.emailcommon;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* TempDirectory caches the directory used for caching file. It is set up during application
|
||||
* initialization.
|
||||
*/
|
||||
public class TempDirectory {
|
||||
private static File sTempDirectory = null;
|
||||
|
||||
public static void setTempDirectory(Context context) {
|
||||
sTempDirectory = context.getCacheDir();
|
||||
}
|
||||
|
||||
public static File getTempDirectory() {
|
||||
if (sTempDirectory == null) {
|
||||
throw new RuntimeException(
|
||||
"TempDirectory not set. " +
|
||||
"If in a unit test, call Email.setTempDirectory(context) in setUp().");
|
||||
}
|
||||
return sTempDirectory;
|
||||
}
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
/*
|
||||
* 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.emailcommon.internet;
|
||||
|
||||
import com.android.emailcommon.TempDirectory;
|
||||
import com.android.emailcommon.mail.Body;
|
||||
import com.android.emailcommon.mail.MessagingException;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import android.util.Base64;
|
||||
import android.util.Base64OutputStream;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* A Body that is backed by a temp file. The Body exposes a getOutputStream method that allows
|
||||
* the user to write to the temp file. After the write the body is available via getInputStream
|
||||
* and writeTo one time. After writeTo is called, or the InputStream returned from
|
||||
* getInputStream is closed the file is deleted and the Body should be considered disposed of.
|
||||
*/
|
||||
public class BinaryTempFileBody implements Body {
|
||||
private File mFile;
|
||||
|
||||
/**
|
||||
* An alternate way to put data into a BinaryTempFileBody is to simply supply an already-
|
||||
* created file. Note that this file will be deleted after it is read.
|
||||
* @param filePath The file containing the data to be stored on disk temporarily
|
||||
*/
|
||||
public void setFile(String filePath) {
|
||||
mFile = new File(filePath);
|
||||
}
|
||||
|
||||
public OutputStream getOutputStream() throws IOException {
|
||||
mFile = File.createTempFile("body", null, TempDirectory.getTempDirectory());
|
||||
mFile.deleteOnExit();
|
||||
return new FileOutputStream(mFile);
|
||||
}
|
||||
|
||||
public InputStream getInputStream() throws MessagingException {
|
||||
try {
|
||||
return new BinaryTempFileBodyInputStream(new FileInputStream(mFile));
|
||||
}
|
||||
catch (IOException ioe) {
|
||||
throw new MessagingException("Unable to open body", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeTo(OutputStream out) throws IOException, MessagingException {
|
||||
InputStream in = getInputStream();
|
||||
Base64OutputStream base64Out = new Base64OutputStream(
|
||||
out, Base64.CRLF | Base64.NO_CLOSE);
|
||||
IOUtils.copy(in, base64Out);
|
||||
base64Out.close();
|
||||
mFile.delete();
|
||||
}
|
||||
|
||||
class BinaryTempFileBodyInputStream extends FilterInputStream {
|
||||
public BinaryTempFileBodyInputStream(InputStream in) {
|
||||
super(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
super.close();
|
||||
mFile.delete();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,193 +0,0 @@
|
||||
/*
|
||||
* 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.emailcommon.internet;
|
||||
|
||||
import com.android.emailcommon.mail.Body;
|
||||
import com.android.emailcommon.mail.BodyPart;
|
||||
import com.android.emailcommon.mail.MessagingException;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* TODO this is a close approximation of Message, need to update along with
|
||||
* Message.
|
||||
*/
|
||||
public class MimeBodyPart extends BodyPart {
|
||||
protected MimeHeader mHeader = new MimeHeader();
|
||||
protected MimeHeader mExtendedHeader;
|
||||
protected Body mBody;
|
||||
protected int mSize;
|
||||
|
||||
// regex that matches content id surrounded by "<>" optionally.
|
||||
private static final Pattern REMOVE_OPTIONAL_BRACKETS = Pattern.compile("^<?([^>]+)>?$");
|
||||
// regex that matches end of line.
|
||||
private static final Pattern END_OF_LINE = Pattern.compile("\r?\n");
|
||||
|
||||
public MimeBodyPart() throws MessagingException {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public MimeBodyPart(Body body) throws MessagingException {
|
||||
this(body, null);
|
||||
}
|
||||
|
||||
public MimeBodyPart(Body body, String mimeType) throws MessagingException {
|
||||
if (mimeType != null) {
|
||||
setHeader(MimeHeader.HEADER_CONTENT_TYPE, mimeType);
|
||||
}
|
||||
setBody(body);
|
||||
}
|
||||
|
||||
protected String getFirstHeader(String name) throws MessagingException {
|
||||
return mHeader.getFirstHeader(name);
|
||||
}
|
||||
|
||||
public void addHeader(String name, String value) throws MessagingException {
|
||||
mHeader.addHeader(name, value);
|
||||
}
|
||||
|
||||
public void setHeader(String name, String value) throws MessagingException {
|
||||
mHeader.setHeader(name, value);
|
||||
}
|
||||
|
||||
public String[] getHeader(String name) throws MessagingException {
|
||||
return mHeader.getHeader(name);
|
||||
}
|
||||
|
||||
public void removeHeader(String name) throws MessagingException {
|
||||
mHeader.removeHeader(name);
|
||||
}
|
||||
|
||||
public Body getBody() throws MessagingException {
|
||||
return mBody;
|
||||
}
|
||||
|
||||
public void setBody(Body body) throws MessagingException {
|
||||
this.mBody = body;
|
||||
if (body instanceof com.android.emailcommon.mail.Multipart) {
|
||||
com.android.emailcommon.mail.Multipart multipart =
|
||||
((com.android.emailcommon.mail.Multipart)body);
|
||||
multipart.setParent(this);
|
||||
setHeader(MimeHeader.HEADER_CONTENT_TYPE, multipart.getContentType());
|
||||
}
|
||||
else if (body instanceof TextBody) {
|
||||
String contentType = String.format("%s;\n charset=utf-8", getMimeType());
|
||||
String name = MimeUtility.getHeaderParameter(getContentType(), "name");
|
||||
if (name != null) {
|
||||
contentType += String.format(";\n name=\"%s\"", name);
|
||||
}
|
||||
setHeader(MimeHeader.HEADER_CONTENT_TYPE, contentType);
|
||||
setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "base64");
|
||||
}
|
||||
}
|
||||
|
||||
public String getContentType() throws MessagingException {
|
||||
String contentType = getFirstHeader(MimeHeader.HEADER_CONTENT_TYPE);
|
||||
if (contentType == null) {
|
||||
return "text/plain";
|
||||
} else {
|
||||
return contentType;
|
||||
}
|
||||
}
|
||||
|
||||
public String getDisposition() throws MessagingException {
|
||||
String contentDisposition = getFirstHeader(MimeHeader.HEADER_CONTENT_DISPOSITION);
|
||||
if (contentDisposition == null) {
|
||||
return null;
|
||||
} else {
|
||||
return contentDisposition;
|
||||
}
|
||||
}
|
||||
|
||||
public String getContentId() throws MessagingException {
|
||||
String contentId = getFirstHeader(MimeHeader.HEADER_CONTENT_ID);
|
||||
if (contentId == null) {
|
||||
return null;
|
||||
} else {
|
||||
// remove optionally surrounding brackets.
|
||||
return REMOVE_OPTIONAL_BRACKETS.matcher(contentId).replaceAll("$1");
|
||||
}
|
||||
}
|
||||
|
||||
public String getMimeType() throws MessagingException {
|
||||
return MimeUtility.getHeaderParameter(getContentType(), null);
|
||||
}
|
||||
|
||||
public boolean isMimeType(String mimeType) throws MessagingException {
|
||||
return getMimeType().equals(mimeType);
|
||||
}
|
||||
|
||||
public void setSize(int size) {
|
||||
this.mSize = size;
|
||||
}
|
||||
|
||||
public int getSize() throws MessagingException {
|
||||
return mSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set extended header
|
||||
*
|
||||
* @param name Extended header name
|
||||
* @param value header value - flattened by removing CR-NL if any
|
||||
* remove header if value is null
|
||||
* @throws MessagingException
|
||||
*/
|
||||
public void setExtendedHeader(String name, String value) throws MessagingException {
|
||||
if (value == null) {
|
||||
if (mExtendedHeader != null) {
|
||||
mExtendedHeader.removeHeader(name);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (mExtendedHeader == null) {
|
||||
mExtendedHeader = new MimeHeader();
|
||||
}
|
||||
mExtendedHeader.setHeader(name, END_OF_LINE.matcher(value).replaceAll(""));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get extended header
|
||||
*
|
||||
* @param name Extended header name
|
||||
* @return header value - null if header does not exist
|
||||
* @throws MessagingException
|
||||
*/
|
||||
public String getExtendedHeader(String name) throws MessagingException {
|
||||
if (mExtendedHeader == null) {
|
||||
return null;
|
||||
}
|
||||
return mExtendedHeader.getFirstHeader(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the MimeMessage out in MIME format.
|
||||
*/
|
||||
public void writeTo(OutputStream out) throws IOException, MessagingException {
|
||||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out), 1024);
|
||||
mHeader.writeTo(out);
|
||||
writer.write("\r\n");
|
||||
writer.flush();
|
||||
if (mBody != null) {
|
||||
mBody.writeTo(out);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,153 +0,0 @@
|
||||
/*
|
||||
* 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.emailcommon.internet;
|
||||
|
||||
import com.android.emailcommon.mail.MessagingException;
|
||||
import com.android.emailcommon.utility.Utility;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class MimeHeader {
|
||||
/**
|
||||
* Application specific header that contains Store specific information about an attachment.
|
||||
* In IMAP this contains the IMAP BODYSTRUCTURE part id so that the ImapStore can later
|
||||
* retrieve the attachment at will from the server.
|
||||
* The info is recorded from this header on LocalStore.appendMessages and is put back
|
||||
* into the MIME data by LocalStore.fetch.
|
||||
*/
|
||||
public static final String HEADER_ANDROID_ATTACHMENT_STORE_DATA = "X-Android-Attachment-StoreData";
|
||||
/**
|
||||
* Application specific header that is used to tag body parts for quoted/forwarded messages.
|
||||
*/
|
||||
public static final String HEADER_ANDROID_BODY_QUOTED_PART = "X-Android-Body-Quoted-Part";
|
||||
|
||||
public static final String HEADER_CONTENT_TYPE = "Content-Type";
|
||||
public static final String HEADER_CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding";
|
||||
public static final String HEADER_CONTENT_DISPOSITION = "Content-Disposition";
|
||||
public static final String HEADER_CONTENT_ID = "Content-ID";
|
||||
|
||||
/**
|
||||
* Fields that should be omitted when writing the header using writeTo()
|
||||
*/
|
||||
private static final String[] WRITE_OMIT_FIELDS = {
|
||||
// HEADER_ANDROID_ATTACHMENT_DOWNLOADED,
|
||||
// HEADER_ANDROID_ATTACHMENT_ID,
|
||||
HEADER_ANDROID_ATTACHMENT_STORE_DATA
|
||||
};
|
||||
|
||||
protected final ArrayList<Field> mFields = new ArrayList<Field>();
|
||||
|
||||
public void clear() {
|
||||
mFields.clear();
|
||||
}
|
||||
|
||||
public String getFirstHeader(String name) throws MessagingException {
|
||||
String[] header = getHeader(name);
|
||||
if (header == null) {
|
||||
return null;
|
||||
}
|
||||
return header[0];
|
||||
}
|
||||
|
||||
public void addHeader(String name, String value) throws MessagingException {
|
||||
mFields.add(new Field(name, value));
|
||||
}
|
||||
|
||||
public void setHeader(String name, String value) throws MessagingException {
|
||||
if (name == null || value == null) {
|
||||
return;
|
||||
}
|
||||
removeHeader(name);
|
||||
addHeader(name, value);
|
||||
}
|
||||
|
||||
public String[] getHeader(String name) throws MessagingException {
|
||||
ArrayList<String> values = new ArrayList<String>();
|
||||
for (Field field : mFields) {
|
||||
if (field.name.equalsIgnoreCase(name)) {
|
||||
values.add(field.value);
|
||||
}
|
||||
}
|
||||
if (values.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
return values.toArray(new String[] {});
|
||||
}
|
||||
|
||||
public void removeHeader(String name) throws MessagingException {
|
||||
ArrayList<Field> removeFields = new ArrayList<Field>();
|
||||
for (Field field : mFields) {
|
||||
if (field.name.equalsIgnoreCase(name)) {
|
||||
removeFields.add(field);
|
||||
}
|
||||
}
|
||||
mFields.removeAll(removeFields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write header into String
|
||||
*
|
||||
* @return CR-NL separated header string except the headers in writeOmitFields
|
||||
* null if header is empty
|
||||
*/
|
||||
public String writeToString() {
|
||||
if (mFields.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (Field field : mFields) {
|
||||
if (!Utility.arrayContains(WRITE_OMIT_FIELDS, field.name)) {
|
||||
builder.append(field.name + ": " + field.value + "\r\n");
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public void writeTo(OutputStream out) throws IOException, MessagingException {
|
||||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out), 1024);
|
||||
for (Field field : mFields) {
|
||||
if (!Utility.arrayContains(WRITE_OMIT_FIELDS, field.name)) {
|
||||
writer.write(field.name + ": " + field.value + "\r\n");
|
||||
}
|
||||
}
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
private static class Field {
|
||||
final String name;
|
||||
final String value;
|
||||
|
||||
public Field(String name, String value) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name + "=" + value;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return (mFields == null) ? null : mFields.toString();
|
||||
}
|
||||
}
|
@ -1,644 +0,0 @@
|
||||
/*
|
||||
* 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.emailcommon.internet;
|
||||
|
||||
import com.android.emailcommon.mail.Address;
|
||||
import com.android.emailcommon.mail.Body;
|
||||
import com.android.emailcommon.mail.BodyPart;
|
||||
import com.android.emailcommon.mail.Message;
|
||||
import com.android.emailcommon.mail.MessagingException;
|
||||
import com.android.emailcommon.mail.Multipart;
|
||||
import com.android.emailcommon.mail.Part;
|
||||
|
||||
import org.apache.james.mime4j.BodyDescriptor;
|
||||
import org.apache.james.mime4j.ContentHandler;
|
||||
import org.apache.james.mime4j.EOLConvertingInputStream;
|
||||
import org.apache.james.mime4j.MimeStreamParser;
|
||||
import org.apache.james.mime4j.field.DateTimeField;
|
||||
import org.apache.james.mime4j.field.Field;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.Stack;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* An implementation of Message that stores all of its metadata in RFC 822 and
|
||||
* RFC 2045 style headers.
|
||||
*
|
||||
* NOTE: Automatic generation of a local message-id is becoming unwieldy and should be removed.
|
||||
* It would be better to simply do it explicitly on local creation of new outgoing messages.
|
||||
*/
|
||||
public class MimeMessage extends Message {
|
||||
private MimeHeader mHeader;
|
||||
private MimeHeader mExtendedHeader;
|
||||
|
||||
// NOTE: The fields here are transcribed out of headers, and values stored here will supercede
|
||||
// the values found in the headers. Use caution to prevent any out-of-phase errors. In
|
||||
// particular, any adds/changes/deletes here must be echoed by changes in the parse() function.
|
||||
private Address[] mFrom;
|
||||
private Address[] mTo;
|
||||
private Address[] mCc;
|
||||
private Address[] mBcc;
|
||||
private Address[] mReplyTo;
|
||||
private Date mSentDate;
|
||||
private Body mBody;
|
||||
protected int mSize;
|
||||
private boolean mInhibitLocalMessageId = false;
|
||||
private boolean mComplete = true;
|
||||
|
||||
// Shared random source for generating local message-id values
|
||||
private static final java.util.Random sRandom = new java.util.Random();
|
||||
|
||||
// In MIME, en_US-like date format should be used. In other words "MMM" should be encoded to
|
||||
// "Jan", not the other localized format like "Ene" (meaning January in locale es).
|
||||
// This conversion is used when generating outgoing MIME messages. Incoming MIME date
|
||||
// headers are parsed by org.apache.james.mime4j.field.DateTimeField which does not have any
|
||||
// localization code.
|
||||
private static final SimpleDateFormat DATE_FORMAT =
|
||||
new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.US);
|
||||
|
||||
// regex that matches content id surrounded by "<>" optionally.
|
||||
private static final Pattern REMOVE_OPTIONAL_BRACKETS = Pattern.compile("^<?([^>]+)>?$");
|
||||
// regex that matches end of line.
|
||||
private static final Pattern END_OF_LINE = Pattern.compile("\r?\n");
|
||||
|
||||
public MimeMessage() {
|
||||
mHeader = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a local message id. This is only used when none has been assigned, and is
|
||||
* installed lazily. Any remote (typically server-assigned) message id takes precedence.
|
||||
* @return a long, locally-generated message-ID value
|
||||
*/
|
||||
private String generateMessageId() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append("<");
|
||||
for (int i = 0; i < 24; i++) {
|
||||
// We'll use a 5-bit range (0..31)
|
||||
int value = sRandom.nextInt() & 31;
|
||||
char c = "0123456789abcdefghijklmnopqrstuv".charAt(value);
|
||||
sb.append(c);
|
||||
}
|
||||
sb.append(".");
|
||||
sb.append(Long.toString(System.currentTimeMillis()));
|
||||
sb.append("@email.android.com>");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the given InputStream using Apache Mime4J to build a MimeMessage.
|
||||
*
|
||||
* @param in
|
||||
* @throws IOException
|
||||
* @throws MessagingException
|
||||
*/
|
||||
public MimeMessage(InputStream in) throws IOException, MessagingException {
|
||||
parse(in);
|
||||
}
|
||||
|
||||
private MimeStreamParser init() {
|
||||
// Before parsing the input stream, clear all local fields that may be superceded by
|
||||
// the new incoming message.
|
||||
getMimeHeaders().clear();
|
||||
mInhibitLocalMessageId = true;
|
||||
mFrom = null;
|
||||
mTo = null;
|
||||
mCc = null;
|
||||
mBcc = null;
|
||||
mReplyTo = null;
|
||||
mSentDate = null;
|
||||
mBody = null;
|
||||
|
||||
MimeStreamParser parser = new MimeStreamParser();
|
||||
parser.setContentHandler(new MimeMessageBuilder());
|
||||
return parser;
|
||||
}
|
||||
|
||||
protected void parse(InputStream in) throws IOException, MessagingException {
|
||||
MimeStreamParser parser = init();
|
||||
parser.parse(new EOLConvertingInputStream(in));
|
||||
mComplete = !parser.getPrematureEof();
|
||||
}
|
||||
|
||||
public void parse(InputStream in, EOLConvertingInputStream.Callback callback)
|
||||
throws IOException, MessagingException {
|
||||
MimeStreamParser parser = init();
|
||||
parser.parse(new EOLConvertingInputStream(in, getSize(), callback));
|
||||
mComplete = !parser.getPrematureEof();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the internal mHeader value, with very lazy initialization.
|
||||
* The goal is to save memory by not creating the headers until needed.
|
||||
*/
|
||||
private MimeHeader getMimeHeaders() {
|
||||
if (mHeader == null) {
|
||||
mHeader = new MimeHeader();
|
||||
}
|
||||
return mHeader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getReceivedDate() throws MessagingException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getSentDate() throws MessagingException {
|
||||
if (mSentDate == null) {
|
||||
try {
|
||||
DateTimeField field = (DateTimeField)Field.parse("Date: "
|
||||
+ MimeUtility.unfoldAndDecode(getFirstHeader("Date")));
|
||||
mSentDate = field.getDate();
|
||||
} catch (Exception e) {
|
||||
|
||||
}
|
||||
}
|
||||
return mSentDate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSentDate(Date sentDate) throws MessagingException {
|
||||
setHeader("Date", DATE_FORMAT.format(sentDate));
|
||||
this.mSentDate = sentDate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentType() throws MessagingException {
|
||||
String contentType = getFirstHeader(MimeHeader.HEADER_CONTENT_TYPE);
|
||||
if (contentType == null) {
|
||||
return "text/plain";
|
||||
} else {
|
||||
return contentType;
|
||||
}
|
||||
}
|
||||
|
||||
public String getDisposition() throws MessagingException {
|
||||
String contentDisposition = getFirstHeader(MimeHeader.HEADER_CONTENT_DISPOSITION);
|
||||
if (contentDisposition == null) {
|
||||
return null;
|
||||
} else {
|
||||
return contentDisposition;
|
||||
}
|
||||
}
|
||||
|
||||
public String getContentId() throws MessagingException {
|
||||
String contentId = getFirstHeader(MimeHeader.HEADER_CONTENT_ID);
|
||||
if (contentId == null) {
|
||||
return null;
|
||||
} else {
|
||||
// remove optionally surrounding brackets.
|
||||
return REMOVE_OPTIONAL_BRACKETS.matcher(contentId).replaceAll("$1");
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isComplete() {
|
||||
return mComplete;
|
||||
}
|
||||
|
||||
public String getMimeType() throws MessagingException {
|
||||
return MimeUtility.getHeaderParameter(getContentType(), null);
|
||||
}
|
||||
|
||||
public int getSize() throws MessagingException {
|
||||
return mSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of the given recipient type from this message. If no addresses are
|
||||
* found the method returns an empty array.
|
||||
*/
|
||||
@Override
|
||||
public Address[] getRecipients(RecipientType type) throws MessagingException {
|
||||
if (type == RecipientType.TO) {
|
||||
if (mTo == null) {
|
||||
mTo = Address.parse(MimeUtility.unfold(getFirstHeader("To")));
|
||||
}
|
||||
return mTo;
|
||||
} else if (type == RecipientType.CC) {
|
||||
if (mCc == null) {
|
||||
mCc = Address.parse(MimeUtility.unfold(getFirstHeader("CC")));
|
||||
}
|
||||
return mCc;
|
||||
} else if (type == RecipientType.BCC) {
|
||||
if (mBcc == null) {
|
||||
mBcc = Address.parse(MimeUtility.unfold(getFirstHeader("BCC")));
|
||||
}
|
||||
return mBcc;
|
||||
} else {
|
||||
throw new MessagingException("Unrecognized recipient type.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRecipients(RecipientType type, Address[] addresses) throws MessagingException {
|
||||
final int TO_LENGTH = 4; // "To: "
|
||||
final int CC_LENGTH = 4; // "Cc: "
|
||||
final int BCC_LENGTH = 5; // "Bcc: "
|
||||
if (type == RecipientType.TO) {
|
||||
if (addresses == null || addresses.length == 0) {
|
||||
removeHeader("To");
|
||||
this.mTo = null;
|
||||
} else {
|
||||
setHeader("To", MimeUtility.fold(Address.toHeader(addresses), TO_LENGTH));
|
||||
this.mTo = addresses;
|
||||
}
|
||||
} else if (type == RecipientType.CC) {
|
||||
if (addresses == null || addresses.length == 0) {
|
||||
removeHeader("CC");
|
||||
this.mCc = null;
|
||||
} else {
|
||||
setHeader("CC", MimeUtility.fold(Address.toHeader(addresses), CC_LENGTH));
|
||||
this.mCc = addresses;
|
||||
}
|
||||
} else if (type == RecipientType.BCC) {
|
||||
if (addresses == null || addresses.length == 0) {
|
||||
removeHeader("BCC");
|
||||
this.mBcc = null;
|
||||
} else {
|
||||
setHeader("BCC", MimeUtility.fold(Address.toHeader(addresses), BCC_LENGTH));
|
||||
this.mBcc = addresses;
|
||||
}
|
||||
} else {
|
||||
throw new MessagingException("Unrecognized recipient type.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unfolded, decoded value of the Subject header.
|
||||
*/
|
||||
@Override
|
||||
public String getSubject() throws MessagingException {
|
||||
return MimeUtility.unfoldAndDecode(getFirstHeader("Subject"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSubject(String subject) throws MessagingException {
|
||||
final int HEADER_NAME_LENGTH = 9; // "Subject: "
|
||||
setHeader("Subject", MimeUtility.foldAndEncode2(subject, HEADER_NAME_LENGTH));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address[] getFrom() throws MessagingException {
|
||||
if (mFrom == null) {
|
||||
String list = MimeUtility.unfold(getFirstHeader("From"));
|
||||
if (list == null || list.length() == 0) {
|
||||
list = MimeUtility.unfold(getFirstHeader("Sender"));
|
||||
}
|
||||
mFrom = Address.parse(list);
|
||||
}
|
||||
return mFrom;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFrom(Address from) throws MessagingException {
|
||||
final int FROM_LENGTH = 6; // "From: "
|
||||
if (from != null) {
|
||||
setHeader("From", MimeUtility.fold(from.toHeader(), FROM_LENGTH));
|
||||
this.mFrom = new Address[] {
|
||||
from
|
||||
};
|
||||
} else {
|
||||
this.mFrom = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address[] getReplyTo() throws MessagingException {
|
||||
if (mReplyTo == null) {
|
||||
mReplyTo = Address.parse(MimeUtility.unfold(getFirstHeader("Reply-to")));
|
||||
}
|
||||
return mReplyTo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReplyTo(Address[] replyTo) throws MessagingException {
|
||||
final int REPLY_TO_LENGTH = 10; // "Reply-to: "
|
||||
if (replyTo == null || replyTo.length == 0) {
|
||||
removeHeader("Reply-to");
|
||||
mReplyTo = null;
|
||||
} else {
|
||||
setHeader("Reply-to", MimeUtility.fold(Address.toHeader(replyTo), REPLY_TO_LENGTH));
|
||||
mReplyTo = replyTo;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the mime "Message-ID" header
|
||||
* @param messageId the new Message-ID value
|
||||
* @throws MessagingException
|
||||
*/
|
||||
@Override
|
||||
public void setMessageId(String messageId) throws MessagingException {
|
||||
setHeader("Message-ID", messageId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mime "Message-ID" header. This value will be preloaded with a locally-generated
|
||||
* random ID, if the value has not previously been set. Local generation can be inhibited/
|
||||
* overridden by explicitly clearing the headers, removing the message-id header, etc.
|
||||
* @return the Message-ID header string, or null if explicitly has been set to null
|
||||
*/
|
||||
@Override
|
||||
public String getMessageId() throws MessagingException {
|
||||
String messageId = getFirstHeader("Message-ID");
|
||||
if (messageId == null && !mInhibitLocalMessageId) {
|
||||
messageId = generateMessageId();
|
||||
setMessageId(messageId);
|
||||
}
|
||||
return messageId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveChanges() throws MessagingException {
|
||||
throw new MessagingException("saveChanges not yet implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Body getBody() throws MessagingException {
|
||||
return mBody;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBody(Body body) throws MessagingException {
|
||||
this.mBody = body;
|
||||
if (body instanceof Multipart) {
|
||||
Multipart multipart = ((Multipart)body);
|
||||
multipart.setParent(this);
|
||||
setHeader(MimeHeader.HEADER_CONTENT_TYPE, multipart.getContentType());
|
||||
setHeader("MIME-Version", "1.0");
|
||||
}
|
||||
else if (body instanceof TextBody) {
|
||||
setHeader(MimeHeader.HEADER_CONTENT_TYPE, String.format("%s;\n charset=utf-8",
|
||||
getMimeType()));
|
||||
setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "base64");
|
||||
}
|
||||
}
|
||||
|
||||
protected String getFirstHeader(String name) throws MessagingException {
|
||||
return getMimeHeaders().getFirstHeader(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addHeader(String name, String value) throws MessagingException {
|
||||
getMimeHeaders().addHeader(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeader(String name, String value) throws MessagingException {
|
||||
getMimeHeaders().setHeader(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getHeader(String name) throws MessagingException {
|
||||
return getMimeHeaders().getHeader(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeHeader(String name) throws MessagingException {
|
||||
getMimeHeaders().removeHeader(name);
|
||||
if ("Message-ID".equalsIgnoreCase(name)) {
|
||||
mInhibitLocalMessageId = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set extended header
|
||||
*
|
||||
* @param name Extended header name
|
||||
* @param value header value - flattened by removing CR-NL if any
|
||||
* remove header if value is null
|
||||
* @throws MessagingException
|
||||
*/
|
||||
public void setExtendedHeader(String name, String value) throws MessagingException {
|
||||
if (value == null) {
|
||||
if (mExtendedHeader != null) {
|
||||
mExtendedHeader.removeHeader(name);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (mExtendedHeader == null) {
|
||||
mExtendedHeader = new MimeHeader();
|
||||
}
|
||||
mExtendedHeader.setHeader(name, END_OF_LINE.matcher(value).replaceAll(""));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get extended header
|
||||
*
|
||||
* @param name Extended header name
|
||||
* @return header value - null if header does not exist
|
||||
* @throws MessagingException
|
||||
*/
|
||||
public String getExtendedHeader(String name) throws MessagingException {
|
||||
if (mExtendedHeader == null) {
|
||||
return null;
|
||||
}
|
||||
return mExtendedHeader.getFirstHeader(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set entire extended headers from String
|
||||
*
|
||||
* @param headers Extended header and its value - "CR-NL-separated pairs
|
||||
* if null or empty, remove entire extended headers
|
||||
* @throws MessagingException
|
||||
*/
|
||||
public void setExtendedHeaders(String headers) throws MessagingException {
|
||||
if (TextUtils.isEmpty(headers)) {
|
||||
mExtendedHeader = null;
|
||||
} else {
|
||||
mExtendedHeader = new MimeHeader();
|
||||
for (String header : END_OF_LINE.split(headers)) {
|
||||
String[] tokens = header.split(":", 2);
|
||||
if (tokens.length != 2) {
|
||||
throw new MessagingException("Illegal extended headers: " + headers);
|
||||
}
|
||||
mExtendedHeader.setHeader(tokens[0].trim(), tokens[1].trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get entire extended headers as String
|
||||
*
|
||||
* @return "CR-NL-separated extended headers - null if extended header does not exist
|
||||
*/
|
||||
public String getExtendedHeaders() {
|
||||
if (mExtendedHeader != null) {
|
||||
return mExtendedHeader.writeToString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write message header and body to output stream
|
||||
*
|
||||
* @param out Output steam to write message header and body.
|
||||
*/
|
||||
public void writeTo(OutputStream out) throws IOException, MessagingException {
|
||||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out), 1024);
|
||||
// Force creation of local message-id
|
||||
getMessageId();
|
||||
getMimeHeaders().writeTo(out);
|
||||
// mExtendedHeader will not be write out to external output stream,
|
||||
// because it is intended to internal use.
|
||||
writer.write("\r\n");
|
||||
writer.flush();
|
||||
if (mBody != null) {
|
||||
mBody.writeTo(out);
|
||||
}
|
||||
}
|
||||
|
||||
public InputStream getInputStream() throws MessagingException {
|
||||
return null;
|
||||
}
|
||||
|
||||
class MimeMessageBuilder implements ContentHandler {
|
||||
private Stack<Object> stack = new Stack<Object>();
|
||||
|
||||
public MimeMessageBuilder() {
|
||||
}
|
||||
|
||||
private void expect(Class c) {
|
||||
if (!c.isInstance(stack.peek())) {
|
||||
throw new IllegalStateException("Internal stack error: " + "Expected '"
|
||||
+ c.getName() + "' found '" + stack.peek().getClass().getName() + "'");
|
||||
}
|
||||
}
|
||||
|
||||
public void startMessage() {
|
||||
if (stack.isEmpty()) {
|
||||
stack.push(MimeMessage.this);
|
||||
} else {
|
||||
expect(Part.class);
|
||||
try {
|
||||
MimeMessage m = new MimeMessage();
|
||||
((Part)stack.peek()).setBody(m);
|
||||
stack.push(m);
|
||||
} catch (MessagingException me) {
|
||||
throw new Error(me);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void endMessage() {
|
||||
expect(MimeMessage.class);
|
||||
stack.pop();
|
||||
}
|
||||
|
||||
public void startHeader() {
|
||||
expect(Part.class);
|
||||
}
|
||||
|
||||
public void field(String fieldData) {
|
||||
expect(Part.class);
|
||||
try {
|
||||
String[] tokens = fieldData.split(":", 2);
|
||||
((Part)stack.peek()).addHeader(tokens[0], tokens[1].trim());
|
||||
} catch (MessagingException me) {
|
||||
throw new Error(me);
|
||||
}
|
||||
}
|
||||
|
||||
public void endHeader() {
|
||||
expect(Part.class);
|
||||
}
|
||||
|
||||
public void startMultipart(BodyDescriptor bd) {
|
||||
expect(Part.class);
|
||||
|
||||
Part e = (Part)stack.peek();
|
||||
try {
|
||||
MimeMultipart multiPart = new MimeMultipart(e.getContentType());
|
||||
e.setBody(multiPart);
|
||||
stack.push(multiPart);
|
||||
} catch (MessagingException me) {
|
||||
throw new Error(me);
|
||||
}
|
||||
}
|
||||
|
||||
public void body(BodyDescriptor bd, InputStream in) throws IOException {
|
||||
expect(Part.class);
|
||||
Body body = MimeUtility.decodeBody(in, bd.getTransferEncoding());
|
||||
try {
|
||||
((Part)stack.peek()).setBody(body);
|
||||
} catch (MessagingException me) {
|
||||
throw new Error(me);
|
||||
}
|
||||
}
|
||||
|
||||
public void endMultipart() {
|
||||
stack.pop();
|
||||
}
|
||||
|
||||
public void startBodyPart() {
|
||||
expect(MimeMultipart.class);
|
||||
|
||||
try {
|
||||
MimeBodyPart bodyPart = new MimeBodyPart();
|
||||
((MimeMultipart)stack.peek()).addBodyPart(bodyPart);
|
||||
stack.push(bodyPart);
|
||||
} catch (MessagingException me) {
|
||||
throw new Error(me);
|
||||
}
|
||||
}
|
||||
|
||||
public void endBodyPart() {
|
||||
expect(BodyPart.class);
|
||||
stack.pop();
|
||||
}
|
||||
|
||||
public void epilogue(InputStream is) throws IOException {
|
||||
expect(MimeMultipart.class);
|
||||
StringBuffer sb = new StringBuffer();
|
||||
int b;
|
||||
while ((b = is.read()) != -1) {
|
||||
sb.append((char)b);
|
||||
}
|
||||
// ((Multipart) stack.peek()).setEpilogue(sb.toString());
|
||||
}
|
||||
|
||||
public void preamble(InputStream is) throws IOException {
|
||||
expect(MimeMultipart.class);
|
||||
StringBuffer sb = new StringBuffer();
|
||||
int b;
|
||||
while ((b = is.read()) != -1) {
|
||||
sb.append((char)b);
|
||||
}
|
||||
try {
|
||||
((MimeMultipart)stack.peek()).setPreamble(sb.toString());
|
||||
} catch (MessagingException me) {
|
||||
throw new Error(me);
|
||||
}
|
||||
}
|
||||
|
||||
public void raw(InputStream is) throws IOException {
|
||||
throw new UnsupportedOperationException("Not supported");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,111 +0,0 @@
|
||||
/*
|
||||
* 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.emailcommon.internet;
|
||||
|
||||
import com.android.emailcommon.mail.BodyPart;
|
||||
import com.android.emailcommon.mail.MessagingException;
|
||||
import com.android.emailcommon.mail.Multipart;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
|
||||
public class MimeMultipart extends Multipart {
|
||||
protected String mPreamble;
|
||||
|
||||
protected String mContentType;
|
||||
|
||||
protected String mBoundary;
|
||||
|
||||
protected String mSubType;
|
||||
|
||||
public MimeMultipart() throws MessagingException {
|
||||
mBoundary = generateBoundary();
|
||||
setSubType("mixed");
|
||||
}
|
||||
|
||||
public MimeMultipart(String contentType) throws MessagingException {
|
||||
this.mContentType = contentType;
|
||||
try {
|
||||
mSubType = MimeUtility.getHeaderParameter(contentType, null).split("/")[1];
|
||||
mBoundary = MimeUtility.getHeaderParameter(contentType, "boundary");
|
||||
if (mBoundary == null) {
|
||||
throw new MessagingException("MultiPart does not contain boundary: " + contentType);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new MessagingException(
|
||||
"Invalid MultiPart Content-Type; must contain subtype and boundary. ("
|
||||
+ contentType + ")", e);
|
||||
}
|
||||
}
|
||||
|
||||
public String generateBoundary() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append("----");
|
||||
for (int i = 0; i < 30; i++) {
|
||||
sb.append(Integer.toString((int)(Math.random() * 35), 36));
|
||||
}
|
||||
return sb.toString().toUpperCase();
|
||||
}
|
||||
|
||||
public String getPreamble() throws MessagingException {
|
||||
return mPreamble;
|
||||
}
|
||||
|
||||
public void setPreamble(String preamble) throws MessagingException {
|
||||
this.mPreamble = preamble;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentType() throws MessagingException {
|
||||
return mContentType;
|
||||
}
|
||||
|
||||
public void setSubType(String subType) throws MessagingException {
|
||||
this.mSubType = subType;
|
||||
mContentType = String.format("multipart/%s; boundary=\"%s\"", subType, mBoundary);
|
||||
}
|
||||
|
||||
public void writeTo(OutputStream out) throws IOException, MessagingException {
|
||||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out), 1024);
|
||||
|
||||
if (mPreamble != null) {
|
||||
writer.write(mPreamble + "\r\n");
|
||||
}
|
||||
|
||||
for (int i = 0, count = mParts.size(); i < count; i++) {
|
||||
BodyPart bodyPart = mParts.get(i);
|
||||
writer.write("--" + mBoundary + "\r\n");
|
||||
writer.flush();
|
||||
bodyPart.writeTo(out);
|
||||
writer.write("\r\n");
|
||||
}
|
||||
|
||||
writer.write("--" + mBoundary + "--\r\n");
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
public InputStream getInputStream() throws MessagingException {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getSubTypeForTest() {
|
||||
return mSubType;
|
||||
}
|
||||
}
|
@ -1,453 +0,0 @@
|
||||
/*
|
||||
* 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.emailcommon.internet;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.util.Base64;
|
||||
import android.util.Base64DataException;
|
||||
import android.util.Base64InputStream;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.emailcommon.Logging;
|
||||
import com.android.emailcommon.mail.Body;
|
||||
import com.android.emailcommon.mail.BodyPart;
|
||||
import com.android.emailcommon.mail.Message;
|
||||
import com.android.emailcommon.mail.MessagingException;
|
||||
import com.android.emailcommon.mail.Multipart;
|
||||
import com.android.emailcommon.mail.Part;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.james.mime4j.codec.EncoderUtil;
|
||||
import org.apache.james.mime4j.decoder.DecoderUtil;
|
||||
import org.apache.james.mime4j.decoder.QuotedPrintableInputStream;
|
||||
import org.apache.james.mime4j.util.CharsetUtil;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class MimeUtility {
|
||||
|
||||
public static final String MIME_TYPE_RFC822 = "message/rfc822";
|
||||
private final static Pattern PATTERN_CR_OR_LF = Pattern.compile("\r|\n");
|
||||
|
||||
/**
|
||||
* Replace sequences of CRLF+WSP with WSP. Tries to preserve original string
|
||||
* object whenever possible.
|
||||
*/
|
||||
public static String unfold(String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
Matcher patternMatcher = PATTERN_CR_OR_LF.matcher(s);
|
||||
if (patternMatcher.find()) {
|
||||
patternMatcher.reset();
|
||||
s = patternMatcher.replaceAll("");
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
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
|
||||
// NOTE: When this really works, we *must* remove all calls to foldAndEncode2() to prevent
|
||||
// duplication of encoding.
|
||||
public static String foldAndEncode(String s) {
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERIM version of foldAndEncode that will be used only by Subject: headers.
|
||||
* This is safer than implementing foldAndEncode() (see above) and risking unknown damage
|
||||
* to other headers.
|
||||
*
|
||||
* TODO: Copy this code to foldAndEncode(), get rid of this function, confirm all working OK.
|
||||
*
|
||||
* @param s original string to encode and fold
|
||||
* @param usedCharacters number of characters already used up by header name
|
||||
|
||||
* @return the String ready to be transmitted
|
||||
*/
|
||||
public static String foldAndEncode2(String s, int usedCharacters) {
|
||||
// james.mime4j.codec.EncoderUtil.java
|
||||
// encode: encodeIfNecessary(text, usage, numUsedInHeaderName)
|
||||
// Usage.TEXT_TOKENlooks like the right thing for subjects
|
||||
// use WORD_ENTITY for address/names
|
||||
|
||||
String encoded = EncoderUtil.encodeIfNecessary(s, EncoderUtil.Usage.TEXT_TOKEN,
|
||||
usedCharacters);
|
||||
|
||||
return fold(encoded, usedCharacters);
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERIM: From newer version of org.apache.james (but we don't want to import
|
||||
* the entire MimeUtil class).
|
||||
*
|
||||
* Splits the specified string into a multiple-line representation with
|
||||
* lines no longer than 76 characters (because the line might contain
|
||||
* encoded words; see <a href='http://www.faqs.org/rfcs/rfc2047.html'>RFC
|
||||
* 2047</a> section 2). If the string contains non-whitespace sequences
|
||||
* longer than 76 characters a line break is inserted at the whitespace
|
||||
* character following the sequence resulting in a line longer than 76
|
||||
* characters.
|
||||
*
|
||||
* @param s
|
||||
* string to split.
|
||||
* @param usedCharacters
|
||||
* number of characters already used up. Usually the number of
|
||||
* characters for header field name plus colon and one space.
|
||||
* @return a multiple-line representation of the given string.
|
||||
*/
|
||||
public static String fold(String s, int usedCharacters) {
|
||||
final int maxCharacters = 76;
|
||||
|
||||
final int length = s.length();
|
||||
if (usedCharacters + length <= maxCharacters)
|
||||
return s;
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
int lastLineBreak = -usedCharacters;
|
||||
int wspIdx = indexOfWsp(s, 0);
|
||||
while (true) {
|
||||
if (wspIdx == length) {
|
||||
sb.append(s.substring(Math.max(0, lastLineBreak)));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
int nextWspIdx = indexOfWsp(s, wspIdx + 1);
|
||||
|
||||
if (nextWspIdx - lastLineBreak > maxCharacters) {
|
||||
sb.append(s.substring(Math.max(0, lastLineBreak), wspIdx));
|
||||
sb.append("\r\n");
|
||||
lastLineBreak = wspIdx;
|
||||
}
|
||||
|
||||
wspIdx = nextWspIdx;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERIM: From newer version of org.apache.james (but we don't want to import
|
||||
* the entire MimeUtil class).
|
||||
*
|
||||
* Search for whitespace.
|
||||
*/
|
||||
private static int indexOfWsp(String s, int fromIndex) {
|
||||
final int len = s.length();
|
||||
for (int index = fromIndex; index < len; index++) {
|
||||
char c = s.charAt(index);
|
||||
if (c == ' ' || c == '\t')
|
||||
return index;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* TODO: quite inefficient with the inner trimming & splitting.
|
||||
* TODO: Also has a latent bug: uses "startsWith" to match the name, which can false-positive.
|
||||
* TODO: The doc says that for a null name you get the first param, but you get the header.
|
||||
* Should probably just fix the doc, but if other code assumes that behavior, fix the code.
|
||||
* TODO: Need to decode %-escaped strings, as in: filename="ab%22d".
|
||||
* ('+' -> ' ' conversion too? check RFC)
|
||||
*
|
||||
* @param header
|
||||
* @param name
|
||||
* @return the entire header (if name=null), the found parameter, or null
|
||||
*/
|
||||
public static String getHeaderParameter(String header, String name) {
|
||||
if (header == null) {
|
||||
return null;
|
||||
}
|
||||
String[] parts = unfold(header).split(";");
|
||||
if (name == null) {
|
||||
return parts[0].trim();
|
||||
}
|
||||
String lowerCaseName = name.toLowerCase();
|
||||
for (String part : parts) {
|
||||
if (part.trim().toLowerCase().startsWith(lowerCaseName)) {
|
||||
String[] parameterParts = part.split("=", 2);
|
||||
if (parameterParts.length < 2) {
|
||||
return null;
|
||||
}
|
||||
String parameter = parameterParts[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 The part containing a body
|
||||
* @return a String containing the converted text in the body, or null if there was no text
|
||||
* or an error during conversion.
|
||||
*/
|
||||
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);
|
||||
in.close();
|
||||
in = null; // we want all of our memory back, and close might not release
|
||||
|
||||
/*
|
||||
* We've got a text part, so let's see if it needs to be processed further.
|
||||
*/
|
||||
String charset = getHeaderParameter(part.getContentType(), "charset");
|
||||
if (charset != null) {
|
||||
/*
|
||||
* See if there is conversion from the MIME charset to the Java one.
|
||||
*/
|
||||
charset = CharsetUtil.toJavaCharset(charset);
|
||||
}
|
||||
/*
|
||||
* No encoding, so use us-ascii, which is the standard.
|
||||
*/
|
||||
if (charset == null) {
|
||||
charset = "ASCII";
|
||||
}
|
||||
/*
|
||||
* Convert and return as new String
|
||||
*/
|
||||
String result = out.toString(charset);
|
||||
out.close();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch (OutOfMemoryError oom) {
|
||||
/*
|
||||
* 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(Logging.LOG_TAG, "Unable to getTextFromPart " + oom.toString());
|
||||
}
|
||||
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(Logging.LOG_TAG, "Unable to getTextFromPart " + e.toString());
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an input stream and a transfer encoding, return a wrapped input stream for that
|
||||
* encoding (or the original if none is required)
|
||||
* @param in the input stream
|
||||
* @param contentTransferEncoding the content transfer encoding
|
||||
* @return a properly wrapped stream
|
||||
*/
|
||||
public static InputStream getInputStreamForContentTransferEncoding(InputStream in,
|
||||
String contentTransferEncoding) {
|
||||
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, Base64.DEFAULT);
|
||||
}
|
||||
}
|
||||
return in;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
in = getInputStreamForContentTransferEncoding(in, contentTransferEncoding);
|
||||
BinaryTempFileBody tempBody = new BinaryTempFileBody();
|
||||
OutputStream out = tempBody.getOutputStream();
|
||||
try {
|
||||
IOUtils.copy(in, out);
|
||||
} catch (Base64DataException bde) {
|
||||
// TODO Need to fix this somehow
|
||||
//String warning = "\n\n" + Email.getMessageDecodeErrorString();
|
||||
//out.write(warning.getBytes());
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
return tempBody;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively scan a Part (usually a Message) and sort out which of its children will be
|
||||
* "viewable" and which will be attachments.
|
||||
*
|
||||
* @param part The part to be broken down
|
||||
* @param viewables This arraylist will be populated with all parts that appear to be
|
||||
* the "message" (e.g. text/plain & text/html)
|
||||
* @param attachments This arraylist will be populated with all parts that appear to be
|
||||
* attachments (including inlines)
|
||||
* @throws MessagingException
|
||||
*/
|
||||
public static void collectParts(Part part, ArrayList<Part> viewables,
|
||||
ArrayList<Part> attachments) throws MessagingException {
|
||||
String disposition = part.getDisposition();
|
||||
String dispositionType = MimeUtility.getHeaderParameter(disposition, null);
|
||||
// If a disposition is not specified, default to "inline"
|
||||
boolean inline =
|
||||
TextUtils.isEmpty(dispositionType) || "inline".equalsIgnoreCase(dispositionType);
|
||||
// The lower-case mime type
|
||||
String mimeType = part.getMimeType().toLowerCase();
|
||||
|
||||
if (part.getBody() instanceof Multipart) {
|
||||
// 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 its pieces recursively.
|
||||
MimeMultipart mp = (MimeMultipart)part.getBody();
|
||||
boolean foundHtml = false;
|
||||
if (mp.getSubTypeForTest().equals("alternative")) {
|
||||
for (int i = 0; i < mp.getCount(); i++) {
|
||||
if (mp.getBodyPart(i).isMimeType("text/html")) {
|
||||
foundHtml = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < mp.getCount(); i++) {
|
||||
// See if we have text and html
|
||||
BodyPart bp = mp.getBodyPart(i);
|
||||
// If there's html, don't bother loading text
|
||||
if (foundHtml && bp.isMimeType("text/plain")) {
|
||||
continue;
|
||||
}
|
||||
collectParts(bp, viewables, attachments);
|
||||
}
|
||||
} else if (part.getBody() instanceof Message) {
|
||||
// If the part is an embedded message we just continue to process
|
||||
// it, pulling any viewables or attachments into the running list.
|
||||
Message message = (Message)part.getBody();
|
||||
collectParts(message, viewables, attachments);
|
||||
} else if (inline && (mimeType.startsWith("text") || (mimeType.startsWith("image")))) {
|
||||
// We'll treat text and images as viewables
|
||||
viewables.add(part);
|
||||
} else {
|
||||
// Everything else is an attachment.
|
||||
attachments.add(part);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
/*
|
||||
* 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.emailcommon.internet;
|
||||
|
||||
import com.android.emailcommon.mail.Body;
|
||||
import com.android.emailcommon.mail.MessagingException;
|
||||
|
||||
import android.util.Base64;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
public class TextBody implements Body {
|
||||
String mBody;
|
||||
|
||||
public TextBody(String body) {
|
||||
this.mBody = body;
|
||||
}
|
||||
|
||||
public void writeTo(OutputStream out) throws IOException, MessagingException {
|
||||
byte[] bytes = mBody.getBytes("UTF-8");
|
||||
out.write(Base64.encode(bytes, Base64.CRLF));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the text of the body in it's unencoded format.
|
||||
* @return
|
||||
*/
|
||||
public String getText() {
|
||||
return mBody;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an InputStream that reads this body's text in UTF-8 format.
|
||||
*/
|
||||
public InputStream getInputStream() throws MessagingException {
|
||||
try {
|
||||
byte[] b = mBody.getBytes("UTF-8");
|
||||
return new ByteArrayInputStream(b);
|
||||
}
|
||||
catch (UnsupportedEncodingException usee) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,428 +0,0 @@
|
||||
/*
|
||||
* 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.emailcommon.mail;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.text.util.Rfc822Token;
|
||||
import android.text.util.Rfc822Tokenizer;
|
||||
|
||||
import com.android.emailcommon.utility.Utility;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import org.apache.james.mime4j.codec.EncoderUtil;
|
||||
import org.apache.james.mime4j.decoder.DecoderUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* This class represent email address.
|
||||
*
|
||||
* RFC822 email address may have following format.
|
||||
* "name" <address> (comment)
|
||||
* "name" <address>
|
||||
* name <address>
|
||||
* address
|
||||
* Name and comment part should be MIME/base64 encoded in header if necessary.
|
||||
*
|
||||
*/
|
||||
public class Address {
|
||||
/**
|
||||
* Address part, in the form local_part@domain_part. No surrounding angle brackets.
|
||||
*/
|
||||
private String mAddress;
|
||||
|
||||
/**
|
||||
* Name part. No surrounding double quote, and no MIME/base64 encoding.
|
||||
* This must be null if Address has no name part.
|
||||
*/
|
||||
private String mPersonal;
|
||||
|
||||
// Regex that matches address surrounded by '<>' optionally. '^<?([^>]+)>?$'
|
||||
private static final Pattern REMOVE_OPTIONAL_BRACKET = Pattern.compile("^<?([^>]+)>?$");
|
||||
// Regex that matches personal name surrounded by '""' optionally. '^"?([^"]+)"?$'
|
||||
private static final Pattern REMOVE_OPTIONAL_DQUOTE = Pattern.compile("^\"?([^\"]*)\"?$");
|
||||
// Regex that matches escaped character '\\([\\"])'
|
||||
private static final Pattern UNQUOTE = Pattern.compile("\\\\([\\\\\"])");
|
||||
|
||||
private static final Address[] EMPTY_ADDRESS_ARRAY = new Address[0];
|
||||
|
||||
// delimiters are chars that do not appear in an email address, used by pack/unpack
|
||||
private static final char LIST_DELIMITER_EMAIL = '\1';
|
||||
private static final char LIST_DELIMITER_PERSONAL = '\2';
|
||||
|
||||
public Address(String address, String personal) {
|
||||
setAddress(address);
|
||||
setPersonal(personal);
|
||||
}
|
||||
|
||||
public Address(String address) {
|
||||
setAddress(address);
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return mAddress;
|
||||
}
|
||||
|
||||
public void setAddress(String address) {
|
||||
mAddress = REMOVE_OPTIONAL_BRACKET.matcher(address).replaceAll("$1");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get name part as UTF-16 string. No surrounding double quote, and no MIME/base64 encoding.
|
||||
*
|
||||
* @return Name part of email address. Returns null if it is omitted.
|
||||
*/
|
||||
public String getPersonal() {
|
||||
return mPersonal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set name part from UTF-16 string. Optional surrounding double quote will be removed.
|
||||
* It will be also unquoted and MIME/base64 decoded.
|
||||
*
|
||||
* @param personal name part of email address as UTF-16 string. Null is acceptable.
|
||||
*/
|
||||
public void setPersonal(String personal) {
|
||||
if (personal != null) {
|
||||
personal = REMOVE_OPTIONAL_DQUOTE.matcher(personal).replaceAll("$1");
|
||||
personal = UNQUOTE.matcher(personal).replaceAll("$1");
|
||||
personal = DecoderUtil.decodeEncodedWords(personal);
|
||||
if (personal.length() == 0) {
|
||||
personal = null;
|
||||
}
|
||||
}
|
||||
mPersonal = personal;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to check that all the addresses that the user
|
||||
* entered in a list (e.g. To:) are valid, so that none is dropped.
|
||||
*/
|
||||
public static boolean isAllValid(String addressList) {
|
||||
// This code mimics the parse() method below.
|
||||
// I don't know how to better avoid the code-duplication.
|
||||
if (addressList != null && addressList.length() > 0) {
|
||||
Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(addressList);
|
||||
for (int i = 0, length = tokens.length; i < length; ++i) {
|
||||
Rfc822Token token = tokens[i];
|
||||
String address = token.getAddress();
|
||||
if (!TextUtils.isEmpty(address) && !isValidAddress(address)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a comma-delimited list of addresses in RFC822 format and return an
|
||||
* array of Address objects.
|
||||
*
|
||||
* @param addressList Address list in comma-delimited string.
|
||||
* @return An array of 0 or more Addresses.
|
||||
*/
|
||||
public static Address[] parse(String addressList) {
|
||||
if (addressList == null || addressList.length() == 0) {
|
||||
return EMPTY_ADDRESS_ARRAY;
|
||||
}
|
||||
Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(addressList);
|
||||
ArrayList<Address> addresses = new ArrayList<Address>();
|
||||
for (int i = 0, length = tokens.length; i < length; ++i) {
|
||||
Rfc822Token token = tokens[i];
|
||||
String address = token.getAddress();
|
||||
if (!TextUtils.isEmpty(address)) {
|
||||
if (isValidAddress(address)) {
|
||||
String name = token.getName();
|
||||
if (TextUtils.isEmpty(name)) {
|
||||
name = null;
|
||||
}
|
||||
addresses.add(new Address(address, name));
|
||||
}
|
||||
}
|
||||
}
|
||||
return addresses.toArray(new Address[] {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a string email address is valid.
|
||||
* E.g. name@domain.com is valid.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static boolean isValidAddress(String address) {
|
||||
// Note: Some email provider may violate the standard, so here we only check that
|
||||
// address consists of two part that are separated by '@', and domain part contains
|
||||
// at least one '.'.
|
||||
int len = address.length();
|
||||
int firstAt = address.indexOf('@');
|
||||
int lastAt = address.lastIndexOf('@');
|
||||
int firstDot = address.indexOf('.', lastAt + 1);
|
||||
int lastDot = address.lastIndexOf('.');
|
||||
return firstAt > 0 && firstAt == lastAt && lastAt + 1 < firstDot
|
||||
&& firstDot <= lastDot && lastDot < len - 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o instanceof Address) {
|
||||
// It seems that the spec says that the "user" part is case-sensitive,
|
||||
// while the domain part in case-insesitive.
|
||||
// So foo@yahoo.com and Foo@yahoo.com are different.
|
||||
// This may seem non-intuitive from the user POV, so we
|
||||
// may re-consider it if it creates UI trouble.
|
||||
// A problem case is "replyAll" sending to both
|
||||
// a@b.c and to A@b.c, which turn out to be the same on the server.
|
||||
// Leave unchanged for now (i.e. case-sensitive).
|
||||
return getAddress().equals(((Address) o).getAddress());
|
||||
}
|
||||
return super.equals(o);
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return getAddress().hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get human readable address string.
|
||||
* Do not use this for email header.
|
||||
*
|
||||
* @return Human readable address string. Not quoted and not encoded.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
if (mPersonal != null && !mPersonal.equals(mAddress)) {
|
||||
if (mPersonal.matches(".*[\\(\\)<>@,;:\\\\\".\\[\\]].*")) {
|
||||
return Utility.quoteString(mPersonal) + " <" + mAddress + ">";
|
||||
} else {
|
||||
return mPersonal + " <" + mAddress + ">";
|
||||
}
|
||||
} else {
|
||||
return mAddress;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get human readable comma-delimited address string.
|
||||
*
|
||||
* @param addresses Address array
|
||||
* @return Human readable comma-delimited address string.
|
||||
*/
|
||||
public static String toString(Address[] addresses) {
|
||||
return toString(addresses, ",");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get human readable address strings joined with the specified separator.
|
||||
*
|
||||
* @param addresses Address array
|
||||
* @param separator Separator
|
||||
* @return Human readable comma-delimited address string.
|
||||
*/
|
||||
public static String toString(Address[] addresses, String separator) {
|
||||
if (addresses == null || addresses.length == 0) {
|
||||
return null;
|
||||
}
|
||||
if (addresses.length == 1) {
|
||||
return addresses[0].toString();
|
||||
}
|
||||
StringBuffer sb = new StringBuffer(addresses[0].toString());
|
||||
for (int i = 1; i < addresses.length; i++) {
|
||||
sb.append(separator);
|
||||
// TODO: investigate why this .trim() is needed.
|
||||
sb.append(addresses[i].toString().trim());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get RFC822/MIME compatible address string.
|
||||
*
|
||||
* @return RFC822/MIME compatible address string.
|
||||
* It may be surrounded by double quote or quoted and MIME/base64 encoded if necessary.
|
||||
*/
|
||||
public String toHeader() {
|
||||
if (mPersonal != null) {
|
||||
return EncoderUtil.encodeAddressDisplayName(mPersonal) + " <" + mAddress + ">";
|
||||
} else {
|
||||
return mAddress;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get RFC822/MIME compatible comma-delimited address string.
|
||||
*
|
||||
* @param addresses Address array
|
||||
* @return RFC822/MIME compatible comma-delimited address string.
|
||||
* it may be surrounded by double quoted or quoted and MIME/base64 encoded if necessary.
|
||||
*/
|
||||
public static String toHeader(Address[] addresses) {
|
||||
if (addresses == null || addresses.length == 0) {
|
||||
return null;
|
||||
}
|
||||
if (addresses.length == 1) {
|
||||
return addresses[0].toHeader();
|
||||
}
|
||||
StringBuffer sb = new StringBuffer(addresses[0].toHeader());
|
||||
for (int i = 1; i < addresses.length; i++) {
|
||||
// We need space character to be able to fold line.
|
||||
sb.append(", ");
|
||||
sb.append(addresses[i].toHeader());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Human friendly address string.
|
||||
*
|
||||
* @return the personal part of this Address, or the address part if the
|
||||
* personal part is not available
|
||||
*/
|
||||
public String toFriendly() {
|
||||
if (mPersonal != null && mPersonal.length() > 0) {
|
||||
return mPersonal;
|
||||
} else {
|
||||
return mAddress;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a comma-delimited list of addresses in the "friendly" format (see toFriendly() for
|
||||
* details on the per-address conversion).
|
||||
*
|
||||
* @param addresses Array of Address[] values
|
||||
* @return A comma-delimited string listing all of the addresses supplied. Null if source
|
||||
* was null or empty.
|
||||
*/
|
||||
public static String toFriendly(Address[] addresses) {
|
||||
if (addresses == null || addresses.length == 0) {
|
||||
return null;
|
||||
}
|
||||
if (addresses.length == 1) {
|
||||
return addresses[0].toFriendly();
|
||||
}
|
||||
StringBuffer sb = new StringBuffer(addresses[0].toFriendly());
|
||||
for (int i = 1; i < addresses.length; i++) {
|
||||
sb.append(", ");
|
||||
sb.append(addresses[i].toFriendly());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns exactly the same result as Address.toString(Address.unpack(packedList)).
|
||||
*/
|
||||
public static String unpackToString(String packedList) {
|
||||
return toString(unpack(packedList));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns exactly the same result as Address.pack(Address.parse(textList)).
|
||||
*/
|
||||
public static String parseAndPack(String textList) {
|
||||
return Address.pack(Address.parse(textList));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns null if the packedList has 0 addresses, otherwise returns the first address.
|
||||
* The same as Address.unpack(packedList)[0] for non-empty list.
|
||||
* This is an utility method that offers some performance optimization opportunities.
|
||||
*/
|
||||
public static Address unpackFirst(String packedList) {
|
||||
Address[] array = unpack(packedList);
|
||||
return array.length > 0 ? array[0] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a packed list of addresses to a form suitable for use in an RFC822 header.
|
||||
* This implementation is brute-force, and could be replaced with a more efficient version
|
||||
* if desired.
|
||||
*/
|
||||
public static String packedToHeader(String packedList) {
|
||||
return toHeader(unpack(packedList));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpacks an address list that is either CSV of RFC822 addresses OR (for backward
|
||||
* compatibility) previously packed with pack()
|
||||
* @param addressList string packed with pack() or CSV of RFC822 addresses
|
||||
* @return array of addresses resulting from unpack
|
||||
*/
|
||||
public static Address[] unpack(String addressList) {
|
||||
if (addressList == null || addressList.length() == 0) {
|
||||
return EMPTY_ADDRESS_ARRAY;
|
||||
}
|
||||
// IF we're CSV, just parse
|
||||
if ((addressList.indexOf(LIST_DELIMITER_PERSONAL) == -1) &&
|
||||
(addressList.indexOf(LIST_DELIMITER_EMAIL) == -1)) {
|
||||
return Address.parse(addressList);
|
||||
}
|
||||
// Otherwise, do backward-compatibile unpack
|
||||
ArrayList<Address> addresses = new ArrayList<Address>();
|
||||
int length = addressList.length();
|
||||
int pairStartIndex = 0;
|
||||
int pairEndIndex = 0;
|
||||
|
||||
/* addressEndIndex is only re-scanned (indexOf()) when a LIST_DELIMITER_PERSONAL
|
||||
is used, not for every email address; i.e. not for every iteration of the while().
|
||||
This reduces the theoretical complexity from quadratic to linear,
|
||||
and provides some speed-up in practice by removing redundant scans of the string.
|
||||
*/
|
||||
int addressEndIndex = addressList.indexOf(LIST_DELIMITER_PERSONAL);
|
||||
|
||||
while (pairStartIndex < length) {
|
||||
pairEndIndex = addressList.indexOf(LIST_DELIMITER_EMAIL, pairStartIndex);
|
||||
if (pairEndIndex == -1) {
|
||||
pairEndIndex = length;
|
||||
}
|
||||
Address address;
|
||||
if (addressEndIndex == -1 || pairEndIndex <= addressEndIndex) {
|
||||
// in this case the DELIMITER_PERSONAL is in a future pair,
|
||||
// so don't use personal, and don't update addressEndIndex
|
||||
address = new Address(addressList.substring(pairStartIndex, pairEndIndex), null);
|
||||
} else {
|
||||
address = new Address(addressList.substring(pairStartIndex, addressEndIndex),
|
||||
addressList.substring(addressEndIndex + 1, pairEndIndex));
|
||||
// only update addressEndIndex when we use the LIST_DELIMITER_PERSONAL
|
||||
addressEndIndex = addressList.indexOf(LIST_DELIMITER_PERSONAL, pairEndIndex + 1);
|
||||
}
|
||||
addresses.add(address);
|
||||
pairStartIndex = pairEndIndex + 1;
|
||||
}
|
||||
return addresses.toArray(EMPTY_ADDRESS_ARRAY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a String containing RFC822 addresses separated by commas
|
||||
* NOTE: We used to "pack" these addresses in an app-specific format, but no longer do so
|
||||
*/
|
||||
public static String pack(Address[] addresses) {
|
||||
return Address.toHeader(addresses);
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces the same result as pack(array), but only packs one (this) address.
|
||||
*/
|
||||
public String pack() {
|
||||
final String address = getAddress();
|
||||
final String personal = getPersonal();
|
||||
if (personal == null) {
|
||||
return address;
|
||||
} else {
|
||||
return address + LIST_DELIMITER_PERSONAL + personal;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
/*
|
||||
* 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.emailcommon.mail;
|
||||
|
||||
|
||||
public class AuthenticationFailedException extends MessagingException {
|
||||
public static final long serialVersionUID = -1;
|
||||
|
||||
public AuthenticationFailedException(String message) {
|
||||
super(MessagingException.AUTHENTICATION_FAILED, message);
|
||||
}
|
||||
|
||||
public AuthenticationFailedException(int exceptionType, String message) {
|
||||
super(exceptionType, message);
|
||||
}
|
||||
|
||||
public AuthenticationFailedException(String message, Throwable throwable) {
|
||||
super(MessagingException.AUTHENTICATION_FAILED, message, throwable);
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
/*
|
||||
* 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.emailcommon.mail;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public interface Body {
|
||||
public InputStream getInputStream() throws MessagingException;
|
||||
public void writeTo(OutputStream out) throws IOException, MessagingException;
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
/*
|
||||
* 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.emailcommon.mail;
|
||||
|
||||
public abstract class BodyPart implements Part {
|
||||
protected Multipart mParent;
|
||||
|
||||
public Multipart getParent() {
|
||||
return mParent;
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
/*
|
||||
* 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.emailcommon.mail;
|
||||
|
||||
|
||||
public class CertificateValidationException extends MessagingException {
|
||||
public static final long serialVersionUID = -1;
|
||||
|
||||
public CertificateValidationException(String message) {
|
||||
super(MessagingException.CERTIFICATE_VALIDATION_ERROR, message);
|
||||
}
|
||||
|
||||
public CertificateValidationException(String message, Throwable throwable) {
|
||||
super(MessagingException.CERTIFICATE_VALIDATION_ERROR, message, throwable);
|
||||
}
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
/*
|
||||
* 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.emailcommon.mail;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* A FetchProfile is a list of items that should be downloaded in bulk for a set of messages.
|
||||
* FetchProfile can contain the following objects:
|
||||
* FetchProfile.Item: Described below.
|
||||
* Message: Indicates that the body of the entire message should be fetched.
|
||||
* Synonymous with FetchProfile.Item.BODY.
|
||||
* Part: Indicates that the given Part should be fetched. The provider
|
||||
* is expected have previously created the given BodyPart and stored
|
||||
* any information it needs to download the content.
|
||||
* </pre>
|
||||
*/
|
||||
public class FetchProfile extends ArrayList<Fetchable> {
|
||||
/**
|
||||
* Default items available for pre-fetching. It should be expected that any
|
||||
* item fetched by using these items could potentially include all of the
|
||||
* previous items.
|
||||
*/
|
||||
public enum Item implements Fetchable {
|
||||
/**
|
||||
* Download the flags of the message.
|
||||
*/
|
||||
FLAGS,
|
||||
|
||||
/**
|
||||
* Download the envelope of the message. This should include at minimum
|
||||
* the size and the following headers: date, subject, from, content-type, to, cc
|
||||
*/
|
||||
ENVELOPE,
|
||||
|
||||
/**
|
||||
* Download the structure of the message. This maps directly to IMAP's BODYSTRUCTURE
|
||||
* and may map to other providers.
|
||||
* The provider should, if possible, fill in a properly formatted MIME structure in
|
||||
* the message without actually downloading any message data. If the provider is not
|
||||
* capable of this operation it should specifically set the body of the message to null
|
||||
* so that upper levels can detect that a full body download is needed.
|
||||
*/
|
||||
STRUCTURE,
|
||||
|
||||
/**
|
||||
* A sane portion of the entire message, cut off at a provider determined limit.
|
||||
* This should generaly be around 50kB.
|
||||
*/
|
||||
BODY_SANE,
|
||||
|
||||
/**
|
||||
* The entire message.
|
||||
*/
|
||||
BODY,
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the first {@link Part} in this collection, or null if it doesn't contain
|
||||
* {@link Part}.
|
||||
*/
|
||||
public Part getFirstPart() {
|
||||
for (Fetchable o : this) {
|
||||
if (o instanceof Part) {
|
||||
return (Part) o;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 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.emailcommon.mail;
|
||||
|
||||
/**
|
||||
* Interface for classes that can be added to {@link FetchProfile}.
|
||||
* i.e. {@link Part} and its subclasses, and {@link FetchProfile.Item}.
|
||||
*/
|
||||
public interface Fetchable {
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
/*
|
||||
* 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.emailcommon.mail;
|
||||
|
||||
/**
|
||||
* Flags that can be applied to Messages.
|
||||
*/
|
||||
public enum Flag {
|
||||
|
||||
// If adding new flags: ALL FLAGS MUST BE UPPER CASE.
|
||||
|
||||
DELETED,
|
||||
SEEN,
|
||||
ANSWERED,
|
||||
FLAGGED,
|
||||
DRAFT,
|
||||
RECENT,
|
||||
|
||||
/*
|
||||
* The following flags are for internal library use only.
|
||||
* TODO Eventually we should creates a Flags class that extends ArrayList that allows
|
||||
* these flags and Strings to represent user defined flags. At that point the below
|
||||
* flags should become user defined flags.
|
||||
*/
|
||||
/**
|
||||
* Delete and remove from the LocalStore immediately.
|
||||
*/
|
||||
X_DESTROYED,
|
||||
|
||||
/**
|
||||
* Sending of an unsent message failed. It will be retried. Used to show status.
|
||||
*/
|
||||
X_SEND_FAILED,
|
||||
|
||||
/**
|
||||
* Sending of an unsent message is in progress.
|
||||
*/
|
||||
X_SEND_IN_PROGRESS,
|
||||
|
||||
/**
|
||||
* Indicates that a message is fully downloaded from the server and can be viewed normally.
|
||||
* This does not include attachments, which are never downloaded fully.
|
||||
*/
|
||||
X_DOWNLOADED_FULL,
|
||||
|
||||
/**
|
||||
* Indicates that a message is partially downloaded from the server and can be viewed but
|
||||
* more content is available on the server.
|
||||
* This does not include attachments, which are never downloaded fully.
|
||||
*/
|
||||
X_DOWNLOADED_PARTIAL,
|
||||
|
||||
/**
|
||||
* General purpose flag that can be used by any remote store. The flag will be
|
||||
* saved and restored by the LocalStore.
|
||||
*/
|
||||
X_STORE_1,
|
||||
|
||||
/**
|
||||
* General purpose flag that can be used by any remote store. The flag will be
|
||||
* saved and restored by the LocalStore.
|
||||
*/
|
||||
X_STORE_2,
|
||||
|
||||
}
|
@ -1,209 +0,0 @@
|
||||
/*
|
||||
* 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.emailcommon.mail;
|
||||
|
||||
import com.android.emailcommon.service.SearchParams;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
|
||||
public abstract class Folder {
|
||||
public enum OpenMode {
|
||||
READ_WRITE, READ_ONLY,
|
||||
}
|
||||
|
||||
public enum FolderType {
|
||||
HOLDS_FOLDERS, HOLDS_MESSAGES,
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifiers of "special" folders.
|
||||
*/
|
||||
public enum FolderRole {
|
||||
INBOX, // NOTE: The folder's name must be INBOX
|
||||
TRASH,
|
||||
SENT,
|
||||
DRAFTS,
|
||||
|
||||
OUTBOX, // Local folders only - not used in remote Stores
|
||||
OTHER, // this folder has no specific role
|
||||
UNKNOWN // the role of this folder is unknown
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for each message retrieval.
|
||||
*
|
||||
* Not all {@link Folder} implementations may invoke it.
|
||||
*/
|
||||
public interface MessageRetrievalListener {
|
||||
public void messageRetrieved(Message message);
|
||||
public void loadAttachmentProgress(int progress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces an open of the MailProvider. If the provider is already open this
|
||||
* function returns without doing anything.
|
||||
*
|
||||
* @param mode READ_ONLY or READ_WRITE
|
||||
* @param callbacks Pointer to callbacks class. This may be used by the folder between this
|
||||
* time and when close() is called. This is only used for remote stores - should be null
|
||||
* for LocalStore.LocalFolder.
|
||||
*/
|
||||
public abstract void open(OpenMode mode)
|
||||
throws MessagingException;
|
||||
|
||||
/**
|
||||
* Forces a close of the MailProvider. Any further access will attempt to
|
||||
* reopen the MailProvider.
|
||||
*
|
||||
* @param expunge If true all deleted messages will be expunged.
|
||||
*/
|
||||
public abstract void close(boolean expunge) throws MessagingException;
|
||||
|
||||
/**
|
||||
* @return True if further commands are not expected to have to open the
|
||||
* connection.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public abstract boolean isOpen();
|
||||
|
||||
/**
|
||||
* Returns the mode the folder was opened with. This may be different than the mode the open
|
||||
* was requested with.
|
||||
*/
|
||||
public abstract OpenMode getMode() throws MessagingException;
|
||||
|
||||
/**
|
||||
* Reports if the Store is able to create folders of the given type.
|
||||
* Does not actually attempt to create a folder.
|
||||
* @param type
|
||||
* @return true if can create, false if cannot create
|
||||
*/
|
||||
public abstract boolean canCreate(FolderType type);
|
||||
|
||||
/**
|
||||
* Attempt to create the given folder remotely using the given type.
|
||||
* @return true if created, false if cannot create (e.g. server side)
|
||||
*/
|
||||
public abstract boolean create(FolderType type) throws MessagingException;
|
||||
|
||||
public abstract boolean exists() throws MessagingException;
|
||||
|
||||
/**
|
||||
* Returns the number of messages in the selected folder.
|
||||
*/
|
||||
public abstract int getMessageCount() throws MessagingException;
|
||||
|
||||
public abstract int getUnreadMessageCount() throws MessagingException;
|
||||
|
||||
public abstract Message getMessage(String uid) throws MessagingException;
|
||||
|
||||
/**
|
||||
* Fetches the given list of messages. The specified listener is notified as
|
||||
* each fetch completes. Messages are downloaded as (as) lightweight (as
|
||||
* possible) objects to be filled in with later requests. In most cases this
|
||||
* means that only the UID is downloaded.
|
||||
*/
|
||||
public abstract Message[] getMessages(int start, int end, MessageRetrievalListener listener)
|
||||
throws MessagingException;
|
||||
|
||||
public abstract Message[] getMessages(SearchParams params,MessageRetrievalListener listener)
|
||||
throws MessagingException;
|
||||
|
||||
public abstract Message[] getMessages(String[] uids, MessageRetrievalListener listener)
|
||||
throws MessagingException;
|
||||
|
||||
/**
|
||||
* Return a set of messages based on the state of the flags.
|
||||
* Note: Not typically implemented in remote stores, so not abstract.
|
||||
*
|
||||
* @param setFlags The flags that should be set for a message to be selected (can be null)
|
||||
* @param clearFlags The flags that should be clear for a message to be selected (can be null)
|
||||
* @param listener
|
||||
* @return A list of messages matching the desired flag states.
|
||||
* @throws MessagingException
|
||||
*/
|
||||
public Message[] getMessages(Flag[] setFlags, Flag[] clearFlags,
|
||||
MessageRetrievalListener listener) throws MessagingException {
|
||||
throw new MessagingException("Not implemented");
|
||||
}
|
||||
|
||||
public abstract void appendMessages(Message[] messages) throws MessagingException;
|
||||
|
||||
/**
|
||||
* Copies the given messages to the destination folder.
|
||||
*/
|
||||
public abstract void copyMessages(Message[] msgs, Folder folder,
|
||||
MessageUpdateCallbacks callbacks) throws MessagingException;
|
||||
|
||||
public abstract void setFlags(Message[] messages, Flag[] flags, boolean value)
|
||||
throws MessagingException;
|
||||
|
||||
public abstract Message[] expunge() throws MessagingException;
|
||||
|
||||
public abstract void fetch(Message[] messages, FetchProfile fp,
|
||||
MessageRetrievalListener listener) throws MessagingException;
|
||||
|
||||
public abstract void delete(boolean recurse) throws MessagingException;
|
||||
|
||||
public abstract String getName();
|
||||
|
||||
public abstract Flag[] getPermanentFlags() throws MessagingException;
|
||||
|
||||
/**
|
||||
* This method returns a string identifying the name of a "role" folder
|
||||
* (such as inbox, draft, sent, or trash). Stores that do not implement this
|
||||
* feature can be used - the account UI will provide default strings. To
|
||||
* let the server identify specific folder roles, simply override this method.
|
||||
*
|
||||
* @return The server- or protocol- specific role for this folder. If some roles are known
|
||||
* but this is not one of them, return FolderRole.OTHER. If roles are unsupported here,
|
||||
* return FolderRole.UNKNOWN.
|
||||
*/
|
||||
public FolderRole getRole() {
|
||||
return FolderRole.UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an empty message of the appropriate type for the Folder.
|
||||
*/
|
||||
public abstract Message createMessage(String uid) throws MessagingException;
|
||||
|
||||
/**
|
||||
* Callback interface by which a folder can report UID changes caused by certain operations.
|
||||
*/
|
||||
public interface MessageUpdateCallbacks {
|
||||
/**
|
||||
* The operation caused the message's UID to change
|
||||
* @param message The message for which the UID changed
|
||||
* @param newUid The new UID for the message
|
||||
*/
|
||||
public void onMessageUidChange(Message message, String newUid) throws MessagingException;
|
||||
|
||||
/**
|
||||
* The operation could not be completed because the message doesn't exist
|
||||
* (for example, it was already deleted from the server side.)
|
||||
* @param message The message that does not exist
|
||||
* @throws MessagingException
|
||||
*/
|
||||
public void onMessageNotFound(Message message) throws MessagingException;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getName();
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 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.emailcommon.mail;
|
||||
|
||||
public class MeetingInfo {
|
||||
// Predefined tags; others can be added
|
||||
public static final String MEETING_DTSTAMP = "DTSTAMP";
|
||||
public static final String MEETING_UID = "UID";
|
||||
public static final String MEETING_ORGANIZER_EMAIL = "ORGMAIL";
|
||||
public static final String MEETING_DTSTART = "DTSTART";
|
||||
public static final String MEETING_DTEND = "DTEND";
|
||||
public static final String MEETING_TITLE = "TITLE";
|
||||
public static final String MEETING_LOCATION = "LOC";
|
||||
public static final String MEETING_RESPONSE_REQUESTED = "RESPONSE";
|
||||
public static final String MEETING_ALL_DAY = "ALLDAY";
|
||||
}
|
@ -1,159 +0,0 @@
|
||||
/*
|
||||
* 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.emailcommon.mail;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
|
||||
public abstract class Message implements Part, Body {
|
||||
public static final Message[] EMPTY_ARRAY = new Message[0];
|
||||
|
||||
public enum RecipientType {
|
||||
TO, CC, BCC,
|
||||
}
|
||||
|
||||
protected String mUid;
|
||||
|
||||
private HashSet<Flag> mFlags = null;
|
||||
|
||||
protected Date mInternalDate;
|
||||
|
||||
protected Folder mFolder;
|
||||
|
||||
public String getUid() {
|
||||
return mUid;
|
||||
}
|
||||
|
||||
public void setUid(String uid) {
|
||||
this.mUid = uid;
|
||||
}
|
||||
|
||||
public Folder getFolder() {
|
||||
return mFolder;
|
||||
}
|
||||
|
||||
public abstract String getSubject() throws MessagingException;
|
||||
|
||||
public abstract void setSubject(String subject) throws MessagingException;
|
||||
|
||||
public Date getInternalDate() {
|
||||
return mInternalDate;
|
||||
}
|
||||
|
||||
public void setInternalDate(Date internalDate) {
|
||||
this.mInternalDate = internalDate;
|
||||
}
|
||||
|
||||
public abstract Date getReceivedDate() throws MessagingException;
|
||||
|
||||
public abstract Date getSentDate() throws MessagingException;
|
||||
|
||||
public abstract void setSentDate(Date sentDate) throws MessagingException;
|
||||
|
||||
public abstract Address[] getRecipients(RecipientType type) throws MessagingException;
|
||||
|
||||
public abstract void setRecipients(RecipientType type, Address[] addresses)
|
||||
throws MessagingException;
|
||||
|
||||
public void setRecipient(RecipientType type, Address address) throws MessagingException {
|
||||
setRecipients(type, new Address[] {
|
||||
address
|
||||
});
|
||||
}
|
||||
|
||||
public abstract Address[] getFrom() throws MessagingException;
|
||||
|
||||
public abstract void setFrom(Address from) throws MessagingException;
|
||||
|
||||
public abstract Address[] getReplyTo() throws MessagingException;
|
||||
|
||||
public abstract void setReplyTo(Address[] from) throws MessagingException;
|
||||
|
||||
public abstract Body getBody() throws MessagingException;
|
||||
|
||||
public abstract String getContentType() throws MessagingException;
|
||||
|
||||
public abstract void addHeader(String name, String value) throws MessagingException;
|
||||
|
||||
public abstract void setHeader(String name, String value) throws MessagingException;
|
||||
|
||||
public abstract String[] getHeader(String name) throws MessagingException;
|
||||
|
||||
public abstract void removeHeader(String name) throws MessagingException;
|
||||
|
||||
// Always use these instead of getHeader("Message-ID") or setHeader("Message-ID");
|
||||
public abstract void setMessageId(String messageId) throws MessagingException;
|
||||
public abstract String getMessageId() throws MessagingException;
|
||||
|
||||
public abstract void setBody(Body body) throws MessagingException;
|
||||
|
||||
public boolean isMimeType(String mimeType) throws MessagingException {
|
||||
return getContentType().startsWith(mimeType);
|
||||
}
|
||||
|
||||
private HashSet<Flag> getFlagSet() {
|
||||
if (mFlags == null) {
|
||||
mFlags = new HashSet<Flag>();
|
||||
}
|
||||
return mFlags;
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO Refactor Flags at some point to be able to store user defined flags.
|
||||
*/
|
||||
public Flag[] getFlags() {
|
||||
return getFlagSet().toArray(new Flag[] {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set/clear a flag directly, without involving overrides of {@link #setFlag} in subclasses.
|
||||
* Only used for testing.
|
||||
*/
|
||||
public final void setFlagDirectlyForTest(Flag flag, boolean set) throws MessagingException {
|
||||
if (set) {
|
||||
getFlagSet().add(flag);
|
||||
} else {
|
||||
getFlagSet().remove(flag);
|
||||
}
|
||||
}
|
||||
|
||||
public void setFlag(Flag flag, boolean set) throws MessagingException {
|
||||
setFlagDirectlyForTest(flag, set);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method calls setFlag(Flag, boolean)
|
||||
* @param flags
|
||||
* @param set
|
||||
*/
|
||||
public void setFlags(Flag[] flags, boolean set) throws MessagingException {
|
||||
for (Flag flag : flags) {
|
||||
setFlag(flag, set);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSet(Flag flag) {
|
||||
return getFlagSet().contains(flag);
|
||||
}
|
||||
|
||||
public abstract void saveChanges() throws MessagingException;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + ':' + mUid;
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
/*
|
||||
* 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.emailcommon.mail;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
public class MessageDateComparator implements Comparator<Message> {
|
||||
public int compare(Message o1, Message o2) {
|
||||
try {
|
||||
if (o1.getSentDate() == null) {
|
||||
return 1;
|
||||
} else if (o2.getSentDate() == null) {
|
||||
return -1;
|
||||
} else
|
||||
return o2.getSentDate().compareTo(o1.getSentDate());
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,136 +0,0 @@
|
||||
/*
|
||||
* 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.emailcommon.mail;
|
||||
|
||||
|
||||
/**
|
||||
* This exception is used for most types of failures that occur during server interactions.
|
||||
*
|
||||
* Data passed through this exception should be considered non-localized. Any strings should
|
||||
* either be internal-only (for debugging) or server-generated.
|
||||
*
|
||||
* TO DO: Does it make sense to further collapse AuthenticationFailedException and
|
||||
* CertificateValidationException and any others into this?
|
||||
*/
|
||||
public class MessagingException extends Exception {
|
||||
public static final long serialVersionUID = -1;
|
||||
|
||||
public static final int NO_ERROR = -1;
|
||||
/** Any exception that does not specify a specific issue */
|
||||
public static final int UNSPECIFIED_EXCEPTION = 0;
|
||||
/** Connection or IO errors */
|
||||
public static final int IOERROR = 1;
|
||||
/** The configuration requested TLS but the server did not support it. */
|
||||
public static final int TLS_REQUIRED = 2;
|
||||
/** Authentication is required but the server did not support it. */
|
||||
public static final int AUTH_REQUIRED = 3;
|
||||
/** General security failures */
|
||||
public static final int GENERAL_SECURITY = 4;
|
||||
/** Authentication failed */
|
||||
public static final int AUTHENTICATION_FAILED = 5;
|
||||
/** Attempt to create duplicate account */
|
||||
public static final int DUPLICATE_ACCOUNT = 6;
|
||||
/** Required security policies reported - advisory only */
|
||||
public static final int SECURITY_POLICIES_REQUIRED = 7;
|
||||
/** Required security policies not supported */
|
||||
public static final int SECURITY_POLICIES_UNSUPPORTED = 8;
|
||||
/** The protocol (or protocol version) isn't supported */
|
||||
public static final int PROTOCOL_VERSION_UNSUPPORTED = 9;
|
||||
/** The server's SSL certificate couldn't be validated */
|
||||
public static final int CERTIFICATE_VALIDATION_ERROR = 10;
|
||||
/** Authentication failed during autodiscover */
|
||||
public static final int AUTODISCOVER_AUTHENTICATION_FAILED = 11;
|
||||
/** Autodiscover completed with a result (non-error) */
|
||||
public static final int AUTODISCOVER_AUTHENTICATION_RESULT = 12;
|
||||
/** Ambiguous failure; server error or bad credentials */
|
||||
public static final int AUTHENTICATION_FAILED_OR_SERVER_ERROR = 13;
|
||||
/** The server refused access */
|
||||
public static final int ACCESS_DENIED = 14;
|
||||
/** The server refused access */
|
||||
public static final int ATTACHMENT_NOT_FOUND = 15;
|
||||
/** A client SSL certificate is required for connections to the server */
|
||||
public static final int CLIENT_CERTIFICATE_REQUIRED = 16;
|
||||
/** The client SSL certificate specified is invalid */
|
||||
public static final int CLIENT_CERTIFICATE_ERROR = 17;
|
||||
|
||||
protected int mExceptionType;
|
||||
// Exception type-specific data
|
||||
protected Object mExceptionData;
|
||||
|
||||
public MessagingException(String message, Throwable throwable) {
|
||||
this(UNSPECIFIED_EXCEPTION, message, throwable);
|
||||
}
|
||||
|
||||
public MessagingException(int exceptionType, String message, Throwable throwable) {
|
||||
super(message, throwable);
|
||||
mExceptionType = exceptionType;
|
||||
mExceptionData = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a MessagingException with an exceptionType and a null message.
|
||||
* @param exceptionType The exception type to set for this exception.
|
||||
*/
|
||||
public MessagingException(int exceptionType) {
|
||||
this(exceptionType, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a MessagingException with a message.
|
||||
* @param message the message for this exception
|
||||
*/
|
||||
public MessagingException(String message) {
|
||||
this(UNSPECIFIED_EXCEPTION, message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a MessagingException with an exceptionType and a message.
|
||||
* @param exceptionType The exception type to set for this exception.
|
||||
*/
|
||||
public MessagingException(int exceptionType, String message) {
|
||||
this(exceptionType, message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a MessagingException with an exceptionType, a message, and data
|
||||
* @param exceptionType The exception type to set for this exception.
|
||||
* @param message the message for the exception (or null)
|
||||
* @param data exception-type specific data for the exception (or null)
|
||||
*/
|
||||
public MessagingException(int exceptionType, String message, Object data) {
|
||||
super(message);
|
||||
mExceptionType = exceptionType;
|
||||
mExceptionData = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the exception type. Will be OTHER_EXCEPTION if not explicitly set.
|
||||
*
|
||||
* @return Returns the exception type.
|
||||
*/
|
||||
public int getExceptionType() {
|
||||
return mExceptionType;
|
||||
}
|
||||
/**
|
||||
* Return the exception data. Will be null if not explicitly set.
|
||||
*
|
||||
* @return Returns the exception data.
|
||||
*/
|
||||
public Object getExceptionData() {
|
||||
return mExceptionData;
|
||||
}
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
/*
|
||||
* 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.emailcommon.mail;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public abstract class Multipart implements Body {
|
||||
protected Part mParent;
|
||||
|
||||
protected ArrayList<BodyPart> mParts = new ArrayList<BodyPart>();
|
||||
|
||||
protected String mContentType;
|
||||
|
||||
public void addBodyPart(BodyPart part) throws MessagingException {
|
||||
mParts.add(part);
|
||||
}
|
||||
|
||||
public void addBodyPart(BodyPart part, int index) throws MessagingException {
|
||||
mParts.add(index, part);
|
||||
}
|
||||
|
||||
public BodyPart getBodyPart(int index) throws MessagingException {
|
||||
return mParts.get(index);
|
||||
}
|
||||
|
||||
public String getContentType() throws MessagingException {
|
||||
return mContentType;
|
||||
}
|
||||
|
||||
public int getCount() throws MessagingException {
|
||||
return mParts.size();
|
||||
}
|
||||
|
||||
public boolean removeBodyPart(BodyPart part) throws MessagingException {
|
||||
return mParts.remove(part);
|
||||
}
|
||||
|
||||
public void removeBodyPart(int index) throws MessagingException {
|
||||
mParts.remove(index);
|
||||
}
|
||||
|
||||
public Part getParent() throws MessagingException {
|
||||
return mParent;
|
||||
}
|
||||
|
||||
public void setParent(Part parent) throws MessagingException {
|
||||
this.mParent = parent;
|
||||
}
|
||||
}
|
@ -1,176 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 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.emailcommon.mail;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A utility class for creating and modifying Strings that are tagged and packed together.
|
||||
*
|
||||
* Uses non-printable (control chars) for internal delimiters; Intended for regular displayable
|
||||
* strings only, so please use base64 or other encoding if you need to hide any binary data here.
|
||||
*
|
||||
* Binary compatible with Address.pack() format, which should migrate to use this code.
|
||||
*/
|
||||
public class PackedString {
|
||||
|
||||
/**
|
||||
* Packing format is:
|
||||
* element : [ value ] or [ value TAG-DELIMITER tag ]
|
||||
* packed-string : [ element ] [ ELEMENT-DELIMITER [ element ] ]*
|
||||
*/
|
||||
private static final char DELIMITER_ELEMENT = '\1';
|
||||
private static final char DELIMITER_TAG = '\2';
|
||||
|
||||
private String mString;
|
||||
private HashMap<String, String> mExploded;
|
||||
private static final HashMap<String, String> EMPTY_MAP = new HashMap<String, String>();
|
||||
|
||||
/**
|
||||
* Create a packed string using an already-packed string (e.g. from database)
|
||||
* @param string packed string
|
||||
*/
|
||||
public PackedString(String string) {
|
||||
mString = string;
|
||||
mExploded = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value referred to by a given tag. If the tag does not exist, return null.
|
||||
* @param tag identifier of string of interest
|
||||
* @return returns value, or null if no string is found
|
||||
*/
|
||||
public String get(String tag) {
|
||||
if (mExploded == null) {
|
||||
mExploded = explode(mString);
|
||||
}
|
||||
return mExploded.get(tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a map of all of the values referred to by a given tag. This is a shallow
|
||||
* copy, don't edit the values.
|
||||
* @return a map of the values in the packed string
|
||||
*/
|
||||
public Map<String, String> unpack() {
|
||||
if (mExploded == null) {
|
||||
mExploded = explode(mString);
|
||||
}
|
||||
return new HashMap<String,String>(mExploded);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read out all values into a map.
|
||||
*/
|
||||
private static HashMap<String, String> explode(String packed) {
|
||||
if (packed == null || packed.length() == 0) {
|
||||
return EMPTY_MAP;
|
||||
}
|
||||
HashMap<String, String> map = new HashMap<String, String>();
|
||||
|
||||
int length = packed.length();
|
||||
int elementStartIndex = 0;
|
||||
int elementEndIndex = 0;
|
||||
int tagEndIndex = packed.indexOf(DELIMITER_TAG);
|
||||
|
||||
while (elementStartIndex < length) {
|
||||
elementEndIndex = packed.indexOf(DELIMITER_ELEMENT, elementStartIndex);
|
||||
if (elementEndIndex == -1) {
|
||||
elementEndIndex = length;
|
||||
}
|
||||
String tag;
|
||||
String value;
|
||||
if (tagEndIndex == -1 || elementEndIndex <= tagEndIndex) {
|
||||
// in this case the DELIMITER_PERSONAL is in a future pair (or not found)
|
||||
// so synthesize a positional tag for the value, and don't update tagEndIndex
|
||||
value = packed.substring(elementStartIndex, elementEndIndex);
|
||||
tag = Integer.toString(map.size());
|
||||
} else {
|
||||
value = packed.substring(elementStartIndex, tagEndIndex);
|
||||
tag = packed.substring(tagEndIndex + 1, elementEndIndex);
|
||||
// scan forward for next tag, if any
|
||||
tagEndIndex = packed.indexOf(DELIMITER_TAG, elementEndIndex + 1);
|
||||
}
|
||||
map.put(tag, value);
|
||||
elementStartIndex = elementEndIndex + 1;
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder class for creating PackedString values. Can also be used for editing existing
|
||||
* PackedString representations.
|
||||
*/
|
||||
static public class Builder {
|
||||
HashMap<String, String> mMap;
|
||||
|
||||
/**
|
||||
* Create a builder that's empty (for filling)
|
||||
*/
|
||||
public Builder() {
|
||||
mMap = new HashMap<String, String>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a builder using the values of an existing PackedString (for editing).
|
||||
*/
|
||||
public Builder(String packed) {
|
||||
mMap = explode(packed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a tagged value
|
||||
* @param tag identifier of string of interest
|
||||
* @param value the value to record in this position. null to delete entry.
|
||||
*/
|
||||
public void put(String tag, String value) {
|
||||
if (value == null) {
|
||||
mMap.remove(tag);
|
||||
} else {
|
||||
mMap.put(tag, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value referred to by a given tag. If the tag does not exist, return null.
|
||||
* @param tag identifier of string of interest
|
||||
* @return returns value, or null if no string is found
|
||||
*/
|
||||
public String get(String tag) {
|
||||
return mMap.get(tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pack the values and return a single, encoded string
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Map.Entry<String,String> entry : mMap.entrySet()) {
|
||||
if (sb.length() > 0) {
|
||||
sb.append(DELIMITER_ELEMENT);
|
||||
}
|
||||
sb.append(entry.getValue());
|
||||
sb.append(DELIMITER_TAG);
|
||||
sb.append(entry.getKey());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
/*
|
||||
* 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.emailcommon.mail;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public interface Part extends Fetchable {
|
||||
public void addHeader(String name, String value) throws MessagingException;
|
||||
|
||||
public void removeHeader(String name) throws MessagingException;
|
||||
|
||||
public void setHeader(String name, String value) throws MessagingException;
|
||||
|
||||
public Body getBody() throws MessagingException;
|
||||
|
||||
public String getContentType() throws MessagingException;
|
||||
|
||||
public String getDisposition() throws MessagingException;
|
||||
|
||||
public String getContentId() throws MessagingException;
|
||||
|
||||
public String[] getHeader(String name) throws MessagingException;
|
||||
|
||||
public void setExtendedHeader(String name, String value) throws MessagingException;
|
||||
|
||||
public String getExtendedHeader(String name) throws MessagingException;
|
||||
|
||||
public int getSize() throws MessagingException;
|
||||
|
||||
public boolean isMimeType(String mimeType) throws MessagingException;
|
||||
|
||||
public String getMimeType() throws MessagingException;
|
||||
|
||||
public void setBody(Body body) throws MessagingException;
|
||||
|
||||
public void writeTo(OutputStream out) throws IOException, MessagingException;
|
||||
}
|
@ -1,137 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 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.emailcommon.service;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.android.emailcommon.provider.Mailbox;
|
||||
import com.google.common.base.Objects;
|
||||
|
||||
public class SearchParams implements Parcelable {
|
||||
public static final long ALL_MAILBOXES = Mailbox.NO_MAILBOX;
|
||||
|
||||
private static final int DEFAULT_LIMIT = 10; // Need input on what this number should be
|
||||
private static final int DEFAULT_OFFSET = 0;
|
||||
|
||||
// The id of the mailbox to be searched; if -1, all mailboxes MUST be searched
|
||||
public final long mMailboxId;
|
||||
// If true, all subfolders of the specified mailbox MUST be searched
|
||||
public boolean mIncludeChildren = true;
|
||||
// The search terms (the search MUST only select messages whose contents include all of the
|
||||
// search terms in the query)
|
||||
public final String mFilter;
|
||||
// The maximum number of results to be created by this search
|
||||
public int mLimit = DEFAULT_LIMIT;
|
||||
// If zero, specifies a "new" search; otherwise, asks for a continuation of the previous
|
||||
// query(ies) starting with the mOffset'th match (0 based)
|
||||
public int mOffset = DEFAULT_OFFSET;
|
||||
// The total number of results for this search
|
||||
public int mTotalCount = 0;
|
||||
// The id of the "search" mailbox being used
|
||||
public long mSearchMailboxId;
|
||||
|
||||
/**
|
||||
* Error codes returned by the searchMessages API
|
||||
*/
|
||||
public static class SearchParamsError {
|
||||
public static final int CANT_SEARCH_ALL_MAILBOXES = -1;
|
||||
public static final int CANT_SEARCH_CHILDREN = -2;
|
||||
}
|
||||
|
||||
public SearchParams(long mailboxId, String filter) {
|
||||
mMailboxId = mailboxId;
|
||||
mFilter = filter;
|
||||
}
|
||||
|
||||
public SearchParams(long mailboxId, String filter, long searchMailboxId) {
|
||||
mMailboxId = mailboxId;
|
||||
mFilter = filter;
|
||||
mSearchMailboxId = searchMailboxId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == this) {
|
||||
return true;
|
||||
}
|
||||
if ((o == null) || !(o instanceof SearchParams)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SearchParams os = (SearchParams) o;
|
||||
return mMailboxId == os.mMailboxId
|
||||
&& mIncludeChildren == os.mIncludeChildren
|
||||
&& mFilter.equals(os.mFilter)
|
||||
&& mLimit == os.mLimit
|
||||
&& mOffset == os.mOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(mMailboxId, mFilter, mOffset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[SearchParams " + mMailboxId + ":" + mFilter + " (" + mOffset + ", " + mLimit + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Supports Parcelable
|
||||
*/
|
||||
public static final Parcelable.Creator<SearchParams> CREATOR
|
||||
= new Parcelable.Creator<SearchParams>() {
|
||||
@Override
|
||||
public SearchParams createFromParcel(Parcel in) {
|
||||
return new SearchParams(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchParams[] newArray(int size) {
|
||||
return new SearchParams[size];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Supports Parcelable
|
||||
*/
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeLong(mMailboxId);
|
||||
dest.writeInt(mIncludeChildren ? 1 : 0);
|
||||
dest.writeString(mFilter);
|
||||
dest.writeInt(mLimit);
|
||||
dest.writeInt(mOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Supports Parcelable
|
||||
*/
|
||||
public SearchParams(Parcel in) {
|
||||
mMailboxId = in.readLong();
|
||||
mIncludeChildren = in.readInt() == 1;
|
||||
mFilter = in.readString();
|
||||
mLimit = in.readInt();
|
||||
mOffset = in.readInt();
|
||||
}
|
||||
}
|
@ -211,34 +211,6 @@ public class Utility {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the given string starts and ends with the double quote character. The string is
|
||||
* not modified in any way except to add the double quote character to start and end if it's not
|
||||
* already there.
|
||||
*
|
||||
* TODO: Rename this, because "quoteString()" can mean so many different things.
|
||||
*
|
||||
* sample -> "sample"
|
||||
* "sample" -> "sample"
|
||||
* ""sample"" -> "sample"
|
||||
* "sample"" -> "sample"
|
||||
* sa"mp"le -> "sa"mp"le"
|
||||
* "sa"mp"le" -> "sa"mp"le"
|
||||
* (empty string) -> ""
|
||||
* " -> ""
|
||||
*/
|
||||
public static String quoteString(String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
if (!s.matches("^\".*\"$")) {
|
||||
return "\"" + s + "\"";
|
||||
}
|
||||
else {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A fast version of URLDecoder.decode() that works only with UTF-8 and does only two
|
||||
* allocations. This version is around 3x as fast as the standard one and I'm using it
|
||||
|
Loading…
Reference in New Issue
Block a user