/* * Zed Attack Proxy (ZAP) and its related class files. * * ZAP is an HTTP/HTTPS proxy for assessing web application security. * * Copyright 2014 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.popup; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.parosproxy.paros.control.Control; import org.parosproxy.paros.control.Control.Mode; import org.parosproxy.paros.network.HttpMessage; import org.zaproxy.zap.extension.ascan.ActiveScanPanel; import org.zaproxy.zap.extension.search.SearchPanel; import org.zaproxy.zap.extension.spider.SpiderPanel; import org.zaproxy.zap.view.popup.PopupMenuUtils; import org.zaproxy.zap.view.messagecontainer.MessageContainer; import org.zaproxy.zap.view.messagecontainer.http.HttpMessageContainer; import org.zaproxy.zap.view.messagecontainer.http.SelectableHttpMessagesContainer; import org.zaproxy.zap.view.messagecontainer.http.SingleHttpMessageContainer; /** * A {@code ExtensionPopupMenuMessageContainer} that exposes the state of {@code HttpMessageContainer}s. * * @since 2.3.0 * @see ExtensionPopupMenuMessageContainer * @see HttpMessageContainer * @see #isEnableForMessageContainer(MessageContainer) */ public class PopupMenuHttpMessageContainer extends ExtensionPopupMenuMessageContainer { private static final long serialVersionUID = -5266647403287261225L; /** * The invokers of the the pop up menu. */ protected static enum Invoker { SITES_PANEL, HISTORY_PANEL, ALERTS_PANEL, ACTIVE_SCANNER_PANEL, SEARCH_PANEL, /** * The panel where spiders' HTTP messages are shown. * * @since 2.5.0 */ SPIDER_PANEL, FUZZER_PANEL, FORCED_BROWSE_PANEL, UNKNOWN }; /** * The current invoker of the menu, {@code null} if none or invalid. */ private Invoker invoker; /** * Flag that indicates that the menu button should be enabled if at least one child is enable for the invoker. * * @see #processExtensionPopupChildren */ private boolean buttonEnabledForEnableChildren; /** * Flag that indicates that the child pop up menus should be notified that the menu is being invoked. */ private boolean processExtensionPopupChildren; /** * Flag that indicates that the menu accepts multiple selections. */ private final boolean multiSelect; /** * Constructs a {@code PopupMenuHttpMessageContainer} with the given label and with no support for multiple selected * messages (the menu button will not be enabled when the invoker has multiple selected messages). * * @param label the label of the menu * @see #setButtonStateOverriddenByChildren(boolean) * @see #setProcessExtensionPopupChildren(boolean) * @see #isEnableForMessageContainer(MessageContainer) */ public PopupMenuHttpMessageContainer(String label) { this(label, false); } /** * Constructs a {@code PopupMenuHttpMessageContainer} with the given label and whether or not the menu supports multiple * selected messages (if {@code false} the menu button will not be enabled when the invoker has multiple selected messages). * * @param label the label of the menu * @param multiSelect {@code true} if the menu supports multiple selected messages, {@code false} otherwise. * @see #setButtonStateOverriddenByChildren(boolean) * @see #setProcessExtensionPopupChildren(boolean) * @see #isEnableForMessageContainer(MessageContainer) */ public PopupMenuHttpMessageContainer(String label, boolean multiSelect) { super(label); invoker = null; buttonEnabledForEnableChildren = true; processExtensionPopupChildren = true; this.multiSelect = multiSelect; } /** * Sets whether or not the menu button enabled state should be overridden by the enable state of child menu items for the * given invoker. * <p> * Default value is {@code true}. * </p> * <p> * The method {@code isProcessExtensionPopupChildren()} must return {@code true}, otherwise this option will have no effect. * </p> * <p> * <strong>Note:</strong> This option overrides the value returned by * {@code isButtonEnabledForHttpMessageContainerState(HttpMessageContainer)}. * </p> * * @param buttonEnabledForEnableChildren {@code true} if the button should be enabled, {@code false} otherwise. * @see #isButtonStateOverriddenByChildren() * @see #isProcessExtensionPopupChildren() * @see #isButtonEnabledForHttpMessageContainerState(HttpMessageContainer) * @see #isEnableForMessageContainer(MessageContainer) * @see ExtensionPopupMenuComponent#isEnableForMessageContainer(MessageContainer) */ public final void setButtonStateOverriddenByChildren(boolean buttonEnabledForEnableChildren) { this.buttonEnabledForEnableChildren = buttonEnabledForEnableChildren; } /** * Tells whether or not the menu button enabled state should be overridden by the enable state of child menu items for the * given invoker. * <p> * The method {@code isProcessExtensionPopupChildren()} must return {@code true}, otherwise this option will have no effect. * * @return {@code true} if the button will be enabled, {@code false} otherwise. * @see #setButtonStateOverriddenByChildren(boolean) * @see #isProcessExtensionPopupChildren() * @see ExtensionPopupMenuComponent#isEnableForMessageContainer(MessageContainer) */ public final boolean isButtonStateOverriddenByChildren() { return buttonEnabledForEnableChildren; } /** * Sets whether or not the children should be notified and processed when the menu is invoked. * <p> * Default value is {@code true}. * * @param processExtensionPopupChildren {@code true} if the children should be notified and processed, {@code false} * otherwise. * @see #isProcessExtensionPopupChildren() * @see #setButtonStateOverriddenByChildren(boolean) * @see #isEnableForMessageContainer(MessageContainer) * @see ExtensionPopupMenuComponent#isEnableForMessageContainer(MessageContainer) */ public final void setProcessExtensionPopupChildren(boolean processExtensionPopupChildren) { this.processExtensionPopupChildren = processExtensionPopupChildren; } /** * Tells whether or not the children should be notified and processed when the menu is invoked. * * @return {@code true} if the children will be notified and processed, {@code false} otherwise. * @see #setProcessExtensionPopupChildren(boolean) */ public final boolean isProcessExtensionPopupChildren() { return processExtensionPopupChildren; } /** * Tells whether or not the menu supports multiple selected messages. If {@code false} the menu button will not be enabled * when the invoker has multiple selected messages. * * @return {@code true} if the menu supports multiple selected messages, {@code false} otherwise. * @see #isButtonEnabledForNumberOfSelectedMessages(int) */ public final boolean isMultiSelect() { return multiSelect; } /** * Returns the invoker of the pop up menu item. * * @return the invoker or {@code null} if has not been invoked or not a valid invoker. */ protected final Invoker getInvoker() { return invoker; } /** * To determine if the menu is enable for the given message container following steps are done: * <ol> * <li>Check if message container is {@code HttpMessageContainer}, if not returns immediately with {@code false};</li> * <li>Call the method {@code isEnable(HttpMessageContainer)}, if it doesn't return {@code true} the method returns * immediately with {@code false};</li> * <li>Call the method {@code isEnableForInvoker(Invoker, HttpMessageContainer)}, if it doesn't return {@code true} the * method returns immediately with {@code false}.</li> * </ol> * Otherwise the menu will be enable for the given message container. * <p> * To determine the menu's button enabled state the following steps are performed: * <ol> * <li>If {@code isProcessExtensionPopupChildren()} and {@code isButtonStateOverriddenByChildren()} return true, use the * value returned from notifying and processing the child menus;</li> * <li>Otherwise call the method {@code isButtonEnabledForHttpMessageContainerState(HttpMessageContainer)} and use the * returned value.</li> * </ol> * <strong>Note:</strong> If the menu is declared as not safe ({@code isSafe()}) the button will be disabled if in * {@code Mode.Safe} or if in {@code Mode.Protected} and not all the selected messages are in scope. * <h3>Notifying and processing child menus</h3> * <p> * When the method {@code isProcessExtensionPopupChildren()} returns true, the method * {@code isEnableForComponent(Component)} is called on all child {@code ExtensionPopupMenuComponent}s. * </p> * <p> * All the child menus that implement {@code ExtensionPopupMenuComponent} will have the methods * {@code precedeWithSeparator()}, {@code succeedWithSeparator()}, {@code getMenuIndex()} and {@code isSafe()} honoured, * with the following caveats: * <ul> * <li>{@code precedeWithSeparator()} - the separator will only be added if there's already a menu component in the menu and * if it is not a separator;</li> * <li>{@code succeedWithSeparator()} - the separator will be added always but removed if there's no item following it when * the menu is ready to be shown;</li> * <li>{@code getMenuIndex()} - the menu index will be honoured only if the method {@code isOrderChildren()} returns * {@code true};</li> * </ul> * The separators will be dynamically added and removed as needed when the pop up menu is shown. * <p> * <strong>Note:</strong> Override of this method should be done with extra care as it might break all the expected * functionality. * </p> * * @see #isEnable(HttpMessageContainer) * @see #isEnableForInvoker(Invoker, HttpMessageContainer) * @see #getInvoker(HttpMessageContainer) * @see #isProcessExtensionPopupChildren() * @see #isButtonStateOverriddenByChildren() * @see #isButtonEnabledForHttpMessageContainerState(HttpMessageContainer) * @see #isSafe() * @see Mode */ @Override public boolean isEnableForMessageContainer(MessageContainer<?> messageContainer) { invoker = null; if (!(messageContainer instanceof HttpMessageContainer)) { return false; } HttpMessageContainer httpMessageContainer = (HttpMessageContainer) messageContainer; if (!isEnable(httpMessageContainer)) { return false; } invoker = getInvoker(httpMessageContainer); if (!isEnableForInvoker(invoker, httpMessageContainer)) { invoker = null; return false; } boolean enabled = false; if (isProcessExtensionPopupChildren()) { boolean childrenEnable = processExtensionPopupChildren(PopupMenuUtils.getPopupMenuInvokerWrapper(httpMessageContainer)); if (isButtonStateOverriddenByChildren()) { enabled = childrenEnable; } } if (!isProcessExtensionPopupChildren() || (isProcessExtensionPopupChildren() && !isButtonStateOverriddenByChildren())) { enabled = isButtonEnabledForHttpMessageContainerState(httpMessageContainer); } if (enabled && !isSafe()) { Mode mode = Control.getSingleton().getMode(); if (mode.equals(Mode.protect)) { enabled = isSelectedMessagesInSessionScope(httpMessageContainer); } else if (mode.equals(Mode.safe)) { enabled = false; } } setEnabled(enabled); return true; } /** * Tells whether or not the menu is enable for the given HTTP message container. * <p> * By default is enable for {@code SingleHttpMessageContainer}s and {@code SelectableHttpMessagesContainer}s. * </p> * <p> * Normally overridden if other implementations of {@code HttpMessageContainer} are supported. The methods * {@code getSelectedMessages(HttpMessageContainer)} and {@code getNumberOfSelectedMessages(HttpMessageContainer)} might * need to be overridden accordingly to the supported implementations. * </p> * * @param httpMessageContainer the message container that will be evaluated * @return {@code true} if the given message container is a {@code SingleHttpMessageContainer} or * {@code SelectableHttpMessagesContainer}. * @see #getSelectedMessages(HttpMessageContainer) * @see #getNumberOfSelectedMessages(HttpMessageContainer) * @see SingleHttpMessageContainer * @see SelectableHttpMessagesContainer */ protected boolean isEnable(HttpMessageContainer httpMessageContainer) { if (httpMessageContainer instanceof SelectableHttpMessagesContainer || httpMessageContainer instanceof SingleHttpMessageContainer) { return true; } return false; } /** * Returns the {@code Invoker} behind the message container. * * @param httpMessageContainer the message container that will be evaluated * @return the invoker or {@code Invoker#UNKNOWN} if the message container was not identified. * @see Invoker */ protected final static Invoker getInvoker(HttpMessageContainer httpMessageContainer) { Invoker invoker; switch (httpMessageContainer.getName()) { case "History Table": invoker = Invoker.HISTORY_PANEL; break; case "treeSite": invoker = Invoker.SITES_PANEL; break; case "treeAlert": invoker = Invoker.ALERTS_PANEL; break; case SearchPanel.HTTP_MESSAGE_CONTAINER_NAME: invoker = Invoker.SEARCH_PANEL; break; case SpiderPanel.HTTP_MESSAGE_CONTAINER_NAME: invoker = Invoker.SPIDER_PANEL; break; case ActiveScanPanel.MESSAGE_CONTAINER_NAME: invoker = Invoker.ACTIVE_SCANNER_PANEL; break; case "fuzz.httpfuzzerResultsContentPanel": invoker = Invoker.FUZZER_PANEL; break; case "ForcedBrowseMessageContainer": invoker = Invoker.FORCED_BROWSE_PANEL; break; default: invoker = Invoker.UNKNOWN; } return invoker; } /** * Tells whether or not the menu is enable for the given invoker (or optionally the given message container). * <p> * By default, the menu is enable for all invokers. * </p> * <p> * The message container can be used to identify {@code Invoker#UNKNOWN} invokers by using its name or component. No hard * reference should be kept to the message container. * </p> * * @param invoker the invoker * @param httpMessageContainer the message container of the invoker * @return {@code true} if the menu is enable for the given invoker. */ protected boolean isEnableForInvoker(Invoker invoker, HttpMessageContainer httpMessageContainer) { return true; } /** * Tells whether or not the button should be enabled for the state of the given message container. Called from * {@code isEnableForMessageContainer(MessageContainer)} when {@code isButtonStateOverriddenByChildren()} returns * {@code false}. * <p> * By default, is only enabled if both methods {@code isButtonEnabledForNumberOfSelectedMessages(HttpMessageContainer)} and * {@code isButtonEnabledForSelectedMessages(HttpMessageContainer)} (called only if the former method returns {@code true}) * return {@code true}.<br> * </p> * <p> * Not normally overridden. * </p> * * @param httpMessageContainer the message container that will be evaluated * @return {@code true} if the button should be enabled for the message container, {@code false} otherwise. * @see #isEnableForMessageContainer(MessageContainer) * @see #isButtonStateOverriddenByChildren() * @see #isButtonEnabledForNumberOfSelectedMessages(HttpMessageContainer) * @see #isButtonEnabledForSelectedMessages(HttpMessageContainer) */ protected boolean isButtonEnabledForHttpMessageContainerState(HttpMessageContainer httpMessageContainer) { boolean enabled = isButtonEnabledForNumberOfSelectedMessages(httpMessageContainer); if (enabled) { enabled = isButtonEnabledForSelectedMessages(httpMessageContainer); } return enabled; } /** * Tells whether or not the button should be enabled for the number of selected messages of the given message container. * <p> * Defaults to call the method {@code isButtonEnabledForNumberOfSelectedMessages(int)} with the number of selected messages * obtained by calling the method {@code getNumberOfSelectedMessages(HttpMessageContainer)}, with the given message * container as parameter. * </p> * <p> * Normally overridden if other implementations of {@code HttpMessageContainer} are supported. * </p> * * @param httpMessageContainer the container that will be evaluated * @return {@code true} if the button should be enabled for the number of selected message, {@code false} otherwise. * @see #isButtonEnabledForNumberOfSelectedMessages(int) * @see #getNumberOfSelectedMessages(HttpMessageContainer) */ protected boolean isButtonEnabledForNumberOfSelectedMessages(HttpMessageContainer httpMessageContainer) { return isButtonEnabledForNumberOfSelectedMessages(getNumberOfSelectedMessages(httpMessageContainer)); } /** * Tells whether or not the button should be enabled for the selected messages of the given message container. * <p> * Defaults to call the method {@code isButtonEnabledForSelectedMessages(List)} with the selected messages obtained by * calling the method {@code getSelectedMessages(HttpMessageContainer)}, with the given message container as parameter. * </p> * <p> * Normally overridden if other implementations of {@code HttpMessageContainer} are supported. * </p> * * @param httpMessageContainer the container that will be evaluated * @return {@code true} if the button should be enabled for the selected messages, {@code false} otherwise. * @see #isButtonEnabledForSelectedMessages(List) * @see #getSelectedMessages(HttpMessageContainer) */ protected boolean isButtonEnabledForSelectedMessages(HttpMessageContainer httpMessageContainer) { return isButtonEnabledForSelectedMessages(getSelectedMessages(httpMessageContainer)); } /** * Returns the number of selected messages of the given message container. * <p> * By default it returns the number of selected messages from {@code SelectableHttpMessagesContainer}s and for * {@code SingleHttpMessageContainer}s returns 1 if it contains a message, 0 otherwise. * </p> * <p> * Normally overridden if other implementations of {@code HttpMessageContainer} are supported. Default are * {@code SingleHttpMessageContainer} and {@code SelectableHttpMessagesContainer}. * </p> * * @param httpMessageContainer the container that will be evaluated * @return the number of selected messages in the message container * @see SingleHttpMessageContainer * @see SelectableHttpMessagesContainer */ protected int getNumberOfSelectedMessages(HttpMessageContainer httpMessageContainer) { if (httpMessageContainer instanceof SelectableHttpMessagesContainer) { return ((SelectableHttpMessagesContainer) httpMessageContainer).getNumberOfSelectedMessages(); } else if (httpMessageContainer instanceof SingleHttpMessageContainer) { return ((SingleHttpMessageContainer) httpMessageContainer).isEmpty() ? 0 : 1; } return 0; } /** * Tells whether or not the button should be enabled for the given number of selected messages. * <p> * By default, it returns {@code false} when there are no messages selected or when {@code isMultiSelect()} returns * {@code true} and there is more than one message selected. * </p> * <p> * If overridden the method {@code isMultiSelect()} should be taken into account. * <p> * * @param numberOfSelectedMessages the number of selected messages in the message container * @return {@code true} if the button should be enabled for the given number of selected messages, {@code false} otherwise. * @see #isMultiSelect() */ protected boolean isButtonEnabledForNumberOfSelectedMessages(int numberOfSelectedMessages) { if (numberOfSelectedMessages == 0) { return false; } else if (numberOfSelectedMessages > 1 && !isMultiSelect()) { return false; } return true; } /** * Returns the selected messages of the given message container. * <p> * By default it returns the selected messages from {@code SelectableHttpMessagesContainer}s and for * {@code SingleHttpMessageContainer}s returns the contained message or empty {@code List} if none. * </p> * <p> * Normally overridden if other implementations of {@code HttpMessageContainer} are supported. Default are * {@code SingleHttpMessageContainer} and {@code SelectableHttpMessagesContainer}. * </p> * * @param httpMessageContainer the container that will be evaluated * @return a {@code List} containing the selected messages * @see #isButtonEnabledForSelectedMessages(List) * @see #isSelectedMessagesInSessionScope(HttpMessageContainer) * @see SingleHttpMessageContainer * @see SelectableHttpMessagesContainer */ protected List<HttpMessage> getSelectedMessages(HttpMessageContainer httpMessageContainer) { if (httpMessageContainer instanceof SelectableHttpMessagesContainer) { return ((SelectableHttpMessagesContainer) httpMessageContainer).getSelectedMessages(); } else if (httpMessageContainer instanceof SingleHttpMessageContainer) { SingleHttpMessageContainer singleContainer = (SingleHttpMessageContainer) httpMessageContainer; if (!singleContainer.isEmpty()) { List<HttpMessage> selectedHttpMessages = new ArrayList<>(1); selectedHttpMessages.add(singleContainer.getMessage()); return selectedHttpMessages; } } return Collections.emptyList(); } /** * Tells whether or not the button should be enabled for the given selected messages. * <p> * By default, it returns {@code true} unless the method {@code isButtonEnabledForSelectedHttpMessage(HttpMessage)} returns * false for one of the selected messages. * </p> * * @param httpMessages the selected messages in the message container * @return {@code true} if the button should be enabled for the given selected messages, {@code false} otherwise. * @see #isButtonEnabledForSelectedHttpMessage(HttpMessage) */ protected boolean isButtonEnabledForSelectedMessages(List<HttpMessage> httpMessages) { for (HttpMessage httpMessage : httpMessages) { if (httpMessage != null && !isButtonEnabledForSelectedHttpMessage(httpMessage)) { return false; } } return true; } /** * Tells whether or not the button should be enabled for the given selected message. * <p> * By default, it returns {@code true}. * * @param httpMessage the selected message, never {@code null} * @return {@code true} if the button should be enabled for the given selected message, {@code false} otherwise. */ protected boolean isButtonEnabledForSelectedHttpMessage(HttpMessage httpMessage) { return true; } /** * Tells whether or not the selected messages of the given message container are in scope. * <p> * By default, the selected messages are obtained by calling the method getSelectedMessages(httpMessageContainer) with the * given message container as parameter and for each selected message is called the method {@code HttpMessage#isInScope()}. * </p> * <p> * Normally overridden if other implementations of {@code HttpMessageContainer} are supported. Default are * {@code SingleHttpMessageContainer} and {@code SelectableHttpMessagesContainer}. * </p> * * @param httpMessageContainer the container that will be evaluated * @return {@code true} if all the selected messages are in scope, {@code false} otherwise. * @see #isEnableForMessageContainer(MessageContainer) * @see HttpMessage#isInScope() */ protected boolean isSelectedMessagesInSessionScope(HttpMessageContainer httpMessageContainer) { for (HttpMessage httpMessage : getSelectedMessages(httpMessageContainer)) { if (httpMessage != null && !httpMessage.isInScope()) { return false; } } return true; } }