├── ci ├── prikeys.asc.enc ├── pubkeys.asc.enc ├── mvnsettings.xml └── deploy.sh ├── core └── src │ ├── main │ └── java │ │ └── com │ │ └── yahoo │ │ └── imapnio │ │ ├── client │ │ └── package-info.java │ │ ├── command │ │ ├── package-info.java │ │ ├── ImapClientRespDecoder.java │ │ ├── Argument.java │ │ └── ProxyProtocol.java │ │ └── async │ │ ├── request │ │ ├── package-info.java │ │ ├── ImapCommandType.java │ │ ├── FetchMacro.java │ │ ├── FlagsAction.java │ │ ├── LiteralSupport.java │ │ ├── NoopCommand.java │ │ ├── CheckCommand.java │ │ ├── CloseCommand.java │ │ ├── CapaCommand.java │ │ ├── ExpungeCommand.java │ │ ├── LogoutCommand.java │ │ ├── UnselectCommand.java │ │ ├── NamespaceCommand.java │ │ ├── CompressCommand.java │ │ ├── CreateFolderCommand.java │ │ ├── DeleteFolderCommand.java │ │ ├── SubscribeFolderCommand.java │ │ ├── UnsubscribeFolderCommand.java │ │ ├── LSubCommand.java │ │ ├── ListCommand.java │ │ ├── FetchCommand.java │ │ ├── SelectFolderCommand.java │ │ ├── AbstractNoArgsCommand.java │ │ ├── ImapClientConstants.java │ │ ├── ExamineFolderCommand.java │ │ ├── ImapRequestAdapter.java │ │ ├── UidMoveMessageCommand.java │ │ ├── CopyMessageCommand.java │ │ ├── UidCopyMessageCommand.java │ │ ├── MoveMessageCommand.java │ │ ├── EnableCommand.java │ │ ├── ImapRFCSupportedCommandType.java │ │ ├── AbstractFolderActionCommand.java │ │ ├── StoreFlagsCommand.java │ │ ├── UidStoreFlagsCommand.java │ │ ├── LoginCommand.java │ │ ├── RenameFolderCommand.java │ │ ├── ImapRequest.java │ │ ├── AbstractQueryFoldersCommand.java │ │ ├── UidExpungeCommand.java │ │ ├── IdCommand.java │ │ ├── AuthXoauth2Command.java │ │ ├── IdleCommand.java │ │ ├── StatusCommand.java │ │ ├── UidSearchCommand.java │ │ ├── SearchCommand.java │ │ ├── ByteBufWriter.java │ │ ├── AuthPlainCommand.java │ │ ├── AuthOauthBearerCommand.java │ │ ├── OpenFolderActionCommand.java │ │ └── UidFetchCommand.java │ │ ├── data │ │ ├── package-info.java │ │ ├── EnableResult.java │ │ ├── SearchResult.java │ │ ├── ListInfoList.java │ │ ├── QResyncSeqMatchData.java │ │ ├── IdResult.java │ │ ├── Capability.java │ │ ├── ListStatusResult.java │ │ ├── QResyncParameter.java │ │ ├── PartialExtensionUidFetchInfo.java │ │ ├── ExtensionMailboxInfo.java │ │ └── ExtensionListInfo.java │ │ ├── response │ │ ├── package-info.java │ │ └── ImapAsyncResponse.java │ │ ├── exception │ │ └── package-info.java │ │ ├── netty │ │ ├── package-info.java │ │ ├── ImapCommandChannelEventProcessor.java │ │ └── ImapClientCommandRespHandler.java │ │ ├── client │ │ ├── package-info.java │ │ ├── ImapAsyncCreateSessionResponse.java │ │ ├── ImapAsyncSessionConfig.java │ │ └── ImapAsyncSession.java │ │ └── internal │ │ └── package-info.java │ └── test │ └── java │ └── com │ └── yahoo │ └── imapnio │ ├── async │ ├── request │ │ ├── ImapRFCSupportedCommandTypeTest.java │ │ ├── ImapCommandTypeTest.java │ │ ├── NoopCommandTest.java │ │ ├── CloseCommandTest.java │ │ ├── LogoutCommandTest.java │ │ ├── ExpungeCommandTest.java │ │ ├── UnselectCommandTest.java │ │ ├── CompressCommandTest.java │ │ ├── NamespaceCommandTest.java │ │ ├── CheckCommandTest.java │ │ ├── CreateFolderCommandTest.java │ │ ├── DeleteFolderCommandTest.java │ │ ├── SubscribeFolderCommandTest.java │ │ ├── UnsubscribeFolderCommandTest.java │ │ ├── RenameFolderCommandTest.java │ │ └── CapaCommandTest.java │ ├── data │ │ ├── PartialExtensionUidFetchInfoTest.java │ │ ├── EnableResultTest.java │ │ ├── SearchResultTest.java │ │ ├── CapabilityTest.java │ │ ├── QResyncSeqMatchDataTest.java │ │ ├── ListInfoListTest.java │ │ ├── IdResultTest.java │ │ ├── QResyncParameterTest.java │ │ └── ListStatusResultTest.java │ ├── client │ │ └── ImapAsyncSessionConfigTest.java │ ├── response │ │ └── ImapAsyncResponseTest.java │ └── exception │ │ └── ImapAsyncClientExceptionTest.java │ └── command │ └── ArgumentTest.java ├── .travis.yml ├── pmd-ruleset.xml ├── issue_template.md ├── spotbugs-include.xml ├── checkstyle_suppressions.xml ├── Contributing.md └── pull_request_template.md /ci/prikeys.asc.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yahoo/imapnio/master/ci/prikeys.asc.enc -------------------------------------------------------------------------------- /ci/pubkeys.asc.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yahoo/imapnio/master/ci/pubkeys.asc.enc -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/client/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * @author lafa 3 | * 4 | */ 5 | package com.yahoo.imapnio.client; -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/command/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * @author lafa 3 | * 4 | */ 5 | package com.yahoo.imapnio.command; -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/request/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This class defines the package for imap asynchronous request. 3 | */ 4 | package com.yahoo.imapnio.async.request; -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/data/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This class defines the package for imap data converted from response. 3 | */ 4 | package com.yahoo.imapnio.async.data; -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/response/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This class defines the package for imap asynchronous response. 3 | */ 4 | package com.yahoo.imapnio.async.response; -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/exception/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This class defines the classes for imap asynchronous framework exception. 3 | */ 4 | package com.yahoo.imapnio.async.exception; 5 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/netty/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This class defines the package for the imap client library that sync will use to communicate with any imap server. 3 | */ 4 | package com.yahoo.imapnio.async.netty; 5 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/client/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This class defines the package for the imap client library that sync will use to communicate with any imap server. 3 | */ 4 | package com.yahoo.imapnio.async.client; 5 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/internal/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This class defines the package for the imap client library that sync will use to communicate with any imap server. 3 | */ 4 | package com.yahoo.imapnio.async.internal; 5 | -------------------------------------------------------------------------------- /ci/mvnsettings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ossrh 5 | ${env.OSSRH_USER} 6 | ${env.OSSRH_TOKEN} 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | branches: 2 | only: 3 | - master 4 | - stable 5 | language: java 6 | jdk: 7 | - openjdk14 8 | dist: focal 9 | before_install: 10 | install: 11 | - mvn clean install -Dgpg.skip=true 12 | before_script: 13 | script: 14 | after_success: 15 | - "./ci/deploy.sh" 16 | after_failure: 17 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/request/ImapCommandType.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.request; 2 | 3 | /** 4 | * IMAP command type. 5 | */ 6 | public interface ImapCommandType { 7 | /** 8 | * @return the type of the command 9 | */ 10 | String getType(); 11 | } 12 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/request/FetchMacro.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.request; 2 | 3 | /** 4 | * Macro for fetch command. 5 | */ 6 | public enum FetchMacro { 7 | /** Fetch macro for ALL. */ 8 | ALL, 9 | /** Fetch macro for FULL. */ 10 | FULL, 11 | /** Fetch macro for FAST. */ 12 | FAST 13 | } 14 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/request/FlagsAction.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.request; 2 | 3 | /** 4 | * Flags action used for store command. 5 | */ 6 | public enum FlagsAction { 7 | /** Replace the flags given in the flags list for the message. */ 8 | REPLACE, 9 | /** Add the flags given in the flags list for the message. */ 10 | ADD, 11 | /** Remove the flags given in the flags list for the message. */ 12 | REMOVE 13 | } 14 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/request/LiteralSupport.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.request; 2 | 3 | /** 4 | * Literal support variation. 5 | */ 6 | public enum LiteralSupport { 7 | /** Use Literal+ support to send literals. */ 8 | ENABLE_LITERAL_PLUS, 9 | /** Use Literal- support to send literals. */ 10 | ENABLE_LITERAL_MINUS, 11 | /** Disabling using literal support, just use standard RFC3501 specifications. */ 12 | DISABLE 13 | } 14 | -------------------------------------------------------------------------------- /pmd-ruleset.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | lafaspot PMD rules. 4 | 5 | 1 6 | 7 | 1 8 | 9 | 10 | -------------------------------------------------------------------------------- /ci/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | if [ "${TRAVIS_BRANCH}" == 'master' ] && [ "${TRAVIS_PULL_REQUEST}" == 'false' ]; then 3 | mkdir ci/deploy 4 | 5 | openssl aes-256-cbc -pass pass:$GPG_ENCPHRASE -in ci/pubkeys.asc.enc -out ci/deploy/pubkeys.asc -pbkdf2 -d 6 | openssl aes-256-cbc -pass pass:$GPG_ENCPHRASE -in ci/prikeys.asc.enc -out ci/deploy/prikeys.asc -pbkdf2 -d 7 | gpg --batch --fast-import ci/deploy/pubkeys.asc 8 | gpg --batch --fast-import ci/deploy/prikeys.asc 9 | 10 | mvn deploy -P ossrh --settings ci/mvnsettings.xml 11 | # delete decrypted keys 12 | rm -rf ci/deploy 13 | fi 14 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/request/NoopCommand.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.request; 2 | 3 | /** 4 | * This class defines imap noop command request from client. 5 | */ 6 | public class NoopCommand extends AbstractNoArgsCommand { 7 | 8 | /** Command name. */ 9 | private static final String NOOP = "NOOP"; 10 | 11 | /** 12 | * Initializes the {@link NoopCommand}. 13 | */ 14 | public NoopCommand() { 15 | super(NOOP); 16 | } 17 | 18 | @Override 19 | public ImapRFCSupportedCommandType getCommandType() { 20 | return ImapRFCSupportedCommandType.NOOP; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/request/CheckCommand.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.request; 2 | 3 | /** 4 | * This class defines imap check command request from client. 5 | */ 6 | public class CheckCommand extends AbstractNoArgsCommand { 7 | 8 | /** Command name. */ 9 | private static final String CHECK = "CHECK"; 10 | 11 | /** 12 | * Initializes the {@link CheckCommand}. 13 | */ 14 | public CheckCommand() { 15 | super(CHECK); 16 | } 17 | 18 | @Override 19 | public ImapRFCSupportedCommandType getCommandType() { 20 | return ImapRFCSupportedCommandType.CHECK; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/request/CloseCommand.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.request; 2 | 3 | /** 4 | * This class defines imap close command request from client. 5 | */ 6 | public class CloseCommand extends AbstractNoArgsCommand { 7 | 8 | /** Command name. */ 9 | private static final String CLOSE = "CLOSE"; 10 | 11 | /** 12 | * Initializes the {@link CloseCommand}. 13 | */ 14 | public CloseCommand() { 15 | super(CLOSE); 16 | } 17 | 18 | @Override 19 | public ImapRFCSupportedCommandType getCommandType() { 20 | return ImapRFCSupportedCommandType.CLOSE; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/request/CapaCommand.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.request; 2 | 3 | /** 4 | * This class defines imap capability command request from client. 5 | */ 6 | public class CapaCommand extends AbstractNoArgsCommand { 7 | 8 | /** Command name. */ 9 | private static final String CAPABILITY = "CAPABILITY"; 10 | 11 | /** 12 | * Initializes a {@link CapaCommand}. 13 | */ 14 | public CapaCommand() { 15 | super(CAPABILITY); 16 | } 17 | 18 | @Override 19 | public ImapRFCSupportedCommandType getCommandType() { 20 | return ImapRFCSupportedCommandType.CAPABILITY; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/request/ExpungeCommand.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.request; 2 | 3 | /** 4 | * This class defines imap expunge command request from client. 5 | */ 6 | public class ExpungeCommand extends AbstractNoArgsCommand { 7 | 8 | /** Command name. */ 9 | private static final String EXPUNGE = "EXPUNGE"; 10 | 11 | /** 12 | * Initializes the {@link ExpungeCommand}. 13 | */ 14 | public ExpungeCommand() { 15 | super(EXPUNGE); 16 | } 17 | 18 | @Override 19 | public ImapRFCSupportedCommandType getCommandType() { 20 | return ImapRFCSupportedCommandType.EXPUNGE; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/request/LogoutCommand.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.request; 2 | 3 | /** 4 | * This class defines imap logout command request from client. 5 | */ 6 | public class LogoutCommand extends AbstractNoArgsCommand { 7 | 8 | /** Command name. */ 9 | private static final String LOGOUT = "LOGOUT"; 10 | 11 | /** 12 | * Initializes the {@link LogoutCommand} command. 13 | */ 14 | public LogoutCommand() { 15 | super(LOGOUT); 16 | } 17 | 18 | @Override 19 | public ImapRFCSupportedCommandType getCommandType() { 20 | return ImapRFCSupportedCommandType.LOGOUT; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/request/UnselectCommand.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.request; 2 | 3 | /** 4 | * This class defines imap unselect command request from client. 5 | */ 6 | public class UnselectCommand extends AbstractNoArgsCommand { 7 | 8 | /** Command name. */ 9 | private static final String UNSELECT = "UNSELECT"; 10 | 11 | /** 12 | * Initializes the {@link UnselectCommand}. 13 | */ 14 | public UnselectCommand() { 15 | super(UNSELECT); 16 | } 17 | 18 | @Override 19 | public ImapRFCSupportedCommandType getCommandType() { 20 | return ImapRFCSupportedCommandType.UNSELECT; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/request/NamespaceCommand.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.request; 2 | 3 | /** 4 | * This class defines IMAP NAMESPACE command request from client. 5 | */ 6 | public class NamespaceCommand extends AbstractNoArgsCommand { 7 | 8 | /** Command name. */ 9 | private static final String NAMESPACE = "NAMESPACE"; 10 | 11 | /** 12 | * Initializes the {@link NamespaceCommand}. 13 | */ 14 | public NamespaceCommand() { 15 | super(NAMESPACE); 16 | } 17 | 18 | @Override 19 | public ImapRFCSupportedCommandType getCommandType() { 20 | return ImapRFCSupportedCommandType.NAMESPACE; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/request/CompressCommand.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.request; 2 | 3 | /** 4 | * This class defines imap Compress request from client. 5 | * 6 | * @see "RFC 4978" 7 | */ 8 | public final class CompressCommand extends AbstractNoArgsCommand { 9 | 10 | /** Command name. */ 11 | private static final String COMPRESS_DEFLATE = "COMPRESS DEFLATE"; 12 | 13 | /** 14 | * Initializes the {@link CompressCommand}. 15 | * 16 | * @see "RFC 4978" 17 | */ 18 | public CompressCommand() { 19 | super(COMPRESS_DEFLATE); 20 | } 21 | 22 | @Override 23 | public ImapRFCSupportedCommandType getCommandType() { 24 | return ImapRFCSupportedCommandType.COMPRESS; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/request/CreateFolderCommand.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.request; 2 | 3 | import javax.annotation.Nonnull; 4 | 5 | /** 6 | * This class defines imap create command request from client. 7 | */ 8 | public class CreateFolderCommand extends AbstractFolderActionCommand { 9 | 10 | /** Literal Create. */ 11 | private static final String CREATE = "CREATE"; 12 | 13 | /** 14 | * Initializes a {@link CreateFolderCommand}. 15 | * 16 | * @param folderName folder name to create 17 | */ 18 | public CreateFolderCommand(@Nonnull final String folderName) { 19 | super(CREATE, folderName); 20 | } 21 | 22 | @Override 23 | public ImapRFCSupportedCommandType getCommandType() { 24 | return ImapRFCSupportedCommandType.CREATE_FOLDER; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/request/DeleteFolderCommand.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.request; 2 | 3 | import javax.annotation.Nonnull; 4 | 5 | /** 6 | * This class defines imap delete command request from client. 7 | */ 8 | public class DeleteFolderCommand extends AbstractFolderActionCommand { 9 | 10 | /** Literal DELETE. */ 11 | private static final String DELETE = "DELETE"; 12 | 13 | /** 14 | * Initializes a {@link DeleteFolderCommand}. 15 | * 16 | * @param folderName folder name to delete 17 | */ 18 | public DeleteFolderCommand(@Nonnull final String folderName) { 19 | super(DELETE, folderName); 20 | } 21 | 22 | @Override 23 | public ImapRFCSupportedCommandType getCommandType() { 24 | return ImapRFCSupportedCommandType.DELETE_FOLDER; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/request/SubscribeFolderCommand.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.request; 2 | 3 | import javax.annotation.Nonnull; 4 | 5 | /** 6 | * This class defines imap subscribe command request from client. 7 | */ 8 | public class SubscribeFolderCommand extends AbstractFolderActionCommand { 9 | 10 | /** Command name. */ 11 | private static final String SUBSCRIBE = "SUBSCRIBE"; 12 | 13 | /** 14 | * Initializes a {@link SubscribeFolderCommand}. 15 | * 16 | * @param folderName folder name to SUBSCRIBE 17 | */ 18 | public SubscribeFolderCommand(@Nonnull final String folderName) { 19 | super(SUBSCRIBE, folderName); 20 | } 21 | 22 | @Override 23 | public ImapRFCSupportedCommandType getCommandType() { 24 | return ImapRFCSupportedCommandType.SUBSCRIBE; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /core/src/test/java/com/yahoo/imapnio/async/request/ImapRFCSupportedCommandTypeTest.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.request; 2 | 3 | import org.testng.Assert; 4 | import org.testng.annotations.Test; 5 | 6 | /** 7 | * Unit test for {@link ImapRFCSupportedCommandType}. 8 | */ 9 | public class ImapRFCSupportedCommandTypeTest { 10 | 11 | /** 12 | * Tests CommandType enum. 13 | */ 14 | @Test 15 | public void testCommandTypeEnum() { 16 | final ImapRFCSupportedCommandType[] enumList = ImapRFCSupportedCommandType.values(); 17 | Assert.assertEquals(enumList.length, 37, "The enum count mismatched."); 18 | final ImapRFCSupportedCommandType uidFetch = ImapRFCSupportedCommandType.valueOf("UID_FETCH"); 19 | Assert.assertSame(uidFetch, ImapRFCSupportedCommandType.UID_FETCH, "Enum does not match."); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/request/UnsubscribeFolderCommand.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.request; 2 | 3 | import javax.annotation.Nonnull; 4 | 5 | /** 6 | * This class defines imap unsubscribe command request from client. 7 | */ 8 | public class UnsubscribeFolderCommand extends AbstractFolderActionCommand { 9 | 10 | /** Command name. */ 11 | private static final String UNSUBSCRIBE = "UNSUBSCRIBE"; 12 | 13 | /** 14 | * Initializes a {@link UnsubscribeFolderCommand}. 15 | * 16 | * @param folderName folder name to UNSUBSCRIBE 17 | */ 18 | public UnsubscribeFolderCommand(@Nonnull final String folderName) { 19 | super(UNSUBSCRIBE, folderName); 20 | } 21 | 22 | @Override 23 | public ImapRFCSupportedCommandType getCommandType() { 24 | return ImapRFCSupportedCommandType.UNSUBSCRIBE; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/request/LSubCommand.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.request; 2 | 3 | import javax.annotation.Nonnull; 4 | 5 | /** 6 | * This class defines imap lsub command request from client. 7 | */ 8 | public class LSubCommand extends AbstractQueryFoldersCommand { 9 | 10 | /** Command name. */ 11 | private static final String LSUB = "LSUB"; 12 | 13 | /** 14 | * Initializes a {@link LSubCommand}. 15 | * 16 | * @param ref the reference string 17 | * @param pattern folder name with possible wildcards, see RFC3501 LSUB command for detail. 18 | */ 19 | public LSubCommand(@Nonnull final String ref, @Nonnull final String pattern) { 20 | super(LSUB, ref, pattern); 21 | } 22 | 23 | @Override 24 | public ImapRFCSupportedCommandType getCommandType() { 25 | return ImapRFCSupportedCommandType.LSUB; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/request/ListCommand.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.request; 2 | 3 | import javax.annotation.Nonnull; 4 | 5 | /** 6 | * This class defines imap list command request from client. 7 | */ 8 | public class ListCommand extends AbstractQueryFoldersCommand { 9 | 10 | /** Command name. */ 11 | private static final String LIST = "LIST"; 12 | 13 | /** 14 | * Initializes a {@link ListCommand}. 15 | * 16 | * @param ref the reference string 17 | * @param pattern folder name with possible wildcards, see RFC3501 list command for detail. 18 | */ 19 | public ListCommand(@Nonnull final String ref, @Nonnull final String pattern) { 20 | super(LIST, ref, pattern); 21 | } 22 | 23 | @Override 24 | public ImapRFCSupportedCommandType getCommandType() { 25 | return ImapRFCSupportedCommandType.LIST; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/command/ImapClientRespDecoder.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.command; 2 | 3 | import io.netty.handler.codec.MessageToMessageDecoder; 4 | 5 | import java.io.IOException; 6 | import java.util.List; 7 | 8 | import com.sun.mail.iap.ProtocolException; 9 | import com.sun.mail.imap.protocol.IMAPResponse; 10 | /** 11 | * @author kraman 12 | * 13 | */ 14 | import io.netty.channel.ChannelHandlerContext; 15 | 16 | /** 17 | * Basic response decoder. A ResponseDecoder (as opposed to a handler) is anything that outputs an IMAPResponse. 18 | * 19 | * @author kraman 20 | */ 21 | public class ImapClientRespDecoder extends MessageToMessageDecoder { 22 | 23 | @Override 24 | protected void decode(final ChannelHandlerContext ctx, final String msg, final List out) throws IOException, ProtocolException { 25 | out.add(new IMAPResponse(msg)); 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /core/src/test/java/com/yahoo/imapnio/async/data/PartialExtensionUidFetchInfoTest.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 PartialExtensionUidFetchInfo}. 8 | */ 9 | public class PartialExtensionUidFetchInfoTest { 10 | 11 | /** 12 | * Tests PartialExtensionUidFetchInfo constructor and getters. 13 | */ 14 | @Test 15 | public void testPartialExtensionUidFetchInfo() { 16 | PartialExtensionUidFetchInfo peufi = new PartialExtensionUidFetchInfo(1, 5); 17 | Assert.assertEquals(peufi.getFirstUid(), 1, "Result mismatched."); 18 | Assert.assertEquals(peufi.getLastUid(), 5, "Result mismatched."); 19 | 20 | peufi = new PartialExtensionUidFetchInfo(-1, -5); 21 | Assert.assertEquals(peufi.getFirstUid(), -1, "Result mismatched."); 22 | Assert.assertEquals(peufi.getLastUid(), -5, "Result mismatched."); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /core/src/test/java/com/yahoo/imapnio/async/client/ImapAsyncSessionConfigTest.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.client; 2 | 3 | import org.testng.Assert; 4 | import org.testng.annotations.Test; 5 | 6 | /** 7 | * Unit test for {@link ImapAsyncSessionConfig}. 8 | */ 9 | public class ImapAsyncSessionConfigTest { 10 | 11 | /** 12 | * Tests ImapAsyncSessionConfig constructor and getters. 13 | */ 14 | @Test 15 | public void testGettersSetters() { 16 | 17 | final ImapAsyncSessionConfig config = new ImapAsyncSessionConfig(); 18 | final int connectionTimeout = 1000; 19 | config.setConnectionTimeoutMillis(connectionTimeout); 20 | Assert.assertEquals(config.getConnectionTimeoutMillis(), connectionTimeout, "Result mismatched."); 21 | 22 | final int readTimeout = 2000; 23 | config.setReadTimeoutMillis(readTimeout); 24 | Assert.assertEquals(config.getReadTimeoutMillis(), readTimeout, "Result mismatched."); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/data/EnableResult.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.data; 2 | 3 | import java.util.Collections; 4 | import java.util.Set; 5 | 6 | import javax.annotation.Nonnull; 7 | 8 | /** 9 | * This class provides the functionality to allow callers to obtain Enable command result given by imap server. 10 | */ 11 | public final class EnableResult { 12 | 13 | /** EnableResult list. */ 14 | private final Set capabilities; 15 | 16 | /** 17 | * Initializes the {@link EnableResult} class. 18 | * 19 | * @param capas set of enabled capability name with its values if existing 20 | */ 21 | public EnableResult(@Nonnull final Set capas) { 22 | this.capabilities = Collections.unmodifiableSet(capas); 23 | } 24 | 25 | /** 26 | * Returns the enabled capabilities. 27 | * 28 | * @return the enabled capabilities 29 | */ 30 | public Set getEnabledCapabilities() { 31 | return capabilities; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /core/src/test/java/com/yahoo/imapnio/async/data/EnableResultTest.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.data; 2 | 3 | import java.util.HashSet; 4 | import java.util.Set; 5 | 6 | import org.testng.Assert; 7 | import org.testng.annotations.Test; 8 | 9 | /** 10 | * Unit test for {@link EnableResult}. 11 | */ 12 | public class EnableResultTest { 13 | 14 | /** 15 | * Tests EnableResult constructor and getters. 16 | */ 17 | @Test 18 | public void testEnableResult() { 19 | final Set set = new HashSet(); 20 | set.add("CONDSTORE"); 21 | set.add("QRSYNC"); 22 | 23 | final EnableResult enableresult = new EnableResult(set); 24 | 25 | final Set capas = enableresult.getEnabledCapabilities(); 26 | Assert.assertNotNull(capas, "Result mismatched."); 27 | Assert.assertEquals(capas.size(), 2, "Result mismatched."); 28 | Assert.assertTrue(capas.contains("CONDSTORE"), "Result mismatched."); 29 | Assert.assertTrue(capas.contains("QRSYNC"), "Result mismatched."); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /core/src/test/java/com/yahoo/imapnio/async/request/ImapCommandTypeTest.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.request; 2 | 3 | import org.testng.Assert; 4 | import org.testng.annotations.Test; 5 | 6 | /** 7 | * Unit test for {@link ImapCommandType}. 8 | */ 9 | public class ImapCommandTypeTest { 10 | 11 | /** 12 | * Tests CommandType enum. 13 | */ 14 | @Test 15 | public void testCommandTypeEnum() { 16 | final ImapTestCommandType[] enumList = ImapTestCommandType.values(); 17 | Assert.assertEquals(enumList.length, 1, "The enum count mismatched."); 18 | final ImapTestCommandType testType = ImapTestCommandType.valueOf("TEST"); 19 | Assert.assertSame(testType, ImapTestCommandType.TEST, "Enum does not match."); 20 | } 21 | 22 | /** 23 | * Test imap command type. 24 | */ 25 | enum ImapTestCommandType implements ImapCommandType { 26 | /** test command. */ 27 | TEST; 28 | 29 | @Override 30 | public String getType() { 31 | return this.name(); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /issue_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description 4 | 5 | 6 | ## Expected Behavior 7 | 8 | 9 | ## Actual Behavior 10 | 11 | 12 | ## Possible Fix 13 | 14 | 15 | ## Steps to Reproduce 16 | 17 | 18 | 1. 19 | 2. 20 | 3. 21 | 4. 22 | 23 | ## Context 24 | 25 | 26 | ## Your Environment 27 | 28 | * Version used: 29 | * Environment name and version (e.g. PHP 5.4 on nginx 1.9.1): 30 | * Server type and version: 31 | * Operating System and version: 32 | * Link to your project: 33 | 34 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/data/SearchResult.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.data; 2 | 3 | import java.util.List; 4 | 5 | import javax.annotation.Nonnull; 6 | import javax.annotation.Nullable; 7 | 8 | /** 9 | * This class provides the list of message sequence numbers from search command response. 10 | */ 11 | public class SearchResult { 12 | /** Search command response sequence number, could be message sequence or UID. */ 13 | @Nonnull 14 | private final List msgNumbers; 15 | 16 | /** 17 | * Initializes a {@link SearchResult} object with message number collection. 18 | * 19 | * @param msgNumbers collection of message number from search command result 20 | */ 21 | public SearchResult(@Nonnull final List msgNumbers) { 22 | this.msgNumbers = msgNumbers; 23 | } 24 | 25 | /** 26 | * @return message number collection from search command or UID search command result 27 | */ 28 | @Nullable 29 | public List getMessageNumbers() { 30 | return this.msgNumbers; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/data/ListInfoList.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.data; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | 6 | import javax.annotation.Nonnull; 7 | 8 | import com.sun.mail.imap.protocol.ListInfo; 9 | 10 | /** 11 | * This class provides the ListInfo information converted from LIST or LSUB command IMAPResponse. 12 | */ 13 | public class ListInfoList { 14 | /** List of ListInfo objects. */ 15 | private final List infos; 16 | 17 | /** 18 | * Initializes a ListInfoList object with the given ListInfo collection. 19 | * 20 | * @param infos list of ListInfo objects 21 | */ 22 | public ListInfoList(@Nonnull final List infos) { 23 | // make it immutable here so we avoid keeping creating UnmodifiableList whenever getter is called, assuming we do not change 24 | this.infos = Collections.unmodifiableList(infos); 25 | } 26 | 27 | /** 28 | * @return list of ListInfo objects 29 | */ 30 | public List getListInfo() { 31 | return this.infos; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /spotbugs-include.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 24 | 25 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/netty/ImapCommandChannelEventProcessor.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.netty; 2 | 3 | import com.sun.mail.imap.protocol.IMAPResponse; 4 | 5 | import io.netty.handler.timeout.IdleStateEvent; 6 | 7 | /** 8 | * This class handles the event coming from {@link ImapClientCommandRespHandler}. 9 | */ 10 | public interface ImapCommandChannelEventProcessor { 11 | /** 12 | * Handles when a channel response arrives. 13 | * 14 | * @param the data type for next command after continuation. 15 | * @param msg the response 16 | */ 17 | void handleChannelResponse(IMAPResponse msg); 18 | 19 | /** 20 | * Handles when a channel has an exception. 21 | * 22 | * @param cause the exception 23 | */ 24 | void handleChannelException(Throwable cause); 25 | 26 | /** 27 | * Handles when a channel receives an idle-too-long event. 28 | * 29 | * @param timeOutEvent the timeout event 30 | */ 31 | void handleIdleEvent(IdleStateEvent timeOutEvent); 32 | 33 | /** 34 | * Handles the event when a channel is closed/disconnected either by server or client. 35 | */ 36 | void handleChannelClosed(); 37 | 38 | } 39 | -------------------------------------------------------------------------------- /core/src/test/java/com/yahoo/imapnio/async/data/SearchResultTest.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.data; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import org.testng.Assert; 7 | import org.testng.annotations.Test; 8 | 9 | /** 10 | * Unit test for {@link SearchResult}. 11 | */ 12 | public class SearchResultTest { 13 | 14 | /** 15 | * Tests SearchResult constructor and getters. 16 | */ 17 | @Test 18 | public void testSearchResult() { 19 | final List ll = new ArrayList<>(); 20 | ll.add(Long.MAX_VALUE - 1); 21 | final SearchResult infos = new SearchResult(ll); 22 | final List result = infos.getMessageNumbers(); 23 | Assert.assertEquals(result.size(), 1, "Result mismatched."); 24 | Assert.assertEquals(result.get(0), Long.valueOf(Long.MAX_VALUE - 1), "Result mismatched."); 25 | } 26 | 27 | /** 28 | * Tests SearchResult constructor and getters when passing null list. 29 | */ 30 | @Test 31 | public void testSearchResultNullList() { 32 | final SearchResult infos = new SearchResult(null); 33 | final List result = infos.getMessageNumbers(); 34 | Assert.assertNull(result, "Result mismatched."); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/request/FetchCommand.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.request; 2 | 3 | import javax.annotation.Nonnull; 4 | 5 | import com.yahoo.imapnio.async.data.MessageNumberSet; 6 | 7 | /** 8 | * This class defines imap fetch command request from client. 9 | */ 10 | public class FetchCommand extends AbstractFetchCommand { 11 | 12 | /** 13 | * Initializes a {@link FetchCommand} with the {@link MessageNumberSet} array and fetch items. 14 | * 15 | * @param msgsets the set of message set 16 | * @param items the data items 17 | */ 18 | public FetchCommand(@Nonnull final MessageNumberSet[] msgsets, @Nonnull final String items) { 19 | super(false, msgsets, items); 20 | } 21 | 22 | /** 23 | * Initializes a {@link FetchCommand} with the {@link MessageNumberSet} array and macro. 24 | * 25 | * @param msgsets the set of message set 26 | * @param macro the macro 27 | */ 28 | public FetchCommand(@Nonnull final MessageNumberSet[] msgsets, @Nonnull final FetchMacro macro) { 29 | super(false, msgsets, macro); 30 | } 31 | 32 | @Override 33 | public ImapRFCSupportedCommandType getCommandType() { 34 | return ImapRFCSupportedCommandType.FETCH; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/request/SelectFolderCommand.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.request; 2 | 3 | import javax.annotation.Nonnull; 4 | 5 | import com.yahoo.imapnio.async.data.QResyncParameter; 6 | 7 | /** 8 | * This class defines imap select command request from client. 9 | */ 10 | public class SelectFolderCommand extends OpenFolderActionCommand { 11 | 12 | /** Command name. */ 13 | private static final String SELECT = "SELECT"; 14 | 15 | /** 16 | * Initializes a {@link SelectFolderCommand}. 17 | * 18 | * @param folderName folder name to select 19 | */ 20 | public SelectFolderCommand(@Nonnull final String folderName) { 21 | super(SELECT, folderName); 22 | } 23 | 24 | /** 25 | * Initializes a {@link SelectFolderCommand}. 26 | * 27 | * @param folderName folder name to select 28 | * @param qResyncParameter qresync parameter 29 | */ 30 | public SelectFolderCommand(@Nonnull final String folderName, @Nonnull final QResyncParameter qResyncParameter) { 31 | super(SELECT, folderName, qResyncParameter); 32 | } 33 | 34 | @Override 35 | public ImapRFCSupportedCommandType getCommandType() { 36 | return ImapRFCSupportedCommandType.SELECT_FOLDER; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /checkstyle_suppressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/data/QResyncSeqMatchData.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.data; 2 | 3 | import javax.annotation.Nonnull; 4 | 5 | /** 6 | * This class models the QRESYNC Sequence match data defined in https://tools.ietf.org/html/rfc7162#page-26. 7 | */ 8 | public class QResyncSeqMatchData { 9 | /** Message sequence number. */ 10 | @Nonnull 11 | private MessageNumberSet[] knownSequenceSet; 12 | 13 | /** Corresponding UIDs. */ 14 | @Nonnull 15 | private MessageNumberSet[] knownUidSet; 16 | 17 | /** 18 | * Constructor. 19 | * @param knownSequenceSet message sequence numbers 20 | * @param knownUidSet UIDs 21 | */ 22 | public QResyncSeqMatchData(@Nonnull final MessageNumberSet[] knownSequenceSet, @Nonnull final MessageNumberSet[] knownUidSet) { 23 | this.knownSequenceSet = knownSequenceSet; 24 | this.knownUidSet = knownUidSet; 25 | } 26 | 27 | /** 28 | * Get the known sequence set. 29 | * @return the known sequence set 30 | */ 31 | public MessageNumberSet[] getKnownSequenceSet() { 32 | return knownSequenceSet; 33 | } 34 | 35 | /** 36 | * Get the known uid set. 37 | * @return known UID set 38 | */ 39 | public MessageNumberSet[] getKnownUidSet() { 40 | return knownUidSet; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/data/IdResult.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.data; 2 | 3 | import java.util.Map; 4 | 5 | import javax.annotation.Nonnull; 6 | import javax.annotation.Nullable; 7 | 8 | /** 9 | * This class provides the functionality to allow callers to obtain Id command result given by imap server. 10 | */ 11 | public final class IdResult { 12 | 13 | /** IdResult list. */ 14 | private final Map params; 15 | 16 | /** 17 | * Initializes the {@link IdResult} class. 18 | * 19 | * @param map map of capability name with its values if existing 20 | */ 21 | public IdResult(@Nonnull final Map map) { 22 | this.params = map; 23 | } 24 | 25 | /** 26 | * Returns true if the keyName given is present in the id list; false otherwise. 27 | * 28 | * @param keyName the capability name to find 29 | * @return true if the result has the key; false otherwise 30 | */ 31 | public boolean hasKey(@Nullable final String keyName) { 32 | return params.containsKey(keyName); 33 | } 34 | 35 | /** 36 | * Returns the value based on the given key name. 37 | * 38 | * @param keyName the capability name to find 39 | * @return the value 40 | */ 41 | public String getValue(@Nullable final String keyName) { 42 | return params.get(keyName); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/request/AbstractNoArgsCommand.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 an Imap command that has no arguments sent from client. 12 | */ 13 | public abstract class AbstractNoArgsCommand extends ImapRequestAdapter { 14 | 15 | /** Byte array for CR and LF, keeping the array local so it cannot be modified by others. */ 16 | private static final byte[] CRLF_B = { '\r', '\n' }; 17 | 18 | /** The Command. */ 19 | private String op; 20 | 21 | /** 22 | * Initializes an IMAP command that has no arguments. 23 | * 24 | * @param op imap command string. For example, "NOOP" 25 | */ 26 | protected AbstractNoArgsCommand(@Nonnull final String op) { 27 | super(); 28 | this.op = op; 29 | } 30 | 31 | @Override 32 | public void cleanup() { 33 | this.op = null; 34 | } 35 | 36 | @Override 37 | public ByteBuf getCommandLineBytes() { 38 | final int len = op.length() + ImapClientConstants.CRLFLEN; 39 | final ByteBuf sb = Unpooled.buffer(len); 40 | sb.writeBytes(op.getBytes(StandardCharsets.US_ASCII)); 41 | sb.writeBytes(CRLF_B); 42 | return sb; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /core/src/test/java/com/yahoo/imapnio/async/data/CapabilityTest.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.data; 2 | 3 | import java.util.Arrays; 4 | import java.util.Collections; 5 | import java.util.HashMap; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | import org.testng.Assert; 10 | import org.testng.annotations.Test; 11 | 12 | /** 13 | * Unit test for {@link Capability}. 14 | */ 15 | public class CapabilityTest { 16 | 17 | /** 18 | * Tests Capability constructor and getters. 19 | */ 20 | @Test 21 | public void testCapability() { 22 | final Map> map = new HashMap>(); 23 | map.put("IMAP4rev1".toUpperCase(), Collections.singletonList("IMAP4rev1")); 24 | map.put("AUTH", Arrays.asList("PLAIN", "XOAUTH2", "OAUTHBEARER")); 25 | 26 | final Capability capa = new Capability(map); 27 | 28 | Assert.assertTrue(capa.hasCapability("IMAP4rev1"), "Result mismatched."); 29 | Assert.assertFalse(capa.hasCapability("IMAP3rev1"), "Result mismatched."); 30 | final List values = capa.getCapability("AUTH"); 31 | Assert.assertNotNull(values, "Result mismatched."); 32 | Assert.assertEquals(values.size(), 3, "Result mismatched."); 33 | Assert.assertEquals(values.get(0), "PLAIN", "Result mismatched."); 34 | Assert.assertEquals(values.get(1), "XOAUTH2", "Result mismatched."); 35 | Assert.assertEquals(values.get(2), "OAUTHBEARER", "Result mismatched."); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Contributing.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | First, thanks for taking the time to contribute to our project! The following information provides a guide for making contributions. 3 | 4 | ## Code of Conduct 5 | 6 | By participating in this project, you agree to abide by the [Verizon Media Code of Conduct](Code-of-Conduct.md). Everyone is welcome to submit a pull request or open an issue to improve the documentation, add improvements, or report bugs. 7 | 8 | ## How to Ask a Question 9 | 10 | If you simply have a question that needs an answer, [create an issue](https://help.github.com/articles/creating-an-issue/), and label it as a question. 11 | 12 | ## How To Contribute 13 | 14 | ### Report a Bug or Request a Feature 15 | 16 | If you encounter any bugs while using this software, or want to request a new feature or enhancement, feel free to [create an issue](https://help.github.com/articles/creating-an-issue/) to report it, make sure you add a label to indicate what type of issue it is. 17 | 18 | ### Contribute Code 19 | Pull requests are welcome for bug fixes. If you want to implement something new, please [request a feature first](#report-a-bug-or-request-a-feature.md) so we can discuss it. 20 | 21 | #### Creating a Pull Request 22 | Please follow [best practices](https://github.com/trein/dev-best-practices/wiki/Git-Commit-Best-Practices) for creating git commits. 23 | 24 | When your code is ready to be submitted, you can [submit a pull request](https://help.github.com/articles/creating-a-pull-request/) to begin the code review process. 25 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/request/ImapClientConstants.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.request; 2 | 3 | /** 4 | * Class for constant string in the IMAP asynchronous client. 5 | */ 6 | final class ImapClientConstants { 7 | 8 | /** Space character. */ 9 | static final char SPACE = ' '; 10 | 11 | /** Literal to cancel the command when server responds error. */ 12 | static final char CANCEL_B = '*'; 13 | 14 | /** Literal for CR and LF. */ 15 | static final int CRLFLEN = "\r\n".length(); 16 | 17 | /** NULL character. */ 18 | static final char NULL = '\0'; 19 | 20 | /** Start of header character. */ 21 | static final char SOH = 0x01; 22 | 23 | /** Left parenthesis and space. */ 24 | static final char L_PAREN = '('; 25 | 26 | /** Left parenthesis and space. */ 27 | static final char R_PAREN = ')'; 28 | 29 | /** Literal for colon. */ 30 | static final String COLON = ":"; 31 | 32 | /** Literal for plus. */ 33 | static final char PLUS = '+'; 34 | 35 | /** Literal for minus. */ 36 | static final char MINUS = '-'; 37 | 38 | /** SASL-IR capability. */ 39 | static final String SASL_IR = "SASL-IR"; 40 | 41 | /** LITERAL+ capability. */ 42 | static final String LITERAL_PLUS = "LITERAL+"; 43 | 44 | /** Extra buffer length for command line builder to add. */ 45 | static final int PAD_LEN = 100; 46 | 47 | /** 48 | * Private constructor to avoid constructing instance of this class. 49 | */ 50 | private ImapClientConstants() { 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description 4 | 5 | 6 | ## Motivation and Context 7 | 8 | 9 | 10 | ## How Has This Been Tested? 11 | 12 | 13 | 14 | 15 | ## Screenshots (if appropriate): 16 | 17 | ## Types of changes 18 | 19 | - [ ] Bug fix (non-breaking change which fixes an issue) 20 | - [ ] New feature (non-breaking change which adds functionality) 21 | - [ ] Breaking change (fix or feature that would cause existing functionality to change) 22 | - [ ] Major release (change is NOT backward compatible with prior release) 23 | 24 | ## Checklist: 25 | 26 | 27 | - [ ] My code follows the code style of this project. 28 | - [ ] My change requires a change to the documentation. 29 | - [ ] I have updated the documentation accordingly. 30 | - [ ] I have read the **CONTRIBUTING** document. 31 | - [ ] I have added tests to cover my changes. 32 | - [ ] All new and existing tests passed. 33 | 34 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/request/ExamineFolderCommand.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.request; 2 | 3 | import javax.annotation.Nonnull; 4 | 5 | import com.yahoo.imapnio.async.data.QResyncParameter; 6 | 7 | /** 8 | * This class defines imap examine command request from client. According to RFC3501: The EXAMINE command is identical to SELECT and returns the same 9 | * output; however, the selected mailbox is identified as read-only. No changes to the permanent state of the mailbox, including per-user state, are 10 | * permitted; in particular, EXAMINE MUST NOT cause messages to lose the \Recent flag. 11 | */ 12 | public class ExamineFolderCommand extends OpenFolderActionCommand { 13 | 14 | /** Command name. */ 15 | private static final String EXAMINE = "EXAMINE"; 16 | 17 | /** 18 | * Initializes a {@link ExamineFolderCommand}. 19 | * 20 | * @param folderName folder name to examine 21 | */ 22 | public ExamineFolderCommand(@Nonnull final String folderName) { 23 | super(EXAMINE, folderName); 24 | } 25 | 26 | /** 27 | * Initializes a {@link ExamineFolderCommand}. 28 | * 29 | * @param folderName folder name to examine 30 | * @param qResyncParameter qresync parameter 31 | */ 32 | public ExamineFolderCommand(@Nonnull final String folderName, @Nonnull final QResyncParameter qResyncParameter) { 33 | super(EXAMINE, folderName, qResyncParameter); 34 | } 35 | 36 | @Override 37 | public ImapRFCSupportedCommandType getCommandType() { 38 | return ImapRFCSupportedCommandType.EXAMINE_FOLDER; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/data/Capability.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.data; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | import javax.annotation.Nonnull; 7 | 8 | /** 9 | * This class provides the functionality to allow callers to obtain capabilities given by imap server. 10 | */ 11 | public final class Capability { 12 | 13 | /** Capability list. */ 14 | private final Map> capas; 15 | 16 | /** 17 | * Initializes the {@link Capability} class. 18 | * 19 | * @param capabilities map of capability name with its values if existing 20 | */ 21 | public Capability(@Nonnull final Map> capabilities) { 22 | this.capas = capabilities; 23 | } 24 | 25 | /** 26 | * Returns true if the capability is supported from server; false otherwise. 27 | * 28 | * @param capaName the capability name to find 29 | * @return true if the capability is supported from server 30 | */ 31 | public boolean hasCapability(@Nonnull final String capaName) { 32 | return capas.containsKey(capaName.toUpperCase()); 33 | } 34 | 35 | /** 36 | * Returns the various values for a specific capability, such as AUTH mechanisms that Imap server supports. For example, passing "AUTH" can return 37 | * a list of PLAIN, XOAUTH2, XBLURDYBLOOP. 38 | * 39 | * @param capaName the capability name to find 40 | * @return list of values for a specific capability name, List is immutable 41 | */ 42 | public List getCapability(@Nonnull final String capaName) { 43 | return capas.get(capaName.toUpperCase()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/request/ImapRequestAdapter.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 | import com.yahoo.imapnio.async.exception.ImapAsyncClientException; 10 | import com.yahoo.imapnio.async.exception.ImapAsyncClientException.FailureType; 11 | 12 | import io.netty.buffer.ByteBuf; 13 | 14 | /** 15 | * This class is an adapter for commands with no continuation request or terminal request. 16 | */ 17 | public abstract class ImapRequestAdapter implements ImapRequest { 18 | 19 | @Override 20 | public boolean isCommandLineDataSensitive() { 21 | return false; 22 | } 23 | 24 | @Override 25 | public String getCommandLine() throws ImapAsyncClientException { 26 | return getCommandLineBytes().toString(StandardCharsets.US_ASCII); 27 | } 28 | 29 | @Override 30 | public String getDebugData() { 31 | return null; 32 | } 33 | 34 | @Override 35 | public ConcurrentLinkedQueue getStreamingResponsesQueue() { 36 | return null; 37 | } 38 | 39 | @Override 40 | public ByteBuf getNextCommandLineAfterContinuation(@Nonnull final IMAPResponse serverResponse) throws ImapAsyncClientException { 41 | throw new ImapAsyncClientException(FailureType.OPERATION_NOT_SUPPORTED_FOR_COMMAND); 42 | } 43 | 44 | @Override 45 | public ByteBuf getTerminateCommandLine() throws ImapAsyncClientException { 46 | throw new ImapAsyncClientException(FailureType.OPERATION_NOT_SUPPORTED_FOR_COMMAND); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /core/src/test/java/com/yahoo/imapnio/async/data/QResyncSeqMatchDataTest.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 QResyncSeqMatchData}. 8 | */ 9 | public class QResyncSeqMatchDataTest { 10 | /** 11 | * Test the get method for known Sequence set. 12 | */ 13 | @Test 14 | public void testGetKnownSequenceSet() { 15 | final MessageNumberSet[] knownSeqSet = new MessageNumberSet[] { new MessageNumberSet(100, 100) }; 16 | final MessageNumberSet[] expectedMsgSeqNumbers = new MessageNumberSet[] { new MessageNumberSet(100, 100) }; 17 | final MessageNumberSet[] knownUidSet = new MessageNumberSet[] { new MessageNumberSet(200, 200) }; 18 | final QResyncSeqMatchData qResyncSeqMatchData = new QResyncSeqMatchData(knownSeqSet, knownUidSet); 19 | Assert.assertEquals(qResyncSeqMatchData.getKnownSequenceSet(), expectedMsgSeqNumbers, "Message sequence number not matched"); 20 | } 21 | 22 | /** 23 | * Test the get method for known UID set. 24 | */ 25 | @Test 26 | public void testGetKnownUidSet() { 27 | final MessageNumberSet[] knownSeqSet = new MessageNumberSet[] { new MessageNumberSet(100, 100) }; 28 | final MessageNumberSet[] knownUidSet = new MessageNumberSet[] { new MessageNumberSet(200, 200) }; 29 | final MessageNumberSet[] expectedUids = new MessageNumberSet[] { new MessageNumberSet(200, 200) }; 30 | final QResyncSeqMatchData qResyncSeqMatchData = new QResyncSeqMatchData(knownSeqSet, knownUidSet); 31 | Assert.assertEquals(qResyncSeqMatchData.getKnownUidSet(), expectedUids, "UIDs number not matched"); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/client/ImapAsyncCreateSessionResponse.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.client; 2 | 3 | import javax.annotation.Nonnull; 4 | import javax.annotation.Nullable; 5 | 6 | import com.sun.mail.imap.protocol.IMAPResponse; 7 | 8 | /** 9 | * This class is the response for the {@link ImapAsyncClient} createSession method response. 10 | */ 11 | public class ImapAsyncCreateSessionResponse { 12 | /** An instance of ImapAsyncSession object created through createSession method in {@link ImapAsyncClient}. */ 13 | private ImapAsyncSession session; 14 | 15 | /** IMAP server greeting responses upon connection established. */ 16 | private IMAPResponse greeting; 17 | 18 | /** 19 | * Instantiates a {@link ImapAsyncCreateSessionResponse} instance with ImapAsyncSession object and server initial greeting. Based on RFC3501, 20 | * server initial greeting is an un-tagged response. 21 | * 22 | * @param session ImapAsyncSession instance that is created by createSession method in {@link ImapAsyncClient} 23 | * @param greeting the initial server one line greeting including OK 24 | */ 25 | public ImapAsyncCreateSessionResponse(@Nonnull final ImapAsyncSession session, @Nonnull final IMAPResponse greeting) { 26 | this.session = session; 27 | this.greeting = greeting; 28 | } 29 | 30 | /** 31 | * @return ImapAsyncSession instance 32 | */ 33 | public ImapAsyncSession getSession() { 34 | return session; 35 | } 36 | 37 | /** 38 | * @return server initial greeting sent immediately after connection is established 39 | */ 40 | @Nullable 41 | public IMAPResponse getServerGreeting() { 42 | return greeting; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/data/ListStatusResult.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.data; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | import javax.annotation.Nonnull; 8 | 9 | import com.sun.mail.imap.protocol.Status; 10 | 11 | /** 12 | * This class provides the result converted from LIST-STATUS responses. It contains a collection of ListInfo (similar to List command), also it has a 13 | * map of Status objects where the key is the Status object's mbox field value. 14 | */ 15 | public class ListStatusResult { 16 | 17 | /** List of ListInfo objects. */ 18 | private final List infos; 19 | 20 | /** A map where the key is mail box name, aka Status.mbox and the value is Status object. */ 21 | private final Map statuses; 22 | 23 | /** 24 | * Initializes a ListStatusResult object with the given list of ListInfo objects and a map of Status objects. 25 | * 26 | * @param infos list of ListStatus objects 27 | * @param statuses a map of Status objects where the key is Status.mbox 28 | */ 29 | public ListStatusResult(@Nonnull final List infos, @Nonnull final Map statuses) { 30 | this.infos = Collections.unmodifiableList(infos); 31 | this.statuses = Collections.unmodifiableMap(statuses); 32 | } 33 | 34 | /** 35 | * @return list of ExtensionListInfo objects 36 | */ 37 | public List getListInfos() { 38 | return this.infos; 39 | } 40 | 41 | /** 42 | * @return a map of Status objects where the key is Status.mbox. 43 | */ 44 | public Map getStatuses() { 45 | return this.statuses; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/request/UidMoveMessageCommand.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.request; 2 | 3 | import javax.annotation.Nonnull; 4 | 5 | import com.sun.mail.imap.protocol.UIDSet; 6 | import com.yahoo.imapnio.async.data.MessageNumberSet; 7 | 8 | /** 9 | * This class defines imap move command request from client. 10 | */ 11 | public class UidMoveMessageCommand extends AbstractMessageActionCommand { 12 | 13 | /** Command name. */ 14 | private static final String MOVE = "MOVE"; 15 | 16 | /** 17 | * Initializes a {@link UidMoveMessageCommand} with the message sequence syntax. 18 | * 19 | * @param uidsets the list of UIDSet 20 | * @param targetFolder the targetFolder to be stored 21 | */ 22 | public UidMoveMessageCommand(@Nonnull final UIDSet[] uidsets, @Nonnull final String targetFolder) { 23 | super(MOVE, true, UIDSet.toString(uidsets), targetFolder); 24 | } 25 | 26 | /** 27 | * Initializes a {@link UidMoveMessageCommand} with the message sequence syntax. 28 | * 29 | * @param uids the string representing UID based on RFC3501 30 | * @param targetFolder the targetFolder to be stored 31 | */ 32 | public UidMoveMessageCommand(@Nonnull final String uids, @Nonnull final String targetFolder) { 33 | super(MOVE, true, uids, targetFolder); 34 | } 35 | 36 | /** 37 | * Initializes a {@link UidMoveMessageCommand} with the {@link MessageNumberSet} array. 38 | * 39 | * @param msgsets the set of {@link MessageNumberSet} 40 | * @param targetFolder the targetFolder to be stored 41 | */ 42 | public UidMoveMessageCommand(@Nonnull final MessageNumberSet[] msgsets, @Nonnull final String targetFolder) { 43 | super(MOVE, true, msgsets, targetFolder); 44 | } 45 | 46 | @Override 47 | public ImapRFCSupportedCommandType getCommandType() { 48 | return ImapRFCSupportedCommandType.UID_MOVE_MESSAGE; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /core/src/test/java/com/yahoo/imapnio/async/data/ListInfoListTest.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.data; 2 | 3 | import java.io.IOException; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | import org.testng.Assert; 8 | import org.testng.annotations.Test; 9 | 10 | import com.sun.mail.iap.ProtocolException; 11 | import com.sun.mail.imap.protocol.IMAPResponse; 12 | import com.sun.mail.imap.protocol.ListInfo; 13 | 14 | /** 15 | * Unit test for {@link ListInfoList}. 16 | */ 17 | public class ListInfoListTest { 18 | 19 | /** 20 | * Tests ListInfoList constructor and getters. 21 | * 22 | * @throws IOException will not throw 23 | * @throws ProtocolException will not throw 24 | */ 25 | @Test 26 | public void testListInfo() throws IOException, ProtocolException { 27 | final List ll = new ArrayList(); 28 | ll.add(new ListInfo(new IMAPResponse("* LIST (\\Archive \\HasNoChildren) \"/\" \"Archive\""))); 29 | ll.add(new ListInfo(new IMAPResponse("* LIST (\\Junk \\HasNoChildren) \"/\" \"Bulk Mail\""))); 30 | ll.add(new ListInfo(new IMAPResponse("* LIST (\\Drafts \\HasNoChildren) \"/\" \"Draft\""))); 31 | ll.add(new ListInfo(new IMAPResponse("* LIST (\\HasNoChildren) \"/\" \"Inbox\""))); 32 | ll.add(new ListInfo(new IMAPResponse("* LIST (\\Sent \\HasNoChildren) \"/\" \"Sent\""))); 33 | ll.add(new ListInfo(new IMAPResponse("* LIST (\\Trash \\HasNoChildren) \"/\" \"Trash\""))); 34 | ll.add(new ListInfo(new IMAPResponse("* LIST (\\HasChildren) \"/\" \"test1\""))); 35 | 36 | final ListInfoList infos = new ListInfoList(ll); 37 | final List result = infos.getListInfo(); 38 | Assert.assertEquals(result.size(), 7, "Result mismatched."); 39 | Assert.assertNotNull(result.get(0), "Result should not be null."); 40 | Assert.assertEquals(result.get(0).name, "Archive", "Result should not be null."); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/request/CopyMessageCommand.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 copy command from client. 10 | */ 11 | public class CopyMessageCommand extends AbstractMessageActionCommand { 12 | 13 | /** Command name. */ 14 | private static final String COPY = "COPY"; 15 | 16 | /** 17 | * Initializes a {@link CopyMessageCommand} 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 CopyMessageCommand(@Nonnull final MessageSet[] msgsets, @Nonnull final String targetFolder) { 23 | super(COPY, false, msgsets, targetFolder); 24 | } 25 | 26 | /** 27 | * Initializes a {@link CopyMessageCommand} 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 CopyMessageCommand(final int start, final int end, @Nonnull final String targetFolder) { 34 | super(COPY, 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 CopyMessageCommand(@Nonnull final MessageNumberSet[] msgsets, @Nonnull final String targetFolder) { 44 | super(COPY, false, msgsets, targetFolder); 45 | } 46 | 47 | @Override 48 | public ImapRFCSupportedCommandType getCommandType() { 49 | return ImapRFCSupportedCommandType.COPY_MESSAGE; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/request/UidCopyMessageCommand.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.request; 2 | 3 | import javax.annotation.Nonnull; 4 | 5 | import com.sun.mail.imap.protocol.UIDSet; 6 | import com.yahoo.imapnio.async.data.MessageNumberSet; 7 | 8 | /** 9 | * This class defines IMAP uid copy command from client. 10 | */ 11 | public class UidCopyMessageCommand extends AbstractMessageActionCommand { 12 | 13 | /** Command name. */ 14 | private static final String COPY = "COPY"; 15 | 16 | /** 17 | * Initializes a {@link UidCopyMessageCommand} with the message sequence syntax using UIDSet object. 18 | * 19 | * @param uidsets list of {@link UIDSet} 20 | * @param targetFolder the targetFolder to be stored 21 | */ 22 | public UidCopyMessageCommand(@Nonnull final UIDSet[] uidsets, @Nonnull final String targetFolder) { 23 | super(COPY, true, UIDSet.toString(uidsets), targetFolder); 24 | } 25 | 26 | /** 27 | * Initializes a {@link UidCopyMessageCommand} with the message sequence syntax. 28 | * 29 | * @param uids the string representing UID based on RFC3501 30 | * @param targetFolder the targetFolder to be stored 31 | */ 32 | public UidCopyMessageCommand(@Nonnull final String uids, @Nonnull final String targetFolder) { 33 | super(COPY, true, uids, targetFolder); 34 | } 35 | 36 | /** 37 | * Initializes a {@link UidCopyMessageCommand} with the {@link MessageNumberSet} array. 38 | * 39 | * @param msgsets the set of {@link MessageNumberSet} 40 | * @param targetFolder the targetFolder to be stored 41 | */ 42 | public UidCopyMessageCommand(@Nonnull final MessageNumberSet[] msgsets, @Nonnull final String targetFolder) { 43 | super(COPY, true, msgsets, targetFolder); 44 | } 45 | 46 | @Override 47 | public ImapRFCSupportedCommandType getCommandType() { 48 | return ImapRFCSupportedCommandType.UID_COPY_MESSAGE; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /core/src/test/java/com/yahoo/imapnio/async/data/IdResultTest.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.data; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import org.testng.Assert; 7 | import org.testng.annotations.Test; 8 | 9 | /** 10 | * Unit test for {@link IdResult}. 11 | */ 12 | public class IdResultTest { 13 | 14 | /** 15 | * Tests IdResult constructor and getters. 16 | */ 17 | @Test 18 | public void testIdResult() { 19 | final Map map = new HashMap<>(); 20 | map.put("name", "Cyrus"); 21 | map.put("version", "1.5"); 22 | map.put("os", "sunos"); 23 | map.put("os-version", "5.5"); 24 | final String expectedVal = "mailto:cyrus-bugs+@andrew.cmu.edu"; 25 | map.put("support-url", expectedVal); 26 | 27 | final IdResult idresult = new IdResult(map); 28 | 29 | Assert.assertTrue(idresult.hasKey("name"), "Result mismatched."); 30 | Assert.assertFalse(idresult.hasKey("Name"), "Result mismatched."); 31 | Assert.assertTrue(idresult.hasKey("support-url"), "Result mismatched."); 32 | Assert.assertEquals(idresult.getValue("support-url"), expectedVal, "Result mismatched."); 33 | 34 | } 35 | 36 | /** 37 | * Tests IdResult constructor and getters. 38 | */ 39 | @Test 40 | public void testIdResultNullKey() { 41 | final Map 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 out) { 36 | processor.handleChannelResponse(msg); 37 | } 38 | 39 | @Override 40 | public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) { 41 | processor.handleChannelException(cause); 42 | } 43 | 44 | /** 45 | * Receives an idle state event when READER_IDLE (no data was received for a while) or WRITER_IDLE (no data was sent for a while). 46 | * 47 | * @param ctx channel handler ctx 48 | * @param msg idle state event generated on idle connections by IdleStateHandler 49 | */ 50 | @Override 51 | public void userEventTriggered(final ChannelHandlerContext ctx, final Object msg) { 52 | if (msg instanceof IdleStateEvent) { // Handle the IdleState if needed 53 | final IdleStateEvent event = (IdleStateEvent) msg; 54 | if (event.state() == IdleState.ALL_IDLE) { 55 | // handle idle event in processor itself: when during idleCommand, we allow server not to send, but disallow during other commands 56 | processor.handleIdleEvent(event); 57 | } 58 | } 59 | } 60 | 61 | /** 62 | * Handles the event when a channel is closed(disconnected) either by server or client. 63 | * 64 | * @param ctx channel handler ctx 65 | */ 66 | @Override 67 | public void channelInactive(final ChannelHandlerContext ctx) { 68 | if (processor == null) { 69 | return; // cleanup() has been called, leave 70 | } 71 | processor.handleChannelClosed(); 72 | cleanup(); 73 | } 74 | 75 | /** 76 | * Avoids loitering. 77 | */ 78 | private void cleanup() { 79 | processor = null; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /core/src/test/java/com/yahoo/imapnio/async/exception/ImapAsyncClientExceptionTest.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.exception; 2 | 3 | import java.io.IOException; 4 | 5 | import org.testng.Assert; 6 | import org.testng.annotations.Test; 7 | 8 | /** 9 | * Unit test for {@link ImapAsyncClientException}. 10 | */ 11 | public class ImapAsyncClientExceptionTest { 12 | 13 | /** 14 | * Tests ImapAsyncClientException. 15 | */ 16 | @Test 17 | public void testImapAsyncClientException() { 18 | final ImapAsyncClientException.FailureType failureType = ImapAsyncClientException.FailureType.CHANNEL_DISCONNECTED; 19 | final ImapAsyncClientException resp = new ImapAsyncClientException(failureType); 20 | 21 | Assert.assertEquals(resp.getFailureType(), failureType, "result mismatched."); 22 | Assert.assertNull(resp.getCause(), "cause of exception mismatched."); 23 | Assert.assertEquals(resp.getMessage(), "failureType=" + failureType.name(), "result mismatched."); 24 | } 25 | 26 | /** 27 | * Tests ImapAsyncClientException constructor. 28 | */ 29 | @Test 30 | public void testImapAsyncClientExceptionWithFailureTypeAndCause() { 31 | final ImapAsyncClientException.FailureType failureType = ImapAsyncClientException.FailureType.CHANNEL_DISCONNECTED; 32 | final IOException cause = new IOException("Failure in IO!"); 33 | final ImapAsyncClientException ex = new ImapAsyncClientException(failureType, cause); 34 | 35 | Assert.assertEquals(ex.getFailureType(), failureType, "result mismatched."); 36 | Assert.assertEquals(ex.getMessage(), "failureType=CHANNEL_DISCONNECTED", "result mismatched."); 37 | Assert.assertEquals(ex.getCause(), cause, "cause of exception mismatched."); 38 | } 39 | 40 | /** 41 | * Tests ImapAsyncClientException constructor. 42 | */ 43 | @Test 44 | public void testImapAsyncClientExceptionWithSessionIdClientContext() { 45 | final ImapAsyncClientException.FailureType failureType = ImapAsyncClientException.FailureType.CHANNEL_DISCONNECTED; 46 | final Long sessionId = new Long(5); 47 | final String sessCtx = "T123riceratops123@scar123y.com"; 48 | final ImapAsyncClientException ex = new ImapAsyncClientException(failureType, sessionId, sessCtx); 49 | 50 | Assert.assertEquals(ex.getFailureType(), failureType, "result mismatched."); 51 | Assert.assertNull(ex.getCause(), "cause of exception mismatched."); 52 | Assert.assertEquals(ex.getMessage(), "failureType=CHANNEL_DISCONNECTED,sId=5,uId=T123riceratops123@scar123y.com", "result mismatched."); 53 | } 54 | 55 | /** 56 | * Tests ImapAsyncClientException when failureType is null. 57 | */ 58 | @Test 59 | public void testFailureType() { 60 | final ImapAsyncClientException.FailureType failureType = ImapAsyncClientException.FailureType.valueOf("CHANNEL_DISCONNECTED"); 61 | Assert.assertEquals(failureType, ImapAsyncClientException.FailureType.CHANNEL_DISCONNECTED, "result mismatched."); 62 | Assert.assertEquals(ImapAsyncClientException.FailureType.values().length, 17, "Number of enums mismatched."); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/request/StatusCommand.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 status command request from client. RFC 3501 ABNF for status command. 15 | * 16 | *
17 |  * status          = "STATUS" SP mailbox SP
18 |  *                   "(" status-att *(SP status-att) ")"
19 |  * status-att      = "MESSAGES" / "RECENT" / "UIDNEXT" / "UIDVALIDITY" /
20 |  *                   "UNSEEN"
21 |  * 
22 | */ 23 | public class StatusCommand extends ImapRequestAdapter { 24 | 25 | /** Byte array for CR and LF, keeping the array local so it cannot be modified by others. */ 26 | private static final byte[] CRLF_B = { '\r', '\n' }; 27 | 28 | /** Status and space. */ 29 | private static final String STATUS_SP = "STATUS "; 30 | 31 | /** Byte array for STATUS. */ 32 | private static final byte[] STATUS_SP_B = STATUS_SP.getBytes(StandardCharsets.US_ASCII); 33 | 34 | /** Folder name. */ 35 | private String folderName; 36 | 37 | /** Status data item names. */ 38 | private String[] items; 39 | 40 | /** 41 | * Initializes a {@link StatusCommand}. 42 | * 43 | * @param folderName folder name 44 | * @param items list of items. Available ones are : "MESSAGES", "RECENT", "UNSEEN", "UIDNEXT", "UIDVALIDITY" 45 | */ 46 | public StatusCommand(@Nonnull final String folderName, @Nonnull final String[] items) { 47 | this.folderName = folderName; 48 | this.items = items; 49 | } 50 | 51 | @Override 52 | public void cleanup() { 53 | this.folderName = null; 54 | this.items = null; 55 | } 56 | 57 | @Override 58 | public ByteBuf getCommandLineBytes() throws ImapAsyncClientException { 59 | 60 | final ByteBuf sb = Unpooled.buffer(ImapClientConstants.PAD_LEN); 61 | // ex: STATUS "test1" (UIDNEXT MESSAGES UIDVALIDITY RECENT) 62 | sb.writeBytes(STATUS_SP_B); 63 | 64 | final ImapArgumentFormatter formatter = new ImapArgumentFormatter(); 65 | 66 | final String encoded64Folder = BASE64MailboxEncoder.encode(folderName); 67 | formatter.formatArgument(encoded64Folder, sb, false); // already base64 encoded so can be formatted and write to sb 68 | 69 | sb.writeByte(ImapClientConstants.SPACE); 70 | sb.writeByte(ImapClientConstants.L_PAREN); 71 | for (int i = 0, len = items.length; i < len; i++) { 72 | formatter.formatArgument(items[i], sb, false); 73 | if (i < len - 1) { // do not add space for last item 74 | sb.writeByte(ImapClientConstants.SPACE); 75 | } 76 | } 77 | sb.writeByte(ImapClientConstants.R_PAREN); 78 | 79 | sb.writeBytes(CRLF_B); 80 | return sb; 81 | } 82 | 83 | @Override 84 | public ImapRFCSupportedCommandType getCommandType() { 85 | return ImapRFCSupportedCommandType.STATUS; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/request/UidSearchCommand.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.request; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.annotation.Nonnull; 6 | import javax.annotation.Nullable; 7 | import javax.mail.search.SearchException; 8 | import javax.mail.search.SearchTerm; 9 | 10 | import com.sun.mail.iap.Argument; 11 | import com.yahoo.imapnio.async.data.Capability; 12 | import com.yahoo.imapnio.async.data.MessageNumberSet; 13 | import com.yahoo.imapnio.async.exception.ImapAsyncClientException; 14 | 15 | /** 16 | * This class defines IMAP UID search command request from client. 17 | */ 18 | public class UidSearchCommand extends AbstractSearchCommand { 19 | 20 | /** 21 | * Initializes this object with the MessageNumberSet array, search string and character set name. 22 | * 23 | * @param msgsets the set of MessageNumberSet 24 | * @param term the search term 25 | * @param capa the capability instance to check if it has literal 26 | * @throws ImapAsyncClientException when both msgsets and searchString are null 27 | * @throws IOException when parsing error for generate sequence 28 | * @throws SearchException when search term cannot be found 29 | */ 30 | public UidSearchCommand(@Nullable final MessageNumberSet[] msgsets, @Nullable final SearchTerm term, @Nullable final Capability capa) 31 | throws ImapAsyncClientException, SearchException, IOException { 32 | super(true, msgsets, term, capa); 33 | } 34 | 35 | /** 36 | * Initializes this object with the string form of message sequence, SearchTerm. 37 | * 38 | * @param msgNumbers the string form message numbers in sequence-set syntax 39 | * @param term the search term 40 | * @param capa the capability instance to find if it has literal 41 | * @throws ImapAsyncClientException when both msgNumber and searchString are null 42 | * @throws IOException when parsing error for generate sequence 43 | * @throws SearchException when search term cannot be found 44 | */ 45 | public UidSearchCommand(@Nullable final String msgNumbers, @Nullable final SearchTerm term, @Nullable final Capability capa) 46 | throws ImapAsyncClientException, SearchException, IOException { 47 | super(true, msgNumbers, term, capa); 48 | } 49 | 50 | /** 51 | * Initializes this object with the string form of message sequence, character set name, and Argument that expresses the search term. 52 | * 53 | * @param msgNumbers the string form message numbers in sequence-set syntax 54 | * @param charset the character set 55 | * @param args the search term in argument format 56 | * @param capa the capability instance to find if it has literal 57 | * @throws ImapAsyncClientException when both msgNumber and searchString are null 58 | */ 59 | public UidSearchCommand(@Nullable final String msgNumbers, @Nullable final String charset, @Nonnull final Argument args, 60 | @Nullable final Capability capa) throws ImapAsyncClientException { 61 | super(true, msgNumbers, charset, args, capa); 62 | } 63 | 64 | @Override 65 | public ImapRFCSupportedCommandType getCommandType() { 66 | return ImapRFCSupportedCommandType.UID_SEARCH; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /core/src/test/java/com/yahoo/imapnio/async/data/ListStatusResultTest.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.data; 2 | 3 | import java.io.IOException; 4 | import java.util.ArrayList; 5 | import java.util.HashMap; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | import org.testng.Assert; 10 | import org.testng.annotations.Test; 11 | 12 | import com.sun.mail.iap.ProtocolException; 13 | import com.sun.mail.imap.protocol.IMAPResponse; 14 | import com.sun.mail.imap.protocol.Status; 15 | 16 | /** 17 | * Unit test for {@link ListStatusResult}. 18 | */ 19 | public class ListStatusResultTest { 20 | 21 | /** 22 | * Tests ListStatusResult constructor and getters. 23 | * 24 | * @throws IOException will not throw 25 | * @throws ProtocolException will not throw 26 | */ 27 | @Test 28 | public void testListInfo() throws IOException, ProtocolException { 29 | final List infos = new ArrayList(); 30 | final ExtensionListInfo linfo1 = new ExtensionListInfo(new IMAPResponse("* LIST (\\Archive \\HasNoChildren) \"/\" \"Archive\"")); 31 | final ExtensionListInfo linfo2 = new ExtensionListInfo(new IMAPResponse("* LIST (\\HasNoChildren) \"/\" \"INBOX\"")); 32 | 33 | infos.add(linfo1); 34 | infos.add(linfo2); 35 | 36 | final Map statuses = new HashMap<>(); 37 | final Status status1 = new Status(new IMAPResponse("* STATUS \"Archive\" (HIGHESTMODSEQ 82676 MESSAGES 0 UIDNEXT 9 UIDVALIDITY 4 UNSEEN 0)")); 38 | statuses.put(status1.mbox, status1); 39 | final Status status2 = new Status(new IMAPResponse("* STATUS \"INBOX\" (HIGHESTMODSEQ 11 MESSAGES 0 UIDNEXT 9 UIDVALIDITY 4 UNSEEN 0)")); 40 | statuses.put(status2.mbox, status2); 41 | 42 | final ListStatusResult result = new ListStatusResult(infos, statuses); 43 | // verify getListInfos() result 44 | final List actualInfos = result.getListInfos(); 45 | Assert.assertNotNull(actualInfos, "getListInfos() should not be null"); 46 | Assert.assertNotSame(actualInfos, infos, "should not return as same instance as input."); 47 | Assert.assertEquals(actualInfos.size(), 2, "getListInfos().size() mismatched."); 48 | Assert.assertNotNull(actualInfos.get(0), "ListInfo(0) mailbox name mismatched."); 49 | Assert.assertEquals(actualInfos.get(0).name, "Archive", "ListInfo(0) mailbox name mismatched."); 50 | Assert.assertNotNull(actualInfos.get(1), "ListInfo(1) mailbox name mismatched."); 51 | Assert.assertEquals(actualInfos.get(1).name, "INBOX", "ListInfo(1) mailbox name mismatched."); 52 | 53 | // verify getStatuses() result 54 | final Map actualStatuses = result.getStatuses(); 55 | Assert.assertNotNull(actualStatuses, "getStatuses() should not be null"); 56 | Assert.assertNotSame(actualStatuses, statuses, "should not return as same instance as input."); 57 | Assert.assertEquals(actualStatuses.size(), 2, "getStatuses().size() mismatched."); 58 | Assert.assertEquals(actualStatuses.get(status1.mbox), status1, "Status mismatched for " + status1.mbox); 59 | Assert.assertEquals(actualStatuses.get(status2.mbox), status2, "Status mismatched for " + status2.mbox); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/request/SearchCommand.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.request; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.annotation.Nonnull; 6 | import javax.annotation.Nullable; 7 | import javax.mail.search.SearchException; 8 | import javax.mail.search.SearchTerm; 9 | 10 | import com.sun.mail.iap.Argument; 11 | import com.yahoo.imapnio.async.data.Capability; 12 | import com.yahoo.imapnio.async.data.MessageNumberSet; 13 | import com.yahoo.imapnio.async.exception.ImapAsyncClientException; 14 | 15 | /** 16 | * This class defines IMAP search command request from client. 17 | */ 18 | public class SearchCommand extends AbstractSearchCommand { 19 | 20 | /** 21 | * Initializes a {@link SearchCommand} with the MessageNumberSet array, search string and character set name. 22 | * 23 | * @param msgsets the set of MessageNumberSet 24 | * @param term the search term 25 | * @param capa the capability instance to check if it has literal 26 | * @throws ImapAsyncClientException when both msgsets and searchString are null 27 | * @throws IOException when parsing error for generate sequence 28 | * @throws SearchException when search term cannot be found 29 | */ 30 | public SearchCommand(@Nullable final MessageNumberSet[] msgsets, @Nullable final SearchTerm term, @Nullable final Capability capa) 31 | throws ImapAsyncClientException, SearchException, IOException { 32 | super(false, msgsets, term, capa); 33 | } 34 | 35 | /** 36 | * Initializes this object with the string form of message sequence, search string and character set name. 37 | * 38 | * @param msgNumbers the string type message numbers in sequence-set syntax 39 | * @param term the search term 40 | * @param capa the capability instance to check if it has literal 41 | * @throws ImapAsyncClientException when both msgNumber and searchString are null 42 | * @throws IOException when parsing error for generate sequence 43 | * @throws SearchException when search term cannot be found 44 | */ 45 | public SearchCommand(@Nullable final String msgNumbers, @Nullable final SearchTerm term, @Nullable final Capability capa) 46 | throws ImapAsyncClientException, SearchException, IOException { 47 | super(false, msgNumbers, term, capa); 48 | } 49 | 50 | /** 51 | * Initializes this object with the string form of message sequence, character set name, and Argument that expresses the search term. 52 | * 53 | * @param msgNumbers the string form message numbers in sequence-set syntax 54 | * @param charset the character set 55 | * @param args the search term in argument format 56 | * @param capa the capability instance to check if it has literal 57 | * @throws ImapAsyncClientException when both msgNumber and searchString are null 58 | */ 59 | public SearchCommand(@Nullable final String msgNumbers, @Nullable final String charset, @Nonnull final Argument args, 60 | @Nullable final Capability capa) throws ImapAsyncClientException { 61 | super(false, msgNumbers, charset, args, capa); 62 | } 63 | 64 | @Override 65 | public ImapRFCSupportedCommandType getCommandType() { 66 | return ImapRFCSupportedCommandType.SEARCH; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/command/ProxyProtocol.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.command; 2 | 3 | import java.io.DataOutputStream; 4 | import java.io.IOException; 5 | import java.io.OutputStream; 6 | import java.util.Properties; 7 | 8 | import com.sun.mail.iap.Protocol; 9 | import com.sun.mail.iap.ProtocolException; 10 | import com.sun.mail.util.MailLogger; 11 | 12 | /** 13 | * Class used to serialize IMAP commands. kraman 14 | */ 15 | public class ProxyProtocol extends Protocol { 16 | /** The OutputStream used by ProxyProtocol. */ 17 | private OutputStream outputStream; 18 | 19 | /** 20 | * Creates a ProxyProtocol object. 21 | * 22 | * @throws IOException 23 | * on failure 24 | */ 25 | protected ProxyProtocol() throws IOException { 26 | super(null, null, new Properties(), false); 27 | 28 | outputStream = new OutputStreamProxy(); 29 | } 30 | 31 | /** 32 | * Never used but must be implemented. 33 | * 34 | * @param host 35 | * IMAP server host 36 | * @param port 37 | * IMAP server port 38 | * @param props 39 | * properties 40 | * @param prefix 41 | * prefix to prepend property keys 42 | * @param isSSL 43 | * is this an SSL connection 44 | * @param logger 45 | * logger 46 | * @throws IOException 47 | * on network failure 48 | * @throws ProtocolException 49 | * on IMAP protocol errors 50 | */ 51 | private ProxyProtocol(final String host, final int port, final Properties props, final String prefix, final boolean isSSL, 52 | final MailLogger logger) throws IOException, ProtocolException { 53 | super(host, port, props, prefix, isSSL, logger); 54 | } 55 | 56 | /** 57 | * Close. 58 | * 59 | * @throws IOException 60 | * on I/O failure 61 | */ 62 | public void close() throws IOException { 63 | outputStream.close(); 64 | outputStream = null; 65 | } 66 | 67 | /** 68 | * We wrap our internal outputStream as a DataOutputStream and return that for Argument to write to. The Protocol interface is really bad: this 69 | * actually has to be a DataOutputStream, not an OutputStream, thus the wrapping. 70 | * 71 | * @return the OutputStream 72 | */ 73 | @Override 74 | protected OutputStream getOutputStream() { 75 | return new DataOutputStream(outputStream); 76 | } 77 | 78 | @Override 79 | public String toString() { 80 | return outputStream.toString(); 81 | } 82 | 83 | @Override 84 | protected synchronized boolean supportsNonSyncLiterals() { 85 | return true; 86 | } 87 | } 88 | 89 | /** 90 | * Proxy class for the OutputStream. 91 | * 92 | * @author kraman 93 | * 94 | */ 95 | class OutputStreamProxy extends OutputStream { 96 | /** Serialized string. */ 97 | private String result = ""; 98 | 99 | @Override 100 | public void write(final int b) { 101 | result = result.concat(String.valueOf((char) b)); 102 | } 103 | 104 | @Override 105 | public String toString() { 106 | return result; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/request/ByteBufWriter.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.request; 2 | 3 | import java.io.DataOutputStream; 4 | import java.io.IOException; 5 | import java.io.OutputStream; 6 | import java.util.Properties; 7 | 8 | import javax.annotation.Nonnull; 9 | 10 | import com.sun.mail.iap.Protocol; 11 | import com.sun.mail.iap.ProtocolException; 12 | import com.sun.mail.iap.Response; 13 | import com.sun.mail.imap.protocol.IMAPResponse; 14 | import com.sun.mail.util.MailLogger; 15 | 16 | import io.netty.buffer.ByteBuf; 17 | 18 | /** 19 | * This class allows writing the data from argument to ByteBuf directly. 20 | */ 21 | final class ByteBufWriter extends Protocol { 22 | 23 | /** Symbol to indicate continue response. */ 24 | private static final String CONTINUE_SYMBOL = "+"; 25 | 26 | /** The OutputStream used by ByteBufWriter. */ 27 | private OutputStream outputStream; 28 | 29 | /** Flag to indicate whether literal plus is enabled. */ 30 | private boolean isLiteralPlus; 31 | 32 | /** 33 | * Creates a ByteBufWriter object. 34 | * 35 | * @param buf ByteBuf instance 36 | * @param isLiteralPlus Flag to indicate whether literal plus is enabled 37 | * @throws IOException on failure 38 | */ 39 | ByteBufWriter(@Nonnull final ByteBuf buf, final boolean isLiteralPlus) throws IOException { 40 | super(null, null, new Properties(), false); 41 | this.outputStream = new ByteBufOutputStream(buf); 42 | this.isLiteralPlus = isLiteralPlus; 43 | } 44 | 45 | /** 46 | * Never used but must be implemented. 47 | * 48 | * @param host IMAP server host 49 | * @param port IMAP server port 50 | * @param props properties 51 | * @param prefix prefix to prepend property keys 52 | * @param isSSL is this an SSL connection 53 | * @param logger logger 54 | * @throws IOException on network failure 55 | * @throws ProtocolException on IMAP protocol errors 56 | */ 57 | private ByteBufWriter(final String host, final int port, final Properties props, final String prefix, final boolean isSSL, 58 | final MailLogger logger) throws IOException, ProtocolException { 59 | super(host, port, props, prefix, isSSL, logger); 60 | } 61 | 62 | /** 63 | * Returns a continuation response in order to avoid {@link com.sun.mail.iap.Argument} blocking on literal method to wait for server continuation. 64 | * 65 | * @return a continuation response 66 | * @throws IOException on network failure 67 | * @throws ProtocolException on IMAP protocol errors 68 | */ 69 | @Override 70 | public Response readResponse() throws IOException, ProtocolException { 71 | return new IMAPResponse(CONTINUE_SYMBOL); 72 | } 73 | 74 | @Override 75 | protected OutputStream getOutputStream() { 76 | return new DataOutputStream(outputStream); 77 | } 78 | 79 | @Override 80 | protected synchronized boolean supportsNonSyncLiterals() { 81 | return isLiteralPlus; 82 | } 83 | } 84 | 85 | /** 86 | * ByteBufOutputStream that allows writing to ByteBuf directly. 87 | */ 88 | class ByteBufOutputStream extends OutputStream { 89 | /** Internal buffer. */ 90 | private ByteBuf buf; 91 | 92 | /** 93 | * Instantiates ByteBufOutputStream instance with ByteBuf. 94 | * 95 | * @param buf ByteBuf instance 96 | */ 97 | ByteBufOutputStream(@Nonnull final ByteBuf buf) { 98 | this.buf = buf; 99 | } 100 | 101 | @Override 102 | public void write(final int b) { 103 | buf.writeByte(b); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/request/AuthPlainCommand.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.request; 2 | 3 | import java.nio.charset.StandardCharsets; 4 | 5 | import javax.annotation.Nonnull; 6 | import javax.annotation.Nullable; 7 | 8 | import org.apache.commons.codec.binary.Base64; 9 | 10 | import com.yahoo.imapnio.async.data.Capability; 11 | 12 | import io.netty.buffer.ByteBuf; 13 | 14 | /** 15 | * This class defines imap authenticate plain command request from client. 16 | */ 17 | public final class AuthPlainCommand extends AbstractAuthCommand { 18 | 19 | /** Literal for Auth plain and space. */ 20 | private static final String AUTH_PLAIN = "AUTHENTICATE PLAIN"; 21 | 22 | /** Byte array for AUTH_PLAIN. */ 23 | private static final byte[] AUTH_PLAIN_B = AUTH_PLAIN.getBytes(StandardCharsets.US_ASCII); 24 | 25 | /** Literal for logging data. */ 26 | private static final String LOG_PREFIX = "AUTHENTICATE PLAIN FOR USER:"; 27 | 28 | /** Literal for 10. */ 29 | private static final int TEN = 10; 30 | 31 | /** Authorize id. */ 32 | private String authId; 33 | 34 | /** User name. */ 35 | private String username; 36 | 37 | /** User pass word. */ 38 | private String dwp; 39 | 40 | /** 41 | * Initializes an authenticate plain command. 42 | * 43 | * @param username user name 44 | * @param dwp pass word 45 | * @param capa the capability obtained from server 46 | */ 47 | public AuthPlainCommand(@Nonnull final String username, @Nonnull final String dwp, @Nonnull final Capability capa) { 48 | this(null, username, dwp, capa); 49 | } 50 | 51 | /** 52 | * Initializes an authenticate plain command. 53 | * 54 | * @param authId authorize-id in rfc2595 55 | * @param username user name 56 | * @param dwp pass word 57 | * @param capa the capability obtained from server 58 | */ 59 | public AuthPlainCommand(@Nullable final String authId, @Nonnull final String username, @Nonnull final String dwp, 60 | @Nonnull final Capability capa) { 61 | super(capa); 62 | this.authId = authId; 63 | this.username = username; 64 | this.dwp = dwp; 65 | } 66 | 67 | @Override 68 | public void cleanup() { 69 | this.authId = null; 70 | this.username = null; 71 | this.dwp = null; 72 | } 73 | 74 | @Override 75 | void buildCommand(@Nonnull final ByteBuf buf) { 76 | buf.writeBytes(AUTH_PLAIN_B); 77 | } 78 | 79 | @Override 80 | public String getDebugData() { 81 | return new StringBuilder(LOG_PREFIX).append(username).toString(); 82 | } 83 | 84 | /** 85 | * Builds the IR, aka client Initial Response (RFC4959). In this command, it is AUTH=PLAIN required format and encoded as base64. 86 | * 87 | * @return an encoded base64 AUTH=PLAIN format 88 | */ 89 | @Override 90 | String buildClientResponse() { 91 | /// NOTE: char cannot be passed to StringBuilder constructor, since it becomes int as capacity 92 | // ex:bob\0bob\0munchkin 93 | final int authLen = (authId != null) ? authId.length() : 0; 94 | final int len = authLen + username.length() + dwp.length() + TEN; 95 | final StringBuilder sb = new StringBuilder(len); 96 | if (authId != null) { 97 | sb.append(authId); 98 | } 99 | final byte[] b = sb.append(ImapClientConstants.NULL).append(username).append(ImapClientConstants.NULL).append(dwp).toString() 100 | .getBytes(StandardCharsets.UTF_8); 101 | return Base64.encodeBase64String(b); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /core/src/test/java/com/yahoo/imapnio/async/request/CreateFolderCommandTest.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 CreateFolderCommand}. 16 | */ 17 | public class CreateFolderCommandTest { 18 | /** Literal for CREATE. */ 19 | private static final String CREATE = "CREATE "; 20 | 21 | /** Fields to check for cleanup. */ 22 | private Set fieldsToCheck; 23 | 24 | /** 25 | * Setup reflection. 26 | */ 27 | @BeforeClass 28 | public void setUp() { 29 | // Use reflection to get all declared non-primitive non-static fields (We do not care about inherited fields) 30 | final Class classUnderTest = CreateFolderCommand.class; 31 | fieldsToCheck = new HashSet<>(); 32 | for (Class c = classUnderTest; c != null; c = c.getSuperclass()) { 33 | for (final Field declaredField : c.getDeclaredFields()) { 34 | if (!declaredField.getType().isPrimitive() && !Modifier.isStatic(declaredField.getModifiers())) { 35 | declaredField.setAccessible(true); 36 | fieldsToCheck.add(declaredField); 37 | } 38 | } 39 | } 40 | } 41 | 42 | /** 43 | * Tests getCommandLine method. 44 | * 45 | * @throws ImapAsyncClientException will not throw 46 | * @throws IllegalAccessException will not throw 47 | * @throws IllegalArgumentException will not throw 48 | */ 49 | @Test 50 | public void testGetCommandLine() throws ImapAsyncClientException, IllegalArgumentException, IllegalAccessException { 51 | final String folderName = "folderABC"; 52 | final ImapRequest cmd = new CreateFolderCommand(folderName); 53 | Assert.assertEquals(cmd.getCommandLine(), CREATE + folderName + "\r\n", "Expected result mismatched."); 54 | 55 | cmd.cleanup(); 56 | // Verify if cleanup happened correctly. 57 | for (final Field field : fieldsToCheck) { 58 | Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null"); 59 | } 60 | } 61 | 62 | /** 63 | * Tests getCommandLine method with folder name containing space. 64 | * 65 | * @throws ImapAsyncClientException will not throw 66 | */ 67 | @Test 68 | public void testGetCommandLineWithEscapeChar() throws ImapAsyncClientException { 69 | final String folderName = "folder ABC"; 70 | final ImapRequest cmd = new CreateFolderCommand(folderName); 71 | Assert.assertEquals(cmd.getCommandLine(), CREATE + "\"" + folderName + "\"\r\n", "Expected result mismatched."); 72 | } 73 | 74 | /** 75 | * Tests getCommandLine method with folder name with other character set encoding. 76 | * 77 | * @throws ImapAsyncClientException will not throw 78 | */ 79 | @Test 80 | public void testGetCommandLineWithOtherCharSet() throws ImapAsyncClientException { 81 | final String folderName = "测试"; 82 | final ImapRequest cmd = new CreateFolderCommand(folderName); 83 | Assert.assertEquals(cmd.getCommandLine(), CREATE + "&bUuL1Q-\r\n", "Expected result mismatched."); 84 | } 85 | 86 | /** 87 | * Tests getCommandType method. 88 | */ 89 | @Test 90 | public void testGetCommandType() { 91 | final ImapRequest cmd = new CreateFolderCommand("folderToBeCreated"); 92 | Assert.assertSame(cmd.getCommandType(), ImapRFCSupportedCommandType.CREATE_FOLDER); 93 | } 94 | } -------------------------------------------------------------------------------- /core/src/test/java/com/yahoo/imapnio/async/request/DeleteFolderCommandTest.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 DeleteFolderCommand}. 16 | */ 17 | public class DeleteFolderCommandTest { 18 | /** Literal for DELETE. */ 19 | private static final String DELETE = "DELETE "; 20 | 21 | /** Fields to check for cleanup. */ 22 | private Set fieldsToCheck; 23 | 24 | /** 25 | * Setup reflection. 26 | */ 27 | @BeforeClass 28 | public void setUp() { 29 | // Use reflection to get all declared non-primitive non-static fields (We do not care about inherited fields) 30 | final Class classUnderTest = DeleteFolderCommand.class; 31 | fieldsToCheck = new HashSet<>(); 32 | for (Class c = classUnderTest; c != null; c = c.getSuperclass()) { 33 | for (final Field declaredField : c.getDeclaredFields()) { 34 | if (!declaredField.getType().isPrimitive() && !Modifier.isStatic(declaredField.getModifiers())) { 35 | declaredField.setAccessible(true); 36 | fieldsToCheck.add(declaredField); 37 | } 38 | } 39 | } 40 | } 41 | 42 | /** 43 | * Tests getCommandLine method. 44 | * 45 | * @throws ImapAsyncClientException will not throw 46 | * @throws IllegalAccessException will not throw 47 | * @throws IllegalArgumentException will not throw 48 | */ 49 | @Test 50 | public void testGetCommandLine() throws ImapAsyncClientException, IllegalArgumentException, IllegalAccessException { 51 | final String folderName = "folderABC"; 52 | final ImapRequest cmd = new DeleteFolderCommand(folderName); 53 | Assert.assertEquals(cmd.getCommandLine(), DELETE + folderName + "\r\n", "Expected result mismatched."); 54 | 55 | cmd.cleanup(); 56 | // Verify if cleanup happened correctly. 57 | for (final Field field : fieldsToCheck) { 58 | Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null"); 59 | } 60 | } 61 | 62 | /** 63 | * Tests getCommandLine method with folder name containing space. 64 | * 65 | * @throws ImapAsyncClientException will not throw 66 | */ 67 | @Test 68 | public void testGetCommandLineWithEscapeChar() throws ImapAsyncClientException { 69 | final String folderName = "folder ABC"; 70 | final ImapRequest cmd = new DeleteFolderCommand(folderName); 71 | Assert.assertEquals(cmd.getCommandLine(), DELETE + "\"" + folderName + "\"\r\n", "Expected result mismatched."); 72 | } 73 | 74 | /** 75 | * Tests getCommandLine method with folder name with other character set encoding. 76 | * 77 | * @throws ImapAsyncClientException will not throw 78 | */ 79 | @Test 80 | public void testGetCommandLineWithOtherCharSet() throws ImapAsyncClientException { 81 | final String folderName = "测试"; 82 | final ImapRequest cmd = new DeleteFolderCommand(folderName); 83 | Assert.assertEquals(cmd.getCommandLine(), DELETE + "&bUuL1Q-\r\n", "Expected result mismatched."); 84 | } 85 | 86 | /** 87 | * Tests getCommandType method. 88 | */ 89 | @Test 90 | public void testGetCommandType() { 91 | final ImapRequest cmd = new DeleteFolderCommand("folderToBeDeleted"); 92 | Assert.assertSame(cmd.getCommandType(), ImapRFCSupportedCommandType.DELETE_FOLDER); 93 | } 94 | } -------------------------------------------------------------------------------- /core/src/test/java/com/yahoo/imapnio/async/request/SubscribeFolderCommandTest.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 SubscribeFolderCommand}. 16 | */ 17 | public class SubscribeFolderCommandTest { 18 | /** Literal for SUBSCRIBE. */ 19 | private static final String SUBSCRIBE = "SUBSCRIBE "; 20 | 21 | /** Fields to check for cleanup. */ 22 | private Set fieldsToCheck; 23 | 24 | /** 25 | * Setup reflection. 26 | */ 27 | @BeforeClass 28 | public void setUp() { 29 | // Use reflection to get all declared non-primitive non-static fields (We do not care about inherited fields) 30 | final Class classUnderTest = SubscribeFolderCommand.class; 31 | fieldsToCheck = new HashSet<>(); 32 | for (Class c = classUnderTest; c != null; c = c.getSuperclass()) { 33 | for (final Field declaredField : c.getDeclaredFields()) { 34 | if (!declaredField.getType().isPrimitive() && !Modifier.isStatic(declaredField.getModifiers())) { 35 | declaredField.setAccessible(true); 36 | fieldsToCheck.add(declaredField); 37 | } 38 | } 39 | } 40 | } 41 | 42 | /** 43 | * Tests getCommandLine method. 44 | * 45 | * @throws ImapAsyncClientException will not throw 46 | * @throws IllegalAccessException will not throw 47 | * @throws IllegalArgumentException will not throw 48 | */ 49 | @Test 50 | public void testGetCommandLine() throws IllegalArgumentException, IllegalAccessException, ImapAsyncClientException { 51 | final String folderName = "folderABC"; 52 | final ImapRequest cmd = new SubscribeFolderCommand(folderName); 53 | Assert.assertEquals(cmd.getCommandLine(), SUBSCRIBE + folderName + "\r\n", "Expected result mismatched."); 54 | 55 | cmd.cleanup(); 56 | // Verify if cleanup happened correctly. 57 | for (final Field field : fieldsToCheck) { 58 | Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null"); 59 | } 60 | } 61 | 62 | /** 63 | * Tests getCommandLine method with folder name containing space. 64 | * 65 | * @throws ImapAsyncClientException will not throw 66 | */ 67 | @Test 68 | public void testGetCommandLineWithEscapeChar() throws ImapAsyncClientException { 69 | final String folderName = "folder ABC"; 70 | final ImapRequest cmd = new SubscribeFolderCommand(folderName); 71 | Assert.assertEquals(cmd.getCommandLine(), SUBSCRIBE + "\"" + folderName + "\"\r\n", "Expected result mismatched."); 72 | } 73 | 74 | /** 75 | * Tests getCommandLine method with folder name with other character set encoding. 76 | * 77 | * @throws ImapAsyncClientException will not throw 78 | */ 79 | @Test 80 | public void testGetCommandLineWithOtherCharSet() throws ImapAsyncClientException { 81 | final String folderName = "测试"; 82 | final ImapRequest cmd = new SubscribeFolderCommand(folderName); 83 | Assert.assertEquals(cmd.getCommandLine(), SUBSCRIBE + "&bUuL1Q-\r\n", "Expected result mismatched."); 84 | } 85 | 86 | /** 87 | * Tests getCommandType method. 88 | */ 89 | @Test 90 | public void testGetCommandType() { 91 | final ImapRequest cmd = new SubscribeFolderCommand("vacation"); 92 | Assert.assertSame(cmd.getCommandType(), ImapRFCSupportedCommandType.SUBSCRIBE); 93 | } 94 | } -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/request/AuthOauthBearerCommand.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 OauthBearer command request from client. 15 | */ 16 | public final class AuthOauthBearerCommand extends AbstractAuthCommand { 17 | 18 | /** Command operator. */ 19 | private static final String AUTH_OAUTHBEARER = "AUTHENTICATE OAUTHBEARER"; 20 | 21 | /** Byte array for AUTH OAUTHBEARER. */ 22 | private static final byte[] AUTH_OAUTHBEARER_B = AUTH_OAUTHBEARER.getBytes(StandardCharsets.US_ASCII); 23 | 24 | /** Literal for logging data. */ 25 | private static final String LOG_PREFIX = "AUTHENTICATE OAUTHBEARER FOR USER:"; 26 | 27 | /** Literal for user=. */ 28 | private static final String N_A = "n,a="; 29 | 30 | /** Literal for auth==Bearer. */ 31 | private static final String AUTH_BEARER = "auth=Bearer "; 32 | 33 | /** Extra length for port and a bunch of SOH. */ 34 | private static final int EXTRA_LEN = 50; 35 | 36 | /** Comma literal. */ 37 | private static final char COMMA = ','; 38 | 39 | /** Email Id. */ 40 | private String emailId; 41 | 42 | /** Host name. */ 43 | private String hostname; 44 | 45 | /** Port. */ 46 | private int port; 47 | 48 | /** User token. */ 49 | private String token; 50 | 51 | /** 52 | * Initializes an authenticate OauthBearer command. 53 | * 54 | * @param emailId the user name 55 | * @param hostname the host name 56 | * @param port the port 57 | * @param token xoauth2 token 58 | * @param capa the capability obtained from server 59 | */ 60 | public AuthOauthBearerCommand(@Nonnull final String emailId, @Nonnull final String hostname, final int port, @Nonnull final String token, 61 | @Nonnull final Capability capa) { 62 | super(capa); 63 | this.emailId = emailId; 64 | this.hostname = hostname; 65 | this.port = port; 66 | this.token = token; 67 | } 68 | 69 | @Override 70 | public void cleanup() { 71 | this.emailId = null; 72 | this.hostname = null; 73 | this.token = null; 74 | } 75 | 76 | @Override 77 | void buildCommand(@Nonnull final ByteBuf buf) { 78 | buf.writeBytes(AUTH_OAUTHBEARER_B); 79 | } 80 | 81 | /** 82 | * Builds the IR, aka client Initial Response (RFC4959). In this command, it is Oauthbearer token format and encoded as base64. 83 | * 84 | * @return an encoded base64 Oauthbearer format 85 | */ 86 | @Override 87 | String buildClientResponse() { 88 | // String format: n,a=user@example.com,^Ahost=server.example.com^Aport=993^Aauth=Bearer ^A^A 89 | final int len = N_A.length() + emailId.length() + hostname.length() + token.length() + EXTRA_LEN; 90 | final StringBuilder sbOauth2 = new StringBuilder(len).append(N_A).append(emailId).append(COMMA).append(ImapClientConstants.SOH); 91 | sbOauth2.append("host=").append(hostname).append(ImapClientConstants.SOH).append("port=").append(port).append(ImapClientConstants.SOH); 92 | sbOauth2.append(AUTH_BEARER).append(token).append(ImapClientConstants.SOH).append(ImapClientConstants.SOH); 93 | return Base64.encodeBase64String(sbOauth2.toString().getBytes(StandardCharsets.US_ASCII)); 94 | } 95 | 96 | @Override 97 | public String getDebugData() { 98 | return new StringBuilder(LOG_PREFIX).append(emailId).toString(); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /core/src/test/java/com/yahoo/imapnio/async/request/UnsubscribeFolderCommandTest.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 UnsubscribeFolderCommand}. 16 | */ 17 | public class UnsubscribeFolderCommandTest { 18 | /** Literal for UNSUBSCRIBE. */ 19 | private static final String UNSUBSCRIBE = "UNSUBSCRIBE "; 20 | 21 | /** Fields to check for cleanup. */ 22 | private Set fieldsToCheck; 23 | 24 | /** 25 | * Setup reflection. 26 | */ 27 | @BeforeClass 28 | public void setUp() { 29 | // Use reflection to get all declared non-primitive non-static fields (We do not care about inherited fields) 30 | final Class classUnderTest = UnsubscribeFolderCommand.class; 31 | fieldsToCheck = new HashSet<>(); 32 | for (Class c = classUnderTest; c != null; c = c.getSuperclass()) { 33 | for (final Field declaredField : c.getDeclaredFields()) { 34 | if (!declaredField.getType().isPrimitive() && !Modifier.isStatic(declaredField.getModifiers())) { 35 | declaredField.setAccessible(true); 36 | fieldsToCheck.add(declaredField); 37 | } 38 | } 39 | } 40 | } 41 | 42 | /** 43 | * Tests getCommandLine method. 44 | * 45 | * @throws IllegalAccessException will not throw 46 | * @throws IllegalArgumentException will not throw 47 | * @throws ImapAsyncClientException will not throw 48 | */ 49 | @Test 50 | public void testGetCommandLine() throws IllegalArgumentException, IllegalAccessException, ImapAsyncClientException { 51 | final String folderName = "folderABC"; 52 | final ImapRequest cmd = new UnsubscribeFolderCommand(folderName); 53 | Assert.assertEquals(cmd.getCommandLine(), UNSUBSCRIBE + folderName + "\r\n", "Expected result mismatched."); 54 | 55 | cmd.cleanup(); 56 | // Verify if cleanup happened correctly. 57 | for (final Field field : fieldsToCheck) { 58 | Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null"); 59 | } 60 | } 61 | 62 | /** 63 | * Tests getCommandLine method with folder name containing space. 64 | * 65 | * @throws ImapAsyncClientException will not throw 66 | */ 67 | @Test 68 | public void testGetCommandLineWithEscapeChar() throws ImapAsyncClientException { 69 | final String folderName = "folder ABC"; 70 | final ImapRequest cmd = new UnsubscribeFolderCommand(folderName); 71 | Assert.assertEquals(cmd.getCommandLine(), UNSUBSCRIBE + "\"" + folderName + "\"\r\n", "Expected result mismatched."); 72 | } 73 | 74 | /** 75 | * Tests getCommandLine method with folder name with other character set encoding. 76 | * 77 | * @throws ImapAsyncClientException will not throw 78 | */ 79 | @Test 80 | public void testGetCommandLineWithOtherCharSet() throws ImapAsyncClientException { 81 | final String folderName = "测试"; 82 | final ImapRequest cmd = new UnsubscribeFolderCommand(folderName); 83 | Assert.assertEquals(cmd.getCommandLine(), UNSUBSCRIBE + "&bUuL1Q-\r\n", "Expected result mismatched."); 84 | } 85 | 86 | /** 87 | * Tests getCommandType method. 88 | */ 89 | @Test 90 | public void testGetCommandType() { 91 | final ImapRequest cmd = new UnsubscribeFolderCommand("testFolder"); 92 | Assert.assertSame(cmd.getCommandType(), ImapRFCSupportedCommandType.UNSUBSCRIBE); 93 | } 94 | } -------------------------------------------------------------------------------- /core/src/test/java/com/yahoo/imapnio/async/request/RenameFolderCommandTest.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 RenameFolderCommand}. 16 | */ 17 | public class RenameFolderCommandTest { 18 | /** Literal for RENAME. */ 19 | private static final String RENAME = "RENAME "; 20 | 21 | /** Fields to check for cleanup. */ 22 | private Set fieldsToCheck; 23 | 24 | /** 25 | * Setup reflection. 26 | */ 27 | @BeforeClass 28 | public void setUp() { 29 | // Use reflection to get all declared non-primitive non-static fields (We do not care about inherited fields) 30 | final Class classUnderTest = RenameFolderCommand.class; 31 | fieldsToCheck = new HashSet<>(); 32 | for (Class c = classUnderTest; c != null; c = c.getSuperclass()) { 33 | for (final Field declaredField : c.getDeclaredFields()) { 34 | if (!declaredField.getType().isPrimitive() && !Modifier.isStatic(declaredField.getModifiers())) { 35 | declaredField.setAccessible(true); 36 | fieldsToCheck.add(declaredField); 37 | } 38 | } 39 | } 40 | } 41 | 42 | /** 43 | * Tests getCommandLine method. 44 | * 45 | * @throws ImapAsyncClientException will not throw 46 | * @throws IllegalAccessException will not throw 47 | * @throws IllegalArgumentException will not throw 48 | */ 49 | @Test 50 | public void testGetCommandLine() throws ImapAsyncClientException, IllegalArgumentException, IllegalAccessException { 51 | final String oldName = "folderABC"; 52 | final String newName = "folderDEF"; 53 | final ImapRequest cmd = new RenameFolderCommand(oldName, newName); 54 | Assert.assertEquals(cmd.getCommandLine(), "RENAME folderABC folderDEF\r\n", "Expected result mismatched."); 55 | 56 | cmd.cleanup(); 57 | // Verify if cleanup happened correctly. 58 | for (final Field field : fieldsToCheck) { 59 | Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null"); 60 | } 61 | } 62 | 63 | /** 64 | * Tests getCommandLine method with folder name containing space. 65 | * 66 | * @throws ImapAsyncClientException will not throw 67 | */ 68 | @Test 69 | public void testGetCommandLineWithEscapeChar() throws ImapAsyncClientException { 70 | final String oldName = "folder ABC"; 71 | final String newName = "folder DEF"; 72 | final ImapRequest cmd = new RenameFolderCommand(oldName, newName); 73 | Assert.assertEquals(cmd.getCommandLine(), "RENAME \"folder ABC\" \"folder DEF\"\r\n", "Expected result mismatched."); 74 | } 75 | 76 | /** 77 | * Tests getCommandLine method with folder name with other character set encoding. 78 | * 79 | * @throws ImapAsyncClientException will not throw 80 | */ 81 | @Test 82 | public void testGetCommandLineWithOtherCharSet() throws ImapAsyncClientException { 83 | final String oldName = "测试"; 84 | final String newName = "folderDEF"; 85 | final ImapRequest cmd = new RenameFolderCommand(oldName, newName); 86 | Assert.assertEquals(cmd.getCommandLine(), RENAME + "&bUuL1Q- folderDEF\r\n", "Expected result mismatched."); 87 | } 88 | 89 | /** 90 | * Tests getCommandType method. 91 | */ 92 | @Test 93 | public void testGetCommandType() { 94 | final ImapRequest cmd = new RenameFolderCommand("oldName", "newName"); 95 | Assert.assertSame(cmd.getCommandType(), ImapRFCSupportedCommandType.RENAME_FOLDER); 96 | } 97 | } -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/data/ExtensionListInfo.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.data; 2 | 3 | import java.util.Collections; 4 | import java.util.HashSet; 5 | import java.util.Set; 6 | 7 | import javax.annotation.Nonnull; 8 | 9 | import com.sun.mail.iap.ParsingException; 10 | import com.sun.mail.imap.protocol.IMAPResponse; 11 | import com.sun.mail.imap.protocol.ListInfo; 12 | 13 | /** 14 | * This class provides the result from LIST-EXTENDED command. It extends from ListInfo so clients can continue use the data from ListInfo. The public 15 | * methods provided here allows callers to easily identify if server sends the specific interested attribute or not. The attributes supported are 16 | * listed in RFC 5258. 17 | */ 18 | public class ExtensionListInfo extends ListInfo { 19 | /** 20 | * Extended attributes supported for LIST-EXTENDED capability, RFC 5258. 21 | */ 22 | public enum ExtendedListAttribute { 23 | 24 | /** HasChildren attribute. */ 25 | HAS_CHILDREN("\\HasChildren"), 26 | 27 | /** HasNoChildren attribute. */ 28 | HAS_NO_CHILDREN("\\HasNoChildren"), 29 | 30 | /** NonExistent attribute. */ 31 | NON_EXISTENT("\\NonExistent"), 32 | 33 | /** Remote attribute. */ 34 | REMOTE("\\Remote"), 35 | 36 | /** Subscribed attribute. */ 37 | SUBSCRIBED("\\Subscribed"); 38 | 39 | /** 40 | * Initializes a ExtendedListAttribute instance with the actual keyword. 41 | * 42 | * @param value the actual keyword 43 | */ 44 | ExtendedListAttribute(@Nonnull final String value) { 45 | this.value = value; 46 | } 47 | 48 | /** 49 | * Returns the actual keyword used for IMAP protocol. 50 | * 51 | * @return the actual keyword 52 | */ 53 | public String getValue() { 54 | return value; 55 | } 56 | 57 | /** IMAP attribute sent from server. */ 58 | @Nonnull 59 | private final String value; 60 | } 61 | 62 | /** A set of ExtendedListAttribute that are present from server responses. */ 63 | private Set extendedAttrs; 64 | 65 | /** 66 | * Initializes an instance of {@link ExtensionListInfo} from the server responses. It also parses the RFC 5258 supported list attribute to 67 | * identify whether the attribute is present or not. 68 | * 69 | * @param resp a LIST command response 70 | * @throws ParsingException for errors parsing the responses 71 | */ 72 | public ExtensionListInfo(@Nonnull final IMAPResponse resp) throws ParsingException { 73 | super(resp); 74 | final Set localSet = new HashSet(); 75 | 76 | for (int i = 0; i < attrs.length; i++) { 77 | // case insensitive to be consistent with ListInfo parsing 78 | if (ExtendedListAttribute.NON_EXISTENT.getValue().equalsIgnoreCase(attrs[i])) { 79 | localSet.add(ExtendedListAttribute.NON_EXISTENT); 80 | // if the folder does not exist, it cannot be open/selected. See https://tools.ietf.org/html/rfc5258#ref-IMAP4 81 | this.canOpen = false; 82 | } else if (ExtendedListAttribute.SUBSCRIBED.getValue().equalsIgnoreCase(attrs[i])) { 83 | localSet.add(ExtendedListAttribute.SUBSCRIBED); 84 | } else if (ExtendedListAttribute.REMOTE.getValue().equalsIgnoreCase(attrs[i])) { 85 | localSet.add(ExtendedListAttribute.REMOTE); 86 | } else if (ExtendedListAttribute.HAS_CHILDREN.getValue().equalsIgnoreCase(attrs[i])) { 87 | localSet.add(ExtendedListAttribute.HAS_CHILDREN); 88 | } else if (ExtendedListAttribute.HAS_NO_CHILDREN.getValue().equalsIgnoreCase(attrs[i])) { 89 | localSet.add(ExtendedListAttribute.HAS_NO_CHILDREN); 90 | } 91 | } 92 | extendedAttrs = Collections.unmodifiableSet(localSet); 93 | } 94 | 95 | /** 96 | * @return available extended attributes in a Set data structure 97 | */ 98 | public Set getAvailableExtendedAttributes() { 99 | return extendedAttrs; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/request/OpenFolderActionCommand.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.data.MessageNumberSet; 9 | import com.yahoo.imapnio.async.data.QResyncParameter; 10 | import com.yahoo.imapnio.async.exception.ImapAsyncClientException; 11 | 12 | import io.netty.buffer.ByteBuf; 13 | import io.netty.buffer.Unpooled; 14 | 15 | /** 16 | * This class defines imap abstract commands related to open operation on folder, like select and examine folder. 17 | */ 18 | abstract class OpenFolderActionCommand extends ImapRequestAdapter { 19 | 20 | /** Byte array for CR and LF, keeping the array local so it cannot be modified by others. */ 21 | private static final byte[] CRLF_B = { '\r', '\n' }; 22 | 23 | /** Command operator, for example, "SELECT". */ 24 | private String op; 25 | 26 | /** Folder name. */ 27 | private String folderName; 28 | 29 | /** Optional QResync parameter. */ 30 | private QResyncParameter qResyncParameter; 31 | 32 | /** 33 | * Initializes a {@link OpenFolderActionCommand}. 34 | * 35 | * @param op command operator 36 | * @param folderName folder name 37 | */ 38 | protected OpenFolderActionCommand(@Nonnull final String op, @Nonnull final String folderName) { 39 | this.op = op; 40 | this.folderName = folderName; 41 | this.qResyncParameter = null; 42 | } 43 | 44 | /** 45 | * Initializes a {@link OpenFolderActionCommand}. 46 | * 47 | * @param op command operator 48 | * @param folderName folder name to select 49 | * @param qResyncParameter qresync parameter 50 | */ 51 | OpenFolderActionCommand(@Nonnull final String op, @Nonnull final String folderName, @Nonnull final QResyncParameter qResyncParameter) { 52 | this.op = op; 53 | this.folderName = folderName; 54 | this.qResyncParameter = qResyncParameter; 55 | } 56 | 57 | @Override 58 | public void cleanup() { 59 | this.op = null; 60 | this.folderName = null; 61 | this.qResyncParameter = null; 62 | } 63 | 64 | @Override 65 | public ByteBuf getCommandLineBytes() throws ImapAsyncClientException { 66 | final String base64Folder = BASE64MailboxEncoder.encode(folderName); 67 | int qResyncParameterSize = 0; 68 | StringBuilder sb = null; 69 | if (qResyncParameter != null) { 70 | sb = new StringBuilder(); 71 | sb.append("(QRESYNC (").append(qResyncParameter.getUidValidity()).append(ImapClientConstants.SPACE).append(qResyncParameter.getModSeq()); 72 | if (qResyncParameter.getKnownUids() != null) { 73 | sb.append(ImapClientConstants.SPACE); 74 | sb.append(MessageNumberSet.buildString(qResyncParameter.getKnownUids())); 75 | } 76 | if (qResyncParameter.getSeqMatchData() != null) { 77 | sb.append(ImapClientConstants.SPACE).append("("); 78 | sb.append(MessageNumberSet.buildString(qResyncParameter.getSeqMatchData().getKnownSequenceSet())); 79 | sb.append(ImapClientConstants.SPACE); 80 | sb.append(MessageNumberSet.buildString(qResyncParameter.getSeqMatchData().getKnownUidSet())); 81 | sb.append(")"); 82 | } 83 | sb.append("))"); 84 | qResyncParameterSize = sb.length(); 85 | } 86 | // 2 * base64Folder.length(): assuming every char needs to be escaped, goal is eliminating resizing, and avoid complex length calculation 87 | final int len = 2 * base64Folder.length() + ImapClientConstants.PAD_LEN + qResyncParameterSize; 88 | final ByteBuf byteBuf = Unpooled.buffer(len); 89 | byteBuf.writeBytes(op.getBytes(StandardCharsets.US_ASCII)); 90 | byteBuf.writeByte(ImapClientConstants.SPACE); 91 | 92 | final ImapArgumentFormatter formatter = new ImapArgumentFormatter(); 93 | formatter.formatArgument(base64Folder, byteBuf, false); // already base64 encoded so can be formatted and write to sb 94 | 95 | if (qResyncParameterSize > 0) { 96 | byteBuf.writeByte(ImapClientConstants.SPACE); 97 | byteBuf.writeBytes(sb.toString().getBytes(StandardCharsets.US_ASCII)); 98 | } 99 | 100 | byteBuf.writeBytes(CRLF_B); 101 | 102 | return byteBuf; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /core/src/main/java/com/yahoo/imapnio/async/request/UidFetchCommand.java: -------------------------------------------------------------------------------- 1 | package com.yahoo.imapnio.async.request; 2 | 3 | import javax.annotation.Nonnull; 4 | 5 | import com.yahoo.imapnio.async.data.MessageNumberSet; 6 | import com.yahoo.imapnio.async.data.PartialExtensionUidFetchInfo; 7 | 8 | /** 9 | * This class defines IMAP UID fetch command request from client. 10 | */ 11 | public class UidFetchCommand extends AbstractFetchCommand { 12 | 13 | /** 14 | * Initializes a {@link UidFetchCommand} with the {@link MessageNumberSet} array. 15 | * 16 | * @param msgsets the set of message set 17 | * @param items the data items 18 | */ 19 | public UidFetchCommand(@Nonnull final MessageNumberSet[] msgsets, @Nonnull final String items) { 20 | super(true, msgsets, items); 21 | } 22 | 23 | /** 24 | * Initializes a {@link UidFetchCommand} with the {@link MessageNumberSet} array. 25 | * 26 | * @param msgsets the set of message set 27 | * @param items the data items 28 | * @param partialExtUidFetchInfo partial extension uid fetch info 29 | */ 30 | public UidFetchCommand(@Nonnull final MessageNumberSet[] msgsets, @Nonnull final String items, 31 | @Nonnull final PartialExtensionUidFetchInfo partialExtUidFetchInfo) { 32 | super(true, msgsets, items, partialExtUidFetchInfo); 33 | } 34 | 35 | /** 36 | * Initializes a {@link UidFetchCommand} with the {@link MessageNumberSet} array. 37 | * 38 | * @param msgsets the set of message set 39 | * @param macro the macro, for example, ALL 40 | */ 41 | public UidFetchCommand(@Nonnull final MessageNumberSet[] msgsets, @Nonnull final FetchMacro macro) { 42 | super(true, msgsets, macro); 43 | } 44 | 45 | /** 46 | * Initializes a {@link UidFetchCommand} with the {@link MessageNumberSet} array. 47 | * 48 | * @param msgsets the set of message set 49 | * @param macro the macro, for example, ALL 50 | * @param partialExtUidFetchInfo partial extension uid fetch info 51 | */ 52 | public UidFetchCommand(@Nonnull final MessageNumberSet[] msgsets, @Nonnull final FetchMacro macro, 53 | @Nonnull final PartialExtensionUidFetchInfo partialExtUidFetchInfo) { 54 | super(true, msgsets, macro, partialExtUidFetchInfo); 55 | } 56 | 57 | /** 58 | * Initializes a {@link UidFetchCommand} with string form uids and data items. 59 | * 60 | * @param uids the UID string following the RFC3501 syntax. For ex:3857529045,3857529047:3857529065 61 | * @param items the data items 62 | */ 63 | public UidFetchCommand(@Nonnull final String uids, @Nonnull final String items) { 64 | super(true, uids, items, null); 65 | } 66 | 67 | /** 68 | * Initializes a {@link UidFetchCommand} with string form uids and data items. 69 | * 70 | * @param uids the UID string following the RFC3501 syntax. For ex:3857529045,3857529047:3857529065 71 | * @param items the data items 72 | * @param partialExtUidFetchInfo partial extension uid fetch info 73 | */ 74 | public UidFetchCommand(@Nonnull final String uids, @Nonnull final String items, 75 | @Nonnull final PartialExtensionUidFetchInfo partialExtUidFetchInfo) { 76 | super(true, uids, items, partialExtUidFetchInfo); 77 | } 78 | 79 | /** 80 | * Initializes a {@link UidFetchCommand} with string form uids and macro. 81 | * 82 | * @param uids the UID string following the RFC3501 syntax. For ex:3857529045,3857529047:3857529065 83 | * @param macro the macro, for example, ALL 84 | */ 85 | public UidFetchCommand(@Nonnull final String uids, @Nonnull final FetchMacro macro) { 86 | super(true, uids, macro, null); 87 | } 88 | 89 | /** 90 | * Initializes a {@link UidFetchCommand} with string form uids and macro. 91 | * 92 | * @param uids the UID string following the RFC3501 syntax. For ex:3857529045,3857529047:3857529065 93 | * @param macro the macro, for example, ALL 94 | * @param partialExtUidFetchInfo partial extension uid fetch info 95 | */ 96 | public UidFetchCommand(@Nonnull final String uids, @Nonnull final FetchMacro macro, 97 | @Nonnull final PartialExtensionUidFetchInfo partialExtUidFetchInfo) { 98 | super(true, uids, macro, partialExtUidFetchInfo); 99 | } 100 | 101 | @Override 102 | public ImapRFCSupportedCommandType getCommandType() { 103 | return ImapRFCSupportedCommandType.UID_FETCH; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /core/src/test/java/com/yahoo/imapnio/async/request/CapaCommandTest.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.sun.mail.imap.protocol.IMAPResponse; 13 | import com.yahoo.imapnio.async.exception.ImapAsyncClientException; 14 | 15 | /** 16 | * Unit test for {@link CapaCommand}. 17 | */ 18 | public class CapaCommandTest { 19 | 20 | /** Fields to check for cleanup. */ 21 | private Set fieldsToCheck; 22 | 23 | /** 24 | * Setup reflection. 25 | */ 26 | @BeforeClass 27 | public void setUp() { 28 | // Use reflection to get all declared non-primitive non-static fields (We do not care about inherited fields) 29 | final Class classUnderTest = CapaCommand.class; 30 | fieldsToCheck = new HashSet<>(); 31 | for (Class c = classUnderTest; c != null; c = c.getSuperclass()) { 32 | for (final Field declaredField : c.getDeclaredFields()) { 33 | if (!declaredField.getType().isPrimitive() && !Modifier.isStatic(declaredField.getModifiers())) { 34 | declaredField.setAccessible(true); 35 | fieldsToCheck.add(declaredField); 36 | } 37 | } 38 | } 39 | } 40 | 41 | /** 42 | * Tests getCommandLine method. 43 | * 44 | * @throws ImapAsyncClientException will not throw 45 | * @throws IllegalAccessException will not throw 46 | * @throws IllegalArgumentException will not throw 47 | */ 48 | @Test 49 | public void testGetCommandLine() throws ImapAsyncClientException, IllegalArgumentException, IllegalAccessException { 50 | final ImapRequest cmd = new CapaCommand(); 51 | Assert.assertEquals(cmd.getCommandLine(), "CAPABILITY\r\n", "Expected result mismatched."); 52 | Assert.assertFalse(cmd.isCommandLineDataSensitive(), "isCommandLineDataSensitive() result mismatched."); 53 | Assert.assertNull(cmd.getDebugData(), "getLogData() mismatched."); 54 | 55 | cmd.cleanup(); 56 | // Verify if cleanup happened correctly. 57 | for (final Field field : fieldsToCheck) { 58 | Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null"); 59 | } 60 | } 61 | 62 | /** 63 | * Tests getStreamingResponsesQueue method. 64 | */ 65 | @Test 66 | public void testGetStreamingResponsesQueue() { 67 | final ImapRequest cmd = new CapaCommand(); 68 | Assert.assertNull(cmd.getStreamingResponsesQueue(), "Expected result mismatched."); 69 | } 70 | 71 | /** 72 | * Tests getNextCommandLineAfterContinuation method. 73 | */ 74 | @Test 75 | public void testGetNextCommandLineAfterContinuation() { 76 | final ImapRequest cmd = new CapaCommand(); 77 | final IMAPResponse serverResponse = null; // null or not null does not matter 78 | ImapAsyncClientException ex = null; 79 | try { 80 | cmd.getNextCommandLineAfterContinuation(serverResponse); 81 | } catch (final ImapAsyncClientException imapAsyncEx) { 82 | ex = imapAsyncEx; 83 | } 84 | Assert.assertNotNull(ex, "Expect exception to be thrown."); 85 | Assert.assertEquals(ex.getFailureType(), ImapAsyncClientException.FailureType.OPERATION_NOT_SUPPORTED_FOR_COMMAND, 86 | "Expected result mismatched."); 87 | } 88 | 89 | /** 90 | * Tests getTerminateCommandLine method. 91 | */ 92 | @Test 93 | public void testGetTerminateCommandLine() { 94 | final ImapRequest cmd = new CapaCommand(); 95 | ImapAsyncClientException ex = null; 96 | try { 97 | cmd.getTerminateCommandLine(); 98 | } catch (final ImapAsyncClientException imapAsyncEx) { 99 | ex = imapAsyncEx; 100 | } 101 | Assert.assertNotNull(ex, "Expect exception to be thrown."); 102 | Assert.assertEquals(ex.getFailureType(), ImapAsyncClientException.FailureType.OPERATION_NOT_SUPPORTED_FOR_COMMAND, 103 | "Expected result mismatched."); 104 | } 105 | 106 | /** 107 | * Tests getCommandType method. 108 | */ 109 | @Test 110 | public void testGetCommandType() { 111 | final ImapRequest cmd = new CapaCommand(); 112 | Assert.assertSame(cmd.getCommandType(), ImapRFCSupportedCommandType.CAPABILITY); 113 | } 114 | } --------------------------------------------------------------------------------