jspwiki-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ajaqu...@apache.org
Subject svn commit: r822989 - in /incubator/jspwiki/trunk: src/java/org/apache/wiki/auth/ src/java/org/apache/wiki/filters/ src/java/org/apache/wiki/tags/ src/java/org/apache/wiki/ui/stripes/ tests/java/org/apache/wiki/ tests/java/org/apache/wiki/auth/ tests/j...
Date Thu, 08 Oct 2009 01:16:09 GMT
Author: ajaquith
Date: Thu Oct  8 01:16:08 2009
New Revision: 822989

URL: http://svn.apache.org/viewvc?rev=822989&view=rev
Log:
First checkin of new content-inspection package, designed to refactor SpamFilter functions into a series of stacked Inspector classes that can analyze and score content in multiple ways, including for spam detection. See the package.html and Javadocs for detailed descriptions on the API design. These features will be used by ANY and ALL ActionBeans to allow for spam filtering via the @SpamProtect annotation and SpamProtect JSP tag. Three notable limitations of this checkin: 1) no CAPTCHA integration yet; 2) Comment.jsp is temporarily broken; 3) User registration not hooked into content inspection just yet. All will be addressed soon.

Modified:
    incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/UserManager.java
    incubator/jspwiki/trunk/src/java/org/apache/wiki/filters/SpamFilter.java
    incubator/jspwiki/trunk/src/java/org/apache/wiki/tags/SpamProtectTag.java
    incubator/jspwiki/trunk/src/java/org/apache/wiki/ui/stripes/SpamInterceptor.java
    incubator/jspwiki/trunk/tests/java/org/apache/wiki/TestEngine.java
    incubator/jspwiki/trunk/tests/java/org/apache/wiki/auth/UserManagerTest.java
    incubator/jspwiki/trunk/tests/java/org/apache/wiki/filters/SpamFilterTest.java
    incubator/jspwiki/trunk/tests/java/org/apache/wiki/ui/stripes/SpamInterceptorTest.java

Modified: incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/UserManager.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/UserManager.java?rev=822989&r1=822988&r2=822989&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/UserManager.java (original)
+++ incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/UserManager.java Thu Oct  8 01:16:08 2009
@@ -25,14 +25,20 @@
 import java.security.Permission;
 import java.security.Principal;
 import java.text.MessageFormat;
-import java.util.*;
+import java.util.Map;
+import java.util.Properties;
+import java.util.ResourceBundle;
+import java.util.WeakHashMap;
 
 import javax.mail.MessagingException;
 import javax.mail.internet.AddressException;
 import javax.security.auth.login.LoginException;
 import javax.servlet.http.HttpServletRequest;
 
-import org.apache.wiki.*;
+import org.apache.wiki.NoRequiredPropertyException;
+import org.apache.wiki.WikiContext;
+import org.apache.wiki.WikiEngine;
+import org.apache.wiki.WikiSession;
 import org.apache.wiki.api.WikiException;
 import org.apache.wiki.auth.permissions.AllPermission;
 import org.apache.wiki.auth.permissions.WikiPermission;
@@ -43,8 +49,6 @@
 import org.apache.wiki.event.WikiEventListener;
 import org.apache.wiki.event.WikiEventManager;
 import org.apache.wiki.event.WikiSecurityEvent;
-import org.apache.wiki.filters.PageFilter;
-import org.apache.wiki.filters.SpamFilter;
 import org.apache.wiki.i18n.InternationalizationManager;
 import org.apache.wiki.log.Logger;
 import org.apache.wiki.log.LoggerFactory;
@@ -498,24 +502,6 @@
         InputValidator validator = new InputValidator( SESSION_MESSAGES, context );
         ResourceBundle rb = context.getBundle( InternationalizationManager.CORE_BUNDLE );
 
-        //
-        //  Query the SpamFilter first
-        //
-        
-        List<PageFilter> ls = m_engine.getFilterManager().getFilterList();
-        for( PageFilter pf : ls )
-        {
-            if( pf instanceof SpamFilter )
-            {
-                if( ((SpamFilter)pf).isValidUserProfile( context, profile ) == false )
-                {
-                    session.addMessage( SESSION_MESSAGES, "Invalid userprofile" );
-                    return;
-                }
-                break;
-            }
-        }
-        
         // If container-managed auth and user not logged in, throw an error
         if ( m_engine.getAuthenticationManager().isContainerAuthenticated()
              && !context.getWikiSession().isAuthenticated() )

Modified: incubator/jspwiki/trunk/src/java/org/apache/wiki/filters/SpamFilter.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/java/org/apache/wiki/filters/SpamFilter.java?rev=822989&r1=822988&r2=822989&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/java/org/apache/wiki/filters/SpamFilter.java (original)
+++ incubator/jspwiki/trunk/src/java/org/apache/wiki/filters/SpamFilter.java Thu Oct  8 01:16:08 2009
@@ -20,49 +20,20 @@
  */
 package org.apache.wiki.filters;
 
-import java.io.*;
-import java.util.*;
-import java.util.regex.MatchResult;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.regex.PatternSyntaxException;
+import java.util.Properties;
 
 import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.jsp.PageContext;
 
-import net.sf.akismet.Akismet;
-import net.sourceforge.stripes.util.CryptoUtil;
-import net.sourceforge.stripes.util.bean.NoSuchPropertyException;
-import net.sourceforge.stripes.util.bean.PropertyExpression;
-import net.sourceforge.stripes.util.bean.PropertyExpressionEvaluation;
-
-import org.apache.commons.jrcs.diff.*;
-import org.apache.commons.jrcs.diff.myers.MyersDiff;
-import org.apache.commons.lang.time.StopWatch;
-import org.apache.wiki.InternalWikiException;
+import org.apache.commons.jrcs.diff.DifferentiationFailedException;
 import org.apache.wiki.WikiContext;
 import org.apache.wiki.WikiEngine;
-import org.apache.wiki.WikiProvider;
-import org.apache.wiki.action.WikiActionBean;
-import org.apache.wiki.action.WikiContextFactory;
-import org.apache.wiki.api.ModuleData;
 import org.apache.wiki.api.WikiPage;
-import org.apache.wiki.attachment.Attachment;
-import org.apache.wiki.auth.user.UserProfile;
-import org.apache.wiki.content.MissingParameterException;
-import org.apache.wiki.content.PageNotFoundException;
+import org.apache.wiki.content.inspect.*;
 import org.apache.wiki.log.Logger;
 import org.apache.wiki.log.LoggerFactory;
-import org.apache.wiki.providers.ProviderException;
-import org.apache.wiki.ui.EditorManager;
-import org.apache.wiki.ui.stripes.WikiActionBeanContext;
-import org.apache.wiki.util.FileUtil;
-import org.apache.wiki.util.TextUtil;
 
 /**
- * This is Herb, the JSPWiki spamfilter that can also do choke modifications.
- * Parameters:
+ * Spam filter that can also do choke modifications. Parameters:
  * <ul>
  * <li>wordlist - Page name where the regexps are found. Use [{SET
  * spamwords='regexp list separated with spaces'}] on that page. Default is
@@ -75,8 +46,8 @@
  * is 5.</li>
  * <li>similarchanges - How many similar page changes are allowed before the
  * host is banned. Default is 2. (since 2.4.72)</li>
- * <li>bantime - How long an IP address stays on the temporary ban list
- * (default is 60 for 60 minutes).</li>
+ * <li>bantime - How long an IP address stays on the temporary ban list (default
+ * is 60 for 60 minutes).</li>
  * <li>maxurls - How many URLs can be added to the page before it is considered
  * spam (default is 5)</li>
  * <li>akismet-apikey - The Akismet API key (see akismet.org)</li>
@@ -87,8 +58,8 @@
  * <li>strategy - Sets the filtering strategy to use. If set to "eager", will
  * stop at the first probable match, and won't consider any other tests. This is
  * the default, as it's considerably lighter. If set to "score", will go through
- * all of the tests and calculates a score for the spam, which is then compared
- * to a filter level value.
+ * all of the tests and until a threshold score is reached. Setting the strategy
+ * to "eager" is equivalent to setting the scoring theshold to 1.
  * </ul>
  * <p>
  * Please see the default editors/plain.jsp for examples on how the SpamFilter
@@ -102,1015 +73,17 @@
  */
 public class SpamFilter extends BasicPageFilter
 {
-    private static final String ATTR_SPAMFILTER_SCORE = "spamfilter.score";
-
-    private static final String REASON_REGEXP = "Regexp";
-
-    private static final String REASON_IP_BANNED_TEMPORARILY = "IPBannedTemporarily";
-
-    private static final String REASON_BOT_TRAP = "BotTrap";
-
-    private static final String REASON_AKISMET = "Akismet";
-
-    private static final String REASON_TOO_MANY_URLS = "TooManyUrls";
-
-    private static final String REASON_SIMILAR_MODIFICATIONS = "SimilarModifications";
-
-    private static final String REASON_TOO_MANY_MODIFICATIONS = "TooManyModifications";
-
-    private static final String REASON_UTF8_TRAP = "UTF8Trap";
-
-    private static final String LISTVAR = "spamwords";
-
-    /** Request parameter containing the UTF8 check. */
-    public static final String REQ_ENCODING_CHECK = "__wikiEncodingcheck";
-
-    /** Request parameter containing the encoded payload. */
-    public static final String REQ_SPAM_PARAM = "__wikiCheck";
-
-    /**
-     * The filter property name for specifying the page which contains the list
-     * of spamwords. Value is <tt>{@value}</tt>.
-     */
-    public static final String PROP_WORDLIST = "wordlist";
-
-    /**
-     * The filter property name for the page to which you are directed if Herb
-     * rejects your edit. Value is <tt>{@value}</tt>.
-     */
-    public static final String PROP_ERRORPAGE = "errorpage";
-
-    /**
-     * The filter property name for specifying how many changes is any given IP
-     * address allowed to do per minute. Value is <tt>{@value}</tt>.
-     */
-    public static final String PROP_PAGECHANGES = "pagechangesinminute";
-
-    /**
-     * The filter property name for specifying how many similar changes are
-     * allowed before a host is banned. Value is <tt>{@value}</tt>.
-     */
-    public static final String PROP_SIMILARCHANGES = "similarchanges";
-
-    /**
-     * The filter property name for specifying how long a host is banned. Value
-     * is <tt>{@value}</tt>.
-     */
-    public static final String PROP_BANTIME = "bantime";
-
-    /**
-     * The filter property name for the attachment containing the blacklist.
-     * Value is <tt>{@value}</tt>.
-     */
-    public static final String PROP_BLACKLIST = "blacklist";
-
-    /**
-     * The filter property name for specifying how many URLs can any given edit
-     * contain. Value is <tt>{@value}</tt>
-     */
-    public static final String PROP_MAXURLS = "maxurls";
-
-    /**
-     * The filter property name for specifying the Akismet API-key. Value is
-     * <tt>{@value}</tt>.
-     */
-    public static final String PROP_AKISMET_API_KEY = "akismet-apikey";
-
-    /**
-     * The filter property name for specifying whether authenticated users
-     * should be ignored. Value is <tt>{@value}</tt>.
-     */
-    public static final String PROP_IGNORE_AUTHENTICATED = "ignoreauthenticated";
-
-    /**
-     * The filter property name for specifying which captcha technology should
-     * be used. Value is <tt>{@value}</tt>.
-     */
-    public static final String PROP_CAPTCHA = "captcha";
-
-    /**
-     * The filter property name for specifying which filter strategy should be
-     * used. Value is <tt>{@value}</tt>.
-     */
-    public static final String PROP_FILTERSTRATEGY = "strategy";
-
-    /** The string specifying the "eager" strategy. Value is <tt>{@value}</tt>. */
-    public static final String STRATEGY_EAGER = "eager";
-
-    /** The string specifying the "score" strategy. Value is <tt>{@value}</tt>. */
-    public static final String STRATEGY_SCORE = "score";
-
-    private static final String URL_REGEXP = "(http://|https://|mailto:)([A-Za-z0-9_/\\.\\+\\?\\#\\-\\@=&;]+)";
-
-    private String m_forbiddenWordsPage = "SpamFilterWordList";
-
-    private String m_errorPage = "RejectedMessage";
-
-    private String m_blacklist = "SpamFilterWordList/blacklist.txt";
-
-    private Collection<Pattern> m_spamPatterns = null;
-
-    private Date m_lastRebuild = new Date( 0L );
-
-    private static Logger c_spamlog = LoggerFactory.getLogger( "SpamLog" );
-
-    private static Logger log = LoggerFactory.getLogger( SpamFilter.class );
-
-    private Vector<Host> m_temporaryBanList = new Vector<Host>();
-
-    private int m_banTime = 60; // minutes
-
-    private Vector<Host> m_lastModifications = new Vector<Host>();
-
-    /**
-     * How many times a single IP address can change a page per minute?
-     */
-    private int m_limitSinglePageChanges = 5;
-
-    /**
-     * How many times can you add the exact same string to a page?
-     */
-    private int m_limitSimilarChanges = 2;
-
-    /**
-     * How many URLs can be added at maximum.
-     */
-    private int m_maxUrls = 10;
-
-    private Pattern m_urlPattern;
-
-    private Akismet m_akismet;
-
-    private String m_akismetAPIKey = null;
-
-    private boolean m_useCaptcha = false;
-
-    /** The limit at which we consider something to be spam. */
-    private int m_scoreLimit = 1;
-
-    /**
-     * If set to true, will ignore anyone who is in Authenticated role.
-     */
-    private boolean m_ignoreAuthenticated = false;
-
-    private boolean m_stopAtFirstMatch = true;
-
-    private static String c_hashFieldName;
-
-    private static long c_lastUpdate;
-
-    /**
-     * The HASH_DELAY value is a maximum amount of time that an user can keep a
-     * session open, because after the value has expired, we will invent a new
-     * hash field name. By default this is {@value} hours, which should be ample
-     * time for someone.
-     */
-    private static final long HASH_DELAY = 24;
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void initialize( WikiEngine engine, Properties properties )
-    {
-        m_forbiddenWordsPage = properties.getProperty( PROP_WORDLIST, m_forbiddenWordsPage );
-        m_errorPage = properties.getProperty( PROP_ERRORPAGE, m_errorPage );
-
-        m_limitSinglePageChanges = TextUtil.getIntegerProperty( properties, PROP_PAGECHANGES, m_limitSinglePageChanges );
-
-        m_limitSimilarChanges = TextUtil.getIntegerProperty( properties, PROP_SIMILARCHANGES, m_limitSimilarChanges );
-
-        m_maxUrls = TextUtil.getIntegerProperty( properties, PROP_MAXURLS, m_maxUrls );
-
-        m_banTime = TextUtil.getIntegerProperty( properties, PROP_BANTIME, m_banTime );
-
-        m_blacklist = properties.getProperty( PROP_BLACKLIST, m_blacklist );
-
-        m_ignoreAuthenticated = TextUtil.getBooleanProperty( properties, PROP_IGNORE_AUTHENTICATED, m_ignoreAuthenticated );
-
-        m_useCaptcha = properties.getProperty( PROP_CAPTCHA, "" ).equals( "asirra" );
-
-        try
-        {
-            m_urlPattern = Pattern.compile( URL_REGEXP );
-        }
-        catch( PatternSyntaxException e )
-        {
-            log.error( "Internal error: Someone put in a faulty pattern.", e );
-            throw new InternalWikiException( "Faulty pattern." );
-        }
-
-        m_akismetAPIKey = TextUtil.getStringProperty( properties, PROP_AKISMET_API_KEY, m_akismetAPIKey );
-
-        m_stopAtFirstMatch = TextUtil.getStringProperty( properties, PROP_FILTERSTRATEGY, STRATEGY_EAGER ).equals( STRATEGY_EAGER );
-
-        log.info( "# Spam filter initialized.  Temporary ban time " + m_banTime + " mins, max page changes/minute: "
-                  + m_limitSinglePageChanges );
-
-    }
-
-    private static final int REJECT = 0;
-
-    private static final int ACCEPT = 1;
-
-    private static final int NOTE = 2;
-
-    private static String log( WikiContext ctx, int type, String source, String message )
-    {
-        message = TextUtil.replaceString( message, "\r\n", "\\r\\n" );
-        message = TextUtil.replaceString( message, "\"", "\\\"" );
-
-        String uid = getUniqueID();
-
-        WikiPage page = ctx.getPage();
-        String pageName = page == null ? "(no page)" : page.getName();
-        String reason = "UNKNOWN";
-        String addr = ctx.getHttpRequest() != null ? ctx.getHttpRequest().getRemoteAddr() : "-";
-
-        switch( type )
-        {
-            case REJECT:
-                reason = "REJECTED";
-                break;
-            case ACCEPT:
-                reason = "ACCEPTED";
-                break;
-            case NOTE:
-                reason = "NOTE";
-                break;
-            default:
-                throw new InternalWikiException( "Illegal type " + type );
-        }
-
-        c_spamlog.info( reason + " " + source + " " + uid + " " + addr + " \"" + pageName + "\" " + message );
-
-        return uid;
-    }
-
-    /** {@inheritDoc} */
-    public String preSave( WikiContext context, String content ) throws RedirectException
-    {
-        cleanBanList();
-        refreshBlacklists( context );
-
-        Change change = getChange( context, content );
-
-        if( !ignoreThisUser( context ) )
-        {
-            checkBanList( context, change );
-            checkSinglePageChange( context, content, change );
-            checkPatternList( context, content, change );
-        }
-
-        if( !m_stopAtFirstMatch )
-        {
-            Integer score = (Integer) context.getVariable( ATTR_SPAMFILTER_SCORE );
-
-            if( score != null && score.intValue() >= m_scoreLimit )
-            {
-                throw new RedirectException( "Herb says you got too many points", getRedirectPage( context ) );
-            }
-        }
-
-        log( context, ACCEPT, "-", change.toString() );
-        return content;
-    }
-
-    private void checkStrategy( WikiContext context, String error, String message ) throws RedirectException
-    {
-        if( m_stopAtFirstMatch )
-        {
-            throw new RedirectException( message, getRedirectPage( context ) );
-        }
-
-        Integer score = (Integer) context.getVariable( ATTR_SPAMFILTER_SCORE );
-
-        if( score != null )
-            score = score + 1;
-        else
-            score = 1;
-
-        context.setVariable( ATTR_SPAMFILTER_SCORE, score );
-    }
-
-    /**
-     * Parses a list of patterns and returns a Collection of compiled Pattern
-     * objects.
-     * 
-     * @param source
-     * @param list
-     * @return A Collection of the Patterns that were found from the lists.
-     */
-    private Collection<Pattern> parseWordList( WikiPage source, String list )
-    {
-        ArrayList<Pattern> compiledpatterns = new ArrayList<Pattern>();
-
-        if( list != null )
-        {
-            StringTokenizer tok = new StringTokenizer( list, " \t\n" );
-
-            while ( tok.hasMoreTokens() )
-            {
-                String pattern = tok.nextToken();
-
-                try
-                {
-                    compiledpatterns.add( Pattern.compile( pattern ) );
-                }
-                catch( PatternSyntaxException e )
-                {
-                    log.debug( "Malformed spam filter pattern " + pattern );
-
-                    source.setAttribute( "error", "Malformed spam filter pattern " + pattern );
-                }
-            }
-        }
-
-        return compiledpatterns;
-    }
-
-    /**
-     * Takes a MT-Blacklist -formatted blacklist and returns a list of compiled
-     * Pattern objects.
-     * 
-     * @param list
-     * @return The parsed blacklist patterns.
-     */
-    private Collection<Pattern> parseBlacklist( String list )
-    {
-        ArrayList<Pattern> compiledpatterns = new ArrayList<Pattern>();
-
-        if( list != null )
-        {
-            try
-            {
-                BufferedReader in = new BufferedReader( new StringReader( list ) );
-
-                String line;
-
-                while ( (line = in.readLine()) != null )
-                {
-                    line = line.trim();
-                    if( line.length() == 0 )
-                        continue; // Empty line
-                    if( line.startsWith( "#" ) )
-                        continue; // It's a comment
-
-                    int ws = line.indexOf( ' ' );
-
-                    if( ws == -1 )
-                        ws = line.indexOf( '\t' );
-
-                    if( ws != -1 )
-                        line = line.substring( 0, ws );
-
-                    try
-                    {
-                        compiledpatterns.add( Pattern.compile( line ) );
-                    }
-                    catch( PatternSyntaxException e )
-                    {
-                        log.debug( "Malformed spam filter pattern " + line );
-                    }
-                }
-            }
-            catch( IOException e )
-            {
-                log.info( "Could not read patterns; returning what I got", e );
-            }
-        }
-
-        return compiledpatterns;
-    }
-
-    /**
-     * Takes a single page change and performs a load of tests on the content
-     * change. An admin can modify anything.
-     * 
-     * @param context
-     * @param content
-     * @throws RedirectException
-     */
-    private synchronized void checkSinglePageChange( WikiContext context, String content, Change change ) throws RedirectException
-    {
-        HttpServletRequest req = context.getHttpRequest();
-
-        if( req != null )
-        {
-            String addr = req.getRemoteAddr();
-            int hostCounter = 0;
-            int changeCounter = 0;
-
-            log.debug( "Change is " + change.m_change );
-
-            long time = System.currentTimeMillis() - 60 * 1000L; // 1 minute
-
-            for( Iterator<Host> i = m_lastModifications.iterator(); i.hasNext(); )
-            {
-                Host host = i.next();
-
-                //
-                // Check if this item is invalid
-                //
-                if( host.getAddedTime() < time )
-                {
-                    log.debug( "Removed host " + host.getAddress() + " from modification queue (expired)" );
-                    i.remove();
-                    continue;
-                }
-
-                //
-                // Check if this IP address has been seen before
-                //
-
-                if( host.getAddress().equals( addr ) )
-                {
-                    hostCounter++;
-                }
-
-                //
-                // Check, if this change has been seen before
-                //
-
-                if( host.getChange() != null && host.getChange().equals( change ) )
-                {
-                    changeCounter++;
-                }
-            }
-
-            //
-            // Now, let's check against the limits.
-            //
-            if( hostCounter >= m_limitSinglePageChanges )
-            {
-                Host host = new Host( addr, null );
-
-                m_temporaryBanList.add( host );
-
-                String uid = log( context, REJECT, REASON_TOO_MANY_MODIFICATIONS, change.m_change );
-                log.info( "SPAM:TooManyModifications (" + uid + "). Added host " + addr
-                          + " to temporary ban list for doing too many modifications/minute" );
-                checkStrategy( context, REASON_TOO_MANY_MODIFICATIONS,
-                               "Herb says you look like a spammer, and I trust Herb! (Incident code " + uid + ")" );
-            }
-
-            if( changeCounter >= m_limitSimilarChanges )
-            {
-                Host host = new Host( addr, null );
-
-                m_temporaryBanList.add( host );
-
-                String uid = log( context, REJECT, REASON_SIMILAR_MODIFICATIONS, change.m_change );
-
-                log.info( "SPAM:SimilarModifications (" + uid + "). Added host " + addr
-                          + " to temporary ban list for doing too many similar modifications" );
-                checkStrategy( context, REASON_SIMILAR_MODIFICATIONS,
-                               "Herb says you look like a spammer, and I trust Herb! (Incident code " + uid + ")" );
-            }
-
-            //
-            // Calculate the number of links in the addition.
-            //
-
-            String tstChange = change.toString();
-            int urlCounter = 0;
-
-            Matcher matcher = m_urlPattern.matcher( tstChange );
-            while ( matcher.find() )
-            {
-                MatchResult m = matcher.toMatchResult();
-
-                tstChange = tstChange.substring( m.end( 0 ) );
-
-                urlCounter++;
-            }
-
-            if( urlCounter > m_maxUrls )
-            {
-                Host host = new Host( addr, null );
-
-                m_temporaryBanList.add( host );
-
-                String uid = log( context, REJECT, REASON_TOO_MANY_URLS, change.toString() );
-
-                log.info( "SPAM:TooManyUrls (" + uid + "). Added host " + addr + " to temporary ban list for adding too many URLs" );
-                checkStrategy( context, REASON_TOO_MANY_URLS,
-                               "Herb says you look like a spammer, and I trust Herb! (Incident code " + uid + ")" );
-            }
-
-            //
-            // Check bot trap
-            //
-
-            checkBotTrap( context, change );
-
-            //
-            // Check UTF-8 mangling
-            //
-
-            checkUTF8( context, change );
-
-            //
-            // Do Akismet check. This is good to be the last, because this is
-            // the most
-            // expensive operation.
-            //
-
-            checkAkismet( context, change );
-
-            m_lastModifications.add( new Host( addr, change ) );
-        }
-    }
-
-    /**
-     * Checks against the akismet system.
-     * 
-     * @param context
-     * @param change
-     * @throws RedirectException
-     */
-    private void checkAkismet( WikiContext context, Change change ) throws RedirectException
-    {
-        if( m_akismetAPIKey != null )
-        {
-            if( m_akismet == null )
-            {
-                log.info( "Initializing Akismet spam protection." );
-
-                m_akismet = new Akismet( m_akismetAPIKey, context.getEngine().getBaseURL() );
-
-                if( !m_akismet.verifyAPIKey() )
-                {
-                    log.error( "Akismet API key cannot be verified.  Please check your config." );
-                    m_akismetAPIKey = null;
-                    m_akismet = null;
-                }
-            }
-
-            HttpServletRequest req = context.getHttpRequest();
-
-            //
-            // Akismet will mark all empty statements as spam, so we'll just
-            // ignore them.
-            //
-            if( change.m_adds == 0 && change.m_removals > 0 )
-            {
-                return;
-            }
-
-            if( req != null && m_akismet != null )
-            {
-                log.debug( "Calling Akismet to check for spam..." );
-
-                StopWatch sw = new StopWatch();
-                sw.start();
-
-                String ipAddress = req.getRemoteAddr();
-                String userAgent = req.getHeader( "User-Agent" );
-                String referrer = req.getHeader( "Referer" );
-                String permalink = context.getViewURL( context.getPage().getName() );
-                String commentType = context.getRequestContext().equals( WikiContext.COMMENT ) ? "comment" : "edit";
-                String commentAuthor = context.getCurrentUser().getName();
-                String commentAuthorEmail = null;
-                String commentAuthorURL = null;
-
-                boolean isSpam = m_akismet.commentCheck( ipAddress, userAgent, referrer, permalink, commentType, commentAuthor,
-                                                         commentAuthorEmail, commentAuthorURL, change.toString(), null );
-
-                sw.stop();
-
-                log.debug( "Akismet request done in: " + sw );
-
-                if( isSpam )
-                {
-                    // Host host = new Host( ipAddress, null );
-
-                    // m_temporaryBanList.add( host );
-
-                    String uid = log( context, REJECT, REASON_AKISMET, change.toString() );
-
-                    log.info( "SPAM:Akismet (" + uid + "). Akismet thinks this change is spam; added host to temporary ban list." );
-
-                    checkStrategy( context, REASON_AKISMET,
-                                   "Akismet tells Herb you're a spammer, Herb trusts Akismet, and I trust Herb! (Incident code "
-                                       + uid + ")" );
-                }
-            }
-        }
-    }
-
-    /**
-     * Returns a static string which can be used to detect spambots which just
-     * wildly fill in all the fields.
-     * 
-     * @return A string
-     */
-    public static String getBotFieldName()
-    {
-        return "submit_auth";
-    }
-
-    /**
-     * This checks whether an invisible field is available in the request, and
-     * whether it's contents are suspected spam.
-     * 
-     * @param context
-     * @param change
-     * @throws RedirectException
-     */
-    private void checkBotTrap( WikiContext context, Change change ) throws RedirectException
-    {
-        HttpServletRequest request = context.getHttpRequest();
-
-        if( request != null )
-        {
-            String unspam = request.getParameter( getBotFieldName() );
-            if( unspam != null && unspam.length() > 0 )
-            {
-                String uid = log( context, REJECT, REASON_BOT_TRAP, change.toString() );
-
-                log.info( "SPAM:BotTrap (" + uid + ").  Wildly behaving bot detected." );
-
-                checkStrategy( context, REASON_BOT_TRAP, "Spamming attempt detected. (Incident code " + uid + ")" );
-
-            }
-        }
-    }
-
-    private void checkUTF8( WikiContext context, Change change ) throws RedirectException
-    {
-        HttpServletRequest request = context.getHttpRequest();
-
-        if( request != null )
-        {
-            String utf8field = request.getParameter( "encodingcheck" );
-
-            if( utf8field != null && !utf8field.equals( "\u3041" ) )
-            {
-                String uid = log( context, REJECT, REASON_UTF8_TRAP, change.toString() );
-
-                log.info( "SPAM:UTF8Trap (" + uid + ").  Wildly posting dumb bot detected." );
-
-                checkStrategy( context, REASON_UTF8_TRAP, "Spamming attempt detected. (Incident code " + uid + ")" );
-            }
-        }
-    }
-
-    /**
-     * Goes through the ban list and cleans away any host which has expired from
-     * it.
-     */
-    private synchronized void cleanBanList()
-    {
-        long now = System.currentTimeMillis();
-
-        for( Iterator<Host> i = m_temporaryBanList.iterator(); i.hasNext(); )
-        {
-            Host host = i.next();
-
-            if( host.getReleaseTime() < now )
-            {
-                log.debug( "Removed host " + host.getAddress() + " from temporary ban list (expired)" );
-                i.remove();
-            }
-        }
-    }
-
-    /**
-     * Checks the ban list if the IP address of the changer is already on it.
-     * 
-     * @param context
-     * @throws RedirectException
-     */
-
-    private void checkBanList( WikiContext context, Change change ) throws RedirectException
-    {
-        HttpServletRequest req = context.getHttpRequest();
-
-        if( req != null )
-        {
-            String remote = req.getRemoteAddr();
-
-            long now = System.currentTimeMillis();
-
-            for( Host host : m_temporaryBanList )
-            {
-                if( host.getAddress().equals( remote ) )
-                {
-                    long timeleft = (host.getReleaseTime() - now) / 1000L;
-
-                    log( context, REJECT, REASON_IP_BANNED_TEMPORARILY, change.m_change );
-
-                    checkStrategy( context, REASON_IP_BANNED_TEMPORARILY,
-                                   "You have been temporarily banned from modifying this wiki. (" + timeleft
-                                       + " seconds of ban left)" );
-                }
-            }
-        }
-
-    }
-
-    /**
-     * If the spam filter notices changes in the black list page, it will
-     * refresh them automatically.
-     * 
-     * @param context
-     */
-    private void refreshBlacklists( WikiContext context )
-    {
-        try
-        {
-            WikiPage source = null;
-            Attachment att = null;
-            try
-            {
-                source = context.getEngine().getPage( m_forbiddenWordsPage );
-                att = context.getEngine().getAttachmentManager().getAttachmentInfo( context, m_blacklist );
-            }
-            catch( PageNotFoundException e )
-            {
-                // No worries
-            }
-
-            boolean rebuild = false;
-
-            //
-            // Rebuild, if the page or the attachment has changed since.
-            //
-            if( source != null )
-            {
-                if( m_spamPatterns == null || m_spamPatterns.isEmpty() || source.getLastModified().after( m_lastRebuild ) )
-                {
-                    rebuild = true;
-                }
-            }
-
-            if( att != null )
-            {
-                if( m_spamPatterns == null || m_spamPatterns.isEmpty() || att.getLastModified().after( m_lastRebuild ) )
-                {
-                    rebuild = true;
-                }
-            }
-
-            //
-            // Do the actual rebuilding. For simplicity's sake, we always
-            // rebuild the complete
-            // filter list regardless of what changed.
-            //
-
-            if( rebuild )
-            {
-                m_lastRebuild = new Date();
-
-                m_spamPatterns = parseWordList( source, (source != null) ? (String) source.getAttribute( LISTVAR ) : null );
-
-                log.info( "Spam filter reloaded - recognizing " + m_spamPatterns.size() + " patterns from page "
-                          + m_forbiddenWordsPage );
-
-                if( att != null )
-                {
-                    InputStream in = context.getEngine().getAttachmentManager().getAttachmentStream( att );
-
-                    StringWriter out = new StringWriter();
-
-                    FileUtil.copyContents( new InputStreamReader( in, "UTF-8" ), out );
-
-                    Collection<Pattern> blackList = parseBlacklist( out.toString() );
-
-                    log.info( "...recognizing additional " + blackList.size() + " patterns from blacklist " + m_blacklist );
-
-                    m_spamPatterns.addAll( blackList );
-                }
-            }
-        }
-        catch( IOException ex )
-        {
-            log.info( "Unable to read attachment data, continuing...", ex );
-        }
-        catch( ProviderException ex )
-        {
-            log.info( "Failed to read spam filter attachment, continuing...", ex );
-        }
-
-    }
-
-    /**
-     * Does a check against a known pattern list.
-     * 
-     * @param context
-     * @param content
-     * @param change
-     * @throws RedirectException
-     */
-    private void checkPatternList( WikiContext context, String content, Change change ) throws RedirectException
-    {
-        //
-        // If we have no spam patterns defined, or we're trying to save
-        // the page containing the patterns, just return.
-        //
-        if( m_spamPatterns == null || context.getPage().getName().equals( m_forbiddenWordsPage ) )
-        {
-            return;
-        }
-
-        String ch = change.toString();
-
-        if( context.getHttpRequest() != null )
-            ch += context.getHttpRequest().getRemoteAddr();
-
-        for( Pattern p : m_spamPatterns )
-        {
-            // log.debug("Attempting to match page contents with
-            // "+p.getPattern());
-
-            Matcher matcher = p.matcher( ch );
-            if( matcher.find() )
-            {
-                //
-                // Spam filter has a match.
-                //
-                String uid = log( context, REJECT, REASON_REGEXP + "(" + p.pattern() + ")", change.toString() );
-
-                log.info( "SPAM:Regexp (" + uid + "). Content matches the spam filter '" + p.pattern() + "'" );
-
-                checkStrategy( context, REASON_REGEXP, "Herb says '" + p.pattern()
-                                                       + "' is a bad spam word and I trust Herb! (Incident code " + uid + ")" );
-            }
-        }
-    }
-
-    private void checkPatternList( WikiContext context, String content, String change ) throws RedirectException
-    {
-        Change c = new Change();
-        c.m_change = change;
-        checkPatternList( context, content, c );
-    }
-
     /**
-     * Creates a simple text string describing the added content.
-     * 
-     * @param context
-     * @param newText
-     * @return Empty string, if there is no change.
-     */
-    private static Change getChange( WikiContext context, String newText )
-    {
-        WikiPage page = context.getPage();
-        StringBuffer change = new StringBuffer();
-        WikiEngine engine = context.getEngine();
-        // Get current page version
-
-        Change ch = new Change();
-
-        try
-        {
-            String oldText = engine.getPureText( page.getName(), WikiProvider.LATEST_VERSION );
-
-            String[] first = Diff.stringToArray( oldText );
-            String[] second = Diff.stringToArray( newText );
-            Revision rev = Diff.diff( first, second, new MyersDiff() );
-
-            if( rev == null || rev.size() == 0 )
-            {
-                return ch;
-            }
-
-            for( int i = 0; i < rev.size(); i++ )
-            {
-                Delta d = rev.getDelta( i );
-
-                if( d instanceof AddDelta )
-                {
-                    d.getRevised().toString( change, "", "\r\n" );
-                    ch.m_adds++;
-                }
-                else if( d instanceof ChangeDelta )
-                {
-                    d.getRevised().toString( change, "", "\r\n" );
-                    ch.m_adds++;
-                }
-                else if( d instanceof DeleteDelta )
-                {
-                    ch.m_removals++;
-                }
-            }
-        }
-        catch( DifferentiationFailedException e )
-        {
-            log.error( "Diff failed", e );
-        }
-
-        //
-        // Don't forget to include the change note, too
-        //
-        String changeNote = (String) page.getAttribute( WikiPage.CHANGENOTE );
-
-        if( changeNote != null )
-        {
-            change.append( "\r\n" );
-            change.append( changeNote );
-        }
-
-        //
-        // And author as well
-        //
-
-        if( page.getAuthor() != null )
-        {
-            change.append( "\r\n" + page.getAuthor() );
-        }
-
-        ch.m_change = change.toString();
-        return ch;
-    }
-
-    /**
-     * Returns true, if this user should be ignored. For example, admin users.
-     * 
-     * @param context
-     * @return True, if this users should be ignored.
-     */
-    private boolean ignoreThisUser( WikiContext context )
-    {
-        if( context.hasAdminPermissions() )
-        {
-            return true;
-        }
-
-        if( m_ignoreAuthenticated && context.getWikiSession().isAuthenticated() )
-        {
-            return true;
-        }
-
-        if( context.getVariable( "captcha" ) != null )
-        {
-            return true;
-        }
-
-        return false;
-    }
-
-    /**
-     * Returns a random string of six uppercase characters.
-     * 
-     * @return A random string
-     */
-    private static String getUniqueID()
-    {
-        StringBuilder sb = new StringBuilder();
-        Random rand = new Random();
-
-        for( int i = 0; i < 6; i++ )
-        {
-            char x = (char) ('A' + rand.nextInt( 26 ));
-
-            sb.append( x );
-        }
-
-        return sb.toString();
-    }
-
-    /**
-     * Returns a page to which we shall redirect, based on the current value of
-     * the "captcha" parameter.
-     * 
-     * @param ctx WikiContext
-     * @return An URL to redirect to
+     * The filter property name for the page to which you are directed if Herb
+     * rejects your edit. Value is <tt>{@value}</tt>.
      */
-    private String getRedirectPage( WikiContext ctx )
-    {
-        if( m_useCaptcha )
-            return ctx.getURL( WikiContext.NONE, "Captcha.jsp", "page=" + ctx.getEngine().encodeName( ctx.getPage().getName() ) );
-
-        return ctx.getURL( WikiContext.VIEW, m_errorPage );
-    }
+    public static final String PROP_ERRORPAGE = "errorpage";
 
     /**
-     * Checks whether the UserProfile matches certain checks.
-     * 
-     * @param profile The profile to check
-     * @param context The WikiContext
-     * @return False, if this userprofile is suspect and should not be allowed
-     *         to be added.
-     * @since 2.6.1
+     * The filter property name for specifying which captcha technology should
+     * be used. Value is <tt>{@value}</tt>.
      */
-    public boolean isValidUserProfile( WikiContext context, UserProfile profile )
-    {
-        try
-        {
-            checkPatternList( context, profile.getEmail(), profile.getEmail() );
-            checkPatternList( context, profile.getFullname(), profile.getFullname() );
-            checkPatternList( context, profile.getLoginName(), profile.getLoginName() );
-        }
-        catch( RedirectException e )
-        {
-            log.info( "Detected attempt to create a spammer user account (see above for rejection reason)" );
-            return false;
-        }
-
-        return true;
-    }
+    public static final String PROP_CAPTCHA = "captcha";
 
     /**
      * This method is used to calculate an unique code when submitting the page
@@ -1134,303 +107,85 @@
         return Long.toString( lastModified ^ remote );
     }
 
-    /**
-     * Returns the name of the hash field to be used in this request. The value
-     * is unique per session, and once the session has expired, you cannot edit
-     * anymore.
-     * 
-     * @param request The page request
-     * @return The name to be used in the hash field
-     * @since 2.6
-     */
-
-    public static final String getHashFieldName( HttpServletRequest request )
-    {
-        String hash = null;
-
-        if( request.getSession() != null )
-        {
-            hash = (String) request.getSession().getAttribute( "_hash" );
+    private String m_errorPage = "RejectedMessage";
 
-            if( hash == null )
-            {
-                hash = c_hashFieldName;
+    private static final String ATTR_SPAMFILTER_SCORE = "spamfilter.score";
 
-                request.getSession().setAttribute( "_hash", hash );
-            }
-        }
+    private InspectionPlan m_plan;
 
-        if( c_hashFieldName == null || c_lastUpdate < (System.currentTimeMillis() - HASH_DELAY * 60 * 60 * 1000) )
-        {
-            c_hashFieldName = getUniqueID().toLowerCase();
+    private boolean m_useCaptcha = false;
 
-            c_lastUpdate = System.currentTimeMillis();
-        }
+    private static Logger log = LoggerFactory.getLogger( SpamFilter.class );
 
-        return hash != null ? hash : c_hashFieldName;
+    public InspectionPlan getInspectionContext()
+    {
+        return m_plan;
     }
 
     /**
-     * <p>
-     * This method checks if the hash value is still valid, i.e. if it exists at
-     * all. This can occur in two cases: either this is a spam bot which is not
-     * adaptive, or it is someone who has been editing one page for too long,
-     * and their session has expired.
-     * </p>
-     * <p>
-     * If the hash is not valid, this method logs the incident in the spam log
-     * (it may or may not be spam, but it's rather likely that it is) and throws
-     * an exception.
-     * </p>
-     * <p>
-     * If the hash is valid, this method simply returns succeeds silently.
-     * </p>
-     * <p>
-     * Other than the differences in the method parameters and return syntax,
-     * this method operates in every other respect identically to
-     * {@link #checkHash(WikiContext, PageContext)}
-     * </p>
-     * 
-     * @param actionBean the WikiActionBean representing the editing activity
-     *            and page.
-     * @param beanProperties an array of Strings containing the names of
-     *            ActionBean properties that are considered "protected" by the
-     *            SpamFilter. Each value represents the name of a bean property.
-     *            For example, the value "text" which would be manipulated with
-     *            the ActionBean's <code>getText</code> and
-     *            <code>setText</code> methods). When spam parameters are
-     *            missing, the protected properties' values will be added to the
-     *            SpamFilter log for analysis.
-     * @throws MissingParameterException if one or more of the spam-protection
-     * parameters are missing or contain invalid values
-     * @since 3.0
+     * {@inheritDoc}
      */
-    public static final void validateSpamParams( WikiActionBean actionBean, String[] beanProperties )
-                                                                                                     throws MissingParameterException
+    @Override
+    public void initialize( WikiEngine engine, Properties properties )
     {
-        WikiActionBeanContext context = actionBean.getContext();
-        HttpServletRequest request = context.getRequest();
-
-        // Recover the encrypted parameter and then validate the trap and token
-        // fields
-        boolean spamParamsValid = false;
-        String encryptedParam = request.getParameter( REQ_SPAM_PARAM );
-        if( encryptedParam != null )
-        {
-            String payload = CryptoUtil.decrypt( encryptedParam );
-            if( payload != null )
-            {
-                String[] spamParams = payload.split( "\n" );
-                if( spamParams.length >= 2 )
-                {
-                    String trapParam = request.getParameter( spamParams[0] );
-                    String tokenParam = request.getParameter( spamParams[1] );
-
-                    // Trap parameter should be blank/null
-                    if( trapParam == null || trapParam.length() == 0 )
-                    {
-
-                        // Token parameter should simply be the session ID
-                        if( tokenParam != null && request.getSession().getId().equals( tokenParam ) )
-                        {
-                            // If we got here, everything validated ok!
-                            spamParamsValid = true;
-                        }
-                    }
-                }
-            }
-        }
-
-        if( !spamParamsValid )
-        {
-            // Collect all of the protected properties and log 'em
-            Map<String, Object> map = getBeanProperties( actionBean, beanProperties );
-            SpamFilter.log( actionBean.getContext(), SpamFilter.REJECT, "MissingHash", map.toString() );
-            throw new MissingParameterException( "Missing SpamProtect parameters! Likely to be a spammer.", null );
-        }
+        m_errorPage = properties.getProperty( PROP_ERRORPAGE, m_errorPage );
+        m_useCaptcha = properties.getProperty( PROP_CAPTCHA, "" ).equals( "asirra" );
+        m_plan = SpamInspectionFactory.getInspectionPlan( engine, properties );
     }
 
-    public static void validateUTF8Param( WikiActionBean actionBean ) throws MissingParameterException
+    /** {@inheritDoc} */
+    public String preSave( WikiContext context, String content ) throws RedirectException
     {
-        WikiActionBeanContext context = actionBean.getContext();
-        HttpServletRequest request = context.getHttpRequest();
-        if( request != null )
+        Change change;
+        try
         {
-            String utf8field = request.getParameter( REQ_ENCODING_CHECK );
-            if( utf8field != null && utf8field.equals( "\u3041" ) )
-            {
-                return;
-            }
+            change = Change.getPageChange( context, content );
         }
-        String uid = SpamFilter.log( actionBean.getContext(), SpamFilter.REJECT, SpamFilter.REASON_UTF8_TRAP, request
-            .getRemoteAddr() );
-        log.info( "SPAM:UTF8Trap (" + uid + ").  Wildly posting dumb bot detected." );
-        throw new MissingParameterException( "Missing UTF-8 parameter! Likely to be a spammer.", null );
-    }
-
-    /**
-     * Introspects an ActionBean and returns the value for one or more supplied
-     * properties. Any properties not found will be cheerfully ignored.
-     * 
-     * @param actionBean the actionBean to inspect
-     * @param beanProperties the bean properties to examine
-     * @return the values if successfully evaluated, or <code>null</code> if
-     *         not (or not set)
-     */
-    protected static Map<String, Object> getBeanProperties( WikiActionBean actionBean, String[] beanProperties )
-    {
-        Map<String, Object> map = new HashMap<String, Object>();
-        for( String beanProperty : beanProperties )
+        catch( DifferentiationFailedException e )
         {
-            try
-            {
-                PropertyExpression propExpression = PropertyExpression.getExpression( beanProperty );
-                PropertyExpressionEvaluation evaluation = new PropertyExpressionEvaluation( propExpression, actionBean );
-                Object value = evaluation.getValue();
-                {
-                    if( value != null )
-                    {
-                        map.put( beanProperty, value );
-                    }
-                }
-            }
-            catch( NoSuchPropertyException e )
-            {
-                // Ignore any missing properties
-            }
+            throw new RedirectException( "Could not diff page.", getRedirectPage( context ) );
         }
-        return map;
-    }
 
-    /**
-     * This method checks if the hash value is still valid, i.e. if it exists at
-     * all. This can occur in two cases: either this is a spam bot which is not
-     * adaptive, or it is someone who has been editing one page for too long,
-     * and their session has expired.
-     * <p>
-     * This method puts a redirect to the http response field to page
-     * "SessionExpired" and logs the incident in the spam log (it may or may not
-     * be spam, but it's rather likely that it is).
-     * 
-     * @param context The WikiContext
-     * @param pageContext The JSP PageContext.
-     * @return True, if hash is okay. False, if hash is not okay, and you need
-     *         to redirect.
-     * @throws IOException If redirection fails
-     * @since 2.6
-     */
-    public static final boolean checkHash( WikiContext context, PageContext pageContext ) throws IOException
-    {
-        String hashName = getHashFieldName( (HttpServletRequest) pageContext.getRequest() );
+        // Run the Inspection
+        Inspection inspection = new Inspection( context, m_plan );
+        float spamScoreLimit = SpamInspectionFactory.defaultSpamLimit( m_engine );
+        SpamInspectionFactory.setSpamLimit( inspection, spamScoreLimit );
+        inspection.inspect( content, change );
+        float spamScore = inspection.getScore( Topic.SPAM );
+        context.setVariable( ATTR_SPAMFILTER_SCORE, spamScore );
 
-        if( pageContext.getRequest().getParameter( hashName ) == null )
+        // Redirect user if score too low
+        if( spamScore <= spamScoreLimit )
         {
-            if( pageContext.getAttribute( hashName ) == null )
+            StringBuilder s = new StringBuilder();
+            for( Finding finding : inspection.getFindings( Topic.SPAM ) )
             {
-                Change change = getChange( context, EditorManager.getEditedText( pageContext ) );
-
-                log( context, REJECT, "MissingHash", change.m_change );
-
-                String redirect = context.getURL( WikiContext.VIEW, "SessionExpired" );
-                ((HttpServletResponse) pageContext.getResponse()).sendRedirect( redirect );
-
-                return false;
+                s.append( finding.getMessage() );
+                s.append( ' ' );
             }
+            throw new RedirectException( s.toString(), getRedirectPage( context ) );
         }
 
-        return true;
-    }
-
-    /**
-     * This helper method adds all the input fields to your editor that the
-     * SpamFilter requires to check for spam. This <i>must</i> be in your
-     * editor form if you intend to use the SpamFilter.
-     * 
-     * @param pageContext The PageContext
-     * @return A HTML string which contains input fields for the SpamFilter.
-     */
-    public static final String insertInputFields( PageContext pageContext )
-    {
-        WikiContext ctx = WikiContextFactory.findContext( pageContext );
-        WikiEngine engine = ctx.getEngine();
-
-        StringBuilder sb = new StringBuilder();
-        if( engine.getContentEncoding().equals( "UTF-8" ) )
+        // Log successful change
+        else
         {
-            sb.append( "<input name='encodingcheck' type='hidden' value='\u3041' />\n" );
+            log.debug( "Not spam: " + change.toString() );
         }
-
-        return sb.toString();
+        return content;
     }
 
     /**
-     * A local class for storing host information.
+     * Returns a page to which we shall redirect, based on the current value of
+     * the "captcha" parameter.
      * 
-     * @since
+     * @param ctx WikiContext
+     * @return An URL to redirect to
      */
-    private class Host
-    {
-        private long m_addedTime = System.currentTimeMillis();
-
-        private long m_releaseTime;
-
-        private String m_address;
-
-        private Change m_change;
-
-        public String getAddress()
-        {
-            return m_address;
-        }
-
-        public long getReleaseTime()
-        {
-            return m_releaseTime;
-        }
-
-        public long getAddedTime()
-        {
-            return m_addedTime;
-        }
-
-        public Change getChange()
-        {
-            return m_change;
-        }
-
-        public Host( String ipaddress, Change change )
-        {
-            m_address = ipaddress;
-            m_change = change;
-
-            m_releaseTime = System.currentTimeMillis() + m_banTime * 60 * 1000L;
-        }
-    }
-
-    private static class Change
+    private String getRedirectPage( WikiContext ctx )
     {
-        public String m_change;
-
-        public int m_adds;
-
-        public int m_removals;
-
-        public String toString()
-        {
-            return m_change;
-        }
-
-        public boolean equals( Object o )
-        {
-            if( o instanceof Change )
-                return m_change.equals( ((Change) o).m_change );
-
-            return false;
-        }
+        if( m_useCaptcha )
+            return ctx.getURL( WikiContext.NONE, "Captcha.jsp", "page=" + ctx.getEngine().encodeName( ctx.getPage().getName() ) );
 
-        public int hashCode()
-        {
-            return m_change.hashCode() + 17;
-        }
+        return ctx.getURL( WikiContext.VIEW, m_errorPage );
     }
 }

Modified: incubator/jspwiki/trunk/src/java/org/apache/wiki/tags/SpamProtectTag.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/java/org/apache/wiki/tags/SpamProtectTag.java?rev=822989&r1=822988&r2=822989&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/java/org/apache/wiki/tags/SpamProtectTag.java (original)
+++ incubator/jspwiki/trunk/src/java/org/apache/wiki/tags/SpamProtectTag.java Thu Oct  8 01:16:08 2009
@@ -31,6 +31,7 @@
 
 import net.sourceforge.stripes.util.CryptoUtil;
 
+import org.apache.wiki.content.inspect.BotTrapInspector;
 import org.apache.wiki.filters.SpamFilter;
 import org.apache.wiki.ui.stripes.SpamInterceptor;
 
@@ -42,12 +43,14 @@
  * annotated with the {@link org.apache.wiki.ui.stripes.SpamProtect} annotation.
  * </p>
  * <p>
- * This tag will inject the following parameters:
+ * This tag must be added as a child of an existing &lt;form&gt; or
+ * &lt;stripes:form&gt; element. The SpamProtect tag will cause the
+ * following parameters into the form:
  * </p>
  * <ol>
  * <li><b>An UTF-8 detector parameter called <code>encodingcheck</code>.</b>
- * This parameter contains a single non-Latin1. SpamInterceptor verifies that
- * the non-Latin1 character has not been mangled by a badly behaving robot or
+ * This parameter contains a single non-Latin1 character. SpamInterceptor verifies
+ * that the non-Latin1 character has not been mangled by a badly behaving robot or
  * user client. Many bots assume a form is Latin1. This also prevents the "hey,
  * my edit destroyed all UTF-8 characters" problem.</li>
  * <li><b>A token field </b>, which has a random name and fixed value</b>
@@ -64,7 +67,7 @@
  * to be empty.</li>
  * </li>
  * <li><b>An an encrypted parameter</b> called
- * {@link SpamFilter#REQ_SPAM_PARAM}, whose contents are the names of
+ * {@link BotTrapInspector#REQ_SPAM_PARAM}, whose contents are the names of
  * parameters 2 and 3, separated by a carriage return character. These contents
  * are then encrypted.</li>
  * </ol>
@@ -91,7 +94,7 @@
         HttpServletRequest request = (HttpServletRequest) this.pageContext.getRequest();
 
         // Inject honey-trap param (should always be submitted with no value)
-        String trapParam = getUniqueID();
+        String trapParam = BotTrapInspector.REQ_TRAP_PARAM;
         out.write( "<div style=\"display: none;\">" );
         out.write( "<input type=\"hidden\" name=\"" );
         out.write( trapParam );
@@ -103,11 +106,11 @@
         out.write( "<input name=\"" + tokenParam + "\" type=\"hidden\" value=\"" + tokenValue + "\" />\n" );
 
         // Inject UTF-8 detector
-        out.write( "<input name=\""+ SpamFilter.REQ_ENCODING_CHECK + "\" type=\"hidden\" value=\"\u3041\" />\n" );
+        out.write( "<input name=\""+ BotTrapInspector.REQ_ENCODING_CHECK + "\" type=\"hidden\" value=\"\u3041\" />\n" );
 
-        // Add encrypted parameter indicating the names of the trap and token fields
-        String encryptedParam = CryptoUtil.encrypt( trapParam + "\n" + tokenParam );
-        out.write( "<input name=\""+ SpamFilter.REQ_SPAM_PARAM+"\" type=\"hidden\" value=\""+encryptedParam+"\" />\n" );
+        // Add encrypted parameter indicating the name of the token field
+        String encryptedParam = CryptoUtil.encrypt( tokenParam );
+        out.write( "<input name=\""+ BotTrapInspector.REQ_SPAM_PARAM+"\" type=\"hidden\" value=\""+encryptedParam+"\" />\n" );
     }
 
     private static String getUniqueID()

Modified: incubator/jspwiki/trunk/src/java/org/apache/wiki/ui/stripes/SpamInterceptor.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/java/org/apache/wiki/ui/stripes/SpamInterceptor.java?rev=822989&r1=822988&r2=822989&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/java/org/apache/wiki/ui/stripes/SpamInterceptor.java (original)
+++ incubator/jspwiki/trunk/src/java/org/apache/wiki/ui/stripes/SpamInterceptor.java Thu Oct  8 01:16:08 2009
@@ -21,26 +21,32 @@
 package org.apache.wiki.ui.stripes;
 
 import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
 
-import net.sourceforge.stripes.action.RedirectResolution;
 import net.sourceforge.stripes.action.Resolution;
 import net.sourceforge.stripes.controller.ExecutionContext;
 import net.sourceforge.stripes.controller.Interceptor;
 import net.sourceforge.stripes.controller.Intercepts;
 import net.sourceforge.stripes.controller.LifecycleStage;
+import net.sourceforge.stripes.util.bean.NoSuchPropertyException;
+import net.sourceforge.stripes.util.bean.PropertyExpression;
+import net.sourceforge.stripes.util.bean.PropertyExpressionEvaluation;
+import net.sourceforge.stripes.validation.LocalizableError;
+import net.sourceforge.stripes.validation.ValidationError;
 
-import org.apache.wiki.action.ViewActionBean;
+import org.apache.wiki.WikiEngine;
 import org.apache.wiki.action.WikiActionBean;
-import org.apache.wiki.content.MissingParameterException;
-import org.apache.wiki.filters.SpamFilter;
+import org.apache.wiki.content.inspect.*;
 
 /**
  * Stripes Interceptor that ensures that SpamFilter algorithms are applied to
  * events annotated with the {@link SpamProtect} annotation. This class
  * processes form parameters generated by the
- * {@link org.apache.wiki.tags.SpamProtectTag} tag. It fires after the
- * {@link LifecycleStage#HandlerResolution} stage; that is, after ActionBean and
- * event handler resolution, but before parameter binding.
+ * {@link org.apache.wiki.tags.SpamProtectTag} tag. It fires before the
+ * {@link LifecycleStage#CustomValidation} stage; that is, after ActionBean and
+ * event handler resolution, and just after parameter binding, but before any
+ * other custom validation routines have run.
  */
 @Intercepts( { LifecycleStage.HandlerResolution } )
 public class SpamInterceptor implements Interceptor
@@ -48,25 +54,25 @@
     /**
      * Validates spam parameters contained in any requests targeting an
      * ActionBean method annotated with the {@link SpamProtect} annotation. This
-     * simply delegates to
-     * {@link SpamFilter#validateSpamParams(WikiActionBean, String[])} and
-     * {@link SpamFilter#validateUTF8Param(WikiActionBean)} in sequence, and
-     * returns a {@link RedirectResolution} to the WikiPage
-     * <code>SessionExpired</code> if either of these checks fail. If the
-     * targeted ActionBean event is not annotated, this method returns
-     * <code>null</code>.
+     * creates a new {@link Inspection} for each ActionBean parameter indicated
+     * by the annotation. The {@link InspectionPlan} for the Inspection is
+     * obtained by calling
+     * {@link SpamInspectionFactory#getInspectionPlan(WikiEngine, java.util.Properties)}
+     * . If any of the modifications are determined to be spam, a Stripes
+     * {@link ValidationError} is added to the ActionBeanContext.
      */
     public Resolution intercept( ExecutionContext context ) throws Exception
     {
         // Execute all other interceptors first
         Resolution r = context.proceed();
-        if ( r != null )
+        if( r != null )
         {
             return r;
         }
 
         // Is the target handler protected by a @SpamProtect annotation?
         WikiActionBean actionBean = (WikiActionBean) context.getActionBean();
+        WikiActionBeanContext actionBeanContext = actionBean.getContext();
         Method handler = context.getHandler();
         SpamProtect ann = handler.getAnnotation( SpamProtect.class );
         if( ann == null )
@@ -74,27 +80,78 @@
             return null;
         }
 
-        // Validate spam token/trap params
-        try
-        {
-            SpamFilter.validateSpamParams( actionBean, ann.content() );
-        }
-        catch( MissingParameterException e )
-        {
-            return new RedirectResolution( ViewActionBean.class, "view" ).addParameter( "page", "SessionExpired" );
+        // Retrieve all of the bean fields named in the @SpamProtect annotation
+        WikiEngine engine = actionBeanContext.getEngine();
+        InspectionPlan plan = SpamInspectionFactory.getInspectionPlan( engine, engine.getWikiProperties() );
+        Map<String, Object> fieldValues = getBeanProperties( actionBean, ann.content() );
+
+        // Create an Inspection for analyzing each field
+        Inspection inspection = new Inspection( actionBeanContext, plan );
+        float spamScoreLimit = SpamInspectionFactory.defaultSpamLimit( engine );
+        SpamInspectionFactory.setSpamLimit( inspection, spamScoreLimit );
+
+        // Go to it!
+        for( Map.Entry<String, Object> entry : fieldValues.entrySet() )
+        {
+            String name = entry.getKey();
+            String value = entry.getValue().toString();
+            Change change;
+            if( "page".equals( name ) )
+            {
+                change = Change.getPageChange( actionBeanContext, value );
+            }
+            else
+            {
+                change = Change.getChange( value );
+            }
+
+            // Run the Inspection
+            inspection.inspect( value, change );
+            float spamScore = inspection.getScore( Topic.SPAM );
+
+            // If it's spam, add a validation error for the field
+            if( spamScore <= spamScoreLimit )
+            {
+                ValidationError error = new LocalizableError( "message.spam" );
+                actionBeanContext.getValidationErrors().add( name, error );
+            }
         }
 
-        // Validate non-Latin1 param
-        try
-        {
-            SpamFilter.validateUTF8Param( actionBean );
-        }
-        catch( MissingParameterException e )
+        return null;
+    }
+
+    /**
+     * Introspects an ActionBean and returns the value for one or more supplied
+     * properties. Any properties not found will be cheerfully ignored.
+     * 
+     * @param actionBean the actionBean to inspect
+     * @param beanProperties the bean properties to examine
+     * @return the values if successfully evaluated, or <code>null</code> if not
+     *         (or not set)
+     */
+    protected static Map<String, Object> getBeanProperties( WikiActionBean actionBean, String[] beanProperties )
+    {
+        Map<String, Object> map = new HashMap<String, Object>();
+        for( String beanProperty : beanProperties )
         {
-            return new RedirectResolution( ViewActionBean.class, "view" ).addParameter( "page", "SessionExpired" );
+            try
+            {
+                PropertyExpression propExpression = PropertyExpression.getExpression( beanProperty );
+                PropertyExpressionEvaluation evaluation = new PropertyExpressionEvaluation( propExpression, actionBean );
+                Object value = evaluation.getValue();
+                {
+                    if( value != null )
+                    {
+                        map.put( beanProperty, value );
+                    }
+                }
+            }
+            catch( NoSuchPropertyException e )
+            {
+                // Ignore any missing properties
+            }
         }
-
-        return null;
+        return map;
     }
 
 }

Modified: incubator/jspwiki/trunk/tests/java/org/apache/wiki/TestEngine.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/tests/java/org/apache/wiki/TestEngine.java?rev=822989&r1=822988&r2=822989&view=diff
==============================================================================
--- incubator/jspwiki/trunk/tests/java/org/apache/wiki/TestEngine.java (original)
+++ incubator/jspwiki/trunk/tests/java/org/apache/wiki/TestEngine.java Thu Oct  8 01:16:08 2009
@@ -49,7 +49,7 @@
 import org.apache.wiki.content.PageAlreadyExistsException;
 import org.apache.wiki.content.PageNotFoundException;
 import org.apache.wiki.content.WikiPath;
-import org.apache.wiki.filters.SpamFilter;
+import org.apache.wiki.content.inspect.BotTrapInspector;
 import org.apache.wiki.log.Logger;
 import org.apache.wiki.log.LoggerFactory;
 import org.apache.wiki.providers.AbstractFileProvider;
@@ -177,13 +177,13 @@
     public static void addSpamProtectParams( MockRoundtrip trip )
     {
         // Add the trap + token params
-        String paramValue = CryptoUtil.encrypt( "TRAPAA\nTOKENA" );
-        trip.addParameter( "TRAPAA", new String[0] );
+        String paramValue = CryptoUtil.encrypt( "TOKENA" );
+        trip.addParameter( BotTrapInspector.REQ_TRAP_PARAM, new String[0] );
         trip.addParameter( "TOKENA", trip.getRequest().getSession().getId() );
-        trip.addParameter( SpamFilter.REQ_SPAM_PARAM, paramValue );
+        trip.addParameter( BotTrapInspector.REQ_SPAM_PARAM, paramValue );
 
         // Add the UTF-8 token
-        trip.addParameter( SpamFilter.REQ_ENCODING_CHECK, "\u3041" );
+        trip.addParameter( BotTrapInspector.REQ_ENCODING_CHECK, "\u3041" );
     }
     
     public static void emptyWorkDir()

Modified: incubator/jspwiki/trunk/tests/java/org/apache/wiki/auth/UserManagerTest.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/tests/java/org/apache/wiki/auth/UserManagerTest.java?rev=822989&r1=822988&r2=822989&view=diff
==============================================================================
--- incubator/jspwiki/trunk/tests/java/org/apache/wiki/auth/UserManagerTest.java (original)
+++ incubator/jspwiki/trunk/tests/java/org/apache/wiki/auth/UserManagerTest.java Thu Oct  8 01:16:08 2009
@@ -277,7 +277,7 @@
       
       // Create a new user with random name
       WikiSession session = m_engine.guestSession();
-      String loginName = "TestUser" + String.valueOf( System.currentTimeMillis() );
+      String loginName = "UserManagerTestUser" + String.valueOf( System.currentTimeMillis() );
       UserProfile profile = m_db.newProfile();
       profile.setEmail( "testuser@testville.com" );
       profile.setLoginName( loginName );
@@ -304,7 +304,7 @@
       
       // Create a new user with random name
       WikiSession session = m_engine.guestSession();
-      String loginName = "TestUser" + String.valueOf( System.currentTimeMillis() );
+      String loginName = "UserManagerTestUser" + String.valueOf( System.currentTimeMillis() );
       UserProfile profile = m_db.newProfile();
       profile.setEmail( "testuser@testville.com" );
       profile.setLoginName( loginName );
@@ -358,7 +358,7 @@
       
       // Create a new user with random name
       WikiSession session = m_engine.guestSession();
-      String loginName = "TestUser" + String.valueOf( System.currentTimeMillis() );
+      String loginName = "UserManagerTestUser" + String.valueOf( System.currentTimeMillis() );
       UserProfile profile = m_db.newProfile();
       profile.setEmail( "testuser@testville.com" );
       profile.setLoginName( loginName );
@@ -406,7 +406,7 @@
       
       // Create a new user with random name
       WikiSession session = m_engine.guestSession();
-      String loginName = "TestUser" + String.valueOf( System.currentTimeMillis() );
+      String loginName = "UserManagerTestUser" + String.valueOf( System.currentTimeMillis() );
       UserProfile profile = m_db.newProfile();
       profile.setEmail( "testuser@testville.com" );
       profile.setLoginName( loginName );

Modified: incubator/jspwiki/trunk/tests/java/org/apache/wiki/filters/SpamFilterTest.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/tests/java/org/apache/wiki/filters/SpamFilterTest.java?rev=822989&r1=822988&r2=822989&view=diff
==============================================================================
--- incubator/jspwiki/trunk/tests/java/org/apache/wiki/filters/SpamFilterTest.java (original)
+++ incubator/jspwiki/trunk/tests/java/org/apache/wiki/filters/SpamFilterTest.java Thu Oct  8 01:16:08 2009
@@ -21,14 +21,10 @@
 
 package org.apache.wiki.filters;
 
-import java.util.Map;
-
 import junit.framework.Test;
 import junit.framework.TestCase;
 import junit.framework.TestSuite;
 
-import org.apache.wiki.action.TestActionBean;
-
 public class SpamFilterTest extends TestCase
 {
     public SpamFilterTest( String s )
@@ -36,19 +32,6 @@
         super( s );
     }
 
-    public void testGetBeanProperties() throws Exception
-    {
-        TestActionBean bean = new TestActionBean();
-        bean.setAcl( "ACL" );
-        bean.setText( "Sample text" );
-        Map<String, Object> map = SpamFilter.getBeanProperties( bean, new String[] { "text", "acl", "nonExistentProperty" } );
-        assertEquals( 2, map.size() );
-        Object value = map.get( "text" );
-        assertEquals( "Sample text", value );
-        value = map.get( "acl" );
-        assertEquals( "ACL", value );
-    }
-
     public static Test suite()
     {
         return new TestSuite( SpamFilterTest.class );

Modified: incubator/jspwiki/trunk/tests/java/org/apache/wiki/ui/stripes/SpamInterceptorTest.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/tests/java/org/apache/wiki/ui/stripes/SpamInterceptorTest.java?rev=822989&r1=822988&r2=822989&view=diff
==============================================================================
--- incubator/jspwiki/trunk/tests/java/org/apache/wiki/ui/stripes/SpamInterceptorTest.java (original)
+++ incubator/jspwiki/trunk/tests/java/org/apache/wiki/ui/stripes/SpamInterceptorTest.java Thu Oct  8 01:16:08 2009
@@ -21,6 +21,7 @@
 
 package org.apache.wiki.ui.stripes;
 
+import java.util.Map;
 import java.util.Properties;
 
 import junit.framework.Test;
@@ -31,7 +32,7 @@
 
 import org.apache.wiki.TestEngine;
 import org.apache.wiki.action.TestActionBean;
-import org.apache.wiki.filters.SpamFilter;
+import org.apache.wiki.content.inspect.BotTrapInspector;
 
 public class SpamInterceptorTest extends TestCase
 {
@@ -60,17 +61,29 @@
         m_engine.shutdown();
     }
 
+    public void testGetBeanProperties() throws Exception
+    {
+        TestActionBean bean = new TestActionBean();
+        bean.setAcl( "ACL" );
+        bean.setText( "Sample text" );
+        Map<String, Object> map = SpamInterceptor.getBeanProperties( bean, new String[] { "text", "acl", "nonExistentProperty" } );
+        assertEquals( 2, map.size() );
+        Object value = map.get( "text" );
+        assertEquals( "Sample text", value );
+        value = map.get( "acl" );
+        assertEquals( "ACL", value );
+    }
+
     public void testInvalidToken() throws Exception
     {
         // Add the trap + token params, but fill in the trap param
         MockRoundtrip trip = m_engine.guestTrip( "/Test.action" );
-        String paramValue = CryptoUtil.encrypt( "TRAPAA\nTOKENA" );
-        trip.addParameter( "TRAPAA", new String[0] );
+        trip.addParameter( BotTrapInspector.REQ_TRAP_PARAM, new String[0] );
         trip.addParameter( "TOKENA", new String[0] );
-        trip.addParameter( SpamFilter.REQ_SPAM_PARAM, paramValue );
+        trip.addParameter( BotTrapInspector.REQ_SPAM_PARAM, CryptoUtil.encrypt( "TOKENA" ) );
         
         // Add the UTF-8 token
-        trip.addParameter( SpamFilter.REQ_ENCODING_CHECK, "\u3041" );
+        trip.addParameter( BotTrapInspector.REQ_ENCODING_CHECK, "\u3041" );
 
         // Verify that we got the ActionBean...
         trip.execute( "test" );
@@ -85,13 +98,12 @@
     {
         // Add the trap + token params, but fill in the trap param
         MockRoundtrip trip = m_engine.guestTrip( "/Test.action" );
-        String paramValue = CryptoUtil.encrypt( "TRAPAA\nTOKENA" );
-        trip.addParameter( "TRAPAA", "BotFilledValue" );
+        trip.addParameter( BotTrapInspector.REQ_TRAP_PARAM, "BotFilledThisIn" );
         trip.addParameter( "TOKENA", trip.getRequest().getSession().getId() );
-        trip.addParameter( SpamFilter.REQ_SPAM_PARAM, paramValue );
+        trip.addParameter( BotTrapInspector.REQ_SPAM_PARAM, CryptoUtil.encrypt( "TOKENA" ) );
 
         // Add the UTF-8 token
-        trip.addParameter( SpamFilter.REQ_ENCODING_CHECK, "\u3041" );
+        trip.addParameter( BotTrapInspector.REQ_ENCODING_CHECK, "\u3041" );
 
         // Verify that we got the ActionBean...
         trip.execute( "test" );
@@ -106,12 +118,11 @@
     {
         // Add the trap param (but not the token param)
         MockRoundtrip trip = m_engine.guestTrip( "/Test.action" );
-        String paramValue = CryptoUtil.encrypt( "TRAPAA" );
-        trip.addParameter( "TRAPAA", new String[0] );
-        trip.addParameter( SpamFilter.REQ_SPAM_PARAM, paramValue );
+        trip.addParameter( BotTrapInspector.REQ_TRAP_PARAM, new String[0] );
+        trip.addParameter( BotTrapInspector.REQ_SPAM_PARAM, CryptoUtil.encrypt( "" ) );
 
         // Add the UTF-8 token
-        trip.addParameter( SpamFilter.REQ_ENCODING_CHECK, "\u3041" );
+        trip.addParameter( BotTrapInspector.REQ_ENCODING_CHECK, "\u3041" );
 
         // Verify that we got the ActionBean...
         trip.execute( "test" );
@@ -126,20 +137,19 @@
     {
         // Add token param (but not the trap param)
         MockRoundtrip trip = m_engine.guestTrip( "/Test.action" );
-        String paramValue = CryptoUtil.encrypt( "TOKENA" );
         trip.addParameter( "TOKENA", trip.getRequest().getSession().getId() );
-        trip.addParameter( SpamFilter.REQ_SPAM_PARAM, paramValue );
+        trip.addParameter( BotTrapInspector.REQ_SPAM_PARAM, CryptoUtil.encrypt( "TOKENA" ) );
 
         // Add the UTF-8 token
-        trip.addParameter( SpamFilter.REQ_ENCODING_CHECK, "\u3041" );
+        trip.addParameter( BotTrapInspector.REQ_ENCODING_CHECK, "\u3041" );
 
         // Verify that we got the ActionBean...
         trip.execute( "test" );
         TestActionBean bean = trip.getActionBean( TestActionBean.class );
         assertEquals( null, bean.getPage() );
 
-        // ...but that we failed the token check
-        assertEquals( "/Wiki.action?view=&page=SessionExpired", trip.getDestination() );
+        // ...and that we passed the token check
+        assertEquals( null, trip.getDestination() );
     }
 
     public void testMissingUTF8Check() throws Exception
@@ -147,9 +157,9 @@
         // Add the trap + token params
         MockRoundtrip trip = m_engine.guestTrip( "/Test.action" );
         String paramValue = CryptoUtil.encrypt( "TRAPAA\nTOKENA" );
-        trip.addParameter( "TRAPAA", new String[0] );
+        trip.addParameter( BotTrapInspector.REQ_TRAP_PARAM, new String[0] );
         trip.addParameter( "TOKENA", trip.getRequest().getSession().getId() );
-        trip.addParameter( SpamFilter.REQ_SPAM_PARAM, paramValue );
+        trip.addParameter( BotTrapInspector.REQ_SPAM_PARAM, paramValue );
 
         // Verify that we got the ActionBean...
         trip.execute( "test" );
@@ -160,13 +170,13 @@
         assertEquals( "/Wiki.action?view=&page=SessionExpired", trip.getDestination() );
     }
 
-    public void testNoTokens() throws Exception
+    public void testNoToken() throws Exception
     {
-        // Execute the SpamProtect-ed handler with no tokens
+        // Execute the SpamProtect-ed handler with no token
         MockRoundtrip trip = m_engine.guestTrip( "/Test.action" );
 
         // Add the UTF-8 token
-        trip.addParameter( SpamFilter.REQ_ENCODING_CHECK, "\u3041" );
+        trip.addParameter( BotTrapInspector.REQ_ENCODING_CHECK, "\u3041" );
 
         // Verify that we got the ActionBean...
         trip.execute( "test" );
@@ -177,7 +187,7 @@
         assertEquals( "/Wiki.action?view=&page=SessionExpired", trip.getDestination() );
     }
 
-    public void testTokens() throws Exception
+    public void testToken() throws Exception
     {
         // Add the trap + token params
         MockRoundtrip trip = m_engine.guestTrip( "/Test.action" );
@@ -191,23 +201,4 @@
         // ...and that we passed the token check
         assertEquals( null, trip.getDestination() );
     }
-
-    public void testWrongOrder() throws Exception
-    {
-        // Add token param (but not the trap param)
-        MockRoundtrip trip = m_engine.guestTrip( "/Test.action" );
-        String paramValue = CryptoUtil.encrypt( "TOKENA\nTRAPAA" );
-        trip.addParameter( "TRAPAA", new String[0] );
-        trip.addParameter( "TOKENA", trip.getRequest().getSession().getId() );
-        trip.addParameter( SpamFilter.REQ_SPAM_PARAM, paramValue );
-
-        // Verify that we got the ActionBean...
-        trip.execute( "test" );
-        TestActionBean bean = trip.getActionBean( TestActionBean.class );
-        assertEquals( null, bean.getPage() );
-
-        // ...but that we failed the token check
-        assertEquals( "/Wiki.action?view=&page=SessionExpired", trip.getDestination() );
-    }
-
 }



Mime
View raw message