Skip to content

Commit

Permalink
Fixing SearchCommandParser cannot be cast to jakarta.mail.search.Sear…
Browse files Browse the repository at this point in the history
…chTerm (fixes #591)
  • Loading branch information
marcelmay committed Sep 14, 2023
1 parent 83a41c9 commit 7fb44b3
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,15 @@ protected enum DataFormats {
STRING
// Complete further types on demand
}

/**
* Parses the request argument into a valid search term. Not yet fully implemented - see SearchKey enum.
* <p>
* Other searches will return everything for now.
* Throws an UnsupportedCharsetException if provided CHARSET is not supported.
*/
public SearchTerm searchTerm(ImapRequestLineReader request)
throws ProtocolException {
throws ProtocolException {
Charset charset = StandardCharsets.US_ASCII; // Default
// Stack contains mix of SearchOperator and SearchTerm instances
// and will be processed in two steps
Expand All @@ -80,16 +81,8 @@ public SearchTerm searchTerm(ImapRequestLineReader request)
request.consume();
request.consumeAll(CHR_SPACE);

LinkedList<SearchTerm> groupItems = new LinkedList<>();
Object item;
while ((item = stack.pop()) != SearchOperator.GROUP) {
groupItems.addFirst((SearchTerm) item);
}
if (groupItems.size() == 1) {
stack.push(groupItems.get(0)); // Single item
} else {
stack.push(new AndTerm(groupItems.toArray(new SearchTerm[0])));
}
// Resolve group to single term
handleGroup(stack);
} else {
throw new IllegalStateException("Unsupported atom special char <" + next + ">");
}
Expand All @@ -110,7 +103,7 @@ else if (CHARSET_TOKEN.equals(token)) {
try {
charset = Charset.forName(charsetName);
} catch (UnsupportedCharsetException ex) {
log.error("Unsupported charset '{}", charsetName);
log.error("Unsupported charset '{}'", charsetName);
throw ex;
}
} else {
Expand Down Expand Up @@ -140,21 +133,37 @@ else if (CHARSET_TOKEN.equals(token)) {
return handleOperators(stack);
}

private void handleGroup(Deque<Object> stack) {
Deque<Object> groupItems = new LinkedList<>();
Object item;
while ((item = stack.pop()) != SearchOperator.GROUP) {
groupItems.addLast(item);
}
final SearchTerm groupTerm = handleOperators(groupItems);
stack.push(groupTerm);
}

private void handleSearchArg(ImapRequestLineReader request, SearchKey key, SearchTermBuilder searchTermBuilder, Charset charset) throws ProtocolException {
String paramValue;
switch(key.getArgDataFormat()) {
switch (key.getArgDataFormat()) {
case STRING:
paramValue = string(request, charset);
break;
case ATOM:
paramValue = atomOnly(request);
break;
default: throw new IllegalStateException("Argument type "+key.getArgDataFormat()+" not implemented for key "+key);
default:
throw new IllegalStateException("Argument type " + key.getArgDataFormat() + " not implemented for key " + key);
}
searchTermBuilder.addParameter(paramValue);
}

private SearchTerm handleOperators(Deque<Object> stack) {
// Must be single term
if (stack.size() == 1) {
return (SearchTerm) stack.pop();
}

LinkedList<SearchTerm> params = new LinkedList<>();
while (!stack.isEmpty()) {
final Object o = stack.pop();
Expand Down Expand Up @@ -184,10 +193,12 @@ private SearchTerm handleOperators(Deque<Object> stack) {
}
}

if (params.size() != 1) {
throw new IllegalStateException("Expected exactly one root search term but got " + params);
if (params.size() > 1) {
SearchTerm[] items = params.toArray(new SearchTerm[0]);
return new AndTerm(items);
} else {
return params.pop();
}
return params.pop();
}

}
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
package com.icegreen.greenmail.imap.commands;

import java.io.ByteArrayInputStream;
import com.icegreen.greenmail.imap.ImapRequestLineReader;
import com.icegreen.greenmail.imap.ProtocolException;
import jakarta.mail.Flags;
import jakarta.mail.Message;
import jakarta.mail.internet.AddressException;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.search.*;

import com.icegreen.greenmail.imap.ImapRequestLineReader;
import com.icegreen.greenmail.imap.ProtocolException;
import org.junit.Test;

import java.io.ByteArrayInputStream;

import static org.assertj.core.api.Assertions.assertThat;

public class SearchCommandParserTest {
@Test
public void testHeader() throws ProtocolException {
SearchTerm expectedTerm = new AndTerm(
new AndTerm(new SearchTerm[]{
new HeaderTerm("Message-ID", "<1627010197.0.1593681191102@[192.168.242.10]>"),
new FlagTerm(new Flags(Flags.Flag.SEEN), true)
}),
SearchTermBuilder.create(SearchKey.ALL).build()
new AndTerm(new SearchTerm[]{
new HeaderTerm("Message-ID", "<1627010197.0.1593681191102@[192.168.242.10]>"),
new FlagTerm(new Flags(Flags.Flag.SEEN), true)
}),
SearchTermBuilder.create(SearchKey.ALL).build()
);

SearchTerm searchTerm = parse("(HEADER Message-ID <1627010197.0.1593681191102@[192.168.242.10]> SEEN) ALL");
Expand Down Expand Up @@ -56,12 +56,12 @@ public void testSmallerAndLargerParseCommand() throws ProtocolException {
@Test
public void testAndSubjectOrToFrom() throws ProtocolException, AddressException {
SearchTerm expectedTerm = new AndTerm(new SearchTerm[]{
new SubjectTerm("Greenmail"),
new OrTerm(
new RecipientTerm(Message.RecipientType.TO, new InternetAddress("to@localhost")),
new FromTerm(new InternetAddress("from@localhost"))
),
SearchTermBuilder.create(SearchKey.ALL).build()});
new SubjectTerm("Greenmail"),
new OrTerm(
new RecipientTerm(Message.RecipientType.TO, new InternetAddress("to@localhost")),
new FromTerm(new InternetAddress("from@localhost"))
),
SearchTermBuilder.create(SearchKey.ALL).build()});

// SUBJECT Greenmail (OR TO from@localhost FROM from@localhost) ALL
SearchTerm searchTerm = parse("SUBJECT Greenmail OR TO to@localhost FROM from@localhost ALL");
Expand All @@ -73,12 +73,48 @@ public void testAndSubjectOrToFrom() throws ProtocolException, AddressException
public void testNotKeyword() throws ProtocolException {
Flags flags = new Flags();
flags.add("ABC");
SearchTerm expectedTerm = new NotTerm(new FlagTerm(flags, true));
SearchTerm expectedTerm = new NotTerm(new FlagTerm(flags, true));
SearchTerm searchTerm = parse("NOT (KEYWORD ABC)");

assertThat(searchTerm).isEqualTo(expectedTerm);
}

@Test
public void testSimpleOr() throws ProtocolException {
SearchTerm expectedTerm = new OrTerm(
new FlagTerm(new Flags(Flags.Flag.DRAFT), true),
new FlagTerm(new Flags(Flags.Flag.SEEN), true)
);
SearchTerm searchTerm = parse("OR (DRAFT) (SEEN)");

assertThat(searchTerm).isEqualTo(expectedTerm);
}

@Test
public void testIssue591simple() throws ProtocolException {
SearchTerm expectedTerm = new OrTerm(
new NotTerm(new FlagTerm(new Flags(Flags.Flag.SEEN), true)),
new FlagTerm(new Flags(Flags.Flag.SEEN), true)
);
SearchTerm searchTerm = parse("OR (NOT (SEEN)) (SEEN)");

assertThat(searchTerm).isEqualTo(expectedTerm);
}

@Test
public void testIssue591complex() throws ProtocolException {
SearchTerm expectedTerm = new OrTerm(
new NotTerm(new FlagTerm(new Flags(Flags.Flag.SEEN), true)),
new OrTerm(
new FlagTerm(new Flags("foo"), true),
new FlagTerm(new Flags("bar"), true)
)
);
SearchTerm searchTerm = parse("OR (NOT (SEEN)) (OR (KEYWORD foo) (KEYWORD bar))");

assertThat(searchTerm).isEqualTo(expectedTerm);
}

private SearchTerm parse(String line) throws ProtocolException {
final byte[] bytes = (line.endsWith("\n") ? line : (line + '\n')).getBytes();
ByteArrayInputStream ins = new ByteArrayInputStream(bytes);
Expand Down

0 comments on commit 7fb44b3

Please sign in to comment.