jspwiki-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ajaqu...@apache.org
Subject svn commit: r766540 [1/2] - in /incubator/jspwiki/trunk: src/java/org/apache/wiki/content/ 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/acti...
Date Sun, 19 Apr 2009 22:31:37 GMT
Author: ajaquith
Date: Sun Apr 19 22:31:36 2009
New Revision: 766540

URL: http://svn.apache.org/viewvc?rev=766540&view=rev
Log:
The @SpamProtect annotation and SpamProtect tag are now fully functional. Any JSP that submits to a WikiActionBean whose handler method is annotated with @SpamProtect receives the protection automatically, thanks to SpamInterceptor and collaborating SpamFilter. All that is needed is to add the tag <wiki:SpamProtect /> as a child element to the <form> or <s:form> elements. The same three-way check used in the old Edit.jsp is still done: random "trap" parameter; random "token" parameter; UTF-8 check parameter. To help ActionBean unit test classes run with "protected" handler events, the static method TestEngine.addSpamProtectParams(MockRoundtrip) was created.

Added:
    incubator/jspwiki/trunk/src/java/org/apache/wiki/content/MissingParameterException.java
    incubator/jspwiki/trunk/src/java/org/apache/wiki/ui/stripes/SpamInterceptor.java
      - copied, changed from r765589, incubator/jspwiki/trunk/src/java/org/apache/wiki/filters/SpamInterceptor.java
    incubator/jspwiki/trunk/src/java/org/apache/wiki/ui/stripes/SpamProtect.java
      - copied, changed from r765589, incubator/jspwiki/trunk/src/java/org/apache/wiki/filters/SpamProtect.java
    incubator/jspwiki/trunk/tests/java/org/apache/wiki/action/TestActionBean.java
    incubator/jspwiki/trunk/tests/java/org/apache/wiki/filters/SpamFilterTest.java
    incubator/jspwiki/trunk/tests/java/org/apache/wiki/ui/stripes/SpamInterceptorTest.java
Removed:
    incubator/jspwiki/trunk/src/java/org/apache/wiki/filters/SpamInterceptor.java
    incubator/jspwiki/trunk/src/java/org/apache/wiki/filters/SpamProtect.java
Modified:
    incubator/jspwiki/trunk/src/java/org/apache/wiki/content/ContentManager.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/tests/java/org/apache/wiki/TestEngine.java
    incubator/jspwiki/trunk/tests/java/org/apache/wiki/filters/AllTests.java

Modified: incubator/jspwiki/trunk/src/java/org/apache/wiki/content/ContentManager.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/java/org/apache/wiki/content/ContentManager.java?rev=766540&r1=766539&r2=766540&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/java/org/apache/wiki/content/ContentManager.java (original)
+++ incubator/jspwiki/trunk/src/java/org/apache/wiki/content/ContentManager.java Sun Apr 19 22:31:36 2009
@@ -1031,6 +1031,7 @@
         }
 
         /** {@inheritDoc} */
+        @SuppressWarnings("unchecked")
         @Override
         public Outcome execute() throws WikiException
         {
@@ -1045,7 +1046,16 @@
             }
             catch( PageNotFoundException e )
             {
-                throw new WikiException( e.getMessage(), e );
+                // Doesn't exist? No problem. Time to make one.
+                try
+                {
+                    page = engine.getContentManager().addPage( name, ContentManager.JSPWIKI_CONTENT_TYPE );
+                }
+                catch( PageAlreadyExistsException pae )
+                {
+                    // This should never happen, but it if does, throw a big honking exception
+                    throw new WikiException( "We were just told the page didn't exist. Now it does? Explain that please...", pae );
+                }
             }
             
             // Retrieve the page ACL, author, attributes, modified-date, name and new text from the workflow

Added: incubator/jspwiki/trunk/src/java/org/apache/wiki/content/MissingParameterException.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/java/org/apache/wiki/content/MissingParameterException.java?rev=766540&view=auto
==============================================================================
--- incubator/jspwiki/trunk/src/java/org/apache/wiki/content/MissingParameterException.java (added)
+++ incubator/jspwiki/trunk/src/java/org/apache/wiki/content/MissingParameterException.java Sun Apr 19 22:31:36 2009
@@ -0,0 +1,23 @@
+package org.apache.wiki.content;
+
+import org.apache.wiki.api.WikiException;
+
+/**
+ * Exception indicating that a required request parameter was not supplied, for example
+ * to SpamFilter.
+ */
+public class MissingParameterException extends WikiException
+{
+    private static final long serialVersionUID = 8665543487480429651L;
+
+    /**
+     * Constructs a new MissingParameterException
+     * @param message the exception message
+     * @param cause the cause of the exception, which may be <code>null</code>
+     */
+    public MissingParameterException( String message, Throwable cause )
+    {
+        super( message, cause );
+    }
+    
+}

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=766540&r1=766539&r2=766540&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 Sun Apr 19 22:31:36 2009
@@ -32,21 +32,22 @@
 import javax.servlet.jsp.PageContext;
 
 import net.sf.akismet.Akismet;
-import net.sourceforge.stripes.action.RedirectResolution;
-import net.sourceforge.stripes.action.Resolution;
 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.*;
-import org.apache.wiki.action.ViewActionBean;
 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.log.Logger;
 import org.apache.wiki.log.LoggerFactory;
@@ -56,196 +57,236 @@
 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:
- *  <ul>
- *    <li>wordlist - Page name where the regexps are found.  Use [{SET spamwords='regexp list separated with spaces'}] on
- *     that page.  Default is "SpamFilterWordList".
- *    <li>blacklist - The name of an attachment containing the list of spam patterns, one per line. Default is
- *        "SpamFilterWordList/blacklist.txt"</li>
- *    <li>errorpage - The page to which the user is redirected.  Has a special variable $msg which states the reason. Default is "RejectedMessage".
- *    <li>pagechangesinminute - How many page changes are allowed/minute.  Default 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>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>
- *    <li>ignoreauthenticated - If set to "true", all authenticated users are ignored and never caught in SpamFilter</li>
- *    <li>captcha - Sets the captcha technology to use.  Current allowed values are "none" and "asirra".</li>
- *    <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.
- *  </ul>
- *
- *  <p>Please see the default editors/plain.jsp for examples on how the SpamFilter integrates
- *  with the editor system.</p>
- *  
- *  <p>Changes by admin users are ignored in any case.</p>
- *
- *  @since 2.1.112
+ * This is Herb, the JSPWiki spamfilter 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
+ * "SpamFilterWordList".
+ * <li>blacklist - The name of an attachment containing the list of spam
+ * patterns, one per line. Default is "SpamFilterWordList/blacklist.txt"</li>
+ * <li>errorpage - The page to which the user is redirected. Has a special
+ * variable $msg which states the reason. Default is "RejectedMessage".
+ * <li>pagechangesinminute - How many page changes are allowed/minute. Default
+ * 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>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>
+ * <li>ignoreauthenticated - If set to "true", all authenticated users are
+ * ignored and never caught in SpamFilter</li>
+ * <li>captcha - Sets the captcha technology to use. Current allowed values are
+ * "none" and "asirra".</li>
+ * <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.
+ * </ul>
+ * <p>
+ * Please see the default editors/plain.jsp for examples on how the SpamFilter
+ * integrates with the editor system.
+ * </p>
+ * <p>
+ * Changes by admin users are ignored in any case.
+ * </p>
+ * 
+ * @since 2.1.112
  */
 @ModuleData( author = "JSPWiki development group" )
-public class SpamFilter
-    extends BasicPageFilter
+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";
-    
-    /** 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";
+
+    /** 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";
-    
+    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";
+    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 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 Date m_lastRebuild = new Date( 0L );
 
-    private static  Logger  c_spamlog = LoggerFactory.getLogger( "SpamLog" );
-    private static  Logger  log = LoggerFactory.getLogger( SpamFilter.class );
+    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 Vector<Host> m_temporaryBanList = new Vector<Host>();
 
-    private int             m_banTime = 60; // minutes
+    private int m_banTime = 60; // minutes
 
-    private Vector<Host>    m_lastModifications = new Vector<Host>();
+    private Vector<Host> m_lastModifications = new Vector<Host>();
 
     /**
-     *  How many times a single IP address can change a page per minute?
+     * How many times a single IP address can change a page per minute?
      */
-    private int             m_limitSinglePageChanges = 5;
+    private int m_limitSinglePageChanges = 5;
 
     /**
-     *  How many times can you add the exact same string to a page?
+     * How many times can you add the exact same string to a page?
      */
-    private int             m_limitSimilarChanges = 2;
+    private int m_limitSimilarChanges = 2;
 
     /**
-     *  How many URLs can be added at maximum.
+     * How many URLs can be added at maximum.
      */
-    private int             m_maxUrls = 10;
+    private int m_maxUrls = 10;
+
+    private Pattern m_urlPattern;
 
-    private Pattern         m_urlPattern;
-    private Akismet         m_akismet;
+    private Akismet m_akismet;
 
-    private String          m_akismetAPIKey = null;
+    private String m_akismetAPIKey = null;
 
-    private boolean         m_useCaptcha = false;
+    private boolean m_useCaptcha = false;
 
     /** The limit at which we consider something to be spam. */
-    private int             m_scoreLimit = 1;
+    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_ignoreAuthenticated = false;
 
-    private boolean         m_stopAtFirstMatch = true;
+    private boolean m_stopAtFirstMatch = true;
 
-    private static String   c_hashFieldName;
-    private static long     c_lastUpdate;
+    private static String c_hashFieldName;
 
-    /** 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 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}
+     * {@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_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_ignoreAuthenticated = TextUtil.getBooleanProperty( properties, PROP_IGNORE_AUTHENTICATED, m_ignoreAuthenticated );
 
-        m_useCaptcha = properties.getProperty( PROP_CAPTCHA, "" ).equals("asirra");
+        m_useCaptcha = properties.getProperty( PROP_CAPTCHA, "" ).equals( "asirra" );
 
         try
         {
@@ -253,27 +294,24 @@
         }
         catch( PatternSyntaxException e )
         {
-            log.error("Internal error: Someone put in a faulty pattern.",e);
-            throw new InternalWikiException("Faulty pattern.");
+            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_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 );
+        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 final int NOTE = 2;
 
     private static String log( WikiContext ctx, int type, String source, String message )
     {
@@ -282,9 +320,10 @@
 
         String uid = getUniqueID();
 
-        String page   = ctx.getPage().getName();
+        WikiPage page = ctx.getPage();
+        String pageName = page == null ? "(no page)" : page.getName();
         String reason = "UNKNOWN";
-        String addr   = ctx.getHttpRequest() != null ? ctx.getHttpRequest().getRemoteAddr() : "-";
+        String addr = ctx.getHttpRequest() != null ? ctx.getHttpRequest().getRemoteAddr() : "-";
 
         switch( type )
         {
@@ -298,38 +337,36 @@
                 reason = "NOTE";
                 break;
             default:
-                throw new InternalWikiException("Illegal type "+type);
+                throw new InternalWikiException( "Illegal type " + type );
         }
 
-        c_spamlog.info( reason+" "+source+" "+uid+" "+addr+" \""+page+"\" "+message );
+        c_spamlog.info( reason + " " + source + " " + uid + " " + addr + " \"" + pageName + "\" " + message );
 
         return uid;
     }
 
     /** {@inheritDoc} */
-    public String preSave( WikiContext context, String content )
-        throws RedirectException
+    public String preSave( WikiContext context, String content ) throws RedirectException
     {
         cleanBanList();
-        refreshBlacklists(context);
+        refreshBlacklists( context );
 
         Change change = getChange( context, content );
 
-        if(!ignoreThisUser(context))
+        if( !ignoreThisUser( context ) )
         {
             checkBanList( context, change );
             checkSinglePageChange( context, content, change );
-            checkPatternList(context, content, change);
+            checkPatternList( context, content, change );
         }
 
         if( !m_stopAtFirstMatch )
         {
-            Integer score = (Integer)context.getVariable(ATTR_SPAMFILTER_SCORE);
+            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) );
+                throw new RedirectException( "Herb says you got too many points", getRedirectPage( context ) );
             }
         }
 
@@ -337,27 +374,27 @@
         return content;
     }
 
-    private void checkStrategy( WikiContext context, String error, String message )
-        throws RedirectException
+    private void checkStrategy( WikiContext context, String error, String message ) throws RedirectException
     {
         if( m_stopAtFirstMatch )
         {
-            throw new RedirectException( message, getRedirectPage(context) );
+            throw new RedirectException( message, getRedirectPage( context ) );
         }
 
-        Integer score = (Integer)context.getVariable( ATTR_SPAMFILTER_SCORE );
+        Integer score = (Integer) context.getVariable( ATTR_SPAMFILTER_SCORE );
 
         if( score != null )
-            score = score+1;
+            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.
-     *
+     * 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.
@@ -370,7 +407,7 @@
         {
             StringTokenizer tok = new StringTokenizer( list, " \t\n" );
 
-            while( tok.hasMoreTokens() )
+            while ( tok.hasMoreTokens() )
             {
                 String pattern = tok.nextToken();
 
@@ -380,9 +417,9 @@
                 }
                 catch( PatternSyntaxException e )
                 {
-                    log.debug( "Malformed spam filter pattern "+pattern );
+                    log.debug( "Malformed spam filter pattern " + pattern );
 
-                    source.setAttribute("error", "Malformed spam filter pattern "+pattern);
+                    source.setAttribute( "error", "Malformed spam filter pattern " + pattern );
                 }
             }
         }
@@ -391,11 +428,11 @@
     }
 
     /**
-     *  Takes a MT-Blacklist -formatted blacklist and returns a list of compiled
-     *  Pattern objects.
-     *
-     *  @param list
-     *  @return The parsed blacklist patterns.
+     * 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 )
     {
@@ -405,21 +442,25 @@
         {
             try
             {
-                BufferedReader in = new BufferedReader( new StringReader(list) );
+                BufferedReader in = new BufferedReader( new StringReader( list ) );
 
                 String line;
 
-                while( (line = in.readLine()) != null )
+                while ( (line = in.readLine()) != null )
                 {
                     line = line.trim();
-                    if( line.length() == 0 ) continue; // Empty line
-                    if( line.startsWith("#") ) continue; // It's a comment
+                    if( line.length() == 0 )
+                        continue; // Empty line
+                    if( line.startsWith( "#" ) )
+                        continue; // It's a comment
 
-                    int ws = line.indexOf(' ');
+                    int ws = line.indexOf( ' ' );
 
-                    if( ws == -1 ) ws = line.indexOf('\t');
+                    if( ws == -1 )
+                        ws = line.indexOf( '\t' );
 
-                    if( ws != -1 ) line = line.substring(0,ws);
+                    if( ws != -1 )
+                        line = line.substring( 0, ws );
 
                     try
                     {
@@ -427,13 +468,13 @@
                     }
                     catch( PatternSyntaxException e )
                     {
-                        log.debug( "Malformed spam filter pattern "+line );
+                        log.debug( "Malformed spam filter pattern " + line );
                     }
                 }
             }
             catch( IOException e )
             {
-                log.info("Could not read patterns; returning what I got",e);
+                log.info( "Could not read patterns; returning what I got", e );
             }
         }
 
@@ -441,15 +482,14 @@
     }
 
     /**
-     *  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
+     * 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
+    private synchronized void checkSinglePageChange( WikiContext context, String content, Change change ) throws RedirectException
     {
         HttpServletRequest req = context.getHttpRequest();
 
@@ -459,20 +499,20 @@
             int hostCounter = 0;
             int changeCounter = 0;
 
-            log.debug("Change is "+change.m_change);
+            log.debug( "Change is " + change.m_change );
 
-            long time = System.currentTimeMillis()-60*1000L; // 1 minute
+            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
+                // Check if this item is invalid
                 //
                 if( host.getAddedTime() < time )
                 {
-                    log.debug("Removed host "+host.getAddress()+" from modification queue (expired)");
+                    log.debug( "Removed host " + host.getAddress() + " from modification queue (expired)" );
                     i.remove();
                     continue;
                 }
@@ -481,23 +521,23 @@
                 // Check if this IP address has been seen before
                 //
 
-                if( host.getAddress().equals(addr) )
+                if( host.getAddress().equals( addr ) )
                 {
                     hostCounter++;
                 }
 
                 //
-                //  Check, if this change has been seen before
+                // Check, if this change has been seen before
                 //
 
-                if( host.getChange() != null && host.getChange().equals(change) )
+                if( host.getChange() != null && host.getChange().equals( change ) )
                 {
                     changeCounter++;
                 }
             }
 
             //
-            //  Now, let's check against the limits.
+            // Now, let's check against the limits.
             //
             if( hostCounter >= m_limitSinglePageChanges )
             {
@@ -506,8 +546,10 @@
                 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+")" );
+                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 )
@@ -518,23 +560,25 @@
 
                 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+")");
+                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.
+            // Calculate the number of links in the addition.
             //
 
             String tstChange = change.toString();
-            int    urlCounter = 0;
+            int urlCounter = 0;
 
             Matcher matcher = m_urlPattern.matcher( tstChange );
-            while( matcher.find() )
+            while ( matcher.find() )
             {
                 MatchResult m = matcher.toMatchResult();
 
-                tstChange = tstChange.substring( m.end(0) );
+                tstChange = tstChange.substring( m.end( 0 ) );
 
                 urlCounter++;
             }
@@ -547,25 +591,27 @@
 
                 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+")" );
+                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
+            // Check bot trap
             //
 
             checkBotTrap( context, change );
 
             //
-            //  Check UTF-8 mangling
+            // Check UTF-8 mangling
             //
 
             checkUTF8( context, change );
 
             //
-            //  Do Akismet check.  This is good to be the last, because this is the most
-            //  expensive operation.
+            // Do Akismet check. This is good to be the last, because this is
+            // the most
+            // expensive operation.
             //
 
             checkAkismet( context, change );
@@ -574,28 +620,26 @@
         }
     }
 
-
     /**
-     *  Checks against the akismet system.
-     *
+     * Checks against the akismet system.
+     * 
      * @param context
      * @param change
      * @throws RedirectException
      */
-    private void checkAkismet( WikiContext context, Change 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.");
+                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.");
+                    log.error( "Akismet API key cannot be verified.  Please check your config." );
                     m_akismetAPIKey = null;
                     m_akismet = null;
                 }
@@ -604,44 +648,36 @@
             HttpServletRequest req = context.getHttpRequest();
 
             //
-            //  Akismet will mark all empty statements as spam, so we'll just
-            //  ignore them.
+            // 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...");
+                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 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;
+                String commentAuthorURL = null;
 
-                boolean isSpam = m_akismet.commentCheck( ipAddress,
-                                                         userAgent,
-                                                         referrer,
-                                                         permalink,
-                                                         commentType,
-                                                         commentAuthor,
-                                                         commentAuthorEmail,
-                                                         commentAuthorURL,
-                                                         change.toString(),
-                                                         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);
+                log.debug( "Akismet request done in: " + sw );
 
                 if( isSpam )
                 {
@@ -651,19 +687,21 @@
 
                     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.");
+                    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+")");
+                    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
+     * 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()
     {
@@ -671,11 +709,11 @@
     }
 
     /**
-     *  This checks whether an invisible field is available in the request, and
-     *  whether it's contents are suspected spam.
-     *
-     *  @param context
-     *  @param change
+     * 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
@@ -689,9 +727,9 @@
             {
                 String uid = log( context, REJECT, REASON_BOT_TRAP, change.toString() );
 
-                log.info("SPAM:BotTrap ("+uid+").  Wildly behaving bot detected.");
+                log.info( "SPAM:BotTrap (" + uid + ").  Wildly behaving bot detected." );
 
-                checkStrategy( context, REASON_BOT_TRAP, "Spamming attempt detected. (Incident code "+uid+")");
+                checkStrategy( context, REASON_BOT_TRAP, "Spamming attempt detected. (Incident code " + uid + ")" );
 
             }
         }
@@ -705,19 +743,20 @@
         {
             String utf8field = request.getParameter( "encodingcheck" );
 
-            if( utf8field != null && !utf8field.equals("\u3041") )
+            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.");
+                log.info( "SPAM:UTF8Trap (" + uid + ").  Wildly posting dumb bot detected." );
 
-                checkStrategy( context, REASON_UTF8_TRAP, "Spamming attempt detected. (Incident code "+uid+")");
+                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.
+     * Goes through the ban list and cleans away any host which has expired from
+     * it.
      */
     private synchronized void cleanBanList()
     {
@@ -729,21 +768,20 @@
 
             if( host.getReleaseTime() < now )
             {
-                log.debug("Removed host "+host.getAddress()+" from temporary ban list (expired)");
+                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
+     * 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
+    private void checkBanList( WikiContext context, Change change ) throws RedirectException
     {
         HttpServletRequest req = context.getHttpRequest();
 
@@ -755,13 +793,15 @@
 
             for( Host host : m_temporaryBanList )
             {
-                if( host.getAddress().equals(remote) )
+                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)");
+                    checkStrategy( context, REASON_IP_BANNED_TEMPORARILY,
+                                   "You have been temporarily banned from modifying this wiki. (" + timeleft
+                                       + " seconds of ban left)" );
                 }
             }
         }
@@ -769,10 +809,10 @@
     }
 
     /**
-     *  If the spam filter notices changes in the black list page, it will refresh
-     *  them automatically.
-     *
-     *  @param context
+     * If the spam filter notices changes in the black list page, it will
+     * refresh them automatically.
+     * 
+     * @param context
      */
     private void refreshBlacklists( WikiContext context )
     {
@@ -793,11 +833,11 @@
             boolean rebuild = false;
 
             //
-            //  Rebuild, if the page or the attachment has changed since.
+            // 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) )
+                if( m_spamPatterns == null || m_spamPatterns.isEmpty() || source.getLastModified().after( m_lastRebuild ) )
                 {
                     rebuild = true;
                 }
@@ -805,38 +845,38 @@
 
             if( att != null )
             {
-                if( m_spamPatterns == null || m_spamPatterns.isEmpty() || att.getLastModified().after(m_lastRebuild) )
+                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.
+            // 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 );
+                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);
+                log.info( "Spam filter reloaded - recognizing " + m_spamPatterns.size() + " patterns from page "
+                          + m_forbiddenWordsPage );
 
                 if( att != null )
                 {
-                    InputStream in = context.getEngine().getAttachmentManager().getAttachmentStream(att);
+                    InputStream in = context.getEngine().getAttachmentManager().getAttachmentStream( att );
 
                     StringWriter out = new StringWriter();
 
-                    FileUtil.copyContents( new InputStreamReader(in,"UTF-8"), out );
+                    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);
+                    log.info( "...recognizing additional " + blackList.size() + " patterns from blacklist " + m_blacklist );
 
                     m_spamPatterns.addAll( blackList );
                 }
@@ -844,28 +884,28 @@
         }
         catch( IOException ex )
         {
-            log.info("Unable to read attachment data, continuing...",ex);
+            log.info( "Unable to read attachment data, continuing...", ex );
         }
         catch( ProviderException ex )
         {
-            log.info("Failed to read spam filter attachment, continuing...",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
+     * 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
+    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 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 ) )
         {
@@ -873,42 +913,44 @@
         }
 
         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());
+            // log.debug("Attempting to match page contents with
+            // "+p.getPattern());
 
             Matcher matcher = p.matcher( ch );
-            if( matcher.find( ) )
+            if( matcher.find() )
             {
                 //
-                //  Spam filter has a match.
+                // Spam filter has a match.
                 //
-                String uid = log( context, REJECT, REASON_REGEXP+"("+p.pattern()+")", change.toString());
+                String uid = log( context, REJECT, REASON_REGEXP + "(" + p.pattern() + ")", change.toString() );
 
-                log.info("SPAM:Regexp ("+uid+"). Content matches the spam filter '"+p.pattern()+"'");
+                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+")");
+                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
+    private void checkPatternList( WikiContext context, String content, String change ) throws RedirectException
     {
         Change c = new Change();
         c.m_change = change;
-        checkPatternList(context,content,c);
+        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.
+     * 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 )
     {
@@ -918,23 +960,23 @@
         // Get current page version
 
         Change ch = new Change();
-        
+
         try
         {
-            String oldText = engine.getPureText(page.getName(), WikiProvider.LATEST_VERSION);
+            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());
+            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);
+                Delta d = rev.getDelta( i );
 
                 if( d instanceof AddDelta )
                 {
@@ -952,29 +994,29 @@
                 }
             }
         }
-        catch (DifferentiationFailedException e)
+        catch( DifferentiationFailedException e )
         {
             log.error( "Diff failed", e );
         }
 
         //
-        //  Don't forget to include the change note, too
+        // Don't forget to include the change note, too
         //
-        String changeNote = (String)page.getAttribute(WikiPage.CHANGENOTE);
+        String changeNote = (String) page.getAttribute( WikiPage.CHANGENOTE );
 
         if( changeNote != null )
         {
-            change.append("\r\n");
-            change.append(changeNote);
+            change.append( "\r\n" );
+            change.append( changeNote );
         }
 
         //
-        //  And author as well
+        // And author as well
         //
 
         if( page.getAuthor() != null )
         {
-            change.append("\r\n"+page.getAuthor());
+            change.append( "\r\n" + page.getAuthor() );
         }
 
         ch.m_change = change.toString();
@@ -982,12 +1024,12 @@
     }
 
     /**
-     *  Returns true, if this user should be ignored.  For example, admin users.
-     *
+     * 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)
+    private boolean ignoreThisUser( WikiContext context )
     {
         if( context.hasAdminPermissions() )
         {
@@ -999,7 +1041,7 @@
             return true;
         }
 
-        if( context.getVariable("captcha") != null )
+        if( context.getVariable( "captcha" ) != null )
         {
             return true;
         }
@@ -1008,9 +1050,9 @@
     }
 
     /**
-     *  Returns a random string of six uppercase characters.
-     *
-     *  @return A random string
+     * Returns a random string of six uppercase characters.
+     * 
+     * @return A random string
      */
     private static String getUniqueID()
     {
@@ -1019,36 +1061,37 @@
 
         for( int i = 0; i < 6; i++ )
         {
-            char x = (char)('A'+rand.nextInt(26));
+            char x = (char) ('A' + rand.nextInt( 26 ));
 
-            sb.append(x);
+            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
+     * 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
      */
     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.NONE, "Captcha.jsp", "page=" + ctx.getEngine().encodeName( ctx.getPage().getName() ) );
 
         return ctx.getURL( WikiContext.VIEW, m_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
+     * 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
      */
     public boolean isValidUserProfile( WikiContext context, UserProfile profile )
     {
@@ -1060,7 +1103,7 @@
         }
         catch( RedirectException e )
         {
-            log.info("Detected attempt to create a spammer user account (see above for rejection reason)");
+            log.info( "Detected attempt to create a spammer user account (see above for rejection reason)" );
             return false;
         }
 
@@ -1068,14 +1111,14 @@
     }
 
     /**
-     *  This method is used to calculate an unique code when submitting the page
-     *  to detect edit conflicts.  It currently incorporates the last-modified
-     *  date of the page, and the IP address of the submitter.
-     *
-     *  @param page The WikiPage under edit
-     *  @param request The HTTP Request
-     *  @since 2.6
-     *  @return A hash value for this page and session
+     * This method is used to calculate an unique code when submitting the page
+     * to detect edit conflicts. It currently incorporates the last-modified
+     * date of the page, and the IP address of the submitter.
+     * 
+     * @param page The WikiPage under edit
+     * @param request The HTTP Request
+     * @since 2.6
+     * @return A hash value for this page and session
      */
     public static final String getSpamHash( WikiPage page, HttpServletRequest request )
     {
@@ -1090,13 +1133,13 @@
     }
 
     /**
-     *  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
+     * 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 )
@@ -1105,7 +1148,7 @@
 
         if( request.getSession() != null )
         {
-            hash = (String)request.getSession().getAttribute("_hash");
+            hash = (String) request.getSession().getAttribute( "_hash" );
 
             if( hash == null )
             {
@@ -1115,7 +1158,7 @@
             }
         }
 
-        if( c_hashFieldName == null || c_lastUpdate < (System.currentTimeMillis() - HASH_DELAY*60*60*1000) )
+        if( c_hashFieldName == null || c_lastUpdate < (System.currentTimeMillis() - HASH_DELAY * 60 * 60 * 1000) )
         {
             c_hashFieldName = getUniqueID().toLowerCase();
 
@@ -1126,47 +1169,67 @@
     }
 
     /**
-     *  <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 returns a Stripes
-     *  {@link net.sourceforge.stripes.action.RedirectResolution} that directs
-     *  users to wiki 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).</p>
-     *  <p>If the hash is valid, this method simply returns <code>null</code>.</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.
-     *  @return <code>null</code> if hash is okay, or a RedirectResolution if not.
-     *  @since 3.0
+     * <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
      */
-    public static final Resolution validateSpamParams( WikiActionBean actionBean )
+    public static final void validateSpamParams( WikiActionBean actionBean, String[] beanProperties )
+                                                                                                     throws MissingParameterException
     {
         WikiActionBeanContext context = actionBean.getContext();
-        HttpServletRequest request = (HttpServletRequest)context.getRequest();
+        HttpServletRequest request = (HttpServletRequest) context.getRequest();
 
-        // Recover the encrypted parameter and then validate the trap and token fields
+        // Recover the encrypted parameter and then validate the trap and token
+        // fields
         boolean spamParamsValid = false;
-        String encryptedParam = request.getParameter( SpamInterceptor.REQ_SPAM_PARAM );
-        if ( encryptedParam != null )
+        String encryptedParam = request.getParameter( REQ_SPAM_PARAM );
+        if( encryptedParam != null )
         {
             String payload = CryptoUtil.decrypt( encryptedParam );
-            if ( payload != null )
+            if( payload != null )
             {
                 String[] spamParams = payload.split( "\n" );
-                if ( spamParams.length != 2 )
+                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 )
+                    if( trapParam == null || trapParam.length() == 0 )
                     {
-                        
+
                         // Token parameter should simply be the session ID
-                        if ( tokenParam != null && request.getSession().getId().equals( tokenParam ))
+                        if( tokenParam != null && request.getSession().getId().equals( tokenParam ) )
                         {
                             // If we got here, everything validated ok!
                             spamParamsValid = true;
@@ -1175,55 +1238,90 @@
                 }
             }
         }
-        
+
         if( !spamParamsValid )
         {
-            Change change = getChange( context, EditorManager.getEditedText( context.getRequest() ) );
-            log( context, REJECT, "MissingHash", change.m_change );
-            return new RedirectResolution( ViewActionBean.class, "view" ).addParameter( "name", "SessionExpired");
+            // 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 );
         }
-        return null;
-        
     }
-    
-    public static Resolution validateUTF8Param( WikiActionBean actionBean )
+
+    public static void validateUTF8Param( WikiActionBean actionBean ) throws MissingParameterException
     {
         WikiActionBeanContext context = actionBean.getContext();
         HttpServletRequest request = context.getHttpRequest();
-        if ( request != null )
+        if( request != null )
         {
-            String utf8field = request.getParameter( SpamInterceptor.REQ_ENCODING_CHECK );
-            if( utf8field != null && utf8field.equals("\u3041") )
+            String utf8field = request.getParameter( REQ_ENCODING_CHECK );
+            if( utf8field != null && utf8field.equals( "\u3041" ) )
             {
-                return null;
+                return;
+            }
+        }
+        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 )
+        {
+            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
             }
         }
-        String uid = log( context, REJECT, REASON_UTF8_TRAP, request.getRemoteAddr()  );
-        log.info("SPAM:UTF8Trap ("+uid+").  Wildly posting dumb bot detected.");
-        return new RedirectResolution( ViewActionBean.class, "view" ).addParameter( "name", "SessionExpired");
+        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
+     * 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
+    public static final boolean checkHash( WikiContext context, PageContext pageContext ) throws IOException
     {
-        String hashName = getHashFieldName( (HttpServletRequest)pageContext.getRequest() );
+        String hashName = getHashFieldName( (HttpServletRequest) pageContext.getRequest() );
 
-        if( pageContext.getRequest().getParameter(hashName) == null )
+        if( pageContext.getRequest().getParameter( hashName ) == null )
         {
             if( pageContext.getAttribute( hashName ) == null )
             {
@@ -1231,8 +1329,8 @@
 
                 log( context, REJECT, "MissingHash", change.m_change );
 
-                String redirect = context.getURL(WikiContext.VIEW,"SessionExpired");
-                ((HttpServletResponse)pageContext.getResponse()).sendRedirect( redirect );
+                String redirect = context.getURL( WikiContext.VIEW, "SessionExpired" );
+                ((HttpServletResponse) pageContext.getResponse()).sendRedirect( redirect );
 
                 return false;
             }
@@ -1242,37 +1340,41 @@
     }
 
     /**
-     *  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.
+     * 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);
+        WikiContext ctx = WikiContextFactory.findContext( pageContext );
         WikiEngine engine = ctx.getEngine();
 
         StringBuilder sb = new StringBuilder();
-        if (engine.getContentEncoding().equals("UTF-8"))
+        if( engine.getContentEncoding().equals( "UTF-8" ) )
         {
-            sb.append("<input name='encodingcheck' type='hidden' value='\u3041' />\n");
+            sb.append( "<input name='encodingcheck' type='hidden' value='\u3041' />\n" );
         }
 
         return sb.toString();
     }
+
     /**
-     *  A local class for storing host information.
-     *
-     *  @since
+     * A local class for storing host information.
+     * 
+     * @since
      */
     private class Host
     {
-        private  long m_addedTime = System.currentTimeMillis();
-        private  long m_releaseTime;
-        private  String m_address;
-        private  Change m_change;
+        private long m_addedTime = System.currentTimeMillis();
+
+        private long m_releaseTime;
+
+        private String m_address;
+
+        private Change m_change;
 
         public String getAddress()
         {
@@ -1297,34 +1399,36 @@
         public Host( String ipaddress, Change change )
         {
             m_address = ipaddress;
-            m_change  = change;
+            m_change = change;
 
             m_releaseTime = System.currentTimeMillis() + m_banTime * 60 * 1000L;
         }
     }
-    
+
     private static class Change
     {
         public String m_change;
-        public int    m_adds;
-        public int    m_removals;
-        
+
+        public int m_adds;
+
+        public int m_removals;
+
         public String toString()
         {
             return m_change;
         }
-        
-        public boolean equals(Object o)
+
+        public boolean equals( Object o )
         {
             if( o instanceof Change )
-                return m_change.equals( ((Change)o).m_change);
-            
+                return m_change.equals( ((Change) o).m_change );
+
             return false;
         }
-        
+
         public int hashCode()
         {
-            return m_change.hashCode()+17;
+            return m_change.hashCode() + 17;
         }
     }
 }

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=766540&r1=766539&r2=766540&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 Sun Apr 19 22:31:36 2009
@@ -1,6 +1,7 @@
 package org.apache.wiki.tags;
 
 import java.io.IOException;
+import java.security.SecureRandom;
 import java.util.Random;
 
 import javax.servlet.http.HttpServletRequest;
@@ -11,14 +12,14 @@
 import net.sourceforge.stripes.util.CryptoUtil;
 
 import org.apache.wiki.filters.SpamFilter;
-import org.apache.wiki.filters.SpamInterceptor;
+import org.apache.wiki.ui.stripes.SpamInterceptor;
 
 /**
  * <p>
  * Tag that injects hidden {@link SpamFilter}-related parameters into the
  * current form, which will be parsed and verified by {@link SpamInterceptor}
  * whenever the input is processed by an ActionBean event handler method
- * annotated with the {@link org.apache.wiki.filters.SpamProtect} annotation.
+ * annotated with the {@link org.apache.wiki.ui.stripes.SpamProtect} annotation.
  * </p>
  * <p>
  * This tag will inject the following parameters:
@@ -43,7 +44,7 @@
  * to be empty.</li>
  * </li>
  * <li><b>An an encrypted parameter</b> called
- * {@link SpamInterceptor#REQ_SPAM_PARAM}, whose contents are the names of
+ * {@link SpamFilter#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>
@@ -82,17 +83,17 @@
         out.write( "<input name=\"" + tokenParam + "\" type=\"hidden\" value=\"" + tokenValue + "\" />\n" );
 
         // Inject UTF-8 detector
-        out.write( "<input name=\""+ SpamInterceptor.REQ_ENCODING_CHECK + "\" type=\"hidden\" value=\"\u3041\" />\n" );
+        out.write( "<input name=\""+ SpamFilter.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=\""+ SpamInterceptor.REQ_SPAM_PARAM+"\" type=\"hidden\" value=\""+encryptedParam+"\" />\n" );
+        out.write( "<input name=\""+ SpamFilter.REQ_SPAM_PARAM+"\" type=\"hidden\" value=\""+encryptedParam+"\" />\n" );
     }
 
     private static String getUniqueID()
     {
         StringBuilder sb = new StringBuilder();
-        Random rand = new Random();
+        Random rand = new SecureRandom();
 
         for( int i = 0; i < 6; i++ )
         {

Copied: incubator/jspwiki/trunk/src/java/org/apache/wiki/ui/stripes/SpamInterceptor.java (from r765589, incubator/jspwiki/trunk/src/java/org/apache/wiki/filters/SpamInterceptor.java)
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/java/org/apache/wiki/ui/stripes/SpamInterceptor.java?p2=incubator/jspwiki/trunk/src/java/org/apache/wiki/ui/stripes/SpamInterceptor.java&p1=incubator/jspwiki/trunk/src/java/org/apache/wiki/filters/SpamInterceptor.java&r1=765589&r2=766540&rev=766540&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/java/org/apache/wiki/filters/SpamInterceptor.java (original)
+++ incubator/jspwiki/trunk/src/java/org/apache/wiki/ui/stripes/SpamInterceptor.java Sun Apr 19 22:31:36 2009
@@ -1,12 +1,18 @@
-package org.apache.wiki.filters;
+package org.apache.wiki.ui.stripes;
 
+import java.lang.reflect.Method;
+
+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 org.apache.wiki.action.ViewActionBean;
 import org.apache.wiki.action.WikiActionBean;
+import org.apache.wiki.content.MissingParameterException;
+import org.apache.wiki.filters.SpamFilter;
 
 /**
  * Stripes Interceptor that ensures that SpamFilter algorithms are applied to
@@ -19,35 +25,52 @@
 @Intercepts( { LifecycleStage.HandlerResolution } )
 public class SpamInterceptor implements Interceptor
 {
-    /** Request parameter containing the UTF8 check. */
-    public static final String REQ_ENCODING_CHECK = "encodingcheck";
-
-    /** Request parameter containing the encoded payload. */
-    public static final String REQ_SPAM_PARAM = "____sp____";
-
     /**
      * 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)}
-     * and {@link SpamFilter#validateUTF8Param(WikiActionBean)} in sequence, and
-     * returns any Resolutions they generate. If the targeted ActionBean event
-     * is not annotated, this method returns <code>null</code>.
+     * 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>.
      */
     public Resolution intercept( ExecutionContext context ) throws Exception
     {
         // Execute all other interceptors first
         context.proceed();
 
-        // Validate spam token/trap params
+        // Is the target handler protected by a @SpamProtect annotation?
         WikiActionBean actionBean = (WikiActionBean) context.getActionBean();
-        Resolution r = SpamFilter.validateSpamParams( actionBean );
+        Method handler = context.getHandler();
+        SpamProtect ann = handler.getAnnotation( SpamProtect.class );
+        if( ann == null )
+        {
+            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" );
+        }
 
         // Validate non-Latin1 param
-        if( r != null )
+        try
         {
-            r = SpamFilter.validateUTF8Param( actionBean );
+            SpamFilter.validateUTF8Param( actionBean );
         }
-        return r;
+        catch( MissingParameterException e )
+        {
+            return new RedirectResolution( ViewActionBean.class, "view" ).addParameter( "page", "SessionExpired" );
+        }
+
+        return null;
     }
 
 }

Copied: incubator/jspwiki/trunk/src/java/org/apache/wiki/ui/stripes/SpamProtect.java (from r765589, incubator/jspwiki/trunk/src/java/org/apache/wiki/filters/SpamProtect.java)
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/java/org/apache/wiki/ui/stripes/SpamProtect.java?p2=incubator/jspwiki/trunk/src/java/org/apache/wiki/ui/stripes/SpamProtect.java&p1=incubator/jspwiki/trunk/src/java/org/apache/wiki/filters/SpamProtect.java&r1=765589&r2=766540&rev=766540&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/java/org/apache/wiki/filters/SpamProtect.java (original)
+++ incubator/jspwiki/trunk/src/java/org/apache/wiki/ui/stripes/SpamProtect.java Sun Apr 19 22:31:36 2009
@@ -1,24 +1,31 @@
 /**
  * 
  */
-package org.apache.wiki.filters;
+package org.apache.wiki.ui.stripes;
 
 import java.lang.annotation.*;
 
+import org.apache.wiki.filters.SpamFilter;
+
 /**
  * Annotation indicating that an event handler method should check that the user
  * has submitted a series of expected {@link SpamFilter}-related parameters
- * with the POST or GET. The SpamProtect annotation can be applied to either
- * class or method targets. If the annotation applies to a Stripes event handler
+ * with the POST or GET. The SpamProtect annotation can be applied to
+ * method targets. When annotating Stripes event handler
  * method, the {@link SpamInterceptor} will apply spam filtering heuristics to
- * just the annotated event. If the annotation applies to a class, all event
- * handler methods will be filtered by default. Method-level annotations always
- * override class-level annotations.
+ * the annotated event.
  */
 @Documented
 @Inherited
 @Retention( value = RetentionPolicy.RUNTIME )
-@Target( { ElementType.METHOD, ElementType.TYPE } )
+@Target( { ElementType.METHOD } )
 public @interface SpamProtect
 {
+    /**
+     * The names of the bean properties containing the content being protected.
+     * This can be used, for example, to extract the protected content from
+     * annotated classes' by introspection. By default, this is a zero-argument
+     * array.
+     */
+    String[] content() default {};
 }

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=766540&r1=766539&r2=766540&view=diff
==============================================================================
--- incubator/jspwiki/trunk/tests/java/org/apache/wiki/TestEngine.java (original)
+++ incubator/jspwiki/trunk/tests/java/org/apache/wiki/TestEngine.java Sun Apr 19 22:31:36 2009
@@ -36,6 +36,7 @@
 import net.sourceforge.stripes.mock.MockHttpSession;
 import net.sourceforge.stripes.mock.MockRoundtrip;
 import net.sourceforge.stripes.mock.MockServletContext;
+import net.sourceforge.stripes.util.CryptoUtil;
 
 import org.apache.wiki.action.WikiActionBean;
 import org.apache.wiki.api.WikiException;
@@ -48,6 +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.log.Logger;
 import org.apache.wiki.log.LoggerFactory;
 import org.apache.wiki.providers.AbstractFileProvider;
@@ -167,6 +169,22 @@
         return request;
     }
     
+    /**
+     * For testing purposes: generates and adds sample spam-protect form parameters to a MockRountrip object.
+     * This is done in the same way that {@link SpamProtectTag} does it.
+     */
+    public static void addSpamProtectParams( MockRoundtrip trip )
+    {
+        // Add the trap + token params
+        String paramValue = CryptoUtil.encrypt( "TRAPAA\nTOKENA" );
+        trip.addParameter( "TRAPAA", new String[0] );
+        trip.addParameter( "TOKENA", trip.getRequest().getSession().getId() );
+        trip.addParameter( SpamFilter.REQ_SPAM_PARAM, paramValue );
+
+        // Add the UTF-8 token
+        trip.addParameter( SpamFilter.REQ_ENCODING_CHECK, "\u3041" );
+    }
+    
     public static void emptyWorkDir()
     {
         Properties properties = new Properties();

Added: incubator/jspwiki/trunk/tests/java/org/apache/wiki/action/TestActionBean.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/tests/java/org/apache/wiki/action/TestActionBean.java?rev=766540&view=auto
==============================================================================
--- incubator/jspwiki/trunk/tests/java/org/apache/wiki/action/TestActionBean.java (added)
+++ incubator/jspwiki/trunk/tests/java/org/apache/wiki/action/TestActionBean.java Sun Apr 19 22:31:36 2009
@@ -0,0 +1,51 @@
+package org.apache.wiki.action;
+
+import net.sourceforge.stripes.action.Resolution;
+import net.sourceforge.stripes.validation.Validate;
+
+import org.apache.wiki.action.AbstractPageActionBean;
+import org.apache.wiki.api.WikiPage;
+import org.apache.wiki.ui.stripes.SpamProtect;
+
+/**
+ * Simple test bean with a single event handler method.
+ */
+public class TestActionBean extends AbstractPageActionBean
+{
+    @SpamProtect( content = "text" )
+    public Resolution test()
+    {
+        return null;
+    }
+    
+    @Validate( required = false )
+    public void setPage( WikiPage page )
+    {
+        super.setPage( page );
+    }
+    
+    private String text = null;
+    
+    private String acl = null;
+
+    public String getText()
+    {
+        return text;
+    }
+
+    public void setText( String text )
+    {
+        this.text = text;
+    }
+
+    public String getAcl()
+    {
+        return acl;
+    }
+
+    public void setAcl( String acl )
+    {
+        this.acl = acl;
+    }
+    
+}
\ No newline at end of file



Mime
View raw message