From 0a6920c63e83f659006bd0d92e61b27888d9fec1 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 18 Dec 2014 11:24:43 +0100 Subject: [PATCH] Refactor IMAP code + tests --- .../k9/mail/store/imap/ImapConnection.java | 106 +++------------ .../k9/mail/store/imap/ImapException.java | 13 ++ .../fsck/k9/mail/store/imap/ImapResponse.java | 26 +++- .../mail/store/imap/ImapResponseCallback.java | 24 ++++ .../mail/store/imap/ImapResponseParser.java | 121 +++++++++++++----- .../fsck/k9/mail/store/imap/ImapStore.java | 60 ++++----- .../k9/mail/store/imap/UntaggedHandler.java | 5 + .../fsck/k9/mail/store/imap/ImapListTest.java | 37 ++++++ .../store/imap/ImapResponseParserTest.java | 108 ++++++++++++++++ .../store/imap/ImapResponseParserTest.java | 95 -------------- 10 files changed, 341 insertions(+), 254 deletions(-) create mode 100644 src/com/fsck/k9/mail/store/imap/ImapException.java create mode 100644 src/com/fsck/k9/mail/store/imap/ImapResponseCallback.java create mode 100644 src/com/fsck/k9/mail/store/imap/UntaggedHandler.java create mode 100644 tests-on-jvm/src/com/fsck/k9/mail/store/imap/ImapListTest.java create mode 100644 tests-on-jvm/src/com/fsck/k9/mail/store/imap/ImapResponseParserTest.java delete mode 100644 tests/src/com/fsck/k9/mail/store/imap/ImapResponseParserTest.java diff --git a/src/com/fsck/k9/mail/store/imap/ImapConnection.java b/src/com/fsck/k9/mail/store/imap/ImapConnection.java index e37f6a69d..f3d20a399 100644 --- a/src/com/fsck/k9/mail/store/imap/ImapConnection.java +++ b/src/com/fsck/k9/mail/store/imap/ImapConnection.java @@ -33,9 +33,7 @@ import java.net.SocketException; import java.security.GeneralSecurityException; import java.security.Security; import java.security.cert.CertificateException; -import java.util.ArrayList; import java.util.HashSet; -import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Locale; @@ -84,41 +82,6 @@ class ImapConnection { return "conn" + hashCode(); } - private List receiveCapabilities(List responses) { - for (ImapResponse response : responses) { - ImapList capabilityList = null; - if (!response.isEmpty() && ImapResponseParser.equalsIgnoreCase(response.get(0), "OK")) { - for (Object thisPart : response) { - if (thisPart instanceof ImapList) { - ImapList thisList = (ImapList)thisPart; - if (ImapResponseParser.equalsIgnoreCase(thisList.get(0), ImapCommands.CAPABILITY_CAPABILITY)) { - capabilityList = thisList; - break; - } - } - } - } else if (response.mTag == null) { - capabilityList = response; - } - - if (capabilityList != null && !capabilityList.isEmpty() && - ImapResponseParser.equalsIgnoreCase(capabilityList.get(0), ImapCommands.CAPABILITY_CAPABILITY)) { - if (K9MailLib.isDebug()) { - Log.d(LOG_TAG, "Saving " + capabilityList.size() + " capabilities for " + getLogId()); - } - for (Object capability : capabilityList) { - if (capability instanceof String) { -// if (K9MailLib.isDebug()) -// { -// Log.v(LOG_TAG, "Saving capability '" + capability + "' for " + getLogId()); -// } - capabilities.add(((String)capability).toUpperCase(Locale.US)); - } - } - } - } - return responses; - } public void open() throws IOException, MessagingException { if (isOpen()) { @@ -395,6 +358,14 @@ class ImapConnection { } } + private List receiveCapabilities(List responses) { + capabilities = ImapResponseParser.parseCapabilities(responses); + if (K9MailLib.isDebug()) { + Log.d(LOG_TAG, "Saving " + capabilities + " capabilities for " + getLogId()); + } + return responses; + } + protected void login() throws IOException, MessagingException { /* * Use quoted strings which permit spaces and quotes. (Using IMAP @@ -412,7 +383,7 @@ class ImapConnection { try { receiveCapabilities(executeSimpleCommand( String.format("LOGIN \"%s\" \"%s\"", username, password), true)); - } catch (ImapStore.ImapException e) { + } catch (ImapException e) { throw new AuthenticationFailedException(e.getMessage()); } } @@ -433,7 +404,7 @@ class ImapConnection { mOut.write('\n'); mOut.flush(); try { - receiveCapabilities(readStatusResponse(tag, command, null)); + receiveCapabilities(mParser.readStatusResponse(tag, command, getLogId(), null)); } catch (MessagingException e) { throw new AuthenticationFailedException(e.getMessage()); } @@ -449,7 +420,7 @@ class ImapConnection { mOut.write('\n'); mOut.flush(); try { - receiveCapabilities(readStatusResponse(tag, command, null)); + receiveCapabilities(mParser.readStatusResponse(tag, command, getLogId(), null)); } catch (MessagingException e) { throw new AuthenticationFailedException(e.getMessage()); } @@ -460,7 +431,7 @@ class ImapConnection { receiveCapabilities(executeSimpleCommand( String.format("AUTHENTICATE EXTERNAL %s", Base64.encode(mSettings.getUsername())), false)); - } catch (ImapStore.ImapException e) { + } catch (ImapException e) { /* * Provide notification to the user of a problem authenticating * using client certificates. We don't use an @@ -477,8 +448,8 @@ class ImapConnection { ImapResponse response; do { response = readResponse(); - if (response.mTag != null) { - if (response.mTag.equalsIgnoreCase(tag)) { + if (response.getTag() != null) { + if (response.getTag().equalsIgnoreCase(tag)) { throw new MessagingException( "Command continuation aborted: " + response); } else { @@ -487,43 +458,10 @@ class ImapConnection { + response + " for " + getLogId()); } } - } while (!response.mCommandContinuationRequested); + } while (!response.isContinuationRequested()); return response; } - protected List readStatusResponse(String tag, - String commandToLog, ImapStore.UntaggedHandler untaggedHandler) - throws IOException, MessagingException { - List responses = new ArrayList(); - ImapResponse response; - do { - response = mParser.readResponse(); - if (K9MailLib.isDebug() && DEBUG_PROTOCOL_IMAP) - Log.v(LOG_TAG, getLogId() + "<<<" + response); - - if (response.mTag != null && !response.mTag.equalsIgnoreCase(tag)) { - Log.w(LOG_TAG, "After sending tag " + tag + ", got tag response from previous command " + response + " for " + getLogId()); - Iterator iter = responses.iterator(); - while (iter.hasNext()) { - ImapResponse delResponse = iter.next(); - if (delResponse.mTag != null || delResponse.size() < 2 - || (!ImapResponseParser.equalsIgnoreCase(delResponse.get(1), "EXISTS") && !ImapResponseParser.equalsIgnoreCase(delResponse.get(1), "EXPUNGE"))) { - iter.remove(); - } - } - response.mTag = null; - continue; - } - if (untaggedHandler != null) { - untaggedHandler.handleAsyncUntaggedResponse(response); - } - responses.add(response); - } while (response.mTag == null); - if (response.size() < 1 || !ImapResponseParser.equalsIgnoreCase(response.get(0), "OK")) { - throw new ImapStore.ImapException("Command: " + commandToLog + "; response: " + response.toString(), response.getAlertText()); - } - return responses; - } protected void setReadTimeout(int millis) throws SocketException { Socket sock = mSocket; @@ -567,7 +505,7 @@ class ImapConnection { return readResponse(null); } - public ImapResponse readResponse(ImapResponseParser.IImapResponseCallback callback) throws IOException { + public ImapResponse readResponse(ImapResponseCallback callback) throws IOException { try { ImapResponse response = mParser.readResponse(callback); if (K9MailLib.isDebug() && DEBUG_PROTOCOL_IMAP) @@ -591,8 +529,7 @@ class ImapConnection { } - public String sendCommand(String command, boolean sensitive) - throws MessagingException, IOException { + public String sendCommand(String command, boolean sensitive) throws MessagingException, IOException { try { open(); String tag = Integer.toString(mNextCommandTag++); @@ -613,7 +550,7 @@ class ImapConnection { } catch (IOException ioe) { close(); throw ioe; - } catch (ImapStore.ImapException ie) { + } catch (ImapException ie) { close(); throw ie; } catch (MessagingException me) { @@ -632,21 +569,18 @@ class ImapConnection { return executeSimpleCommand(command, sensitive, null); } - public List executeSimpleCommand(String command, boolean sensitive, ImapStore.UntaggedHandler untaggedHandler) + public List executeSimpleCommand(String command, boolean sensitive, UntaggedHandler untaggedHandler) throws IOException, MessagingException { String commandToLog = command; if (sensitive && !K9MailLib.isDebugSensitive()) { commandToLog = "*sensitive*"; } - - //if (K9MailLib.isDebug()) // Log.v(LOG_TAG, "Sending IMAP command " + commandToLog + " on connection " + getLogId()); - String tag = sendCommand(command, sensitive); //if (K9MailLib.isDebug()) // Log.v(LOG_TAG, "Sent IMAP command " + commandToLog + " with tag " + tag + " for " + getLogId()); - return readStatusResponse(tag, commandToLog, untaggedHandler); + return mParser.readStatusResponse(tag, commandToLog, getLogId(), untaggedHandler); } } diff --git a/src/com/fsck/k9/mail/store/imap/ImapException.java b/src/com/fsck/k9/mail/store/imap/ImapException.java new file mode 100644 index 000000000..3a3040803 --- /dev/null +++ b/src/com/fsck/k9/mail/store/imap/ImapException.java @@ -0,0 +1,13 @@ +package com.fsck.k9.mail.store.imap; + +import com.fsck.k9.mail.MessagingException; + +class ImapException extends MessagingException { + private static final long serialVersionUID = 3725007182205882394L; + private final String mAlertText; + + public ImapException(String message, String alertText) { + super(message, true); + this.mAlertText = alertText; + } +} diff --git a/src/com/fsck/k9/mail/store/imap/ImapResponse.java b/src/com/fsck/k9/mail/store/imap/ImapResponse.java index 848aa021b..85d0747d1 100644 --- a/src/com/fsck/k9/mail/store/imap/ImapResponse.java +++ b/src/com/fsck/k9/mail/store/imap/ImapResponse.java @@ -11,20 +11,32 @@ package com.fsck.k9.mail.store.imap; public class ImapResponse extends ImapList { private static final long serialVersionUID = 6886458551615975669L; - private ImapResponseParser.IImapResponseCallback mCallback; + private ImapResponseCallback mCallback; - boolean mCommandContinuationRequested; - String mTag; + private final boolean mCommandContinuationRequested; + private final String mTag; - public ImapResponse(ImapResponseParser.IImapResponseCallback mCallback) { - this.mCallback = mCallback; + + public ImapResponse(ImapResponseCallback callback, + boolean mCommandContinuationRequested, String mTag) { + this.mCallback = callback; + this.mCommandContinuationRequested = mCommandContinuationRequested; + this.mTag = mTag; } - public ImapResponseParser.IImapResponseCallback getCallback() { + public boolean isContinuationRequested() { + return mCommandContinuationRequested; + } + + public String getTag() { + return mTag; + } + + public ImapResponseCallback getCallback() { return mCallback; } - public void setCallback(ImapResponseParser.IImapResponseCallback mCallback) { + public void setCallback(ImapResponseCallback mCallback) { this.mCallback = mCallback; } diff --git a/src/com/fsck/k9/mail/store/imap/ImapResponseCallback.java b/src/com/fsck/k9/mail/store/imap/ImapResponseCallback.java new file mode 100644 index 000000000..5453b49d6 --- /dev/null +++ b/src/com/fsck/k9/mail/store/imap/ImapResponseCallback.java @@ -0,0 +1,24 @@ +package com.fsck.k9.mail.store.imap; + +import com.fsck.k9.mail.filter.FixedLengthInputStream; + +public interface ImapResponseCallback { + /** + * Callback method that is called by the parser when a literal string + * is found in an IMAP response. + * + * @param response ImapResponse object with the fields that have been + * parsed up until now (excluding the literal string). + * @param literal FixedLengthInputStream that can be used to access + * the literal string. + * + * @return an Object that will be put in the ImapResponse object at the + * place of the literal string. + * + * @throws java.io.IOException passed-through if thrown by FixedLengthInputStream + * @throws Exception if something goes wrong. Parsing will be resumed + * and the exception will be thrown after the + * complete IMAP response has been parsed. + */ + public Object foundLiteral(ImapResponse response, FixedLengthInputStream literal) throws Exception; +} diff --git a/src/com/fsck/k9/mail/store/imap/ImapResponseParser.java b/src/com/fsck/k9/mail/store/imap/ImapResponseParser.java index 1c92c018a..336fe30f3 100644 --- a/src/com/fsck/k9/mail/store/imap/ImapResponseParser.java +++ b/src/com/fsck/k9/mail/store/imap/ImapResponseParser.java @@ -1,13 +1,25 @@ package com.fsck.k9.mail.store.imap; import android.text.TextUtils; +import android.util.Log; +import com.fsck.k9.mail.K9MailLib; +import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.filter.FixedLengthInputStream; import com.fsck.k9.mail.filter.PeekableInputStream; import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +import static com.fsck.k9.mail.K9MailLib.DEBUG_PROTOCOL_IMAP; +import static com.fsck.k9.mail.K9MailLib.LOG_TAG; +import static com.fsck.k9.mail.store.imap.ImapCommands.CAPABILITY_CAPABILITY; class ImapResponseParser { - private PeekableInputStream mIn; private ImapResponse mResponse; private Exception mException; @@ -24,20 +36,18 @@ class ImapResponseParser { * Reads the next response available on the stream and returns an * ImapResponse object that represents it. */ - public ImapResponse readResponse(IImapResponseCallback callback) throws IOException { + public ImapResponse readResponse(ImapResponseCallback callback) throws IOException { try { - mResponse = new ImapResponse(callback); - mResponse.setCallback(callback); - int ch = mIn.peek(); if (ch == '*') { parseUntaggedResponse(); + mResponse = new ImapResponse(callback, false, null); readTokens(mResponse); } else if (ch == '+') { - mResponse.mCommandContinuationRequested = parseCommandContinuationRequest(); + mResponse = new ImapResponse(callback, parseCommandContinuationRequest(), null); parseResponseText(mResponse); } else { - mResponse.mTag = parseTaggedResponse(); + mResponse = new ImapResponse(callback, false, parseTaggedResponse()); readTokens(mResponse); } @@ -52,6 +62,80 @@ class ImapResponseParser { } } + protected List readStatusResponse(String tag, + String commandToLog, + String logId, + UntaggedHandler untaggedHandler) + throws IOException, MessagingException { + List responses = new ArrayList(); + ImapResponse response; + do { + response = readResponse(); + if (K9MailLib.isDebug() && DEBUG_PROTOCOL_IMAP) { + Log.v(LOG_TAG, logId + "<<<" + response); + } + + if (response.getTag() != null && !response.getTag().equalsIgnoreCase(tag)) { + Log.w(LOG_TAG, "After sending tag " + tag + ", got tag response from previous command " + response + " for " + logId); + + Iterator iter = responses.iterator(); + + while (iter.hasNext()) { + ImapResponse delResponse = iter.next(); + if (delResponse.getTag() != null + || delResponse.size() < 2 + || (!equalsIgnoreCase(delResponse.get(1), "EXISTS") && + !equalsIgnoreCase(delResponse.get(1), "EXPUNGE"))) { + iter.remove(); + } + } + response = null; + continue; + } + if (untaggedHandler != null) { + untaggedHandler.handleAsyncUntaggedResponse(response); + } + responses.add(response); + + } while (response == null || response.getTag() == null); + + if (response.size() < 1 || !equalsIgnoreCase(response.get(0), "OK")) { + throw new ImapException("Command: " + commandToLog + "; response: " + response.toString(), response.getAlertText()); + } + + return responses; + } + + protected static Set parseCapabilities(List responses) { + HashSet capabilities = new HashSet(); + for (ImapResponse response : responses) { + ImapList list = null; + if (!response.isEmpty() && equalsIgnoreCase(response.get(0), "OK")) { + for (Object thisPart : response) { + if (thisPart instanceof ImapList) { + ImapList thisList = (ImapList)thisPart; + if (equalsIgnoreCase(thisList.get(0), CAPABILITY_CAPABILITY)) { + list = thisList; + break; + } + } + } + } else if (response.getTag() == null) { + list = response; + } + + if (list != null && list.size() > 1 && + equalsIgnoreCase(list.get(0), CAPABILITY_CAPABILITY)) { + for (Object capability : list.subList(1, list.size())) { + if (capability instanceof String) { + capabilities.add(((String)capability).toUpperCase(Locale.US)); + } + } + } + } + return capabilities; + } + private void readTokens(ImapResponse response) throws IOException { response.clear(); @@ -184,8 +268,7 @@ class ImapResponseParser { // 3 OK [READ-WRITE] Select completed. private String parseTaggedResponse() throws IOException { - String tag = readStringUntil(' '); - return tag; + return readStringUntil(' '); } private ImapList parseList(ImapList parent) throws IOException { @@ -377,24 +460,4 @@ class ImapResponseParser { } } - public interface IImapResponseCallback { - /** - * Callback method that is called by the parser when a literal string - * is found in an IMAP response. - * - * @param response ImapResponse object with the fields that have been - * parsed up until now (excluding the literal string). - * @param literal FixedLengthInputStream that can be used to access - * the literal string. - * - * @return an Object that will be put in the ImapResponse object at the - * place of the literal string. - * - * @throws IOException passed-through if thrown by FixedLengthInputStream - * @throws Exception if something goes wrong. Parsing will be resumed - * and the exception will be thrown after the - * complete IMAP response has been parsed. - */ - public Object foundLiteral(ImapResponse response, FixedLengthInputStream literal) throws Exception; - } } diff --git a/src/com/fsck/k9/mail/store/imap/ImapStore.java b/src/com/fsck/k9/mail/store/imap/ImapStore.java index 458dfca15..3a3b48a84 100644 --- a/src/com/fsck/k9/mail/store/imap/ImapStore.java +++ b/src/com/fsck/k9/mail/store/imap/ImapStore.java @@ -789,7 +789,7 @@ public class ImapStore extends RemoteStore { Object keyObj = bracketed.get(0); if (keyObj instanceof String) { String key = (String) keyObj; - if (response.mTag != null) { + if (response.getTag() != null) { if ("READ-ONLY".equalsIgnoreCase(key)) { mMode = OPEN_MODE_RO; @@ -1253,7 +1253,7 @@ public class ImapStore extends RemoteStore { List uids = new ArrayList(); List responses = searcher.search(); // for (ImapResponse response : responses) { - if (response.mTag == null) { + if (response.getTag() == null) { if (ImapResponseParser.equalsIgnoreCase(response.get(0), "SEARCH")) { for (int i = 1, count = response.size(); i < count; i++) { uids.add(response.getLong(i)); @@ -1385,7 +1385,7 @@ public class ImapStore extends RemoteStore { ImapResponse response; int messageNumber = 0; - ImapResponseParser.IImapResponseCallback callback = null; + ImapResponseCallback callback = null; if (fp.contains(FetchProfile.Item.BODY) || fp.contains(FetchProfile.Item.BODY_SANE)) { callback = new FetchBodyCallback(messageMap); } @@ -1393,7 +1393,7 @@ public class ImapStore extends RemoteStore { do { response = mConnection.readResponse(callback); - if (response.mTag == null && ImapResponseParser.equalsIgnoreCase(response.get(1), "FETCH")) { + if (response.getTag() == null && ImapResponseParser.equalsIgnoreCase(response.get(1), "FETCH")) { ImapList fetchList = (ImapList)response.getKeyedValue("FETCH"); String uid = fetchList.getKeyedString("UID"); long msgSeq = response.getLong(0); @@ -1444,7 +1444,7 @@ public class ImapStore extends RemoteStore { handleUntaggedResponse(response); } - } while (response.mTag == null); + } while (response.getTag() == null); } catch (IOException ioe) { throw ioExceptionHandler(mConnection, ioe); } @@ -1479,12 +1479,12 @@ public class ImapStore extends RemoteStore { ImapResponse response; int messageNumber = 0; - ImapResponseParser.IImapResponseCallback callback = new FetchPartCallback(part); + ImapResponseCallback callback = new FetchPartCallback(part); do { response = mConnection.readResponse(callback); - if ((response.mTag == null) && + if ((response.getTag() == null) && (ImapResponseParser.equalsIgnoreCase(response.get(1), "FETCH"))) { ImapList fetchList = (ImapList)response.getKeyedValue("FETCH"); String uid = fetchList.getKeyedString("UID"); @@ -1531,7 +1531,7 @@ public class ImapStore extends RemoteStore { handleUntaggedResponse(response); } - } while (response.mTag == null); + } while (response.getTag() == null); } catch (IOException ioe) { throw ioExceptionHandler(mConnection, ioe); } @@ -1643,7 +1643,7 @@ public class ImapStore extends RemoteStore { * @param response */ protected void handleUntaggedResponse(ImapResponse response) { - if (response.mTag == null && response.size() > 1) { + if (response.getTag() == null && response.size() > 1) { if (ImapResponseParser.equalsIgnoreCase(response.get(1), "EXISTS")) { mMessageCount = response.getNumber(0); if (K9MailLib.isDebug()) @@ -1873,14 +1873,14 @@ public class ImapStore extends RemoteStore { do { response = mConnection.readResponse(); handleUntaggedResponse(response); - if (response.mCommandContinuationRequested) { + if (response.isContinuationRequested()) { EOLConvertingOutputStream eolOut = new EOLConvertingOutputStream(mConnection.getOutputStream()); message.writeTo(eolOut); eolOut.write('\r'); eolOut.write('\n'); eolOut.flush(); } - } while (response.mTag == null); + } while (response.getTag() == null); if (response.size() > 1) { /* @@ -1957,7 +1957,7 @@ public class ImapStore extends RemoteStore { executeSimpleCommand( String.format("UID SEARCH HEADER MESSAGE-ID %s", encodeString(messageId))); for (ImapResponse response1 : responses) { - if (response1.mTag == null && ImapResponseParser.equalsIgnoreCase(response1.get(0), "SEARCH") + if (response1.getTag() == null && ImapResponseParser.equalsIgnoreCase(response1.get(0), "SEARCH") && response1.size() > 1) { return response1.getString(1); } @@ -2232,16 +2232,6 @@ public class ImapStore extends RemoteStore { } } - static class ImapException extends MessagingException { - private static final long serialVersionUID = 3725007182205882394L; - private final String mAlertText; - - public ImapException(String message, String alertText) { - super(message, true); - this.mAlertText = alertText; - } - } - public class ImapFolderPusher extends ImapFolder implements UntaggedHandler { private final PushReceiver receiver; private Thread listeningThread = null; @@ -2459,7 +2449,7 @@ public class ImapStore extends RemoteStore { @Override protected void handleUntaggedResponse(ImapResponse response) { - if (response.mTag == null && response.size() > 1) { + if (response.getTag() == null && response.size() > 1) { Object responseType = response.get(1); if (ImapResponseParser.equalsIgnoreCase(responseType, "FETCH") || ImapResponseParser.equalsIgnoreCase(responseType, "EXPUNGE") @@ -2588,7 +2578,7 @@ public class ImapStore extends RemoteStore { protected int processUntaggedResponse(long oldMessageCount, ImapResponse response, List flagSyncMsgSeqs, List removeMsgUids) { super.handleUntaggedResponse(response); int messageCountDelta = 0; - if (response.mTag == null && response.size() > 1) { + if (response.getTag() == null && response.size() > 1) { try { Object responseType = response.get(1); if (ImapResponseParser.equalsIgnoreCase(responseType, "FETCH")) { @@ -2703,7 +2693,7 @@ public class ImapStore extends RemoteStore { Log.e(LOG_TAG, "Exception while sending DONE for " + getLogId(), e); } } else { - if (response.mTag == null) { + if (response.getTag() == null) { if (response.size() > 1) { boolean started = false; Object responseType = response.get(1); @@ -2723,7 +2713,7 @@ public class ImapStore extends RemoteStore { Log.e(LOG_TAG, "Exception while sending DONE for " + getLogId(), e); } } - } else if (response.mCommandContinuationRequested) { + } else if (response.isContinuationRequested()) { if (K9MailLib.isDebug()) Log.d(LOG_TAG, "Idling " + getLogId()); @@ -2812,10 +2802,6 @@ public class ImapStore extends RemoteStore { public void setLastRefresh(long lastRefresh) { this.lastRefresh = lastRefresh; } - - } - public static interface UntaggedHandler { - void handleAsyncUntaggedResponse(ImapResponse respose); } protected static class ImapPushState { @@ -2856,7 +2842,7 @@ public class ImapStore extends RemoteStore { List search() throws IOException, MessagingException; } - private static class FetchBodyCallback implements ImapResponseParser.IImapResponseCallback { + private static class FetchBodyCallback implements ImapResponseCallback { private Map mMessageMap; FetchBodyCallback(Map messageMap) { @@ -2865,8 +2851,8 @@ public class ImapStore extends RemoteStore { @Override public Object foundLiteral(ImapResponse response, - FixedLengthInputStream literal) throws IOException, Exception { - if (response.mTag == null && + FixedLengthInputStream literal) throws MessagingException, IOException { + if (response.getTag() == null && ImapResponseParser.equalsIgnoreCase(response.get(1), "FETCH")) { ImapList fetchList = (ImapList)response.getKeyedValue("FETCH"); String uid = fetchList.getKeyedString("UID"); @@ -2875,13 +2861,13 @@ public class ImapStore extends RemoteStore { message.parse(literal); // Return placeholder object - return Integer.valueOf(1); + return 1; } return null; } } - private static class FetchPartCallback implements ImapResponseParser.IImapResponseCallback { + private static class FetchPartCallback implements ImapResponseCallback { private Part mPart; FetchPartCallback(Part part) { @@ -2890,8 +2876,8 @@ public class ImapStore extends RemoteStore { @Override public Object foundLiteral(ImapResponse response, - FixedLengthInputStream literal) throws IOException, Exception { - if (response.mTag == null && + FixedLengthInputStream literal) throws MessagingException, IOException { + if (response.getTag() == null && ImapResponseParser.equalsIgnoreCase(response.get(1), "FETCH")) { //TODO: check for correct UID diff --git a/src/com/fsck/k9/mail/store/imap/UntaggedHandler.java b/src/com/fsck/k9/mail/store/imap/UntaggedHandler.java new file mode 100644 index 000000000..444d88c3c --- /dev/null +++ b/src/com/fsck/k9/mail/store/imap/UntaggedHandler.java @@ -0,0 +1,5 @@ +package com.fsck.k9.mail.store.imap; + +interface UntaggedHandler { + void handleAsyncUntaggedResponse(ImapResponse response); +} diff --git a/tests-on-jvm/src/com/fsck/k9/mail/store/imap/ImapListTest.java b/tests-on-jvm/src/com/fsck/k9/mail/store/imap/ImapListTest.java new file mode 100644 index 000000000..050a7fb87 --- /dev/null +++ b/tests-on-jvm/src/com/fsck/k9/mail/store/imap/ImapListTest.java @@ -0,0 +1,37 @@ +package com.fsck.k9.mail.store.imap; + +import junit.framework.TestCase; + +import java.io.IOException; + +public class ImapListTest extends TestCase { + public void testImapListMethods() throws IOException { + ImapList list = new ImapList(); + list.add("ONE"); + list.add("TWO"); + list.add("THREE"); + + assertTrue(list.containsKey("ONE")); + assertTrue(list.containsKey("TWO")); + assertFalse(list.containsKey("THREE")); + assertFalse(list.containsKey("nonexistent")); + + assertEquals("TWO", list.getKeyedValue("ONE")); + assertEquals("THREE", list.getKeyedValue("TWO")); + assertNull(list.getKeyedValue("THREE")); + assertNull(list.getKeyedValue("nonexistent")); + + assertEquals(0, list.getKeyIndex("ONE")); + assertEquals(1, list.getKeyIndex("TWO")); + + try { + list.getKeyIndex("THREE"); + fail("IllegalArgumentException should have been thrown"); + } catch (IllegalArgumentException e) { /* do nothing */ } + + try { + list.getKeyIndex("nonexistent"); + fail("IllegalArgumentException should have been thrown"); + } catch (IllegalArgumentException e) { /* do nothing */ } + } +} diff --git a/tests-on-jvm/src/com/fsck/k9/mail/store/imap/ImapResponseParserTest.java b/tests-on-jvm/src/com/fsck/k9/mail/store/imap/ImapResponseParserTest.java new file mode 100644 index 000000000..d9ce8b445 --- /dev/null +++ b/tests-on-jvm/src/com/fsck/k9/mail/store/imap/ImapResponseParserTest.java @@ -0,0 +1,108 @@ +package com.fsck.k9.mail.store.imap; + +import com.fsck.k9.mail.filter.PeekableInputStream; + +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +import static com.fsck.k9.mail.store.imap.ImapResponseParser.parseCapabilities; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class ImapResponseParserTest { + + @Test public void testSimpleOkResponse() throws IOException { + ImapResponseParser parser = createParser("* OK\r\n"); + ImapResponse response = parser.readResponse(); + + assertNotNull(response); + assertEquals(1, response.size()); + assertEquals("OK", response.get(0)); + } + + @Test public void testOkResponseWithText() throws IOException { + ImapResponseParser parser = createParser("* OK Some text here\r\n"); + ImapResponse response = parser.readResponse(); + + assertNotNull(response); + assertEquals(2, response.size()); + assertEquals("OK", response.get(0)); + assertEquals("Some text here", response.get(1)); + } + + @Test public void testOkResponseWithRespTextCode() throws IOException { + ImapResponseParser parser = createParser("* OK [UIDVALIDITY 3857529045]\r\n"); + ImapResponse response = parser.readResponse(); + + assertNotNull(response); + assertEquals(2, response.size()); + assertEquals("OK", response.get(0)); + assertTrue(response.get(1) instanceof ImapList); + + ImapList respTextCode = (ImapList) response.get(1); + assertEquals(2, respTextCode.size()); + assertEquals("UIDVALIDITY", respTextCode.get(0)); + assertEquals("3857529045", respTextCode.get(1)); + } + + @Test public void testOkResponseWithRespTextCodeAndText() throws IOException { + ImapResponseParser parser = createParser("* OK [token1 token2] {x} test [...]\r\n"); + ImapResponse response = parser.readResponse(); + + assertNotNull(response); + assertEquals(3, response.size()); + assertEquals("OK", response.get(0)); + assertTrue(response.get(1) instanceof ImapList); + assertEquals("{x} test [...]", response.get(2)); + + ImapList respTextCode = (ImapList) response.get(1); + assertEquals(2, respTextCode.size()); + assertEquals("token1", respTextCode.get(0)); + assertEquals("token2", respTextCode.get(1)); + } + + + @Test public void testReadStatusResponseWithOKResponse() throws Exception { + ImapResponseParser parser = createParser("* COMMAND BAR BAZ\r\nTAG OK COMMAND completed\r\n"); + List responses = parser.readStatusResponse("TAG", null, null, null); + + assertEquals(2, responses.size()); + assertEquals(asList("COMMAND", "BAR", "BAZ"), responses.get(0)); + assertEquals(asList("OK", "COMMAND completed"), responses.get(1)); + } + + @Test(expected = ImapException.class) + public void testReadStatusResponseWithErrorResponse() throws Exception { + ImapResponseParser parser = createParser("* COMMAND BAR BAZ\r\nTAG ERROR COMMAND errored\r\n"); + parser.readStatusResponse("TAG", null, null, null); + } + + @Test public void testParseCapabilities() throws Exception { + ImapResponse capabilityResponse = new ImapResponse(null, false, null); + capabilityResponse.addAll(Arrays.asList("CAPABILITY", "FOO", "BAR")); + Set capabilities = parseCapabilities(Arrays.asList(capabilityResponse)); + + assertEquals(2, capabilities.size()); + assertTrue(capabilities.contains("FOO")); + assertTrue(capabilities.contains("BAR")); + } + + @Test public void testParseCapabilitiesWithInvalidResponse() throws Exception { + ImapResponse capabilityResponse = new ImapResponse(null, false, null); + capabilityResponse.addAll(Arrays.asList("FOO", "BAZ")); + assertTrue(parseCapabilities(Arrays.asList(capabilityResponse)).isEmpty()); + } + + private ImapResponseParser createParser(String response) { + ByteArrayInputStream in = new ByteArrayInputStream(response.getBytes()); + PeekableInputStream pin = new PeekableInputStream(in); + return new ImapResponseParser(pin); + } +} diff --git a/tests/src/com/fsck/k9/mail/store/imap/ImapResponseParserTest.java b/tests/src/com/fsck/k9/mail/store/imap/ImapResponseParserTest.java deleted file mode 100644 index 377c2bd06..000000000 --- a/tests/src/com/fsck/k9/mail/store/imap/ImapResponseParserTest.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.fsck.k9.mail.store.imap; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import com.fsck.k9.mail.filter.PeekableInputStream; -import junit.framework.TestCase; - -public class ImapResponseParserTest extends TestCase { - - public void testSimpleOkResponse() throws IOException { - ImapResponseParser parser = createParser("* OK\r\n"); - ImapResponse response = parser.readResponse(); - - assertNotNull(response); - assertEquals(1, response.size()); - assertEquals("OK", response.get(0)); - } - - public void testOkResponseWithText() throws IOException { - ImapResponseParser parser = createParser("* OK Some text here\r\n"); - ImapResponse response = parser.readResponse(); - - assertNotNull(response); - assertEquals(2, response.size()); - assertEquals("OK", response.get(0)); - assertEquals("Some text here", response.get(1)); - } - - public void testOkResponseWithRespTextCode() throws IOException { - ImapResponseParser parser = createParser("* OK [UIDVALIDITY 3857529045]\r\n"); - ImapResponse response = parser.readResponse(); - - assertNotNull(response); - assertEquals(2, response.size()); - assertEquals("OK", response.get(0)); - assertTrue(response.get(1) instanceof ImapList); - - ImapList respTextCode = (ImapList) response.get(1); - assertEquals(2, respTextCode.size()); - assertEquals("UIDVALIDITY", respTextCode.get(0)); - assertEquals("3857529045", respTextCode.get(1)); - } - - public void testOkResponseWithRespTextCodeAndText() throws IOException { - ImapResponseParser parser = createParser("* OK [token1 token2] {x} test [...]\r\n"); - ImapResponse response = parser.readResponse(); - - assertNotNull(response); - assertEquals(3, response.size()); - assertEquals("OK", response.get(0)); - assertTrue(response.get(1) instanceof ImapList); - assertEquals("{x} test [...]", response.get(2)); - - ImapList respTextCode = (ImapList) response.get(1); - assertEquals(2, respTextCode.size()); - assertEquals("token1", respTextCode.get(0)); - assertEquals("token2", respTextCode.get(1)); - } - - public void testImapListMethods() throws IOException { - ImapList list = new ImapList(); - list.add("ONE"); - list.add("TWO"); - list.add("THREE"); - - assertTrue(list.containsKey("ONE")); - assertTrue(list.containsKey("TWO")); - assertFalse(list.containsKey("THREE")); - assertFalse(list.containsKey("nonexistent")); - - assertEquals("TWO", list.getKeyedValue("ONE")); - assertEquals("THREE", list.getKeyedValue("TWO")); - assertNull(list.getKeyedValue("THREE")); - assertNull(list.getKeyedValue("nonexistent")); - - assertEquals(0, list.getKeyIndex("ONE")); - assertEquals(1, list.getKeyIndex("TWO")); - - try { - list.getKeyIndex("THREE"); - fail("IllegalArgumentException should have been thrown"); - } catch (IllegalArgumentException e) { /* do nothing */ } - - try { - list.getKeyIndex("nonexistent"); - fail("IllegalArgumentException should have been thrown"); - } catch (IllegalArgumentException e) { /* do nothing */ } - } - - private ImapResponseParser createParser(String response) { - ByteArrayInputStream in = new ByteArrayInputStream(response.getBytes()); - PeekableInputStream pin = new PeekableInputStream(in); - return new ImapResponseParser(pin); - } -}