rebuilding head
[mir.git] / source / mircoders / localizer / basic / filters / ThrottleFilter.java
diff --git a/source/mircoders/localizer/basic/filters/ThrottleFilter.java b/source/mircoders/localizer/basic/filters/ThrottleFilter.java
new file mode 100755 (executable)
index 0000000..65b4732
--- /dev/null
@@ -0,0 +1,237 @@
+/*\r
+ * Copyright (C) 2001, 2002 The Mir-coders group\r
+ *\r
+ * This file is part of Mir.\r
+ *\r
+ * Mir is free software; you can redistribute it and/or modify\r
+ * it under the terms of the GNU General Public License as published by\r
+ * the Free Software Foundation; either version 2 of the License, or\r
+ * (at your option) any later version.\r
+ *\r
+ * Mir is distributed in the hope that it will be useful,\r
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+ * GNU General Public License for more details.\r
+ *\r
+ * You should have received a copy of the GNU General Public License\r
+ * along with Mir; if not, write to the Free Software\r
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\r
+ *\r
+ * In addition, as a special exception, The Mir-coders gives permission to link\r
+ * the code of this program with  any library licensed under the Apache Software License,\r
+ * The Sun (tm) Java Advanced Imaging library (JAI), The Sun JIMI library\r
+ * (or with modified versions of the above that use the same license as the above),\r
+ * and distribute linked combinations including the two.  You must obey the\r
+ * GNU General Public License in all respects for all of the code used other than\r
+ * the above mentioned libraries.  If you modify this file, you may extend this\r
+ * exception to your version of the file, but you are not obligated to do so.\r
+ * If you do not wish to do so, delete this exception statement from your version.\r
+ */\r
+package mircoders.localizer.basic.filters;\r
+\r
+import mircoders.localizer.basic.MirBasicAntiAbuseFilterTypes;\r
+import mircoders.entity.EntityComment;\r
+\r
+import java.util.*;\r
+\r
+import mir.util.StringRoutines;\r
+import mir.entity.Entity;\r
+import mir.session.Request;\r
+\r
+/**\r
+ * A ip-based throttling filter.\r
+ *\r
+ * <p>\r
+ * Expressions have the form <time in minutes>:<posting limit>\r
+ */\r
+public class ThrottleFilter extends MirBasicAntiAbuseFilterTypes.BasicFilterType {\r
+  private long overallHorizon;\r
+\r
+  private ThrottleManager throttleManager;\r
+\r
+  public ThrottleFilter(String aName, long anOverallHorizon) {\r
+    super(aName);\r
+\r
+    overallHorizon = anOverallHorizon;\r
+    throttleManager = new ThrottleManager(overallHorizon);\r
+  }\r
+\r
+  /** * {@inheritDoc} */\r
+  public boolean validate(String anExpression) {\r
+    List parts = StringRoutines.splitString(anExpression.trim(), ":");\r
+\r
+    try {\r
+      if (parts.size()==2) {\r
+        Integer.parseInt((String) parts.get(0));\r
+        Integer.parseInt((String) parts.get(1));\r
+\r
+        return true;\r
+      }\r
+    }\r
+    catch (Throwable t) {\r
+    }\r
+\r
+    return false;\r
+  }\r
+\r
+  /**\r
+   *\r
+   */\r
+  public boolean test(String anExpression, Entity anEntity, Request aRequest) {\r
+    String ip = aRequest.getHeader("ip");\r
+    int limit;\r
+    long period;\r
+\r
+    List parts = StringRoutines.splitString(anExpression, ":");\r
+\r
+    try {\r
+      period = Integer.parseInt((String) parts.get(0))*1000*60;\r
+      limit = Integer.parseInt((String) parts.get(1));\r
+    }\r
+    catch (Throwable t) {\r
+      return false;\r
+    }\r
+\r
+    return throttleManager.addMessage(ip, anEntity, limit, period);\r
+  };\r
+\r
+  private class ThrottleManager {\r
+    private Map throttles;\r
+    private long overallHorizon;\r
+    private Thread cleanUpThread;\r
+\r
+    public ThrottleManager(long anOverallHorizon) {\r
+      throttles = new HashMap();\r
+      overallHorizon = anOverallHorizon;\r
+\r
+      cleanUpThread = new Thread() {\r
+        public void run() {\r
+          while (true) {\r
+            synchronized(throttles) {\r
+              List toDelete = new ArrayList();\r
+              Iterator i = throttles.entrySet().iterator();\r
+\r
+              while (i.hasNext()) {\r
+                Map.Entry entry = (Map.Entry) i.next();\r
+                try {\r
+                  if (((Throttle) entry.getValue()).flush()) {\r
+                    toDelete.add(entry.getKey());\r
+                  }\r
+                }\r
+                catch (Throwable t) {\r
+                  toDelete.add(entry.getKey());\r
+                }\r
+              }\r
+\r
+              i = toDelete.iterator();\r
+              while (i.hasNext()) {\r
+                throttles.remove(i.next());\r
+              }\r
+            }\r
+            try {\r
+              Thread.sleep(60*10*1000);\r
+            }\r
+            catch (InterruptedException e) {\r
+              break;\r
+            }\r
+          }\r
+        }\r
+      };\r
+\r
+      cleanUpThread.setDaemon(true);\r
+      cleanUpThread.start();\r
+    }\r
+\r
+    public boolean addMessage(String anIP, Entity anEntity, int aLimit, long aPeriod) {\r
+      synchronized (throttles) {\r
+        Throttle throttle = (Throttle) throttles.get(anIP);\r
+\r
+        if (throttle==null) {\r
+          throttle = new Throttle(overallHorizon);\r
+          throttles.put(anIP, throttle);\r
+        }\r
+        return throttle.addMessage(anEntity, aLimit, aPeriod);\r
+      }\r
+    }\r
+\r
+    private class Throttle {\r
+      private List messages;\r
+      private long horizon;\r
+\r
+      public Throttle(long aHorizon) {\r
+        messages = new ArrayList();\r
+        horizon = aHorizon;\r
+      }\r
+\r
+      public boolean flush() {\r
+        long limit = System.currentTimeMillis() - horizon;\r
+\r
+        while (messages.size()>0 && ((Message) messages.get(0)).getTime()<=limit) {\r
+          messages.remove(0);\r
+        }\r
+\r
+        return messages.size()==0;\r
+      }\r
+\r
+      public boolean addMessage(Entity anEntity, int aLimit, long aPeriod) {\r
+        Message lastMessage=null;\r
+        if (messages.size()>0) {\r
+          lastMessage = (Message) messages.get(messages.size()-1);\r
+        }\r
+\r
+        Message newMessage = new Message(anEntity.getId(),\r
+                anEntity instanceof EntityComment, System.currentTimeMillis());\r
+\r
+        if (!newMessage.equals(lastMessage))\r
+          messages.add(newMessage);\r
+\r
+        if (messages.size()>=aLimit) {\r
+          Message message = (Message) messages.get(messages.size()-aLimit);\r
+          return (System.currentTimeMillis()-message.getTime())<aPeriod;\r
+        }\r
+\r
+        return false;\r
+      }\r
+\r
+      private class Message {\r
+        private String id;\r
+        private boolean isComment;\r
+        private long time;\r
+\r
+        public Message(String anId, boolean anIsComment, long aTime) {\r
+          id = anId;\r
+          isComment = anIsComment;\r
+          time = aTime;\r
+        }\r
+\r
+        public String getId() {\r
+          return id;\r
+        }\r
+\r
+        public boolean getIsComment() {\r
+          return isComment;\r
+        }\r
+\r
+        public long getTime() {\r
+          return time;\r
+        }\r
+\r
+        public int hashCode() {\r
+          return getId().hashCode();\r
+        }\r
+\r
+        public boolean equals(Object anObject) {\r
+          if (anObject instanceof Message) {\r
+            Message that = (Message) anObject;\r
+\r
+            if (that.getId().equals(getId()) && that.getIsComment() == getIsComment()) {\r
+              return true;\r
+            }\r
+          }\r
+\r
+          return false;\r
+        }\r
+      }\r
+    }\r
+  }\r
+}\r