/*
* Zed Attack Proxy (ZAP) and its related class files.
*
* ZAP is an HTTP/HTTPS proxy for assessing web application security.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.zaproxy.zap.extension.spider;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Event;
import java.awt.EventQueue;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.lang.reflect.InvocationTargetException;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import org.apache.log4j.Logger;
import org.jdesktop.swingx.JXTable;
import org.jdesktop.swingx.renderer.DefaultTableRenderer;
import org.jdesktop.swingx.renderer.IconValues;
import org.jdesktop.swingx.renderer.MappedValue;
import org.jdesktop.swingx.renderer.StringValues;
import org.parosproxy.paros.Constant;
import org.parosproxy.paros.view.View;
import org.zaproxy.zap.model.ScanController;
import org.zaproxy.zap.model.ScanListenner2;
import org.zaproxy.zap.spider.SpiderParam;
import org.zaproxy.zap.utils.DisplayUtils;
import org.zaproxy.zap.view.ScanPanel2;
import org.zaproxy.zap.view.ZapTable;
import org.zaproxy.zap.view.table.decorator.AbstractTableCellItemIconHighlighter;
/**
* The Class SpiderPanel implements the Panel that is shown to the users when selecting the Spider Scan Tab.
*/
public class SpiderPanel extends ScanPanel2<SpiderScan, ScanController<SpiderScan>> implements ScanListenner2 {
/**
* The name of the spider's HTTP messages container.
*
* @since 2.5.0
* @see org.zaproxy.zap.view.messagecontainer.http.HttpMessageContainer
*/
public static final String HTTP_MESSAGE_CONTAINER_NAME = "SpiderHttpMessageContainer";
/** The Constant serialVersionUID. */
private static final long serialVersionUID = 1L;
/** The Constant log. */
private static final Logger log = Logger.getLogger(SpiderPanel.class);
private static final String ZERO_REQUESTS_LABEL_TEXT = "0";
private static final SpiderPanelTableModel EMPTY_URLS_TABLE_MODEL = new SpiderPanelTableModel();
private static final SpiderMessagesTableModel EMPTY_MESSAGES_TABLE_MODEL = new SpiderMessagesTableModel(false);
/** The Constant defining the PANEL's NAME. */
public static final String PANEL_NAME = "SpiderPanel";
/**
* The main panel, where the {@link #tabbedPane} or {@link #urlsTableScrollPane} are added.
* <p>
* Lazily initialised.
*
* @see #getWorkPanel()
*/
private JPanel mainPanel;
/**
* The {@code JTabbedPane} used to show the tabs for URLs found and HTTP messages sent.
*/
private final JTabbedPane tabbedPane;
private JButton scanButton = null;
/**
* The table with URLs found.
* <p>
* Lazily initialised.
*
* @see #getUrlsTable()
* @see #urlsTableScrollPane
*/
private ZapTable urlsTable;
/**
* The scroll pane for the URLs table.
* <p>
* Lazily initialised.
*
* @see #getUrlsTableScrollPane()
* @see #urlsTable
*/
private JScrollPane urlsTableScrollPane;
/**
* The table with HTTP messages sent.
* <p>
* Lazily initialised.
*
* @see #getMessagesTable()
* @see #messagesTableScrollPane
*/
private SpiderMessagesTable messagesTable;
/**
* The scroll pane for the HTTP messages table.
* <p>
* Lazily initialised.
*
* @see #getMessagesTableScrollPanel()
* @see #messagesTable
*/
private JScrollPane messagesTableScrollPane;
/**
* The {@code JToggleButton} that allows to show the tab of the HTTP messages sent.
* <p>
* Lazily initialised.
*
* @see #getShowMessagesToggleButton()
* @see #showTabs()
* @see #hideMessagesTab()
*/
private JToggleButton showMessageToggleButton;
/** The found count name label. */
private JLabel foundCountNameLabel;
/** The found count value label. */
private JLabel foundCountValueLabel;
private ExtensionSpider extension = null;
/**
* Instantiates a new spider panel.
*
* @param extension the extension
* @param spiderScanParam the spider scan parameters
*/
public SpiderPanel(ExtensionSpider extension, SpiderParam spiderScanParam) {
super("spider", new ImageIcon(SpiderPanel.class.getResource("/resource/icon/16/spider.png")), extension);
tabbedPane = new JTabbedPane();
this.extension = extension;
this.setDefaultAccelerator(KeyStroke.getKeyStroke(
KeyEvent.VK_D, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() | Event.SHIFT_MASK, false));
this.setMnemonic(Constant.messages.getChar("spider.panel.mnemonic"));
}
/**
* This method initializes the working Panel.
*
* @return javax.swing.JScrollPane
*/
@Override
protected JPanel getWorkPanel() {
if (mainPanel == null) {
mainPanel = new JPanel(new BorderLayout());
mainPanel.add(getUrlsTableScrollPane(), BorderLayout.CENTER);
}
return mainPanel;
}
private JScrollPane getUrlsTableScrollPane() {
if (urlsTableScrollPane == null) {
urlsTableScrollPane = new JScrollPane();
urlsTableScrollPane.setName("SpiderUrlsPane");
urlsTableScrollPane.setViewportView(getUrlsTable());
}
return urlsTableScrollPane;
}
/**
* Gets the scan results table.
*
* @return the scan results table
*/
private JXTable getUrlsTable() {
if (urlsTable == null) {
// Create the table with a default, empty TableModel and the proper settings
urlsTable = new ZapTable(EMPTY_URLS_TABLE_MODEL);
urlsTable.setColumnSelectionAllowed(false);
urlsTable.setCellSelectionEnabled(false);
urlsTable.setRowSelectionAllowed(true);
urlsTable.setAutoCreateRowSorter(true);
urlsTable.setAutoCreateColumnsFromModel(false);
urlsTable.getColumnExt(0).setCellRenderer(
new DefaultTableRenderer(new MappedValue(StringValues.EMPTY, IconValues.NONE), JLabel.CENTER));
urlsTable.getColumnExt(0).setHighlighters(new ProcessedCellItemIconHighlighter(0));
urlsTable.getColumnModel().getColumn(0).setMinWidth(80);
urlsTable.getColumnModel().getColumn(0).setPreferredWidth(90); // processed
urlsTable.getColumnModel().getColumn(1).setMinWidth(60);
urlsTable.getColumnModel().getColumn(1).setPreferredWidth(70); // method
urlsTable.getColumnModel().getColumn(2).setMinWidth(300); // name
urlsTable.getColumnModel().getColumn(3).setMinWidth(50);
urlsTable.getColumnModel().getColumn(3).setPreferredWidth(250); // flags
urlsTable.setName(PANEL_NAME);
urlsTable.setDoubleBuffered(true);
urlsTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
urlsTable.setComponentPopupMenu(new JPopupMenu() {
private static final long serialVersionUID = 6608291059686282641L;
@Override
public void show(Component invoker, int x, int y) {
View.getSingleton().getPopupMenu().show(invoker, x, y);
}
});
}
return urlsTable;
}
private JScrollPane getMessagesTableScrollPanel() {
if (messagesTableScrollPane == null) {
messagesTableScrollPane = new JScrollPane();
messagesTableScrollPane.setName("SpiderMessagesPane");
messagesTableScrollPane.setViewportView(getMessagesTable());
}
return messagesTableScrollPane;
}
private SpiderMessagesTable getMessagesTable() {
if (messagesTable == null) {
messagesTable = new SpiderMessagesTable(EMPTY_MESSAGES_TABLE_MODEL);
messagesTable.setName(HTTP_MESSAGE_CONTAINER_NAME);
}
return messagesTable;
}
/**
* Gets the label storing the name of the count of found URIs.
*
* @return the found count name label
*/
private JLabel getFoundCountNameLabel() {
if (foundCountNameLabel == null) {
foundCountNameLabel = new javax.swing.JLabel();
foundCountNameLabel.setText(Constant.messages.getString("spider.toolbar.found.label"));
}
return foundCountNameLabel;
}
/**
* Gets the label storing the value for count of found URIs.
*
* @return the found count value label
*/
private JLabel getFoundCountValueLabel() {
if (foundCountValueLabel == null) {
foundCountValueLabel = new javax.swing.JLabel();
foundCountValueLabel.setText(ZERO_REQUESTS_LABEL_TEXT);
}
return foundCountValueLabel;
}
@Override
protected int addToolBarElements(JToolBar toolBar, Location location, int gridX) {
if (ScanPanel2.Location.afterProgressBar == location) {
toolBar.add(new JToolBar.Separator(), getGBC(gridX++, 0));
toolBar.add(getFoundCountNameLabel(), getGBC(gridX++, 0));
toolBar.add(getFoundCountValueLabel(), getGBC(gridX++, 0));
toolBar.add(new JToolBar.Separator(), getGBC(gridX++, 0));
toolBar.add(getShowMessagesToggleButton(), getGBC(gridX++, 0));
}
return gridX;
}
private JToggleButton getShowMessagesToggleButton() {
if (showMessageToggleButton == null) {
showMessageToggleButton = new JToggleButton(
Constant.messages.getString("spider.toolbar.button.showmessages.label"),
new ImageIcon(SpiderPanel.class.getResource("/resource/icon/16/178.png")));
showMessageToggleButton.setToolTipText(Constant.messages.getString("spider.toolbar.button.showmessages.tooltip"));
showMessageToggleButton.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
if (ItemEvent.SELECTED == e.getStateChange()) {
showTabs();
} else {
hideMessagesTab();
}
}
});
}
return showMessageToggleButton;
}
/**
* Shows both tabs, the one of the URLs found and the other of the HTTP messages sent.
*
* @see #hideMessagesTab()
*/
private void showTabs() {
mainPanel.removeAll();
tabbedPane.addTab(Constant.messages.getString("spider.panel.tab.urls"), getUrlsTableScrollPane());
tabbedPane.addTab(Constant.messages.getString("spider.panel.tab.messages"), getMessagesTableScrollPanel());
getShowMessagesToggleButton().setText(Constant.messages.getString("spider.toolbar.button.showmessages.label.selected"));
tabbedPane.setSelectedIndex(1);
mainPanel.add(tabbedPane);
mainPanel.revalidate();
mainPanel.repaint();
}
/**
* Hides the HTTP messages tab and the tabbed pane, leaving just the table with URLs found.
*
* @see #showTabs()
*/
private void hideMessagesTab() {
getShowMessagesToggleButton().setText(Constant.messages.getString("spider.toolbar.button.showmessages.label"));
tabbedPane.removeAll();
mainPanel.removeAll();
mainPanel.add(getUrlsTableScrollPane());
mainPanel.revalidate();
}
/**
* Update the count of found URIs.
*/
protected void updateFoundCount() {
SpiderScan sc = this.getSelectedScanner();
if (sc != null) {
this.getFoundCountValueLabel().setText(Integer.toString(sc.getNumberOfURIsFound()));
} else {
this.getFoundCountValueLabel().setText(ZERO_REQUESTS_LABEL_TEXT);
}
}
@Override
protected void switchView(final SpiderScan scanner) {
if (View.isInitialised() && !EventQueue.isDispatchThread()) {
try {
EventQueue.invokeAndWait(new Runnable() {
@Override
public void run() {
switchView(scanner);
}
});
} catch (InvocationTargetException | InterruptedException e) {
log.error("Failed to switch view: " + e.getMessage(), e);
}
return;
}
if (scanner != null) {
getUrlsTable().setModel(scanner.getResultsTableModel());
getMessagesTable().setModel(scanner.getMessagesTableModel());
} else {
getUrlsTable().setModel(EMPTY_URLS_TABLE_MODEL);
getMessagesTable().setModel(EMPTY_MESSAGES_TABLE_MODEL);
}
this.updateFoundCount();
}
@Override
public JButton getNewScanButton() {
if (scanButton == null) {
scanButton = new JButton(Constant.messages.getString("spider.toolbar.button.new"));
scanButton.setIcon(DisplayUtils.getScaledIcon(new ImageIcon(SpiderPanel.class.getResource("/resource/icon/16/spider.png"))));
scanButton.addActionListener(new ActionListener () {
@Override
public void actionPerformed(ActionEvent e) {
extension.showSpiderDialog(null);
}
});
}
return scanButton;
}
@Override
protected int getNumberOfScansToShow() {
return extension.getSpiderParam().getMaxScansInUI();
}
/**
* A {@link org.jdesktop.swingx.decorator.Highlighter Highlighter} for a column that indicates, using icons, whether or not
* an entry was processed, that is, is or not in scope.
* <p>
* The expected type/class of the cell values is {@code Boolean}.
*/
private static class ProcessedCellItemIconHighlighter extends AbstractTableCellItemIconHighlighter {
/** The icon that indicates the entry was processed. */
private static final ImageIcon PROCESSED_ICON = new ImageIcon(
SpiderPanelTableModel.class.getResource("/resource/icon/16/152.png"));
/** The icon that indicates the entry was not processed. */
private static final ImageIcon NOT_PROCESSED_ICON = new ImageIcon(
SpiderPanelTableModel.class.getResource("/resource/icon/16/149.png"));
public ProcessedCellItemIconHighlighter(final int columnIndex) {
super(columnIndex);
}
@Override
protected Icon getIcon(final Object cellItem) {
return getProcessedIcon(((Boolean) cellItem).booleanValue());
}
private static Icon getProcessedIcon(final boolean processed) {
return processed ? PROCESSED_ICON : NOT_PROCESSED_ICON;
}
@Override
protected boolean isHighlighted(final Object cellItem) {
return true;
}
}
}