diff --git a/.gitignore b/.gitignore index 5416c39e1a..32737cab1c 100644 --- a/.gitignore +++ b/.gitignore @@ -49,7 +49,7 @@ nb-configuration.xml ### Intellij ### # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm -/*.iml +*.iml ## Directory-based project format: .idea/ diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/ImapHandler.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/ImapHandler.java index 429498700c..feadb084a0 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/ImapHandler.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/ImapHandler.java @@ -1,108 +1,12 @@ -/* - * Copyright (c) 2014 Wael Chatila / Icegreen Technologies. All Rights Reserved. - * This software is released under the Apache license 2.0 - * This file has been modified by the copyright holder. - * Original file can be found at http://james.apache.org - */ -package com.icegreen.greenmail.imap; - -import com.icegreen.greenmail.server.BuildInfo; -import com.icegreen.greenmail.server.ProtocolHandler; -import com.icegreen.greenmail.user.UserManager; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.*; -import java.net.Socket; - -/** - * The handler class for IMAP connections. - * - * @author Federico Barbieri - * @author Peter M. Goldstein - */ -public class ImapHandler implements ImapConstants, ProtocolHandler { - protected final Logger log = LoggerFactory.getLogger(getClass()); - private ImapRequestHandler requestHandler = new ImapRequestHandler(); - private ImapSession session; - private final Object closeMonitor = new Object(); - - /** - * The TCP/IP socket over which the IMAP interaction - * is occurring - */ - private Socket socket; - - private ImapResponse response; - - UserManager userManager; - private ImapHostManager imapHost; - - public ImapHandler(UserManager userManager, ImapHostManager imapHost, Socket socket) { - this.userManager = userManager; - this.imapHost = imapHost; - this.socket = socket; - } - - public void forceConnectionClose(final String message) { - response.byeResponse(message); - close(); - } - - @Override - public void run() { - // Closed automatically when socket is closed via #close() - try (InputStream ins = new BufferedInputStream(socket.getInputStream(), 512); - OutputStream outs = new BufferedOutputStream(socket.getOutputStream(), 1024) - ) { - - response = new ImapResponse(outs); - - // Write welcome message - String responseBuffer = VERSION + " Server GreenMail v" + - BuildInfo.INSTANCE.getProjectVersion() + " ready"; - response.okResponse(null, responseBuffer); - - session = new ImapSessionImpl(imapHost, - userManager, - this, - socket.getInetAddress().getHostAddress()); - - while (requestHandler.handleRequest(ins, outs, session)) { - // Loop ... - } - } catch (Exception e) { - log.error("Can not handle IMAP connection", e); - throw new IllegalStateException("Can not handle IMAP connection", e); - } finally { - close(); - } - } - - /** - * Resets the handler data to a basic state. - */ - @Override - public void close() { - // Use monitor to avoid race between external close and handler thread run() - synchronized (closeMonitor) { - // Close and clear streams, sockets etc. - if (socket != null) { - try { - // Terminates thread blocking on socket read - // and automatically closed depending streams - socket.close(); - } catch (IOException e) { - log.warn("Can not close socket", e); - } finally { - socket = null; - } - } - - // Clear user data - session = null; - response = null; - } - } -} - +package com.icegreen.greenmail.imap; + +import com.icegreen.greenmail.server.ProtocolHandler; +import com.icegreen.greenmail.user.UserManager; + +import java.net.Socket; + +public interface ImapHandler extends ProtocolHandler { + ImapHandler init(UserManager userManager, ImapHostManager imapHost, Socket socket); + + void forceConnectionClose(String message); +} diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/ImapHandlerImpl.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/ImapHandlerImpl.java new file mode 100644 index 0000000000..bf90f1676a --- /dev/null +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/ImapHandlerImpl.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2014 Wael Chatila / Icegreen Technologies. All Rights Reserved. + * This software is released under the Apache license 2.0 + * This file has been modified by the copyright holder. + * Original file can be found at http://james.apache.org + */ +package com.icegreen.greenmail.imap; + +import com.icegreen.greenmail.server.BuildInfo; +import com.icegreen.greenmail.server.ProtocolHandler; +import com.icegreen.greenmail.user.UserManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.net.Socket; + +/** + * The handler class for IMAP connections. + * + * @author Federico Barbieri + * @author Peter M. Goldstein + */ +public class ImapHandlerImpl implements ImapConstants, ProtocolHandler, ImapHandler { + protected final Logger log = LoggerFactory.getLogger(getClass()); + private ImapRequestHandler requestHandler = new ImapRequestHandler(); + private ImapSession session; + private final Object closeMonitor = new Object(); + + /** + * The TCP/IP socket over which the IMAP interaction + * is occurring + */ + private Socket socket; + + private ImapResponse response; + + UserManager userManager; + private ImapHostManager imapHost; + + @Override + public ImapHandler init(UserManager userManager, ImapHostManager imapHost, Socket socket) { + this.userManager = userManager; + this.imapHost = imapHost; + this.socket = socket; + return this; + } + + @Override + public void forceConnectionClose(final String message) { + response.byeResponse(message); + close(); + } + + @Override + public void run() { + // Closed automatically when socket is closed via #close() + try (InputStream ins = new BufferedInputStream(socket.getInputStream(), 512); + OutputStream outs = new BufferedOutputStream(socket.getOutputStream(), 1024) + ) { + + response = new ImapResponse(outs); + + // Write welcome message + String responseBuffer = VERSION + " Server GreenMail v" + + BuildInfo.INSTANCE.getProjectVersion() + " ready"; + response.okResponse(null, responseBuffer); + + session = new ImapSessionImpl(imapHost, + userManager, + this, + socket.getInetAddress().getHostAddress()); + + while (requestHandler.handleRequest(ins, outs, session)) { + // Loop ... + } + } catch (Exception e) { + log.error("Can not handle IMAP connection", e); + throw new IllegalStateException("Can not handle IMAP connection", e); + } finally { + close(); + } + } + + /** + * Resets the handler data to a basic state. + */ + @Override + public void close() { + // Use monitor to avoid race between external close and handler thread run() + synchronized (closeMonitor) { + // Close and clear streams, sockets etc. + if (socket != null) { + try { + // Terminates thread blocking on socket read + // and automatically closed depending streams + socket.close(); + } catch (IOException e) { + log.warn("Can not close socket", e); + } finally { + socket = null; + } + } + + // Clear user data + session = null; + response = null; + } + } + + public ImapRequestHandler getRequestHandler() { + return requestHandler; + } + + public void setRequestHandler(ImapRequestHandler requestHandler) { + this.requestHandler = requestHandler; + } +} + diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/ImapRequestHandler.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/ImapRequestHandler.java index d1f28acef7..97be709242 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/ImapRequestHandler.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/ImapRequestHandler.java @@ -6,7 +6,7 @@ */ package com.icegreen.greenmail.imap; -import com.icegreen.greenmail.imap.commands.CommandParser; +import com.icegreen.greenmail.imap.commands.parsers.CommandParser; import com.icegreen.greenmail.imap.commands.ImapCommand; import com.icegreen.greenmail.imap.commands.ImapCommandFactory; import org.slf4j.Logger; @@ -19,7 +19,7 @@ * @author Darrell DeBoer * @version $Revision: 109034 $ */ -public final class ImapRequestHandler { +public class ImapRequestHandler { protected final Logger log = LoggerFactory.getLogger(getClass()); private ImapCommandFactory imapCommands = new ImapCommandFactory(); private CommandParser parser = new CommandParser(); @@ -96,5 +96,11 @@ private void doProcessRequest(ImapRequestLineReader request, command.process(request, response, session); } + public ImapCommandFactory getImapCommands() { + return imapCommands; + } + public void setImapCommands(ImapCommandFactory imapCommands) { + this.imapCommands = imapCommands; + } } diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/ImapServer.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/ImapServer.java index a6856de0b2..011479e78b 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/ImapServer.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/ImapServer.java @@ -23,7 +23,11 @@ public ImapServer(ServerSetup setup, Managers managers) { @Override protected ProtocolHandler createProtocolHandler(Socket clientSocket) { - return new ImapHandler(managers.getUserManager(), managers.getImapHostManager(), clientSocket); + if (null != setup.getImapHandler()) { + return setup.getImapHandler().init(managers.getUserManager(), managers.getImapHostManager(), clientSocket); + } else { + return new ImapHandlerImpl().init(managers.getUserManager(), managers.getImapHostManager(), clientSocket); + } } @Override diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/AppendCommand.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/AppendCommand.java index bd28709790..9a06d294b9 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/AppendCommand.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/AppendCommand.java @@ -10,6 +10,7 @@ import com.icegreen.greenmail.imap.ImapResponse; import com.icegreen.greenmail.imap.ImapSession; import com.icegreen.greenmail.imap.ProtocolException; +import com.icegreen.greenmail.imap.commands.parsers.AppendCommandParser; import com.icegreen.greenmail.store.FolderException; import com.icegreen.greenmail.store.MailFolder; import com.icegreen.greenmail.util.GreenMailUtil; @@ -25,13 +26,13 @@ * @author Darrell DeBoer * @version $Revision: 109034 $ */ -class AppendCommand extends AuthenticatedStateCommand { +public class AppendCommand extends AuthenticatedStateCommand { public static final String NAME = "APPEND"; public static final String ARGS = " [] [] literal"; private AppendCommandParser appendCommandParser = new AppendCommandParser(); - AppendCommand() { + public AppendCommand() { super(NAME, ARGS); } @@ -68,58 +69,6 @@ protected void doProcess(ImapRequestLineReader request, session.unsolicitedResponses(response); response.commandComplete(this, "APPENDUID" + SP + folder.getUidValidity() + SP + uid); } - - private static class AppendCommandParser extends CommandParser { - /** - * If the next character in the request is a '(', tries to read - * a "flag_list" argument from the request. If not, returns a - * MessageFlags with no flags set. - */ - public Flags optionalAppendFlags(ImapRequestLineReader request) - throws ProtocolException { - char next = request.nextWordChar(); - if (next == '(') { - return flagList(request); - } else { - return null; - } - } - - /** - * If the next character in the request is a '"', tries to read - * a DateTime argument. If not, returns null. - */ - public Date optionalDateTime(ImapRequestLineReader request) - throws ProtocolException { - char next = request.nextWordChar(); - if (next == '"') { - return dateTime(request); - } else { - return null; - } - } - - /** - * Reads a MimeMessage encoded as a string literal from the request. - * TODO shouldn't need to read as a string and write out bytes - * use FixedLengthInputStream instead. Hopefully it can then be dynamic. - * - * @param request The Imap APPEND request - * @return A MimeMessage read off the request. - */ - public MimeMessage mimeMessage(ImapRequestLineReader request) - throws ProtocolException { - request.nextWordChar(); - byte[] mail = consumeLiteralAsBytes(request); - - try { - return GreenMailUtil.newMimeMessage(new ByteArrayInputStream(mail)); - } catch (Exception e) { - throw new ProtocolException("Can not create new mime message", e); - } - } - - } } /* diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/AuthenticateCommand.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/AuthenticateCommand.java index b86c7fa22b..0519401c29 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/AuthenticateCommand.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/AuthenticateCommand.java @@ -17,11 +17,11 @@ * @author Darrell DeBoer * @version $Revision: 109034 $ */ -class AuthenticateCommand extends NonAuthenticatedStateCommand { +public class AuthenticateCommand extends NonAuthenticatedStateCommand { public static final String NAME = "AUTHENTICATE"; public static final String ARGS = " *(CRLF base64)"; - AuthenticateCommand() { + public AuthenticateCommand() { super(NAME, ARGS); } diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/AuthenticatedStateCommand.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/AuthenticatedStateCommand.java index 041cd22227..02eb48e41a 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/AuthenticatedStateCommand.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/AuthenticatedStateCommand.java @@ -14,9 +14,9 @@ * @author Darrell DeBoer * @version $Revision: 109034 $ */ -abstract class AuthenticatedStateCommand extends CommandTemplate { +public abstract class AuthenticatedStateCommand extends CommandTemplate { - AuthenticatedStateCommand(String name, String argSyntax) { + public AuthenticatedStateCommand(String name, String argSyntax) { super(name, argSyntax); } diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/CapabilityCommand.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/CapabilityCommand.java index d451ee28a7..03909e44eb 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/CapabilityCommand.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/CapabilityCommand.java @@ -18,13 +18,13 @@ * @author Darrell DeBoer * @version $Revision: 109034 $ */ -class CapabilityCommand extends CommandTemplate { +public class CapabilityCommand extends CommandTemplate { public static final String NAME = "CAPABILITY"; public static final String ARGS = null; public static final String CAPABILITY_RESPONSE = NAME + SP + VERSION + SP + CAPABILITIES; - CapabilityCommand() { + public CapabilityCommand() { super(NAME, ARGS); } diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/CheckCommand.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/CheckCommand.java index 2c26b9de3c..7d22b96771 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/CheckCommand.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/CheckCommand.java @@ -18,11 +18,11 @@ * @author Darrell DeBoer * @version $Revision: 109034 $ */ -class CheckCommand extends SelectedStateCommand { +public class CheckCommand extends SelectedStateCommand { public static final String NAME = "CHECK"; public static final String ARGS = null; - CheckCommand() { + public CheckCommand() { super(NAME, ARGS); } diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/CloseCommand.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/CloseCommand.java index 557d75b6ad..5379db8b7a 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/CloseCommand.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/CloseCommand.java @@ -19,10 +19,10 @@ * @author Darrell DeBoer * @version $Revision: 109034 $ */ -class CloseCommand extends SelectedStateCommand { +public class CloseCommand extends SelectedStateCommand { public static final String NAME = "CLOSE"; - CloseCommand() { + public CloseCommand() { super(NAME, null); } diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/CommandTemplate.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/CommandTemplate.java index fb47d42115..23ad2d7b7d 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/CommandTemplate.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/CommandTemplate.java @@ -7,6 +7,7 @@ package com.icegreen.greenmail.imap.commands; import com.icegreen.greenmail.imap.*; +import com.icegreen.greenmail.imap.commands.parsers.CommandParser; import com.icegreen.greenmail.store.FolderException; import com.icegreen.greenmail.store.MailFolder; import org.slf4j.Logger; @@ -19,14 +20,14 @@ * @author Darrell DeBoer * @version $Revision: 109034 $ */ -abstract class CommandTemplate +public abstract class CommandTemplate implements ImapCommand, ImapConstants { protected final Logger log = LoggerFactory.getLogger(getClass()); protected CommandParser parser = new CommandParser(); private String name; private String argSyntax; - CommandTemplate(String name, String argSyntax) { + public CommandTemplate(String name, String argSyntax) { this.name = name; this.argSyntax = argSyntax; } diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/CopyCommand.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/CopyCommand.java index 5455ffcd9a..d4b905d17f 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/CopyCommand.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/CopyCommand.java @@ -19,11 +19,11 @@ * @author Darrell DeBoer * @version $Revision: 109034 $ */ -class CopyCommand extends SelectedStateCommand implements UidEnabledCommand { +public class CopyCommand extends SelectedStateCommand implements UidEnabledCommand { public static final String NAME = "COPY"; public static final String ARGS = " "; - CopyCommand() { + public CopyCommand() { super(NAME, ARGS); } diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/CreateCommand.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/CreateCommand.java index 9e3c37d51e..ec3782ddfb 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/CreateCommand.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/CreateCommand.java @@ -15,11 +15,11 @@ * @author Darrell DeBoer * @version $Revision: 109034 $ */ -class CreateCommand extends AuthenticatedStateCommand { +public class CreateCommand extends AuthenticatedStateCommand { public static final String NAME = "CREATE"; public static final String ARGS = ""; - CreateCommand() { + public CreateCommand() { super(NAME, ARGS); } diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/DeleteCommand.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/DeleteCommand.java index e09f97caee..9aaf39bc8e 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/DeleteCommand.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/DeleteCommand.java @@ -16,11 +16,11 @@ * @author Darrell DeBoer * @version $Revision: 109034 $ */ -class DeleteCommand extends AuthenticatedStateCommand { +public class DeleteCommand extends AuthenticatedStateCommand { public static final String NAME = "DELETE"; public static final String ARGS = ""; - DeleteCommand() { + public DeleteCommand() { super(NAME, ARGS); } diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/ExamineCommand.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/ExamineCommand.java index 206f5918fa..01eca1ee04 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/ExamineCommand.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/ExamineCommand.java @@ -10,10 +10,10 @@ * @author Darrell DeBoer * @version $Revision: 109034 $ */ -class ExamineCommand extends SelectCommand { +public class ExamineCommand extends SelectCommand { public static final String NAME = "EXAMINE"; - ExamineCommand() { + public ExamineCommand() { super(NAME); } } diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/ExpungeCommand.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/ExpungeCommand.java index c4d3216473..4c760e7138 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/ExpungeCommand.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/ExpungeCommand.java @@ -21,10 +21,10 @@ * @author Darrell DeBoer * @version $Revision: 109034 $ */ -class ExpungeCommand extends SelectedStateCommand implements UidEnabledCommand { +public class ExpungeCommand extends SelectedStateCommand implements UidEnabledCommand { public static final String NAME = "EXPUNGE"; - ExpungeCommand() { + public ExpungeCommand() { super(NAME, ""); } diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/FetchCommand.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/FetchCommand.java index 8ed18969e6..11f405a8cb 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/FetchCommand.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/FetchCommand.java @@ -7,6 +7,10 @@ package com.icegreen.greenmail.imap.commands; import com.icegreen.greenmail.imap.*; +import com.icegreen.greenmail.imap.commands.parsers.FetchCommandParser; +import com.icegreen.greenmail.imap.commands.parsers.fetch.BodyFetchElement; +import com.icegreen.greenmail.imap.commands.parsers.fetch.FetchRequest; +import com.icegreen.greenmail.imap.commands.parsers.fetch.Partial; import com.icegreen.greenmail.store.FolderException; import com.icegreen.greenmail.store.MessageFlags; import com.icegreen.greenmail.store.StoredMessage; @@ -32,7 +36,7 @@ * @author Darrell DeBoer * @version $Revision: 109034 $ */ -class FetchCommand extends SelectedStateCommand implements UidEnabledCommand { +public class FetchCommand extends SelectedStateCommand implements UidEnabledCommand { public static final String NAME = "FETCH"; public static final String ARGS = " "; private static final Flags FLAGS_SEEN = new Flags(Flags.Flag.SEEN); @@ -40,7 +44,7 @@ class FetchCommand extends SelectedStateCommand implements UidEnabledCommand { private FetchCommandParser fetchParser = new FetchCommandParser(); - FetchCommand() { + public FetchCommand() { super(NAME, ARGS); } @@ -63,7 +67,7 @@ public void doProcess(ImapRequestLineReader request, fetchParser.endLine(request); if (useUids) { - fetch.uid = true; + fetch.setUid(true); } ImapSessionFolder mailbox = session.getSelected(); @@ -112,13 +116,13 @@ private String outputMessage(FetchRequest fetch, StoredMessage message, StringBuilder response = new StringBuilder(); // FLAGS response - if (fetch.flags || ensureFlagsResponse) { + if (fetch.isFlags() || ensureFlagsResponse) { response.append(" FLAGS "); response.append(MessageFlags.format(message.getFlags())); } // INTERNALDATE response - if (fetch.internalDate) { + if (fetch.isInternalDate()) { response.append(" INTERNALDATE \""); // TODO format properly response.append(message.getAttributes().getReceivedDateAsString()); @@ -126,31 +130,31 @@ private String outputMessage(FetchRequest fetch, StoredMessage message, } // RFC822.SIZE response - if (fetch.size) { + if (fetch.isSize()) { response.append(" RFC822.SIZE "); response.append(message.getAttributes().getSize()); } // ENVELOPE response - if (fetch.envelope) { + if (fetch.isEnvelope()) { response.append(" ENVELOPE "); response.append(message.getAttributes().getEnvelope()); } // BODY response - if (fetch.body) { + if (fetch.isBody()) { response.append(" BODY "); response.append(message.getAttributes().getBodyStructure(false)); } // BODYSTRUCTURE response - if (fetch.bodyStructure) { + if (fetch.isBodyStructure()) { response.append(" BODYSTRUCTURE "); response.append(message.getAttributes().getBodyStructure(true)); } // UID response - if (fetch.uid) { + if (fetch.isUid()) { response.append(" UID "); response.append(message.getUid()); } @@ -287,7 +291,7 @@ private byte[] doPartial(Partial partial, byte[] bytes, StringBuilder response) byte[] newBytes = new byte[len]; System.arraycopy(bytes, start, newBytes, 0, len); bytes = newBytes; - response.append('<').append(partial.start).append('>'); + response.append('<').append(partial.getStart()).append('>'); } return bytes; } @@ -340,7 +344,7 @@ private void addHeaders(Enumeration inum, StringBuilder response, Partial par int len = partial.computeLength(partialContent.length()); // TODO : Charset? int start = partial.computeStart(partialContent.length()); - response.append('<').append(partial.start).append('>'); + response.append('<').append(partial.getStart()).append('>'); response.append(" {"); response.append(len); response.append('}'); @@ -356,231 +360,6 @@ private void addHeaders(Enumeration inum, StringBuilder response, Partial par response.append(buf); } } - - private static class FetchCommandParser extends CommandParser { - - FetchRequest fetchRequest(ImapRequestLineReader request) - throws ProtocolException { - FetchRequest fetch = new FetchRequest(); - - // Parenthesis optional if single 'atom' - char next = nextNonSpaceChar(request); - boolean parenthesis = '(' == next; - if (parenthesis) { - consumeChar(request, '('); - - next = nextNonSpaceChar(request); - while (next != ')') { - addNextElement(request, fetch); - next = nextNonSpaceChar(request); - } - - consumeChar(request, ')'); - } else { - // Single item - addNextElement(request, fetch); - } - - return fetch; - } - - private void addNextElement(ImapRequestLineReader command, FetchRequest fetch) - throws ProtocolException { - char next = nextCharInLine(command); - StringBuilder element = new StringBuilder(); - while (next != ' ' && next != '[' && next != ')' && !isCrOrLf(next)) { - element.append(next); - command.consume(); - next = command.nextChar(); - } - String name = element.toString(); - // Simple elements with no '[]' parameters. - if (next == ' ' || next == ')' || isCrOrLf(next)) { - if ("FAST".equalsIgnoreCase(name)) { - fetch.flags = true; - fetch.internalDate = true; - fetch.size = true; - } else if ("FULL".equalsIgnoreCase(name)) { - fetch.flags = true; - fetch.internalDate = true; - fetch.size = true; - fetch.envelope = true; - fetch.body = true; - } else if ("ALL".equalsIgnoreCase(name)) { - fetch.flags = true; - fetch.internalDate = true; - fetch.size = true; - fetch.envelope = true; - } else if ("FLAGS".equalsIgnoreCase(name)) { - fetch.flags = true; - } else if ("RFC822.SIZE".equalsIgnoreCase(name)) { - fetch.size = true; - } else if ("ENVELOPE".equalsIgnoreCase(name)) { - fetch.envelope = true; - } else if ("INTERNALDATE".equalsIgnoreCase(name)) { - fetch.internalDate = true; - } else if ("BODY".equalsIgnoreCase(name)) { - fetch.body = true; - } else if ("BODYSTRUCTURE".equalsIgnoreCase(name)) { - fetch.bodyStructure = true; - } else if ("UID".equalsIgnoreCase(name)) { - fetch.uid = true; - } else if ("RFC822".equalsIgnoreCase(name)) { - fetch.add(new BodyFetchElement("RFC822", ""), false); - } else if ("RFC822.HEADER".equalsIgnoreCase(name)) { - fetch.add(new BodyFetchElement("RFC822.HEADER", "HEADER"), true); - } else if ("RFC822.TEXT".equalsIgnoreCase(name)) { - fetch.add(new BodyFetchElement("RFC822.TEXT", "TEXT"), false); - } else { - throw new ProtocolException("Invalid fetch attribute: " + name); - } - } else { - consumeChar(command, '['); - - StringBuilder sectionIdentifier = new StringBuilder(); - next = nextCharInLine(command); - while (next != ']') { - sectionIdentifier.append(next); - command.consume(); - next = nextCharInLine(command); - } - consumeChar(command, ']'); - - String parameter = sectionIdentifier.toString(); - - Partial partial = null; - next = command.nextChar(); // Can be end of line if single option - if ('<' == next) { // Partial eg <2000> or <0.1000> - partial = parsePartial(command); - } - - if ("BODY".equalsIgnoreCase(name)) { - fetch.add(new BodyFetchElement("BODY[" + parameter + ']', parameter, partial), false); - } else if ("BODY.PEEK".equalsIgnoreCase(name)) { - fetch.add(new BodyFetchElement("BODY[" + parameter + ']', parameter, partial), true); - } else { - throw new ProtocolException("Invalid fetch attribute: " + name + "[]"); - } - } - } - - private Partial parsePartial(ImapRequestLineReader command) throws ProtocolException { - consumeChar(command, '<'); - int size = (int) consumeLong(command); // Assume - int start = 0; - if (command.nextChar() == '.') { - consumeChar(command, '.'); - start = size; // Assume , so switch fields - size = (int) consumeLong(command); - } - consumeChar(command, '>'); - return Partial.as(start, size); - } - - private char nextCharInLine(ImapRequestLineReader request) - throws ProtocolException { - char next = request.nextChar(); - if (isCrOrLf(next)) { - request.dumpLine(); - throw new ProtocolException("Unexpected end of line (CR or LF)."); - } - return next; - } - - private char nextNonSpaceChar(ImapRequestLineReader request) - throws ProtocolException { - char next = request.nextChar(); - while (next == ' ') { - request.consume(); - next = request.nextChar(); - } - return next; - } - - } - - private static class FetchRequest { - boolean flags; - boolean uid; - boolean internalDate; - boolean size; - boolean envelope; - boolean body; - boolean bodyStructure; - - private boolean setSeen = false; - - private Set bodyElements = new HashSet<>(); - - public Collection getBodyElements() { - return bodyElements; - } - - public boolean isSetSeen() { - return setSeen; - } - - public void add(BodyFetchElement element, boolean peek) { - if (!peek) { - setSeen = true; - } - bodyElements.add(element); - } - } - /** See https://tools.ietf.org/html/rfc3501#page-55 : partial */ - private static class Partial { - int start; - int size; - - int computeLength(final int contentSize) { - if ( size > 0) { - return Math.min(size, contentSize - start); // Only up to max available bytes - } else { - // First len bytes - return contentSize; - } - } - - int computeStart(final int contentSize) { - return Math.min(start, contentSize); - } - - public static Partial as(int start, int size) { - Partial p = new Partial(); - p.start = start; - p.size = size; - return p; - } - } - - private static class BodyFetchElement { - private String name; - private String sectionIdentifier; - private Partial partial; - - public BodyFetchElement(String name, String sectionIdentifier) { - this(name, sectionIdentifier, null); - } - - public BodyFetchElement(String name, String sectionIdentifier, Partial partial) { - this.name = name; - this.sectionIdentifier = sectionIdentifier; - this.partial = partial; - } - - public String getParameters() { - return this.sectionIdentifier; - } - - public String getResponseName() { - return this.name; - } - - public Partial getPartial() { - return partial; - } - } - } /* diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/ImapCommandFactory.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/ImapCommandFactory.java index 89988ececc..4bcf42ab61 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/ImapCommandFactory.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/ImapCommandFactory.java @@ -97,4 +97,7 @@ private ImapCommand createCommand(Class commandClass) { } } + protected Map> getImapCommands() { + return imapCommands; + } } diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/ListCommand.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/ListCommand.java index 0e41d91dc7..b7cd36addc 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/ListCommand.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/ListCommand.java @@ -10,6 +10,7 @@ import com.icegreen.greenmail.imap.ImapResponse; import com.icegreen.greenmail.imap.ImapSession; import com.icegreen.greenmail.imap.ProtocolException; +import com.icegreen.greenmail.imap.commands.parsers.ListCommandParser; import com.icegreen.greenmail.store.FolderException; import com.icegreen.greenmail.store.MailFolder; import com.sun.mail.imap.protocol.BASE64MailboxDecoder; // NOSONAR @@ -24,17 +25,17 @@ * @author Darrell DeBoer * @version $Revision: 109034 $ */ -class ListCommand extends AuthenticatedStateCommand { +public class ListCommand extends AuthenticatedStateCommand { public static final String NAME = "LIST"; public static final String ARGS = " "; private ListCommandParser listParser = new ListCommandParser(); - ListCommand() { + public ListCommand() { super(NAME, ARGS); } - ListCommand(String name) { + public ListCommand(String name) { super(name, null); } @@ -162,38 +163,6 @@ private String combineSearchTerms(String referenceName, String mailboxMatch) { buffer.insert(0, referenceName); return buffer.toString(); } - - private static class ListCommandParser extends CommandParser { - /** - * Reads an argument of type "list_mailbox" from the request, which is - * the second argument for a LIST or LSUB command. Valid values are a "string" - * argument, an "atom" with wildcard characters. - * - * @return An argument of type "list_mailbox" - */ - public String listMailbox(ImapRequestLineReader request) throws ProtocolException { - char next = request.nextWordChar(); - String name; - switch (next) { - case '"': - name = consumeQuoted(request); - break; - case '{': - name = consumeLiteral(request); - break; - default: - name = consumeWord(request, new ListCharValidator()); - } - return BASE64MailboxDecoder.decode(name); - } - - private class ListCharValidator extends AtomCharValidator { - @Override - public boolean isValid(char chr) { - return isListWildcard(chr) || super.isValid(chr); - } - } - } } /* diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/LoginCommand.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/LoginCommand.java index b4dc50633f..2096af0135 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/LoginCommand.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/LoginCommand.java @@ -18,11 +18,11 @@ * @author Darrell DeBoer * @version $Revision: 109034 $ */ -class LoginCommand extends NonAuthenticatedStateCommand { +public class LoginCommand extends NonAuthenticatedStateCommand { public static final String NAME = "LOGIN"; public static final String ARGS = " "; - LoginCommand() { + public LoginCommand() { super(NAME, ARGS); } diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/LogoutCommand.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/LogoutCommand.java index 6c96ad8951..e033e204d6 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/LogoutCommand.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/LogoutCommand.java @@ -17,11 +17,11 @@ * @author Darrell DeBoer * @version $Revision: 109034 $ */ -class LogoutCommand extends CommandTemplate { +public class LogoutCommand extends CommandTemplate { public static final String NAME = "LOGOUT"; public static final String BYE_MESSAGE = VERSION + SP + "Server logging out"; - LogoutCommand() { + public LogoutCommand() { super(NAME, null); } diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/LsubCommand.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/LsubCommand.java index 6a2734afa0..6bf4027a10 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/LsubCommand.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/LsubCommand.java @@ -16,10 +16,10 @@ * @author Darrell DeBoer * @version $Revision: 109034 $ */ -class LsubCommand extends ListCommand { +public class LsubCommand extends ListCommand { public static final String NAME = "LSUB"; - LsubCommand() { + public LsubCommand() { super(NAME); } diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/NonAuthenticatedStateCommand.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/NonAuthenticatedStateCommand.java index 08947d9cf7..6bc30c5406 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/NonAuthenticatedStateCommand.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/NonAuthenticatedStateCommand.java @@ -14,9 +14,9 @@ * @author Darrell DeBoer * @version $Revision: 109034 $ */ -abstract class NonAuthenticatedStateCommand extends CommandTemplate { +public abstract class NonAuthenticatedStateCommand extends CommandTemplate { - NonAuthenticatedStateCommand(String name, String argSyntax) { + public NonAuthenticatedStateCommand(String name, String argSyntax) { super(name, argSyntax); } diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/NoopCommand.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/NoopCommand.java index 73c23f2917..69e6ea30be 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/NoopCommand.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/NoopCommand.java @@ -18,10 +18,10 @@ * @author Darrell DeBoer * @version $Revision: 109034 $ */ -class NoopCommand extends CommandTemplate { +public class NoopCommand extends CommandTemplate { public static final String NAME = "NOOP"; - NoopCommand() { + public NoopCommand() { super(NAME, null); } diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/QuotaCommand.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/QuotaCommand.java index 2b62b9ca58..d86df87105 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/QuotaCommand.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/QuotaCommand.java @@ -19,7 +19,7 @@ public class QuotaCommand extends AuthenticatedStateCommand { public static final String NAME = "QUOTA"; - QuotaCommand() { + public QuotaCommand() { super(NAME, null); } diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/QuotaRootCommand.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/QuotaRootCommand.java index 9592e8e9ce..d415a84b75 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/QuotaRootCommand.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/QuotaRootCommand.java @@ -20,7 +20,7 @@ public class QuotaRootCommand extends QuotaCommand { public static final String NAME = "GETQUOTAROOT"; - QuotaRootCommand() { + public QuotaRootCommand() { super(NAME); } @Override diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/RenameCommand.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/RenameCommand.java index f9277960c8..b3c91c78eb 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/RenameCommand.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/RenameCommand.java @@ -15,11 +15,11 @@ * @author Darrell DeBoer * @version $Revision: 109034 $ */ -class RenameCommand extends AuthenticatedStateCommand { +public class RenameCommand extends AuthenticatedStateCommand { public static final String NAME = "RENAME"; public static final String ARGS = "existing-mailbox-name SPACE new-mailbox-name"; - RenameCommand() { + public RenameCommand() { super(NAME, ARGS); } diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/SearchCommand.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/SearchCommand.java index 6033e5064c..1d31e56095 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/SearchCommand.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/SearchCommand.java @@ -10,6 +10,7 @@ import com.icegreen.greenmail.imap.ImapResponse; import com.icegreen.greenmail.imap.ImapSession; import com.icegreen.greenmail.imap.ProtocolException; +import com.icegreen.greenmail.imap.commands.parsers.SearchCommandParser; import com.icegreen.greenmail.store.FolderException; import com.icegreen.greenmail.store.MailFolder; @@ -21,13 +22,13 @@ * * @author Darrell DeBoer */ -class SearchCommand extends SelectedStateCommand implements UidEnabledCommand { +public class SearchCommand extends SelectedStateCommand implements UidEnabledCommand { public static final String NAME = "SEARCH"; public static final String ARGS = ""; private SearchCommandParser searchParser = new SearchCommandParser(); - SearchCommand() { + public SearchCommand() { super(NAME, ARGS); } diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/SelectCommand.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/SelectCommand.java index 92212eb231..59eb031491 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/SelectCommand.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/SelectCommand.java @@ -16,11 +16,11 @@ * @author Darrell DeBoer * @version $Revision: 109034 $ */ -class SelectCommand extends AuthenticatedStateCommand { +public class SelectCommand extends AuthenticatedStateCommand { public static final String NAME = "SELECT"; public static final String ARGS = "mailbox"; - SelectCommand() { + public SelectCommand() { super(NAME, ARGS); } diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/SelectedStateCommand.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/SelectedStateCommand.java index 56d4e4aaad..d9a8dcd338 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/SelectedStateCommand.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/SelectedStateCommand.java @@ -14,9 +14,9 @@ * @author Darrell DeBoer * @version $Revision: 109034 $ */ -abstract class SelectedStateCommand extends CommandTemplate { +public abstract class SelectedStateCommand extends CommandTemplate { - SelectedStateCommand(String name, String argSyntax) { + public SelectedStateCommand(String name, String argSyntax) { super(name, argSyntax); } diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/SetQuotaCommand.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/SetQuotaCommand.java index f8e361d32d..efa99281a2 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/SetQuotaCommand.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/SetQuotaCommand.java @@ -17,7 +17,7 @@ public class SetQuotaCommand extends AuthenticatedStateCommand { public static final String NAME = "SETQUOTA"; - SetQuotaCommand() { + public SetQuotaCommand() { super(NAME, null); } diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/SortCommand.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/SortCommand.java index 122ff574d6..4f8bd8baf1 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/SortCommand.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/SortCommand.java @@ -1,6 +1,7 @@ package com.icegreen.greenmail.imap.commands; import com.icegreen.greenmail.imap.*; +import com.icegreen.greenmail.imap.commands.parsers.SortCommandParser; import com.icegreen.greenmail.store.FolderException; import com.icegreen.greenmail.store.MailFolder; import com.icegreen.greenmail.store.StoredMessage; @@ -15,14 +16,14 @@ * * @author Reda.Housni-Alaoui */ -class SortCommand extends SelectedStateCommand implements UidEnabledCommand { +public class SortCommand extends SelectedStateCommand implements UidEnabledCommand { public static final String NAME = "SORT"; public static final String ARGS = "() "; private SortCommandParser sortCommandParser = new SortCommandParser(); - SortCommand() { + public SortCommand() { super(NAME, ARGS); } diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/SortTerm.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/SortTerm.java index f0e5cf4408..c97150009a 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/SortTerm.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/SortTerm.java @@ -9,7 +9,7 @@ * * @author Reda.Housni-Alaoui */ -class SortTerm { +public class SortTerm { private final List sortCriteria = new ArrayList<>(); private String charset; private SearchTerm searchTerm; diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/StatusCommand.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/StatusCommand.java index d97da3e04c..448db217c8 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/StatusCommand.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/StatusCommand.java @@ -10,6 +10,8 @@ import com.icegreen.greenmail.imap.ImapResponse; import com.icegreen.greenmail.imap.ImapSession; import com.icegreen.greenmail.imap.ProtocolException; +import com.icegreen.greenmail.imap.commands.parsers.StatusCommandParser; +import com.icegreen.greenmail.imap.commands.parsers.status.StatusDataItems; import com.icegreen.greenmail.store.FolderException; import com.icegreen.greenmail.store.MailFolder; import com.sun.mail.imap.protocol.BASE64MailboxEncoder; // NOSONAR @@ -20,19 +22,19 @@ * @author Darrell DeBoer * @version $Revision: 109034 $ */ -class StatusCommand extends AuthenticatedStateCommand { +public class StatusCommand extends AuthenticatedStateCommand { public static final String NAME = "STATUS"; public static final String ARGS = " ( + )"; - private static final String MESSAGES = "MESSAGES"; - private static final String RECENT = "RECENT"; - private static final String UIDNEXT = "UIDNEXT"; - private static final String UIDVALIDITY = "UIDVALIDITY"; - private static final String UNSEEN = "UNSEEN"; + public static final String MESSAGES = "MESSAGES"; + public static final String RECENT = "RECENT"; + public static final String UIDNEXT = "UIDNEXT"; + public static final String UIDVALIDITY = "UIDVALIDITY"; + public static final String UNSEEN = "UNSEEN"; private StatusCommandParser statusParser = new StatusCommandParser(); - StatusCommand() { + public StatusCommand() { super(NAME, ARGS); } @@ -61,35 +63,35 @@ protected void doProcess(ImapRequestLineReader request, buffer.append(SP); buffer.append('('); - if (statusDataItems.messages) { + if (statusDataItems.isMessages()) { buffer.append(MESSAGES); buffer.append(SP); buffer.append(folder.getMessageCount()); buffer.append(SP); } - if (statusDataItems.recent) { + if (statusDataItems.isRecent()) { buffer.append(RECENT); buffer.append(SP); buffer.append(folder.getRecentCount(false)); buffer.append(SP); } - if (statusDataItems.uidNext) { + if (statusDataItems.isUidNext()) { buffer.append(UIDNEXT); buffer.append(SP); buffer.append(folder.getUidNext()); buffer.append(SP); } - if (statusDataItems.uidValidity) { + if (statusDataItems.isUidValidity()) { buffer.append(UIDVALIDITY); buffer.append(SP); buffer.append(folder.getUidValidity()); buffer.append(SP); } - if (statusDataItems.unseen) { + if (statusDataItems.isUnseen()) { buffer.append(UNSEEN); buffer.append(SP); buffer.append(folder.getUnseenCount()); @@ -104,53 +106,6 @@ protected void doProcess(ImapRequestLineReader request, session.unsolicitedResponses(response); response.commandComplete(this); } - - private static class StatusCommandParser extends CommandParser { - StatusDataItems statusDataItems(ImapRequestLineReader request) - throws ProtocolException { - StatusDataItems items = new StatusDataItems(); - - request.nextWordChar(); - consumeChar(request, '('); - CharacterValidator validator = new NoopCharValidator(); - String nextWord = consumeWord(request, validator); - while (!nextWord.endsWith(")")) { - addItem(nextWord, items); - nextWord = consumeWord(request, validator); - } - // Got the closing ")", may be attached to a word. - if (nextWord.length() > 1) { - addItem(nextWord.substring(0, nextWord.length() - 1), items); - } - - return items; - } - - private void addItem(String nextWord, StatusDataItems items) - throws ProtocolException { - if (nextWord.equals(MESSAGES)) { - items.messages = true; - } else if (nextWord.equals(RECENT)) { - items.recent = true; - } else if (nextWord.equals(UIDNEXT)) { - items.uidNext = true; - } else if (nextWord.equals(UIDVALIDITY)) { - items.uidValidity = true; - } else if (nextWord.equals(UNSEEN)) { - items.unseen = true; - } else { - throw new ProtocolException("Unknown status item: '" + nextWord + '\''); - } - } - } - - private static class StatusDataItems { - boolean messages; - boolean recent; - boolean uidNext; - boolean uidValidity; - boolean unseen; - } } /* diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/StoreCommand.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/StoreCommand.java index fd9777cd8f..60fae194aa 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/StoreCommand.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/StoreCommand.java @@ -7,6 +7,8 @@ package com.icegreen.greenmail.imap.commands; import com.icegreen.greenmail.imap.*; +import com.icegreen.greenmail.imap.commands.parsers.StoreCommandParser; +import com.icegreen.greenmail.imap.commands.parsers.store.StoreDirective; import com.icegreen.greenmail.store.FolderException; import com.icegreen.greenmail.store.FolderListener; @@ -19,13 +21,13 @@ * @author Darrell DeBoer * @version $Revision: 109034 $ */ -class StoreCommand extends SelectedStateCommand implements UidEnabledCommand { +public class StoreCommand extends SelectedStateCommand implements UidEnabledCommand { public static final String NAME = "STORE"; public static final String ARGS = " ['+'|'-']FLAG[.SILENT] "; private final StoreCommandParser storeParser = new StoreCommandParser(); - StoreCommand() { + public StoreCommand() { super(NAME, ARGS); } @@ -91,52 +93,6 @@ public void doProcess(ImapRequestLineReader request, session.unsolicitedResponses(response, omitExpunged); response.commandComplete(this); } - - private static class StoreCommandParser extends CommandParser { - StoreDirective storeDirective(ImapRequestLineReader request) throws ProtocolException { - int sign = 0; - boolean silent = false; - - char next = request.nextWordChar(); - if (next == '+') { - sign = 1; - request.consume(); - } else if (next == '-') { - sign = -1; - request.consume(); - } else { - sign = 0; - } - - String directive = consumeWord(request, new NoopCharValidator()); - if ("FLAGS".equalsIgnoreCase(directive)) { - silent = false; - } else if ("FLAGS.SILENT".equalsIgnoreCase(directive)) { - silent = true; - } else { - throw new ProtocolException("Invalid Store Directive: '" + directive + '\''); - } - return new StoreDirective(sign, silent); - } - } - - private static class StoreDirective { - private int sign; - private boolean silent; - - public StoreDirective(int sign, boolean silent) { - this.sign = sign; - this.silent = silent; - } - - public int getSign() { - return sign; - } - - public boolean isSilent() { - return silent; - } - } } /* diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/StoredMessageSorter.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/StoredMessageSorter.java index 61de51227c..073fddb521 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/StoredMessageSorter.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/StoredMessageSorter.java @@ -15,13 +15,13 @@ * * @author Reda.Housni-Alaoui */ -class StoredMessageSorter implements Comparator { +public class StoredMessageSorter implements Comparator { private SortTerm sortTerm; private final AtomicBoolean reverse = new AtomicBoolean(); - StoredMessageSorter(SortTerm sortTerm) { + public StoredMessageSorter(SortTerm sortTerm) { this.sortTerm = sortTerm; } diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/SubscribeCommand.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/SubscribeCommand.java index f6cfb09d9e..59525f84fd 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/SubscribeCommand.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/SubscribeCommand.java @@ -18,10 +18,10 @@ * @author Darrell DeBoer * @version $Revision: 109034 $ */ -class SubscribeCommand extends AuthenticatedStateCommand { +public class SubscribeCommand extends AuthenticatedStateCommand { public static final String NAME = "SUBSCRIBE"; - SubscribeCommand() { + public SubscribeCommand() { super(NAME, ""); } diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/UidCommand.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/UidCommand.java index fdfd65bb86..ef9953d749 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/UidCommand.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/UidCommand.java @@ -18,12 +18,12 @@ * @author Darrell DeBoer * @version $Revision: 109034 $ */ -class UidCommand extends SelectedStateCommand { +public class UidCommand extends SelectedStateCommand { public static final String NAME = "UID"; private ImapCommandFactory commandFactory; - UidCommand() { + public UidCommand() { super(NAME, "||||"); } diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/UnsubscribeCommand.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/UnsubscribeCommand.java index 786e142f41..d518a20165 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/UnsubscribeCommand.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/UnsubscribeCommand.java @@ -18,10 +18,10 @@ * @author Darrell DeBoer * @version $Revision: 109034 $ */ -class UnsubscribeCommand extends AuthenticatedStateCommand { +public class UnsubscribeCommand extends AuthenticatedStateCommand { public static final String NAME = "UNSUBSCRIBE"; - UnsubscribeCommand() { + public UnsubscribeCommand() { super(NAME, ""); } diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/AppendCommandParser.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/AppendCommandParser.java new file mode 100644 index 0000000000..45a20a4098 --- /dev/null +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/AppendCommandParser.java @@ -0,0 +1,62 @@ +package com.icegreen.greenmail.imap.commands.parsers; + +import com.icegreen.greenmail.imap.ImapRequestLineReader; +import com.icegreen.greenmail.imap.ProtocolException; +import com.icegreen.greenmail.util.GreenMailUtil; + +import javax.mail.Flags; +import javax.mail.internet.MimeMessage; +import java.io.ByteArrayInputStream; +import java.util.Date; + +public class AppendCommandParser extends CommandParser { + /** + * If the next character in the request is a '(', tries to read + * a "flag_list" argument from the request. If not, returns a + * MessageFlags with no flags set. + */ + public Flags optionalAppendFlags(ImapRequestLineReader request) + throws ProtocolException { + char next = request.nextWordChar(); + if (next == '(') { + return flagList(request); + } else { + return null; + } + } + + /** + * If the next character in the request is a '"', tries to read + * a DateTime argument. If not, returns null. + */ + public Date optionalDateTime(ImapRequestLineReader request) + throws ProtocolException { + char next = request.nextWordChar(); + if (next == '"') { + return dateTime(request); + } else { + return null; + } + } + + /** + * Reads a MimeMessage encoded as a string literal from the request. + * TODO shouldn't need to read as a string and write out bytes + * use FixedLengthInputStream instead. Hopefully it can then be dynamic. + * + * @param request The Imap APPEND request + * @return A MimeMessage read off the request. + */ + public MimeMessage mimeMessage(ImapRequestLineReader request) + throws ProtocolException { + request.nextWordChar(); + byte[] mail = consumeLiteralAsBytes(request); + + try { + return GreenMailUtil.newMimeMessage(new ByteArrayInputStream(mail)); + } catch (Exception e) { + throw new ProtocolException("Can not create new mime message", e); + } + } + +} \ No newline at end of file diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/CommandParser.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/CommandParser.java similarity index 98% rename from greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/CommandParser.java rename to greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/CommandParser.java index f0eae6f1bc..060ba60aff 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/CommandParser.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/CommandParser.java @@ -4,11 +4,12 @@ * This file has been modified by the copyright holder. * Original file can be found at http://james.apache.org */ -package com.icegreen.greenmail.imap.commands; +package com.icegreen.greenmail.imap.commands.parsers; import com.icegreen.greenmail.imap.ImapConstants; import com.icegreen.greenmail.imap.ImapRequestLineReader; import com.icegreen.greenmail.imap.ProtocolException; +import com.icegreen.greenmail.imap.commands.IdRange; import com.icegreen.greenmail.store.MessageFlags; import com.sun.mail.imap.protocol.BASE64MailboxDecoder; // NOSONAR @@ -234,7 +235,7 @@ private void consumeCRLF(ImapRequestLineReader request) * Consumes the next character in the request, checking that it matches the * expected one. This method should be used when the */ - protected void consumeChar(ImapRequestLineReader request, char expected) + public void consumeChar(ImapRequestLineReader request, char expected) throws ProtocolException { char consumed = request.consume(); if (consumed != expected) { diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/FetchCommandParser.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/FetchCommandParser.java new file mode 100644 index 0000000000..eab6541063 --- /dev/null +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/FetchCommandParser.java @@ -0,0 +1,148 @@ +package com.icegreen.greenmail.imap.commands.parsers; + +import com.icegreen.greenmail.imap.ImapRequestLineReader; +import com.icegreen.greenmail.imap.ProtocolException; +import com.icegreen.greenmail.imap.commands.parsers.fetch.BodyFetchElement; +import com.icegreen.greenmail.imap.commands.parsers.fetch.FetchRequest; +import com.icegreen.greenmail.imap.commands.parsers.fetch.Partial; + +public class FetchCommandParser extends CommandParser { + + public FetchRequest fetchRequest(ImapRequestLineReader request) throws ProtocolException { + FetchRequest fetch = new FetchRequest(); + + // Parenthesis optional if single 'atom' + char next = nextNonSpaceChar(request); + boolean parenthesis = '(' == next; + if (parenthesis) { + consumeChar(request, '('); + + next = nextNonSpaceChar(request); + while (next != ')') { + addNextElement(request, fetch); + next = nextNonSpaceChar(request); + } + + consumeChar(request, ')'); + } else { + // Single item + addNextElement(request, fetch); + } + + return fetch; + } + + private void addNextElement(ImapRequestLineReader command, FetchRequest fetch) + throws ProtocolException { + char next = nextCharInLine(command); + StringBuilder element = new StringBuilder(); + while (next != ' ' && next != '[' && next != ')' && !isCrOrLf(next)) { + element.append(next); + command.consume(); + next = command.nextChar(); + } + String name = element.toString(); + // Simple elements with no '[]' parameters. + if (next == ' ' || next == ')' || isCrOrLf(next)) { + if ("FAST".equalsIgnoreCase(name)) { + fetch.setFlags(true); + fetch.setInternalDate(true); + fetch.setSize(true); + } else if ("FULL".equalsIgnoreCase(name)) { + fetch.setFlags(true); + fetch.setInternalDate(true); + fetch.setSize(true); + fetch.setEnvelope(true); + fetch.setBody(true); + } else if ("ALL".equalsIgnoreCase(name)) { + fetch.setFlags(true); + fetch.setInternalDate(true); + fetch.setSize(true); + fetch.setEnvelope(true); + } else if ("FLAGS".equalsIgnoreCase(name)) { + fetch.setFlags(true); + } else if ("RFC822.SIZE".equalsIgnoreCase(name)) { + fetch.setSize(true); + } else if ("ENVELOPE".equalsIgnoreCase(name)) { + fetch.setEnvelope(true); + } else if ("INTERNALDATE".equalsIgnoreCase(name)) { + fetch.setInternalDate(true); + } else if ("BODY".equalsIgnoreCase(name)) { + fetch.setBody(true); + } else if ("BODYSTRUCTURE".equalsIgnoreCase(name)) { + fetch.setBodyStructure(true); + } else if ("UID".equalsIgnoreCase(name)) { + fetch.setUid(true); + } else if ("RFC822".equalsIgnoreCase(name)) { + fetch.add(new BodyFetchElement("RFC822", ""), false); + } else if ("RFC822.HEADER".equalsIgnoreCase(name)) { + fetch.add(new BodyFetchElement("RFC822.HEADER", "HEADER"), true); + } else if ("RFC822.TEXT".equalsIgnoreCase(name)) { + fetch.add(new BodyFetchElement("RFC822.TEXT", "TEXT"), false); + } else { + throw new ProtocolException("Invalid fetch attribute: " + name); + } + } else { + consumeChar(command, '['); + + StringBuilder sectionIdentifier = new StringBuilder(); + next = nextCharInLine(command); + while (next != ']') { + sectionIdentifier.append(next); + command.consume(); + next = nextCharInLine(command); + } + consumeChar(command, ']'); + + String parameter = sectionIdentifier.toString(); + + Partial partial = null; + next = command.nextChar(); // Can be end of line if single option + if ('<' == next) { // Partial eg <2000> or <0.1000> + partial = parsePartial(command); + } + + if ("BODY".equalsIgnoreCase(name)) { + fetch.add(new BodyFetchElement("BODY[" + parameter + ']', parameter, partial), false); + } else if ("BODY.PEEK".equalsIgnoreCase(name)) { + fetch.add(new BodyFetchElement("BODY[" + parameter + ']', parameter, partial), true); + } else { + throw new ProtocolException("Invalid fetch attribute: " + name + "[]"); + } + } + } + + private Partial parsePartial(ImapRequestLineReader command) throws ProtocolException { + consumeChar(command, '<'); + int size = (int) consumeLong(command); // Assume + int start = 0; + if (command.nextChar() == '.') { + consumeChar(command, '.'); + start = size; // Assume , so switch fields + size = (int) consumeLong(command); + } + consumeChar(command, '>'); + return Partial.as(start, size); + } + + private char nextCharInLine(ImapRequestLineReader request) + throws ProtocolException { + char next = request.nextChar(); + if (isCrOrLf(next)) { + request.dumpLine(); + throw new ProtocolException("Unexpected end of line (CR or LF)."); + } + return next; + } + + private char nextNonSpaceChar(ImapRequestLineReader request) + throws ProtocolException { + char next = request.nextChar(); + while (next == ' ') { + request.consume(); + next = request.nextChar(); + } + return next; + } + +} \ No newline at end of file diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/ListCommandParser.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/ListCommandParser.java new file mode 100644 index 0000000000..ddb39b5be2 --- /dev/null +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/ListCommandParser.java @@ -0,0 +1,37 @@ +package com.icegreen.greenmail.imap.commands.parsers; + +import com.icegreen.greenmail.imap.ImapRequestLineReader; +import com.icegreen.greenmail.imap.ProtocolException; +import com.sun.mail.imap.protocol.BASE64MailboxDecoder; + +public class ListCommandParser extends CommandParser { + /** + * Reads an argument of type "list_mailbox" from the request, which is + * the second argument for a LIST or LSUB command. Valid values are a "string" + * argument, an "atom" with wildcard characters. + * + * @return An argument of type "list_mailbox" + */ + public String listMailbox(ImapRequestLineReader request) throws ProtocolException { + char next = request.nextWordChar(); + String name; + switch (next) { + case '"': + name = consumeQuoted(request); + break; + case '{': + name = consumeLiteral(request); + break; + default: + name = consumeWord(request, new ListCharValidator()); + } + return BASE64MailboxDecoder.decode(name); + } + + private class ListCharValidator extends AtomCharValidator { + @Override + public boolean isValid(char chr) { + return isListWildcard(chr) || super.isValid(chr); + } + } +} \ No newline at end of file diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/SearchCommandParser.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/SearchCommandParser.java similarity index 96% rename from greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/SearchCommandParser.java rename to greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/SearchCommandParser.java index 2942059edc..766243ca4f 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/SearchCommandParser.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/SearchCommandParser.java @@ -4,10 +4,12 @@ * This file has been modified by the copyright holder. * Original file can be found at http://james.apache.org */ -package com.icegreen.greenmail.imap.commands; +package com.icegreen.greenmail.imap.commands.parsers; import com.icegreen.greenmail.imap.ImapRequestLineReader; import com.icegreen.greenmail.imap.ProtocolException; +import com.icegreen.greenmail.imap.commands.SearchKey; +import com.icegreen.greenmail.imap.commands.parsers.search.SearchTermBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,7 +28,7 @@ * * @author Darrell DeBoer */ -class SearchCommandParser extends CommandParser { +public class SearchCommandParser extends CommandParser { private final Logger log = LoggerFactory.getLogger(SearchCommandParser.class); private static final String CHARSET_TOKEN = "CHARSET"; @@ -40,7 +42,7 @@ public SearchTerm searchTerm(ImapRequestLineReader request) SearchTerm resultTerm = null; SearchTermBuilder b = null; SearchKey key = null; - boolean orKey = false; + boolean orKey = false; boolean negated = false; // Dummy implementation // Consume to the end of the line. diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/SortCommandParser.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/SortCommandParser.java similarity index 87% rename from greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/SortCommandParser.java rename to greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/SortCommandParser.java index f3dcdc7571..656b0005fa 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/SortCommandParser.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/SortCommandParser.java @@ -1,7 +1,9 @@ -package com.icegreen.greenmail.imap.commands; +package com.icegreen.greenmail.imap.commands.parsers; import com.icegreen.greenmail.imap.ImapRequestLineReader; import com.icegreen.greenmail.imap.ProtocolException; +import com.icegreen.greenmail.imap.commands.SortKey; +import com.icegreen.greenmail.imap.commands.SortTerm; import java.nio.charset.CharacterCodingException; @@ -10,7 +12,7 @@ * * @author Reda.Housni-Alaoui */ -class SortCommandParser extends CommandParser { +public class SortCommandParser extends CommandParser { private SearchCommandParser searchCommandParser = new SearchCommandParser(); diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/StatusCommandParser.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/StatusCommandParser.java new file mode 100644 index 0000000000..88b22fd204 --- /dev/null +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/StatusCommandParser.java @@ -0,0 +1,45 @@ +package com.icegreen.greenmail.imap.commands.parsers; + +import com.icegreen.greenmail.imap.ImapRequestLineReader; +import com.icegreen.greenmail.imap.ProtocolException; +import com.icegreen.greenmail.imap.commands.StatusCommand; +import com.icegreen.greenmail.imap.commands.parsers.status.StatusDataItems; + +public class StatusCommandParser extends CommandParser { + public StatusDataItems statusDataItems(ImapRequestLineReader request) + throws ProtocolException { + StatusDataItems items = new StatusDataItems(); + + request.nextWordChar(); + consumeChar(request, '('); + CharacterValidator validator = new NoopCharValidator(); + String nextWord = consumeWord(request, validator); + while (!nextWord.endsWith(")")) { + addItem(nextWord, items); + nextWord = consumeWord(request, validator); + } + // Got the closing ")", may be attached to a word. + if (nextWord.length() > 1) { + addItem(nextWord.substring(0, nextWord.length() - 1), items); + } + + return items; + } + + private void addItem(String nextWord, StatusDataItems items) + throws ProtocolException { + if (nextWord.equals(StatusCommand.MESSAGES)) { + items.setMessages(true); + } else if (nextWord.equals(StatusCommand.RECENT)) { + items.setRecent(true); + } else if (nextWord.equals(StatusCommand.UIDNEXT)) { + items.setUidNext(true); + } else if (nextWord.equals(StatusCommand.UIDVALIDITY)) { + items.setUidValidity(true); + } else if (nextWord.equals(StatusCommand.UNSEEN)) { + items.setUnseen(true); + } else { + throw new ProtocolException("Unknown status item: '" + nextWord + '\''); + } + } +} diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/StoreCommandParser.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/StoreCommandParser.java new file mode 100644 index 0000000000..b0e4351a04 --- /dev/null +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/StoreCommandParser.java @@ -0,0 +1,33 @@ +package com.icegreen.greenmail.imap.commands.parsers; + +import com.icegreen.greenmail.imap.ImapRequestLineReader; +import com.icegreen.greenmail.imap.ProtocolException; +import com.icegreen.greenmail.imap.commands.parsers.store.StoreDirective; + +public class StoreCommandParser extends CommandParser { + public StoreDirective storeDirective(ImapRequestLineReader request) throws ProtocolException { + int sign = 0; + boolean silent = false; + + char next = request.nextWordChar(); + if (next == '+') { + sign = 1; + request.consume(); + } else if (next == '-') { + sign = -1; + request.consume(); + } else { + sign = 0; + } + + String directive = consumeWord(request, new NoopCharValidator()); + if ("FLAGS".equalsIgnoreCase(directive)) { + silent = false; + } else if ("FLAGS.SILENT".equalsIgnoreCase(directive)) { + silent = true; + } else { + throw new ProtocolException("Invalid Store Directive: '" + directive + '\''); + } + return new StoreDirective(sign, silent); + } +} \ No newline at end of file diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/fetch/BodyFetchElement.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/fetch/BodyFetchElement.java new file mode 100644 index 0000000000..85c479e1da --- /dev/null +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/fetch/BodyFetchElement.java @@ -0,0 +1,29 @@ +package com.icegreen.greenmail.imap.commands.parsers.fetch; + +public class BodyFetchElement { + private String name; + private String sectionIdentifier; + private Partial partial; + + public BodyFetchElement(String name, String sectionIdentifier) { + this(name, sectionIdentifier, null); + } + + public BodyFetchElement(String name, String sectionIdentifier, Partial partial) { + this.name = name; + this.sectionIdentifier = sectionIdentifier; + this.partial = partial; + } + + public String getParameters() { + return this.sectionIdentifier; + } + + public String getResponseName() { + return this.name; + } + + public Partial getPartial() { + return partial; + } +} diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/fetch/FetchRequest.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/fetch/FetchRequest.java new file mode 100644 index 0000000000..eff004bb5a --- /dev/null +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/fetch/FetchRequest.java @@ -0,0 +1,98 @@ +package com.icegreen.greenmail.imap.commands.parsers.fetch; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +public class FetchRequest { + private boolean flags; + private boolean uid; + private boolean internalDate; + private boolean size; + private boolean envelope; + private boolean body; + private boolean bodyStructure; + + private boolean setSeen = false; + + private Set bodyElements = new HashSet<>(); + + public Collection getBodyElements() { + return bodyElements; + } + + public boolean isSetSeen() { + return setSeen; + } + + public void add(BodyFetchElement element, boolean peek) { + if (!peek) { + setSeen = true; + } + bodyElements.add(element); + } + + public boolean isFlags() { + return flags; + } + + public void setFlags(boolean flags) { + this.flags = flags; + } + + public boolean isUid() { + return uid; + } + + public void setUid(boolean uid) { + this.uid = uid; + } + + public boolean isInternalDate() { + return internalDate; + } + + public void setInternalDate(boolean internalDate) { + this.internalDate = internalDate; + } + + public boolean isSize() { + return size; + } + + public void setSize(boolean size) { + this.size = size; + } + + public boolean isEnvelope() { + return envelope; + } + + public void setEnvelope(boolean envelope) { + this.envelope = envelope; + } + + public boolean isBody() { + return body; + } + + public void setBody(boolean body) { + this.body = body; + } + + public boolean isBodyStructure() { + return bodyStructure; + } + + public void setBodyStructure(boolean bodyStructure) { + this.bodyStructure = bodyStructure; + } + + public void setSetSeen(boolean setSeen) { + this.setSeen = setSeen; + } + + public void setBodyElements(Set bodyElements) { + this.bodyElements = bodyElements; + } +} \ No newline at end of file diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/fetch/Partial.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/fetch/Partial.java new file mode 100644 index 0000000000..47f1d35495 --- /dev/null +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/fetch/Partial.java @@ -0,0 +1,43 @@ +package com.icegreen.greenmail.imap.commands.parsers.fetch; + +/** See https://tools.ietf.org/html/rfc3501#page-55 : partial */ +public class Partial { + private int start; + private int size; + + public int computeLength(final int contentSize) { + if ( size > 0) { + return Math.min(size, contentSize - start); // Only up to max available bytes + } else { + // First len bytes + return contentSize; + } + } + + public int computeStart(final int contentSize) { + return Math.min(start, contentSize); + } + + public static Partial as(int start, int size) { + Partial p = new Partial(); + p.start = start; + p.size = size; + return p; + } + + public int getStart() { + return start; + } + + public void setStart(int start) { + this.start = start; + } + + public int getSize() { + return size; + } + + public void setSize(int size) { + this.size = size; + } +} diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/SearchTermBuilder.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/search/SearchTermBuilder.java similarity index 98% rename from greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/SearchTermBuilder.java rename to greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/search/SearchTermBuilder.java index 58ba4b34b1..5375da5720 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/SearchTermBuilder.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/search/SearchTermBuilder.java @@ -1,4 +1,4 @@ -package com.icegreen.greenmail.imap.commands; +package com.icegreen.greenmail.imap.commands.parsers.search; import java.text.DateFormat; import java.text.ParseException; @@ -28,6 +28,8 @@ import javax.mail.search.SentDateTerm; import javax.mail.search.SubjectTerm; +import com.icegreen.greenmail.imap.commands.IdRange; +import com.icegreen.greenmail.imap.commands.SearchKey; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -206,7 +208,7 @@ public SearchTerm build() { }; } - SearchTermBuilder addParameter(final String pParameter) { + public SearchTermBuilder addParameter(final String pParameter) { if (Collections.emptyList() == parameters) { parameters = new ArrayList<>(); } @@ -226,7 +228,7 @@ public boolean expectsParameter() { return parameters.size() < key.getNumberOfParameters(); } - boolean isCharsetAware() { + public boolean isCharsetAware() { return key.isCharsetAware(); } diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/status/StatusDataItems.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/status/StatusDataItems.java new file mode 100644 index 0000000000..201372e22b --- /dev/null +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/status/StatusDataItems.java @@ -0,0 +1,49 @@ +package com.icegreen.greenmail.imap.commands.parsers.status; + +public class StatusDataItems { + private boolean messages; + private boolean recent; + private boolean uidNext; + private boolean uidValidity; + private boolean unseen; + + public boolean isMessages() { + return messages; + } + + public void setMessages(boolean messages) { + this.messages = messages; + } + + public boolean isRecent() { + return recent; + } + + public void setRecent(boolean recent) { + this.recent = recent; + } + + public boolean isUidNext() { + return uidNext; + } + + public void setUidNext(boolean uidNext) { + this.uidNext = uidNext; + } + + public boolean isUidValidity() { + return uidValidity; + } + + public void setUidValidity(boolean uidValidity) { + this.uidValidity = uidValidity; + } + + public boolean isUnseen() { + return unseen; + } + + public void setUnseen(boolean unseen) { + this.unseen = unseen; + } +} \ No newline at end of file diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/store/StoreDirective.java b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/store/StoreDirective.java new file mode 100644 index 0000000000..a49dbea7fa --- /dev/null +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/imap/commands/parsers/store/StoreDirective.java @@ -0,0 +1,19 @@ +package com.icegreen.greenmail.imap.commands.parsers.store; + +public class StoreDirective { + private int sign; + private boolean silent; + + public StoreDirective(int sign, boolean silent) { + this.sign = sign; + this.silent = silent; + } + + public int getSign() { + return sign; + } + + public boolean isSilent() { + return silent; + } +} diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/store/StoredMessage.java b/greenmail-core/src/main/java/com/icegreen/greenmail/store/StoredMessage.java index 737e1452ab..b50c6c20b3 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/store/StoredMessage.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/store/StoredMessage.java @@ -6,6 +6,8 @@ */ package com.icegreen.greenmail.store; +import com.icegreen.greenmail.imap.commands.parsers.search.SearchTermBuilder; + import java.util.Date; import javax.mail.Flags; @@ -30,7 +32,7 @@ public class StoredMessage { * Wraps a mime message and provides support for uid. * Required for searching. * - * @see com.icegreen.greenmail.imap.commands.SearchTermBuilder.UidSearchTerm + * @see SearchTermBuilder.UidSearchTerm */ public static class UidAwareMimeMessage extends MimeMessage { private long uid; diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/util/ServerSetup.java b/greenmail-core/src/main/java/com/icegreen/greenmail/util/ServerSetup.java index 63a30f3341..80e0a48947 100644 --- a/greenmail-core/src/main/java/com/icegreen/greenmail/util/ServerSetup.java +++ b/greenmail-core/src/main/java/com/icegreen/greenmail/util/ServerSetup.java @@ -4,6 +4,9 @@ */ package com.icegreen.greenmail.util; +import com.icegreen.greenmail.imap.ImapHandler; +import com.icegreen.greenmail.server.ProtocolHandler; + import java.util.Properties; /** @@ -84,6 +87,7 @@ public class ServerSetup { * Timeout when GreenMail starts a server, in milliseconds. */ private long serverStartupTimeout = SERVER_STARTUP_TIMEOUT; + private ImapHandler imapHandler; public ServerSetup(int port, String bindAddress, String protocol) { this.port = port; @@ -239,6 +243,15 @@ public Properties configureJavaMailSessionProperties(Properties properties, bool return props; } + public ServerSetup withImapHandler(ImapHandler imapHandler) { + this.imapHandler = imapHandler; + return this; + } + + public ImapHandler getImapHandler() { + return imapHandler; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -303,6 +316,7 @@ public ServerSetup createCopy(String bindAddress) { setup.setConnectionTimeout(getConnectionTimeout()); setup.setReadTimeout(getReadTimeout()); setup.setWriteTimeout(getWriteTimeout()); + setup.withImapHandler(getImapHandler()); setup.setVerbose(isVerbose()); return setup; diff --git a/greenmail-core/src/test/java/com/icegreen/greenmail/imap/commands/CommandParserTest.java b/greenmail-core/src/test/java/com/icegreen/greenmail/imap/commands/CommandParserTest.java index 7b08d19f8f..5cc5712d9a 100644 --- a/greenmail-core/src/test/java/com/icegreen/greenmail/imap/commands/CommandParserTest.java +++ b/greenmail-core/src/test/java/com/icegreen/greenmail/imap/commands/CommandParserTest.java @@ -1,5 +1,6 @@ package com.icegreen.greenmail.imap.commands; +import com.icegreen.greenmail.imap.commands.parsers.CommandParser; import org.junit.Test; import static org.junit.Assert.assertFalse; diff --git a/greenmail-core/src/test/java/com/icegreen/greenmail/imap/commands/SearchTermBuilderTest.java b/greenmail-core/src/test/java/com/icegreen/greenmail/imap/commands/SearchTermBuilderTest.java index 4261e67ab3..429d2104bf 100644 --- a/greenmail-core/src/test/java/com/icegreen/greenmail/imap/commands/SearchTermBuilderTest.java +++ b/greenmail-core/src/test/java/com/icegreen/greenmail/imap/commands/SearchTermBuilderTest.java @@ -1,5 +1,6 @@ package com.icegreen.greenmail.imap.commands; +import com.icegreen.greenmail.imap.commands.parsers.search.SearchTermBuilder; import org.junit.Test; import java.util.List;