map = new HashMap<>();
42 | map.put("name", "Cyrus");
43 | map.put("version", "1.5");
44 | map.put("os", "sunos");
45 | map.put("os-version", "5.5");
46 | final String expectedVal = "mailto:cyrus-bugs+@andrew.cmu.edu";
47 | map.put("support-url", expectedVal);
48 |
49 | final IdResult idresult = new IdResult(map);
50 | Assert.assertFalse(idresult.hasKey(null), "Result mismatched.");
51 | final String nullKey = null;
52 | Assert.assertNull(idresult.getValue(nullKey), "Result mismatched.");
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/core/src/main/java/com/yahoo/imapnio/async/request/MoveMessageCommand.java:
--------------------------------------------------------------------------------
1 | package com.yahoo.imapnio.async.request;
2 |
3 | import javax.annotation.Nonnull;
4 |
5 | import com.sun.mail.imap.protocol.MessageSet;
6 | import com.yahoo.imapnio.async.data.MessageNumberSet;
7 |
8 | /**
9 | * This class defines imap move command request from client.
10 | */
11 | public class MoveMessageCommand extends AbstractMessageActionCommand {
12 |
13 | /** Command name. */
14 | private static final String MOVE = "MOVE";
15 |
16 | /**
17 | * Initializes a {@link MoveMessageCommand} with the message sequence syntax.
18 | *
19 | * @param msgsets the set of message set
20 | * @param targetFolder the targetFolder to be stored
21 | */
22 | public MoveMessageCommand(@Nonnull final MessageSet[] msgsets, @Nonnull final String targetFolder) {
23 | super(MOVE, false, msgsets, targetFolder);
24 | }
25 |
26 | /**
27 | * Initializes a {@link MoveMessageCommand} with the start and end message sequence.
28 | *
29 | * @param start the starting message sequence
30 | * @param end the ending message sequence
31 | * @param targetFolder the targetFolder to be stored
32 | */
33 | public MoveMessageCommand(final int start, final int end, @Nonnull final String targetFolder) {
34 | super(MOVE, false, start, end, targetFolder);
35 | }
36 |
37 | /**
38 | * Initializes a {@link CopyMessageCommand} with the {@link MessageNumberSet} array.
39 | *
40 | * @param msgsets the set of {@link MessageNumberSet}
41 | * @param targetFolder the targetFolder to be stored
42 | */
43 | public MoveMessageCommand(@Nonnull final MessageNumberSet[] msgsets, @Nonnull final String targetFolder) {
44 | super(MOVE, false, msgsets, targetFolder);
45 | }
46 |
47 | @Override
48 | public ImapRFCSupportedCommandType getCommandType() {
49 | return ImapRFCSupportedCommandType.MOVE_MESSAGE;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/core/src/main/java/com/yahoo/imapnio/async/client/ImapAsyncSessionConfig.java:
--------------------------------------------------------------------------------
1 | package com.yahoo.imapnio.async.client;
2 |
3 | /**
4 | * Class for IMAP Client connection and channel settings.
5 | */
6 | public final class ImapAsyncSessionConfig {
7 |
8 | /** Default connection timeout value in milliseconds. */
9 | public static final int DEFAULT_CONNECTION_TIMEOUT_MILLIS = 10000;
10 |
11 | /** Default IMAP command response read from server timeout value in milliseconds. */
12 | public static final int DEFAULT_READ_TIMEOUT_MILLIS = 10000;
13 |
14 | /**
15 | * Maximum time in milliseconds for opening a connection, this maps to CONNECT_TIMEOUT_MILLIS in {@code ChannelOption}, it will be used when
16 | * establishing a connection.
17 | */
18 | private int connectionTimeoutMillis = DEFAULT_CONNECTION_TIMEOUT_MILLIS;
19 |
20 | /**
21 | * Maximum time in milliseconds for read timeout. The maximum time allowing no responses from server since client command sent.
22 | */
23 | private int readTimeoutMillis = DEFAULT_READ_TIMEOUT_MILLIS;
24 |
25 | /**
26 | * @return Maximum time for opening a connection
27 | */
28 | public int getConnectionTimeoutMillis() {
29 | return connectionTimeoutMillis;
30 | }
31 |
32 | /**
33 | * Sets the maximum time for opening a connection in milliseconds.
34 | *
35 | * @param connectionTimeoutMillis time in milliseconds
36 | */
37 | public void setConnectionTimeoutMillis(final int connectionTimeoutMillis) {
38 | this.connectionTimeoutMillis = connectionTimeoutMillis;
39 | }
40 |
41 | /**
42 | * @return Maximum time for read timeout
43 | */
44 | public int getReadTimeoutMillis() {
45 | return readTimeoutMillis;
46 | }
47 |
48 | /**
49 | * Sets the maximum time for read timeout, this means the time waiting for server to respond.
50 | *
51 | * @param readTimeoutMillis time in milliseconds
52 | */
53 | public void setReadTimeoutMillis(final int readTimeoutMillis) {
54 | this.readTimeoutMillis = readTimeoutMillis;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/core/src/test/java/com/yahoo/imapnio/async/data/QResyncParameterTest.java:
--------------------------------------------------------------------------------
1 | package com.yahoo.imapnio.async.data;
2 |
3 | import org.testng.Assert;
4 | import org.testng.annotations.Test;
5 |
6 | /**
7 | * Unit test for {@link QResyncParameter}.
8 | */
9 | public class QResyncParameterTest {
10 | /**
11 | * Test the get method for uidvalidity.
12 | */
13 | @Test
14 | public void testGetUidValidity() {
15 | final QResyncParameter qResyncParameter = new QResyncParameter(100L, 200L, null, null);
16 | Assert.assertEquals(100L, qResyncParameter.getUidValidity());
17 | }
18 |
19 | /**
20 | * Test the get method for moseq.
21 | */
22 | @Test
23 | public void testGetModSeq() {
24 | final QResyncParameter qResyncParameter = new QResyncParameter(100L, 200L, null, null);
25 | Assert.assertEquals(200L, qResyncParameter.getModSeq());
26 | }
27 |
28 | /**
29 | * Test the get method for known UIDs.
30 | */
31 | @Test
32 | public void testGetKnownUids() {
33 | final MessageNumberSet[] uids = new MessageNumberSet[] { new MessageNumberSet(1, 5) };
34 | final MessageNumberSet[] expectedUids = new MessageNumberSet[] { new MessageNumberSet(1, 5) };
35 | final QResyncParameter qResyncParameter = new QResyncParameter(100L, 200L, uids, null);
36 | Assert.assertEquals(qResyncParameter.getKnownUids(), expectedUids, "UIDs should match");
37 | }
38 |
39 | /**
40 | * Test the get method for message sequence set.
41 | */
42 | @Test
43 | public void testGetSeqMatchData() {
44 | final MessageNumberSet[] uids = new MessageNumberSet[] { new MessageNumberSet(1, 5) };
45 | final MessageNumberSet[] knownSeqSet = new MessageNumberSet[] { new MessageNumberSet(1, 20) };
46 | final MessageNumberSet[] knownUidSet = new MessageNumberSet[] { new MessageNumberSet(1, 10) };
47 | final QResyncSeqMatchData seqMatchData = new QResyncSeqMatchData(knownSeqSet, knownUidSet);
48 | final QResyncParameter qResyncParameter = new QResyncParameter(100L, 200L, uids, seqMatchData);
49 | Assert.assertEquals(seqMatchData, qResyncParameter.getSeqMatchData());
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/core/src/main/java/com/yahoo/imapnio/async/data/QResyncParameter.java:
--------------------------------------------------------------------------------
1 | package com.yahoo.imapnio.async.data;
2 |
3 | import javax.annotation.Nullable;
4 |
5 | /**
6 | * This class models the QRESYNC parameter defined in https://tools.ietf.org/html/rfc7162#page-26.
7 | */
8 | public class QResyncParameter {
9 | /** Last known UIDVALIDITY. */
10 | private long uidValidity;
11 |
12 | /** Last known modification sequence. */
13 | private long modSeq;
14 |
15 | /** Optional set of known UIDs. */
16 | private MessageNumberSet[] knownUids;
17 |
18 | /** Optional parenthesized list of known sequence ranges and their corresponding UIDs. */
19 | private QResyncSeqMatchData seqMatchData;
20 |
21 | /**
22 | * Constructor.
23 | *
24 | * @param uidValidity last known uidvalidity
25 | * @param modSeq last known modification sequence
26 | * @param knownUids known UIDs
27 | * @param seqMatchData known message sequence set and their corresponding UID
28 | */
29 | public QResyncParameter(final long uidValidity, final long modSeq, @Nullable final MessageNumberSet[] knownUids,
30 | @Nullable final QResyncSeqMatchData seqMatchData) {
31 | this.uidValidity = uidValidity;
32 | this.modSeq = modSeq;
33 | this.knownUids = knownUids;
34 | this.seqMatchData = seqMatchData;
35 | }
36 |
37 | /**
38 | * Get the last known uidvalidity.
39 | * @return last known uidvalidity
40 | */
41 | public long getUidValidity() {
42 | return uidValidity;
43 | }
44 |
45 | /**
46 | * Get the last known modification sequence.
47 | * @return last known modification sequence
48 | */
49 | public long getModSeq() {
50 | return modSeq;
51 | }
52 |
53 | /**
54 | * Get the last known UIDs.
55 | * @return known UIDs
56 | */
57 | public MessageNumberSet[] getKnownUids() {
58 | return knownUids;
59 | }
60 |
61 | /**
62 | * Get the message sequence set and corresponding UID.
63 | * @return QResyncSeqMatchData
64 | */
65 | public QResyncSeqMatchData getSeqMatchData() {
66 | return seqMatchData;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/core/src/main/java/com/yahoo/imapnio/async/data/PartialExtensionUidFetchInfo.java:
--------------------------------------------------------------------------------
1 | package com.yahoo.imapnio.async.data;
2 |
3 | /**
4 | * The PARTIAL extension of the Internet Message Access Protocol (RFC 3501/RFC 9051) allows clients
5 | * to limit the number of search results returned, as well as to perform incremental (paged) searches.
6 | * This also helps servers to optimize resource usage when performing searches.
7 | *
8 | *
9 | * {@code
10 | * partial-range-first = nz-number ":" nz-number
11 | * ;; Request to search from oldest (lowest UIDs) to
12 | * ;; more recent messages.
13 | * ;; A range 500:400 is the same as 400:500.
14 | * ;; This is similar to from [RFC3501],
15 | * ;; but cannot contain "*".
16 | *
17 | * partial-range-last = MINUS nz-number ":" MINUS nz-number
18 | * ;; Request to search from newest (highest UIDs) to
19 | * ;; oldest messages.
20 | * ;; A range -500:-400 is the same as -400:-500.
21 | *
22 | * nz-number = digit-nz *DIGIT
23 | * ; Non-zero unsigned 32-bit integer
24 | * ; (0 < n < 4,294,967,296)
25 | * }
26 | *
27 | */
28 |
29 | public class PartialExtensionUidFetchInfo {
30 | /** First uid number that needs to be searched. */
31 | private final int firstUid;
32 |
33 | /** Last uid number that needs to be searched. */
34 | private final int lastUid;
35 |
36 | /**
37 | * Instantiates a {@link PartialExtensionUidFetchInfo} with specific range including first and last uids.
38 | *
39 | * @param firstUid first uid to be searched
40 | * @param lastUid last uid to be searched
41 | */
42 | public PartialExtensionUidFetchInfo(final int firstUid, final int lastUid) {
43 | this.firstUid = firstUid;
44 | this.lastUid = lastUid;
45 | }
46 |
47 | /**
48 | * Returns first uid.
49 | *
50 | * @return first uid
51 | */
52 | public int getFirstUid() {
53 | return firstUid;
54 | }
55 |
56 | /**
57 | * Returns last uid.
58 | *
59 | * @return last uid
60 | */
61 | public int getLastUid() {
62 | return lastUid;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/core/src/main/java/com/yahoo/imapnio/async/request/EnableCommand.java:
--------------------------------------------------------------------------------
1 | package com.yahoo.imapnio.async.request;
2 |
3 | import java.nio.charset.StandardCharsets;
4 |
5 | import javax.annotation.Nonnull;
6 |
7 | import io.netty.buffer.ByteBuf;
8 | import io.netty.buffer.Unpooled;
9 |
10 | /**
11 | * This class defines imap enable command request from client. RFC5161 ABNF: https://tools.ietf.org/html/rfc5161
12 | *
13 | *
14 | * capability =/ "ENABLE"
15 | *
16 | * command-any =/ "ENABLE" 1*(SP capability)
17 | *
18 | * response-data =/ "*" SP enable-data CRLF
19 | *
20 | * enable-data = "ENABLED" *(SP capability)
21 | *
22 | *
23 | */
24 | public class EnableCommand extends ImapRequestAdapter {
25 |
26 | /**
27 | * Initializes a {@link EnableCommand}.
28 | *
29 | * @param capabilities List of capability to enable
30 | */
31 | public EnableCommand(@Nonnull final String[] capabilities) {
32 | this.capabilities = capabilities;
33 | }
34 |
35 | @Override
36 | public void cleanup() {
37 | capabilities = null;
38 | }
39 |
40 | @Override
41 | public ByteBuf getCommandLineBytes() {
42 | final ByteBuf sb = Unpooled.buffer(ENABLE_BUF_LEN);
43 |
44 | sb.writeBytes(ENABLE_B);
45 |
46 | for (int i = 0; i < capabilities.length; i++) {
47 | sb.writeByte(ImapClientConstants.SPACE);
48 | // capability ABNF is:
49 | // capability = ("AUTH=" auth-type) / atom
50 | sb.writeBytes(capabilities[i].getBytes(StandardCharsets.US_ASCII));
51 | }
52 | sb.writeBytes(CRLF_B);
53 | return sb;
54 | }
55 |
56 | @Override
57 | public ImapRFCSupportedCommandType getCommandType() {
58 | return ImapRFCSupportedCommandType.ENABLE;
59 | }
60 |
61 | /** Byte array for CR and LF, keeping the array local so it cannot be modified by others. */
62 | private static final byte[] CRLF_B = { '\r', '\n' };
63 |
64 | /** Enable and space. */
65 | private static final byte[] ENABLE_B = "ENABLE".getBytes(StandardCharsets.US_ASCII);
66 |
67 | /** Enable command buffer length. */
68 | private static final int ENABLE_BUF_LEN = 200;
69 |
70 | /** Capability values. */
71 | private String[] capabilities;
72 | }
73 |
--------------------------------------------------------------------------------
/core/src/main/java/com/yahoo/imapnio/async/request/ImapRFCSupportedCommandType.java:
--------------------------------------------------------------------------------
1 | package com.yahoo.imapnio.async.request;
2 |
3 | /**
4 | * IMAP command type which is RFC-supported.
5 | */
6 | public enum ImapRFCSupportedCommandType implements ImapCommandType {
7 | /** Append message command. */
8 | APPEND_MESSAGE,
9 | /** Authenticate plain command. */
10 | AUTHENTICATE,
11 | /** Capability command. */
12 | CAPABILITY,
13 | /** Check command. */
14 | CHECK,
15 | /** Close command. */
16 | CLOSE,
17 | /** Compress command. */
18 | COMPRESS,
19 | /** Copy message command. */
20 | COPY_MESSAGE,
21 | /** Create folder command. */
22 | CREATE_FOLDER,
23 | /** Delete folder command. */
24 | DELETE_FOLDER,
25 | /** Enable capability command. */
26 | ENABLE,
27 | /** Examine folder command. */
28 | EXAMINE_FOLDER,
29 | /** Expunge command. */
30 | EXPUNGE,
31 | /** Fetch command. */
32 | FETCH,
33 | /** Id command. */
34 | ID,
35 | /** Idle command. */
36 | IDLE,
37 | /** List command. */
38 | LIST,
39 | /** List command. */
40 | LIST_STATUS,
41 | /** Login command. */
42 | LOGIN,
43 | /** Logout command. */
44 | LOGOUT,
45 | /** LSUB command. */
46 | LSUB,
47 | /** Move message command. */
48 | MOVE_MESSAGE,
49 | /** Namespace command. */
50 | NAMESPACE,
51 | /** Noop command. */
52 | NOOP,
53 | /** Rename folder command. */
54 | RENAME_FOLDER,
55 | /** Search command. */
56 | SEARCH,
57 | /** Select folder command. */
58 | SELECT_FOLDER,
59 | /** Status command. */
60 | STATUS,
61 | /** Store flags command. */
62 | STORE_FLAGS,
63 | /** Subscribe command. */
64 | SUBSCRIBE,
65 | /** UID copy command. */
66 | UID_COPY_MESSAGE,
67 | /** UID expunge command. */
68 | UID_EXPUNGE,
69 | /** UID fetch command. */
70 | UID_FETCH,
71 | /** UID move command. */
72 | UID_MOVE_MESSAGE,
73 | /** UID search command. */
74 | UID_SEARCH,
75 | /** UID store command. */
76 | UID_STORE_FLAGS,
77 | /** Unselect command. */
78 | UNSELECT,
79 | /** Unsubscribe command. */
80 | UNSUBSCRIBE;
81 |
82 | @Override
83 | public String getType() {
84 | return this.name();
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/core/src/main/java/com/yahoo/imapnio/async/request/AbstractFolderActionCommand.java:
--------------------------------------------------------------------------------
1 | package com.yahoo.imapnio.async.request;
2 |
3 | import java.nio.charset.StandardCharsets;
4 |
5 | import javax.annotation.Nonnull;
6 |
7 | import com.sun.mail.imap.protocol.BASE64MailboxEncoder;
8 | import com.yahoo.imapnio.async.exception.ImapAsyncClientException;
9 |
10 | import io.netty.buffer.ByteBuf;
11 | import io.netty.buffer.Unpooled;
12 |
13 | /**
14 | * This class defines imap abstract commands related to change operation on folder, like create folder, rename folder, delete folder.
15 | */
16 | abstract class AbstractFolderActionCommand extends ImapRequestAdapter {
17 |
18 | /** Byte array for CR and LF, keeping the array local so it cannot be modified by others. */
19 | private static final byte[] CRLF_B = { '\r', '\n' };
20 |
21 | /** Command operator, for example, "CREATE". */
22 | private String op;
23 |
24 | /** Folder name. */
25 | private String folderName;
26 |
27 | /**
28 | * Initializes a {@link AbstractFolderActionCommand}.
29 | *
30 | * @param op command operator
31 | * @param folderName folder name
32 | */
33 | protected AbstractFolderActionCommand(@Nonnull final String op, @Nonnull final String folderName) {
34 | this.op = op;
35 | this.folderName = folderName;
36 | }
37 |
38 | @Override
39 | public void cleanup() {
40 | this.op = null;
41 | this.folderName = null;
42 | }
43 |
44 | @Override
45 | public ByteBuf getCommandLineBytes() throws ImapAsyncClientException {
46 |
47 | final String base64Folder = BASE64MailboxEncoder.encode(folderName);
48 | // 2 * base64Folder.length(): assuming every char needs to be escaped, goal is eliminating resizing, and avoid complex length calculation
49 | final int len = 2 * base64Folder.length() + ImapClientConstants.PAD_LEN;
50 | final ByteBuf sb = Unpooled.buffer(len);
51 | sb.writeBytes(op.getBytes(StandardCharsets.US_ASCII));
52 | sb.writeByte(ImapClientConstants.SPACE);
53 |
54 | final ImapArgumentFormatter formatter = new ImapArgumentFormatter();
55 | formatter.formatArgument(base64Folder, sb, false); // already base64 encoded so can be formatted and write to sb
56 | sb.writeBytes(CRLF_B);
57 |
58 | return sb;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/core/src/main/java/com/yahoo/imapnio/command/Argument.java:
--------------------------------------------------------------------------------
1 | package com.yahoo.imapnio.command;
2 |
3 | import java.io.IOException;
4 |
5 | import com.sun.mail.iap.ProtocolException;
6 |
7 | /**
8 | * We extend Sun's IMAP Argument class for two reasons: 1. It doesn't have a reasonable constructor by default. 2. We want to make it easy to convert
9 | * Argument to a string instead of only writing directly to DataOutputStream/Protocol.
10 | *
11 | * Sun's IMAP Argument isn't great but its read/write methods are worthwhile.
12 | *
13 | * @author kraman
14 | */
15 | public class Argument extends com.sun.mail.iap.Argument {
16 | /**
17 | * Creates a IMAP Argument object.
18 | */
19 | public Argument() {
20 | }
21 |
22 | /**
23 | * Add string.
24 | *
25 | * @param s
26 | * argument string
27 | * @return this Argument object
28 | */
29 | public Argument addString(final String s) {
30 | writeString(s);
31 | return this;
32 | }
33 |
34 | /**
35 | * Add string literal.
36 | *
37 | * @param s
38 | * argument literal
39 | * @return this Argument object
40 | */
41 | public Argument addLiteral(final String s) {
42 | writeAtom(s);
43 | return this;
44 | }
45 |
46 | /**
47 | * Convert arguments to space-separated list. Because of the number of private classes and methods in Argument, we cannot simply copy the logic
48 | * there - it (sadly) makes more sense to proxy Protocol.
49 | *
50 | * @return string version of the argument
51 | */
52 | @Override
53 | public String toString() {
54 | String result = "";
55 |
56 | ProxyProtocol proxyProtocol = null;
57 | try {
58 | proxyProtocol = new ProxyProtocol();
59 | write(proxyProtocol);
60 | result = proxyProtocol.toString();
61 | } catch (final IOException | ProtocolException e) {
62 | e.printStackTrace();
63 | } finally {
64 | try {
65 | if (proxyProtocol != null) {
66 | proxyProtocol.close();
67 | }
68 | } catch (final IOException e) {
69 | // TODO: remove this code e.printStackTrace();
70 | }
71 | }
72 |
73 | return result;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/core/src/main/java/com/yahoo/imapnio/async/request/StoreFlagsCommand.java:
--------------------------------------------------------------------------------
1 | package com.yahoo.imapnio.async.request;
2 |
3 | import javax.annotation.Nonnull;
4 | import javax.mail.Flags;
5 |
6 | import com.yahoo.imapnio.async.data.MessageNumberSet;
7 |
8 | /**
9 | * This class defines imap store command request from client.
10 | */
11 | public class StoreFlagsCommand extends AbstractStoreFlagsCommand {
12 |
13 | /**
14 | * Initializes a {@link StoreFlagsCommand} with the MessageNumberSet array, Flags and action.Requests server to return the new value.
15 | *
16 | * @param msgsets the set of message set
17 | * @param flags the flags to be stored
18 | * @param action whether to replace, add or remove the flags
19 | */
20 | public StoreFlagsCommand(@Nonnull final MessageNumberSet[] msgsets, @Nonnull final Flags flags, @Nonnull final FlagsAction action) {
21 | super(false, msgsets, flags, action, false);
22 | }
23 |
24 | /**
25 | * Initializes a {@link StoreFlagsCommand} with the MessageNumberSet array and flags.
26 | *
27 | * @param msgsets the set of message set
28 | * @param flags the flags to be stored
29 | * @param action whether to replace, add or remove the flags
30 | * @param silent true if asking server to respond silently
31 | */
32 | public StoreFlagsCommand(@Nonnull final MessageNumberSet[] msgsets, @Nonnull final Flags flags, @Nonnull final FlagsAction action,
33 | final boolean silent) {
34 | super(false, msgsets, flags, action, silent);
35 | }
36 |
37 | /**
38 | * Initializes a {@link StoreFlagsCommand} with string form message numbers, Flags, action, flag whether to request server to return the new
39 | * value.
40 | *
41 | * @param msgNumbers the message numbers in string format
42 | * @param flags the flags to be stored
43 | * @param action whether to replace, add or remove the flags
44 | * @param silent true if asking server to respond silently; false if requesting server to return the new values
45 | */
46 | public StoreFlagsCommand(@Nonnull final String msgNumbers, @Nonnull final Flags flags, @Nonnull final FlagsAction action, final boolean silent) {
47 | super(false, msgNumbers, flags, action, silent);
48 | }
49 |
50 | @Override
51 | public ImapRFCSupportedCommandType getCommandType() {
52 | return ImapRFCSupportedCommandType.STORE_FLAGS;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/core/src/test/java/com/yahoo/imapnio/async/request/NoopCommandTest.java:
--------------------------------------------------------------------------------
1 | package com.yahoo.imapnio.async.request;
2 |
3 | import java.lang.reflect.Field;
4 | import java.lang.reflect.Modifier;
5 | import java.util.HashSet;
6 | import java.util.Set;
7 |
8 | import org.testng.Assert;
9 | import org.testng.annotations.BeforeClass;
10 | import org.testng.annotations.Test;
11 |
12 | import com.yahoo.imapnio.async.exception.ImapAsyncClientException;
13 |
14 | /**
15 | * Unit test for {@link NoopCommand}.
16 | */
17 | public class NoopCommandTest {
18 |
19 | /** Fields to check for cleanup. */
20 | private Set fieldsToCheck;
21 |
22 | /**
23 | * Setup reflection.
24 | */
25 | @BeforeClass
26 | public void setUp() {
27 | // Use reflection to get all declared non-primitive non-static fields (We do not care about inherited fields)
28 | final Class> classUnderTest = NoopCommand.class;
29 | fieldsToCheck = new HashSet<>();
30 | for (Class> c = classUnderTest; c != null; c = c.getSuperclass()) {
31 | for (final Field declaredField : c.getDeclaredFields()) {
32 | if (!declaredField.getType().isPrimitive() && !Modifier.isStatic(declaredField.getModifiers())) {
33 | declaredField.setAccessible(true);
34 | fieldsToCheck.add(declaredField);
35 | }
36 | }
37 | }
38 | }
39 |
40 | /**
41 | * Tests getCommandLine method.
42 | *
43 | * @throws ImapAsyncClientException will not throw
44 | * @throws IllegalAccessException will not throw
45 | * @throws IllegalArgumentException will not throw
46 | */
47 | @Test
48 | public void testGetCommandLine() throws ImapAsyncClientException, IllegalArgumentException, IllegalAccessException {
49 | final ImapRequest cmd = new NoopCommand();
50 | Assert.assertEquals(cmd.getCommandLine(), "NOOP\r\n", "Expected result mismatched.");
51 |
52 | cmd.cleanup();
53 | // Verify if cleanup happened correctly.
54 | for (final Field field : fieldsToCheck) {
55 | Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null");
56 | }
57 | }
58 |
59 | /**
60 | * Tests getCommandType method.
61 | */
62 | @Test
63 | public void testGetCommandType() {
64 | final ImapRequest cmd = new NoopCommand();
65 | Assert.assertSame(cmd.getCommandType(), ImapRFCSupportedCommandType.NOOP);
66 | }
67 | }
--------------------------------------------------------------------------------
/core/src/test/java/com/yahoo/imapnio/async/request/CloseCommandTest.java:
--------------------------------------------------------------------------------
1 | package com.yahoo.imapnio.async.request;
2 |
3 | import java.lang.reflect.Field;
4 | import java.lang.reflect.Modifier;
5 | import java.util.HashSet;
6 | import java.util.Set;
7 |
8 | import org.testng.Assert;
9 | import org.testng.annotations.BeforeClass;
10 | import org.testng.annotations.Test;
11 |
12 | import com.yahoo.imapnio.async.exception.ImapAsyncClientException;
13 |
14 | /**
15 | * Unit test for {@link CloseCommand}.
16 | */
17 | public class CloseCommandTest {
18 |
19 | /** Fields to check for cleanup. */
20 | private Set fieldsToCheck;
21 |
22 | /**
23 | * Setup reflection.
24 | */
25 | @BeforeClass
26 | public void setUp() {
27 | // Use reflection to get all declared non-primitive non-static fields (We do not care about inherited fields)
28 | final Class> classUnderTest = CloseCommand.class;
29 | fieldsToCheck = new HashSet<>();
30 | for (Class> c = classUnderTest; c != null; c = c.getSuperclass()) {
31 | for (final Field declaredField : c.getDeclaredFields()) {
32 | if (!declaredField.getType().isPrimitive() && !Modifier.isStatic(declaredField.getModifiers())) {
33 | declaredField.setAccessible(true);
34 | fieldsToCheck.add(declaredField);
35 | }
36 | }
37 | }
38 | }
39 |
40 | /**
41 | * Tests getCommandLine method.
42 | *
43 | * @throws ImapAsyncClientException will not throw
44 | * @throws IllegalAccessException will not throw
45 | * @throws IllegalArgumentException will not throw
46 | */
47 | @Test
48 | public void testGetCommandLine() throws ImapAsyncClientException, IllegalArgumentException, IllegalAccessException {
49 | final ImapRequest cmd = new CloseCommand();
50 | Assert.assertEquals(cmd.getCommandLine(), "CLOSE\r\n", "Expected result mismatched.");
51 |
52 | cmd.cleanup();
53 | // Verify if cleanup happened correctly.
54 | for (final Field field : fieldsToCheck) {
55 | Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null");
56 | }
57 | }
58 |
59 | /**
60 | * Tests getCommandType method.
61 | */
62 | @Test
63 | public void testGetCommandType() {
64 | final ImapRequest cmd = new CloseCommand();
65 | Assert.assertSame(cmd.getCommandType(), ImapRFCSupportedCommandType.CLOSE);
66 | }
67 | }
--------------------------------------------------------------------------------
/core/src/test/java/com/yahoo/imapnio/async/request/LogoutCommandTest.java:
--------------------------------------------------------------------------------
1 | package com.yahoo.imapnio.async.request;
2 |
3 | import java.lang.reflect.Field;
4 | import java.lang.reflect.Modifier;
5 | import java.util.HashSet;
6 | import java.util.Set;
7 |
8 | import org.testng.Assert;
9 | import org.testng.annotations.BeforeClass;
10 | import org.testng.annotations.Test;
11 |
12 | import com.yahoo.imapnio.async.exception.ImapAsyncClientException;
13 |
14 | /**
15 | * Unit test for {@link LogoutCommand}.
16 | */
17 | public class LogoutCommandTest {
18 |
19 | /** Fields to check for cleanup. */
20 | private Set fieldsToCheck;
21 |
22 | /**
23 | * Setup reflection.
24 | */
25 | @BeforeClass
26 | public void setUp() {
27 | // Use reflection to get all declared non-primitive non-static fields (We do not care about inherited fields)
28 | final Class> classUnderTest = LogoutCommand.class;
29 | fieldsToCheck = new HashSet<>();
30 | for (Class> c = classUnderTest; c != null; c = c.getSuperclass()) {
31 | for (final Field declaredField : c.getDeclaredFields()) {
32 | if (!declaredField.getType().isPrimitive() && !Modifier.isStatic(declaredField.getModifiers())) {
33 | declaredField.setAccessible(true);
34 | fieldsToCheck.add(declaredField);
35 | }
36 | }
37 | }
38 | }
39 |
40 | /**
41 | * Tests getCommandLine method.
42 | *
43 | * @throws ImapAsyncClientException will not throw
44 | * @throws IllegalAccessException will not throw
45 | * @throws IllegalArgumentException will not throw
46 | */
47 | @Test
48 | public void testGetCommandLine() throws ImapAsyncClientException, IllegalArgumentException, IllegalAccessException {
49 | final ImapRequest cmd = new LogoutCommand();
50 | Assert.assertEquals(cmd.getCommandLine(), "LOGOUT\r\n", "Expected result mismatched.");
51 |
52 | cmd.cleanup();
53 | // Verify if cleanup happened correctly.
54 | for (final Field field : fieldsToCheck) {
55 | Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null");
56 | }
57 | }
58 |
59 | /**
60 | * Tests getCommandType method.
61 | */
62 | @Test
63 | public void testGetCommandType() {
64 | final ImapRequest cmd = new LogoutCommand();
65 | Assert.assertSame(cmd.getCommandType(), ImapRFCSupportedCommandType.LOGOUT);
66 | }
67 | }
--------------------------------------------------------------------------------
/core/src/test/java/com/yahoo/imapnio/async/request/ExpungeCommandTest.java:
--------------------------------------------------------------------------------
1 | package com.yahoo.imapnio.async.request;
2 |
3 | import java.lang.reflect.Field;
4 | import java.lang.reflect.Modifier;
5 | import java.util.HashSet;
6 | import java.util.Set;
7 |
8 | import org.testng.Assert;
9 | import org.testng.annotations.BeforeClass;
10 | import org.testng.annotations.Test;
11 |
12 | import com.yahoo.imapnio.async.exception.ImapAsyncClientException;
13 |
14 | /**
15 | * Unit test for {@link ExpungeCommand}.
16 | */
17 | public class ExpungeCommandTest {
18 |
19 | /** Fields to check for cleanup. */
20 | private Set fieldsToCheck;
21 |
22 | /**
23 | * Setup reflection.
24 | */
25 | @BeforeClass
26 | public void setUp() {
27 | // Use reflection to get all declared non-primitive non-static fields (We do not care about inherited fields)
28 | final Class> classUnderTest = ExpungeCommand.class;
29 | fieldsToCheck = new HashSet<>();
30 | for (Class> c = classUnderTest; c != null; c = c.getSuperclass()) {
31 | for (final Field declaredField : c.getDeclaredFields()) {
32 | if (!declaredField.getType().isPrimitive() && !Modifier.isStatic(declaredField.getModifiers())) {
33 | declaredField.setAccessible(true);
34 | fieldsToCheck.add(declaredField);
35 | }
36 | }
37 | }
38 | }
39 |
40 | /**
41 | * Tests getCommandLine method.
42 | *
43 | * @throws ImapAsyncClientException will not throw
44 | * @throws IllegalAccessException will not throw
45 | * @throws IllegalArgumentException will not throw
46 | */
47 | @Test
48 | public void testGetCommandLine() throws ImapAsyncClientException, IllegalArgumentException, IllegalAccessException {
49 | final ImapRequest cmd = new ExpungeCommand();
50 | Assert.assertEquals(cmd.getCommandLine(), "EXPUNGE\r\n", "Expected result mismatched.");
51 |
52 | cmd.cleanup();
53 | // Verify if cleanup happened correctly.
54 | for (final Field field : fieldsToCheck) {
55 | Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null");
56 | }
57 | }
58 |
59 | /**
60 | * Tests getCommandType method.
61 | */
62 | @Test
63 | public void testGetCommandType() {
64 | final ImapRequest cmd = new ExpungeCommand();
65 | Assert.assertSame(cmd.getCommandType(), ImapRFCSupportedCommandType.EXPUNGE);
66 | }
67 | }
--------------------------------------------------------------------------------
/core/src/test/java/com/yahoo/imapnio/async/request/UnselectCommandTest.java:
--------------------------------------------------------------------------------
1 | package com.yahoo.imapnio.async.request;
2 |
3 | import java.lang.reflect.Field;
4 | import java.lang.reflect.Modifier;
5 | import java.util.HashSet;
6 | import java.util.Set;
7 |
8 | import org.testng.Assert;
9 | import org.testng.annotations.BeforeClass;
10 | import org.testng.annotations.Test;
11 |
12 | import com.yahoo.imapnio.async.exception.ImapAsyncClientException;
13 |
14 | /**
15 | * Unit test for {@link UnselectCommand}.
16 | */
17 | public class UnselectCommandTest {
18 |
19 | /** Fields to check for cleanup. */
20 | private Set fieldsToCheck;
21 |
22 | /**
23 | * Setup reflection.
24 | */
25 | @BeforeClass
26 | public void setUp() {
27 | // Use reflection to get all declared non-primitive non-static fields (We do not care about inherited fields)
28 | final Class> classUnderTest = UnselectCommand.class;
29 | fieldsToCheck = new HashSet<>();
30 | for (Class> c = classUnderTest; c != null; c = c.getSuperclass()) {
31 | for (final Field declaredField : c.getDeclaredFields()) {
32 | if (!declaredField.getType().isPrimitive() && !Modifier.isStatic(declaredField.getModifiers())) {
33 | declaredField.setAccessible(true);
34 | fieldsToCheck.add(declaredField);
35 | }
36 | }
37 | }
38 | }
39 |
40 | /**
41 | * Tests getCommandLine method.
42 | *
43 | * @throws ImapAsyncClientException will not throw
44 | * @throws IllegalAccessException will not throw
45 | * @throws IllegalArgumentException will not throw
46 | */
47 | @Test
48 | public void testGetCommandLine() throws IllegalArgumentException, IllegalAccessException, ImapAsyncClientException {
49 | final ImapRequest cmd = new UnselectCommand();
50 | Assert.assertEquals(cmd.getCommandLine(), "UNSELECT\r\n", "Expected result mismatched.");
51 |
52 | cmd.cleanup();
53 | // Verify if cleanup happened correctly.
54 | for (final Field field : fieldsToCheck) {
55 | Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null");
56 | }
57 | }
58 |
59 | /**
60 | * Tests getCommandType method.
61 | */
62 | @Test
63 | public void testGetCommandType() {
64 | final ImapRequest cmd = new UnselectCommand();
65 | Assert.assertSame(cmd.getCommandType(), ImapRFCSupportedCommandType.UNSELECT);
66 | }
67 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/yahoo/imapnio/async/client/ImapAsyncSession.java:
--------------------------------------------------------------------------------
1 | package com.yahoo.imapnio.async.client;
2 |
3 | import javax.annotation.Nonnull;
4 |
5 | import com.yahoo.imapnio.async.exception.ImapAsyncClientException;
6 | import com.yahoo.imapnio.async.request.ImapRequest;
7 | import com.yahoo.imapnio.async.response.ImapAsyncResponse;
8 |
9 | /**
10 | * A class that defines the behavior of Asynchronous IMAP session.
11 | */
12 | public interface ImapAsyncSession {
13 | /**
14 | * Flag to turn on or off debugging for this session.
15 | */
16 | enum DebugMode {
17 | /** Debugging is off for this session. */
18 | DEBUG_OFF,
19 | /** Debugging is on for this session. */
20 | DEBUG_ON
21 | }
22 |
23 | /**
24 | * Starts the compression, assuming caller verified the support of compression capability in server.
25 | *
26 | * @param the data type for returning in getNextCommandLineAfterContinuation call
27 | *
28 | * @return the future object for this command
29 | * @throws ImapAsyncClientException on failure
30 | */
31 | ImapFuture startCompression() throws ImapAsyncClientException;
32 |
33 | /**
34 | * Turns on or off the debugging.
35 | *
36 | * @param debugMode the debugging mode
37 | */
38 | void setDebugMode(DebugMode debugMode);
39 |
40 | /**
41 | * Sends a IMAP command to the server.
42 | *
43 | * @param the data type for returning in getNextCommandLineAfterContinuation call
44 | *
45 | * @param command the command request.
46 | * @return the future object for this command
47 | * @throws ImapAsyncClientException on failure
48 | */
49 | ImapFuture execute(ImapRequest command) throws ImapAsyncClientException;
50 |
51 | /**
52 | * Terminates the current running command.
53 | *
54 | * @param command the command request.
55 | * @return the future object for this command
56 | * @throws ImapAsyncClientException on failure
57 | */
58 | ImapFuture terminateCommand(@Nonnull ImapRequest command) throws ImapAsyncClientException;
59 |
60 | /**
61 | * Closes/disconnects this session.
62 | *
63 | * @return a future when it is completed. True means successful, otherwise failure.
64 | */
65 | ImapFuture close();
66 |
67 | /**
68 | * @return true if channel is closed; false otherwise
69 | */
70 | boolean isChannelClosed();
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/core/src/test/java/com/yahoo/imapnio/async/request/CompressCommandTest.java:
--------------------------------------------------------------------------------
1 | package com.yahoo.imapnio.async.request;
2 |
3 | import java.lang.reflect.Field;
4 | import java.lang.reflect.Modifier;
5 | import java.util.HashSet;
6 | import java.util.Set;
7 |
8 | import org.testng.Assert;
9 | import org.testng.annotations.BeforeClass;
10 | import org.testng.annotations.Test;
11 |
12 | import com.yahoo.imapnio.async.exception.ImapAsyncClientException;
13 |
14 | /**
15 | * Unit test for {@link CompressCommand}.
16 | */
17 | public class CompressCommandTest {
18 |
19 | /** Fields to check for cleanup. */
20 | private Set fieldsToCheck;
21 |
22 | /**
23 | * Setup reflection.
24 | */
25 | @BeforeClass
26 | public void setUp() {
27 | // Use reflection to get all declared non-primitive non-static fields (We do not care about inherited fields)
28 | final Class> classUnderTest = CompressCommand.class;
29 | fieldsToCheck = new HashSet<>();
30 | for (Class> c = classUnderTest; c != null; c = c.getSuperclass()) {
31 | for (final Field declaredField : c.getDeclaredFields()) {
32 | if (!declaredField.getType().isPrimitive() && !Modifier.isStatic(declaredField.getModifiers())) {
33 | declaredField.setAccessible(true);
34 | fieldsToCheck.add(declaredField);
35 | }
36 | }
37 | }
38 | }
39 |
40 | /**
41 | * Tests getCommandLine method.
42 | *
43 | * @throws ImapAsyncClientException will not throw
44 | * @throws IllegalAccessException will not throw
45 | * @throws IllegalArgumentException will not throw
46 | */
47 | @Test
48 | public void testGetCommandLine() throws ImapAsyncClientException, IllegalArgumentException, IllegalAccessException {
49 | final ImapRequest cmd = new CompressCommand();
50 | Assert.assertEquals(cmd.getCommandLine(), "COMPRESS DEFLATE\r\n", "Expected result mismatched.");
51 |
52 | cmd.cleanup();
53 | // Verify if cleanup happened correctly.
54 | for (final Field field : fieldsToCheck) {
55 | Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null");
56 | }
57 | }
58 |
59 | /**
60 | * Tests getCommandType method.
61 | */
62 | @Test
63 | public void testGetCommandType() {
64 | final ImapRequest cmd = new CompressCommand();
65 | Assert.assertSame(cmd.getCommandType(), ImapRFCSupportedCommandType.COMPRESS);
66 | }
67 | }
--------------------------------------------------------------------------------
/core/src/test/java/com/yahoo/imapnio/async/request/NamespaceCommandTest.java:
--------------------------------------------------------------------------------
1 | package com.yahoo.imapnio.async.request;
2 |
3 | import java.lang.reflect.Field;
4 | import java.lang.reflect.Modifier;
5 | import java.util.HashSet;
6 | import java.util.Set;
7 |
8 | import org.testng.Assert;
9 | import org.testng.annotations.BeforeClass;
10 | import org.testng.annotations.Test;
11 |
12 | import com.yahoo.imapnio.async.exception.ImapAsyncClientException;
13 |
14 | /**
15 | * Unit test for {@link NamespaceCommand}.
16 | */
17 | public class NamespaceCommandTest {
18 |
19 | /** Fields to check for cleanup. */
20 | private Set fieldsToCheck;
21 |
22 | /**
23 | * Setup reflection.
24 | */
25 | @BeforeClass
26 | public void setUp() {
27 | // Use reflection to get all declared non-primitive non-static fields (We do not care about inherited fields)
28 | final Class> classUnderTest = NamespaceCommand.class;
29 | fieldsToCheck = new HashSet<>();
30 | for (Class> c = classUnderTest; c != null; c = c.getSuperclass()) {
31 | for (final Field declaredField : c.getDeclaredFields()) {
32 | if (!declaredField.getType().isPrimitive() && !Modifier.isStatic(declaredField.getModifiers())) {
33 | declaredField.setAccessible(true);
34 | fieldsToCheck.add(declaredField);
35 | }
36 | }
37 | }
38 | }
39 |
40 | /**
41 | * Tests getCommandLine method.
42 | *
43 | * @throws ImapAsyncClientException will not throw
44 | * @throws IllegalAccessException will not throw
45 | * @throws IllegalArgumentException will not throw
46 | */
47 | @Test
48 | public void testGetCommandLine() throws ImapAsyncClientException, IllegalArgumentException, IllegalAccessException {
49 | final ImapRequest cmd = new NamespaceCommand();
50 | Assert.assertEquals(cmd.getCommandLine(), "NAMESPACE\r\n", "Expected result mismatched.");
51 |
52 | cmd.cleanup();
53 | // Verify if cleanup happened correctly.
54 | for (final Field field : fieldsToCheck) {
55 | Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null");
56 | }
57 | }
58 |
59 | /**
60 | * Tests getCommandType method.
61 | */
62 | @Test
63 | public void testGetCommandType() {
64 | final ImapRequest cmd = new NamespaceCommand();
65 | Assert.assertSame(cmd.getCommandType(), ImapRFCSupportedCommandType.NAMESPACE);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/core/src/test/java/com/yahoo/imapnio/async/response/ImapAsyncResponseTest.java:
--------------------------------------------------------------------------------
1 | package com.yahoo.imapnio.async.response;
2 |
3 | import java.io.IOException;
4 | import java.nio.charset.StandardCharsets;
5 | import java.util.ArrayList;
6 | import java.util.Collection;
7 |
8 | import org.testng.Assert;
9 | import org.testng.annotations.Test;
10 |
11 | import com.sun.mail.iap.ProtocolException;
12 | import com.sun.mail.imap.protocol.IMAPResponse;
13 | import com.yahoo.imapnio.async.exception.ImapAsyncClientException;
14 | import com.yahoo.imapnio.async.request.CapaCommand;
15 | import com.yahoo.imapnio.async.request.ImapRequest;
16 |
17 | /**
18 | * Unit test for {@link ImapAsyncResponse}.
19 | */
20 | public class ImapAsyncResponseTest {
21 |
22 | /**
23 | * Tests ImapAsyncResponse constructor and getters.
24 | *
25 | * @throws IOException will not throw
26 | * @throws ProtocolException will not throw
27 | * @throws ImapAsyncClientException will not throw
28 | */
29 | @Test
30 | public void testImapAsyncResponse() throws IOException, ProtocolException, ImapAsyncClientException {
31 | final ImapRequest imapRequest = new CapaCommand();
32 | final int requestTotalBytes = "a0".getBytes(StandardCharsets.US_ASCII).length + 1 + imapRequest.getCommandLineBytes().readableBytes();
33 | final Collection imapResponses = new ArrayList();
34 | final IMAPResponse oneImapResponse = new IMAPResponse("a1 OK CAPABILITY completed");
35 | imapResponses.add(oneImapResponse);
36 | final int responseTotalBytes = oneImapResponse.toString().getBytes(StandardCharsets.US_ASCII).length + 2;
37 | final long elapsedTime = 1234L;
38 | final ImapAsyncResponse resp = new ImapAsyncResponse(imapRequest.getCommandType(), requestTotalBytes, responseTotalBytes, imapResponses,
39 | elapsedTime);
40 |
41 | Assert.assertEquals(resp.getCommandType(), imapRequest.getCommandType(), "command type mismatched.");
42 | Assert.assertEquals(resp.getRequestTotalBytes(), requestTotalBytes, "request bytes length mismatched.");
43 | Assert.assertEquals(resp.getResponseTotalBytes(), responseTotalBytes, "response bytes length mismatched.");
44 | final Collection actual = resp.getResponseLines();
45 | Assert.assertEquals(actual, imapResponses, "result mismatched.");
46 | Assert.assertEquals(actual.iterator().next(), oneImapResponse, "result mismatched.");
47 | Assert.assertEquals(resp.getTotalTimeElapsed(), elapsedTime);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/core/src/main/java/com/yahoo/imapnio/async/request/UidStoreFlagsCommand.java:
--------------------------------------------------------------------------------
1 | package com.yahoo.imapnio.async.request;
2 |
3 | import javax.annotation.Nonnull;
4 | import javax.mail.Flags;
5 |
6 | import com.yahoo.imapnio.async.data.MessageNumberSet;
7 |
8 | /**
9 | * This class defines IMAP UID store command request from client.
10 | */
11 | public class UidStoreFlagsCommand extends AbstractStoreFlagsCommand {
12 |
13 | /**
14 | * Initializes a {@link UidStoreFlagsCommand} with the MessageNumberSet array, Flags and action. Requests server to return the new value.
15 | *
16 | * @param msgsets the set of message set
17 | * @param flags the flags to be stored
18 | * @param action whether to replace, add or remove the flags
19 | */
20 | public UidStoreFlagsCommand(@Nonnull final MessageNumberSet[] msgsets, @Nonnull final Flags flags, @Nonnull final FlagsAction action) {
21 | super(true, msgsets, flags, action, false);
22 | }
23 |
24 | /**
25 | * Initializes a {@link UidStoreFlagsCommand} with the MessageNumberSet array, Flags, action, flag whether to request server to return the new
26 | * value.
27 | *
28 | * @param msgsets the set of message set
29 | * @param flags the flags to be stored
30 | * @param action whether to replace, add or remove the flags
31 | * @param silent true if asking server to respond silently; false if requesting server to return the new values
32 | */
33 | public UidStoreFlagsCommand(@Nonnull final MessageNumberSet[] msgsets, @Nonnull final Flags flags, @Nonnull final FlagsAction action,
34 | final boolean silent) {
35 | super(true, msgsets, flags, action, silent);
36 | }
37 |
38 | /**
39 | * Initializes a {@link UidStoreFlagsCommand} with string form message numbers, Flags, action, flag whether to request server to return the new
40 | * value.
41 | *
42 | * @param uids the string representing UID based on RFC3501
43 | * @param flags the flags to be stored
44 | * @param action whether to replace, add or remove the flags
45 | * @param silent true if asking server to respond silently; false if requesting server to return the new values
46 | */
47 | public UidStoreFlagsCommand(@Nonnull final String uids, @Nonnull final Flags flags, @Nonnull final FlagsAction action, final boolean silent) {
48 | super(true, uids, flags, action, silent);
49 | }
50 |
51 | @Override
52 | public ImapRFCSupportedCommandType getCommandType() {
53 | return ImapRFCSupportedCommandType.UID_STORE_FLAGS;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/core/src/main/java/com/yahoo/imapnio/async/response/ImapAsyncResponse.java:
--------------------------------------------------------------------------------
1 | package com.yahoo.imapnio.async.response;
2 |
3 | import java.util.Collection;
4 |
5 | import com.sun.mail.imap.protocol.IMAPResponse;
6 | import com.yahoo.imapnio.async.request.ImapCommandType;
7 |
8 | /**
9 | * This class defines the Response for IMAP asynchronous requests.
10 | */
11 | public class ImapAsyncResponse {
12 |
13 | /** Command type. */
14 | private ImapCommandType commandType;
15 | /** Request total number of bytes. */
16 | private int requestTotalBytes;
17 | /** Response total number of bytes. */
18 | private int responseTotalBytes;
19 | /** List of IMAPResponse lines. */
20 | private Collection responses;
21 | /** Total time elapsed by command in millis. */
22 | private long totalTimeElapsedInMillis;
23 |
24 | /**
25 | * Initializes an {@link ImapAsyncResponse} object.
26 | *
27 | * @param commandType imap command type
28 | * @param requestTotalBytes number of bytes in request
29 | * @param responseTotalBytes number of bytes in response
30 | * @param responses list of response lines
31 | * @param totalTimeElapsedInMillis total time elapsed in millis
32 | */
33 | public ImapAsyncResponse(final ImapCommandType commandType, final int requestTotalBytes, final int responseTotalBytes,
34 | final Collection responses, final long totalTimeElapsedInMillis) {
35 | this.responses = responses;
36 | this.commandType = commandType;
37 | this.requestTotalBytes = requestTotalBytes;
38 | this.responseTotalBytes = responseTotalBytes;
39 | this.totalTimeElapsedInMillis = totalTimeElapsedInMillis;
40 | }
41 |
42 | /**
43 | * @return command type
44 | */
45 | public ImapCommandType getCommandType() {
46 | return commandType;
47 | }
48 |
49 | /**
50 | * @return number of bytes in request
51 | */
52 | public int getRequestTotalBytes() {
53 | return requestTotalBytes;
54 | }
55 |
56 | /**
57 | * @return number of bytes in response
58 | */
59 | public int getResponseTotalBytes() {
60 | return responseTotalBytes;
61 | }
62 |
63 | /**
64 | * @return list of IMAPResponse lines.
65 | */
66 | public Collection getResponseLines() {
67 | return responses;
68 | }
69 |
70 | /**
71 | * @return total time elapsed in milli seconds.
72 | */
73 | public long getTotalTimeElapsed() {
74 | return totalTimeElapsedInMillis;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/core/src/test/java/com/yahoo/imapnio/command/ArgumentTest.java:
--------------------------------------------------------------------------------
1 | package com.yahoo.imapnio.command;
2 |
3 | import java.io.IOException;
4 |
5 | import javax.mail.Flags;
6 | import javax.mail.internet.MimeUtility;
7 | import javax.mail.search.FlagTerm;
8 | import javax.mail.search.SearchException;
9 | import javax.mail.search.SubjectTerm;
10 |
11 | import org.testng.Assert;
12 | import org.testng.annotations.Test;
13 |
14 | import com.sun.mail.imap.protocol.SearchSequence;
15 |
16 | /**
17 | * Unit test for {@link Argument}.
18 | */
19 | public class ArgumentTest {
20 |
21 | /**
22 | * Tests constructor and toString() method with null character set.
23 | *
24 | * @throws IOException will not throw
25 | * @throws SearchException will not throw
26 | */
27 | @Test
28 | public void testConstructorAndToStringNullCharset() throws SearchException, IOException {
29 |
30 | final SearchSequence searchSeq = new SearchSequence();
31 | // message id
32 | final String msgIds = "1:5";
33 |
34 | // charset
35 | final String charset = null;
36 |
37 | // SearchTerm
38 | final Flags flags = new Flags();
39 | flags.add(Flags.Flag.SEEN);
40 | flags.add(Flags.Flag.DELETED);
41 | final FlagTerm term = new FlagTerm(flags, true);
42 | final Argument args = new Argument();
43 | args.append(searchSeq.generateSequence(term, null));
44 |
45 | args.writeAtom(msgIds);
46 |
47 | final String searchStr = args.toString();
48 | Assert.assertEquals(searchStr, "DELETED SEEN 1:5", "result mismatched.");
49 | }
50 |
51 | /**
52 | * Tests constructor and toString() method with null character set.
53 | *
54 | * @throws IOException will not throw
55 | * @throws SearchException will not throw
56 | */
57 | @Test
58 | public void testConstructorAndToStringNoneNullCharset() throws SearchException, IOException {
59 |
60 | final SearchSequence searchSeq = new SearchSequence();
61 | // message id
62 | final String msgIds = "1:5";
63 |
64 | // charset
65 | final String charset = "UTF-8";
66 |
67 | // SearchTerm
68 | final SubjectTerm term = new SubjectTerm("ΩΩ");
69 | final Argument args = new Argument();
70 | args.append(searchSeq.generateSequence(term, MimeUtility.javaCharset(charset)));
71 |
72 | args.writeAtom(msgIds);
73 |
74 | final String searchStr = args.toString();
75 | Assert.assertEquals(searchStr, "SUBJECT {4+}\r\nᅫ뢔ᄅ 1:5", "result mismatched.");
76 | }
77 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/yahoo/imapnio/async/data/ExtensionMailboxInfo.java:
--------------------------------------------------------------------------------
1 | package com.yahoo.imapnio.async.data;
2 |
3 | import javax.annotation.Nonnull;
4 | import javax.annotation.Nullable;
5 |
6 | import com.sun.mail.iap.ParsingException;
7 | import com.sun.mail.imap.protocol.IMAPResponse;
8 | import com.sun.mail.imap.protocol.MailboxInfo;
9 |
10 | /**
11 | * This class provides the mailbox information and extension items.
12 | */
13 | public class ExtensionMailboxInfo extends MailboxInfo {
14 |
15 | /** Literal for MAILBOXID. */
16 | private static final String MAILBOX_ID = "MAILBOXID";
17 |
18 | /** Variable to store mailbox Id. */
19 | private String mailboxId;
20 |
21 | /**
22 | * Initializes an instance of {@link ExtensionMailboxInfo} from the server responses for the select or examine command.
23 | *
24 | * @param resps response array from server
25 | * @throws ParsingException for errors parsing the responses
26 | */
27 | public ExtensionMailboxInfo(@Nonnull final IMAPResponse[] resps) throws ParsingException {
28 | super(resps);
29 | for (int i = 0; i < resps.length; i++) {
30 | if (resps[i] == null) { // since MailboxInfo nulls it out when finishing parsing an identified response
31 | continue;
32 | }
33 | final IMAPResponse ir = resps[i];
34 |
35 | ir.skipSpaces();
36 | if (ir.readByte() != '[') {
37 | ir.reset();
38 | continue;
39 | }
40 |
41 | String key = ir.readAtom();
42 | if (key == null) { // no key present
43 | ir.reset();
44 | continue;
45 | }
46 | key = key.toUpperCase();
47 | if (key.equals(MAILBOX_ID)) { // example when 26 is the mailbox id:"* OK [MAILBOXID (26)] Ok"
48 | final String[] values = ir.readSimpleList(); // reading the string, aka as above example, "(26)", within parentheses
49 | if (values != null && values.length >= 1) {
50 | mailboxId = values[0];
51 | resps[i] = null; // Nulls out this element in array to be consistent with MailboxInfo behavior
52 | break;
53 | }
54 | }
55 | ir.reset(); // default back the parsing index
56 | }
57 | }
58 |
59 | /**
60 | * @return MAILBOXID, a server-allocated unique identifier for each mailbox. Please refer to OBJECTID, RFC 8474, for more detail.
61 | */
62 | @Nullable
63 | public String getMailboxId() {
64 | return mailboxId;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/core/src/main/java/com/yahoo/imapnio/async/request/LoginCommand.java:
--------------------------------------------------------------------------------
1 | package com.yahoo.imapnio.async.request;
2 |
3 | import java.nio.charset.StandardCharsets;
4 |
5 | import javax.annotation.Nonnull;
6 |
7 | import com.yahoo.imapnio.async.exception.ImapAsyncClientException;
8 |
9 | import io.netty.buffer.ByteBuf;
10 | import io.netty.buffer.Unpooled;
11 |
12 | /**
13 | * This class defines IMAP login command request from client.
14 | */
15 | public class LoginCommand extends ImapRequestAdapter {
16 |
17 | /** Byte array for CR and LF, keeping the array local so it cannot be modified by others. */
18 | private static final byte[] CRLF_B = { '\r', '\n' };
19 |
20 | /** Literal for Login and space. */
21 | private static final String LOGIN_SP = "LOGIN ";
22 |
23 | /** Byte array for LOGIN. */
24 | private static final byte[] LOGIN_SP_B = LOGIN_SP.getBytes(StandardCharsets.US_ASCII);
25 |
26 | /** Literal for logging data. */
27 | private static final String LOG_PREFIX = "LOGIN FOR USER:";
28 |
29 | /** User name. */
30 | private String username;
31 |
32 | /** User pass word. */
33 | private String dwp;
34 |
35 | /**
36 | * Initializes an {@link LoginCommand}. User name and pass given have to be ASCII.
37 | *
38 | * @param username the user name
39 | * @param dwp the secret
40 | */
41 | public LoginCommand(@Nonnull final String username, @Nonnull final String dwp) {
42 | this.username = username;
43 | this.dwp = dwp;
44 | }
45 |
46 | @Override
47 | public void cleanup() {
48 | this.username = null;
49 | this.dwp = null;
50 | }
51 |
52 | @Override
53 | public ByteBuf getCommandLineBytes() throws ImapAsyncClientException {
54 |
55 | final ByteBuf sb = Unpooled.buffer(username.length() + dwp.length() + ImapClientConstants.PAD_LEN);
56 | sb.writeBytes(LOGIN_SP_B);
57 |
58 | final ImapArgumentFormatter formatter = new ImapArgumentFormatter();
59 | formatter.formatArgument(username, sb, false);
60 | sb.writeByte(ImapClientConstants.SPACE);
61 |
62 | formatter.formatArgument(dwp, sb, false);
63 | sb.writeBytes(CRLF_B);
64 |
65 | return sb;
66 | }
67 |
68 | @Override
69 | public boolean isCommandLineDataSensitive() {
70 | return true;
71 | }
72 |
73 | @Override
74 | public String getDebugData() {
75 | return new StringBuilder(LOG_PREFIX).append(username).toString();
76 | }
77 |
78 | @Override
79 | public ImapRFCSupportedCommandType getCommandType() {
80 | return ImapRFCSupportedCommandType.LOGIN;
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/core/src/main/java/com/yahoo/imapnio/async/request/RenameFolderCommand.java:
--------------------------------------------------------------------------------
1 | package com.yahoo.imapnio.async.request;
2 |
3 | import java.nio.charset.StandardCharsets;
4 |
5 | import javax.annotation.Nonnull;
6 |
7 | import com.sun.mail.imap.protocol.BASE64MailboxEncoder;
8 | import com.yahoo.imapnio.async.exception.ImapAsyncClientException;
9 |
10 | import io.netty.buffer.ByteBuf;
11 | import io.netty.buffer.Unpooled;
12 |
13 | /**
14 | * This class defines IMAP rename command request from client.
15 | */
16 | public class RenameFolderCommand extends ImapRequestAdapter {
17 |
18 | /** Byte array for CR and LF, keeping the array local so it cannot be modified by others. */
19 | private static final byte[] CRLF_B = { '\r', '\n' };
20 |
21 | /** Command name. */
22 | private static final String RENAME_SP = "RENAME ";
23 |
24 | /** Byte array for RENAME. */
25 | private static final byte[] RENAME_SP_B = RENAME_SP.getBytes(StandardCharsets.US_ASCII);
26 |
27 | /** Old folder name. */
28 | private String oldFolder;
29 |
30 | /** folder name. */
31 | private String newFolder;
32 |
33 | /**
34 | * Initializes a {@link RenameFolderCommand}.
35 | *
36 | * @param oldFolder old folder name
37 | * @param newFolder new folder name
38 | */
39 | public RenameFolderCommand(@Nonnull final String oldFolder, @Nonnull final String newFolder) {
40 | this.oldFolder = oldFolder;
41 | this.newFolder = newFolder;
42 | }
43 |
44 | @Override
45 | public void cleanup() {
46 | this.oldFolder = null;
47 | this.newFolder = null;
48 | }
49 |
50 | @Override
51 | public ByteBuf getCommandLineBytes() throws ImapAsyncClientException {
52 | final int len = oldFolder.length() * 2 + newFolder.length() * 2 + ImapClientConstants.PAD_LEN;
53 | final ByteBuf sb = Unpooled.buffer(len);
54 | sb.writeBytes(RENAME_SP_B);
55 |
56 | final ImapArgumentFormatter formatter = new ImapArgumentFormatter();
57 | final String o = BASE64MailboxEncoder.encode(oldFolder);
58 | formatter.formatArgument(o, sb, false); // already base64 encoded so can be formatted and write to sb
59 | sb.writeByte(ImapClientConstants.SPACE);
60 | final String n = BASE64MailboxEncoder.encode(newFolder);
61 | formatter.formatArgument(n, sb, false); // already base64 encoded so can be formatted and write to sb
62 | sb.writeBytes(CRLF_B);
63 |
64 | return sb;
65 | }
66 |
67 | @Override
68 | public ImapRFCSupportedCommandType getCommandType() {
69 | return ImapRFCSupportedCommandType.RENAME_FOLDER;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/core/src/main/java/com/yahoo/imapnio/async/request/ImapRequest.java:
--------------------------------------------------------------------------------
1 | package com.yahoo.imapnio.async.request;
2 |
3 | import java.util.concurrent.ConcurrentLinkedQueue;
4 |
5 | import javax.annotation.Nonnull;
6 | import javax.annotation.Nullable;
7 |
8 | import com.sun.mail.imap.protocol.IMAPResponse;
9 | import com.yahoo.imapnio.async.exception.ImapAsyncClientException;
10 |
11 | import io.netty.buffer.ByteBuf;
12 |
13 | /**
14 | * This class defines an Imap command sent from client.
15 | */
16 | public interface ImapRequest {
17 | /**
18 | * @return true if the command line data is sensitive; false otherwise
19 | */
20 | boolean isCommandLineDataSensitive();
21 |
22 | /**
23 | * Builds the command line in bytes - the line to be sent over wire.
24 | *
25 | * @return command line in binary form
26 | * @throws ImapAsyncClientException when encountering an error in building terminate command line
27 | */
28 | @Nonnull
29 | ByteBuf getCommandLineBytes() throws ImapAsyncClientException;
30 |
31 | /**
32 | * Builds the command line for this command - the line to be sent over wire.
33 | *
34 | * @return command line
35 | * @throws ImapAsyncClientException when encountering an error in building terminate command line
36 | */
37 | @Nonnull
38 | String getCommandLine() throws ImapAsyncClientException;
39 |
40 | /**
41 | * @return IMAP command type
42 | */
43 | @Nullable
44 | ImapCommandType getCommandType();
45 |
46 | /**
47 | * @return log data appropriate for the command
48 | */
49 | @Nullable
50 | String getDebugData();
51 |
52 | /**
53 | * @return the queue for holding the server streaming responses
54 | */
55 | @Nullable
56 | ConcurrentLinkedQueue getStreamingResponsesQueue();
57 |
58 | /**
59 | * Builds the next command line after server challenge.
60 | *
61 | * @param serverResponse the server response
62 | * @throws ImapAsyncClientException when building command line encounters an error
63 | * @return command line
64 | */
65 | @Nullable
66 | ByteBuf getNextCommandLineAfterContinuation(@Nonnull IMAPResponse serverResponse) throws ImapAsyncClientException;
67 |
68 | /**
69 | * Builds the next command line after server challenge.
70 | *
71 | * @throws ImapAsyncClientException when encountering an error in building terminate command line
72 | * @return command line
73 | */
74 | @Nullable
75 | ByteBuf getTerminateCommandLine() throws ImapAsyncClientException;
76 |
77 | /**
78 | * Avoids loitering.
79 | */
80 | @Nullable
81 | void cleanup();
82 | }
83 |
--------------------------------------------------------------------------------
/core/src/main/java/com/yahoo/imapnio/async/request/AbstractQueryFoldersCommand.java:
--------------------------------------------------------------------------------
1 | package com.yahoo.imapnio.async.request;
2 |
3 | import java.nio.charset.StandardCharsets;
4 |
5 | import javax.annotation.Nonnull;
6 |
7 | import com.sun.mail.imap.protocol.BASE64MailboxEncoder;
8 | import com.yahoo.imapnio.async.exception.ImapAsyncClientException;
9 |
10 | import io.netty.buffer.ByteBuf;
11 | import io.netty.buffer.Unpooled;
12 |
13 | /**
14 | * This class defines imap select command request from client.
15 | */
16 | abstract class AbstractQueryFoldersCommand extends ImapRequestAdapter {
17 |
18 | /** Byte array for CR and LF, keeping the array local so it cannot be modified by others. */
19 | private static final byte[] CRLF_B = { '\r', '\n' };
20 |
21 | /** The Command. */
22 | private String op;
23 |
24 | /** reference name. */
25 | private String ref;
26 |
27 | /** search pattern. */
28 | private String pattern;
29 |
30 | /**
31 | * Initializes with command name, reference name, and pattern.
32 | *
33 | * @param op command/operator name, for ex, "LIST"
34 | * @param ref the reference string
35 | * @param pattern folder name with possible wildcards, see RFC3501 list command for detail.
36 | */
37 | AbstractQueryFoldersCommand(@Nonnull final String op, @Nonnull final String ref, @Nonnull final String pattern) {
38 | this.op = op;
39 | this.ref = ref;
40 | this.pattern = pattern;
41 | }
42 |
43 | @Override
44 | public void cleanup() {
45 | this.op = null;
46 | this.ref = null;
47 | this.pattern = null;
48 | }
49 |
50 | @Override
51 | public ByteBuf getCommandLineBytes() throws ImapAsyncClientException {
52 | // Ex:LIST /usr/staff/jones ""
53 |
54 | // encode the arguments as per RFC2060
55 | final String ref64 = BASE64MailboxEncoder.encode(ref);
56 | final String pat64 = BASE64MailboxEncoder.encode(pattern);
57 |
58 | final int len = 2 * ref64.length() + 2 * pat64.length() + ImapClientConstants.PAD_LEN;
59 | final ByteBuf sb = Unpooled.buffer(len);
60 | sb.writeBytes(op.getBytes(StandardCharsets.US_ASCII));
61 | sb.writeByte(ImapClientConstants.SPACE);
62 |
63 | final ImapArgumentFormatter formatter = new ImapArgumentFormatter();
64 | formatter.formatArgument(ref64, sb, false); // already base64 encoded so can be formatted and write to sb
65 | sb.writeByte(ImapClientConstants.SPACE);
66 |
67 | formatter.formatArgument(pat64, sb, false);
68 | sb.writeBytes(CRLF_B); // already base64 encoded so can be formatted and write to sb
69 |
70 | return sb;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/core/src/test/java/com/yahoo/imapnio/async/request/CheckCommandTest.java:
--------------------------------------------------------------------------------
1 | package com.yahoo.imapnio.async.request;
2 |
3 | import java.lang.reflect.Field;
4 | import java.lang.reflect.Modifier;
5 | import java.util.HashSet;
6 | import java.util.Set;
7 |
8 | import org.testng.Assert;
9 | import org.testng.annotations.BeforeClass;
10 | import org.testng.annotations.Test;
11 |
12 | import com.yahoo.imapnio.async.exception.ImapAsyncClientException;
13 |
14 | /**
15 | * Unit test for {@link CheckCommand}.
16 | */
17 | public class CheckCommandTest {
18 |
19 | /** Fields to check for cleanup. */
20 | private Set fieldsToCheck;
21 |
22 | /**
23 | * Setup reflection.
24 | */
25 | @BeforeClass
26 | public void setUp() {
27 | // Use reflection to get all declared non-primitive non-static fields (We do not care about inherited fields)
28 | final Class> classUnderTest = CheckCommand.class;
29 | fieldsToCheck = new HashSet<>();
30 | for (Class> c = classUnderTest; c != null; c = c.getSuperclass()) {
31 | for (final Field declaredField : c.getDeclaredFields()) {
32 | if (!declaredField.getType().isPrimitive() && !Modifier.isStatic(declaredField.getModifiers())) {
33 | declaredField.setAccessible(true);
34 | fieldsToCheck.add(declaredField);
35 | }
36 | }
37 | }
38 | }
39 |
40 | /**
41 | * Tests getCommandLine method.
42 | *
43 | * @throws ImapAsyncClientException will not throw
44 | * @throws IllegalAccessException will not throw
45 | * @throws IllegalArgumentException will not throw
46 | */
47 | @Test
48 | public void testGetCommandLine() throws ImapAsyncClientException, IllegalArgumentException, IllegalAccessException {
49 | final ImapRequest cmd = new CheckCommand();
50 | Assert.assertEquals(cmd.getCommandLine(), "CHECK\r\n", "Expected result mismatched.");
51 | Assert.assertFalse(cmd.isCommandLineDataSensitive(), "isCommandLineDataSensitive() result mismatched.");
52 | Assert.assertNull(cmd.getDebugData(), "getLogData() mismatched.");
53 |
54 | cmd.cleanup();
55 | // Verify if cleanup happened correctly.
56 | for (final Field field : fieldsToCheck) {
57 | Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null");
58 | }
59 | }
60 |
61 | /**
62 | * Tests getCommandType method.
63 | */
64 | @Test
65 | public void testGetCommandType() {
66 | final ImapRequest cmd = new CheckCommand();
67 | Assert.assertSame(cmd.getCommandType(), ImapRFCSupportedCommandType.CHECK);
68 | }
69 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/yahoo/imapnio/async/request/UidExpungeCommand.java:
--------------------------------------------------------------------------------
1 | package com.yahoo.imapnio.async.request;
2 |
3 | import java.nio.charset.StandardCharsets;
4 |
5 | import javax.annotation.Nonnull;
6 |
7 | import com.sun.mail.imap.protocol.UIDSet;
8 | import com.yahoo.imapnio.async.data.MessageNumberSet;
9 |
10 | import io.netty.buffer.ByteBuf;
11 | import io.netty.buffer.Unpooled;
12 |
13 | /**
14 | * This class defines IMAP UID EXPUNGE command from client.
15 | */
16 | public class UidExpungeCommand extends ImapRequestAdapter {
17 |
18 | /** Byte array for CR and LF, keeping the array local so it cannot be modified by others. */
19 | private static final byte[] CRLF_B = { '\r', '\n' };
20 |
21 | /** Command name. */
22 | private static final String UID_EXPUNGE = "UID EXPUNGE";
23 |
24 | /** Byte array for UID EXPUNGE. */
25 | private static final byte[] UID_EXPUNGE_B = UID_EXPUNGE.getBytes(StandardCharsets.US_ASCII);
26 |
27 | /** Message Id, aka UID. */
28 | private String uids;
29 |
30 | /**
31 | * Initializes a {@link UidExpungeCommand} with the message sequence syntax.
32 | *
33 | * @param uidsets the set of UIDSet representing UID based on RFC3501
34 | */
35 | public UidExpungeCommand(@Nonnull final UIDSet[] uidsets) {
36 | this(UIDSet.toString(uidsets));
37 | }
38 |
39 | /**
40 | * Initializes a {@link UidExpungeCommand} with the message sequence syntax. MessageNumberSet allows last message.
41 | *
42 | * @param uidsets the set of MessageNumberSet representing UID based on RFC3501
43 | */
44 | public UidExpungeCommand(@Nonnull final MessageNumberSet[] uidsets) {
45 | this(MessageNumberSet.buildString(uidsets));
46 | }
47 |
48 | /**
49 | * Initializes a {@link UidExpungeCommand} with the message sequence syntax.
50 | *
51 | * @param uids the string representing UID string based on RFC3501
52 | */
53 | public UidExpungeCommand(@Nonnull final String uids) {
54 | this.uids = uids;
55 | }
56 |
57 | @Override
58 | public void cleanup() {
59 | this.uids = null;
60 | }
61 |
62 | @Override
63 | public ByteBuf getCommandLineBytes() {
64 | final ByteBuf buf = Unpooled.buffer(UID_EXPUNGE.length() + uids.length() + ImapClientConstants.PAD_LEN);
65 | buf.writeBytes(UID_EXPUNGE_B);
66 | buf.writeByte(ImapClientConstants.SPACE);
67 | buf.writeBytes(uids.getBytes(StandardCharsets.US_ASCII));
68 | buf.writeBytes(CRLF_B);
69 | return buf;
70 | }
71 |
72 | @Override
73 | public ImapRFCSupportedCommandType getCommandType() {
74 | return ImapRFCSupportedCommandType.UID_EXPUNGE;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/core/src/main/java/com/yahoo/imapnio/async/request/IdCommand.java:
--------------------------------------------------------------------------------
1 | package com.yahoo.imapnio.async.request;
2 |
3 | import java.nio.charset.StandardCharsets;
4 | import java.util.Map;
5 |
6 | import com.yahoo.imapnio.async.exception.ImapAsyncClientException;
7 |
8 | import io.netty.buffer.ByteBuf;
9 | import io.netty.buffer.Unpooled;
10 |
11 | /**
12 | * This class defines imap id command request from client.
13 | */
14 | public class IdCommand extends ImapRequestAdapter {
15 |
16 | /** Byte array for CR and LF, keeping the array local so it cannot be modified by others. */
17 | private static final byte[] CRLF_B = { '\r', '\n' };
18 |
19 | /** NIL literal byte array. */
20 | private static final byte[] NIL_B = { 'N', 'I', 'L' };
21 |
22 | /** ID and space. */
23 | private static final String ID_SP = "ID ";
24 |
25 | /** Byte array for ID and space. */
26 | private static final byte[] ID_SP_B = ID_SP.getBytes(StandardCharsets.US_ASCII);
27 |
28 | /** ID command line initial space. */
29 | private static final int IDLINE_LEN = 200;
30 |
31 | /** Key and value pair, key and value should all be ascii. */
32 | private Map params;
33 |
34 | /**
35 | * Initializes a {@link IdCommand}.
36 | *
37 | * @param params a collection of parameters, key and value should all be ascii.
38 | */
39 | public IdCommand(final Map params) {
40 | this.params = params;
41 | }
42 |
43 | @Override
44 | public void cleanup() {
45 | this.params = null;
46 | }
47 |
48 | @Override
49 | public ByteBuf getCommandLineBytes() throws ImapAsyncClientException {
50 | final ByteBuf sb = Unpooled.buffer(IDLINE_LEN);
51 | sb.writeBytes(ID_SP_B);
52 |
53 | if (params == null) {
54 | sb.writeBytes(NIL_B);
55 | } else {
56 | // every token has to be encoded (double quoted and escaped) if needed
57 | // ex: a023 ID ("name" "so/"dr" "version" "19.34")
58 | final ImapArgumentFormatter formatter = new ImapArgumentFormatter();
59 | sb.writeByte(ImapClientConstants.L_PAREN);
60 | boolean isFirstEntry = true;
61 | for (final Map.Entry e : params.entrySet()) {
62 | if (!isFirstEntry) {
63 | sb.writeByte(ImapClientConstants.SPACE);
64 | } else {
65 | isFirstEntry = false;
66 | }
67 | formatter.formatArgument(e.getKey(), sb, true);
68 | sb.writeByte(ImapClientConstants.SPACE);
69 | formatter.formatArgument(e.getValue(), sb, true);
70 | }
71 | sb.writeByte(ImapClientConstants.R_PAREN);
72 | }
73 |
74 | sb.writeBytes(CRLF_B);
75 | return sb;
76 | }
77 |
78 | @Override
79 | public ImapRFCSupportedCommandType getCommandType() {
80 | return ImapRFCSupportedCommandType.ID;
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/core/src/main/java/com/yahoo/imapnio/async/request/AuthXoauth2Command.java:
--------------------------------------------------------------------------------
1 | package com.yahoo.imapnio.async.request;
2 |
3 | import java.nio.charset.StandardCharsets;
4 |
5 | import javax.annotation.Nonnull;
6 |
7 | import org.apache.commons.codec.binary.Base64;
8 |
9 | import com.yahoo.imapnio.async.data.Capability;
10 |
11 | import io.netty.buffer.ByteBuf;
12 |
13 | /**
14 | * This class defines imap authenticate xoauth2 command request from client.
15 | */
16 | public final class AuthXoauth2Command extends AbstractAuthCommand {
17 |
18 | /** Command operator. */
19 | private static final String AUTH_XOAUTH2 = "AUTHENTICATE XOAUTH2";
20 |
21 | /** Byte array for AUTH XOAUTH2. */
22 | private static final byte[] AUTH_XOAUTH2_B = AUTH_XOAUTH2.getBytes(StandardCharsets.US_ASCII);
23 |
24 | /** Literal for logging data. */
25 | private static final String LOG_PREFIX = "AUTHENTICATE XOAUTH2 FOR USER:";
26 |
27 | /** Literal for user=. */
28 | private static final String USER = "user=";
29 |
30 | /** Literal for auth==Bearer. */
31 | private static final String AUTH_BEARER = "auth=Bearer ";
32 |
33 | /** Extra length for string. */
34 | private static final int EXTRA_LEN = 10;
35 |
36 | /** User name. */
37 | private String username;
38 |
39 | /** User token. */
40 | private String token;
41 |
42 | /**
43 | * Initializes an authenticate xoauth2 command.
44 | *
45 | * @param username the user name
46 | * @param token xoauth2 token
47 | * @param capa the capability obtained from server
48 | */
49 | public AuthXoauth2Command(@Nonnull final String username, @Nonnull final String token, @Nonnull final Capability capa) {
50 | super(capa);
51 | this.username = username;
52 | this.token = token;
53 | }
54 |
55 | @Override
56 | public void cleanup() {
57 | this.username = null;
58 | this.token = null;
59 | }
60 |
61 | @Override
62 | void buildCommand(@Nonnull final ByteBuf buf) {
63 | buf.writeBytes(AUTH_XOAUTH2_B);
64 | }
65 |
66 | /**
67 | * Builds the IR, aka client Initial Response (RFC4959). In this command, it is XOauth2 token format and encoded as base64.
68 | *
69 | * @return an encoded base64 XOauth2 format
70 | */
71 | @Override
72 | String buildClientResponse() {
73 | // Xoath2 format: "user=%s\001auth=Bearer %s\001\001";
74 | final int len = USER.length() + username.length() + token.length() + EXTRA_LEN;
75 | final StringBuilder sbOauth2 = new StringBuilder(len).append(USER).append(username).append(ImapClientConstants.SOH).append(AUTH_BEARER)
76 | .append(token).append(ImapClientConstants.SOH).append(ImapClientConstants.SOH);
77 | return Base64.encodeBase64String(sbOauth2.toString().getBytes(StandardCharsets.UTF_8));
78 | }
79 |
80 | @Override
81 | public String getDebugData() {
82 | return new StringBuilder(LOG_PREFIX).append(username).toString();
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/core/src/main/java/com/yahoo/imapnio/async/request/IdleCommand.java:
--------------------------------------------------------------------------------
1 | package com.yahoo.imapnio.async.request;
2 |
3 | import java.nio.charset.StandardCharsets;
4 | import java.util.concurrent.ConcurrentLinkedQueue;
5 |
6 | import javax.annotation.Nonnull;
7 |
8 | import com.sun.mail.imap.protocol.IMAPResponse;
9 |
10 | import io.netty.buffer.ByteBuf;
11 | import io.netty.buffer.Unpooled;
12 |
13 | /**
14 | * This class defines imap idle command request from client.
15 | */
16 | public class IdleCommand extends ImapRequestAdapter {
17 |
18 | /** Byte array for CR and LF, keeping the array local so it cannot be modified by others. */
19 | private static final byte[] CRLF_B = { '\r', '\n' };
20 |
21 | /** Command name. */
22 | private static final String IDLE = "IDLE";
23 |
24 | /** Byte array for IDLE. */
25 | private static final byte[] IDLE_B = IDLE.getBytes(StandardCharsets.US_ASCII);
26 |
27 | /** Literal for DONE. */
28 | private static final String DONE = "DONE";
29 |
30 | /** Byte array for DONE. */
31 | private static final byte[] DONE_B = DONE.getBytes(StandardCharsets.US_ASCII);
32 |
33 | /** Initial buffer length, enough for the word IDLE or DONE with some room to grow. */
34 | private static final int LINE_LEN = 20;
35 |
36 | /** ConcurrentLinkedQueue shared from caller and {@code ImapAsyncSession}. */
37 | private ConcurrentLinkedQueue serverStreamingResponses;
38 |
39 | /**
40 | * Initializes a {@link IdleCommand}.
41 | *
42 | * @param serverStreamingResponses server streaming responses will be placed in this parameter
43 | */
44 | public IdleCommand(@Nonnull final ConcurrentLinkedQueue serverStreamingResponses) {
45 | this.serverStreamingResponses = serverStreamingResponses;
46 | }
47 |
48 | @Override
49 | public void cleanup() {
50 | this.serverStreamingResponses = null;
51 | }
52 |
53 | @Override
54 | public ConcurrentLinkedQueue getStreamingResponsesQueue() {
55 | return serverStreamingResponses;
56 | }
57 |
58 | @Override
59 | public boolean isCommandLineDataSensitive() {
60 | return false;
61 | }
62 |
63 | @Override
64 | public String getDebugData() {
65 | return null;
66 | }
67 |
68 | @Override
69 | public ByteBuf getCommandLineBytes() {
70 | final ByteBuf buf = Unpooled.buffer(LINE_LEN);
71 | buf.writeBytes(IDLE_B);
72 | buf.writeBytes(CRLF_B);
73 | return buf;
74 | }
75 |
76 | @Override
77 | public ByteBuf getNextCommandLineAfterContinuation(@Nonnull final IMAPResponse serverResponse) {
78 | return null;
79 | }
80 |
81 | @Override
82 | public ByteBuf getTerminateCommandLine() {
83 | final ByteBuf buf = Unpooled.buffer(LINE_LEN);
84 | buf.writeBytes(DONE_B);
85 | buf.writeBytes(CRLF_B);
86 | return buf;
87 | }
88 |
89 | @Override
90 | public ImapRFCSupportedCommandType getCommandType() {
91 | return ImapRFCSupportedCommandType.IDLE;
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/core/src/main/java/com/yahoo/imapnio/async/netty/ImapClientCommandRespHandler.java:
--------------------------------------------------------------------------------
1 | package com.yahoo.imapnio.async.netty;
2 |
3 | import java.util.List;
4 |
5 | import javax.annotation.Nonnull;
6 |
7 | import com.sun.mail.imap.protocol.IMAPResponse;
8 |
9 | import io.netty.channel.ChannelHandlerContext;
10 | import io.netty.handler.codec.MessageToMessageDecoder;
11 | import io.netty.handler.timeout.IdleState;
12 | import io.netty.handler.timeout.IdleStateEvent;
13 |
14 | /**
15 | * This class handles the business logic of how to process messages and handle events.
16 | */
17 | public class ImapClientCommandRespHandler extends MessageToMessageDecoder {
18 |
19 | /** Literal for the name registered in pipeline. */
20 | public static final String HANDLER_NAME = "ImapClientCommandRespHandler";
21 |
22 | /** The imap channel event processor. */
23 | private ImapCommandChannelEventProcessor processor;
24 |
25 | /**
26 | * Initialized a handler to process pipeline response and events. This handler should include client business logic.
27 | *
28 | * @param processor imap channel processor that handles the imap events
29 | */
30 | public ImapClientCommandRespHandler(@Nonnull final ImapCommandChannelEventProcessor processor) {
31 | this.processor = processor;
32 | }
33 |
34 | @Override
35 | public void decode(final ChannelHandlerContext ctx, final IMAPResponse msg, final List