Merge "Support IMAP search in UTF-8, in addition to US-ASCII"
This commit is contained in:
commit
28a48b6001
@ -16,6 +16,9 @@
|
||||
|
||||
package com.android.email.mail.store;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.email.Email;
|
||||
import com.android.email.mail.Transport;
|
||||
import com.android.email.mail.store.ImapStore.ImapException;
|
||||
@ -31,9 +34,6 @@ import com.android.emailcommon.mail.AuthenticationFailedException;
|
||||
import com.android.emailcommon.mail.CertificateValidationException;
|
||||
import com.android.emailcommon.mail.MessagingException;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@ -248,14 +248,53 @@ class ImapConnection {
|
||||
return tag;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send a single, complex command to the server. The command will be preceded by an IMAP
|
||||
* command tag and followed by \r\n (caller need not supply them). After each piece of the
|
||||
* command, a response will be read which MUST be a continuation request.
|
||||
*
|
||||
* @param commands An array of Strings comprising the command to be sent to the server
|
||||
* @return Returns the command tag that was sent
|
||||
*/
|
||||
String sendComplexCommand(List<String> commands, boolean sensitive) throws MessagingException,
|
||||
IOException {
|
||||
open();
|
||||
String tag = Integer.toString(mNextCommandTag.incrementAndGet());
|
||||
int len = commands.size();
|
||||
for (int i = 0; i < len; i++) {
|
||||
String commandToSend = commands.get(i);
|
||||
// The first part of the command gets the tag
|
||||
if (i == 0) {
|
||||
commandToSend = tag + " " + commandToSend;
|
||||
} else {
|
||||
// Otherwise, read the response from the previous part of the command
|
||||
ImapResponse response = readResponse();
|
||||
// If it isn't a continuation request, that's an error
|
||||
if (!response.isContinuationRequest()) {
|
||||
throw new MessagingException("Expected continuation request");
|
||||
}
|
||||
}
|
||||
// Send the command
|
||||
mTransport.writeLine(commandToSend, null);
|
||||
mDiscourse.addSentCommand(sensitive ? IMAP_REDACTED_LOG : commandToSend);
|
||||
}
|
||||
return tag;
|
||||
}
|
||||
|
||||
List<ImapResponse> executeSimpleCommand(String command) throws IOException,
|
||||
MessagingException {
|
||||
return executeSimpleCommand(command, false);
|
||||
}
|
||||
|
||||
List<ImapResponse> executeSimpleCommand(String command, boolean sensitive)
|
||||
throws IOException, MessagingException {
|
||||
String tag = sendCommand(command, sensitive);
|
||||
/**
|
||||
* Read and return all of the responses from the most recent command sent to the server
|
||||
*
|
||||
* @return a list of ImapResponses
|
||||
* @throws IOException
|
||||
* @throws MessagingException
|
||||
*/
|
||||
List<ImapResponse> getCommandResponses() throws IOException, MessagingException {
|
||||
ArrayList<ImapResponse> responses = new ArrayList<ImapResponse>();
|
||||
ImapResponse response;
|
||||
do {
|
||||
@ -271,6 +310,38 @@ class ImapConnection {
|
||||
return responses;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
List<ImapResponse> executeSimpleCommand(String command, boolean sensitive)
|
||||
throws IOException, MessagingException {
|
||||
sendCommand(command, sensitive);
|
||||
return getCommandResponses();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a complex command at the server, a complex command being one that must be sent in
|
||||
* multiple lines due to the use of string literals
|
||||
*
|
||||
* @param commands a list of strings that comprise the command to be sent 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
|
||||
*/
|
||||
List<ImapResponse> executeComplexCommand(List<String> commands, boolean sensitive)
|
||||
throws IOException, MessagingException {
|
||||
sendComplexCommand(commands, sensitive);
|
||||
return getCommandResponses();
|
||||
}
|
||||
|
||||
/**
|
||||
* Query server for capabilities.
|
||||
*/
|
||||
|
@ -366,19 +366,7 @@ class ImapFolder extends Folder {
|
||||
throw new Error("ImapStore.delete() not yet implemented");
|
||||
}
|
||||
|
||||
/* package */ String[] searchForUids(String searchCriteria)
|
||||
throws MessagingException {
|
||||
checkOpen();
|
||||
List<ImapResponse> responses;
|
||||
try {
|
||||
try {
|
||||
responses = mConnection.executeSimpleCommand(
|
||||
ImapConstants.UID_SEARCH + " " + searchCriteria);
|
||||
} catch (ImapException e) {
|
||||
return Utility.EMPTY_STRINGS; // not found;
|
||||
} catch (IOException ioe) {
|
||||
throw ioExceptionHandler(mConnection, ioe);
|
||||
}
|
||||
String[] getSearchUids(List<ImapResponse> responses) {
|
||||
// S: * SEARCH 2 3 6
|
||||
final ArrayList<String> uids = new ArrayList<String>();
|
||||
for (ImapResponse response : responses) {
|
||||
@ -394,6 +382,21 @@ class ImapFolder extends Folder {
|
||||
}
|
||||
}
|
||||
return uids.toArray(Utility.EMPTY_STRINGS);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
String[] searchForUids(String searchCriteria) throws MessagingException {
|
||||
checkOpen();
|
||||
try {
|
||||
try {
|
||||
String command = ImapConstants.UID_SEARCH + " " + searchCriteria;
|
||||
return getSearchUids(mConnection.executeSimpleCommand(command));
|
||||
} catch (ImapException e) {
|
||||
Log.d(Logging.LOG_TAG, "ImapException in search: " + searchCriteria);
|
||||
return Utility.EMPTY_STRINGS; // not found;
|
||||
} catch (IOException ioe) {
|
||||
throw ioExceptionHandler(mConnection, ioe);
|
||||
}
|
||||
} finally {
|
||||
destroyResponses();
|
||||
}
|
||||
@ -413,29 +416,58 @@ class ImapFolder extends Folder {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve messages based on search parameters. We search FROM, TO, CC, SUBJECT, and BODY
|
||||
* We send: SEARCH OR FROM "foo" (OR TO "foo" (OR CC "foo" (OR SUBJECT "foo" BODY "foo")))
|
||||
* TODO: Properly quote the filter
|
||||
* We send: SEARCH OR FROM "foo" (OR TO "foo" (OR CC "foo" (OR SUBJECT "foo" BODY "foo"))), but
|
||||
* with the additional CHARSET argument and sending "foo" as a literal (e.g. {3}<CRLF>foo}
|
||||
*/
|
||||
@Override
|
||||
@VisibleForTesting
|
||||
public Message[] getMessages(SearchParams params, MessageRetrievalListener listener)
|
||||
throws MessagingException {
|
||||
List<String> commands = new ArrayList<String>();
|
||||
String filter = params.mFilter;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("OR FROM \"");
|
||||
sb.append(filter);
|
||||
sb.append("\" (OR TO \"");
|
||||
sb.append(filter);
|
||||
sb.append("\" (OR CC \"");
|
||||
sb.append(filter);
|
||||
sb.append("\" (OR SUBJECT \"");
|
||||
sb.append(filter);
|
||||
sb.append("\" BODY \"");
|
||||
sb.append(filter);
|
||||
sb.append("\")))");
|
||||
return getMessagesInternal(searchForUids(sb.toString()), listener);
|
||||
// All servers MUST accept US-ASCII, so we'll send this as the CHARSET unless we're really
|
||||
// dealing with a string that contains non-ascii characters
|
||||
String charset = "US-ASCII";
|
||||
if (!isAsciiString(filter)) {
|
||||
charset = "UTF-8";
|
||||
}
|
||||
// This is the length of the string in octets (bytes), formatted as a string literal {n}
|
||||
String octetLength = "{" + filter.getBytes().length + "}";
|
||||
// Break the command up into pieces ending with the string literal length
|
||||
commands.add(ImapConstants.UID_SEARCH + " CHARSET " + charset + " OR FROM " + octetLength);
|
||||
commands.add(filter + " (OR TO " + octetLength);
|
||||
commands.add(filter + " (OR CC " + octetLength);
|
||||
commands.add(filter + " (OR SUBJECT " + octetLength);
|
||||
commands.add(filter + " BODY " + octetLength);
|
||||
commands.add(filter + ")))");
|
||||
return getMessagesInternal(complexSearchForUids(commands), listener);
|
||||
}
|
||||
|
||||
/* package */ String[] complexSearchForUids(List<String> commands) throws MessagingException {
|
||||
checkOpen();
|
||||
try {
|
||||
try {
|
||||
return getSearchUids(mConnection.executeComplexCommand(commands, false));
|
||||
} catch (ImapException e) {
|
||||
return Utility.EMPTY_STRINGS; // not found;
|
||||
} catch (IOException ioe) {
|
||||
throw ioExceptionHandler(mConnection, ioe);
|
||||
}
|
||||
} finally {
|
||||
destroyResponses();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
Loading…
Reference in New Issue
Block a user