All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.parosproxy.paros.model.SiteMap Maven / Gradle / Ivy

Go to download

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.

There is a newer version: 2.15.0
Show newest version
/*
 *
 * 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
// ZAP: 2017/06/29: Issue 3714: Added newOnly option to addPath
// ZAP: 2017/07/09: Issue 3727: Sorting of SiteMap should not include HTTP method (verb) in the node's name
// ZAP: 2017/11/22 Expose method to create temporary nodes (Issue 4065).

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 the HttpMessage
     * @return the SiteNode that corresponds to the HttpMessage
     */
    public SiteNode addPath(HistoryReference ref, HttpMessage msg) {
        return this.addPath(ref, msg, false);
    }

    /**
     * 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 the HttpMessage
     * @param newOnly   Only return a SiteNode if one was newly created
     * @return the SiteNode that corresponds to the HttpMessage, or null if newOnly and the node already exists
     * @since 2.7.0
     */
    public SiteNode addPath(HistoryReference ref, HttpMessage msg, boolean newOnly) {
    	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 = "";
        boolean isNew = false;
        
        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 (newOnly) {
                        // Check to see if it already exists
                        String leafName = getLeafName(folder, msg);
                        isNew = (findChild(parent, leafName) == null);
                    }
                    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);
        }

        if (! newOnly || isNew) {
            return leaf;
        }
        return null;
    }
    
    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;
    }
    
    public 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.getName();
		String s2 = sn2.getName();
		int initialComparison = s1.compareToIgnoreCase(s2);

		if (initialComparison == 0) {
			s1 = sn1.getNodeName();
			s2 = sn2.getNodeName();
			
			return s1.compareToIgnoreCase(s2);
		}
		return initialComparison;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy