/*
*
* Paros and its related class files.
*
* Paros is an HTTP/HTTPS proxy for assessing web application security.
* Copyright (C) 2003-2004 Chinotec Technologies Company
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the Clarified Artistic License
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Clarified Artistic License for more details.
*
* You should have received a copy of the Clarified Artistic License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
// ZAP: 2011/11/20 Set order and name
// ZAP: 2012/03/15 Added the method getProxyListenerOrder. Set the name of the
// extension filter thread.
// ZAP: 2012/03/17 Issue 282 Added getAuthor()
// ZAP: 2012/04/25 Added type argument to generic type, removed unnecessary
// casts and added @Override annotation to all appropriate methods.
// ZAP: 2012/06/25 Added addFilter() plus searchFilterIndex() method, that
// allows to add some custom filter to the FilterFactory (e.g.: by third
// party extensions).
// ZAP: 2012/08/01 Issue 332: added support for Modes
// ZAP: 2013/01/25 Added method removeFilter().
// ZAP: 2013/01/25 Removed the "(non-Javadoc)" comments.
// ZAP: 2013/03/03 Issue 546: Remove all template Javadoc comments
// ZAP: 2013/11/16 Issue 902 - Change all ExtensionAdaptor#hook(ExtensionHook) overriding methods
// to call the base implementation
// ZAP: 2014/01/28 Issue 207: Support keyboard shortcuts
// ZAP: 2014/11/26 Fixed an issue in the implementation of searchFilterIndex.
// ZAP: 2015/03/16 Issue 1525: Further database independence changes
// ZAP: 2016/06/28 Do not start the timer thread if no filter is enabled
package org.parosproxy.paros.extension.filter;
import java.util.List;
import org.apache.log4j.Logger;
import org.parosproxy.paros.Constant;
import org.parosproxy.paros.control.Control;
import org.parosproxy.paros.core.proxy.ProxyListener;
import org.parosproxy.paros.extension.ExtensionAdaptor;
import org.parosproxy.paros.extension.ExtensionHook;
import org.parosproxy.paros.extension.ViewDelegate;
import org.parosproxy.paros.model.Model;
import org.parosproxy.paros.network.HttpMessage;
import org.zaproxy.zap.view.ZapMenuItem;
public class ExtensionFilter extends ExtensionAdaptor implements ProxyListener {
private static final Logger log = Logger.getLogger(ExtensionFilter.class);
public static final String NAME = "ExtensionFilter";
public static final int PROXY_LISTENER_ORDER = 0;
private ZapMenuItem menuToolsFilter = null;
private FilterFactory filterFactory = new FilterFactory();
private TimerFilterThread timerFilterThread;
public ExtensionFilter() {
super();
this.setOrder(8);
}
@Override
public void init() {
this.setName(NAME);
filterFactory.loadAllFilter();
}
@Override
public void initModel(Model model) {
// ZAP: changed to init(Model)
super.initModel(model);
Filter filter = null;
// ZAP: Added type argument.
List<Filter> filters = filterFactory.getAllFilter();
for (int i=0; i<filters.size(); i++) {
// ZAP: Removed unnecessary cast.
filter = filters.get(i);
try {
filter.init(model);
} catch (Exception ignore) {
log.warn("Error initializing filter. Continuing.", ignore);
}
}
}
@Override
public void initView(ViewDelegate view) {
super.initView(view);
Filter filter = null;
for (int i=0; i<filterFactory.getAllFilter().size(); i++) {
// ZAP: Removed unnecessary cast.
filter = filterFactory.getAllFilter().get(i);
try {
filter.initView(view);
} catch (Exception ignore) {
log.warn("Error initializing view for filter. Continuing.", ignore);
}
}
}
/**
* This method initializes menuToolsFilter
*
* @return javax.swing.JMenuItem
*/
private ZapMenuItem getMenuToolsFilter() {
if (menuToolsFilter == null) {
menuToolsFilter = new ZapMenuItem("menu.tools.filter");
menuToolsFilter.addActionListener(new java.awt.event.ActionListener() {
@Override
public void actionPerformed(java.awt.event.ActionEvent e) {
FilterDialog dialog = new FilterDialog(getView().getMainFrame());
dialog.setAllFilters(filterFactory.getAllFilter());
dialog.showDialog(false);
boolean startThread = false;
for (Filter filter : filterFactory.getAllFilter()) {
if (filter.isEnabled()) {
startThread = true;
break;
}
}
if (startThread) {
if (timerFilterThread == null) {
timerFilterThread = new TimerFilterThread(filterFactory.getAllFilter());
timerFilterThread.start();
}
} else if (timerFilterThread != null) {
timerFilterThread.setStopped();
timerFilterThread = null;
}
}
});
}
return menuToolsFilter;
}
@Override
public void hook(ExtensionHook extensionHook) {
super.hook(extensionHook);
if (getView() != null) {
extensionHook.getHookMenu().addToolsMenuItem(getMenuToolsFilter());
}
extensionHook.addProxyListener(this);
}
// ZAP: Added the method.
@Override
public int getArrangeableListenerOrder() {
return PROXY_LISTENER_ORDER;
}
@Override
public boolean onHttpRequestSend(HttpMessage httpMessage) {
Filter filter = null;
// Check mode
switch(Control.getSingleton().getMode()) {
case safe:
// Only safe thing to do is to disable all filters
return true;
case protect:
if (!httpMessage.isInScope()) {
// Target not in scope, so ignore
return true;
}
}
for (int i=0; i<filterFactory.getAllFilter().size(); i++) {
// ZAP: Removed unnecessary cast.
filter = filterFactory.getAllFilter().get(i);
try {
if (filter.isEnabled()) {
filter.onHttpRequestSend(httpMessage);
}
} catch (Exception e) {
// ZAP: Changed to log the exception.
log.error(e.getMessage(), e);
}
}
return true;
}
@Override
public boolean onHttpResponseReceive(HttpMessage httpMessage) {
Filter filter = null;
// Check mode
switch(Control.getSingleton().getMode()) {
case safe:
return true;
case protect:
if (!httpMessage.isInScope()) {
return true;
}
}
for (int i=0; i<filterFactory.getAllFilter().size(); i++) {
// ZAP: Removed unnecessary cast.
filter = filterFactory.getAllFilter().get(i);
try {
if (filter.isEnabled()) {
filter.onHttpResponseReceive(httpMessage);
}
} catch (Exception e) {
// ZAP: Changed to log the exception.
log.error(e.getMessage(), e);
}
}
return true;
}
/**
* Destroy every filter during extension destroy.
*/
@Override
public void destroy() {
if (timerFilterThread != null) {
timerFilterThread.setStopped();
timerFilterThread = null;
}
Filter filter = null;
for (int i=0; i<filterFactory.getAllFilter().size(); i++) {
// ZAP: Removed unnecessary cast.
filter = filterFactory.getAllFilter().get(i);
try {
filter.destroy();
} catch (Exception e) {}
}
}
@Override
public String getAuthor() {
return Constant.PAROS_TEAM;
}
/**
* Allows to add a filter. The {@link Filter#getId()} method is used to
* determine its position in the list, as a TreeMap is used.
*
* @param filter
*/
// ZAP: Added the method.
public void addFilter(Filter filter) {
List<Filter> filters = filterFactory.getAllFilter();
int index = searchFilterIndex(filters, filter.getId(), 0, filters.size());
if (index == -1) {
// not found - put at the end
filters.add(filter);
} else {
filters.add(index, filter);
}
}
public void removeFilter(Filter filter) {
List<Filter> filters = filterFactory.getAllFilter();
filters.remove(filter);
}
/**
* Does a binary search for the given filter id. Used to determine where
* (index) to insert the filter to the filter's list.
*
* @param A
* @param key
* @param imin
* @param imax
* @return
*/
// ZAP: Added the method.
private int searchFilterIndex(List<Filter> filters, int targetId, int min, int max) {
// Basic algorithm taken from Wikipedia:
// http://en.wikipedia.org/wiki/Binary_search_algorithm#Recursive
if (max <= min) {
// set is empty, so return value showing not found
return -1;
}
// calculate midpoint to cut set in half
int mid = (min + max) / 2;
// three-way comparison
int id = filters.get(mid).getId();
if (id > targetId) {
// id is in lower subset
return searchFilterIndex(filters, targetId, min, mid - 1);
} else if (id < targetId) {
// id is in upper subset
return searchFilterIndex(filters, targetId, mid + 1, max);
}
// index has been found
return mid + 1;
}
/**
* No database tables used, so all supported
*/
@Override
public boolean supportsDb(String type) {
return true;
}
private static class TimerFilterThread extends Thread {
private final List<Filter> filters;
private boolean stop;
public TimerFilterThread(List<Filter> filters) {
super("ZAP-ExtensionFilter");
setDaemon(true);
this.filters = filters;
}
@Override
public void run() {
while (!stop) {
try {
Thread.sleep(5000);
} catch (InterruptedException e1) {
}
for (Filter filter : filters) {
try {
if (filter.isEnabled()) {
filter.timer();
}
} catch (Exception e) {
}
}
}
}
public void setStopped() {
this.stop = true;
}
}
}