org.parosproxy.paros.model.SiteMap Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of zap Show documentation
Show all versions of zap Show documentation
The Zed Attack Proxy (ZAP) is an easy to use integrated penetration testing tool for finding vulnerabilities in web applications. It is designed to be used by people with a wide range of security experience and as such is ideal for developers and functional testers who are new to penetration testing. ZAP provides automated scanners as well as a set of tools that allow you to find security vulnerabilities manually.
/*
*
* 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/09/19 Handle multipart node name
// ZAP: 2011/12/04 Support deleting alerts
// ZAP: 2012/02/11 Re-ordered icons, added spider icon and notify via SiteMap
// ZAP: 2012/03/03 Moved popups to stdmenus extension
// ZAP: 2012/03/11 Issue 280: Escape URLs in sites tree
// ZAP: 2012/03/15 Changed the methods getQueryParamString and createReference to
// use the class StringBuilder instead of StringBuffer
// ZAP: 2012/07/03 Issue 320: AScan can miss subtrees if invoked via the API
// ZAP: 2012/07/29 Issue 43: Added support for Scope
// ZAP: 2012/08/29 Issue 250 Support for authentication management
// ZAP: 2013/01/29 Handle structural nodes in findNode
// ZAP: 2013/09/26 Issue 656: Content-length: 0 in GET requests
// ZAP: 2014/01/06 Issue 965: Support 'single page' apps and 'non standard' parameter separators
// ZAP: 2014/01/16 Issue 979: Sites and Alerts trees can get corrupted
// ZAP: 2014/03/23 Issue 997: Session.open complains about improper use of addPath
// ZAP: 2014/04/10 Initialise the root SiteNode with a reference to SiteMap
// ZAP: 2014/04/10 Allow to delete history ID to SiteNode map entries
// ZAP: 2014/06/16 Issue 1227: Active scanner sends GET requests with content in request body
// ZAP: 2014/09/22 Issue 1345: Support Attack mode
// ZAP: 2014/11/18 Issue 1408: Extend the structural parameter handling to forms param
// ZAP: 2014/11/27 Issue 1416: Allow spider to be restricted by the number of children
// ZAP: 2014/12/17 Issue 1174: Support a Site filter
// ZAP: 2015/02/09 Issue 1525: Introduce a database interface layer to allow for alternative implementations
// ZAP: 2015/04/02 Issue 1582: Low memory option
// ZAP: 2015/08/19 Change to cope with deprecation of HttpMessage.getParamNameSet(HtmlParameter.Type, String)
// ZAP: 2015/08/19 Issue 1784: NullPointerException when active scanning through the API with a target without scheme
// ZAP: 2015/10/21 Issue 1576: Support data driven content
// ZAP: 2015/11/05 Change findNode(..) methods to match top level nodes
// ZAP: 2015/11/09 Fix NullPointerException when creating a HistoryReference with a request URI without path
// ZAP: 2016/04/21 Issue 2342: Checks non-empty method for deletion of SiteNodes via API
// ZAP: 2016/04/28 Issue 1171: Raise site and node add or remove events
// ZAP: 2016/07/07 Do not add the message to past history if it already belongs to the node
// ZAP: 2017/01/23: Issue 1800: Alpha sort the site tree
package org.parosproxy.paros.model;
import java.awt.EventQueue;
import java.security.InvalidParameterException;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeNode;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.URIException;
import org.apache.log4j.Logger;
import org.parosproxy.paros.Constant;
import org.parosproxy.paros.db.DatabaseException;
import org.parosproxy.paros.network.HtmlParameter;
import org.parosproxy.paros.network.HttpHeader;
import org.parosproxy.paros.network.HttpMalformedHeaderException;
import org.parosproxy.paros.network.HttpMessage;
import org.parosproxy.paros.network.HttpRequestHeader;
import org.parosproxy.paros.network.HttpStatusCode;
import org.parosproxy.paros.view.View;
import org.zaproxy.zap.ZAP;
import org.zaproxy.zap.eventBus.Event;
import org.zaproxy.zap.model.Target;
import org.zaproxy.zap.view.SiteTreeFilter;
public class SiteMap extends SortedTreeModel {
private static final long serialVersionUID = 2311091007687218751L;
private enum EventType {ADD, REMOVE};
private static Map hrefMap = new HashMap<>();
private Model model = null;
private SiteTreeFilter filter = null;
// ZAP: Added log
private static Logger log = Logger.getLogger(SiteMap.class);
public static SiteMap createTree(Model model) {
SiteMap siteMap = new SiteMap(null, model);
SiteNode root = new SiteNode(siteMap, -1, Constant.messages.getString("tab.sites"));
siteMap.setRoot(root);
hrefMap = new HashMap<>();
return siteMap;
}
public SiteMap(SiteNode root, Model model) {
super(root);
this.model = model;
}
/**
* Return the a HttpMessage of the same type under the tree path.
* @param msg
* @return null = not found
*/
public synchronized HttpMessage pollPath(HttpMessage msg) {
SiteNode resultNode = null;
URI uri = msg.getRequestHeader().getURI();
SiteNode parent = (SiteNode) getRoot();
String folder;
try {
String host = getHostName(uri);
// no host yet
parent = findChild(parent, host);
if (parent == null) {
return null;
}
List path = model.getSession().getTreePath(uri);
if (path.size() == 0) {
// Its a top level node
resultNode = parent;
}
for (int i=0; i < path.size(); i++) {
folder = path.get(i);
if (folder != null && !folder.equals("")) {
if (i == path.size()-1) {
String leafName = getLeafName(folder, msg);
resultNode = findChild(parent, leafName);
} else {
parent = findChild(parent, folder);
if (parent == null) {
return null;
}
}
}
}
} catch (URIException e) {
// ZAP: Added error
log.error(e.getMessage(), e);
}
if (resultNode == null || resultNode.getHistoryReference() == null) {
return null;
}
HttpMessage nodeMsg = null;
try {
nodeMsg = resultNode.getHistoryReference().getHttpMessage();
} catch (Exception e) {
// ZAP: Added error
log.error(e.getMessage(), e);
}
return nodeMsg;
}
public SiteNode findNode(HttpMessage msg) {
return this.findNode(msg, false);
}
public synchronized SiteNode findNode(HttpMessage msg, boolean matchStructural) {
if (Constant.isLowMemoryOptionSet()) {
throw new InvalidParameterException("SiteMap should not be accessed when the low memory option is set");
}
if (msg == null || msg.getRequestHeader() == null) {
return null;
}
SiteNode resultNode = null;
URI uri = msg.getRequestHeader().getURI();
SiteNode parent = (SiteNode) getRoot();
String folder = "";
try {
String host = getHostName(uri);
// no host yet
parent = findChild(parent, host);
if (parent == null) {
return null;
}
List path = model.getSession().getTreePath(msg);
if (path.size() == 0) {
// Its a top level node
resultNode = parent;
}
for (int i=0; i < path.size(); i++) {
folder = path.get(i);
if (folder != null && !folder.equals("")) {
if (i == path.size()-1) {
if (matchStructural) {
resultNode = findChild(parent, folder);
} else {
String leafName = getLeafName(folder, msg);
resultNode = findChild(parent, leafName);
}
} else {
parent = findChild(parent, folder);
if (parent == null) {
return null;
}
}
}
}
} catch (URIException e) {
log.error(e.getMessage(), e);
}
return resultNode;
}
public synchronized SiteNode findNode(URI uri) {
// Look for 'structural' nodes first
SiteNode node = this.findNode(uri, null, null);
if (node != null) {
return node;
}
return this.findNode(uri, "GET", null);
}
public synchronized SiteNode findNode(URI uri, String method, String postData) {
if (Constant.isLowMemoryOptionSet()) {
throw new InvalidParameterException("SiteMap should not be accessed when the low memory option is set");
}
SiteNode resultNode = null;
String folder = "";
try {
String host = getHostName(uri);
// no host yet
resultNode = findChild((SiteNode) getRoot(), host);
if (resultNode == null) {
return null;
}
List path = model.getSession().getTreePath(uri);
for (int i=0; i < path.size(); i++) {
folder = path.get(i);
if (folder != null && !folder.equals("")) {
if (i == path.size()-1) {
String leafName = getLeafName(folder, uri, method, postData);
resultNode = findChild(resultNode, leafName);
} else {
resultNode = findChild(resultNode, folder);
if (resultNode == null) {
return null;
}
}
}
}
} catch (URIException e) {
log.error(e.getMessage(), e);
}
return resultNode;
}
/*
* Find the closest parent for the message - no new nodes will be created
*/
public synchronized SiteNode findClosestParent(HttpMessage msg) {
if (msg == null || msg.getRequestHeader() == null) {
return null;
}
return this.findClosestParent(msg.getRequestHeader().getURI());
}
/*
* Find the closest parent for the uri - no new nodes will be created
*/
public synchronized SiteNode findClosestParent(URI uri) {
if (uri == null) {
return null;
}
SiteNode lastParent = null;
SiteNode parent = (SiteNode) getRoot();
String folder = "";
try {
String host = getHostName(uri);
// no host yet
parent = findChild(parent, host);
if (parent == null) {
return null;
}
lastParent = parent;
List path = model.getSession().getTreePath(uri);
for (int i=0; i < path.size(); i++) {
folder = path.get(i);
if (folder != null && !folder.equals("")) {
if (i == path.size()-1) {
lastParent = parent;
} else {
parent = findChild(parent, folder);
if (parent == null) {
break;
}
lastParent = parent;
}
}
}
} catch (URIException e) {
log.error(e.getMessage(), e);
}
return lastParent;
}
/**
* Add the HistoryReference into the SiteMap.
* This method will rely on reading message from the History table.
* Note that this method must only be called on the EventDispatchThread
* @param ref
*/
public synchronized SiteNode addPath(HistoryReference ref) {
if (Constant.isLowMemoryOptionSet()) {
throw new InvalidParameterException("SiteMap should not be accessed when the low memory option is set");
}
HttpMessage msg = null;
try {
msg = ref.getHttpMessage();
} catch (Exception e) {
// ZAP: Added error
log.error(e.getMessage(), e);
return null;
}
return addPath(ref, msg);
}
/**
* Add the HistoryReference with the corresponding HttpMessage into the SiteMap.
* This method saves the msg to be read from the reference table. Use
* this method if the HttpMessage is known.
* Note that this method must only be called on the EventDispatchThread
* @param msg
* @return
*/
public SiteNode addPath(HistoryReference ref, HttpMessage msg) {
if (Constant.isLowMemoryOptionSet()) {
throw new InvalidParameterException("SiteMap should not be accessed when the low memory option is set");
}
if (View.isInitialised() && Constant.isDevBuild() && ! EventQueue.isDispatchThread()) {
// In developer mode log an error if we're not on the EDT
// Adding to the site tree on GUI ('initial') threads causes problems
log.error("SiteMap.addPath not on EDT " + Thread.currentThread().getName(), new Exception());
}
URI uri = msg.getRequestHeader().getURI();
log.debug("addPath " + uri.toString());
SiteNode parent = (SiteNode) getRoot();
SiteNode leaf = null;
String folder = "";
try {
String host = getHostName(uri);
// add host
parent = findAndAddChild(parent, host, ref, msg);
List path = model.getSession().getTreePath(msg);
for (int i=0; i < path.size(); i++) {
folder = path.get(i);
if (folder != null && !folder.equals("")) {
if (i == path.size()-1) {
leaf = findAndAddLeaf(parent, folder, ref, msg);
ref.setSiteNode(leaf);
} else {
parent = findAndAddChild(parent, folder, ref, msg);
}
}
}
if (leaf == null) {
// No leaf found, which means the parent was really the leaf
// The parent will have been added with a 'blank' href, so replace it with the real one
parent.setHistoryReference(ref);
leaf = parent;
}
} catch (Exception e) {
// ZAP: Added error
log.error("Exception adding " + uri.toString() + " " + e.getMessage(), e);
}
if (hrefMap.get(ref.getHistoryId()) == null) {
hrefMap.put(ref.getHistoryId(), leaf);
}
return leaf;
}
private SiteNode findAndAddChild(SiteNode parent, String nodeName, HistoryReference baseRef, HttpMessage baseMsg) throws URIException, HttpMalformedHeaderException, NullPointerException, DatabaseException {
log.debug("findAndAddChild " + parent.getNodeName() + " / " + nodeName);
SiteNode result = findChild(parent, nodeName);
if (result == null) {
SiteNode newNode =null;
if(!baseRef.getCustomIcons().isEmpty()) {
newNode = new SiteNode(this, baseRef.getHistoryType(), nodeName);
newNode.setCustomIcons(baseRef.getCustomIcons(), baseRef.getClearIfManual());
} else {
newNode = new SiteNode(this, baseRef.getHistoryType(), nodeName);
}
int pos = parent.getChildCount();
for (int i=0; i< parent.getChildCount(); i++) {
if (((SiteNode)parent.getChildAt(i)).isParentOf(nodeName)) {
pos = i;
break;
}
}
insertNodeInto(newNode, parent, pos);
result = newNode;
result.setHistoryReference(createReference(result, baseRef, baseMsg));
// Check if its in or out of scope - has to be done after the node is entered into the tree
newNode.setIncludedInScope(model.getSession().isIncludedInScope(newNode), true);
newNode.setExcludedFromScope(model.getSession().isExcludedFromScope(newNode), true);
hrefMap.put(result.getHistoryReference().getHistoryId(), result);
applyFilter(newNode);
handleEvent(parent, result, EventType.ADD);
}
// ZAP: Cope with getSiteNode() returning null
if (baseRef.getSiteNode() == null) {
baseRef.setSiteNode(result);
}
return result;
}
private SiteNode findChild(SiteNode parent, String nodeName) {
// ZAP: Added debug
log.debug("findChild " + parent.getNodeName() + " / " + nodeName);
for (int i=0; i map) {
TreeSet set = new TreeSet<>();
for (Entry entry : map.entrySet()) {
set.add(entry.getKey());
}
return this.getQueryParamString(set);
}
private String getQueryParamString(SortedSet querySet) {
StringBuilder sb = new StringBuilder();
Iterator iterator = querySet.iterator();
for (int i=0; iterator.hasNext(); i++) {
String name = iterator.next();
if (name == null) {
continue;
}
if (i > 0) {
sb.append(',');
}
if (name.length() > 40) {
// Truncate
name = name.substring(0, 40);
}
sb.append(name);
}
String result = "";
if (sb.length()>0) {
result = sb.insert(0, '(').append(')').toString();
}
return result;
}
private HistoryReference createReference(SiteNode node, HistoryReference baseRef, HttpMessage base) throws HttpMalformedHeaderException, DatabaseException, URIException, NullPointerException {
TreeNode[] path = node.getPath();
StringBuilder sb = new StringBuilder();
String nodeName;
String uriPath = baseRef.getURI().getPath();
if (uriPath == null) {
uriPath = "";
}
String [] origPath = uriPath.split("/");
for (int i=1; i i-1) {
log.debug("Replace Data Driven element " + nodeName + " with " + origPath[i-1]);
sb.append(origPath[i-1]);
} else {
log.error("Failed to determine original node name for element " + i +
nodeName + " original request: " + baseRef.getURI().toString());
sb.append(nodeName);
}
} else {
sb.append(nodeName);
}
if (iSorted Tree Example
*/
class SortedTreeModel extends DefaultTreeModel {
private static final long serialVersionUID = 4130060741120936997L;
private Comparator comparator;
public SortedTreeModel(TreeNode node, SiteNodeStringComparator siteNodeStringComparator) {
super(node);
this.comparator = siteNodeStringComparator;
}
public SortedTreeModel(TreeNode node) {
super(node);
this.comparator = new SiteNodeStringComparator();
}
public SortedTreeModel(TreeNode node, boolean asksAllowsChildren, Comparator aComparator) {
super(node, asksAllowsChildren);
this.comparator = aComparator;
}
public void insertNodeInto(SiteNode child, SiteNode parent) {
int index = findIndexFor(child, parent);
super.insertNodeInto(child, parent, index);
}
public void insertNodeInto(SiteNode child, SiteNode parent, int i) {
// The index is useless in this model, so just ignore it.
insertNodeInto(child, parent);
}
private int findIndexFor(SiteNode child, SiteNode parent) {
int childCount = parent.getChildCount();
if (childCount == 0) {
return 0;
}
if (childCount == 1) {
return comparator.compare(child, (SiteNode) parent.getChildAt(0)) <= 0 ? 0 : 1;
}
return findIndexFor(child, parent, 0, childCount - 1);
}
private int findIndexFor(SiteNode child, SiteNode parent, int idx1, int idx2) {
if (idx1 == idx2) {
return comparator.compare(child, (SiteNode) parent.getChildAt(idx1)) <= 0 ? idx1 : idx1 + 1;
}
int half = (idx1 + idx2) / 2;
if (comparator.compare(child, (SiteNode) parent.getChildAt(half)) <= 0) {
return findIndexFor(child, parent, idx1, half);
}
return findIndexFor(child, parent, half + 1, idx2);
}
}
class SiteNodeStringComparator implements Comparator {
public int compare(SiteNode sn1, SiteNode sn2) {
String s1 = sn1.getNodeName();
String s2 = sn2.getNodeName();
return s1.compareToIgnoreCase(s2);
}
}