JSIEVE-73 Body test argument parsing should be RFC-5173 compliant

git-svn-id: https://svn.apache.org/repos/asf/james/jsieve/trunk@1722971 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/core/pom.xml b/core/pom.xml
index 99980a6..d5c5329 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -75,6 +75,11 @@
             <artifactId>${javax.activation.artifactId}</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/core/src/main/java/org/apache/jsieve/mail/MailAdapter.java b/core/src/main/java/org/apache/jsieve/mail/MailAdapter.java
index cfe0901..12c5012 100644
--- a/core/src/main/java/org/apache/jsieve/mail/MailAdapter.java
+++ b/core/src/main/java/org/apache/jsieve/mail/MailAdapter.java
@@ -170,12 +170,35 @@
     /**
      * Is the given phrase found in the body text of this mail?
      * This search should be case insensitive.
-     * @param phraseCaseInsensitive the phrase to search
+     * @param phrasesCaseInsensitive the phrases to search
      * @return true when the mail has a textual body and contains the phrase
      * (case insensitive), false otherwise
      * @throws SieveMailException when the search cannot be completed
      */
-    public boolean isInBodyText(final String phraseCaseInsensitive) throws SieveMailException;
+    public boolean isInBodyText(final List<String> phrasesCaseInsensitive) throws SieveMailException;
+
+
+    /**
+     * Is the given phrase found in the body raw of this mail?
+     * This search should be case insensitive and the mail should not be decoded.
+     * @param phrasesCaseInsensitive the phrases to search
+     * @return true when the mail has a textual body and contains the phrase
+     * (case insensitive), false otherwise
+     * @throws SieveMailException when the search cannot be completed
+     */
+    public boolean isInBodyRaw(final List<String> phrasesCaseInsensitive) throws SieveMailException;
+
+
+    /**
+     * Is the given phrase found in the body contents of the specified content types of this mail?
+     * This search should be case insensitive.
+     * @param contentTypes Content types of the body parts we should search into.
+     * @param phrasesCaseInsensitive the phrases to search
+     * @return true when the mail has a textual body and contains the phrase
+     * (case insensitive), false otherwise
+     * @throws SieveMailException when the search cannot be completed
+     */
+    public boolean isInBodyContent(final List<String> contentTypes, final List<String> phrasesCaseInsensitive) throws SieveMailException;
     
     /**
      * <p>
diff --git a/core/src/main/java/org/apache/jsieve/tests/optional/Body.java b/core/src/main/java/org/apache/jsieve/tests/optional/Body.java
index 2899cc7..4d61e1d 100644
--- a/core/src/main/java/org/apache/jsieve/tests/optional/Body.java
+++ b/core/src/main/java/org/apache/jsieve/tests/optional/Body.java
@@ -19,6 +19,7 @@
 
 package org.apache.jsieve.tests.optional;
 
+import java.util.Iterator;
 import java.util.List;
 
 import org.apache.jsieve.Argument;
@@ -29,7 +30,7 @@
 import org.apache.jsieve.exception.SieveException;
 import org.apache.jsieve.exception.SyntaxException;
 import org.apache.jsieve.mail.MailAdapter;
-import org.apache.jsieve.mail.SieveMailException;
+import org.apache.jsieve.parser.generated.Token;
 import org.apache.jsieve.tests.AbstractTest;
 
 /**
@@ -37,60 +38,120 @@
  * <a href='http://tools.ietf.org/html/rfc5173'>RFC5173</a>.
  */
 public class Body extends AbstractTest {
-    private StringListArgument strings;
 
-    public Body() {
-        super();
-        strings = null;
+    public static final String TEXT = ":text";
+    public static final String RAW = ":raw";
+    public static final String CONTENT = ":content";
+    private TagArgument transformation;
+    private StringListArgument contentTypes;
+    private TagArgument matcher;
+    private StringListArgument valuesToBeMatched;
+
+    protected void validateArguments(Arguments args, SieveContext ctx) throws SieveException {
+        Iterator<Argument> matchingSpecifications = retrieveTransformationAndMatchingSpecificationIterator(args.getArgumentList());
+
+        if (transformation.getTag().equals(TEXT)) {
+            parseDefaultArguments(matchingSpecifications);
+        } else if (transformation.getTag().equals(RAW)) {
+            parseDefaultArguments(matchingSpecifications);
+        } else if (transformation.getTag().equals(CONTENT)) {
+            parseContentArguments(matchingSpecifications);
+        } else {
+            throw new SyntaxException("Unknown transformation " + transformation.getTag() + ". See RFC-5173 section 5.");
+        }
     }
 
-    // TODO: Check how complete this is of the body specification
-    // Validate (sorta); we're only implementing part of the spec
-    protected void validateArguments(Arguments args, SieveContext ctx)
-            throws SieveException {
+    private void parseContentArguments(Iterator<Argument> matchingSpecifications) throws SyntaxException {
+        retrieveContentTypes(matchingSpecifications);
+        retrieveMatcher(matchingSpecifications);
+        retrieveMatchValues(matchingSpecifications);
+        assureNoMoreArguments(matchingSpecifications);
+    }
 
-        final List<Argument> arglist = args.getArgumentList();
-        if (arglist.size() != 2) {
-            throw new SyntaxException(
-                    "Currently body-test can only two arguments");
+    private void parseDefaultArguments(Iterator<Argument> matchingSpecifications) throws SyntaxException {
+        retrieveMatcher(matchingSpecifications);
+        retrieveMatchValues(matchingSpecifications);
+        assureNoMoreArguments(matchingSpecifications);
+    }
+
+    private Iterator<Argument> retrieveTransformationAndMatchingSpecificationIterator(List<Argument> arglist) throws SyntaxException {
+        if (arglist.size() < 1 ) {
+            throw new SyntaxException("Transformations should be specified. See RFC-5173 section 5.");
         }
-
-        // TODO: FIXME: As this is a limited implementation force the use of
-        // ':contains'.
         Argument arg = arglist.get(0);
         if (!(arg instanceof TagArgument)) {
-            throw new SyntaxException("Body expects a :contains tag");
-        }
-
-        if (!((TagArgument) arg).getTag().equals(":contains")) {
-            throw new SyntaxException("Body expects a :contains tag");
-        }
-
-        // Get list of strings to search for
-        arg = arglist.get(1);
-        if (!(arg instanceof StringListArgument)) {
-            throw new SyntaxException("Body expects a list of strings");
-        }
-        strings = (StringListArgument) args.getArgumentList().get(1);
-    }
-
-    // This implement body tests of the form
-    // "body :contains ['string' 'string' ....]"
-    protected boolean executeBasic(MailAdapter mail, Arguments args,
-            SieveContext ctx) throws SieveException {
-        // Attempt to fetch content as a string. If we can't do this it's
-        // not a message we can handle.
-        if (mail.getContentType().indexOf("text/") != 0) {
-            throw new SieveMailException("Message is not of type 'text'");
-        }
-
-        // Compare each test string with body, ignoring case
-        for (final String phrase:strings.getList()) {
-            if (mail.isInBodyText(phrase)) {
-                return true;
+            // by default transformation should be :text
+            transformation = new TagArgument(new Token(0, TEXT));
+            return arglist.iterator();
+        } else {
+            TagArgument transformationCandidate = (TagArgument) arg;
+            if (transformationCandidate.getTag().equals(TEXT) ||
+                    transformationCandidate.getTag().equals(RAW) ||
+                    transformationCandidate.getTag().equals(CONTENT) ) {
+                transformation = (TagArgument) arg;
+                Iterator<Argument> matchingSpecifications = arglist.iterator();
+                matchingSpecifications.next();
+                return matchingSpecifications;
+            } else {
+                // by default transformation should be :text
+                transformation = new TagArgument(new Token(0, TEXT));
+                return arglist.iterator();
             }
         }
-        return false;
+    }
+
+    protected boolean executeBasic(MailAdapter mail, Arguments args, SieveContext ctx) throws SieveException {
+        if (transformation.getTag().equals(RAW)) {
+            return mail.isInBodyRaw(valuesToBeMatched.getList());
+        } else if (transformation.getTag().equals(CONTENT)) {
+            return mail.isInBodyContent(contentTypes.getList(), valuesToBeMatched.getList());
+        } else if (transformation.getTag().equals(TEXT)) {
+            return mail.isInBodyText(valuesToBeMatched.getList());
+        } else {
+            throw new RuntimeException("Invalid transformation caught. Is your argument parsing buggy ?");
+        }
+    }
+
+    private void retrieveContentTypes(Iterator<Argument> matchingSpecifications) throws SyntaxException {
+        if (!matchingSpecifications.hasNext()) {
+            throw new SyntaxException("Expecting the list of content types following :content");
+        }
+        Argument contentTypesArgument = matchingSpecifications.next();
+        if (! (contentTypesArgument instanceof StringListArgument)) {
+            throw new SyntaxException("Expecting a String list to specify content types and not a" + contentTypesArgument.getClass());
+        }
+        contentTypes = (StringListArgument) contentTypesArgument;
+    }
+
+    private void retrieveMatcher(Iterator<Argument> matchingSpecifications) throws SyntaxException {
+        if (!matchingSpecifications.hasNext()) {
+            throw new SyntaxException("Expecting a matcher :contains");
+        }
+        Argument matcherArgument = matchingSpecifications.next();
+        if (! (matcherArgument instanceof TagArgument)) {
+            throw new SyntaxException("Expecting a matcher :contains and not a" + matcherArgument.getClass());
+        }
+        if (!((TagArgument)matcherArgument).getTag().equals(":contains")) {
+            throw new SyntaxException("Expecting a matcher :contains. Matcher " + ((TagArgument) matcherArgument).getTag() + " is currently not supported.");
+        }
+        matcher = (TagArgument) matcherArgument;
+    }
+
+    private void retrieveMatchValues(Iterator<Argument> matchingSpecifications) throws SyntaxException {
+        if (!matchingSpecifications.hasNext()) {
+            throw new SyntaxException("Matcher :contains should be followed by a StringList");
+        }
+        Argument matchValues = matchingSpecifications.next();
+        if (! (matchValues instanceof StringListArgument)) {
+            throw new SyntaxException("Matcher :contains should be followed by a StringList and not a " + matchValues.getClass());
+        }
+        valuesToBeMatched = (StringListArgument) matchValues;
+    }
+
+    private void assureNoMoreArguments(Iterator<Argument> matchingSpecifications) throws SyntaxException {
+        if (matchingSpecifications.hasNext()) {
+            throw new SyntaxException("Too many arguments for Body test");
+        }
     }
 
 }
diff --git a/core/src/test/java/org/apache/jsieve/BodyTest.java b/core/src/test/java/org/apache/jsieve/BodyTest.java
index 164f990..cf6cefa 100644
--- a/core/src/test/java/org/apache/jsieve/BodyTest.java
+++ b/core/src/test/java/org/apache/jsieve/BodyTest.java
@@ -19,117 +19,309 @@
 
 package org.apache.jsieve;
 
-import javax.mail.MessagingException;
-import javax.mail.internet.MimeMultipart;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
-import junit.framework.TestCase;
-
-import org.apache.jsieve.commands.ThrowTestException;
-import org.apache.jsieve.exception.SieveException;
-import org.apache.jsieve.parser.generated.ParseException;
+import org.apache.jsieve.exception.SyntaxException;
 import org.apache.jsieve.utils.JUnitUtils;
 import org.apache.jsieve.utils.SieveMailAdapter;
-import org.junit.*;
+import org.junit.Before;
 import org.junit.Test;
 
-/**
- * Class BodyTest
- */
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
 public class BodyTest {
 
-    protected SieveMailAdapter textMail() throws MessagingException {
-        SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
-        mail.getMessage().setContent("Wibble\n\n" + "Wibble\n", "text/plain");
-        return mail;
+    private SieveMailAdapter sieveMailAdapter;
+
+    @Before
+    public void setUp() {
+        sieveMailAdapter = mock(SieveMailAdapter.class);
     }
 
-    protected SieveMailAdapter nonTextMail() throws MessagingException {
-        SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
-        // FIXME: This doesn't work
-        mail.getMessage().setContent(new MimeMultipart("image/png"));
-        return mail;
+    @Test(expected = SyntaxException.class)
+    public void NoArgumentsShouldThrow() throws Exception {
+        String script = "if body {throwTestException;}";
+
+        JUnitUtils.interpret(sieveMailAdapter, script);
     }
 
-    /**
-     * Test for Test 'header'
-     */
-    @org.junit.Test
-    public void testBasic() {
-        boolean isTestPassed = false;
-        String script = "if body :contains [\"Wibble\"] {throwTestException;}";
-        try {
-            JUnitUtils.interpret(textMail(), script);
-        } catch (MessagingException e) {
-        } catch (ThrowTestException.TestException e) {
-            isTestPassed = true;
-        } catch (ParseException e) {
-        } catch (SieveException e) {
-        }
-        Assert.assertTrue(isTestPassed);
+    @Test(expected = SyntaxException.class)
+    public void invalidTransformationShouldThrow() throws Exception {
+        String script = "if body :invalid :contains \"Wibble\" {throwTestException;}";
+
+        JUnitUtils.interpret(sieveMailAdapter, script);
     }
 
-    /**
-     * Test for Test 'body'
-     */
+    @Test(expected = SyntaxException.class)
+    public void rawShouldThrowWithoutMatcher() throws Exception {
+        String script = "if body :raw \"Wibble\" {throwTestException;}";
+
+        JUnitUtils.interpret(sieveMailAdapter, script);
+    }
+
+    @Test(expected = SyntaxException.class)
+    public void rawShouldThrowWithoutMatcherContains() throws Exception {
+        String script = "if body :raw :is \"Wibble\" {throwTestException;}";
+
+        JUnitUtils.interpret(sieveMailAdapter, script);
+    }
+
+    @Test(expected = SyntaxException.class)
+    public void rawShouldThrowWithoutValuesToMatch() throws Exception {
+        String script = "if body :raw :contains {throwTestException;}";
+
+        JUnitUtils.interpret(sieveMailAdapter, script);
+    }
+
+    @Test(expected = SyntaxException.class)
+    public void rawShouldThrowWithInvalidValuesToMatch() throws Exception {
+        String script = "if body :raw :contains :fake {throwTestException;}";
+
+        JUnitUtils.interpret(sieveMailAdapter, script);
+    }
+
+    @Test(expected = SyntaxException.class)
+    public void rawShouldThrowWithExtraArguments() throws Exception {
+        String script = "if body :raw :contains \"Wibble\" :hello {throwTestException;}";
+
+        JUnitUtils.interpret(sieveMailAdapter, script);
+    }
+
     @Test
-    public void testBodyCaseInsensitivity() {
-        boolean isTestPassed = false;
-        String script = "if body :contains [\"wibble\"] {throwTestException;}";
-        try {
-            JUnitUtils.interpret(textMail(), script);
-        } catch (MessagingException e) {
-        } catch (ThrowTestException.TestException e) {
-            isTestPassed = true;
-        } catch (ParseException e) {
-        } catch (SieveException e) {
-        }
-        Assert.assertTrue(isTestPassed);
+    public void rawShouldWork() throws Exception {
+        String script = "if body :raw :contains \"Wibble\" {throwTestException;}";
+        List<String> containedText = Collections.singletonList("Wibble");
+        when(sieveMailAdapter.isInBodyRaw(containedText)).thenReturn(false);
+
+        JUnitUtils.interpret(sieveMailAdapter, script);
+
+        verify(sieveMailAdapter).isInBodyRaw(containedText);
     }
 
-    /**
-     * Test for Test 'body'
-     */
     @Test
-    public void testBodyNoContains() {
-        boolean isTestPassed = false;
-        String script = "if body [\"wibble\"] {throwTestException;}";
-        try {
-            JUnitUtils.interpret(textMail(), script);
-        } catch (MessagingException e) {
-        } catch (ThrowTestException.TestException e) {
-        } catch (ParseException e) {
-        } catch (SieveException e) {
-            isTestPassed = true;
-        }
-        Assert.assertTrue(isTestPassed);
+    public void rawShouldWorkWithMultipleValues() throws Exception {
+        String script = "if body :raw :contains [\"Wibble\",\"other\"] {throwTestException;}";
+        List<String> containedText = Arrays.asList("Wibble", "other");
+        when(sieveMailAdapter.isInBodyRaw(containedText)).thenReturn(false);
+
+        JUnitUtils.interpret(sieveMailAdapter, script);
+
+        verify(sieveMailAdapter).isInBodyRaw(containedText);
     }
 
-    /**
-     * Test for Test 'body'
-     */
-    // FIXME: I can't find a method of forcing the mime type, so this test
-    // always fails ...
-    // public void testBodyNonText()
-    // {
-    // boolean isTestPassed = false;
-    // String script = "if body :contains [\"wibble\"] {throwTestException;}";
-    // try
-    // {
-    // JUnitUtils.interpret(nonTextMail(), script);
-    // }
-    // catch (MessagingException e)
-    // {
-    // }
-    // catch (ThrowTestException.TestException e)
-    // {
-    // }
-    // catch (ParseException e)
-    // {
-    // }
-    // catch (SieveException e)
-    // {
-    // isTestPassed = true;
-    // }
-    // assertTrue(isTestPassed);
-    // }
+    @Test(expected = SyntaxException.class)
+    public void textShouldThrowWithoutMatcher() throws Exception {
+        String script = "if body :text \"Wibble\" {throwTestException;}";
+
+        JUnitUtils.interpret(sieveMailAdapter, script);
+    }
+
+    @Test(expected = SyntaxException.class)
+    public void textShouldThrowWithoutMatcherContains() throws Exception {
+        String script = "if body :text :is \"Wibble\" {throwTestException;}";
+
+        JUnitUtils.interpret(sieveMailAdapter, script);
+    }
+
+    @Test(expected = SyntaxException.class)
+    public void textShouldThrowWithoutValuesToMatch() throws Exception {
+        String script = "if body :text :contains {throwTestException;}";
+
+        JUnitUtils.interpret(sieveMailAdapter, script);
+    }
+
+    @Test(expected = SyntaxException.class)
+    public void textShouldThrowWithInvalidValuesToMatch() throws Exception {
+        String script = "if body :text :contains :fake {throwTestException;}";
+
+        JUnitUtils.interpret(sieveMailAdapter, script);
+    }
+
+    @Test(expected = SyntaxException.class)
+    public void textShouldThrowWithExtraArguments() throws Exception {
+        String script = "if body :text :contains \"Wibble\" :hello {throwTestException;}";
+
+        JUnitUtils.interpret(sieveMailAdapter, script);
+    }
+
+    @Test
+    public void textShouldWork() throws Exception {
+        String script = "if body :text :contains \"Wibble\" {throwTestException;}";
+        List<String> containedText = Collections.singletonList("Wibble");
+        when(sieveMailAdapter.isInBodyText(containedText)).thenReturn(false);
+
+        JUnitUtils.interpret(sieveMailAdapter, script);
+
+        verify(sieveMailAdapter).isInBodyText(containedText);
+    }
+
+    @Test
+    public void textShouldWorkWithMultipleValues() throws Exception {
+        String script = "if body :text :contains [\"Wibble\",\"other\"] {throwTestException;}";
+        List<String> containedText = Arrays.asList("Wibble", "other");
+        when(sieveMailAdapter.isInBodyText(containedText)).thenReturn(false);
+
+        JUnitUtils.interpret(sieveMailAdapter, script);
+
+        verify(sieveMailAdapter).isInBodyText(containedText);
+    }
+
+    @Test(expected = SyntaxException.class)
+    public void contentShouldThrowWithoutContentTypes() throws Exception {
+        String script = "if body :content {throwTestException;}";
+
+        JUnitUtils.interpret(sieveMailAdapter, script);
+    }
+
+    @Test(expected = SyntaxException.class)
+    public void contentShouldThrowWithInvalidContentTypes() throws Exception {
+        String script = "if body :content :fake {throwTestException;}";
+
+        JUnitUtils.interpret(sieveMailAdapter, script);
+    }
+
+    @Test(expected = SyntaxException.class)
+    public void contentShouldThrowWithoutMatcher() throws Exception {
+        String script = "if body :content \"any\" {throwTestException;}";
+
+        JUnitUtils.interpret(sieveMailAdapter, script);
+    }
+
+    @Test(expected = SyntaxException.class)
+    public void contentShouldThrowWithInvalidMatcher() throws Exception {
+        String script = "if body :content \"text/plain\" \"Wibble\" {throwTestException;}";
+
+        JUnitUtils.interpret(sieveMailAdapter, script);
+    }
+
+    @Test(expected = SyntaxException.class)
+    public void contentShouldThrowWithoutMatcherContains() throws Exception {
+        String script = "if body :content \"text/plain\" :is \"Wibble\" {throwTestException;}";
+
+        JUnitUtils.interpret(sieveMailAdapter, script);
+    }
+
+    @Test(expected = SyntaxException.class)
+    public void contentShouldThrowWithoutValuesToMatch() throws Exception {
+        String script = "if body :content \"text/plain\" :contains {throwTestException;}";
+
+        JUnitUtils.interpret(sieveMailAdapter, script);
+    }
+
+    @Test(expected = SyntaxException.class)
+    public void contentShouldThrowWithInvalidValuesToMatch() throws Exception {
+        String script = "if body :content \"text/plain\" :contains :fake {throwTestException;}";
+
+        JUnitUtils.interpret(sieveMailAdapter, script);
+    }
+
+    @Test(expected = SyntaxException.class)
+    public void contentShouldThrowWithExtraArguments() throws Exception {
+        String script = "if body :content \"text/plain\" :contains \"Wibble\" :hello {throwTestException;}";
+
+        JUnitUtils.interpret(sieveMailAdapter, script);
+    }
+
+    @Test
+    public void contentShouldWork() throws Exception {
+        String script = "if body :content \"text/plain\" :contains \"Wibble\" {throwTestException;}";
+        List<String> contentTypes = Collections.singletonList("text/plain");
+        List<String> containedText = Collections.singletonList("Wibble");
+        when(sieveMailAdapter.isInBodyContent(contentTypes, containedText)).thenReturn(false);
+
+        JUnitUtils.interpret(sieveMailAdapter, script);
+
+        verify(sieveMailAdapter).isInBodyContent(contentTypes, containedText);
+    }
+
+    @Test
+    public void contentShouldWorkWithMultipleValues() throws Exception {
+        String script = "if body :content \"text/plain\" :contains [\"Wibble\",\"other\"] {throwTestException;}";
+        List<String> containedText = Arrays.asList("Wibble", "other");
+        List<String> contentTypes = Collections.singletonList("text/plain");
+        when(sieveMailAdapter.isInBodyContent(contentTypes, containedText)).thenReturn(false);
+
+        JUnitUtils.interpret(sieveMailAdapter, script);
+
+        verify(sieveMailAdapter).isInBodyContent(contentTypes, containedText);
+    }
+
+    @Test
+    public void contentShouldWorkWithMultipleContentTypes() throws Exception {
+        String script = "if body :content [\"text/plain\",\"text/html\"] :contains \"Wibble\" {throwTestException;}";
+        List<String> contentTypes = Arrays.asList("text/plain", "text/html");
+        List<String> containedText = Collections.singletonList("Wibble");
+        when(sieveMailAdapter.isInBodyContent(contentTypes, containedText)).thenReturn(false);
+
+        JUnitUtils.interpret(sieveMailAdapter, script);
+
+        verify(sieveMailAdapter).isInBodyContent(contentTypes, containedText);
+    }
+
+    @Test
+    public void contentShouldWorkWithMultipleValuesAndMultipleContentTypes() throws Exception {
+        String script = "if body :content [\"text/plain\",\"text/html\"] :contains [\"Wibble\",\"other\"] {throwTestException;}";
+        List<String> containedText = Arrays.asList("Wibble", "other");
+        List<String> contentTypes = Arrays.asList("text/plain", "text/html");
+        when(sieveMailAdapter.isInBodyContent(contentTypes, containedText)).thenReturn(false);
+
+        JUnitUtils.interpret(sieveMailAdapter, script);
+
+        verify(sieveMailAdapter).isInBodyContent(contentTypes, containedText);
+    }
+
+    @Test(expected = SyntaxException.class)
+    public void defaultShouldThrowWithoutMatcher() throws Exception {
+        String script = "if body \"Wibble\" {throwTestException;}";
+
+        JUnitUtils.interpret(sieveMailAdapter, script);
+    }
+
+    @Test(expected = SyntaxException.class)
+    public void defaultShouldThrowWithoutValuesToMatch() throws Exception {
+        String script = "if body :contains {throwTestException;}";
+
+        JUnitUtils.interpret(sieveMailAdapter, script);
+    }
+
+    @Test(expected = SyntaxException.class)
+    public void defaultShouldThrowWithInvalidValuesToMatch() throws Exception {
+        String script = "if body :contains :fake {throwTestException;}";
+
+        JUnitUtils.interpret(sieveMailAdapter, script);
+    }
+
+    @Test(expected = SyntaxException.class)
+    public void defaultShouldThrowWithExtraArguments() throws Exception {
+        String script = "if body :contains \"Wibble\" :hello {throwTestException;}";
+
+        JUnitUtils.interpret(sieveMailAdapter, script);
+    }
+
+    @Test
+    public void defaultShouldWork() throws Exception {
+        String script = "if body :contains \"Wibble\" {throwTestException;}";
+        List<String> containedText = Collections.singletonList("Wibble");
+        when(sieveMailAdapter.isInBodyText(containedText)).thenReturn(false);
+
+        JUnitUtils.interpret(sieveMailAdapter, script);
+
+        verify(sieveMailAdapter).isInBodyText(containedText);
+    }
+
+    @Test
+    public void defaultShouldWorkWithMultipleValues() throws Exception {
+        String script = "if body :contains [\"Wibble\",\"other\"] {throwTestException;}";
+        List<String> containedText = Arrays.asList("Wibble", "other");
+        when(sieveMailAdapter.isInBodyText(containedText)).thenReturn(false);
+
+        JUnitUtils.interpret(sieveMailAdapter, script);
+
+        verify(sieveMailAdapter).isInBodyText(containedText);
+    }
+
 }
diff --git a/core/src/test/java/org/apache/jsieve/util/check/ScriptCheckMailAdapter.java b/core/src/test/java/org/apache/jsieve/util/check/ScriptCheckMailAdapter.java
index 7b28024..c044a61 100644
--- a/core/src/test/java/org/apache/jsieve/util/check/ScriptCheckMailAdapter.java
+++ b/core/src/test/java/org/apache/jsieve/util/check/ScriptCheckMailAdapter.java
@@ -244,18 +244,16 @@
         return result;
     }
 
-    public boolean isInBodyText(String phraseCaseInsensitive) throws SieveMailException {
-        boolean result = false;
-        if (mail != null) {
-            try {
-                result = mail.getContent().toString().toLowerCase().contains(phraseCaseInsensitive);
-            } catch (MessagingException e) {
-                throw new SieveMailException(e);
-            } catch (IOException e) {
-                throw new SieveMailException(e);
-            }
-        }
-        return result;
+    public boolean isInBodyText(List<String> phrasesCaseInsensitive) throws SieveMailException {
+        throw new SieveMailException("Not yet implemented");
+    }
+
+    public boolean isInBodyRaw(List<String> phrasesCaseInsensitive) throws SieveMailException {
+        throw new SieveMailException("Not yet implemented");
+    }
+
+    public boolean isInBodyContent(List<String> contentTypes, List<String> phrasesCaseInsensitive) throws SieveMailException {
+        throw new SieveMailException("Not yet implemented");
     }
 
     public Address[] parseAddresses(String headerName)
diff --git a/core/src/test/java/org/apache/jsieve/utils/SieveMailAdapter.java b/core/src/test/java/org/apache/jsieve/utils/SieveMailAdapter.java
index 1233714..a8d80ac 100644
--- a/core/src/test/java/org/apache/jsieve/utils/SieveMailAdapter.java
+++ b/core/src/test/java/org/apache/jsieve/utils/SieveMailAdapter.java
@@ -270,9 +270,15 @@
         }
     }
 
-    public boolean isInBodyText(String phraseCaseInsensitive) throws SieveMailException {
+    @Override
+    public boolean isInBodyText(List<String> phrasesCaseInsensitive) throws SieveMailException {
         try {
-            return contentAsText().contains(phraseCaseInsensitive.toLowerCase());
+            for (String phrase : phrasesCaseInsensitive) {
+                if (contentAsText().contains(phrase.toLowerCase())) {
+                    return true;
+                }
+            }
+            return false;
         } catch (MessagingException ex) {
             throw new SieveMailException(ex);
         } catch (IOException ex) {
@@ -280,6 +286,16 @@
         }
     }
 
+    @Override
+    public boolean isInBodyRaw(List<String> phrasesCaseInsensitive) throws SieveMailException {
+        throw new SieveMailException("Not yet implemented");
+    }
+
+    @Override
+    public boolean isInBodyContent(List<String> contentTypes, List<String> phrasesCaseInsensitive) throws SieveMailException {
+        throw new SieveMailException("Not yet implemented");
+    }
+
     private String contentAsText() throws IOException, MessagingException {
         if (contentAsLowerCaseString == null) {
             contentAsLowerCaseString = getMessage().getContent().toString().toLowerCase();
diff --git a/mailet/src/main/java/org/apache/jsieve/mailet/SieveMailAdapter.java b/mailet/src/main/java/org/apache/jsieve/mailet/SieveMailAdapter.java
index d54c537..204fb8f 100644
--- a/mailet/src/main/java/org/apache/jsieve/mailet/SieveMailAdapter.java
+++ b/mailet/src/main/java/org/apache/jsieve/mailet/SieveMailAdapter.java
@@ -445,5 +445,17 @@
         }
     }
 
+    public boolean isInBodyText(List<String> phrasesCaseInsensitive) throws SieveMailException {
+        throw new SieveMailException("Not yet implemented");
+    }
+
+    public boolean isInBodyRaw(List<String> phrasesCaseInsensitive) throws SieveMailException {
+        throw new SieveMailException("Not yet implemented");
+    }
+
+    public boolean isInBodyContent(List<String> contentTypes, List<String> phrasesCaseInsensitive) throws SieveMailException {
+        throw new SieveMailException("Not yet implemented");
+    }
+
     public void setContext(SieveContext context) {}
 }
diff --git a/pom.xml b/pom.xml
index a4439c6..8054cf5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -87,6 +87,8 @@
         <geronimo-activation.version>1.1</geronimo-activation.version>
         <geronimo-javamail.version>1.8.3</geronimo-javamail.version>
         <commons-io.version>2.1</commons-io.version>
+        <mockito-core.version>1.9.0</mockito-core.version>
+        <assertj.version>1.7.1</assertj.version>
     </properties>
 
     <dependencyManagement>
@@ -213,6 +215,12 @@
                 <artifactId>geronimo-javamail_1.4_mail</artifactId>
                 <version>${geronimo-javamail.version}</version>
             </dependency>
+            <dependency>
+                <groupId>org.mockito</groupId>
+                <artifactId>mockito-core</artifactId>
+                <version>${mockito-core.version}</version>
+                <scope>test</scope>
+            </dependency>
         </dependencies>
     </dependencyManagement>
 
diff --git a/util/src/main/java/org/apache/jsieve/util/check/ScriptCheckMailAdapter.java b/util/src/main/java/org/apache/jsieve/util/check/ScriptCheckMailAdapter.java
index 53c4496..58d089e 100644
--- a/util/src/main/java/org/apache/jsieve/util/check/ScriptCheckMailAdapter.java
+++ b/util/src/main/java/org/apache/jsieve/util/check/ScriptCheckMailAdapter.java
@@ -275,18 +275,16 @@
         }
     }
 
-    public boolean isInBodyText(String phraseCaseInsensitive) throws SieveMailException {
-        boolean result = false;
-        if (mail != null) {
-            try {
-                result = mail.getContent().toString().toLowerCase().contains(phraseCaseInsensitive);
-            } catch (MessagingException e) {
-                throw new SieveMailException(e);
-            } catch (IOException e) {
-                throw new SieveMailException(e);
-            }
-        }
-        return result;
+    public boolean isInBodyText(List<String> phrasesCaseInsensitive) throws SieveMailException {
+        throw new SieveMailException("Not yet implemented");
+    }
+
+    public boolean isInBodyRaw(List<String> phrasesCaseInsensitive) throws SieveMailException {
+        throw new SieveMailException("Not yet implemented");
+    }
+
+    public boolean isInBodyContent(List<String> contentTypes, List<String> phrasesCaseInsensitive) throws SieveMailException {
+        throw new SieveMailException("Not yet implemented");
     }
 
     public void setContext(SieveContext context) {