com.yahoo.imapnio.async.response.ImapResponseMapper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of imapnio.core Show documentation
Show all versions of imapnio.core Show documentation
imapnio component ${project.name}
The newest version!
package com.yahoo.imapnio.async.response;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.mail.Folder;
import com.sun.mail.iap.ParsingException;
import com.sun.mail.iap.Response;
import com.sun.mail.imap.AppendUID;
import com.sun.mail.imap.CopyUID;
import com.sun.mail.imap.protocol.IMAPResponse;
import com.sun.mail.imap.protocol.ListInfo;
import com.sun.mail.imap.protocol.MailboxInfo;
import com.sun.mail.imap.protocol.Status;
import com.sun.mail.imap.protocol.UIDSet;
import com.yahoo.imapnio.async.data.Capability;
import com.yahoo.imapnio.async.data.EnableResult;
import com.yahoo.imapnio.async.data.ExtensionListInfo;
import com.yahoo.imapnio.async.data.ExtensionMailboxInfo;
import com.yahoo.imapnio.async.data.IdResult;
import com.yahoo.imapnio.async.data.ListInfoList;
import com.yahoo.imapnio.async.data.ListStatusResult;
import com.yahoo.imapnio.async.data.SearchResult;
import com.yahoo.imapnio.async.exception.ImapAsyncClientException;
import com.yahoo.imapnio.async.exception.ImapAsyncClientException.FailureType;
/**
* This class parses the IMAP response to the proper IMAP object that SUN supports.
*/
public class ImapResponseMapper {
/** APPENDUID keyword. */
private static final String APPENDUID = "APPENDUID";
/** EQUAL sign. */
private static final String EQUAL = "=";
/** [ char. */
private static final char L_BRACKET = '[';
/** ] char. */
private static final char R_BRACKET = ']';
/** Inner class instance parser. */
private ImapResponseParser parser;
/**
* Initializes a {@link ImapResponseMapper} object.
*/
public ImapResponseMapper() {
parser = new ImapResponseParser();
}
/**
* Method to deserialize IMAPResponse content from given IMAPResponse content String.
*
* @param the object to serialize to
* @param content list of IMAPResponse obtained from server.
* @param valueType class name that this api will convert to.
* @return the serialized object
* @throws ParsingException if underlying input contains invalid content of type for the returned type
* @throws ImapAsyncClientException when target class to covert to is not supported
*/
@SuppressWarnings("unchecked")
@Nonnull
public T readValue(@Nonnull final IMAPResponse[] content, @Nonnull final Class valueType)
throws ImapAsyncClientException, ParsingException {
if (valueType == Capability.class) {
return (T) parser.parseToCapabilities(content);
}
if (valueType == AppendUID.class) {
return (T) parser.parseToAppendUid(content);
}
if (valueType == CopyUID.class) {
return (T) parser.parseToCopyUid(content);
}
if (valueType == ExtensionMailboxInfo.class) {
return (T) parser.parseToExtensionMailboxInfo(content);
}
if (valueType == MailboxInfo.class) {
return (T) parser.parseToMailboxInfo(content);
}
if (valueType == ListInfoList.class) {
return (T) parser.parseToListInfoList(content);
}
if (valueType == Status.class) {
return (T) parser.parseToStatus(content);
}
if (valueType == IdResult.class) {
return (T) parser.parseToIdResult(content);
}
if (valueType == SearchResult.class) {
return (T) parser.parseToSearchResult(content);
}
if (valueType == ListStatusResult.class) {
return (T) parser.parseToListStatusResult(content);
}
if (valueType == EnableResult.class) {
return (T) parser.parseToEnableResult(content);
}
throw new ImapAsyncClientException(FailureType.UNKNOWN_PARSE_RESULT_TYPE);
}
/**
* Inner class to perform the parsing of IMAPResponse to various objects.
*/
private class ImapResponseParser {
/** Capability string. */
private static final String CAPABILITY = "CAPABILITY";
/**
* Parses the capabilities from a CAPABILITY response or from a CAPABILITY response code attached to (e.g.) an OK response.
*
* @param rs the CAPABILITY responses
* @return a map that has the key (token) and all its values (after = sign), if there is no equal sign, value is same as key
* @throws ImapAsyncClientException when input IMAPResponse array is not valid
*/
@Nonnull
private Capability parseToCapabilities(@Nonnull final IMAPResponse[] rs) throws ImapAsyncClientException {
String s;
final Map> capas = new HashMap>();
if (rs.length < 1) {
throw new ImapAsyncClientException(FailureType.INVALID_INPUT);
}
for (final IMAPResponse r : rs) {
if (!hasCapability(r)) {
continue;
}
while ((s = r.readAtom()) != null) {
if (s.length() == 0) {
if (r.peekByte() == (byte) R_BRACKET) {
break;
}
// Probably found something here that's not an atom. Rather than loop forever or fail completely, we'll try to skip this bogus
// capability. This is known to happen with: Netscape Messaging Server 4.03 (built Apr 27 1999) that returns:
// * CAPABILITY * CAPABILITY IMAP4 IMAP4rev1 ...
// The "*" in the middle of the capability list causes us to loop forever here.
r.skipToken();
} else {
final String[] tokens = s.split(EQUAL);
final String key = tokens[0];
final String value = (tokens.length > 1) ? tokens[1] : null;
final String upperCase = key.toUpperCase(Locale.ENGLISH);
List values = capas.get(upperCase);
if (values == null) {
values = new ArrayList<>();
capas.put(upperCase, values);
}
// AUTH key allows more than one pair(ex:AUTH=XOAUTH2 AUTH=PLAIN), parsing value out to List, otherwise add key to list
if (value != null) {
values.add(value);
}
}
}
}
// making the value list immutable
for (final Map.Entry> entry : capas.entrySet()) {
entry.setValue(Collections.unmodifiableList(entry.getValue()));
}
return new Capability(capas);
}
/**
* Returns true if the response has capability keyword; false otherwise.
*
* @param r the response to check
* @return true if the response has capability keyword; false otherwise
*/
private boolean hasCapability(final IMAPResponse r) {
// case 1, from capability or authenticate command. EX: * CAPABILITY IMAP4rev1 SASL-IR
if (r.keyEquals(CAPABILITY)) {
return true;
}
// case 2. from server greeting. EX: OK [CAPABILITY IMAP4rev1 SASL-IR AUTH=PLAIN] IMAP4rev1 Hello
byte b;
while ((b = r.readByte()) > 0 && b != (byte) '[') {
// eat chars till [
}
if (b == 0) { // left bracket not found
return false;
}
final String s = r.readAtom();
return s.equalsIgnoreCase(CAPABILITY);
}
/**
* Parses APPEND response to a AppendUID instance.
*
* @param rs the APPEND responses
* @return AppendUID instance
* @throws ImapAsyncClientException when input value is not valid
*/
@Nullable
private AppendUID parseToAppendUid(@Nonnull final IMAPResponse[] rs) throws ImapAsyncClientException {
if (rs.length < 1) {
throw new ImapAsyncClientException(FailureType.INVALID_INPUT);
}
final IMAPResponse r = rs[rs.length - 1];
if (!r.isOK()) {
throw new ImapAsyncClientException(FailureType.INVALID_INPUT);
}
byte b;
while ((b = r.readByte()) > 0 && b != (byte) L_BRACKET) {
// eat chars till [
}
if (b == 0) {
throw new ImapAsyncClientException(FailureType.INVALID_INPUT);
}
final String s = r.readAtom();
if (!s.equalsIgnoreCase(APPENDUID)) {
throw new ImapAsyncClientException(FailureType.INVALID_INPUT);
}
final long uidvalidity = r.readLong();
final long uid = r.readLong();
return new AppendUID(uidvalidity, uid);
}
/**
* Parses COPY or MOVE command responses to a CopyUID instance.
*
* @param rr the COPY responses
* @return COPYUID instance built from copy command responses
* @throws ImapAsyncClientException when input value is not valid
*/
@Nonnull
private CopyUID parseToCopyUid(@Nonnull final IMAPResponse[] rr) throws ImapAsyncClientException {
// For copy response, it is at the last response, for move command response, it is the first response
for (int i = rr.length - 1; i >= 0; i--) {
final Response r = rr[i];
if (r == null || !r.isOK()) {
continue;
}
byte b;
while ((b = r.readByte()) > 0 && b != (byte) L_BRACKET) {
// eat chars till [
}
if (b == 0) {
continue;
}
final String s = r.readAtom();
if (!s.equalsIgnoreCase("COPYUID")) { // expunge response from MOVE, for ex: 2 EXPUNGE
continue;
}
final long uidvalidity = r.readLong();
final String src = r.readAtom();
final String dst = r.readAtom();
return new CopyUID(uidvalidity, UIDSet.parseUIDSets(src), UIDSet.parseUIDSets(dst));
}
throw new ImapAsyncClientException(FailureType.INVALID_INPUT); // when rr length is 0
}
/**
* Parses the SELECT or EXAMINE responses to a MailboxInfo instance.
*
* @param rr the list of responses from SELECT or EXAMINE, this input r array should contain the tagged/final one
* @return MailboxInfo instance
* @throws ParsingException when encountering parsing exception
* @throws ImapAsyncClientException when input is invalid
*/
@Nonnull
private MailboxInfo parseToMailboxInfo(@Nonnull final IMAPResponse[] rr) throws ParsingException, ImapAsyncClientException {
if (rr.length < 1) {
throw new ImapAsyncClientException(FailureType.INVALID_INPUT);
}
final MailboxInfo minfo = new MailboxInfo(rr);
setupMailboxInfoAccessMode(rr[rr.length - 1], minfo);
return minfo;
}
/**
* Sets up the mode for the {@link MailboxInfo}.
*
* @param lastResp the tagged response
* @param minfo the {@link MailboxInfo} instance
*/
private void setupMailboxInfoAccessMode(@Nonnull final Response lastResp, @Nonnull final MailboxInfo minfo) {
if (lastResp.isTagged() && lastResp.isOK()) { // command successful
if (lastResp.toString().indexOf("READ-ONLY") != -1) {
minfo.mode = Folder.READ_ONLY;
} else {
minfo.mode = Folder.READ_WRITE;
}
}
}
/**
* Parses the SELECT or EXAMINE responses to a {@link ExtensionMailboxInfo} instance.
*
* @param rr the list of responses from SELECT or EXAMINE, this input r array should contain the tagged/final one
* @return MailboxInfo instance
* @throws ParsingException when encountering parsing exception
* @throws ImapAsyncClientException when input is invalid
*/
@Nonnull
private ExtensionMailboxInfo parseToExtensionMailboxInfo(@Nonnull final IMAPResponse[] rr) throws ParsingException, ImapAsyncClientException {
if (rr.length < 1) {
throw new ImapAsyncClientException(FailureType.INVALID_INPUT);
}
final ExtensionMailboxInfo minfo = new ExtensionMailboxInfo(rr);
setupMailboxInfoAccessMode(rr[rr.length - 1], minfo);
return minfo;
}
/**
* Parses the LIST or LSUB responses to a {@link ListInfo} list. List responses example:
*
*
* * LIST () "/" INBOX
* * LIST (\NoSelect) "/" "Public mailboxes"
*
*
* @param r the list of responses from SELECT, the input responses array should contain the tagged/final one
* @return list of ListInfo objects
* @throws ParsingException when encountering parsing exception
* @throws ImapAsyncClientException when input value is not valid
*/
@Nonnull
private ListInfoList parseToListInfoList(@Nonnull final IMAPResponse[] r) throws ParsingException, ImapAsyncClientException {
if (r.length < 1) {
throw new ImapAsyncClientException(FailureType.INVALID_INPUT);
}
final Response response = r[r.length - 1];
if (!response.isOK()) {
throw new ImapAsyncClientException(FailureType.INVALID_INPUT);
}
// command successful reaching here
final List v = new ArrayList();
for (int i = 0, len = r.length - 1; i < len; i++) {
final IMAPResponse ir = r[i];
if (ir.keyEquals("LIST") || ir.keyEquals("LSUB")) {
v.add(new ListInfo(ir));
}
}
// could be an empty list if the search criteria ends up no result. Ex:
// a002 LIST "" "*t3*"
// a002 OK LIST completed
return new ListInfoList(v);
}
/**
* Parses the Status responses to a {@link Status}.
*
* @param r the list of responses from Status command, the input responses array should contain the tagged/final one
* @return Status object constructed based on the r array
* @throws ParsingException when encountering parsing exception
* @throws ImapAsyncClientException when input value is not valid
*/
@Nonnull
private Status parseToStatus(@Nonnull final IMAPResponse[] r) throws ParsingException, ImapAsyncClientException {
if (r.length < 1) {
throw new ImapAsyncClientException(FailureType.INVALID_INPUT);
}
final Response taggedResponse = r[r.length - 1];
if (!taggedResponse.isOK()) {
throw new ImapAsyncClientException(FailureType.INVALID_INPUT);
}
Status status = null;
for (int i = 0, len = r.length; i < len; i++) {
final IMAPResponse ir = r[i];
if (ir.keyEquals("STATUS")) {
if (status == null) {
status = new Status(ir);
} else { // collect them all if each attributes comes in its own line
Status.add(status, new Status(ir));
}
}
}
if (status == null) {
throw new ImapAsyncClientException(FailureType.INVALID_INPUT); // when r length is 0 or no Status response
}
return status;
}
/**
* Parses the LIST-STATUS command responses to a ListStatusResult object. See RFC 5819 for details. For each selectable mailbox matching the
* list pattern and selection options, the server MUST return an untagged LIST response followed by an untagged STATUS response containing the
* information requested in the STATUS return option. If an attempted STATUS for a listed mailbox fails because the mailbox can't be selected
* , the STATUS response MUST NOT be returned and the LIST response MUST include the \NoSelect attribute.
*
* @param r the list of responses from Status command, the input responses array should contain the tagged/final one
* @return Status object constructed based on the r array
* @throws ParsingException when encountering parsing exception
* @throws ImapAsyncClientException when input value is not valid
*/
@Nonnull
private ListStatusResult parseToListStatusResult(@Nonnull final IMAPResponse[] r) throws ParsingException, ImapAsyncClientException {
if (r.length < 1) {
throw new ImapAsyncClientException(FailureType.INVALID_INPUT);
}
final Response taggedResponse = r[r.length - 1];
if (!taggedResponse.isOK()) {
throw new ImapAsyncClientException(FailureType.INVALID_INPUT);
}
// command successful reaching here
final List v = new ArrayList();
final Map m = new HashMap();
for (int i = 0; i < r.length; i++) {
final IMAPResponse ir = r[i];
if (ir.keyEquals("LIST")) {
v.add(new ExtensionListInfo(ir));
} else if (ir.keyEquals("STATUS")) {
final Status status = new Status(ir);
m.put(status.mbox, status);
}
}
return new ListStatusResult(v, m);
}
/**
* Parses the ID responses to a {@link IdResult} object.
*
* @param ir the list of responses from ID command, the input responses array should contain the tagged/final one
* @return IdResult object constructed based on the given IMAPResponse array
* @throws ImapAsyncClientException when input value is not valid
*/
@Nonnull
private IdResult parseToIdResult(@Nonnull final IMAPResponse[] ir) throws ImapAsyncClientException {
if (ir.length < 1) {
throw new ImapAsyncClientException(FailureType.INVALID_INPUT);
}
final Response taggedResponse = ir[ir.length - 1];
if (!taggedResponse.isOK()) {
throw new ImapAsyncClientException(FailureType.INVALID_INPUT);
}
final Map serverParams = new HashMap();
for (int j = 0, len = ir.length; j < len; j++) {
final IMAPResponse r = ir[j];
if (r.keyEquals("ID")) {
r.skipSpaces();
int c = r.peekByte();
if (c == 'N' || c == 'n') { // assume NIL
return new IdResult(Collections.unmodifiableMap(Collections.EMPTY_MAP));
}
final String[] v = r.readStringList();
if (v == null) {
// this means it does not start with (, ID result is expected to have () enclosed
throw new ImapAsyncClientException(FailureType.INVALID_INPUT);
}
for (int i = 0; i < v.length; i += 2) {
final String name = v[i];
if (name == null || (i + 1 >= v.length)) {
throw new ImapAsyncClientException(FailureType.INVALID_INPUT);
}
final String value = v[i + 1];
serverParams.put(name, value);
}
}
}
return new IdResult(Collections.unmodifiableMap(serverParams));
}
/**
* Parses the Enable responses to a {@link EnableResult} object.
*
* @param ir the list of responses from ENABLE command, the input responses array should contain the tagged/final one
* @return EnableResult object constructed based on the given IMAPResponse array
* @throws ImapAsyncClientException when input value is not valid
*/
@Nonnull
private EnableResult parseToEnableResult(@Nonnull final IMAPResponse[] ir) throws ImapAsyncClientException {
if (ir.length < 1) {
throw new ImapAsyncClientException(FailureType.INVALID_INPUT);
}
final Response taggedResponse = ir[ir.length - 1];
if (!taggedResponse.isOK()) {
throw new ImapAsyncClientException(FailureType.INVALID_INPUT);
}
final Set enabledCapabilities = new HashSet();
for (final IMAPResponse resp : ir) {
if (resp.isTagged()) {
continue;
}
if (resp.keyEquals("ENABLED")) {
String s;
while ((s = resp.readAtom()) != null && s.length() != 0) {
enabledCapabilities.add(s.toUpperCase());
}
}
}
return new EnableResult(enabledCapabilities);
}
/**
* Parses the responses from UID search command to a {@link SearchResult} object.
*
* @param ir the list of responses from UID search command, the input responses array should contain the tagged/final one
* @return SearchResult object constructed based on the given IMAPResponse array
* @throws ImapAsyncClientException when tagged response is not OK or given response length is 0
*/
@Nonnull
private SearchResult parseToSearchResult(@Nonnull final IMAPResponse[] ir) throws ImapAsyncClientException {
if (ir.length < 1) {
throw new ImapAsyncClientException(FailureType.INVALID_INPUT);
}
final Response taggedResponse = ir[ir.length - 1];
if (!taggedResponse.isOK()) {
throw new ImapAsyncClientException(FailureType.INVALID_INPUT);
}
final List v = new ArrayList(); // will always return a non-null array
// Grab all SEARCH responses
long num;
for (final IMAPResponse sr : ir) {
// There *will* be one SEARCH response.
if (sr.keyEquals("SEARCH")) {
while ((num = sr.readLong()) != -1) {
v.add(Long.valueOf(num));
}
}
}
return new SearchResult(v);
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy