producer batch scripts now report an error when the producer/verb is unknown
[mir.git] / source / mircoders / global / Abuse.java
index 41b31f8..184142f 100755 (executable)
 
 package mircoders.global;
 
-import gnu.regexp.RE;
-
+import java.io.BufferedOutputStream;
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.util.Arrays;
 import java.util.Date;
+import java.util.GregorianCalendar;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -45,25 +45,28 @@ import java.util.Random;
 import java.util.Vector;
 
 import javax.servlet.http.Cookie;
-import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import mir.config.MirPropertiesConfiguration;
 import mir.entity.Entity;
 import mir.log.LoggerWrapper;
-import mir.session.HTTPAdapters;
 import mir.session.Request;
-import mir.util.DateToMapAdapter;
-import mir.util.InternetFunctions;
+import mir.util.DateTimeFunctions;
+import mir.util.GeneratorFormatAdapters;
 import mir.util.StringRoutines;
+import mir.util.EntityUtility;
 import mircoders.entity.EntityComment;
 import mircoders.entity.EntityContent;
 import mircoders.localizer.MirAdminInterfaceLocalizer;
+import mircoders.localizer.MirAntiAbuseFilterType;
 
 import org.apache.commons.collections.ExtendedProperties;
 
 
 public class Abuse {
-  private List filters;
+  private List filterRules;
+  private Map filterTypes;
+  private List filterTypeIds;
   private int maxIdentifier;
   private LoggerWrapper logger;
   private int logSize;
@@ -76,18 +79,24 @@ public class Abuse {
   private List log;
   private String configFile = MirGlobal.config().getStringWithHome("Abuse.Config");
 
+  private MirPropertiesConfiguration configuration;
 
-  private static final String IP_FILTER_TYPE="ip";
-  private static final String REGEXP_FILTER_TYPE="regexp";
-  private static String cookieName=MirGlobal.config().getString("Abuse.CookieName");
-  private static int cookieMaxAge = 60*60*MirGlobal.config().getInt("Abuse.CookieMaxAge");
+  private static String cookieName = MirGlobal.config().getString("Abuse.CookieName");
+  private static int cookieMaxAge = 60 * 60 * MirGlobal.config().getInt("Abuse.CookieMaxAge");
 
   public Abuse() {
     logger = new LoggerWrapper("Global.Abuse");
-    filters = new Vector();
+    filterRules = new Vector();
     maxIdentifier = 0;
     log = new Vector();
 
+    try {
+      configuration = MirPropertiesConfiguration.instance();
+    }
+    catch (Throwable e) {
+      throw new RuntimeException("Can't get configuration: " + e.getMessage());
+    }
+
     logSize = 100;
     logEnabled = false;
     articleBlockAction = "";
@@ -96,61 +105,23 @@ public class Abuse {
     openPostingDisabled = false;
     cookieOnBlock = false;
 
-    load();
-  }
+    try {
+      filterTypes = new HashMap();
+      filterTypeIds = new Vector();
 
-  public boolean checkIpFilter(String anIpAddress) {
-    synchronized (filters) {
-      Iterator i = filters.iterator();
+      Iterator i = MirGlobal.localizer().openPostings().getAntiAbuseFilterTypes().iterator();
 
       while (i.hasNext()) {
-        Filter filter = (Filter) i.next();
-
-        try {
-          if ( (filter.getType().equals(IP_FILTER_TYPE)) &&
-              InternetFunctions.isIpAddressInNetwork(anIpAddress, filter.getExpression())) {
-            logger.debug("ip match on " + filter.getExpression());
-            return true;
-          }
-        }
-        catch (Throwable t) {
-          logger.warn("error while checking ip address " + anIpAddress + " over network " + filter.expression + ": " + t.getMessage());
-        }
+        MirAntiAbuseFilterType filterType = (MirAntiAbuseFilterType) i.next();
+        filterTypes.put(filterType.getName(), filterType);
+        filterTypeIds.add(filterType.getName());
       }
-
-      return false;
     }
-  }
-
-  private boolean checkRegExpFilter(Entity anEntity) {
-    synchronized (filters) {
-      Iterator i = filters.iterator();
-
-      while (i.hasNext()) {
-        Filter filter = (Filter) i.next();
-
-        if (filter.getType().equals(REGEXP_FILTER_TYPE)) {
-          try {
-            RE regularExpression = new RE(filter.getExpression());
-
-            Iterator j = anEntity.getFields().iterator();
-            while (j.hasNext()) {
-              String field = anEntity.getValue( (String) j.next());
-
-              if (field != null && regularExpression.isMatch(field.toLowerCase())) {
-                logger.debug("regexp match on " + filter.getExpression());
-                return true;
-              }
-            }
-          }
-          catch (Throwable t) {
-            logger.warn("error while checking entity with regexp " + filter.getExpression() + ": " + t.getMessage());
-          }
-        }
-      }
-
-      return false;
+    catch (Throwable t) {
+      throw new RuntimeException("Can't get filter types: " + t.getMessage());
     }
+
+    load();
   }
 
   private void setCookie(HttpServletResponse aResponse) {
@@ -160,7 +131,7 @@ public class Abuse {
     cookie.setMaxAge(cookieMaxAge);
     cookie.setPath("/");
 
-    if (aResponse!=null)
+    if (aResponse != null)
       aResponse.addCookie(cookie);
   }
 
@@ -181,39 +152,55 @@ public class Abuse {
     return false;
   }
 
+  FilterRule findMatchingFilter(Entity anEntity, Request aRequest) {
+    Iterator iterator = filterRules.iterator();
+
+    while (iterator.hasNext()) {
+      FilterRule rule = (FilterRule) iterator.next();
+
+      if (rule.test(anEntity, aRequest))
+        return rule;
+    }
+
+    return null;
+  }
+
   public void checkComment(EntityComment aComment, Request aRequest, HttpServletResponse aResponse) {
     try {
       long time = System.currentTimeMillis();
-      String address = "0.0.0.0";
-      String browser = "unknown";
-      Cookie[] cookies = {};
 
-      HttpServletRequest request = null;
+      FilterRule filterRule = findMatchingFilter(aComment, aRequest);
 
-      if (aRequest instanceof HTTPAdapters.HTTPParsedRequestAdapter) {
-        request = ((HTTPAdapters.HTTPParsedRequestAdapter) aRequest).getRequest();
-      }
-      else if (aRequest instanceof HTTPAdapters.HTTPRequestAdapter) {
-        request = ((HTTPAdapters.HTTPRequestAdapter) aRequest).getRequest();
-      }
-      if (request!=null) {
-        browser = (String) request.getHeader("User-Agent");
-        address = request.getRemoteAddr();
-        cookies = request.getCookies();
-      }
+      if (filterRule != null) {
+        logger.debug("Match for " + filterRule.getType() + " rule '" + filterRule.getExpression() + "'");
+        filterRule.setLastHit(new GregorianCalendar().getTime());
+
+        StringBuffer line = new StringBuffer();
 
-      logComment(address, aComment.getId(), new Date(), browser);
+        line.append(DateTimeFunctions.advancedDateFormat(
+            configuration.getString("Mir.DefaultDateTimeFormat"),
+            (new GregorianCalendar()).getTime(), configuration.getString("Mir.DefaultTimezone")));
 
-      MirAdminInterfaceLocalizer.MirSimpleEntityOperation operation = MirGlobal.localizer().adminInterface().simpleCommentOperationForName(commentBlockAction);
+        line.append(" ");
+        line.append("filter");
 
-      if (checkCookie(Arrays.asList(cookies)) || checkIpFilter(address) || checkRegExpFilter(aComment)) {
-        operation.perform(null, MirGlobal.localizer().dataModel().adapterModel().makeEntityAdapter("comment", aComment));
+        line.append(" ");
+        line.append(filterRule.getType() +" ("+ filterRule.getExpression()+")");
+        EntityUtility.appendLineToField(aComment, "comment", line.toString());
+
+        MirGlobal.performCommentOperation(null, aComment, filterRule.getCommentAction());
         setCookie(aResponse);
+        save();
+        logComment(aComment, aRequest, filterRule.getType(), filterRule.getExpression());
       }
+      else
+        logComment(aComment, aRequest);
+
 
-      logger.info("checkComment: " + (System.currentTimeMillis()-time) + "ms");
+      logger.info("checkComment: " + (System.currentTimeMillis() - time) + "ms");
     }
     catch (Throwable t) {
+      t.printStackTrace(logger.asPrintWriter(LoggerWrapper.DEBUG_MESSAGE));
       logger.error("Abuse.checkComment: " + t.toString());
     }
   }
@@ -221,36 +208,38 @@ public class Abuse {
   public void checkArticle(EntityContent anArticle, Request aRequest, HttpServletResponse aResponse) {
     try {
       long time = System.currentTimeMillis();
-      String address = "0.0.0.0";
-      String browser = "unknown";
-      Cookie[] cookies = {};
 
-      HttpServletRequest request = null;
+      FilterRule filterRule = findMatchingFilter(anArticle, aRequest);
 
-      if (aRequest instanceof HTTPAdapters.HTTPParsedRequestAdapter) {
-        request = ((HTTPAdapters.HTTPParsedRequestAdapter) aRequest).getRequest();
-      }
-      else if (aRequest instanceof HTTPAdapters.HTTPRequestAdapter) {
-        request = ((HTTPAdapters.HTTPRequestAdapter) aRequest).getRequest();
-      }
-      if (request!=null) {
-        browser = (String) request.getHeader("User-Agent");
-        address = request.getRemoteAddr();
-        cookies = request.getCookies();
-      }
+      if (filterRule != null) {
+        logger.debug("Match for " + filterRule.getType() + " rule '" + filterRule.getExpression() + "'");
+        filterRule.setLastHit(new GregorianCalendar().getTime());
+
+        StringBuffer line = new StringBuffer();
 
-      logArticle(address, anArticle.getId(), new Date(), browser);
+        line.append(DateTimeFunctions.advancedDateFormat(
+            configuration.getString("Mir.DefaultDateTimeFormat"),
+            (new GregorianCalendar()).getTime(), configuration.getString("Mir.DefaultTimezone")));
 
-      MirAdminInterfaceLocalizer.MirSimpleEntityOperation operation = MirGlobal.localizer().adminInterface().simpleCommentOperationForName(commentBlockAction);
+        line.append(" ");
+        line.append("filter");
 
-      if (checkCookie(Arrays.asList(cookies)) || checkIpFilter(address) || checkRegExpFilter(anArticle)) {
-        operation.perform(null, MirGlobal.localizer().dataModel().adapterModel().makeEntityAdapter("content", anArticle));
+        line.append(" ");
+        line.append(filterRule.getType() +" ("+ filterRule.getExpression()+")");
+        EntityUtility.appendLineToField(anArticle, "comment", line.toString());
+
+        MirGlobal.performArticleOperation(null, anArticle, filterRule.getArticleAction());
         setCookie(aResponse);
+        save();
+        logArticle(anArticle, aRequest, filterRule.getType(), filterRule.getExpression());
       }
+      else
+        logArticle(anArticle, aRequest);
 
-      logger.info("checkArticle: " + (System.currentTimeMillis()-time) + "ms");
+      logger.info("checkArticle: " + (System.currentTimeMillis() - time) + "ms");
     }
     catch (Throwable t) {
+      t.printStackTrace(logger.asPrintWriter(LoggerWrapper.DEBUG_MESSAGE));
       logger.error("Abuse.checkArticle: " + t.toString());
     }
   }
@@ -260,7 +249,8 @@ public class Abuse {
   }
 
   public void setLogEnabled(boolean anEnabled) {
-    logEnabled = anEnabled;
+    if (!configuration.getString("Abuse.DisallowIPLogging", "0").equals("1"))
+      logEnabled = anEnabled;
     truncateLog();
   }
 
@@ -313,99 +303,140 @@ public class Abuse {
     commentBlockAction = anAction;
   }
 
-
   public List getLog() {
-    synchronized(log) {
-      List result = new Vector();
+    synchronized (log) {
+      try {
+        List result = new Vector();
+
+        Iterator i = log.iterator();
+        while (i.hasNext()) {
+          LogEntry logEntry = (LogEntry) i.next();
+          Map entry = new HashMap();
+
+          entry.put("ip", logEntry.getIpNumber());
+          entry.put("id", logEntry.getId());
+          entry.put("timestamp", new GeneratorFormatAdapters.DateFormatAdapter(logEntry.getTimeStamp(), MirPropertiesConfiguration.instance().getString("Mir.DefaultTimezone")));
+          if (logEntry.getIsArticle())
+            entry.put("type", "content");
+          else
+            entry.put("type", "comment");
+          entry.put("browser", logEntry.getBrowserString());
+          entry.put("hitfiltertype", logEntry.getHitFilterType());
+          entry.put("hitfilterexpression", logEntry.getHitFilterExpression());
+
+          result.add(entry);
+        }
 
-      Iterator i = log.iterator();
-      while (i.hasNext()) {
-        LogEntry logEntry = (LogEntry) i.next();
-        Map entry = new HashMap();
-
-        entry.put("ip", logEntry.getIpNumber());
-        entry.put("id", logEntry.getId());
-        entry.put("timestamp", new DateToMapAdapter(logEntry.getTimeStamp()));
-        if (logEntry.getIsArticle())
-          entry.put("type", "content");
-        else
-          entry.put("type", "comment");
-        entry.put("browser", logEntry.getBrowserString());
-
-        result.add(entry);
+        return result;
+      }
+      catch (Throwable t) {
+        throw new RuntimeException(t.toString());
       }
-
-      return result;
     }
   }
 
-  public void logComment(String anIp, String anId, Date aTimeStamp, String aBrowser) {
-    appendLog(new LogEntry(aTimeStamp, anIp, aBrowser, anId, false));
+  public void logComment(Entity aComment, Request aRequest) {
+    logComment(aComment, aRequest, null, null);
   }
 
-  public void logArticle(String anIp, String anId, Date aTimeStamp, String aBrowser) {
-    appendLog(new LogEntry(aTimeStamp, anIp, aBrowser, anId, true));
+  public void logComment(Entity aComment, Request aRequest, String aHitFilterType, String aHitFilterExpression) {
+    String ipAddress = aRequest.getHeader("ip");
+    String id = aComment.getId();
+    String browser = aRequest.getHeader("User-Agent");
+
+    logComment(ipAddress, id, new Date(), browser, aHitFilterType, aHitFilterExpression);
   }
 
-  public void load() {
-    try {
-      ExtendedProperties configuration = new ExtendedProperties();
+  public void logArticle(Entity anArticle, Request aRequest) {
+    logArticle(anArticle, aRequest, null, null);
+  }
+
+  public void logArticle(Entity anArticle, Request aRequest, String aHitFilterType, String aHitFilterExpression) {
+    String ipAddress = aRequest.getHeader("ip");
+    String id = anArticle.getId();
+    String browser = aRequest.getHeader("User-Agent");
+
+    logArticle(ipAddress, id, new Date(), browser, aHitFilterType, aHitFilterExpression);
+  }
+
+  public void logComment(String anIp, String anId, Date aTimeStamp, String aBrowser, String aHitFilterType, String aHitFilterExpression) {
+    appendLog(new LogEntry(aTimeStamp, anIp, aBrowser, anId, false, aHitFilterType, aHitFilterExpression));
+  }
 
+  public void logArticle(String anIp, String anId, Date aTimeStamp, String aBrowser, String aHitFilterType, String aHitFilterExpression) {
+    appendLog(new LogEntry(aTimeStamp, anIp, aBrowser, anId, true, aHitFilterType, aHitFilterExpression));
+  }
+
+  public void load() {
+    synchronized (filterRules) {
       try {
-        configuration = new ExtendedProperties(configFile);
-      }
-      catch (FileNotFoundException e) {
-      }
+        ExtendedProperties configuration = new ExtendedProperties();
+
+        try {
+          configuration = new ExtendedProperties(configFile);
+        }
+        catch (FileNotFoundException e) {
+        }
 
-      getFilterConfig(filters, "abuse.filter", configuration);
+        getFilterConfig(filterRules, "abuse.filter", configuration);
 
-      setOpenPostingDisabled(configuration.getString("abuse.openPostingDisabled", "0").equals("1"));
-      setOpenPostingPassword(configuration.getString("abuse.openPostingPassword", "0").equals("1"));
-      setCookieOnBlock(configuration.getString("abuse.cookieOnBlock", "0").equals("1"));
-      setLogEnabled(configuration.getString("abuse.logEnabled", "0").equals("1"));
-      setLogSize(configuration.getInt("abuse.logSize", 10));
-      setArticleBlockAction(configuration.getString("abuse.articleBlockAction", ""));
-      setCommentBlockAction(configuration.getString("abuse.commentBlockAction", ""));
-    }
-    catch (Throwable t) {
-      throw new RuntimeException(t.toString());
+        setOpenPostingDisabled(configuration.getString("abuse.openPostingDisabled", "0").equals("1"));
+        setOpenPostingPassword(configuration.getString("abuse.openPostingPassword", "0").equals("1"));
+        setCookieOnBlock(configuration.getString("abuse.cookieOnBlock", "0").equals("1"));
+        setLogEnabled(configuration.getString("abuse.logEnabled", "0").equals("1"));
+        setLogSize(configuration.getInt("abuse.logSize", 10));
+        setArticleBlockAction(configuration.getString("abuse.articleBlockAction", ""));
+        setCommentBlockAction(configuration.getString("abuse.commentBlockAction", ""));
+      }
+      catch (Throwable t) {
+        throw new RuntimeException(t.toString());
+      }
     }
   }
+
   public void save() {
-    try {
-      ExtendedProperties configuration = new ExtendedProperties();
+    synchronized (filterRules) {
+      try {
+        ExtendedProperties configuration = new ExtendedProperties();
 
-      setFilterConfig(filters, "abuse.filter", configuration);
+        setFilterConfig(filterRules, "abuse.filter", configuration);
 
-      configuration.addProperty("abuse.openPostingDisabled", getOpenPostingDisabled()?"1":"0");
-      configuration.addProperty("abuse.openPostingPassword", getOpenPostingPassword()?"1":"0");
-      configuration.addProperty("abuse.cookieOnBlock", getCookieOnBlock()?"1":"0");
-      configuration.addProperty("abuse.logEnabled", getLogEnabled()?"1":"0");
-      configuration.addProperty("abuse.logSize", Integer.toString(getLogSize()));
-      configuration.addProperty("abuse.articleBlockAction", getArticleBlockAction());
-      configuration.addProperty("abuse.commentBlockAction", getCommentBlockAction());
+        configuration.addProperty("abuse.openPostingDisabled", getOpenPostingDisabled() ? "1" : "0");
+        configuration.addProperty("abuse.openPostingPassword", getOpenPostingPassword() ? "1" : "0");
+        configuration.addProperty("abuse.cookieOnBlock", getCookieOnBlock() ? "1" : "0");
+        configuration.addProperty("abuse.logEnabled", getLogEnabled() ? "1" : "0");
+        configuration.addProperty("abuse.logSize", Integer.toString(getLogSize()));
+        configuration.addProperty("abuse.articleBlockAction", getArticleBlockAction());
+        configuration.addProperty("abuse.commentBlockAction", getCommentBlockAction());
 
-      configuration.save(new FileOutputStream(new File(configFile)), "Anti abuse configuration");
-    }
-    catch (Throwable t) {
-      throw new RuntimeException(t.toString());
+        configuration.save(new BufferedOutputStream(new FileOutputStream(new File(configFile)),8192), "Anti abuse configuration");
+      }
+      catch (Throwable t) {
+        throw new RuntimeException(t.toString());
+      }
     }
   }
 
   public List getFilterTypes() {
-    List result = new Vector();
+    try {
+      List result = new Vector();
+
+      Iterator i = filterTypeIds.iterator();
+      while (i.hasNext()) {
+        String id = (String) i.next();
 
-    Map entry = new HashMap();
-    entry.put("resource", "ip");
-    entry.put("id", IP_FILTER_TYPE);
-    result.add(entry);
+        Map action = new HashMap();
+        action.put("resource", id);
+        action.put("identifier", id);
 
-    entry = new HashMap();
-    entry.put("resource", "regexp");
-    entry.put("id", REGEXP_FILTER_TYPE);
-    result.add(entry);
+        result.add(action);
+      }
 
-    return result;
+      return result;
+    }
+    catch (Throwable t) {
+      throw new RuntimeException("can't get article actions");
+    }
   }
 
   public List getArticleActions() {
@@ -455,71 +486,117 @@ public class Abuse {
   }
 
   public List getFilters() {
-    return getFiltersAsMaps(filters);
+    List result = new Vector();
+
+    synchronized (filterRules) {
+      Iterator i = filterRules.iterator();
+      while (i.hasNext()) {
+        FilterRule filter = (FilterRule) i.next();
+        result.add(filter.clone());
+      }
+      return result;
+    }
+  }
+
+  public String addFilter(String aType, String anExpression, String aComments, String aCommentAction, String anArticleAction) {
+    return addFilter(aType, anExpression, aComments, aCommentAction, anArticleAction, null);
   }
 
-  public void addFilter(String aType, String anExpression) {
-    addFilter(filters, aType, anExpression);
+  public String addFilter(String aType, String anExpression, String aComments, String aCommentAction, String anArticleAction, Date aListHit) {
+    return addFilter(filterRules, aType, anExpression, aComments, aCommentAction, anArticleAction, aListHit);
+  }
+
+  public FilterRule getFilter(String anId) {
+    synchronized (filterRules) {
+      FilterRule result = findFilter(filterRules, anId);
+      if (result == null)
+        return result;
+      else
+        return (FilterRule) result.clone();
+    }
   }
 
-  public void setFilter(String anIdentifier, String aType, String anExpression) {
-    setFilter(filters, anIdentifier, aType, anExpression);
+  public String setFilter(String anIdentifier, String aType, String anExpression, String aComments, String aCommentAction, String anArticleAction) {
+    return setFilter(filterRules, anIdentifier, aType, anExpression, aComments, aCommentAction, anArticleAction);
   }
 
   public void deleteFilter(String anIdentifier) {
-    deleteFilter(filters, anIdentifier);
+    deleteFilter(filterRules, anIdentifier);
   }
 
-  public void validateIpFilter(String anIdentifier, String anArticleAction, String aCommentAction) throws Exception {
+  public void moveFilterUp(String anIdentifier) {
+    moveFilter(filterRules, anIdentifier, -1);
   }
 
-  private List getFiltersAsMaps(List aFilters) {
-    synchronized(aFilters) {
-      List result = new Vector();
+  public void moveFilterDown(String anIdentifier) {
+    moveFilter(filterRules, anIdentifier, 1);
+  }
 
-      Iterator i = aFilters.iterator();
-      while (i.hasNext()) {
-        Filter filter = (Filter) i.next();
-        Map map = new HashMap();
+  public void moveFilterToTop(String anIdentifier) {
+    setFilterPosition(filterRules, anIdentifier, 0);
+  }
+
+  public void moveFilterToBottom(String anIdentifier) {
+    setFilterPosition(filterRules, anIdentifier, Integer.MAX_VALUE);
+  }
 
-        map.put("id", filter.getId());
-        map.put("expression", filter.getExpression());
-        map.put("type", filter.getType());
+  private String addFilter(List aFilters, String aType, String anExpression, String aComments, String aCommentAction, String anArticleAction, Date aLastHit) {
+    MirAntiAbuseFilterType type = (MirAntiAbuseFilterType) filterTypes.get(aType);
 
-        result.add(map);
-      }
-      return result;
+    if (type == null)
+      return "invalidtype";
+
+    if (!type.validate(anExpression)) {
+      return "invalidexpression";
     }
-  }
 
-  private void addFilter(List aFilters, String aType, String anExpression) {
-    Filter filter = new Filter();
+    FilterRule filter = new FilterRule();
 
     filter.setId(generateId());
     filter.setExpression(anExpression);
     filter.setType(aType);
+    filter.setComments(aComments);
+    filter.setArticleAction(anArticleAction);
+    filter.setCommentAction(aCommentAction);
+    filter.setLastHit(aLastHit);
 
     synchronized (aFilters) {
       aFilters.add(filter);
     }
+
+    return null;
   }
 
-  private void setFilter(List aFilters, String anIdentifier, String aType, String anExpression) {
+  private String setFilter(List aFilters, String anIdentifier, String aType, String anExpression, String aComments, String aCommentAction, String anArticleAction) {
+    MirAntiAbuseFilterType type = (MirAntiAbuseFilterType) filterTypes.get(aType);
+
+    if (type == null)
+      return "invalidtype";
+
+    if (!type.validate(anExpression)) {
+      return "invalidexpression";
+    }
+
     synchronized (aFilters) {
-      Filter filter = findFilter(aFilters, anIdentifier);
+      FilterRule filter = findFilter(aFilters, anIdentifier);
 
-      if (filter!=null) {
+      if (filter != null) {
         filter.setExpression(anExpression);
         filter.setType(aType);
+        filter.setCommentAction(aCommentAction);
+        filter.setArticleAction(anArticleAction);
+        filter.setComments(aComments);
       }
+
+      return null;
     }
   }
 
-  private Filter findFilter(List aFilters, String anIdentifier) {
+  private FilterRule findFilter(List aFilters, String anIdentifier) {
     synchronized (aFilters) {
       Iterator i = aFilters.iterator();
       while (i.hasNext()) {
-        Filter filter = (Filter) i.next();
+        FilterRule filter = (FilterRule) i.next();
 
         if (filter.getId().equals(anIdentifier)) {
           return filter;
@@ -530,33 +607,84 @@ public class Abuse {
     return null;
   }
 
+  private void setFilterPosition(List aFilters, String anIdentifier, int aPosition) {
+    synchronized (aFilters) {
+      if (aPosition<0)
+        aPosition=0;
+
+      for (int i = 0; i < aFilters.size(); i++) {
+        FilterRule rule = (FilterRule) aFilters.get(i);
+
+        if (rule.getId().equals(anIdentifier)) {
+          aFilters.remove(rule);
+
+          if (aPosition<aFilters.size())
+            aFilters.add(aPosition, rule);
+          else
+            aFilters.add(rule);
+          break;
+        }
+      }
+    }
+  }
+
+  private void moveFilter(List aFilters, String anIdentifier, int aDirection) {
+    synchronized (aFilters) {
+      for (int i = 0; i < aFilters.size(); i++) {
+        FilterRule rule = (FilterRule) aFilters.get(i);
+
+        if (rule.getId().equals(anIdentifier) && (i + aDirection >= 0) && (i + aDirection < aFilters.size())) {
+          aFilters.remove(rule);
+          aFilters.add(i + aDirection, rule);
+          break;
+        }
+      }
+    }
+  }
+
   private void deleteFilter(List aFilters, String anIdentifier) {
     synchronized (aFilters) {
-      Filter filter = findFilter(aFilters, anIdentifier);
+      FilterRule filter = findFilter(aFilters, anIdentifier);
 
-      if (filter!=null) {
+      if (filter != null) {
         aFilters.remove(filter);
       }
     }
   }
 
   private String generateId() {
-    synchronized(this) {
-      maxIdentifier = maxIdentifier+1;
+    synchronized (this) {
+      maxIdentifier = maxIdentifier + 1;
 
       return Integer.toString(maxIdentifier);
     }
   }
 
-  private static class Filter {
+  public class FilterRule {
     private String identifier;
     private String expression;
     private String type;
+    private String comments;
+    private String articleAction;
+    private String commentAction;
+    private Date lastHit;
+
+    public FilterRule() {
+      expression = "";
+      type = "";
+      identifier = "";
+      comments = "";
+      articleAction = articleBlockAction;
+      commentAction = commentBlockAction;
+      lastHit = null;
+    }
+
+    public Date getLastHit() {
+      return lastHit;
+    }
 
-    public Filter() {
-      expression="";
-      type="";
-      identifier="";
+    public void setLastHit(Date aDate) {
+      lastHit = aDate;
     }
 
     public String getId() {
@@ -582,32 +710,131 @@ public class Abuse {
     public void setType(String aType) {
       type = aType;
     }
+
+    public void setComments(String aComments) {
+      comments = aComments;
+    }
+
+    public String getComments() {
+      return comments;
+    }
+
+    public String getArticleAction() {
+      return articleAction;
+    }
+
+    public void setArticleAction(String anArticleAction) {
+      articleAction = anArticleAction;
+    }
+
+    public String getCommentAction() {
+      return commentAction;
+    }
+
+    public void setCommentAction(String aCommentAction) {
+      commentAction = aCommentAction;
+    }
+
+    public boolean test(Entity anEntity, Request aRequest) {
+      MirAntiAbuseFilterType filterType = (MirAntiAbuseFilterType) filterTypes.get(type);
+      try {
+        if (filterType != null)
+          return filterType.test(expression, anEntity, aRequest);
+      }
+      catch (Throwable t) {
+        logger.error("error while testing " + type + "-filter '" + expression + "'");
+      }
+
+      return false;
+    };
+
+    public Object clone() {
+      FilterRule result = new FilterRule();
+      result.setComments(getComments());
+      result.setExpression(getExpression());
+      result.setId(getId());
+      result.setType(getType());
+      result.setArticleAction(getArticleAction());
+      result.setCommentAction(getCommentAction());
+      result.setLastHit(getLastHit());
+
+      return result;
+    }
+  }
+
+  private String escapeFilterPart(String aFilterPart) {
+    return StringRoutines.replaceStringCharacters(aFilterPart,
+        new char[] {'\\', ':', '\n', '\r', '\t', ' '},
+        new String[] {"\\\\", "\\:", "\\n", "\\r", "\\t", "\\ "});
+  }
+
+  private String deescapeFilterPart(String aFilterPart) {
+    return StringRoutines.replaceEscapedStringCharacters(aFilterPart,
+        '\\',
+        new char[] {'\\', ':', 'n', 'r', 't', ' '},
+        new String[] {"\\", ":", "\n", "\r", "\t", " "});
   }
 
   private void setFilterConfig(List aFilters, String aConfigKey, ExtendedProperties aConfiguration) {
-    synchronized(aFilters) {
+    synchronized (aFilters) {
       Iterator i = aFilters.iterator();
 
       while (i.hasNext()) {
-        Filter filter = (Filter) i.next();
+        FilterRule filter = (FilterRule) i.next();
+
+        String filterconfig =
+            escapeFilterPart(filter.getType()) + ":" +
+            escapeFilterPart(filter.getExpression()) + ":" +
+            escapeFilterPart(filter.getArticleAction()) + ":" +
+            escapeFilterPart(filter.getCommentAction()) + ":" +
+            escapeFilterPart(filter.getComments()) + ":";
+
+        if (filter.getLastHit() != null)
+          filterconfig = filterconfig + filter.getLastHit().getTime();
 
-        aConfiguration.addProperty(aConfigKey, filter.getType()+":"+filter.getExpression());
+        aConfiguration.addProperty(aConfigKey, filterconfig);
       }
     }
   }
 
   private void getFilterConfig(List aFilters, String aConfigKey, ExtendedProperties aConfiguration) {
-    synchronized(aFilters) {
+    synchronized (aFilters) {
       aFilters.clear();
 
-      Iterator i = Arrays.asList(aConfiguration.getStringArray(aConfigKey)).iterator();
+      if (aConfiguration.getStringArray(aConfigKey) != null) {
 
-      while (i.hasNext()) {
-        String filter = (String) i.next();
-        List parts = StringRoutines.separateString(filter, ":");
+        Iterator i = Arrays.asList(aConfiguration.getStringArray(aConfigKey)).
+            iterator();
 
-        if (parts.size()==2) {
-          addFilter( (String) parts.get(0), (String) parts.get(1));
+        while (i.hasNext()) {
+          String filter = (String) i.next();
+          List parts = StringRoutines.splitStringWithEscape(filter, ':', '\\');
+          if (parts.size() == 2) {
+            parts.add(articleBlockAction);
+            parts.add(commentBlockAction);
+            parts.add("");
+            parts.add("");
+          }
+
+          if (parts.size() >= 5) {
+            Date lastHit = null;
+
+            if (parts.size() >= 6) {
+              String lastHitString = (String) parts.get(5);
+
+              try {
+                lastHit = new Date(Long.parseLong(lastHitString));
+              }
+              catch (Throwable t) {
+              }
+            }
+
+            addFilter(deescapeFilterPart( (String) parts.get(0)),
+                      deescapeFilterPart( (String) parts.get(1)),
+                      deescapeFilterPart( (String) parts.get(4)),
+                      deescapeFilterPart( (String) parts.get(3)),
+                      deescapeFilterPart( (String) parts.get(2)), lastHit);
+          }
         }
       }
     }
@@ -619,13 +846,21 @@ public class Abuse {
     private String id;
     private Date timeStamp;
     private boolean isArticle;
+    private String hitFilterType;
+    private String hitFilterExpression;
 
-    public LogEntry(Date aTimeStamp, String anIpNumber, String aBrowserString, String anId, boolean anIsArticle) {
+    public LogEntry(Date aTimeStamp, String anIpNumber, String aBrowserString, String anId, boolean anIsArticle, String aHitFilterType, String aHitFilterExpression) {
       ipNumber = anIpNumber;
       browserString = aBrowserString;
       id = anId;
       isArticle = anIsArticle;
-      timeStamp=aTimeStamp;
+      timeStamp = aTimeStamp;
+      hitFilterType = aHitFilterType;
+      hitFilterExpression = aHitFilterExpression;
+    }
+
+    public LogEntry(Date aTimeStamp, String anIpNumber, String aBrowserString, String anId, boolean anIsArticle) {
+      this(aTimeStamp, anIpNumber, aBrowserString, anId, anIsArticle, null, null);
     }
 
     public String getIpNumber() {
@@ -640,6 +875,14 @@ public class Abuse {
       return id;
     }
 
+    public String getHitFilterType() {
+      return hitFilterType;
+    }
+
+    public String getHitFilterExpression() {
+      return hitFilterExpression;
+    }
+
     public Date getTimeStamp() {
       return timeStamp;
     }
@@ -650,12 +893,12 @@ public class Abuse {
   }
 
   private void truncateLog() {
-    synchronized(log) {
+    synchronized (log) {
       if (!logEnabled)
         log.clear();
       else {
-        while (log.size()>0 && log.size()>logSize) {
-          log.remove(0);
+        while (log.size() > 0 && log.size() > logSize) {
+          log.remove(log.size()-1);
         }
       }
     }
@@ -664,10 +907,9 @@ public class Abuse {
   private void appendLog(LogEntry anEntry) {
     synchronized (log) {
       if (logEnabled) {
-        log.add(anEntry);
+        log.add(0, anEntry);
         truncateLog();
       }
     }
   }
-
-}
\ No newline at end of file
+}