/* * 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. * * Note that this extension ane the other classes in this package are heavily * based on the orriginal Paros ExtensionSpider! */ package org.zaproxy.zap.view; import java.awt.CardLayout; import java.awt.Dimension; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.List; import java.util.regex.Pattern; import javax.swing.JCheckBox; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.SortOrder; import org.parosproxy.paros.Constant; import org.parosproxy.paros.model.Session; import org.parosproxy.paros.view.View; import org.zaproxy.zap.model.Context; import org.zaproxy.zap.model.ParameterParser; import org.zaproxy.zap.model.StandardParameterParser; import org.zaproxy.zap.model.StructuralNodeModifier; import org.zaproxy.zap.utils.ZapTextField; public class ContextStructurePanel extends AbstractContextPropertiesPanel { private static final String PANEL_NAME = Constant.messages.getString("context.struct.title"); private static final long serialVersionUID = -1; private StructuralNodeModifiersTableModel ddnTableModel; private JPanel panelSession = null; private ZapTextField urlKvPairSeparators = null; private ZapTextField urlKeyValueSeparators = null; private ZapTextField postKeyValueSeparators = null; private ZapTextField postKvPairSeparators = null; /** * Returns the name of the panel "Structure" for the given context index. * * @param contextId the context index that will be used to create the name of the panel * @return the name of the panel "Include in context" for the given context index * @since 2.2.0 * @see Context#getIndex() */ public static String getPanelName(int contextId) { // Panel names hav to be unique, so prefix with the context id return contextId + ": " + PANEL_NAME; } /** * Constructs a {@code ContextStructurePanel} for the given context. * * @param context the target context, must not be {@code null}. */ public ContextStructurePanel(Context context) { super(context.getIndex()); this.setLayout(new CardLayout()); this.setName(getPanelName(this.getContextIndex())); this.add(getPanel(), getPanel().getName()); } private JPanel getPanel() { if (panelSession == null) { /* +----------------+-----------------------------------------+ | | + Contexts | 1: Structure | | | + Include | | | | + Exclude | URL Key value pair delimiters [ & ] | | | + Structure | URL Key value delimiters [ = ] | | | | POST Key value pair delimiters [ & ] | | | | POST Key value delimiters [ = ] | | | | Structural Modifiers: | | | | +-----------------------------+ | | | | | | [ Add ] | | | | | | [ Mod ] | | | | | | [ Rem ] | | | | | | | | | | | | | | | | +-----------------------------+ | */ panelSession = new JPanel(); panelSession.setLayout(new GridBagLayout()); panelSession.setName("SessionStructure"); panelSession.setLayout(new GridBagLayout()); panelSession.add(new JLabel(Constant.messages.getString("context.struct.label.url.kvpsep")), LayoutHelper.getGBC(0, 0, 1, 1.0D)); panelSession.add(getUrlKvPairSeparators(), LayoutHelper.getGBC(1, 0, 1, 1.0D, new Insets(2, 0, 2, 0))); panelSession.add(new JLabel(Constant.messages.getString("context.struct.label.url.kvsep")), LayoutHelper.getGBC(0, 1, 1, 1.0D)); panelSession.add(getUrlKeyValueSeparators(), LayoutHelper.getGBC(1, 1, 1, 1.0D, new Insets(2, 0, 2, 0))); panelSession.add(new JLabel(Constant.messages.getString("context.struct.label.post.kvpsep")), LayoutHelper.getGBC(0, 2, 1, 1.0D)); panelSession.add(getPostKvPairSeparators(), LayoutHelper.getGBC(1, 2, 1, 1.0D, new Insets(2, 0, 2, 0))); panelSession.add(new JLabel(Constant.messages.getString("context.struct.label.post.kvsep")), LayoutHelper.getGBC(0, 3, 1, 1.0D)); panelSession.add(getPostKeyValueSeparators(), LayoutHelper.getGBC(1, 3, 1, 1.0D, new Insets(2, 0, 2, 0))); panelSession.add(new JLabel(Constant.messages.getString("context.struct.label.struct")), LayoutHelper.getGBC(0, 4, 1, 1.0D)); ddnTableModel = new StructuralNodeModifiersTableModel(); DataDrivenNodesMultipleOptionsPanel ddnOptionsPanel = new DataDrivenNodesMultipleOptionsPanel(ddnTableModel); panelSession.add(ddnOptionsPanel, LayoutHelper.getGBC(0, 5, 2, 1.0d, 1.0d)); } return panelSession; } private ZapTextField getUrlKvPairSeparators() { if (urlKvPairSeparators == null) { urlKvPairSeparators = new ZapTextField(); } return urlKvPairSeparators; } private ZapTextField getUrlKeyValueSeparators() { if (urlKeyValueSeparators == null) { urlKeyValueSeparators = new ZapTextField(); } return urlKeyValueSeparators; } private ZapTextField getPostKeyValueSeparators() { if (postKeyValueSeparators == null) { postKeyValueSeparators = new ZapTextField(); } return postKeyValueSeparators; } private ZapTextField getPostKvPairSeparators() { if (postKvPairSeparators == null) { postKvPairSeparators = new ZapTextField(); } return postKvPairSeparators; } @Override public void initContextData(Session session, Context context) { ParameterParser urlParamParser = context.getUrlParamParser(); ParameterParser formParamParser = context.getPostParamParser(); this.ddnTableModel.setStructuralNodeModifiers(context.getDataDrivenNodes()); if (urlParamParser instanceof StandardParameterParser) { StandardParameterParser urlStdParamParser = (StandardParameterParser) urlParamParser; this.getUrlKvPairSeparators().setText(urlStdParamParser.getKeyValuePairSeparators()); this.getUrlKeyValueSeparators().setText(urlStdParamParser.getKeyValueSeparators()); for (String structParam : urlStdParamParser.getStructuralParameters()) { this.ddnTableModel.addStructuralNodeModifier( new StructuralNodeModifier( StructuralNodeModifier.Type.StructuralParameter, null, structParam)); } } if (formParamParser instanceof StandardParameterParser) { StandardParameterParser formStdParamParser = (StandardParameterParser) formParamParser; this.getPostKvPairSeparators().setText(formStdParamParser.getKeyValuePairSeparators()); this.getPostKeyValueSeparators().setText(formStdParamParser.getKeyValueSeparators()); } } @Override public void validateContextData(Session session) throws Exception { if (this.urlKvPairSeparators.getText().length() == 0) { throw new IllegalArgumentException(Constant.messages.getString("context.struct.warning.stdparser.nokvpsep")); } if (this.urlKeyValueSeparators.getText().length() == 0) { throw new IllegalArgumentException(Constant.messages.getString("context.struct.warning.stdparser.nokvsep")); } // Don't allow any common characters for (char ch : this.urlKvPairSeparators.getText().toCharArray()) { if (this.urlKeyValueSeparators.getText().contains("" + ch)) { throw new IllegalArgumentException(Constant.messages.getString("context.struct.warning.stdparser.dup")); } } if (this.postKvPairSeparators.getText().length() == 0) { throw new IllegalArgumentException(Constant.messages.getString("context.struct.warning.stdparser.nokvpsep")); } if (this.postKeyValueSeparators.getText().length() == 0) { throw new IllegalArgumentException(Constant.messages.getString("context.struct.warning.stdparser.nokvsep")); } // Don't allow any common characters for (char ch : this.postKvPairSeparators.getText().toCharArray()) { if (this.postKeyValueSeparators.getText().contains("" + ch)) { throw new IllegalArgumentException(Constant.messages.getString("context.struct.warning.stdparser.dup")); } } } /** * Save the data from this panel to the provided context. * * @param context the context * @param updateSiteStructure {@code true} if the nodes of the context should be restructured, {@code false} otherwise * @see Context#restructureSiteTree() */ private void saveToContext(Context context, boolean updateSiteStructure) { ParameterParser urlParamParser = context.getUrlParamParser(); ParameterParser formParamParser = context.getPostParamParser(); List<String> structParams = new ArrayList<String>(); List<StructuralNodeModifier> ddns = new ArrayList<StructuralNodeModifier>(); for (StructuralNodeModifier snm : this.ddnTableModel.getElements()) { if (snm.getType().equals(StructuralNodeModifier.Type.StructuralParameter)) { structParams.add(snm.getName()); } else { ddns.add(snm); } } if (urlParamParser instanceof StandardParameterParser) { StandardParameterParser urlStdParamParser = (StandardParameterParser) urlParamParser; urlStdParamParser.setKeyValuePairSeparators(this.getUrlKvPairSeparators().getText()); urlStdParamParser.setKeyValueSeparators(this.getUrlKeyValueSeparators().getText()); urlStdParamParser.setStructuralParameters(structParams); context.setUrlParamParser(urlStdParamParser); urlStdParamParser.setContext(context); } if (formParamParser instanceof StandardParameterParser) { StandardParameterParser formStdParamParser = (StandardParameterParser) formParamParser; formStdParamParser.setKeyValuePairSeparators(this.getPostKvPairSeparators().getText()); formStdParamParser.setKeyValueSeparators(this.getPostKeyValueSeparators().getText()); context.setPostParamParser(formStdParamParser); formStdParamParser.setContext(context); } context.setDataDrivenNodes(ddns); if (updateSiteStructure) { context.restructureSiteTree(); } } @Override public void saveContextData(Session session) throws Exception { Context context = session.getContext(getContextIndex()); saveToContext(context, true); } @Override public void saveTemporaryContextData(Context uiSharedContext) { saveToContext(uiSharedContext, false); } @Override public String getHelpIndex() { return "ui.dialogs.context-struct"; } public static class DataDrivenNodesMultipleOptionsPanel extends AbstractMultipleOptionsBaseTablePanel<StructuralNodeModifier> { private static final long serialVersionUID = -7216673905642941770L; private static final String REMOVE_DIALOG_TITLE = Constant.messages.getString("context.ddn.dialog.remove.title"); private static final String REMOVE_DIALOG_TEXT = Constant.messages.getString("context.ddn.dialog.remove.text"); private static final String REMOVE_DIALOG_CONFIRM_BUTTON_LABEL = Constant.messages.getString("all.button.remove"); private static final String REMOVE_DIALOG_CANCEL_BUTTON_LABEL = Constant.messages.getString("all.button.cancel"); private static final String REMOVE_DIALOG_CHECKBOX_LABEL = Constant.messages.getString("all.prompt.dontshow"); public DataDrivenNodesMultipleOptionsPanel(StructuralNodeModifiersTableModel model) { super(model); getTable().getColumnExt(0).setPreferredWidth(50); getTable().getColumnExt(1).setPreferredWidth(50); getTable().getColumnExt(2).setPreferredWidth(200); getTable().setSortOrder(1, SortOrder.ASCENDING); } @Override public StructuralNodeModifier showAddDialogue() { StructuralModifierDialog ddnDialog = new StructuralModifierDialog( View.getSingleton().getSessionDialog(), "context.ddn.dialog.add.title", new Dimension(500, 200)); return ddnDialog.showDialog(null); } @Override public StructuralNodeModifier showModifyDialogue(StructuralNodeModifier ddn) { StructuralModifierDialog ddnDialog = new StructuralModifierDialog( View.getSingleton().getSessionDialog(), "context.ddn.dialog.modify.title", new Dimension(500, 200)); return ddnDialog.showDialog(ddn); } @Override public boolean showRemoveDialogue(StructuralNodeModifier 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) { setRemoveWithoutConfirmation(removeWithoutConfirmationCheckBox.isSelected()); return true; } return false; } } public static class StructuralModifierDialog extends StandardFieldsDialog { private static final long serialVersionUID = 1L; private static final String FIELD_TYPE = "context.ddn.dialog.type"; private static final String FIELD_NAME = "context.ddn.dialog.name"; private static final String FIELD_REGEX = "context.ddn.dialog.regex"; private static final String VALUE_TYPE_DATA = "context.ddn.dialog.type.data"; private static final String VALUE_TYPE_STRUCT = "context.ddn.dialog.type.struct"; private StructuralNodeModifier.Type type = StructuralNodeModifier.Type.StructuralParameter; private StructuralNodeModifier ddn = null; private boolean ro = false; public StructuralModifierDialog(JDialog owner, String titleLabel, Dimension dim) { super(owner, titleLabel, dim, true); } public StructuralNodeModifier showDialog(StructuralNodeModifier ddn) { String regex = ""; String name = ""; this.ddn = ddn; if (ddn != null) { type = ddn.getType(); if (ddn.getPattern() != null) { regex = ddn.getPattern().pattern(); } name = ddn.getName(); ro = true; this.addReadOnlyField(FIELD_NAME, getModVal(type), false); } else { this.addComboField(FIELD_TYPE, new String [] { Constant.messages.getString(VALUE_TYPE_STRUCT), Constant.messages.getString(VALUE_TYPE_DATA)}, getModVal(type)); this.addFieldListener(FIELD_TYPE, new ActionListener() { @Override public void actionPerformed(ActionEvent e) { setFieldStates(); }}); } this.addTextField(FIELD_NAME, name); this.addTextField(FIELD_REGEX, regex); setFieldStates(); this.setVisible(true); return this.ddn; } private void setFieldStates() { if (! ro) { if (Constant.messages.getString(VALUE_TYPE_STRUCT).equals( this.getStringValue(FIELD_TYPE))) { type = StructuralNodeModifier.Type.StructuralParameter; } else { type = StructuralNodeModifier.Type.DataDrivenNode; } } this.getField(FIELD_REGEX).setEnabled( StructuralNodeModifier.Type.DataDrivenNode.equals(type)); } private String getModVal(StructuralNodeModifier.Type type) { switch (type) { case StructuralParameter: return Constant.messages.getString(VALUE_TYPE_STRUCT); case DataDrivenNode: return Constant.messages.getString(VALUE_TYPE_DATA); } return ""; } @Override public void save() { ddn = new StructuralNodeModifier( type, Pattern.compile(this.getStringValue(FIELD_REGEX)), this.getStringValue(FIELD_NAME)); } @Override public String validateFields() { if (! this.getStringValue(FIELD_NAME).matches("[A-Za-z0-9_]+")) { // Must supply a name just made up of alphanumeric characters return Constant.messages.getString("context.ddn.dialog.error.name"); } if (StructuralNodeModifier.Type.DataDrivenNode.equals(type)) { if (this.isEmptyField(FIELD_REGEX)) { return Constant.messages.getString("context.ddn.dialog.error.regex"); } if (! this.getStringValue(FIELD_REGEX).matches(".*\\(.*\\).*\\(.*\\).*")) { // We need at least 2 groups return Constant.messages.getString("context.ddn.dialog.error.regex"); } try { Pattern.compile(this.getStringValue(FIELD_REGEX)); } catch (Exception e) { // Not a valid regex expression return Constant.messages.getString("context.ddn.dialog.error.regex"); } } return null; } } }