diff --git a/src/main/java/ch/csnc/burp/CopyRequestResponseConfiguration.java b/src/main/java/ch/csnc/burp/CopyRequestResponseConfiguration.java index 8ffe3db..935e229 100644 --- a/src/main/java/ch/csnc/burp/CopyRequestResponseConfiguration.java +++ b/src/main/java/ch/csnc/burp/CopyRequestResponseConfiguration.java @@ -6,6 +6,7 @@ public class CopyRequestResponseConfiguration { private static final String CUT_TEXT_NBSP_KEY = "CopyRequestResponseCutTextNbsp"; private static final String COPY_FULL_FULL_OR_SELECTION_KEY = "CopyRequestResponseCopyFullFullOrSelectionHotKey"; private static final String COPY_FULL_HEADER_KEY = "CopyRequestResponseCopyFullHeaderHotKey"; + private static final String ENABLE_JSON_FORMATTING_KEY = "CopyRequestResponseEnableJSONFormatting"; public static String cutText() { var cutText = CopyRequestResponseExtension.api().persistence().preferences().getString(CUT_TEXT_KEY); @@ -62,6 +63,19 @@ public static void setCopyFullHeaderHotKey(String hotKey) { CopyRequestResponseExtension.api().persistence().preferences().setString(COPY_FULL_HEADER_KEY, hotKey); } + public static boolean enableJsonFormatting() { + var enableJson = CopyRequestResponseExtension.api().persistence().preferences().getBoolean(ENABLE_JSON_FORMATTING_KEY); + if (enableJson == null) { + enableJson = false; + } + setEnableJsonFormatting(enableJson); + return enableJson; + } + + public static void setEnableJsonFormatting(boolean enabled) { + CopyRequestResponseExtension.api().persistence().preferences().setBoolean(ENABLE_JSON_FORMATTING_KEY, enabled); + } + private CopyRequestResponseConfiguration() { // static class } diff --git a/src/main/java/ch/csnc/burp/CopyRequestResponseConfigurationDialog.java b/src/main/java/ch/csnc/burp/CopyRequestResponseConfigurationDialog.java index ba2c530..8663f45 100644 --- a/src/main/java/ch/csnc/burp/CopyRequestResponseConfigurationDialog.java +++ b/src/main/java/ch/csnc/burp/CopyRequestResponseConfigurationDialog.java @@ -51,6 +51,10 @@ public void keyReleased(KeyEvent e) { useNbspCheckbox.setSelected(CopyRequestResponseConfiguration.useNonBreakableSpace()); useNbspCheckbox.addActionListener(event -> CopyRequestResponseConfiguration.setUseNonBreakableSpace(useNbspCheckbox.isSelected())); + var enableJsonFormattingCheckbox = new JCheckBox("Enable pretty-printing of JSON data"); + enableJsonFormattingCheckbox.setSelected(CopyRequestResponseConfiguration.enableJsonFormatting()); + enableJsonFormattingCheckbox.addActionListener(event -> CopyRequestResponseConfiguration.setEnableJsonFormatting(enableJsonFormattingCheckbox.isSelected())); + var panel = new JPanel(); panel.setLayout(new MigLayout()); panel.add(cutTextLabel); @@ -60,6 +64,7 @@ public void keyReleased(KeyEvent e) { panel.add(copyFullFullOrSelectionHotKeyTextField, "grow, wrap"); panel.add(copyFullHeaderLabel); panel.add(copyFullHeaderTextField, "grow, wrap"); + panel.add(enableJsonFormattingCheckbox, "grow, wrap"); this.dialog = new JDialog(CopyRequestResponseExtension.api().userInterface().swingUtils().suiteFrame(), true); this.dialog.setLocationRelativeTo(CopyRequestResponseExtension.api().userInterface().swingUtils().suiteFrame()); diff --git a/src/main/java/ch/csnc/burp/CopyRequestResponseCopyActions.java b/src/main/java/ch/csnc/burp/CopyRequestResponseCopyActions.java index a09d27d..aa605f9 100644 --- a/src/main/java/ch/csnc/burp/CopyRequestResponseCopyActions.java +++ b/src/main/java/ch/csnc/burp/CopyRequestResponseCopyActions.java @@ -1,9 +1,13 @@ package ch.csnc.burp; import burp.api.montoya.http.handler.TimingData; +import burp.api.montoya.http.message.HttpMessage; import burp.api.montoya.http.message.HttpRequestResponse; import burp.api.montoya.http.message.responses.HttpResponse; import burp.api.montoya.ui.contextmenu.MessageEditorHttpRequestResponse; +import burp.api.montoya.utilities.json.JsonNode; +import burp.api.montoya.utilities.json.JsonParseException; + import java.awt.Toolkit; import java.awt.datatransfer.StringSelection; import java.time.LocalDateTime; @@ -16,15 +20,36 @@ public class CopyRequestResponseCopyActions { + public static String httpMessageBodyToJson(HttpMessage httpMessage) { + if (!CopyRequestResponseConfiguration.enableJsonFormatting()) { + return httpMessage.toString().strip(); + } + + String messageHeaders = httpMessage.toString().substring(0, httpMessage.bodyOffset()).strip(); + String messageBody = httpMessage.bodyToString(); + + try { + JsonNode jsonNode = JsonNode.jsonNode(messageBody); + messageBody = jsonNode.toJsonString(); + } catch (JsonParseException | IllegalArgumentException e) { + // ignore if JSON cannot be parsed or the input is empty + } + + return "%s\n\n%s".formatted( + messageHeaders, + messageBody + ).strip(); + } + public static void copyFullFull(List requestResponses) { var text = requestResponses .stream() .map(requestResponse -> "%s\n\n%s".formatted( - requestResponse.request().toString().strip(), + httpMessageBodyToJson(requestResponse.request()), Optional.ofNullable(requestResponse.response()) - .map(HttpResponse::toString) + .map(CopyRequestResponseCopyActions::httpMessageBodyToJson) .map(String::strip) .orElse(""))) .collect(Collectors.joining("\n\n")); @@ -37,7 +62,7 @@ public static void copyFullHeader(List requestResponses) { requestResponses .stream() .map(requestResponse -> { - var requestString = requestResponse.request().toString().strip(); + var requestString = httpMessageBodyToJson(requestResponse.request()); var responseString = ""; if (requestResponse.hasResponse()) { @@ -56,7 +81,7 @@ public static void copyFullHeader(List requestResponses) { public static void copyFullHeaderPlusSelectedData(MessageEditorHttpRequestResponse editor) { var requestResponse = editor.requestResponse(); - var requestString = requestResponse.request().toString().strip(); + var requestString = httpMessageBodyToJson(requestResponse.request()); Supplier responseStringSupplier = () -> { if (!requestResponse.hasResponse()) {