diff --git a/CHANGELOG.md b/CHANGELOG.md
index 30a0b9b..5c9e35f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,24 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [1.3.2] - 2024-07-16
+
+### Added
+
+* Add operation JSON Beautifier
+* Add Sequencer filter option
+* Add GZIP operation option to set the compression level
+* Add comment function to operations and recipe lanes
+* Add editable lane names
+
+### Changed
+
+* Fix the emergence of null bytes when using variables
+* Fix GUI issues with using the operation Drag-and-Drop
+* Change the saved recipe structure and add CSTC version, operation comments, lane comments and lane names
+* Refactor operation button icons
+
+
## [1.3.1] - 2024-05-22
### Added
diff --git a/README.md b/README.md
index 2f39285..9d7b663 100644
--- a/README.md
+++ b/README.md
@@ -83,6 +83,15 @@ Take a look at our basic tutorial on [YouTube](https://www.youtube.com/watch?v=B
**UPDATE:** Due to some incompatibility issues when installing *CSTC* via *BApp Store*, we had to switch to a new variable prefix.
Variables from other *lanes* have now to be prefixed by ``$`` e.g. like ``$Outgoing_step1``.
+## FAQ
+
+### How does the CSTC interact with other Extensions?
+
+Requests and responses pass through the extensions in the order that they are listed, from top to bottom (as described [here](https://portswigger.net/burp/documentation/desktop/extensions/managing-extensions)).
+Depending on the extensions in use, it may make sense to adjust the position of the CSTC. If you want to process a request manipulated by the CSTC in another extension,
+the CSTC should be positioned above this extension. Conversely, the CSTC should be positioned below an extension if the CSTC is to work with the response processed by the extension in question.
+Currently the Burp Montoya API doesn't offer a way to change this order automatically, therefore the CSTC cannot influence the interaction with other extensions itself.
+
## Feedback
We gladly appreciate all feedback, bug reports and feature requests.
diff --git a/media/CSTC_Workflow.gif b/media/CSTC_Workflow.gif
index 1112123..d918b9f 100644
Binary files a/media/CSTC_Workflow.gif and b/media/CSTC_Workflow.gif differ
diff --git a/pom.xml b/pom.xml
index 5d3e429..ed56f26 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
4.0.0
de.usd.CSTC
CSTC
- 1.3.1
+ 1.3.2
CSTC
CSTC
@@ -35,13 +35,13 @@
com.fasterxml.jackson.core
jackson-core
- 2.17.1
+ 2.17.2
com.fasterxml.jackson.core
jackson-databind
- 2.17.1
+ 2.17.2
@@ -85,6 +85,7 @@
res
+ true
@@ -102,7 +103,7 @@
org.apache.maven.plugins
maven-surefire-plugin
- 3.2.5
+ 3.3.1
false
true
diff --git a/res/comment.png b/res/comment.png
new file mode 100755
index 0000000..8880c9e
Binary files /dev/null and b/res/comment.png differ
diff --git a/res/disable.png b/res/disable.png
old mode 100644
new mode 100755
index 60149af..0e80c76
Binary files a/res/disable.png and b/res/disable.png differ
diff --git a/res/help.png b/res/help.png
old mode 100644
new mode 100755
index e66abc6..28c16d3
Binary files a/res/help.png and b/res/help.png differ
diff --git a/res/no_comment.png b/res/no_comment.png
new file mode 100755
index 0000000..7ba13c9
Binary files /dev/null and b/res/no_comment.png differ
diff --git a/res/operation.png b/res/operation.png
old mode 100644
new mode 100755
index c1103f7..84c2f02
Binary files a/res/operation.png and b/res/operation.png differ
diff --git a/res/remove.png b/res/remove.png
old mode 100644
new mode 100755
index 406976c..fc42e25
Binary files a/res/remove.png and b/res/remove.png differ
diff --git a/res/stop.png b/res/stop.png
old mode 100644
new mode 100755
index e450c91..29ea6d9
Binary files a/res/stop.png and b/res/stop.png differ
diff --git a/res/stop_active.png b/res/stop_active.png
old mode 100644
new mode 100755
index 9ca2fee..a94eb52
Binary files a/res/stop_active.png and b/res/stop_active.png differ
diff --git a/res/version.properties b/res/version.properties
new file mode 100644
index 0000000..713c915
--- /dev/null
+++ b/res/version.properties
@@ -0,0 +1 @@
+version = ${project.version}
\ No newline at end of file
diff --git a/src/main/java/burp/BurpExtender.java b/src/main/java/burp/BurpExtender.java
index 86c306f..659e6df 100644
--- a/src/main/java/burp/BurpExtender.java
+++ b/src/main/java/burp/BurpExtender.java
@@ -26,7 +26,6 @@ public void initialize(MontoyaApi api) {
api.http().registerHttpHandler(new CstcHttpHandler(view));
api.userInterface().registerSuiteTab(extensionName, view);
api.userInterface().registerHttpRequestEditorProvider(new MyHttpRequestEditorProvider(view));
- api.userInterface().registerHttpResponseEditorProvider(new MyHttpResponseEditorProvider(view));
if (!api.burpSuite().version().edition().equals(BurpSuiteEdition.COMMUNITY_EDITION)) {
PersistedObject persistence = api.persistence().extensionData();
diff --git a/src/main/java/burp/CstcHttpHandler.java b/src/main/java/burp/CstcHttpHandler.java
index 3b45924..4ac4901 100644
--- a/src/main/java/burp/CstcHttpHandler.java
+++ b/src/main/java/burp/CstcHttpHandler.java
@@ -28,7 +28,6 @@ public RequestToBeSentAction handleHttpRequestToBeSent(HttpRequestToBeSent reque
if (BurpUtils.getInstance().getFilterState().shouldProcess(FilterState.BurpOperation.OUTGOING, requestToBeSent.toolSource())) {
ByteArray request = requestToBeSent.toByteArray();
ByteArray modifiedRequest = view.getOutgoingRecipePanel().bake(request, MessageType.REQUEST);
- Logger.getInstance().log("modified request: \n" + new String(modifiedRequest.getBytes()));
return continueWith(HttpRequest.httpRequest(modifiedRequest).withService(requestToBeSent.httpService()));
}
else{
@@ -41,7 +40,6 @@ public ResponseReceivedAction handleHttpResponseReceived(HttpResponseReceived re
if (BurpUtils.getInstance().getFilterState().shouldProcess(FilterState.BurpOperation.INCOMING, responseReceived.toolSource())) {
ByteArray response = responseReceived.toByteArray();
ByteArray modifiedResponse = view.getIncomingRecipePanel().bake(response, MessageType.RESPONSE);
- Logger.getInstance().log("modified response: \n" + new String(modifiedResponse.getBytes()));
return continueWith(HttpResponse.httpResponse(modifiedResponse));
}
else{
diff --git a/src/main/java/burp/MyExtensionProvidedHttpRequestEditor.java b/src/main/java/burp/MyExtensionProvidedHttpRequestEditor.java
index 94191e2..76ba9de 100644
--- a/src/main/java/burp/MyExtensionProvidedHttpRequestEditor.java
+++ b/src/main/java/burp/MyExtensionProvidedHttpRequestEditor.java
@@ -2,8 +2,10 @@
import burp.api.montoya.MontoyaApi;
import burp.api.montoya.core.ByteArray;
+import burp.api.montoya.core.ToolType;
import burp.api.montoya.http.message.HttpRequestResponse;
import burp.api.montoya.http.message.requests.HttpRequest;
+import burp.api.montoya.repeater.Repeater;
import burp.api.montoya.ui.Selection;
import burp.api.montoya.ui.editor.EditorOptions;
import burp.api.montoya.ui.editor.RawEditor;
@@ -23,9 +25,16 @@ public class MyExtensionProvidedHttpRequestEditor implements ExtensionProvidedHt
MyExtensionProvidedHttpRequestEditor(EditorCreationContext creationContext, View view)
{
- this.api = BurpUtils.getInstance().getApi();
- this.view = view;
- requestEditor = api.userInterface().createRawEditor(EditorOptions.READ_ONLY);
+ if(creationContext.toolSource().isFromTool(ToolType.REPEATER)) {
+ this.api = BurpUtils.getInstance().getApi();
+ this.view = view;
+ requestEditor = api.userInterface().createRawEditor(EditorOptions.READ_ONLY);
+ }
+ else {
+ this.api = null;
+ this.view = null;
+ this.requestEditor = null;
+ }
}
@Override
diff --git a/src/main/java/burp/MyExtensionProvidedHttpResponseEditor.java b/src/main/java/burp/MyExtensionProvidedHttpResponseEditor.java
deleted file mode 100644
index 2ccb508..0000000
--- a/src/main/java/burp/MyExtensionProvidedHttpResponseEditor.java
+++ /dev/null
@@ -1,73 +0,0 @@
-package burp;
-
-import burp.api.montoya.MontoyaApi;
-import burp.api.montoya.core.ByteArray;
-import burp.api.montoya.http.message.HttpRequestResponse;
-import burp.api.montoya.http.message.responses.HttpResponse;
-import burp.api.montoya.ui.Selection;
-import burp.api.montoya.ui.editor.EditorOptions;
-import burp.api.montoya.ui.editor.RawEditor;
-import burp.api.montoya.ui.editor.extension.EditorCreationContext;
-import burp.api.montoya.ui.editor.extension.ExtensionProvidedHttpResponseEditor;
-import de.usd.cstchef.Utils.MessageType;
-import de.usd.cstchef.view.View;
-
-import java.awt.*;
-
-public class MyExtensionProvidedHttpResponseEditor implements ExtensionProvidedHttpResponseEditor
-{
- private final RawEditor requestEditor;
- private HttpRequestResponse requestResponse;
- private final MontoyaApi api;
- private final View view;
-
- MyExtensionProvidedHttpResponseEditor(EditorCreationContext creationContext, View view)
- {
- this.api = BurpUtils.getInstance().getApi();
- this.view = view;
- requestEditor = api.userInterface().createRawEditor(EditorOptions.READ_ONLY);
- }
-
- @Override
- public HttpResponse getResponse()
- {
- return requestResponse.response();
- }
-
- @Override
- public void setRequestResponse(HttpRequestResponse requestResponse)
- {
- ByteArray result = view.getIncomingRecipePanel().bake(requestResponse.response().toByteArray(), MessageType.RESPONSE);
- this.requestEditor.setContents(result);
- }
-
- @Override
- public boolean isEnabledFor(HttpRequestResponse requestResponse)
- {
- return requestResponse.response() != null;
- }
-
- @Override
- public String caption()
- {
- return "CSTC";
- }
-
- @Override
- public Component uiComponent()
- {
- return requestEditor.uiComponent();
- }
-
- @Override
- public Selection selectedData()
- {
- return requestEditor.selection().isPresent() ? requestEditor.selection().get() : null;
- }
-
- @Override
- public boolean isModified()
- {
- return requestEditor.isModified();
- }
-}
\ No newline at end of file
diff --git a/src/main/java/burp/MyHttpResponseEditorProvider.java b/src/main/java/burp/MyHttpResponseEditorProvider.java
deleted file mode 100644
index cc8a9c7..0000000
--- a/src/main/java/burp/MyHttpResponseEditorProvider.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package burp;
-
-import burp.api.montoya.MontoyaApi;
-import burp.api.montoya.ui.editor.extension.EditorCreationContext;
-import burp.api.montoya.ui.editor.extension.ExtensionProvidedHttpRequestEditor;
-import burp.api.montoya.ui.editor.extension.ExtensionProvidedHttpResponseEditor;
-import burp.api.montoya.ui.editor.extension.HttpResponseEditorProvider;
-import de.usd.cstchef.view.View;
-
-class MyHttpResponseEditorProvider implements HttpResponseEditorProvider
-{
- private final View view;
-
- MyHttpResponseEditorProvider(View view){
- this.view = view;
- }
-
-
- @Override
- public ExtensionProvidedHttpResponseEditor provideHttpResponseEditor(EditorCreationContext creationContext) {
- return new MyExtensionProvidedHttpResponseEditor(creationContext, view);
- }
-}
\ No newline at end of file
diff --git a/src/main/java/de/usd/cstchef/Utils.java b/src/main/java/de/usd/cstchef/Utils.java
index 1e99477..b6db945 100644
--- a/src/main/java/de/usd/cstchef/Utils.java
+++ b/src/main/java/de/usd/cstchef/Utils.java
@@ -59,6 +59,7 @@
import de.usd.cstchef.operations.conditional.StringContains;
import de.usd.cstchef.operations.conditional.StringMatch;
import de.usd.cstchef.operations.dataformat.FromBase64;
+import de.usd.cstchef.operations.dataformat.JsonBeautifier;
import de.usd.cstchef.operations.dataformat.FromHex;
import de.usd.cstchef.operations.dataformat.HtmlDecode;
import de.usd.cstchef.operations.dataformat.HtmlEncode;
@@ -217,8 +218,11 @@ public static HttpRequest addCookieToHttpRequest(HttpRequest request, Cookie coo
}
public static ByteArray insertAtOffset(ByteArray input, int start, int end, ByteArray newValue) {
- ByteArray prefix = BurpUtils.subArray(input, 0, start);
- ByteArray rest = BurpUtils.subArray(input, end, input.length());
+ ByteArray prefix = input.subArray(0, start);
+ ByteArray rest = input.subArray(0, 0);
+ if(end < input.length()) {
+ rest = input.subArray(end, input.length());
+ }
ByteArray output = prefix.withAppended(newValue).withAppended(rest);
return output;
@@ -318,7 +322,7 @@ public static Class extends Operation>[] getOperationsDev() {
TimestampOffset.class, TimestampToDateTime.class, ToBase64.class, ToHex.class, UnixTimestamp.class,
UrlDecode.class, UrlEncode.class,
Whirlpool.class, WriteFile.class, XmlFullSignature.class, XmlMultiSignature.class,
- Xor.class, SoapMultiSignature.class, Luhn.class, Concatenate.class
+ Xor.class, SoapMultiSignature.class, Luhn.class, Concatenate.class, JsonBeautifier.class
};
}
diff --git a/src/main/java/de/usd/cstchef/operations/Operation.java b/src/main/java/de/usd/cstchef/operations/Operation.java
index fceb094..efd807b 100644
--- a/src/main/java/de/usd/cstchef/operations/Operation.java
+++ b/src/main/java/de/usd/cstchef/operations/Operation.java
@@ -9,6 +9,8 @@
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
import java.io.EOFException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -19,6 +21,7 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
@@ -28,6 +31,7 @@
import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
+import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JSpinner;
@@ -68,6 +72,8 @@ public abstract class Operation extends JPanel {
private static ImageIcon disableIcon = new ImageIcon(Operation.class.getResource("/disable.png"));
private static ImageIcon removeIcon = new ImageIcon(Operation.class.getResource("/remove.png"));
private static ImageIcon helpIcon = new ImageIcon(Operation.class.getResource("/help.png"));
+ private static ImageIcon commentIcon = new ImageIcon(Operation.class.getResource("/comment.png"));
+ private static ImageIcon noCommentIcon = new ImageIcon(Operation.class.getResource("/no_comment.png"));
private NotifyChangeListener notifyChangeListener;
@@ -80,6 +86,9 @@ public abstract class Operation extends JPanel {
private Box contentBox;
private Map uiElements;
+ private String comment;
+ private JButton commentBtn;
+
private int operationSkip = 0;
private int laneSkip = 0;
@@ -122,6 +131,19 @@ public Operation() {
removeBtn.setToolTipText("Remove");
JButton helpBtn = createIconButton(Operation.helpIcon);
helpBtn.setToolTipText(opInfos.description());
+ commentBtn = createIconButton(noCommentIcon);
+
+ commentBtn.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ commentBtn.setToolTipText(getComment());
+ String comment = JOptionPane.showInputDialog("Edit comment:", commentBtn.getToolTipText());
+ commentBtn.setToolTipText(comment);
+ setComment(comment);
+ ImageIcon newIcon = comment.isEmpty() ? Operation.noCommentIcon : Operation.commentIcon;
+ commentBtn.setIcon(newIcon);
+ }
+ });
+
disableBtn.addActionListener(new ActionListener() {
@Override
@@ -162,6 +184,8 @@ public void actionPerformed(ActionEvent e) {
header.add(titleLbl);
header.add(Box.createHorizontalStrut(6));
header.add(helpBtn);
+ header.add(Box.createHorizontalStrut(3));
+ header.add(commentBtn);
header.add(Box.createHorizontalGlue());
header.add(disableBtn);
header.add(Box.createHorizontalStrut(3));
@@ -189,6 +213,18 @@ public void actionPerformed(ActionEvent e) {
this.refreshColors();
}
+ public String getComment() {
+ return this.comment;
+ }
+
+ public void setComment(String comment) {
+ if(comment != null) {
+ this.comment = comment;
+ commentBtn.setIcon(Operation.commentIcon);
+ commentBtn.setToolTipText(comment);
+ }
+ }
+
public Map getState() {
Map properties = new HashMap<>();
for (String key : this.uiElements.keySet()) {
diff --git a/src/main/java/de/usd/cstchef/operations/compression/Gzip.java b/src/main/java/de/usd/cstchef/operations/compression/Gzip.java
index 649b4a6..b6f55b3 100644
--- a/src/main/java/de/usd/cstchef/operations/compression/Gzip.java
+++ b/src/main/java/de/usd/cstchef/operations/compression/Gzip.java
@@ -2,6 +2,8 @@
import java.util.zip.GZIPOutputStream;
+import javax.swing.JComboBox;
+
import burp.api.montoya.core.ByteArray;
import java.io.ByteArrayInputStream;
@@ -11,14 +13,19 @@
import de.usd.cstchef.operations.Operation;
import de.usd.cstchef.operations.OperationCategory;
import de.usd.cstchef.operations.Operation.OperationInfos;
+import de.usd.cstchef.wrapper.GZIPOutputStreamWrapper;
@OperationInfos(name = "GZIP", category = OperationCategory.COMPRESSION, description = "Compresses the input using GZIP.")
public class Gzip extends Operation {
+ private JComboBox compressionLevelBox;
+
@Override
protected ByteArray perform(ByteArray input, MessageType messageType) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
- GZIPOutputStream gzos = new GZIPOutputStream(out);
+ //GZIPOutputStream gzos = new GZIPOutputStream(out);
+ GZIPOutputStreamWrapper gzos = new GZIPOutputStreamWrapper(out);
+ gzos.setCompressionLevel((int)compressionLevelBox.getSelectedItem());
ByteArrayInputStream in = new ByteArrayInputStream(input.getBytes());
byte[] buffer = new byte[1024];
@@ -33,4 +40,13 @@ protected ByteArray perform(ByteArray input, MessageType messageType) throws Exc
return factory.createByteArray(out.toByteArray());
}
+ @Override
+ public void createUI()
+ {
+ Integer[] compressionLevel = {1, 2, 3, 4, 5, 6, 7, 8, 9};
+ this.compressionLevelBox = new JComboBox<>(compressionLevel);
+ this.compressionLevelBox.setSelectedIndex(5);
+ this.addUIElement("Compression Level", this.compressionLevelBox);
+ }
+
}
diff --git a/src/main/java/de/usd/cstchef/operations/dataformat/JsonBeautifier.java b/src/main/java/de/usd/cstchef/operations/dataformat/JsonBeautifier.java
new file mode 100644
index 0000000..0b44a30
--- /dev/null
+++ b/src/main/java/de/usd/cstchef/operations/dataformat/JsonBeautifier.java
@@ -0,0 +1,24 @@
+package de.usd.cstchef.operations.dataformat;
+
+import burp.api.montoya.core.ByteArray;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import de.usd.cstchef.Utils;
+import de.usd.cstchef.operations.Operation;
+import de.usd.cstchef.operations.OperationCategory;
+
+@Operation.OperationInfos(name = "JSON Beautifier", category = OperationCategory.DATAFORMAT, description = "Format JSON Data.")
+public class JsonBeautifier extends Operation {
+ @Override
+ protected ByteArray perform(ByteArray input, Utils.MessageType messageType) throws Exception {
+ try {
+ ObjectMapper objectMapper = new ObjectMapper();
+ objectMapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);
+ JsonNode jsonNode = objectMapper.readTree(input.toString());
+ return factory.createByteArray(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonNode));
+ } catch (Exception e2) {
+ return input;
+ }
+ }
+}
diff --git a/src/main/java/de/usd/cstchef/operations/setter/HttpHeaderSetter.java b/src/main/java/de/usd/cstchef/operations/setter/HttpHeaderSetter.java
index 95f8599..b08524c 100644
--- a/src/main/java/de/usd/cstchef/operations/setter/HttpHeaderSetter.java
+++ b/src/main/java/de/usd/cstchef/operations/setter/HttpHeaderSetter.java
@@ -40,11 +40,16 @@ protected ByteArray perform(ByteArray input, MessageType messageType) throws Exc
}
else if(messageType == MessageType.RESPONSE){
HttpResponse response = HttpResponse.httpResponse(input);
- if(response.hasHeader(headerName) || addIfNotPresent.isSelected()){
- return response.withAddedHeader(HttpHeader.httpHeader(headerName, newValue)).toByteArray();
+ if(response.hasHeader(headerName)) {
+ return response.withUpdatedHeader(HttpHeader.httpHeader(headerName, newValue)).toByteArray();
}
- else{
- return input;
+ else {
+ if(addIfNotPresent.isSelected()) {
+ return response.withAddedHeader(HttpHeader.httpHeader(headerName, newValue)).toByteArray();
+ }
+ else {
+ return input;
+ }
}
}
else{
diff --git a/src/main/java/de/usd/cstchef/operations/utils/RandomNumber.java b/src/main/java/de/usd/cstchef/operations/utils/RandomNumber.java
index 48b3a32..d8328c7 100644
--- a/src/main/java/de/usd/cstchef/operations/utils/RandomNumber.java
+++ b/src/main/java/de/usd/cstchef/operations/utils/RandomNumber.java
@@ -89,10 +89,6 @@ public void createUI() {
this.textFieldMaximum.setText("9999");
this.addUIElement("Maximum Number", this.textFieldMaximum);
- // use a separator
- JSeparator separator = new JSeparator(SwingConstants.HORIZONTAL);
- this.addUIElement(null, separator);
-
// fields for formatting: Integer digits
this.textFieldFormatMinIntDigits = new JTextField();
this.textFieldFormatMinIntDigits.setText("1");
diff --git a/src/main/java/de/usd/cstchef/view/RecipePanel.java b/src/main/java/de/usd/cstchef/view/RecipePanel.java
index 8f4cd84..efeb97e 100644
--- a/src/main/java/de/usd/cstchef/view/RecipePanel.java
+++ b/src/main/java/de/usd/cstchef/view/RecipePanel.java
@@ -12,11 +12,13 @@
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
+import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Properties;
import java.util.Timer;
import java.util.TimerTask;
@@ -350,10 +352,23 @@ public void setFormatMessage(HttpRequestResponse requestResponse, MessageType me
}
public void restoreState(String jsonState) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
- // TODO do we want to remove all existing operations before loading here?
- this.clear(); // Yes!
+ this.clear();
ObjectMapper mapper = new ObjectMapper();
- JsonNode stepNodes = mapper.readTree(jsonState);
+ JsonNode rootNode = mapper.readTree(jsonState);
+ JsonNode stepNodes;
+ JsonNode versionNode;
+
+ // check if "version" ObjectNode is there (since 1.3.2)
+ if(rootNode.get(0) != null && rootNode.get(0).get("version") == null) {
+ // recipes saved by CSTC <= 1.3.1
+ stepNodes = rootNode;
+ }
+ else {
+ // currently 1.3.2
+ versionNode = rootNode.get(0);
+ stepNodes = rootNode.get(1);
+ }
+
if (!stepNodes.isArray()) {
throw new IOException("wrong data format");
}
@@ -364,29 +379,81 @@ public void restoreState(String jsonState) throws IOException, ClassNotFoundExce
throw new IOException("wrong data format");
}
- for (int i = 0; i < operationNodes.size(); i++) {
+ RecipeStepPanel panel = (RecipeStepPanel) this.operationLines.getComponent(step);
+
+ /* two types of ObjectNodes for every RecipeStepPanel:
+ Lane information (always at index 0, if set) and the Operations
+
+ If there's a lane ObjectNode we need to tell the inner loop to begin at index 1.
+ The inner loop iterates over the Operations
+ */
+ int index = 0;
+ if(operationNodes.get(0) != null) {
+ if(operationNodes.get(0).get("lane_title") != null) {
+ index = 1;
+ panel.setTitle(operationNodes.get(0).get("lane_title").asText());
+ }
+ if(operationNodes.get(0).get("lane_comment") != null) {
+ index = 1;
+ panel.setComment(operationNodes.get(0).get("lane_comment").asText());
+ }
+ }
+
+ for (int i = index; i < operationNodes.size(); i++) {
JsonNode operationNode = operationNodes.get(i);
String operation = operationNode.get("operation").asText();
Map parameters = mapper.convertValue(operationNode.get("parameters"), Map.class);
Class cls = (Class) Class.forName(operation);
+
// check if it is an operation
Operation op = cls.newInstance();
op.load(parameters);
op.setDisabled(!operationNode.get("is_enabled").asBoolean());
- RecipeStepPanel panel = (RecipeStepPanel) this.operationLines.getComponent(step);
- panel.addComponent(op, i);
+
+ // check if "comment" attribute is set (since 1.3.2)
+ if(operationNode.get("comment") != null) {
+ if(operationNode.get("comment").asText() != "null") {
+ op.setComment(operationNode.get("comment").asText());
+ }
+ }
+ // depending on if lane name is set we may start the loop at index 1, but want to add the first component at index 0
+ panel.addComponent(op, index == 1 ? i-1 : i);
}
}
}
private String getStateAsJSON() throws IOException {
ObjectMapper mapper = new ObjectMapper();
+ ArrayNode rootNode = mapper.createArrayNode();
+ ObjectNode versionNode = mapper.createObjectNode();
ArrayNode stepsNode = mapper.createArrayNode();
for (int step = 0; step < this.operationSteps; step++) {
ArrayNode operationsNode = mapper.createArrayNode();
RecipeStepPanel stepPanel = (RecipeStepPanel) this.operationLines.getComponent(step);
+
+ // save lane name in case it differs from the default
+ int laneNumber = step + 1;
+ boolean laneNodeCreated = false;
+ if(!stepPanel.getTitle().equals("Lane " + laneNumber)) {
+ laneNodeCreated = true;
+ ObjectNode laneNode = mapper.createObjectNode();
+ laneNode.put("lane_title", stepPanel.getTitle());
+ // save comment in same node in case it is set
+ if(stepPanel.getComment() != null && !stepPanel.getComment().equals("")) {
+ laneNode.put("lane_comment", stepPanel.getComment());
+ }
+ operationsNode.add(laneNode);
+ }
+
+ // save comment in case it's not already
+ if(!laneNodeCreated && stepPanel.getComment() != null && !stepPanel.getComment().equals("")) {
+ ObjectNode laneNode = mapper.createObjectNode();
+ laneNode.put("lane_comment", stepPanel.getComment());
+ operationsNode.add(laneNode);
+ }
+
List operations = stepPanel.getOperations();
for (Operation op : operations) {
ObjectNode operationNode = mapper.createObjectNode();
@@ -394,10 +461,25 @@ private String getStateAsJSON() throws IOException {
operationsNode.add(operationNode);
operationNode.putPOJO("parameters", op.getState());
operationNode.putPOJO("is_enabled", !op.isDisabled());
+ // "comment":null if empty
+ operationNode.put("comment", op.getComment());
}
stepsNode.add(operationsNode);
}
- return mapper.writeValueAsString(stepsNode);
+
+ /* maven performs a substitution at compile time in "/res/version.properties"
+ with the version from pom.xml and here it reads from this file
+ */
+ Properties properties = new Properties();
+ properties.load(RecipePanel.class.getResourceAsStream("/version.properties"));
+ String version = properties.getProperty("version");
+
+ versionNode.put("version", version);
+
+ rootNode.add(versionNode);
+ rootNode.add(stepsNode);
+
+ return mapper.writeValueAsString(rootNode);
}
private void save(File file) throws IOException {
@@ -587,6 +669,9 @@ private void saveFilterState() {
private void clear() {
for (int step = 0; step < this.operationSteps; step++) {
RecipeStepPanel stepPanel = (RecipeStepPanel) this.operationLines.getComponent(step);
+ int laneIndex = step + 1;
+ stepPanel.setTitle("Lane " + laneIndex);
+ stepPanel.clearComment();
stepPanel.clearOperations();
}
}
diff --git a/src/main/java/de/usd/cstchef/view/RecipeStepPanel.java b/src/main/java/de/usd/cstchef/view/RecipeStepPanel.java
index 89b79da..f479220 100644
--- a/src/main/java/de/usd/cstchef/view/RecipeStepPanel.java
+++ b/src/main/java/de/usd/cstchef/view/RecipeStepPanel.java
@@ -3,14 +3,24 @@
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
+import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.Box;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
@@ -27,6 +37,13 @@ public class RecipeStepPanel extends JPanel {
private JPanel operationsLine;
private GridBagConstraints addContraints;
private ChangeListener changeListener;
+ private JTextField contentTextField;
+
+ private String comment;
+ private JButton commentBtn;
+
+ private static ImageIcon commentIcon = new ImageIcon(Operation.class.getResource("/comment.png"));
+ private static ImageIcon noCommentIcon = new ImageIcon(Operation.class.getResource("/no_comment.png"));
public RecipeStepPanel(String title, ChangeListener changelistener) {
this.changeListener = changelistener;
@@ -41,12 +58,39 @@ public RecipeStepPanel(String title, ChangeListener changelistener) {
CompoundBorder border = new CompoundBorder(lineBorder, margin);
headerBox.setBorder(border);
- JTextField contentTextField = new JTextField();
+ contentTextField = new JTextField();
contentTextField.setBorder(null);
contentTextField.setBackground(new Color(0, 0, 0, 0));
contentTextField.setText(title);
+ contentTextField.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ String newTitle = JOptionPane.showInputDialog("Edit title:", getTitle());
+ contentTextField.setText(newTitle.length() <= 50 ? newTitle : getTitle());
+ setTitle(newTitle.length() <= 50 ? newTitle : getTitle()); // lane name should be leq 50 chars
+ }
+ });
headerBox.add(contentTextField);
+ JPanel panel = new JPanel();
+ panel.setBackground(new Color(0, 0, 0, 0)); // transparent
+
+ commentBtn = createIconButton(noCommentIcon);
+
+ commentBtn.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ commentBtn.setToolTipText(getComment());
+ String comment = JOptionPane.showInputDialog("Edit comment:", commentBtn.getToolTipText());
+ commentBtn.setToolTipText(comment);
+ setComment(comment);
+ ImageIcon newIcon = comment.isEmpty() ? RecipeStepPanel.noCommentIcon : RecipeStepPanel.commentIcon;
+ commentBtn.setIcon(newIcon);
+ }
+ });
+
+ panel.add(commentBtn);
+ headerBox.add(panel);
+
this.add(headerBox, BorderLayout.NORTH);
// body
@@ -124,4 +168,41 @@ public void clearOperations() {
operationsLine.repaint();
this.changeListener.stateChanged(new ChangeEvent(this));
}
+
+ public String getTitle() {
+ return contentTextField.getText();
+ }
+
+ public void setTitle(String title) {
+ contentTextField.setText(title);
+ }
+
+ private JButton createIconButton(ImageIcon icon) {
+ JButton btn = new JButton();
+ btn.setBorder(BorderFactory.createEmptyBorder());
+ btn.setIcon(icon);
+ btn.setContentAreaFilled(false);
+ btn.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ btn.setAlignmentX(Component.RIGHT_ALIGNMENT);
+
+ return btn;
+ }
+
+ public String getComment() {
+ return this.comment;
+ }
+
+ public void setComment(String comment) {
+ if(comment != null) {
+ this.comment = comment;
+ commentBtn.setIcon(RecipeStepPanel.commentIcon);
+ commentBtn.setToolTipText(comment);
+ }
+ }
+
+ public void clearComment() {
+ this.comment = "";
+ commentBtn.setToolTipText("");
+ commentBtn.setIcon(RecipeStepPanel.noCommentIcon);
+ }
}
diff --git a/src/main/java/de/usd/cstchef/view/RequestFilterDialog.java b/src/main/java/de/usd/cstchef/view/RequestFilterDialog.java
index 60caa44..e36c9e4 100644
--- a/src/main/java/de/usd/cstchef/view/RequestFilterDialog.java
+++ b/src/main/java/de/usd/cstchef/view/RequestFilterDialog.java
@@ -36,7 +36,7 @@ private RequestFilterDialog() {
JPanel labelPanel = new JPanel();
labelPanel.setLayout(new GridLayout(7, 0));
labelPanel.add(new JLabel(""));
- List labels = Arrays.asList("Proxy", "Repeater", "Scanner", "Intruder", "Extender");
+ List labels = Arrays.asList("Proxy", "Repeater", "Scanner", "Intruder", "Extender", "Sequencer");
for (String label : labels) {
labelPanel.add(new JLabel(label));
}
@@ -51,13 +51,11 @@ private RequestFilterDialog() {
private JPanel createPanel(BurpOperation operation) {
if (BurpUtils.getInstance().getFilterState().getFilterMask(operation).isEmpty()) {
BurpUtils.getInstance().getFilterState().getFilterMask(operation).put(new Filter(ToolType.PROXY, ToolType.PROXY.ordinal()), false);
- BurpUtils.getInstance().getFilterState().getFilterMask(operation).put(new Filter(ToolType.REPEATER, ToolType.REPEATER.ordinal()),
- false);
+ BurpUtils.getInstance().getFilterState().getFilterMask(operation).put(new Filter(ToolType.REPEATER, ToolType.REPEATER.ordinal()), false);
BurpUtils.getInstance().getFilterState().getFilterMask(operation).put(new Filter(ToolType.SCANNER, ToolType.SCANNER.ordinal()), false);
- BurpUtils.getInstance().getFilterState().getFilterMask(operation).put(new Filter(ToolType.INTRUDER, ToolType.INTRUDER.ordinal()),
- false);
- BurpUtils.getInstance().getFilterState().getFilterMask(operation).put(new Filter(ToolType.EXTENSIONS, ToolType.EXTENSIONS.ordinal()),
- false);
+ BurpUtils.getInstance().getFilterState().getFilterMask(operation).put(new Filter(ToolType.INTRUDER, ToolType.INTRUDER.ordinal()), false);
+ BurpUtils.getInstance().getFilterState().getFilterMask(operation).put(new Filter(ToolType.EXTENSIONS, ToolType.EXTENSIONS.ordinal()), false);
+ BurpUtils.getInstance().getFilterState().getFilterMask(operation).put(new Filter(ToolType.SEQUENCER, ToolType.SEQUENCER.ordinal()), false);
}
JPanel panel = new JPanel();
diff --git a/src/main/java/de/usd/cstchef/wrapper/GZIPOutputStreamWrapper.java b/src/main/java/de/usd/cstchef/wrapper/GZIPOutputStreamWrapper.java
new file mode 100644
index 0000000..de35b95
--- /dev/null
+++ b/src/main/java/de/usd/cstchef/wrapper/GZIPOutputStreamWrapper.java
@@ -0,0 +1,33 @@
+package de.usd.cstchef.wrapper;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.zip.GZIPOutputStream;
+
+public class GZIPOutputStreamWrapper extends GZIPOutputStream {
+
+ public GZIPOutputStreamWrapper(OutputStream out) throws IOException {
+ super(out);
+ //TODO Auto-generated constructor stub
+ }
+
+ public GZIPOutputStreamWrapper(OutputStream out, int size) throws IOException {
+ super(out, size);
+ //TODO Auto-generated constructor stub
+ }
+
+ public GZIPOutputStreamWrapper(OutputStream out, boolean syncFlush) throws IOException {
+ super(out, syncFlush);
+ //TODO Auto-generated constructor stub
+ }
+
+ public GZIPOutputStreamWrapper(OutputStream out, int size, boolean syncFlush) throws IOException {
+ super(out, size, syncFlush);
+ //TODO Auto-generated constructor stub
+ }
+
+ public void setCompressionLevel(int level) {
+ def.setLevel(level);
+ }
+
+}