diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..a253de0e --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea +html +target diff --git a/README.md b/README.md index b615d211..101cfa27 100644 --- a/README.md +++ b/README.md @@ -1,126 +1,126 @@ - - -# imapnio -A Java library that supports NIO (Non-blocking I/O) based IMAP clients. - -The IMAP NIO client provides a framework to access an IMAP message store using None-blocking I/O mechanism. This design scales well for thousands of connections per machine and reduces contention when using a large number of threads and CPUs. - - -## Table of Contents - -- [Background](#background) -- [Install](#install) -- [Usage](#usage) -- [Release](#release) -- [Contribute](#contribute) -- [License](#license) - - -## Background - -The traditional accessing IMAP message store uses [JavaMail API](https://www.oracle.com/technetwork/java/javamail/index.html), which requires a blocking I/O. In this case, threads are blocked when performing I/O with the other end. This project was developed to relieve the waiting thread to perform other tasks, and it's design efficiently improves thread utilization to maximize hardware throughput and capacity. - -Some of the more distinguishing features of this library are: -- Highly customizable thread model and server/client idle max limit. -- Leverages the well-established framework [Netty](https://netty.io/) -- Future-based design enables a clean separation of IMAP client threads pool versus consumers threads pool. -- IMAPv4, [RFC3501](https://tools.ietf.org/html/rfc3501) support. -- ID command, [RFC2971](https://tools.ietf.org/html/rfc2971) support. -- IDLE command, [RFC2177](https://tools.ietf.org/html/rfc2177) support -- MOVE command, [RFC6851](https://tools.ietf.org/html/rfc6851) support -- UNSELECT command, [RFC3691](https://tools.ietf.org/html/rfc3691) support - -This project is ideal for applications that have a high requirement to optimize thread utilization and improve overall resource capacity. Specifically, this is best for situations where users perform a very high level of sessions and communication with the IMAP server. - -## Install - -This is a Java library. After downloading it, compile it using `mvn clean install` - -Then, update your project's pom.xml file dependencies, as follows: - -``` - - com.yahoo.imapnio - imapnio.core - 4.3.7 - -``` -Finally, import the relevant classes and use this library according to the usage section below. - -- For contributors run deploy to do a push to nexus servers - -``` - $ mvn clean deploy -Dgpg.passphrase=[pathPhrase] -``` - -## Usage - -The following code examples demonstrate basic functionality relate to connecting to and communicating with IMAP servers. - -### Create a client -```java - // Create a ImapAsyncClient instance with number of threads to handle the server requests - final int numOfThreadsServed = 5; - final ImapAsyncClient imapClient = new ImapAsyncClient(numOfThreads); -``` -### Establish a session with an IMAP server -```java - // Create a new session via the ImapAsyncClient instance created above and connect to that server. For the illustration purpose, - // "imaps://imap.server.com:993" is used - final URI serverUri = new URI("imaps://imap.server.com:993"); - final ImapAsyncSessionConfig config = new ImapAsyncSessionConfig(); - config.setConnectionTimeoutMillis(5000); - config.setReadTimeoutMillis(6000); - final List sniNames = null; - - final InetSocketAddress localAddress = null; - final Future future = imapClient.createSession(serverUri, config, localAddress, sniNames, DebugMode.DEBUG_OFF); - - //this version is a future-based nio client. Check whether future is done by following code. - if (future.isDone()) { - System.out.println("Future is done."); - } -``` - -### Execute the IMAP command to IMAP server -Following codes uses a Capability command as an example. - -```java - // Executes the capability command - final Future capaCmdFuture = session.execute(new CapaCommand()); - -``` - -### Handle the response from IMAP server -If the future of the executed command is done, obtain the response. -Following example shows how to read ImapAsyncResponse which wraps the content sent from the server. - -```java - if (capaCmdFuture.isDone()) { - System.out.println("Capability command is done."); - final ImapAsyncResponse resp = future.get(5, TimeUnit.MILLISECONDS); - final ImapResponseMapper mapper = new ImapResponseMapper(); - final Capability capa = mapper.readValue(resp.getResponseLines().toArray(new IMAPResponse[0]), Capability.class); - final List values = capa.getCapability("AUTH"); - } -``` - -## Release - -This release, 2.0.x, is a major release. Changes are: -- It supports non-blocking IO functionality through providing callers with java.util.concurrent.Future object. -- Listener-based non-blocking IO capability will not be supported in this release. - -## Contribute - -Please refer to the [contributing.md](Contributing.md) for information about how to get involved. We welcome issues, questions, and pull requests. Pull Requests are welcome. - - -## Maintainers - -Luis Alves: lafa@verizonmedia.com - - -## License - -This project is licensed under the terms of the [Apache 2.0](LICENSE-Apache-2.0) open source license. Please refer to [LICENSE](LICENSE) for the full terms. + + +# imapnio +A Java library that supports NIO (Non-blocking I/O) based IMAP clients. + +The IMAP NIO client provides a framework to access an IMAP message store using None-blocking I/O mechanism. This design scales well for thousands of connections per machine and reduces contention when using a large number of threads and CPUs. + + +## Table of Contents + +- [Background](#background) +- [Install](#install) +- [Usage](#usage) +- [Release](#release) +- [Contribute](#contribute) +- [License](#license) + + +## Background + +The traditional accessing IMAP message store uses [JavaMail API](https://www.oracle.com/technetwork/java/javamail/index.html), which requires a blocking I/O. In this case, threads are blocked when performing I/O with the other end. This project was developed to relieve the waiting thread to perform other tasks, and it's design efficiently improves thread utilization to maximize hardware throughput and capacity. + +Some of the more distinguishing features of this library are: +- Highly customizable thread model and server/client idle max limit. +- Leverages the well-established framework [Netty](https://netty.io/) +- Future-based design enables a clean separation of IMAP client threads pool versus consumers threads pool. +- IMAPv4, [RFC3501](https://tools.ietf.org/html/rfc3501) support. +- ID command, [RFC2971](https://tools.ietf.org/html/rfc2971) support. +- IDLE command, [RFC2177](https://tools.ietf.org/html/rfc2177) support +- MOVE command, [RFC6851](https://tools.ietf.org/html/rfc6851) support +- UNSELECT command, [RFC3691](https://tools.ietf.org/html/rfc3691) support + +This project is ideal for applications that have a high requirement to optimize thread utilization and improve overall resource capacity. Specifically, this is best for situations where users perform a very high level of sessions and communication with the IMAP server. + +## Install + +This is a Java library. After downloading it, compile it using `mvn clean install` + +Then, update your project's pom.xml file dependencies, as follows: + +``` + + com.yahoo.imapnio + imapnio.core + 4.3.8 + +``` +Finally, import the relevant classes and use this library according to the usage section below. + +- For contributors run deploy to do a push to nexus servers + +``` + $ mvn clean deploy -Dgpg.passphrase=[pathPhrase] +``` + +## Usage + +The following code examples demonstrate basic functionality relate to connecting to and communicating with IMAP servers. + +### Create a client +```java + // Create a ImapAsyncClient instance with number of threads to handle the server requests + final int numOfThreadsServed = 5; + final ImapAsyncClient imapClient = new ImapAsyncClient(numOfThreads); +``` +### Establish a session with an IMAP server +```java + // Create a new session via the ImapAsyncClient instance created above and connect to that server. For the illustration purpose, + // "imaps://imap.server.com:993" is used + final URI serverUri = new URI("imaps://imap.server.com:993"); + final ImapAsyncSessionConfig config = new ImapAsyncSessionConfig(); + config.setConnectionTimeoutMillis(5000); + config.setReadTimeoutMillis(6000); + final List sniNames = null; + + final InetSocketAddress localAddress = null; + final Future future = imapClient.createSession(serverUri, config, localAddress, sniNames, DebugMode.DEBUG_OFF); + + //this version is a future-based nio client. Check whether future is done by following code. + if (future.isDone()) { + System.out.println("Future is done."); + } +``` + +### Execute the IMAP command to IMAP server +Following codes uses a Capability command as an example. + +```java + // Executes the capability command + final Future capaCmdFuture = session.execute(new CapaCommand()); + +``` + +### Handle the response from IMAP server +If the future of the executed command is done, obtain the response. +Following example shows how to read ImapAsyncResponse which wraps the content sent from the server. + +```java + if (capaCmdFuture.isDone()) { + System.out.println("Capability command is done."); + final ImapAsyncResponse resp = future.get(5, TimeUnit.MILLISECONDS); + final ImapResponseMapper mapper = new ImapResponseMapper(); + final Capability capa = mapper.readValue(resp.getResponseLines().toArray(new IMAPResponse[0]), Capability.class); + final List values = capa.getCapability("AUTH"); + } +``` + +## Release + +This release, 2.0.x, is a major release. Changes are: +- It supports non-blocking IO functionality through providing callers with java.util.concurrent.Future object. +- Listener-based non-blocking IO capability will not be supported in this release. + +## Contribute + +Please refer to the [contributing.md](Contributing.md) for information about how to get involved. We welcome issues, questions, and pull requests. Pull Requests are welcome. + + +## Maintainers + +Yahoo Inc.: https://github.com/yahoo + + +## License + +This project is licensed under the terms of the [Apache 2.0](LICENSE-Apache-2.0) open source license. Please refer to [LICENSE](LICENSE) for the full terms. diff --git a/core/pom.xml b/core/pom.xml index c297839b..a6e354a5 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -1,142 +1,142 @@ - - - - - com.yahoo.imapnio - imapnio - 4.3.7 - ../pom.xml - - 4.0.0 - imapnio.core - jar - ${project.artifactId} - https://github.com/yahoo/imapnio - imapnio component ${project.name} - - - Apache License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - repo - - - - - ${project.parent.basedir} - 5 - 0 - - - - - - org.testng - testng - test - - - - com.google.code.findbugs - jsr305 - - - - io.netty - netty-handler - - - com.sun.mail - javax.mail - - - org.slf4j - slf4j-api - - - - ch.qos.logback - logback-core - - - ch.qos.logback - logback-classic - - - org.mockito - mockito-all - test - - - commons-codec - commons-codec - - - - - - org.jacoco - jacoco-maven-plugin - - - default-check - - check - - - - - BUNDLE - - - INSTRUCTION - COVEREDRATIO - 0.98 - - - COMPLEXITY - COVEREDRATIO - 0.97 - - - LINE - COVEREDRATIO - 0.98 - - - CLASS - COVEREDRATIO - 1 - - - METHOD - COVEREDRATIO - 0.98 - - - BRANCH - COVEREDRATIO - 0.98 - - - CLASS - MISSEDCOUNT - 0 - - - - - - - - - - - - - - - - - + + + + + com.yahoo.imapnio + imapnio + 4.3.8 + ../pom.xml + + 4.0.0 + imapnio.core + jar + ${project.artifactId} + https://github.com/yahoo/imapnio + imapnio component ${project.name} + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + ${project.parent.basedir} + 5 + 0 + + + + + + org.testng + testng + test + + + + com.google.code.findbugs + jsr305 + + + + io.netty + netty-handler + + + com.sun.mail + javax.mail + + + org.slf4j + slf4j-api + + + + ch.qos.logback + logback-core + + + ch.qos.logback + logback-classic + + + org.mockito + mockito-all + test + + + commons-codec + commons-codec + + + + + + org.jacoco + jacoco-maven-plugin + + + default-check + + check + + + + + BUNDLE + + + INSTRUCTION + COVEREDRATIO + 0.98 + + + COMPLEXITY + COVEREDRATIO + 0.97 + + + LINE + COVEREDRATIO + 0.98 + + + CLASS + COVEREDRATIO + 1 + + + METHOD + COVEREDRATIO + 0.98 + + + BRANCH + COVEREDRATIO + 0.98 + + + CLASS + MISSEDCOUNT + 0 + + + + + + + + + + + + + + + + + diff --git a/core/src/main/java/com/yahoo/imapnio/async/data/ExtendedModifiedSinceTerm.java b/core/src/main/java/com/yahoo/imapnio/async/data/ExtendedModifiedSinceTerm.java new file mode 100644 index 00000000..fad9f47f --- /dev/null +++ b/core/src/main/java/com/yahoo/imapnio/async/data/ExtendedModifiedSinceTerm.java @@ -0,0 +1,127 @@ +package com.yahoo.imapnio.async.data; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.mail.Flags; +import javax.mail.Message; +import javax.mail.search.SearchTerm; + +import com.yahoo.imapnio.async.exception.ImapAsyncClientException; +import com.yahoo.imapnio.async.request.EntryTypeRequest; + +/** + * This class models search-modsequence with {@code EntryTypeReq} from RFC7162. ABNF from RFC7162 is documented below. + * + *
+ * {@code
+ * 3.1.5.  MODSEQ Search Criterion in SEARCH
+ *
+ *    The MODSEQ criterion for the SEARCH (or UID SEARCH) command allows a
+ *    client to search for the metadata items that were modified since a
+ *    specified moment.
+ *    Syntax: MODSEQ [ ] 
+ * ============================================================================
+ * ABNF:
+ * search-modsequence  = "MODSEQ" [search-modseq-ext] SP mod-sequence-valzer
+ *
+ * entry-name          = entry-flag-name
+ *
+ * entry-flag-name     = DQUOTE "/flags/" attr-flag DQUOTE
+ *                       ;; Each system or user-defined flag 
+ *                       ;; is mapped to "/flags/".
+ *                       ;;
+ *                       ;;  follows the escape rules
+ *                       ;; used by "quoted" string as described in
+ *                       ;; Section 4.3 of [RFC3501]; e.g., for the
+ *                       ;; flag \Seen, the corresponding 
+ *                       ;; is "/flags/\\seen", and for the flag
+ *                       ;; $MDNSent, the corresponding 
+ *                       ;; is "/flags/$mdnsent".
+ *
+ * entry-type-resp     = "priv" / "shared"
+ *                       ;; Metadata item type.
+ *
+ * entry-type-req      = entry-type-resp / "all"
+ *                       ;; Perform SEARCH operation on a private
+ *                       ;; metadata item, shared metadata item,
+ *                       ;; or both.
+ *
+ * }
+ * 
+ */ +public final class ExtendedModifiedSinceTerm extends SearchTerm { + + /** The name of the metadata item. */ + private final Flags entryName; + + /** The type of metadata item. */ + private final EntryTypeRequest entryType; + + /** The given modification sequence. */ + private final long modSeq; + + /** + * Initializes the {@code ExtendedModifiedSinceTerm} class with modification sequence. + * + * @param modSeq modification sequence number + */ + public ExtendedModifiedSinceTerm(final long modSeq) { + this.entryName = null; + this.entryType = null; + this.modSeq = modSeq; + } + + /** + * Initializes the {@code ExtendedModifiedSinceTerm} class with entry name, entry type, and modification sequence. + * + * @param entryName name of the metadata item + * @param entryType type of the metadata item + * @param modSeq modification sequence number + * @throws ImapAsyncClientException if entryName has more than one flag. + */ + public ExtendedModifiedSinceTerm(@Nonnull final Flags entryName, @Nonnull final EntryTypeRequest entryType, final long modSeq) + throws ImapAsyncClientException { + final Flags.Flag[] sf = entryName.getSystemFlags(); // get the system flags + final String[] uf = entryName.getUserFlags(); // get the user flag strings + if (sf.length + uf.length != 1) { + throw new ImapAsyncClientException(ImapAsyncClientException.FailureType.INVALID_INPUT); + } + this.entryName = entryName; + this.entryType = entryType; + this.modSeq = modSeq; + } + + /** + * @return the entry name + */ + @Nullable + public Flags getEntryName() { + return entryName; + } + + /** + * @return the entry type + */ + @Nullable + public EntryTypeRequest getEntryType() { + return entryType; + } + + /** + * @return the modification sequence + */ + public long getModSeq() { + return modSeq; + } + + /** + * This method should never be called for ExtendedModifiedSinceTerm. + * + * @param msg the date comparator is applied to this Message's MODSEQ + * @return UnsupportedOperationException if the method is called + */ + @Override + public boolean match(final Message msg) { + throw new UnsupportedOperationException("This method should not be used. Only for override purpose."); + } +} diff --git a/core/src/main/java/com/yahoo/imapnio/async/data/ExtensionMailboxInfo.java b/core/src/main/java/com/yahoo/imapnio/async/data/ExtensionMailboxInfo.java index 741fec41..1e1760c4 100644 --- a/core/src/main/java/com/yahoo/imapnio/async/data/ExtensionMailboxInfo.java +++ b/core/src/main/java/com/yahoo/imapnio/async/data/ExtensionMailboxInfo.java @@ -15,9 +15,15 @@ public class ExtensionMailboxInfo extends MailboxInfo { /** Literal for MAILBOXID. */ private static final String MAILBOX_ID = "MAILBOXID"; + /** Literal for NOMODSEQ. */ + private static final String NOMODSEQ = "NOMODSEQ"; + /** Variable to store mailbox Id. */ private String mailboxId; + /** Variable to indicate whether a server doesn't support the persistent storage of mod-sequencese after enabling CONDSTORE command. */ + private boolean isNoModSeq; + /** * Initializes an instance of {@link ExtensionMailboxInfo} from the server responses for the select or examine command. * @@ -43,16 +49,26 @@ public ExtensionMailboxInfo(@Nonnull final IMAPResponse[] resps) throws ParsingE ir.reset(); continue; } + boolean handled = true; key = key.toUpperCase(); if (key.equals(MAILBOX_ID)) { // example when 26 is the mailbox id:"* OK [MAILBOXID (26)] Ok" final String[] values = ir.readSimpleList(); // reading the string, aka as above example, "(26)", within parentheses if (values != null && values.length >= 1) { mailboxId = values[0]; - resps[i] = null; // Nulls out this element in array to be consistent with MailboxInfo behavior - break; + } else { + handled = false; } + } else if (key.equals(NOMODSEQ)) { // example: * OK [NOMODSEQ] Sorry, this mailbox format doesn't support modsequences + isNoModSeq = true; + } else { + handled = false; + } + + if (handled) { + resps[i] = null; // Nulls out this element in array to be consistent with MailboxInfo behavior + } else { + ir.reset(); // default back the parsing index } - ir.reset(); // default back the parsing index } } @@ -63,4 +79,11 @@ public ExtensionMailboxInfo(@Nonnull final IMAPResponse[] resps) throws ParsingE public String getMailboxId() { return mailboxId; } + + /** + * @return isNoModSeq, true if the server return NOMODSEQ response. + */ + public boolean isNoModSeq() { + return isNoModSeq; + } } diff --git a/core/src/main/java/com/yahoo/imapnio/async/data/FetchResult.java b/core/src/main/java/com/yahoo/imapnio/async/data/FetchResult.java new file mode 100644 index 00000000..8e785d25 --- /dev/null +++ b/core/src/main/java/com/yahoo/imapnio/async/data/FetchResult.java @@ -0,0 +1,33 @@ +package com.yahoo.imapnio.async.data; + +import java.util.List; + +import javax.annotation.Nonnull; + +import com.sun.mail.imap.protocol.FetchResponse; + +/** + * This class provides the list of Fetch responses from fetch command response with parsing from FetchResponseMapper. + */ +public class FetchResult { + + /** The collection of Fetch responses. */ + private final List fetchResponses; + + /** + * Initializes a {@link FetchResult} object with Fetch responses collection. + * + * @param fetchResponses collection of Fetch responses from fetch command result + */ + public FetchResult(@Nonnull final List fetchResponses) { + this.fetchResponses = fetchResponses; + } + + /** + * @return Fetch responses collection from fetch or UID fetch command result + */ + @Nonnull + public List getFetchResponses() { + return fetchResponses; + } +} diff --git a/core/src/main/java/com/yahoo/imapnio/async/data/MessageNumberSet.java b/core/src/main/java/com/yahoo/imapnio/async/data/MessageNumberSet.java index e61862e3..d1fe15d1 100644 --- a/core/src/main/java/com/yahoo/imapnio/async/data/MessageNumberSet.java +++ b/core/src/main/java/com/yahoo/imapnio/async/data/MessageNumberSet.java @@ -257,4 +257,51 @@ public static String buildString(@Nullable final MessageNumberSet[] msgsets) { } return s.toString(); } + + /** + * Converts an IMAP RFC3501 sequence-set syntax into an array of MessageNumberSet. + * + * @param msgNumbers the message numbers string + * @return the array of MessageNumberSet + * @throws ImapAsyncClientException will not throw + */ + public static MessageNumberSet[] buildMessageNumberSets(@Nullable final String msgNumbers) throws ImapAsyncClientException { + if (msgNumbers == null || msgNumbers.isEmpty()) { + return null; + } + + final String[] msgSetStrs = msgNumbers.split(","); + final MessageNumberSet[] msgSets = new MessageNumberSet[msgSetStrs.length]; + + int i = 0; // msgset index + try { + for (final String element : msgSetStrs) { + if (element.contains(":")) { + final String[] elements = element.split(":"); + if (elements.length != 2) { + throw new ImapAsyncClientException(ImapAsyncClientException.FailureType.INVALID_INPUT); + } + if (elements[1].equals("*")) { // Ex: 1:* + msgSets[i] = new MessageNumberSet(Long.parseLong(elements[0]), LastMessage.LAST_MESSAGE); + } else if (elements[0].equals("*")) { // Ex: *:1 + msgSets[i] = new MessageNumberSet(Long.parseLong(elements[1]), LastMessage.LAST_MESSAGE); + } else { // Ex: 1:2 + msgSets[i] = new MessageNumberSet(Long.parseLong(elements[0]), Long.parseLong(elements[1])); + } + } else { + if (element.equals("*")) { // Ex: * + msgSets[i] = new MessageNumberSet(LastMessage.LAST_MESSAGE); + } else { // Ex: 1 + final long num = Long.parseLong(element); + msgSets[i] = new MessageNumberSet(num, num); + } + } + i++; + } + } catch (final NumberFormatException ex) { + throw new ImapAsyncClientException(ImapAsyncClientException.FailureType.INVALID_INPUT); + } + + return msgSets; + } } diff --git a/core/src/main/java/com/yahoo/imapnio/async/data/SearchResult.java b/core/src/main/java/com/yahoo/imapnio/async/data/SearchResult.java index 413c6d56..44372f4e 100644 --- a/core/src/main/java/com/yahoo/imapnio/async/data/SearchResult.java +++ b/core/src/main/java/com/yahoo/imapnio/async/data/SearchResult.java @@ -6,27 +6,40 @@ import javax.annotation.Nullable; /** - * This class provides the list of message sequence numbers from search command response. + * This class provides the modification sequence and the list of message sequence numbers from search command response. */ public class SearchResult { /** Search command response sequence number, could be message sequence or UID. */ @Nonnull private final List msgNumbers; + /** Modification sequence, is only shown when CondStore is enabled. */ + private final Long highestModSeq; + /** - * Initializes a {@link SearchResult} object with message number collection. + * Initializes a {@link SearchResult} object with message number collection and modification sequence. * * @param msgNumbers collection of message number from search command result + * @param highestModSeq the highest modification sequence from search command result */ - public SearchResult(@Nonnull final List msgNumbers) { + public SearchResult(@Nonnull final List msgNumbers, @Nullable final Long highestModSeq) { this.msgNumbers = msgNumbers; + this.highestModSeq = highestModSeq; } /** * @return message number collection from search command or UID search command result */ - @Nullable + @Nonnull public List getMessageNumbers() { return this.msgNumbers; } + + /** + * @return the highest modification sequence from search command or UID search command result + */ + @Nullable + public Long getHighestModSeq() { + return this.highestModSeq; + } } diff --git a/core/src/main/java/com/yahoo/imapnio/async/data/StoreResult.java b/core/src/main/java/com/yahoo/imapnio/async/data/StoreResult.java new file mode 100644 index 00000000..6ab580da --- /dev/null +++ b/core/src/main/java/com/yahoo/imapnio/async/data/StoreResult.java @@ -0,0 +1,68 @@ +package com.yahoo.imapnio.async.data; + +import java.util.List; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.sun.mail.imap.protocol.FetchResponse; + +/** + * This class provides the highest modification sequence, the list of Fetch responses, and the list of modified messages number + * from store command response based on RFC3501 and RFC7162 as following. + * + *
+ * resp-text-code      =/ "HIGHESTMODSEQ" SP mod-sequence-value /
+ *                        "MODIFIED" SP sequence-set
+ * 
+ */ +public class StoreResult { + + /** The highest modification sequence. */ + private Long highestModSeq; + + /** The collection of Fetch responses. */ + private final List fetchResponses; + + /** The collection of message number as sequence-set, only shown when CondStore is enabled. */ + private final MessageNumberSet[] modifiedMsgSets; + + /** + * Initializes a {@link StoreResult} object with the highest modification sequence, Fetch responses collection, + * and modified message number collection. + * + * @param highestModSeq the highest modification from store command result + * @param fetchResponses collection of Fetch responses from store command result + * @param modifiedMsgSets collection of modified message number from store command result + */ + public StoreResult(@Nullable final Long highestModSeq, @Nonnull final List fetchResponses, + @Nullable final MessageNumberSet[] modifiedMsgSets) { + this.highestModSeq = highestModSeq; + this.fetchResponses = fetchResponses; + this.modifiedMsgSets = modifiedMsgSets; + } + + /** + * @return the highest modification sequence from store or UID store command result + */ + @Nullable + public Long getHighestModSeq() { + return highestModSeq; + } + + /** + * @return Fetch responses collection from store or UID store command result + */ + @Nonnull + public List getFetchResponses() { + return fetchResponses; + } + + /** + * @return modified message number collection from store or UID store command result + */ + @Nullable + public MessageNumberSet[] getModifiedMsgSets() { + return modifiedMsgSets; + } +} diff --git a/core/src/main/java/com/yahoo/imapnio/async/request/AbstractFetchCommand.java b/core/src/main/java/com/yahoo/imapnio/async/request/AbstractFetchCommand.java index 2c63e73e..ef4d884a 100644 --- a/core/src/main/java/com/yahoo/imapnio/async/request/AbstractFetchCommand.java +++ b/core/src/main/java/com/yahoo/imapnio/async/request/AbstractFetchCommand.java @@ -3,6 +3,7 @@ import java.nio.charset.StandardCharsets; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import com.yahoo.imapnio.async.data.MessageNumberSet; @@ -10,18 +11,47 @@ import io.netty.buffer.Unpooled; /** - * This class defines IMAP fetch command request from client. ABNF in RFC3501 is described as following: + * This class defines IMAP fetch command request from client. ABNF in RFC3501 and RFC7162 is described as following: * *
  * {@code
- * fetch           = "FETCH" SP sequence-set SP ("ALL" / "FULL" / "FAST" /
- *                   fetch-att / "(" fetch-att *(SP fetch-att) ")")
+ * fetch               = "FETCH" SP sequence-set SP ("ALL" / "FULL" / "FAST" /
+ *                       fetch-att / "(" fetch-att *(SP fetch-att) ")")
  *
- * fetch-att       = "ENVELOPE" / "FLAGS" / "INTERNALDATE" /
- *                   "RFC822" [".HEADER" / ".SIZE" / ".TEXT"] /
- *                   "BODY" ["STRUCTURE"] / "UID" /
- *                   "BODY" section ["<" number "." nz-number ">"] /
- *                   "BODY.PEEK" section ["<" number "." nz-number ">"]
+ * fetch-att           = "ENVELOPE" / "FLAGS" / "INTERNALDATE" /
+ *                       "RFC822" [".HEADER" / ".SIZE" / ".TEXT"] /
+ *                       "BODY" ["STRUCTURE"] / "UID" /
+ *                       "BODY" section ["<" number "." nz-number ">"] /
+ *                       "BODY.PEEK" section ["<" number "." nz-number ">"]
+ *
+ * fetch-modifier      =/ chgsince-fetch-mod
+ *                       ;; Conforms to the generic "fetch-modifier"
+ *                       ;; syntax defined in [RFC4466].
+ *
+ * chgsince-fetch-mod  = "CHANGEDSINCE" SP mod-sequence-value
+ *                       ;; CHANGEDSINCE FETCH modifier conforms to
+ *                       ;; the fetch-modifier syntax.
+ *
+ * fetch-att           =/ fetch-mod-sequence
+ *                       ;; Modifies original IMAP4 fetch-att.
+ *
+ * fetch-mod-sequence  = "MODSEQ"
+ *
+ * fetch-mod-resp      = "MODSEQ" SP "(" permsg-modsequence ")"
+ *
+ * permsg-modsequence  = mod-sequence-value
+ *                       ;; Per-message mod-sequence.
+ *
+ * mod-sequence-value  = 1*DIGIT
+ *                       ;; Positive unsigned 63-bit integer
+ *                       ;; (mod-sequence)
+ *                       ;; (1 <= n <= 9,223,372,036,854,775,807).
+ *
+ * rexpunges-fetch-mod =  "VANISHED"
+ *                       ;; VANISHED UID FETCH modifier conforms
+ *                       ;; to the fetch-modifier syntax
+ *                       ;; defined in [RFC4466].  It is only
+ *                       ;; allowed in the UID FETCH command.
  * }
  * 
*/ @@ -39,6 +69,18 @@ public abstract class AbstractFetchCommand extends ImapRequestAdapter { /** Byte array for UID FETCH. */ private static final byte[] UID_FETCH_SP_B = UID_FETCH_SP.getBytes(StandardCharsets.US_ASCII); + /** Changed Since and space. */ + private static final String CHANGEDSINCE_SP = "CHANGEDSINCE "; + + /** Byte array for Changed Since and Space. */ + private static final byte[] CHANGEDSINCE_SP_B = CHANGEDSINCE_SP.getBytes(StandardCharsets.US_ASCII); + + /** Space and Vanished. */ + private static final String SP_VANISHED = " VANISHED"; + + /** Byte array for Space and Vanished. */ + private static final byte[] SP_VANISHED_B = SP_VANISHED.getBytes(StandardCharsets.US_ASCII); + /** Byte array for CR and LF, keeping the array local so it cannot be modified by others. */ private static final byte[] CRLF_B = { '\r', '\n' }; @@ -54,54 +96,125 @@ public abstract class AbstractFetchCommand extends ImapRequestAdapter { /** True if prepending UID; false otherwise. */ private boolean isUid; + /** Changed since the given modification sequence. */ + private Long changedSince; + + /** Indicate whether vanished flag is used. */ + private boolean isVanished; + /** - * Initializes a {@link FetchCommand} with the {@link MessageNumberSet} array. + * Initializes a {@link AbstractFetchCommand} with the flag indicate whether prepending UID, the {@link MessageNumberSet} array and the data + * items. * * @param isUid whether prepending UID * @param msgsets the set of message set * @param items the data items */ - public AbstractFetchCommand(final boolean isUid, @Nonnull final MessageNumberSet[] msgsets, @Nonnull final String items) { - this(isUid, MessageNumberSet.buildString(msgsets), items); + protected AbstractFetchCommand(final boolean isUid, @Nonnull final MessageNumberSet[] msgsets, @Nonnull final String items) { + this(isUid, MessageNumberSet.buildString(msgsets), items, null, false); } /** - * Initializes a {@link FetchCommand} with the {@link MessageNumberSet} array. + * Initializes a {@link AbstractFetchCommand} with the flag indicate whether prepending UID, the {@link MessageNumberSet} array, and the macro. * * @param isUid whether prepending UID * @param msgsets the set of message set * @param macro the macro */ - public AbstractFetchCommand(final boolean isUid, @Nonnull final MessageNumberSet[] msgsets, @Nonnull final FetchMacro macro) { + protected AbstractFetchCommand(final boolean isUid, @Nonnull final MessageNumberSet[] msgsets, @Nonnull final FetchMacro macro) { this(isUid, MessageNumberSet.buildString(msgsets), macro); } /** - * Initializes a {@link FetchCommand} with the {@link MessageNumberSet} array. + * Initializes a {@link AbstractFetchCommand} with the flag indicate whether prepending UID, the the message numbers string, and the macro. + * + * @param isUid whether prepending UID + * @param msgNumbers the message numbers string + * @param macro the macro + */ + protected AbstractFetchCommand(final boolean isUid, @Nonnull final String msgNumbers, @Nonnull final FetchMacro macro) { + this(isUid, msgNumbers, macro, null, false); + } + + /** + * Initializes a {@link AbstractFetchCommand} with the flag indicate whether prepending UID, the the message numbers string, and the data items. * * @param isUid whether prepending UID * @param msgNumbers the message numbers string * @param items the data items */ protected AbstractFetchCommand(final boolean isUid, @Nonnull final String msgNumbers, @Nonnull final String items) { + this(isUid, msgNumbers, items, null, false); + } + + /** + * Initializes a {@link AbstractFetchCommand} with the flag indicate whether prepending UID, the {@link MessageNumberSet} array, the data items, + * changed since the given modification sequence, and the flag to check whether uid fetch with vanished option. + * + * @param isUid whether prepending UID + * @param msgsets the set of message set + * @param items the data items + * @param changedSince changed since the given modification sequence + * @param isVanished whether uid fetch with vanished option + */ + public AbstractFetchCommand(final boolean isUid, @Nonnull final MessageNumberSet[] msgsets, @Nonnull final String items, + @Nullable final Long changedSince, final boolean isVanished) { + this(isUid, MessageNumberSet.buildString(msgsets), items, changedSince, isVanished); + } + + /** + * Initializes a {@link AbstractFetchCommand} with the flag indicate whether prepending UID, the {@link MessageNumberSet} array, the macro, + * changed since the given modification sequence, and the flag to check whether uid fetch with vanished option. + * + * @param isUid whether prepending UID + * @param msgsets the set of message set + * @param macro the macro + * @param changedSince changed since the given modification sequence + * @param isVanished whether uid fetch with vanished option + */ + public AbstractFetchCommand(final boolean isUid, @Nonnull final MessageNumberSet[] msgsets, @Nonnull final FetchMacro macro, + @Nullable final Long changedSince, final boolean isVanished) { + this(isUid, MessageNumberSet.buildString(msgsets), macro, changedSince, isVanished); + } + + /** + * Initializes a {@link AbstractFetchCommand} with the flag indicate whether prepending UID, the message numbers string, the data items, + * changed since the given modification sequence, and the flag to check whether uid fetch with vanished option. + * + * @param isUid whether prepending UID + * @param msgNumbers the message numbers string + * @param items the data items + * @param changedSince changed since the given modification sequence + * @param isVanished whether uid fetch with vanished option + */ + protected AbstractFetchCommand(final boolean isUid, @Nonnull final String msgNumbers, @Nonnull final String items, + @Nullable final Long changedSince, final boolean isVanished) { this.isUid = isUid; this.msgNumbers = msgNumbers; this.dataItems = items; this.macro = null; + this.changedSince = changedSince; + this.isVanished = isVanished; } /** - * Initializes a {@link FetchCommand} with the {@link MessageNumberSet} array. + * Initializes a {@link AbstractFetchCommand} with the flag indicate whether prepending UID, the message numbers string, the macro, + * changed since the given modification sequence, and the flag to check whether uid fetch with vanished option. * * @param isUid whether prepending UID * @param msgNumbers the message numbers string * @param macro the macro + * @param changedSince changed since the given modification sequence + * @param isVanished whether uid fetch with vanished option */ - protected AbstractFetchCommand(final boolean isUid, @Nonnull final String msgNumbers, @Nonnull final FetchMacro macro) { + protected AbstractFetchCommand(final boolean isUid, @Nonnull final String msgNumbers, @Nonnull final FetchMacro macro, + @Nullable final Long changedSince, final boolean isVanished) { this.isUid = isUid; this.msgNumbers = msgNumbers; this.macro = macro; this.dataItems = null; + this.changedSince = changedSince; + this.isVanished = isVanished; } @Override @@ -109,11 +222,18 @@ public void cleanup() { this.msgNumbers = null; this.dataItems = null; this.macro = null; + this.changedSince = null; } @Override public ByteBuf getCommandLineBytes() { - final ByteBuf sb = Unpooled.buffer(); + // Ex:UID FETCH 300:500 (FLAGS) (CHANGEDSINCE 1234 VANISHED) + final int dataItemsSize = dataItems == null ? macro.name().length() : dataItems.length(); + final String changedSinceStr = changedSince == null ? "" : changedSince.toString(); + final int vanishedSize = isVanished ? SP_VANISHED.length() : 0; + final int len = ImapClientConstants.PAD_LEN + dataItemsSize + msgNumbers.length() + vanishedSize; + final ByteBuf sb = Unpooled.buffer(len); + sb.writeBytes(isUid ? UID_FETCH_SP_B : FETCH_SP_B); sb.writeBytes(msgNumbers.getBytes(StandardCharsets.US_ASCII)); sb.writeByte(ImapClientConstants.SPACE); @@ -125,6 +245,18 @@ public ByteBuf getCommandLineBytes() { } else { sb.writeBytes(macro.name().getBytes(StandardCharsets.US_ASCII)); } + + if (changedSince != null) { + sb.writeByte(ImapClientConstants.SPACE); + sb.writeByte(ImapClientConstants.L_PAREN); + sb.writeBytes(CHANGEDSINCE_SP_B); + sb.writeBytes(changedSinceStr.getBytes(StandardCharsets.US_ASCII)); + if (isVanished) { + sb.writeBytes(SP_VANISHED_B); + } + sb.writeByte(ImapClientConstants.R_PAREN); + } + sb.writeBytes(CRLF_B); return sb; diff --git a/core/src/main/java/com/yahoo/imapnio/async/request/AbstractSearchCommand.java b/core/src/main/java/com/yahoo/imapnio/async/request/AbstractSearchCommand.java index 67e2826e..da3d28f4 100644 --- a/core/src/main/java/com/yahoo/imapnio/async/request/AbstractSearchCommand.java +++ b/core/src/main/java/com/yahoo/imapnio/async/request/AbstractSearchCommand.java @@ -24,26 +24,38 @@ * This class defines IMAP search command request from client. * *
- * search         = "SEARCH" [SP "CHARSET" SP astring] 1*(SP search-key)
- *                   ; CHARSET argument to MUST be registered with IANA
+ * search              = "SEARCH" [SP "CHARSET" SP astring] 1*(SP search-key)
+ *                       ; CHARSET argument to MUST be registered with IANA
  *
- * search-key     = "ALL" / "ANSWERED" / "BCC" SP astring /
- *                  "BEFORE" SP date / "BODY" SP astring /
- *                  "CC" SP astring / "DELETED" / "FLAGGED" /
- *                  "FROM" SP astring / "KEYWORD" SP flag-keyword /
- *                  "NEW" / "OLD" / "ON" SP date / "RECENT" / "SEEN" /
- *                  "SINCE" SP date / "SUBJECT" SP astring /
- *                  "TEXT" SP astring / "TO" SP astring /
- *                  "UNANSWERED" / "UNDELETED" / "UNFLAGGED" /
- *                  "UNKEYWORD" SP flag-keyword / "UNSEEN" /
- *                    ; Above this line were in [IMAP2]
- *                  "DRAFT" / "HEADER" SP header-fld-name SP astring /
- *                  "LARGER" SP number / "NOT" SP search-key /
- *                  "OR" SP search-key SP search-key /
- *                  "SENTBEFORE" SP date / "SENTON" SP date /
- *                  "SENTSINCE" SP date / "SMALLER" SP number /
- *                  "UID" SP sequence-set / "UNDRAFT" / sequence-set /
- *                  "(" search-key *(SP search-key) ")"
+ * search-key          = "ALL" / "ANSWERED" / "BCC" SP astring /
+ *                       "BEFORE" SP date / "BODY" SP astring /
+ *                       "CC" SP astring / "DELETED" / "FLAGGED" /
+ *                       "FROM" SP astring / "KEYWORD" SP flag-keyword /
+ *                       "NEW" / "OLD" / "ON" SP date / "RECENT" / "SEEN" /
+ *                       "SINCE" SP date / "SUBJECT" SP astring /
+ *                       "TEXT" SP astring / "TO" SP astring /
+ *                       "UNANSWERED" / "UNDELETED" / "UNFLAGGED" /
+ *                       "UNKEYWORD" SP flag-keyword / "UNSEEN" /
+ *                         ; Above this line were in [IMAP2]
+ *                       "DRAFT" / "HEADER" SP header-fld-name SP astring /
+ *                       "LARGER" SP number / "NOT" SP search-key /
+ *                       "OR" SP search-key SP search-key /
+ *                       "SENTBEFORE" SP date / "SENTON" SP date /
+ *                       "SENTSINCE" SP date / "SMALLER" SP number /
+ *                       "UID" SP sequence-set / "UNDRAFT" / sequence-set /
+ *                       "(" search-key *(SP search-key) ")"
+ *
+ * search-key          =/ search-modsequence
+ *                       ;; Modifies original IMAP4 search-key.
+ *                       ;;
+ *                       ;; This change applies to all commands
+ *                       ;; referencing this non-terminal -- in
+ *                       ;; particular, SEARCH, SORT, and THREAD.
+ *
+ * search-modsequence  = "MODSEQ" [search-modseq-ext] SP
+ *                          mod-sequence-valzer
+ *
+ * search-modseq-ext   = SP entry-name SP entry-type-req
  * 
*/ public abstract class AbstractSearchCommand extends ImapRequestAdapter { @@ -124,7 +136,7 @@ protected AbstractSearchCommand(final boolean isUid, @Nullable final String msgN this.charset = SearchSequence.isAscii(term) ? null : StandardCharsets.UTF_8.name(); if (term != null) { - final SearchSequence searchSeq = new SearchSequence(); + final ExtendedSearchSequence searchSeq = new ExtendedSearchSequence(); this.searchExpr = searchSeq.generateSequence(term, charset == null ? null : MimeUtility.javaCharset(charset)); } this.isLiteralPlusEnabled = (capa != null) ? capa.hasCapability(ImapClientConstants.LITERAL_PLUS) : false; diff --git a/core/src/main/java/com/yahoo/imapnio/async/request/AbstractStoreFlagsCommand.java b/core/src/main/java/com/yahoo/imapnio/async/request/AbstractStoreFlagsCommand.java index 5593bb12..7b335ace 100644 --- a/core/src/main/java/com/yahoo/imapnio/async/request/AbstractStoreFlagsCommand.java +++ b/core/src/main/java/com/yahoo/imapnio/async/request/AbstractStoreFlagsCommand.java @@ -15,22 +15,34 @@ * *
  *
- * store           = "STORE" SP sequence-set SP store-att-flags
+ * store                = "STORE" SP sequence-set SP store-att-flags
  *
- * store-att-flags = (["+" / "-"] "FLAGS" [".SILENT"]) SP
- *                   (flag-list / (flag *(SP flag)))
- * flag-list       = "(" [flag *(SP flag)] ")"
- * flag            = "\Answered" / "\Flagged" / "\Deleted" /
- *                   "\Seen" / "\Draft" / flag-keyword / flag-extension
- *                   ; Does not include "\Recent"
+ * store-att-flags      = (["+" / "-"] "FLAGS" [".SILENT"]) SP
+ *                        (flag-list / (flag *(SP flag)))
+ * flag-list            = "(" [flag *(SP flag)] ")"
  *
- * flag-extension  = "\" atom
- *                   ; Future expansion.  Client implementations
- *                   ; MUST accept flag-extension flags.  Server
- *                   ; implementations MUST NOT generate
- *                   ; flag-extension flags except as defined by
- *                   ; future standard or standards-track
- *                   ; revisions of this specification.
+ * flag                 = "\Answered" / "\Flagged" / "\Deleted" /
+ *                        "\Seen" / "\Draft" / flag-keyword / flag-extension
+ *                        ; Does not include "\Recent"
+ *
+ * flag-extension       = "\" atom
+ *                        ; Future expansion.  Client implementations
+ *                        ; MUST accept flag-extension flags.  Server
+ *                        ; implementations MUST NOT generate
+ *                        ; flag-extension flags except as defined by
+ *                        ; future standard or standards-track
+ *                        ; revisions of this specification.
+ *
+ * store-modifier      =/ "UNCHANGEDSINCE" SP mod-sequence-valzer
+ *                       ;; Only a single "UNCHANGEDSINCE" may be
+ *                       ;; specified in a STORE operation.
+ *
+ * mod-sequence-value  = 1*DIGIT
+ *                       ;; Positive unsigned 63-bit integer
+ *                       ;; (mod-sequence)
+ *                       ;; (1 \leq n \leq 9,223,372,036,854,775,807).
+ *
+ * mod-sequence-valzer = "0" / mod-sequence-value
  * 
*/ public abstract class AbstractStoreFlagsCommand extends ImapRequestAdapter { @@ -62,6 +74,15 @@ public abstract class AbstractStoreFlagsCommand extends ImapRequestAdapter { /** Byte array for SILENT. */ private static final byte[] SILENT_B = SILENT.getBytes(StandardCharsets.US_ASCII); + /** Literal for UNCHANGEDSINCE and Space. */ + private static final String UNCHANGEDSINCE_SP = "UNCHANGEDSINCE "; + + /** Byte array for UNCHANGEDSINCE and Space. */ + private static final byte[] UNCHANGEDSINCE_SP_B = UNCHANGEDSINCE_SP.getBytes(StandardCharsets.US_ASCII); + + /** Unchanged since the modification sequence. */ + private Long unchangedSince; + /** Flag whether adding UID before store command. */ private boolean isUid; @@ -93,7 +114,24 @@ protected AbstractStoreFlagsCommand(final boolean isUid, @Nonnull final MessageN } /** - * Initializes a {@link AbstractStoreFlagsCommand} with string form message numbers (could be sequence sets or UIDs) and all other parameters. + * Initializes a {@link AbstractStoreFlagsCommand} with the MessageNumberSet array, flags, action, silent flag whether server should return new + * values, and unchanged since the given modification sequence. + * + * @param isUid whether to have UID prepended + * @param msgsets the set of message set + * @param flags the flags to be stored + * @param action whether to replace, add or remove the flags + * @param silent true if asking server to respond silently + * @param unchangedSince unchanged since the given modification sequence + */ + protected AbstractStoreFlagsCommand(final boolean isUid, @Nonnull final MessageNumberSet[] msgsets, @Nonnull final Flags flags, + @Nonnull final FlagsAction action, final boolean silent, @Nonnull final Long unchangedSince) { + this(isUid, MessageNumberSet.buildString(msgsets), flags, action, silent, unchangedSince); + } + + /** + * Initializes a {@link AbstractStoreFlagsCommand} with string form message numbers (could be sequence sets or UIDs), flags, action, + * and silent flag whether server should return new value. * * @param isUid whether to have UID prepended * @param msgNumbers the message id @@ -108,6 +146,28 @@ protected AbstractStoreFlagsCommand(final boolean isUid, @Nonnull final String m this.flags = flags; this.action = action; this.isSilent = silent; + this.unchangedSince = null; + } + + /** + * Initializes a {@link AbstractStoreFlagsCommand} with string form message numbers (could be sequence sets or UIDs), flags, action, + * silent flag whether server should return new values, and unchanged since the given modification sequence. + * + * @param isUid whether to have UID prepended + * @param msgNumbers the message id + * @param flags the flags to be stored + * @param action whether to replace, add or remove the flags + * @param silent true if asking server to respond silently + * @param unchangedSince unchanged since the given modification sequence + */ + protected AbstractStoreFlagsCommand(final boolean isUid, @Nonnull final String msgNumbers, @Nonnull final Flags flags, + @Nonnull final FlagsAction action, final boolean silent, @Nonnull final Long unchangedSince) { + this.isUid = isUid; + this.msgNumbers = msgNumbers; + this.flags = flags; + this.action = action; + this.isSilent = silent; + this.unchangedSince = unchangedSince; } @Override @@ -115,16 +175,29 @@ public void cleanup() { this.msgNumbers = null; this.flags = null; this.action = null; + this.unchangedSince = null; } @Override public ByteBuf getCommandLineBytes() { // Ex:STORE 2:4 +FLAGS (\Deleted) - final ByteBuf sb = Unpooled.buffer(); + final ImapArgumentFormatter argWriter = new ImapArgumentFormatter(); + final String flagListString = argWriter.buildFlagString(flags); + final String unchangedSinceStr = unchangedSince == null ? "" : unchangedSince.toString(); + final int len = ImapClientConstants.PAD_LEN + msgNumbers.length() + flagListString.length(); + final ByteBuf sb = Unpooled.buffer(len); sb.writeBytes(isUid ? UID_STORE_SP_B : STORE_SP_B); sb.writeBytes(msgNumbers.getBytes(StandardCharsets.US_ASCII)); sb.writeByte(ImapClientConstants.SPACE); + if (unchangedSince != null) { + sb.writeByte(ImapClientConstants.L_PAREN); + sb.writeBytes(UNCHANGEDSINCE_SP_B); + sb.writeBytes(unchangedSinceStr.getBytes(StandardCharsets.US_ASCII)); + sb.writeByte(ImapClientConstants.R_PAREN); + sb.writeByte(ImapClientConstants.SPACE); + } + if (action == FlagsAction.ADD) { sb.writeByte(ImapClientConstants.PLUS); } else if (action == FlagsAction.REMOVE) { @@ -138,9 +211,8 @@ public ByteBuf getCommandLineBytes() { } // buildFlagString generates "(" [flag *(SP flag)] ")" - final ImapArgumentFormatter argWriter = new ImapArgumentFormatter(); sb.writeByte(ImapClientConstants.SPACE); - sb.writeBytes(argWriter.buildFlagString(flags).getBytes(StandardCharsets.US_ASCII)); + sb.writeBytes(flagListString.getBytes(StandardCharsets.US_ASCII)); sb.writeBytes(CRLF_B); return sb; diff --git a/core/src/main/java/com/yahoo/imapnio/async/request/EntryTypeRequest.java b/core/src/main/java/com/yahoo/imapnio/async/request/EntryTypeRequest.java new file mode 100644 index 00000000..809870f6 --- /dev/null +++ b/core/src/main/java/com/yahoo/imapnio/async/request/EntryTypeRequest.java @@ -0,0 +1,35 @@ +package com.yahoo.imapnio.async.request; + +import javax.annotation.Nonnull; + +/** + * Metadata item type (entry-type-req) for search command with the modification sequence extension. + */ +public enum EntryTypeRequest { + /** Entry type for a private metadata item. */ + PRIVATE("PRIV"), + /** Entry type for a shared metadata item. */ + SHARED("SHARED"), + /** Entry type to indicate to use the biggest value among "PRIV" and "SHARED" mod-sequences for the metadata item. */ + ALL("ALL"); + + /** Name of entry type. */ + private final String typeName; + + /** + * Initializes a {@link EntryTypeRequest} with type name. + * + * @param typeName name of entry type + */ + EntryTypeRequest(@Nonnull final String typeName) { + this.typeName = typeName; + } + + /** + * @return the entry type name + */ + @Nonnull + public String getTypeName() { + return this.typeName; + } +} diff --git a/core/src/main/java/com/yahoo/imapnio/async/request/ExamineFolderCommand.java b/core/src/main/java/com/yahoo/imapnio/async/request/ExamineFolderCommand.java index 7177ab23..d039bfef 100644 --- a/core/src/main/java/com/yahoo/imapnio/async/request/ExamineFolderCommand.java +++ b/core/src/main/java/com/yahoo/imapnio/async/request/ExamineFolderCommand.java @@ -33,6 +33,16 @@ public ExamineFolderCommand(@Nonnull final String folderName, @Nonnull final QRe super(EXAMINE, folderName, qResyncParameter); } + /** + * Initializes a {@link ExamineFolderCommand}. + * + * @param folderName folder name to examine + * @param isCondstoreEnabled whether to enable CondStore + */ + public ExamineFolderCommand(@Nonnull final String folderName, final boolean isCondstoreEnabled) { + super(EXAMINE, folderName, isCondstoreEnabled); + } + @Override public ImapRFCSupportedCommandType getCommandType() { return ImapRFCSupportedCommandType.EXAMINE_FOLDER; diff --git a/core/src/main/java/com/yahoo/imapnio/async/request/ExtendedSearchSequence.java b/core/src/main/java/com/yahoo/imapnio/async/request/ExtendedSearchSequence.java new file mode 100644 index 00000000..74d09793 --- /dev/null +++ b/core/src/main/java/com/yahoo/imapnio/async/request/ExtendedSearchSequence.java @@ -0,0 +1,75 @@ +package com.yahoo.imapnio.async.request; + +import java.io.IOException; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.mail.search.SearchException; +import javax.mail.search.SearchTerm; + +import com.sun.mail.iap.Argument; +import com.sun.mail.imap.protocol.SearchSequence; +import com.yahoo.imapnio.async.data.ExtendedModifiedSinceTerm; + +/** + * This class extends the search sequence for modification sequence with the optional fields entry + * name and type defined in https://tools.ietf.org/html/rfc7162#section-3.1.5. + */ +public class ExtendedSearchSequence extends SearchSequence { + + /** Literal for MODSEQ. */ + private static final String MODSEQ = "MODSEQ"; + + /** + * Generate the IMAP search sequence for the given search expression. + * + * @param term the search term + * @return the IMAP search sequence argument + * @throws SearchException when search too complex + * @throws IOException when failing to convert the given string into bytes in the specified charset + */ + public Argument generateSequence(@Nonnull final SearchTerm term) throws SearchException, IOException { + if (term instanceof ExtendedModifiedSinceTerm) { + return modifiedSince((ExtendedModifiedSinceTerm) term); + } else { + return super.generateSequence(term, null); + } + } + + /** + * Generate the IMAP search sequence for the given search expression. + * + * @param term the search term + * @param charset the character set + * @return the IMAP search sequence argument + * @throws SearchException when search too complex + * @throws IOException when failing to convert the given string into bytes in the specified charset + */ + @Override + public Argument generateSequence(@Nonnull final SearchTerm term, @Nullable final String charset) throws SearchException, IOException { + if (term instanceof ExtendedModifiedSinceTerm) { + return modifiedSince((ExtendedModifiedSinceTerm) term); + } else { + return super.generateSequence(term, charset); + } + } + + /** + * Modification of SearchSequence modifiedSince method to support optional entry name and entry type. + * + * @param term the extended modified since search term + * @return the IMAP search sequence argument + */ + private Argument modifiedSince(@Nonnull final ExtendedModifiedSinceTerm term) { + final Argument result = new Argument(); + result.writeAtom(MODSEQ); + if (term.getEntryName() != null && term.getEntryType() != null) { + final ImapArgumentFormatter argWriter = new ImapArgumentFormatter(); + result.writeAtom(argWriter.buildEntryFlagName(term.getEntryName())); + result.writeAtom(term.getEntryType().getTypeName()); + } + + result.writeNumber(term.getModSeq()); + return result; + } +} diff --git a/core/src/main/java/com/yahoo/imapnio/async/request/FetchCommand.java b/core/src/main/java/com/yahoo/imapnio/async/request/FetchCommand.java index bd51ea0f..1e1cc882 100644 --- a/core/src/main/java/com/yahoo/imapnio/async/request/FetchCommand.java +++ b/core/src/main/java/com/yahoo/imapnio/async/request/FetchCommand.java @@ -29,6 +29,28 @@ public FetchCommand(@Nonnull final MessageNumberSet[] msgsets, @Nonnull final Fe super(false, msgsets, macro); } + /** + * Initializes a {@link FetchCommand} with the {@code MessageNumberSet} array, fetch items, and changed since the modification sequence. + * + * @param msgsets the set of message set + * @param items the data items + * @param changedSince changed since the given modification sequence + */ + public FetchCommand(@Nonnull final MessageNumberSet[] msgsets, @Nonnull final String items, @Nonnull final Long changedSince) { + super(false, msgsets, items, changedSince, false); + } + + /** + * Initializes a {@link FetchCommand} with the {@code MessageNumberSet} array, macro, and changed since the modification sequence. + * + * @param msgsets the set of message set + * @param macro the macro + * @param changedSince changed since the given modification sequence + */ + public FetchCommand(@Nonnull final MessageNumberSet[] msgsets, @Nonnull final FetchMacro macro, @Nonnull final Long changedSince) { + super(false, msgsets, macro, changedSince, false); + } + @Override public ImapRFCSupportedCommandType getCommandType() { return ImapRFCSupportedCommandType.FETCH; diff --git a/core/src/main/java/com/yahoo/imapnio/async/request/ImapArgumentFormatter.java b/core/src/main/java/com/yahoo/imapnio/async/request/ImapArgumentFormatter.java index bfb02973..bba521aa 100644 --- a/core/src/main/java/com/yahoo/imapnio/async/request/ImapArgumentFormatter.java +++ b/core/src/main/java/com/yahoo/imapnio/async/request/ImapArgumentFormatter.java @@ -226,4 +226,51 @@ String buildFlagString(@Nonnull final Flags flags) { sb.append(ImapClientConstants.R_PAREN); // terminate flag_list return sb.toString(); } + + /** + * Creates an IMAP entry-flag-name from the given Flags with only one flag object. The following is the ABNF from RFC7162 + *
+     * entry-flag-name     = DQUOTE "/flags/" attr-flag DQUOTE
+     *                        ;; Each system or user-defined flag 
+     *                        ;; is mapped to "/flags/".
+     *                        ;;
+     *                        ;;  follows the escape rules
+     *                        ;; used by "quoted" string as described in
+     *                        ;; Section 4.3 of [RFC3501]; e.g., for the
+     *                        ;; flag \Seen, the corresponding 
+     *                        ;; is "/flags/\\seen", and for the flag
+     *                        ;; $MDNSent, the corresponding 
+     *                        ;; is "/flags/$mdnsent".
+     * 
+ * + * @param flags the flags with one flag + * @return the entry flag name string + */ + String buildEntryFlagName(@Nonnull final Flags flags) { + final Flags.Flag[] sf = flags.getSystemFlags(); // get the system flags + final String[] uf = flags.getUserFlags(); // get the user flag strings + final StringBuilder s = new StringBuilder("\"/flags/"); // start of entry flag name + + if (sf.length == 1) { + s.append(ImapClientConstants.BACKSLASH); + if (sf[0] == Flags.Flag.ANSWERED) { + s.append(ANSWERED); + } else if (sf[0] == Flags.Flag.DELETED) { + s.append(DELETED); + } else if (sf[0] == Flags.Flag.DRAFT) { + s.append(DRAFT); + } else if (sf[0] == Flags.Flag.FLAGGED) { + s.append(FLAGGED); + } else if (sf[0] == Flags.Flag.RECENT) { + s.append(RECENT); + } else if (sf[0] == Flags.Flag.SEEN) { + s.append(SEEN); + } + } else { // user flag == 1 + s.append(uf[0]); + } + + s.append(ImapClientConstants.DQUOTA); + return s.toString(); + } } diff --git a/core/src/main/java/com/yahoo/imapnio/async/request/ImapClientConstants.java b/core/src/main/java/com/yahoo/imapnio/async/request/ImapClientConstants.java index a58f3af2..044321ba 100644 --- a/core/src/main/java/com/yahoo/imapnio/async/request/ImapClientConstants.java +++ b/core/src/main/java/com/yahoo/imapnio/async/request/ImapClientConstants.java @@ -47,6 +47,12 @@ final class ImapClientConstants { /** Extra buffer length for command line builder to add. */ static final int PAD_LEN = 100; + /** Literal for double quota. */ + static final char DQUOTA = '\"'; + + /** Literal for back slash. */ + static final char BACKSLASH = '\\'; + /** * Private constructor to avoid constructing instance of this class. */ diff --git a/core/src/main/java/com/yahoo/imapnio/async/request/OpenFolderActionCommand.java b/core/src/main/java/com/yahoo/imapnio/async/request/OpenFolderActionCommand.java index 3062cf9c..9e81797c 100644 --- a/core/src/main/java/com/yahoo/imapnio/async/request/OpenFolderActionCommand.java +++ b/core/src/main/java/com/yahoo/imapnio/async/request/OpenFolderActionCommand.java @@ -20,6 +20,12 @@ abstract class OpenFolderActionCommand extends ImapRequestAdapter { /** Byte array for CR and LF, keeping the array local so it cannot be modified by others. */ private static final byte[] CRLF_B = { '\r', '\n' }; + /** Literal for CONDSTORE. */ + private static final String SP_CONDSTORE = " (CONDSTORE)"; + + /** Byte array for CONDSTORE. */ + private static final byte[] SP_CONDSTORE_B = SP_CONDSTORE.getBytes(StandardCharsets.US_ASCII); + /** Command operator, for example, "SELECT". */ private String op; @@ -29,6 +35,9 @@ abstract class OpenFolderActionCommand extends ImapRequestAdapter { /** Optional QResync parameter. */ private QResyncParameter qResyncParameter; + /** Optional CondStore parameter. */ + private boolean isCondStoreEnabled; + /** * Initializes a {@link OpenFolderActionCommand}. * @@ -39,6 +48,7 @@ protected OpenFolderActionCommand(@Nonnull final String op, @Nonnull final Strin this.op = op; this.folderName = folderName; this.qResyncParameter = null; + this.isCondStoreEnabled = false; } /** @@ -52,6 +62,21 @@ public OpenFolderActionCommand(@Nonnull final String op, @Nonnull final String f this.op = op; this.folderName = folderName; this.qResyncParameter = qResyncParameter; + this.isCondStoreEnabled = false; + } + + /** + * Initializes a {@link OpenFolderActionCommand}. + * + * @param op command operator + * @param folderName folder name + * @param isCondStoreEnabled whether to enable CondStore + */ + protected OpenFolderActionCommand(@Nonnull final String op, @Nonnull final String folderName, final boolean isCondStoreEnabled) { + this.op = op; + this.folderName = folderName; + this.qResyncParameter = null; + this.isCondStoreEnabled = isCondStoreEnabled; } @Override @@ -83,8 +108,11 @@ public ByteBuf getCommandLineBytes() throws ImapAsyncClientException { sb.append("))"); qResyncParameterSize = sb.length(); } + + final int condStoreSize = isCondStoreEnabled ? SP_CONDSTORE.length() : 0; + // 2 * base64Folder.length(): assuming every char needs to be escaped, goal is eliminating resizing, and avoid complex length calculation - final int len = 2 * base64Folder.length() + ImapClientConstants.PAD_LEN + qResyncParameterSize; + final int len = 2 * base64Folder.length() + ImapClientConstants.PAD_LEN + qResyncParameterSize + condStoreSize; final ByteBuf byteBuf = Unpooled.buffer(len); byteBuf.writeBytes(op.getBytes(StandardCharsets.US_ASCII)); byteBuf.writeByte(ImapClientConstants.SPACE); @@ -97,6 +125,10 @@ public ByteBuf getCommandLineBytes() throws ImapAsyncClientException { byteBuf.writeBytes(sb.toString().getBytes(StandardCharsets.US_ASCII)); } + if (isCondStoreEnabled) { + byteBuf.writeBytes(SP_CONDSTORE_B); + } + byteBuf.writeBytes(CRLF_B); return byteBuf; diff --git a/core/src/main/java/com/yahoo/imapnio/async/request/SelectFolderCommand.java b/core/src/main/java/com/yahoo/imapnio/async/request/SelectFolderCommand.java index bd567082..42ae26fd 100644 --- a/core/src/main/java/com/yahoo/imapnio/async/request/SelectFolderCommand.java +++ b/core/src/main/java/com/yahoo/imapnio/async/request/SelectFolderCommand.java @@ -31,6 +31,16 @@ public SelectFolderCommand(@Nonnull final String folderName, @Nonnull final QRes super(SELECT, folderName, qResyncParameter); } + /** + * Initializes a {@link SelectFolderCommand}. + * + * @param folderName folder name to select + * @param isCondstoreEnabled whether to enable CondStore + */ + public SelectFolderCommand(@Nonnull final String folderName, final boolean isCondstoreEnabled) { + super(SELECT, folderName, isCondstoreEnabled); + } + @Override public ImapRFCSupportedCommandType getCommandType() { return ImapRFCSupportedCommandType.SELECT_FOLDER; diff --git a/core/src/main/java/com/yahoo/imapnio/async/request/StoreFlagsCommand.java b/core/src/main/java/com/yahoo/imapnio/async/request/StoreFlagsCommand.java index 04baf568..cf65565e 100644 --- a/core/src/main/java/com/yahoo/imapnio/async/request/StoreFlagsCommand.java +++ b/core/src/main/java/com/yahoo/imapnio/async/request/StoreFlagsCommand.java @@ -47,6 +47,50 @@ public StoreFlagsCommand(@Nonnull final String msgNumbers, @Nonnull final Flags super(false, msgNumbers, flags, action, silent); } + /** + * Initializes a {@link StoreFlagsCommand} with the MessageNumberSet array, Flags, action, and unchanged since modification sequence. + * Requests server to return the new value. + * + * @param msgsets the set of message set + * @param flags the flags to be stored + * @param action whether to replace, add or remove the flags + * @param unchangedSince unchanged since the given modification sequence + */ + public StoreFlagsCommand(@Nonnull final MessageNumberSet[] msgsets, @Nonnull final Flags flags, @Nonnull final FlagsAction action, + @Nonnull final Long unchangedSince) { + super(false, msgsets, flags, action, false, unchangedSince); + } + + /** + * Initializes a {@link StoreFlagsCommand} with the MessageNumberSet array, flags, action, flag whether to request server to return the new + * value, and unchanged since modification sequence. + * + * @param msgsets the set of message set + * @param flags the flags to be stored + * @param action whether to replace, add or remove the flags + * @param silent true if asking server to respond silently + * @param unchangedSince unchanged since the given modification sequence + */ + public StoreFlagsCommand(@Nonnull final MessageNumberSet[] msgsets, @Nonnull final Flags flags, @Nonnull final FlagsAction action, + final boolean silent, @Nonnull final Long unchangedSince) { + super(false, msgsets, flags, action, silent, unchangedSince); + } + + /** + * Initializes a {@link StoreFlagsCommand} with string form message numbers, Flags, action, flag whether to request server to return the new + * value. + * + * @param msgNumbers the message numbers in string format + * @param flags the flags to be stored + * @param action whether to replace, add or remove the flags + * @param silent true if asking server to respond silently; false if requesting server to return the new values + * @param unchangedSince unchanged since the given modification sequence + */ + public StoreFlagsCommand(@Nonnull final String msgNumbers, @Nonnull final Flags flags, @Nonnull final FlagsAction action, + final boolean silent, @Nonnull final Long unchangedSince) { + super(false, msgNumbers, flags, action, silent, unchangedSince); + } + @Override public ImapRFCSupportedCommandType getCommandType() { return ImapRFCSupportedCommandType.STORE_FLAGS; diff --git a/core/src/main/java/com/yahoo/imapnio/async/request/UidFetchCommand.java b/core/src/main/java/com/yahoo/imapnio/async/request/UidFetchCommand.java index 712c3397..a8a41172 100644 --- a/core/src/main/java/com/yahoo/imapnio/async/request/UidFetchCommand.java +++ b/core/src/main/java/com/yahoo/imapnio/async/request/UidFetchCommand.java @@ -49,6 +49,56 @@ public UidFetchCommand(@Nonnull final String uids, @Nonnull final FetchMacro mac super(true, uids, macro); } + /** + * Initializes a {@link UidFetchCommand} with the {@code MessageNumberSet} array, data items, and changed since the modification sequence. + * + * @param msgsets the set of message set + * @param items the data items + * @param changedSince changed since the given modification sequence + */ + public UidFetchCommand(@Nonnull final MessageNumberSet[] msgsets, @Nonnull final String items, @Nonnull final Long changedSince) { + super(true, msgsets, items, changedSince, false); + } + + /** + * Initializes a {@link UidFetchCommand} with the {@code MessageNumberSet} array, macro, and changed since the modification sequence. + * + * @param msgsets the set of message set + * @param macro the macro, for example, ALL + * @param changedSince changed since the given modification sequence + */ + public UidFetchCommand(@Nonnull final MessageNumberSet[] msgsets, @Nonnull final FetchMacro macro, @Nonnull final Long changedSince) { + super(true, msgsets, macro, changedSince, false); + } + + /** + * Initializes a {@link UidFetchCommand} with the {@code MessageNumberSet} array, data items, changed since the modification sequence, + * and isVanished flag. + * + * @param msgsets the set of message set + * @param items the data items + * @param changedSince changed since the given modification sequence + * @param isVanished the flag to check whether uid fetch with isVanished option + */ + public UidFetchCommand(@Nonnull final MessageNumberSet[] msgsets, @Nonnull final String items, @Nonnull final Long changedSince, + final boolean isVanished) { + super(true, msgsets, items, changedSince, isVanished); + } + + /** + * Initializes a {@link UidFetchCommand} with the {@code MessageNumberSet} array, macro, changed since the modification sequence, + * and isVanished flag. + * + * @param msgsets the set of message set + * @param macro the macro, for example, ALL + * @param changedSince changed since the given modification sequence + * @param isVanished the flag to check whether uid fetch with isVanished option + */ + public UidFetchCommand(@Nonnull final MessageNumberSet[] msgsets, @Nonnull final FetchMacro macro, @Nonnull final Long changedSince, + final boolean isVanished) { + super(true, msgsets, macro, changedSince, isVanished); + } + @Override public ImapRFCSupportedCommandType getCommandType() { return ImapRFCSupportedCommandType.UID_FETCH; diff --git a/core/src/main/java/com/yahoo/imapnio/async/request/UidStoreFlagsCommand.java b/core/src/main/java/com/yahoo/imapnio/async/request/UidStoreFlagsCommand.java index d8239872..95c94660 100644 --- a/core/src/main/java/com/yahoo/imapnio/async/request/UidStoreFlagsCommand.java +++ b/core/src/main/java/com/yahoo/imapnio/async/request/UidStoreFlagsCommand.java @@ -48,6 +48,49 @@ public UidStoreFlagsCommand(@Nonnull final String uids, @Nonnull final Flags fla super(true, uids, flags, action, silent); } + /** + * Initializes a {@link UidStoreFlagsCommand} with the MessageNumberSet array, Flags and action. Requests server to return the new value. + * + * @param msgsets the set of message set + * @param flags the flags to be stored + * @param action whether to replace, add or remove the flags + * @param unchangedSince unchanged since the given modification sequence + */ + public UidStoreFlagsCommand(@Nonnull final MessageNumberSet[] msgsets, @Nonnull final Flags flags, @Nonnull final FlagsAction action, + @Nonnull final Long unchangedSince) { + super(true, msgsets, flags, action, false, unchangedSince); + } + + /** + * Initializes a {@link UidStoreFlagsCommand} with the MessageNumberSet array, Flags, action, flag whether to request server to return the new + * value, and unchanged since the modification sequence. + * + * @param msgsets the set of message set + * @param flags the flags to be stored + * @param action whether to replace, add or remove the flags + * @param silent true if asking server to respond silently; false if requesting server to return the new values + * @param unchangedSince unchanged since the given modification sequence + */ + public UidStoreFlagsCommand(@Nonnull final MessageNumberSet[] msgsets, @Nonnull final Flags flags, @Nonnull final FlagsAction action, + final boolean silent, @Nonnull final Long unchangedSince) { + super(true, msgsets, flags, action, silent, unchangedSince); + } + + /** + * Initializes a {@link UidStoreFlagsCommand} with string form message numbers, Flags, action, flag whether to request server to return the new + * value, and the unchanged since the given modification sequence. + * + * @param uids the string representing UID based on RFC3501 + * @param flags the flags to be stored + * @param action whether to replace, add or remove the flags + * @param silent true if asking server to respond silently; false if requesting server to return the new values + * @param unchangedSince unchanged since the given modification sequence + */ + public UidStoreFlagsCommand(@Nonnull final String uids, @Nonnull final Flags flags, @Nonnull final FlagsAction action, + final boolean silent, @Nonnull final Long unchangedSince) { + super(true, uids, flags, action, silent, unchangedSince); + } + @Override public ImapRFCSupportedCommandType getCommandType() { return ImapRFCSupportedCommandType.UID_STORE_FLAGS; diff --git a/core/src/main/java/com/yahoo/imapnio/async/response/ImapResponseMapper.java b/core/src/main/java/com/yahoo/imapnio/async/response/ImapResponseMapper.java index 06acf2c5..6400c5c3 100644 --- a/core/src/main/java/com/yahoo/imapnio/async/response/ImapResponseMapper.java +++ b/core/src/main/java/com/yahoo/imapnio/async/response/ImapResponseMapper.java @@ -1,541 +1,677 @@ -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); - } - } -} +package com.yahoo.imapnio.async.response; + +import java.io.IOException; +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.ProtocolException; +import com.sun.mail.iap.Response; +import com.sun.mail.imap.AppendUID; +import com.sun.mail.imap.CopyUID; +import com.sun.mail.imap.protocol.FetchItem; +import com.sun.mail.imap.protocol.FetchResponse; +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.FetchResult; +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.MessageNumberSet; +import com.yahoo.imapnio.async.data.SearchResult; +import com.yahoo.imapnio.async.data.StoreResult; +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"; + + /** HIGHESTMODSEQ keyword. */ + private static final String HIGHESTMODSEQ = "HIGHESTMODSEQ"; + + /** MODIFIED keyword. */ + private static final String MODIFIED = "MODIFIED"; + + /** MODSEQ keyword. */ + private static final String MODSEQ = "MODSEQ"; + + /** FETCH keyword. */ + private static final String FETCH = "FETCH"; + + /** EQUAL sign. */ + private static final String EQUAL = "="; + + /** [ char. */ + private static final char L_BRACKET = '['; + + /** ] char. */ + private static final char R_BRACKET = ']'; + + /** ( char. */ + private static final char L_PAREN = '('; + + /** 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 ImapAsyncClientException when target class to covert to is not supported + * @throws ParsingException if underlying input contains invalid content of type for the returned type + */ + @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); + } + if (valueType == FetchResult.class) { + return (T) parser.parseToFetchResult(content, new FetchItem[0]); + } + if (valueType == StoreResult.class) { + return (T) parser.parseToStoreResult(content); + } + + throw new ImapAsyncClientException(FailureType.UNKNOWN_PARSE_RESULT_TYPE); + } + + /** + * 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. + * @param extensionItems the array of extension FetchItem + * @return the serialized object + * @throws ImapAsyncClientException when target class to covert to is not supported + */ + @Nonnull + public T readValue(@Nonnull final IMAPResponse[] content, @Nonnull final Class valueType, @Nonnull final FetchItem[] extensionItems) + throws ImapAsyncClientException { + if (valueType == FetchResult.class) { + return (T) parser.parseToFetchResult(content, extensionItems); + } + + 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 + + Long modSeq = null; + + // 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)); + } + if (sr.readByte() == L_PAREN) { + final String s = sr.readAtom(); + if (MODSEQ.equals(s)) { + modSeq = sr.readLong(); + } + } + } + } + + return new SearchResult(v, modSeq); + } + + /** + * Parses the responses from Store command and UID Store command to a {@link StoreResult} object. + * + * @param ir the list of responses from Store or UID Store command, the input responses array should contain the tagged/final one + * @return StoreResult object constructed based on the given IMAPResponse array, + * @throws ImapAsyncClientException when tagged response is bad or given response length is 0 or fail to parse Fetch Response + */ + @Nonnull + private StoreResult parseToStoreResult(@Nonnull final IMAPResponse[] ir) + throws ImapAsyncClientException { + if (ir.length < 1) { + throw new ImapAsyncClientException(FailureType.INVALID_INPUT); + } + final Response taggedResponse = ir[ir.length - 1]; + taggedResponse.skipSpaces(); + MessageNumberSet[] modifiedMsgsets = null; // only one response will contain modified numbers + if (!taggedResponse.isBAD() && taggedResponse.readByte() == (byte) L_BRACKET && MODIFIED.equals(taggedResponse.readAtom())) { + // ex: d105 OK [MODIFIED 7,9] Conditional STORE failed + modifiedMsgsets = MessageNumberSet.buildMessageNumberSets(taggedResponse.readAtom()); + } else if (!taggedResponse.isOK()) { + throw new ImapAsyncClientException(FailureType.INVALID_INPUT); + } + + final List fetchResponses = new ArrayList<>(); // will always return a non-null array + + Long highestModSeq = null; + + for (final IMAPResponse sr: ir) { + sr.skipSpaces(); + if (sr.keyEquals(FETCH)) { + try { + fetchResponses.add(new FetchResponse(sr)); + } catch (final IOException | ProtocolException e) { + throw new ImapAsyncClientException(FailureType.INVALID_INPUT); + } + } else if (sr.readByte() == (byte) L_BRACKET) { // HIGHESTMODSEQ or MODIFIED + final String responseCode = sr.readAtom(); + if (HIGHESTMODSEQ.equals(responseCode)) { + highestModSeq = sr.readLong(); + } + } + } + + return new StoreResult(highestModSeq, fetchResponses, modifiedMsgsets); + } + + /** + * Parses the responses from Fetch command and UID Fetch command to a {@link FetchResult} object. + * + * @param ir the list of responses from Fetch or UID Fetch command, the input responses array should contain the tagged/final one + * @param extensionItems the array of extension FetchItem + * @return FetchResult object constructed based on the given IMAPResponse array, + * @throws ImapAsyncClientException when tagged response is not OK or given response length is 0 or fail to parse Fetch Response + */ + @Nonnull + private FetchResult parseToFetchResult(@Nonnull final IMAPResponse[] ir, @Nonnull final FetchItem[] extensionItems) + 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 fetchResponses = new ArrayList<>(); // will always return a non-null array + + for (final IMAPResponse sr: ir) { + if (sr.keyEquals(FETCH)) { + try { + fetchResponses.add(new FetchResponse(sr, extensionItems)); + } catch (final IOException | ProtocolException e) { + throw new ImapAsyncClientException(FailureType.INVALID_INPUT); + } + } + } + + return new FetchResult(fetchResponses); + } + } +} diff --git a/core/src/test/java/com/yahoo/imapnio/async/data/ExtendedModifiedSinceTermTest.java b/core/src/test/java/com/yahoo/imapnio/async/data/ExtendedModifiedSinceTermTest.java new file mode 100644 index 00000000..9febde66 --- /dev/null +++ b/core/src/test/java/com/yahoo/imapnio/async/data/ExtendedModifiedSinceTermTest.java @@ -0,0 +1,72 @@ +package com.yahoo.imapnio.async.data; + +import javax.mail.Flags; + +import org.testng.Assert; +import org.testng.annotations.Test; + +import com.yahoo.imapnio.async.exception.ImapAsyncClientException; +import com.yahoo.imapnio.async.request.EntryTypeRequest; + +/** + * Unit test for {@code ExtendedModifiedSinceTerm}. + */ +public class ExtendedModifiedSinceTermTest { + + /** + * Tests ExtendedModifiedSinceTerm constructor and getters. + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testExtendedModifiedSinceTermWithOptionalField() throws ImapAsyncClientException { + final Flags flags = new Flags(); + flags.add(Flags.Flag.SEEN); + final ExtendedModifiedSinceTerm extendedModifiedSinceTerm = new ExtendedModifiedSinceTerm(flags, EntryTypeRequest.ALL, 1L); + + Assert.assertEquals(extendedModifiedSinceTerm.getModSeq(), 1L, "getModSeq() mismatched."); + Assert.assertNotNull(extendedModifiedSinceTerm.getEntryName(), "getEntryName() should not return null."); + Assert.assertTrue(extendedModifiedSinceTerm.getEntryName().contains(Flags.Flag.SEEN), "getEntryName() mismatched."); + Assert.assertNotNull(extendedModifiedSinceTerm.getEntryType(), "getEntryType() should not return null."); + Assert.assertEquals(extendedModifiedSinceTerm.getEntryType().name(), "ALL", "getEntryType() mismatched."); + } + + /** + * Tests ExtendedModifiedSinceTerm constructor and getters. + */ + @Test + public void testExtendedModifiedSinceTermWithoutOptionalField() { + final ExtendedModifiedSinceTerm extendedModifiedSinceTerm = new ExtendedModifiedSinceTerm(1L); + + Assert.assertEquals(extendedModifiedSinceTerm.getModSeq(), 1L, "getModSeq() mismatched."); + Assert.assertNull(extendedModifiedSinceTerm.getEntryName(), "Entry name should be null"); + Assert.assertNull(extendedModifiedSinceTerm.getEntryType(), "Entry type should be null."); + } + + /** + * Tests ExtendedModifiedSinceTerm match throws UnsupportedOperationException. + */ + @Test(expectedExceptions = UnsupportedOperationException.class) + public void testExtendedModifiedSinceTermMatchException() { + final ExtendedModifiedSinceTerm extendedModifiedSinceTerm = new ExtendedModifiedSinceTerm(1L); + extendedModifiedSinceTerm.match(null); + } + + /** + * Tests buildEntryFlagName with ImapAsyncClientException. + */ + @Test + public void testBuildEntryFlagNameFailed() { + final Flags flags = new Flags(); + flags.add(Flags.Flag.ANSWERED); + flags.add(Flags.Flag.DELETED); + ImapAsyncClientException actual = null; + + try { + new ExtendedModifiedSinceTerm(flags, EntryTypeRequest.ALL, 1L); + } catch (final ImapAsyncClientException e) { + actual = e; + } + Assert.assertNotNull(actual, "Should throw ImapAsyncClientException"); + Assert.assertEquals(actual.getFailureType(), ImapAsyncClientException.FailureType.INVALID_INPUT, "FailureType should be INVALID_INPUT"); + } +} diff --git a/core/src/test/java/com/yahoo/imapnio/async/data/ExtensionMailboxInfoTest.java b/core/src/test/java/com/yahoo/imapnio/async/data/ExtensionMailboxInfoTest.java index 3fe13787..39a07b58 100644 --- a/core/src/test/java/com/yahoo/imapnio/async/data/ExtensionMailboxInfoTest.java +++ b/core/src/test/java/com/yahoo/imapnio/async/data/ExtensionMailboxInfoTest.java @@ -48,6 +48,7 @@ public void testCreateExtensionMailboxInfoSuccess() throws IOException, Protocol Assert.assertEquals(minfo.uidvalidity, 1459808247, "uidvalidity mismatched."); Assert.assertEquals(minfo.uidnext, 150400, "uidnext mismatched."); Assert.assertEquals(minfo.getMailboxId(), "214-mailbox", "MailboxId mismatched."); + Assert.assertFalse(minfo.isNoModSeq(), "isNoModSeq() mismatched."); Assert.assertNull(content[7], "This element should be nulled out"); } @@ -198,4 +199,42 @@ public void testCreateExtensionMailboxInfoNoValueInParenthsis() throws IOExcepti Assert.assertNotNull(content[7], "This element should not be nulled out"); Assert.assertEquals(content[7].getRest(), "[MAILBOXID ()] Ok", "The index is not reset to the point of the status code."); } + + /** + * Tests calling constructor with no mod seq successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testCreateExtensionMailboxInfoSuccessNoModSequence() throws IOException, ProtocolException { + + final IMAPResponse[] content = new IMAPResponse[9]; + content[0] = new IMAPResponse("* 3 EXISTS"); + content[1] = new IMAPResponse("* 0 RECENT"); + content[2] = new IMAPResponse("* OK [UIDVALIDITY 1459808247] UIDs valid"); + content[3] = new IMAPResponse("* OK [UIDNEXT 150400] Predicted next UID"); + content[4] = new IMAPResponse("* FLAGS (\\Answered \\Deleted \\Draft \\Flagged \\Seen $Forwarded $Junk $NotJunk)"); + content[5] = new IMAPResponse("* OK [PERMANENTFLAGS ()] No permanent flags permitted"); + content[6] = new IMAPResponse("* OK [MAILBOXID (214-mailbox)] Ok"); + content[7] = new IMAPResponse("* OK [NOMODSEQ] Sorry, this mailbox format doesn't support modsequences"); + content[8] = new IMAPResponse("002 OK [READ-ONLY] EXAMINE completed; now in selected state"); + final ExtensionMailboxInfo minfo = new ExtensionMailboxInfo(content); + + // verify the result + Assert.assertNotNull(minfo, "result mismatched."); + Assert.assertNotNull(minfo.availableFlags, "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.ANSWERED), "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.DELETED), "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.DRAFT), "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.FLAGGED), "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.SEEN), "availableFlags mismatched."); + Assert.assertEquals(minfo.highestmodseq, -1, "highestmodseq mismatched."); + Assert.assertEquals(minfo.uidvalidity, 1459808247, "uidvalidity mismatched."); + Assert.assertEquals(minfo.uidnext, 150400, "uidnext mismatched."); + Assert.assertEquals(minfo.getMailboxId(), "214-mailbox", "MailboxId mismatched."); + Assert.assertTrue(minfo.isNoModSeq(), "isNoModSeq() mismatched."); + Assert.assertNull(content[6], "This element should be nulled out"); + Assert.assertNull(content[7], "This element should be nulled out"); + } } diff --git a/core/src/test/java/com/yahoo/imapnio/async/data/FetchResultTest.java b/core/src/test/java/com/yahoo/imapnio/async/data/FetchResultTest.java new file mode 100644 index 00000000..31694196 --- /dev/null +++ b/core/src/test/java/com/yahoo/imapnio/async/data/FetchResultTest.java @@ -0,0 +1,33 @@ +package com.yahoo.imapnio.async.data; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +import org.testng.Assert; +import org.testng.annotations.Test; + +import com.sun.mail.iap.ProtocolException; +import com.sun.mail.imap.protocol.FetchResponse; +import com.sun.mail.imap.protocol.IMAPResponse; + +/** + * Unit test for {@code FetchResult}. + */ +public class FetchResultTest { + /** + * Tests FetchResult constructor and getters. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testFetchResult() throws IOException, ProtocolException { + final FetchResponse fetchResponse = new FetchResponse(new IMAPResponse("* 1 FETCH (UID 4 MODSEQ (12121231000))")); + final List expectedFetchResponses = Collections.singletonList(fetchResponse); + final FetchResult fr = new FetchResult(expectedFetchResponses); + final ListfetchResponsesResult = fr.getFetchResponses(); + Assert.assertEquals(fetchResponsesResult.size(), 1, "getFetchResponses() mismatched."); + Assert.assertTrue(fetchResponsesResult.get(0).keyEquals("FETCH"), "getFetchResponses() mismatched."); + } +} diff --git a/core/src/test/java/com/yahoo/imapnio/async/data/MessageNumberSetTest.java b/core/src/test/java/com/yahoo/imapnio/async/data/MessageNumberSetTest.java index d82e0599..1a8896bd 100644 --- a/core/src/test/java/com/yahoo/imapnio/async/data/MessageNumberSetTest.java +++ b/core/src/test/java/com/yahoo/imapnio/async/data/MessageNumberSetTest.java @@ -141,6 +141,103 @@ public void testBuildStringWith0LengthMessageNumberSets() { Assert.assertNull(MessageNumberSet.buildString(new MessageNumberSet[0]), "Result mismatched."); } + /** + * Tests buildMessageNumberSets(String) method with only one number. + * + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testBuildMessageNumberSetsWithOneString() throws ImapAsyncClientException { + final MessageNumberSet[] expectedMsgSets = { new MessageNumberSet(1, 1) }; + final String msgSetStr = MessageNumberSet.buildString(expectedMsgSets); + final MessageNumberSet[] actualMsgSets = MessageNumberSet.buildMessageNumberSets(msgSetStr); + Assert.assertNotNull(actualMsgSets, "buildMessageNumberSets() should not return null."); + Assert.assertEquals(actualMsgSets.length, 1, "buildMessageNumberSets() size mismatched."); + Assert.assertEquals(actualMsgSets[0], expectedMsgSets[0], "buildMessageNumberSets() mismatched."); + } + + /** + * Tests buildMessageNumberSets(String) method having both start and end as number. + * + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testBuildMessageNumberSetsWithStartEnd() throws ImapAsyncClientException { + final MessageNumberSet[] expectedMsgSets = { new MessageNumberSet(1, 100) }; + final String msgSetStr = MessageNumberSet.buildString(expectedMsgSets); + final MessageNumberSet[] actualMsgSets = MessageNumberSet.buildMessageNumberSets(msgSetStr); + Assert.assertNotNull(actualMsgSets, "buildMessageNumberSets() should not return null."); + Assert.assertEquals(actualMsgSets.length, 1, "buildMessageNumberSets() size mismatched."); + Assert.assertEquals(actualMsgSets[0], expectedMsgSets[0], "buildMessageNumberSets() mismatched."); + } + + /** + * Tests buildMessageNumberSets(String) method having start with a number and end with last. + * + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testBuildMessageNumberSetsWithStartEndWithLast() throws ImapAsyncClientException { + final MessageNumberSet[] expectedMsgSets = { new MessageNumberSet(1, LastMessage.LAST_MESSAGE) }; + final String msgSetStr = MessageNumberSet.buildString(expectedMsgSets); + final MessageNumberSet[] actualMsgSets = MessageNumberSet.buildMessageNumberSets(msgSetStr); + Assert.assertNotNull(actualMsgSets, "buildMessageNumberSets() should not return null."); + Assert.assertEquals(actualMsgSets.length, 1, "buildMessageNumberSets() size mismatched."); + Assert.assertEquals(actualMsgSets[0], expectedMsgSets[0], "buildMessageNumberSets() mismatched."); + } + + /** + * Tests buildMessageNumberSets(String) method with last message only. + * + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testBuildMessageNumberSetsWithLastMessageOnly() throws ImapAsyncClientException { + final MessageNumberSet[] expectedMsgSets = { new MessageNumberSet(LastMessage.LAST_MESSAGE) }; + final String msgSetStr = MessageNumberSet.buildString(expectedMsgSets); + final MessageNumberSet[] actualMsgSets = MessageNumberSet.buildMessageNumberSets(msgSetStr); + Assert.assertNotNull(actualMsgSets, "buildMessageNumberSets() should not return null."); + Assert.assertEquals(actualMsgSets.length, 1, "buildMessageNumberSets() size mismatched."); + Assert.assertEquals(actualMsgSets[0], expectedMsgSets[0], "buildMessageNumberSets() mismatched."); + } + + /** + * Tests buildMessageNumberSets(String) method with last message only. + * + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testBuildMessageNumberSetsWithEndStartWithLast() throws ImapAsyncClientException { + final MessageNumberSet[] expectedMsgSets = { new MessageNumberSet(1, LastMessage.LAST_MESSAGE) }; + final String msgSetStr = "*:1"; + final MessageNumberSet[] actualMsgSets = MessageNumberSet.buildMessageNumberSets(msgSetStr); + Assert.assertNotNull(actualMsgSets, "buildMessageNumberSets() should not return null."); + Assert.assertEquals(actualMsgSets.length, 1, "buildMessageNumberSets() size mismatched."); + Assert.assertEquals(actualMsgSets[0], expectedMsgSets[0], "buildMessageNumberSets() mismatched."); + } + + /** + * Tests buildMessageNumberSets(String) method with multiple of message number sets. + * + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testBuildMessageNumberSetsWithMultipleMsgNumSets() throws ImapAsyncClientException { + final int numMsgs = 4; + final MessageNumberSet[] expectedMsgs = new MessageNumberSet[numMsgs]; + expectedMsgs[0] = new MessageNumberSet(1, 5); + expectedMsgs[1] = new MessageNumberSet(1, LastMessage.LAST_MESSAGE); + expectedMsgs[2] = new MessageNumberSet(2, 2); + expectedMsgs[3] = new MessageNumberSet(LastMessage.LAST_MESSAGE); + final String msgSetStr = MessageNumberSet.buildString(expectedMsgs); + final MessageNumberSet[] messageNumberSets = MessageNumberSet.buildMessageNumberSets(msgSetStr); + Assert.assertNotNull(messageNumberSets, "buildMessageNumberSets() should not return null."); + Assert.assertEquals(messageNumberSets.length, numMsgs, "buildMessageNumberSets() size mismatched."); + for (int i = 0; i < numMsgs; i++) { + Assert.assertEquals(messageNumberSets[i], expectedMsgs[i], "buildMessageNumberSets() mismatched."); + } + } + /** * Tests constructor and converting it to string. * @@ -219,4 +316,124 @@ public void testCommandTypeEnum() { final LastMessage value = LastMessage.valueOf("LAST_MESSAGE"); Assert.assertSame(value, LastMessage.LAST_MESSAGE, "Enum does not match."); } + + /** + * Tests BuildMessageNumberSets with bad input with three elements in one sequence set. + */ + @Test + public void testBuildMessageNumberSetsWithBadInputTwoColonThreeElements() { + final String input = "1,2,1:2:3"; + ImapAsyncClientException ex = null; + try { + MessageNumberSet.buildMessageNumberSets(input); + } catch (final ImapAsyncClientException e) { + ex = e; + } + Assert.assertNotNull(ex, "IMAP Async Client Exception should be thrown."); + Assert.assertEquals(ex.getFailureType(), FailureType.INVALID_INPUT, "Wrong fail type was thrown."); + } + + /** + * Tests BuildMessageNumberSets with bad input with one and two colons in one sequence set. + */ + @Test + public void testBuildMessageNumberSetsWithBadInputTwoColonOneElement() { + final String input = "1,2,1::"; + ImapAsyncClientException ex = null; + try { + MessageNumberSet.buildMessageNumberSets(input); + } catch (final ImapAsyncClientException e) { + ex = e; + } + Assert.assertNotNull(ex, "IMAP Async Client Exception should be thrown."); + Assert.assertEquals(ex.getFailureType(), FailureType.INVALID_INPUT, "Wrong fail type was thrown."); + } + + /** + * Tests BuildMessageNumberSets with bad input with only one element and one colon in one sequence set. + */ + @Test + public void testBuildMessageNumberSetsWithBadInputOneColonOneElement() { + final String input = "1,2,:3"; + ImapAsyncClientException ex = null; + try { + MessageNumberSet.buildMessageNumberSets(input); + } catch (final ImapAsyncClientException e) { + ex = e; + } + Assert.assertNotNull(ex, "IMAP Async Client Exception should be thrown."); + Assert.assertEquals(ex.getFailureType(), FailureType.INVALID_INPUT, "Wrong fail type was thrown."); + } + + /** + * Tests BuildMessageNumberSets with bad input with extra space. + */ + @Test + public void testBuildMessageNumberSetsWithBadInputWithSpaceAfterColon() { + final String input = "1,2: 3"; + ImapAsyncClientException ex = null; + try { + MessageNumberSet.buildMessageNumberSets(input); + } catch (final ImapAsyncClientException e) { + ex = e; + } + Assert.assertNotNull(ex, "IMAP Async Client Exception should be thrown."); + Assert.assertEquals(ex.getFailureType(), FailureType.INVALID_INPUT, "Wrong fail type was thrown."); + } + + /** + * Tests BuildMessageNumberSets with bad input with extra space after number. + */ + @Test + public void testBuildMessageNumberSetsWithBadInputWithSpace() { + final String input = "1 ,2:3"; + ImapAsyncClientException ex = null; + try { + MessageNumberSet.buildMessageNumberSets(input); + } catch (final ImapAsyncClientException e) { + ex = e; + } + Assert.assertNotNull(ex, "IMAP Async Client Exception should be thrown."); + Assert.assertEquals(ex.getFailureType(), FailureType.INVALID_INPUT, "Wrong fail type was thrown."); + } + + /** + * Tests BuildMessageNumberSets with bad input with only one element and one colon in one sequence set. + */ + @Test + public void testBuildMessageNumberSetsWithBadInputCharacter() { + final String input = "1,2,a:3"; + ImapAsyncClientException ex = null; + try { + MessageNumberSet.buildMessageNumberSets(input); + } catch (final ImapAsyncClientException e) { + ex = e; + } + Assert.assertNotNull(ex, "IMAP Async Client Exception should be thrown."); + Assert.assertEquals(ex.getFailureType(), FailureType.INVALID_INPUT, "Wrong fail type was thrown."); + } + + /** + * Tests BuildMessageNumberSets with null input. + * + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testBuildMessageNumberSetsWithNull() throws ImapAsyncClientException { + final String input = null; + final MessageNumberSet[] mns = MessageNumberSet.buildMessageNumberSets(input); + Assert.assertNull(mns, "buildMessageNumberSets() should return null"); + } + + /** + * Tests BuildMessageNumberSets with empty string input. + * + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testBuildMessageNumberSetsWithEmptyString() throws ImapAsyncClientException { + final String input = ""; + final MessageNumberSet[] mns = MessageNumberSet.buildMessageNumberSets(input); + Assert.assertNull(mns, "buildMessageNumberSets() should return null"); + } } diff --git a/core/src/test/java/com/yahoo/imapnio/async/data/SearchResultTest.java b/core/src/test/java/com/yahoo/imapnio/async/data/SearchResultTest.java index a44dd95d..c34d11a0 100644 --- a/core/src/test/java/com/yahoo/imapnio/async/data/SearchResultTest.java +++ b/core/src/test/java/com/yahoo/imapnio/async/data/SearchResultTest.java @@ -1,6 +1,6 @@ package com.yahoo.imapnio.async.data; -import java.util.ArrayList; +import java.util.Collections; import java.util.List; import org.testng.Assert; @@ -16,21 +16,15 @@ public class SearchResultTest { */ @Test public void testSearchResult() { - final List ll = new ArrayList<>(); - ll.add(Long.MAX_VALUE - 1); - final SearchResult infos = new SearchResult(ll); - final List result = infos.getMessageNumbers(); - Assert.assertEquals(result.size(), 1, "Result mismatched."); - Assert.assertEquals(result.get(0), Long.valueOf(Long.MAX_VALUE - 1), "Result mismatched."); - } + final List ll = Collections.singletonList(Long.MAX_VALUE - 1); + final SearchResult sr = new SearchResult(ll, 1L); + final List result = sr.getMessageNumbers(); + final Long modSeq = sr.getHighestModSeq(); - /** - * Tests SearchResult constructor and getters when passing null list. - */ - @Test - public void testSearchResultNullList() { - final SearchResult infos = new SearchResult(null); - final List result = infos.getMessageNumbers(); - Assert.assertNull(result, "Result mismatched."); + Assert.assertNotNull(result, "getMessageNumbers() should not return null."); + Assert.assertEquals(result.size(), 1, "getMessageNumbers() size mismatched."); + Assert.assertEquals(result.get(0), Long.valueOf(Long.MAX_VALUE - 1), "getMessageNumbers() mismatched."); + Assert.assertNotNull(modSeq, "getHighestModSeq() should not return null"); + Assert.assertEquals(modSeq, Long.valueOf(1), "getHighestModSeq() mismatched."); } } diff --git a/core/src/test/java/com/yahoo/imapnio/async/data/StoreResultTest.java b/core/src/test/java/com/yahoo/imapnio/async/data/StoreResultTest.java new file mode 100644 index 00000000..5a8cb017 --- /dev/null +++ b/core/src/test/java/com/yahoo/imapnio/async/data/StoreResultTest.java @@ -0,0 +1,41 @@ +package com.yahoo.imapnio.async.data; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +import org.testng.Assert; +import org.testng.annotations.Test; + +import com.sun.mail.iap.ProtocolException; +import com.sun.mail.imap.protocol.FetchResponse; +import com.sun.mail.imap.protocol.IMAPResponse; + +/** + * Unit test for {@code StoreResult}. + */ +public class StoreResultTest { + /** + * Tests StoreResult constructor and getters. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testStoreResult() throws IOException, ProtocolException { + final FetchResponse fr = new FetchResponse(new IMAPResponse("* 1 FETCH (UID 4 MODSEQ (12121231000))")); + final List fetchResponses = Collections.singletonList(fr); + final MessageNumberSet[] modifiedMsgSet = { new MessageNumberSet(1L, 1L) }; + final StoreResult storeResult = new StoreResult(1L, fetchResponses, modifiedMsgSet); + final Long highestModSeq = storeResult.getHighestModSeq(); + final List responses = storeResult.getFetchResponses(); + + Assert.assertEquals(responses.size(), 1, "getFetchResponses() mismatched."); + Assert.assertNotNull(highestModSeq, "getHighestModSeq() should not return null"); + Assert.assertEquals(highestModSeq, Long.valueOf(1L), "getHighestModSeq() mismatched."); + final MessageNumberSet[] modifiedMsgsets = storeResult.getModifiedMsgSets(); + Assert.assertNotNull(modifiedMsgsets, "getModifiedMsgSets() should not return null."); + Assert.assertEquals(modifiedMsgsets.length, 1, "getModifiedMsgSets() size mismatched."); + Assert.assertEquals(modifiedMsgsets[0], modifiedMsgSet[0], "getModifiedMsgSets() mismatched."); + } +} diff --git a/core/src/test/java/com/yahoo/imapnio/async/request/ExamineFolderCommandTest.java b/core/src/test/java/com/yahoo/imapnio/async/request/ExamineFolderCommandTest.java index 0d62edae..d5a8264e 100644 --- a/core/src/test/java/com/yahoo/imapnio/async/request/ExamineFolderCommandTest.java +++ b/core/src/test/java/com/yahoo/imapnio/async/request/ExamineFolderCommandTest.java @@ -120,4 +120,16 @@ public void testGetCommandLineWithQResyncParam() throws ImapAsyncClientException cmd = new ExamineFolderCommand(folderName, qResyncParameter); Assert.assertEquals(cmd.getCommandLine(), EXAMINE + "&bUuL1Q- (QRESYNC (100 4223212 1:200 (1 1:10)))\r\n", "Expected result mismatched."); } + + /** + * Tests getCommandLine method with CONDSTORE enable. + * + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testGetCommandLineWithCondStore() throws ImapAsyncClientException { + final String folderName = "测试"; + ImapRequest cmd = new ExamineFolderCommand(folderName, true); + Assert.assertEquals(cmd.getCommandLine(), EXAMINE + "&bUuL1Q- (CONDSTORE)\r\n", "getCommandLine() mismatched."); + } } diff --git a/core/src/test/java/com/yahoo/imapnio/async/request/FetchCommandTest.java b/core/src/test/java/com/yahoo/imapnio/async/request/FetchCommandTest.java index 7897ab31..cd1d473c 100644 --- a/core/src/test/java/com/yahoo/imapnio/async/request/FetchCommandTest.java +++ b/core/src/test/java/com/yahoo/imapnio/async/request/FetchCommandTest.java @@ -109,6 +109,54 @@ public void testGetCommandLineWithStartEndConstructor() } } + /** + * Tests getCommandLine method using MessageNumberSet[], macro, and changed since the given modification sequence. + * + * @throws ImapAsyncClientException will not throw + * @throws IllegalAccessException will not throw + * @throws IllegalArgumentException will not throw + */ + @Test + public void testGetCommandLineFromConstructorWithChangedSince() throws ImapAsyncClientException, IllegalArgumentException, + IllegalAccessException { + + final long[] msgs = { 4294967293L, 4294967294L, 4294967295L }; + final MessageNumberSet[] msgsets = MessageNumberSet.createMessageNumberSets(msgs); + final ImapRequest cmd = new FetchCommand(msgsets, FetchMacro.FAST, 1L); + Assert.assertEquals(cmd.getCommandLine(), "FETCH 4294967293:4294967295 FAST (CHANGEDSINCE 1)\r\n", + "getCommandLine() mismatched."); + + cmd.cleanup(); + // Verify if cleanup happened correctly. + for (final Field field : fieldsToCheck) { + Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null"); + } + } + + /** + * Tests getCommandLine method using MessageNumberSet[], data items, and changed since the given modification sequence. + * + * @throws ImapAsyncClientException will not throw + * @throws IllegalAccessException will not throw + * @throws IllegalArgumentException will not throw + */ + @Test + public void testGetCommandLineFromConstructorWithDataItemsChangedSince() throws ImapAsyncClientException, IllegalArgumentException, + IllegalAccessException { + + final int[] msgs = { 1, 2, 3 }; + final MessageNumberSet[] msgsets = MessageNumberSet.createMessageNumberSets(msgs); + final ImapRequest cmd = new FetchCommand(msgsets, DATA_ITEMS, 1L); + Assert.assertEquals(cmd.getCommandLine(), "FETCH 1:3 (FLAGS BODY[HEADER.FIELDS (DATE FROM)]) (CHANGEDSINCE 1)\r\n", + "getCommandLine() mismatched."); + + cmd.cleanup(); + // Verify if cleanup happened correctly. + for (final Field field : fieldsToCheck) { + Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null"); + } + } + /** * Tests getStreamingResponsesQueue method. */ diff --git a/core/src/test/java/com/yahoo/imapnio/async/request/ImapArgumentFormatterTest.java b/core/src/test/java/com/yahoo/imapnio/async/request/ImapArgumentFormatterTest.java index a3b03304..b00cb426 100644 --- a/core/src/test/java/com/yahoo/imapnio/async/request/ImapArgumentFormatterTest.java +++ b/core/src/test/java/com/yahoo/imapnio/async/request/ImapArgumentFormatterTest.java @@ -215,4 +215,36 @@ public void testBuildFlagStringWithUserFlagString() { Assert.assertNotNull(s, "buildFlagString() should not return null."); Assert.assertEquals(s, "(userflag1 userflag2)", "result mismatched."); } + + /** + * Tests buildEntryFlagName with all flags. + */ + @Test + public void testBuildEntryFlagName() { + final String[] expectedFlags = new String[] {"\\Answered", "\\Deleted", "\\Draft", "\\Flagged", "\\Recent", "\\Seen"}; + final Flags flags = new Flags(); + flags.add(Flags.Flag.ANSWERED); + flags.add(Flags.Flag.DELETED); + flags.add(Flags.Flag.DRAFT); + flags.add(Flags.Flag.FLAGGED); + flags.add(Flags.Flag.RECENT); + flags.add(Flags.Flag.SEEN); + flags.add("userflag1"); + final ImapArgumentFormatter writer = new ImapArgumentFormatter(); + final Flags.Flag[] systemFlags = flags.getSystemFlags(); + for (int i = 0; i < systemFlags.length; i++) { + final Flags singleSystemFlag = new Flags(); + singleSystemFlag.add(systemFlags[i]); + final String systemEntryName = writer.buildEntryFlagName(singleSystemFlag); + Assert.assertEquals(systemEntryName, "\"/flags/\\" + expectedFlags[i] + "\"", "buildEntryFlagName() mismatched."); + } + + final String[] userFlags = flags.getUserFlags(); + final Flags singleUserFlag = new Flags(); + singleUserFlag.add(userFlags[0]); + final String userEntryName = writer.buildEntryFlagName(singleUserFlag); + + Assert.assertEquals(userFlags.length, 1, "result mismatched"); + Assert.assertEquals(userEntryName, "\"/flags/userflag1\"", "buildEntryFlagName() mismatched"); + } } diff --git a/core/src/test/java/com/yahoo/imapnio/async/request/SearchCommandTest.java b/core/src/test/java/com/yahoo/imapnio/async/request/SearchCommandTest.java index d5e60d09..d4b0cbb1 100644 --- a/core/src/test/java/com/yahoo/imapnio/async/request/SearchCommandTest.java +++ b/core/src/test/java/com/yahoo/imapnio/async/request/SearchCommandTest.java @@ -14,7 +14,9 @@ import java.util.Set; import javax.mail.Flags; +import javax.mail.search.BodyTerm; import javax.mail.search.FlagTerm; +import javax.mail.search.OrTerm; import javax.mail.search.SearchException; import javax.mail.search.SubjectTerm; @@ -25,6 +27,7 @@ import com.sun.mail.iap.Argument; import com.sun.mail.iap.Literal; import com.yahoo.imapnio.async.data.Capability; +import com.yahoo.imapnio.async.data.ExtendedModifiedSinceTerm; import com.yahoo.imapnio.async.data.MessageNumberSet; import com.yahoo.imapnio.async.data.MessageNumberSet.LastMessage; import com.yahoo.imapnio.async.exception.ImapAsyncClientException; @@ -237,6 +240,137 @@ public void testGetCommandLineNoneNullMessageSeqSetsNullSearchTermNullCharset() } } + /** + * Tests getCommandLine method with null message sequences set, MODSEQ SearchTerm. + * + * @throws IOException will not throw + * @throws IllegalAccessException will not throw + * @throws IllegalArgumentException will not throw + * @throws ImapAsyncClientException will not throw + * @throws SearchException will not throw + */ + @Test + public void testGetCommandLineNonNullModSeq() throws IOException, IllegalArgumentException, IllegalAccessException, SearchException, + ImapAsyncClientException { + final ExtendedModifiedSinceTerm extendedModifiedSinceTerm = new ExtendedModifiedSinceTerm(1L); + final MessageNumberSet[] msgsets = null; + final ImapRequest cmd = new SearchCommand(msgsets, extendedModifiedSinceTerm, null); + Assert.assertEquals(cmd.getCommandLine(), "SEARCH MODSEQ 1\r\n", "getCommandLine() mismatched."); + + cmd.cleanup(); + // Verify if cleanup happened correctly. + for (final Field field : fieldsToCheck) { + Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null"); + } + } + + /** + * Tests getCommandLine method with null message sequences set, MODSEQ SearchTerm with entry name and entry type. + * + * @throws IOException will not throw + * @throws IllegalAccessException will not throw + * @throws IllegalArgumentException will not throw + * @throws ImapAsyncClientException will not throw + * @throws SearchException will not throw + */ + @Test + public void testGetCommandLineNonNullModSeqWithFlagAnsweredTypeAll() throws IOException, IllegalArgumentException, IllegalAccessException, + SearchException, ImapAsyncClientException { + final Flags flags = new Flags(); + flags.add(Flags.Flag.ANSWERED); + final ExtendedModifiedSinceTerm extendedModifiedSinceTerm = new ExtendedModifiedSinceTerm(flags, EntryTypeRequest.ALL, 1L); + final MessageNumberSet[] msgsets = null; + final ImapRequest cmd = new SearchCommand(msgsets, extendedModifiedSinceTerm, null); + Assert.assertEquals(cmd.getCommandLine(), "SEARCH MODSEQ \"/flags/\\\\Answered\" ALL 1\r\n", "getCommandLine() mismatched."); + + cmd.cleanup(); + // Verify if cleanup happened correctly. + for (final Field field : fieldsToCheck) { + Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null"); + } + } + + /** + * Tests getCommandLine method with null message sequences set, MODSEQ SearchTerm with entry name and entry type. + * + * @throws IOException will not throw + * @throws IllegalArgumentException will not throw + * @throws IllegalAccessException will not throw + * @throws SearchException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testGetCommandLineNonNullModSeqWithFlagDeletedTypePriv() throws IOException, IllegalArgumentException, IllegalAccessException, + SearchException, ImapAsyncClientException { + final Flags flags = new Flags(); + flags.add(Flags.Flag.DELETED); + final ExtendedModifiedSinceTerm extendedModifiedSinceTerm = new ExtendedModifiedSinceTerm(flags, EntryTypeRequest.PRIVATE, 1L); + final MessageNumberSet[] msgsets = null; + final ImapRequest cmd = new SearchCommand(msgsets, extendedModifiedSinceTerm, null); + Assert.assertEquals(cmd.getCommandLine(), "SEARCH MODSEQ \"/flags/\\\\Deleted\" PRIV 1\r\n", "getCommandLine() mismatched."); + + cmd.cleanup(); + // Verify if cleanup happened correctly. + for (final Field field : fieldsToCheck) { + Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null"); + } + } + + /** + * Tests getCommandLine method with null message sequences set, MODSEQ SearchTerm with entry name and entry type. + * + * @throws IOException will not throw + * @throws IllegalAccessException will not throw + * @throws IllegalArgumentException will not throw + * @throws ImapAsyncClientException will not throw + * @throws SearchException will not throw + */ + @Test + public void testGetCommandLineNonNullModSeqWithFlagDraftTypeShared() throws IOException, IllegalArgumentException, IllegalAccessException, + SearchException, ImapAsyncClientException { + final Flags flags = new Flags(); + flags.add(Flags.Flag.DRAFT); + final ExtendedModifiedSinceTerm extendedModifiedSinceTerm = new ExtendedModifiedSinceTerm(flags, EntryTypeRequest.SHARED, 1L); + final MessageNumberSet[] msgsets = null; + final ImapRequest cmd = new SearchCommand(msgsets, extendedModifiedSinceTerm, null); + Assert.assertEquals(cmd.getCommandLine(), "SEARCH MODSEQ \"/flags/\\\\Draft\" SHARED 1\r\n", "getCommandLine() mismatched."); + + cmd.cleanup(); + // Verify if cleanup happened correctly. + for (final Field field : fieldsToCheck) { + Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null"); + } + } + + /** + * Tests getCommandLine method with null message sequences set, MODSEQ SearchTerm with entry name and entry type. + * + * @throws IOException will not throw + * @throws IllegalAccessException will not throw + * @throws IllegalArgumentException will not throw + * @throws ImapAsyncClientException will not throw + * @throws SearchException will not throw + */ + @Test + public void testGetCommandLineOrNonNullModSeqWithFlagDraftTypeShared() throws IOException, IllegalArgumentException, IllegalAccessException, + SearchException, ImapAsyncClientException { + final Flags flags = new Flags(); + flags.add(Flags.Flag.DRAFT); + final ExtendedModifiedSinceTerm extendedModifiedSinceTerm = new ExtendedModifiedSinceTerm(flags, EntryTypeRequest.SHARED, 1L); + final BodyTerm bodyTerm = new BodyTerm("Text"); + final OrTerm orTerm = new OrTerm(extendedModifiedSinceTerm, bodyTerm); + final MessageNumberSet[] msgsets = null; + final ImapRequest cmd = new SearchCommand(msgsets, orTerm, null); + Assert.assertEquals(cmd.getCommandLine(), "SEARCH OR MODSEQ \"/flags/\\\\Draft\" SHARED 1 BODY Text\r\n", + "getCommandLine() mismatched."); + + cmd.cleanup(); + // Verify if cleanup happened correctly. + for (final Field field : fieldsToCheck) { + Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null"); + } + } + /** * Tests getCommandLine method with none-null message sequences set, null SearchTerm. * diff --git a/core/src/test/java/com/yahoo/imapnio/async/request/SelectFolderCommandTest.java b/core/src/test/java/com/yahoo/imapnio/async/request/SelectFolderCommandTest.java index 0d2edad4..2afa9029 100644 --- a/core/src/test/java/com/yahoo/imapnio/async/request/SelectFolderCommandTest.java +++ b/core/src/test/java/com/yahoo/imapnio/async/request/SelectFolderCommandTest.java @@ -121,4 +121,16 @@ public void testGetCommandLineWithQResyncParam() throws ImapAsyncClientException cmd = new SelectFolderCommand(folderName, qResyncParameter); Assert.assertEquals(cmd.getCommandLine(), SELECT + "&bUuL1Q- (QRESYNC (100 4223212 1:200 (1 1:10)))\r\n", "Expected result mismatched."); } + + /** + * Tests getCommandLine method with CONDSTORE enabled. + * + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testGetCommandLineWithCondStore() throws ImapAsyncClientException { + final String folderName = "测试"; + ImapRequest cmd = new SelectFolderCommand(folderName, true); + Assert.assertEquals(cmd.getCommandLine(), SELECT + "&bUuL1Q- (CONDSTORE)\r\n", "getCommandLine() mismatched."); + } } diff --git a/core/src/test/java/com/yahoo/imapnio/async/request/StoreFlagsCommandTest.java b/core/src/test/java/com/yahoo/imapnio/async/request/StoreFlagsCommandTest.java index 64e8b03b..24710d69 100644 --- a/core/src/test/java/com/yahoo/imapnio/async/request/StoreFlagsCommandTest.java +++ b/core/src/test/java/com/yahoo/imapnio/async/request/StoreFlagsCommandTest.java @@ -216,6 +216,85 @@ public void testGetCommandLineWithMessageSeqStringFlagsAddedAndSilent() } } + /** + * Tests getCommandLine method using message sequences, flags, adding flags, not silent, and the given unchanged since modification sequence. + * + * @throws IllegalAccessException will not throw + * @throws IllegalArgumentException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testGetCommandLineWithFlagsAddedNotSilentUnchangedSince() throws IllegalArgumentException, IllegalAccessException, + ImapAsyncClientException { + + final int[] msgs = { 1, 2, 3 }; + final MessageNumberSet[] msgsets = MessageNumberSet.createMessageNumberSets(msgs); + final Flags flags = new Flags(); + flags.add(Flags.Flag.SEEN); + flags.add(Flags.Flag.DELETED); + final ImapRequest cmd = new StoreFlagsCommand(msgsets, flags, FlagsAction.ADD, 1L); + Assert.assertEquals(cmd.getCommandLine(), "STORE 1:3 (UNCHANGEDSINCE 1) +FLAGS (\\Deleted \\Seen)\r\n", + "getCommandLine() mismatched."); + + cmd.cleanup(); + // Verify if cleanup happened correctly. + for (final Field field : fieldsToCheck) { + Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null"); + } + } + + /** + * Tests getCommandLine method using message sequences, flags, adding flags, silent, and the given unchanged since modification sequence. + * + * @throws IllegalAccessException will not throw + * @throws IllegalArgumentException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testGetCommandLineWithFlagsAddedSilentUnchangedSince() throws IllegalArgumentException, IllegalAccessException, + ImapAsyncClientException { + + final int[] msgs = { 1, 2, 3 }; + final MessageNumberSet[] msgsets = MessageNumberSet.createMessageNumberSets(msgs); + final Flags flags = new Flags(); + flags.add(Flags.Flag.SEEN); + flags.add(Flags.Flag.DELETED); + final boolean isSilent = true; + final ImapRequest cmd = new StoreFlagsCommand(msgsets, flags, FlagsAction.ADD, isSilent, 1L); + Assert.assertEquals(cmd.getCommandLine(), "STORE 1:3 (UNCHANGEDSINCE 1) +FLAGS.SILENT (\\Deleted \\Seen)\r\n", + "getCommandLine() mismatched."); + + cmd.cleanup(); + // Verify if cleanup happened correctly. + for (final Field field : fieldsToCheck) { + Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null"); + } + } + + /** + * Tests getCommandLine method with message sequences, adding flags, silent, and the given unchanged since modification sequence. + * + * @throws IllegalAccessException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testGetCommandLineWithMessageSeqStringFlagsAddedAndSilentUnchangedSince() throws IllegalAccessException, ImapAsyncClientException { + + final Flags flags = new Flags(); + flags.add(Flags.Flag.SEEN); + flags.add(Flags.Flag.DELETED); + final boolean isSilent = true; + final ImapRequest cmd = new StoreFlagsCommand("1:*", flags, FlagsAction.ADD, isSilent, 1L); + Assert.assertEquals(cmd.getCommandLine(), "STORE 1:* (UNCHANGEDSINCE 1) +FLAGS.SILENT (\\Deleted \\Seen)\r\n", + "getCommandLine() mismatched."); + + cmd.cleanup(); + // Verify if cleanup happened correctly. + for (final Field field : fieldsToCheck) { + Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null"); + } + } + /** * Tests getStreamingResponsesQueue method. */ diff --git a/core/src/test/java/com/yahoo/imapnio/async/request/UidFetchCommandTest.java b/core/src/test/java/com/yahoo/imapnio/async/request/UidFetchCommandTest.java index cab47b64..e8055083 100644 --- a/core/src/test/java/com/yahoo/imapnio/async/request/UidFetchCommandTest.java +++ b/core/src/test/java/com/yahoo/imapnio/async/request/UidFetchCommandTest.java @@ -129,6 +129,102 @@ public void testGetCommandLineFromConstructorWithUidStringAndMacro() } } + /** + * Tests getCommandLine method using MessageNumberSet[], data items, and changed since the given modification sequence. + * + * @throws ImapAsyncClientException will not throw + * @throws IllegalAccessException will not throw + * @throws IllegalArgumentException will not throw + */ + @Test + public void testGetCommandLineFromConstructorWithChangedSince() throws ImapAsyncClientException, IllegalArgumentException, + IllegalAccessException { + + final long[] msgs = { 1L, 2L, 3L }; + final MessageNumberSet[] msgsets = MessageNumberSet.createMessageNumberSets(msgs); + final ImapRequest cmd = new UidFetchCommand(msgsets, DATA_ITEMS, 1L); + Assert.assertEquals(cmd.getCommandLine(), "UID FETCH 1:3 (FLAGS BODY[HEADER.FIELDS (DATE FROM)]) (CHANGEDSINCE 1)\r\n", + "getCommandLine() mismatched."); + + cmd.cleanup(); + // Verify if cleanup happened correctly. + for (final Field field : fieldsToCheck) { + Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null"); + } + } + + /** + * Tests getCommandLine method using MessageNumberSet[], macro, and changed since the given modification sequence. + * + * @throws ImapAsyncClientException will not throw + * @throws IllegalAccessException will not throw + * @throws IllegalArgumentException will not throw + */ + @Test + public void testGetCommandLineFromConstructorWithMacroChangedSince() throws ImapAsyncClientException, IllegalArgumentException, + IllegalAccessException { + + final long[] msgs = { 4294967293L, 4294967294L, 4294967295L }; + final MessageNumberSet[] msgsets = MessageNumberSet.createMessageNumberSets(msgs); + final ImapRequest cmd = new UidFetchCommand(msgsets, FetchMacro.FAST, 1L); + Assert.assertEquals(cmd.getCommandLine(), "UID FETCH 4294967293:4294967295 FAST (CHANGEDSINCE 1)\r\n", + "getCommandLine() mismatched."); + + cmd.cleanup(); + // Verify if cleanup happened correctly. + for (final Field field : fieldsToCheck) { + Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null"); + } + } + + /** + * Tests getCommandLine method using MessageNumberSet[], data items, changed since the given modification sequence and vanished flag. + * + * @throws ImapAsyncClientException will not throw + * @throws IllegalAccessException will not throw + * @throws IllegalArgumentException will not throw + */ + @Test + public void testGetCommandLineFromConstructorWithChangedSinceVanished() throws ImapAsyncClientException, IllegalArgumentException, + IllegalAccessException { + + final long[] msgs = { 1L, 2L, 3L }; + final MessageNumberSet[] msgsets = MessageNumberSet.createMessageNumberSets(msgs); + final ImapRequest cmd = new UidFetchCommand(msgsets, DATA_ITEMS, 1L, true); + Assert.assertEquals(cmd.getCommandLine(), "UID FETCH 1:3 (FLAGS BODY[HEADER.FIELDS (DATE FROM)]) (CHANGEDSINCE 1 VANISHED)\r\n", + "getCommandLine() mismatched."); + + cmd.cleanup(); + // Verify if cleanup happened correctly. + for (final Field field : fieldsToCheck) { + Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null"); + } + } + + /** + * Tests getCommandLine method using MessageNumberSet[], macro, changed since the given modification sequence and vanished flag. + * + * @throws ImapAsyncClientException will not throw + * @throws IllegalAccessException will not throw + * @throws IllegalArgumentException will not throw + */ + @Test + public void testGetCommandLineFromConstructorWithMacroChangedSinceVanished() throws ImapAsyncClientException, IllegalArgumentException, + IllegalAccessException { + + final long[] msgs = { 4294967293L, 4294967294L, 4294967295L }; + final MessageNumberSet[] msgsets = MessageNumberSet.createMessageNumberSets(msgs); + final ImapRequest cmd = new UidFetchCommand(msgsets, FetchMacro.FAST, 1L, true); + Assert.assertEquals(cmd.getCommandLine(), "UID FETCH 4294967293:4294967295 FAST (CHANGEDSINCE 1 VANISHED)\r\n", + "getCommandLine() mismatched."); + + cmd.cleanup(); + // Verify if cleanup happened correctly. + for (final Field field : fieldsToCheck) { + Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null"); + } + } + /** * Tests getCommandType method. */ diff --git a/core/src/test/java/com/yahoo/imapnio/async/request/UidStoreFlagsCommandTest.java b/core/src/test/java/com/yahoo/imapnio/async/request/UidStoreFlagsCommandTest.java index 2f8fb100..11dc2a01 100644 --- a/core/src/test/java/com/yahoo/imapnio/async/request/UidStoreFlagsCommandTest.java +++ b/core/src/test/java/com/yahoo/imapnio/async/request/UidStoreFlagsCommandTest.java @@ -216,6 +216,85 @@ public void testGetCommandLineWithMessageSeqStringFlagsAddedAndSilent() } } + /** + * Tests getCommandLine method using message sequences, flags, adding flags, not silent, and unchanged since modification sequence.. + * + * @throws IllegalAccessException will not throw + * @throws IllegalArgumentException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testGetCommandLineWithFlagsAddedNotSilentUnchangedSince() throws IllegalArgumentException, IllegalAccessException, + ImapAsyncClientException { + + final int[] msgs = { 1, 2, 3 }; + final MessageNumberSet[] msgsets = MessageNumberSet.createMessageNumberSets(msgs); + final Flags flags = new Flags(); + flags.add(Flags.Flag.SEEN); + flags.add(Flags.Flag.DELETED); + final ImapRequest cmd = new UidStoreFlagsCommand(msgsets, flags, FlagsAction.ADD, 1L); + Assert.assertEquals(cmd.getCommandLine(), "UID STORE 1:3 (UNCHANGEDSINCE 1) +FLAGS (\\Deleted \\Seen)\r\n", + "getCommandLine() mismatched."); + + cmd.cleanup(); + // Verify if cleanup happened correctly. + for (final Field field : fieldsToCheck) { + Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null"); + } + } + + /** + * Tests getCommandLine method using message sequences, flags, adding flags, silent, and unchanged since modification sequence. + * + * @throws IllegalAccessException will not throw + * @throws IllegalArgumentException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testGetCommandLineWithFlagsAddedSilentUnchangedSince() throws IllegalArgumentException, IllegalAccessException, + ImapAsyncClientException { + + final int[] msgs = { 1, 2, 3 }; + final MessageNumberSet[] msgsets = MessageNumberSet.createMessageNumberSets(msgs); + final Flags flags = new Flags(); + flags.add(Flags.Flag.SEEN); + flags.add(Flags.Flag.DELETED); + final boolean isSilent = true; + final ImapRequest cmd = new UidStoreFlagsCommand(msgsets, flags, FlagsAction.ADD, isSilent, 1L); + Assert.assertEquals(cmd.getCommandLine(), "UID STORE 1:3 (UNCHANGEDSINCE 1) +FLAGS.SILENT (\\Deleted \\Seen)\r\n", + "getCommandLine() mismatched."); + + cmd.cleanup(); + // Verify if cleanup happened correctly. + for (final Field field : fieldsToCheck) { + Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null"); + } + } + + /** + * Tests getCommandLine method with message sequences, adding flags, silent, and unchanged since modification sequence. + * + * @throws IllegalAccessException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testGetCommandLineWithMessageSeqStringFlagsAddedAndSilentUnchangedSince() throws IllegalAccessException, ImapAsyncClientException { + + final Flags flags = new Flags(); + flags.add(Flags.Flag.SEEN); + flags.add(Flags.Flag.DELETED); + final boolean isSilent = true; + final ImapRequest cmd = new UidStoreFlagsCommand("1:*", flags, FlagsAction.ADD, isSilent, 1L); + Assert.assertEquals(cmd.getCommandLine(), "UID STORE 1:* (UNCHANGEDSINCE 1) +FLAGS.SILENT (\\Deleted \\Seen)\r\n", + "getCommandLine() mismatched."); + + cmd.cleanup(); + // Verify if cleanup happened correctly. + for (final Field field : fieldsToCheck) { + Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null"); + } + } + /** * Tests getCommandType method. */ diff --git a/core/src/test/java/com/yahoo/imapnio/async/response/ImapResponseMapperTest.java b/core/src/test/java/com/yahoo/imapnio/async/response/ImapResponseMapperTest.java index c2a218d5..d28f406a 100644 --- a/core/src/test/java/com/yahoo/imapnio/async/response/ImapResponseMapperTest.java +++ b/core/src/test/java/com/yahoo/imapnio/async/response/ImapResponseMapperTest.java @@ -1,1441 +1,1987 @@ -package com.yahoo.imapnio.async.response; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.mail.Flags.Flag; -import javax.mail.Folder; - -import org.testng.Assert; -import org.testng.annotations.Test; - -import com.sun.mail.iap.ProtocolException; -import com.sun.mail.imap.AppendUID; -import com.sun.mail.imap.CopyUID; -import com.sun.mail.imap.protocol.ID; -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.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; - -/** - * Unit test for {@link ImapResponseMapper}. - */ -public class ImapResponseMapperTest { - /** Imap server greeting. */ - private static final String GREETING = "* OK [CAPABILITY IMAP4rev1 SASL-IR AUTH=PLAIN AUTH=XOAUTH2 AUTH=OAUTHBEARER ID MOVE NAMESPACE " - + "XYMHIGHESTMODSEQ UIDPLUS LITERAL+ CHILDREN X-MSG-EXT] IMAP4rev1 Hello"; - - /** - * Tests parseToCapabilities method successfully. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseToCapabilitiesFromCapaCommand() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final List content = new ArrayList<>(); - content.add(new IMAPResponse("* some junks\r\n")); - content.add(new IMAPResponse("* CAPABILITY IMAP4rev1 SASL-IR AUTH=PLAIN AUTH=XOAUTH2 AUTH=OAUTHBEARER ID MOVE NAMESPACE\r\n")); - content.add(new IMAPResponse("* more junks\r\n")); - content.add(new IMAPResponse("a1 OK CAPABILITY completed\r\n")); - final Capability capa = mapper.readValue(content.toArray(new IMAPResponse[0]), Capability.class); - - // verify the result - Assert.assertNotNull(capa, "result should never return null."); - Assert.assertTrue(capa.hasCapability("IMAP4rev1".toUpperCase()), "One capability missed."); - Assert.assertTrue(capa.hasCapability("SASL-IR"), "One capability missed."); - Assert.assertTrue(capa.hasCapability("ID"), "One capability missed."); - Assert.assertTrue(capa.hasCapability("MOVE"), "One capability missed."); - Assert.assertTrue(capa.hasCapability("NAMESPACE"), "One capability missed."); - final List authValues = capa.getCapability("AUTH"); - Assert.assertNotNull(authValues, "AUTH values missed."); - Assert.assertEquals(authValues.size(), 3, "One Auth value missed"); - Assert.assertEquals(authValues.get(0), "PLAIN", "One Auth value missed"); - Assert.assertEquals(authValues.get(1), "XOAUTH2", "One Auth value missed"); - Assert.assertEquals(authValues.get(2), "OAUTHBEARER", "One Auth value missed"); - } - - /** - * Tests parseToCapabilities method when ImapResponse array has zero length. - * - * @throws ProtocolException will not throw - */ - @Test - public void testParseToCapabilitiesArrayLengthZero() throws ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = {}; - - ImapAsyncClientException cause = null; - try { - mapper.readValue(content, Capability.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - - /** - * Tests parseToCapabilities method successfully from an OK response that has Capability response attached to. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseToCapabilitiesFromGreeting() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = { new IMAPResponse(GREETING) }; - final Capability capa = mapper.readValue(content, Capability.class); - - // verify the result - Assert.assertNotNull(capa, "result should never return null."); - Assert.assertTrue(capa.hasCapability("IMAP4rev1".toUpperCase()), "One capability missed."); - Assert.assertTrue(capa.hasCapability("SASL-IR"), "One capability missed."); - Assert.assertTrue(capa.hasCapability("ID"), "One capability missed."); - Assert.assertTrue(capa.hasCapability("MOVE"), "One capability missed."); - Assert.assertTrue(capa.hasCapability("NAMESPACE"), "One capability missed."); - Assert.assertTrue(capa.hasCapability("X-MSG-EXT"), "One capability missed."); - Assert.assertTrue(capa.hasCapability("LITERAL+"), "One capability missed."); - final List authValues = capa.getCapability("AUTH"); - Assert.assertNotNull(authValues, "AUTH values missed."); - Assert.assertEquals(authValues.size(), 3, "One Auth value missed"); - Assert.assertEquals(authValues.get(0), "PLAIN", "One Auth value missed"); - Assert.assertEquals(authValues.get(1), "XOAUTH2", "One Auth value missed"); - Assert.assertEquals(authValues.get(2), "OAUTHBEARER", "One Auth value missed"); - } - - /** - * Tests parseToCapabilities method successfully when it has Netscape Messaging Server response. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseToCapabilitiesSkipStar() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = { - new IMAPResponse("* CAPABILITY * IMAP4rev1 SASL-IR AUTH=PLAIN AUTH=XOAUTH2 AUTH=OAUTHBEARER ID MOVE NAMESPACE") }; - final Capability capa = mapper.readValue(content, Capability.class); - - // verify the result - Assert.assertTrue(capa.hasCapability("IMAP4rev1"), "One capability missed."); - Assert.assertTrue(capa.hasCapability("SASL-IR"), "One capability missed."); - Assert.assertTrue(capa.hasCapability("ID"), "One capability missed."); - Assert.assertTrue(capa.hasCapability("MOVE"), "One capability missed."); - Assert.assertTrue(capa.hasCapability("NAMESPACE"), "One capability missed."); - final List authValues = capa.getCapability("AUTH"); - Assert.assertNotNull(authValues, "AUTH values missed."); - Assert.assertEquals(authValues.size(), 3, "One Auth value missed"); - Assert.assertEquals(authValues.get(0), "PLAIN", "One Auth value missed"); - Assert.assertEquals(authValues.get(1), "XOAUTH2", "One Auth value missed"); - Assert.assertEquals(authValues.get(2), "OAUTHBEARER", "One Auth value missed"); - } - - /** - * Tests parseCopyUid successfully. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseCopyUidSuccess() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[5]; - content[0] = new IMAPResponse("* OK [COPYUID 1549405125 150395 3]"); // a good response - content[1] = new IMAPResponse("* BAD Some junks"); // test if it skips the bad response - content[2] = null; // test if it skips null - content[3] = new IMAPResponse("* OK [SOMETHING 111 222 3]"); // test if it detects it is not COPYUID keyword - content[4] = new IMAPResponse("* OK"); // test when b is 0 - - final CopyUID copyUid = mapper.readValue(content, CopyUID.class); - - // verify the result - Assert.assertNotNull(copyUid, "result mismatched."); - Assert.assertEquals(copyUid.uidvalidity, 1549405125, "result mismatched."); - Assert.assertNotNull(copyUid.src, "result mismatched."); - Assert.assertNotNull(copyUid.dst, "result mismatched."); - Assert.assertEquals(copyUid.src.length, 1, "result mismatched."); - Assert.assertEquals(copyUid.src[0].start, 150395, "result mismatched."); - Assert.assertEquals(copyUid.src[0].end, 150395, "result mismatched."); - Assert.assertEquals(copyUid.dst.length, 1, "result mismatched."); - Assert.assertEquals(copyUid.dst[0].start, 3, "result mismatched."); - Assert.assertEquals(copyUid.dst[0].end, 3, "result mismatched."); - } - - /** - * Tests ImapResponseParse parseCopyUid when Responses array is empty. - * - * @throws ProtocolException will not throw - */ - @Test - public void testParseCopyUidResponseArrayZero() throws ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[0]; - - ImapAsyncClientException cause = null; - try { - mapper.readValue(content, CopyUID.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - - } - - /** - * Tests parseAppendUid successfully. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseAppendUidSuccess() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[3]; - content[0] = new IMAPResponse("+ Ready for literal data"); - content[1] = new IMAPResponse("* 3 EXISTS"); - content[2] = new IMAPResponse("a5 OK [APPENDUID 1459808247 150399] APPEND completed"); - final AppendUID appendUid = mapper.readValue(content, AppendUID.class); - - // verify the result - Assert.assertNotNull(appendUid, "result mismatched."); - Assert.assertEquals(appendUid.uidvalidity, 1459808247, "result mismatched."); - Assert.assertEquals(appendUid.uid, 150399, "result mismatched."); - } - - /** - * Tests parseToCapabilities method when ImapResponse array has zero length. - * - * @throws ProtocolException will not throw - */ - @Test - public void testParseToAppendUidsArrayLengthZero() throws ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = {}; - - ImapAsyncClientException cause = null; - try { - mapper.readValue(content, AppendUID.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - - /** - * Tests parseAppendUid with a BAD response. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - */ - @Test - public void testParseAppendUidNotOK() throws IOException, ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = { new IMAPResponse("* BAD Some junks") }; - - ImapAsyncClientException cause = null; - try { - mapper.readValue(content, AppendUID.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - - } - - /** - * Tests parseAppendUid when b is 0, or when left bracket is not found. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - */ - @Test - public void testParseAppendUidByteReadExhausted() throws IOException, ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = { new IMAPResponse("* OK") }; // test when b is 0 - - ImapAsyncClientException cause = null; - try { - mapper.readValue(content, AppendUID.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - - /** - * Tests parseAppendUid when b is 0. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - */ - @Test - public void testParseAppendUidNoAppendUidKeyword() throws IOException, ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = { new IMAPResponse("* OK [appendButNotUid 111 222 3]") }; // test when b is 0 - - ImapAsyncClientException cause = null; - try { - mapper.readValue(content, AppendUID.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - - } - - /** - * Tests parseMailboxInfo method successfully. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseMailboxInfoReadOnlySuccess() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[8]; - content[0] = new IMAPResponse("* 3 EXISTS"); - content[1] = new IMAPResponse("* 0 RECENT"); - content[2] = new IMAPResponse("* OK [UIDVALIDITY 1459808247] UIDs valid"); - content[3] = new IMAPResponse("* OK [UIDNEXT 150400] Predicted next UID"); - content[4] = new IMAPResponse("* FLAGS (\\Answered \\Deleted \\Draft \\Flagged \\Seen $Forwarded $Junk $NotJunk)"); - content[5] = new IMAPResponse("* OK [PERMANENTFLAGS ()] No permanent flags permitted"); - content[6] = new IMAPResponse("* OK [HIGHESTMODSEQ 614]"); - content[7] = new IMAPResponse("002 OK [READ-ONLY] EXAMINE completed; now in selected state"); - final MailboxInfo minfo = mapper.readValue(content, MailboxInfo.class); - - // verify the result - Assert.assertNotNull(minfo, "result mismatched."); - Assert.assertEquals(minfo.mode, Folder.READ_ONLY, "mode mismatched."); - Assert.assertNotNull(minfo.availableFlags, "availableFlags mismatched."); - Assert.assertTrue(minfo.availableFlags.contains(Flag.ANSWERED), "availableFlags mismatched."); - Assert.assertTrue(minfo.availableFlags.contains(Flag.DELETED), "availableFlags mismatched."); - Assert.assertTrue(minfo.availableFlags.contains(Flag.DRAFT), "availableFlags mismatched."); - Assert.assertTrue(minfo.availableFlags.contains(Flag.FLAGGED), "availableFlags mismatched."); - Assert.assertTrue(minfo.availableFlags.contains(Flag.SEEN), "availableFlags mismatched."); - Assert.assertEquals(minfo.highestmodseq, 614, "highestmodseq mismatched."); - Assert.assertEquals(minfo.uidvalidity, 1459808247, "uidvalidity mismatched."); - Assert.assertEquals(minfo.uidnext, 150400, "uidnext mismatched."); - } - - /** - * Tests parseExtensionMailboxInfo method successfully. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseExtensionMailboxInfoReadOnlySuccess() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[9]; - content[0] = new IMAPResponse("* 3 EXISTS"); - content[1] = new IMAPResponse("* 0 RECENT"); - content[2] = new IMAPResponse("* OK [UIDVALIDITY 1459808247] UIDs valid"); - content[3] = new IMAPResponse("* OK [UIDNEXT 150400] Predicted next UID"); - content[4] = new IMAPResponse("* FLAGS (\\Answered \\Deleted \\Draft \\Flagged \\Seen $Forwarded $Junk $NotJunk)"); - content[5] = new IMAPResponse("* OK [PERMANENTFLAGS ()] No permanent flags permitted"); - content[6] = new IMAPResponse("* OK [HIGHESTMODSEQ 614]"); - content[7] = new IMAPResponse("* OK [MAILBOXID (A26)] Ok"); - content[8] = new IMAPResponse("002 OK [READ-ONLY] EXAMINE completed; now in selected state"); - final ExtensionMailboxInfo minfo = mapper.readValue(content, ExtensionMailboxInfo.class); - - // verify the result - Assert.assertNotNull(minfo, "result mismatched."); - Assert.assertEquals(minfo.mode, Folder.READ_ONLY, "mode mismatched."); - Assert.assertNotNull(minfo.availableFlags, "availableFlags mismatched."); - Assert.assertTrue(minfo.availableFlags.contains(Flag.ANSWERED), "availableFlags mismatched."); - Assert.assertTrue(minfo.availableFlags.contains(Flag.DELETED), "availableFlags mismatched."); - Assert.assertTrue(minfo.availableFlags.contains(Flag.DRAFT), "availableFlags mismatched."); - Assert.assertTrue(minfo.availableFlags.contains(Flag.FLAGGED), "availableFlags mismatched."); - Assert.assertTrue(minfo.availableFlags.contains(Flag.SEEN), "availableFlags mismatched."); - Assert.assertEquals(minfo.highestmodseq, 614, "highestmodseq mismatched."); - Assert.assertEquals(minfo.uidvalidity, 1459808247, "uidvalidity mismatched."); - Assert.assertEquals(minfo.uidnext, 150400, "uidnext mismatched."); - Assert.assertEquals(minfo.getMailboxId(), "A26", "MailboxId mismatched."); - } - - /** - * Tests parseMailboxInfo method successfully with READ-WRITE mode. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseMailboxInfoReadWriteSuccess() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[8]; - content[0] = new IMAPResponse("* 3 EXISTS"); - content[1] = new IMAPResponse("* 0 RECENT"); - content[2] = new IMAPResponse("* OK [UIDVALIDITY 1459808247] UIDs valid"); - content[3] = new IMAPResponse("* OK [UIDNEXT 150400] Predicted next UID"); - content[4] = new IMAPResponse("* FLAGS (\\Answered \\Deleted \\Draft \\Flagged \\Seen $Forwarded $Junk $NotJunk)"); - content[5] = new IMAPResponse("* OK [PERMANENTFLAGS ()] No permanent flags permitted"); - content[6] = new IMAPResponse("* OK [HIGHESTMODSEQ 614]"); - content[7] = new IMAPResponse("002 OK [READ-WRITE] EXAMINE completed; now in selected state"); - final MailboxInfo minfo = mapper.readValue(content, MailboxInfo.class); - - // verify the result - Assert.assertNotNull(minfo, "result mismatched."); - Assert.assertEquals(minfo.mode, Folder.READ_WRITE, "mode mismatched."); - Assert.assertNotNull(minfo.availableFlags, "availableFlags mismatched."); - Assert.assertTrue(minfo.availableFlags.contains(Flag.ANSWERED), "availableFlags mismatched."); - Assert.assertTrue(minfo.availableFlags.contains(Flag.DELETED), "availableFlags mismatched."); - Assert.assertTrue(minfo.availableFlags.contains(Flag.DRAFT), "availableFlags mismatched."); - Assert.assertTrue(minfo.availableFlags.contains(Flag.FLAGGED), "availableFlags mismatched."); - Assert.assertTrue(minfo.availableFlags.contains(Flag.SEEN), "availableFlags mismatched."); - Assert.assertEquals(minfo.highestmodseq, 614, "highestmodseq mismatched."); - Assert.assertEquals(minfo.uidvalidity, 1459808247, "uidvalidity mismatched."); - Assert.assertEquals(minfo.uidnext, 150400, "uidnext mismatched."); - } - - /** - * Tests parseMailboxInfo method successfully with READ-WRITE mode. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseMailboxInfoBad() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[8]; - content[0] = new IMAPResponse("* 3 EXISTS"); - content[1] = new IMAPResponse("* 0 RECENT"); - content[2] = new IMAPResponse("* OK [UIDVALIDITY 1459808247] UIDs valid"); - content[3] = new IMAPResponse("* OK [UIDNEXT 150400] Predicted next UID"); - content[4] = new IMAPResponse("* FLAGS (\\Answered \\Deleted \\Draft \\Flagged \\Seen $Forwarded $Junk $NotJunk)"); - content[5] = new IMAPResponse("* OK [PERMANENTFLAGS ()] No permanent flags permitted"); - content[6] = new IMAPResponse("* OK [HIGHESTMODSEQ 614]"); - content[7] = new IMAPResponse("002 BAD"); // make it bad so it does not update mode - final MailboxInfo minfo = mapper.readValue(content, MailboxInfo.class); - - // verify the result - Assert.assertNotNull(minfo, "result mismatched."); - Assert.assertEquals(minfo.mode, 0, "mode mismatched."); - Assert.assertNotNull(minfo.availableFlags, "availableFlags mismatched."); - Assert.assertTrue(minfo.availableFlags.contains(Flag.ANSWERED), "availableFlags mismatched."); - Assert.assertTrue(minfo.availableFlags.contains(Flag.DELETED), "availableFlags mismatched."); - Assert.assertTrue(minfo.availableFlags.contains(Flag.DRAFT), "availableFlags mismatched."); - Assert.assertTrue(minfo.availableFlags.contains(Flag.FLAGGED), "availableFlags mismatched."); - Assert.assertTrue(minfo.availableFlags.contains(Flag.SEEN), "availableFlags mismatched."); - Assert.assertEquals(minfo.highestmodseq, 614, "highestmodseq mismatched."); - Assert.assertEquals(minfo.uidvalidity, 1459808247, "uidvalidity mismatched."); - Assert.assertEquals(minfo.uidnext, 150400, "uidnext mismatched."); - } - - /** - * Tests parseMailboxInfo method successfully with READ-WRITE mode. - * - * @throws ProtocolException will not throw - */ - @Test - public void testParseMailboxInfoResponseArray0() throws ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[0]; - ImapAsyncClientException cause = null; - try { - mapper.readValue(content, MailboxInfo.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - - /** - * Adds the data for test input. - * - * @param rr output parameter, list of IMAPResponse - * @param expectedNames output parameter, list of expected folder names - * @param respStr response string - * @param folder folder name - * @throws IOException will not throw - * @throws ProtocolException will not throw - */ - private void buildListInfoIMAPResponse(final List rr, final List expectedNames, final String respStr, final String folder) - throws IOException, ProtocolException { - expectedNames.add(folder); - rr.add(new IMAPResponse(respStr + " \"" + folder + "\"\r\n")); - } - - /** - * Tests parseListInfos method successfully. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseListInfosSuccess() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final List content = new ArrayList<>(); - final List names = new ArrayList<>(); - // add a non-relevant response - content.add(new IMAPResponse("* 115140 EXPUNGE\r\n")); - buildListInfoIMAPResponse(content, names, "* LIST (\\Archive \\HasNoChildren) \"/\"", "Archive"); - content.add(new IMAPResponse("* some junks\r\n")); - buildListInfoIMAPResponse(content, names, "* LIST (\\Junk \\HasNoChildren) \"/\"", "Bulk Mail"); - buildListInfoIMAPResponse(content, names, "* LIST (\\Drafts \\HasNoChildren) \"/\"", "Draft"); - buildListInfoIMAPResponse(content, names, "* LIST (\\HasNoChildren) \"/\"", "Inbox"); - buildListInfoIMAPResponse(content, names, "* LIST (\\Sent \\HasNoChildren) \"/\"", "Sent"); - buildListInfoIMAPResponse(content, names, "* LIST (\\Trash \\HasNoChildren) \"/\"", "Trash"); - buildListInfoIMAPResponse(content, names, "* LIST (\\HasChildren) \"/\"", "test1"); - content.add(new IMAPResponse("* 115141 EXISTS\r\n")); - buildListInfoIMAPResponse(content, names, "* LIST (\\HasNoChildren) \"/\"", "test1/test1_1"); - content.add(new IMAPResponse("a3 OK LIST completed")); - final ListInfoList ll = mapper.readValue(content.toArray(new IMAPResponse[0]), ListInfoList.class); - final List infos = ll.getListInfo(); - - // verify the result - Assert.assertNotNull(infos, "result mismatched."); - Assert.assertEquals(infos.size(), 8, "ListInfo count mismatched."); - Assert.assertEquals(infos.size(), names.size(), "ListInfo count mismatched."); - for (int i = 0; i < infos.size(); i++) { - final ListInfo info = infos.get(i); - final String expectedFolder = names.get(i); - Assert.assertNotNull(info, "ListInfo should not be null."); - Assert.assertNotNull(expectedFolder, "folder name should not be null."); - Assert.assertTrue(info.hasInferiors, "hasInferiors mismatched."); - Assert.assertNotNull(info.name, "Name mismatched."); - Assert.assertEquals(info.name, expectedFolder, "folder name mismatched."); - } - } - - /** - * Tests parseListInfos method successfully when responses are results of LSUB command. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseListInfosFromLSubSuccess() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final List content = new ArrayList<>(); - final List names = new ArrayList<>(); - buildListInfoIMAPResponse(content, names, "* LSUB (\\Archive \\HasNoChildren) \"/\"", "Archive"); - content.add(new IMAPResponse("* 115140 EXPUNGE\r\n")); - content.add(new IMAPResponse("* MORE JUNKS\r\n")); - buildListInfoIMAPResponse(content, names, "* LSUB (\\Junk \\HasNoChildren) \"/\"", "Bulk Mail"); - buildListInfoIMAPResponse(content, names, "* LSUB (\\Drafts \\HasNoChildren) \"/\"", "Draft"); - buildListInfoIMAPResponse(content, names, "* LSUB (\\HasNoChildren) \"/\"", "Inbox"); - buildListInfoIMAPResponse(content, names, "* LSUB (\\Sent \\HasNoChildren) \"/\"", "Sent"); - buildListInfoIMAPResponse(content, names, "* LSUB (\\Trash \\HasNoChildren) \"/\"", "Trash"); - buildListInfoIMAPResponse(content, names, "* LSUB (\\HasChildren) \"/\"", "test1"); - content.add(new IMAPResponse("* 115141 EXISTS\r\n")); - buildListInfoIMAPResponse(content, names, "* LSUB (\\HasNoChildren) \"/\"", "test1/test1_1"); - content.add(new IMAPResponse("a3 OK LSUB completed")); - final ListInfoList ll = mapper.readValue(content.toArray(new IMAPResponse[0]), ListInfoList.class); - final List infos = ll.getListInfo(); - - // verify the result - Assert.assertNotNull(infos, "result mismatched."); - Assert.assertEquals(infos.size(), 8, "ListInfo count mismatched."); - Assert.assertEquals(infos.size(), names.size(), "ListInfo count mismatched."); - for (int i = 0; i < infos.size(); i++) { - final ListInfo info = infos.get(i); - final String expectedFolder = names.get(i); - Assert.assertNotNull(info, "ListInfo should not be null."); - Assert.assertNotNull(expectedFolder, "folder name should not be null."); - Assert.assertTrue(info.hasInferiors, "hasInferiors mismatched."); - Assert.assertNotNull(info.name, "Name mismatched."); - Assert.assertEquals(info.name, expectedFolder, "folder name mismatched."); - } - } - - /** - * Tests parseListInfos method when final response is not OK. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - */ - @Test - public void testParseListInfosNoOK() throws IOException, ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final List content = new ArrayList<>(); - final List names = new ArrayList<>(); - buildListInfoIMAPResponse(content, names, "* LIST (\\Archive \\HasNoChildren) \"/\"", "\"Archive\""); - buildListInfoIMAPResponse(content, names, "* LIST (\\Junk \\HasNoChildren) \"/\"", "\"Bulk Mail\""); - buildListInfoIMAPResponse(content, names, "* LIST (\\Drafts \\HasNoChildren) \"/\"", "\"Draft\""); - buildListInfoIMAPResponse(content, names, "* LIST (\\HasNoChildren) \"/\"", "\"Inbox\""); - buildListInfoIMAPResponse(content, names, "* LIST (\\Sent \\HasNoChildren) \"/\"", "\"Sent\""); - buildListInfoIMAPResponse(content, names, "* LIST (\\Trash \\HasNoChildren) \"/\"", "\"Trash\""); - buildListInfoIMAPResponse(content, names, "* LIST (\\HasChildren) \"/\"", "\"test1\""); - buildListInfoIMAPResponse(content, names, "* LIST (\\HasNoChildren) \"/\"", "\"test1/test1_1\""); - content.add(new IMAPResponse("a3 BAD LIST completed")); - - ImapAsyncClientException cause = null; - try { - mapper.readValue(content.toArray(new IMAPResponse[0]), ListInfoList.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - - } - - /** - * Tests parseListInfos method when final response is not OK. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseListInfosOnlyOKResponse() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final List rr = new ArrayList<>(); - rr.add(new IMAPResponse("a3 OK LIST completed")); - final ListInfoList infos = mapper.readValue(rr.toArray(new IMAPResponse[0]), ListInfoList.class); - - // verify the result - Assert.assertNotNull(infos, "result mismatched."); - } - - /** - * Tests parseListInfos method when response array length is 0. - * - * @throws ProtocolException will not throw - */ - @Test - public void testParseListInfosEmptyResponses() throws ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final List content = new ArrayList<>(); - ImapAsyncClientException cause = null; - try { - mapper.readValue(content.toArray(new IMAPResponse[0]), ListInfoList.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - - /** - * Tests parseListStatus method successfully. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseListStatusSuccess() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final List content = new ArrayList<>(); - final List expectedLinfo = new ArrayList<>(); - final List expectedStatuses = new ArrayList<>(); - - // 0 - final String lresp0 = "* LIST (\\HasNoChildren) \"/\" \"INBOX\""; - content.add(new IMAPResponse(lresp0)); - // add some non-relevant responses - content.add(new IMAPResponse("* 115140 EXPUNGE\r\n")); - content.add(new IMAPResponse("* 115141 EXPUNGE\r\n")); - final String sresp0 = "* STATUS \"INBOX\" (HIGHESTMODSEQ 82676 MESSAGES 774 UIDNEXT 913 UIDVALIDITY 1 UNSEEN 769)"; - popluateContentAndBuildStatus(lresp0, sresp0, content, expectedLinfo, expectedStatuses); - - // 1 - content.add(new IMAPResponse("* 115142 EXPUNGE\r\n")); - final String lresp1 = "* LIST (\\HasChildren \\NonExistent) \"/\" \"[Zmail]\""; - content.add(new IMAPResponse(lresp1)); - content.add(new IMAPResponse("* 115143 EXPUNGE\r\n")); - popluateContentAndBuildStatus(lresp1, null, content, expectedLinfo, expectedStatuses); // status is null - - // 2 - final String lresp2 = "* LIST (\\HasNoChildren) \"/\" \"[Zmail]/All Mail\""; - content.add(new IMAPResponse(lresp2)); - final String sresp2 = "* STATUS \"[Zmail]/All Mail\" (HIGHESTMODSEQ 82676 MESSAGES 777 UIDNEXT 1109 UIDVALIDITY 12 UNSEEN 770)"; - popluateContentAndBuildStatus(lresp2, sresp2, content, expectedLinfo, expectedStatuses); - - // 3 - final String lresp3 = "* LIST (\\HasNoChildren) \"/\" \"[Zmail]/Drafts\""; - content.add(new IMAPResponse(lresp3)); - final String sresp3 = "* STATUS \"[Zmail]/Drafts\" (HIGHESTMODSEQ 82676 MESSAGES 1 UIDNEXT 98 UIDVALIDITY 6 UNSEEN 0)"; - popluateContentAndBuildStatus(lresp3, sresp3, content, expectedLinfo, expectedStatuses); - - // 4 - final String lresp4 = "* LIST (\\HasNoChildren) \"/\" \"[Zmail]/Important\""; - content.add(new IMAPResponse(lresp4)); - final String sresp4 = "* STATUS \"[Zmail]/Important\" (HIGHESTMODSEQ 82676 MESSAGES 1 UIDNEXT 118 UIDVALIDITY 9 UNSEEN 0)"; - popluateContentAndBuildStatus(lresp4, sresp4, content, expectedLinfo, expectedStatuses); - - // 5 - final String lresp5 = "* LIST (\\HasNoChildren) \"/\" \"[Zmail]/Sent Mail\""; - content.add(new IMAPResponse(lresp5)); - final String sresp5 = "* STATUS \"[Zmail]/Sent Mail\" (HIGHESTMODSEQ 82676 MESSAGES 0 UIDNEXT 88 UIDVALIDITY 5 UNSEEN 0)"; - popluateContentAndBuildStatus(lresp5, sresp5, content, expectedLinfo, expectedStatuses); - - // 6 - final String lresp6 = "* LIST (\\HasNoChildren) \"/\" \"[Zmail]/Spam\""; - content.add(new IMAPResponse(lresp6)); - final String sresp6 = "* STATUS \"[Zmail]/Spam\" (HIGHESTMODSEQ 82676 MESSAGES 1 UIDNEXT 101 UIDVALIDITY 3 UNSEEN 1)"; - popluateContentAndBuildStatus(lresp6, sresp6, content, expectedLinfo, expectedStatuses); - - // 7 - final String lresp7 = "* LIST (\\HasNoChildren) \"/\" \"[Zmail]/Starred\""; - content.add(new IMAPResponse(lresp7)); - final String sresp7 = "* STATUS \"[Zmail]/Starred\" (HIGHESTMODSEQ 82676 MESSAGES 0 UIDNEXT 9 UIDVALIDITY 4 UNSEEN 0)"; - popluateContentAndBuildStatus(lresp7, sresp7, content, expectedLinfo, expectedStatuses); - - // 8 - final String lresp8 = "* LIST (\\HasNoChildren) \"/\" \"[Zmail]/Trash\""; - content.add(new IMAPResponse(lresp8)); - final String sresp8 = "* STATUS \"[Zmail]/Trash\" (HIGHESTMODSEQ 82676 MESSAGES 1 UIDNEXT 137 UIDVALIDITY 2 UNSEEN 0)"; - popluateContentAndBuildStatus(lresp8, sresp8, content, expectedLinfo, expectedStatuses); - - // 9 - final String lresp9 = "* LIST (\\HasChildren) \"/\" \"parent_folder\""; - content.add(new IMAPResponse(lresp9)); - final String sresp9 = "* STATUS \"parent_folder\" (HIGHESTMODSEQ 82676 MESSAGES 0 UIDNEXT 1 UIDVALIDITY 15 UNSEEN 0)"; - popluateContentAndBuildStatus(lresp9, sresp9, content, expectedLinfo, expectedStatuses); - - // 10 - final String lresp10 = "* LIST (\\HasNoChildren) \"/\" \"parent_folder/child_folder\""; - content.add(new IMAPResponse(lresp10)); - final String sresp10 = "* STATUS \"parent_folder/child_folder\" (HIGHESTMODSEQ 82676 MESSAGES 1 UIDNEXT 2 UIDVALIDITY 16 UNSEEN 0)"; - popluateContentAndBuildStatus(lresp10, sresp10, content, expectedLinfo, expectedStatuses); - - // 11 - final String lresp11 = "* LIST (\\HasChildren \\NonExistent) \"/\" \"abc_folder\""; - content.add(new IMAPResponse(lresp11)); - content.add(new IMAPResponse("* 115143 EXPUNGE\r\n")); - popluateContentAndBuildStatus(lresp11, null, content, expectedLinfo, expectedStatuses); // status is null - - content.add(new IMAPResponse("a3 OK Success")); - - final ListStatusResult ll = mapper.readValue(content.toArray(new IMAPResponse[0]), ListStatusResult.class); - - // verify the result - final List infos = ll.getListInfos(); - Assert.assertNotNull(infos, "result mismatched."); - Assert.assertEquals(infos.size(), 12, "ListInfo count mismatched."); - final Map statuses = ll.getStatuses(); - - for (int i = 0; i < infos.size(); i++) { - final ExtensionListInfo info = infos.get(i); - final ExtensionListInfo expectedInfo = expectedLinfo.get(i); - final Status expectedStatus = expectedStatuses.get(i); - - Assert.assertNotNull(info, "ListStatus should not be null."); - - // verify ListInfo - Assert.assertNotNull(info, "ListInfo should not be null."); - Assert.assertEquals(info.getAvailableExtendedAttributes(), expectedInfo.getAvailableExtendedAttributes(), "Data mismatched."); - - Assert.assertEquals(info.hasInferiors, expectedInfo.hasInferiors, "hasInferiors mismatched."); - Assert.assertEquals(info.name, expectedInfo.name, "ListInfo name mismatched."); - Assert.assertEquals(info.attrs.length, expectedInfo.attrs.length, "ListInfo attrs size mismatched."); - for (int j = 0; j < expectedInfo.attrs.length; j++) { - Assert.assertEquals(info.attrs[j], expectedInfo.attrs[j], "info.attrs[j] mismatched."); - } - final Status st = statuses.get(info.name); - - // Verify Status - if (expectedStatus == null) { // if expecting no status for this folder - Assert.assertNull(st, "Expected Status mismatched."); - continue; - } - Assert.assertEquals(st.mbox, expectedStatus.mbox, "Status.mbox mismatched."); - Assert.assertEquals(info.name, st.mbox, "ListInfo and Status mismatched. "); - Assert.assertEquals(st.highestmodseq, expectedStatus.highestmodseq, "highestmodseq mismatched."); - Assert.assertEquals(st.total, expectedStatus.total, "total mismatched."); - Assert.assertEquals(st.recent, expectedStatus.recent, "recent mismatched."); - Assert.assertEquals(st.uidnext, expectedStatus.uidnext, "highestmodseq mismatched."); - Assert.assertEquals(st.uidvalidity, expectedStatus.uidvalidity, "highestmodseq mismatched."); - Assert.assertEquals(st.unseen, expectedStatus.unseen, "highestmodseq mismatched."); - Assert.assertEquals(st.items, expectedStatus.items, "items mismatched."); - if (expectedStatus.items != null) { - Assert.assertEquals(st.items.size(), expectedStatus.items.size(), "items mismatched."); - for (final String itemName : expectedStatus.items.keySet()) { - Assert.assertEquals(st.items.get(itemName), expectedStatus.items.get(itemName), "Item value mismatched."); - } - } - } - } - - /** - * Builds the Status for expected Status and populate given Content for input. - * - * @param listResp List response in string format - * @param statusResp Status response in string format - * @param content IMAPResponse list - * @param expectedInfo expected ListInfo list - * @param expectedSt expected Status list - * @return - * @throws ProtocolException will not throw - * @throws IOException will not throw - */ - private void popluateContentAndBuildStatus(final String listResp, final String statusResp, final List content, - final List expectedInfo, final List expectedSt) throws IOException, ProtocolException { - - expectedInfo.add(new ExtensionListInfo(new IMAPResponse(listResp))); - if (statusResp != null) { - content.add(new IMAPResponse(statusResp)); - expectedSt.add(new Status(new IMAPResponse(statusResp))); - } else { - expectedSt.add(null); - } - } - - /** - * Tests parseListInfos method when response array length is 0. - * - * @throws ProtocolException will not throw - */ - @Test - public void testParseListStausEmptyResponses() throws ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final List content = new ArrayList<>(); - ImapAsyncClientException cause = null; - try { - mapper.readValue(content.toArray(new IMAPResponse[0]), ListStatusResult.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - - /** - * Tests parseListInfos method when final response is not OK. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - */ - @Test - public void testParseListStatusNoOK() throws IOException, ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final List content = new ArrayList<>(); - final List names = new ArrayList<>(); - buildListInfoIMAPResponse(content, names, "* LIST (\\Archive \\HasNoChildren) \"/\"", "\"Archive\""); - buildListInfoIMAPResponse(content, names, "* LIST (\\Junk \\HasNoChildren) \"/\"", "\"Bulk Mail\""); - buildListInfoIMAPResponse(content, names, "* LIST (\\Drafts \\HasNoChildren) \"/\"", "\"Draft\""); - buildListInfoIMAPResponse(content, names, "* LIST (\\HasNoChildren) \"/\"", "\"Inbox\""); - buildListInfoIMAPResponse(content, names, "* LIST (\\Sent \\HasNoChildren) \"/\"", "\"Sent\""); - buildListInfoIMAPResponse(content, names, "* LIST (\\Trash \\HasNoChildren) \"/\"", "\"Trash\""); - buildListInfoIMAPResponse(content, names, "* LIST (\\HasChildren) \"/\"", "\"test1\""); - buildListInfoIMAPResponse(content, names, "* LIST (\\HasNoChildren) \"/\"", "\"test1/test1_1\""); - content.add(new IMAPResponse("a3 BAD LIST completed")); - - ImapAsyncClientException cause = null; - try { - mapper.readValue(content.toArray(new IMAPResponse[0]), ListStatusResult.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - - } - - /** - * Tests parseListStatus method when final response is not OK. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseListStatusOnlyOKResponse() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final List rr = new ArrayList<>(); - rr.add(new IMAPResponse("a3 OK LIST completed")); - final ListStatusResult infos = mapper.readValue(rr.toArray(new IMAPResponse[0]), ListStatusResult.class); - - // verify the result - Assert.assertNotNull(infos, "result mismatched."); - } - - /** - * Tests ExtensionMailboxInfo method when response array length is 0. - * - * @throws ProtocolException will not throw - */ - @Test - public void testParseMailboxExtensionInfosEmptyResponses() throws ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final List content = new ArrayList<>(); - ImapAsyncClientException cause = null; - try { - mapper.readValue(content.toArray(new IMAPResponse[0]), ExtensionMailboxInfo.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - - /** - * Tests parseStatus method with response array 0. - * - * @throws ProtocolException will not throw - */ - @Test - public void testParseStatusArray0() throws ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[0]; - ImapAsyncClientException cause = null; - try { - mapper.readValue(content, Status.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - - /** - * Tests parse a class that mapper does not support. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - */ - @Test - public void testParseClassUnknown() throws IOException, ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[1]; - content[0] = new IMAPResponse("002 OK"); // make it bad so it does not update mode - - ImapAsyncClientException cause = null; - try { - mapper.readValue(content, ID.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.UNKNOWN_PARSE_RESULT_TYPE, "Failure type mismatched."); - } - - /** - * Tests parseStatus method with not OK response. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - */ - @Test - public void testParseStatusNotOK() throws IOException, ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[1]; - content[0] = new IMAPResponse("002 BAD"); // make it bad so it does not update mode - - // verify the result - ImapAsyncClientException cause = null; - try { - mapper.readValue(content, Status.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - - /** - * Tests parseStatus method successfully. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - */ - @Test - public void testParseStatusNoStatusResponse() throws IOException, ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[2]; - content[0] = new IMAPResponse("* NOSTATUS blurdybloop (MESSAGES 231 UIDNEXT 44292)"); - content[1] = new IMAPResponse("A042 OK STATUS completed"); - - // verify the result - ImapAsyncClientException cause = null; - try { - mapper.readValue(content, Status.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - - /** - * Tests parseStatus method successfully. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseStatusOK() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final List content = new ArrayList<>(); - content.add(new IMAPResponse("* STATUS blurdybloop (MESSAGES 231 UIDNEXT 44292 UNSEEN 3)")); - content.add(new IMAPResponse("* S2TATUS blurdybloop (MESSAGES 232 UIDNEXT 44293)")); - content.add(new IMAPResponse("* STATUS blurdybloop (UNSEEN 4)")); - content.add(new IMAPResponse("* STATUS blurdybloop (UIDVALIDITY 999333)")); - content.add(new IMAPResponse("A042 OK STATUS completed")); - - final Status status = mapper.readValue(content.toArray(new IMAPResponse[0]), Status.class); - - // verify the result - Assert.assertNotNull(status, "status mismatched."); - Assert.assertEquals(status.uidnext, 44292, "uidnext mismatched."); - Assert.assertEquals(status.total, 231, "total mismatched."); - Assert.assertEquals(status.unseen, 4, "unseen mismatched, should take the latter one."); - Assert.assertEquals(status.uidvalidity, 999333, "uidvalidity mismatched."); - } - - /** - * Tests parseToID method with response array 0. - * - * @throws ProtocolException will not throw - */ - @Test - public void testParseToIdResultArray0() throws ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[0]; - ImapAsyncClientException cause = null; - try { - mapper.readValue(content, IdResult.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - - /** - * Tests parseToID method with not OK response. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - */ - @Test - public void testParseToIdResultNotOK() throws IOException, ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[1]; - content[0] = new IMAPResponse("002 BAD"); // make it bad so it does not update mode - - // verify the result - ImapAsyncClientException cause = null; - try { - mapper.readValue(content, IdResult.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - - /** - * Tests parseToIdResult with first byte is N. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseToIdResultFirstByteIsN() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[2]; - content[0] = new IMAPResponse("* ID NIL \n"); - content[1] = new IMAPResponse("a042 OK ID command completed"); - - final IdResult id = mapper.readValue(content, IdResult.class); - - // verify the result - Assert.assertNotNull(id, "id mismatched."); - Assert.assertFalse(id.hasKey("name"), "name key mismatched."); - Assert.assertNull(id.getValue("name"), "name value mismatched."); - } - - /** - * Tests parseToIdResult with first byte is n. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseToIdResultFirstByteIsLowercaseN() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[2]; - content[0] = new IMAPResponse("* ID nIL \n"); - content[1] = new IMAPResponse("a042 OK ID command completed"); - - final IdResult id = mapper.readValue(content, IdResult.class); - - // verify the result - Assert.assertNotNull(id, "id mismatched."); - Assert.assertFalse(id.hasKey("name"), "name key mismatched."); - Assert.assertNull(id.getValue("name"), "name value mismatched."); - } - - /** - * Tests parseToIdResult with first byte not left parenthesis. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - */ - @Test - public void testParseToIdResultNotStartWithLeftParenthesis() throws IOException, ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[2]; - content[0] = new IMAPResponse("* ID X \n"); - content[1] = new IMAPResponse("a042 OK ID command completed"); - - // verify the result - ImapAsyncClientException cause = null; - try { - mapper.readValue(content, IdResult.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - - /** - * Tests parseToIdResult with first byte not left parenthesis. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseToIdResultNameAbsent() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[2]; - // sun java mail library 1.5.5 has bug fix in readStringList() to return 0 length array instead of 1 length with a null element - - content[0] = new IMAPResponse("* ID () \n"); - content[1] = new IMAPResponse("a042 OK ID command completed"); - - // verify the result - final IdResult id = mapper.readValue(content, IdResult.class); - // verify the result - Assert.assertNotNull(id, "result mismatched."); - } - - /** - * Tests parseToIdResult with first byte not left parenthesis. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - */ - @Test - public void testParseToIdResultValueAbsent() throws IOException, ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[2]; - content[0] = new IMAPResponse("* ID (') \n"); - content[1] = new IMAPResponse("a042 OK ID command completed"); - - // verify the result - ImapAsyncClientException cause = null; - try { - mapper.readValue(content, IdResult.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - - /** - * Tests parseToID method successfully. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseToIdResultOK() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[3]; - content[0] = new IMAPResponse("* ID (\"name\" \"Cyrus\" \"version\" \"1.5\" \"os\" \"sunos\"\n\"os-version\" \"5.5\" \"support-url\"\n" - + "\"mailto:cyrus-bugs+@andrew.cmu.edu\")\n"); - content[1] = new IMAPResponse("junk"); - content[2] = new IMAPResponse("a042 OK ID command completed"); - - final IdResult id = mapper.readValue(content, IdResult.class); - - // verify the result - Assert.assertNotNull(id, "id mismatched."); - Assert.assertTrue(id.hasKey("name"), "name should be present."); - Assert.assertEquals(id.getValue("name"), "Cyrus", "name value mismatched."); - } - - /** - * Tests parseSearchResult method successfully. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseToSearchResultOK() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[2]; - content[0] = new IMAPResponse("* SEARCH 150404 150406 150407\r\n"); - content[1] = new IMAPResponse("a3 OK UID SEARCH completed\r\n"); - - final SearchResult result = mapper.readValue(content, SearchResult.class); - - // verify the result - Assert.assertNotNull(result, "result mismatched."); - final List list = result.getMessageNumbers(); - Assert.assertNotNull(list, "getMessageSequence() mismatched."); - Assert.assertEquals(list.size(), 3, "getMessageSequence() mismatched."); - Assert.assertEquals(list.get(0), Long.valueOf(150404), "getMessageSequence() mismatched."); - Assert.assertEquals(list.get(1), Long.valueOf(150406), "getMessageSequence() mismatched."); - Assert.assertEquals(list.get(2), Long.valueOf(150407), "getMessageSequence() mismatched."); - } - - /** - * Tests parseSearchResult method successfully. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseToSearchResultOKNoSearchResult() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[2]; - content[0] = new IMAPResponse("* SEARCH\r\n"); - content[1] = new IMAPResponse("a3 OK UID SEARCH completed\r\n"); - - final SearchResult result = mapper.readValue(content, SearchResult.class); - - // verify the result - Assert.assertNotNull(result, "result mismatched."); - final List list = result.getMessageNumbers(); - Assert.assertNotNull(list, "getMessageSequence() mismatched."); - Assert.assertEquals(list.size(), 0, "getMessageSequence() mismatched."); - } - - /** - * Tests parseToSearchResult method when tagged response is not OK. - * - * @throws ProtocolException will not throw - */ - @Test - public void testParseToSearchResultZeroLengthResponse() throws ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[0]; - - ImapAsyncClientException actual = null; - try { - final SearchResult result = mapper.readValue(content, SearchResult.class); - } catch (final ImapAsyncClientException e) { - actual = e; - } - // verify the result - Assert.assertNotNull(actual, "ImapAsyncClientException should occur."); - Assert.assertEquals(actual.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - - /** - * Tests parseToSearchResult method when tagged response is not OK. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - */ - @Test - public void testParseToSearchResultNotOK() throws IOException, ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[1]; - content[0] = new IMAPResponse("a3 BAD SEARCH completed (Failure)\r\n"); - - ImapAsyncClientException actual = null; - try { - final SearchResult result = mapper.readValue(content, SearchResult.class); - } catch (final ImapAsyncClientException e) { - actual = e; - } - // verify the result - Assert.assertNotNull(actual, "ImapAsyncClientException should occur."); - Assert.assertEquals(actual.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - - /** - * Tests parseToEnableResult method successfully. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseToEnableResult() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final List content = new ArrayList<>(); - content.add(new IMAPResponse("* some junks\r\n")); - content.add(new IMAPResponse("* ENABLED CONDSTORE QRSYNC\r\n")); - content.add(new IMAPResponse("a3 OK ENABLE completed\r\n")); - final EnableResult enableResult = mapper.readValue(content.toArray(new IMAPResponse[0]), EnableResult.class); - - // verify the result - Assert.assertNotNull(enableResult, "result should never return null."); - final Set capas = enableResult.getEnabledCapabilities(); - Assert.assertEquals(capas.size(), 2, "capability missed."); - Assert.assertTrue(capas.contains("CONDSTORE"), "One capability missed."); - Assert.assertTrue(capas.contains("QRSYNC"), "One capability missed."); - } - - /** - * Tests parseToEnableResult method with response contains length zero line. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseToEnableResultLengthZero() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final List content = new ArrayList<>(); - content.add(new IMAPResponse("* some junks\r\n")); - content.add(new IMAPResponse("* ENABLED CONDSTORE QRSYNC\r\n")); - content.add(new IMAPResponse("")); - content.add(new IMAPResponse("a3 OK ENABLE completed\r\n")); - final EnableResult enableResult = mapper.readValue(content.toArray(new IMAPResponse[0]), EnableResult.class); - - // verify the result - Assert.assertNotNull(enableResult, "result should never return null."); - final Set capas = enableResult.getEnabledCapabilities(); - Assert.assertEquals(capas.size(), 2, "capability missed."); - Assert.assertTrue(capas.contains("CONDSTORE"), "One capability missed."); - Assert.assertTrue(capas.contains("QRSYNC"), "One capability missed."); - } - - /** - * Tests parseToEnableResult method with response contains only * ENABLED but no capability. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseToEnableResultNoCapa() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final List content = new ArrayList<>(); - content.add(new IMAPResponse("* ENABLED\r\n")); - content.add(new IMAPResponse("a3 OK ENABLE completed\r\n")); - final EnableResult enableResult = mapper.readValue(content.toArray(new IMAPResponse[0]), EnableResult.class); - - // verify the result - Assert.assertNotNull(enableResult, "result should never return null."); - final Set capas = enableResult.getEnabledCapabilities(); - Assert.assertEquals(capas.size(), 0, "capability missed."); - } - - /** - * Tests parseToEnableResult method when server does not enable all the capability. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseToEnableResultNotEnabled() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final List content = new ArrayList<>(); - content.add(new IMAPResponse("a3 OK ENABLE completed\r\n")); - final EnableResult enableResult = mapper.readValue(content.toArray(new IMAPResponse[0]), EnableResult.class); - - // verify the result - Assert.assertNotNull(enableResult, "result should never return null."); - final Set capas = enableResult.getEnabledCapabilities(); - Assert.assertEquals(capas.size(), 0, "capability missed."); - } - - /** - * Tests parseToEnableResult method when server returns with no response contents. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseToEnableResultNoContent() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final List content = new ArrayList<>(); - ImapAsyncClientException cause = null; - try { - mapper.readValue(content.toArray(new IMAPResponse[0]), EnableResult.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - - /** - * Tests parseToEnableResult method when server returns with not OK response. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseToEnableResultNotOK() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final List content = new ArrayList<>(); - content.add(new IMAPResponse("* some junks\r\n")); - content.add(new IMAPResponse("* ENABLED CONDSTORE QRSYNC\r\n")); - content.add(new IMAPResponse("a3 BAD ENABLE fail\r\n")); - ImapAsyncClientException cause = null; - try { - mapper.readValue(content.toArray(new IMAPResponse[0]), EnableResult.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } -} +package com.yahoo.imapnio.async.response; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.mail.Flags.Flag; +import javax.mail.Folder; + +import org.testng.Assert; +import org.testng.annotations.Test; + +import com.sun.mail.iap.ProtocolException; +import com.sun.mail.imap.AppendUID; +import com.sun.mail.imap.CopyUID; +import com.sun.mail.imap.protocol.FetchItem; +import com.sun.mail.imap.protocol.FetchResponse; +import com.sun.mail.imap.protocol.ID; +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.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.FetchResult; +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.MessageNumberSet; +import com.yahoo.imapnio.async.data.SearchResult; +import com.yahoo.imapnio.async.data.StoreResult; +import com.yahoo.imapnio.async.exception.ImapAsyncClientException; +import com.yahoo.imapnio.async.exception.ImapAsyncClientException.FailureType; + +/** + * Unit test for {@link ImapResponseMapper}. + */ +public class ImapResponseMapperTest { + /** Imap server greeting. */ + private static final String GREETING = "* OK [CAPABILITY IMAP4rev1 SASL-IR AUTH=PLAIN AUTH=XOAUTH2 AUTH=OAUTHBEARER ID MOVE NAMESPACE " + + "XYMHIGHESTMODSEQ UIDPLUS LITERAL+ CHILDREN X-MSG-EXT] IMAP4rev1 Hello"; + + /** + * Tests parseToCapabilities method successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseToCapabilitiesFromCapaCommand() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final List content = new ArrayList<>(); + content.add(new IMAPResponse("* some junks\r\n")); + content.add(new IMAPResponse("* CAPABILITY IMAP4rev1 SASL-IR AUTH=PLAIN AUTH=XOAUTH2 AUTH=OAUTHBEARER ID MOVE NAMESPACE\r\n")); + content.add(new IMAPResponse("* more junks\r\n")); + content.add(new IMAPResponse("a1 OK CAPABILITY completed\r\n")); + final Capability capa = mapper.readValue(content.toArray(new IMAPResponse[0]), Capability.class); + + // verify the result + Assert.assertNotNull(capa, "result should never return null."); + Assert.assertTrue(capa.hasCapability("IMAP4rev1".toUpperCase()), "One capability missed."); + Assert.assertTrue(capa.hasCapability("SASL-IR"), "One capability missed."); + Assert.assertTrue(capa.hasCapability("ID"), "One capability missed."); + Assert.assertTrue(capa.hasCapability("MOVE"), "One capability missed."); + Assert.assertTrue(capa.hasCapability("NAMESPACE"), "One capability missed."); + final List authValues = capa.getCapability("AUTH"); + Assert.assertNotNull(authValues, "AUTH values missed."); + Assert.assertEquals(authValues.size(), 3, "One Auth value missed"); + Assert.assertEquals(authValues.get(0), "PLAIN", "One Auth value missed"); + Assert.assertEquals(authValues.get(1), "XOAUTH2", "One Auth value missed"); + Assert.assertEquals(authValues.get(2), "OAUTHBEARER", "One Auth value missed"); + } + + /** + * Tests parseToCapabilities method when ImapResponse array has zero length. + * + * @throws ProtocolException will not throw + */ + @Test + public void testParseToCapabilitiesArrayLengthZero() throws ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = {}; + + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, Capability.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseToCapabilities method successfully from an OK response that has Capability response attached to. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseToCapabilitiesFromGreeting() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = { new IMAPResponse(GREETING) }; + final Capability capa = mapper.readValue(content, Capability.class); + + // verify the result + Assert.assertNotNull(capa, "result should never return null."); + Assert.assertTrue(capa.hasCapability("IMAP4rev1".toUpperCase()), "One capability missed."); + Assert.assertTrue(capa.hasCapability("SASL-IR"), "One capability missed."); + Assert.assertTrue(capa.hasCapability("ID"), "One capability missed."); + Assert.assertTrue(capa.hasCapability("MOVE"), "One capability missed."); + Assert.assertTrue(capa.hasCapability("NAMESPACE"), "One capability missed."); + Assert.assertTrue(capa.hasCapability("X-MSG-EXT"), "One capability missed."); + Assert.assertTrue(capa.hasCapability("LITERAL+"), "One capability missed."); + final List authValues = capa.getCapability("AUTH"); + Assert.assertNotNull(authValues, "AUTH values missed."); + Assert.assertEquals(authValues.size(), 3, "One Auth value missed"); + Assert.assertEquals(authValues.get(0), "PLAIN", "One Auth value missed"); + Assert.assertEquals(authValues.get(1), "XOAUTH2", "One Auth value missed"); + Assert.assertEquals(authValues.get(2), "OAUTHBEARER", "One Auth value missed"); + } + + /** + * Tests parseToCapabilities method successfully when it has Netscape Messaging Server response. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseToCapabilitiesSkipStar() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = { + new IMAPResponse("* CAPABILITY * IMAP4rev1 SASL-IR AUTH=PLAIN AUTH=XOAUTH2 AUTH=OAUTHBEARER ID MOVE NAMESPACE") }; + final Capability capa = mapper.readValue(content, Capability.class); + + // verify the result + Assert.assertTrue(capa.hasCapability("IMAP4rev1"), "One capability missed."); + Assert.assertTrue(capa.hasCapability("SASL-IR"), "One capability missed."); + Assert.assertTrue(capa.hasCapability("ID"), "One capability missed."); + Assert.assertTrue(capa.hasCapability("MOVE"), "One capability missed."); + Assert.assertTrue(capa.hasCapability("NAMESPACE"), "One capability missed."); + final List authValues = capa.getCapability("AUTH"); + Assert.assertNotNull(authValues, "AUTH values missed."); + Assert.assertEquals(authValues.size(), 3, "One Auth value missed"); + Assert.assertEquals(authValues.get(0), "PLAIN", "One Auth value missed"); + Assert.assertEquals(authValues.get(1), "XOAUTH2", "One Auth value missed"); + Assert.assertEquals(authValues.get(2), "OAUTHBEARER", "One Auth value missed"); + } + + /** + * Tests parseCopyUid successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseCopyUidSuccess() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[5]; + content[0] = new IMAPResponse("* OK [COPYUID 1549405125 150395 3]"); // a good response + content[1] = new IMAPResponse("* BAD Some junks"); // test if it skips the bad response + content[2] = null; // test if it skips null + content[3] = new IMAPResponse("* OK [SOMETHING 111 222 3]"); // test if it detects it is not COPYUID keyword + content[4] = new IMAPResponse("* OK"); // test when b is 0 + + final CopyUID copyUid = mapper.readValue(content, CopyUID.class); + + // verify the result + Assert.assertNotNull(copyUid, "result mismatched."); + Assert.assertEquals(copyUid.uidvalidity, 1549405125, "result mismatched."); + Assert.assertNotNull(copyUid.src, "result mismatched."); + Assert.assertNotNull(copyUid.dst, "result mismatched."); + Assert.assertEquals(copyUid.src.length, 1, "result mismatched."); + Assert.assertEquals(copyUid.src[0].start, 150395, "result mismatched."); + Assert.assertEquals(copyUid.src[0].end, 150395, "result mismatched."); + Assert.assertEquals(copyUid.dst.length, 1, "result mismatched."); + Assert.assertEquals(copyUid.dst[0].start, 3, "result mismatched."); + Assert.assertEquals(copyUid.dst[0].end, 3, "result mismatched."); + } + + /** + * Tests ImapResponseParse parseCopyUid when Responses array is empty. + * + * @throws ProtocolException will not throw + */ + @Test + public void testParseCopyUidResponseArrayZero() throws ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[0]; + + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, CopyUID.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + + } + + /** + * Tests parseAppendUid successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseAppendUidSuccess() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[3]; + content[0] = new IMAPResponse("+ Ready for literal data"); + content[1] = new IMAPResponse("* 3 EXISTS"); + content[2] = new IMAPResponse("a5 OK [APPENDUID 1459808247 150399] APPEND completed"); + final AppendUID appendUid = mapper.readValue(content, AppendUID.class); + + // verify the result + Assert.assertNotNull(appendUid, "result mismatched."); + Assert.assertEquals(appendUid.uidvalidity, 1459808247, "result mismatched."); + Assert.assertEquals(appendUid.uid, 150399, "result mismatched."); + } + + /** + * Tests parseToCapabilities method when ImapResponse array has zero length. + * + * @throws ProtocolException will not throw + */ + @Test + public void testParseToAppendUidsArrayLengthZero() throws ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = {}; + + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, AppendUID.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseAppendUid with a BAD response. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseAppendUidNotOK() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = { new IMAPResponse("* BAD Some junks") }; + + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, AppendUID.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + + } + + /** + * Tests parseAppendUid when b is 0, or when left bracket is not found. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseAppendUidByteReadExhausted() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = { new IMAPResponse("* OK") }; // test when b is 0 + + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, AppendUID.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseAppendUid when b is 0. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseAppendUidNoAppendUidKeyword() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = { new IMAPResponse("* OK [appendButNotUid 111 222 3]") }; // test when b is 0 + + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, AppendUID.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + + } + + /** + * Tests parseMailboxInfo method successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseMailboxInfoReadOnlySuccess() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[8]; + content[0] = new IMAPResponse("* 3 EXISTS"); + content[1] = new IMAPResponse("* 0 RECENT"); + content[2] = new IMAPResponse("* OK [UIDVALIDITY 1459808247] UIDs valid"); + content[3] = new IMAPResponse("* OK [UIDNEXT 150400] Predicted next UID"); + content[4] = new IMAPResponse("* FLAGS (\\Answered \\Deleted \\Draft \\Flagged \\Seen $Forwarded $Junk $NotJunk)"); + content[5] = new IMAPResponse("* OK [PERMANENTFLAGS ()] No permanent flags permitted"); + content[6] = new IMAPResponse("* OK [HIGHESTMODSEQ 614]"); + content[7] = new IMAPResponse("002 OK [READ-ONLY] EXAMINE completed; now in selected state"); + final MailboxInfo minfo = mapper.readValue(content, MailboxInfo.class); + + // verify the result + Assert.assertNotNull(minfo, "result mismatched."); + Assert.assertEquals(minfo.mode, Folder.READ_ONLY, "mode mismatched."); + Assert.assertNotNull(minfo.availableFlags, "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.ANSWERED), "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.DELETED), "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.DRAFT), "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.FLAGGED), "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.SEEN), "availableFlags mismatched."); + Assert.assertEquals(minfo.highestmodseq, 614, "highestmodseq mismatched."); + Assert.assertEquals(minfo.uidvalidity, 1459808247, "uidvalidity mismatched."); + Assert.assertEquals(minfo.uidnext, 150400, "uidnext mismatched."); + } + + /** + * Tests parseExtensionMailboxInfo method successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseExtensionMailboxInfoReadOnlySuccess() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[9]; + content[0] = new IMAPResponse("* 3 EXISTS"); + content[1] = new IMAPResponse("* 0 RECENT"); + content[2] = new IMAPResponse("* OK [UIDVALIDITY 1459808247] UIDs valid"); + content[3] = new IMAPResponse("* OK [UIDNEXT 150400] Predicted next UID"); + content[4] = new IMAPResponse("* FLAGS (\\Answered \\Deleted \\Draft \\Flagged \\Seen $Forwarded $Junk $NotJunk)"); + content[5] = new IMAPResponse("* OK [PERMANENTFLAGS ()] No permanent flags permitted"); + content[6] = new IMAPResponse("* OK [HIGHESTMODSEQ 614]"); + content[7] = new IMAPResponse("* OK [MAILBOXID (A26)] Ok"); + content[8] = new IMAPResponse("002 OK [READ-ONLY] EXAMINE completed; now in selected state"); + final ExtensionMailboxInfo minfo = mapper.readValue(content, ExtensionMailboxInfo.class); + + // verify the result + Assert.assertNotNull(minfo, "result mismatched."); + Assert.assertEquals(minfo.mode, Folder.READ_ONLY, "mode mismatched."); + Assert.assertNotNull(minfo.availableFlags, "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.ANSWERED), "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.DELETED), "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.DRAFT), "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.FLAGGED), "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.SEEN), "availableFlags mismatched."); + Assert.assertEquals(minfo.highestmodseq, 614, "highestmodseq mismatched."); + Assert.assertEquals(minfo.uidvalidity, 1459808247, "uidvalidity mismatched."); + Assert.assertEquals(minfo.uidnext, 150400, "uidnext mismatched."); + Assert.assertEquals(minfo.getMailboxId(), "A26", "MailboxId mismatched."); + } + + /** + * Tests parseMailboxInfo method successfully with READ-WRITE mode. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseMailboxInfoReadWriteSuccess() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[8]; + content[0] = new IMAPResponse("* 3 EXISTS"); + content[1] = new IMAPResponse("* 0 RECENT"); + content[2] = new IMAPResponse("* OK [UIDVALIDITY 1459808247] UIDs valid"); + content[3] = new IMAPResponse("* OK [UIDNEXT 150400] Predicted next UID"); + content[4] = new IMAPResponse("* FLAGS (\\Answered \\Deleted \\Draft \\Flagged \\Seen $Forwarded $Junk $NotJunk)"); + content[5] = new IMAPResponse("* OK [PERMANENTFLAGS ()] No permanent flags permitted"); + content[6] = new IMAPResponse("* OK [HIGHESTMODSEQ 614]"); + content[7] = new IMAPResponse("002 OK [READ-WRITE] EXAMINE completed; now in selected state"); + final MailboxInfo minfo = mapper.readValue(content, MailboxInfo.class); + + // verify the result + Assert.assertNotNull(minfo, "result mismatched."); + Assert.assertEquals(minfo.mode, Folder.READ_WRITE, "mode mismatched."); + Assert.assertNotNull(minfo.availableFlags, "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.ANSWERED), "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.DELETED), "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.DRAFT), "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.FLAGGED), "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.SEEN), "availableFlags mismatched."); + Assert.assertEquals(minfo.highestmodseq, 614, "highestmodseq mismatched."); + Assert.assertEquals(minfo.uidvalidity, 1459808247, "uidvalidity mismatched."); + Assert.assertEquals(minfo.uidnext, 150400, "uidnext mismatched."); + } + + /** + * Tests parseMailboxInfo method successfully with READ-WRITE mode. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseMailboxInfoBad() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[8]; + content[0] = new IMAPResponse("* 3 EXISTS"); + content[1] = new IMAPResponse("* 0 RECENT"); + content[2] = new IMAPResponse("* OK [UIDVALIDITY 1459808247] UIDs valid"); + content[3] = new IMAPResponse("* OK [UIDNEXT 150400] Predicted next UID"); + content[4] = new IMAPResponse("* FLAGS (\\Answered \\Deleted \\Draft \\Flagged \\Seen $Forwarded $Junk $NotJunk)"); + content[5] = new IMAPResponse("* OK [PERMANENTFLAGS ()] No permanent flags permitted"); + content[6] = new IMAPResponse("* OK [HIGHESTMODSEQ 614]"); + content[7] = new IMAPResponse("002 BAD"); // make it bad so it does not update mode + final MailboxInfo minfo = mapper.readValue(content, MailboxInfo.class); + + // verify the result + Assert.assertNotNull(minfo, "result mismatched."); + Assert.assertEquals(minfo.mode, 0, "mode mismatched."); + Assert.assertNotNull(minfo.availableFlags, "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.ANSWERED), "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.DELETED), "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.DRAFT), "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.FLAGGED), "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.SEEN), "availableFlags mismatched."); + Assert.assertEquals(minfo.highestmodseq, 614, "highestmodseq mismatched."); + Assert.assertEquals(minfo.uidvalidity, 1459808247, "uidvalidity mismatched."); + Assert.assertEquals(minfo.uidnext, 150400, "uidnext mismatched."); + } + + /** + * Tests parseMailboxInfo method successfully with READ-WRITE mode. + * + * @throws ProtocolException will not throw + */ + @Test + public void testParseMailboxInfoResponseArray0() throws ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[0]; + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, MailboxInfo.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Adds the data for test input. + * + * @param rr output parameter, list of IMAPResponse + * @param expectedNames output parameter, list of expected folder names + * @param respStr response string + * @param folder folder name + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + private void buildListInfoIMAPResponse(final List rr, final List expectedNames, final String respStr, final String folder) + throws IOException, ProtocolException { + expectedNames.add(folder); + rr.add(new IMAPResponse(respStr + " \"" + folder + "\"\r\n")); + } + + /** + * Tests parseListInfos method successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseListInfosSuccess() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final List content = new ArrayList<>(); + final List names = new ArrayList<>(); + // add a non-relevant response + content.add(new IMAPResponse("* 115140 EXPUNGE\r\n")); + buildListInfoIMAPResponse(content, names, "* LIST (\\Archive \\HasNoChildren) \"/\"", "Archive"); + content.add(new IMAPResponse("* some junks\r\n")); + buildListInfoIMAPResponse(content, names, "* LIST (\\Junk \\HasNoChildren) \"/\"", "Bulk Mail"); + buildListInfoIMAPResponse(content, names, "* LIST (\\Drafts \\HasNoChildren) \"/\"", "Draft"); + buildListInfoIMAPResponse(content, names, "* LIST (\\HasNoChildren) \"/\"", "Inbox"); + buildListInfoIMAPResponse(content, names, "* LIST (\\Sent \\HasNoChildren) \"/\"", "Sent"); + buildListInfoIMAPResponse(content, names, "* LIST (\\Trash \\HasNoChildren) \"/\"", "Trash"); + buildListInfoIMAPResponse(content, names, "* LIST (\\HasChildren) \"/\"", "test1"); + content.add(new IMAPResponse("* 115141 EXISTS\r\n")); + buildListInfoIMAPResponse(content, names, "* LIST (\\HasNoChildren) \"/\"", "test1/test1_1"); + content.add(new IMAPResponse("a3 OK LIST completed")); + final ListInfoList ll = mapper.readValue(content.toArray(new IMAPResponse[0]), ListInfoList.class); + final List infos = ll.getListInfo(); + + // verify the result + Assert.assertNotNull(infos, "result mismatched."); + Assert.assertEquals(infos.size(), 8, "ListInfo count mismatched."); + Assert.assertEquals(infos.size(), names.size(), "ListInfo count mismatched."); + for (int i = 0; i < infos.size(); i++) { + final ListInfo info = infos.get(i); + final String expectedFolder = names.get(i); + Assert.assertNotNull(info, "ListInfo should not be null."); + Assert.assertNotNull(expectedFolder, "folder name should not be null."); + Assert.assertTrue(info.hasInferiors, "hasInferiors mismatched."); + Assert.assertNotNull(info.name, "Name mismatched."); + Assert.assertEquals(info.name, expectedFolder, "folder name mismatched."); + } + } + + /** + * Tests parseListInfos method successfully when responses are results of LSUB command. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseListInfosFromLSubSuccess() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final List content = new ArrayList<>(); + final List names = new ArrayList<>(); + buildListInfoIMAPResponse(content, names, "* LSUB (\\Archive \\HasNoChildren) \"/\"", "Archive"); + content.add(new IMAPResponse("* 115140 EXPUNGE\r\n")); + content.add(new IMAPResponse("* MORE JUNKS\r\n")); + buildListInfoIMAPResponse(content, names, "* LSUB (\\Junk \\HasNoChildren) \"/\"", "Bulk Mail"); + buildListInfoIMAPResponse(content, names, "* LSUB (\\Drafts \\HasNoChildren) \"/\"", "Draft"); + buildListInfoIMAPResponse(content, names, "* LSUB (\\HasNoChildren) \"/\"", "Inbox"); + buildListInfoIMAPResponse(content, names, "* LSUB (\\Sent \\HasNoChildren) \"/\"", "Sent"); + buildListInfoIMAPResponse(content, names, "* LSUB (\\Trash \\HasNoChildren) \"/\"", "Trash"); + buildListInfoIMAPResponse(content, names, "* LSUB (\\HasChildren) \"/\"", "test1"); + content.add(new IMAPResponse("* 115141 EXISTS\r\n")); + buildListInfoIMAPResponse(content, names, "* LSUB (\\HasNoChildren) \"/\"", "test1/test1_1"); + content.add(new IMAPResponse("a3 OK LSUB completed")); + final ListInfoList ll = mapper.readValue(content.toArray(new IMAPResponse[0]), ListInfoList.class); + final List infos = ll.getListInfo(); + + // verify the result + Assert.assertNotNull(infos, "result mismatched."); + Assert.assertEquals(infos.size(), 8, "ListInfo count mismatched."); + Assert.assertEquals(infos.size(), names.size(), "ListInfo count mismatched."); + for (int i = 0; i < infos.size(); i++) { + final ListInfo info = infos.get(i); + final String expectedFolder = names.get(i); + Assert.assertNotNull(info, "ListInfo should not be null."); + Assert.assertNotNull(expectedFolder, "folder name should not be null."); + Assert.assertTrue(info.hasInferiors, "hasInferiors mismatched."); + Assert.assertNotNull(info.name, "Name mismatched."); + Assert.assertEquals(info.name, expectedFolder, "folder name mismatched."); + } + } + + /** + * Tests parseListInfos method when final response is not OK. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseListInfosNoOK() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final List content = new ArrayList<>(); + final List names = new ArrayList<>(); + buildListInfoIMAPResponse(content, names, "* LIST (\\Archive \\HasNoChildren) \"/\"", "\"Archive\""); + buildListInfoIMAPResponse(content, names, "* LIST (\\Junk \\HasNoChildren) \"/\"", "\"Bulk Mail\""); + buildListInfoIMAPResponse(content, names, "* LIST (\\Drafts \\HasNoChildren) \"/\"", "\"Draft\""); + buildListInfoIMAPResponse(content, names, "* LIST (\\HasNoChildren) \"/\"", "\"Inbox\""); + buildListInfoIMAPResponse(content, names, "* LIST (\\Sent \\HasNoChildren) \"/\"", "\"Sent\""); + buildListInfoIMAPResponse(content, names, "* LIST (\\Trash \\HasNoChildren) \"/\"", "\"Trash\""); + buildListInfoIMAPResponse(content, names, "* LIST (\\HasChildren) \"/\"", "\"test1\""); + buildListInfoIMAPResponse(content, names, "* LIST (\\HasNoChildren) \"/\"", "\"test1/test1_1\""); + content.add(new IMAPResponse("a3 BAD LIST completed")); + + ImapAsyncClientException cause = null; + try { + mapper.readValue(content.toArray(new IMAPResponse[0]), ListInfoList.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + + } + + /** + * Tests parseListInfos method when final response is not OK. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseListInfosOnlyOKResponse() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final List rr = new ArrayList<>(); + rr.add(new IMAPResponse("a3 OK LIST completed")); + final ListInfoList infos = mapper.readValue(rr.toArray(new IMAPResponse[0]), ListInfoList.class); + + // verify the result + Assert.assertNotNull(infos, "result mismatched."); + } + + /** + * Tests parseListInfos method when response array length is 0. + * + * @throws ProtocolException will not throw + */ + @Test + public void testParseListInfosEmptyResponses() throws ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final List content = new ArrayList<>(); + ImapAsyncClientException cause = null; + try { + mapper.readValue(content.toArray(new IMAPResponse[0]), ListInfoList.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseListStatus method successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseListStatusSuccess() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final List content = new ArrayList<>(); + final List expectedLinfo = new ArrayList<>(); + final List expectedStatuses = new ArrayList<>(); + + // 0 + final String lresp0 = "* LIST (\\HasNoChildren) \"/\" \"INBOX\""; + content.add(new IMAPResponse(lresp0)); + // add some non-relevant responses + content.add(new IMAPResponse("* 115140 EXPUNGE\r\n")); + content.add(new IMAPResponse("* 115141 EXPUNGE\r\n")); + final String sresp0 = "* STATUS \"INBOX\" (HIGHESTMODSEQ 82676 MESSAGES 774 UIDNEXT 913 UIDVALIDITY 1 UNSEEN 769)"; + popluateContentAndBuildStatus(lresp0, sresp0, content, expectedLinfo, expectedStatuses); + + // 1 + content.add(new IMAPResponse("* 115142 EXPUNGE\r\n")); + final String lresp1 = "* LIST (\\HasChildren \\NonExistent) \"/\" \"[Zmail]\""; + content.add(new IMAPResponse(lresp1)); + content.add(new IMAPResponse("* 115143 EXPUNGE\r\n")); + popluateContentAndBuildStatus(lresp1, null, content, expectedLinfo, expectedStatuses); // status is null + + // 2 + final String lresp2 = "* LIST (\\HasNoChildren) \"/\" \"[Zmail]/All Mail\""; + content.add(new IMAPResponse(lresp2)); + final String sresp2 = "* STATUS \"[Zmail]/All Mail\" (HIGHESTMODSEQ 82676 MESSAGES 777 UIDNEXT 1109 UIDVALIDITY 12 UNSEEN 770)"; + popluateContentAndBuildStatus(lresp2, sresp2, content, expectedLinfo, expectedStatuses); + + // 3 + final String lresp3 = "* LIST (\\HasNoChildren) \"/\" \"[Zmail]/Drafts\""; + content.add(new IMAPResponse(lresp3)); + final String sresp3 = "* STATUS \"[Zmail]/Drafts\" (HIGHESTMODSEQ 82676 MESSAGES 1 UIDNEXT 98 UIDVALIDITY 6 UNSEEN 0)"; + popluateContentAndBuildStatus(lresp3, sresp3, content, expectedLinfo, expectedStatuses); + + // 4 + final String lresp4 = "* LIST (\\HasNoChildren) \"/\" \"[Zmail]/Important\""; + content.add(new IMAPResponse(lresp4)); + final String sresp4 = "* STATUS \"[Zmail]/Important\" (HIGHESTMODSEQ 82676 MESSAGES 1 UIDNEXT 118 UIDVALIDITY 9 UNSEEN 0)"; + popluateContentAndBuildStatus(lresp4, sresp4, content, expectedLinfo, expectedStatuses); + + // 5 + final String lresp5 = "* LIST (\\HasNoChildren) \"/\" \"[Zmail]/Sent Mail\""; + content.add(new IMAPResponse(lresp5)); + final String sresp5 = "* STATUS \"[Zmail]/Sent Mail\" (HIGHESTMODSEQ 82676 MESSAGES 0 UIDNEXT 88 UIDVALIDITY 5 UNSEEN 0)"; + popluateContentAndBuildStatus(lresp5, sresp5, content, expectedLinfo, expectedStatuses); + + // 6 + final String lresp6 = "* LIST (\\HasNoChildren) \"/\" \"[Zmail]/Spam\""; + content.add(new IMAPResponse(lresp6)); + final String sresp6 = "* STATUS \"[Zmail]/Spam\" (HIGHESTMODSEQ 82676 MESSAGES 1 UIDNEXT 101 UIDVALIDITY 3 UNSEEN 1)"; + popluateContentAndBuildStatus(lresp6, sresp6, content, expectedLinfo, expectedStatuses); + + // 7 + final String lresp7 = "* LIST (\\HasNoChildren) \"/\" \"[Zmail]/Starred\""; + content.add(new IMAPResponse(lresp7)); + final String sresp7 = "* STATUS \"[Zmail]/Starred\" (HIGHESTMODSEQ 82676 MESSAGES 0 UIDNEXT 9 UIDVALIDITY 4 UNSEEN 0)"; + popluateContentAndBuildStatus(lresp7, sresp7, content, expectedLinfo, expectedStatuses); + + // 8 + final String lresp8 = "* LIST (\\HasNoChildren) \"/\" \"[Zmail]/Trash\""; + content.add(new IMAPResponse(lresp8)); + final String sresp8 = "* STATUS \"[Zmail]/Trash\" (HIGHESTMODSEQ 82676 MESSAGES 1 UIDNEXT 137 UIDVALIDITY 2 UNSEEN 0)"; + popluateContentAndBuildStatus(lresp8, sresp8, content, expectedLinfo, expectedStatuses); + + // 9 + final String lresp9 = "* LIST (\\HasChildren) \"/\" \"parent_folder\""; + content.add(new IMAPResponse(lresp9)); + final String sresp9 = "* STATUS \"parent_folder\" (HIGHESTMODSEQ 82676 MESSAGES 0 UIDNEXT 1 UIDVALIDITY 15 UNSEEN 0)"; + popluateContentAndBuildStatus(lresp9, sresp9, content, expectedLinfo, expectedStatuses); + + // 10 + final String lresp10 = "* LIST (\\HasNoChildren) \"/\" \"parent_folder/child_folder\""; + content.add(new IMAPResponse(lresp10)); + final String sresp10 = "* STATUS \"parent_folder/child_folder\" (HIGHESTMODSEQ 82676 MESSAGES 1 UIDNEXT 2 UIDVALIDITY 16 UNSEEN 0)"; + popluateContentAndBuildStatus(lresp10, sresp10, content, expectedLinfo, expectedStatuses); + + // 11 + final String lresp11 = "* LIST (\\HasChildren \\NonExistent) \"/\" \"abc_folder\""; + content.add(new IMAPResponse(lresp11)); + content.add(new IMAPResponse("* 115143 EXPUNGE\r\n")); + popluateContentAndBuildStatus(lresp11, null, content, expectedLinfo, expectedStatuses); // status is null + + content.add(new IMAPResponse("a3 OK Success")); + + final ListStatusResult ll = mapper.readValue(content.toArray(new IMAPResponse[0]), ListStatusResult.class); + + // verify the result + final List infos = ll.getListInfos(); + Assert.assertNotNull(infos, "result mismatched."); + Assert.assertEquals(infos.size(), 12, "ListInfo count mismatched."); + final Map statuses = ll.getStatuses(); + + for (int i = 0; i < infos.size(); i++) { + final ExtensionListInfo info = infos.get(i); + final ExtensionListInfo expectedInfo = expectedLinfo.get(i); + final Status expectedStatus = expectedStatuses.get(i); + + Assert.assertNotNull(info, "ListStatus should not be null."); + + // verify ListInfo + Assert.assertNotNull(info, "ListInfo should not be null."); + Assert.assertEquals(info.getAvailableExtendedAttributes(), expectedInfo.getAvailableExtendedAttributes(), "Data mismatched."); + + Assert.assertEquals(info.hasInferiors, expectedInfo.hasInferiors, "hasInferiors mismatched."); + Assert.assertEquals(info.name, expectedInfo.name, "ListInfo name mismatched."); + Assert.assertEquals(info.attrs.length, expectedInfo.attrs.length, "ListInfo attrs size mismatched."); + for (int j = 0; j < expectedInfo.attrs.length; j++) { + Assert.assertEquals(info.attrs[j], expectedInfo.attrs[j], "info.attrs[j] mismatched."); + } + final Status st = statuses.get(info.name); + + // Verify Status + if (expectedStatus == null) { // if expecting no status for this folder + Assert.assertNull(st, "Expected Status mismatched."); + continue; + } + Assert.assertEquals(st.mbox, expectedStatus.mbox, "Status.mbox mismatched."); + Assert.assertEquals(info.name, st.mbox, "ListInfo and Status mismatched. "); + Assert.assertEquals(st.highestmodseq, expectedStatus.highestmodseq, "highestmodseq mismatched."); + Assert.assertEquals(st.total, expectedStatus.total, "total mismatched."); + Assert.assertEquals(st.recent, expectedStatus.recent, "recent mismatched."); + Assert.assertEquals(st.uidnext, expectedStatus.uidnext, "highestmodseq mismatched."); + Assert.assertEquals(st.uidvalidity, expectedStatus.uidvalidity, "highestmodseq mismatched."); + Assert.assertEquals(st.unseen, expectedStatus.unseen, "highestmodseq mismatched."); + Assert.assertEquals(st.items, expectedStatus.items, "items mismatched."); + if (expectedStatus.items != null) { + Assert.assertEquals(st.items.size(), expectedStatus.items.size(), "items mismatched."); + for (final String itemName : expectedStatus.items.keySet()) { + Assert.assertEquals(st.items.get(itemName), expectedStatus.items.get(itemName), "Item value mismatched."); + } + } + } + } + + /** + * Builds the Status for expected Status and populate given Content for input. + * + * @param listResp List response in string format + * @param statusResp Status response in string format + * @param content IMAPResponse list + * @param expectedInfo expected ListInfo list + * @param expectedSt expected Status list + * @return + * @throws ProtocolException will not throw + * @throws IOException will not throw + */ + private void popluateContentAndBuildStatus(final String listResp, final String statusResp, final List content, + final List expectedInfo, final List expectedSt) throws IOException, ProtocolException { + + expectedInfo.add(new ExtensionListInfo(new IMAPResponse(listResp))); + if (statusResp != null) { + content.add(new IMAPResponse(statusResp)); + expectedSt.add(new Status(new IMAPResponse(statusResp))); + } else { + expectedSt.add(null); + } + } + + /** + * Tests parseListInfos method when response array length is 0. + * + * @throws ProtocolException will not throw + */ + @Test + public void testParseListStausEmptyResponses() throws ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final List content = new ArrayList<>(); + ImapAsyncClientException cause = null; + try { + mapper.readValue(content.toArray(new IMAPResponse[0]), ListStatusResult.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseListInfos method when final response is not OK. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseListStatusNoOK() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final List content = new ArrayList<>(); + final List names = new ArrayList<>(); + buildListInfoIMAPResponse(content, names, "* LIST (\\Archive \\HasNoChildren) \"/\"", "\"Archive\""); + buildListInfoIMAPResponse(content, names, "* LIST (\\Junk \\HasNoChildren) \"/\"", "\"Bulk Mail\""); + buildListInfoIMAPResponse(content, names, "* LIST (\\Drafts \\HasNoChildren) \"/\"", "\"Draft\""); + buildListInfoIMAPResponse(content, names, "* LIST (\\HasNoChildren) \"/\"", "\"Inbox\""); + buildListInfoIMAPResponse(content, names, "* LIST (\\Sent \\HasNoChildren) \"/\"", "\"Sent\""); + buildListInfoIMAPResponse(content, names, "* LIST (\\Trash \\HasNoChildren) \"/\"", "\"Trash\""); + buildListInfoIMAPResponse(content, names, "* LIST (\\HasChildren) \"/\"", "\"test1\""); + buildListInfoIMAPResponse(content, names, "* LIST (\\HasNoChildren) \"/\"", "\"test1/test1_1\""); + content.add(new IMAPResponse("a3 BAD LIST completed")); + + ImapAsyncClientException cause = null; + try { + mapper.readValue(content.toArray(new IMAPResponse[0]), ListStatusResult.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + + } + + /** + * Tests parseListStatus method when final response is not OK. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseListStatusOnlyOKResponse() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final List rr = new ArrayList<>(); + rr.add(new IMAPResponse("a3 OK LIST completed")); + final ListStatusResult infos = mapper.readValue(rr.toArray(new IMAPResponse[0]), ListStatusResult.class); + + // verify the result + Assert.assertNotNull(infos, "result mismatched."); + } + + /** + * Tests ExtensionMailboxInfo method when response array length is 0. + * + * @throws ProtocolException will not throw + */ + @Test + public void testParseMailboxExtensionInfosEmptyResponses() throws ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final List content = new ArrayList<>(); + ImapAsyncClientException cause = null; + try { + mapper.readValue(content.toArray(new IMAPResponse[0]), ExtensionMailboxInfo.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseStatus method with response array 0. + * + * @throws ProtocolException will not throw + */ + @Test + public void testParseStatusArray0() throws ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[0]; + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, Status.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parse a class that mapper does not support. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseClassUnknown() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[1]; + content[0] = new IMAPResponse("002 OK"); // make it bad so it does not update mode + + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, ID.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.UNKNOWN_PARSE_RESULT_TYPE, "Failure type mismatched."); + } + + /** + * Tests parseStatus method with not OK response. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseStatusNotOK() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[1]; + content[0] = new IMAPResponse("002 BAD"); // make it bad so it does not update mode + + // verify the result + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, Status.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseStatus method successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseStatusNoStatusResponse() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[2]; + content[0] = new IMAPResponse("* NOSTATUS blurdybloop (MESSAGES 231 UIDNEXT 44292)"); + content[1] = new IMAPResponse("A042 OK STATUS completed"); + + // verify the result + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, Status.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseStatus method successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseStatusOK() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final List content = new ArrayList<>(); + content.add(new IMAPResponse("* STATUS blurdybloop (MESSAGES 231 UIDNEXT 44292 UNSEEN 3)")); + content.add(new IMAPResponse("* S2TATUS blurdybloop (MESSAGES 232 UIDNEXT 44293)")); + content.add(new IMAPResponse("* STATUS blurdybloop (UNSEEN 4)")); + content.add(new IMAPResponse("* STATUS blurdybloop (UIDVALIDITY 999333)")); + content.add(new IMAPResponse("* STATUS blurdybloop (HIGHESTMODSEQ 2483)")); + content.add(new IMAPResponse("A042 OK STATUS completed")); + + final Status status = mapper.readValue(content.toArray(new IMAPResponse[0]), Status.class); + + // verify the result + Assert.assertNotNull(status, "status mismatched."); + Assert.assertEquals(status.uidnext, 44292, "uidnext mismatched."); + Assert.assertEquals(status.total, 231, "total mismatched."); + Assert.assertEquals(status.unseen, 4, "unseen mismatched, should take the latter one."); + Assert.assertEquals(status.uidvalidity, 999333, "uidvalidity mismatched."); + Assert.assertEquals(status.highestmodseq, 2483, "highest modseq mismatched."); + } + + /** + * Tests parseToID method with response array 0. + * + * @throws ProtocolException will not throw + */ + @Test + public void testParseToIdResultArray0() throws ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[0]; + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, IdResult.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseToID method with not OK response. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseToIdResultNotOK() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[1]; + content[0] = new IMAPResponse("002 BAD"); // make it bad so it does not update mode + + // verify the result + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, IdResult.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseToIdResult with first byte is N. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseToIdResultFirstByteIsN() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[2]; + content[0] = new IMAPResponse("* ID NIL \n"); + content[1] = new IMAPResponse("a042 OK ID command completed"); + + final IdResult id = mapper.readValue(content, IdResult.class); + + // verify the result + Assert.assertNotNull(id, "id mismatched."); + Assert.assertFalse(id.hasKey("name"), "name key mismatched."); + Assert.assertNull(id.getValue("name"), "name value mismatched."); + } + + /** + * Tests parseToIdResult with first byte is n. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseToIdResultFirstByteIsLowercaseN() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[2]; + content[0] = new IMAPResponse("* ID nIL \n"); + content[1] = new IMAPResponse("a042 OK ID command completed"); + + final IdResult id = mapper.readValue(content, IdResult.class); + + // verify the result + Assert.assertNotNull(id, "id mismatched."); + Assert.assertFalse(id.hasKey("name"), "name key mismatched."); + Assert.assertNull(id.getValue("name"), "name value mismatched."); + } + + /** + * Tests parseToIdResult with first byte not left parenthesis. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseToIdResultNotStartWithLeftParenthesis() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[2]; + content[0] = new IMAPResponse("* ID X \n"); + content[1] = new IMAPResponse("a042 OK ID command completed"); + + // verify the result + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, IdResult.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseToIdResult with first byte not left parenthesis. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseToIdResultNameAbsent() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[2]; + // sun java mail library 1.5.5 has bug fix in readStringList() to return 0 length array instead of 1 length with a null element + + content[0] = new IMAPResponse("* ID () \n"); + content[1] = new IMAPResponse("a042 OK ID command completed"); + + // verify the result + final IdResult id = mapper.readValue(content, IdResult.class); + // verify the result + Assert.assertNotNull(id, "result mismatched."); + } + + /** + * Tests parseToIdResult with first byte not left parenthesis. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseToIdResultValueAbsent() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[2]; + content[0] = new IMAPResponse("* ID (') \n"); + content[1] = new IMAPResponse("a042 OK ID command completed"); + + // verify the result + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, IdResult.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseToID method successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseToIdResultOK() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[3]; + content[0] = new IMAPResponse("* ID (\"name\" \"Cyrus\" \"version\" \"1.5\" \"os\" \"sunos\"\n\"os-version\" \"5.5\" \"support-url\"\n" + + "\"mailto:cyrus-bugs+@andrew.cmu.edu\")\n"); + content[1] = new IMAPResponse("junk"); + content[2] = new IMAPResponse("a042 OK ID command completed"); + + final IdResult id = mapper.readValue(content, IdResult.class); + + // verify the result + Assert.assertNotNull(id, "id mismatched."); + Assert.assertTrue(id.hasKey("name"), "name should be present."); + Assert.assertEquals(id.getValue("name"), "Cyrus", "name value mismatched."); + } + + /** + * Tests parseSearchResult method successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseToSearchResultOK() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[2]; + content[0] = new IMAPResponse("* SEARCH 150404 150406 150407\r\n"); + content[1] = new IMAPResponse("a3 OK UID SEARCH completed\r\n"); + + final SearchResult result = mapper.readValue(content, SearchResult.class); + + // verify the result + Assert.assertNotNull(result, "result mismatched."); + final List list = result.getMessageNumbers(); + Assert.assertNotNull(list, "getMessageNumbers() mismatched."); + Assert.assertEquals(list.size(), 3, "getMessageNumbers() mismatched."); + Assert.assertEquals(list.get(0), Long.valueOf(150404), "getMessageNumbers() mismatched."); + Assert.assertEquals(list.get(1), Long.valueOf(150406), "getMessageNumbers() mismatched."); + Assert.assertEquals(list.get(2), Long.valueOf(150407), "getMessageNumbers() mismatched."); + Assert.assertNull(result.getHighestModSeq(), "getHighestModSeq() mismatch."); + } + + /** + * Tests parseSearchResult method successfully with modification sequence. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseToSearchResultModSeqOK() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[2]; + content[0] = new IMAPResponse("* SEARCH 150404 150406 150407 (MODSEQ 2473)\r\n"); + content[1] = new IMAPResponse("a3 OK UID SEARCH completed\r\n"); + + final SearchResult result = mapper.readValue(content, SearchResult.class); + + // verify the result + Assert.assertNotNull(result, "result mismatched."); + final List list = result.getMessageNumbers(); + final Long modSeq = result.getHighestModSeq(); + Assert.assertNotNull(list, "getMessageNumbers() mismatched."); + Assert.assertEquals(list.size(), 3, "getMessageNumbers() mismatched."); + Assert.assertEquals(list.get(0), Long.valueOf(150404), "getMessageNumbers() mismatched."); + Assert.assertEquals(list.get(1), Long.valueOf(150406), "getMessageNumbers() mismatched."); + Assert.assertEquals(list.get(2), Long.valueOf(150407), "getMessageNumbers() mismatched."); + Assert.assertNotNull(modSeq, "getHighestModSeq() should not return null."); + Assert.assertEquals(modSeq, Long.valueOf(2473), "getHighestModSeq() mismatched."); + } + + /** + * Tests parseSearchResult method successfully without correct modification sequence string. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseToSearchResultNoModSeqOK() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[2]; + content[0] = new IMAPResponse("* SEARCH 150404 150406 150407 (MODSE 2473)\r\n"); + content[1] = new IMAPResponse("a3 OK UID SEARCH completed\r\n"); + + final SearchResult result = mapper.readValue(content, SearchResult.class); + + // verify the result + Assert.assertNotNull(result, "result mismatched."); + final List list = result.getMessageNumbers(); + final Long modSeq = result.getHighestModSeq(); + Assert.assertNotNull(list, "getMessageNumbers() mismatched."); + Assert.assertEquals(list.size(), 3, "getMessageNumbers() mismatched."); + Assert.assertEquals(list.get(0), Long.valueOf(150404), "getMessageNumbers() mismatched."); + Assert.assertEquals(list.get(1), Long.valueOf(150406), "getMessageNumbers() mismatched."); + Assert.assertEquals(list.get(2), Long.valueOf(150407), "getMessageNumbers() mismatched."); + Assert.assertNull(modSeq, "getHighestModSeq() should return null."); + } + + /** + * Tests parseSearchResult method successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseToSearchResultOKNoSearchResult() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[2]; + content[0] = new IMAPResponse("* SEARCH\r\n"); + content[1] = new IMAPResponse("a3 OK UID SEARCH completed\r\n"); + + final SearchResult result = mapper.readValue(content, SearchResult.class); + + // verify the result + Assert.assertNotNull(result, "result mismatched."); + final List list = result.getMessageNumbers(); + Assert.assertNotNull(list, "getMessageSequence() mismatched."); + Assert.assertEquals(list.size(), 0, "getMessageSequence() mismatched."); + } + + /** + * Tests parseToSearchResult method when tagged response is not OK. + * + * @throws ProtocolException will not throw + */ + @Test + public void testParseToSearchResultZeroLengthResponse() throws ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[0]; + + ImapAsyncClientException actual = null; + try { + final SearchResult result = mapper.readValue(content, SearchResult.class); + } catch (final ImapAsyncClientException e) { + actual = e; + } + // verify the result + Assert.assertNotNull(actual, "ImapAsyncClientException should occur."); + Assert.assertEquals(actual.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseToSearchResult method when tagged response is not OK. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseToSearchResultNotOK() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[1]; + content[0] = new IMAPResponse("a3 BAD SEARCH completed (Failure)\r\n"); + + ImapAsyncClientException actual = null; + try { + final SearchResult result = mapper.readValue(content, SearchResult.class); + } catch (final ImapAsyncClientException e) { + actual = e; + } + // verify the result + Assert.assertNotNull(actual, "ImapAsyncClientException should occur."); + Assert.assertEquals(actual.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseToEnableResult method successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseToEnableResult() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final List content = new ArrayList<>(); + content.add(new IMAPResponse("* some junks\r\n")); + content.add(new IMAPResponse("* ENABLED CONDSTORE QRSYNC\r\n")); + content.add(new IMAPResponse("a3 OK ENABLE completed\r\n")); + final EnableResult enableResult = mapper.readValue(content.toArray(new IMAPResponse[0]), EnableResult.class); + + // verify the result + Assert.assertNotNull(enableResult, "result should never return null."); + final Set capas = enableResult.getEnabledCapabilities(); + Assert.assertEquals(capas.size(), 2, "capability missed."); + Assert.assertTrue(capas.contains("CONDSTORE"), "One capability missed."); + Assert.assertTrue(capas.contains("QRSYNC"), "One capability missed."); + } + + /** + * Tests parseToEnableResult method with response contains length zero line. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseToEnableResultLengthZero() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final List content = new ArrayList<>(); + content.add(new IMAPResponse("* some junks\r\n")); + content.add(new IMAPResponse("* ENABLED CONDSTORE QRSYNC\r\n")); + content.add(new IMAPResponse("")); + content.add(new IMAPResponse("a3 OK ENABLE completed\r\n")); + final EnableResult enableResult = mapper.readValue(content.toArray(new IMAPResponse[0]), EnableResult.class); + + // verify the result + Assert.assertNotNull(enableResult, "result should never return null."); + final Set capas = enableResult.getEnabledCapabilities(); + Assert.assertEquals(capas.size(), 2, "capability missed."); + Assert.assertTrue(capas.contains("CONDSTORE"), "One capability missed."); + Assert.assertTrue(capas.contains("QRSYNC"), "One capability missed."); + } + + /** + * Tests parseToEnableResult method with response contains only * ENABLED but no capability. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseToEnableResultNoCapa() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final List content = new ArrayList<>(); + content.add(new IMAPResponse("* ENABLED\r\n")); + content.add(new IMAPResponse("a3 OK ENABLE completed\r\n")); + final EnableResult enableResult = mapper.readValue(content.toArray(new IMAPResponse[0]), EnableResult.class); + + // verify the result + Assert.assertNotNull(enableResult, "result should never return null."); + final Set capas = enableResult.getEnabledCapabilities(); + Assert.assertEquals(capas.size(), 0, "capability missed."); + } + + /** + * Tests parseToEnableResult method when server does not enable all the capability. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseToEnableResultNotEnabled() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final List content = new ArrayList<>(); + content.add(new IMAPResponse("a3 OK ENABLE completed\r\n")); + final EnableResult enableResult = mapper.readValue(content.toArray(new IMAPResponse[0]), EnableResult.class); + + // verify the result + Assert.assertNotNull(enableResult, "result should never return null."); + final Set capas = enableResult.getEnabledCapabilities(); + Assert.assertEquals(capas.size(), 0, "capability missed."); + } + + /** + * Tests parseToEnableResult method when server returns with no response contents. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseToEnableResultNoContent() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final List content = new ArrayList<>(); + ImapAsyncClientException cause = null; + try { + mapper.readValue(content.toArray(new IMAPResponse[0]), EnableResult.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseToEnableResult method when server returns with not OK response. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseToEnableResultNotOK() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final List content = new ArrayList<>(); + content.add(new IMAPResponse("* some junks\r\n")); + content.add(new IMAPResponse("* ENABLED CONDSTORE QRSYNC\r\n")); + content.add(new IMAPResponse("a3 BAD ENABLE fail\r\n")); + ImapAsyncClientException cause = null; + try { + mapper.readValue(content.toArray(new IMAPResponse[0]), EnableResult.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + /** + * Tests parseStore method successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseStoreOK() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[1]; + content[0] = new IMAPResponse("A042 OK Store success"); + final StoreResult storeResult = mapper.readValue(content, StoreResult.class); + + // verify the result + Assert.assertNotNull(storeResult, "store result mismatched."); + Assert.assertEquals(storeResult.getFetchResponses().size(), 0, "getFetchResponses() mismatched."); + } + + /** + * Tests parseStore method successfully with CondStore. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseStoreOKCondStore() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[5]; + content[0] = new IMAPResponse("* OK [HIGHESTMODSEQ 2682"); + content[1] = new IMAPResponse("* 1 FETCH (FLAGS (\\Seen SEEN) MODSEQ (2529))"); + content[2] = new IMAPResponse("* 2 FETCH (FLAGS (\\Seen) MODSEQ (2531))"); + content[3] = new IMAPResponse("* 3 FETCH (FLAGS (\\Seen) MODSEQ (2648))"); + content[4] = new IMAPResponse("a4 OK [MODIFIED 1] Conditional Store Failed (Success)"); + final StoreResult storeResult = mapper.readValue(content, StoreResult.class); + + // verify the result + Assert.assertNotNull(storeResult, "store result should not be null."); + Assert.assertNotNull(storeResult.getHighestModSeq(), "getHighestModSeq() should not return null."); + Assert.assertEquals(storeResult.getHighestModSeq(), Long.valueOf(2682), "getHighestModSeq() mismatched."); + final List irs = storeResult.getFetchResponses(); + Assert.assertEquals(irs.size(), 3, "getFetchResponses() mismatched."); + Assert.assertTrue(irs.get(0) instanceof FetchResponse, "getFetchResponses() mismatched."); + Assert.assertTrue(irs.get(1) instanceof FetchResponse, "getFetchResponses() mismatched."); + Assert.assertTrue(irs.get(2) instanceof FetchResponse, "getFetchResponses() mismatched."); + Assert.assertNotNull(storeResult.getModifiedMsgSets(), "getModifiedMsgsets() should not return null."); + Assert.assertEquals(storeResult.getModifiedMsgSets().length, 1, "getModifiedMsgsets() size mismatched"); + final String msgSets = MessageNumberSet.buildString(storeResult.getModifiedMsgSets()); + Assert.assertEquals(msgSets, "1", "getModifiedMsgsets() mismatched."); + } + + /** + * Tests parseStore method successfully with CondStore and wrong modified string. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseStoreOKCondStoreWithWrongModified() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[5]; + content[0] = new IMAPResponse("* OK [HIGHESTMODSEQ 2682"); + content[1] = new IMAPResponse("* 1 FETCH (FLAGS (\\Seen SEEN) MODSEQ (2529))"); + content[2] = new IMAPResponse("* 2 FETCH (FLAGS (\\Seen) MODSEQ (2531))"); + content[3] = new IMAPResponse("* 3 FETCH (FLAGS (\\Seen) MODSEQ (2648))"); + content[4] = new IMAPResponse("a4 OK [MODIFIE 1] Conditional Store Failed (Success)"); + final StoreResult storeResult = mapper.readValue(content, StoreResult.class); + + // verify the result + Assert.assertNotNull(storeResult, "store result should not be null."); + Assert.assertNotNull(storeResult.getHighestModSeq(), "getHighestModSeq() should not return null."); + Assert.assertEquals(storeResult.getHighestModSeq(), Long.valueOf(2682), "getHighestModSeq() mismatched."); + final List irs = storeResult.getFetchResponses(); + Assert.assertEquals(irs.size(), 3, "getFetchResponses() mismatched."); + Assert.assertTrue(irs.get(0) instanceof FetchResponse, "getFetchResponses() mismatched."); + Assert.assertTrue(irs.get(1) instanceof FetchResponse, "getFetchResponses() mismatched."); + Assert.assertTrue(irs.get(2) instanceof FetchResponse, "getFetchResponses() mismatched."); + Assert.assertNull(storeResult.getModifiedMsgSets(), "getModifiedMsgsets() should return null."); + } + + /** + * Tests parseStore method successfully with No response and CondStore. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseStoreNoCondStore() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[5]; + content[0] = new IMAPResponse("* OK [HIGHEST 2682"); + content[1] = new IMAPResponse("* 1 FETCH (FLAGS (\\Seen SEEN) MODSEQ (2529))"); + content[2] = new IMAPResponse("* 2 FETCH (FLAGS (\\Seen) MODSEQ (2531))"); + content[3] = new IMAPResponse("* 3 FETCH (FLAGS (\\Seen) MODSEQ (2648))"); + content[4] = new IMAPResponse("a4 No [MODIFIED 1] Conditional Store Failed (Success)"); + final StoreResult storeResult = mapper.readValue(content, StoreResult.class); + + // verify the result + Assert.assertNotNull(storeResult, "store result should not be null."); + Assert.assertNull(storeResult.getHighestModSeq(), "getHighestModSeq() should return null."); + final List irs = storeResult.getFetchResponses(); + Assert.assertEquals(irs.size(), 3, "getFetchResponses() mismatched."); + Assert.assertTrue(irs.get(0) instanceof FetchResponse, "getFetchResponses() mismatched."); + Assert.assertTrue(irs.get(1) instanceof FetchResponse, "getFetchResponses() mismatched."); + Assert.assertTrue(irs.get(2) instanceof FetchResponse, "getFetchResponses() mismatched."); + Assert.assertNotNull(storeResult.getModifiedMsgSets(), "getModifiedMsgsets() should not return null."); + Assert.assertEquals(storeResult.getModifiedMsgSets().length, 1, "getModifiedMsgsets() size mismatched"); + final String msgSets = MessageNumberSet.buildString(storeResult.getModifiedMsgSets()); + Assert.assertEquals(msgSets, "1", "getModifiedMsgsets() mismatched."); + } + + /** + * Tests parseStore method successfully with multiple modification numbers. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseStoreOKCondStoreWithMultipleModified() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[5]; + content[0] = new IMAPResponse("* OK [HIGHESTMODSEQ 2682"); + content[1] = new IMAPResponse("* 1 FETCH (FLAGS (\\Seen SEEN) MODSEQ (2529))"); + content[2] = new IMAPResponse("* 2 FETCH (FLAGS (\\Seen) MODSEQ (2531))"); + content[3] = new IMAPResponse("* 3 FETCH (FLAGS (\\Seen) MODSEQ (2648))"); + content[4] = new IMAPResponse("a4 OK [MODIFIED 1:2,5:4] Conditional Store Failed (Success)"); + final StoreResult storeResult = mapper.readValue(content, StoreResult.class); + + // verify the result + Assert.assertNotNull(storeResult, "store result should not be null."); + final List irs = storeResult.getFetchResponses(); + Assert.assertNotNull(storeResult.getHighestModSeq(), "getHighestModSeq() should not return null."); + Assert.assertEquals(storeResult.getHighestModSeq(), Long.valueOf(2682), "getHighestModSeq() mismatched."); + Assert.assertEquals(irs.size(), 3, "getFetchResponses() mismatched."); + Assert.assertNotNull(storeResult.getModifiedMsgSets(), "getModifiedMsgsets() mismatched"); + Assert.assertEquals(storeResult.getModifiedMsgSets().length, 2, "getModifiedMsgsets() mismatched"); + final String msgSets = MessageNumberSet.buildString(storeResult.getModifiedMsgSets()); + Assert.assertEquals(msgSets, "1:2,4:5", "getModifiedMsgsets() mismatched."); + } + + /** + * Tests parseStore method successfully with vanished response.. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseStoreOKCondStoreVanished() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[5]; + content[0] = new IMAPResponse("* OK [HIGHESTMODSEQ 2682"); + content[1] = new IMAPResponse("* 1 FETCH (FLAGS (\\Seen SEEN) MODSEQ (2529))"); + content[2] = new IMAPResponse("* 2 FETCH (FLAGS (\\Seen SEEN) MODSEQ (2600))"); + content[3] = new IMAPResponse("* VANISHED (EARLIER) 41,43:116,118,120:211,214:540"); + content[4] = new IMAPResponse("a4 OK [MODIFIED 1,2,3] Conditional Store Failed (Success)"); + final StoreResult storeResult = mapper.readValue(content, StoreResult.class); + + // verify the result + Assert.assertNotNull(storeResult, "store result mismatched."); + final List irs = storeResult.getFetchResponses(); + Assert.assertNotNull(storeResult.getHighestModSeq(), "getHighestModSeq() should not return null."); + Assert.assertEquals(storeResult.getHighestModSeq(), Long.valueOf(2682), "getHighestModSeq() mismatched."); + Assert.assertEquals(irs.size(), 2, "getFetchResponses() mismatched."); + Assert.assertNotNull(storeResult.getModifiedMsgSets(), "getModifiedMsgsets() should not return null"); + Assert.assertEquals(storeResult.getModifiedMsgSets().length, 3, "getModifiedMsgsets() mismatched"); + final String msgSets = MessageNumberSet.buildString(storeResult.getModifiedMsgSets()); + Assert.assertEquals(msgSets, "1,2,3", "getModifiedMsgsets() mismatched."); + } + + /** + * Tests parseStore method with Bad response. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseStoreBad() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = { new IMAPResponse("002 BAD") }; // make it bad so it does not update mode + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, StoreResult.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseStore method with No response. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseStoreNo() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = { new IMAPResponse("002 BAD") }; // make it bad so it does not update mode + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, StoreResult.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseStore method with 0 response throw exception. + * + * @throws ProtocolException will not throw + */ + @Test + public void testParseStoreZeroResponse() throws ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[0]; + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, StoreResult.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseStore method with invalid fetch response. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseStoreFailParsingFetchResponse() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[2]; + content[0] = new IMAPResponse("* FETCH abc (FLAG (\\Seen SEEN))"); + content[1] = new IMAPResponse("* OK"); + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, StoreResult.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseStore method successfully with NO response. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseStoreNoResponseCondStore() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = { new IMAPResponse("B001 NO [MODIFIED 2] Some of the messages no longer exist.") }; + final StoreResult storeResult = mapper.readValue(content, StoreResult.class); + + // verify the result + Assert.assertNotNull(storeResult, "store result mismatched."); + Assert.assertEquals(storeResult.getFetchResponses().size(), 0, "getFetchResponses() mismatched."); + Assert.assertNotNull(storeResult.getModifiedMsgSets(), "getModifiedMsgsets() should not return null"); + Assert.assertEquals(storeResult.getModifiedMsgSets().length, 1, "getModifiedMsgsets() mismatched."); + Assert.assertEquals(MessageNumberSet.buildString(storeResult.getModifiedMsgSets()), "2", "getModifiedMsgsets() mismatched."); + } + + /** + * Tests parseStore method successfully with NO response and space after left bracket. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseStoreNoResponseCondStoreWithSpace() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = { new IMAPResponse("B001 NO [ MODIFIED 2] Some of the messages no longer exist.") }; + final StoreResult storeResult = mapper.readValue(content, StoreResult.class); + + // verify the result + Assert.assertNotNull(storeResult, "store result mismatched."); + Assert.assertEquals(storeResult.getFetchResponses().size(), 0, "getFetchResponses() mismatched."); + Assert.assertNotNull(storeResult.getModifiedMsgSets(), "getModifiedMsgsets() should not return null"); + Assert.assertEquals(storeResult.getModifiedMsgSets().length, 1, "getModifiedMsgsets() mismatched."); + Assert.assertEquals(MessageNumberSet.buildString(storeResult.getModifiedMsgSets()), "2", "getModifiedMsgsets() mismatched."); + } + + /** + * Tests parseFetch method successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseFetchOK() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = { new IMAPResponse("A042 OK Fetch success") }; + final FetchResult fetchResult = mapper.readValue(content, FetchResult.class); + + // verify the result + Assert.assertNotNull(fetchResult, "Fetch result mismatched."); + Assert.assertEquals(fetchResult.getFetchResponses().size(), 0, "getFetchResponses() mismatched."); + } + + /** + * Tests parseFetch method successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseFetchOKCondStore() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[4]; + content[0] = new IMAPResponse("* 1 FETCH (FLAGS (\\Seen SEEN) MODSEQ (2529))"); + content[1] = new IMAPResponse("* 2 FETCH (FLAGS (\\Seen) MODSEQ (2531))"); + content[2] = new IMAPResponse("* 3 FETCH (FLAGS (\\Seen) MODSEQ (2648))"); + content[3] = new IMAPResponse("a4 OK Success"); + final FetchResult fetchResult = mapper.readValue(content, FetchResult.class); + final List irs = fetchResult.getFetchResponses(); + + // verify the result + Assert.assertNotNull(fetchResult, "Fetch result mismatched."); + Assert.assertEquals(irs.size(), 3, "getFetchResponses() mismatched."); + } + + /** + * Tests parseFetch method with not OK response. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseFetchNotOK() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = { new IMAPResponse("002 BAD") }; // make it bad so it does not update mode + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, FetchResult.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseFetch method with 0 response. + * + * @throws ProtocolException will not throw + */ + @Test + public void testParseFetchZeroResponse() throws ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[0]; + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, FetchResult.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseFetch method with invalid fetch response. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseFetchFailParsingFetchResponse() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[2]; + content[0] = new IMAPResponse("* FETCH abc (FLAG (\\Seen SEEN))"); + content[1] = new IMAPResponse("* OK"); + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, FetchResult.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseFetch method successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseFetchOKCondStoreExpunge() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[6]; + content[0] = new IMAPResponse("* 1 EXPUNGE"); + content[1] = new IMAPResponse("* 2 FETCH (FLAGS (\\Seen SEEN) MODSEQ (2529))"); + content[2] = new IMAPResponse("* 3 EXPUNGE"); + content[3] = new IMAPResponse("* 4 FETCH (FLAGS (\\Seen) MODSEQ (2531))"); + content[4] = new IMAPResponse("* 5 FETCH (FLAGS (\\Seen) MODSEQ (2648))"); + content[5] = new IMAPResponse("a4 OK Success"); + final FetchResult fetchResult = mapper.readValue(content, FetchResult.class); + final List irs = fetchResult.getFetchResponses(); + + // verify the result + Assert.assertNotNull(fetchResult, "fetch result mismatched."); + Assert.assertEquals(irs.size(), 3, "fetch result mismatched."); + } + + /** + * Tests parseFetch method successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseFetchExtension() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[2]; + content[0] = new IMAPResponse("* 1 FETCH (FLAGS (\\Seen SEEN) MODSEQ (2529))"); + content[1] = new IMAPResponse("a4 OK Success"); + final FetchResult fetchResult = mapper.readValue(content, FetchResult.class, new FetchItem[0]); + final List irs = fetchResult.getFetchResponses(); + + // verify the result + Assert.assertNotNull(fetchResult, "fetch result mismatched."); + Assert.assertEquals(irs.size(), 1, "fetch result mismatched."); + } + + /** + * Tests parse a class that mapper does not support. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseClassUnknownWithFetchExtension() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[1]; + content[0] = new IMAPResponse("002 OK"); // make it bad so it does not update mode + + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, ID.class, new FetchItem[0]); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.UNKNOWN_PARSE_RESULT_TYPE, "Failure type mismatched."); + } +} diff --git a/core/src/test/java/com/yahoo/imapnio/command/ArgumentTest.java b/core/src/test/java/com/yahoo/imapnio/command/ArgumentTest.java index f9c25981..993fe34c 100644 --- a/core/src/test/java/com/yahoo/imapnio/command/ArgumentTest.java +++ b/core/src/test/java/com/yahoo/imapnio/command/ArgumentTest.java @@ -4,7 +4,11 @@ import javax.mail.Flags; import javax.mail.internet.MimeUtility; +import javax.mail.search.AndTerm; +import javax.mail.search.BodyTerm; import javax.mail.search.FlagTerm; +import javax.mail.search.NotTerm; +import javax.mail.search.OrTerm; import javax.mail.search.SearchException; import javax.mail.search.SubjectTerm; @@ -12,6 +16,8 @@ import org.testng.annotations.Test; import com.sun.mail.imap.protocol.SearchSequence; +import com.yahoo.imapnio.async.data.ExtendedModifiedSinceTerm; +import com.yahoo.imapnio.async.request.ExtendedSearchSequence; /** * Unit test for {@link Argument}. @@ -74,4 +80,63 @@ public void testConstructorAndToStringNoneNullCharset() throws SearchException, final String searchStr = args.toString(); Assert.assertEquals(searchStr, "SUBJECT {4+}\r\nᅫ뢔ᄅ 1:5", "result mismatched."); } -} \ No newline at end of file + + /** + * Tests constructor and toString() method with null character set and extended search sequence. + * + * @throws IOException will not throw + * @throws SearchException will not throw + */ + @Test + public void testConstructorModifiedSince() throws SearchException, IOException { + + final ExtendedSearchSequence searchSeq = new ExtendedSearchSequence(); + final ExtendedModifiedSinceTerm extendedModifiedSinceTerm = new ExtendedModifiedSinceTerm(1L); + final Argument args = new Argument(); + args.append(searchSeq.generateSequence(extendedModifiedSinceTerm)); + + final String searchStr = args.toString(); + Assert.assertEquals(searchStr, "MODSEQ 1", "generateSequence() mismatched."); + } + + /** + * Tests constructor and toString() method with null character set and extended search sequence with Or And search terms. + * + * @throws IOException will not throw + * @throws SearchException will not throw + */ + @Test + public void testConstructorOrAnd() throws SearchException, IOException { + + final ExtendedSearchSequence searchSeq = new ExtendedSearchSequence(); + final BodyTerm bodyTerm = new BodyTerm("test"); + final AndTerm andTerm = new AndTerm(bodyTerm, bodyTerm); + final OrTerm orTerm = new OrTerm(andTerm, andTerm); + final Argument args = new Argument(); + args.append(searchSeq.generateSequence(orTerm, null)); + + final String searchStr = args.toString(); + Assert.assertEquals(searchStr, "OR (BODY test BODY test) (BODY test BODY test)", "generateSequence() mismatched."); + } + + /** + * Tests constructor and toString() method with null character set and extended search sequence with Not And Modified Since terms. + * + * @throws IOException will not throw + * @throws SearchException will not throw + */ + @Test + public void testConstructorNotAndModifiedSince() throws SearchException, IOException { + + final ExtendedSearchSequence searchSeq = new ExtendedSearchSequence(); + final ExtendedModifiedSinceTerm extendedModifiedSinceTerm = new ExtendedModifiedSinceTerm(1L); + final BodyTerm bodyTerm = new BodyTerm("test"); + final AndTerm andTerm = new AndTerm(bodyTerm, extendedModifiedSinceTerm); + final NotTerm notTerm = new NotTerm(andTerm); + final Argument args = new Argument(); + args.append(searchSeq.generateSequence(notTerm)); + + final String searchStr = args.toString(); + Assert.assertEquals(searchStr, "NOT (BODY test MODSEQ 1)", "generateSequence() mismatched."); + } +} diff --git a/pom.xml b/pom.xml index abf0eaa3..27ab3239 100644 --- a/pom.xml +++ b/pom.xml @@ -1,604 +1,598 @@ - - - 4.0.0 - com.yahoo.imapnio - imapnio - pom - 4.3.7 - ${project.artifactId} - https://github.com/yahoo/imapnio - Parent POM file for imapnio project - - 3.0 - - - - core - - - - - lafa - Lafa - luis.alves@lafaspot.com - - - kraman - Kumar Raman - kumar.raman@yahoo.com - - - - - scm:git:https://github.com/yahoo/imapnio.git - scm:git:https://github.com/yahoo/imapnio.git - https://github.com/yahoo/imapnio.git - imapnio-1.0 - - - - GitHub - https://github.com/yahoo/imapnio/issues - - - - - Apache License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - repo - - - - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - - - - imapnio - UTF-8 - ${project.build.sourceEncoding} - - - 1.8 - 3.0.5 - 2.17 - 3.4 - 2.13 - 2.18 - 2.5.3 - 0.8.5 - 0 - ${project.basedir} - - - - - - - - - - - org.testng - testng - 6.8.8 - test - - - junit - junit - - - - - - - com.google.code.findbugs - jsr305 - 1.3.9 - - - io.netty - netty-handler - 4.1.48.Final - - - com.sun.mail - javax.mail - 1.5.5 - - - org.slf4j - slf4j-api - 1.7.12 - - - ch.qos.logback - logback-core - 1.2.0 - - - ch.qos.logback - logback-classic - 1.2.0 - - - commons-codec - commons-codec - 1.10 - - - org.mockito - mockito-all - 1.10.19 - test - - - - - - - ${project.artifactId} - - - org.apache.maven.wagon - wagon-ssh-external - 1.0-beta-6 - - - - - - org.jacoco - jacoco-maven-plugin - ${jacoco-maven-plugin.version} - - - org.apache.maven.plugins - maven-compiler-plugin - 3.3 - - - org.codehaus.mojo - build-helper-maven-plugin - 1.9.1 - - - org.apache.maven.plugins - maven-release-plugin - 2.5.2 - - - maven-assembly-plugin - 2.5.5 - - - maven-clean-plugin - 2.6.1 - - - maven-enforcer-plugin - 1.4 - - - maven-pmd-plugin - ${maven-pmd-plugin.version} - - - org.apache.maven.plugins - maven-checkstyle-plugin - ${maven-checkstyle-plugin.version} - - - maven-project-info-reports-plugin - 2.8 - - - maven-site-plugin - 3.4 - - - maven-source-plugin - 3.2.0 - - - org.codehaus.mojo - exec-maven-plugin - 1.2.1 - - - org.codehaus.mojo - findbugs-maven-plugin - ${maven-findbugs-plugin.version} - - - org.apache.maven.plugins - maven-scm-plugin - 1.9.4 - false - - - org.apache.maven.plugins - maven-surefire-report-plugin - ${maven-surefire-plugin.version} - - - - org.eclipse.m2e - lifecycle-mapping - 1.0.0 - - - - - - org.codehaus.mojo - findbugs-maven-plugin - [3.0.1,) - - check - - - - - - - - - org.codehaus.mojo - javacc-maven-plugin - [2.6,) - - javacc - - - - - - - - - org.apache.maven.plugins - maven-dependency-plugin - [2.9,) - - copy-dependencies - - - - - - - - - org.apache.maven.plugins - maven-enforcer-plugin - [1.3.1,) - - enforce - - - - - - - - - - org.apache.maven.plugins - - - maven-checkstyle-plugin - - - [2.13,) - - - check - - - - - - - - - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - true - 128m - 512m - ${jdk.version} - ${jdk.version} - true - true - true - UTF-8 - - - - org.apache.maven.plugins - maven-enforcer-plugin - - - enforce - - enforce - - - - - ${jdk.version} - - - - - - - - org.apache.maven.plugins - maven-scm-plugin - false - - connection - *.state,*.yidf - ${main.basedir}/ci/buildtime - Auto-update-build - - - - pre-site - - checkin - - - - - - org.codehaus.mojo - findbugs-maven-plugin - - - true - 2048 - - true - - low - - true - ${main.basedir}/findbugs-include.xml - ${main.basedir}/findbugs-exclude.xml - medium - - false - false - - - - - check-compile - verify - - check - - - - - - org.apache.maven.plugins - maven-pmd-plugin - - false - true - 1 - - ${main.basedir}/pmd-ruleset.xml - - false - true - true - - - - pmd - - check - - - - - - org.apache.maven.plugins - maven-surefire-plugin - ${maven-surefire-plugin.version} - - once - - -Dfile.encoding=ANSI_X3.4-1968 - -Djava.library.path= - -javaagent:"${settings.localRepository}"/org/jacoco/org.jacoco.agent/${jacoco-maven-plugin.version}/org.jacoco.agent-${jacoco-maven-plugin.version}-runtime.jar=destfile=${basedir}/target/jacoco.exec - notIsolate,EventListenersRegression - - true - false - - - - org.apache.maven.plugins - maven-checkstyle-plugin - - - checkstyle - validate - - UTF-8 - true - false - checkstyle.xml - true - true - true - config_loc=${main.basedir} - checkstyle_suppressions.xml - checkstyle.suppressions.file - - - check - - - - - - org.jacoco - jacoco-maven-plugin - - - jacoco-initialize - initialize - - prepare-agent - - - ${basedir}/target/jacoco.exec - - - - jacoco-site - package - - report - - - ${basedir}/target/jacoco.exec - ${basedir}/target/jacoco - - - - - - org.apache.maven.plugins - maven-source-plugin - - - attach-sources - - jar-no-fork - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 3.2.0 - - 8 - - - - attach-javadocs - - jar - - - - - - - - - - commit - - test - - - - commit - - - - - - - - - ossrh - - gpg - ${env.GPG_KEYNAME} - ${env.GPG_PASSPHRASE} - false - ${main.basedir}/ci/deploy - pubring.gpg - secring.gpg - - - - performRelease - true - - - - - - org.apache.maven.plugins - maven-gpg-plugin - 1.6 - - - sign-artifacts - verify - - sign - - - - --pinentry-mode - loopback - - - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.7 - true - - ossrh - https://oss.sonatype.org/ - true - - - - - - - - - + + + 4.0.0 + com.yahoo.imapnio + imapnio + pom + 4.3.8 + ${project.artifactId} + https://github.com/yahoo/imapnio + Parent POM file for imapnio project + + 3.0 + + + + core + + + + + Yahoo Inc. + https://github.com/yahoo + + + + + scm:git:https://github.com/yahoo/imapnio.git + scm:git:https://github.com/yahoo/imapnio.git + https://github.com/yahoo/imapnio.git + imapnio-1.0 + + + + GitHub + https://github.com/yahoo/imapnio/issues + + + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + imapnio + UTF-8 + ${project.build.sourceEncoding} + + + 1.8 + 3.0.5 + 2.17 + 3.4 + 2.13 + 2.18 + 2.5.3 + 0.8.5 + 0 + ${project.basedir} + + + + + + + + + + + org.testng + testng + 6.8.8 + test + + + junit + junit + + + + + + + com.google.code.findbugs + jsr305 + 1.3.9 + + + io.netty + netty-handler + 4.1.48.Final + + + com.sun.mail + javax.mail + 1.5.5 + + + org.slf4j + slf4j-api + 1.7.12 + + + ch.qos.logback + logback-core + 1.2.0 + + + ch.qos.logback + logback-classic + 1.2.0 + + + commons-codec + commons-codec + 1.10 + + + org.mockito + mockito-all + 1.10.19 + test + + + + + + + ${project.artifactId} + + + org.apache.maven.wagon + wagon-ssh-external + 1.0-beta-6 + + + + + + org.jacoco + jacoco-maven-plugin + ${jacoco-maven-plugin.version} + + + org.apache.maven.plugins + maven-compiler-plugin + 3.3 + + + org.codehaus.mojo + build-helper-maven-plugin + 1.9.1 + + + org.apache.maven.plugins + maven-release-plugin + 2.5.2 + + + maven-assembly-plugin + 2.5.5 + + + maven-clean-plugin + 2.6.1 + + + maven-enforcer-plugin + 1.4 + + + maven-pmd-plugin + ${maven-pmd-plugin.version} + + + org.apache.maven.plugins + maven-checkstyle-plugin + ${maven-checkstyle-plugin.version} + + + maven-project-info-reports-plugin + 2.8 + + + maven-site-plugin + 3.4 + + + maven-source-plugin + 3.2.0 + + + org.codehaus.mojo + exec-maven-plugin + 1.2.1 + + + org.codehaus.mojo + findbugs-maven-plugin + ${maven-findbugs-plugin.version} + + + org.apache.maven.plugins + maven-scm-plugin + 1.9.4 + false + + + org.apache.maven.plugins + maven-surefire-report-plugin + ${maven-surefire-plugin.version} + + + + org.eclipse.m2e + lifecycle-mapping + 1.0.0 + + + + + + org.codehaus.mojo + findbugs-maven-plugin + [3.0.1,) + + check + + + + + + + + + org.codehaus.mojo + javacc-maven-plugin + [2.6,) + + javacc + + + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + [2.9,) + + copy-dependencies + + + + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + [1.3.1,) + + enforce + + + + + + + + + + org.apache.maven.plugins + + + maven-checkstyle-plugin + + + [2.13,) + + + check + + + + + + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + true + 128m + 512m + ${jdk.version} + ${jdk.version} + true + true + true + UTF-8 + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + enforce + + enforce + + + + + ${jdk.version} + + + + + + + + org.apache.maven.plugins + maven-scm-plugin + false + + connection + *.state,*.yidf + ${main.basedir}/ci/buildtime + Auto-update-build + + + + pre-site + + checkin + + + + + + org.codehaus.mojo + findbugs-maven-plugin + + + true + 2048 + + true + + low + + true + ${main.basedir}/findbugs-include.xml + ${main.basedir}/findbugs-exclude.xml + medium + + false + false + + + + + check-compile + verify + + check + + + + + + org.apache.maven.plugins + maven-pmd-plugin + + false + true + 1 + + ${main.basedir}/pmd-ruleset.xml + + false + true + true + + + + pmd + + check + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + once + + -Dfile.encoding=ANSI_X3.4-1968 + -Djava.library.path= + -javaagent:"${settings.localRepository}"/org/jacoco/org.jacoco.agent/${jacoco-maven-plugin.version}/org.jacoco.agent-${jacoco-maven-plugin.version}-runtime.jar=destfile=${basedir}/target/jacoco.exec + notIsolate,EventListenersRegression + + true + false + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + + checkstyle + validate + + UTF-8 + true + false + checkstyle.xml + true + true + true + config_loc=${main.basedir} + checkstyle_suppressions.xml + checkstyle.suppressions.file + + + check + + + + + + org.jacoco + jacoco-maven-plugin + + + jacoco-initialize + initialize + + prepare-agent + + + ${basedir}/target/jacoco.exec + + + + jacoco-site + package + + report + + + ${basedir}/target/jacoco.exec + ${basedir}/target/jacoco + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.2.0 + + 8 + + + + attach-javadocs + + jar + + + + + + + + + + commit + + test + + + + commit + + + + + + + + + ossrh + + gpg + ${env.GPG_KEYNAME} + ${env.GPG_PASSPHRASE} + false + ${main.basedir}/ci/deploy + pubring.gpg + secring.gpg + + + + performRelease + true + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.6 + + + sign-artifacts + verify + + sign + + + + --pinentry-mode + loopback + + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.7 + true + + ossrh + https://oss.sonatype.org/ + true + + + + + + + + +