/* * * 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: 2012/03/03 Moved popups to stdmenus extension // ZAP: 2012/03/15 Changed to initiate the tree with a default model. Changed to // clear the http panels when the root node is selected. // ZAP: 2012/04/23 Added @Override annotation to all appropriate methods. // ZAP: 2012/06/13 Added custom tree cell renderer to treeSite in getTreeSite(). // ZAP: 2013/01/25 Added method for removing listener. // ZAP: 2013/11/16 Issue 886: Main pop up menu invoked twice on some components // ZAP: 2014/01/28 Issue 207: Support keyboard shortcuts // ZAP: 2014/03/23 Tidy up, removed the instance variable rootTreePath, no need to // cache the path // ZAP: 2014/03/23 Issue 609: Provide a common interface to query the state and // access the data (HttpMessage and HistoryReference) displayed in the tabs // ZAP: 2014/10/07 Issue 1357: Hide unused tabs // ZAP: 2014/12/17 Issue 1174: Support a Site filter // ZAP: 2014/12/22 Issue 1476: Display contexts in the Sites tree // ZAP: 2015/02/09 Issue 1525: Introduce a database interface layer to allow for alternative implementations // ZAP: 2015/02/10 Issue 1528: Support user defined font size // ZAP: 2015/06/01 Issue 1653: Support context menu key for trees // ZAP: 2016/04/14 Use View to display the HTTP messages // ZAP: 2016/07/01 Issue 2642: Slow mouse wheel scrolling in site tree package org.parosproxy.paros.view; import java.awt.Component; import java.awt.Dimension; import java.awt.Event; import java.awt.EventQueue; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Toolkit; import java.awt.event.KeyEvent; import java.util.ArrayList; import java.util.Collections; import java.util.List; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPopupMenu; import javax.swing.JToggleButton; import javax.swing.JTree; import javax.swing.KeyStroke; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeCellRenderer; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; import org.apache.log4j.Logger; import org.parosproxy.paros.Constant; import org.parosproxy.paros.control.Control; import org.parosproxy.paros.db.DatabaseException; import org.parosproxy.paros.extension.AbstractPanel; import org.parosproxy.paros.extension.history.LogPanel; import org.parosproxy.paros.model.HistoryReference; import org.parosproxy.paros.model.Model; import org.parosproxy.paros.model.SiteMap; import org.parosproxy.paros.model.SiteNode; import org.parosproxy.paros.network.HttpMessage; import org.zaproxy.zap.extension.history.HistoryFilterPlusDialog; import org.zaproxy.zap.model.Context; import org.zaproxy.zap.model.Target; import org.zaproxy.zap.utils.DisplayUtils; import org.zaproxy.zap.view.ContextCreateDialog; import org.zaproxy.zap.view.ContextGeneralPanel; import org.zaproxy.zap.view.ContextsSitesPanel; import org.zaproxy.zap.view.ContextsTreeCellRenderer; import org.zaproxy.zap.view.LayoutHelper; import org.zaproxy.zap.view.SiteMapListener; import org.zaproxy.zap.view.SiteMapTreeCellRenderer; import org.zaproxy.zap.view.SiteTreeFilter; import org.zaproxy.zap.view.ZapToggleButton; import org.zaproxy.zap.view.messagecontainer.http.DefaultSelectableHistoryReferencesContainer; import org.zaproxy.zap.view.messagecontainer.http.SelectableHistoryReferencesContainer; public class SiteMapPanel extends AbstractPanel { public static final String CONTEXT_TREE_COMPONENT_NAME = "ContextTree"; private static final long serialVersionUID = -3161729504065679088L; // ZAP: Added logger private static Logger log = Logger.getLogger(SiteMapPanel.class); private JTree treeSite = null; private JTree treeContext = null; private DefaultTreeModel contextTree = null; private View view = null; private javax.swing.JToolBar panelToolbar = null; private ZapToggleButton scopeButton = null; private JButton filterButton = null; private JLabel filterStatus = null; private HistoryFilterPlusDialog filterPlusDialog = null; private JButton createContextButton = null; private JButton importContextButton = null; private JButton exportContextButton = null; // ZAP: Added SiteMapListenners private List<SiteMapListener> listeners = new ArrayList<>(); /** * This is the default constructor */ public SiteMapPanel() { super(); initialize(); } private View getView() { if (view == null) { view = View.getSingleton(); } return view; } /** * This method initializes this */ private void initialize() { this.setHideable(false); this.setIcon(new ImageIcon(View.class.getResource("/resource/icon/16/094.png"))); this.setName(Constant.messages.getString("sites.panel.title")); this.setDefaultAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() | Event.SHIFT_MASK, false)); this.setMnemonic(Constant.messages.getChar("sites.panel.mnemonic")); if (Model.getSingleton().getOptionsParam().getViewParam().getWmUiHandlingOption() == 0) { this.setSize(300,200); } this.setLayout(new GridBagLayout()); this.add(this.getPanelToolbar(), LayoutHelper.getGBC(0, 0, 1, 0, new Insets(2,2,2,2))); this.add(new ContextsSitesPanel(getTreeContext(), getTreeSite(), "sitesPanelScrollPane"), LayoutHelper.getGBC(0, 1, 1, 1.0, 1.0, GridBagConstraints.BOTH, new Insets(2,2,2,2))); expandRoot(); } private javax.swing.JToolBar getPanelToolbar() { if (panelToolbar == null) { panelToolbar = new javax.swing.JToolBar(); panelToolbar.setLayout(new GridBagLayout()); panelToolbar.setEnabled(true); panelToolbar.setFloatable(false); panelToolbar.setRollover(true); panelToolbar.setPreferredSize(new Dimension(800,30)); panelToolbar.setName("ScriptsListToolbar"); int i = 1; panelToolbar.add(getScopeButton(), LayoutHelper.getGBC(i++, 0, 1, 0.0D)); panelToolbar.add(getCreateContextButton(), LayoutHelper.getGBC(i++, 0, 1, 0.0D)); panelToolbar.add(getImportContextButton(), LayoutHelper.getGBC(i++, 0, 1, 0.0D)); panelToolbar.add(getExportContextButton(), LayoutHelper.getGBC(i++, 0, 1, 0.0D)); // TODO Disabled for now due to problems with scrolling with sparcely populated filtered trees //panelToolbar.add(getFilterButton(), LayoutHelper.getGBC(i++, 0, 1, 0.0D)); //panelToolbar.add(getFilterStatus(), LayoutHelper.getGBC(i++, 0, 1, 0.0D)); panelToolbar.add(new JLabel(), LayoutHelper.getGBC(20, 0, 1, 1.0D)); // spacer } return panelToolbar; } private HistoryFilterPlusDialog getFilterPlusDialog() { if (filterPlusDialog == null) { filterPlusDialog = new HistoryFilterPlusDialog(getView().getMainFrame(), true); // Override the title as we're reusing the history filter dialog filterPlusDialog.setTitle(Constant.messages.getString("sites.filter.title")); } return filterPlusDialog; } private JLabel getFilterStatus() { filterStatus = new JLabel(Constant.messages.getString("history.filter.label.filter") + Constant.messages.getString("history.filter.label.off")); return filterStatus; } private JButton getFilterButton() { if (filterButton == null) { filterButton = new JButton(); filterButton.setIcon(DisplayUtils.getScaledIcon( new ImageIcon(LogPanel.class.getResource("/resource/icon/16/054.png")))); // 'filter' icon filterButton.setToolTipText(Constant.messages.getString("history.filter.button.filter")); filterButton.addActionListener(new java.awt.event.ActionListener() { @Override public void actionPerformed(java.awt.event.ActionEvent e) { showFilterPlusDialog(); } }); } return filterButton; } private JButton getCreateContextButton() { if (createContextButton == null) { createContextButton = new JButton(); createContextButton.setIcon(DisplayUtils.getScaledIcon(new ImageIcon( LogPanel.class.getResource("/resource/icon/fugue/application-blue-plus.png")))); createContextButton.setToolTipText(Constant.messages.getString("menu.file.context.create")); createContextButton.addActionListener(new java.awt.event.ActionListener() { @Override public void actionPerformed(java.awt.event.ActionEvent e) { ContextCreateDialog ccd = new ContextCreateDialog(View.getSingleton().getMainFrame()); ccd.setVisible(true); } }); } return createContextButton; } private JButton getImportContextButton() { if (importContextButton == null) { importContextButton = new JButton(); importContextButton.setIcon(DisplayUtils.getScaledIcon(new ImageIcon( LogPanel.class.getResource("/resource/icon/fugue/application-blue-import.png")))); importContextButton.setToolTipText(Constant.messages.getString("menu.file.context.import")); importContextButton.addActionListener(new java.awt.event.ActionListener() { @Override public void actionPerformed(java.awt.event.ActionEvent e) { Control.getSingleton().getMenuFileControl().importContext(); } }); } return importContextButton; } private JButton getExportContextButton() { if (exportContextButton == null) { exportContextButton = new JButton(); exportContextButton.setIcon(DisplayUtils.getScaledIcon(new ImageIcon( LogPanel.class.getResource("/resource/icon/fugue/application-blue-export.png")))); exportContextButton.setToolTipText(Constant.messages.getString("menu.file.context.export")); exportContextButton.addActionListener(new java.awt.event.ActionListener() { @Override public void actionPerformed(java.awt.event.ActionEvent e) { Control.getSingleton().getMenuFileControl().exportContext(); } }); } return exportContextButton; } private void showFilterPlusDialog() { HistoryFilterPlusDialog dialog = getFilterPlusDialog(); dialog.setModal(true); try { dialog.setAllTags(Model.getSingleton().getDb().getTableTag().getAllTags()); } catch (DatabaseException e) { log.error(e.getMessage(), e); } int exit = dialog.showDialog(); SiteTreeFilter filter = new SiteTreeFilter(dialog.getFilter()); filter.setInScope(this.getScopeButton().isSelected()); if (exit != JOptionPane.CANCEL_OPTION) { setFilter(); } } private void setFilter() { SiteTreeFilter filter = new SiteTreeFilter(getFilterPlusDialog().getFilter()); filter.setInScope(scopeButton.isSelected()); ((SiteMap)treeSite.getModel()).setFilter(filter); ((DefaultTreeModel)treeSite.getModel()).nodeStructureChanged((SiteNode)treeSite.getModel().getRoot()); getFilterStatus().setText(filter.toShortString()); getFilterStatus().setToolTipText(filter.toLongString()); expandRoot(); // Remove any out of scope contexts too this.reloadContextTree(); } private JToggleButton getScopeButton() { if (scopeButton == null) { scopeButton = new ZapToggleButton(); scopeButton.setIcon(DisplayUtils.getScaledIcon(new ImageIcon(SiteMapPanel.class.getResource("/resource/icon/fugue/target-grey.png")))); scopeButton.setToolTipText(Constant.messages.getString("history.scope.button.unselected")); scopeButton.setSelectedIcon(DisplayUtils.getScaledIcon(new ImageIcon(SiteMapPanel.class.getResource("/resource/icon/fugue/target.png")))); scopeButton.setSelectedToolTipText(Constant.messages.getString("history.scope.button.selected")); scopeButton.addActionListener(new java.awt.event.ActionListener() { @Override public void actionPerformed(java.awt.event.ActionEvent e) { setFilter(); } }); } return scopeButton; } /** * This method initializes treeSite * * @return javax.swing.JTree */ public JTree getTreeSite() { if (treeSite == null) { treeSite = new JTree(new DefaultTreeModel(new DefaultMutableTreeNode())); treeSite.setShowsRootHandles(true); treeSite.setName("treeSite"); treeSite.setToggleClickCount(1); treeSite.addTreeSelectionListener(new javax.swing.event.TreeSelectionListener() { @Override public void valueChanged(javax.swing.event.TreeSelectionEvent e) { SiteNode node = (SiteNode) treeSite.getLastSelectedPathComponent(); if (node == null) { return; } if (!node.isRoot()) { HttpMessage msg = null; try { msg = node.getHistoryReference().getHttpMessage(); } catch (Exception e1) { // ZAP: Log exceptions log.warn(e1.getMessage(), e1); return; } getView().displayMessage(msg); // ZAP: Call SiteMapListenners for (SiteMapListener listener : listeners) { listener.nodeSelected(node); } } else { // ZAP: clear the views when the root is selected getView().displayMessage(null); } } }); treeSite.setComponentPopupMenu(new SitesCustomPopupMenu()); // ZAP: Add custom tree cell renderer. DefaultTreeCellRenderer renderer = new SiteMapTreeCellRenderer(listeners); treeSite.setCellRenderer(renderer); } return treeSite; } public void reloadContextTree() { SiteNode root; if (this.contextTree == null) { root = new SiteNode(null, -1, Constant.messages.getString("context.list")); this.contextTree = new DefaultTreeModel(root); } else { root = (SiteNode)this.contextTree.getRoot(); root.removeAllChildren(); } for (Context ctx : Model.getSingleton().getSession().getContexts()) { if (ctx.isInScope() || ! this.getScopeButton().isSelected()) { // Add all in scope contexts, and out of scope ones if scope button not pressed SiteNode node = new SiteNode(null, HistoryReference.TYPE_PROXIED, ctx.getName()); node.setUserObject(new Target(ctx)); root.add(node); } } this.contextTree.nodeStructureChanged(root); } private JTree getTreeContext() { if (treeContext == null) { reloadContextTree(); treeContext = new JTree(this.contextTree); treeContext.setShowsRootHandles(true); treeContext.setName(CONTEXT_TREE_COMPONENT_NAME); treeContext.setToggleClickCount(1); treeContext.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); treeContext.addMouseListener(new java.awt.event.MouseAdapter() { @Override public void mousePressed(java.awt.event.MouseEvent e) { } @Override public void mouseReleased(java.awt.event.MouseEvent e) { mouseClicked(e); } @Override public void mouseClicked(java.awt.event.MouseEvent e) { if (treeSite.getLastSelectedPathComponent() != null) { // They selected a context node, deselect any context getTreeSite().clearSelection(); } if (e.getClickCount() > 1) { // Its a double click - show the relevant context dialog SiteNode node = (SiteNode) treeContext.getLastSelectedPathComponent(); if (node != null && node.getUserObject() != null) { Target target = (Target)node.getUserObject(); getView().showSessionDialog(Model.getSingleton().getSession(), ContextGeneralPanel.getPanelName(target.getContext())); } } } }); treeContext.setComponentPopupMenu(new ContextsCustomPopupMenu()); treeContext.setCellRenderer(new ContextsTreeCellRenderer()); } return treeContext; } public void expandRoot() { TreeNode root = (TreeNode) treeSite.getModel().getRoot(); if (root == null) { return; } final TreePath rootTreePath = new TreePath(root); if (EventQueue.isDispatchThread()) { getTreeSite().expandPath(rootTreePath); return; } try { EventQueue.invokeLater(new Runnable() { @Override public void run() { getTreeSite().expandPath(rootTreePath); } }); } catch (Exception e) { // ZAP: Log exceptions log.warn(e.getMessage(), e); } } // ZAP: Added addSiteMapListenners public void addSiteMapListener(SiteMapListener listenner) { this.listeners.add(listenner); } public void removeSiteMapListener(SiteMapListener listener) { this.listeners.remove(listener); } public void showInSites (SiteNode node) { TreeNode[] path = node.getPath(); TreePath tp = new TreePath(path); treeSite.setExpandsSelectedPaths(true); treeSite.setSelectionPath(tp); treeSite.scrollPathToVisible(tp); } public void contextChanged(Context c) { getTreeContext(); SiteNode root = (SiteNode)this.contextTree.getRoot(); for (int i=0; i < root.getChildCount(); i++) { SiteNode node = (SiteNode)root.getChildAt(i); Target target = (Target)node.getUserObject(); if (c.getIndex() == target.getContext().getIndex()) { target.setContext(c); if (node.getNodeName().equals(c.getName())) { this.contextTree.nodeChanged(node); } else { this.reloadContextTree(); } break; } } } protected class SitesCustomPopupMenu extends JPopupMenu { private static final long serialVersionUID = 1L; @Override public void show(Component invoker, int x, int y) { // ZAP: Select site list item on right click / menu key TreePath tp = treeSite.getPathForLocation( x, y ); if ( tp != null ) { boolean select = true; // Only select a new item if the current item is not // already selected - this is to allow multiple items // to be selected if (treeSite.getSelectionPaths() != null) { for (TreePath t : treeSite.getSelectionPaths()) { if (t.equals(tp)) { select = false; break; } } } if (select) { treeSite.getSelectionModel().setSelectionPath(tp); } } final int countSelectedNodes = treeSite.getSelectionCount(); final List<HistoryReference> historyReferences = new ArrayList<>(countSelectedNodes); if (countSelectedNodes > 0) { for (TreePath path : treeSite.getSelectionPaths()) { final SiteNode node = (SiteNode) path.getLastPathComponent(); final HistoryReference historyReference = node.getHistoryReference(); if (historyReference != null) { historyReferences.add(historyReference); } } } SelectableHistoryReferencesContainer messageContainer = new DefaultSelectableHistoryReferencesContainer( treeSite.getName(), treeSite, Collections.<HistoryReference> emptyList(), historyReferences); View.getSingleton().getPopupMenu().show(messageContainer, x, y); } } protected class ContextsCustomPopupMenu extends JPopupMenu { private static final long serialVersionUID = 1L; @Override public void show(Component invoker, int x, int y) { // Select context list item on right click TreePath tp = treeContext.getPathForLocation(x, y); if ( tp != null ) { boolean select = true; // Only select a new item if the current item is not // already selected - this is to allow multiple items // to be selected if (treeContext.getSelectionPaths() != null) { for (TreePath t : treeContext.getSelectionPaths()) { if (t.equals(tp)) { select = false; break; } } } if (select) { treeContext.getSelectionModel().setSelectionPath(tp); } } View.getSingleton().getPopupMenu().show(treeContext, x, y); } } }