/*
* Zed Attack Proxy (ZAP) and its related class files.
*
* ZAP is an HTTP/HTTPS proxy for assessing web application security.
*
* Copyright 2015 The ZAP Development Team
*
* 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.messagelocation;
import java.awt.Component;
import javax.swing.JCheckBox;
import javax.swing.JOptionPane;
import javax.swing.ListSelectionModel;
import javax.swing.SortOrder;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import org.jdesktop.swingx.JXTable;
import org.parosproxy.paros.Constant;
import org.parosproxy.paros.view.View;
import org.zaproxy.zap.extension.httppanel.ComponentChangedEvent;
import org.zaproxy.zap.extension.httppanel.MessagePanelEventListener;
import org.zaproxy.zap.extension.httppanel.MessageViewSelectedEvent;
import org.zaproxy.zap.extension.httppanel.view.HttpPanelView;
import org.zaproxy.zap.model.MessageLocation;
import org.zaproxy.zap.view.AbstractMultipleOptionsBaseTablePanel;
/**
* An {@code AbstractMultipleOptionsBaseTablePanel} that allows to manage highlights and message locations.
*
* @param <T> the type that contains the details of the highlight and message location
* @param <S> the type of the table model that contains the elements with the details
* @since 2.4.0
* @see MessageLocationTableEntry
* @see MessageLocationsTableModel
*/
public abstract class AbstractMessageLocationsPanel<T extends MessageLocationTableEntry, S extends MessageLocationsTableModel<T>>
extends AbstractMultipleOptionsBaseTablePanel<T> {
private static final long serialVersionUID = -8990789229815588716L;
private static final String REMOVE_DIALOG_TITLE = Constant.messages.getString("messagelocationspanel.dialog.remove.location.title");
private static final String REMOVE_DIALOG_TEXT = Constant.messages.getString("messagelocationspanel.dialog.remove.location.text");
private static final String REMOVE_DIALOG_CONFIRM_BUTTON_LABEL = Constant.messages.getString("messagelocationspanel.dialog.remove.location.button.confirm");
private static final String REMOVE_DIALOG_CANCEL_BUTTON_LABEL = Constant.messages.getString("messagelocationspanel.dialog.remove.location.button.cancel");
private static final String REMOVE_DIALOG_CHECKBOX_LABEL = Constant.messages.getString("messagelocationspanel.dialog.remove.location.checkbox.label");
private MessageLocationProducerFocusListener addButtonFocusListenerEnabler;
private SelectMessageLocationsPanel selectMessageLocationsPanel;
private final Component parent;
public AbstractMessageLocationsPanel(Component parent, SelectMessageLocationsPanel selectMessageLocationsPanel, S model) {
this(parent, selectMessageLocationsPanel, model, false);
}
public AbstractMessageLocationsPanel(
Component parent,
SelectMessageLocationsPanel selectMessageLocationsPanel,
S model,
boolean allowModifications) {
super(model, allowModifications);
this.parent = parent;
this.selectMessageLocationsPanel = selectMessageLocationsPanel;
this.selectMessageLocationsPanel.addMessagePanelEventListener(createMessagePanelEventListener());
getModel().addMessageLocationHighlightChangedListener(createHighlightChangedListener());
addButton.setEnabled(false);
addButton.setToolTipText(Constant.messages.getString("messagelocationspanel.add.location.tooltip"));
addButtonFocusListenerEnabler = createFocusListener();
getTable().setSortOrder(1, SortOrder.ASCENDING);
getTable().setDefaultRenderer(MessageLocationHighlight.class, new DefaultMessageLocationHighlightRenderer());
getRemoveWithoutConfirmationCheckBox().setSelected(true);
}
protected MessagePanelEventListener createMessagePanelEventListener() {
return new MessagePanelEventListener() {
@Override
public void componentChanged(ComponentChangedEvent event) {
for (T entry : getModel().getElements()) {
MessageLocationHighlight highlight = entry.getHighlight();
if (highlight != null) {
MessageLocationHighlight highlightReference = selectMessageLocationsPanel.highlight(
entry.getLocation(),
highlight);
entry.setHighlightReference(highlightReference);
}
}
}
@Override
public void viewSelected(MessageViewSelectedEvent event) {
HttpPanelView view = event.getCurrentView();
if (view instanceof MessageLocationHighlighter) {
MessageLocationHighlighter highlighter = (MessageLocationHighlighter) view;
for (T entry : getModel().getElements()) {
MessageLocationHighlight highlight = entry.getHighlight();
if (highlight != null) {
MessageLocationHighlight highlightReference = highlighter.highlight(entry.getLocation(), highlight);
entry.setHighlightReference(highlightReference);
}
}
} else {
for (T entry : getModel().getElements()) {
entry.setHighlightReference(null);
}
}
}
};
}
protected HighlightChangedListener<T> createHighlightChangedListener() {
return new MessageLocationsHighlightChangedListener();
}
@Override
protected JXTable createTable() {
MessageLocationsTable table = new MessageLocationsTable();
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
table.setColumnControlVisible(true);
return table;
}
public MessageLocationProducerFocusListener getFocusListenerAddButtonEnabler() {
return addButtonFocusListenerEnabler;
}
protected MessageLocationProducerFocusListener createFocusListener() {
return new MessageLocationsFocusListener();
}
@Override
public T showAddDialogue() {
addButton.setEnabled(false);
return addMessageLocationImpl(true, selectMessageLocationsPanel.getSelection());
}
public boolean addMessageLocation(MessageLocation messageLocation) {
if (messageLocation == null) {
throw new IllegalArgumentException("Parameter messageLocation must not be null.");
}
return addMessageLocationImpl(false, messageLocation) != null;
}
private T addMessageLocationImpl(boolean buttonAddedLocation, MessageLocation messageLocation) {
for (T locationUI : getModel().getElements()) {
if (locationUI.getLocation().overlaps(messageLocation)) {
View.getSingleton().showWarningDialog(
Constant.messages.getString("messagelocationspanel.add.location.warning.locations.overlap"));
return null;
}
}
MessageLocationHighlight highlight = null;
MessageLocationHighlight highlightReference = null;
MessageLocationHighlightsManager highlightsManager = selectMessageLocationsPanel.create();
if (highlightsManager != null) {
highlight = highlightsManager.getHighlight(messageLocation);
highlightReference = selectMessageLocationsPanel.highlight(messageLocation, highlight);
}
T entry = createMessageLocationTableEntry(buttonAddedLocation, messageLocation, highlight, highlightReference);
if (entry == null) {
if (highlightsManager != null) {
selectMessageLocationsPanel.removeHighlight(messageLocation, highlightReference);
}
return null;
}
getModel().addElement(entry);
int row = getTable().convertRowIndexToView(getModel().getRow(entry));
getTable().setRowSelectionInterval(row, row);
return null;
}
protected abstract T createMessageLocationTableEntry(
boolean buttonAddedLocation,
MessageLocation messageLocation,
MessageLocationHighlight highlight,
MessageLocationHighlight highlightReference);
@Override
public T showModifyDialogue(T e) {
return null;
}
@Override
public boolean showRemoveDialogue(T e) {
if (!getRemoveWithoutConfirmationCheckBox().isSelected()) {
if (!showRemoveDialogueImpl(e)) {
return false;
}
}
MessageLocationHighlight highlightReference = e.getHighlightReference();
if (highlightReference != null) {
selectMessageLocationsPanel.removeHighlight(e.getLocation(), highlightReference);
}
return true;
}
protected Component getParentOwner() {
return parent;
}
protected boolean showRemoveDialogueImpl(T e) {
JCheckBox removeWithoutConfirmationCheckBox = new JCheckBox(REMOVE_DIALOG_CHECKBOX_LABEL);
Object[] messages = { REMOVE_DIALOG_TEXT, " ", removeWithoutConfirmationCheckBox };
int option = JOptionPane.showOptionDialog(
View.getSingleton().getMainFrame(),
messages,
REMOVE_DIALOG_TITLE,
JOptionPane.OK_CANCEL_OPTION,
JOptionPane.QUESTION_MESSAGE,
null,
new String[] { REMOVE_DIALOG_CONFIRM_BUTTON_LABEL, REMOVE_DIALOG_CANCEL_BUTTON_LABEL },
null);
if (option == JOptionPane.OK_OPTION) {
getRemoveWithoutConfirmationCheckBox().setSelected(removeWithoutConfirmationCheckBox.isSelected());
return true;
}
return false;
}
@Override
public boolean isRemoveWithoutConfirmation() {
// Force base class to call the method showRemoveDialogue(T1) so the
// state of the panels can be changed before deleting the entries
return false;
}
public void reset() {
for (T entry : getMultipleOptionsModel().getElements()) {
MessageLocationHighlight highlightReference = entry.getHighlightReference();
if (highlightReference != null) {
selectMessageLocationsPanel.removeHighlight(entry.getLocation(), highlightReference);
}
}
getMultipleOptionsModel().clear();
}
@Override
protected S getModel() {
@SuppressWarnings("unchecked")
// Safe cast it's the model that's set in the constructor (which can't be changed)
S model = (S) super.getMultipleOptionsModel();
return model;
}
private static class DefaultMessageLocationHighlightRenderer extends DefaultTableCellRenderer {
private static final long serialVersionUID = 427590735065539815L;
@Override
protected void setValue(Object value) {
}
}
protected class MessageLocationsTable extends JXTable {
private static final long serialVersionUID = -3277532157790764376L;
@Override
public TableCellEditor getCellEditor(int row, int column) {
Class<?> columnClass = AbstractMessageLocationsPanel.this.getModel().getColumnClass(row, column);
if (columnClass != null && MessageLocationHighlight.class.isAssignableFrom(columnClass)) {
@SuppressWarnings("unchecked")
TableCellEditor editor = MessageLocationHighlightRenderersEditors.getInstance().getEditor(
(Class<? extends MessageLocationHighlight>) columnClass);
if (editor != null) {
return editor;
}
}
return super.getCellEditor(row, column);
}
@Override
public TableCellRenderer getCellRenderer(int row, int column) {
Class<?> columnClass = AbstractMessageLocationsPanel.this.getModel().getColumnClass(row, column);
if (columnClass != null && MessageLocationHighlight.class.isAssignableFrom(columnClass)) {
@SuppressWarnings("unchecked")
TableCellRenderer renderer = MessageLocationHighlightRenderersEditors.getInstance().getRenderer(
(Class<? extends MessageLocationHighlight>) columnClass);
if (renderer != null) {
return renderer;
}
}
return super.getCellRenderer(row, column);
}
}
protected class MessageLocationsHighlightChangedListener implements HighlightChangedListener<T> {
@Override
public void highlightChanged(HighlightChangedEvent<T> event) {
T tableEntry = event.getEntry();
MessageLocationHighlight highlightReference = event.getHighlightReference();
if (highlightReference != null) {
selectMessageLocationsPanel.removeHighlight(tableEntry.getLocation(), event.getHighlightReference());
}
MessageLocationHighlight highlight = tableEntry.getHighlight();
if (highlight != null) {
highlightReference = selectMessageLocationsPanel.highlight(tableEntry.getLocation(), tableEntry.getHighlight());
tableEntry.setHighlightReference(highlightReference);
}
};
}
protected class MessageLocationsFocusListener implements MessageLocationProducerFocusListener {
@Override
public void focusLost(MessageLocationProducerFocusEvent e) {
if (e.getFocusEvent().getOppositeComponent() != addButton) {
addButton.setEnabled(false);
}
}
@Override
public void focusGained(MessageLocationProducerFocusEvent e) {
addButton.setEnabled(true);
}
}
}