diff --git a/addOns/brk/CHANGELOG.md b/addOns/brk/CHANGELOG.md
new file mode 100644
index 00000000000..35236720953
--- /dev/null
+++ b/addOns/brk/CHANGELOG.md
@@ -0,0 +1,7 @@
+# Changelog
+All notable changes to this add-on will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
+
+## Unreleased
+
diff --git a/addOns/brk/brk.gradle.kts b/addOns/brk/brk.gradle.kts
new file mode 100644
index 00000000000..1a6d7ed04df
--- /dev/null
+++ b/addOns/brk/brk.gradle.kts
@@ -0,0 +1,13 @@
+description = "Allows you to add breakpoints"
+
+zapAddOn {
+ addOnName.set("brk")
+ manifest {
+ author.set("ZAP Dev Team")
+ url.set("https://www.zaproxy.org/docs/desktop/addons/brk/")
+ }
+}
+
+dependencies {
+ testImplementation(project(":testutils"))
+}
diff --git a/addOns/brk/gradle.properties b/addOns/brk/gradle.properties
new file mode 100644
index 00000000000..0ea36b1d480
--- /dev/null
+++ b/addOns/brk/gradle.properties
@@ -0,0 +1,2 @@
+version=0.1.0
+release=false
diff --git a/addOns/brk/src/main/java/org/zaproxy/addon/brk/AbstractBreakPointMessage.java b/addOns/brk/src/main/java/org/zaproxy/addon/brk/AbstractBreakPointMessage.java
new file mode 100644
index 00000000000..2ba8da5d7a9
--- /dev/null
+++ b/addOns/brk/src/main/java/org/zaproxy/addon/brk/AbstractBreakPointMessage.java
@@ -0,0 +1,35 @@
+/*
+ * Zed Attack Proxy (ZAP) and its related class files.
+ *
+ * ZAP is an HTTP/HTTPS proxy for assessing web application security.
+ *
+ * Copyright 2012 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.addon.brk;
+
+public abstract class AbstractBreakPointMessage implements BreakpointMessageInterface {
+
+ private boolean isEnabled = true;
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ this.isEnabled = enabled;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return isEnabled;
+ }
+}
diff --git a/addOns/brk/src/main/java/org/zaproxy/addon/brk/BreakAPI.java b/addOns/brk/src/main/java/org/zaproxy/addon/brk/BreakAPI.java
new file mode 100644
index 00000000000..2e0b73fd600
--- /dev/null
+++ b/addOns/brk/src/main/java/org/zaproxy/addon/brk/BreakAPI.java
@@ -0,0 +1,353 @@
+/*
+ * Zed Attack Proxy (ZAP) and its related class files.
+ *
+ * ZAP is an HTTP/HTTPS proxy for assessing web application security.
+ *
+ * Copyright 2013 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.addon.brk;
+
+import java.io.IOException;
+import net.sf.json.JSONObject;
+import org.parosproxy.paros.network.HttpHeader;
+import org.parosproxy.paros.network.HttpInputStream;
+import org.parosproxy.paros.network.HttpMalformedHeaderException;
+import org.parosproxy.paros.network.HttpMessage;
+import org.parosproxy.paros.network.HttpOutputStream;
+import org.zaproxy.zap.extension.api.API;
+import org.zaproxy.zap.extension.api.ApiAction;
+import org.zaproxy.zap.extension.api.ApiException;
+import org.zaproxy.zap.extension.api.ApiImplementor;
+import org.zaproxy.zap.extension.api.ApiPersistentConnection;
+import org.zaproxy.zap.extension.api.ApiResponse;
+import org.zaproxy.zap.extension.api.ApiResponseElement;
+import org.zaproxy.zap.extension.api.ApiView;
+import org.zaproxy.zap.extension.httppanel.Message;
+import org.zaproxy.zap.utils.ApiUtils;
+
+public class BreakAPI extends ApiImplementor {
+
+ private static final String PREFIX = "break";
+
+ private static final String ACTION_BREAK = "break";
+ private static final String ACTION_BREAK_ON_ID = "breakOnId";
+ private static final String ACTION_ADD_HTTP_BREAK_POINT = "addHttpBreakpoint";
+ private static final String ACTION_REM_HTTP_BREAK_POINT = "removeHttpBreakpoint";
+ private static final String ACTION_CONTINUE = "continue";
+ private static final String ACTION_STEP = "step";
+ private static final String ACTION_DROP = "drop";
+ private static final String ACTION_SET_HTTP_MESSAGE = "setHttpMessage";
+
+ private static final String VIEW_IS_BREAK_ALL = "isBreakAll";
+ private static final String VIEW_IS_BREAK_REQUEST = "isBreakRequest";
+ private static final String VIEW_IS_BREAK_RESPONSE = "isBreakResponse";
+ private static final String VIEW_HTTP_MESSAGE = "httpMessage";
+
+ private static final String PCONN_WAIT_FOR_HTTP_BREAK = "waitForHttpBreak";
+
+ private static final String PARAM_STRING = "string";
+ private static final String PARAM_LOCATION = "location";
+ private static final String PARAM_MATCH = "match";
+ private static final String PARAM_INVERSE = "inverse";
+ private static final String PARAM_IGNORECASE = "ignorecase";
+ private static final String PARAM_KEY = "key";
+ private static final String PARAM_SCOPE = "scope";
+ private static final String PARAM_STATE = "state";
+ private static final String PARAM_TYPE = "type";
+ private static final String PARAM_HTTP_HEADER = "httpHeader";
+ private static final String PARAM_HTTP_BODY = "httpBody";
+ private static final String PARAM_POLL = "poll";
+ private static final String PARAM_KEEP_ALIVE = "keepalive";
+
+ private static final String VALUE_TYPE_HTTP_ALL = "http-all";
+ private static final String VALUE_TYPE_HTTP_REQUESTS = "http-requests";
+ private static final String VALUE_TYPE_HTTP_RESPONSES = "http-responses";
+
+ private ExtensionBreak extension = null;
+
+ public BreakAPI(ExtensionBreak ext) {
+ extension = ext;
+
+ this.addApiView(new ApiView(VIEW_IS_BREAK_ALL));
+ this.addApiView(new ApiView(VIEW_IS_BREAK_REQUEST));
+ this.addApiView(new ApiView(VIEW_IS_BREAK_RESPONSE));
+ this.addApiView(new ApiView(VIEW_HTTP_MESSAGE));
+
+ this.addApiAction(
+ new ApiAction(
+ ACTION_BREAK,
+ new String[] {PARAM_TYPE, PARAM_STATE},
+ new String[] {
+ PARAM_SCOPE
+ })); // Not currently used but kept for compatibility purposes
+ this.addApiAction(
+ new ApiAction(
+ ACTION_SET_HTTP_MESSAGE,
+ new String[] {PARAM_HTTP_HEADER},
+ new String[] {PARAM_HTTP_BODY}));
+ this.addApiAction(new ApiAction(ACTION_CONTINUE));
+ this.addApiAction(new ApiAction(ACTION_STEP));
+ this.addApiAction(new ApiAction(ACTION_DROP));
+ this.addApiAction(
+ new ApiAction(
+ ACTION_ADD_HTTP_BREAK_POINT,
+ new String[] {
+ PARAM_STRING,
+ PARAM_LOCATION,
+ PARAM_MATCH,
+ PARAM_INVERSE,
+ PARAM_IGNORECASE
+ }));
+ this.addApiAction(
+ new ApiAction(
+ ACTION_REM_HTTP_BREAK_POINT,
+ new String[] {
+ PARAM_STRING,
+ PARAM_LOCATION,
+ PARAM_MATCH,
+ PARAM_INVERSE,
+ PARAM_IGNORECASE
+ }));
+
+ this.addApiPersistentConnection(
+ new ApiPersistentConnection(
+ PCONN_WAIT_FOR_HTTP_BREAK,
+ new String[] {},
+ new String[] {PARAM_POLL, PARAM_KEEP_ALIVE}));
+ }
+
+ @Override
+ public String getPrefix() {
+ return PREFIX;
+ }
+
+ @Override
+ public ApiResponse handleApiAction(String name, JSONObject params) throws ApiException {
+ if (ACTION_BREAK.equals(name)) {
+ String type = params.getString(PARAM_TYPE).toLowerCase();
+ boolean state = ApiUtils.getBooleanParam(params, PARAM_STATE);
+ if (type.equals(VALUE_TYPE_HTTP_ALL)) {
+ extension.setBreakAllRequests(state);
+ extension.setBreakAllResponses(state);
+ } else if (type.equals(VALUE_TYPE_HTTP_REQUESTS)) {
+ extension.setBreakAllRequests(state);
+ } else if (type.equals(VALUE_TYPE_HTTP_RESPONSES)) {
+ extension.setBreakAllResponses(state);
+ } else {
+ throw new ApiException(
+ ApiException.Type.ILLEGAL_PARAMETER,
+ PARAM_TYPE
+ + " not in ["
+ + VALUE_TYPE_HTTP_ALL
+ + ","
+ + VALUE_TYPE_HTTP_REQUESTS
+ + ","
+ + VALUE_TYPE_HTTP_RESPONSES
+ + "]");
+ }
+
+ } else if (ACTION_BREAK_ON_ID.equals(name)) {
+ extension.setBreakOnId(
+ params.getString(PARAM_KEY),
+ params.getString(PARAM_STATE).equalsIgnoreCase("on"));
+
+ } else if (ACTION_CONTINUE.equals(name)) {
+ extension.getBreakpointManagementInterface().cont();
+
+ } else if (ACTION_STEP.equals(name)) {
+ extension.getBreakpointManagementInterface().step();
+
+ } else if (ACTION_DROP.equals(name)) {
+ extension.getBreakpointManagementInterface().drop();
+
+ } else if (ACTION_SET_HTTP_MESSAGE.equals(name)) {
+ if (extension.getBreakpointManagementInterface().getMessage() == null) {
+ // We've not got an intercepted message
+ throw new ApiException(ApiException.Type.DOES_NOT_EXIST);
+ }
+
+ String header = params.getString(PARAM_HTTP_HEADER);
+ String body = this.getParam(params, PARAM_HTTP_BODY, "");
+
+ if (header.indexOf(HttpHeader.CRLF) < 0) {
+ if (header.indexOf("\\n") >= 0) {
+ // Makes it easier to use via API UI
+ header = header.replace("\\r", "\r").replace("\\n", "\n");
+ }
+ }
+
+ Message msg = extension.getBreakpointManagementInterface().getMessage();
+
+ if (msg instanceof HttpMessage) {
+ HttpMessage httpMsg = (HttpMessage) msg;
+ if (extension.getBreakpointManagementInterface().isRequest()) {
+
+ try {
+ httpMsg.setRequestHeader(header);
+ httpMsg.setRequestBody(body);
+ extension.getBreakpointManagementInterface().setMessage(httpMsg, true);
+
+ } catch (HttpMalformedHeaderException e) {
+ throw new ApiException(ApiException.Type.ILLEGAL_PARAMETER, e.getMessage());
+ }
+ } else {
+ try {
+ httpMsg.setResponseHeader(header);
+ httpMsg.setResponseBody(body);
+ extension.getBreakpointManagementInterface().setMessage(httpMsg, false);
+
+ } catch (HttpMalformedHeaderException e) {
+ throw new ApiException(ApiException.Type.ILLEGAL_PARAMETER, e.getMessage());
+ }
+ }
+ }
+
+ } else if (ACTION_ADD_HTTP_BREAK_POINT.equals(name)) {
+ try {
+ extension.addHttpBreakpoint(
+ params.getString(PARAM_STRING),
+ params.getString(PARAM_LOCATION),
+ params.getString(PARAM_MATCH),
+ ApiUtils.getBooleanParam(params, PARAM_INVERSE),
+ ApiUtils.getBooleanParam(params, PARAM_IGNORECASE));
+ } catch (IllegalArgumentException e) {
+ throw new ApiException(ApiException.Type.ILLEGAL_PARAMETER, e.getMessage());
+ }
+
+ } else if (ACTION_REM_HTTP_BREAK_POINT.equals(name)) {
+ try {
+ extension.removeHttpBreakpoint(
+ params.getString(PARAM_STRING),
+ params.getString(PARAM_LOCATION),
+ params.getString(PARAM_MATCH),
+ ApiUtils.getBooleanParam(params, PARAM_INVERSE),
+ ApiUtils.getBooleanParam(params, PARAM_IGNORECASE));
+ } catch (IllegalArgumentException e) {
+ throw new ApiException(ApiException.Type.ILLEGAL_PARAMETER, e.getMessage());
+ }
+
+ } else {
+ throw new ApiException(ApiException.Type.BAD_ACTION);
+ }
+ return ApiResponseElement.OK;
+ }
+
+ @Override
+ public ApiResponse handleApiView(String name, JSONObject params) throws ApiException {
+ if (VIEW_IS_BREAK_ALL.equals(name)) {
+ return new ApiResponseElement(
+ name,
+ Boolean.toString(extension.getBreakpointManagementInterface().isBreakAll()));
+ } else if (VIEW_IS_BREAK_REQUEST.equals(name)) {
+ return new ApiResponseElement(
+ name,
+ Boolean.toString(
+ extension.getBreakpointManagementInterface().isBreakRequest()));
+ } else if (VIEW_IS_BREAK_RESPONSE.equals(name)) {
+ return new ApiResponseElement(
+ name,
+ Boolean.toString(
+ extension.getBreakpointManagementInterface().isBreakResponse()));
+ } else if (VIEW_HTTP_MESSAGE.equals(name)) {
+ Message msg = extension.getBreakpointManagementInterface().getMessage();
+ if (msg == null) {
+ return new ApiResponseElement(name, "");
+ } else if (msg instanceof HttpMessage) {
+ HttpMessage httpMsg = (HttpMessage) msg;
+ StringBuilder sb = new StringBuilder();
+ if (extension.getBreakpointManagementInterface().isRequest()) {
+ sb.append(httpMsg.getRequestHeader().toString());
+ sb.append(httpMsg.getRequestBody().toString());
+ } else {
+ sb.append(httpMsg.getResponseHeader().toString());
+ sb.append(httpMsg.getResponseBody().toString());
+ }
+ return new ApiResponseElement(name, sb.toString());
+ }
+ throw new ApiException(ApiException.Type.BAD_TYPE);
+ } else {
+ throw new ApiException(ApiException.Type.BAD_VIEW);
+ }
+ }
+
+ @Override
+ public void handleApiPersistentConnection(
+ HttpMessage msg,
+ HttpInputStream httpIn,
+ HttpOutputStream httpOut,
+ String name,
+ JSONObject params)
+ throws ApiException {
+ if (PCONN_WAIT_FOR_HTTP_BREAK.equals(name)) {
+ int poll = params.optInt(PARAM_POLL, 500);
+ int keepAlive = params.optInt(PARAM_KEEP_ALIVE, -1);
+
+ try {
+ String contentType;
+ int nextKeepAlive = keepAlive * 1000;
+ int alive = 0;
+ if (keepAlive > 0) {
+ contentType = "text/plain";
+ } else {
+ contentType = "text/event-stream";
+ }
+ msg.setResponseHeader(API.getDefaultResponseHeader(contentType, -1));
+ msg.getResponseHeader().setHeader(HttpHeader.CONNECTION, HttpHeader._KEEP_ALIVE);
+
+ httpOut.write(msg.getResponseHeader());
+ while (true) {
+ Message brkMsg = extension.getBreakpointManagementInterface().getMessage();
+ if (brkMsg != null && brkMsg instanceof HttpMessage) {
+ String event;
+ HttpMessage httpMsg = (HttpMessage) brkMsg;
+ JSONObject jo = new JSONObject();
+ if (extension.getBreakpointManagementInterface().isRequest()) {
+ event = "httpRequest";
+ jo.put("header", httpMsg.getRequestHeader().toString());
+ jo.put("body", httpMsg.getRequestBody().toString());
+ } else {
+ event = "httpResponse";
+ jo.put("header", httpMsg.getResponseHeader().toString());
+ jo.put("body", httpMsg.getResponseBody().toString());
+ }
+ httpOut.write("event: " + event + "\n");
+ httpOut.write("data: " + jo.toString() + "\n\n");
+ httpOut.flush();
+ break;
+ }
+ try {
+ Thread.sleep(poll);
+ alive += poll;
+ } catch (InterruptedException e) {
+ // Ignore
+ }
+ if (keepAlive > 0 && alive > nextKeepAlive) {
+ httpOut.write("event: keepalive\n");
+ httpOut.write("data: {}\n\n");
+ httpOut.flush();
+ nextKeepAlive = alive + (keepAlive * 1000);
+ }
+ }
+ } catch (IOException e) {
+ // Ignore - likely to just mean the client has closed the connection
+ } finally {
+ httpOut.close();
+ httpIn.close();
+ }
+ return;
+ }
+ throw new ApiException(ApiException.Type.BAD_PCONN);
+ }
+}
diff --git a/addOns/brk/src/main/java/org/zaproxy/addon/brk/BreakEventPublisher.java b/addOns/brk/src/main/java/org/zaproxy/addon/brk/BreakEventPublisher.java
new file mode 100644
index 00000000000..de8803dfeb1
--- /dev/null
+++ b/addOns/brk/src/main/java/org/zaproxy/addon/brk/BreakEventPublisher.java
@@ -0,0 +1,87 @@
+/*
+ * Zed Attack Proxy (ZAP) and its related class files.
+ *
+ * ZAP is an HTTP/HTTPS proxy for assessing web application security.
+ *
+ * Copyright 2018 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.addon.brk;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import org.zaproxy.zap.ZAP;
+import org.zaproxy.zap.eventBus.Event;
+import org.zaproxy.zap.eventBus.EventPublisher;
+import org.zaproxy.zap.extension.httppanel.Message;
+
+/**
+ * @since 2.8.0
+ */
+public class BreakEventPublisher implements EventPublisher {
+
+ private static BreakEventPublisher publisher = null;
+
+ /** Indicates that a message hit a breakpoint. */
+ public static final String BREAK_POINT_HIT = "break.hit";
+
+ /**
+ * Indicates the message currently being changed (active).
+ *
+ *
Only one message can be active at the same time.
+ */
+ public static final String BREAK_POINT_ACTIVE = "break.active";
+
+ /** Indicates that the active message no longer is, it might have been dropped or forwarded. */
+ public static final String BREAK_POINT_INACTIVE = "break.inactive";
+
+ public static final String MESSAGE_TYPE = "messageType";
+
+ @Override
+ public String getPublisherName() {
+ return BreakEventPublisher.class.getCanonicalName();
+ }
+
+ public static synchronized BreakEventPublisher getPublisher() {
+ if (publisher == null) {
+ publisher = new BreakEventPublisher();
+ ZAP.getEventBus()
+ .registerPublisher(
+ publisher, BREAK_POINT_HIT, BREAK_POINT_ACTIVE, BREAK_POINT_INACTIVE);
+ }
+ return publisher;
+ }
+
+ public void publishHitEvent(Message msg) {
+ this.publishEvent(BREAK_POINT_HIT, msg, msg.toEventData());
+ }
+
+ public void publishActiveEvent(Message msg) {
+ this.publishEvent(BREAK_POINT_ACTIVE, msg, msg.toEventData());
+ }
+
+ public void publishInactiveEvent(Message msg) {
+ this.publishEvent(BREAK_POINT_INACTIVE, msg, Collections.emptyMap());
+ }
+
+ private void publishEvent(String event, Message msg, Map parameters) {
+ Map map = new HashMap<>();
+ map.putAll(parameters); // Could be an empty map
+ map.put(MESSAGE_TYPE, msg.getType());
+
+ ZAP.getEventBus()
+ .publishSyncEvent(getPublisher(), new Event(getPublisher(), event, null, map));
+ }
+}
diff --git a/addOns/brk/src/main/java/org/zaproxy/addon/brk/BreakPanel.java b/addOns/brk/src/main/java/org/zaproxy/addon/brk/BreakPanel.java
new file mode 100644
index 00000000000..df1d0a42cc2
--- /dev/null
+++ b/addOns/brk/src/main/java/org/zaproxy/addon/brk/BreakPanel.java
@@ -0,0 +1,763 @@
+/*
+ * Zed Attack Proxy (ZAP) and its related class files.
+ *
+ * ZAP is an HTTP/HTTPS proxy for assessing web application security.
+ *
+ * Copyright 2010 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.addon.brk;
+
+import java.awt.BorderLayout;
+import java.awt.CardLayout;
+import java.awt.EventQueue;
+import java.awt.event.KeyEvent;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.swing.BorderFactory;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JToggleButton;
+import javax.swing.JToolBar;
+import javax.swing.JToolBar.Separator;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.parosproxy.paros.Constant;
+import org.parosproxy.paros.control.Control.Mode;
+import org.parosproxy.paros.extension.AbstractPanel;
+import org.parosproxy.paros.extension.option.OptionsParamView;
+import org.parosproxy.paros.model.Model;
+import org.parosproxy.paros.network.HttpMessage;
+import org.parosproxy.paros.view.View;
+import org.zaproxy.zap.extension.httppanel.HttpPanel;
+import org.zaproxy.zap.extension.httppanel.HttpPanelRequest;
+import org.zaproxy.zap.extension.httppanel.HttpPanelResponse;
+import org.zaproxy.zap.extension.httppanel.InvalidMessageDataException;
+import org.zaproxy.zap.extension.httppanel.Message;
+import org.zaproxy.zap.extension.httppanel.view.impl.models.http.HttpPanelViewModelUtils;
+import org.zaproxy.zap.utils.DisplayUtils;
+import org.zaproxy.zap.view.ZapToggleButton;
+
+@SuppressWarnings("serial")
+public class BreakPanel extends AbstractPanel implements BreakpointManagementInterface {
+
+ private static final long serialVersionUID = 1L;
+ private static final Logger LOGGER = LogManager.getLogger(BreakPanel.class);
+
+ private static final String REQUEST_PANEL = "request";
+ private static final String RESPONSE_PANEL = "response";
+
+ private HttpPanelRequest requestPanel;
+ private HttpPanelResponse responsePanel;
+
+ private ExtensionBreak extension;
+ private JPanel panelContent;
+ private BreakPanelToolbarFactory breakToolbarFactory;
+ private BreakpointsParam breakpointsParams;
+
+ private final JToggleButton toolBarReqButton;
+ private final JToggleButton toolBarResButton;
+ private final JToggleButton toolBarAllButton;
+ private final JButton toolBarBtnStep;
+ private final JButton toolBarBtnContinue;
+ private final JButton toolBarBtnDrop;
+ private final JButton toolBarBtnBreakPoint;
+
+ private ZapToggleButton fixRequestContentLength;
+ private ZapToggleButton fixResponseContentLength;
+ private ZapToggleButton fixRequestHostHeader;
+
+ private Message msg;
+ private boolean isAlwaysOnTop = false;
+ private boolean request;
+
+ /** The break buttons shown in the main panel of the Break tab. */
+ private final BreakButtonsUI mainBreakButtons;
+
+ /** The break buttons shown in the request panel of the Break tab. */
+ private final BreakButtonsUI requestBreakButtons;
+
+ /** The break buttons shown in the response panel of the Break tab. */
+ private final BreakButtonsUI responseBreakButtons;
+
+ /**
+ * The current location of the break buttons.
+ *
+ * @see #setButtonsLocation(int)
+ */
+ private int currentButtonsLocation;
+
+ /**
+ * The current button mode.
+ *
+ * @see #setButtonMode(int)
+ */
+ private int currentButtonMode;
+
+ public BreakPanel(ExtensionBreak extension, BreakpointsParam breakpointsParams) {
+ super();
+ this.extension = extension;
+ this.breakpointsParams = breakpointsParams;
+
+ this.setIcon(
+ new ImageIcon(
+ BreakPanel.class.getResource(
+ "/resource/icon/16/101grey.png"))); // 'grey X' icon
+
+ this.setDefaultAccelerator(
+ extension
+ .getView()
+ .getMenuShortcutKeyStroke(KeyEvent.VK_B, KeyEvent.SHIFT_DOWN_MASK, false));
+ this.setMnemonic(Constant.messages.getChar("brk.panel.mnemonic"));
+
+ this.setLayout(new BorderLayout());
+
+ breakToolbarFactory = new BreakPanelToolbarFactory(extension, breakpointsParams, this);
+
+ panelContent = new JPanel(new CardLayout());
+ this.add(panelContent, BorderLayout.CENTER);
+
+ requestPanel = new HttpPanelRequest(false, OptionsParamView.BASE_VIEW_KEY + ".break.");
+ requestPanel.loadConfig(Model.getSingleton().getOptionsParam().getConfig());
+ responsePanel = new HttpPanelResponse(false, OptionsParamView.BASE_VIEW_KEY + ".break.");
+ responsePanel.loadConfig(Model.getSingleton().getOptionsParam().getConfig());
+
+ panelContent.add(requestPanel, REQUEST_PANEL);
+ panelContent.add(responsePanel, RESPONSE_PANEL);
+
+ toolBarReqButton = breakToolbarFactory.getBtnBreakRequest();
+ View.getSingleton().addMainToolbarButton(toolBarReqButton);
+
+ toolBarResButton = breakToolbarFactory.getBtnBreakResponse();
+ View.getSingleton().addMainToolbarButton(toolBarResButton);
+
+ toolBarAllButton = breakToolbarFactory.getBtnBreakAll();
+ View.getSingleton().addMainToolbarButton(toolBarAllButton);
+
+ toolBarBtnStep = breakToolbarFactory.getBtnStep();
+ View.getSingleton().addMainToolbarButton(toolBarBtnStep);
+
+ toolBarBtnContinue = breakToolbarFactory.getBtnContinue();
+ View.getSingleton().addMainToolbarButton(toolBarBtnContinue);
+
+ toolBarBtnDrop = breakToolbarFactory.getBtnDrop();
+ View.getSingleton().addMainToolbarButton(toolBarBtnDrop);
+
+ toolBarBtnBreakPoint = breakToolbarFactory.getBtnBreakPoint();
+ View.getSingleton().addMainToolbarButton(toolBarBtnBreakPoint);
+
+ mainBreakButtons = new BreakButtonsUI("mainBreakButtons", breakToolbarFactory);
+ this.add(mainBreakButtons.getComponent(), BorderLayout.NORTH);
+
+ requestBreakButtons = new BreakButtonsUI("requestBreakButtons", breakToolbarFactory);
+ requestPanel.addOptions(
+ requestBreakButtons.getComponent(), HttpPanel.OptionsLocation.AFTER_COMPONENTS);
+
+ responseBreakButtons = new BreakButtonsUI("responseBreakButtons", breakToolbarFactory);
+ responsePanel.addOptions(
+ responseBreakButtons.getComponent(), HttpPanel.OptionsLocation.AFTER_COMPONENTS);
+
+ // The options toolbars are always added just to the Break request and response panels
+ JToolBar requestOptionsToolBar = new JToolBar();
+ requestOptionsToolBar.setFloatable(false);
+ requestOptionsToolBar.setBorder(BorderFactory.createEmptyBorder());
+ requestOptionsToolBar.setRollover(true);
+
+ requestOptionsToolBar.add(this.getRequestButtonFixContentLength());
+ requestOptionsToolBar.add(this.getRequestButtonFixHostHeader());
+ requestPanel.addOptions(requestOptionsToolBar, HttpPanel.OptionsLocation.AFTER_COMPONENTS);
+
+ JToolBar responseOptionsToolBar = new JToolBar();
+ responseOptionsToolBar.setFloatable(false);
+ responseOptionsToolBar.setBorder(BorderFactory.createEmptyBorder());
+ responseOptionsToolBar.setRollover(true);
+
+ responseOptionsToolBar.add(this.getResponseButtonFixContentLength());
+ responsePanel.addOptions(
+ responseOptionsToolBar, HttpPanel.OptionsLocation.AFTER_COMPONENTS);
+
+ currentButtonsLocation = -1;
+ }
+
+ /**
+ * Sets the location of the break buttons.
+ *
+ *
If the location is already set and the main tool bar visibility is the same, no change is
+ * done.
+ *
+ * @param location the location to set
+ */
+ void setButtonsLocation(int location) {
+ if (currentButtonsLocation == location) {
+ mainBreakButtons.setVisible(location == 0 && isMainToolBarHidden());
+ return;
+ }
+ currentButtonsLocation = location;
+
+ switch (location) {
+ case 0:
+ requestBreakButtons.setVisible(false);
+ responseBreakButtons.setVisible(false);
+ setToolbarButtonsVisible(true);
+
+ // If the user decided to disable the main toolbar, the break
+ // buttons have to be force to be displayed in the break panel
+ mainBreakButtons.setVisible(isMainToolBarHidden());
+ break;
+ case 1:
+ case 2:
+ requestBreakButtons.setVisible(true);
+ responseBreakButtons.setVisible(true);
+ setToolbarButtonsVisible(location == 2);
+
+ mainBreakButtons.setVisible(false);
+ break;
+ default:
+ setToolbarButtonsVisible(true);
+ }
+ }
+
+ /**
+ * Tells whether or not the main tool bar is hidden.
+ *
+ * @return {@code true} if the main tool bar is hidden, {@code false} otherwise
+ */
+ private boolean isMainToolBarHidden() {
+ return !extension.getModel().getOptionsParam().getViewParam().isShowMainToolbar();
+ }
+
+ @Override
+ public boolean isBreakRequest() {
+ return breakToolbarFactory.isBreakRequest();
+ }
+
+ @Override
+ public boolean isBreakResponse() {
+ return breakToolbarFactory.isBreakResponse();
+ }
+
+ @Override
+ public boolean isBreakAll() {
+ return breakToolbarFactory.isBreakAll();
+ }
+
+ @Override
+ public void breakpointHit() {
+ breakToolbarFactory.breakpointHit();
+ }
+
+ @Override
+ public boolean isHoldMessage(Message aMessage) {
+ return breakToolbarFactory.isHoldMessage();
+ }
+
+ public boolean isHoldMessage() {
+ return breakToolbarFactory.isHoldMessage();
+ }
+
+ @Override
+ public boolean isStepping() {
+ return breakToolbarFactory.isStepping();
+ }
+
+ @Override
+ public boolean isToBeDropped() {
+ return breakToolbarFactory.isToBeDropped();
+ }
+
+ @Override
+ public void breakpointDisplayed() {
+ if (!View.getSingleton().isCanGetFocus()) {
+ return;
+ }
+ final Boolean alwaysOnTopOption = breakpointsParams.getAlwaysOnTop();
+ if (alwaysOnTopOption == null || alwaysOnTopOption) {
+
+ java.awt.EventQueue.invokeLater(
+ new Runnable() {
+ @Override
+ public void run() {
+
+ View.getSingleton().getMainFrame().setAlwaysOnTop(true);
+ View.getSingleton().getMainFrame().toFront();
+ setTabFocus();
+ isAlwaysOnTop = true;
+
+ if (alwaysOnTopOption == null) {
+ // Prompt the user the first time
+ boolean keepOn =
+ View.getSingleton()
+ .showConfirmDialog(
+ Constant.messages.getString(
+ "brk.alwaysOnTop.message"))
+ == JOptionPane.OK_OPTION;
+ breakpointsParams.setAlwaysOnTop(keepOn);
+ if (!keepOn) {
+ // Turn it off
+ View.getSingleton().getMainFrame().setAlwaysOnTop(false);
+ isAlwaysOnTop = false;
+ }
+ }
+ }
+ });
+ }
+ try {
+ EventQueue.invokeAndWait(
+ new Runnable() {
+ @Override
+ public void run() {
+ View.getSingleton().getMainFrame().toFront();
+ }
+ });
+ } catch (Exception e) {
+ LOGGER.warn(e.getMessage(), e);
+ }
+ }
+
+ private void setToolbarButtonsVisible(boolean visible) {
+ boolean simple = currentButtonMode == BreakpointsParam.BUTTON_MODE_SIMPLE;
+ toolBarReqButton.setVisible(visible && !simple);
+ toolBarResButton.setVisible(visible && !simple);
+ toolBarAllButton.setVisible(visible && simple);
+ toolBarBtnStep.setVisible(visible);
+ toolBarBtnContinue.setVisible(visible);
+ toolBarBtnDrop.setVisible(visible);
+ toolBarBtnBreakPoint.setVisible(visible);
+ }
+
+ @Override
+ public void setMessage(final Message aMessage, final boolean isRequest) {
+ try {
+ EventQueue.invokeAndWait(
+ new Runnable() {
+ @Override
+ public void run() {
+ msg = aMessage;
+ CardLayout cl = (CardLayout) (panelContent.getLayout());
+ request = isRequest;
+
+ if (isRequest) {
+ requestPanel.setMessage(aMessage, true);
+ requestPanel.setEditable(true);
+ getRequestButtonFixContentLength()
+ .setVisible(msg instanceof HttpMessage);
+ getRequestButtonFixHostHeader()
+ .setVisible(msg instanceof HttpMessage);
+ cl.show(panelContent, REQUEST_PANEL);
+ } else {
+ responsePanel.setMessage(aMessage, true);
+ responsePanel.setEditable(true);
+ getResponseButtonFixContentLength()
+ .setVisible(msg instanceof HttpMessage);
+ cl.show(panelContent, RESPONSE_PANEL);
+ }
+ }
+ });
+ } catch (Exception e) {
+ LOGGER.warn(e.getMessage(), e);
+ }
+ }
+
+ @Override
+ public boolean isRequest() {
+ return this.request;
+ }
+
+ @Override
+ public Message getMessage() {
+ return msg;
+ }
+
+ @Override
+ public void saveMessage(final boolean isRequest) {
+ try {
+ EventQueue.invokeAndWait(
+ new Runnable() {
+ @Override
+ public void run() {
+ CardLayout cl = (CardLayout) (panelContent.getLayout());
+
+ if (isRequest) {
+ Message msg = getMessage();
+ if (msg instanceof HttpMessage) {
+ updateHttpRequestMessage((HttpMessage) msg);
+ }
+ cl.show(panelContent, REQUEST_PANEL);
+ } else {
+ Message msg = getMessage();
+ if (msg instanceof HttpMessage
+ && getResponseButtonFixContentLength().isSelected()) {
+ HttpPanelViewModelUtils.updateResponseContentLength(
+ (HttpMessage) msg);
+ }
+ cl.show(panelContent, RESPONSE_PANEL);
+ }
+ }
+ });
+ } catch (Exception ie) {
+ LOGGER.warn(ie.getMessage(), ie);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private void updateHttpRequestMessage(HttpMessage msg) {
+ if (getRequestButtonFixContentLength().isSelected()) {
+ HttpPanelViewModelUtils.updateRequestContentLength(msg);
+ }
+ if (!getRequestButtonFixHostHeader().isSelected()) {
+ Map properties;
+ if (msg.getUserObject() instanceof Map, ?>) {
+ properties = (Map) msg.getUserObject();
+ } else {
+ properties = new HashMap<>();
+ msg.setUserObject(properties);
+ }
+
+ properties.put("host.normalization", Boolean.FALSE);
+ }
+ }
+
+ boolean isValidState() {
+ HttpPanel panel = isRequest() ? requestPanel : responsePanel;
+ try {
+ panel.saveData();
+ return true;
+ } catch (InvalidMessageDataException e) {
+ StringBuilder warnMessage = new StringBuilder(150);
+ warnMessage.append(Constant.messages.getString("brk.panel.warn.datainvalid"));
+
+ String exceptionMessage = e.getLocalizedMessage();
+ if (exceptionMessage != null && !exceptionMessage.isEmpty()) {
+ warnMessage.append('\n').append(exceptionMessage);
+ }
+ View.getSingleton().showWarningDialog(warnMessage.toString());
+ }
+ return false;
+ }
+
+ public void savePanels() {
+ requestPanel.saveConfig(Model.getSingleton().getOptionsParam().getConfig());
+ responsePanel.saveConfig(Model.getSingleton().getOptionsParam().getConfig());
+ }
+
+ @Override
+ public void clearAndDisableRequest() {
+ if (EventQueue.isDispatchThread()) {
+ clearAndDisableRequestEventHandler();
+ } else {
+ try {
+ EventQueue.invokeAndWait(
+ new Runnable() {
+ @Override
+ public void run() {
+ clearAndDisableRequestEventHandler();
+ }
+ });
+ } catch (Exception e) {
+ LOGGER.warn(e.getMessage(), e);
+ }
+ }
+ }
+
+ private void clearAndDisableRequestEventHandler() {
+ this.msg = null;
+ requestPanel.clearView(false);
+ requestPanel.setEditable(false);
+ getRequestButtonFixContentLength().setVisible(false);
+ getRequestButtonFixHostHeader().setVisible(false);
+ breakpointLeft();
+ }
+
+ @Override
+ public void clearAndDisableResponse() {
+ if (EventQueue.isDispatchThread()) {
+ clearAndDisableResponseEventHandler();
+ } else {
+ try {
+ EventQueue.invokeAndWait(
+ new Runnable() {
+ @Override
+ public void run() {
+ clearAndDisableResponseEventHandler();
+ }
+ });
+ } catch (Exception e) {
+ LOGGER.warn(e.getMessage(), e);
+ }
+ }
+ }
+
+ private void clearAndDisableResponseEventHandler() {
+ this.msg = null;
+ responsePanel.clearView(false);
+ responsePanel.setEditable(false);
+ getResponseButtonFixContentLength().setVisible(false);
+ breakpointLeft();
+ }
+
+ private void breakpointLeft() {
+ if (this.isAlwaysOnTop) {
+ View.getSingleton().getMainFrame().setAlwaysOnTop(false);
+ this.isAlwaysOnTop = false;
+ }
+ }
+
+ @Override
+ public void init() {
+ breakToolbarFactory.init();
+ }
+
+ @Override
+ public void reset() {
+ this.msg = null;
+ breakToolbarFactory.reset();
+ }
+
+ @Override
+ public void sessionModeChanged(Mode mode) {
+ if (mode.equals(Mode.safe)) {
+ this.breakToolbarFactory.setBreakEnabled(false);
+ } else {
+ this.breakToolbarFactory.setBreakEnabled(true);
+ }
+ }
+
+ @Override
+ public void setBreakAllRequests(boolean brk) {
+ breakToolbarFactory.setBreakRequest(brk);
+ }
+
+ @Override
+ public void setBreakAllResponses(boolean brk) {
+ breakToolbarFactory.setBreakResponse(brk);
+ }
+
+ @Override
+ public void setBreakAll(boolean brk) {
+ breakToolbarFactory.setBreakAll(brk);
+ }
+
+ @Override
+ public void step() {
+ if (!isValidState()) {
+ return;
+ }
+
+ breakToolbarFactory.step();
+ }
+
+ @Override
+ public void cont() {
+ if (!isValidState()) {
+ return;
+ }
+
+ breakToolbarFactory.setContinue(true);
+ breakToolbarFactory.setBreakAll(false);
+ breakToolbarFactory.setBreakRequest(false);
+ breakToolbarFactory.setBreakResponse(false);
+ }
+
+ @Override
+ public void drop() {
+ breakToolbarFactory.drop();
+ }
+
+ public void showNewBreakPointDialog() {
+ extension.addUiBreakpoint(new HttpMessage());
+ }
+
+ public void setButtonMode(int mode) {
+ if (currentButtonMode == mode) {
+ return;
+ }
+ currentButtonMode = mode;
+
+ this.breakToolbarFactory.setButtonMode(mode);
+
+ if (currentButtonsLocation == 0 || currentButtonsLocation == 2) {
+ boolean simple = mode == BreakpointsParam.BUTTON_MODE_SIMPLE;
+ toolBarReqButton.setVisible(!simple);
+ toolBarResButton.setVisible(!simple);
+ toolBarAllButton.setVisible(simple);
+ }
+
+ mainBreakButtons.setButtonMode(mode);
+ requestBreakButtons.setButtonMode(mode);
+ responseBreakButtons.setButtonMode(mode);
+ }
+
+ protected void setShowIgnoreFilesButtons(boolean showButtons) {
+ this.breakToolbarFactory.setShowIgnoreFilesButtons(showButtons);
+
+ mainBreakButtons.setShowIgnoreFilesButtons(showButtons);
+ requestBreakButtons.setShowIgnoreFilesButtons(showButtons);
+ responseBreakButtons.setShowIgnoreFilesButtons(showButtons);
+ }
+
+ public List getIgnoreRulesEnableList() {
+ return breakToolbarFactory.getIgnoreRulesEnableList();
+ }
+
+ public void updateIgnoreFileTypesRegexs() {
+ breakToolbarFactory.updateIgnoreFileTypesRegexs();
+ }
+
+ private ZapToggleButton getRequestButtonFixContentLength() {
+ if (fixRequestContentLength == null) {
+ fixRequestContentLength =
+ new ZapToggleButton(
+ DisplayUtils.getScaledIcon(
+ new ImageIcon(
+ BreakPanel.class.getResource(
+ "/resource/icon/fugue/application-resize.png"))),
+ true);
+ fixRequestContentLength.setToolTipText(
+ Constant.messages.getString("brk.checkBox.fixLength"));
+ fixRequestContentLength.setVisible(false);
+ }
+ return fixRequestContentLength;
+ }
+
+ private ZapToggleButton getResponseButtonFixContentLength() {
+ if (fixResponseContentLength == null) {
+ fixResponseContentLength =
+ new ZapToggleButton(
+ DisplayUtils.getScaledIcon(
+ new ImageIcon(
+ BreakPanel.class.getResource(
+ "/resource/icon/fugue/application-resize.png"))),
+ true);
+ fixResponseContentLength.setToolTipText(
+ Constant.messages.getString("brk.checkBox.fixLength"));
+ fixResponseContentLength.setVisible(false);
+ }
+ return fixResponseContentLength;
+ }
+
+ private ZapToggleButton getRequestButtonFixHostHeader() {
+ if (fixRequestHostHeader == null) {
+ fixRequestHostHeader =
+ new ZapToggleButton(
+ DisplayUtils.getScaledIcon(
+ new ImageIcon(
+ BreakPanel.class.getResource(
+ "/resource/icon/fugue/server.png"))),
+ true);
+ fixRequestHostHeader.setToolTipText(
+ Constant.messages.getString("brk.checkBox.fixHostHeader"));
+ fixRequestHostHeader.setVisible(false);
+ }
+ return fixRequestHostHeader;
+ }
+
+ /**
+ * A wrapper of a view component with break related buttons/functionality.
+ *
+ * @see #getComponent()
+ */
+ private static class BreakButtonsUI {
+
+ private final JToolBar toolBar;
+
+ private final JToggleButton requestButton;
+ private final JToggleButton responseButton;
+ private final JToggleButton allButton;
+ private final Separator separatorForBreakOnButtons;
+ private final JToggleButton brkOnJavaScriptButton;
+ private final JToggleButton brkOnCssAndFontsButton;
+ private final JToggleButton brkOnMultimediaButton;
+ private final Separator separatorForBreakOnlyScopeButton;
+ private final JToggleButton brkOnlyOnScopeButton;
+
+ public BreakButtonsUI(String name, BreakPanelToolbarFactory breakToolbarFactory) {
+ requestButton = breakToolbarFactory.getBtnBreakRequest();
+ responseButton = breakToolbarFactory.getBtnBreakResponse();
+ allButton = breakToolbarFactory.getBtnBreakAll();
+ separatorForBreakOnButtons = new JToolBar.Separator();
+ brkOnJavaScriptButton = breakToolbarFactory.getBtnBreakOnJavaScript();
+ brkOnCssAndFontsButton = breakToolbarFactory.getBtnBreakOnCssAndFonts();
+ brkOnMultimediaButton = breakToolbarFactory.getBtnBreakOnMultimedia();
+ separatorForBreakOnlyScopeButton = new JToolBar.Separator();
+ brkOnlyOnScopeButton = breakToolbarFactory.getBtnOnlyBreakOnScope();
+
+ toolBar = new JToolBar();
+ toolBar.setFloatable(false);
+ toolBar.setBorder(BorderFactory.createEmptyBorder());
+ toolBar.setRollover(true);
+
+ toolBar.setName(name);
+
+ toolBar.add(requestButton);
+ toolBar.add(responseButton);
+ toolBar.add(allButton);
+ toolBar.add(breakToolbarFactory.getBtnStep());
+ toolBar.add(breakToolbarFactory.getBtnContinue());
+ toolBar.add(breakToolbarFactory.getBtnDrop());
+ toolBar.add(breakToolbarFactory.getBtnBreakPoint());
+ toolBar.add(separatorForBreakOnButtons);
+ toolBar.add(brkOnJavaScriptButton);
+ toolBar.add(brkOnCssAndFontsButton);
+ toolBar.add(brkOnMultimediaButton);
+ toolBar.add(separatorForBreakOnlyScopeButton);
+ toolBar.add(brkOnlyOnScopeButton);
+ }
+
+ /**
+ * Sets whether or not the underlying view component is visible.
+ *
+ * @param visible {@code true} if the view component should be visible, {@code false}
+ * otherwise
+ */
+ public void setVisible(boolean visible) {
+ toolBar.setVisible(visible);
+ }
+
+ /**
+ * Sets the current button mode.
+ *
+ * @param mode the mode to be set
+ * @see BreakpointsParam#BUTTON_MODE_SIMPLE
+ * @see BreakpointsParam#BUTTON_MODE_DUAL
+ */
+ public void setButtonMode(int mode) {
+ boolean simple = mode == BreakpointsParam.BUTTON_MODE_SIMPLE;
+ requestButton.setVisible(!simple);
+ responseButton.setVisible(!simple);
+ allButton.setVisible(simple);
+ }
+
+ public void setShowIgnoreFilesButtons(boolean showButtons) {
+ separatorForBreakOnButtons.setVisible(showButtons);
+ brkOnJavaScriptButton.setVisible(showButtons);
+ brkOnCssAndFontsButton.setVisible(showButtons);
+ brkOnMultimediaButton.setVisible(showButtons);
+ separatorForBreakOnlyScopeButton.setVisible(showButtons);
+ brkOnlyOnScopeButton.setVisible(showButtons);
+ }
+
+ /**
+ * Gets the underlying view component, with the break buttons.
+ *
+ * @return the view component
+ */
+ public JComponent getComponent() {
+ return toolBar;
+ }
+ }
+}
diff --git a/addOns/brk/src/main/java/org/zaproxy/addon/brk/BreakPanelToolbarFactory.java b/addOns/brk/src/main/java/org/zaproxy/addon/brk/BreakPanelToolbarFactory.java
new file mode 100644
index 00000000000..e147c6e79d4
--- /dev/null
+++ b/addOns/brk/src/main/java/org/zaproxy/addon/brk/BreakPanelToolbarFactory.java
@@ -0,0 +1,871 @@
+/*
+ * Zed Attack Proxy (ZAP) and its related class files.
+ *
+ * ZAP is an HTTP/HTTPS proxy for assessing web application security.
+ *
+ * Copyright 2011 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.addon.brk;
+
+import java.awt.event.ActionEvent;
+import java.util.ArrayList;
+import java.util.List;
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JOptionPane;
+import javax.swing.JToggleButton;
+import org.parosproxy.paros.Constant;
+import org.parosproxy.paros.control.Control;
+import org.parosproxy.paros.view.TabbedPanel;
+import org.parosproxy.paros.view.View;
+import org.zaproxy.addon.brk.impl.http.HttpBreakpointMessage;
+import org.zaproxy.zap.utils.DisplayUtils;
+import org.zaproxy.zap.utils.Stats;
+import org.zaproxy.zap.view.TabbedPanel2;
+import org.zaproxy.zap.view.ZapToggleButton;
+
+// Button notes
+// BreakRequest button, if set all requests trapped
+// BreakResponse button, ditto for responses
+// If breakpoint hit, Break tab gets focus and icon goes red
+// Step button, only if breakpoint hit, submits just this req/resp, breaks on next
+// Continue button, only if breakpoint hit, submits this req/resp and continues until next break
+// point hit
+// If BreakReq & Resp both selected Step and Continue buttons have same effect
+//
+
+public class BreakPanelToolbarFactory {
+
+ private static final String ICON_RESOURCE_PATH = "/resource/icon/";
+
+ private ContinueButtonAction continueButtonAction;
+ private StepButtonAction stepButtonAction;
+ private DropButtonAction dropButtonAction;
+ private AddBreakpointButtonAction addBreakpointButtonAction;
+
+ private BreakRequestsButtonAction breakRequestsButtonAction;
+ private BreakResponsesButtonAction breakResponsesButtonAction;
+ private BreakAllButtonAction breakAllButtonAction;
+
+ private SetBreakOnJavaScriptAction setBreakOnJavaScriptAction;
+ private SetBreakOnCssAndFontsAction setBreakOnCssAndFontsAction;
+ private SetBreakOnMultimediaAction setBreakOnMultimediaAction;
+ private SetBreakOnlyOnScopeAction setOnlyBreakOnScopeAction;
+
+ private boolean cont = false;
+ private boolean step = false;
+ private boolean stepping = false;
+ private boolean drop = false;
+ private boolean isBreakRequest = false;
+ private boolean isBreakResponse = false;
+ private boolean isBreakAll = false;
+ private boolean isBreakOnJavaScript = true;
+ private boolean isBreakOnCssAndFonts = true;
+ private boolean isBreakOnMultimedia = true;
+
+ private BreakPanel breakPanel = null;
+
+ private BreakpointsParam breakpointsParams;
+ private int mode = 0;
+
+ private List ignoreRulesEnable;
+ private HttpBreakpointMessage ignoreJavascriptBreakpointMessage;
+ private HttpBreakpointMessage ignoreCssAndFontsBreakpointMessage;
+ private HttpBreakpointMessage ignoreMultimediaBreakpointMessage;
+
+ /**
+ * A counter to keep track of how many messages are currently caught, to disable the break
+ * buttons when no message is left.
+ *
+ *
The counter is increased when a {@link #breakpointHit() breakpoint is hit} and decreased
+ * when a message is no longer {@link #isHoldMessage() being held}.
+ *
+ * @see #countLock
+ */
+ private int countCaughtMessages;
+
+ /** The object to synchronise changes to {@link #countCaughtMessages}. */
+ private final Object countLock = new Object();
+
+ private final ExtensionBreak extensionBreak;
+
+ public BreakPanelToolbarFactory(BreakpointsParam breakpointsParams, BreakPanel breakPanel) {
+ this(null, breakpointsParams, breakPanel);
+ }
+
+ public BreakPanelToolbarFactory(
+ ExtensionBreak extensionBreak,
+ BreakpointsParam breakpointsParams,
+ BreakPanel breakPanel) {
+ super();
+
+ this.extensionBreak = extensionBreak;
+
+ continueButtonAction = new ContinueButtonAction();
+ stepButtonAction = new StepButtonAction();
+ dropButtonAction = new DropButtonAction();
+ addBreakpointButtonAction = new AddBreakpointButtonAction();
+
+ breakRequestsButtonAction = new BreakRequestsButtonAction();
+ breakResponsesButtonAction = new BreakResponsesButtonAction();
+ breakAllButtonAction = new BreakAllButtonAction();
+
+ setBreakOnJavaScriptAction = new SetBreakOnJavaScriptAction();
+ setBreakOnCssAndFontsAction = new SetBreakOnCssAndFontsAction();
+ setBreakOnMultimediaAction = new SetBreakOnMultimediaAction();
+ setOnlyBreakOnScopeAction = new SetBreakOnlyOnScopeAction();
+
+ this.breakpointsParams = breakpointsParams;
+ this.breakPanel = breakPanel;
+
+ this.ignoreRulesEnable = new ArrayList<>(3);
+
+ ignoreJavascriptBreakpointMessage =
+ new HttpBreakpointMessage(
+ breakpointsParams.getJavascriptUrlRegex(),
+ HttpBreakpointMessage.Location.url,
+ HttpBreakpointMessage.Match.regex,
+ false,
+ true);
+ ignoreRulesEnable.add(ignoreJavascriptBreakpointMessage);
+
+ ignoreCssAndFontsBreakpointMessage =
+ new HttpBreakpointMessage(
+ breakpointsParams.getCssAndFontsUrlRegex(),
+ HttpBreakpointMessage.Location.url,
+ HttpBreakpointMessage.Match.regex,
+ false,
+ true);
+ ignoreRulesEnable.add(ignoreCssAndFontsBreakpointMessage);
+
+ ignoreMultimediaBreakpointMessage =
+ new HttpBreakpointMessage(
+ breakpointsParams.getMultimediaUrlRegex(),
+ HttpBreakpointMessage.Location.url,
+ HttpBreakpointMessage.Match.regex,
+ false,
+ true);
+ ignoreRulesEnable.add(ignoreMultimediaBreakpointMessage);
+ }
+
+ public List getIgnoreRulesEnableList() {
+ return ignoreRulesEnable;
+ }
+
+ private static ImageIcon getScaledIcon(String path) {
+ return DisplayUtils.getScaledIcon(
+ new ImageIcon(
+ BreakPanelToolbarFactory.class.getResource(ICON_RESOURCE_PATH + path)));
+ }
+
+ private void setActiveIcon(boolean active) {
+ if (active) {
+ // Have to do this before the getParent() call
+ breakPanel.setTabFocus();
+ }
+ if (breakPanel.getParent() instanceof TabbedPanel) {
+ TabbedPanel parent = (TabbedPanel) breakPanel.getParent();
+ if (active) {
+ parent.setIconAt(
+ parent.indexOfComponent(breakPanel), getScaledIcon("16/101.png")); // Red X
+ } else {
+ parent.setIconAt(
+ parent.indexOfComponent(breakPanel),
+ getScaledIcon("16/101grey.png")); // Grey X
+ }
+ if (parent instanceof TabbedPanel2) {
+ // If possible lock the tab while it is active so it cant be closed
+ ((TabbedPanel2) parent).setTabLocked(breakPanel, !active);
+ }
+ }
+ }
+
+ public void breakpointHit() {
+ synchronized (countLock) {
+ countCaughtMessages++;
+ }
+
+ // This could have been via a breakpoint, so force the serialisation
+ resetRequestSerialization(true);
+
+ // Set the active icon and reset the continue button
+ this.setActiveIcon(true);
+ setContinue(false);
+ }
+
+ public boolean isBreakRequest() {
+ return isBreakRequest || isBreakAll;
+ }
+
+ public boolean isBreakResponse() {
+ return isBreakResponse || isBreakAll;
+ }
+
+ public boolean isBreakAll() {
+ return isBreakAll;
+ }
+
+ public JButton getBtnStep() {
+ return new JButton(stepButtonAction);
+ }
+
+ public JButton getBtnContinue() {
+ return new JButton(continueButtonAction);
+ }
+
+ public JButton getBtnDrop() {
+ return new JButton(dropButtonAction);
+ }
+
+ private int askForDropConfirmation() {
+ String title = Constant.messages.getString("brk.dialogue.confirmDropMessage.title");
+ String message = Constant.messages.getString("brk.dialogue.confirmDropMessage.message");
+ JCheckBox checkBox =
+ new JCheckBox(
+ Constant.messages.getString(
+ "brk.dialogue.confirmDropMessage.option.dontAskAgain"));
+ String confirmButtonLabel =
+ Constant.messages.getString("brk.dialogue.confirmDropMessage.button.confirm.label");
+ String cancelButtonLabel =
+ Constant.messages.getString("brk.dialogue.confirmDropMessage.button.cancel.label");
+
+ int option =
+ JOptionPane.showOptionDialog(
+ View.getSingleton().getMainFrame(),
+ new Object[] {message, " ", checkBox},
+ title,
+ JOptionPane.OK_CANCEL_OPTION,
+ JOptionPane.QUESTION_MESSAGE,
+ null,
+ new String[] {confirmButtonLabel, cancelButtonLabel},
+ null);
+
+ if (checkBox.isSelected()) {
+ breakpointsParams.setConfirmDropMessage(false);
+ }
+
+ return option;
+ }
+
+ public JToggleButton getBtnBreakRequest() {
+ ZapToggleButton btnBreakRequest;
+
+ btnBreakRequest = new ZapToggleButton(breakRequestsButtonAction);
+ btnBreakRequest.setSelectedIcon(getScaledIcon("16/105r.png"));
+
+ btnBreakRequest.setSelectedToolTipText(
+ Constant.messages.getString("brk.toolbar.button.request.unset"));
+
+ return btnBreakRequest;
+ }
+
+ public JToggleButton getBtnBreakResponse() {
+ ZapToggleButton btnBreakResponse;
+
+ btnBreakResponse = new ZapToggleButton(breakResponsesButtonAction);
+ btnBreakResponse.setSelectedIcon(getScaledIcon("16/106r.png"));
+
+ btnBreakResponse.setSelectedToolTipText(
+ Constant.messages.getString("brk.toolbar.button.response.unset"));
+
+ return btnBreakResponse;
+ }
+
+ public JToggleButton getBtnBreakAll() {
+ ZapToggleButton btnBreakAll;
+
+ btnBreakAll = new ZapToggleButton(breakAllButtonAction);
+ btnBreakAll.setSelectedIcon(getScaledIcon("16/151.png"));
+
+ btnBreakAll.setSelectedToolTipText(
+ Constant.messages.getString("brk.toolbar.button.all.unset"));
+
+ return btnBreakAll;
+ }
+
+ public JToggleButton getBtnBreakOnJavaScript() {
+ ZapToggleButton btnBreakOnJavaScript;
+
+ btnBreakOnJavaScript = new ZapToggleButton(setBreakOnJavaScriptAction);
+ btnBreakOnJavaScript.setSelectedIcon(getScaledIcon("breakTypes/javascriptNotBreaking.png"));
+ btnBreakOnJavaScript.setSelectedToolTipText(
+ Constant.messages.getString("brk.toolbar.button.brkjavascript.set"));
+
+ return btnBreakOnJavaScript;
+ }
+
+ public JToggleButton getBtnBreakOnCssAndFonts() {
+ ZapToggleButton btnBreakOnCssAndFonts;
+
+ btnBreakOnCssAndFonts = new ZapToggleButton(setBreakOnCssAndFontsAction);
+ btnBreakOnCssAndFonts.setSelectedIcon(
+ getScaledIcon("breakTypes/cssAndFontsNotBreaking.png"));
+ btnBreakOnCssAndFonts.setSelectedToolTipText(
+ Constant.messages.getString("brk.toolbar.button.brkcssfonts.set"));
+
+ return btnBreakOnCssAndFonts;
+ }
+
+ public JToggleButton getBtnBreakOnMultimedia() {
+ ZapToggleButton btnBreakOnMultimedia;
+
+ btnBreakOnMultimedia = new ZapToggleButton(setBreakOnMultimediaAction);
+ btnBreakOnMultimedia.setSelectedIcon(getScaledIcon("breakTypes/multimediaNotBreaking.png"));
+ btnBreakOnMultimedia.setSelectedToolTipText(
+ Constant.messages.getString("brk.toolbar.button.brkmultimedia.set"));
+
+ return btnBreakOnMultimedia;
+ }
+
+ public JToggleButton getBtnOnlyBreakOnScope() {
+ ZapToggleButton btnOnlyBreakOnScope;
+
+ btnOnlyBreakOnScope = new ZapToggleButton(setOnlyBreakOnScopeAction);
+ btnOnlyBreakOnScope.setSelectedIcon(getScaledIcon("fugue/target.png"));
+
+ btnOnlyBreakOnScope.setSelectedToolTipText(
+ Constant.messages.getString("brk.toolbar.button.brkOnlyOnScope.unset"));
+
+ return btnOnlyBreakOnScope;
+ }
+
+ public JButton getBtnBreakPoint() {
+ return new JButton(addBreakpointButtonAction);
+ }
+
+ public boolean isStepping() {
+ return stepping;
+ }
+
+ private void resetRequestSerialization(boolean forceSerialize) {
+ if (Control.getSingleton() == null) {
+ // Still in setup
+ return;
+ }
+ // If forces or either break buttons are pressed force the proxy to submit requests and
+ // responses serially
+ boolean serialise = forceSerialize || isBreakRequest() || isBreakResponse() || isBreakAll;
+ if (extensionBreak != null) {
+ extensionBreak.getSerialisationRequiredListeners().forEach(e -> e.accept(serialise));
+ }
+ }
+
+ public void setBreakRequest(boolean brk) {
+ isBreakRequest = brk;
+ resetRequestSerialization(false);
+
+ breakRequestsButtonAction.setSelected(isBreakRequest);
+ }
+
+ public void setBreakResponse(boolean brk) {
+ isBreakResponse = brk;
+ resetRequestSerialization(false);
+
+ breakResponsesButtonAction.setSelected(isBreakResponse);
+ }
+
+ public void setBreakAll(boolean brk) {
+ isBreakAll = brk;
+ if (!brk) {
+ stepping = false;
+ }
+
+ resetRequestSerialization(false);
+
+ breakAllButtonAction.setSelected(isBreakAll);
+ }
+
+ public void setBreakOnJavaScript(boolean brk) {
+ isBreakOnJavaScript = brk;
+ setBreakOnJavaScriptAction.setSelected(!isBreakOnJavaScript);
+ ignoreJavascriptBreakpointMessage.setEnabled(!isBreakOnJavaScript);
+ }
+
+ public void setBreakOnCssAndFonts(boolean brk) {
+ isBreakOnCssAndFonts = brk;
+ setBreakOnCssAndFontsAction.setSelected(!isBreakOnCssAndFonts);
+ ignoreCssAndFontsBreakpointMessage.setEnabled(!isBreakOnCssAndFonts);
+ }
+
+ public void setBreakOnMultimedia(boolean brk) {
+ isBreakOnMultimedia = brk;
+ setBreakOnMultimediaAction.setSelected(!isBreakOnMultimedia);
+ ignoreMultimediaBreakpointMessage.setEnabled(!isBreakOnMultimedia);
+ }
+
+ public void setOnlyBreakOnScope(boolean brk) {
+ breakpointsParams.setInScopeOnly(brk);
+ setOnlyBreakOnScopeAction.setSelected(brk);
+ }
+
+ private void toggleBreakRequest() {
+ setBreakRequest(!isBreakRequest);
+ }
+
+ private void toggleBreakResponse() {
+ setBreakResponse(!isBreakResponse);
+ }
+
+ private void toggleBreakAll() {
+ setBreakAll(!isBreakAll);
+ }
+
+ private void toggleBreakOnJavascript() {
+ setBreakOnJavaScript(!isBreakOnJavaScript);
+ }
+
+ private void toggleBreakOnCssAndFonts() {
+ setBreakOnCssAndFonts(!isBreakOnCssAndFonts);
+ }
+
+ private void toggleBreakOnMultimedia() {
+ setBreakOnMultimedia(!isBreakOnMultimedia);
+ }
+
+ private void toggleBreakOnScopeOnly() {
+ setOnlyBreakOnScope(!breakpointsParams.isInScopeOnly());
+ }
+
+ public boolean isHoldMessage() {
+ if (isHoldMessageImpl()) {
+ return true;
+ }
+
+ synchronized (countLock) {
+ countCaughtMessages--;
+ if (countCaughtMessages == 0) {
+ setButtonsAndIconState(false);
+ }
+ }
+ return false;
+ }
+
+ private boolean isHoldMessageImpl() {
+ if (step) {
+ // Only works one time, until its pressed again
+ stepping = true;
+ step = false;
+ return false;
+ }
+ if (cont) {
+ // They've pressed the continue button, stop stepping
+ stepping = false;
+ resetRequestSerialization(false);
+ return false;
+ }
+ if (drop) {
+ return false;
+ }
+ return true;
+ }
+
+ public boolean isContinue() {
+ return cont;
+ }
+
+ public void setBreakEnabled(boolean enabled) {
+ if (!enabled) {
+ this.isBreakRequest = false;
+ this.isBreakResponse = false;
+ this.isBreakAll = false;
+ this.setContinue(true);
+ }
+ breakRequestsButtonAction.setSelected(false);
+ breakRequestsButtonAction.setEnabled(enabled);
+
+ breakResponsesButtonAction.setSelected(false);
+ breakResponsesButtonAction.setEnabled(enabled);
+
+ breakAllButtonAction.setSelected(false);
+ breakAllButtonAction.setEnabled(enabled);
+ }
+
+ private void setButtonsAndIconState(boolean enabled) {
+ stepButtonAction.setEnabled(enabled);
+ continueButtonAction.setEnabled(enabled);
+ dropButtonAction.setEnabled(enabled);
+ if (!enabled) {
+ this.setActiveIcon(false);
+ }
+ }
+
+ protected void setContinue(boolean isContinue) {
+ this.cont = isContinue;
+ setButtonsAndIconState(!isContinue);
+ }
+
+ protected void step() {
+ step = true;
+ Stats.incCounter(ExtensionBreak.BREAK_POINT_STEP_STATS);
+ }
+
+ protected void drop() {
+ if (breakpointsParams.isConfirmDropMessage()
+ && askForDropConfirmation() != JOptionPane.OK_OPTION) {
+ return;
+ }
+ drop = true;
+ Stats.incCounter(ExtensionBreak.BREAK_POINT_DROP_STATS);
+ }
+
+ public boolean isToBeDropped() {
+ if (drop) {
+ drop = false;
+ return true;
+ }
+ return false;
+ }
+
+ public void init() {
+ cont = false;
+ step = false;
+ stepping = false;
+ drop = false;
+ isBreakRequest = false;
+ isBreakResponse = false;
+ isBreakAll = false;
+ setShowIgnoreFilesButtons(false);
+ countCaughtMessages = 0;
+ }
+
+ public void reset() {
+ if (isBreakRequest()) {
+ toggleBreakRequest();
+ }
+
+ if (isBreakResponse()) {
+ toggleBreakResponse();
+ }
+
+ if (isBreakAll()) {
+ toggleBreakAll();
+ }
+
+ setContinue(true);
+ }
+
+ /**
+ * Sets the current button mode.
+ *
+ *
If the mode is already set no change is done, otherwise it does the following:
+ *
+ *
+ *
When changing from {@link BreakpointsParam#BUTTON_MODE_SIMPLE BUTTON_MODE_SIMPLE} to
+ * {@link BreakpointsParam#BUTTON_MODE_DUAL BUTTON_MODE_DUAL} set "break on request" and
+ * "on response" enabled and "break on all" disabled, if "break on all" is enabled;
+ *
When changing from {@code BUTTON_MODE_DUAL} to {@code BUTTON_MODE_SIMPLE} set "break on
+ * all" enabled and "break on request" and "on response" disabled, if at least one of
+ * "break on request" and "on response" is enabled;
+ *
If none of the "break on ..." states is enabled there's no changes in its states.
+ *
+ *
+ * The enabled state of previous mode is disabled to prevent interferences between the modes.
+ *
+ * @param mode the mode to be set
+ * @see #isBreakAll()
+ * @see #isBreakRequest()
+ * @see #isBreakResponse()
+ */
+ public void setButtonMode(int mode) {
+ if (this.mode == mode) {
+ return;
+ }
+ if (this.mode == BreakpointsParam.BUTTON_MODE_SIMPLE) {
+ if (isBreakAll) {
+ setBreakAll(false);
+ setBreakRequest(true);
+ setBreakResponse(true);
+ }
+ } else if (isBreakRequest || isBreakResponse) {
+ setBreakRequest(false);
+ setBreakResponse(false);
+ setBreakAll(true);
+ }
+ this.mode = mode;
+ }
+
+ public void setShowIgnoreFilesButtons(boolean showButtons) {
+ if (!showButtons) {
+ setBreakOnJavaScript(true);
+ setBreakOnCssAndFonts(true);
+ setBreakOnMultimedia(true);
+ setOnlyBreakOnScope(breakpointsParams.isInScopeOnly());
+ }
+ }
+
+ public void updateIgnoreFileTypesRegexs() {
+ ignoreJavascriptBreakpointMessage.setString(this.breakpointsParams.getJavascriptUrlRegex());
+ ignoreCssAndFontsBreakpointMessage.setString(
+ this.breakpointsParams.getCssAndFontsUrlRegex());
+ ignoreMultimediaBreakpointMessage.setString(this.breakpointsParams.getMultimediaUrlRegex());
+ }
+
+ private class ContinueButtonAction extends AbstractAction {
+
+ private static final long serialVersionUID = 1L;
+
+ public ContinueButtonAction() {
+ super(null, getScaledIcon("16/131.png"));
+
+ putValue(
+ Action.SHORT_DESCRIPTION,
+ Constant.messages.getString("brk.toolbar.button.cont"));
+
+ setEnabled(false);
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (!breakPanel.isValidState()) {
+ return;
+ }
+
+ setContinue(true);
+ setBreakAll(false);
+ setBreakRequest(false);
+ setBreakResponse(false);
+ }
+ }
+
+ private class StepButtonAction extends AbstractAction {
+
+ private static final long serialVersionUID = 1L;
+
+ public StepButtonAction() {
+ super(null, getScaledIcon("16/143.png"));
+
+ putValue(
+ Action.SHORT_DESCRIPTION,
+ Constant.messages.getString("brk.toolbar.button.step"));
+
+ setEnabled(false);
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (!breakPanel.isValidState()) {
+ return;
+ }
+
+ if (mode == BreakpointsParam.BUTTON_MODE_SIMPLE && !isBreakAll) {
+ // In simple mode 'step' if the breakAll button is disabled then it acts like
+ // 'continue'
+ // so that its hopefully obvious to users when break is on or not
+ setContinue(true);
+ } else {
+ step();
+ }
+ }
+ }
+
+ private class DropButtonAction extends AbstractAction {
+
+ private static final long serialVersionUID = 1L;
+
+ public DropButtonAction() {
+ super(null, getScaledIcon("16/150.png"));
+
+ putValue(
+ Action.SHORT_DESCRIPTION,
+ Constant.messages.getString("brk.toolbar.button.bin"));
+
+ setEnabled(false);
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ drop();
+ }
+ }
+
+ private class AddBreakpointButtonAction extends AbstractAction {
+
+ private static final long serialVersionUID = 1L;
+
+ public AddBreakpointButtonAction() {
+ super(null, getScaledIcon("16/break_add.png"));
+
+ putValue(
+ Action.SHORT_DESCRIPTION,
+ Constant.messages.getString("brk.toolbar.button.brkpoint"));
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ breakPanel.showNewBreakPointDialog();
+ }
+ }
+
+ private class BreakRequestsButtonAction extends SelectableAbstractAction {
+
+ private static final long serialVersionUID = 1L;
+
+ public BreakRequestsButtonAction() {
+ super(null, getScaledIcon("16/105.png"));
+
+ putValue(
+ Action.SHORT_DESCRIPTION,
+ Constant.messages.getString("brk.toolbar.button.request.set"));
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ toggleBreakRequest();
+ }
+ }
+
+ private class BreakResponsesButtonAction extends SelectableAbstractAction {
+
+ private static final long serialVersionUID = 1L;
+
+ public BreakResponsesButtonAction() {
+ super(null, getScaledIcon("16/106.png"));
+
+ putValue(
+ Action.SHORT_DESCRIPTION,
+ Constant.messages.getString("brk.toolbar.button.response.set"));
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ toggleBreakResponse();
+ }
+ }
+
+ private class BreakAllButtonAction extends SelectableAbstractAction {
+
+ private static final long serialVersionUID = 1L;
+
+ public BreakAllButtonAction() {
+ super(null, getScaledIcon("16/152.png"));
+
+ putValue(
+ Action.SHORT_DESCRIPTION,
+ Constant.messages.getString("brk.toolbar.button.all.set"));
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ toggleBreakAll();
+ }
+ }
+
+ private class SetBreakOnJavaScriptAction extends SelectableAbstractAction {
+
+ private static final long serialVersionUID = 1L;
+
+ public SetBreakOnJavaScriptAction() {
+ super(null, getScaledIcon("breakTypes/javascript.png"));
+
+ putValue(
+ Action.SHORT_DESCRIPTION,
+ Constant.messages.getString("brk.toolbar.button.brkjavascript.unset"));
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ toggleBreakOnJavascript();
+ }
+ }
+
+ private class SetBreakOnCssAndFontsAction extends SelectableAbstractAction {
+
+ private static final long serialVersionUID = 1L;
+
+ public SetBreakOnCssAndFontsAction() {
+ super(null, getScaledIcon("breakTypes/cssAndFonts.png"));
+
+ putValue(
+ Action.SHORT_DESCRIPTION,
+ Constant.messages.getString("brk.toolbar.button.brkcssfonts.unset"));
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ toggleBreakOnCssAndFonts();
+ }
+ }
+
+ private class SetBreakOnMultimediaAction extends SelectableAbstractAction {
+
+ private static final long serialVersionUID = 1L;
+
+ public SetBreakOnMultimediaAction() {
+ super(null, getScaledIcon("breakTypes/multimedia.png"));
+
+ putValue(
+ Action.SHORT_DESCRIPTION,
+ Constant.messages.getString("brk.toolbar.button.brkmultimedia.unset"));
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ toggleBreakOnMultimedia();
+ }
+ }
+
+ private class SetBreakOnlyOnScopeAction extends SelectableAbstractAction {
+
+ private static final long serialVersionUID = 1L;
+
+ public SetBreakOnlyOnScopeAction() {
+ super(null, getScaledIcon("fugue/target-grey.png"));
+
+ putValue(
+ Action.SHORT_DESCRIPTION,
+ Constant.messages.getString("brk.toolbar.button.brkOnlyOnScope.set"));
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ toggleBreakOnScopeOnly();
+ }
+ }
+
+ /**
+ * An {@code AbstractAction} which allows to be selected.
+ *
+ * @see AbstractAction
+ * @see #setSelected(boolean)
+ */
+ private abstract static class SelectableAbstractAction extends AbstractAction {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Creates a {@code SelectableAbstractAction} with the specified {@code name} and {@code
+ * icon}.
+ *
+ * @param name the name for the action or {@code null} for no name
+ * @param icon the icon for the action or {@code null} for no icon
+ */
+ public SelectableAbstractAction(String name, Icon icon) {
+ super(name, icon);
+ }
+
+ /**
+ * Sets whether the action is selected or not.
+ *
+ * @param selected {@code true} if the action should be selected, {@code false} otherwise
+ */
+ public void setSelected(boolean selected) {
+ putValue(Action.SELECTED_KEY, selected);
+ }
+ }
+}
diff --git a/addOns/brk/src/main/java/org/zaproxy/addon/brk/BreakpointManagementInterface.java b/addOns/brk/src/main/java/org/zaproxy/addon/brk/BreakpointManagementInterface.java
new file mode 100644
index 00000000000..434291ea446
--- /dev/null
+++ b/addOns/brk/src/main/java/org/zaproxy/addon/brk/BreakpointManagementInterface.java
@@ -0,0 +1,72 @@
+/*
+ * Zed Attack Proxy (ZAP) and its related class files.
+ *
+ * ZAP is an HTTP/HTTPS proxy for assessing web application security.
+ *
+ * Copyright 2016 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.addon.brk;
+
+import org.parosproxy.paros.control.Control.Mode;
+import org.zaproxy.zap.extension.httppanel.Message;
+
+public interface BreakpointManagementInterface {
+
+ boolean isBreakRequest();
+
+ boolean isBreakResponse();
+
+ boolean isBreakAll();
+
+ void breakpointHit();
+
+ boolean isHoldMessage(Message aMessage);
+
+ boolean isStepping();
+
+ boolean isToBeDropped();
+
+ Message getMessage();
+
+ void setMessage(Message aMessage, boolean isRequest);
+
+ boolean isRequest();
+
+ void saveMessage(boolean isRequest);
+
+ void clearAndDisableRequest();
+
+ void clearAndDisableResponse();
+
+ void init();
+
+ void reset();
+
+ void sessionModeChanged(Mode mode);
+
+ void setBreakAllRequests(boolean brk);
+
+ void setBreakAllResponses(boolean brk);
+
+ void setBreakAll(boolean brk);
+
+ void step();
+
+ void cont();
+
+ void drop();
+
+ void breakpointDisplayed();
+}
diff --git a/addOns/brk/src/main/java/org/zaproxy/addon/brk/BreakpointMessageHandler2.java b/addOns/brk/src/main/java/org/zaproxy/addon/brk/BreakpointMessageHandler2.java
new file mode 100644
index 00000000000..04f2dd74395
--- /dev/null
+++ b/addOns/brk/src/main/java/org/zaproxy/addon/brk/BreakpointMessageHandler2.java
@@ -0,0 +1,239 @@
+/*
+ * Zed Attack Proxy (ZAP) and its related class files.
+ *
+ * ZAP is an HTTP/HTTPS proxy for assessing web application security.
+ *
+ * Copyright 2016 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.addon.brk;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.parosproxy.paros.control.Control.Mode;
+import org.zaproxy.zap.extension.httppanel.Message;
+import org.zaproxy.zap.utils.Stats;
+
+public class BreakpointMessageHandler2 {
+
+ private static final Logger LOGGER = LogManager.getLogger(BreakpointMessageHandler2.class);
+
+ protected static final Object SEMAPHORE = new Object();
+
+ protected final BreakpointManagementInterface breakMgmt;
+
+ protected List enabledBreakpoints;
+
+ protected List enabledIgnoreRules;
+
+ private List enabledKeyBreakpoints = new ArrayList<>();
+
+ public List getEnabledKeyBreakpoints() {
+ return enabledKeyBreakpoints;
+ }
+
+ public void setEnabledKeyBreakpoints(List enabledKeyBreakpoints) {
+ this.enabledKeyBreakpoints = enabledKeyBreakpoints;
+ }
+
+ public BreakpointMessageHandler2(BreakpointManagementInterface aBreakPanel) {
+ this.breakMgmt = aBreakPanel;
+ }
+
+ public void setEnabledBreakpoints(List breakpoints) {
+ this.enabledBreakpoints = breakpoints;
+ }
+
+ public void setEnabledIgnoreRules(List IgnoreRules) {
+ this.enabledIgnoreRules = IgnoreRules;
+ }
+
+ /**
+ * Do not call if in {@link Mode#safe}.
+ *
+ * @param aMessage
+ * @param onlyIfInScope
+ * @return False if message should be dropped.
+ */
+ public boolean handleMessageReceivedFromClient(Message aMessage, boolean onlyIfInScope) {
+ if (!isBreakpoint(aMessage, true, onlyIfInScope)) {
+ return true;
+ }
+
+ // Do this outside of the semaphore loop so that the 'continue' button can apply to all
+ // queued breakpoints
+ // but be reset when the next breakpoint is hit
+ breakMgmt.breakpointHit();
+ Stats.incCounter(ExtensionBreak.BREAK_POINT_HIT_STATS);
+ BreakEventPublisher.getPublisher().publishHitEvent(aMessage);
+
+ synchronized (SEMAPHORE) {
+ if (breakMgmt.isHoldMessage(aMessage)) {
+ BreakEventPublisher.getPublisher().publishActiveEvent(aMessage);
+ setBreakDisplay(aMessage, true);
+ waitUntilContinue(aMessage, true);
+ BreakEventPublisher.getPublisher().publishInactiveEvent(aMessage);
+ }
+ }
+ breakMgmt.clearAndDisableRequest();
+ return !breakMgmt.isToBeDropped();
+ }
+
+ /**
+ * Do not call if in {@link Mode#safe}.
+ *
+ * @param aMessage
+ * @param onlyIfInScope
+ * @return False if message should be dropped.
+ */
+ public boolean handleMessageReceivedFromServer(Message aMessage, boolean onlyIfInScope) {
+ if (!isBreakpoint(aMessage, false, onlyIfInScope)) {
+ return true;
+ }
+
+ // Do this outside of the semaphore loop so that the 'continue' button can apply to all
+ // queued breakpoints
+ // but be reset when the next breakpoint is hit
+ breakMgmt.breakpointHit();
+ Stats.incCounter(ExtensionBreak.BREAK_POINT_HIT_STATS);
+ BreakEventPublisher.getPublisher().publishHitEvent(aMessage);
+
+ synchronized (SEMAPHORE) {
+ if (breakMgmt.isHoldMessage(aMessage)) {
+ BreakEventPublisher.getPublisher().publishActiveEvent(aMessage);
+ setBreakDisplay(aMessage, false);
+ waitUntilContinue(aMessage, false);
+ BreakEventPublisher.getPublisher().publishInactiveEvent(aMessage);
+ }
+ }
+ breakMgmt.clearAndDisableResponse();
+ return !breakMgmt.isToBeDropped();
+ }
+
+ private void setBreakDisplay(final Message msg, boolean isRequest) {
+ breakMgmt.setMessage(msg, isRequest);
+ breakMgmt.breakpointDisplayed();
+ }
+
+ private void waitUntilContinue(Message aMessage, final boolean isRequest) {
+ // Note that multiple requests and responses can get built up, so pressing continue only
+ // releases the current break, not all of them.
+ while (breakMgmt.isHoldMessage(aMessage)) {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ LOGGER.warn(e.getMessage(), e);
+ }
+ }
+ breakMgmt.saveMessage(isRequest);
+ }
+
+ /**
+ * You have to handle {@link Mode#safe} outside.
+ *
+ * @param aMessage
+ * @param isRequest
+ * @param onlyIfInScope
+ * @return True if a breakpoint for given message exists.
+ */
+ public boolean isBreakpoint(Message aMessage, boolean isRequest, boolean onlyIfInScope) {
+ if (aMessage.isForceIntercept()) {
+ // The browser told us to do it Your Honour
+ return true;
+ }
+
+ if (isSkipOnIgnoreRules(aMessage, isRequest, onlyIfInScope)) {
+ return false;
+ }
+
+ if (onlyIfInScope && !aMessage.isInScope()) {
+ return false;
+ }
+
+ if (isBreakOnAllRequests(aMessage, isRequest)) {
+ // Break on all requests
+ return true;
+ } else if (isBreakOnAllResponses(aMessage, isRequest)) {
+ // Break on all responses
+ return true;
+ } else if (isBreakOnStepping(aMessage, isRequest)) {
+ // Stopping through all requests and responses
+ return true;
+ }
+
+ return isBreakOnEnabledBreakpoint(aMessage, isRequest, onlyIfInScope);
+ }
+
+ protected boolean isBreakOnAllRequests(Message aMessage, boolean isRequest) {
+ return isRequest && breakMgmt.isBreakRequest();
+ }
+
+ protected boolean isBreakOnAllResponses(Message aMessage, boolean isRequest) {
+ return !isRequest && breakMgmt.isBreakResponse();
+ }
+
+ protected boolean isBreakOnStepping(Message aMessage, boolean isRequest) {
+ return breakMgmt.isStepping();
+ }
+
+ protected boolean isBreakOnEnabledBreakpoint(
+ Message aMessage, boolean isRequest, boolean onlyIfInScope) {
+ if (enabledBreakpoints.isEmpty()) {
+ // No breakpoints
+ return false;
+ }
+
+ // match against the breakpoints
+ synchronized (enabledBreakpoints) {
+ Iterator it = enabledBreakpoints.iterator();
+
+ while (it.hasNext()) {
+ BreakpointMessageInterface breakpoint = it.next();
+
+ if (breakpoint.match(aMessage, isRequest, onlyIfInScope)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ protected boolean isSkipOnIgnoreRules(
+ Message aMessage, boolean isRequest, boolean onlyIfInScope) {
+ if (enabledIgnoreRules == null || enabledIgnoreRules.isEmpty()) {
+ // No Ignoring rules
+ return false;
+ }
+
+ // match against the ignoring rule
+ synchronized (enabledIgnoreRules) {
+ Iterator it = enabledIgnoreRules.iterator();
+
+ while (it.hasNext()) {
+ BreakpointMessageInterface ignoreRule = it.next();
+
+ if (ignoreRule.isEnabled()
+ && ignoreRule.match(aMessage, isRequest, onlyIfInScope)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/addOns/brk/src/main/java/org/zaproxy/addon/brk/BreakpointMessageInterface.java b/addOns/brk/src/main/java/org/zaproxy/addon/brk/BreakpointMessageInterface.java
new file mode 100644
index 00000000000..b72b80a1d2b
--- /dev/null
+++ b/addOns/brk/src/main/java/org/zaproxy/addon/brk/BreakpointMessageInterface.java
@@ -0,0 +1,35 @@
+/*
+ * Zed Attack Proxy (ZAP) and its related class files.
+ *
+ * ZAP is an HTTP/HTTPS proxy for assessing web application security.
+ *
+ * Copyright 2012 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.addon.brk;
+
+import org.zaproxy.zap.extension.httppanel.Message;
+
+public interface BreakpointMessageInterface {
+
+ String getType();
+
+ boolean match(Message aMessage, boolean isRequest, boolean onlyIfInScope);
+
+ void setEnabled(boolean enabled);
+
+ boolean isEnabled();
+
+ String getDisplayMessage();
+}
diff --git a/addOns/brk/src/main/java/org/zaproxy/addon/brk/BreakpointsOptionsPanel.java b/addOns/brk/src/main/java/org/zaproxy/addon/brk/BreakpointsOptionsPanel.java
new file mode 100644
index 00000000000..b2f15fd9670
--- /dev/null
+++ b/addOns/brk/src/main/java/org/zaproxy/addon/brk/BreakpointsOptionsPanel.java
@@ -0,0 +1,245 @@
+/*
+ * Zed Attack Proxy (ZAP) and its related class files.
+ *
+ * ZAP is an HTTP/HTTPS proxy for assessing web application security.
+ *
+ * Copyright 2013 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.addon.brk;
+
+import java.awt.CardLayout;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.border.EmptyBorder;
+import org.parosproxy.paros.Constant;
+import org.parosproxy.paros.extension.option.OptionsViewPanel;
+import org.parosproxy.paros.model.OptionsParam;
+import org.parosproxy.paros.view.AbstractParamPanel;
+import org.zaproxy.zap.utils.ZapTextField;
+import org.zaproxy.zap.view.LayoutHelper;
+
+/**
+ * The GUI breakpoints options panel.
+ *
+ *
It allows to change the following breakpoints options:
+ *
+ *
+ *
Confirm drop message - asks for confirmation when a trapped message is dropped.
+ *
+ *
+ * @see org.zaproxy.addon.brk.BreakPanelToolbarFactory#getBtnDrop()
+ */
+public class BreakpointsOptionsPanel extends AbstractParamPanel {
+
+ private static final long serialVersionUID = 7483614036849207715L;
+
+ private JCheckBox checkBoxConfirmDropMessage = null;
+ private JCheckBox checkBoxAlwaysOnTop = null;
+ private JCheckBox checkBoxInScopeOnly = null;
+ private JCheckBox checkBoxShowIgnoreFilesButtons = null;
+ private ZapTextField javascriptUrlRegexField = null;
+ private ZapTextField cssAndFontsUrlRegexField = null;
+ private ZapTextField multimediaUrlRegexField = null;
+ private JComboBox buttonMode = null;
+
+ public BreakpointsOptionsPanel() {
+ super();
+ setName(Constant.messages.getString("brk.optionspanel.name"));
+
+ this.setLayout(new CardLayout());
+
+ JPanel panel = new JPanel(new GridBagLayout());
+ panel.setBorder(new EmptyBorder(2, 2, 2, 2));
+
+ int row = 0;
+ panel.add(
+ getCheckBoxConfirmDropMessage(),
+ LayoutHelper.getGBC(0, row++, 2, 1.0, new Insets(2, 2, 2, 2)));
+ panel.add(
+ getCheckBoxInScopeOnly(),
+ LayoutHelper.getGBC(0, row++, 2, 1.0, new Insets(2, 2, 2, 2)));
+ panel.add(
+ getCheckBoxAlwaysOnTop(),
+ LayoutHelper.getGBC(0, row++, 2, 1.0, new Insets(2, 2, 2, 2)));
+ panel.add(
+ getCheckBoxShowIgnoreFilesButtons(),
+ LayoutHelper.getGBC(0, row++, 2, 1.0, new Insets(2, 2, 2, 2)));
+
+ JLabel javascriptUrlRegexLabel =
+ new JLabel(
+ Constant.messages.getString(
+ "brk.optionspanel.option.javaScriptUrlRegex.label"));
+ javascriptUrlRegexLabel.setLabelFor(getJavascriptUrlRegexField());
+
+ panel.add(javascriptUrlRegexLabel, LayoutHelper.getGBC(0, 4, 1, 2.0));
+ panel.add(getJavascriptUrlRegexField(), LayoutHelper.getGBC(1, 4, 1, 8.0));
+
+ JLabel cssAndFontsUrlRegexLabel =
+ new JLabel(
+ Constant.messages.getString(
+ "brk.optionspanel.option.cssAndFontsUrlRegex.label"));
+ cssAndFontsUrlRegexLabel.setLabelFor(getCssAndFontsUrlRegexField());
+
+ panel.add(cssAndFontsUrlRegexLabel, LayoutHelper.getGBC(0, 5, 1, 2.0));
+ panel.add(getCssAndFontsUrlRegexField(), LayoutHelper.getGBC(1, 5, 1, 8.0));
+
+ JLabel multimediaUrlRegexLabel =
+ new JLabel(
+ Constant.messages.getString(
+ "brk.optionspanel.option.multimediaUrlRegex.label"));
+ multimediaUrlRegexLabel.setLabelFor(getMultimediaUrlRegexField());
+
+ panel.add(multimediaUrlRegexLabel, LayoutHelper.getGBC(0, 6, 1, 2.0));
+ panel.add(getMultimediaUrlRegexField(), LayoutHelper.getGBC(1, 6, 1, 8.0));
+
+ JLabel modeLabel =
+ new JLabel(Constant.messages.getString("brk.optionspanel.option.breakmode.label"));
+ modeLabel.setLabelFor(getButtonMode());
+ panel.add(modeLabel, LayoutHelper.getGBC(0, 7, 1, 0.5));
+ panel.add(getButtonMode(), LayoutHelper.getGBC(1, 7, 1, 0.5));
+ panel.add(new JLabel(), LayoutHelper.getGBC(0, 10, 1, 0.5D, 1.0D)); // Spacer
+
+ add(panel);
+ }
+
+ private JCheckBox getCheckBoxConfirmDropMessage() {
+ if (checkBoxConfirmDropMessage == null) {
+ checkBoxConfirmDropMessage =
+ new JCheckBox(
+ Constant.messages.getString(
+ "brk.optionspanel.option.confirmDropMessage.label"));
+ }
+ return checkBoxConfirmDropMessage;
+ }
+
+ private JCheckBox getCheckBoxAlwaysOnTop() {
+ if (checkBoxAlwaysOnTop == null) {
+ checkBoxAlwaysOnTop =
+ new JCheckBox(
+ Constant.messages.getString(
+ "brk.optionspanel.option.alwaysOnTop.label"));
+ }
+ return checkBoxAlwaysOnTop;
+ }
+
+ private JCheckBox getCheckBoxInScopeOnly() {
+ if (checkBoxInScopeOnly == null) {
+ checkBoxInScopeOnly =
+ new JCheckBox(
+ Constant.messages.getString(
+ "brk.optionspanel.option.inScopeOnly.label"));
+ }
+ return checkBoxInScopeOnly;
+ }
+
+ private JCheckBox getCheckBoxShowIgnoreFilesButtons() {
+ if (checkBoxShowIgnoreFilesButtons == null) {
+ checkBoxShowIgnoreFilesButtons =
+ new JCheckBox(
+ Constant.messages.getString(
+ "brk.optionspanel.option.showBreakFilteringButtons.label"));
+ }
+ return checkBoxShowIgnoreFilesButtons;
+ }
+
+ private ZapTextField getJavascriptUrlRegexField() {
+ if (javascriptUrlRegexField == null) {
+ javascriptUrlRegexField = new ZapTextField();
+ }
+ return javascriptUrlRegexField;
+ }
+
+ private ZapTextField getCssAndFontsUrlRegexField() {
+ if (cssAndFontsUrlRegexField == null) {
+ cssAndFontsUrlRegexField = new ZapTextField();
+ }
+ return cssAndFontsUrlRegexField;
+ }
+
+ private ZapTextField getMultimediaUrlRegexField() {
+ if (multimediaUrlRegexField == null) {
+ multimediaUrlRegexField = new ZapTextField();
+ }
+ return multimediaUrlRegexField;
+ }
+
+ private JComboBox getButtonMode() {
+ if (buttonMode == null) {
+ buttonMode = new JComboBox<>();
+ buttonMode.addItem(
+ Constant.messages.getString("brk.optionspanel.option.breakmode.simple.label"));
+ buttonMode.addItem(
+ Constant.messages.getString("brk.optionspanel.option.breakmode.dual.label"));
+ }
+ return buttonMode;
+ }
+
+ @Override
+ public void initParam(Object obj) {
+ final OptionsParam options = (OptionsParam) obj;
+ final BreakpointsParam param = options.getParamSet(BreakpointsParam.class);
+
+ getCheckBoxConfirmDropMessage().setSelected(param.isConfirmDropMessage());
+ // Note param.alwaysOnTop will be null if the user hasn't specified a preference yet
+ getCheckBoxAlwaysOnTop().setSelected(!Boolean.FALSE.equals(param.getAlwaysOnTop()));
+ getCheckBoxInScopeOnly().setSelected(param.isInScopeOnly());
+ getCheckBoxShowIgnoreFilesButtons().setSelected(param.isShowIgnoreFilesButtons());
+ if (options.getViewParam().getBrkPanelViewOption()
+ == OptionsViewPanel.BreakLocation.TOOL_BAR_ONLY.getValue()) {
+ checkBoxShowIgnoreFilesButtons.setEnabled(false);
+ checkBoxShowIgnoreFilesButtons.setToolTipText(
+ Constant.messages.getString("brk.optionspanel.option.notpossibletoshowtip"));
+ } else {
+ checkBoxShowIgnoreFilesButtons.setEnabled(true);
+ checkBoxShowIgnoreFilesButtons.setToolTipText("");
+ }
+ getButtonMode().setSelectedIndex(param.getButtonMode() - 1);
+ getJavascriptUrlRegexField().setText(param.getJavascriptUrlRegex());
+ getJavascriptUrlRegexField().discardAllEdits();
+ getCssAndFontsUrlRegexField().setText(param.getCssAndFontsUrlRegex());
+ getCssAndFontsUrlRegexField().discardAllEdits();
+ getMultimediaUrlRegexField().setText(param.getMultimediaUrlRegex());
+ getMultimediaUrlRegexField().discardAllEdits();
+ }
+
+ @Override
+ public void saveParam(Object obj) throws Exception {
+ final OptionsParam options = (OptionsParam) obj;
+ final BreakpointsParam param = options.getParamSet(BreakpointsParam.class);
+
+ param.setConfirmDropMessage(getCheckBoxConfirmDropMessage().isSelected());
+ if (param.getAlwaysOnTop() != null || !getCheckBoxAlwaysOnTop().isSelected()) {
+ // Dont set the option if its not already set, unless the user has changed it
+ // This is so that the warning message will still be shown the first time a breakpoint
+ // is hit
+ param.setAlwaysOnTop(getCheckBoxAlwaysOnTop().isSelected());
+ }
+ param.setInScopeOnly(getCheckBoxInScopeOnly().isSelected());
+ param.setShowIgnoreFilesButtons(getCheckBoxShowIgnoreFilesButtons().isSelected());
+ param.setButtonMode(this.getButtonMode().getSelectedIndex() + 1);
+ param.setJavascriptUrlRegex(getJavascriptUrlRegexField().getText());
+ param.setCssAndFontsUrlRegex(getCssAndFontsUrlRegexField().getText());
+ param.setMultimediaUrlRegex(getMultimediaUrlRegexField().getText());
+ }
+
+ @Override
+ public String getHelpIndex() {
+ return "ui.dialogs.options.breakpoints";
+ }
+}
diff --git a/addOns/brk/src/main/java/org/zaproxy/addon/brk/BreakpointsPanel.java b/addOns/brk/src/main/java/org/zaproxy/addon/brk/BreakpointsPanel.java
new file mode 100644
index 00000000000..54d38327b86
--- /dev/null
+++ b/addOns/brk/src/main/java/org/zaproxy/addon/brk/BreakpointsPanel.java
@@ -0,0 +1,355 @@
+/*
+ * Zed Attack Proxy (ZAP) and its related class files.
+ *
+ * ZAP is an HTTP/HTTPS proxy for assessing web application security.
+ *
+ * Copyright 2010 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.addon.brk;
+
+import java.awt.CardLayout;
+import java.awt.EventQueue;
+import java.awt.GridBagConstraints;
+import java.awt.event.KeyEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.prefs.BackingStoreException;
+import java.util.prefs.Preferences;
+import javax.swing.ImageIcon;
+import javax.swing.JScrollPane;
+import javax.swing.SwingUtilities;
+import javax.swing.table.TableColumn;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.jdesktop.swingx.JXTable;
+import org.parosproxy.paros.Constant;
+import org.parosproxy.paros.extension.AbstractPanel;
+import org.parosproxy.paros.view.View;
+
+@SuppressWarnings("serial")
+public class BreakpointsPanel extends AbstractPanel {
+
+ private static final long serialVersionUID = 1L;
+
+ public static final String PANEL_NAME = "breakpoints";
+
+ private ExtensionBreak extension;
+ private javax.swing.JPanel panelCommand = null;
+ private javax.swing.JLabel jLabel = null;
+ private JScrollPane jScrollPane = null;
+ private JXTable breakpointsTable = null;
+ private BreakpointsTableModel model = new BreakpointsTableModel();
+
+ private static final String BRK_TABLE = "brk.table";
+ private static final String PREF_COLUMN_WIDTH = "column.width";
+ private final Preferences preferences;
+ private final String prefnzPrefix = this.getClass().getSimpleName() + ".";
+
+ private static final Logger LOGGER = LogManager.getLogger(BreakpointsPanel.class);
+
+ public BreakpointsPanel(ExtensionBreak extension) {
+ super();
+ this.extension = extension;
+ this.preferences = Preferences.userNodeForPackage(getClass());
+
+ initialize();
+ }
+
+ private void initialize() {
+ this.setLayout(new CardLayout());
+ this.setSize(474, 251);
+ this.setName(Constant.messages.getString("brk.panel.title"));
+ this.setIcon(
+ new ImageIcon(
+ BreakpointsPanel.class.getResource(
+ "/resource/icon/16/101.png"))); // 'red X' icon
+ this.setDefaultAccelerator(
+ extension
+ .getView()
+ .getMenuShortcutKeyStroke(
+ KeyEvent.VK_B,
+ KeyEvent.ALT_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK,
+ false));
+ this.setMnemonic(Constant.messages.getChar("brk.panel.mnemonic"));
+ this.add(getPanelCommand(), getPanelCommand().getName());
+ }
+
+ private javax.swing.JPanel getPanelCommand() {
+ if (panelCommand == null) {
+
+ panelCommand = new javax.swing.JPanel();
+ panelCommand.setLayout(new java.awt.GridBagLayout());
+ panelCommand.setName(Constant.messages.getString("brk.panel.title"));
+
+ jLabel = getJLabel();
+ GridBagConstraints gridBagConstraints1 = new GridBagConstraints();
+ GridBagConstraints gridBagConstraints2 = new GridBagConstraints();
+
+ // Better without this?
+ // jLabel.setText("Breakpoints:");
+ 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(jLabel, gridBagConstraints1);
+ panelCommand.add(getJScrollPane(), gridBagConstraints2);
+ }
+ return panelCommand;
+ }
+
+ private javax.swing.JLabel getJLabel() {
+ if (jLabel == null) {
+ jLabel = new javax.swing.JLabel();
+ jLabel.setText(" ");
+ }
+ return jLabel;
+ }
+
+ private JScrollPane getJScrollPane() {
+ if (jScrollPane == null) {
+ jScrollPane = new JScrollPane();
+ jScrollPane.setViewportView(getBreakpoints());
+ }
+ return jScrollPane;
+ }
+
+ protected JXTable getBreakpoints() {
+ if (breakpointsTable == null) {
+ breakpointsTable = new JXTable(model);
+
+ breakpointsTable.setColumnSelectionAllowed(false);
+ breakpointsTable.setCellSelectionEnabled(false);
+ breakpointsTable.setRowSelectionAllowed(true);
+ breakpointsTable.setColumnControlVisible(true);
+
+ breakpointsTable
+ .getColumnModel()
+ .getColumn(0)
+ .setPreferredWidth(restoreColumnWidth(BRK_TABLE, 100));
+ breakpointsTable
+ .getColumnModel()
+ .getColumn(0)
+ .addPropertyChangeListener(new ColumnResizedListener(BRK_TABLE));
+ breakpointsTable.getColumnModel().getColumn(0).setMaxWidth(250);
+
+ breakpointsTable.getTableHeader().setReorderingAllowed(false);
+
+ breakpointsTable.setName(PANEL_NAME);
+ breakpointsTable.setDoubleBuffered(true);
+ breakpointsTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
+ breakpointsTable.addMouseListener(
+ new java.awt.event.MouseAdapter() {
+ @Override
+ public void mousePressed(java.awt.event.MouseEvent e) {
+
+ showPopupMenuIfTriggered(e);
+ }
+
+ @Override
+ public void mouseReleased(java.awt.event.MouseEvent e) {
+ showPopupMenuIfTriggered(e);
+ }
+
+ private void showPopupMenuIfTriggered(java.awt.event.MouseEvent e) {
+ if (e.isPopupTrigger()) {
+
+ // Select table item
+ int row = breakpointsTable.rowAtPoint(e.getPoint());
+ if (row < 0
+ || !breakpointsTable
+ .getSelectionModel()
+ .isSelectedIndex(row)) {
+ breakpointsTable.getSelectionModel().clearSelection();
+ if (row >= 0) {
+ breakpointsTable
+ .getSelectionModel()
+ .setSelectionInterval(row, row);
+ }
+ }
+
+ View.getSingleton()
+ .getPopupMenu()
+ .show(e.getComponent(), e.getX(), e.getY());
+ }
+ }
+
+ @Override
+ public void mouseClicked(java.awt.event.MouseEvent e) {
+ if (SwingUtilities.isLeftMouseButton(e) && e.getClickCount() > 1) {
+ // Its a double click
+ extension.editUiSelectedBreakpoint();
+ }
+ }
+ });
+ }
+ return breakpointsTable;
+ }
+
+ public BreakpointMessageInterface getSelectedBreakpoint() {
+ int selectedRow = breakpointsTable.getSelectedRow();
+ if (selectedRow != -1) {
+ return model.getBreakpointAtRow(breakpointsTable.convertRowIndexToModel(selectedRow));
+ }
+ return null;
+ }
+
+ private void selectRowAndEnsureVisible(int row) {
+ if (row != -1) {
+ breakpointsTable.getSelectionModel().setSelectionInterval(row, row);
+ breakpointsTable.scrollRectToVisible(breakpointsTable.getCellRect(row, 0, true));
+ }
+ }
+
+ private void addBreakpointModel(BreakpointMessageInterface breakpoint) {
+ model.addBreakpoint(breakpoint);
+ selectRowAndEnsureVisible(model.getLastAffectedRow());
+ }
+
+ void addBreakpoint(final BreakpointMessageInterface breakpoint) {
+ if (EventQueue.isDispatchThread()) {
+ addBreakpointModel(breakpoint);
+ return;
+ }
+ try {
+ EventQueue.invokeAndWait(
+ new Runnable() {
+ @Override
+ public void run() {
+ addBreakpointModel(breakpoint);
+ }
+ });
+ } catch (Exception e) {
+ LOGGER.error(e.getMessage(), e);
+ }
+ }
+
+ private void editBreakpointModel(
+ BreakpointMessageInterface oldBreakpoint, BreakpointMessageInterface newBreakpoint) {
+ model.editBreakpoint(oldBreakpoint, newBreakpoint);
+ selectRowAndEnsureVisible(model.getLastAffectedRow());
+ }
+
+ void editBreakpoint(
+ final BreakpointMessageInterface oldBreakpoint,
+ final BreakpointMessageInterface newBreakpoint) {
+ if (EventQueue.isDispatchThread()) {
+ editBreakpointModel(oldBreakpoint, newBreakpoint);
+ return;
+ }
+ try {
+ EventQueue.invokeAndWait(
+ new Runnable() {
+ @Override
+ public void run() {
+ editBreakpointModel(oldBreakpoint, newBreakpoint);
+ }
+ });
+ } catch (Exception e) {
+ LOGGER.error(e.getMessage(), e);
+ }
+ }
+
+ private void removeBreakpointModel(BreakpointMessageInterface breakpoint) {
+ model.removeBreakpoint(breakpoint);
+ }
+
+ public void removeBreakpoint(final BreakpointMessageInterface breakpoint) {
+ if (EventQueue.isDispatchThread()) {
+ removeBreakpointModel(breakpoint);
+ return;
+ }
+ try {
+ EventQueue.invokeAndWait(
+ new Runnable() {
+ @Override
+ public void run() {
+ removeBreakpointModel(breakpoint);
+ }
+ });
+ } catch (Exception e) {
+ LOGGER.error(e.getMessage(), e);
+ }
+ }
+
+ private void saveColumnWidth(String prefix, int width) {
+ if (width > 0) {
+ LOGGER.debug(
+ "Saving preference {}{}.{}={}", prefnzPrefix, prefix, PREF_COLUMN_WIDTH, width);
+ this.preferences.put(
+ prefnzPrefix + prefix + "." + PREF_COLUMN_WIDTH, Integer.toString(width));
+ // immediate flushing
+ try {
+ this.preferences.flush();
+ } catch (final BackingStoreException e) {
+ LOGGER.error("Error while saving the preferences", e);
+ }
+ }
+ }
+
+ private int restoreColumnWidth(String prefix, int fallback) {
+ int result = fallback;
+ final String sizestr =
+ preferences.get(prefnzPrefix + prefix + "." + PREF_COLUMN_WIDTH, null);
+ if (sizestr != null) {
+ int width = 0;
+ try {
+ width = Integer.parseInt(sizestr.trim());
+ } catch (final Exception e) {
+ // ignoring, cause is prevented by default values;
+ }
+ if (width > 0) {
+ result = width;
+ LOGGER.debug(
+ "Restoring preference {}{}.{}={}",
+ prefnzPrefix,
+ prefix,
+ PREF_COLUMN_WIDTH,
+ width);
+ }
+ }
+ return result;
+ }
+
+ private final class ColumnResizedListener implements PropertyChangeListener {
+
+ private final String prefix;
+
+ public ColumnResizedListener(String prefix) {
+ super();
+ assert prefix != null;
+ this.prefix = prefix;
+ }
+
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ TableColumn column = (TableColumn) evt.getSource();
+ if (column != null) {
+ LOGGER.debug(
+ "{}{}.{}={}", prefnzPrefix, prefix, PREF_COLUMN_WIDTH, column.getWidth());
+ saveColumnWidth(prefix, column.getWidth());
+ }
+ }
+ }
+}
diff --git a/addOns/brk/src/main/java/org/zaproxy/addon/brk/BreakpointsParam.java b/addOns/brk/src/main/java/org/zaproxy/addon/brk/BreakpointsParam.java
new file mode 100644
index 00000000000..e90d1a3d803
--- /dev/null
+++ b/addOns/brk/src/main/java/org/zaproxy/addon/brk/BreakpointsParam.java
@@ -0,0 +1,182 @@
+/*
+ * Zed Attack Proxy (ZAP) and its related class files.
+ *
+ * ZAP is an HTTP/HTTPS proxy for assessing web application security.
+ *
+ * Copyright 2013 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.addon.brk;
+
+import org.parosproxy.paros.common.AbstractParam;
+
+/**
+ * Manages the breakpoints configurations saved in the configuration file.
+ *
+ *
It allows to change, programmatically, the following breakpoints options:
+ *
+ *
+ *
Confirm drop message - asks for confirmation when a trapped message is dropped.
+ *
+ */
+ @Override
+ protected void parse() {
+ confirmDropMessage = getBoolean(PARAM_CONFIRM_DROP_MESSAGE_KEY, false);
+ buttonMode = getInt(PARAM_UI_BUTTON_MODE, BUTTON_MODE_SIMPLE);
+ alwaysOnTop = getConfig().getBoolean(PARAM_BRK_ALWAYS_ON_TOP, null);
+ inScopeOnly = getBoolean(PARAM_BRK_IN_SCOPE_ONLY, false);
+ showIgnoreFilesButtons = getBoolean(SHOW_IGNORE_REQUESTS_BUTTONS, false);
+ javascriptUrlRegex = getString(JAVASCRIPT_URL_REGEX, JAVASCRIPT_URL_REGEX_DEFAULT);
+ cssAndFontsUrlRegex = getString(CSS_AND_FONTS_URL_REGEX, CSS_AND_FONTS_URL_REGEX_DEFAULT);
+ multimediaUrlRegex = getString(MULTIMEDIA_URL_REGEX_DEFAULT, MULTIMEDIA_URL_REGEX_DEFAULT);
+ }
+
+ /**
+ * Tells whether the user should confirm the drop of the trapped message.
+ *
+ * @return {@code true} if the user should confirm the drop, {@code false} otherwise
+ * @see #setConfirmDropMessage(boolean)
+ */
+ public boolean isConfirmDropMessage() {
+ return confirmDropMessage;
+ }
+
+ /**
+ * Sets whether the user should confirm the drop of the trapped message.
+ *
+ * @param confirmDrop {@code true} if the user should confirm the drop, {@code false} otherwise
+ * @see #isConfirmDropMessage()
+ * @see org.zaproxy.addon.brk.BreakPanelToolbarFactory#getBtnDrop()
+ */
+ public void setConfirmDropMessage(boolean confirmDrop) {
+ if (confirmDropMessage != confirmDrop) {
+ this.confirmDropMessage = confirmDrop;
+ getConfig().setProperty(PARAM_CONFIRM_DROP_MESSAGE_KEY, confirmDrop);
+ }
+ }
+
+ public int getButtonMode() {
+ return buttonMode;
+ }
+
+ public void setButtonMode(int buttonMode) {
+ this.buttonMode = buttonMode;
+ getConfig().setProperty(PARAM_UI_BUTTON_MODE, buttonMode);
+ }
+
+ public Boolean getAlwaysOnTop() {
+ return alwaysOnTop;
+ }
+
+ public void setAlwaysOnTop(Boolean alwaysOnTop) {
+ this.alwaysOnTop = alwaysOnTop;
+ getConfig().setProperty(PARAM_BRK_ALWAYS_ON_TOP, alwaysOnTop);
+ }
+
+ public boolean isInScopeOnly() {
+ return inScopeOnly;
+ }
+
+ public void setInScopeOnly(boolean inScopeOnly) {
+ this.inScopeOnly = inScopeOnly;
+ getConfig().setProperty(PARAM_BRK_IN_SCOPE_ONLY, inScopeOnly);
+ }
+
+ public boolean isShowIgnoreFilesButtons() {
+ return showIgnoreFilesButtons;
+ }
+
+ public void setShowIgnoreFilesButtons(boolean showIgnoreFilesButtons) {
+ this.showIgnoreFilesButtons = showIgnoreFilesButtons;
+ getConfig().setProperty(SHOW_IGNORE_REQUESTS_BUTTONS, showIgnoreFilesButtons);
+ }
+
+ public String getJavascriptUrlRegex() {
+ return javascriptUrlRegex;
+ }
+
+ public void setJavascriptUrlRegex(String javascriptUrlRegex) {
+ this.javascriptUrlRegex = javascriptUrlRegex;
+ getConfig().setProperty(JAVASCRIPT_URL_REGEX, javascriptUrlRegex);
+ }
+
+ public String getCssAndFontsUrlRegex() {
+ return cssAndFontsUrlRegex;
+ }
+
+ public void setCssAndFontsUrlRegex(String cssAndFontsUrlRegex) {
+ this.cssAndFontsUrlRegex = cssAndFontsUrlRegex;
+ getConfig().setProperty(CSS_AND_FONTS_URL_REGEX, cssAndFontsUrlRegex);
+ }
+
+ public String getMultimediaUrlRegex() {
+ return multimediaUrlRegex;
+ }
+
+ public void setMultimediaUrlRegex(String multimediaUrlRegex) {
+ this.multimediaUrlRegex = multimediaUrlRegex;
+ getConfig().setProperty(MULTIMEDIA_URL_REGEX, multimediaUrlRegex);
+ }
+}
diff --git a/addOns/brk/src/main/java/org/zaproxy/addon/brk/BreakpointsTableModel.java b/addOns/brk/src/main/java/org/zaproxy/addon/brk/BreakpointsTableModel.java
new file mode 100644
index 00000000000..467a865543c
--- /dev/null
+++ b/addOns/brk/src/main/java/org/zaproxy/addon/brk/BreakpointsTableModel.java
@@ -0,0 +1,203 @@
+/*
+ * Zed Attack Proxy (ZAP) and its related class files.
+ *
+ * ZAP is an HTTP/HTTPS proxy for assessing web application security.
+ *
+ * Copyright 2012 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.addon.brk;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import javax.swing.table.AbstractTableModel;
+import org.parosproxy.paros.Constant;
+
+@SuppressWarnings("serial")
+public class BreakpointsTableModel extends AbstractTableModel {
+
+ private static final long serialVersionUID = -8160051343126299124L;
+
+ private static final int COLUMN_COUNT = 3;
+
+ private static final String[] columnNames = {
+ Constant.messages.getString("brk.table.header.enabled"),
+ Constant.messages.getString("brk.table.header.type"),
+ Constant.messages.getString("brk.table.header.condition")
+ };
+
+ private List breakpoints;
+ private List breakpointsEnabled;
+
+ private Map mapBreakpointRow;
+
+ private int lastAffectedRow;
+
+ public BreakpointsTableModel() {
+ super();
+
+ breakpoints = new ArrayList<>(0);
+ breakpointsEnabled = new ArrayList<>(0);
+
+ mapBreakpointRow = new HashMap<>();
+
+ lastAffectedRow = -1;
+ }
+
+ public List getBreakpointsList() {
+ return breakpoints;
+ }
+
+ public List getBreakpointsEnabledList() {
+ return breakpointsEnabled;
+ }
+
+ @Override
+ public int getColumnCount() {
+ return COLUMN_COUNT;
+ }
+
+ @Override
+ public int getRowCount() {
+ return breakpoints.size();
+ }
+
+ @Override
+ public String getColumnName(int col) {
+ return columnNames[col];
+ }
+
+ @Override
+ public Object getValueAt(int row, int column) {
+ Object obj = null;
+ BreakpointMessageInterface breakpoint = breakpoints.get(row);
+ if (column == 0) {
+ obj = breakpoint.isEnabled();
+ } else if (column == 1) {
+ obj = breakpoint.getType();
+ } else {
+ obj = breakpoint.getDisplayMessage();
+ }
+ return obj;
+ }
+
+ public BreakpointMessageInterface getBreakpointAtRow(int row) {
+ return breakpoints.get(row);
+ }
+
+ public void addBreakpoint(BreakpointMessageInterface breakpoint) {
+ breakpoints.add(breakpoint);
+ this.fireTableRowsInserted(breakpoints.size() - 1, breakpoints.size() - 1);
+
+ rebuildMapBreakpointRow();
+ lastAffectedRow = mapBreakpointRow.get(breakpoint);
+
+ if (breakpoint.isEnabled()) {
+ synchronized (breakpointsEnabled) {
+ breakpointsEnabled.add(breakpoint);
+ }
+ }
+ }
+
+ public void editBreakpoint(
+ BreakpointMessageInterface oldBreakpoint, BreakpointMessageInterface newBreakpoint) {
+ int row = mapBreakpointRow.remove(oldBreakpoint);
+ breakpoints.remove(row);
+ this.fireTableRowsDeleted(row, row);
+
+ mapBreakpointRow.put(newBreakpoint, 0);
+ breakpoints.add(newBreakpoint);
+ this.fireTableRowsInserted(breakpoints.size() - 1, breakpoints.size() - 1);
+
+ rebuildMapBreakpointRow();
+ lastAffectedRow = mapBreakpointRow.get(newBreakpoint);
+
+ synchronized (breakpointsEnabled) {
+ if (oldBreakpoint.isEnabled()) {
+ breakpointsEnabled.remove(oldBreakpoint);
+ }
+ if (newBreakpoint.isEnabled()) {
+ breakpointsEnabled.add(newBreakpoint);
+ }
+ }
+ }
+
+ public void removeBreakpoint(BreakpointMessageInterface breakpoint) {
+ Integer row = mapBreakpointRow.remove(breakpoint);
+
+ if (row != null) {
+ breakpoints.remove(breakpoint);
+ this.fireTableRowsDeleted(row, row);
+
+ rebuildMapBreakpointRow();
+
+ synchronized (breakpointsEnabled) {
+ if (breakpoint.isEnabled()) {
+ breakpointsEnabled.remove(breakpoint);
+ }
+ }
+ }
+ }
+
+ public int getLastAffectedRow() {
+ return lastAffectedRow;
+ }
+
+ @Override
+ public boolean isCellEditable(int row, int column) {
+ return (column == 0);
+ }
+
+ @Override
+ public void setValueAt(Object value, int row, int column) {
+ if (column == 0) {
+ if (value instanceof Boolean) {
+ boolean isEnabled = breakpoints.get(row).isEnabled();
+ breakpoints.get(row).setEnabled((Boolean) value);
+ this.fireTableCellUpdated(row, column);
+
+ if (isEnabled) {
+ synchronized (breakpointsEnabled) {
+ breakpointsEnabled.remove(breakpoints.get(row));
+ }
+ } else {
+ synchronized (breakpointsEnabled) {
+ breakpointsEnabled.add(breakpoints.get(row));
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public Class> getColumnClass(int column) {
+ if (column == 0) {
+ return Boolean.class;
+ }
+ return String.class;
+ }
+
+ private void rebuildMapBreakpointRow() {
+ mapBreakpointRow.clear();
+ int i = 0;
+ for (Iterator iterator = breakpoints.iterator();
+ iterator.hasNext();
+ ++i) {
+ mapBreakpointRow.put(iterator.next(), i);
+ }
+ }
+}
diff --git a/addOns/brk/src/main/java/org/zaproxy/addon/brk/BreakpointsUiManagerInterface.java b/addOns/brk/src/main/java/org/zaproxy/addon/brk/BreakpointsUiManagerInterface.java
new file mode 100644
index 00000000000..4c1e86a18ad
--- /dev/null
+++ b/addOns/brk/src/main/java/org/zaproxy/addon/brk/BreakpointsUiManagerInterface.java
@@ -0,0 +1,39 @@
+/*
+ * Zed Attack Proxy (ZAP) and its related class files.
+ *
+ * ZAP is an HTTP/HTTPS proxy for assessing web application security.
+ *
+ * Copyright 2012 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.addon.brk;
+
+import org.zaproxy.zap.extension.httppanel.Message;
+
+public interface BreakpointsUiManagerInterface {
+
+ Class extends Message> getMessageClass();
+
+ Class extends BreakpointMessageInterface> getBreakpointClass();
+
+ String getType();
+
+ void handleAddBreakpoint(Message aMessage);
+
+ void handleEditBreakpoint(BreakpointMessageInterface breakpoint);
+
+ void handleRemoveBreakpoint(BreakpointMessageInterface breakpoint);
+
+ void reset();
+}
diff --git a/addOns/brk/src/main/java/org/zaproxy/addon/brk/ExtensionBreak.java b/addOns/brk/src/main/java/org/zaproxy/addon/brk/ExtensionBreak.java
new file mode 100644
index 00000000000..ee895ff5c31
--- /dev/null
+++ b/addOns/brk/src/main/java/org/zaproxy/addon/brk/ExtensionBreak.java
@@ -0,0 +1,708 @@
+/*
+ * Zed Attack Proxy (ZAP) and its related class files.
+ *
+ * ZAP is an HTTP/HTTPS proxy for assessing web application security.
+ *
+ * Copyright 2010 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.addon.brk;
+
+import java.awt.Component;
+import java.awt.EventQueue;
+import java.awt.event.KeyEvent;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Consumer;
+import javax.swing.JList;
+import javax.swing.JTree;
+import javax.swing.tree.TreePath;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.parosproxy.paros.Constant;
+import org.parosproxy.paros.control.Control;
+import org.parosproxy.paros.control.Control.Mode;
+import org.parosproxy.paros.extension.ExtensionAdaptor;
+import org.parosproxy.paros.extension.ExtensionHook;
+import org.parosproxy.paros.extension.ExtensionHookView;
+import org.parosproxy.paros.extension.OptionsChangedListener;
+import org.parosproxy.paros.extension.SessionChangedListener;
+import org.parosproxy.paros.extension.option.ExtensionOption;
+import org.parosproxy.paros.model.HistoryReference;
+import org.parosproxy.paros.model.OptionsParam;
+import org.parosproxy.paros.model.Session;
+import org.parosproxy.paros.model.SiteNode;
+import org.zaproxy.addon.brk.impl.http.HttpBreakpointManagementDaemonImpl;
+import org.zaproxy.addon.brk.impl.http.HttpBreakpointMessage;
+import org.zaproxy.addon.brk.impl.http.HttpBreakpointMessage.Location;
+import org.zaproxy.addon.brk.impl.http.HttpBreakpointMessage.Match;
+import org.zaproxy.addon.brk.impl.http.HttpBreakpointsUiManagerInterface;
+import org.zaproxy.addon.brk.impl.http.ProxyListenerBreak;
+import org.zaproxy.zap.extension.help.ExtensionHelp;
+import org.zaproxy.zap.extension.httppanel.Message;
+import org.zaproxy.zap.view.ZapMenuItem;
+
+public class ExtensionBreak extends ExtensionAdaptor
+ implements SessionChangedListener, OptionsChangedListener {
+
+ public static final String NAME = "ExtensionBreak";
+
+ private static final Logger LOGGER = LogManager.getLogger(ExtensionBreak.class);
+
+ public static final String BREAK_POINT_HIT_STATS = "stats.break.hit";
+ public static final String BREAK_POINT_STEP_STATS = "stats.break.step";
+ public static final String BREAK_POINT_DROP_STATS = "stats.break.drop";
+
+ private BreakPanel breakPanel = null;
+ private ProxyListenerBreak proxyListener = null;
+
+ private BreakpointsPanel breakpointsPanel = null;
+
+ private PopupMenuEditBreak popupMenuEditBreak = null;
+ private PopupMenuRemove popupMenuRemove = null;
+
+ private BreakpointMessageHandler2 breakpointMessageHandler;
+
+ private Map, BreakpointsUiManagerInterface>
+ mapBreakpointUiManager;
+
+ private Map, BreakpointsUiManagerInterface> mapMessageUiManager;
+
+ private Mode mode = Control.getSingleton().getMode();
+
+ private BreakpointsParam breakpointsParams;
+
+ private BreakpointsOptionsPanel breakpointsOptionsPanel;
+
+ private BreakpointManagementInterface breakpointManagementInterface;
+ private HttpBreakpointsUiManagerInterface httpBreakpoints;
+
+ private ZapMenuItem menuBreakOnRequests = null;
+ private ZapMenuItem menuBreakOnResponses = null;
+ private ZapMenuItem menuStep = null;
+ private ZapMenuItem menuContinue = null;
+ private ZapMenuItem menuDrop = null;
+ private ZapMenuItem menuHttpBreakpoint = null;
+
+ private BreakAPI api = new BreakAPI(this);
+ private List> serialisationRequiredListeners;
+
+ public ExtensionBreak() {
+ super(NAME);
+ this.setOrder(24);
+ }
+
+ @Override
+ public String getUIName() {
+ return Constant.messages.getString("brk.name");
+ }
+
+ @Override
+ public void init() {
+ serialisationRequiredListeners = Collections.synchronizedList(new ArrayList<>(1));
+ try {
+ ExtensionOption extOption =
+ Control.getSingleton().getExtensionLoader().getExtension(ExtensionOption.class);
+ Class> breakOptionsHandlerClass =
+ getClass()
+ .getClassLoader()
+ .loadClass("org.parosproxy.paros.extension.option.BreakOptionsHandler");
+ Method method =
+ extOption
+ .getClass()
+ .getDeclaredMethod("setBreakOptionsHandler", breakOptionsHandlerClass);
+ method.invoke(
+ extOption,
+ Proxy.newProxyInstance(
+ breakOptionsHandlerClass.getClassLoader(),
+ new Class>[] {breakOptionsHandlerClass},
+ (proxy, m, args) -> {
+ getOptionsParam().setShowIgnoreFilesButtons((Boolean) args[0]);
+ return null;
+ }));
+ } catch (NoSuchMethodException
+ | IllegalAccessException
+ | InvocationTargetException
+ | ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public BreakpointManagementInterface getBreakpointManagementInterface() {
+ return this.breakpointManagementInterface;
+ }
+
+ public void addSerialisationRequiredListener(Consumer listener) {
+ Objects.requireNonNull(listener);
+ serialisationRequiredListeners.add(listener);
+ }
+
+ public void removeSerialisationRequiredListener(Consumer listener) {
+ Objects.requireNonNull(listener);
+ serialisationRequiredListeners.remove(listener);
+ }
+
+ List> getSerialisationRequiredListeners() {
+ return serialisationRequiredListeners;
+ }
+
+ @Override
+ public void hook(ExtensionHook extensionHook) {
+ super.hook(extensionHook);
+
+ extensionHook.addOptionsParamSet(getOptionsParam());
+ extensionHook.addProxyListener(getProxyListenerBreak());
+ extensionHook.addSessionListener(this);
+ extensionHook.addOptionsChangedListener(this);
+
+ extensionHook.addApiImplementor(api);
+
+ if (getView() != null) {
+ breakPanel = new BreakPanel(this, getOptionsParam());
+ breakPanel.setName(Constant.messages.getString("tab.break"));
+ breakpointMessageHandler = new BreakpointMessageHandler2(breakPanel);
+ breakpointMessageHandler.setEnabledBreakpoints(
+ getBreakpointsModel().getBreakpointsEnabledList());
+ breakpointMessageHandler.setEnabledIgnoreRules(breakPanel.getIgnoreRulesEnableList());
+ breakpointManagementInterface = breakPanel;
+
+ ExtensionHookView pv = extensionHook.getHookView();
+ pv.addWorkPanel(breakPanel);
+ pv.addOptionPanel(getOptionsPanel());
+
+ extensionHook.getHookView().addStatusPanel(getBreakpointsPanel());
+
+ extensionHook.getHookMenu().addPopupMenuItem(getPopupMenuEdit());
+ extensionHook.getHookMenu().addPopupMenuItem(getPopupMenuDelete());
+
+ mapBreakpointUiManager = new HashMap<>();
+ mapMessageUiManager = new HashMap<>();
+
+ httpBreakpoints =
+ new HttpBreakpointsUiManagerInterface(extensionHook.getHookMenu(), this);
+
+ addBreakpointsUiManager(httpBreakpoints);
+
+ extensionHook.getHookMenu().addToolsMenuItem(getMenuToggleBreakOnRequests());
+ extensionHook.getHookMenu().addToolsMenuItem(getMenuToggleBreakOnResponses());
+ extensionHook.getHookMenu().addToolsMenuItem(getMenuStep());
+ extensionHook.getHookMenu().addToolsMenuItem(getMenuContinue());
+ extensionHook.getHookMenu().addToolsMenuItem(getMenuDrop());
+ extensionHook.getHookMenu().addToolsMenuItem(getMenuAddHttpBreakpoint());
+
+ ExtensionHelp.enableHelpKey(breakPanel, "ui.tabs.break");
+ ExtensionHelp.enableHelpKey(getBreakpointsPanel(), "ui.tabs.breakpoints");
+ } else {
+ this.breakpointManagementInterface = new HttpBreakpointManagementDaemonImpl();
+
+ breakpointMessageHandler = new BreakpointMessageHandler2(breakpointManagementInterface);
+ breakpointMessageHandler.setEnabledBreakpoints(new ArrayList<>());
+ breakpointMessageHandler.setEnabledIgnoreRules(new ArrayList<>());
+ }
+ }
+
+ private BreakpointsParam getOptionsParam() {
+ if (breakpointsParams == null) {
+ breakpointsParams = new BreakpointsParam();
+ }
+ return breakpointsParams;
+ }
+
+ private BreakpointsOptionsPanel getOptionsPanel() {
+ if (breakpointsOptionsPanel == null) {
+ breakpointsOptionsPanel = new BreakpointsOptionsPanel();
+ }
+ return breakpointsOptionsPanel;
+ }
+
+ private BreakpointsPanel getBreakpointsPanel() {
+ if (breakpointsPanel == null) {
+ breakpointsPanel = new BreakpointsPanel(this);
+ }
+ return breakpointsPanel;
+ }
+
+ public void addBreakpoint(BreakpointMessageInterface breakpoint) {
+ this.getBreakpointsPanel().addBreakpoint(breakpoint);
+ // Switch to the panel for some visual feedback
+ this.getBreakpointsPanel().setTabFocus();
+ }
+
+ public void editBreakpoint(
+ BreakpointMessageInterface oldBreakpoint, BreakpointMessageInterface newBreakpoint) {
+ this.getBreakpointsPanel().editBreakpoint(oldBreakpoint, newBreakpoint);
+ }
+
+ public void removeBreakpoint(BreakpointMessageInterface breakpoint) {
+ this.getBreakpointsPanel().removeBreakpoint(breakpoint);
+ }
+
+ public List getBreakpointsList() {
+ return getBreakpointsModel().getBreakpointsList();
+ }
+
+ public BreakpointMessageInterface getUiSelectedBreakpoint() {
+ return getBreakpointsPanel().getSelectedBreakpoint();
+ }
+
+ public void addBreakpointsUiManager(BreakpointsUiManagerInterface uiManager) {
+ if (getView() == null) {
+ return;
+ }
+ mapBreakpointUiManager.put(uiManager.getBreakpointClass(), uiManager);
+ mapMessageUiManager.put(uiManager.getMessageClass(), uiManager);
+ }
+
+ public void removeBreakpointsUiManager(BreakpointsUiManagerInterface uiManager) {
+ if (getView() == null) {
+ return;
+ }
+ mapBreakpointUiManager.remove(uiManager.getBreakpointClass());
+ mapMessageUiManager.remove(uiManager.getMessageClass());
+ }
+
+ public void setBreakAllRequests(boolean brk) {
+ this.breakpointManagementInterface.setBreakAllRequests(brk);
+ }
+
+ public void setBreakAllResponses(boolean brk) {
+ this.breakpointManagementInterface.setBreakAllResponses(brk);
+ }
+
+ public void addHttpBreakpoint(
+ String string, String location, String match, boolean inverse, boolean ignoreCase) {
+ this.addBreakpoint(
+ new HttpBreakpointMessage(
+ string,
+ getLocationEnum(location),
+ getMatchEnum(match),
+ inverse,
+ ignoreCase));
+ }
+
+ private static Location getLocationEnum(String location) {
+ try {
+ return Location.valueOf(location);
+ } catch (Exception e) {
+ throw new IllegalArgumentException(
+ "location must be one of " + Arrays.toString(Location.values()));
+ }
+ }
+
+ private static Match getMatchEnum(String match) {
+ try {
+ return Match.valueOf(match);
+ } catch (Exception e) {
+ throw new IllegalArgumentException(
+ "match must be one of " + Arrays.toString(Match.values()));
+ }
+ }
+
+ public void removeHttpBreakpoint(
+ String string, String location, String match, boolean inverse, boolean ignoreCase) {
+ this.removeBreakpoint(
+ new HttpBreakpointMessage(
+ string,
+ getLocationEnum(location),
+ getMatchEnum(match),
+ inverse,
+ ignoreCase));
+ }
+
+ public void addUiBreakpoint(Message aMessage) {
+ BreakpointsUiManagerInterface uiManager = getBreakpointUiManager(aMessage.getClass());
+ if (uiManager != null) {
+ uiManager.handleAddBreakpoint(aMessage);
+ }
+ }
+
+ private BreakpointsUiManagerInterface getBreakpointUiManager(Class> clazz) {
+ if (!Message.class.isAssignableFrom(clazz)) {
+ return null;
+ }
+
+ BreakpointsUiManagerInterface uiManager = mapMessageUiManager.get(clazz);
+ if (uiManager == null) {
+ uiManager = getBreakpointUiManager(clazz.getSuperclass());
+ }
+
+ return uiManager;
+ }
+
+ public void editUiSelectedBreakpoint() {
+ BreakpointMessageInterface breakpoint = getBreakpointsPanel().getSelectedBreakpoint();
+ if (breakpoint != null) {
+ BreakpointsUiManagerInterface uiManager =
+ mapBreakpointUiManager.get(breakpoint.getClass());
+ if (uiManager != null) {
+ uiManager.handleEditBreakpoint(breakpoint);
+ }
+ }
+ }
+
+ public void removeUiSelectedBreakpoint() {
+ BreakpointMessageInterface breakpoint = getBreakpointsPanel().getSelectedBreakpoint();
+ if (breakpoint != null) {
+ BreakpointsUiManagerInterface uiManager =
+ mapBreakpointUiManager.get(breakpoint.getClass());
+ if (uiManager != null) {
+ uiManager.handleRemoveBreakpoint(breakpoint);
+ }
+ }
+ }
+
+ private BreakpointsTableModel getBreakpointsModel() {
+ return (BreakpointsTableModel) this.getBreakpointsPanel().getBreakpoints().getModel();
+ }
+
+ private ProxyListenerBreak getProxyListenerBreak() {
+ if (proxyListener == null) {
+ proxyListener = new ProxyListenerBreak(getModel(), this);
+ }
+ return proxyListener;
+ }
+
+ private PopupMenuEditBreak getPopupMenuEdit() {
+ if (popupMenuEditBreak == null) {
+ popupMenuEditBreak = new PopupMenuEditBreak();
+ popupMenuEditBreak.setExtension(this);
+ }
+ return popupMenuEditBreak;
+ }
+
+ private PopupMenuRemove getPopupMenuDelete() {
+ if (popupMenuRemove == null) {
+ popupMenuRemove = new PopupMenuRemove();
+ popupMenuRemove.setExtension(this);
+ }
+ return popupMenuRemove;
+ }
+
+ private ZapMenuItem getMenuToggleBreakOnRequests() {
+ if (menuBreakOnRequests == null) {
+ menuBreakOnRequests =
+ new ZapMenuItem(
+ "menu.tools.brk.req",
+ getView().getMenuShortcutKeyStroke(KeyEvent.VK_B, 0, false));
+ menuBreakOnRequests.addActionListener(
+ new java.awt.event.ActionListener() {
+ @Override
+ public void actionPerformed(java.awt.event.ActionEvent e) {
+ if (getOptionsParam().getButtonMode()
+ == BreakpointsParam.BUTTON_MODE_SIMPLE) {
+ // Single button mode - toggle break on all
+ breakpointManagementInterface.setBreakAll(
+ !breakpointManagementInterface.isBreakAll());
+ } else {
+ // Toggle break on requests
+ breakpointManagementInterface.setBreakAllRequests(
+ !breakpointManagementInterface.isBreakRequest());
+ }
+ }
+ });
+ }
+ return menuBreakOnRequests;
+ }
+
+ private ZapMenuItem getMenuToggleBreakOnResponses() {
+ if (menuBreakOnResponses == null) {
+ menuBreakOnResponses =
+ new ZapMenuItem(
+ "menu.tools.brk.resp",
+ getView()
+ .getMenuShortcutKeyStroke(
+ KeyEvent.VK_B, KeyEvent.ALT_DOWN_MASK, false));
+ menuBreakOnResponses.addActionListener(
+ new java.awt.event.ActionListener() {
+ @Override
+ public void actionPerformed(java.awt.event.ActionEvent e) {
+ if (getOptionsParam().getButtonMode()
+ == BreakpointsParam.BUTTON_MODE_SIMPLE) {
+ // Single button mode - toggle break on all
+ breakpointManagementInterface.setBreakAll(
+ !breakpointManagementInterface.isBreakAll());
+ } else {
+ // Toggle break on Responses
+ breakpointManagementInterface.setBreakAllResponses(
+ !breakpointManagementInterface.isBreakResponse());
+ }
+ }
+ });
+ }
+ return menuBreakOnResponses;
+ }
+
+ private ZapMenuItem getMenuStep() {
+ if (menuStep == null) {
+ menuStep =
+ new ZapMenuItem(
+ "menu.tools.brk.step",
+ getView().getMenuShortcutKeyStroke(KeyEvent.VK_S, 0, false));
+ menuStep.addActionListener(
+ new java.awt.event.ActionListener() {
+ @Override
+ public void actionPerformed(java.awt.event.ActionEvent e) {
+ if (breakpointManagementInterface.isHoldMessage(null)) {
+ // Menu currently always enabled, but dont do anything unless a
+ // message is being held
+ breakpointManagementInterface.step();
+ }
+ }
+ });
+ }
+ return menuStep;
+ }
+
+ private ZapMenuItem getMenuContinue() {
+ if (menuContinue == null) {
+ menuContinue =
+ new ZapMenuItem(
+ "menu.tools.brk.cont",
+ getView().getMenuShortcutKeyStroke(KeyEvent.VK_C, 0, false));
+ menuContinue.addActionListener(
+ new java.awt.event.ActionListener() {
+ @Override
+ public void actionPerformed(java.awt.event.ActionEvent e) {
+ if (breakpointManagementInterface.isHoldMessage(null)) {
+ // Menu currently always enabled, but dont do anything unless a
+ // message is being held
+ breakpointManagementInterface.cont();
+ }
+ }
+ });
+ }
+ return menuContinue;
+ }
+
+ private ZapMenuItem getMenuDrop() {
+ if (menuDrop == null) {
+ menuDrop =
+ new ZapMenuItem(
+ "menu.tools.brk.drop",
+ getView().getMenuShortcutKeyStroke(KeyEvent.VK_X, 0, false));
+ menuDrop.addActionListener(
+ new java.awt.event.ActionListener() {
+ @Override
+ public void actionPerformed(java.awt.event.ActionEvent e) {
+ if (breakpointManagementInterface.isHoldMessage(null)) {
+ // Menu currently always enabled, but dont do anything unless a
+ // message is being held
+ breakpointManagementInterface.drop();
+ }
+ }
+ });
+ }
+ return menuDrop;
+ }
+
+ private ZapMenuItem getMenuAddHttpBreakpoint() {
+ if (menuHttpBreakpoint == null) {
+ menuHttpBreakpoint =
+ new ZapMenuItem(
+ "menu.tools.brk.custom",
+ getView().getMenuShortcutKeyStroke(KeyEvent.VK_A, 0, false));
+ menuHttpBreakpoint.addActionListener(
+ new java.awt.event.ActionListener() {
+ @Override
+ public void actionPerformed(java.awt.event.ActionEvent e) {
+ // Check to see if anything is selected in the main tabs
+ String url = "";
+ Component c = getView().getMainFrame().getFocusOwner();
+ if (c != null) {
+ if (c instanceof JList) {
+ // Handles the history list and similar
+ @SuppressWarnings("rawtypes")
+ Object sel = ((JList) c).getSelectedValue();
+ try {
+ if (sel != null
+ && sel instanceof HistoryReference
+ && ((HistoryReference) sel).getURI() != null) {
+ url = ((HistoryReference) sel).getURI().toString();
+ }
+ } catch (Exception e1) {
+ // Ignore
+ }
+ } else if (c instanceof JTree) {
+ // Handles the Sites tree
+ TreePath path = ((JTree) c).getSelectionPath();
+ try {
+ if (path != null
+ && path.getLastPathComponent()
+ instanceof SiteNode) {
+ url =
+ ((SiteNode) path.getLastPathComponent())
+ .getHistoryReference()
+ .getURI()
+ .toString();
+ }
+ } catch (Exception e1) {
+ // Ignore
+ }
+ }
+ }
+ httpBreakpoints.handleAddBreakpoint(url);
+ }
+ });
+ }
+ return menuHttpBreakpoint;
+ }
+
+ @Override
+ public String getAuthor() {
+ return Constant.ZAP_TEAM;
+ }
+
+ @Override
+ public String getDescription() {
+ return Constant.messages.getString("brk.desc");
+ }
+
+ @Override
+ public void sessionAboutToChange(final Session session) {
+ if (EventQueue.isDispatchThread()) {
+ sessionAboutToChange();
+ } else {
+ try {
+ EventQueue.invokeAndWait(
+ new Runnable() {
+ @Override
+ public void run() {
+ sessionAboutToChange();
+ }
+ });
+ } catch (Exception e) {
+ LOGGER.error(e.getMessage(), e);
+ }
+ }
+ }
+
+ @Override
+ public void sessionChanged(Session session) {
+ if (getView() == null) {
+ return;
+ }
+ breakpointManagementInterface.init();
+ }
+
+ private void sessionAboutToChange() {
+ if (getView() == null) {
+ return;
+ }
+ breakpointManagementInterface.reset();
+ }
+
+ @Override
+ public void sessionScopeChanged(Session session) {}
+
+ @Override
+ public void destroy() {
+ if (breakPanel != null) {
+ breakPanel.savePanels();
+ }
+ }
+
+ public boolean messageReceivedFromClient(Message aMessage) {
+ if (mode.equals(Mode.safe)) {
+ return true;
+ }
+ return breakpointMessageHandler.handleMessageReceivedFromClient(
+ aMessage, mode.equals(Mode.protect));
+ }
+
+ public boolean messageReceivedFromServer(Message aMessage) {
+ if (mode.equals(Mode.safe)) {
+ return true;
+ }
+ return breakpointMessageHandler.handleMessageReceivedFromServer(
+ aMessage, mode.equals(Mode.protect));
+ }
+
+ /**
+ * Exposes list of enabled breakpoints.
+ *
+ * @return list of enabled breakpoints
+ */
+ public List getBreakpointsEnabledList() {
+ if (mode.equals(Mode.safe)) {
+ return new ArrayList<>();
+ }
+ return getBreakpointsModel().getBreakpointsEnabledList();
+ }
+
+ @Override
+ public void sessionModeChanged(Mode mode) {
+ this.mode = mode;
+ if (getView() == null) {
+ return;
+ }
+ this.breakpointManagementInterface.sessionModeChanged(mode);
+ }
+
+ public void setBreakOnId(String id, boolean enable) {
+ LOGGER.debug("setBreakOnId {} {}", id, enable);
+ if (enable) {
+ breakpointMessageHandler.getEnabledKeyBreakpoints().add(id);
+ } else {
+ breakpointMessageHandler.getEnabledKeyBreakpoints().remove(id);
+ }
+ }
+
+ @Override
+ public void optionsChanged(OptionsParam optionsParam) {
+ applyViewOptions(optionsParam);
+ }
+
+ /**
+ * Applies the given (view) options to the break components, by setting the location of the
+ * break buttons and the break buttons mode.
+ *
+ *
The call to this method has no effect if there's no view.
+ *
+ * @param options the current options
+ */
+ private void applyViewOptions(OptionsParam options) {
+ if (getView() == null) {
+ return;
+ }
+
+ breakPanel.setButtonsLocation(options.getViewParam().getBrkPanelViewOption());
+ breakPanel.setButtonMode(options.getParamSet(BreakpointsParam.class).getButtonMode());
+ breakPanel.setShowIgnoreFilesButtons(
+ options.getParamSet(BreakpointsParam.class).isShowIgnoreFilesButtons());
+ breakPanel.updateIgnoreFileTypesRegexs();
+ }
+
+ @Override
+ public void optionsLoaded() {
+ applyViewOptions(getModel().getOptionsParam());
+ }
+
+ public boolean isInScopeOnly() {
+ return this.getOptionsParam().isInScopeOnly();
+ }
+
+ @Override
+ /** No database tables used, so all supported */
+ public boolean supportsDb(String type) {
+ return true;
+ }
+}
diff --git a/addOns/brk/src/main/java/org/zaproxy/addon/brk/PopupMenuEditBreak.java b/addOns/brk/src/main/java/org/zaproxy/addon/brk/PopupMenuEditBreak.java
new file mode 100644
index 00000000000..f8e9e543027
--- /dev/null
+++ b/addOns/brk/src/main/java/org/zaproxy/addon/brk/PopupMenuEditBreak.java
@@ -0,0 +1,56 @@
+/*
+ * Zed Attack Proxy (ZAP) and its related class files.
+ *
+ * ZAP is an HTTP/HTTPS proxy for assessing web application security.
+ *
+ * Copyright 2010 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.addon.brk;
+
+import java.awt.Component;
+import org.parosproxy.paros.Constant;
+import org.parosproxy.paros.extension.ExtensionPopupMenuItem;
+
+@SuppressWarnings("serial")
+public class PopupMenuEditBreak extends ExtensionPopupMenuItem {
+
+ private static final long serialVersionUID = 1L;
+
+ private ExtensionBreak extension;
+
+ public PopupMenuEditBreak() {
+ super(Constant.messages.getString("brk.edit.popup"));
+ this.addActionListener(
+ new java.awt.event.ActionListener() {
+
+ @Override
+ public void actionPerformed(java.awt.event.ActionEvent e) {
+ extension.editUiSelectedBreakpoint();
+ }
+ });
+ }
+
+ public void setExtension(ExtensionBreak extension) {
+ this.extension = extension;
+ }
+
+ @Override
+ public boolean isEnableForComponent(Component invoker) {
+ if (invoker.getName() != null && invoker.getName().equals(BreakpointsPanel.PANEL_NAME)) {
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/addOns/brk/src/main/java/org/zaproxy/addon/brk/PopupMenuRemove.java b/addOns/brk/src/main/java/org/zaproxy/addon/brk/PopupMenuRemove.java
new file mode 100644
index 00000000000..598757da6e8
--- /dev/null
+++ b/addOns/brk/src/main/java/org/zaproxy/addon/brk/PopupMenuRemove.java
@@ -0,0 +1,57 @@
+/*
+ * Zed Attack Proxy (ZAP) and its related class files.
+ *
+ * ZAP is an HTTP/HTTPS proxy for assessing web application security.
+ *
+ * Copyright 2010 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.addon.brk;
+
+import java.awt.Component;
+import org.parosproxy.paros.Constant;
+import org.parosproxy.paros.extension.ExtensionPopupMenuItem;
+
+@SuppressWarnings("serial")
+public class PopupMenuRemove extends ExtensionPopupMenuItem {
+
+ private static final long serialVersionUID = 1L;
+ private ExtensionBreak extension = null;
+
+ public PopupMenuRemove() {
+ super(Constant.messages.getString("brk.remove.popup"));
+
+ this.addActionListener(
+ new java.awt.event.ActionListener() {
+
+ @Override
+ public void actionPerformed(java.awt.event.ActionEvent e) {
+ extension.removeUiSelectedBreakpoint();
+ }
+ });
+ }
+
+ @Override
+ public boolean isEnableForComponent(Component invoker) {
+ if (invoker.getName() != null && invoker.getName().equals(BreakpointsPanel.PANEL_NAME)) {
+ this.setEnabled(true);
+ return true;
+ }
+ return false;
+ }
+
+ void setExtension(ExtensionBreak extension) {
+ this.extension = extension;
+ }
+}
diff --git a/addOns/brk/src/main/java/org/zaproxy/addon/brk/impl/http/BreakAddEditDialog.java b/addOns/brk/src/main/java/org/zaproxy/addon/brk/impl/http/BreakAddEditDialog.java
new file mode 100644
index 00000000000..c760695971b
--- /dev/null
+++ b/addOns/brk/src/main/java/org/zaproxy/addon/brk/impl/http/BreakAddEditDialog.java
@@ -0,0 +1,166 @@
+/*
+ * Zed Attack Proxy (ZAP) and its related class files.
+ *
+ * ZAP is an HTTP/HTTPS proxy for assessing web application security.
+ *
+ * Copyright 2013 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.addon.brk.impl.http;
+
+import java.awt.Dimension;
+import java.awt.Frame;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+import org.parosproxy.paros.Constant;
+import org.zaproxy.addon.brk.impl.http.HttpBreakpointMessage.Location;
+import org.zaproxy.addon.brk.impl.http.HttpBreakpointMessage.Match;
+import org.zaproxy.zap.view.StandardFieldsDialog;
+
+@SuppressWarnings("serial")
+public class BreakAddEditDialog extends StandardFieldsDialog {
+
+ private static final String FIELD_LOCATION = "brk.brkpoint.location.label";
+ private static final String FIELD_MATCH = "brk.brkpoint.match.label";
+ private static final String FIELD_STRING = "brk.brkpoint.string.label";
+ private static final String FIELD_INVERSE = "brk.brkpoint.inverse.label";
+ private static final String FIELD_IGNORECASE = "brk.brkpoint.ignorecase.label";
+
+ private static final long serialVersionUID = 1L;
+
+ private HttpBreakpointsUiManagerInterface breakPointsManager;
+ private boolean add = false;
+ private HttpBreakpointMessage breakpoint;
+
+ public BreakAddEditDialog(
+ HttpBreakpointsUiManagerInterface breakPointsManager, Frame owner, Dimension dim) {
+ super(owner, "brk.brkpoint.add.title", dim, true);
+ this.breakPointsManager = breakPointsManager;
+ setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+ }
+
+ public void init(HttpBreakpointMessage breakpoint, boolean add) {
+ this.add = add;
+ this.breakpoint = breakpoint;
+
+ this.removeAllFields();
+
+ if (add) {
+ this.setTitle(Constant.messages.getString("brk.brkpoint.add.title"));
+ } else {
+ this.setTitle(Constant.messages.getString("brk.brkpoint.edit.title"));
+ }
+
+ this.addComboField(FIELD_LOCATION, getLocations(), this.locToStr(breakpoint.getLocation()));
+ this.addComboField(FIELD_MATCH, getMatches(), this.matchToStr(breakpoint.getMatch()));
+ this.addTextField(FIELD_STRING, breakpoint.getString());
+ this.addCheckBoxField(FIELD_INVERSE, breakpoint.isInverse());
+ this.addCheckBoxField(FIELD_IGNORECASE, breakpoint.isIgnoreCase());
+
+ this.addPadding();
+ }
+
+ private List getLocations() {
+ ArrayList list = new ArrayList<>();
+ for (Location loc : HttpBreakpointMessage.Location.values()) {
+ list.add(this.locToStr(loc));
+ }
+ return list;
+ }
+
+ private String locToStr(Location loc) {
+ return Constant.messages.getString("brk.brkpoint.location." + loc.name());
+ }
+
+ private Location strToLoc(String str) {
+ for (Location loc : HttpBreakpointMessage.Location.values()) {
+ if (this.locToStr(loc).equals(str)) {
+ return loc;
+ }
+ }
+ return null;
+ }
+
+ private List getMatches() {
+ ArrayList list = new ArrayList<>();
+ for (Match match : HttpBreakpointMessage.Match.values()) {
+ list.add(this.matchToStr(match));
+ }
+ return list;
+ }
+
+ private String matchToStr(Match match) {
+ return Constant.messages.getString("brk.brkpoint.match." + match.name());
+ }
+
+ private Match strToMatch(String str) {
+ for (Match match : HttpBreakpointMessage.Match.values()) {
+ if (this.matchToStr(match).equals(str)) {
+ return match;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void save() {
+ HttpBreakpointMessage brk =
+ new HttpBreakpointMessage(
+ this.getStringValue(FIELD_STRING),
+ this.strToLoc(this.getStringValue(FIELD_LOCATION)),
+ this.strToMatch(this.getStringValue(FIELD_MATCH)),
+ this.getBoolValue(FIELD_INVERSE),
+ this.getBoolValue(FIELD_IGNORECASE));
+
+ if (add) {
+ breakPointsManager.addBreakpoint(brk);
+ dispose();
+ } else {
+ breakPointsManager.editBreakpoint(breakpoint, brk);
+ breakpoint = null;
+ dispose();
+ }
+ }
+
+ @Override
+ public String validateFields() {
+ if (this.isEmptyField(FIELD_STRING)) {
+ return Constant.messages.getString("brk.brkpoint.error.nostr");
+ }
+ if (Match.regex.equals(this.strToMatch(this.getStringValue(FIELD_MATCH)))) {
+ try {
+ Pattern.compile(this.getStringValue(FIELD_STRING));
+ } catch (Exception e) {
+ return Constant.messages.getString("brk.brkpoint.error.regex");
+ }
+ }
+ if (this.getStringValue(FIELD_STRING).contains("#")
+ && Location.url.equals(this.strToLoc(this.getStringValue(FIELD_LOCATION)))
+ && Match.contains.equals(this.strToMatch(this.getStringValue(FIELD_MATCH)))) {
+ return Constant.messages.getString("brk.brkpoint.warn.urlfragment");
+ }
+ return null;
+ }
+
+ @Override
+ public void cancelPressed() {
+ dispose();
+ }
+
+ @Override
+ public String getHelpIndex() {
+ return "ui.dialogs.addbreak";
+ }
+}
diff --git a/addOns/brk/src/main/java/org/zaproxy/addon/brk/impl/http/HttpBreakpointManagementDaemonImpl.java b/addOns/brk/src/main/java/org/zaproxy/addon/brk/impl/http/HttpBreakpointManagementDaemonImpl.java
new file mode 100644
index 00000000000..064f6497a8b
--- /dev/null
+++ b/addOns/brk/src/main/java/org/zaproxy/addon/brk/impl/http/HttpBreakpointManagementDaemonImpl.java
@@ -0,0 +1,219 @@
+/*
+ * Zed Attack Proxy (ZAP) and its related class files.
+ *
+ * ZAP is an HTTP/HTTPS proxy for assessing web application security.
+ *
+ * Copyright 2016 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.addon.brk.impl.http;
+
+import org.parosproxy.paros.control.Control;
+import org.parosproxy.paros.control.Control.Mode;
+import org.parosproxy.paros.network.HttpMessage;
+import org.zaproxy.addon.brk.BreakpointManagementInterface;
+import org.zaproxy.addon.brk.ExtensionBreak;
+import org.zaproxy.zap.extension.httppanel.Message;
+import org.zaproxy.zap.utils.Stats;
+
+public class HttpBreakpointManagementDaemonImpl implements BreakpointManagementInterface {
+
+ private boolean breakRequest;
+ private boolean breakResponse;
+ private boolean request;
+ private HttpMessage msg;
+ private boolean step;
+ private boolean stepping;
+ private boolean drop;
+
+ @Override
+ public boolean isBreakRequest() {
+ return breakRequest;
+ }
+
+ @Override
+ public boolean isBreakResponse() {
+ return breakResponse;
+ }
+
+ @Override
+ public boolean isBreakAll() {
+ return (breakRequest && breakResponse);
+ }
+
+ @Override
+ public void breakpointHit() {
+ // Ignore
+ }
+
+ @Override
+ public boolean isHoldMessage(Message aMessage) {
+ if (step) {
+ step = false;
+ return false;
+ }
+ if (stepping) {
+ return true;
+ }
+ if (drop) {
+ return false;
+ }
+ if (aMessage instanceof HttpMessage) {
+ HttpMessage msg = (HttpMessage) aMessage;
+ if (msg.getResponseHeader().isEmpty()) {
+ // Its a request
+ if (this.isBreakRequest()) {
+ return true;
+ }
+ } else if (this.isBreakResponse()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isStepping() {
+ return stepping;
+ }
+
+ @Override
+ public boolean isToBeDropped() {
+ if (drop) {
+ drop = false;
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void setMessage(Message msg, boolean isRequest) {
+ if (msg instanceof HttpMessage) {
+ switch (Control.getSingleton().getMode()) {
+ case safe:
+ throw new IllegalStateException("Not allowed in safe mode");
+ case protect:
+ if (!msg.isInScope()) {
+ throw new IllegalStateException(
+ "Not allowed in protected mode for out of scope message");
+ }
+ break;
+ case standard:
+ break;
+ case attack:
+ break;
+ }
+ HttpMessage httpMsg = (HttpMessage) msg;
+ if (this.msg == null) {
+ this.msg = httpMsg;
+ this.request = isRequest;
+ } else {
+ if (isRequest) {
+ this.msg.setRequestHeader(httpMsg.getRequestHeader());
+ this.msg.setRequestBody(httpMsg.getRequestBody());
+ } else {
+ this.msg.setResponseHeader(httpMsg.getResponseHeader());
+ this.msg.setResponseBody(httpMsg.getResponseBody());
+ }
+ }
+ } else {
+ throw new IllegalArgumentException("Not an HttpMessage");
+ }
+ }
+
+ @Override
+ public boolean isRequest() {
+ return this.request;
+ }
+
+ @Override
+ public Message getMessage() {
+ return this.msg;
+ }
+
+ @Override
+ public void saveMessage(boolean isRequest) {
+ // Ignore
+ }
+
+ @Override
+ public void clearAndDisableRequest() {
+ this.msg = null;
+ }
+
+ @Override
+ public void clearAndDisableResponse() {
+ this.msg = null;
+ }
+
+ @Override
+ public void init() {}
+
+ @Override
+ public void reset() {
+ // Ignore
+ }
+
+ @Override
+ public void sessionModeChanged(Mode mode) {
+ breakRequest = false;
+ breakResponse = false;
+ msg = null;
+ step = false;
+ stepping = false;
+ drop = false;
+ }
+
+ @Override
+ public void setBreakAllRequests(boolean brk) {
+ this.breakRequest = brk;
+ }
+
+ @Override
+ public void setBreakAllResponses(boolean brk) {
+ this.breakResponse = brk;
+ }
+
+ @Override
+ public void setBreakAll(boolean brk) {
+ this.setBreakAllRequests(brk);
+ this.setBreakAllResponses(brk);
+ }
+
+ @Override
+ public void step() {
+ this.step = true;
+ this.stepping = true;
+ Stats.incCounter(ExtensionBreak.BREAK_POINT_STEP_STATS);
+ }
+
+ @Override
+ public void cont() {
+ this.setBreakAllRequests(false);
+ this.setBreakAllResponses(false);
+ this.step = false;
+ this.stepping = false;
+ }
+
+ @Override
+ public void drop() {
+ this.drop = true;
+ Stats.incCounter(ExtensionBreak.BREAK_POINT_DROP_STATS);
+ }
+
+ @Override
+ public void breakpointDisplayed() {
+ // Ignore
+ }
+}
diff --git a/addOns/brk/src/main/java/org/zaproxy/addon/brk/impl/http/HttpBreakpointMessage.java b/addOns/brk/src/main/java/org/zaproxy/addon/brk/impl/http/HttpBreakpointMessage.java
new file mode 100644
index 00000000000..cf9299d7fc6
--- /dev/null
+++ b/addOns/brk/src/main/java/org/zaproxy/addon/brk/impl/http/HttpBreakpointMessage.java
@@ -0,0 +1,252 @@
+/*
+ * Zed Attack Proxy (ZAP) and its related class files.
+ *
+ * ZAP is an HTTP/HTTPS proxy for assessing web application security.
+ *
+ * Copyright 2012 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.addon.brk.impl.http;
+
+import java.util.regex.Pattern;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.parosproxy.paros.Constant;
+import org.parosproxy.paros.model.Model;
+import org.parosproxy.paros.network.HttpMessage;
+import org.zaproxy.addon.brk.AbstractBreakPointMessage;
+import org.zaproxy.zap.extension.httppanel.Message;
+
+public class HttpBreakpointMessage extends AbstractBreakPointMessage {
+
+ public enum Location {
+ url,
+ request_header,
+ request_body,
+ response_header,
+ response_body
+ }
+
+ public enum Match {
+ contains,
+ regex
+ }
+
+ private static final Logger LOGGER = LogManager.getLogger(HttpBreakpointMessage.class);
+
+ private static final String TYPE = "HTTP";
+
+ private String string;
+ private Pattern pattern;
+ private Location location;
+ private Match match;
+ private boolean inverse;
+ private boolean ignoreCase;
+
+ public HttpBreakpointMessage(
+ String string, Location location, Match match, boolean inverse, boolean ignoreCase) {
+ super();
+ this.string = string;
+ this.location = location;
+ this.match = match;
+ this.inverse = inverse;
+ this.ignoreCase = ignoreCase;
+
+ compilePattern();
+ }
+
+ @Override
+ public String getType() {
+ return TYPE;
+ }
+
+ public String getString() {
+ return string;
+ }
+
+ public void setString(String str) {
+ this.string = str;
+ compilePattern();
+ }
+
+ public Pattern getPattern() {
+ return pattern;
+ }
+
+ public void setPattern(Pattern pattern) {
+ this.pattern = pattern;
+ }
+
+ public Location getLocation() {
+ return location;
+ }
+
+ public void setLocation(Location location) {
+ this.location = location;
+ }
+
+ public Match getMatch() {
+ return match;
+ }
+
+ public void setMatch(Match match) {
+ this.match = match;
+ compilePattern();
+ }
+
+ public boolean isInverse() {
+ return inverse;
+ }
+
+ public void setInverse(boolean inverse) {
+ this.inverse = inverse;
+ }
+
+ public boolean isIgnoreCase() {
+ return ignoreCase;
+ }
+
+ public void setIgnoreCase(boolean ignoreCase) {
+ this.ignoreCase = ignoreCase;
+ compilePattern();
+ }
+
+ @Override
+ public boolean match(Message aMessage, boolean isRequest, boolean onlyIfInScope) {
+ if (aMessage instanceof HttpMessage) {
+ HttpMessage message = (HttpMessage) aMessage;
+
+ try {
+ String uri = message.getRequestHeader().getURI().toString();
+
+ if (onlyIfInScope) {
+ if (!Model.getSingleton().getSession().isInScope(uri)) {
+ return false;
+ }
+ }
+
+ String src;
+ switch (location) {
+ default:
+ case url:
+ src = uri;
+ break;
+ case request_header:
+ if (!isRequest) {
+ return false;
+ }
+ src = message.getRequestHeader().toString();
+ break;
+ case request_body:
+ if (!isRequest) {
+ return false;
+ }
+ src = message.getRequestBody().toString();
+ break;
+ case response_header:
+ if (isRequest) {
+ return false;
+ }
+ src = message.getResponseHeader().toString();
+ break;
+ case response_body:
+ if (isRequest) {
+ return false;
+ }
+ src = message.getResponseBody().toString();
+ break;
+ }
+
+ boolean res;
+ if (Match.contains.equals(this.match)) {
+ if (ignoreCase) {
+ res = src.toLowerCase().contains(string.toLowerCase());
+ } else {
+ res = src.contains(string);
+ }
+
+ } else {
+ res = pattern.matcher(src).find();
+ }
+
+ if (inverse) {
+ return !res;
+ } else {
+ return res;
+ }
+
+ } catch (Exception e) {
+ LOGGER.error(e.getMessage(), e);
+ }
+ }
+
+ return false;
+ }
+
+ private void compilePattern() {
+ try {
+ if (ignoreCase) {
+ pattern = Pattern.compile(string, Pattern.CASE_INSENSITIVE);
+ } else {
+ pattern = Pattern.compile(string);
+ }
+ } catch (Exception e) {
+ // This wont be a problem if its a 'contains' match
+ LOGGER.debug("Potentially invalid regex", e);
+ }
+ }
+
+ @Override
+ public String getDisplayMessage() {
+ return Constant.messages.getString("brk.brkpoint.location." + location.name())
+ + ": "
+ + Constant.messages.getString("brk.brkpoint.match." + match.name())
+ + ": "
+ + (ignoreCase ? Constant.messages.getString("brk.brkpoint.ignorecase.label") : "")
+ + (inverse ? Constant.messages.getString("brk.brkpoint.inverse.label") : "")
+ + string;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof HttpBreakpointMessage)) {
+ return false;
+ }
+ HttpBreakpointMessage hbm = (HttpBreakpointMessage) obj;
+ return this.getString().equals(hbm.getString())
+ && this.getLocation().equals(hbm.getLocation())
+ && this.getMatch().equals(hbm.getMatch())
+ && this.isIgnoreCase() == hbm.isIgnoreCase()
+ && this.isInverse() == hbm.isInverse();
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder(349, 631)
+ . // two 'randomly' chosen prime numbers
+ append(string)
+ .append(location)
+ .append(match)
+ .append(ignoreCase)
+ .append(inverse)
+ .toHashCode();
+ }
+}
diff --git a/addOns/brk/src/main/java/org/zaproxy/addon/brk/impl/http/HttpBreakpointsUiManagerInterface.java b/addOns/brk/src/main/java/org/zaproxy/addon/brk/impl/http/HttpBreakpointsUiManagerInterface.java
new file mode 100644
index 00000000000..96a56fe7a9e
--- /dev/null
+++ b/addOns/brk/src/main/java/org/zaproxy/addon/brk/impl/http/HttpBreakpointsUiManagerInterface.java
@@ -0,0 +1,162 @@
+/*
+ * Zed Attack Proxy (ZAP) and its related class files.
+ *
+ * ZAP is an HTTP/HTTPS proxy for assessing web application security.
+ *
+ * Copyright 2012 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.addon.brk.impl.http;
+
+import java.awt.Dimension;
+import org.parosproxy.paros.db.DatabaseException;
+import org.parosproxy.paros.extension.ExtensionHookMenu;
+import org.parosproxy.paros.network.HttpMessage;
+import org.parosproxy.paros.view.View;
+import org.zaproxy.addon.brk.BreakpointMessageInterface;
+import org.zaproxy.addon.brk.BreakpointsUiManagerInterface;
+import org.zaproxy.addon.brk.ExtensionBreak;
+import org.zaproxy.zap.extension.httppanel.Message;
+import org.zaproxy.zap.model.StructuralSiteNode;
+
+public class HttpBreakpointsUiManagerInterface implements BreakpointsUiManagerInterface {
+
+ private BreakAddEditDialog breakDialog = null;
+
+ private ExtensionBreak extensionBreak;
+
+ private PopupMenuAddBreakSites popupMenuAddBreakSites = null;
+ private PopupMenuAddBreakHistory popupMenuAddBreakHistory = null;
+
+ public HttpBreakpointsUiManagerInterface(
+ ExtensionHookMenu hookMenu, ExtensionBreak extensionBreak) {
+ this.extensionBreak = extensionBreak;
+
+ hookMenu.addPopupMenuItem(getPopupMenuAddBreakSites());
+ hookMenu.addPopupMenuItem(getPopupMenuAddBreakHistory());
+ }
+
+ @Override
+ public Class getMessageClass() {
+ return HttpMessage.class;
+ }
+
+ @Override
+ public Class getBreakpointClass() {
+ return HttpBreakpointMessage.class;
+ }
+
+ @Override
+ public String getType() {
+ return "HTTP";
+ }
+
+ @Override
+ public void handleAddBreakpoint(Message aMessage) {
+ showAddDialog(aMessage);
+ }
+
+ public void handleAddBreakpoint(String url) {
+ showAddDialog(url, HttpBreakpointMessage.Match.regex);
+ }
+
+ void addBreakpoint(HttpBreakpointMessage breakpoint) {
+ extensionBreak.addBreakpoint(breakpoint);
+ }
+
+ @Override
+ public void handleEditBreakpoint(BreakpointMessageInterface breakpoint) {
+ showEditDialog((HttpBreakpointMessage) breakpoint);
+ }
+
+ void editBreakpoint(
+ BreakpointMessageInterface oldBreakpoint, BreakpointMessageInterface newBreakpoint) {
+ extensionBreak.editBreakpoint(oldBreakpoint, newBreakpoint);
+ }
+
+ @Override
+ public void handleRemoveBreakpoint(BreakpointMessageInterface breakpoint) {
+ extensionBreak.removeBreakpoint(breakpoint);
+ }
+
+ @Override
+ public void reset() {}
+
+ private void populateAddDialogAndSetVisible(String url, HttpBreakpointMessage.Match match) {
+ breakDialog.init(
+ new HttpBreakpointMessage(
+ url, HttpBreakpointMessage.Location.url, match, false, true),
+ true);
+ breakDialog.setVisible(true);
+ }
+
+ private void showAddDialog(Message aMessage) {
+ HttpBreakpointMessage.Match match = HttpBreakpointMessage.Match.regex;
+ HttpMessage msg = (HttpMessage) aMessage;
+ String regex = "";
+
+ if (msg.getHistoryRef() != null && msg.getHistoryRef().getSiteNode() != null) {
+ try {
+ regex =
+ new StructuralSiteNode(msg.getHistoryRef().getSiteNode())
+ .getRegexPattern(false);
+ } catch (DatabaseException e) {
+ // Ignore
+ }
+ }
+ if (regex.length() == 0 && msg.getRequestHeader().getURI() != null) {
+ // Just use the escaped url
+ regex = msg.getRequestHeader().getURI().toString();
+ match = HttpBreakpointMessage.Match.contains;
+ }
+ this.showAddDialog(regex, match);
+ }
+
+ private void showAddDialog(String url, HttpBreakpointMessage.Match match) {
+ if (breakDialog == null) {
+ breakDialog =
+ new BreakAddEditDialog(
+ this, View.getSingleton().getMainFrame(), new Dimension(407, 255));
+ }
+ populateAddDialogAndSetVisible(url, match);
+ }
+
+ private void populateEditDialogAndSetVisible(HttpBreakpointMessage breakpoint) {
+ breakDialog.init(breakpoint, false);
+ breakDialog.setVisible(true);
+ }
+
+ private void showEditDialog(HttpBreakpointMessage breakpoint) {
+ if (breakDialog == null) {
+ breakDialog =
+ new BreakAddEditDialog(
+ this, View.getSingleton().getMainFrame(), new Dimension(407, 255));
+ }
+ populateEditDialogAndSetVisible(breakpoint);
+ }
+
+ private PopupMenuAddBreakSites getPopupMenuAddBreakSites() {
+ if (popupMenuAddBreakSites == null) {
+ popupMenuAddBreakSites = new PopupMenuAddBreakSites(this);
+ }
+ return popupMenuAddBreakSites;
+ }
+
+ private PopupMenuAddBreakHistory getPopupMenuAddBreakHistory() {
+ if (popupMenuAddBreakHistory == null) {
+ popupMenuAddBreakHistory = new PopupMenuAddBreakHistory(extensionBreak);
+ }
+ return popupMenuAddBreakHistory;
+ }
+}
diff --git a/addOns/brk/src/main/java/org/zaproxy/addon/brk/impl/http/PopupMenuAddBreakHistory.java b/addOns/brk/src/main/java/org/zaproxy/addon/brk/impl/http/PopupMenuAddBreakHistory.java
new file mode 100644
index 00000000000..e791c598c1b
--- /dev/null
+++ b/addOns/brk/src/main/java/org/zaproxy/addon/brk/impl/http/PopupMenuAddBreakHistory.java
@@ -0,0 +1,63 @@
+/*
+ * Zed Attack Proxy (ZAP) and its related class files.
+ *
+ * ZAP is an HTTP/HTTPS proxy for assessing web application security.
+ *
+ * Copyright 2010 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.addon.brk.impl.http;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.parosproxy.paros.Constant;
+import org.parosproxy.paros.db.DatabaseException;
+import org.parosproxy.paros.model.HistoryReference;
+import org.parosproxy.paros.network.HttpMalformedHeaderException;
+import org.zaproxy.addon.brk.ExtensionBreak;
+import org.zaproxy.zap.view.messagecontainer.http.HttpMessageContainer;
+import org.zaproxy.zap.view.popup.PopupMenuItemHistoryReferenceContainer;
+
+@SuppressWarnings("serial")
+public class PopupMenuAddBreakHistory extends PopupMenuItemHistoryReferenceContainer {
+
+ private static final long serialVersionUID = -1984801437717248474L;
+
+ private static final Logger LOGGER = LogManager.getLogger(PopupMenuAddBreakHistory.class);
+
+ private final ExtensionBreak extension;
+
+ public PopupMenuAddBreakHistory(ExtensionBreak extension) {
+ super(Constant.messages.getString("brk.add.popup"));
+
+ this.extension = extension;
+ }
+
+ @Override
+ public boolean isEnableForInvoker(Invoker invoker, HttpMessageContainer httpMessageContainer) {
+ return (invoker == Invoker.HISTORY_PANEL);
+ }
+
+ @Override
+ public void performAction(HistoryReference href) {
+ try {
+ extension.addUiBreakpoint(href.getHttpMessage());
+ } catch (HttpMalformedHeaderException | DatabaseException e) {
+ LOGGER.error(e.getMessage(), e);
+ extension
+ .getView()
+ .showWarningDialog(Constant.messages.getString("brk.add.error.history"));
+ }
+ }
+}
diff --git a/addOns/brk/src/main/java/org/zaproxy/addon/brk/impl/http/PopupMenuAddBreakSites.java b/addOns/brk/src/main/java/org/zaproxy/addon/brk/impl/http/PopupMenuAddBreakSites.java
new file mode 100644
index 00000000000..73502af3167
--- /dev/null
+++ b/addOns/brk/src/main/java/org/zaproxy/addon/brk/impl/http/PopupMenuAddBreakSites.java
@@ -0,0 +1,55 @@
+/*
+ * Zed Attack Proxy (ZAP) and its related class files.
+ *
+ * ZAP is an HTTP/HTTPS proxy for assessing web application security.
+ *
+ * Copyright 2010 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.addon.brk.impl.http;
+
+import org.parosproxy.paros.Constant;
+import org.parosproxy.paros.db.DatabaseException;
+import org.parosproxy.paros.model.SiteNode;
+import org.zaproxy.zap.model.StructuralSiteNode;
+import org.zaproxy.zap.view.messagecontainer.http.HttpMessageContainer;
+import org.zaproxy.zap.view.popup.PopupMenuItemSiteNodeContainer;
+
+@SuppressWarnings("serial")
+public class PopupMenuAddBreakSites extends PopupMenuItemSiteNodeContainer {
+
+ private static final long serialVersionUID = -7635703590177283587L;
+
+ private HttpBreakpointsUiManagerInterface uiManager;
+
+ public PopupMenuAddBreakSites(HttpBreakpointsUiManagerInterface uiManager) {
+ super(Constant.messages.getString("brk.add.popup"));
+
+ this.uiManager = uiManager;
+ }
+
+ @Override
+ public boolean isEnableForInvoker(Invoker invoker, HttpMessageContainer httpMessageContainer) {
+ return (invoker == Invoker.SITES_PANEL);
+ }
+
+ @Override
+ public void performAction(SiteNode sn) {
+ try {
+ uiManager.handleAddBreakpoint(new StructuralSiteNode(sn).getRegexPattern(false));
+ } catch (DatabaseException e) {
+ // Ignore
+ }
+ }
+}
diff --git a/addOns/brk/src/main/java/org/zaproxy/addon/brk/impl/http/ProxyListenerBreak.java b/addOns/brk/src/main/java/org/zaproxy/addon/brk/impl/http/ProxyListenerBreak.java
new file mode 100644
index 00000000000..cb60af929d0
--- /dev/null
+++ b/addOns/brk/src/main/java/org/zaproxy/addon/brk/impl/http/ProxyListenerBreak.java
@@ -0,0 +1,99 @@
+/*
+ * Zed Attack Proxy (ZAP) and its related class files.
+ *
+ * ZAP is an HTTP/HTTPS proxy for assessing web application security.
+ *
+ * Copyright 2012 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.addon.brk.impl.http;
+
+import org.parosproxy.paros.core.proxy.ProxyListener;
+import org.parosproxy.paros.extension.history.ProxyListenerLog;
+import org.parosproxy.paros.model.Model;
+import org.parosproxy.paros.model.Session;
+import org.parosproxy.paros.network.HttpHeader;
+import org.parosproxy.paros.network.HttpMessage;
+import org.zaproxy.addon.brk.ExtensionBreak;
+
+public class ProxyListenerBreak implements ProxyListener {
+
+ // Should be the last one before the listener that saves the HttpMessage to
+ // the DB, this way the HttpMessage will be correctly shown to the user (to
+ // edit it) because it could have been changed by other ProxyListener.
+ public static final int PROXY_LISTENER_ORDER = ProxyListenerLog.PROXY_LISTENER_ORDER - 1;
+
+ private Model model = null;
+ private ExtensionBreak extension = null;
+
+ public ProxyListenerBreak(Model model, ExtensionBreak extension) {
+ this.model = model;
+ this.extension = extension;
+ }
+
+ @Override
+ public int getArrangeableListenerOrder() {
+ return PROXY_LISTENER_ORDER;
+ }
+
+ @Override
+ public boolean onHttpRequestSend(HttpMessage msg) {
+ if (isSkipImage(msg.getRequestHeader())) {
+ return true;
+ }
+
+ if (extension.isInScopeOnly()) {
+ // Cant use msg,isInScope() as it wont have been initialised
+ Session session = Model.getSingleton().getSession();
+ if (!session.isInScope(msg.getRequestHeader().getURI().toString())) {
+ return true;
+ }
+ }
+
+ if (extension.messageReceivedFromClient(msg)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean onHttpResponseReceive(HttpMessage msg) {
+ if (isSkipImage(msg.getRequestHeader()) || isSkipImage(msg.getResponseHeader())) {
+ return true;
+ }
+
+ if (extension.isInScopeOnly()) {
+ // Cant use msg,isInScope() as it wont have been initialised
+ Session session = Model.getSingleton().getSession();
+ if (!session.isInScope(msg.getRequestHeader().getURI().toString())) {
+ return true;
+ }
+ }
+
+ if (extension.messageReceivedFromServer(msg)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ private boolean isSkipImage(HttpHeader header) {
+ if (header.isImage() && !model.getOptionsParam().getViewParam().isProcessImages()) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/addOns/brk/src/main/javahelp/org/zaproxy/zap/extension/brk/resources/help/contents/addbreak.html b/addOns/brk/src/main/javahelp/org/zaproxy/zap/extension/brk/resources/help/contents/addbreak.html
new file mode 100644
index 00000000000..f4111036bdf
--- /dev/null
+++ b/addOns/brk/src/main/javahelp/org/zaproxy/zap/extension/brk/resources/help/contents/addbreak.html
@@ -0,0 +1,37 @@
+
+
+
+
+
+Add/Edit Breakpoint dialog
+
+
+
+
Add/Edit Breakpoint dialog
+
+A breakpoint is defined by the following fields:
+
+
+
Location - where the String is checked in the
+ HTTP message: URL, Request Header, Request Body,
+ Response Header, or Response Body.
+
Match - how the String is interpreted, Regex
+ or Contains, for regular expression or exact match, respectively, in the Location.
+ The regular expression does not need to match the whole content of the Location.
+
String - the string that triggers the breakpoint.
+
Inverse - if the result of the Match should be
+ the inverse.
+
Ignore case - if the case of the String should be
+ ignored.
+
+
+If you proxy a HTTP message that matches a breakpoint then ZAP will intercept it and allow you to change either the request
+or the response.
+
+
+Note: ZAP will warn and prevent adding breakpoints with a fragment identifier component (#), if
+the breakpoint has match Contains and location URL. Such breakpoint would not work because the
+fragment identifier is not sent to the server.
+
+
+
diff --git a/addOns/brk/src/main/javahelp/org/zaproxy/zap/extension/brk/resources/help/contents/images/break_add.png b/addOns/brk/src/main/javahelp/org/zaproxy/zap/extension/brk/resources/help/contents/images/break_add.png
new file mode 100644
index 00000000000..e08dead3fab
Binary files /dev/null and b/addOns/brk/src/main/javahelp/org/zaproxy/zap/extension/brk/resources/help/contents/images/break_add.png differ
diff --git a/addOns/brk/src/main/javahelp/org/zaproxy/zap/extension/brk/resources/help/helpset.hs b/addOns/brk/src/main/javahelp/org/zaproxy/zap/extension/brk/resources/help/helpset.hs
new file mode 100644
index 00000000000..7dcf863b69e
--- /dev/null
+++ b/addOns/brk/src/main/javahelp/org/zaproxy/zap/extension/brk/resources/help/helpset.hs
@@ -0,0 +1,41 @@
+
+
+
+ Active Scan Rules - Alpha | ZAP Extension
+
+
+ top
+
+
+
+
+ TOC
+
+ org.zaproxy.zap.extension.help.ZapTocView
+ toc.xml
+
+
+
+ Index
+
+ javax.help.IndexView
+ index.xml
+
+
+
+ Search
+
+ javax.help.SearchView
+
+ JavaHelpSearch
+
+
+
+
+ Favorites
+
+ javax.help.FavoritesView
+
+
diff --git a/addOns/brk/src/main/javahelp/org/zaproxy/zap/extension/brk/resources/help/index.xml b/addOns/brk/src/main/javahelp/org/zaproxy/zap/extension/brk/resources/help/index.xml
new file mode 100644
index 00000000000..40f59c20433
--- /dev/null
+++ b/addOns/brk/src/main/javahelp/org/zaproxy/zap/extension/brk/resources/help/index.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
diff --git a/addOns/brk/src/main/javahelp/org/zaproxy/zap/extension/brk/resources/help/map.jhm b/addOns/brk/src/main/javahelp/org/zaproxy/zap/extension/brk/resources/help/map.jhm
new file mode 100644
index 00000000000..43accad8642
--- /dev/null
+++ b/addOns/brk/src/main/javahelp/org/zaproxy/zap/extension/brk/resources/help/map.jhm
@@ -0,0 +1,9 @@
+
+
+
+
diff --git a/addOns/brk/src/main/javahelp/org/zaproxy/zap/extension/brk/resources/help/toc.xml b/addOns/brk/src/main/javahelp/org/zaproxy/zap/extension/brk/resources/help/toc.xml
new file mode 100644
index 00000000000..d61534fae2b
--- /dev/null
+++ b/addOns/brk/src/main/javahelp/org/zaproxy/zap/extension/brk/resources/help/toc.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/addOns/brk/src/main/resources/org/zaproxy/addon/brk/Messages.properties b/addOns/brk/src/main/resources/org/zaproxy/addon/brk/Messages.properties
new file mode 100644
index 00000000000..3c566681184
--- /dev/null
+++ b/addOns/brk/src/main/resources/org/zaproxy/addon/brk/Messages.properties
@@ -0,0 +1,103 @@
+
+break.api.action.addHttpBreakpoint = Adds a custom HTTP breakpoint. The string is the string to match. Location may be one of: url, request_header, request_body, response_header or response_body. Match may be: contains or regex. Inverse (match) may be true or false. Lastly, ignorecase (when matching the string) may be true or false.
+break.api.action.addHttpBreakpoint.param.ignorecase =
+break.api.action.addHttpBreakpoint.param.inverse =
+break.api.action.addHttpBreakpoint.param.location =
+break.api.action.addHttpBreakpoint.param.match =
+break.api.action.addHttpBreakpoint.param.string =
+break.api.action.break = Controls the global break functionality. The type may be one of: http-all, http-request or http-response. The state may be true (for turning break on for the specified type) or false (for turning break off). Scope is not currently used.
+break.api.action.break.param.scope =
+break.api.action.break.param.state =
+break.api.action.break.param.type =
+break.api.action.continue = Submits the currently intercepted message and unsets the global request/response breakpoints
+break.api.action.drop = Drops the currently intercepted message
+break.api.action.removeHttpBreakpoint = Removes the specified breakpoint
+break.api.action.removeHttpBreakpoint.param.ignorecase =
+break.api.action.removeHttpBreakpoint.param.inverse =
+break.api.action.removeHttpBreakpoint.param.location =
+break.api.action.removeHttpBreakpoint.param.match =
+break.api.action.removeHttpBreakpoint.param.string =
+break.api.action.setHttpMessage = Overwrites the currently intercepted message with the data provided
+break.api.action.setHttpMessage.param.httpBody =
+break.api.action.setHttpMessage.param.httpHeader =
+break.api.action.step = Submits the currently intercepted message, the next request or response will automatically be intercepted
+break.api.desc =
+break.api.pconn.waitForHttpBreak = Waits until an HTTP breakpoint has been hit, at which point it returns the message. Poll is the number of milliseconds ZAP will pause between checking for breakpoints being hit (default 500). If keepalive is zero or less then the response will be returned as a Server Sent Event, otherwise it is used as the frequency in seconds at which 'keepalive' events should be returned and the response is sent as a standard response.
+break.api.view.httpMessage = Returns the HTTP message currently intercepted (if any)
+break.api.view.isBreakAll = Returns True if ZAP will break on both requests and responses
+break.api.view.isBreakRequest = Returns True if ZAP will break on requests
+break.api.view.isBreakResponse = Returns True if ZAP will break on responses
+
+brk.add.button.add = Add
+brk.add.button.cancel = Cancel
+brk.add.error.history = Error getting History
+brk.add.popup = Break...
+brk.add.title = Add Breakpoint
+brk.alwaysOnTop.message = By default ZAP will remain on top of all other windows when a breakpoint is hit.\nPress 'Cancel' to disable this feature.\nThis option can changed via Tools/Breakpoints
+brk.brkpoint.add.title = Add Breakpoint
+brk.brkpoint.edit.title = Edit Breakpoint
+brk.brkpoint.error.nostr = You must supply a string
+brk.brkpoint.error.regex = Invalid regular expression
+brk.brkpoint.ignorecase.label = Ignore Case:
+brk.brkpoint.inverse.label = Inverse:
+brk.brkpoint.location.label = Location:
+brk.brkpoint.location.request_body = Request Body
+brk.brkpoint.location.request_header = Request Header
+brk.brkpoint.location.response_body = Response Body
+brk.brkpoint.location.response_header = Response Header
+brk.brkpoint.location.url = URL
+brk.brkpoint.match.contains = Contains
+brk.brkpoint.match.label = Match:
+brk.brkpoint.match.regex = Regex
+brk.brkpoint.onscope = Break if out of scope
+brk.brkpoint.string.label = String:
+brk.brkpoint.warn.urlfragment = Pattern shouldn't include URL fragment (#)
+brk.checkBox.fixHostHeader = Update Host Header
+brk.checkBox.fixLength = Update Content Length
+brk.desc = Allows you to intercept and modify requests and responses
+brk.dialogue.confirmDropMessage.button.cancel.label = Cancel
+brk.dialogue.confirmDropMessage.button.confirm.label = Drop
+brk.dialogue.confirmDropMessage.message = Are you sure you want to drop the trapped message?
+brk.dialogue.confirmDropMessage.option.dontAskAgain = Don't ask again
+brk.dialogue.confirmDropMessage.title = Confirm Drop Trapped Message
+brk.edit.button.save = Save
+brk.edit.popup = Edit...
+brk.edit.title = Edit Breakpoint
+brk.name = Breakpoint Extension
+brk.optionspanel.name = Breakpoints
+brk.optionspanel.option.alwaysOnTop.label = ZAP always on top when breakpoint hit.
+brk.optionspanel.option.breakmode.dual.label = Separate Request and Response Buttons
+brk.optionspanel.option.breakmode.label = Break Buttons Mode:
+brk.optionspanel.option.breakmode.simple.label = Single Combined Button
+brk.optionspanel.option.confirmDropMessage.label = Confirm drop trapped message.
+brk.optionspanel.option.cssAndFontsUrlRegex.label = CSS and Fonts URL Regex:
+brk.optionspanel.option.inScopeOnly.label = Only break on messages in scope.
+brk.optionspanel.option.javaScriptUrlRegex.label = Javascript URL Regex:
+brk.optionspanel.option.multimediaUrlRegex.label = Multimedia URL Regex:
+brk.optionspanel.option.notpossibletoshowtip = Cannot show these buttons when break buttons are only shown in the main toolbar.
+brk.optionspanel.option.showBreakFilteringButtons.label = Show buttons to select the requests you don't want ZAP to break on.
+brk.panel.mnemonic = b
+brk.panel.title = Breakpoints
+brk.panel.warn.datainvalid = Unable to set the data to the message.
+brk.remove.popup = Remove
+brk.table.header.condition = Condition
+brk.table.header.enabled = Enabled
+brk.table.header.type = Type
+brk.toolbar.button.all.set = Set Break on All Requests and Responses
+brk.toolbar.button.all.unset = Unset break on all requests and responses
+brk.toolbar.button.bin = Bin Request or Response
+brk.toolbar.button.brkOnlyOnScope.set = Set break only in scope
+brk.toolbar.button.brkOnlyOnScope.unset = Unset break only in scope
+brk.toolbar.button.brkcssfonts.set = Set can break on CSS and Fonts
+brk.toolbar.button.brkcssfonts.unset = Set ignore breaks on CSS and Fonts
+brk.toolbar.button.brkjavascript.set = Set can break on JavaScript
+brk.toolbar.button.brkjavascript.unset = Set ignore breaks on JavaScript files
+brk.toolbar.button.brkmultimedia.set = Set can break on Multimedia
+brk.toolbar.button.brkmultimedia.unset = Set ignore breaks on Multimedia
+brk.toolbar.button.brkpoint = Add a custom HTTP breakpoint...
+brk.toolbar.button.cont = Submit and Continue to Next Breakpoint
+brk.toolbar.button.request.set = Set break on all requests
+brk.toolbar.button.request.unset = Unset break on all requests
+brk.toolbar.button.response.set = Set break on all responses
+brk.toolbar.button.response.unset = Unset break on all responses
+brk.toolbar.button.step = Submit and Step to Next Request or Response
diff --git a/addOns/brk/src/test/java/org/zaproxy/addon/brk/BreakpointMessageHandler2UnitTest.java b/addOns/brk/src/test/java/org/zaproxy/addon/brk/BreakpointMessageHandler2UnitTest.java
new file mode 100644
index 00000000000..6df58089725
--- /dev/null
+++ b/addOns/brk/src/test/java/org/zaproxy/addon/brk/BreakpointMessageHandler2UnitTest.java
@@ -0,0 +1,259 @@
+/*
+ * Zed Attack Proxy (ZAP) and its related class files.
+ *
+ * ZAP is an HTTP/HTTPS proxy for assessing web application security.
+ *
+ * Copyright 2020 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.addon.brk;
+
+import static java.util.Arrays.asList;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.params.provider.Arguments.arguments;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.mock;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.zaproxy.zap.extension.httppanel.Message;
+
+/** Unit test for {@link BreakpointMessageHandler2}. */
+class BreakpointMessageHandler2UnitTest {
+
+ private BreakpointManagementInterface breakpointManagementInterface;
+
+ private BreakpointMessageHandler2 breakpointMessageHandler;
+
+ @BeforeEach
+ void setup() {
+ breakpointManagementInterface = mock(BreakpointManagementInterface.class);
+ breakpointMessageHandler = new BreakpointMessageHandler2(breakpointManagementInterface);
+ breakpointMessageHandler.setEnabledBreakpoints(Collections.emptyList());
+ breakpointMessageHandler.setEnabledIgnoreRules(Collections.emptyList());
+ }
+
+ @ParameterizedTest
+ @MethodSource("requestAndOnlyIfInScope")
+ void shouldBeBreakpointIfMessageForceIntercept(boolean request, boolean onlyIfInScope) {
+ // Given
+ Message message = mock(Message.class);
+ given(message.isForceIntercept()).willReturn(true);
+ // When
+ boolean breakpoint = breakpointMessageHandler.isBreakpoint(message, request, onlyIfInScope);
+ // Then
+ assertThat(breakpoint, is(equalTo(true)));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"true", "false"})
+ void shouldNotBeBreakpointIfIgnoreRuleMatch(boolean request) {
+ // Given
+ Message message = mock(Message.class);
+ given(message.isInScope()).willReturn(true);
+ given(breakpointManagementInterface.isBreakRequest()).willReturn(true);
+ given(breakpointManagementInterface.isBreakResponse()).willReturn(true);
+
+ BreakpointMessageInterface skipBreakpoint = mock(BreakpointMessageInterface.class);
+ given(skipBreakpoint.isEnabled()).willReturn(true);
+ given(skipBreakpoint.match(message, request, false)).willReturn(true);
+ List ignoreRules = Arrays.asList(skipBreakpoint);
+ breakpointMessageHandler.setEnabledIgnoreRules(ignoreRules);
+ // When
+ boolean breakpoint = breakpointMessageHandler.isBreakpoint(message, request, false);
+ // Then
+ assertThat(breakpoint, is(equalTo(false)));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"true", "false"})
+ void shouldBeBreakpointIfIgnoreRulesDoNotMatch(boolean request) {
+ // Given
+ Message message = mock(Message.class);
+ given(message.isInScope()).willReturn(true);
+ given(breakpointManagementInterface.isBreakRequest()).willReturn(true);
+ given(breakpointManagementInterface.isBreakResponse()).willReturn(true);
+
+ BreakpointMessageInterface skipBreakpoint = mock(BreakpointMessageInterface.class);
+ given(skipBreakpoint.isEnabled()).willReturn(true);
+ given(skipBreakpoint.match(message, request, false)).willReturn(false);
+ List ignoreRules = Arrays.asList(skipBreakpoint);
+ breakpointMessageHandler.setEnabledIgnoreRules(ignoreRules);
+ // When
+ boolean breakpoint = breakpointMessageHandler.isBreakpoint(message, request, false);
+ // Then
+ assertThat(breakpoint, is(equalTo(true)));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"true", "false"})
+ void shouldBeBreakpointIfIgnoreRulesMatchButNotEnable(boolean request) {
+ // Given
+ Message message = mock(Message.class);
+ given(message.isInScope()).willReturn(true);
+ given(breakpointManagementInterface.isBreakRequest()).willReturn(true);
+ given(breakpointManagementInterface.isBreakResponse()).willReturn(true);
+
+ BreakpointMessageInterface skipBreakpoint = mock(BreakpointMessageInterface.class);
+ given(skipBreakpoint.isEnabled()).willReturn(false);
+ given(skipBreakpoint.match(message, request, false)).willReturn(true);
+ List ignoreRules = Arrays.asList(skipBreakpoint);
+ breakpointMessageHandler.setEnabledIgnoreRules(ignoreRules);
+ // When
+ boolean breakpoint = breakpointMessageHandler.isBreakpoint(message, request, false);
+ // Then
+ assertThat(breakpoint, is(equalTo(true)));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"true", "false"})
+ void shouldNotFailWithoutIgnoreRules(boolean request) {
+ // Given
+ Message message = mock(Message.class);
+ given(message.isInScope()).willReturn(false);
+ boolean onlyIfInScope = true;
+ breakpointMessageHandler.setEnabledIgnoreRules(null);
+ // When
+ boolean breakpoint =
+ assertDoesNotThrow(
+ () ->
+ breakpointMessageHandler.isBreakpoint(
+ message, request, onlyIfInScope));
+ // Then
+ assertThat(breakpoint, is(equalTo(false)));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"true", "false"})
+ void shouldNotBeBreakpointIfMessageNotInScopeWithOnlyIfInScope(boolean request) {
+ // Given
+ Message message = mock(Message.class);
+ given(message.isInScope()).willReturn(false);
+ boolean onlyIfInScope = true;
+ // When
+ boolean breakpoint = breakpointMessageHandler.isBreakpoint(message, request, onlyIfInScope);
+ // Then
+ assertThat(breakpoint, is(equalTo(false)));
+ }
+
+ @Test
+ void shouldBeBreakpointIfRequestAndBreakingOnAllRequests() {
+ // Given
+ Message message = mock(Message.class);
+ given(breakpointManagementInterface.isBreakRequest()).willReturn(true);
+ boolean request = true;
+ // When
+ boolean breakpoint = breakpointMessageHandler.isBreakpoint(message, request, false);
+ // Then
+ assertThat(breakpoint, is(equalTo(true)));
+ }
+
+ @Test
+ void shouldNotBeBreakpointIfNotRequestAndBreakingOnAllRequests() {
+ // Given
+ Message message = mock(Message.class);
+ given(breakpointManagementInterface.isBreakRequest()).willReturn(true);
+ boolean request = false;
+ // When
+ boolean breakpoint = breakpointMessageHandler.isBreakpoint(message, request, false);
+ // Then
+ assertThat(breakpoint, is(equalTo(false)));
+ }
+
+ @Test
+ void shouldBeBreakpointIfResponseAndBreakingOnAllResponses() {
+ // Given
+ Message message = mock(Message.class);
+ given(breakpointManagementInterface.isBreakResponse()).willReturn(true);
+ boolean request = false;
+ // When
+ boolean breakpoint = breakpointMessageHandler.isBreakpoint(message, request, false);
+ // Then
+ assertThat(breakpoint, is(equalTo(true)));
+ }
+
+ @Test
+ void shouldNotBeBreakpointIfNotResponseAndBreakingOnAllResponses() {
+ // Given
+ Message message = mock(Message.class);
+ given(breakpointManagementInterface.isBreakResponse()).willReturn(true);
+ boolean request = true;
+ // When
+ boolean breakpoint = breakpointMessageHandler.isBreakpoint(message, request, false);
+ // Then
+ assertThat(breakpoint, is(equalTo(false)));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"true", "false"})
+ void shouldBeBreakpointIfStepping(boolean request) {
+ // Given
+ Message message = mock(Message.class);
+ given(breakpointManagementInterface.isStepping()).willReturn(true);
+ // When
+ boolean breakpoint = breakpointMessageHandler.isBreakpoint(message, request, false);
+ // Then
+ assertThat(breakpoint, is(equalTo(true)));
+ }
+
+ @ParameterizedTest
+ @MethodSource("requestAndOnlyIfInScope")
+ void shouldNotBeBreakpointIfBreakpointNotHit(boolean request, boolean onlyIfInScope) {
+ // Given
+ Message message = mock(Message.class);
+ given(message.isInScope()).willReturn(onlyIfInScope);
+ BreakpointMessageInterface breakpointMessage = mock(BreakpointMessageInterface.class);
+ given(breakpointMessage.match(message, request, onlyIfInScope)).willReturn(false);
+ breakpointMessageHandler.setEnabledBreakpoints(asList(breakpointMessage));
+ // When
+ boolean breakpoint = breakpointMessageHandler.isBreakpoint(message, request, onlyIfInScope);
+ // Then
+ assertThat(breakpoint, is(equalTo(false)));
+ }
+
+ @ParameterizedTest
+ @MethodSource("requestAndOnlyIfInScope")
+ void shouldBeBreakpointIfAtLeastOneBreakpointHit(boolean request, boolean onlyIfInScope) {
+ // Given
+ Message message = mock(Message.class);
+ given(message.isInScope()).willReturn(onlyIfInScope);
+ BreakpointMessageInterface breakpointMessage = mock(BreakpointMessageInterface.class);
+ given(breakpointMessage.match(message, request, onlyIfInScope)).willReturn(true);
+ breakpointMessageHandler.setEnabledBreakpoints(
+ asList(mock(BreakpointMessageInterface.class), breakpointMessage));
+ // When
+ boolean breakpoint = breakpointMessageHandler.isBreakpoint(message, request, onlyIfInScope);
+ // Then
+ assertThat(breakpoint, is(equalTo(true)));
+ }
+
+ static Stream requestAndOnlyIfInScope() {
+ return Stream.of(
+ arguments(true, true),
+ arguments(true, false),
+ arguments(false, false),
+ arguments(false, true));
+ }
+}
diff --git a/addOns/brk/src/test/java/org/zaproxy/addon/brk/impl/http/HttpBreakpointManagementDaemonImplUnitTest.java b/addOns/brk/src/test/java/org/zaproxy/addon/brk/impl/http/HttpBreakpointManagementDaemonImplUnitTest.java
new file mode 100644
index 00000000000..aae3c8744e8
--- /dev/null
+++ b/addOns/brk/src/test/java/org/zaproxy/addon/brk/impl/http/HttpBreakpointManagementDaemonImplUnitTest.java
@@ -0,0 +1,149 @@
+/*
+ * Zed Attack Proxy (ZAP) and its related class files.
+ *
+ * ZAP is an HTTP/HTTPS proxy for assessing web application security.
+ *
+ * Copyright 2016 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.addon.brk.impl.http;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.parosproxy.paros.network.HttpHeader;
+import org.parosproxy.paros.network.HttpMalformedHeaderException;
+import org.parosproxy.paros.network.HttpMessage;
+import org.parosproxy.paros.network.HttpResponseHeader;
+import org.zaproxy.zap.testutils.TestUtils;
+
+class HttpBreakpointManagementDaemonImplUnitTest extends TestUtils {
+
+ private static String OK_RESPONSE =
+ "HTTP/1.1 200 OK"
+ + HttpHeader.CRLF
+ + "Connection: close"
+ + HttpHeader.CRLF
+ + HttpHeader.CRLF;
+
+ private HttpBreakpointManagementDaemonImpl impl;
+
+ @BeforeEach
+ void setUp() throws Exception {
+ impl = new HttpBreakpointManagementDaemonImpl();
+ }
+
+ @Test
+ void shouldInitBreakPointsToFalseOnInit() {
+ assertFalse(impl.isBreakAll());
+ assertFalse(impl.isBreakRequest());
+ assertFalse(impl.isBreakResponse());
+ }
+
+ @Test
+ void shouldBreakOnAllHttpRequestsAndResponses() throws HttpMalformedHeaderException {
+ impl.setBreakAll(true);
+ HttpMessage msg = new HttpMessage();
+ assertTrue(impl.isHoldMessage(msg));
+
+ HttpResponseHeader resHeader = new HttpResponseHeader(OK_RESPONSE);
+ msg.setResponseHeader(resHeader);
+ assertTrue(impl.isHoldMessage(msg));
+ }
+
+ @Test
+ void shouldBreakOnJustHttpRequests() throws HttpMalformedHeaderException {
+ impl.setBreakAllRequests(true);
+ HttpMessage msg = new HttpMessage();
+ assertTrue(impl.isHoldMessage(msg));
+
+ HttpResponseHeader resHeader = new HttpResponseHeader(OK_RESPONSE);
+ msg.setResponseHeader(resHeader);
+ assertFalse(impl.isHoldMessage(msg));
+ }
+
+ @Test
+ void shouldBreakOnJustHttpResponses() throws HttpMalformedHeaderException {
+ impl.setBreakAllResponses(true);
+ HttpMessage msg = new HttpMessage();
+ assertFalse(impl.isHoldMessage(msg));
+
+ HttpResponseHeader resHeader = new HttpResponseHeader(OK_RESPONSE);
+ msg.setResponseHeader(resHeader);
+ assertTrue(impl.isHoldMessage(msg));
+ }
+
+ @Test
+ void shouldStep() throws HttpMalformedHeaderException {
+ impl.setBreakAll(true);
+ HttpMessage msg = new HttpMessage();
+ assertTrue(impl.isHoldMessage(msg));
+
+ impl.step();
+ assertTrue(impl.isStepping());
+ // False the first time
+ assertFalse(impl.isHoldMessage(msg));
+ // Then true for subsequent times
+ assertTrue(impl.isHoldMessage(msg));
+ assertTrue(impl.isStepping());
+
+ HttpResponseHeader resHeader = new HttpResponseHeader(OK_RESPONSE);
+ msg.setResponseHeader(resHeader);
+
+ impl.step();
+ assertTrue(impl.isStepping());
+ // False the first time
+ assertFalse(impl.isHoldMessage(msg));
+ // Then true for subsequent times
+ assertTrue(impl.isHoldMessage(msg));
+ assertTrue(impl.isStepping());
+ }
+
+ @Test
+ void shouldClearBreaksOnContinue() throws HttpMalformedHeaderException {
+ impl.setBreakAll(true);
+ HttpMessage msg = new HttpMessage();
+ assertTrue(impl.isHoldMessage(msg));
+
+ impl.cont();
+ assertFalse(impl.isHoldMessage(msg));
+ assertFalse(impl.isBreakAll());
+ assertFalse(impl.isBreakRequest());
+ assertFalse(impl.isBreakResponse());
+
+ assertFalse(impl.isHoldMessage(msg));
+ // Deliberate duplicate check due to the side effects of stepping
+ assertFalse(impl.isHoldMessage(msg));
+ assertFalse(impl.isStepping());
+ }
+
+ @Test
+ void shouldDrop() throws HttpMalformedHeaderException {
+ impl.setBreakAll(true);
+ HttpMessage msg = new HttpMessage();
+ assertTrue(impl.isHoldMessage(msg));
+
+ impl.drop();
+ assertTrue(impl.isToBeDropped());
+ assertFalse(impl.isToBeDropped());
+ assertFalse(impl.isToBeDropped());
+
+ impl.drop();
+ assertTrue(impl.isToBeDropped());
+ assertFalse(impl.isToBeDropped());
+ assertFalse(impl.isToBeDropped());
+ }
+}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index feb2851974d..da9793fab78 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -35,6 +35,7 @@ var addOns = listOf(
"authstats",
"automation",
"beanshell",
+ "brk",
"browserView",
"bruteforce",
"bugtracker",