summaryrefslogtreecommitdiff
path: root/java/com/android/voicemailomtp/mail
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/android/voicemailomtp/mail')
-rw-r--r--java/com/android/voicemailomtp/mail/Address.java541
-rw-r--r--java/com/android/voicemailomtp/mail/AuthenticationFailedException.java33
-rw-r--r--java/com/android/voicemailomtp/mail/Base64Body.java62
-rw-r--r--java/com/android/voicemailomtp/mail/Body.java25
-rw-r--r--java/com/android/voicemailomtp/mail/BodyPart.java24
-rw-r--r--java/com/android/voicemailomtp/mail/CertificateValidationException.java29
-rw-r--r--java/com/android/voicemailomtp/mail/FetchProfile.java84
-rw-r--r--java/com/android/voicemailomtp/mail/Fetchable.java23
-rw-r--r--java/com/android/voicemailomtp/mail/FixedLengthInputStream.java79
-rw-r--r--java/com/android/voicemailomtp/mail/Flag.java29
-rw-r--r--java/com/android/voicemailomtp/mail/MailTransport.java344
-rw-r--r--java/com/android/voicemailomtp/mail/MeetingInfo.java29
-rw-r--r--java/com/android/voicemailomtp/mail/Message.java144
-rw-r--r--java/com/android/voicemailomtp/mail/MessageDateComparator.java34
-rw-r--r--java/com/android/voicemailomtp/mail/MessagingException.java139
-rw-r--r--java/com/android/voicemailomtp/mail/Multipart.java62
-rw-r--r--java/com/android/voicemailomtp/mail/PackedString.java175
-rw-r--r--java/com/android/voicemailomtp/mail/Part.java51
-rw-r--r--java/com/android/voicemailomtp/mail/PeekableInputStream.java80
-rw-r--r--java/com/android/voicemailomtp/mail/TempDirectory.java41
-rw-r--r--java/com/android/voicemailomtp/mail/internet/BinaryTempFileBody.java91
-rw-r--r--java/com/android/voicemailomtp/mail/internet/MimeBodyPart.java207
-rw-r--r--java/com/android/voicemailomtp/mail/internet/MimeHeader.java161
-rw-r--r--java/com/android/voicemailomtp/mail/internet/MimeMessage.java675
-rw-r--r--java/com/android/voicemailomtp/mail/internet/MimeMultipart.java112
-rw-r--r--java/com/android/voicemailomtp/mail/internet/MimeUtility.java416
-rw-r--r--java/com/android/voicemailomtp/mail/internet/TextBody.java63
-rw-r--r--java/com/android/voicemailomtp/mail/store/ImapConnection.java413
-rw-r--r--java/com/android/voicemailomtp/mail/store/ImapFolder.java784
-rw-r--r--java/com/android/voicemailomtp/mail/store/ImapStore.java176
-rw-r--r--java/com/android/voicemailomtp/mail/store/imap/DigestMd5Utils.java335
-rw-r--r--java/com/android/voicemailomtp/mail/store/imap/ImapConstants.java144
-rw-r--r--java/com/android/voicemailomtp/mail/store/imap/ImapElement.java120
-rw-r--r--java/com/android/voicemailomtp/mail/store/imap/ImapList.java235
-rw-r--r--java/com/android/voicemailomtp/mail/store/imap/ImapMemoryLiteral.java76
-rw-r--r--java/com/android/voicemailomtp/mail/store/imap/ImapResponse.java158
-rw-r--r--java/com/android/voicemailomtp/mail/store/imap/ImapResponseParser.java432
-rw-r--r--java/com/android/voicemailomtp/mail/store/imap/ImapSimpleString.java62
-rw-r--r--java/com/android/voicemailomtp/mail/store/imap/ImapString.java192
-rw-r--r--java/com/android/voicemailomtp/mail/store/imap/ImapTempFileLiteral.java123
-rw-r--r--java/com/android/voicemailomtp/mail/store/imap/ImapUtility.java125
-rw-r--r--java/com/android/voicemailomtp/mail/utility/CountingOutputStream.java48
-rw-r--r--java/com/android/voicemailomtp/mail/utility/EOLConvertingOutputStream.java48
-rw-r--r--java/com/android/voicemailomtp/mail/utils/LogUtils.java413
-rw-r--r--java/com/android/voicemailomtp/mail/utils/Utility.java80
45 files changed, 0 insertions, 7717 deletions
diff --git a/java/com/android/voicemailomtp/mail/Address.java b/java/com/android/voicemailomtp/mail/Address.java
deleted file mode 100644
index ed3f44c03..000000000
--- a/java/com/android/voicemailomtp/mail/Address.java
+++ /dev/null
@@ -1,541 +0,0 @@
-/*
- * Copyright (C) 2015 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.voicemailomtp.mail;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.support.annotation.VisibleForTesting;
-import android.text.Html;
-import android.text.TextUtils;
-import android.text.util.Rfc822Token;
-import android.text.util.Rfc822Tokenizer;
-import com.android.voicemailomtp.mail.utils.LogUtils;
-import java.util.ArrayList;
-import java.util.regex.Pattern;
-import org.apache.james.mime4j.codec.EncoderUtil;
-import org.apache.james.mime4j.decoder.DecoderUtil;
-
-/**
- * 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 implements Parcelable {
- public static final String ADDRESS_DELIMETER = ",";
- /**
- * 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;
-
- /**
- * When personal is set, it will return the first token of the personal
- * string. Otherwise, it will return the e-mail address up to the '@' sign.
- */
- private String mSimplifiedName;
-
- // 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("\\\\([\\\\\"])");
-
- // TODO: LOCAL_PART and DOMAIN_PART_PART are too permissive and can be improved.
- // TODO: Fix this to better constrain comments.
- /** Regex for the local part of an email address. */
- private static final String LOCAL_PART = "[^@]+";
- /** Regex for each part of the domain part, i.e. the thing between the dots. */
- private static final String DOMAIN_PART_PART = "[[\\w][\\d]\\-\\(\\)\\[\\]]+";
- /** Regex for the domain part, which is two or more {@link #DOMAIN_PART_PART} separated by . */
- private static final String DOMAIN_PART =
- "(" + DOMAIN_PART_PART + "\\.)+" + DOMAIN_PART_PART;
-
- /** Pattern to check if an email address is valid. */
- private static final Pattern EMAIL_ADDRESS =
- Pattern.compile("\\A" + LOCAL_PART + "@" + DOMAIN_PART + "\\z");
-
- private static final Address[] EMPTY_ADDRESS_ARRAY = new Address[0];
-
- // delimiters are chars that do not appear in an email address, used by fromHeader
- private static final char LIST_DELIMITER_EMAIL = '\1';
- private static final char LIST_DELIMITER_PERSONAL = '\2';
-
- private static final String LOG_TAG = "Email Address";
-
- @VisibleForTesting
- public Address(String address) {
- setAddress(address);
- }
-
- public Address(String address, String personal) {
- setPersonal(personal);
- setAddress(address);
- }
-
- /**
- * Returns a simplified string for this e-mail address.
- * When a name is known, it will return the first token of that name. Otherwise, it will
- * return the e-mail address up to the '@' sign.
- */
- public String getSimplifiedName() {
- if (mSimplifiedName == null) {
- if (TextUtils.isEmpty(mPersonal) && !TextUtils.isEmpty(mAddress)) {
- int atSign = mAddress.indexOf('@');
- mSimplifiedName = (atSign != -1) ? mAddress.substring(0, atSign) : "";
- } else if (!TextUtils.isEmpty(mPersonal)) {
-
- // TODO: use Contacts' NameSplitter for more reliable first-name extraction
-
- int end = mPersonal.indexOf(' ');
- while (end > 0 && mPersonal.charAt(end - 1) == ',') {
- end--;
- }
- mSimplifiedName = (end < 1) ? mPersonal : mPersonal.substring(0, end);
-
- } else {
- LogUtils.w(LOG_TAG, "Unable to get a simplified name");
- mSimplifiedName = "";
- }
- }
- return mSimplifiedName;
- }
-
- public static synchronized Address getEmailAddress(String rawAddress) {
- if (TextUtils.isEmpty(rawAddress)) {
- return null;
- }
- String name, address;
- final Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(rawAddress);
- if (tokens.length > 0) {
- final String tokenizedName = tokens[0].getName();
- name = tokenizedName != null ? Html.fromHtml(tokenizedName.trim()).toString()
- : "";
- address = Html.fromHtml(tokens[0].getAddress()).toString();
- } else {
- name = "";
- address = rawAddress == null ?
- "" : Html.fromHtml(rawAddress).toString();
- }
- return new Address(address, name);
- }
-
- 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 personal 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) {
- mPersonal = decodeAddressPersonal(personal);
- }
-
- /**
- * Decodes name 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 static String decodeAddressPersonal(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;
- }
- }
- return 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.
- */
- @VisibleForTesting
- 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[addresses.size()]);
- }
-
- /**
- * Checks whether a string email address is valid.
- * E.g. name@domain.com is valid.
- */
- @VisibleForTesting
- static boolean isValidAddress(final String address) {
- return EMAIL_ADDRESS.matcher(address).find();
- }
-
- @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);
- }
-
- @Override
- 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 ensureQuotedString(mPersonal) + " <" + mAddress + ">";
- } else {
- return mPersonal + " <" + mAddress + ">";
- }
- } else {
- return mAddress;
- }
- }
-
- /**
- * 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.
- *
- * sample -> "sample"
- * "sample" -> "sample"
- * ""sample"" -> "sample"
- * "sample"" -> "sample"
- * sa"mp"le -> "sa"mp"le"
- * "sa"mp"le" -> "sa"mp"le"
- * (empty string) -> ""
- * " -> ""
- */
- private static String ensureQuotedString(String s) {
- if (s == null) {
- return null;
- }
- if (!s.matches("^\".*\"$")) {
- return "\"" + s + "\"";
- } else {
- return s;
- }
- }
-
- /**
- * Get human readable comma-delimited address string.
- *
- * @param addresses Address array
- * @return Human readable comma-delimited address string.
- */
- @VisibleForTesting
- public static String toString(Address[] addresses) {
- return toString(addresses, ADDRESS_DELIMETER);
- }
-
- /**
- * 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();
- }
- StringBuilder sb = new StringBuilder(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();
- }
- StringBuilder sb = new StringBuilder(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
- */
- @VisibleForTesting
- 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.
- */
- @VisibleForTesting
- public static String toFriendly(Address[] addresses) {
- if (addresses == null || addresses.length == 0) {
- return null;
- }
- if (addresses.length == 1) {
- return addresses[0].toFriendly();
- }
- StringBuilder sb = new StringBuilder(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.fromHeader(addressList)).
- */
- @VisibleForTesting
- public static String fromHeaderToString(String addressList) {
- return toString(fromHeader(addressList));
- }
-
- /**
- * Returns exactly the same result as Address.toHeader(Address.parse(addressList)).
- */
- @VisibleForTesting
- public static String parseToHeader(String addressList) {
- return Address.toHeader(Address.parse(addressList));
- }
-
- /**
- * Returns null if the addressList has 0 addresses, otherwise returns the first address.
- * The same as Address.fromHeader(addressList)[0] for non-empty list.
- * This is an utility method that offers some performance optimization opportunities.
- */
- @VisibleForTesting
- public static Address firstAddress(String addressList) {
- Address[] array = fromHeader(addressList);
- return array.length > 0 ? array[0] : null;
- }
-
- /**
- * This method exists to convert an address list formatted in a deprecated legacy format to the
- * standard RFC822 header format. {@link #fromHeader(String)} is capable of reading the legacy
- * format and the RFC822 format. {@link #toHeader()} always produces the RFC822 format.
- *
- * This implementation is brute-force, and could be replaced with a more efficient version
- * if desired.
- */
- public static String reformatToHeader(String addressList) {
- return toHeader(fromHeader(addressList));
- }
-
- /**
- * @param addressList a CSV of RFC822 addresses or the deprecated legacy string format
- * @return array of addresses parsed from <code>addressList</code>
- */
- @VisibleForTesting
- public static Address[] fromHeader(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-compatible unpack
- ArrayList<Address> addresses = new ArrayList<Address>();
- int length = addressList.length();
- int pairStartIndex = 0;
- int pairEndIndex;
-
- /* 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(new Address[addresses.size()]);
- }
-
- public static final Creator<Address> CREATOR = new Creator<Address>() {
- @Override
- public Address createFromParcel(Parcel parcel) {
- return new Address(parcel);
- }
-
- @Override
- public Address[] newArray(int size) {
- return new Address[size];
- }
- };
-
- public Address(Parcel in) {
- setPersonal(in.readString());
- setAddress(in.readString());
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel out, int flags) {
- out.writeString(mPersonal);
- out.writeString(mAddress);
- }
-}
diff --git a/java/com/android/voicemailomtp/mail/AuthenticationFailedException.java b/java/com/android/voicemailomtp/mail/AuthenticationFailedException.java
deleted file mode 100644
index 995d5d348..000000000
--- a/java/com/android/voicemailomtp/mail/AuthenticationFailedException.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2015 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.voicemailomtp.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);
- }
-} \ No newline at end of file
diff --git a/java/com/android/voicemailomtp/mail/Base64Body.java b/java/com/android/voicemailomtp/mail/Base64Body.java
deleted file mode 100644
index 6e1deff44..000000000
--- a/java/com/android/voicemailomtp/mail/Base64Body.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2015 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.voicemailomtp.mail;
-
-import android.util.Base64;
-import android.util.Base64OutputStream;
-
-import org.apache.commons.io.IOUtils;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-public class Base64Body implements Body {
- private final InputStream mSource;
- // Because we consume the input stream, we can only write out once
- private boolean mAlreadyWritten;
-
- public Base64Body(InputStream source) {
- mSource = source;
- }
-
- @Override
- public InputStream getInputStream() throws MessagingException {
- return mSource;
- }
-
- /**
- * This method consumes the input stream, so can only be called once
- * @param out Stream to write to
- * @throws IllegalStateException If called more than once
- * @throws IOException
- * @throws MessagingException
- */
- @Override
- public void writeTo(OutputStream out)
- throws IllegalStateException, IOException, MessagingException {
- if (mAlreadyWritten) {
- throw new IllegalStateException("Base64Body can only be written once");
- }
- mAlreadyWritten = true;
- try {
- final Base64OutputStream b64out = new Base64OutputStream(out, Base64.DEFAULT);
- IOUtils.copyLarge(mSource, b64out);
- } finally {
- mSource.close();
- }
- }
-}
diff --git a/java/com/android/voicemailomtp/mail/Body.java b/java/com/android/voicemailomtp/mail/Body.java
deleted file mode 100644
index 393e1823c..000000000
--- a/java/com/android/voicemailomtp/mail/Body.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2015 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.voicemailomtp.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;
-}
diff --git a/java/com/android/voicemailomtp/mail/BodyPart.java b/java/com/android/voicemailomtp/mail/BodyPart.java
deleted file mode 100644
index 62390a43e..000000000
--- a/java/com/android/voicemailomtp/mail/BodyPart.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2015 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.voicemailomtp.mail;
-
-public abstract class BodyPart implements Part {
- protected Multipart mParent;
-
- public Multipart getParent() {
- return mParent;
- }
-}
diff --git a/java/com/android/voicemailomtp/mail/CertificateValidationException.java b/java/com/android/voicemailomtp/mail/CertificateValidationException.java
deleted file mode 100644
index 8ebe5480b..000000000
--- a/java/com/android/voicemailomtp/mail/CertificateValidationException.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2015 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.voicemailomtp.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);
- }
-} \ No newline at end of file
diff --git a/java/com/android/voicemailomtp/mail/FetchProfile.java b/java/com/android/voicemailomtp/mail/FetchProfile.java
deleted file mode 100644
index d050692cc..000000000
--- a/java/com/android/voicemailomtp/mail/FetchProfile.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2015 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.voicemailomtp.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 generally 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;
- }
-}
diff --git a/java/com/android/voicemailomtp/mail/Fetchable.java b/java/com/android/voicemailomtp/mail/Fetchable.java
deleted file mode 100644
index 1d8d0005b..000000000
--- a/java/com/android/voicemailomtp/mail/Fetchable.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2015 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.voicemailomtp.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 {
-}
diff --git a/java/com/android/voicemailomtp/mail/FixedLengthInputStream.java b/java/com/android/voicemailomtp/mail/FixedLengthInputStream.java
deleted file mode 100644
index 65655efd5..000000000
--- a/java/com/android/voicemailomtp/mail/FixedLengthInputStream.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2015 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.voicemailomtp.mail;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * A filtering InputStream that stops allowing reads after the given length has been read. This
- * is used to allow a client to read directly from an underlying protocol stream without reading
- * past where the protocol handler intended the client to read.
- */
-public class FixedLengthInputStream extends InputStream {
- private final InputStream mIn;
- private final int mLength;
- private int mCount;
-
- public FixedLengthInputStream(InputStream in, int length) {
- this.mIn = in;
- this.mLength = length;
- }
-
- @Override
- public int available() throws IOException {
- return mLength - mCount;
- }
-
- @Override
- public int read() throws IOException {
- if (mCount < mLength) {
- mCount++;
- return mIn.read();
- } else {
- return -1;
- }
- }
-
- @Override
- public int read(byte[] b, int offset, int length) throws IOException {
- if (mCount < mLength) {
- int d = mIn.read(b, offset, Math.min(mLength - mCount, length));
- if (d == -1) {
- return -1;
- } else {
- mCount += d;
- return d;
- }
- } else {
- return -1;
- }
- }
-
- @Override
- public int read(byte[] b) throws IOException {
- return read(b, 0, b.length);
- }
-
- public int getLength() {
- return mLength;
- }
-
- @Override
- public String toString() {
- return String.format("FixedLengthInputStream(in=%s, length=%d)", mIn.toString(), mLength);
- }
-} \ No newline at end of file
diff --git a/java/com/android/voicemailomtp/mail/Flag.java b/java/com/android/voicemailomtp/mail/Flag.java
deleted file mode 100644
index a9f927099..000000000
--- a/java/com/android/voicemailomtp/mail/Flag.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2015 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.voicemailomtp.mail;
-
-/**
- * Flags that can be applied to Messages.
- */
-public class Flag {
- // If adding new flags: ALL FLAGS MUST BE UPPER CASE.
- public static final String DELETED = "deleted";
- public static final String SEEN = "seen";
- public static final String ANSWERED = "answered";
- public static final String FLAGGED = "flagged";
- public static final String DRAFT = "draft";
- public static final String RECENT = "recent";
-}
diff --git a/java/com/android/voicemailomtp/mail/MailTransport.java b/java/com/android/voicemailomtp/mail/MailTransport.java
deleted file mode 100644
index 3bf851fd8..000000000
--- a/java/com/android/voicemailomtp/mail/MailTransport.java
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
- * Copyright (C) 2015 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.voicemailomtp.mail;
-
-import android.content.Context;
-import android.net.Network;
-import android.support.annotation.VisibleForTesting;
-import com.android.voicemailomtp.OmtpEvents;
-import com.android.voicemailomtp.imap.ImapHelper;
-import com.android.voicemailomtp.mail.store.ImapStore;
-import com.android.voicemailomtp.mail.utils.LogUtils;
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.Socket;
-import java.util.ArrayList;
-import java.util.List;
-import javax.net.ssl.HostnameVerifier;
-import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.SSLException;
-import javax.net.ssl.SSLPeerUnverifiedException;
-import javax.net.ssl.SSLSession;
-import javax.net.ssl.SSLSocket;
-
-/**
- * Make connection and perform operations on mail server by reading and writing lines.
- */
-public class MailTransport {
- private static final String TAG = "MailTransport";
-
- // TODO protected eventually
- /*protected*/ public static final int SOCKET_CONNECT_TIMEOUT = 10000;
- /*protected*/ public static final int SOCKET_READ_TIMEOUT = 60000;
-
- private static final HostnameVerifier HOSTNAME_VERIFIER =
- HttpsURLConnection.getDefaultHostnameVerifier();
-
- private final Context mContext;
- private final ImapHelper mImapHelper;
- private final Network mNetwork;
- private final String mHost;
- private final int mPort;
- private Socket mSocket;
- private BufferedInputStream mIn;
- private BufferedOutputStream mOut;
- private final int mFlags;
- private SocketCreator mSocketCreator;
- private InetSocketAddress mAddress;
-
- public MailTransport(Context context, ImapHelper imapHelper, Network network, String address,
- int port, int flags) {
- mContext = context;
- mImapHelper = imapHelper;
- mNetwork = network;
- mHost = address;
- mPort = port;
- mFlags = flags;
- }
-
- /**
- * Returns a new transport, using the current transport as a model. The new transport is
- * configured identically, but not opened or connected in any way.
- */
- @Override
- public MailTransport clone() {
- return new MailTransport(mContext, mImapHelper, mNetwork, mHost, mPort, mFlags);
- }
-
- public boolean canTrySslSecurity() {
- return (mFlags & ImapStore.FLAG_SSL) != 0;
- }
-
- public boolean canTrustAllCertificates() {
- return (mFlags & ImapStore.FLAG_TRUST_ALL) != 0;
- }
-
- /**
- * Attempts to open a connection using the Uri supplied for connection parameters. Will attempt
- * an SSL connection if indicated.
- */
- public void open() throws MessagingException {
- LogUtils.d(TAG, "*** IMAP open " + mHost + ":" + String.valueOf(mPort));
-
- List<InetSocketAddress> socketAddresses = new ArrayList<InetSocketAddress>();
-
- if (mNetwork == null) {
- socketAddresses.add(new InetSocketAddress(mHost, mPort));
- } else {
- try {
- InetAddress[] inetAddresses = mNetwork.getAllByName(mHost);
- if (inetAddresses.length == 0) {
- throw new MessagingException(MessagingException.IOERROR,
- "Host name " + mHost + "cannot be resolved on designated network");
- }
- for (int i = 0; i < inetAddresses.length; i++) {
- socketAddresses.add(new InetSocketAddress(inetAddresses[i], mPort));
- }
- } catch (IOException ioe) {
- LogUtils.d(TAG, ioe.toString());
- mImapHelper.handleEvent(OmtpEvents.DATA_CANNOT_RESOLVE_HOST_ON_NETWORK);
- throw new MessagingException(MessagingException.IOERROR, ioe.toString());
- }
- }
-
- boolean success = false;
- while (socketAddresses.size() > 0) {
- mSocket = createSocket();
- try {
- mAddress = socketAddresses.remove(0);
- mSocket.connect(mAddress, SOCKET_CONNECT_TIMEOUT);
-
- if (canTrySslSecurity()) {
- /*
- SSLSocket cannot be created with a connection timeout, so instead of doing a
- direct SSL connection, we connect with a normal connection and upgrade it into
- SSL
- */
- reopenTls();
- } else {
- mIn = new BufferedInputStream(mSocket.getInputStream(), 1024);
- mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512);
- mSocket.setSoTimeout(SOCKET_READ_TIMEOUT);
- }
- success = true;
- return;
- } catch (IOException ioe) {
- LogUtils.d(TAG, ioe.toString());
- if (socketAddresses.size() == 0) {
- // Only throw an error when there are no more sockets to try.
- mImapHelper.handleEvent(OmtpEvents.DATA_ALL_SOCKET_CONNECTION_FAILED);
- throw new MessagingException(MessagingException.IOERROR, ioe.toString());
- }
- } finally {
- if (!success) {
- try {
- mSocket.close();
- mSocket = null;
- } catch (IOException ioe) {
- throw new MessagingException(MessagingException.IOERROR, ioe.toString());
- }
-
- }
- }
- }
- }
-
- // For testing. We need something that can replace the behavior of "new Socket()"
- @VisibleForTesting
- interface SocketCreator {
-
- Socket createSocket() throws MessagingException;
- }
-
- @VisibleForTesting
- void setSocketCreator(SocketCreator creator) {
- mSocketCreator = creator;
- }
-
- protected Socket createSocket() throws MessagingException {
- if (mSocketCreator != null) {
- return mSocketCreator.createSocket();
- }
-
- if (mNetwork == null) {
- LogUtils.v(TAG, "createSocket: network not specified");
- return new Socket();
- }
-
- try {
- LogUtils.v(TAG, "createSocket: network specified");
- return mNetwork.getSocketFactory().createSocket();
- } catch (IOException ioe) {
- LogUtils.d(TAG, ioe.toString());
- throw new MessagingException(MessagingException.IOERROR, ioe.toString());
- }
- }
-
- /**
- * Attempts to reopen a normal connection into a TLS connection.
- */
- public void reopenTls() throws MessagingException {
- try {
- LogUtils.d(TAG, "open: converting to TLS socket");
- mSocket = HttpsURLConnection.getDefaultSSLSocketFactory()
- .createSocket(mSocket, mAddress.getHostName(), mAddress.getPort(), true);
- // After the socket connects to an SSL server, confirm that the hostname is as
- // expected
- if (!canTrustAllCertificates()) {
- verifyHostname(mSocket, mHost);
- }
- mSocket.setSoTimeout(SOCKET_READ_TIMEOUT);
- mIn = new BufferedInputStream(mSocket.getInputStream(), 1024);
- mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512);
-
- } catch (SSLException e) {
- LogUtils.d(TAG, e.toString());
- throw new CertificateValidationException(e.getMessage(), e);
- } catch (IOException ioe) {
- LogUtils.d(TAG, ioe.toString());
- throw new MessagingException(MessagingException.IOERROR, ioe.toString());
- }
- }
-
- /**
- * Lightweight version of SSLCertificateSocketFactory.verifyHostname, which provides this
- * service but is not in the public API.
- *
- * Verify the hostname of the certificate used by the other end of a
- * connected socket. It is harmless to call this method redundantly if the hostname has already
- * been verified.
- *
- * <p>Wildcard certificates are allowed to verify any matching hostname,
- * so "foo.bar.example.com" is verified if the peer has a certificate
- * for "*.example.com".
- *
- * @param socket An SSL socket which has been connected to a server
- * @param hostname The expected hostname of the remote server
- * @throws IOException if something goes wrong handshaking with the server
- * @throws SSLPeerUnverifiedException if the server cannot prove its identity
- */
- private void verifyHostname(Socket socket, String hostname) throws IOException {
- // The code at the start of OpenSSLSocketImpl.startHandshake()
- // ensures that the call is idempotent, so we can safely call it.
- SSLSocket ssl = (SSLSocket) socket;
- ssl.startHandshake();
-
- SSLSession session = ssl.getSession();
- if (session == null) {
- mImapHelper.handleEvent(OmtpEvents.DATA_CANNOT_ESTABLISH_SSL_SESSION);
- throw new SSLException("Cannot verify SSL socket without session");
- }
- // TODO: Instead of reporting the name of the server we think we're connecting to,
- // we should be reporting the bad name in the certificate. Unfortunately this is buried
- // in the verifier code and is not available in the verifier API, and extracting the
- // CN & alts is beyond the scope of this patch.
- if (!HOSTNAME_VERIFIER.verify(hostname, session)) {
- mImapHelper.handleEvent(OmtpEvents.DATA_SSL_INVALID_HOST_NAME);
- throw new SSLPeerUnverifiedException("Certificate hostname not useable for server: "
- + session.getPeerPrincipal());
- }
- }
-
- public boolean isOpen() {
- return (mIn != null && mOut != null &&
- mSocket != null && mSocket.isConnected() && !mSocket.isClosed());
- }
-
- /**
- * Close the connection. MUST NOT return any exceptions - must be "best effort" and safe.
- */
- public void close() {
- try {
- mIn.close();
- } catch (Exception e) {
- // May fail if the connection is already closed.
- }
- try {
- mOut.close();
- } catch (Exception e) {
- // May fail if the connection is already closed.
- }
- try {
- mSocket.close();
- } catch (Exception e) {
- // May fail if the connection is already closed.
- }
- mIn = null;
- mOut = null;
- mSocket = null;
- }
-
- public String getHost() {
- return mHost;
- }
-
- public InputStream getInputStream() {
- return mIn;
- }
-
- public OutputStream getOutputStream() {
- return mOut;
- }
-
- /**
- * Writes a single line to the server using \r\n termination.
- */
- public void writeLine(String s, String sensitiveReplacement) throws IOException {
- if (sensitiveReplacement != null) {
- LogUtils.d(TAG, ">>> " + sensitiveReplacement);
- } else {
- LogUtils.d(TAG, ">>> " + s);
- }
-
- OutputStream out = getOutputStream();
- out.write(s.getBytes());
- out.write('\r');
- out.write('\n');
- out.flush();
- }
-
- /**
- * Reads a single line from the server, using either \r\n or \n as the delimiter. The
- * delimiter char(s) are not included in the result.
- */
- public String readLine(boolean loggable) throws IOException {
- StringBuffer sb = new StringBuffer();
- InputStream in = getInputStream();
- int d;
- while ((d = in.read()) != -1) {
- if (((char)d) == '\r') {
- continue;
- } else if (((char)d) == '\n') {
- break;
- } else {
- sb.append((char)d);
- }
- }
- if (d == -1) {
- LogUtils.d(TAG, "End of stream reached while trying to read line.");
- }
- String ret = sb.toString();
- if (loggable) {
- LogUtils.d(TAG, "<<< " + ret);
- }
- return ret;
- }
-}
diff --git a/java/com/android/voicemailomtp/mail/MeetingInfo.java b/java/com/android/voicemailomtp/mail/MeetingInfo.java
deleted file mode 100644
index 0505bbf2c..000000000
--- a/java/com/android/voicemailomtp/mail/MeetingInfo.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2015 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.voicemailomtp.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";
-}
diff --git a/java/com/android/voicemailomtp/mail/Message.java b/java/com/android/voicemailomtp/mail/Message.java
deleted file mode 100644
index 41555690f..000000000
--- a/java/com/android/voicemailomtp/mail/Message.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2015 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.voicemailomtp.mail;
-
-import android.support.annotation.VisibleForTesting;
-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 static final String RECIPIENT_TYPE_TO = "to";
- public static final String RECIPIENT_TYPE_CC = "cc";
- public static final String RECIPIENT_TYPE_BCC = "bcc";
- public enum RecipientType {
- TO, CC, BCC,
- }
-
- protected String mUid;
-
- private HashSet<String> mFlags = null;
-
- protected Date mInternalDate;
-
- public String getUid() {
- return mUid;
- }
-
- public void setUid(String uid) {
- this.mUid = uid;
- }
-
- 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(String type) throws MessagingException;
-
- public abstract void setRecipients(String type, Address[] addresses)
- throws MessagingException;
-
- public void setRecipient(String 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;
-
- // 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;
-
- @Override
- public boolean isMimeType(String mimeType) throws MessagingException {
- return getContentType().startsWith(mimeType);
- }
-
- private HashSet<String> getFlagSet() {
- if (mFlags == null) {
- mFlags = new HashSet<String>();
- }
- return mFlags;
- }
-
- /*
- * TODO Refactor Flags at some point to be able to store user defined flags.
- */
- public String[] getFlags() {
- return getFlagSet().toArray(new String[] {});
- }
-
- /**
- * Set/clear a flag directly, without involving overrides of {@link #setFlag} in subclasses.
- * Only used for testing.
- */
- @VisibleForTesting
- private final void setFlagDirectlyForTest(String flag, boolean set) throws MessagingException {
- if (set) {
- getFlagSet().add(flag);
- } else {
- getFlagSet().remove(flag);
- }
- }
-
- public void setFlag(String flag, boolean set) throws MessagingException {
- setFlagDirectlyForTest(flag, set);
- }
-
- /**
- * This method calls setFlag(String, boolean)
- * @param flags
- * @param set
- */
- public void setFlags(String[] flags, boolean set) throws MessagingException {
- for (String flag : flags) {
- setFlag(flag, set);
- }
- }
-
- public boolean isSet(String flag) {
- return getFlagSet().contains(flag);
- }
-
- public abstract void saveChanges() throws MessagingException;
-
- @Override
- public String toString() {
- return getClass().getSimpleName() + ':' + mUid;
- }
-}
diff --git a/java/com/android/voicemailomtp/mail/MessageDateComparator.java b/java/com/android/voicemailomtp/mail/MessageDateComparator.java
deleted file mode 100644
index 37071034a..000000000
--- a/java/com/android/voicemailomtp/mail/MessageDateComparator.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2015 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.voicemailomtp.mail;
-
-import java.util.Comparator;
-
-public class MessageDateComparator implements Comparator<Message> {
- @Override
- 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;
- }
- }
-}
diff --git a/java/com/android/voicemailomtp/mail/MessagingException.java b/java/com/android/voicemailomtp/mail/MessagingException.java
deleted file mode 100644
index 28550527f..000000000
--- a/java/com/android/voicemailomtp/mail/MessagingException.java
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright (C) 2015 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.voicemailomtp.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;
- /** The server indicates it does not support OAuth authentication */
- public static final int OAUTH_NOT_SUPPORTED = 18;
- /** The server indicates it experienced an internal error */
- public static final int SERVER_ERROR = 19;
-
- 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;
- }
-} \ No newline at end of file
diff --git a/java/com/android/voicemailomtp/mail/Multipart.java b/java/com/android/voicemailomtp/mail/Multipart.java
deleted file mode 100644
index b45ebab3d..000000000
--- a/java/com/android/voicemailomtp/mail/Multipart.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2015 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.voicemailomtp.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;
- }
-}
diff --git a/java/com/android/voicemailomtp/mail/PackedString.java b/java/com/android/voicemailomtp/mail/PackedString.java
deleted file mode 100644
index 585759611..000000000
--- a/java/com/android/voicemailomtp/mail/PackedString.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2015 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.voicemailomtp.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();
- }
- }
-}
diff --git a/java/com/android/voicemailomtp/mail/Part.java b/java/com/android/voicemailomtp/mail/Part.java
deleted file mode 100644
index 51f8a4c38..000000000
--- a/java/com/android/voicemailomtp/mail/Part.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2015 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.voicemailomtp.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;
-}
diff --git a/java/com/android/voicemailomtp/mail/PeekableInputStream.java b/java/com/android/voicemailomtp/mail/PeekableInputStream.java
deleted file mode 100644
index c1181d189..000000000
--- a/java/com/android/voicemailomtp/mail/PeekableInputStream.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2015 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.voicemailomtp.mail;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * A filtering InputStream that allows single byte "peeks" without consuming the byte. The
- * client of this stream can call peek() to see the next available byte in the stream
- * and a subsequent read will still return the peeked byte.
- */
-public class PeekableInputStream extends InputStream {
- private final InputStream mIn;
- private boolean mPeeked;
- private int mPeekedByte;
-
- public PeekableInputStream(InputStream in) {
- this.mIn = in;
- }
-
- @Override
- public int read() throws IOException {
- if (!mPeeked) {
- return mIn.read();
- } else {
- mPeeked = false;
- return mPeekedByte;
- }
- }
-
- public int peek() throws IOException {
- if (!mPeeked) {
- mPeekedByte = read();
- mPeeked = true;
- }
- return mPeekedByte;
- }
-
- @Override
- public int read(byte[] b, int offset, int length) throws IOException {
- if (!mPeeked) {
- return mIn.read(b, offset, length);
- } else {
- b[0] = (byte)mPeekedByte;
- mPeeked = false;
- int r = mIn.read(b, offset + 1, length - 1);
- if (r == -1) {
- return 1;
- } else {
- return r + 1;
- }
- }
- }
-
- @Override
- public int read(byte[] b) throws IOException {
- return read(b, 0, b.length);
- }
-
- @Override
- public String toString() {
- return String.format("PeekableInputStream(in=%s, peeked=%b, peekedByte=%d)",
- mIn.toString(), mPeeked, mPeekedByte);
- }
-} \ No newline at end of file
diff --git a/java/com/android/voicemailomtp/mail/TempDirectory.java b/java/com/android/voicemailomtp/mail/TempDirectory.java
deleted file mode 100644
index dfae36026..000000000
--- a/java/com/android/voicemailomtp/mail/TempDirectory.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2015 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.voicemailomtp.mail;
-
-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;
- }
-} \ No newline at end of file
diff --git a/java/com/android/voicemailomtp/mail/internet/BinaryTempFileBody.java b/java/com/android/voicemailomtp/mail/internet/BinaryTempFileBody.java
deleted file mode 100644
index 52c43de16..000000000
--- a/java/com/android/voicemailomtp/mail/internet/BinaryTempFileBody.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2015 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.voicemailomtp.mail.internet;
-
-import com.android.voicemailomtp.mail.Body;
-import com.android.voicemailomtp.mail.MessagingException;
-import com.android.voicemailomtp.mail.TempDirectory;
-
-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);
- }
-
- @Override
- public InputStream getInputStream() throws MessagingException {
- try {
- return new BinaryTempFileBodyInputStream(new FileInputStream(mFile));
- }
- catch (IOException ioe) {
- throw new MessagingException("Unable to open body", ioe);
- }
- }
-
- @Override
- 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();
- in.close();
- }
-
- class BinaryTempFileBodyInputStream extends FilterInputStream {
- public BinaryTempFileBodyInputStream(InputStream in) {
- super(in);
- }
-
- @Override
- public void close() throws IOException {
- super.close();
- mFile.delete();
- }
- }
-}
diff --git a/java/com/android/voicemailomtp/mail/internet/MimeBodyPart.java b/java/com/android/voicemailomtp/mail/internet/MimeBodyPart.java
deleted file mode 100644
index 8a9c45cf9..000000000
--- a/java/com/android/voicemailomtp/mail/internet/MimeBodyPart.java
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * Copyright (C) 2015 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.voicemailomtp.mail.internet;
-
-import com.android.voicemailomtp.mail.Body;
-import com.android.voicemailomtp.mail.BodyPart;
-import com.android.voicemailomtp.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);
- }
-
- @Override
- public void addHeader(String name, String value) throws MessagingException {
- mHeader.addHeader(name, value);
- }
-
- @Override
- public void setHeader(String name, String value) throws MessagingException {
- mHeader.setHeader(name, value);
- }
-
- @Override
- public String[] getHeader(String name) throws MessagingException {
- return mHeader.getHeader(name);
- }
-
- @Override
- public void removeHeader(String name) throws MessagingException {
- mHeader.removeHeader(name);
- }
-
- @Override
- public Body getBody() throws MessagingException {
- return mBody;
- }
-
- @Override
- public void setBody(Body body) throws MessagingException {
- this.mBody = body;
- if (body instanceof com.android.voicemailomtp.mail.Multipart) {
- com.android.voicemailomtp.mail.Multipart multipart =
- ((com.android.voicemailomtp.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");
- }
- }
-
- @Override
- public String getContentType() throws MessagingException {
- String contentType = getFirstHeader(MimeHeader.HEADER_CONTENT_TYPE);
- if (contentType == null) {
- return "text/plain";
- } else {
- return contentType;
- }
- }
-
- @Override
- public String getDisposition() throws MessagingException {
- String contentDisposition = getFirstHeader(MimeHeader.HEADER_CONTENT_DISPOSITION);
- if (contentDisposition == null) {
- return null;
- } else {
- return contentDisposition;
- }
- }
-
- @Override
- 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");
- }
- }
-
- @Override
- public String getMimeType() throws MessagingException {
- return MimeUtility.getHeaderParameter(getContentType(), null);
- }
-
- @Override
- public boolean isMimeType(String mimeType) throws MessagingException {
- return getMimeType().equals(mimeType);
- }
-
- public void setSize(int size) {
- this.mSize = size;
- }
-
- @Override
- 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
- */
- @Override
- 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
- */
- @Override
- public String getExtendedHeader(String name) throws MessagingException {
- if (mExtendedHeader == null) {
- return null;
- }
- return mExtendedHeader.getFirstHeader(name);
- }
-
- /**
- * Write the MimeMessage out in MIME format.
- */
- @Override
- 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);
- }
- }
-}
diff --git a/java/com/android/voicemailomtp/mail/internet/MimeHeader.java b/java/com/android/voicemailomtp/mail/internet/MimeHeader.java
deleted file mode 100644
index 4b0aea749..000000000
--- a/java/com/android/voicemailomtp/mail/internet/MimeHeader.java
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright (C) 2015 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.voicemailomtp.mail.internet;
-
-import com.android.voicemailomtp.mail.MessagingException;
-
-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.appendMessage and is put back
- * into the MIME data by LocalStore.fetch.
- */
- public static final String HEADER_ANDROID_ATTACHMENT_STORE_DATA = "X-Android-Attachment-StoreData";
-
- 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 (!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 (!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();
- }
-
- public final static boolean arrayContains(Object[] a, Object o) {
- int index = arrayIndex(a, o);
- return (index >= 0);
- }
-
- public final static int arrayIndex(Object[] a, Object o) {
- for (int i = 0, count = a.length; i < count; i++) {
- if (a[i].equals(o)) {
- return i;
- }
- }
- return -1;
- }
-}
diff --git a/java/com/android/voicemailomtp/mail/internet/MimeMessage.java b/java/com/android/voicemailomtp/mail/internet/MimeMessage.java
deleted file mode 100644
index a11cd6d83..000000000
--- a/java/com/android/voicemailomtp/mail/internet/MimeMessage.java
+++ /dev/null
@@ -1,675 +0,0 @@
-/*
- * Copyright (C) 2015 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.voicemailomtp.mail.internet;
-
-import com.android.voicemailomtp.mail.Address;
-import com.android.voicemailomtp.mail.Body;
-import com.android.voicemailomtp.mail.BodyPart;
-import com.android.voicemailomtp.mail.Message;
-import com.android.voicemailomtp.mail.MessagingException;
-import com.android.voicemailomtp.mail.Multipart;
-import com.android.voicemailomtp.mail.Part;
-import com.android.voicemailomtp.mail.utils.LogUtils;
-
-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 supersede
- // 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 static String generateMessageId() {
- final StringBuilder sb = new StringBuilder();
- sb.append("<");
- for (int i = 0; i < 24; i++) {
- // We'll use a 5-bit range (0..31)
- final int value = sRandom.nextInt() & 31;
- final 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 InputStream providing message content
- * @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;
-
- final MimeStreamParser parser = new MimeStreamParser();
- parser.setContentHandler(new MimeMessageBuilder());
- return parser;
- }
-
- protected void parse(InputStream in) throws IOException, MessagingException {
- final MimeStreamParser parser = init();
- parser.parse(new EOLConvertingInputStream(in));
- mComplete = !parser.getPrematureEof();
- }
-
- public void parse(InputStream in, EOLConvertingInputStream.Callback callback)
- throws IOException, MessagingException {
- final 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();
- // TODO: We should make it more clear what exceptions can be thrown here,
- // and whether they reflect a normal or error condition.
- } catch (Exception e) {
- LogUtils.v(LogUtils.TAG, "Message missing Date header");
- }
- }
- if (mSentDate == null) {
- // If we still don't have a date, fall back to "Delivery-date"
- try {
- DateTimeField field = (DateTimeField)Field.parse("Date: "
- + MimeUtility.unfoldAndDecode(getFirstHeader("Delivery-date")));
- mSentDate = field.getDate();
- // TODO: We should make it more clear what exceptions can be thrown here,
- // and whether they reflect a normal or error condition.
- } catch (Exception e) {
- LogUtils.v(LogUtils.TAG, "Message also missing Delivery-Date header");
- }
- }
- 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 {
- final String contentType = getFirstHeader(MimeHeader.HEADER_CONTENT_TYPE);
- if (contentType == null) {
- return "text/plain";
- } else {
- return contentType;
- }
- }
-
- @Override
- public String getDisposition() throws MessagingException {
- return getFirstHeader(MimeHeader.HEADER_CONTENT_DISPOSITION);
- }
-
- @Override
- public String getContentId() throws MessagingException {
- final 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;
- }
-
- @Override
- public String getMimeType() throws MessagingException {
- return MimeUtility.getHeaderParameter(getContentType(), null);
- }
-
- @Override
- 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(String type) throws MessagingException {
- if (type == RECIPIENT_TYPE_TO) {
- if (mTo == null) {
- mTo = Address.parse(MimeUtility.unfold(getFirstHeader("To")));
- }
- return mTo;
- } else if (type == RECIPIENT_TYPE_CC) {
- if (mCc == null) {
- mCc = Address.parse(MimeUtility.unfold(getFirstHeader("CC")));
- }
- return mCc;
- } else if (type == RECIPIENT_TYPE_BCC) {
- if (mBcc == null) {
- mBcc = Address.parse(MimeUtility.unfold(getFirstHeader("BCC")));
- }
- return mBcc;
- } else {
- throw new MessagingException("Unrecognized recipient type.");
- }
- }
-
- @Override
- public void setRecipients(String 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 == RECIPIENT_TYPE_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 == RECIPIENT_TYPE_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 == RECIPIENT_TYPE_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) {
- final 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
- */
- @Override
- 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
- */
- @Override
- 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 (final String header : END_OF_LINE.split(headers)) {
- final 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.
- */
- @Override
- public void writeTo(OutputStream out) throws IOException, MessagingException {
- final 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);
- }
- }
-
- @Override
- public InputStream getInputStream() throws MessagingException {
- return null;
- }
-
- class MimeMessageBuilder implements ContentHandler {
- private final 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() + "'");
- }
- }
-
- @Override
- public void startMessage() {
- if (stack.isEmpty()) {
- stack.push(MimeMessage.this);
- } else {
- expect(Part.class);
- try {
- final MimeMessage m = new MimeMessage();
- ((Part)stack.peek()).setBody(m);
- stack.push(m);
- } catch (MessagingException me) {
- throw new Error(me);
- }
- }
- }
-
- @Override
- public void endMessage() {
- expect(MimeMessage.class);
- stack.pop();
- }
-
- @Override
- public void startHeader() {
- expect(Part.class);
- }
-
- @Override
- public void field(String fieldData) {
- expect(Part.class);
- try {
- final String[] tokens = fieldData.split(":", 2);
- ((Part)stack.peek()).addHeader(tokens[0], tokens[1].trim());
- } catch (MessagingException me) {
- throw new Error(me);
- }
- }
-
- @Override
- public void endHeader() {
- expect(Part.class);
- }
-
- @Override
- public void startMultipart(BodyDescriptor bd) {
- expect(Part.class);
-
- final Part e = (Part)stack.peek();
- try {
- final MimeMultipart multiPart = new MimeMultipart(e.getContentType());
- e.setBody(multiPart);
- stack.push(multiPart);
- } catch (MessagingException me) {
- throw new Error(me);
- }
- }
-
- @Override
- public void body(BodyDescriptor bd, InputStream in) throws IOException {
- expect(Part.class);
- final Body body = MimeUtility.decodeBody(in, bd.getTransferEncoding());
- try {
- ((Part)stack.peek()).setBody(body);
- } catch (MessagingException me) {
- throw new Error(me);
- }
- }
-
- @Override
- public void endMultipart() {
- stack.pop();
- }
-
- @Override
- public void startBodyPart() {
- expect(MimeMultipart.class);
-
- try {
- final MimeBodyPart bodyPart = new MimeBodyPart();
- ((MimeMultipart)stack.peek()).addBodyPart(bodyPart);
- stack.push(bodyPart);
- } catch (MessagingException me) {
- throw new Error(me);
- }
- }
-
- @Override
- public void endBodyPart() {
- expect(BodyPart.class);
- stack.pop();
- }
-
- @Override
- public void epilogue(InputStream is) throws IOException {
- expect(MimeMultipart.class);
- final StringBuilder sb = new StringBuilder();
- int b;
- while ((b = is.read()) != -1) {
- sb.append((char)b);
- }
- // TODO: why is this commented out?
- // ((Multipart) stack.peek()).setEpilogue(sb.toString());
- }
-
- @Override
- public void preamble(InputStream is) throws IOException {
- expect(MimeMultipart.class);
- final StringBuilder sb = new StringBuilder();
- 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);
- }
- }
-
- @Override
- public void raw(InputStream is) throws IOException {
- throw new UnsupportedOperationException("Not supported");
- }
- }
-}
diff --git a/java/com/android/voicemailomtp/mail/internet/MimeMultipart.java b/java/com/android/voicemailomtp/mail/internet/MimeMultipart.java
deleted file mode 100644
index 111924336..000000000
--- a/java/com/android/voicemailomtp/mail/internet/MimeMultipart.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2015 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.voicemailomtp.mail.internet;
-
-import com.android.voicemailomtp.mail.BodyPart;
-import com.android.voicemailomtp.mail.MessagingException;
-import com.android.voicemailomtp.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);
- }
-
- @Override
- 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();
- }
-
- @Override
- public InputStream getInputStream() throws MessagingException {
- return null;
- }
-
- public String getSubTypeForTest() {
- return mSubType;
- }
-}
diff --git a/java/com/android/voicemailomtp/mail/internet/MimeUtility.java b/java/com/android/voicemailomtp/mail/internet/MimeUtility.java
deleted file mode 100644
index 4d310b0f5..000000000
--- a/java/com/android/voicemailomtp/mail/internet/MimeUtility.java
+++ /dev/null
@@ -1,416 +0,0 @@
-/*
- * Copyright (C) 2015 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.voicemailomtp.mail.internet;
-
-import android.text.TextUtils;
-import android.util.Base64;
-import android.util.Base64DataException;
-import android.util.Base64InputStream;
-
-import com.android.voicemailomtp.mail.Body;
-import com.android.voicemailomtp.mail.BodyPart;
-import com.android.voicemailomtp.mail.Message;
-import com.android.voicemailomtp.mail.MessagingException;
-import com.android.voicemailomtp.mail.Multipart;
-import com.android.voicemailomtp.mail.Part;
-import com.android.voicemailomtp.VvmLog;
-
-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 {
- private static final String LOG_TAG = "Email";
-
- 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;
- }
-
- /**
- * 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.
- */
- VvmLog.e(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.
- */
- VvmLog.e(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);
- }
- }
-}
diff --git a/java/com/android/voicemailomtp/mail/internet/TextBody.java b/java/com/android/voicemailomtp/mail/internet/TextBody.java
deleted file mode 100644
index 578193eff..000000000
--- a/java/com/android/voicemailomtp/mail/internet/TextBody.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2015 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.voicemailomtp.mail.internet;
-
-import android.util.Base64;
-
-import com.android.voicemailomtp.mail.Body;
-import com.android.voicemailomtp.mail.MessagingException;
-
-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;
- }
-
- @Override
- 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.
- */
- @Override
- public InputStream getInputStream() throws MessagingException {
- try {
- byte[] b = mBody.getBytes("UTF-8");
- return new ByteArrayInputStream(b);
- }
- catch (UnsupportedEncodingException usee) {
- return null;
- }
- }
-}
diff --git a/java/com/android/voicemailomtp/mail/store/ImapConnection.java b/java/com/android/voicemailomtp/mail/store/ImapConnection.java
deleted file mode 100644
index 61dcf1281..000000000
--- a/java/com/android/voicemailomtp/mail/store/ImapConnection.java
+++ /dev/null
@@ -1,413 +0,0 @@
-/*
- * Copyright (C) 2015 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.voicemailomtp.mail.store;
-
-import android.util.ArraySet;
-import android.util.Base64;
-import com.android.voicemailomtp.mail.AuthenticationFailedException;
-import com.android.voicemailomtp.mail.CertificateValidationException;
-import com.android.voicemailomtp.mail.MailTransport;
-import com.android.voicemailomtp.mail.MessagingException;
-import com.android.voicemailomtp.mail.store.ImapStore.ImapException;
-import com.android.voicemailomtp.mail.store.imap.DigestMd5Utils;
-import com.android.voicemailomtp.mail.store.imap.ImapConstants;
-import com.android.voicemailomtp.mail.store.imap.ImapResponse;
-import com.android.voicemailomtp.mail.store.imap.ImapResponseParser;
-import com.android.voicemailomtp.mail.store.imap.ImapUtility;
-import com.android.voicemailomtp.mail.utils.LogUtils;
-import com.android.voicemailomtp.OmtpEvents;
-import com.android.voicemailomtp.VvmLog;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicInteger;
-import javax.net.ssl.SSLException;
-
-/**
- * A cacheable class that stores the details for a single IMAP connection.
- */
-public class ImapConnection {
- private final String TAG = "ImapConnection";
-
- private String mLoginPhrase;
- private ImapStore mImapStore;
- private MailTransport mTransport;
- private ImapResponseParser mParser;
- private Set<String> mCapabilities = new ArraySet<>();
-
- static final String IMAP_REDACTED_LOG = "[IMAP command redacted]";
-
- /**
- * Next tag to use. All connections associated to the same ImapStore instance share the same
- * counter to make tests simpler.
- * (Some of the tests involve multiple connections but only have a single counter to track the
- * tag.)
- */
- private final AtomicInteger mNextCommandTag = new AtomicInteger(0);
-
- ImapConnection(ImapStore store) {
- setStore(store);
- }
-
- void setStore(ImapStore store) {
- // TODO: maybe we should throw an exception if the connection is not closed here,
- // if it's not currently closed, then we won't reopen it, so if the credentials have
- // changed, the connection will not be reestablished.
- mImapStore = store;
- mLoginPhrase = null;
- }
-
- /**
- * Generates and returns the phrase to be used for authentication. This will be a LOGIN with
- * username and password.
- *
- * @return the login command string to sent to the IMAP server
- */
- String getLoginPhrase() {
- if (mLoginPhrase == null) {
- if (mImapStore.getUsername() != null && mImapStore.getPassword() != null) {
- // build the LOGIN string once (instead of over-and-over again.)
- // apply the quoting here around the built-up password
- mLoginPhrase = ImapConstants.LOGIN + " " + mImapStore.getUsername() + " "
- + ImapUtility.imapQuoted(mImapStore.getPassword());
- }
- }
- return mLoginPhrase;
- }
-
- public void open() throws IOException, MessagingException {
- if (mTransport != null && mTransport.isOpen()) {
- return;
- }
-
- try {
- // copy configuration into a clean transport, if necessary
- if (mTransport == null) {
- mTransport = mImapStore.cloneTransport();
- }
-
- mTransport.open();
-
- createParser();
-
- // The server should greet us with something like
- // * OK IMAP4rev1 Server
- // consume the response before doing anything else.
- ImapResponse response = mParser.readResponse(false);
- if (!response.isOk()) {
- mImapStore.getImapHelper()
- .handleEvent(OmtpEvents.DATA_INVALID_INITIAL_SERVER_RESPONSE);
- throw new MessagingException(
- MessagingException.AUTHENTICATION_FAILED_OR_SERVER_ERROR,
- "Invalid server initial response");
- }
-
- queryCapability();
-
- maybeDoStartTls();
-
- // LOGIN
- doLogin();
- } catch (SSLException e) {
- LogUtils.d(TAG, "SSLException ", e);
- mImapStore.getImapHelper().handleEvent(OmtpEvents.DATA_SSL_EXCEPTION);
- throw new CertificateValidationException(e.getMessage(), e);
- } catch (IOException ioe) {
- LogUtils.d(TAG, "IOException", ioe);
- mImapStore.getImapHelper().handleEvent(OmtpEvents.DATA_IOE_ON_OPEN);
- throw ioe;
- } finally {
- destroyResponses();
- }
- }
-
- void logout() {
- try {
- sendCommand(ImapConstants.LOGOUT, false);
- if (!mParser.readResponse(true).is(0, ImapConstants.BYE)) {
- VvmLog.e(TAG, "Server did not respond LOGOUT with BYE");
- }
- if (!mParser.readResponse(false).isOk()) {
- VvmLog.e(TAG, "Server did not respond OK after LOGOUT");
- }
- } catch (IOException | MessagingException e) {
- VvmLog.e(TAG, "Error while logging out:" + e);
- }
- }
-
- /**
- * Closes the connection and releases all resources. This connection can not be used again
- * until {@link #setStore(ImapStore)} is called.
- */
- void close() {
- if (mTransport != null) {
- logout();
- mTransport.close();
- mTransport = null;
- }
- destroyResponses();
- mParser = null;
- mImapStore = null;
- }
-
- /**
- * Attempts to convert the connection into secure connection.
- */
- private void maybeDoStartTls() throws IOException, MessagingException {
- // STARTTLS is required in the OMTP standard but not every implementation support it.
- // Make sure the server does have this capability
- if (hasCapability(ImapConstants.CAPABILITY_STARTTLS)) {
- executeSimpleCommand(ImapConstants.STARTTLS);
- mTransport.reopenTls();
- createParser();
- // The cached capabilities should be refreshed after TLS is established.
- queryCapability();
- }
- }
-
- /**
- * Logs into the IMAP server
- */
- private void doLogin() throws IOException, MessagingException, AuthenticationFailedException {
- try {
- if (mCapabilities.contains(ImapConstants.CAPABILITY_AUTH_DIGEST_MD5)) {
- doDigestMd5Auth();
- } else {
- executeSimpleCommand(getLoginPhrase(), true);
- }
- } catch (ImapException ie) {
- LogUtils.d(TAG, "ImapException", ie);
- String status = ie.getStatus();
- String statusMessage = ie.getStatusMessage();
- String alertText = ie.getAlertText();
-
- if (ImapConstants.NO.equals(status)) {
- switch (statusMessage) {
- case ImapConstants.NO_UNKNOWN_USER:
- mImapStore.getImapHelper().handleEvent(OmtpEvents.DATA_AUTH_UNKNOWN_USER);
- break;
- case ImapConstants.NO_UNKNOWN_CLIENT:
- mImapStore.getImapHelper().handleEvent(OmtpEvents.DATA_AUTH_UNKNOWN_DEVICE);
- break;
- case ImapConstants.NO_INVALID_PASSWORD:
- mImapStore.getImapHelper()
- .handleEvent(OmtpEvents.DATA_AUTH_INVALID_PASSWORD);
- break;
- case ImapConstants.NO_MAILBOX_NOT_INITIALIZED:
- mImapStore.getImapHelper()
- .handleEvent(OmtpEvents.DATA_AUTH_MAILBOX_NOT_INITIALIZED);
- break;
- case ImapConstants.NO_SERVICE_IS_NOT_PROVISIONED:
- mImapStore.getImapHelper()
- .handleEvent(OmtpEvents.DATA_AUTH_SERVICE_NOT_PROVISIONED);
- break;
- case ImapConstants.NO_SERVICE_IS_NOT_ACTIVATED:
- mImapStore.getImapHelper()
- .handleEvent(OmtpEvents.DATA_AUTH_SERVICE_NOT_ACTIVATED);
- break;
- case ImapConstants.NO_USER_IS_BLOCKED:
- mImapStore.getImapHelper()
- .handleEvent(OmtpEvents.DATA_AUTH_USER_IS_BLOCKED);
- break;
- case ImapConstants.NO_APPLICATION_ERROR:
- mImapStore.getImapHelper()
- .handleEvent(OmtpEvents.DATA_REJECTED_SERVER_RESPONSE);
- default:
- mImapStore.getImapHelper().handleEvent(OmtpEvents.DATA_BAD_IMAP_CREDENTIAL);
- }
- throw new AuthenticationFailedException(alertText, ie);
- }
-
- mImapStore.getImapHelper().handleEvent(OmtpEvents.DATA_REJECTED_SERVER_RESPONSE);
- throw new MessagingException(alertText, ie);
- }
- }
-
- private void doDigestMd5Auth() throws IOException, MessagingException {
-
- // Initiate the authentication.
- // The server will issue us a challenge, asking to run MD5 on the nonce with our password
- // and other data, including the cnonce we randomly generated.
- //
- // C: a AUTHENTICATE DIGEST-MD5
- // S: (BASE64) realm="elwood.innosoft.com",nonce="OA6MG9tEQGm2hh",qop="auth",
- // algorithm=md5-sess,charset=utf-8
- List<ImapResponse> responses = executeSimpleCommand(
- ImapConstants.AUTHENTICATE + " " + ImapConstants.AUTH_DIGEST_MD5);
- String decodedChallenge = decodeBase64(responses.get(0).getStringOrEmpty(0).getString());
-
- Map<String, String> challenge = DigestMd5Utils.parseDigestMessage(decodedChallenge);
- DigestMd5Utils.Data data = new DigestMd5Utils.Data(mImapStore, mTransport, challenge);
-
- String response = data.createResponse();
- // Respond to the challenge. If the server accepts it, it will reply a response-auth which
- // is the MD5 of our password and the cnonce we've provided, to prove the server does know
- // the password.
- //
- // C: (BASE64) charset=utf-8,username="chris",realm="elwood.innosoft.com",
- // nonce="OA6MG9tEQGm2hh",nc=00000001,cnonce="OA6MHXh6VqTrRk",
- // digest-uri="imap/elwood.innosoft.com",
- // response=d388dad90d4bbd760a152321f2143af7,qop=auth
- // S: (BASE64) rspauth=ea40f60335c427b5527b84dbabcdfffd
-
- responses = executeContinuationResponse(encodeBase64(response), true);
-
- // Verify response-auth.
- // If failed verifyResponseAuth() will throw a MessagingException, terminating the
- // connection
- String decodedResponseAuth = decodeBase64(responses.get(0).getStringOrEmpty(0).getString());
- data.verifyResponseAuth(decodedResponseAuth);
-
- // Send a empty response to indicate we've accepted the response-auth
- //
- // C: (empty)
- // S: a OK User logged in
- executeContinuationResponse("", false);
-
- }
-
- private static String decodeBase64(String string) {
- return new String(Base64.decode(string, Base64.DEFAULT));
- }
-
- private static String encodeBase64(String string) {
- return Base64.encodeToString(string.getBytes(), Base64.NO_WRAP);
- }
-
- private void queryCapability() throws IOException, MessagingException {
- List<ImapResponse> responses = executeSimpleCommand(ImapConstants.CAPABILITY);
- mCapabilities.clear();
- Set<String> disabledCapabilities = mImapStore.getImapHelper().getConfig()
- .getDisabledCapabilities();
- for (ImapResponse response : responses) {
- if (response.isTagged()) {
- continue;
- }
- for (int i = 0; i < response.size(); i++) {
- String capability = response.getStringOrEmpty(i).getString();
- if (disabledCapabilities != null) {
- if (!disabledCapabilities.contains(capability)) {
- mCapabilities.add(capability);
- }
- } else {
- mCapabilities.add(capability);
- }
- }
- }
-
- LogUtils.d(TAG, "Capabilities: " + mCapabilities.toString());
- }
-
- private boolean hasCapability(String capability) {
- return mCapabilities.contains(capability);
- }
- /**
- * Create an {@link ImapResponseParser} from {@code mTransport.getInputStream()} and
- * set it to {@link #mParser}.
- *
- * If we already have an {@link ImapResponseParser}, we
- * {@link #destroyResponses()} and throw it away.
- */
- private void createParser() {
- destroyResponses();
- mParser = new ImapResponseParser(mTransport.getInputStream());
- }
-
-
- public void destroyResponses() {
- if (mParser != null) {
- mParser.destroyResponses();
- }
- }
-
- public ImapResponse readResponse() throws IOException, MessagingException {
- return mParser.readResponse(false);
- }
-
- public List<ImapResponse> executeSimpleCommand(String command)
- throws IOException, MessagingException{
- return executeSimpleCommand(command, false);
- }
-
- /**
- * Send a single command to the server. The command will be preceded by an IMAP command
- * tag and followed by \r\n (caller need not supply them).
- * Execute a simple command at the server, a simple command being one that is sent in a single
- * line of text
- *
- * @param command the command to send to the server
- * @param sensitive whether the command should be redacted in logs (used for login)
- * @return a list of ImapResponses
- * @throws IOException
- * @throws MessagingException
- */
- public List<ImapResponse> executeSimpleCommand(String command, boolean sensitive)
- throws IOException, MessagingException {
- // TODO: It may be nice to catch IOExceptions and close the connection here.
- // Currently, we expect callers to do that, but if they fail to we'll be in a broken state.
- sendCommand(command, sensitive);
- return getCommandResponses();
- }
-
- public String sendCommand(String command, boolean sensitive)
- throws IOException, MessagingException {
- open();
-
- if (mTransport == null) {
- throw new IOException("Null transport");
- }
- String tag = Integer.toString(mNextCommandTag.incrementAndGet());
- String commandToSend = tag + " " + command;
- mTransport.writeLine(commandToSend, (sensitive ? IMAP_REDACTED_LOG : command));
- return tag;
- }
-
- List<ImapResponse> executeContinuationResponse(String response, boolean sensitive)
- throws IOException, MessagingException {
- mTransport.writeLine(response, (sensitive ? IMAP_REDACTED_LOG : response));
- return getCommandResponses();
- }
-
- /**
- * Read and return all of the responses from the most recent command sent to the server
- *
- * @return a list of ImapResponses
- * @throws IOException
- * @throws MessagingException
- */
- List<ImapResponse> getCommandResponses()
- throws IOException, MessagingException {
- final List<ImapResponse> responses = new ArrayList<ImapResponse>();
- ImapResponse response;
- do {
- response = mParser.readResponse(false);
- responses.add(response);
- } while (!(response.isTagged() || response.isContinuationRequest()));
-
- if (!(response.isOk() || response.isContinuationRequest())) {
- final String toString = response.toString();
- final String status = response.getStatusOrEmpty().getString();
- final String statusMessage = response.getStatusResponseTextOrEmpty().getString();
- final String alert = response.getAlertTextOrEmpty().getString();
- final String responseCode = response.getResponseCodeOrEmpty().getString();
- destroyResponses();
- throw new ImapException(toString, status, statusMessage, alert, responseCode);
- }
- return responses;
- }
-}
diff --git a/java/com/android/voicemailomtp/mail/store/ImapFolder.java b/java/com/android/voicemailomtp/mail/store/ImapFolder.java
deleted file mode 100644
index eca349876..000000000
--- a/java/com/android/voicemailomtp/mail/store/ImapFolder.java
+++ /dev/null
@@ -1,784 +0,0 @@
-/*
- * Copyright (C) 2015 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.voicemailomtp.mail.store;
-
-import android.content.Context;
-import android.support.annotation.Nullable;
-import android.support.annotation.VisibleForTesting;
-import android.text.TextUtils;
-import android.util.Base64DataException;
-import com.android.voicemailomtp.OmtpEvents;
-import com.android.voicemailomtp.mail.AuthenticationFailedException;
-import com.android.voicemailomtp.mail.Body;
-import com.android.voicemailomtp.mail.FetchProfile;
-import com.android.voicemailomtp.mail.Flag;
-import com.android.voicemailomtp.mail.Message;
-import com.android.voicemailomtp.mail.MessagingException;
-import com.android.voicemailomtp.mail.Part;
-import com.android.voicemailomtp.mail.internet.BinaryTempFileBody;
-import com.android.voicemailomtp.mail.internet.MimeBodyPart;
-import com.android.voicemailomtp.mail.internet.MimeHeader;
-import com.android.voicemailomtp.mail.internet.MimeMultipart;
-import com.android.voicemailomtp.mail.internet.MimeUtility;
-import com.android.voicemailomtp.mail.store.ImapStore.ImapException;
-import com.android.voicemailomtp.mail.store.ImapStore.ImapMessage;
-import com.android.voicemailomtp.mail.store.imap.ImapConstants;
-import com.android.voicemailomtp.mail.store.imap.ImapElement;
-import com.android.voicemailomtp.mail.store.imap.ImapList;
-import com.android.voicemailomtp.mail.store.imap.ImapResponse;
-import com.android.voicemailomtp.mail.store.imap.ImapString;
-import com.android.voicemailomtp.mail.utils.LogUtils;
-import com.android.voicemailomtp.mail.utils.Utility;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Locale;
-
-public class ImapFolder {
- private static final String TAG = "ImapFolder";
- private final static String[] PERMANENT_FLAGS =
- { Flag.DELETED, Flag.SEEN, Flag.FLAGGED, Flag.ANSWERED };
- private static final int COPY_BUFFER_SIZE = 16*1024;
-
- private final ImapStore mStore;
- private final String mName;
- private int mMessageCount = -1;
- private ImapConnection mConnection;
- private String mMode;
- private boolean mExists;
- /** A set of hashes that can be used to track dirtiness */
- Object mHash[];
-
- public static final String MODE_READ_ONLY = "mode_read_only";
- public static final String MODE_READ_WRITE = "mode_read_write";
-
- public ImapFolder(ImapStore store, String name) {
- mStore = store;
- mName = name;
- }
-
- /**
- * Callback for each message retrieval.
- */
- public interface MessageRetrievalListener {
- public void messageRetrieved(Message message);
- }
-
- private void destroyResponses() {
- if (mConnection != null) {
- mConnection.destroyResponses();
- }
- }
-
- public void open(String mode) throws MessagingException {
- try {
- if (isOpen()) {
- throw new AssertionError("Duplicated open on ImapFolder");
- }
- synchronized (this) {
- mConnection = mStore.getConnection();
- }
- // * FLAGS (\Answered \Flagged \Deleted \Seen \Draft NonJunk
- // $MDNSent)
- // * OK [PERMANENTFLAGS (\Answered \Flagged \Deleted \Seen \Draft
- // NonJunk $MDNSent \*)] Flags permitted.
- // * 23 EXISTS
- // * 0 RECENT
- // * OK [UIDVALIDITY 1125022061] UIDs valid
- // * OK [UIDNEXT 57576] Predicted next UID
- // 2 OK [READ-WRITE] Select completed.
- try {
- doSelect();
- } catch (IOException ioe) {
- throw ioExceptionHandler(mConnection, ioe);
- } finally {
- destroyResponses();
- }
- } catch (AuthenticationFailedException e) {
- // Don't cache this connection, so we're forced to try connecting/login again
- mConnection = null;
- close(false);
- throw e;
- } catch (MessagingException e) {
- mExists = false;
- close(false);
- throw e;
- }
- }
-
- public boolean isOpen() {
- return mExists && mConnection != null;
- }
-
- public String getMode() {
- return mMode;
- }
-
- public void close(boolean expunge) {
- if (expunge) {
- try {
- expunge();
- } catch (MessagingException e) {
- LogUtils.e(TAG, e, "Messaging Exception");
- }
- }
- mMessageCount = -1;
- synchronized (this) {
- mConnection = null;
- }
- }
-
- public int getMessageCount() {
- return mMessageCount;
- }
-
- String[] getSearchUids(List<ImapResponse> responses) {
- // S: * SEARCH 2 3 6
- final ArrayList<String> uids = new ArrayList<String>();
- for (ImapResponse response : responses) {
- if (!response.isDataResponse(0, ImapConstants.SEARCH)) {
- continue;
- }
- // Found SEARCH response data
- for (int i = 1; i < response.size(); i++) {
- ImapString s = response.getStringOrEmpty(i);
- if (s.isString()) {
- uids.add(s.getString());
- }
- }
- }
- return uids.toArray(Utility.EMPTY_STRINGS);
- }
-
- @VisibleForTesting
- String[] searchForUids(String searchCriteria) throws MessagingException {
- checkOpen();
- try {
- try {
- final String command = ImapConstants.UID_SEARCH + " " + searchCriteria;
- final String[] result = getSearchUids(mConnection.executeSimpleCommand(command));
- LogUtils.d(TAG, "searchForUids '" + searchCriteria + "' results: " +
- result.length);
- return result;
- } catch (ImapException me) {
- LogUtils.d(TAG, "ImapException in search: " + searchCriteria, me);
- return Utility.EMPTY_STRINGS; // Not found
- } catch (IOException ioe) {
- LogUtils.d(TAG, "IOException in search: " + searchCriteria, ioe);
- mStore.getImapHelper().handleEvent(OmtpEvents.DATA_GENERIC_IMAP_IOE);
- throw ioExceptionHandler(mConnection, ioe);
- }
- } finally {
- destroyResponses();
- }
- }
-
- @Nullable
- public Message getMessage(String uid) throws MessagingException {
- checkOpen();
-
- final String[] uids = searchForUids(ImapConstants.UID + " " + uid);
- for (int i = 0; i < uids.length; i++) {
- if (uids[i].equals(uid)) {
- return new ImapMessage(uid, this);
- }
- }
- LogUtils.e(TAG, "UID " + uid + " not found on server");
- return null;
- }
-
- @VisibleForTesting
- protected static boolean isAsciiString(String str) {
- int len = str.length();
- for (int i = 0; i < len; i++) {
- char c = str.charAt(i);
- if (c >= 128) return false;
- }
- return true;
- }
-
- public Message[] getMessages(String[] uids) throws MessagingException {
- if (uids == null) {
- uids = searchForUids("1:* NOT DELETED");
- }
- return getMessagesInternal(uids);
- }
-
- public Message[] getMessagesInternal(String[] uids) {
- final ArrayList<Message> messages = new ArrayList<Message>(uids.length);
- for (int i = 0; i < uids.length; i++) {
- final String uid = uids[i];
- final ImapMessage message = new ImapMessage(uid, this);
- messages.add(message);
- }
- return messages.toArray(Message.EMPTY_ARRAY);
- }
-
- public void fetch(Message[] messages, FetchProfile fp,
- MessageRetrievalListener listener) throws MessagingException {
- try {
- fetchInternal(messages, fp, listener);
- } catch (RuntimeException e) { // Probably a parser error.
- LogUtils.w(TAG, "Exception detected: " + e.getMessage());
- throw e;
- }
- }
-
- public void fetchInternal(Message[] messages, FetchProfile fp,
- MessageRetrievalListener listener) throws MessagingException {
- if (messages.length == 0) {
- return;
- }
- checkOpen();
- HashMap<String, Message> messageMap = new HashMap<String, Message>();
- for (Message m : messages) {
- messageMap.put(m.getUid(), m);
- }
-
- /*
- * Figure out what command we are going to run:
- * FLAGS - UID FETCH (FLAGS)
- * ENVELOPE - UID FETCH (INTERNALDATE UID RFC822.SIZE FLAGS BODY.PEEK[
- * HEADER.FIELDS (date subject from content-type to cc)])
- * STRUCTURE - UID FETCH (BODYSTRUCTURE)
- * BODY_SANE - UID FETCH (BODY.PEEK[]<0.N>) where N = max bytes returned
- * BODY - UID FETCH (BODY.PEEK[])
- * Part - UID FETCH (BODY.PEEK[ID]) where ID = mime part ID
- */
-
- final LinkedHashSet<String> fetchFields = new LinkedHashSet<String>();
-
- fetchFields.add(ImapConstants.UID);
- if (fp.contains(FetchProfile.Item.FLAGS)) {
- fetchFields.add(ImapConstants.FLAGS);
- }
- if (fp.contains(FetchProfile.Item.ENVELOPE)) {
- fetchFields.add(ImapConstants.INTERNALDATE);
- fetchFields.add(ImapConstants.RFC822_SIZE);
- fetchFields.add(ImapConstants.FETCH_FIELD_HEADERS);
- }
- if (fp.contains(FetchProfile.Item.STRUCTURE)) {
- fetchFields.add(ImapConstants.BODYSTRUCTURE);
- }
-
- if (fp.contains(FetchProfile.Item.BODY_SANE)) {
- fetchFields.add(ImapConstants.FETCH_FIELD_BODY_PEEK_SANE);
- }
- if (fp.contains(FetchProfile.Item.BODY)) {
- fetchFields.add(ImapConstants.FETCH_FIELD_BODY_PEEK);
- }
-
- // TODO Why are we only fetching the first part given?
- final Part fetchPart = fp.getFirstPart();
- if (fetchPart != null) {
- final String[] partIds =
- fetchPart.getHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA);
- // TODO Why can a single part have more than one Id? And why should we only fetch
- // the first id if there are more than one?
- if (partIds != null) {
- fetchFields.add(ImapConstants.FETCH_FIELD_BODY_PEEK_BARE
- + "[" + partIds[0] + "]");
- }
- }
-
- try {
- mConnection.sendCommand(String.format(Locale.US,
- ImapConstants.UID_FETCH + " %s (%s)", ImapStore.joinMessageUids(messages),
- Utility.combine(fetchFields.toArray(new String[fetchFields.size()]), ' ')
- ), false);
- ImapResponse response;
- do {
- response = null;
- try {
- response = mConnection.readResponse();
-
- if (!response.isDataResponse(1, ImapConstants.FETCH)) {
- continue; // Ignore
- }
- final ImapList fetchList = response.getListOrEmpty(2);
- final String uid = fetchList.getKeyedStringOrEmpty(ImapConstants.UID)
- .getString();
- if (TextUtils.isEmpty(uid)) continue;
-
- ImapMessage message = (ImapMessage) messageMap.get(uid);
- if (message == null) continue;
-
- if (fp.contains(FetchProfile.Item.FLAGS)) {
- final ImapList flags =
- fetchList.getKeyedListOrEmpty(ImapConstants.FLAGS);
- for (int i = 0, count = flags.size(); i < count; i++) {
- final ImapString flag = flags.getStringOrEmpty(i);
- if (flag.is(ImapConstants.FLAG_DELETED)) {
- message.setFlagInternal(Flag.DELETED, true);
- } else if (flag.is(ImapConstants.FLAG_ANSWERED)) {
- message.setFlagInternal(Flag.ANSWERED, true);
- } else if (flag.is(ImapConstants.FLAG_SEEN)) {
- message.setFlagInternal(Flag.SEEN, true);
- } else if (flag.is(ImapConstants.FLAG_FLAGGED)) {
- message.setFlagInternal(Flag.FLAGGED, true);
- }
- }
- }
- if (fp.contains(FetchProfile.Item.ENVELOPE)) {
- final Date internalDate = fetchList.getKeyedStringOrEmpty(
- ImapConstants.INTERNALDATE).getDateOrNull();
- final int size = fetchList.getKeyedStringOrEmpty(
- ImapConstants.RFC822_SIZE).getNumberOrZero();
- final String header = fetchList.getKeyedStringOrEmpty(
- ImapConstants.BODY_BRACKET_HEADER, true).getString();
-
- message.setInternalDate(internalDate);
- message.setSize(size);
- message.parse(Utility.streamFromAsciiString(header));
- }
- if (fp.contains(FetchProfile.Item.STRUCTURE)) {
- ImapList bs = fetchList.getKeyedListOrEmpty(
- ImapConstants.BODYSTRUCTURE);
- if (!bs.isEmpty()) {
- try {
- parseBodyStructure(bs, message, ImapConstants.TEXT);
- } catch (MessagingException e) {
- LogUtils.v(TAG, e, "Error handling message");
- message.setBody(null);
- }
- }
- }
- if (fp.contains(FetchProfile.Item.BODY)
- || fp.contains(FetchProfile.Item.BODY_SANE)) {
- // Body is keyed by "BODY[]...".
- // Previously used "BODY[..." but this can be confused with "BODY[HEADER..."
- // TODO Should we accept "RFC822" as well??
- ImapString body = fetchList.getKeyedStringOrEmpty("BODY[]", true);
- InputStream bodyStream = body.getAsStream();
- message.parse(bodyStream);
- }
- if (fetchPart != null) {
- InputStream bodyStream =
- fetchList.getKeyedStringOrEmpty("BODY[", true).getAsStream();
- String encodings[] = fetchPart.getHeader(
- MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING);
-
- String contentTransferEncoding = null;
- if (encodings != null && encodings.length > 0) {
- contentTransferEncoding = encodings[0];
- } else {
- // According to http://tools.ietf.org/html/rfc2045#section-6.1
- // "7bit" is the default.
- contentTransferEncoding = "7bit";
- }
-
- try {
- // TODO Don't create 2 temp files.
- // decodeBody creates BinaryTempFileBody, but we could avoid this
- // if we implement ImapStringBody.
- // (We'll need to share a temp file. Protect it with a ref-count.)
- message.setBody(decodeBody(mStore.getContext(), bodyStream,
- contentTransferEncoding, fetchPart.getSize(), listener));
- } catch(Exception e) {
- // TODO: Figure out what kinds of exceptions might actually be thrown
- // from here. This blanket catch-all is because we're not sure what to
- // do if we don't have a contentTransferEncoding, and we don't have
- // time to figure out what exceptions might be thrown.
- LogUtils.e(TAG, "Error fetching body %s", e);
- }
- }
-
- if (listener != null) {
- listener.messageRetrieved(message);
- }
- } finally {
- destroyResponses();
- }
- } while (!response.isTagged());
- } catch (IOException ioe) {
- mStore.getImapHelper().handleEvent(OmtpEvents.DATA_GENERIC_IMAP_IOE);
- throw ioExceptionHandler(mConnection, ioe);
- }
- }
-
- /**
- * Removes any content transfer encoding from the stream and returns a Body.
- * This code is taken/condensed from MimeUtility.decodeBody
- */
- private static Body decodeBody(Context context,InputStream in, String contentTransferEncoding,
- int size, MessageRetrievalListener listener) throws IOException {
- // Get a properly wrapped input stream
- in = MimeUtility.getInputStreamForContentTransferEncoding(in, contentTransferEncoding);
- BinaryTempFileBody tempBody = new BinaryTempFileBody();
- OutputStream out = tempBody.getOutputStream();
- try {
- byte[] buffer = new byte[COPY_BUFFER_SIZE];
- int n = 0;
- int count = 0;
- while (-1 != (n = in.read(buffer))) {
- out.write(buffer, 0, n);
- count += n;
- }
- } catch (Base64DataException bde) {
- String warning = "\n\nThere was an error while decoding the message.";
- out.write(warning.getBytes());
- } finally {
- out.close();
- }
- return tempBody;
- }
-
- public String[] getPermanentFlags() {
- return PERMANENT_FLAGS;
- }
-
- /**
- * Handle any untagged responses that the caller doesn't care to handle themselves.
- * @param responses
- */
- private void handleUntaggedResponses(List<ImapResponse> responses) {
- for (ImapResponse response : responses) {
- handleUntaggedResponse(response);
- }
- }
-
- /**
- * Handle an untagged response that the caller doesn't care to handle themselves.
- * @param response
- */
- private void handleUntaggedResponse(ImapResponse response) {
- if (response.isDataResponse(1, ImapConstants.EXISTS)) {
- mMessageCount = response.getStringOrEmpty(0).getNumberOrZero();
- }
- }
-
- private static void parseBodyStructure(ImapList bs, Part part, String id)
- throws MessagingException {
- if (bs.getElementOrNone(0).isList()) {
- /*
- * This is a multipart/*
- */
- MimeMultipart mp = new MimeMultipart();
- for (int i = 0, count = bs.size(); i < count; i++) {
- ImapElement e = bs.getElementOrNone(i);
- if (e.isList()) {
- /*
- * For each part in the message we're going to add a new BodyPart and parse
- * into it.
- */
- MimeBodyPart bp = new MimeBodyPart();
- if (id.equals(ImapConstants.TEXT)) {
- parseBodyStructure(bs.getListOrEmpty(i), bp, Integer.toString(i + 1));
-
- } else {
- parseBodyStructure(bs.getListOrEmpty(i), bp, id + "." + (i + 1));
- }
- mp.addBodyPart(bp);
-
- } else {
- if (e.isString()) {
- mp.setSubType(bs.getStringOrEmpty(i).getString().toLowerCase(Locale.US));
- }
- break; // Ignore the rest of the list.
- }
- }
- part.setBody(mp);
- } else {
- /*
- * This is a body. We need to add as much information as we can find out about
- * it to the Part.
- */
-
- /*
- body type
- body subtype
- body parameter parenthesized list
- body id
- body description
- body encoding
- body size
- */
-
- final ImapString type = bs.getStringOrEmpty(0);
- final ImapString subType = bs.getStringOrEmpty(1);
- final String mimeType =
- (type.getString() + "/" + subType.getString()).toLowerCase(Locale.US);
-
- final ImapList bodyParams = bs.getListOrEmpty(2);
- final ImapString cid = bs.getStringOrEmpty(3);
- final ImapString encoding = bs.getStringOrEmpty(5);
- final int size = bs.getStringOrEmpty(6).getNumberOrZero();
-
- if (MimeUtility.mimeTypeMatches(mimeType, MimeUtility.MIME_TYPE_RFC822)) {
- // A body type of type MESSAGE and subtype RFC822
- // contains, immediately after the basic fields, the
- // envelope structure, body structure, and size in
- // text lines of the encapsulated message.
- // [MESSAGE, RFC822, [NAME, filename.eml], NIL, NIL, 7BIT, 5974, NIL,
- // [INLINE, [FILENAME*0, Fwd: Xxx..., FILENAME*1, filename.eml]], NIL]
- /*
- * This will be caught by fetch and handled appropriately.
- */
- throw new MessagingException("BODYSTRUCTURE " + MimeUtility.MIME_TYPE_RFC822
- + " not yet supported.");
- }
-
- /*
- * Set the content type with as much information as we know right now.
- */
- final StringBuilder contentType = new StringBuilder(mimeType);
-
- /*
- * If there are body params we might be able to get some more information out
- * of them.
- */
- for (int i = 1, count = bodyParams.size(); i < count; i += 2) {
-
- // TODO We need to convert " into %22, but
- // because MimeUtility.getHeaderParameter doesn't recognize it,
- // we can't fix it for now.
- contentType.append(String.format(";\n %s=\"%s\"",
- bodyParams.getStringOrEmpty(i - 1).getString(),
- bodyParams.getStringOrEmpty(i).getString()));
- }
-
- part.setHeader(MimeHeader.HEADER_CONTENT_TYPE, contentType.toString());
-
- // Extension items
- final ImapList bodyDisposition;
-
- if (type.is(ImapConstants.TEXT) && bs.getElementOrNone(9).isList()) {
- // If media-type is TEXT, 9th element might be: [body-fld-lines] := number
- // So, if it's not a list, use 10th element.
- // (Couldn't find evidence in the RFC if it's ALWAYS 10th element.)
- bodyDisposition = bs.getListOrEmpty(9);
- } else {
- bodyDisposition = bs.getListOrEmpty(8);
- }
-
- final StringBuilder contentDisposition = new StringBuilder();
-
- if (bodyDisposition.size() > 0) {
- final String bodyDisposition0Str =
- bodyDisposition.getStringOrEmpty(0).getString().toLowerCase(Locale.US);
- if (!TextUtils.isEmpty(bodyDisposition0Str)) {
- contentDisposition.append(bodyDisposition0Str);
- }
-
- final ImapList bodyDispositionParams = bodyDisposition.getListOrEmpty(1);
- if (!bodyDispositionParams.isEmpty()) {
- /*
- * If there is body disposition information we can pull some more
- * information about the attachment out.
- */
- for (int i = 1, count = bodyDispositionParams.size(); i < count; i += 2) {
-
- // TODO We need to convert " into %22. See above.
- contentDisposition.append(String.format(Locale.US, ";\n %s=\"%s\"",
- bodyDispositionParams.getStringOrEmpty(i - 1)
- .getString().toLowerCase(Locale.US),
- bodyDispositionParams.getStringOrEmpty(i).getString()));
- }
- }
- }
-
- if ((size > 0)
- && (MimeUtility.getHeaderParameter(contentDisposition.toString(), "size")
- == null)) {
- contentDisposition.append(String.format(Locale.US, ";\n size=%d", size));
- }
-
- if (contentDisposition.length() > 0) {
- /*
- * Set the content disposition containing at least the size. Attachment
- * handling code will use this down the road.
- */
- part.setHeader(MimeHeader.HEADER_CONTENT_DISPOSITION,
- contentDisposition.toString());
- }
-
- /*
- * Set the Content-Transfer-Encoding header. Attachment code will use this
- * to parse the body.
- */
- if (!encoding.isEmpty()) {
- part.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING,
- encoding.getString());
- }
-
- /*
- * Set the Content-ID header.
- */
- if (!cid.isEmpty()) {
- part.setHeader(MimeHeader.HEADER_CONTENT_ID, cid.getString());
- }
-
- if (size > 0) {
- if (part instanceof ImapMessage) {
- ((ImapMessage) part).setSize(size);
- } else if (part instanceof MimeBodyPart) {
- ((MimeBodyPart) part).setSize(size);
- } else {
- throw new MessagingException("Unknown part type " + part.toString());
- }
- }
- part.setHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA, id);
- }
-
- }
-
- public Message[] expunge() throws MessagingException {
- checkOpen();
- try {
- handleUntaggedResponses(mConnection.executeSimpleCommand(ImapConstants.EXPUNGE));
- } catch (IOException ioe) {
- mStore.getImapHelper().handleEvent(OmtpEvents.DATA_GENERIC_IMAP_IOE);
- throw ioExceptionHandler(mConnection, ioe);
- } finally {
- destroyResponses();
- }
- return null;
- }
-
- public void setFlags(Message[] messages, String[] flags, boolean value)
- throws MessagingException {
- checkOpen();
-
- String allFlags = "";
- if (flags.length > 0) {
- StringBuilder flagList = new StringBuilder();
- for (int i = 0, count = flags.length; i < count; i++) {
- String flag = flags[i];
- if (flag == Flag.SEEN) {
- flagList.append(" " + ImapConstants.FLAG_SEEN);
- } else if (flag == Flag.DELETED) {
- flagList.append(" " + ImapConstants.FLAG_DELETED);
- } else if (flag == Flag.FLAGGED) {
- flagList.append(" " + ImapConstants.FLAG_FLAGGED);
- } else if (flag == Flag.ANSWERED) {
- flagList.append(" " + ImapConstants.FLAG_ANSWERED);
- }
- }
- allFlags = flagList.substring(1);
- }
- try {
- mConnection.executeSimpleCommand(String.format(Locale.US,
- ImapConstants.UID_STORE + " %s %s" + ImapConstants.FLAGS_SILENT + " (%s)",
- ImapStore.joinMessageUids(messages),
- value ? "+" : "-",
- allFlags));
-
- } catch (IOException ioe) {
- mStore.getImapHelper().handleEvent(OmtpEvents.DATA_GENERIC_IMAP_IOE);
- throw ioExceptionHandler(mConnection, ioe);
- } finally {
- destroyResponses();
- }
- }
-
- /**
- * Selects the folder for use. Before performing any operations on this folder, it
- * must be selected.
- */
- private void doSelect() throws IOException, MessagingException {
- final List<ImapResponse> responses = mConnection.executeSimpleCommand(
- String.format(Locale.US, ImapConstants.SELECT + " \"%s\"", mName));
-
- // Assume the folder is opened read-write; unless we are notified otherwise
- mMode = MODE_READ_WRITE;
- int messageCount = -1;
- for (ImapResponse response : responses) {
- if (response.isDataResponse(1, ImapConstants.EXISTS)) {
- messageCount = response.getStringOrEmpty(0).getNumberOrZero();
- } else if (response.isOk()) {
- final ImapString responseCode = response.getResponseCodeOrEmpty();
- if (responseCode.is(ImapConstants.READ_ONLY)) {
- mMode = MODE_READ_ONLY;
- } else if (responseCode.is(ImapConstants.READ_WRITE)) {
- mMode = MODE_READ_WRITE;
- }
- } else if (response.isTagged()) { // Not OK
- mStore.getImapHelper().handleEvent(OmtpEvents.DATA_MAILBOX_OPEN_FAILED);
- throw new MessagingException("Can't open mailbox: "
- + response.getStatusResponseTextOrEmpty());
- }
- }
- if (messageCount == -1) {
- throw new MessagingException("Did not find message count during select");
- }
- mMessageCount = messageCount;
- mExists = true;
- }
-
- public class Quota {
-
- public final int occupied;
- public final int total;
-
- public Quota(int occupied, int total) {
- this.occupied = occupied;
- this.total = total;
- }
- }
-
- public Quota getQuota() throws MessagingException {
- try {
- final List<ImapResponse> responses = mConnection.executeSimpleCommand(
- String.format(Locale.US, ImapConstants.GETQUOTAROOT + " \"%s\"", mName));
-
- for (ImapResponse response : responses) {
- if (!response.isDataResponse(0, ImapConstants.QUOTA)) {
- continue;
- }
- ImapList list = response.getListOrEmpty(2);
- for (int i = 0; i < list.size(); i += 3) {
- if (!list.getStringOrEmpty(i).is("voice")) {
- continue;
- }
- return new Quota(
- list.getStringOrEmpty(i + 1).getNumber(-1),
- list.getStringOrEmpty(i + 2).getNumber(-1));
- }
- }
- } catch (IOException ioe) {
- mStore.getImapHelper().handleEvent(OmtpEvents.DATA_GENERIC_IMAP_IOE);
- throw ioExceptionHandler(mConnection, ioe);
- } finally {
- destroyResponses();
- }
- return null;
- }
-
- private void checkOpen() throws MessagingException {
- if (!isOpen()) {
- throw new MessagingException("Folder " + mName + " is not open.");
- }
- }
-
- private MessagingException ioExceptionHandler(ImapConnection connection, IOException ioe) {
- LogUtils.d(TAG, "IO Exception detected: ", ioe);
- connection.close();
- if (connection == mConnection) {
- mConnection = null; // To prevent close() from returning the connection to the pool.
- close(false);
- }
- return new MessagingException(MessagingException.IOERROR, "IO Error", ioe);
- }
-
- public Message createMessage(String uid) {
- return new ImapMessage(uid, this);
- }
-} \ No newline at end of file
diff --git a/java/com/android/voicemailomtp/mail/store/ImapStore.java b/java/com/android/voicemailomtp/mail/store/ImapStore.java
deleted file mode 100644
index f3e0c098e..000000000
--- a/java/com/android/voicemailomtp/mail/store/ImapStore.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright (C) 2015 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.voicemailomtp.mail.store;
-
-import android.content.Context;
-import android.net.Network;
-
-import com.android.voicemailomtp.mail.MailTransport;
-import com.android.voicemailomtp.mail.Message;
-import com.android.voicemailomtp.mail.MessagingException;
-import com.android.voicemailomtp.mail.internet.MimeMessage;
-import com.android.voicemailomtp.imap.ImapHelper;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-public class ImapStore {
- /**
- * A global suggestion to Store implementors on how much of the body
- * should be returned on FetchProfile.Item.BODY_SANE requests. We'll use 125k now.
- */
- public static final int FETCH_BODY_SANE_SUGGESTED_SIZE = (125 * 1024);
- private final Context mContext;
- private final ImapHelper mHelper;
- private final String mUsername;
- private final String mPassword;
- private final MailTransport mTransport;
- private ImapConnection mConnection;
-
- public static final int FLAG_NONE = 0x00; // No flags
- public static final int FLAG_SSL = 0x01; // Use SSL
- public static final int FLAG_TLS = 0x02; // Use TLS
- public static final int FLAG_AUTHENTICATE = 0x04; // Use name/password for authentication
- public static final int FLAG_TRUST_ALL = 0x08; // Trust all certificates
- public static final int FLAG_OAUTH = 0x10; // Use OAuth for authentication
-
- /**
- * Contains all the information necessary to log into an imap server
- */
- public ImapStore(Context context, ImapHelper helper, String username, String password, int port,
- String serverName, int flags, Network network) {
- mContext = context;
- mHelper = helper;
- mUsername = username;
- mPassword = password;
- mTransport = new MailTransport(context, this.getImapHelper(),
- network, serverName, port, flags);
- }
-
- public Context getContext() {
- return mContext;
- }
-
- public ImapHelper getImapHelper() {
- return mHelper;
- }
-
- public String getUsername() {
- return mUsername;
- }
-
- public String getPassword() {
- return mPassword;
- }
-
- /** Returns a clone of the transport associated with this store. */
- MailTransport cloneTransport() {
- return mTransport.clone();
- }
-
- /**
- * Returns UIDs of Messages joined with "," as the separator.
- */
- static String joinMessageUids(Message[] messages) {
- StringBuilder sb = new StringBuilder();
- boolean notFirst = false;
- for (Message m : messages) {
- if (notFirst) {
- sb.append(',');
- }
- sb.append(m.getUid());
- notFirst = true;
- }
- return sb.toString();
- }
-
- static class ImapMessage extends MimeMessage {
- private ImapFolder mFolder;
-
- ImapMessage(String uid, ImapFolder folder) {
- mUid = uid;
- mFolder = folder;
- }
-
- public void setSize(int size) {
- mSize = size;
- }
-
- @Override
- public void parse(InputStream in) throws IOException, MessagingException {
- super.parse(in);
- }
-
- public void setFlagInternal(String flag, boolean set) throws MessagingException {
- super.setFlag(flag, set);
- }
-
- @Override
- public void setFlag(String flag, boolean set) throws MessagingException {
- super.setFlag(flag, set);
- mFolder.setFlags(new Message[] { this }, new String[] { flag }, set);
- }
- }
-
- static class ImapException extends MessagingException {
- private static final long serialVersionUID = 1L;
-
- private final String mStatus;
- private final String mStatusMessage;
- private final String mAlertText;
- private final String mResponseCode;
-
- public ImapException(String message, String status, String statusMessage, String alertText,
- String responseCode) {
- super(message);
- mStatus = status;
- mStatusMessage = statusMessage;
- mAlertText = alertText;
- mResponseCode = responseCode;
- }
-
- public String getStatus() {
- return mStatus;
- }
-
- public String getStatusMessage() {
- return mStatusMessage;
- }
-
- public String getAlertText() {
- return mAlertText;
- }
-
- public String getResponseCode() {
- return mResponseCode;
- }
- }
-
- public void closeConnection() {
- if (mConnection != null) {
- mConnection.close();
- mConnection = null;
- }
- }
-
- public ImapConnection getConnection() {
- if (mConnection == null) {
- mConnection = new ImapConnection(this);
- }
- return mConnection;
- }
-} \ No newline at end of file
diff --git a/java/com/android/voicemailomtp/mail/store/imap/DigestMd5Utils.java b/java/com/android/voicemailomtp/mail/store/imap/DigestMd5Utils.java
deleted file mode 100644
index b78f55293..000000000
--- a/java/com/android/voicemailomtp/mail/store/imap/DigestMd5Utils.java
+++ /dev/null
@@ -1,335 +0,0 @@
-/*
- * Copyright (C) 2016 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.voicemailomtp.mail.store.imap;
-
-import android.annotation.TargetApi;
-import android.os.Build.VERSION_CODES;
-import android.support.annotation.Nullable;
-import android.support.annotation.VisibleForTesting;
-import android.util.ArrayMap;
-import android.util.Base64;
-import com.android.voicemailomtp.VvmLog;
-import com.android.voicemailomtp.mail.MailTransport;
-import com.android.voicemailomtp.mail.MessagingException;
-import com.android.voicemailomtp.mail.store.ImapStore;
-import java.nio.charset.StandardCharsets;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-import java.util.Map;
-
-@SuppressWarnings("AndroidApiChecker") // Map.getOrDefault() is java8
-@TargetApi(VERSION_CODES.CUR_DEVELOPMENT)
-public class DigestMd5Utils {
-
- private static final String TAG = "DigestMd5Utils";
-
- private static final String DIGEST_CHARSET = "CHARSET";
- private static final String DIGEST_USERNAME = "username";
- private static final String DIGEST_REALM = "realm";
- private static final String DIGEST_NONCE = "nonce";
- private static final String DIGEST_NC = "nc";
- private static final String DIGEST_CNONCE = "cnonce";
- private static final String DIGEST_URI = "digest-uri";
- private static final String DIGEST_RESPONSE = "response";
- private static final String DIGEST_QOP = "qop";
-
- private static final String RESPONSE_AUTH_HEADER = "rspauth=";
- private static final String HEX_CHARS = "0123456789abcdef";
-
- /**
- * Represents the set of data we need to generate the DIGEST-MD5 response.
- */
- public static class Data {
-
- private static final String CHARSET = "utf-8";
-
- public String username;
- public String password;
- public String realm;
- public String nonce;
- public String nc;
- public String cnonce;
- public String digestUri;
- public String qop;
-
- @VisibleForTesting
- Data() {
- // Do nothing
- }
-
- public Data(ImapStore imapStore, MailTransport transport, Map<String, String> challenge) {
- username = imapStore.getUsername();
- password = imapStore.getPassword();
- realm = challenge.getOrDefault(DIGEST_REALM, "");
- nonce = challenge.get(DIGEST_NONCE);
- cnonce = createCnonce();
- nc = "00000001"; // Subsequent Authentication not supported, nounce count always 1.
- qop = "auth"; // Other config not supported
- digestUri = "imap/" + transport.getHost();
- }
-
- private static String createCnonce() {
- SecureRandom generator = new SecureRandom();
-
- // At least 64 bits of entropy is required
- byte[] rawBytes = new byte[8];
- generator.nextBytes(rawBytes);
-
- return Base64.encodeToString(rawBytes, Base64.NO_WRAP);
- }
-
- /**
- * Verify the response-auth returned by the server is correct.
- */
- public void verifyResponseAuth(String response)
- throws MessagingException {
- if (!response.startsWith(RESPONSE_AUTH_HEADER)) {
- throw new MessagingException("response-auth expected");
- }
- if (!response.substring(RESPONSE_AUTH_HEADER.length())
- .equals(DigestMd5Utils.getResponse(this, true))) {
- throw new MessagingException("invalid response-auth return from the server.");
- }
- }
-
- public String createResponse() {
- String response = getResponse(this, false);
- ResponseBuilder builder = new ResponseBuilder();
- builder
- .append(DIGEST_CHARSET, CHARSET)
- .appendQuoted(DIGEST_USERNAME, username)
- .appendQuoted(DIGEST_REALM, realm)
- .appendQuoted(DIGEST_NONCE, nonce)
- .append(DIGEST_NC, nc)
- .appendQuoted(DIGEST_CNONCE, cnonce)
- .appendQuoted(DIGEST_URI, digestUri)
- .append(DIGEST_RESPONSE, response)
- .append(DIGEST_QOP, qop);
- return builder.toString();
- }
-
- private static class ResponseBuilder {
-
- private StringBuilder mBuilder = new StringBuilder();
-
- public ResponseBuilder appendQuoted(String key, String value) {
- if (mBuilder.length() != 0) {
- mBuilder.append(",");
- }
- mBuilder.append(key).append("=\"").append(value).append("\"");
- return this;
- }
-
- public ResponseBuilder append(String key, String value) {
- if (mBuilder.length() != 0) {
- mBuilder.append(",");
- }
- mBuilder.append(key).append("=").append(value);
- return this;
- }
-
- @Override
- public String toString() {
- return mBuilder.toString();
- }
- }
- }
-
- /*
- response-value =
- toHex( getKeyDigest ( toHex(getMd5(a1)),
- { nonce-value, ":" nc-value, ":",
- cnonce-value, ":", qop-value, ":", toHex(getMd5(a2)) }))
- * @param isResponseAuth is the response the one the server is returning us. response-auth has
- * different a2 format.
- */
- @VisibleForTesting
- static String getResponse(Data data, boolean isResponseAuth) {
- StringBuilder a1 = new StringBuilder();
- a1.append(new String(
- getMd5(data.username + ":" + data.realm + ":" + data.password),
- StandardCharsets.ISO_8859_1));
- a1.append(":").append(data.nonce).append(":").append(data.cnonce);
-
- StringBuilder a2 = new StringBuilder();
- if (!isResponseAuth) {
- a2.append("AUTHENTICATE");
- }
- a2.append(":").append(data.digestUri);
-
- return toHex(getKeyDigest(
- toHex(getMd5(a1.toString())),
- data.nonce + ":" + data.nc + ":" + data.cnonce + ":" + data.qop + ":" + toHex(
- getMd5(a2.toString()))
- ));
- }
-
- /**
- * Let getMd5(s) be the 16 octet MD5 hash [RFC 1321] of the octet string s.
- */
- private static byte[] getMd5(String s) {
- try {
- MessageDigest digester = MessageDigest.getInstance("MD5");
- digester.update(s.getBytes(StandardCharsets.ISO_8859_1));
- return digester.digest();
- } catch (NoSuchAlgorithmException e) {
- throw new AssertionError(e);
- }
- }
-
- /**
- * Let getKeyDigest(k, s) be getMd5({k, ":", s}), i.e., the 16 octet hash of the string k, a colon and the
- * string s.
- */
- private static byte[] getKeyDigest(String k, String s) {
- StringBuilder builder = new StringBuilder(k).append(":").append(s);
- return getMd5(builder.toString());
- }
-
- /**
- * Let toHex(n) be the representation of the 16 octet MD5 hash n as a string of 32 hex digits
- * (with alphabetic characters always in lower case, since MD5 is case sensitive).
- */
- private static String toHex(byte[] n) {
- StringBuilder result = new StringBuilder();
- for (byte b : n) {
- int unsignedByte = b & 0xFF;
- result.append(HEX_CHARS.charAt(unsignedByte / 16))
- .append(HEX_CHARS.charAt(unsignedByte % 16));
- }
- return result.toString();
- }
-
- public static Map<String, String> parseDigestMessage(String message) throws MessagingException {
- Map<String, String> result = new DigestMessageParser(message).parse();
- if (!result.containsKey(DIGEST_NONCE)) {
- throw new MessagingException("nonce missing from server DIGEST-MD5 challenge");
- }
- return result;
- }
-
- /**
- * Parse the key-value pair returned by the server.
- */
- private static class DigestMessageParser {
-
- private final String mMessage;
- private int mPosition = 0;
- private Map<String, String> mResult = new ArrayMap<>();
-
- public DigestMessageParser(String message) {
- mMessage = message;
- }
-
- @Nullable
- public Map<String, String> parse() {
- try {
- while (mPosition < mMessage.length()) {
- parsePair();
- if (mPosition != mMessage.length()) {
- expect(',');
- }
- }
- } catch (IndexOutOfBoundsException e) {
- VvmLog.e(TAG, e.toString());
- return null;
- }
- return mResult;
- }
-
- private void parsePair() {
- String key = parseKey();
- expect('=');
- String value = parseValue();
- mResult.put(key, value);
- }
-
- private void expect(char c) {
- if (pop() != c) {
- throw new IllegalStateException(
- "unexpected character " + mMessage.charAt(mPosition));
- }
- }
-
- private char pop() {
- char result = peek();
- mPosition++;
- return result;
- }
-
- private char peek() {
- return mMessage.charAt(mPosition);
- }
-
- private void goToNext(char c) {
- while (peek() != c) {
- mPosition++;
- }
- }
-
- private String parseKey() {
- int start = mPosition;
- goToNext('=');
- return mMessage.substring(start, mPosition);
- }
-
- private String parseValue() {
- if (peek() == '"') {
- return parseQuotedValue();
- } else {
- return parseUnquotedValue();
- }
- }
-
- private String parseQuotedValue() {
- expect('"');
- StringBuilder result = new StringBuilder();
- while (true) {
- char c = pop();
- if (c == '\\') {
- result.append(pop());
- } else if (c == '"') {
- break;
- } else {
- result.append(c);
- }
- }
- return result.toString();
- }
-
- private String parseUnquotedValue() {
- StringBuilder result = new StringBuilder();
- while (true) {
- char c = pop();
- if (c == '\\') {
- result.append(pop());
- } else if (c == ',') {
- mPosition--;
- break;
- } else {
- result.append(c);
- }
-
- if (mPosition == mMessage.length()) {
- break;
- }
- }
- return result.toString();
- }
- }
-}
diff --git a/java/com/android/voicemailomtp/mail/store/imap/ImapConstants.java b/java/com/android/voicemailomtp/mail/store/imap/ImapConstants.java
deleted file mode 100644
index d8e75752f..000000000
--- a/java/com/android/voicemailomtp/mail/store/imap/ImapConstants.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2015 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.voicemailomtp.mail.store.imap;
-
-import com.android.voicemailomtp.mail.store.ImapStore;
-
-import java.util.Locale;
-
-public final class ImapConstants {
- private ImapConstants() {}
-
- public static final String FETCH_FIELD_BODY_PEEK_BARE = "BODY.PEEK";
- public static final String FETCH_FIELD_BODY_PEEK = FETCH_FIELD_BODY_PEEK_BARE + "[]";
- public static final String FETCH_FIELD_BODY_PEEK_SANE = String.format(
- Locale.US, "BODY.PEEK[]<0.%d>", ImapStore.FETCH_BODY_SANE_SUGGESTED_SIZE);
- public static final String FETCH_FIELD_HEADERS =
- "BODY.PEEK[HEADER.FIELDS (date subject from content-type to cc message-id)]";
-
- public static final String ALERT = "ALERT";
- public static final String APPEND = "APPEND";
- public static final String AUTHENTICATE = "AUTHENTICATE";
- public static final String BAD = "BAD";
- public static final String BADCHARSET = "BADCHARSET";
- public static final String BODY = "BODY";
- public static final String BODY_BRACKET_HEADER = "BODY[HEADER";
- public static final String BODYSTRUCTURE = "BODYSTRUCTURE";
- public static final String BYE = "BYE";
- public static final String CAPABILITY = "CAPABILITY";
- public static final String CHECK = "CHECK";
- public static final String CLOSE = "CLOSE";
- public static final String COPY = "COPY";
- public static final String COPYUID = "COPYUID";
- public static final String CREATE = "CREATE";
- public static final String DELETE = "DELETE";
- public static final String EXAMINE = "EXAMINE";
- public static final String EXISTS = "EXISTS";
- public static final String EXPUNGE = "EXPUNGE";
- public static final String FETCH = "FETCH";
- public static final String FLAG_ANSWERED = "\\ANSWERED";
- public static final String FLAG_DELETED = "\\DELETED";
- public static final String FLAG_FLAGGED = "\\FLAGGED";
- public static final String FLAG_NO_SELECT = "\\NOSELECT";
- public static final String FLAG_SEEN = "\\SEEN";
- public static final String FLAGS = "FLAGS";
- public static final String FLAGS_SILENT = "FLAGS.SILENT";
- public static final String ID = "ID";
- public static final String INBOX = "INBOX";
- public static final String INTERNALDATE = "INTERNALDATE";
- public static final String LIST = "LIST";
- public static final String LOGIN = "LOGIN";
- public static final String LOGOUT = "LOGOUT";
- public static final String LSUB = "LSUB";
- public static final String NAMESPACE = "NAMESPACE";
- public static final String NO = "NO";
- public static final String NOOP = "NOOP";
- public static final String OK = "OK";
- public static final String PARSE = "PARSE";
- public static final String PERMANENTFLAGS = "PERMANENTFLAGS";
- public static final String PREAUTH = "PREAUTH";
- public static final String READ_ONLY = "READ-ONLY";
- public static final String READ_WRITE = "READ-WRITE";
- public static final String RENAME = "RENAME";
- public static final String RFC822_SIZE = "RFC822.SIZE";
- public static final String SEARCH = "SEARCH";
- public static final String SELECT = "SELECT";
- public static final String STARTTLS = "STARTTLS";
- public static final String STATUS = "STATUS";
- public static final String STORE = "STORE";
- public static final String SUBSCRIBE = "SUBSCRIBE";
- public static final String TEXT = "TEXT";
- public static final String TRYCREATE = "TRYCREATE";
- public static final String UID = "UID";
- public static final String UID_COPY = "UID COPY";
- public static final String UID_FETCH = "UID FETCH";
- public static final String UID_SEARCH = "UID SEARCH";
- public static final String UID_STORE = "UID STORE";
- public static final String UIDNEXT = "UIDNEXT";
- public static final String UIDPLUS = "UIDPLUS";
- public static final String UIDVALIDITY = "UIDVALIDITY";
- public static final String UNSEEN = "UNSEEN";
- public static final String UNSUBSCRIBE = "UNSUBSCRIBE";
- public static final String XOAUTH2 = "XOAUTH2";
- public static final String APPENDUID = "APPENDUID";
- public static final String NIL = "NIL";
-
- /**
- * NO responses
- */
- public static final String NO_COMMAND_NOT_ALLOWED = "command not allowed";
- public static final String NO_RESERVATION_FAILED = "reservation failed";
- public static final String NO_APPLICATION_ERROR = "application error";
- public static final String NO_INVALID_PARAMETER = "invalid parameter";
- public static final String NO_INVALID_COMMAND = "invalid command";
- public static final String NO_UNKNOWN_COMMAND = "unknown command";
- // AUTHENTICATE
- // The subscriber can not be located in the system.
- public static final String NO_UNKNOWN_USER = "unknown user";
- // The Client Type or Protocol Version is unknown.
- public static final String NO_UNKNOWN_CLIENT = "unknown client";
- // The password received from the client does not match the password defined in the subscriber's profile.
- public static final String NO_INVALID_PASSWORD = "invalid password";
- // The subscriber's mailbox has not yet been initialised via the TUI
- public static final String NO_MAILBOX_NOT_INITIALIZED = "mailbox not initialized";
- // The subscriber has not been provisioned for the VVM service.
- public static final String NO_SERVICE_IS_NOT_PROVISIONED =
- "service is not provisioned";
- // The subscriber is provisioned for the VVM service but the VVM service is currently not active
- public static final String NO_SERVICE_IS_NOT_ACTIVATED = "service is not activated";
- // The Voice Mail Blocked flag in the subscriber's profile is set to YES.
- public static final String NO_USER_IS_BLOCKED = "user is blocked";
-
- /**
- * extensions
- */
- public static final String GETQUOTA = "GETQUOTA";
- public static final String GETQUOTAROOT = "GETQUOTAROOT";
- public static final String QUOTAROOT = "QUOTAROOT";
- public static final String QUOTA = "QUOTA";
-
- /**
- * capabilities
- */
- public static final String CAPABILITY_AUTH_DIGEST_MD5 = "AUTH=DIGEST-MD5";
- public static final String CAPABILITY_STARTTLS = "STARTTLS";
-
- /**
- * authentication
- */
- public static final String AUTH_DIGEST_MD5 = "DIGEST-MD5";
-} \ No newline at end of file
diff --git a/java/com/android/voicemailomtp/mail/store/imap/ImapElement.java b/java/com/android/voicemailomtp/mail/store/imap/ImapElement.java
deleted file mode 100644
index 9f272e31c..000000000
--- a/java/com/android/voicemailomtp/mail/store/imap/ImapElement.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2015 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.voicemailomtp.mail.store.imap;
-
-/**
- * Class representing "element"s in IMAP responses.
- *
- * <p>Class hierarchy:
- * <pre>
- * ImapElement
- * |
- * |-- ImapElement.NONE (for 'index out of range')
- * |
- * |-- ImapList (isList() == true)
- * | |
- * | |-- ImapList.EMPTY
- * | |
- * | --- ImapResponse
- * |
- * --- ImapString (isString() == true)
- * |
- * |-- ImapString.EMPTY
- * |
- * |-- ImapSimpleString
- * |
- * |-- ImapMemoryLiteral
- * |
- * --- ImapTempFileLiteral
- * </pre>
- */
-public abstract class ImapElement {
- /**
- * An element that is returned by {@link ImapList#getElementOrNone} to indicate an index
- * is out of range.
- */
- public static final ImapElement NONE = new ImapElement() {
- @Override public void destroy() {
- // Don't call super.destroy().
- // It's a shared object. We don't want the mDestroyed to be set on this.
- }
-
- @Override public boolean isList() {
- return false;
- }
-
- @Override public boolean isString() {
- return false;
- }
-
- @Override public String toString() {
- return "[NO ELEMENT]";
- }
-
- @Override
- public boolean equalsForTest(ImapElement that) {
- return super.equalsForTest(that);
- }
- };
-
- private boolean mDestroyed = false;
-
- public abstract boolean isList();
-
- public abstract boolean isString();
-
- protected boolean isDestroyed() {
- return mDestroyed;
- }
-
- /**
- * Clean up the resources used by the instance.
- * It's for removing a temp file used by {@link ImapTempFileLiteral}.
- */
- public void destroy() {
- mDestroyed = true;
- }
-
- /**
- * Throws {@link RuntimeException} if it's already destroyed.
- */
- protected final void checkNotDestroyed() {
- if (mDestroyed) {
- throw new RuntimeException("Already destroyed");
- }
- }
-
- /**
- * Return a string that represents this object; it's purely for the debug purpose. Don't
- * mistake it for {@link ImapString#getString}.
- *
- * Abstract to force subclasses to implement it.
- */
- @Override
- public abstract String toString();
-
- /**
- * The equals implementation that is intended to be used only for unit testing.
- * (Because it may be heavy and has a special sense of "equal" for testing.)
- */
- public boolean equalsForTest(ImapElement that) {
- if (that == null) {
- return false;
- }
- return this.getClass() == that.getClass(); // Has to be the same class.
- }
-} \ No newline at end of file
diff --git a/java/com/android/voicemailomtp/mail/store/imap/ImapList.java b/java/com/android/voicemailomtp/mail/store/imap/ImapList.java
deleted file mode 100644
index 970423cbd..000000000
--- a/java/com/android/voicemailomtp/mail/store/imap/ImapList.java
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- * Copyright (C) 2015 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.voicemailomtp.mail.store.imap;
-
-import java.util.ArrayList;
-
-/**
- * Class represents an IMAP list.
- */
-public class ImapList extends ImapElement {
- /**
- * {@link ImapList} representing an empty list.
- */
- public static final ImapList EMPTY = new ImapList() {
- @Override public void destroy() {
- // Don't call super.destroy().
- // It's a shared object. We don't want the mDestroyed to be set on this.
- }
-
- @Override void add(ImapElement e) {
- throw new RuntimeException();
- }
- };
-
- private ArrayList<ImapElement> mList = new ArrayList<ImapElement>();
-
- /* package */ void add(ImapElement e) {
- if (e == null) {
- throw new RuntimeException("Can't add null");
- }
- mList.add(e);
- }
-
- @Override
- public final boolean isString() {
- return false;
- }
-
- @Override
- public final boolean isList() {
- return true;
- }
-
- public final int size() {
- return mList.size();
- }
-
- public final boolean isEmpty() {
- return size() == 0;
- }
-
- /**
- * Return true if the element at {@code index} exists, is string, and equals to {@code s}.
- * (case insensitive)
- */
- public final boolean is(int index, String s) {
- return is(index, s, false);
- }
-
- /**
- * Same as {@link #is(int, String)}, but does the prefix match if {@code prefixMatch}.
- */
- public final boolean is(int index, String s, boolean prefixMatch) {
- if (!prefixMatch) {
- return getStringOrEmpty(index).is(s);
- } else {
- return getStringOrEmpty(index).startsWith(s);
- }
- }
-
- /**
- * Return the element at {@code index}.
- * If {@code index} is out of range, returns {@link ImapElement#NONE}.
- */
- public final ImapElement getElementOrNone(int index) {
- return (index >= mList.size()) ? ImapElement.NONE : mList.get(index);
- }
-
- /**
- * Return the element at {@code index} if it's a list.
- * If {@code index} is out of range or not a list, returns {@link ImapList#EMPTY}.
- */
- public final ImapList getListOrEmpty(int index) {
- ImapElement el = getElementOrNone(index);
- return el.isList() ? (ImapList) el : EMPTY;
- }
-
- /**
- * Return the element at {@code index} if it's a string.
- * If {@code index} is out of range or not a string, returns {@link ImapString#EMPTY}.
- */
- public final ImapString getStringOrEmpty(int index) {
- ImapElement el = getElementOrNone(index);
- return el.isString() ? (ImapString) el : ImapString.EMPTY;
- }
-
- /**
- * Return an element keyed by {@code key}. Return null if not found. {@code key} has to be
- * at an even index.
- */
- /* package */ final ImapElement getKeyedElementOrNull(String key, boolean prefixMatch) {
- for (int i = 1; i < size(); i += 2) {
- if (is(i-1, key, prefixMatch)) {
- return mList.get(i);
- }
- }
- return null;
- }
-
- /**
- * Return an {@link ImapList} keyed by {@code key}.
- * Return {@link ImapList#EMPTY} if not found.
- */
- public final ImapList getKeyedListOrEmpty(String key) {
- return getKeyedListOrEmpty(key, false);
- }
-
- /**
- * Return an {@link ImapList} keyed by {@code key}.
- * Return {@link ImapList#EMPTY} if not found.
- */
- public final ImapList getKeyedListOrEmpty(String key, boolean prefixMatch) {
- ImapElement e = getKeyedElementOrNull(key, prefixMatch);
- return (e != null) ? ((ImapList) e) : ImapList.EMPTY;
- }
-
- /**
- * Return an {@link ImapString} keyed by {@code key}.
- * Return {@link ImapString#EMPTY} if not found.
- */
- public final ImapString getKeyedStringOrEmpty(String key) {
- return getKeyedStringOrEmpty(key, false);
- }
-
- /**
- * Return an {@link ImapString} keyed by {@code key}.
- * Return {@link ImapString#EMPTY} if not found.
- */
- public final ImapString getKeyedStringOrEmpty(String key, boolean prefixMatch) {
- ImapElement e = getKeyedElementOrNull(key, prefixMatch);
- return (e != null) ? ((ImapString) e) : ImapString.EMPTY;
- }
-
- /**
- * Return true if it contains {@code s}.
- */
- public final boolean contains(String s) {
- for (int i = 0; i < size(); i++) {
- if (getStringOrEmpty(i).is(s)) {
- return true;
- }
- }
- return false;
- }
-
- @Override
- public void destroy() {
- if (mList != null) {
- for (ImapElement e : mList) {
- e.destroy();
- }
- mList = null;
- }
- super.destroy();
- }
-
- @Override
- public String toString() {
- return mList.toString();
- }
-
- /**
- * Return the text representations of the contents concatenated with ",".
- */
- public final String flatten() {
- return flatten(new StringBuilder()).toString();
- }
-
- /**
- * Returns text representations (i.e. getString()) of contents joined together with
- * "," as the separator.
- *
- * Only used for building the capability string passed to vendor policies.
- *
- * We can't use toString(), because it's for debugging (meaning the format may change any time),
- * and it won't expand literals.
- */
- private final StringBuilder flatten(StringBuilder sb) {
- sb.append('[');
- for (int i = 0; i < mList.size(); i++) {
- if (i > 0) {
- sb.append(',');
- }
- final ImapElement e = getElementOrNone(i);
- if (e.isList()) {
- getListOrEmpty(i).flatten(sb);
- } else if (e.isString()) {
- sb.append(getStringOrEmpty(i).getString());
- }
- }
- sb.append(']');
- return sb;
- }
-
- @Override
- public boolean equalsForTest(ImapElement that) {
- if (!super.equalsForTest(that)) {
- return false;
- }
- ImapList thatList = (ImapList) that;
- if (size() != thatList.size()) {
- return false;
- }
- for (int i = 0; i < size(); i++) {
- if (!mList.get(i).equalsForTest(thatList.getElementOrNone(i))) {
- return false;
- }
- }
- return true;
- }
-} \ No newline at end of file
diff --git a/java/com/android/voicemailomtp/mail/store/imap/ImapMemoryLiteral.java b/java/com/android/voicemailomtp/mail/store/imap/ImapMemoryLiteral.java
deleted file mode 100644
index ad60ca7a4..000000000
--- a/java/com/android/voicemailomtp/mail/store/imap/ImapMemoryLiteral.java
+++ /dev/null
@@ -1,76 +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.voicemailomtp.mail.store.imap;
-
-import com.android.voicemailomtp.mail.FixedLengthInputStream;
-import com.android.voicemailomtp.VvmLog;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-
-/**
- * Subclass of {@link ImapString} used for literals backed by an in-memory byte array.
- */
-public class ImapMemoryLiteral extends ImapString {
- private final String TAG = "ImapMemoryLiteral";
- private byte[] mData;
-
- /* package */ ImapMemoryLiteral(FixedLengthInputStream in) throws IOException {
- // We could use ByteArrayOutputStream and IOUtils.copy, but it'd perform an unnecessary
- // copy....
- mData = new byte[in.getLength()];
- int pos = 0;
- while (pos < mData.length) {
- int read = in.read(mData, pos, mData.length - pos);
- if (read < 0) {
- break;
- }
- pos += read;
- }
- if (pos != mData.length) {
- VvmLog.w(TAG, "length mismatch");
- }
- }
-
- @Override
- public void destroy() {
- mData = null;
- super.destroy();
- }
-
- @Override
- public String getString() {
- try {
- return new String(mData, "US-ASCII");
- } catch (UnsupportedEncodingException e) {
- VvmLog.e(TAG, "Unsupported encoding: ", e);
- }
- return null;
- }
-
- @Override
- public InputStream getAsStream() {
- return new ByteArrayInputStream(mData);
- }
-
- @Override
- public String toString() {
- return String.format("{%d byte literal(memory)}", mData.length);
- }
-} \ No newline at end of file
diff --git a/java/com/android/voicemailomtp/mail/store/imap/ImapResponse.java b/java/com/android/voicemailomtp/mail/store/imap/ImapResponse.java
deleted file mode 100644
index 412f16d8a..000000000
--- a/java/com/android/voicemailomtp/mail/store/imap/ImapResponse.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Copyright (C) 2015 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.voicemailomtp.mail.store.imap;
-
-/**
- * Class represents an IMAP response.
- */
-public class ImapResponse extends ImapList {
- private final String mTag;
- private final boolean mIsContinuationRequest;
-
- /* package */ ImapResponse(String tag, boolean isContinuationRequest) {
- mTag = tag;
- mIsContinuationRequest = isContinuationRequest;
- }
-
- /* package */ static boolean isStatusResponse(String symbol) {
- return ImapConstants.OK.equalsIgnoreCase(symbol)
- || ImapConstants.NO.equalsIgnoreCase(symbol)
- || ImapConstants.BAD.equalsIgnoreCase(symbol)
- || ImapConstants.PREAUTH.equalsIgnoreCase(symbol)
- || ImapConstants.BYE.equalsIgnoreCase(symbol);
- }
-
- /**
- * @return whether it's a tagged response.
- */
- public boolean isTagged() {
- return mTag != null;
- }
-
- /**
- * @return whether it's a continuation request.
- */
- public boolean isContinuationRequest() {
- return mIsContinuationRequest;
- }
-
- public boolean isStatusResponse() {
- return isStatusResponse(getStringOrEmpty(0).getString());
- }
-
- /**
- * @return whether it's an OK response.
- */
- public boolean isOk() {
- return is(0, ImapConstants.OK);
- }
-
- /**
- * @return whether it's an BAD response.
- */
- public boolean isBad() {
- return is(0, ImapConstants.BAD);
- }
-
- /**
- * @return whether it's an NO response.
- */
- public boolean isNo() {
- return is(0, ImapConstants.NO);
- }
-
- /**
- * @return whether it's an {@code responseType} data response. (i.e. not tagged).
- * @param index where {@code responseType} should appear. e.g. 1 for "FETCH"
- * @param responseType e.g. "FETCH"
- */
- public final boolean isDataResponse(int index, String responseType) {
- return !isTagged() && getStringOrEmpty(index).is(responseType);
- }
-
- /**
- * @return Response code (RFC 3501 7.1) if it's a status response.
- *
- * e.g. "ALERT" for "* OK [ALERT] System shutdown in 10 minutes"
- */
- public ImapString getResponseCodeOrEmpty() {
- if (!isStatusResponse()) {
- return ImapString.EMPTY; // Not a status response.
- }
- return getListOrEmpty(1).getStringOrEmpty(0);
- }
-
- /**
- * @return Alert message it it has ALERT response code.
- *
- * e.g. "System shutdown in 10 minutes" for "* OK [ALERT] System shutdown in 10 minutes"
- */
- public ImapString getAlertTextOrEmpty() {
- if (!getResponseCodeOrEmpty().is(ImapConstants.ALERT)) {
- return ImapString.EMPTY; // Not an ALERT
- }
- // The 3rd element contains all the rest of line.
- return getStringOrEmpty(2);
- }
-
- /**
- * @return Response text in a status response.
- */
- public ImapString getStatusResponseTextOrEmpty() {
- if (!isStatusResponse()) {
- return ImapString.EMPTY;
- }
- return getStringOrEmpty(getElementOrNone(1).isList() ? 2 : 1);
- }
-
- public ImapString getStatusOrEmpty() {
- if (!isStatusResponse()) {
- return ImapString.EMPTY;
- }
- return getStringOrEmpty(0);
- }
-
- @Override
- public String toString() {
- String tag = mTag;
- if (isContinuationRequest()) {
- tag = "+";
- }
- return "#" + tag + "# " + super.toString();
- }
-
- @Override
- public boolean equalsForTest(ImapElement that) {
- if (!super.equalsForTest(that)) {
- return false;
- }
- final ImapResponse thatResponse = (ImapResponse) that;
- if (mTag == null) {
- if (thatResponse.mTag != null) {
- return false;
- }
- } else {
- if (!mTag.equals(thatResponse.mTag)) {
- return false;
- }
- }
- if (mIsContinuationRequest != thatResponse.mIsContinuationRequest) {
- return false;
- }
- return true;
- }
-} \ No newline at end of file
diff --git a/java/com/android/voicemailomtp/mail/store/imap/ImapResponseParser.java b/java/com/android/voicemailomtp/mail/store/imap/ImapResponseParser.java
deleted file mode 100644
index 692596f14..000000000
--- a/java/com/android/voicemailomtp/mail/store/imap/ImapResponseParser.java
+++ /dev/null
@@ -1,432 +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.voicemailomtp.mail.store.imap;
-
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.voicemailomtp.mail.FixedLengthInputStream;
-import com.android.voicemailomtp.mail.MessagingException;
-import com.android.voicemailomtp.mail.PeekableInputStream;
-import com.android.voicemailomtp.VvmLog;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-
-/**
- * IMAP response parser.
- */
-public class ImapResponseParser {
- private static final String TAG = "ImapResponseParser";
-
- /**
- * Literal larger than this will be stored in temp file.
- */
- public static final int LITERAL_KEEP_IN_MEMORY_THRESHOLD = 2 * 1024 * 1024;
-
- /** Input stream */
- private final PeekableInputStream mIn;
-
- private final int mLiteralKeepInMemoryThreshold;
-
- /** StringBuilder used by readUntil() */
- private final StringBuilder mBufferReadUntil = new StringBuilder();
-
- /** StringBuilder used by parseBareString() */
- private final StringBuilder mParseBareString = new StringBuilder();
-
- /**
- * We store all {@link ImapResponse} in it. {@link #destroyResponses()} must be called from
- * time to time to destroy them and clear it.
- */
- private final ArrayList<ImapResponse> mResponsesToDestroy = new ArrayList<ImapResponse>();
-
- /**
- * Exception thrown when we receive BYE. It derives from IOException, so it'll be treated
- * in the same way EOF does.
- */
- public static class ByeException extends IOException {
- public static final String MESSAGE = "Received BYE";
- public ByeException() {
- super(MESSAGE);
- }
- }
-
- /**
- * Public constructor for normal use.
- */
- public ImapResponseParser(InputStream in) {
- this(in, LITERAL_KEEP_IN_MEMORY_THRESHOLD);
- }
-
- /**
- * Constructor for testing to override the literal size threshold.
- */
- /* package for test */ ImapResponseParser(InputStream in, int literalKeepInMemoryThreshold) {
- mIn = new PeekableInputStream(in);
- mLiteralKeepInMemoryThreshold = literalKeepInMemoryThreshold;
- }
-
- private static IOException newEOSException() {
- final String message = "End of stream reached";
- VvmLog.d(TAG, message);
- return new IOException(message);
- }
-
- /**
- * Peek next one byte.
- *
- * Throws IOException() if reaches EOF. As long as logical response lines end with \r\n,
- * we shouldn't see EOF during parsing.
- */
- private int peek() throws IOException {
- final int next = mIn.peek();
- if (next == -1) {
- throw newEOSException();
- }
- return next;
- }
-
- /**
- * Read and return one byte from {@link #mIn}, and put it in {@link #mDiscourseLogger}.
- *
- * Throws IOException() if reaches EOF. As long as logical response lines end with \r\n,
- * we shouldn't see EOF during parsing.
- */
- private int readByte() throws IOException {
- int next = mIn.read();
- if (next == -1) {
- throw newEOSException();
- }
- return next;
- }
-
- /**
- * Destroy all the {@link ImapResponse}s stored in the internal storage and clear it.
- *
- * @see #readResponse()
- */
- public void destroyResponses() {
- for (ImapResponse r : mResponsesToDestroy) {
- r.destroy();
- }
- mResponsesToDestroy.clear();
- }
-
- /**
- * Reads the next response available on the stream and returns an
- * {@link ImapResponse} object that represents it.
- *
- * <p>When this method successfully returns an {@link ImapResponse}, the {@link ImapResponse}
- * is stored in the internal storage. When the {@link ImapResponse} is no longer used
- * {@link #destroyResponses} should be called to destroy all the responses in the array.
- *
- * @param byeExpected is a untagged BYE response expected? If not proper cleanup will be done
- * and {@link ByeException} will be thrown.
- * @return the parsed {@link ImapResponse} object.
- * @exception ByeException when detects BYE and <code>byeExpected</code> is false.
- */
- public ImapResponse readResponse(boolean byeExpected) throws IOException, MessagingException {
- ImapResponse response = null;
- try {
- response = parseResponse();
- } catch (RuntimeException e) {
- // Parser crash -- log network activities.
- onParseError(e);
- throw e;
- } catch (IOException e) {
- // Network error, or received an unexpected char.
- onParseError(e);
- throw e;
- }
-
- // Handle this outside of try-catch. We don't have to dump protocol log when getting BYE.
- if (!byeExpected && response.is(0, ImapConstants.BYE)) {
- Log.w(TAG, ByeException.MESSAGE);
- response.destroy();
- throw new ByeException();
- }
- mResponsesToDestroy.add(response);
- return response;
- }
-
- private void onParseError(Exception e) {
- // Read a few more bytes, so that the log will contain some more context, even if the parser
- // crashes in the middle of a response.
- // This also makes sure the byte in question will be logged, no matter where it crashes.
- // e.g. when parseAtom() peeks and finds at an unexpected char, it throws an exception
- // before actually reading it.
- // However, we don't want to read too much, because then it may get into an email message.
- try {
- for (int i = 0; i < 4; i++) {
- int b = readByte();
- if (b == -1 || b == '\n') {
- break;
- }
- }
- } catch (IOException ignore) {
- }
- VvmLog.w(TAG, "Exception detected: " + e.getMessage());
- }
-
- /**
- * Read next byte from stream and throw it away. If the byte is different from {@code expected}
- * throw {@link MessagingException}.
- */
- /* package for test */ void expect(char expected) throws IOException {
- final int next = readByte();
- if (expected != next) {
- throw new IOException(String.format("Expected %04x (%c) but got %04x (%c)",
- (int) expected, expected, next, (char) next));
- }
- }
-
- /**
- * Read bytes until we find {@code end}, and return all as string.
- * The {@code end} will be read (rather than peeked) and won't be included in the result.
- */
- /* package for test */ String readUntil(char end) throws IOException {
- mBufferReadUntil.setLength(0);
- for (;;) {
- final int ch = readByte();
- if (ch != end) {
- mBufferReadUntil.append((char) ch);
- } else {
- return mBufferReadUntil.toString();
- }
- }
- }
-
- /**
- * Read all bytes until \r\n.
- */
- /* package */ String readUntilEol() throws IOException {
- String ret = readUntil('\r');
- expect('\n'); // TODO Should this really be error?
- return ret;
- }
-
- /**
- * Parse and return the response line.
- */
- private ImapResponse parseResponse() throws IOException, MessagingException {
- // We need to destroy the response if we get an exception.
- // So, we first store the response that's being built in responseToDestroy, until it's
- // completely built, at which point we copy it into responseToReturn and null out
- // responseToDestroyt.
- // If responseToDestroy is not null in finally, we destroy it because that means
- // we got an exception somewhere.
- ImapResponse responseToDestroy = null;
- final ImapResponse responseToReturn;
-
- try {
- final int ch = peek();
- if (ch == '+') { // Continuation request
- readByte(); // skip +
- expect(' ');
- responseToDestroy = new ImapResponse(null, true);
-
- // If it's continuation request, we don't really care what's in it.
- responseToDestroy.add(new ImapSimpleString(readUntilEol()));
-
- // Response has successfully been built. Let's return it.
- responseToReturn = responseToDestroy;
- responseToDestroy = null;
- } else {
- // Status response or response data
- final String tag;
- if (ch == '*') {
- tag = null;
- readByte(); // skip *
- expect(' ');
- } else {
- tag = readUntil(' ');
- }
- responseToDestroy = new ImapResponse(tag, false);
-
- final ImapString firstString = parseBareString();
- responseToDestroy.add(firstString);
-
- // parseBareString won't eat a space after the string, so we need to skip it,
- // if exists.
- // If the next char is not ' ', it should be EOL.
- if (peek() == ' ') {
- readByte(); // skip ' '
-
- if (responseToDestroy.isStatusResponse()) { // It's a status response
-
- // Is there a response code?
- final int next = peek();
- if (next == '[') {
- responseToDestroy.add(parseList('[', ']'));
- if (peek() == ' ') { // Skip following space
- readByte();
- }
- }
-
- String rest = readUntilEol();
- if (!TextUtils.isEmpty(rest)) {
- // The rest is free-form text.
- responseToDestroy.add(new ImapSimpleString(rest));
- }
- } else { // It's a response data.
- parseElements(responseToDestroy, '\0');
- }
- } else {
- expect('\r');
- expect('\n');
- }
-
- // Response has successfully been built. Let's return it.
- responseToReturn = responseToDestroy;
- responseToDestroy = null;
- }
- } finally {
- if (responseToDestroy != null) {
- // We get an exception.
- responseToDestroy.destroy();
- }
- }
-
- return responseToReturn;
- }
-
- private ImapElement parseElement() throws IOException, MessagingException {
- final int next = peek();
- switch (next) {
- case '(':
- return parseList('(', ')');
- case '[':
- return parseList('[', ']');
- case '"':
- readByte(); // Skip "
- return new ImapSimpleString(readUntil('"'));
- case '{':
- return parseLiteral();
- case '\r': // CR
- readByte(); // Consume \r
- expect('\n'); // Should be followed by LF.
- return null;
- case '\n': // LF // There shouldn't be a bare LF, but just in case.
- readByte(); // Consume \n
- return null;
- default:
- return parseBareString();
- }
- }
-
- /**
- * Parses an atom.
- *
- * Special case: If an atom contains '[', everything until the next ']' will be considered
- * a part of the atom.
- * (e.g. "BODY[HEADER.FIELDS ("DATE" ...)]" will become a single ImapString)
- *
- * If the value is "NIL", returns an empty string.
- */
- private ImapString parseBareString() throws IOException, MessagingException {
- mParseBareString.setLength(0);
- for (;;) {
- final int ch = peek();
-
- // TODO Can we clean this up? (This condition is from the old parser.)
- if (ch == '(' || ch == ')' || ch == '{' || ch == ' ' ||
- // ']' is not part of atom (it's in resp-specials)
- ch == ']' ||
- // docs claim that flags are \ atom but atom isn't supposed to
- // contain
- // * and some flags contain *
- // ch == '%' || ch == '*' ||
- ch == '%' ||
- // TODO probably should not allow \ and should recognize
- // it as a flag instead
- // ch == '"' || ch == '\' ||
- ch == '"' || (0x00 <= ch && ch <= 0x1f) || ch == 0x7f) {
- if (mParseBareString.length() == 0) {
- throw new MessagingException("Expected string, none found.");
- }
- String s = mParseBareString.toString();
-
- // NIL will be always converted into the empty string.
- if (ImapConstants.NIL.equalsIgnoreCase(s)) {
- return ImapString.EMPTY;
- }
- return new ImapSimpleString(s);
- } else if (ch == '[') {
- // Eat all until next ']'
- mParseBareString.append((char) readByte());
- mParseBareString.append(readUntil(']'));
- mParseBareString.append(']'); // readUntil won't include the end char.
- } else {
- mParseBareString.append((char) readByte());
- }
- }
- }
-
- private void parseElements(ImapList list, char end)
- throws IOException, MessagingException {
- for (;;) {
- for (;;) {
- final int next = peek();
- if (next == end) {
- return;
- }
- if (next != ' ') {
- break;
- }
- // Skip space
- readByte();
- }
- final ImapElement el = parseElement();
- if (el == null) { // EOL
- return;
- }
- list.add(el);
- }
- }
-
- private ImapList parseList(char opening, char closing)
- throws IOException, MessagingException {
- expect(opening);
- final ImapList list = new ImapList();
- parseElements(list, closing);
- expect(closing);
- return list;
- }
-
- private ImapString parseLiteral() throws IOException, MessagingException {
- expect('{');
- final int size;
- try {
- size = Integer.parseInt(readUntil('}'));
- } catch (NumberFormatException nfe) {
- throw new MessagingException("Invalid length in literal");
- }
- if (size < 0) {
- throw new MessagingException("Invalid negative length in literal");
- }
- expect('\r');
- expect('\n');
- FixedLengthInputStream in = new FixedLengthInputStream(mIn, size);
- if (size > mLiteralKeepInMemoryThreshold) {
- return new ImapTempFileLiteral(in);
- } else {
- return new ImapMemoryLiteral(in);
- }
- }
-} \ No newline at end of file
diff --git a/java/com/android/voicemailomtp/mail/store/imap/ImapSimpleString.java b/java/com/android/voicemailomtp/mail/store/imap/ImapSimpleString.java
deleted file mode 100644
index 22d8141a0..000000000
--- a/java/com/android/voicemailomtp/mail/store/imap/ImapSimpleString.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2015 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.voicemailomtp.mail.store.imap;
-
-import com.android.voicemailomtp.VvmLog;
-
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-
-/**
- * Subclass of {@link ImapString} used for non literals.
- */
-public class ImapSimpleString extends ImapString {
- private final String TAG = "ImapSimpleString";
- private String mString;
-
- /* package */ ImapSimpleString(String string) {
- mString = (string != null) ? string : "";
- }
-
- @Override
- public void destroy() {
- mString = null;
- super.destroy();
- }
-
- @Override
- public String getString() {
- return mString;
- }
-
- @Override
- public InputStream getAsStream() {
- try {
- return new ByteArrayInputStream(mString.getBytes("US-ASCII"));
- } catch (UnsupportedEncodingException e) {
- VvmLog.e(TAG, "Unsupported encoding: ", e);
- }
- return null;
- }
-
- @Override
- public String toString() {
- // Purposefully not return just mString, in order to prevent using it instead of getString.
- return "\"" + mString + "\"";
- }
-} \ No newline at end of file
diff --git a/java/com/android/voicemailomtp/mail/store/imap/ImapString.java b/java/com/android/voicemailomtp/mail/store/imap/ImapString.java
deleted file mode 100644
index 83efb6479..000000000
--- a/java/com/android/voicemailomtp/mail/store/imap/ImapString.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Copyright (C) 2015 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.voicemailomtp.mail.store.imap;
-
-import com.android.voicemailomtp.VvmLog;
-
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-
-/**
- * Class represents an IMAP "element" that is not a list.
- *
- * An atom, quoted string, literal, are all represented by this. Values like OK, STATUS are too.
- * Also, this class class may contain more arbitrary value like "BODY[HEADER.FIELDS ("DATE")]".
- * See {@link ImapResponseParser}.
- */
-public abstract class ImapString extends ImapElement {
- private static final byte[] EMPTY_BYTES = new byte[0];
-
- public static final ImapString EMPTY = new ImapString() {
- @Override public void destroy() {
- // Don't call super.destroy().
- // It's a shared object. We don't want the mDestroyed to be set on this.
- }
-
- @Override public String getString() {
- return "";
- }
-
- @Override public InputStream getAsStream() {
- return new ByteArrayInputStream(EMPTY_BYTES);
- }
-
- @Override public String toString() {
- return "";
- }
- };
-
- // This is used only for parsing IMAP's FETCH ENVELOPE command, in which
- // en_US-like date format is used like "01-Jan-2009 11:20:39 -0800", so this should be
- // handled by Locale.US
- private final static SimpleDateFormat DATE_TIME_FORMAT =
- new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss Z", Locale.US);
-
- private boolean mIsInteger;
- private int mParsedInteger;
- private Date mParsedDate;
-
- @Override
- public final boolean isList() {
- return false;
- }
-
- @Override
- public final boolean isString() {
- return true;
- }
-
- /**
- * @return true if and only if the length of the string is larger than 0.
- *
- * Note: IMAP NIL is considered an empty string. See {@link ImapResponseParser
- * #parseBareString}.
- * On the other hand, a quoted/literal string with value NIL (i.e. "NIL" and {3}\r\nNIL) is
- * treated literally.
- */
- public final boolean isEmpty() {
- return getString().length() == 0;
- }
-
- public abstract String getString();
-
- public abstract InputStream getAsStream();
-
- /**
- * @return whether it can be parsed as a number.
- */
- public final boolean isNumber() {
- if (mIsInteger) {
- return true;
- }
- try {
- mParsedInteger = Integer.parseInt(getString());
- mIsInteger = true;
- return true;
- } catch (NumberFormatException e) {
- return false;
- }
- }
-
- /**
- * @return value parsed as a number, or 0 if the string is not a number.
- */
- public final int getNumberOrZero() {
- return getNumber(0);
- }
-
- /**
- * @return value parsed as a number, or {@code defaultValue} if the string is not a number.
- */
- public final int getNumber(int defaultValue) {
- if (!isNumber()) {
- return defaultValue;
- }
- return mParsedInteger;
- }
-
- /**
- * @return whether it can be parsed as a date using {@link #DATE_TIME_FORMAT}.
- */
- public final boolean isDate() {
- if (mParsedDate != null) {
- return true;
- }
- if (isEmpty()) {
- return false;
- }
- try {
- mParsedDate = DATE_TIME_FORMAT.parse(getString());
- return true;
- } catch (ParseException e) {
- VvmLog.w("ImapString", getString() + " can't be parsed as a date.");
- return false;
- }
- }
-
- /**
- * @return value it can be parsed as a {@link Date}, or null otherwise.
- */
- public final Date getDateOrNull() {
- if (!isDate()) {
- return null;
- }
- return mParsedDate;
- }
-
- /**
- * @return whether the value case-insensitively equals to {@code s}.
- */
- public final boolean is(String s) {
- if (s == null) {
- return false;
- }
- return getString().equalsIgnoreCase(s);
- }
-
-
- /**
- * @return whether the value case-insensitively starts with {@code s}.
- */
- public final boolean startsWith(String prefix) {
- if (prefix == null) {
- return false;
- }
- final String me = this.getString();
- if (me.length() < prefix.length()) {
- return false;
- }
- return me.substring(0, prefix.length()).equalsIgnoreCase(prefix);
- }
-
- // To force subclasses to implement it.
- @Override
- public abstract String toString();
-
- @Override
- public final boolean equalsForTest(ImapElement that) {
- if (!super.equalsForTest(that)) {
- return false;
- }
- ImapString thatString = (ImapString) that;
- return getString().equals(thatString.getString());
- }
-} \ No newline at end of file
diff --git a/java/com/android/voicemailomtp/mail/store/imap/ImapTempFileLiteral.java b/java/com/android/voicemailomtp/mail/store/imap/ImapTempFileLiteral.java
deleted file mode 100644
index efe5c3848..000000000
--- a/java/com/android/voicemailomtp/mail/store/imap/ImapTempFileLiteral.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright (C) 2015 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.voicemailomtp.mail.store.imap;
-
-import com.android.voicemailomtp.mail.FixedLengthInputStream;
-import com.android.voicemailomtp.mail.TempDirectory;
-import com.android.voicemailomtp.mail.utils.Utility;
-import com.android.voicemailomtp.mail.utils.LogUtils;
-
-import org.apache.commons.io.IOUtils;
-
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-/**
- * Subclass of {@link ImapString} used for literals backed by a temp file.
- */
-public class ImapTempFileLiteral extends ImapString {
- private final String TAG = "ImapTempFileLiteral";
-
- /* package for test */ final File mFile;
-
- /** Size is purely for toString() */
- private final int mSize;
-
- /* package */ ImapTempFileLiteral(FixedLengthInputStream stream) throws IOException {
- mSize = stream.getLength();
- mFile = File.createTempFile("imap", ".tmp", TempDirectory.getTempDirectory());
-
- // Unfortunately, we can't really use deleteOnExit(), because temp filenames are random
- // so it'd simply cause a memory leak.
- // deleteOnExit() simply adds filenames to a static list and the list will never shrink.
- // mFile.deleteOnExit();
- OutputStream out = new FileOutputStream(mFile);
- IOUtils.copy(stream, out);
- out.close();
- }
-
- /**
- * Make sure we delete the temp file.
- *
- * We should always be calling {@link ImapResponse#destroy()}, but it's here as a last resort.
- */
- @Override
- protected void finalize() throws Throwable {
- try {
- destroy();
- } finally {
- super.finalize();
- }
- }
-
- @Override
- public InputStream getAsStream() {
- checkNotDestroyed();
- try {
- return new FileInputStream(mFile);
- } catch (FileNotFoundException e) {
- // It's probably possible if we're low on storage and the system clears the cache dir.
- LogUtils.w(TAG, "ImapTempFileLiteral: Temp file not found");
-
- // Return 0 byte stream as a dummy...
- return new ByteArrayInputStream(new byte[0]);
- }
- }
-
- @Override
- public String getString() {
- checkNotDestroyed();
- try {
- byte[] bytes = IOUtils.toByteArray(getAsStream());
- // Prevent crash from OOM; we've seen this, but only rarely and not reproducibly
- if (bytes.length > ImapResponseParser.LITERAL_KEEP_IN_MEMORY_THRESHOLD) {
- throw new IOException();
- }
- return Utility.fromAscii(bytes);
- } catch (IOException e) {
- LogUtils.w(TAG, "ImapTempFileLiteral: Error while reading temp file", e);
- return "";
- }
- }
-
- @Override
- public void destroy() {
- try {
- if (!isDestroyed() && mFile.exists()) {
- mFile.delete();
- }
- } catch (RuntimeException re) {
- // Just log and ignore.
- LogUtils.w(TAG, "Failed to remove temp file: " + re.getMessage());
- }
- super.destroy();
- }
-
- @Override
- public String toString() {
- return String.format("{%d byte literal(file)}", mSize);
- }
-
- public boolean tempFileExistsForTest() {
- return mFile.exists();
- }
-} \ No newline at end of file
diff --git a/java/com/android/voicemailomtp/mail/store/imap/ImapUtility.java b/java/com/android/voicemailomtp/mail/store/imap/ImapUtility.java
deleted file mode 100644
index b045eb32f..000000000
--- a/java/com/android/voicemailomtp/mail/store/imap/ImapUtility.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2015 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.voicemailomtp.mail.store.imap;
-
-import com.android.voicemailomtp.mail.utils.LogUtils;
-
-import java.util.ArrayList;
-
-/**
- * Utility methods for use with IMAP.
- */
-public class ImapUtility {
- public static final String TAG = "ImapUtility";
- /**
- * Apply quoting rules per IMAP RFC,
- * quoted = DQUOTE *QUOTED-CHAR DQUOTE
- * QUOTED-CHAR = <any TEXT-CHAR except quoted-specials> / "\" quoted-specials
- * quoted-specials = DQUOTE / "\"
- *
- * This is used primarily for IMAP login, but might be useful elsewhere.
- *
- * NOTE: Not very efficient - you may wish to preflight this, or perhaps it should check
- * for trouble chars before calling the replace functions.
- *
- * @param s The string to be quoted.
- * @return A copy of the string, having undergone quoting as described above
- */
- public static String imapQuoted(String s) {
-
- // First, quote any backslashes by replacing \ with \\
- // regex Pattern: \\ (Java string const = \\\\)
- // Substitute: \\\\ (Java string const = \\\\\\\\)
- String result = s.replaceAll("\\\\", "\\\\\\\\");
-
- // Then, quote any double-quotes by replacing " with \"
- // regex Pattern: " (Java string const = \")
- // Substitute: \\" (Java string const = \\\\\")
- result = result.replaceAll("\"", "\\\\\"");
-
- // return string with quotes around it
- return "\"" + result + "\"";
- }
-
- /**
- * Gets all of the values in a sequence set per RFC 3501. Any ranges are expanded into a
- * list of individual numbers. If the set is invalid, an empty array is returned.
- * <pre>
- * sequence-number = nz-number / "*"
- * sequence-range = sequence-number ":" sequence-number
- * sequence-set = (sequence-number / sequence-range) *("," sequence-set)
- * </pre>
- */
- public static String[] getImapSequenceValues(String set) {
- ArrayList<String> list = new ArrayList<String>();
- if (set != null) {
- String[] setItems = set.split(",");
- for (String item : setItems) {
- if (item.indexOf(':') == -1) {
- // simple item
- try {
- Integer.parseInt(item); // Don't need the value; just ensure it's valid
- list.add(item);
- } catch (NumberFormatException e) {
- LogUtils.d(TAG, "Invalid UID value", e);
- }
- } else {
- // range
- for (String rangeItem : getImapRangeValues(item)) {
- list.add(rangeItem);
- }
- }
- }
- }
- String[] stringList = new String[list.size()];
- return list.toArray(stringList);
- }
-
- /**
- * Expand the given number range into a list of individual numbers. If the range is not valid,
- * an empty array is returned.
- * <pre>
- * sequence-number = nz-number / "*"
- * sequence-range = sequence-number ":" sequence-number
- * sequence-set = (sequence-number / sequence-range) *("," sequence-set)
- * </pre>
- */
- public static String[] getImapRangeValues(String range) {
- ArrayList<String> list = new ArrayList<String>();
- try {
- if (range != null) {
- int colonPos = range.indexOf(':');
- if (colonPos > 0) {
- int first = Integer.parseInt(range.substring(0, colonPos));
- int second = Integer.parseInt(range.substring(colonPos + 1));
- if (first < second) {
- for (int i = first; i <= second; i++) {
- list.add(Integer.toString(i));
- }
- } else {
- for (int i = first; i >= second; i--) {
- list.add(Integer.toString(i));
- }
- }
- }
- }
- } catch (NumberFormatException e) {
- LogUtils.d(TAG, "Invalid range value", e);
- }
- String[] stringList = new String[list.size()];
- return list.toArray(stringList);
- }
-} \ No newline at end of file
diff --git a/java/com/android/voicemailomtp/mail/utility/CountingOutputStream.java b/java/com/android/voicemailomtp/mail/utility/CountingOutputStream.java
deleted file mode 100644
index fdf81d44a..000000000
--- a/java/com/android/voicemailomtp/mail/utility/CountingOutputStream.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2015 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.voicemailomtp.mail.utility;
-
-import java.io.IOException;
-import java.io.OutputStream;
-
-/**
- * A simple pass-thru OutputStream that also counts how many bytes are written to it and
- * makes that count available to callers.
- */
-public class CountingOutputStream extends OutputStream {
- private long mCount;
- private final OutputStream mOutputStream;
-
- public CountingOutputStream(OutputStream outputStream) {
- mOutputStream = outputStream;
- }
-
- public long getCount() {
- return mCount;
- }
-
- @Override
- public void write(byte[] buffer, int offset, int count) throws IOException {
- mOutputStream.write(buffer, offset, count);
- mCount += count;
- }
-
- @Override
- public void write(int oneByte) throws IOException {
- mOutputStream.write(oneByte);
- mCount++;
- }
-} \ No newline at end of file
diff --git a/java/com/android/voicemailomtp/mail/utility/EOLConvertingOutputStream.java b/java/com/android/voicemailomtp/mail/utility/EOLConvertingOutputStream.java
deleted file mode 100644
index 5b93a92ab..000000000
--- a/java/com/android/voicemailomtp/mail/utility/EOLConvertingOutputStream.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2015 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.voicemailomtp.mail.utility;
-
-import java.io.FilterOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-
-public class EOLConvertingOutputStream extends FilterOutputStream {
- int lastChar;
-
- public EOLConvertingOutputStream(OutputStream out) {
- super(out);
- }
-
- @Override
- public void write(int oneByte) throws IOException {
- if (oneByte == '\n') {
- if (lastChar != '\r') {
- super.write('\r');
- }
- }
- super.write(oneByte);
- lastChar = oneByte;
- }
-
- @Override
- public void flush() throws IOException {
- if (lastChar == '\r') {
- super.write('\n');
- lastChar = '\n';
- }
- super.flush();
- }
-} \ No newline at end of file
diff --git a/java/com/android/voicemailomtp/mail/utils/LogUtils.java b/java/com/android/voicemailomtp/mail/utils/LogUtils.java
deleted file mode 100644
index a213a835e..000000000
--- a/java/com/android/voicemailomtp/mail/utils/LogUtils.java
+++ /dev/null
@@ -1,413 +0,0 @@
-/**
- * Copyright (c) 2015 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.voicemailomtp.mail.utils;
-
-import android.net.Uri;
-import android.support.annotation.VisibleForTesting;
-import android.text.TextUtils;
-import android.util.Log;
-import com.android.voicemailomtp.VvmLog;
-import java.util.List;
-import java.util.regex.Pattern;
-
-public class LogUtils {
- public static final String TAG = "Email Log";
-
- // "GMT" + "+" or "-" + 4 digits
- private static final Pattern DATE_CLEANUP_PATTERN_WRONG_TIMEZONE =
- Pattern.compile("GMT([-+]\\d{4})$");
-
- private static final String ACCOUNT_PREFIX = "account:";
-
- /**
- * Priority constant for the println method; use LogUtils.v.
- */
- public static final int VERBOSE = Log.VERBOSE;
-
- /**
- * Priority constant for the println method; use LogUtils.d.
- */
- public static final int DEBUG = Log.DEBUG;
-
- /**
- * Priority constant for the println method; use LogUtils.i.
- */
- public static final int INFO = Log.INFO;
-
- /**
- * Priority constant for the println method; use LogUtils.w.
- */
- public static final int WARN = Log.WARN;
-
- /**
- * Priority constant for the println method; use LogUtils.e.
- */
- public static final int ERROR = Log.ERROR;
-
- /**
- * Used to enable/disable logging that we don't want included in production releases. This should
- * be set to DEBUG for production releases, and VERBOSE for internal builds.
- */
- private static final int MAX_ENABLED_LOG_LEVEL = DEBUG;
-
- private static Boolean sDebugLoggingEnabledForTests = null;
-
- /**
- * Enable debug logging for unit tests.
- */
- @VisibleForTesting
- public static void setDebugLoggingEnabledForTests(boolean enabled) {
- setDebugLoggingEnabledForTestsInternal(enabled);
- }
-
- protected static void setDebugLoggingEnabledForTestsInternal(boolean enabled) {
- sDebugLoggingEnabledForTests = Boolean.valueOf(enabled);
- }
-
- /**
- * Returns true if the build configuration prevents debug logging.
- */
- @VisibleForTesting
- public static boolean buildPreventsDebugLogging() {
- return MAX_ENABLED_LOG_LEVEL > VERBOSE;
- }
-
- /**
- * Returns a boolean indicating whether debug logging is enabled.
- */
- protected static boolean isDebugLoggingEnabled(String tag) {
- if (buildPreventsDebugLogging()) {
- return false;
- }
- if (sDebugLoggingEnabledForTests != null) {
- return sDebugLoggingEnabledForTests.booleanValue();
- }
- return Log.isLoggable(tag, Log.DEBUG) || Log.isLoggable(TAG, Log.DEBUG);
- }
-
- /**
- * Returns a String for the specified content provider uri. This will do
- * sanitation of the uri to remove PII if debug logging is not enabled.
- */
- public static String contentUriToString(final Uri uri) {
- return contentUriToString(TAG, uri);
- }
-
- /**
- * Returns a String for the specified content provider uri. This will do
- * sanitation of the uri to remove PII if debug logging is not enabled.
- */
- public static String contentUriToString(String tag, Uri uri) {
- if (isDebugLoggingEnabled(tag)) {
- // Debug logging has been enabled, so log the uri as is
- return uri.toString();
- } else {
- // Debug logging is not enabled, we want to remove the email address from the uri.
- List<String> pathSegments = uri.getPathSegments();
-
- Uri.Builder builder = new Uri.Builder()
- .scheme(uri.getScheme())
- .authority(uri.getAuthority())
- .query(uri.getQuery())
- .fragment(uri.getFragment());
-
- // This assumes that the first path segment is the account
- final String account = pathSegments.get(0);
-
- builder = builder.appendPath(sanitizeAccountName(account));
- for (int i = 1; i < pathSegments.size(); i++) {
- builder.appendPath(pathSegments.get(i));
- }
- return builder.toString();
- }
- }
-
- /**
- * Sanitizes an account name. If debug logging is not enabled, a sanitized name
- * is returned.
- */
- public static String sanitizeAccountName(String accountName) {
- if (TextUtils.isEmpty(accountName)) {
- return "";
- }
-
- return ACCOUNT_PREFIX + sanitizeName(TAG, accountName);
- }
-
- public static String sanitizeName(final String tag, final String name) {
- if (TextUtils.isEmpty(name)) {
- return "";
- }
-
- if (isDebugLoggingEnabled(tag)) {
- return name;
- }
-
- return String.valueOf(name.hashCode());
- }
-
- /**
- * Checks to see whether or not a log for the specified tag is loggable at the specified level.
- */
- public static boolean isLoggable(String tag, int level) {
- if (MAX_ENABLED_LOG_LEVEL > level) {
- return false;
- }
- return Log.isLoggable(tag, level) || Log.isLoggable(TAG, level);
- }
-
- /**
- * Send a {@link #VERBOSE} log message.
- * @param tag Used to identify the source of a log message. It usually identifies
- * the class or activity where the log call occurs.
- * @param format the format string (see {@link java.util.Formatter#format})
- * @param args
- * the list of arguments passed to the formatter. If there are
- * more arguments than required by {@code format},
- * additional arguments are ignored.
- */
- public static int v(String tag, String format, Object... args) {
- if (isLoggable(tag, VERBOSE)) {
- return VvmLog.v(tag, String.format(format, args));
- }
- return 0;
- }
-
- /**
- * Send a {@link #VERBOSE} log message.
- * @param tag Used to identify the source of a log message. It usually identifies
- * the class or activity where the log call occurs.
- * @param tr An exception to log
- * @param format the format string (see {@link java.util.Formatter#format})
- * @param args
- * the list of arguments passed to the formatter. If there are
- * more arguments than required by {@code format},
- * additional arguments are ignored.
- */
- public static int v(String tag, Throwable tr, String format, Object... args) {
- if (isLoggable(tag, VERBOSE)) {
- return VvmLog.v(tag, String.format(format, args), tr);
- }
- return 0;
- }
-
- /**
- * Send a {@link #DEBUG} log message.
- * @param tag Used to identify the source of a log message. It usually identifies
- * the class or activity where the log call occurs.
- * @param format the format string (see {@link java.util.Formatter#format})
- * @param args
- * the list of arguments passed to the formatter. If there are
- * more arguments than required by {@code format},
- * additional arguments are ignored.
- */
- public static int d(String tag, String format, Object... args) {
- if (isLoggable(tag, DEBUG)) {
- return VvmLog.d(tag, String.format(format, args));
- }
- return 0;
- }
-
- /**
- * Send a {@link #DEBUG} log message.
- * @param tag Used to identify the source of a log message. It usually identifies
- * the class or activity where the log call occurs.
- * @param tr An exception to log
- * @param format the format string (see {@link java.util.Formatter#format})
- * @param args
- * the list of arguments passed to the formatter. If there are
- * more arguments than required by {@code format},
- * additional arguments are ignored.
- */
- public static int d(String tag, Throwable tr, String format, Object... args) {
- if (isLoggable(tag, DEBUG)) {
- return VvmLog.d(tag, String.format(format, args), tr);
- }
- return 0;
- }
-
- /**
- * Send a {@link #INFO} log message.
- * @param tag Used to identify the source of a log message. It usually identifies
- * the class or activity where the log call occurs.
- * @param format the format string (see {@link java.util.Formatter#format})
- * @param args
- * the list of arguments passed to the formatter. If there are
- * more arguments than required by {@code format},
- * additional arguments are ignored.
- */
- public static int i(String tag, String format, Object... args) {
- if (isLoggable(tag, INFO)) {
- return VvmLog.i(tag, String.format(format, args));
- }
- return 0;
- }
-
- /**
- * Send a {@link #INFO} log message.
- * @param tag Used to identify the source of a log message. It usually identifies
- * the class or activity where the log call occurs.
- * @param tr An exception to log
- * @param format the format string (see {@link java.util.Formatter#format})
- * @param args
- * the list of arguments passed to the formatter. If there are
- * more arguments than required by {@code format},
- * additional arguments are ignored.
- */
- public static int i(String tag, Throwable tr, String format, Object... args) {
- if (isLoggable(tag, INFO)) {
- return VvmLog.i(tag, String.format(format, args), tr);
- }
- return 0;
- }
-
- /**
- * Send a {@link #WARN} log message.
- * @param tag Used to identify the source of a log message. It usually identifies
- * the class or activity where the log call occurs.
- * @param format the format string (see {@link java.util.Formatter#format})
- * @param args
- * the list of arguments passed to the formatter. If there are
- * more arguments than required by {@code format},
- * additional arguments are ignored.
- */
- public static int w(String tag, String format, Object... args) {
- if (isLoggable(tag, WARN)) {
- return VvmLog.w(tag, String.format(format, args));
- }
- return 0;
- }
-
- /**
- * Send a {@link #WARN} log message.
- * @param tag Used to identify the source of a log message. It usually identifies
- * the class or activity where the log call occurs.
- * @param tr An exception to log
- * @param format the format string (see {@link java.util.Formatter#format})
- * @param args
- * the list of arguments passed to the formatter. If there are
- * more arguments than required by {@code format},
- * additional arguments are ignored.
- */
- public static int w(String tag, Throwable tr, String format, Object... args) {
- if (isLoggable(tag, WARN)) {
- return VvmLog.w(tag, String.format(format, args), tr);
- }
- return 0;
- }
-
- /**
- * Send a {@link #ERROR} log message.
- * @param tag Used to identify the source of a log message. It usually identifies
- * the class or activity where the log call occurs.
- * @param format the format string (see {@link java.util.Formatter#format})
- * @param args
- * the list of arguments passed to the formatter. If there are
- * more arguments than required by {@code format},
- * additional arguments are ignored.
- */
- public static int e(String tag, String format, Object... args) {
- if (isLoggable(tag, ERROR)) {
- return VvmLog.e(tag, String.format(format, args));
- }
- return 0;
- }
-
- /**
- * Send a {@link #ERROR} log message.
- * @param tag Used to identify the source of a log message. It usually identifies
- * the class or activity where the log call occurs.
- * @param tr An exception to log
- * @param format the format string (see {@link java.util.Formatter#format})
- * @param args
- * the list of arguments passed to the formatter. If there are
- * more arguments than required by {@code format},
- * additional arguments are ignored.
- */
- public static int e(String tag, Throwable tr, String format, Object... args) {
- if (isLoggable(tag, ERROR)) {
- return VvmLog.e(tag, String.format(format, args), tr);
- }
- return 0;
- }
-
- /**
- * What a Terrible Failure: Report a condition that should never happen.
- * The error will always be logged at level ASSERT with the call stack.
- * Depending on system configuration, a report may be added to the
- * {@link android.os.DropBoxManager} and/or the process may be terminated
- * immediately with an error dialog.
- * @param tag Used to identify the source of a log message. It usually identifies
- * the class or activity where the log call occurs.
- * @param format the format string (see {@link java.util.Formatter#format})
- * @param args
- * the list of arguments passed to the formatter. If there are
- * more arguments than required by {@code format},
- * additional arguments are ignored.
- */
- public static int wtf(String tag, String format, Object... args) {
- return VvmLog.wtf(tag, String.format(format, args), new Error());
- }
-
- /**
- * What a Terrible Failure: Report a condition that should never happen.
- * The error will always be logged at level ASSERT with the call stack.
- * Depending on system configuration, a report may be added to the
- * {@link android.os.DropBoxManager} and/or the process may be terminated
- * immediately with an error dialog.
- * @param tag Used to identify the source of a log message. It usually identifies
- * the class or activity where the log call occurs.
- * @param tr An exception to log
- * @param format the format string (see {@link java.util.Formatter#format})
- * @param args
- * the list of arguments passed to the formatter. If there are
- * more arguments than required by {@code format},
- * additional arguments are ignored.
- */
- public static int wtf(String tag, Throwable tr, String format, Object... args) {
- return VvmLog.wtf(tag, String.format(format, args), tr);
- }
-
-
- /**
- * Try to make a date MIME(RFC 2822/5322)-compliant.
- *
- * It fixes:
- * - "Thu, 10 Dec 09 15:08:08 GMT-0700" to "Thu, 10 Dec 09 15:08:08 -0700"
- * (4 digit zone value can't be preceded by "GMT")
- * We got a report saying eBay sends a date in this format
- */
- public static String cleanUpMimeDate(String date) {
- if (TextUtils.isEmpty(date)) {
- return date;
- }
- date = DATE_CLEANUP_PATTERN_WRONG_TIMEZONE.matcher(date).replaceFirst("$1");
- return date;
- }
-
-
- public static String byteToHex(int b) {
- return byteToHex(new StringBuilder(), b).toString();
- }
-
- public static StringBuilder byteToHex(StringBuilder sb, int b) {
- b &= 0xFF;
- sb.append("0123456789ABCDEF".charAt(b >> 4));
- sb.append("0123456789ABCDEF".charAt(b & 0xF));
- return sb;
- }
-
-}
diff --git a/java/com/android/voicemailomtp/mail/utils/Utility.java b/java/com/android/voicemailomtp/mail/utils/Utility.java
deleted file mode 100644
index c7286fa64..000000000
--- a/java/com/android/voicemailomtp/mail/utils/Utility.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/**
- * Copyright (c) 2015 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.voicemailomtp.mail.utils;
-
-import java.io.ByteArrayInputStream;
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
-import java.nio.charset.Charset;
-
-/**
- * Simple utility methods used in email functions.
- */
-public class Utility {
- public static final Charset ASCII = Charset.forName("US-ASCII");
-
- public static final String[] EMPTY_STRINGS = new String[0];
-
- /**
- * Returns a concatenated string containing the output of every Object's
- * toString() method, each separated by the given separator character.
- */
- public static String combine(Object[] parts, char separator) {
- if (parts == null) {
- return null;
- }
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < parts.length; i++) {
- sb.append(parts[i].toString());
- if (i < parts.length - 1) {
- sb.append(separator);
- }
- }
- return sb.toString();
- }
-
- /** Converts a String to ASCII bytes */
- public static byte[] toAscii(String s) {
- return encode(ASCII, s);
- }
-
- /** Builds a String from ASCII bytes */
- public static String fromAscii(byte[] b) {
- return decode(ASCII, b);
- }
-
- private static byte[] encode(Charset charset, String s) {
- if (s == null) {
- return null;
- }
- final ByteBuffer buffer = charset.encode(CharBuffer.wrap(s));
- final byte[] bytes = new byte[buffer.limit()];
- buffer.get(bytes);
- return bytes;
- }
-
- private static String decode(Charset charset, byte[] b) {
- if (b == null) {
- return null;
- }
- final CharBuffer cb = charset.decode(ByteBuffer.wrap(b));
- return new String(cb.array(), 0, cb.length());
- }
-
- public static ByteArrayInputStream streamFromAsciiString(String ascii) {
- return new ByteArrayInputStream(toAscii(ascii));
- }
-}