/* * Zed Attack Proxy (ZAP) and its related class files. * * ZAP is an HTTP/HTTPS proxy for assessing web application security. * * Copyright 2010 psiinon@gmail.com * * 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.view; import java.awt.CardLayout; import java.awt.Component; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.List; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JProgressBar; import javax.swing.JToggleButton; import javax.swing.JToolBar; import org.apache.log4j.Logger; import org.parosproxy.paros.Constant; import org.parosproxy.paros.common.AbstractParam; import org.parosproxy.paros.control.Control; import org.parosproxy.paros.control.Control.Mode; import org.parosproxy.paros.extension.AbstractPanel; import org.parosproxy.paros.model.Model; import org.parosproxy.paros.model.Session; import org.parosproxy.paros.view.View; import org.zaproxy.zap.model.GenericScanner2; import org.zaproxy.zap.model.ScanController; import org.zaproxy.zap.utils.DisplayUtils; import org.zaproxy.zap.utils.SortedComboBoxModel; /* * This is a cleaner version of ScanPanel which doesnt mix functionality and the UI. * Implemented as a new set of classes for backwards compatibility with existing add-ons */ public abstract class ScanPanel2<GS extends GenericScanner2, SC extends ScanController<GS>> extends AbstractPanel { private static final long serialVersionUID = 1L; protected enum Location {start, beforeSites, beforeButtons, beforeProgressBar, afterProgressBar}; private final String prefix; private final SC controller; private JPanel panelCommand = null; private JToolBar panelToolbar = null; private JLabel scannedCountNameLabel = null; private JLabel foundCountNameLabel = null; private JComboBox<ScanEntry<GS>> progressSelect = null; private SortedComboBoxModel<ScanEntry<GS>> progressModel = new SortedComboBoxModel<>(); private final ScanEntry<GS> selectScanEntry; private JButton stopScanButton = null; private ZapToggleButton pauseScanButton = null; private JButton newScanButton = null; private JButton clearScansButton = null; private JButton optionsButton = null; private JProgressBar progressBar = null; private ScanStatus scanStatus = null; private Mode mode = Control.getSingleton().getMode(); private static Logger log = Logger.getLogger(ScanPanel2.class); /** * Constructs a {@code ScanPanel2} with the given message resources prefix, tab icon and scan controller. * * @param prefix the prefix for the resource messages * @param icon the icon for the tab of the panel * @param controller the scan controller * @param scanParam unused * @deprecated (2.6.0) Use {@link #ScanPanel2(String, ImageIcon, ScanController)} instead. */ @Deprecated public ScanPanel2(String prefix, ImageIcon icon, SC controller, AbstractParam scanParam) { this(prefix, icon, controller); } /** * Constructs a {@code ScanPanel2} with the given message resources prefix, tab icon and scan controller. * * @param prefix the prefix for the resource messages * @param icon the icon for the tab of the panel * @param controller the scan controller * @since 2.6.0 */ public ScanPanel2(String prefix, ImageIcon icon, SC controller) { super(); this.prefix = prefix; this.controller = controller; selectScanEntry = new ScanEntry<>(Constant.messages.getString(prefix + ".toolbar.progress.select")); initialize(icon); log.debug("Constructor " + prefix); } /** * This method initializes this * */ private void initialize(ImageIcon icon) { this.setLayout(new CardLayout()); if (Model.getSingleton().getOptionsParam().getViewParam().getWmUiHandlingOption() == 0) { this.setSize(474, 251); } this.setName(Constant.messages.getString(prefix + ".panel.title")); this.setIcon(icon); this.add(getPanelCommand(), prefix + ".panel"); scanStatus = new ScanStatus(icon, Constant.messages.getString(prefix + ".panel.title")); if (View.isInitialised()) { View.getSingleton().getMainFrame().getMainFooterPanel().addFooterToolbarRightLabel(scanStatus.getCountLabel()); } } /** * This method initializes panelCommand * * @return javax.swing.JPanel */ private javax.swing.JPanel getPanelCommand() { if (panelCommand == null) { panelCommand = new javax.swing.JPanel(); panelCommand.setLayout(new java.awt.GridBagLayout()); panelCommand.setName(prefix + ".panel"); GridBagConstraints gridBagConstraints1 = new GridBagConstraints(); GridBagConstraints gridBagConstraints2 = new GridBagConstraints(); gridBagConstraints1.gridx = 0; gridBagConstraints1.gridy = 0; gridBagConstraints1.insets = new java.awt.Insets(2,2,2,2); gridBagConstraints1.anchor = java.awt.GridBagConstraints.NORTHWEST; gridBagConstraints1.fill = java.awt.GridBagConstraints.HORIZONTAL; gridBagConstraints1.weightx = 1.0D; gridBagConstraints2.gridx = 0; gridBagConstraints2.gridy = 1; gridBagConstraints2.weightx = 1.0; gridBagConstraints2.weighty = 1.0; gridBagConstraints2.fill = java.awt.GridBagConstraints.BOTH; gridBagConstraints2.insets = new java.awt.Insets(0,0,0,0); gridBagConstraints2.anchor = java.awt.GridBagConstraints.NORTHWEST; panelCommand.add(this.getPanelToolbar(), gridBagConstraints1); panelCommand.add(getWorkPanel(), gridBagConstraints2); } return panelCommand; } protected GridBagConstraints getGBC(int gridx, int gridy) { return this.getGBC(gridx, gridy, 0.0, new Insets(0, 2, 0, 0)); } protected GridBagConstraints getGBC(int gridx, int gridy, double weightx, Insets insets) { GridBagConstraints gbc = new GridBagConstraints(); gbc.gridx = gridx; gbc.gridy = gridy; gbc.weightx = weightx; if (weightx > 0.0) { gbc.fill = java.awt.GridBagConstraints.HORIZONTAL; } gbc.insets = insets; gbc.anchor = java.awt.GridBagConstraints.WEST; return gbc; } 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 java.awt.Dimension(800,30)); panelToolbar.setName(prefix + ".toolbar"); int x = 0; x = this.addToolBarElements(panelToolbar, Location.start, x); newScanButton = getNewScanButton(); if (newScanButton != null) { panelToolbar.add(newScanButton, getGBC(x++,0)); newScanButton.setEnabled( ! Mode.safe.equals(mode)); panelToolbar.addSeparator(); x++; } panelToolbar.add(new JLabel(Constant.messages.getString(prefix + ".toolbar.progress.label")), getGBC(x++,0)); panelToolbar.add(getProgressSelect(), getGBC(x++,0)); x = this.addToolBarElements(panelToolbar, Location.beforeButtons, x); panelToolbar.add(getPauseScanButton(), getGBC(x++,0)); panelToolbar.add(getStopScanButton(), getGBC(x++,0)); x = this.addToolBarElements(panelToolbar, Location.beforeProgressBar, x); panelToolbar.add(getProgressBar(), getGBC(x++,0, 1.0, new Insets(0,5,0,5))); panelToolbar.add(getClearScansButton(), getGBC(x++,0)); panelToolbar.add(getActiveScansNameLabel(), getGBC(x++,0)); panelToolbar.add(getActiveScansValueLabel(), getGBC(x++,0)); x = this.addToolBarElements(panelToolbar, Location.afterProgressBar, x); panelToolbar.add(new JLabel(), getGBC(x++,0, 1.0, new Insets(0,0,0,0))); // Spacer panelToolbar.add(getOptionsButton(), getGBC(x++,0)); } return panelToolbar; } /** * Adds elements to the tool bar. The method is called while initializing the ScanPanel, at the * points specified by the {@link Location} enumeration. Should be overridden by all subclasses * that want to add new elements to the ScanPanel's tool bar. * * <p> * The tool bar uses a {@code GridBagLayout}, so elements have to be added with a * {@code GridBagConstraints}. For this, the {@code getGBC} methods can be used. The {@code gridX} parameter * specifies the cell (as used in {@code GridBagConstraints.gridx}) of the current row where the elements can * be added. * </p> * <p> * The method must return the new coordinates of the current cell, after the elements have been * added. * </p> * * @param toolBar the tool bar * @param location the current location where elements will be added * @param gridX the x coordinates of the current cell in the {@code GridBagLayout} * @return the new coordinates of the current cell, after the elements have been added. * @see #getGBC(int, int) * @see #getGBC(int, int, double, Insets) * @see GridBagConstraints * @see GridBagLayout */ protected int addToolBarElements(JToolBar toolBar, Location location, int gridX) { return gridX; } private JLabel getActiveScansNameLabel() { if (scannedCountNameLabel == null) { scannedCountNameLabel = new javax.swing.JLabel(); scannedCountNameLabel.setText(Constant.messages.getString(prefix + ".toolbar.ascans.label")); } return scannedCountNameLabel; } private JLabel getActiveScansValueLabel() { if (foundCountNameLabel == null) { foundCountNameLabel = new javax.swing.JLabel(); foundCountNameLabel.setText(String.valueOf(controller.getActiveScans().size())); } return foundCountNameLabel; } private void setActiveScanLabelsEventHandler() { List<GS> ascans = controller.getActiveScans(); getActiveScansValueLabel().setText(String.valueOf(ascans.size())); StringBuilder sb = new StringBuilder(); sb.append("<html>"); for (GS ascan : ascans) { sb.append(ascan.getDisplayName()); sb.append("<br>"); } sb.append("</html>"); final String toolTip = sb.toString(); getActiveScansNameLabel().setToolTipText(toolTip); getActiveScansValueLabel().setToolTipText(toolTip); scanStatus.setScanCount(ascans.size()); this.getClearScansButton().setEnabled(controller.getAllScans().size() - ascans.size() > 0); } protected JProgressBar getProgressBar() { if (progressBar == null) { progressBar = new JProgressBar(0, 100); progressBar.setValue(0); progressBar.setSize(new Dimension(80,20)); progressBar.setStringPainted(true); progressBar.setEnabled(false); } return progressBar; } protected JButton getStopScanButton() { if (stopScanButton == null) { stopScanButton = new JButton(); stopScanButton.setToolTipText(Constant.messages.getString(prefix + ".toolbar.button.stop")); stopScanButton.setIcon(DisplayUtils.getScaledIcon(new ImageIcon(ScanPanel2.class.getResource("/resource/icon/16/142.png")))); stopScanButton.setEnabled(false); stopScanButton.addActionListener(new ActionListener () { @Override public void actionPerformed(ActionEvent e) { GS scanner = getSelectedScanner(); if (scanner != null) { controller.stopScan(scanner.getScanId()); } } }); } return stopScanButton; } protected JToggleButton getPauseScanButton() { if (pauseScanButton == null) { pauseScanButton = new ZapToggleButton(); pauseScanButton.setToolTipText(Constant.messages.getString(prefix + ".toolbar.button.pause")); pauseScanButton.setSelectedToolTipText(Constant.messages.getString(prefix + ".toolbar.button.unpause")); pauseScanButton.setIcon(DisplayUtils.getScaledIcon(new ImageIcon(ScanPanel2.class.getResource("/resource/icon/16/141.png")))); pauseScanButton.setRolloverIcon(DisplayUtils.getScaledIcon(new ImageIcon(ScanPanel2.class.getResource("/resource/icon/16/141.png")))); pauseScanButton.setSelectedIcon(DisplayUtils.getScaledIcon(new ImageIcon(ScanPanel2.class.getResource("/resource/icon/16/131.png")))); pauseScanButton.setRolloverSelectedIcon(DisplayUtils.getScaledIcon(new ImageIcon(ScanPanel2.class.getResource("/resource/icon/16/131.png")))); pauseScanButton.setEnabled(false); pauseScanButton.addActionListener(new ActionListener () { @Override public void actionPerformed(ActionEvent e) { GS scanner = getSelectedScanner(); if (scanner != null) { if (pauseScanButton.isSelected()) { controller.pauseScan(scanner.getScanId()); } else { controller.resumeScan(scanner.getScanId()); } } } }); } return pauseScanButton; } private JButton getOptionsButton() { if (optionsButton == null) { optionsButton = new JButton(); optionsButton.setToolTipText(Constant.messages.getString(prefix + ".toolbar.button.options")); optionsButton.setIcon(DisplayUtils.getScaledIcon(new ImageIcon(ScanPanel2.class.getResource("/resource/icon/16/041.png")))); optionsButton.addActionListener(new ActionListener () { @Override public void actionPerformed(ActionEvent e) { Control.getSingleton().getMenuToolsControl().options( Constant.messages.getString(prefix + ".options.title")); } }); } return optionsButton; } private JButton getClearScansButton() { if (clearScansButton == null) { clearScansButton = new JButton(); clearScansButton.setToolTipText(Constant.messages.getString(prefix + ".toolbar.button.clear")); clearScansButton.setIcon(DisplayUtils.getScaledIcon(new ImageIcon(ScanPanel2.class.getResource("/resource/icon/fugue/broom.png")))); clearScansButton.setEnabled(false); clearScansButton.addActionListener(new ActionListener () { @Override public void actionPerformed(ActionEvent e) { clearFinishedScans(); } }); } return clearScansButton; } public void clearFinishedScans() { // Remove via controller int count = controller.removeFinishedScans(); if (count > 0) { // Some were removed - remove all and add back the remaining ones progressModel.removeAllElements(); progressModel.addElement(selectScanEntry); for (GS scan : controller.getAllScans()) { progressModel.addElement(new ScanEntry<>(scan)); } updateScannerUI(); } clearScansButton.setEnabled(false); } public GS getSelectedScanner() { Object selectedItem = progressModel.getSelectedItem(); if (selectedItem == null) { return null; } @SuppressWarnings("unchecked") GS scan = ((ScanEntry<GS>) selectedItem).getScan(); return scan; } protected JComboBox<ScanEntry<GS>> getProgressSelect() { if (progressSelect == null) { progressSelect = new JComboBox<>(progressModel); progressSelect.addItem(selectScanEntry); progressSelect.setSelectedIndex(0); progressSelect.setEnabled(false); progressSelect.addActionListener(new java.awt.event.ActionListener() { @Override public void actionPerformed(java.awt.event.ActionEvent e) { scannerSelected(getSelectedScanner()); } }); } return progressSelect; } public void updateScannerUI() { scannerSelected(this.getSelectedScanner()); } private void scannerSelected(GS scanner) { updateProgressAndButtonsState(scanner); switchView(scanner); } private void updateProgressAndButtonsState(GS scanner) { if (scanner == null || Mode.safe.equals(Control.getSingleton().getMode())) { // Disable everything getStopScanButton().setEnabled(false); getPauseScanButton().setEnabled(false); getPauseScanButton().setSelected(false); getProgressBar().setEnabled(false); } else if (scanner.isStopped()) { getStopScanButton().setEnabled(false); getPauseScanButton().setEnabled(false); getPauseScanButton().setSelected(false); getProgressBar().setEnabled(false); } else { getStopScanButton().setEnabled(true); getPauseScanButton().setEnabled(true); getPauseScanButton().setSelected(scanner.isPaused()); getProgressBar().setEnabled(true); } if (scanner != null) { getProgressBar().setValue(scanner.getProgress()); getProgressBar().setMaximum(scanner.getMaximum()); } else { getProgressBar().setValue(0); } } public void scanFinshed(final int id, final String host) { if (EventQueue.isDispatchThread()) { scanFinshedEventHandler(id, host); } else { try { EventQueue.invokeLater(new Runnable() { @Override public void run() { scanFinshedEventHandler(id, host); } }); } catch (Exception e) { log.error(e.getMessage(), e); } } } private void scanFinshedEventHandler(int id, String host) { log.debug("scanFinished " + prefix + " on " + host); if (this.getSelectedScanner() != null && this.getSelectedScanner().getScanId() == id) { updateProgressAndButtonsState(getSelectedScanner()); } setActiveScanLabelsEventHandler(); } public void scanProgress(final int id, final String host, final int progress, final int maximum) { if (EventQueue.isDispatchThread()) { scanProgressEventHandler(id, host, progress, maximum); } else { EventQueue.invokeLater(new Runnable() { @Override public void run() { scanProgressEventHandler(id, host, progress, maximum); } }); } } private void scanProgressEventHandler(int id, String host, int progress, int maximum) { //log.debug("scanProgress " + prefix + " on " + currentSite + " " + progress); if (this.getSelectedScanner() != null && id == this.getSelectedScanner().getScanId()) { updateProgressAndButtonsState(getSelectedScanner()); } setActiveScanLabelsEventHandler(); } public void scannerStarted(GS scanner) { ScanEntry<GS> scanEntry = new ScanEntry<>(scanner); this.progressModel.addElement(scanEntry); this.getProgressSelect().setEnabled(true); this.getProgressSelect().setSelectedItem(scanEntry); this.trimProgressList(); this.scannerSelected(scanner); } public void trimProgressList() { /* * We only trim scans that have completed, so if the user kicks off a load of scans then * we could have a lot more in the list than the 'maximum' */ if (this.progressModel.getSize() > this.getNumberOfScansToShow() + 1) { // Trim past results - the +1 is for the initial 'select scan' message for (int i=1; i < this.progressModel.getSize(); i++) { GS scan = this.progressModel.getElementAt(i).getScan(); if (scan != null && scan.isStopped()) { controller.removeScan(scan.getScanId()); this.progressModel.removeElementAt(i); if (this.progressModel.getSize() <= this.getNumberOfScansToShow() + 1) { // Have removed enough break; } // Need to remove more, but the indexes will have changed so go back 1 i--; } } } } public void reset() { log.debug("reset " + prefix); progressModel.removeAllElements(); progressSelect.addItem(selectScanEntry); progressSelect.setSelectedIndex(0); clearScansButton.setEnabled(false); } public void sessionScopeChanged(Session session) { } public void sessionModeChanged(Mode mode) { if (newScanButton != null) { this.newScanButton.setEnabled( ! Mode.safe.equals(mode)); } // This will handle the remaining changes needed updateScannerUI(); } protected void unload() { if (View.isInitialised()) { View.getSingleton().getMainFrame().getMainFooterPanel().removeFooterToolbarRightLabel(scanStatus.getCountLabel()); } } protected SC getController() { return controller; } protected abstract Component getWorkPanel(); protected abstract void switchView (GS scanner); /* * Returns the scan button. Can return null if not relevant */ protected abstract JButton getNewScanButton(); protected abstract int getNumberOfScansToShow(); private static class ScanEntry<GS extends GenericScanner2> implements Comparable<ScanEntry<GS>> { private final GS scan; private final String label; public ScanEntry(String label) { this.scan = null; this.label = label; } public ScanEntry(GS scan) { this.scan = scan; this.label = scan.getScanId() + ": " + scan.getDisplayName(); } public GS getScan() { return scan; } @Override public int hashCode() { return 31 + ((scan == null) ? 0 : scan.getScanId()); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } ScanEntry<?> other = (ScanEntry<?>) obj; if (scan == null) { return (other.scan == null); } else if (other.scan == null) { return false; } return scan.getScanId() == other.scan.getScanId(); } @Override public int compareTo(ScanEntry<GS> other) { if (other == null || other.scan == null) { return 1; } if (scan == null) { return -1; } return scan.getScanId() - other.scan.getScanId(); } @Override public String toString() { return label; } } }