From d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9 Mon Sep 17 00:00:00 2001 From: Eric Erfanian Date: Wed, 15 Mar 2017 14:41:07 -0700 Subject: Update Dialer source from latest green build. * Refactor voicemail component * Add new enriched calling components Test: treehugger, manual aosp testing Change-Id: I521a0f86327d4b42e14d93927c7d613044ed5942 --- java/com/android/voicemailomtp/mail/Address.java | 541 -------------- .../mail/AuthenticationFailedException.java | 33 - .../com/android/voicemailomtp/mail/Base64Body.java | 62 -- java/com/android/voicemailomtp/mail/Body.java | 25 - java/com/android/voicemailomtp/mail/BodyPart.java | 24 - .../mail/CertificateValidationException.java | 29 - .../android/voicemailomtp/mail/FetchProfile.java | 84 --- java/com/android/voicemailomtp/mail/Fetchable.java | 23 - .../voicemailomtp/mail/FixedLengthInputStream.java | 79 --- java/com/android/voicemailomtp/mail/Flag.java | 29 - .../android/voicemailomtp/mail/MailTransport.java | 344 --------- .../android/voicemailomtp/mail/MeetingInfo.java | 29 - java/com/android/voicemailomtp/mail/Message.java | 144 ---- .../voicemailomtp/mail/MessageDateComparator.java | 34 - .../voicemailomtp/mail/MessagingException.java | 139 ---- java/com/android/voicemailomtp/mail/Multipart.java | 62 -- .../android/voicemailomtp/mail/PackedString.java | 175 ----- java/com/android/voicemailomtp/mail/Part.java | 51 -- .../voicemailomtp/mail/PeekableInputStream.java | 80 --- .../android/voicemailomtp/mail/TempDirectory.java | 41 -- .../mail/internet/BinaryTempFileBody.java | 91 --- .../voicemailomtp/mail/internet/MimeBodyPart.java | 207 ------ .../voicemailomtp/mail/internet/MimeHeader.java | 161 ----- .../voicemailomtp/mail/internet/MimeMessage.java | 675 ------------------ .../voicemailomtp/mail/internet/MimeMultipart.java | 112 --- .../voicemailomtp/mail/internet/MimeUtility.java | 416 ----------- .../voicemailomtp/mail/internet/TextBody.java | 63 -- .../voicemailomtp/mail/store/ImapConnection.java | 413 ----------- .../voicemailomtp/mail/store/ImapFolder.java | 784 --------------------- .../voicemailomtp/mail/store/ImapStore.java | 176 ----- .../mail/store/imap/DigestMd5Utils.java | 335 --------- .../mail/store/imap/ImapConstants.java | 144 ---- .../voicemailomtp/mail/store/imap/ImapElement.java | 120 ---- .../voicemailomtp/mail/store/imap/ImapList.java | 235 ------ .../mail/store/imap/ImapMemoryLiteral.java | 76 -- .../mail/store/imap/ImapResponse.java | 158 ----- .../mail/store/imap/ImapResponseParser.java | 432 ------------ .../mail/store/imap/ImapSimpleString.java | 62 -- .../voicemailomtp/mail/store/imap/ImapString.java | 192 ----- .../mail/store/imap/ImapTempFileLiteral.java | 123 ---- .../voicemailomtp/mail/store/imap/ImapUtility.java | 125 ---- .../mail/utility/CountingOutputStream.java | 48 -- .../mail/utility/EOLConvertingOutputStream.java | 48 -- .../android/voicemailomtp/mail/utils/LogUtils.java | 413 ----------- .../android/voicemailomtp/mail/utils/Utility.java | 80 --- 45 files changed, 7717 deletions(-) delete mode 100644 java/com/android/voicemailomtp/mail/Address.java delete mode 100644 java/com/android/voicemailomtp/mail/AuthenticationFailedException.java delete mode 100644 java/com/android/voicemailomtp/mail/Base64Body.java delete mode 100644 java/com/android/voicemailomtp/mail/Body.java delete mode 100644 java/com/android/voicemailomtp/mail/BodyPart.java delete mode 100644 java/com/android/voicemailomtp/mail/CertificateValidationException.java delete mode 100644 java/com/android/voicemailomtp/mail/FetchProfile.java delete mode 100644 java/com/android/voicemailomtp/mail/Fetchable.java delete mode 100644 java/com/android/voicemailomtp/mail/FixedLengthInputStream.java delete mode 100644 java/com/android/voicemailomtp/mail/Flag.java delete mode 100644 java/com/android/voicemailomtp/mail/MailTransport.java delete mode 100644 java/com/android/voicemailomtp/mail/MeetingInfo.java delete mode 100644 java/com/android/voicemailomtp/mail/Message.java delete mode 100644 java/com/android/voicemailomtp/mail/MessageDateComparator.java delete mode 100644 java/com/android/voicemailomtp/mail/MessagingException.java delete mode 100644 java/com/android/voicemailomtp/mail/Multipart.java delete mode 100644 java/com/android/voicemailomtp/mail/PackedString.java delete mode 100644 java/com/android/voicemailomtp/mail/Part.java delete mode 100644 java/com/android/voicemailomtp/mail/PeekableInputStream.java delete mode 100644 java/com/android/voicemailomtp/mail/TempDirectory.java delete mode 100644 java/com/android/voicemailomtp/mail/internet/BinaryTempFileBody.java delete mode 100644 java/com/android/voicemailomtp/mail/internet/MimeBodyPart.java delete mode 100644 java/com/android/voicemailomtp/mail/internet/MimeHeader.java delete mode 100644 java/com/android/voicemailomtp/mail/internet/MimeMessage.java delete mode 100644 java/com/android/voicemailomtp/mail/internet/MimeMultipart.java delete mode 100644 java/com/android/voicemailomtp/mail/internet/MimeUtility.java delete mode 100644 java/com/android/voicemailomtp/mail/internet/TextBody.java delete mode 100644 java/com/android/voicemailomtp/mail/store/ImapConnection.java delete mode 100644 java/com/android/voicemailomtp/mail/store/ImapFolder.java delete mode 100644 java/com/android/voicemailomtp/mail/store/ImapStore.java delete mode 100644 java/com/android/voicemailomtp/mail/store/imap/DigestMd5Utils.java delete mode 100644 java/com/android/voicemailomtp/mail/store/imap/ImapConstants.java delete mode 100644 java/com/android/voicemailomtp/mail/store/imap/ImapElement.java delete mode 100644 java/com/android/voicemailomtp/mail/store/imap/ImapList.java delete mode 100644 java/com/android/voicemailomtp/mail/store/imap/ImapMemoryLiteral.java delete mode 100644 java/com/android/voicemailomtp/mail/store/imap/ImapResponse.java delete mode 100644 java/com/android/voicemailomtp/mail/store/imap/ImapResponseParser.java delete mode 100644 java/com/android/voicemailomtp/mail/store/imap/ImapSimpleString.java delete mode 100644 java/com/android/voicemailomtp/mail/store/imap/ImapString.java delete mode 100644 java/com/android/voicemailomtp/mail/store/imap/ImapTempFileLiteral.java delete mode 100644 java/com/android/voicemailomtp/mail/store/imap/ImapUtility.java delete mode 100644 java/com/android/voicemailomtp/mail/utility/CountingOutputStream.java delete mode 100644 java/com/android/voicemailomtp/mail/utility/EOLConvertingOutputStream.java delete mode 100644 java/com/android/voicemailomtp/mail/utils/LogUtils.java delete mode 100644 java/com/android/voicemailomtp/mail/utils/Utility.java (limited to 'java/com/android/voicemailomtp/mail') 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"
(comment) - * "name"
- * name
- * 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
addresses = new ArrayList
(); - 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 addressList - */ - @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
addresses = new ArrayList
(); - 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
CREATOR = new Creator
() { - @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; - -/** - *
- * 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.
- * 
- */ -public class FetchProfile extends ArrayList { - /** - * 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 socketAddresses = new ArrayList(); - - 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. - * - *

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 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 getFlagSet() { - if (mFlags == null) { - mFlags = new HashSet(); - } - 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 { - @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 mParts = new ArrayList(); - - 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 mExploded; - private static final HashMap EMPTY_MAP = new HashMap(); - - /** - * 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 unpack() { - if (mExploded == null) { - mExploded = explode(mString); - } - return new HashMap(mExploded); - } - - /** - * Read out all values into a map. - */ - private static HashMap explode(String packed) { - if (packed == null || packed.length() == 0) { - return EMPTY_MAP; - } - HashMap map = new HashMap(); - - 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 mMap; - - /** - * Create a builder that's empty (for filling) - */ - public Builder() { - mMap = new HashMap(); - } - - /** - * 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 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 mFields = new ArrayList(); - - 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 values = new ArrayList(); - 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 removeFields = new ArrayList(); - 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 stack = new Stack(); - - 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 RFC - * 2047 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 viewables, - ArrayList 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 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 responses = executeSimpleCommand( - ImapConstants.AUTHENTICATE + " " + ImapConstants.AUTH_DIGEST_MD5); - String decodedChallenge = decodeBase64(responses.get(0).getStringOrEmpty(0).getString()); - - Map 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 responses = executeSimpleCommand(ImapConstants.CAPABILITY); - mCapabilities.clear(); - Set 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 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 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 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 getCommandResponses() - throws IOException, MessagingException { - final List responses = new ArrayList(); - 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 responses) { - // S: * SEARCH 2 3 6 - final ArrayList uids = new ArrayList(); - 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 messages = new ArrayList(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 messageMap = new HashMap(); - 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 fetchFields = new LinkedHashSet(); - - 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 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 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 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 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 parseDigestMessage(String message) throws MessagingException { - Map 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 mResult = new ArrayMap<>(); - - public DigestMessageParser(String message) { - mMessage = message; - } - - @Nullable - public Map 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. - * - *

Class hierarchy: - *

- * ImapElement
- *   |
- *   |-- ImapElement.NONE (for 'index out of range')
- *   |
- *   |-- ImapList (isList() == true)
- *   |   |
- *   |   |-- ImapList.EMPTY
- *   |   |
- *   |   --- ImapResponse
- *   |
- *   --- ImapString (isString() == true)
- *       |
- *       |-- ImapString.EMPTY
- *       |
- *       |-- ImapSimpleString
- *       |
- *       |-- ImapMemoryLiteral
- *       |
- *       --- ImapTempFileLiteral
- * 
- */ -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 mList = new ArrayList(); - - /* 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 mResponsesToDestroy = new ArrayList(); - - /** - * 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. - * - *

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 byeExpected 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 = / "\" 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. - *

-     * sequence-number = nz-number / "*"
-     * sequence-range  = sequence-number ":" sequence-number
-     * sequence-set    = (sequence-number / sequence-range) *("," sequence-set)
-     * 
- */ - public static String[] getImapSequenceValues(String set) { - ArrayList list = new ArrayList(); - 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. - *
-     * sequence-number = nz-number / "*"
-     * sequence-range  = sequence-number ":" sequence-number
-     * sequence-set    = (sequence-number / sequence-range) *("," sequence-set)
-     * 
- */ - public static String[] getImapRangeValues(String range) { - ArrayList list = new ArrayList(); - 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 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)); - } -} -- cgit v1.2.3