2 * Copyright (C) 2001, 2002 The Mir-coders group
4 * This file is part of Mir.
6 * Mir is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * Mir is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with Mir; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 * In addition, as a special exception, The Mir-coders gives permission to link
21 * the code of this program with any library licensed under the Apache Software License,
22 * The Sun (tm) Java Advanced Imaging library (JAI), The Sun JIMI library
23 * (or with modified versions of the above that use the same license as the above),
24 * and distribute linked combinations including the two. You must obey the
25 * GNU General Public License in all respects for all of the code used other than
26 * the above mentioned libraries. If you modify this file, you may extend this
27 * exception to your version of the file, but you are not obligated to do so.
28 * If you do not wish to do so, delete this exception statement from your version.
31 package mircoders.global;
33 import java.io.BufferedOutputStream;
35 import java.io.FileNotFoundException;
36 import java.io.FileOutputStream;
37 import java.util.Arrays;
38 import java.util.Date;
39 import java.util.GregorianCalendar;
40 import java.util.HashMap;
41 import java.util.Iterator;
42 import java.util.List;
44 import java.util.Random;
45 import java.util.Vector;
47 import javax.servlet.http.Cookie;
48 import javax.servlet.http.HttpServletResponse;
50 import mir.config.MirPropertiesConfiguration;
51 import mir.entity.Entity;
52 import mir.log.LoggerWrapper;
53 import mir.session.Request;
54 import mir.util.DateTimeFunctions;
55 import mir.util.GeneratorFormatAdapters;
56 import mir.util.StringRoutines;
57 import mircoders.entity.EntityComment;
58 import mircoders.entity.EntityContent;
59 import mircoders.localizer.MirAdminInterfaceLocalizer;
60 import mircoders.localizer.MirAntiAbuseFilterType;
62 import org.apache.commons.collections.ExtendedProperties;
66 private List filterRules;
67 private Map filterTypes;
68 private List filterTypeIds;
69 private int maxIdentifier;
70 private LoggerWrapper logger;
72 private boolean logEnabled;
73 private boolean openPostingDisabled;
74 private boolean openPostingPassword;
75 private boolean cookieOnBlock;
76 private String articleBlockAction;
77 private String commentBlockAction;
79 private String configFile = MirGlobal.config().getStringWithHome("Abuse.Config");
81 private MirPropertiesConfiguration configuration;
83 private static String cookieName = MirGlobal.config().getString("Abuse.CookieName");
84 private static int cookieMaxAge = 60 * 60 * MirGlobal.config().getInt("Abuse.CookieMaxAge");
87 logger = new LoggerWrapper("Global.Abuse");
88 filterRules = new Vector();
93 configuration = MirPropertiesConfiguration.instance();
96 throw new RuntimeException("Can't get configuration: " + e.getMessage());
101 articleBlockAction = "";
102 commentBlockAction = "";
103 openPostingPassword = false;
104 openPostingDisabled = false;
105 cookieOnBlock = false;
108 filterTypes = new HashMap();
109 filterTypeIds = new Vector();
111 Iterator i = MirGlobal.localizer().openPostings().getAntiAbuseFilterTypes().iterator();
113 while (i.hasNext()) {
114 MirAntiAbuseFilterType filterType = (MirAntiAbuseFilterType) i.next();
115 filterTypes.put(filterType.getName(), filterType);
116 filterTypeIds.add(filterType.getName());
119 catch (Throwable t) {
120 throw new RuntimeException("Can't get filter types: " + t.getMessage());
126 private void setCookie(HttpServletResponse aResponse) {
127 Random random = new Random();
129 Cookie cookie = new Cookie(cookieName, Integer.toString(random.nextInt(1000000000)));
130 cookie.setMaxAge(cookieMaxAge);
133 if (aResponse != null)
134 aResponse.addCookie(cookie);
137 private boolean checkCookie(List aCookies) {
138 if (getCookieOnBlock()) {
139 Iterator i = aCookies.iterator();
141 while (i.hasNext()) {
142 Cookie cookie = (Cookie) i.next();
144 if (cookie.getName().equals(cookieName)) {
145 logger.debug("cookie match");
154 FilterRule findMatchingFilter(Entity anEntity, Request aRequest) {
155 Iterator iterator = filterRules.iterator();
157 while (iterator.hasNext()) {
158 FilterRule rule = (FilterRule) iterator.next();
160 if (rule.test(anEntity, aRequest))
167 public void checkComment(EntityComment aComment, Request aRequest, HttpServletResponse aResponse) {
169 long time = System.currentTimeMillis();
171 FilterRule filterRule = findMatchingFilter(aComment, aRequest);
173 if (filterRule != null) {
174 logger.debug("Match for " + filterRule.getType() + " rule '" + filterRule.getExpression() + "'");
175 filterRule.setLastHit(new GregorianCalendar().getTime());
177 StringBuffer line = new StringBuffer();
179 line.append(DateTimeFunctions.advancedDateFormat(
180 configuration.getString("Mir.DefaultDateTimeFormat"),
181 (new GregorianCalendar()).getTime(), configuration.getString("Mir.DefaultTimezone")));
184 line.append("filter");
187 line.append(filterRule.getType() +" ("+ filterRule.getExpression()+")");
188 aComment.appendLineToField("comment", line.toString());
190 MirGlobal.performCommentOperation(null, aComment, filterRule.getCommentAction());
191 setCookie(aResponse);
193 logComment(aComment, aRequest, filterRule.getType(), filterRule.getExpression());
196 logComment(aComment, aRequest);
199 logger.info("checkComment: " + (System.currentTimeMillis() - time) + "ms");
201 catch (Throwable t) {
202 t.printStackTrace(logger.asPrintWriter(LoggerWrapper.DEBUG_MESSAGE));
203 logger.error("Abuse.checkComment: " + t.toString());
207 public void checkArticle(EntityContent anArticle, Request aRequest, HttpServletResponse aResponse) {
209 long time = System.currentTimeMillis();
211 FilterRule filterRule = findMatchingFilter(anArticle, aRequest);
213 if (filterRule != null) {
214 logger.debug("Match for " + filterRule.getType() + " rule '" + filterRule.getExpression() + "'");
215 filterRule.setLastHit(new GregorianCalendar().getTime());
217 StringBuffer line = new StringBuffer();
219 line.append(DateTimeFunctions.advancedDateFormat(
220 configuration.getString("Mir.DefaultDateTimeFormat"),
221 (new GregorianCalendar()).getTime(), configuration.getString("Mir.DefaultTimezone")));
224 line.append("filter");
227 line.append(filterRule.getType() +" ("+ filterRule.getExpression()+")");
228 anArticle.appendLineToField("comment", line.toString());
230 MirGlobal.performArticleOperation(null, anArticle, filterRule.getArticleAction());
231 setCookie(aResponse);
233 logArticle(anArticle, aRequest, filterRule.getType(), filterRule.getExpression());
236 logArticle(anArticle, aRequest);
238 logger.info("checkArticle: " + (System.currentTimeMillis() - time) + "ms");
240 catch (Throwable t) {
241 t.printStackTrace(logger.asPrintWriter(LoggerWrapper.DEBUG_MESSAGE));
242 logger.error("Abuse.checkArticle: " + t.toString());
246 public boolean getLogEnabled() {
250 public void setLogEnabled(boolean anEnabled) {
251 if (!configuration.getString("Abuse.DisallowIPLogging", "0").equals("1"))
252 logEnabled = anEnabled;
256 public int getLogSize() {
260 public void setLogSize(int aSize) {
265 public boolean getOpenPostingDisabled() {
266 return openPostingDisabled;
269 public void setOpenPostingDisabled(boolean anOpenPostingDisabled) {
270 openPostingDisabled = anOpenPostingDisabled;
273 public boolean getOpenPostingPassword() {
274 return openPostingPassword;
277 public void setOpenPostingPassword(boolean anOpenPostingPassword) {
278 openPostingPassword = anOpenPostingPassword;
281 public boolean getCookieOnBlock() {
282 return cookieOnBlock;
285 public void setCookieOnBlock(boolean aCookieOnBlock) {
286 cookieOnBlock = aCookieOnBlock;
289 public String getArticleBlockAction() {
290 return articleBlockAction;
293 public void setArticleBlockAction(String anAction) {
294 articleBlockAction = anAction;
297 public String getCommentBlockAction() {
298 return commentBlockAction;
301 public void setCommentBlockAction(String anAction) {
302 commentBlockAction = anAction;
305 public List getLog() {
308 List result = new Vector();
310 Iterator i = log.iterator();
311 while (i.hasNext()) {
312 LogEntry logEntry = (LogEntry) i.next();
313 Map entry = new HashMap();
315 entry.put("ip", logEntry.getIpNumber());
316 entry.put("id", logEntry.getId());
317 entry.put("timestamp", new GeneratorFormatAdapters.DateFormatAdapter(logEntry.getTimeStamp(), MirPropertiesConfiguration.instance().getString("Mir.DefaultTimezone")));
318 if (logEntry.getIsArticle())
319 entry.put("type", "content");
321 entry.put("type", "comment");
322 entry.put("browser", logEntry.getBrowserString());
323 entry.put("hitfiltertype", logEntry.getHitFilterType());
324 entry.put("hitfilterexpression", logEntry.getHitFilterExpression());
331 catch (Throwable t) {
332 throw new RuntimeException(t.toString());
337 public void logComment(Entity aComment, Request aRequest) {
338 logComment(aComment, aRequest, null, null);
341 public void logComment(Entity aComment, Request aRequest, String aHitFilterType, String aHitFilterExpression) {
342 String ipAddress = aRequest.getHeader("ip");
343 String id = aComment.getId();
344 String browser = aRequest.getHeader("User-Agent");
346 logComment(ipAddress, id, new Date(), browser, aHitFilterType, aHitFilterExpression);
349 public void logArticle(Entity anArticle, Request aRequest) {
350 logArticle(anArticle, aRequest, null, null);
353 public void logArticle(Entity anArticle, Request aRequest, String aHitFilterType, String aHitFilterExpression) {
354 String ipAddress = aRequest.getHeader("ip");
355 String id = anArticle.getId();
356 String browser = aRequest.getHeader("User-Agent");
358 logArticle(ipAddress, id, new Date(), browser, aHitFilterType, aHitFilterExpression);
361 public void logComment(String anIp, String anId, Date aTimeStamp, String aBrowser, String aHitFilterType, String aHitFilterExpression) {
362 appendLog(new LogEntry(aTimeStamp, anIp, aBrowser, anId, false, aHitFilterType, aHitFilterExpression));
365 public void logArticle(String anIp, String anId, Date aTimeStamp, String aBrowser, String aHitFilterType, String aHitFilterExpression) {
366 appendLog(new LogEntry(aTimeStamp, anIp, aBrowser, anId, true, aHitFilterType, aHitFilterExpression));
370 synchronized (filterRules) {
372 ExtendedProperties configuration = new ExtendedProperties();
375 configuration = new ExtendedProperties(configFile);
377 catch (FileNotFoundException e) {
380 getFilterConfig(filterRules, "abuse.filter", configuration);
382 setOpenPostingDisabled(configuration.getString("abuse.openPostingDisabled", "0").equals("1"));
383 setOpenPostingPassword(configuration.getString("abuse.openPostingPassword", "0").equals("1"));
384 setCookieOnBlock(configuration.getString("abuse.cookieOnBlock", "0").equals("1"));
385 setLogEnabled(configuration.getString("abuse.logEnabled", "0").equals("1"));
386 setLogSize(configuration.getInt("abuse.logSize", 10));
387 setArticleBlockAction(configuration.getString("abuse.articleBlockAction", ""));
388 setCommentBlockAction(configuration.getString("abuse.commentBlockAction", ""));
390 catch (Throwable t) {
391 throw new RuntimeException(t.toString());
397 synchronized (filterRules) {
399 ExtendedProperties configuration = new ExtendedProperties();
401 setFilterConfig(filterRules, "abuse.filter", configuration);
403 configuration.addProperty("abuse.openPostingDisabled", getOpenPostingDisabled() ? "1" : "0");
404 configuration.addProperty("abuse.openPostingPassword", getOpenPostingPassword() ? "1" : "0");
405 configuration.addProperty("abuse.cookieOnBlock", getCookieOnBlock() ? "1" : "0");
406 configuration.addProperty("abuse.logEnabled", getLogEnabled() ? "1" : "0");
407 configuration.addProperty("abuse.logSize", Integer.toString(getLogSize()));
408 configuration.addProperty("abuse.articleBlockAction", getArticleBlockAction());
409 configuration.addProperty("abuse.commentBlockAction", getCommentBlockAction());
411 configuration.save(new BufferedOutputStream(new FileOutputStream(new File(configFile)),8192), "Anti abuse configuration");
413 catch (Throwable t) {
414 throw new RuntimeException(t.toString());
419 public List getFilterTypes() {
421 List result = new Vector();
423 Iterator i = filterTypeIds.iterator();
424 while (i.hasNext()) {
425 String id = (String) i.next();
427 Map action = new HashMap();
428 action.put("resource", id);
429 action.put("identifier", id);
436 catch (Throwable t) {
437 throw new RuntimeException("can't get article actions");
441 public List getArticleActions() {
443 List result = new Vector();
445 Iterator i = MirGlobal.localizer().adminInterface().simpleArticleOperations().iterator();
446 while (i.hasNext()) {
447 MirAdminInterfaceLocalizer.MirSimpleEntityOperation operation =
448 (MirAdminInterfaceLocalizer.MirSimpleEntityOperation) i.next();
450 Map action = new HashMap();
451 action.put("resource", operation.getName());
452 action.put("identifier", operation.getName());
459 catch (Throwable t) {
460 throw new RuntimeException("can't get article actions");
464 public List getCommentActions() {
466 List result = new Vector();
468 Iterator i = MirGlobal.localizer().adminInterface().simpleCommentOperations().iterator();
469 while (i.hasNext()) {
470 MirAdminInterfaceLocalizer.MirSimpleEntityOperation operation =
471 (MirAdminInterfaceLocalizer.MirSimpleEntityOperation) i.next();
473 Map action = new HashMap();
474 action.put("resource", operation.getName());
475 action.put("identifier", operation.getName());
482 catch (Throwable t) {
483 throw new RuntimeException("can't get comment actions");
487 public List getFilters() {
488 List result = new Vector();
490 synchronized (filterRules) {
491 Iterator i = filterRules.iterator();
492 while (i.hasNext()) {
493 FilterRule filter = (FilterRule) i.next();
494 result.add(filter.clone());
500 public String addFilter(String aType, String anExpression, String aComments, String aCommentAction, String anArticleAction) {
501 return addFilter(aType, anExpression, aComments, aCommentAction, anArticleAction, null);
504 public String addFilter(String aType, String anExpression, String aComments, String aCommentAction, String anArticleAction, Date aListHit) {
505 return addFilter(filterRules, aType, anExpression, aComments, aCommentAction, anArticleAction, aListHit);
508 public FilterRule getFilter(String anId) {
509 synchronized (filterRules) {
510 FilterRule result = (FilterRule) findFilter(filterRules, anId);
514 return (FilterRule) result.clone();
518 public String setFilter(String anIdentifier, String aType, String anExpression, String aComments, String aCommentAction, String anArticleAction) {
519 return setFilter(filterRules, anIdentifier, aType, anExpression, aComments, aCommentAction, anArticleAction);
522 public void deleteFilter(String anIdentifier) {
523 deleteFilter(filterRules, anIdentifier);
526 public void moveFilterUp(String anIdentifier) {
527 moveFilter(filterRules, anIdentifier, -1);
530 public void moveFilterDown(String anIdentifier) {
531 moveFilter(filterRules, anIdentifier, 1);
534 private String addFilter(List aFilters, String aType, String anExpression, String aComments, String aCommentAction, String anArticleAction, Date aLastHit) {
535 MirAntiAbuseFilterType type = (MirAntiAbuseFilterType) filterTypes.get(aType);
538 return "invalidtype";
540 if (!type.validate(anExpression)) {
541 return "invalidexpression";
544 FilterRule filter = new FilterRule();
546 filter.setId(generateId());
547 filter.setExpression(anExpression);
548 filter.setType(aType);
549 filter.setComments(aComments);
550 filter.setArticleAction(anArticleAction);
551 filter.setCommentAction(aCommentAction);
552 filter.setLastHit(aLastHit);
554 synchronized (aFilters) {
555 aFilters.add(filter);
561 private String setFilter(List aFilters, String anIdentifier, String aType, String anExpression, String aComments, String aCommentAction, String anArticleAction) {
562 MirAntiAbuseFilterType type = (MirAntiAbuseFilterType) filterTypes.get(aType);
565 return "invalidtype";
567 if (!type.validate(anExpression)) {
568 return "invalidexpression";
571 synchronized (aFilters) {
572 FilterRule filter = findFilter(aFilters, anIdentifier);
574 if (filter != null) {
575 filter.setExpression(anExpression);
576 filter.setType(aType);
577 filter.setCommentAction(aCommentAction);
578 filter.setArticleAction(anArticleAction);
579 filter.setComments(aComments);
586 private FilterRule findFilter(List aFilters, String anIdentifier) {
587 synchronized (aFilters) {
588 Iterator i = aFilters.iterator();
589 while (i.hasNext()) {
590 FilterRule filter = (FilterRule) i.next();
592 if (filter.getId().equals(anIdentifier)) {
601 private void moveFilter(List aFilters, String anIdentifier, int aDirection) {
602 synchronized (aFilters) {
603 for (int i = 0; i < aFilters.size(); i++) {
604 FilterRule rule = (FilterRule) aFilters.get(i);
606 if (rule.getId().equals(anIdentifier) && (i + aDirection >= 0) && (i + aDirection < aFilters.size())) {
607 aFilters.remove(rule);
608 aFilters.add(i + aDirection, rule);
615 private void deleteFilter(List aFilters, String anIdentifier) {
616 synchronized (aFilters) {
617 FilterRule filter = findFilter(aFilters, anIdentifier);
619 if (filter != null) {
620 aFilters.remove(filter);
625 private String generateId() {
626 synchronized (this) {
627 maxIdentifier = maxIdentifier + 1;
629 return Integer.toString(maxIdentifier);
633 public class FilterRule {
634 private String identifier;
635 private String expression;
637 private String comments;
638 private String articleAction;
639 private String commentAction;
640 private Date lastHit;
642 public FilterRule() {
647 articleAction = articleBlockAction;
648 commentAction = commentBlockAction;
652 public Date getLastHit() {
656 public void setLastHit(Date aDate) {
660 public String getId() {
664 public void setId(String anId) {
668 public String getExpression() {
672 public void setExpression(String anExpression) {
673 expression = anExpression;
676 public String getType() {
680 public void setType(String aType) {
684 public void setComments(String aComments) {
685 comments = aComments;
688 public String getComments() {
692 public String getArticleAction() {
693 return articleAction;
696 public void setArticleAction(String anArticleAction) {
697 articleAction = anArticleAction;
700 public String getCommentAction() {
701 return commentAction;
704 public void setCommentAction(String aCommentAction) {
705 commentAction = aCommentAction;
708 public boolean test(Entity anEntity, Request aRequest) {
709 MirAntiAbuseFilterType filterType = (MirAntiAbuseFilterType) filterTypes.get(type);
711 if (filterType != null)
712 return filterType.test(expression, anEntity, aRequest);
714 catch (Throwable t) {
715 logger.error("error while testing " + type + "-filter '" + expression + "'");
721 public Object clone() {
722 FilterRule result = new FilterRule();
723 result.setComments(getComments());
724 result.setExpression(getExpression());
725 result.setId(getId());
726 result.setType(getType());
727 result.setArticleAction(getArticleAction());
728 result.setCommentAction(getCommentAction());
729 result.setLastHit(getLastHit());
735 private String escapeFilterPart(String aFilterPart) {
736 return StringRoutines.replaceStringCharacters(aFilterPart,
737 new char[] {'\\', ':', '\n', '\r', '\t', ' '},
738 new String[] {"\\\\", "\\:", "\\n", "\\r", "\\t", "\\ "});
741 private String deescapeFilterPart(String aFilterPart) {
742 return StringRoutines.replaceEscapedStringCharacters(aFilterPart,
744 new char[] {'\\', ':', 'n', 'r', 't', ' '},
745 new String[] {"\\", ":", "\n", "\r", "\t", " "});
748 private void setFilterConfig(List aFilters, String aConfigKey, ExtendedProperties aConfiguration) {
749 synchronized (aFilters) {
750 Iterator i = aFilters.iterator();
752 while (i.hasNext()) {
753 FilterRule filter = (FilterRule) i.next();
755 String filterconfig =
756 escapeFilterPart(filter.getType()) + ":" +
757 escapeFilterPart(filter.getExpression()) + ":" +
758 escapeFilterPart(filter.getArticleAction()) + ":" +
759 escapeFilterPart(filter.getCommentAction()) + ":" +
760 escapeFilterPart(filter.getComments()) + ":";
762 if (filter.getLastHit() != null)
763 filterconfig = filterconfig + filter.getLastHit().getTime();
765 aConfiguration.addProperty(aConfigKey, filterconfig);
770 private void getFilterConfig(List aFilters, String aConfigKey, ExtendedProperties aConfiguration) {
771 synchronized (aFilters) {
774 if (aConfiguration.getStringArray(aConfigKey) != null) {
776 Iterator i = Arrays.asList(aConfiguration.getStringArray(aConfigKey)).
779 while (i.hasNext()) {
780 String filter = (String) i.next();
781 List parts = StringRoutines.splitStringWithEscape(filter, ':', '\\');
782 if (parts.size() == 2) {
783 parts.add(articleBlockAction);
784 parts.add(commentBlockAction);
789 if (parts.size() >= 5) {
792 if (parts.size() >= 6) {
793 String lastHitString = (String) parts.get(5);
796 lastHit = new Date(Long.parseLong(lastHitString));
798 catch (Throwable t) {
802 addFilter(deescapeFilterPart( (String) parts.get(0)),
803 deescapeFilterPart( (String) parts.get(1)),
804 deescapeFilterPart( (String) parts.get(4)),
805 deescapeFilterPart( (String) parts.get(3)),
806 deescapeFilterPart( (String) parts.get(2)), lastHit);
813 private static class LogEntry {
814 private String ipNumber;
815 private String browserString;
817 private Date timeStamp;
818 private boolean isArticle;
819 private String hitFilterType;
820 private String hitFilterExpression;
822 public LogEntry(Date aTimeStamp, String anIpNumber, String aBrowserString, String anId, boolean anIsArticle, String aHitFilterType, String aHitFilterExpression) {
823 ipNumber = anIpNumber;
824 browserString = aBrowserString;
826 isArticle = anIsArticle;
827 timeStamp = aTimeStamp;
828 hitFilterType = aHitFilterType;
829 hitFilterExpression = aHitFilterExpression;
832 public LogEntry(Date aTimeStamp, String anIpNumber, String aBrowserString, String anId, boolean anIsArticle) {
833 this(aTimeStamp, anIpNumber, aBrowserString, anId, anIsArticle, null, null);
836 public String getIpNumber() {
840 public String getBrowserString() {
841 return browserString;
844 public String getId() {
848 public String getHitFilterType() {
849 return hitFilterType;
852 public String getHitFilterExpression() {
853 return hitFilterExpression;
856 public Date getTimeStamp() {
860 public boolean getIsArticle() {
865 private void truncateLog() {
870 while (log.size() > 0 && log.size() > logSize) {
871 log.remove(log.size()-1);
877 private void appendLog(LogEntry anEntry) {