From a888df0b4eedd9730f33b1b22189f0c3fcf4ec9f Mon Sep 17 00:00:00 2001 From: ddwightx Date: Sun, 18 Aug 2024 15:39:58 -0400 Subject: [PATCH 1/2] Quickly add Whens and Thens via Match & Replace dialog. Added support for Workspaces. --- .github/workflows/gradle.yml | 2 +- Readme.md | 6 +- extension/build.gradle | 4 +- .../src/main/java/burp/BurpExtender.java | 72 +++-- .../synfron/reshaper/burp/core/BurpTool.java | 20 +- .../reshaper/burp/core/HttpConnector.java | 67 +++-- .../core/SessionHandlingActionExchange.java | 76 ++++++ .../burp/core/WebSocketConnector.java | 44 +++- .../burp/core/{Tab.java => WorkspaceTab.java} | 8 +- .../reshaper/burp/core/events/Event.java | 21 +- .../burp/core/events/message/LogMessage.java | 21 ++ .../burp/core/events/message/MessageType.java | 3 +- .../burp/core/messages/EventInfo.java | 20 +- .../burp/core/messages/HttpEventInfo.java | 17 +- .../core/messages/WebSocketEventInfo.java | 5 +- .../entities/http/HttpRequestMessage.java | 13 +- .../entities/http/HttpResponseMessage.java | 13 +- .../burp/core/rules/IRuleOperation.java | 3 +- .../reshaper/burp/core/rules/Rule.java | 16 +- .../reshaper/burp/core/rules/RulesEngine.java | 4 +- .../core/rules/diagnostics/Diagnostics.java | 9 +- .../rules/thens/ThenBuildHttpMessage.java | 4 +- .../burp/core/rules/thens/ThenExtract.java | 2 +- .../rules/thens/ThenParseHttpMessage.java | 4 +- .../burp/core/rules/thens/ThenPrompt.java | 26 +- .../burp/core/rules/thens/ThenRepeat.java | 2 +- .../burp/core/rules/thens/ThenRunRules.java | 5 +- .../core/rules/thens/ThenSendRequest.java | 2 +- .../thens/entities/script/ConsoleObj.java | 8 +- .../thens/entities/script/Dispatcher.java | 9 +- .../thens/entities/script/ReshaperObj.java | 45 ++-- .../entities/script/XmlHttpRequestObj.java | 12 +- .../burp/core/rules/whens/WhenRepeat.java | 2 +- .../burp/core/settings/ExportSettings.java | 15 -- .../burp/core/settings/GeneralSettings.java | 17 +- .../burp/core/settings/SettingsManager.java | 75 +++--- .../burp/core/settings/Workspace.java | 85 ++++++ .../core/settings/WorkspaceDataExport.java | 41 +++ .../burp/core/settings/WorkspaceExport.java | 34 +++ .../burp/core/settings/Workspaces.java | 102 +++++++ .../burp/core/settings/WorkspacesExport.java | 34 +++ .../synfron/reshaper/burp/core/utils/Log.java | 45 ++-- .../burp/core/vars/GlobalVariables.java | 8 - .../reshaper/burp/core/vars/VariableTag.java | 6 +- .../vars/getters/AccessorVariableGetter.java | 13 +- .../getters/CustomListVariableGetter.java | 2 +- .../vars/getters/CustomVariableGetter.java | 3 +- .../burp/ui/components/IFormComponent.java | 49 +++- .../burp/ui/components/LogsComponent.java | 33 ++- .../burp/ui/components/ReshaperComponent.java | 245 ++++++++++++++--- .../ui/components/rules/RuleComponent.java | 47 +++- .../components/rules/RuleListComponent.java | 24 +- .../rules/RuleOperationComponent.java | 14 +- .../rules/RuleOperationListComponent.java | 35 ++- .../rules/thens/ThenListComponent.java | 16 +- .../rules/thens/ThenRunRulesComponent.java | 5 +- .../MessageValueSetterComponent.java | 7 + .../generate/BytesGeneratorComponent.java | 2 + .../thens/generate/GeneratorComponent.java | 7 + .../MessageValueGetterComponent.java | 7 + .../transform/Base64TransformerComponent.java | 2 + .../thens/transform/TransformerComponent.java | 7 + .../whens/WhenEventDirectionComponent.java | 14 + .../rules/whens/WhenListComponent.java | 15 +- .../MatchAndReplaceWizardOptionPane.java | 155 +++++++++++ .../AnnotationVariableTagWizardComponent.java | 7 + .../CookieJarVariableTagWizardComponent.java | 7 + .../CustomVariableTagWizardComponent.java | 7 + .../vars/FileVariableTagWizardComponent.java | 9 +- .../GeneratorVariableTagWizardComponent.java | 7 + .../vars/MacroVariableTagWizardComponent.java | 7 + .../MessageVariableTagWizardComponent.java | 7 + .../SpecialVariableTagWizardComponent.java | 9 +- .../vars/VariableTagWizardOptionPane.java | 11 +- .../wizard/whens/WhenWizardItemComponent.java | 6 + .../wizard/whens/WhenWizardOptionPane.java | 18 +- .../settings/HideItemsOptionPane.java | 17 +- .../settings/SettingsTabComponent.java | 125 ++++++--- .../ui/components/shared/PromptTextField.java | 15 ++ .../burp/ui/components/shared/TextPrompt.java | 248 ++++++++++++++++++ .../ui/components/vars/VariableComponent.java | 13 +- .../vars/VariableListComponent.java | 22 +- .../workspaces/IWorkspaceDependent.java | 10 + .../IWorkspaceDependentComponent.java | 20 ++ .../components/workspaces/IWorkspaceHost.java | 31 +++ .../workspaces/WorkspaceComponent.java | 88 +++++++ .../workspaces/WorkspaceNameOptionPane.java | 77 ++++++ .../burp/ui/models/rules/RuleModel.java | 13 + .../models/rules/whens/WhenFromToolModel.java | 2 +- .../MatchAndReplaceWizardModel.java | 239 +++++++++++++++++ .../rules/wizard/matchreplace/MatchType.java | 53 ++++ .../GlobalListVariableTagWizardModel.java | 10 +- .../vars/GlobalVariableTagWizardModel.java | 10 +- .../rules/wizard/whens/WhenWizardModel.java | 14 +- .../ui/models/settings/HideItemsModel.java | 8 +- .../burp/ui/models/vars/VariableModel.java | 20 +- .../models/workspaces/WorkspaceNameModel.java | 100 +++++++ .../ui/utils/ComponentVisibilityManager.java | 4 + .../burp/ui/utils/ContextMenuHandler.java | 7 +- .../reshaper/burp/ui/utils/ModalPrompter.java | 7 +- .../synfron/reshaper/burp/runner/Api.java | 8 +- .../synfron/reshaper/burp/runner/Window.java | 3 +- 102 files changed, 2526 insertions(+), 462 deletions(-) create mode 100644 extension/src/main/java/synfron/reshaper/burp/core/SessionHandlingActionExchange.java rename extension/src/main/java/synfron/reshaper/burp/core/{Tab.java => WorkspaceTab.java} (68%) create mode 100644 extension/src/main/java/synfron/reshaper/burp/core/events/message/LogMessage.java delete mode 100644 extension/src/main/java/synfron/reshaper/burp/core/settings/ExportSettings.java create mode 100644 extension/src/main/java/synfron/reshaper/burp/core/settings/Workspace.java create mode 100644 extension/src/main/java/synfron/reshaper/burp/core/settings/WorkspaceDataExport.java create mode 100644 extension/src/main/java/synfron/reshaper/burp/core/settings/WorkspaceExport.java create mode 100644 extension/src/main/java/synfron/reshaper/burp/core/settings/Workspaces.java create mode 100644 extension/src/main/java/synfron/reshaper/burp/core/settings/WorkspacesExport.java create mode 100644 extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/matchreplace/MatchAndReplaceWizardOptionPane.java create mode 100644 extension/src/main/java/synfron/reshaper/burp/ui/components/shared/PromptTextField.java create mode 100644 extension/src/main/java/synfron/reshaper/burp/ui/components/shared/TextPrompt.java create mode 100644 extension/src/main/java/synfron/reshaper/burp/ui/components/workspaces/IWorkspaceDependent.java create mode 100644 extension/src/main/java/synfron/reshaper/burp/ui/components/workspaces/IWorkspaceDependentComponent.java create mode 100644 extension/src/main/java/synfron/reshaper/burp/ui/components/workspaces/IWorkspaceHost.java create mode 100644 extension/src/main/java/synfron/reshaper/burp/ui/components/workspaces/WorkspaceComponent.java create mode 100644 extension/src/main/java/synfron/reshaper/burp/ui/components/workspaces/WorkspaceNameOptionPane.java create mode 100644 extension/src/main/java/synfron/reshaper/burp/ui/models/rules/wizard/matchreplace/MatchAndReplaceWizardModel.java create mode 100644 extension/src/main/java/synfron/reshaper/burp/ui/models/rules/wizard/matchreplace/MatchType.java create mode 100644 extension/src/main/java/synfron/reshaper/burp/ui/models/workspaces/WorkspaceNameModel.java diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 339ad3a..76e7cc7 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -9,7 +9,7 @@ name: Java CI with Gradle on: push: - branches: [ "release" ] + branches: [ "release", "post_release" ] pull_request: branches: [ "release" ] diff --git a/Readme.md b/Readme.md index 2173025..e754a09 100644 --- a/Readme.md +++ b/Readme.md @@ -2,7 +2,7 @@ Extension for Burp Suite to trigger actions and reshape HTTP request/response and WebSocket traffic using configurable Rules -![Screenshot](https://user-images.githubusercontent.com/48854453/206939994-3cf7beb7-61bb-4f12-8b7b-10239e4d0281.png) +![Screenshot](https://github.com/user-attachments/assets/3e07dbe2-a71a-4a8a-8f75-206ce7bb9254) [Example Usage](https://synfron.github.io/ReshaperForBurp/Examples.html) @@ -104,6 +104,10 @@ Share values across different Rules while processing the same event or all event [More](https://synfron.github.io/ReshaperForBurp/Variables.html) +## Additional Features + +[Additional Features](https://synfron.github.io/ReshaperForBurp/Features.html) + ## Development ### Build JAR with IntelliJ diff --git a/extension/build.gradle b/extension/build.gradle index bf35276..796fc02 100644 --- a/extension/build.gradle +++ b/extension/build.gradle @@ -4,7 +4,7 @@ plugins { group 'com.synfron.reshaper.burp' archivesBaseName = 'reshaper-for-burp' -version '2.4.0' +version '2.5.0' targetCompatibility = '21' sourceCompatibility = '21' @@ -41,7 +41,7 @@ dependencies { compileOnly ( 'org.projectlombok:lombok:1.18.30', - 'net.portswigger.burp.extensions:montoya-api:2023.10' + 'net.portswigger.burp.extensions:montoya-api:2023.12.1' ) annotationProcessor 'org.projectlombok:lombok:1.18.30' diff --git a/extension/src/main/java/burp/BurpExtender.java b/extension/src/main/java/burp/BurpExtender.java index 9b2732c..dff5c3a 100644 --- a/extension/src/main/java/burp/BurpExtender.java +++ b/extension/src/main/java/burp/BurpExtender.java @@ -3,80 +3,68 @@ import burp.api.montoya.BurpExtension; import burp.api.montoya.MontoyaApi; import burp.api.montoya.extension.ExtensionUnloadingHandler; -import burp.api.montoya.ui.editor.EditorOptions; -import burp.api.montoya.ui.editor.WebSocketMessageEditor; import lombok.Getter; import synfron.reshaper.burp.core.HttpConnector; -import synfron.reshaper.burp.core.ProtocolType; +import synfron.reshaper.burp.core.SessionHandlingActionExchange; import synfron.reshaper.burp.core.WebSocketConnector; -import synfron.reshaper.burp.core.events.MessageEvent; -import synfron.reshaper.burp.core.rules.RulesRegistry; -import synfron.reshaper.burp.core.settings.GeneralSettings; -import synfron.reshaper.burp.core.settings.SettingsManager; +import synfron.reshaper.burp.core.events.CollectionChangedAction; +import synfron.reshaper.burp.core.events.CollectionChangedArgs; +import synfron.reshaper.burp.core.events.IEventListener; +import synfron.reshaper.burp.core.settings.Workspace; +import synfron.reshaper.burp.core.settings.Workspaces; import synfron.reshaper.burp.core.utils.Log; import synfron.reshaper.burp.ui.components.ReshaperComponent; import synfron.reshaper.burp.ui.utils.ContextMenuHandler; -import synfron.reshaper.burp.ui.utils.UiMessageHandler; public class BurpExtender implements BurpExtension, ExtensionUnloadingHandler { @Getter private static MontoyaApi api; - @Getter - private final static HttpConnector httpConnector = new HttpConnector(); - @Getter - private final static WebSocketConnector webSocketConnector = new WebSocketConnector(); - @Getter - private static WebSocketMessageEditor logTextEditor; - @Getter - private static final GeneralSettings generalSettings = new GeneralSettings(); - @Getter - private static final MessageEvent messageEvent = new MessageEvent(); - private static final UiMessageHandler uiMessageHandler = new UiMessageHandler(messageEvent); private static final ContextMenuHandler contextMenuHandler = new ContextMenuHandler(); + private IEventListener onWorkspacesChangedListener = this::onWorkspacesChanged; @Override public void initialize(MontoyaApi api) { try { BurpExtender.api = api; - logTextEditor = api.userInterface().createWebSocketMessageEditor(EditorOptions.READ_ONLY); - - SettingsManager.loadSettings(); - - httpConnector.init(); - api.extension().setName("Reshaper"); - api.userInterface().registerSuiteTab("Reshaper", new ReshaperComponent()); + api.userInterface().registerSuiteTab("Reshaper", new ReshaperComponent(Workspaces.get())); + Workspaces.get().getCollectionChangedEvent().add(onWorkspacesChangedListener); + Workspaces.get().getWorkspaces().forEach(this::registerWorkspace); + api.http().registerSessionHandlingAction(new SessionHandlingActionExchange(Workspaces.get())); - api.proxy().registerRequestHandler(httpConnector); - api.proxy().registerResponseHandler(httpConnector); - api.proxy().registerWebSocketCreationHandler(webSocketConnector); - api.http().registerHttpHandler(httpConnector); - api.http().registerSessionHandlingAction(httpConnector); api.extension().registerUnloadingHandler(this); - api.websockets().registerWebSocketCreatedHandler(webSocketConnector); api.userInterface().registerContextMenuItemsProvider(contextMenuHandler); - Log.get().withMessage("Reshaper started").log(); + Log.getSystem().withMessage("Reshaper started").log(); } catch (Exception e) { - Log.get().withMessage("Reshaper startup failed").withException(e).logErr(); + Log.getSystem().withMessage("Reshaper startup failed").withException(e).logErr(); throw e; } } + private void registerWorkspace(Workspace workspace) { + HttpConnector httpConnector = workspace.getHttpConnector(); + WebSocketConnector webSocketConnector = workspace.getWebSocketConnector(); + + workspace.load(); + api.proxy().registerRequestHandler(httpConnector); + api.proxy().registerResponseHandler(httpConnector); + api.proxy().registerWebSocketCreationHandler(webSocketConnector); + api.http().registerHttpHandler(httpConnector); + api.websockets().registerWebSocketCreatedHandler(webSocketConnector); + } + @Override public void extensionUnloaded() { - httpConnector.unload(); - SettingsManager.saveSettings(); + Workspaces.get().unload(); Log.get().withMessage("Reshaper unloaded").log(); } - public static RulesRegistry getRulesRegistry(ProtocolType protocolType) { - return switch (protocolType) { - case Http -> httpConnector.getRulesEngine().getRulesRegistry(); - case WebSocket -> webSocketConnector.getRulesEngine().getRulesRegistry(); - case Any -> throw new IllegalArgumentException(protocolType + " not valid in this context"); - }; + private void onWorkspacesChanged(CollectionChangedArgs collectionChangedArgs) { + if (collectionChangedArgs.getAction() == CollectionChangedAction.Add) { + registerWorkspace((Workspace) collectionChangedArgs.getItem()); + } } } diff --git a/extension/src/main/java/synfron/reshaper/burp/core/BurpTool.java b/extension/src/main/java/synfron/reshaper/burp/core/BurpTool.java index 6b06ad7..9a40238 100644 --- a/extension/src/main/java/synfron/reshaper/burp/core/BurpTool.java +++ b/extension/src/main/java/synfron/reshaper/burp/core/BurpTool.java @@ -8,10 +8,20 @@ public enum BurpTool { Intruder, Target, Scanner, - Extender, + Extender("Extensions"), Session, WebSockets; + private final String displayName; + + BurpTool() { + displayName = name(); + } + + BurpTool(String displayName) { + this.displayName = displayName; + } + public static BurpTool from(ToolType toolType) { return switch (toolType) { case PROXY -> Proxy; @@ -20,7 +30,13 @@ public static BurpTool from(ToolType toolType) { case INTRUDER -> Intruder; case REPEATER -> Repeater; case EXTENSIONS -> Extender; - case SUITE, SEQUENCER, RECORDED_LOGIN_REPLAYER, COMPARER, DECODER, LOGGER -> null; + case SUITE, SEQUENCER, RECORDED_LOGIN_REPLAYER, COMPARER, DECODER, LOGGER, ORGANIZER -> null; }; } + + + @Override + public String toString() { + return displayName; + } } diff --git a/extension/src/main/java/synfron/reshaper/burp/core/HttpConnector.java b/extension/src/main/java/synfron/reshaper/burp/core/HttpConnector.java index f68c554..17aae6d 100644 --- a/extension/src/main/java/synfron/reshaper/burp/core/HttpConnector.java +++ b/extension/src/main/java/synfron/reshaper/burp/core/HttpConnector.java @@ -1,6 +1,5 @@ package synfron.reshaper.burp.core; -import burp.BurpExtender; import burp.api.montoya.core.Annotations; import burp.api.montoya.core.ByteArray; import burp.api.montoya.core.ToolType; @@ -20,6 +19,8 @@ import synfron.reshaper.burp.core.messages.HttpEventInfo; import synfron.reshaper.burp.core.messages.entities.http.HttpRequestMessage; import synfron.reshaper.burp.core.rules.RulesEngine; +import synfron.reshaper.burp.core.settings.Workspace; +import synfron.reshaper.burp.core.settings.Workspaces; import synfron.reshaper.burp.core.utils.CollectionUtils; import synfron.reshaper.burp.core.utils.Log; import synfron.reshaper.burp.core.utils.ObjectUtils; @@ -43,7 +44,8 @@ public class HttpConnector implements HttpHandler { @Getter - private final RulesEngine rulesEngine = new RulesEngine(); + private RulesEngine rulesEngine = new RulesEngine(); + private Workspace workspace; private ServerSocket serverSocket; private final Map continuationMap = ExpiringMap.builder() .expiration(30, TimeUnit.SECONDS).build(); @@ -55,6 +57,11 @@ public class HttpConnector implements private final String interceptAndDropWarning = "Sanity Check - Warning: Cannot both intercept and drop the same message."; private boolean activated = true; + public HttpConnector(Workspace workspace) { + + this.workspace = workspace; + } + public void init() { activated = true; createServer(); @@ -63,6 +70,8 @@ public void init() { public void unload() { activated = false; closeServer(); + workspace = null; + rulesEngine = null; } private void closeServer() { @@ -94,7 +103,7 @@ private void processServerConnection(Socket socket) { bufferedOutputStream.flush(); } catch (Exception e) { if (activated) { - Log.get().withMessage("Event processing failed").withException(e).logErr(); + Log.get(workspace).withMessage("Event processing failed").withException(e).logErr(); } } finally { close(socket); @@ -113,7 +122,7 @@ private void createServer() { processServerConnection(socket); } catch (Exception e) { if (activated) { - Log.get().withMessage("Server accept new connection failed").withException(e).logErr(); + Log.get(workspace).withMessage("Server accept new connection failed").withException(e).logErr(); } } } @@ -130,7 +139,7 @@ private void close(Closeable closeable) { } } catch (Exception ex) { if (activated) { - Log.get().withMessage("Failed closing stream").withException(ex).logErr(); + Log.get(workspace).withMessage("Failed closing stream").withException(ex).logErr(); } } } @@ -158,7 +167,7 @@ private HttpEventInfo asEventInfo(boolean messageIsRequest, BurpTool burpTool, S } Variables sessionVariables = !messageIsRequest ? Variables.defaultVariables(sessionVariableMap.get(messageId)) : new Variables(); HttpEventInfo eventInfo = new HttpEventInfo( - messageIsRequest ? HttpDataDirection.Request : HttpDataDirection.Response, + workspace, messageIsRequest ? HttpDataDirection.Request : HttpDataDirection.Response, burpTool, messageId, httpRequest, @@ -168,7 +177,7 @@ private HttpEventInfo asEventInfo(boolean messageIsRequest, BurpTool burpTool, S sourceIpAddress != null ? sourceIpAddress.getHostAddress() : "burp::", sessionVariables ); - eventInfo.getDiagnostics().setEventEnabled(BurpExtender.getGeneralSettings().isEnableEventDiagnostics()); + eventInfo.getDiagnostics().setEventEnabled(workspace.getGeneralSettings().isEnableEventDiagnostics()); return eventInfo; } @@ -181,6 +190,7 @@ private String getReshaperId(String header) { private EventResult processEvent(boolean isRequest, HttpEventInfo eventInfo, boolean isIntercept) { EventResult eventResult = new EventResult(eventInfo); + Workspaces.get().setCurrentWorkspace(workspace); try { rulesEngine.run(eventInfo); storeSessionVariables(eventInfo); @@ -206,10 +216,10 @@ private EventResult processEvent(boolean isRequest, HttpEventInfo eventInfo, boo } } } catch (Exception e) { - Log.get().withMessage("Critical Error").withException(e).logErr(); + Log.get(workspace).withMessage("Critical Error").withException(e).logErr(); } finally { if (eventInfo.getDiagnostics().hasLogs()) { - Log.get().withMessage(eventInfo.getDiagnostics().getLogs()).logRaw(); + Log.get(workspace).withMessage(eventInfo.getDiagnostics().getLogs()).logRaw(); } } return eventResult; @@ -225,19 +235,19 @@ private void storeSessionVariables(HttpEventInfo eventInfo) { } private void sanityCheck(HttpEventInfo eventInfo) { - if (BurpExtender.getGeneralSettings().isEnableSanityCheckWarnings()) { + if (workspace.getGeneralSettings().isEnableSanityCheckWarnings()) { if (eventInfo.isRequestChanged() && eventInfo.getDataDirection() == HttpDataDirection.Response) { - Log.get().withMessage(String.format(dataDirectionWarning, "request", "Response")).log(); + Log.get(workspace).withMessage(String.format(dataDirectionWarning, "request", "Response")).log(); } if (eventInfo.isResponseChanged() && eventInfo.getDataDirection() == HttpDataDirection.Request) { - Log.get().withMessage(String.format(dataDirectionWarning, "response", "Request")).log(); + Log.get(workspace).withMessage(String.format(dataDirectionWarning, "response", "Request")).log(); } if (eventInfo.getDefaultInterceptResponse() == InterceptResponse.Intercept) { if (eventInfo.isShouldDrop()) { - Log.get().withMessage(interceptAndDropWarning).log(); + Log.get(workspace).withMessage(interceptAndDropWarning).log(); } if (eventInfo.getBurpTool() != BurpTool.Proxy) { - Log.get().withMessage(interceptWarning).log(); + Log.get(workspace).withMessage(interceptWarning).log(); } } } @@ -266,12 +276,13 @@ private void sendToSelf(HttpEventInfo eventInfo) { private BurpTool getBurpToolIfEnabled(ToolType toolType) { BurpTool burpTool = BurpTool.from(toolType); - return burpTool != null && BurpExtender.getGeneralSettings().isCapture(burpTool) ? burpTool : null; + return burpTool != null && workspace.getGeneralSettings().isCapture(burpTool) ? burpTool : null; } @Override public ProxyRequestReceivedAction handleRequestReceived(InterceptedRequest interceptedRequest) { - if (BurpExtender.getGeneralSettings().isCapture(BurpTool.Proxy)) { + if (workspace == null) return ProxyRequestReceivedAction.continueWith(interceptedRequest); + if (workspace.getGeneralSettings().isCapture(BurpTool.Proxy)) { HttpEventInfo eventInfo = asEventInfo(true, BurpTool.Proxy, getMessageId(BurpTool.Proxy, interceptedRequest.messageId()), interceptedRequest, null, interceptedRequest.annotations(), interceptedRequest.listenerInterface(), interceptedRequest.sourceIpAddress()); return processEvent(true, eventInfo, true).asProxyRequestAction(); } @@ -282,6 +293,7 @@ public ProxyRequestReceivedAction handleRequestReceived(InterceptedRequest inter @Override public RequestToBeSentAction handleHttpRequestToBeSent(HttpRequestToBeSent requestToBeSent) { + if (workspace == null) return RequestToBeSentAction.continueWith(requestToBeSent); if (!requestToBeSent.toolSource().isFromTool(ToolType.PROXY)) { BurpTool burpTool = getBurpToolIfEnabled(requestToBeSent.toolSource().toolType()); if (burpTool != null && burpTool != BurpTool.Proxy) { @@ -295,6 +307,7 @@ public RequestToBeSentAction handleHttpRequestToBeSent(HttpRequestToBeSent reque @Override public ActionResult performAction(SessionHandlingActionData actionData) { + if (workspace == null || !workspace.getGeneralSettings().isCapture(BurpTool.Session)) return ActionResult.actionResult(actionData.request()); HttpEventInfo eventInfo = asEventInfo(true, BurpTool.Session, null, actionData.request(), null, actionData.annotations()); eventInfo.setMacros(CollectionUtils.defaultIfNull(actionData.macroRequestResponses())); processEvent(true, eventInfo, false); @@ -303,12 +316,13 @@ public ActionResult performAction(SessionHandlingActionData actionData) { @Override public ProxyRequestToBeSentAction handleRequestToBeSent(InterceptedRequest interceptedRequest) { - return ProxyRequestToBeSentAction.continueWith(interceptedRequest); + return ProxyRequestToBeSentAction.continueWith(interceptedRequest, interceptedRequest.annotations()); } @Override public ProxyResponseReceivedAction handleResponseReceived(InterceptedResponse interceptedResponse) { - if (BurpExtender.getGeneralSettings().isCapture(BurpTool.Proxy)) { + if (workspace == null) return ProxyResponseReceivedAction.continueWith(interceptedResponse); + if (workspace.getGeneralSettings().isCapture(BurpTool.Proxy)) { HttpEventInfo eventInfo = asEventInfo(false, BurpTool.Proxy, getMessageId(BurpTool.Proxy, interceptedResponse.messageId()), interceptedResponse.initiatingRequest(), interceptedResponse, interceptedResponse.annotations(), interceptedResponse.listenerInterface(), null); return processEvent(false, eventInfo, true).asProxyResponseAction(); } @@ -319,6 +333,7 @@ public ProxyResponseReceivedAction handleResponseReceived(InterceptedResponse in @Override public ResponseReceivedAction handleHttpResponseReceived(HttpResponseReceived responseReceived) { + if (workspace == null) return ResponseReceivedAction.continueWith(responseReceived); if (responseReceived.toolSource().toolType() != ToolType.PROXY) { BurpTool burpTool = getBurpToolIfEnabled(responseReceived.toolSource().toolType()); if (burpTool != null && burpTool != BurpTool.Proxy) { @@ -358,25 +373,29 @@ public HttpRequest getRequest() { return eventInfo.asHttpRequest(); } + public Annotations getAnnotation() { + return eventInfo.getAnnotations(); + } + public HttpResponse getResponse() { return eventInfo.asHttpResponse(); } public ProxyRequestReceivedAction asProxyRequestAction() { return switch (interceptResponse) { - case UserDefined -> ProxyRequestReceivedAction.continueWith(getRequest()); + case UserDefined -> ProxyRequestReceivedAction.continueWith(getRequest(), getAnnotation()); case Drop -> ProxyRequestReceivedAction.drop(); - case Intercept -> ProxyRequestReceivedAction.intercept(getRequest()); - case Disable -> ProxyRequestReceivedAction.doNotIntercept(getRequest()); + case Intercept -> ProxyRequestReceivedAction.intercept(getRequest(), getAnnotation()); + case Disable -> ProxyRequestReceivedAction.doNotIntercept(getRequest(), getAnnotation()); }; } public ProxyResponseReceivedAction asProxyResponseAction() { return switch (interceptResponse) { - case UserDefined -> ProxyResponseReceivedAction.continueWith(getResponse()); + case UserDefined -> ProxyResponseReceivedAction.continueWith(getResponse(), getAnnotation()); case Drop -> ProxyResponseReceivedAction.drop(); - case Intercept -> ProxyResponseReceivedAction.intercept(getResponse()); - case Disable -> ProxyResponseReceivedAction.doNotIntercept(getResponse()); + case Intercept -> ProxyResponseReceivedAction.intercept(getResponse(), getAnnotation()); + case Disable -> ProxyResponseReceivedAction.doNotIntercept(getResponse(), getAnnotation()); }; } } diff --git a/extension/src/main/java/synfron/reshaper/burp/core/SessionHandlingActionExchange.java b/extension/src/main/java/synfron/reshaper/burp/core/SessionHandlingActionExchange.java new file mode 100644 index 0000000..2ce287d --- /dev/null +++ b/extension/src/main/java/synfron/reshaper/burp/core/SessionHandlingActionExchange.java @@ -0,0 +1,76 @@ +package synfron.reshaper.burp.core; + +import burp.api.montoya.core.Annotations; +import burp.api.montoya.http.message.HttpRequestResponse; +import burp.api.montoya.http.message.requests.HttpRequest; +import burp.api.montoya.http.sessions.ActionResult; +import burp.api.montoya.http.sessions.SessionHandlingAction; +import burp.api.montoya.http.sessions.SessionHandlingActionData; +import lombok.Setter; +import synfron.reshaper.burp.core.settings.Workspace; +import synfron.reshaper.burp.core.settings.Workspaces; +import synfron.reshaper.burp.core.utils.Log; + +import java.util.List; + +public class SessionHandlingActionExchange implements SessionHandlingAction { + + private final Workspaces workspaces; + + public SessionHandlingActionExchange(Workspaces workspaces) { + this.workspaces = workspaces; + } + + @Override + public String name() { + return "Reshaper"; + } + + @Override + public ActionResult performAction(SessionHandlingActionData sessionHandlingActionData) { + TransientSessionHandlingActionData actionData = new TransientSessionHandlingActionData(sessionHandlingActionData); + for (Workspace workspace : workspaces.getWorkspaces()) { + try { + ActionResult actionResult = workspace.getHttpConnector().performAction(actionData); + if (actionResult.request() != null) { + actionData.setHttpRequest(actionResult.request()); + } + if (actionResult.annotations() != null) { + actionData.setAnnotations(actionResult.annotations()); + } + } catch (Exception e) { + Log.get(workspace).withMessage("Failed processing Session event").withException(e).logErr(); + } + } + return ActionResult.actionResult(actionData.httpRequest); + } + + @Setter + private static class TransientSessionHandlingActionData implements SessionHandlingActionData { + + private HttpRequest httpRequest; + private List macroRequestResponses; + private Annotations annotations; + + public TransientSessionHandlingActionData(SessionHandlingActionData sessionHandlingActionData) { + httpRequest = sessionHandlingActionData.request(); + macroRequestResponses = sessionHandlingActionData.macroRequestResponses(); + annotations = sessionHandlingActionData.annotations(); + } + + @Override + public HttpRequest request() { + return httpRequest; + } + + @Override + public List macroRequestResponses() { + return macroRequestResponses; + } + + @Override + public Annotations annotations() { + return annotations; + } + } +} diff --git a/extension/src/main/java/synfron/reshaper/burp/core/WebSocketConnector.java b/extension/src/main/java/synfron/reshaper/burp/core/WebSocketConnector.java index 37e4c84..aeb1902 100644 --- a/extension/src/main/java/synfron/reshaper/burp/core/WebSocketConnector.java +++ b/extension/src/main/java/synfron/reshaper/burp/core/WebSocketConnector.java @@ -1,6 +1,5 @@ package synfron.reshaper.burp.core; -import burp.BurpExtender; import burp.api.montoya.core.Annotations; import burp.api.montoya.core.ByteArray; import burp.api.montoya.core.ToolType; @@ -14,6 +13,8 @@ import synfron.reshaper.burp.core.messages.WebSocketMessageSender; import synfron.reshaper.burp.core.messages.WebSocketMessageType; import synfron.reshaper.burp.core.rules.RulesEngine; +import synfron.reshaper.burp.core.settings.Workspace; +import synfron.reshaper.burp.core.settings.Workspaces; import synfron.reshaper.burp.core.utils.Log; import synfron.reshaper.burp.core.vars.Variables; @@ -21,30 +22,42 @@ public class WebSocketConnector implements ProxyWebSocketCreationHandler, WebSocketCreatedHandler { @Getter - private final RulesEngine rulesEngine = new RulesEngine(); + private RulesEngine rulesEngine = new RulesEngine(); + private Workspace workspace; + + public WebSocketConnector(Workspace workspace) { + this.workspace = workspace; + } private BurpTool getBurpToolIfEnabled(ToolType toolType) { BurpTool burpTool = BurpTool.from(toolType); - return burpTool != null && BurpExtender.getGeneralSettings().isCapture(burpTool) ? burpTool : null; + return burpTool != null && workspace.getGeneralSettings().isCapture(burpTool) ? burpTool : null; } @Override public void handleWebSocketCreation(ProxyWebSocketCreation webSocketCreation) { - if (BurpExtender.getGeneralSettings().isCapture(BurpTool.Proxy) && BurpExtender.getGeneralSettings().isCapture(BurpTool.WebSockets)) { + if (workspace == null) return; + if (workspace.getGeneralSettings().isCapture(BurpTool.Proxy) && workspace.getGeneralSettings().isCapture(BurpTool.WebSockets)) { webSocketCreation.proxyWebSocket().registerProxyMessageHandler(new WebSocketMessageConnector(BurpTool.Proxy, webSocketCreation.proxyWebSocket(), webSocketCreation.upgradeRequest())); } } @Override public void handleWebSocketCreated(WebSocketCreated webSocketCreated) { + if (workspace == null) return; if (!webSocketCreated.toolSource().isFromTool(ToolType.PROXY)) { BurpTool burpTool = getBurpToolIfEnabled(webSocketCreated.toolSource().toolType()); - if (burpTool != null && burpTool != BurpTool.Proxy && BurpExtender.getGeneralSettings().isCapture(BurpTool.WebSockets)) { + if (burpTool != null && burpTool != BurpTool.Proxy && workspace.getGeneralSettings().isCapture(BurpTool.WebSockets)) { webSocketCreated.webSocket().registerMessageHandler(new WebSocketMessageConnector(BurpTool.Proxy, webSocketCreated.webSocket(), webSocketCreated.upgradeRequest())); } } } + public void unload() { + rulesEngine = null; + workspace = null; + } + private class WebSocketMessageConnector implements ProxyMessageHandler, MessageHandler { private final WebSocketMessageSender messageSender; @@ -68,7 +81,8 @@ public WebSocketMessageConnector(BurpTool burpTool, WebSocket webSocket, HttpReq @Override public TextMessageReceivedAction handleTextMessageReceived(InterceptedTextMessage interceptedTextMessage) { - if (BurpExtender.getGeneralSettings().isCapture(BurpTool.WebSockets)) { + if (workspace == null) return TextMessageReceivedAction.continueWith(interceptedTextMessage); + if (workspace.getGeneralSettings().isCapture(BurpTool.WebSockets)) { WebSocketEventInfo eventInfo = asEventInfo(WebSocketMessageType.Text, interceptedTextMessage.annotations(), interceptedTextMessage.payload(), interceptedTextMessage.direction()); return processEvent(eventInfo).asProxyTextAction(); } @@ -77,7 +91,8 @@ public TextMessageReceivedAction handleTextMessageReceived(InterceptedTextMessag @Override public TextMessageAction handleTextMessage(TextMessage textMessage) { - if (BurpExtender.getGeneralSettings().isCapture(BurpTool.WebSockets)) { + if (workspace == null) return TextMessageAction.continueWith(textMessage); + if (workspace.getGeneralSettings().isCapture(BurpTool.WebSockets)) { WebSocketEventInfo eventInfo = asEventInfo(WebSocketMessageType.Text, null, textMessage.payload(), textMessage.direction()); return processEvent(eventInfo).asTextAction(); } @@ -86,7 +101,8 @@ public TextMessageAction handleTextMessage(TextMessage textMessage) { @Override public BinaryMessageReceivedAction handleBinaryMessageReceived(InterceptedBinaryMessage interceptedBinaryMessage) { - if (BurpExtender.getGeneralSettings().isCapture(BurpTool.WebSockets)) { + if (workspace == null) return BinaryMessageReceivedAction.continueWith(interceptedBinaryMessage); + if (workspace.getGeneralSettings().isCapture(BurpTool.WebSockets)) { WebSocketEventInfo eventInfo = asEventInfo(WebSocketMessageType.Binary, interceptedBinaryMessage.annotations(), interceptedBinaryMessage.payload().getBytes(), interceptedBinaryMessage.direction()); return processEvent(eventInfo).asProxyBinaryAction(); } @@ -95,7 +111,8 @@ public BinaryMessageReceivedAction handleBinaryMessageReceived(InterceptedBinary @Override public BinaryMessageAction handleBinaryMessage(BinaryMessage binaryMessage) { - if (BurpExtender.getGeneralSettings().isCapture(BurpTool.WebSockets)) { + if (workspace == null) return BinaryMessageAction.continueWith(binaryMessage); + if (workspace.getGeneralSettings().isCapture(BurpTool.WebSockets)) { WebSocketEventInfo eventInfo = asEventInfo(WebSocketMessageType.Binary, null, binaryMessage.payload().getBytes(), binaryMessage.direction()); return processEvent(eventInfo).asBinaryAction(); } @@ -104,15 +121,16 @@ public BinaryMessageAction handleBinaryMessage(BinaryMessage binaryMessage) { private WebSocketEventInfo asEventInfo(WebSocketMessageType messageType, Annotations annotations, T data, Direction direction) { WebSocketEventInfo eventInfo = new WebSocketEventInfo<>( - messageType, + workspace, messageType, WebSocketDataDirection.from(direction), burpTool, messageSender, httpRequest, annotations, data, sessionVariables ); - eventInfo.getDiagnostics().setEventEnabled(BurpExtender.getGeneralSettings().isEnableEventDiagnostics()); + eventInfo.getDiagnostics().setEventEnabled(workspace.getGeneralSettings().isEnableEventDiagnostics()); return eventInfo; } private EventResult processEvent(WebSocketEventInfo eventInfo) { EventResult eventResult = new EventResult<>(eventInfo); + Workspaces.get().setCurrentWorkspace(workspace); try { rulesEngine.run(eventInfo); if (eventInfo.isChanged()) { @@ -123,10 +141,10 @@ private EventResult processEvent(WebSocketEventInfo eventInfo) { } } } catch (Exception e) { - Log.get().withMessage("Critical Error").withException(e).logErr(); + Log.get(workspace).withMessage("Critical Error").withException(e).logErr(); } finally { if (eventInfo.getDiagnostics().hasLogs()) { - Log.get().withMessage(eventInfo.getDiagnostics().getLogs()).logRaw(); + Log.get(workspace).withMessage(eventInfo.getDiagnostics().getLogs()).logRaw(); } } return eventResult; diff --git a/extension/src/main/java/synfron/reshaper/burp/core/Tab.java b/extension/src/main/java/synfron/reshaper/burp/core/WorkspaceTab.java similarity index 68% rename from extension/src/main/java/synfron/reshaper/burp/core/Tab.java rename to extension/src/main/java/synfron/reshaper/burp/core/WorkspaceTab.java index 6844c5b..f575b36 100644 --- a/extension/src/main/java/synfron/reshaper/burp/core/Tab.java +++ b/extension/src/main/java/synfron/reshaper/burp/core/WorkspaceTab.java @@ -7,7 +7,7 @@ @Getter @AllArgsConstructor -public enum Tab { +public enum WorkspaceTab { HttpRules("HTTP Rules"), WebSocketRules ("WebSocket Rules"), GlobalVariables("Global Variables"), @@ -17,7 +17,7 @@ public enum Tab { private String name; private boolean hideable = true; - Tab(String name) { + WorkspaceTab(String name) { this.name = name; } @@ -26,7 +26,7 @@ public String toString() { return name; } - public static Tab byName(String name) { - return Arrays.stream(Tab.values()).filter(tab -> tab.name.equals(name)).findFirst().orElse(null); + public static WorkspaceTab byName(String name) { + return Arrays.stream(WorkspaceTab.values()).filter(tab -> tab.name.equals(name)).findFirst().orElse(null); } } diff --git a/extension/src/main/java/synfron/reshaper/burp/core/events/Event.java b/extension/src/main/java/synfron/reshaper/burp/core/events/Event.java index 816739e..5fa9b86 100644 --- a/extension/src/main/java/synfron/reshaper/burp/core/events/Event.java +++ b/extension/src/main/java/synfron/reshaper/burp/core/events/Event.java @@ -4,19 +4,23 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; public class Event { private List>> listeners; public synchronized void clearListeners() { if (listeners != null) { - listeners.clear(); + listeners = null; } } public synchronized void remove(IEventListener listener) { if (listeners != null) { - listeners.removeIf(listenerReference -> listenerReference.get() == null || listenerReference.get().equals(listener)); + listeners = listeners.stream() + .filter(listenerReference -> listenerReference.get() != null && !Objects.equals(listenerReference.get(), listener)) + .collect(Collectors.toCollection(LinkedList::new)); if (listeners.isEmpty()) { listeners = null; } @@ -24,7 +28,11 @@ public synchronized void remove(IEventListener listener) { } public synchronized void add(IEventListener listener) { - getListeners().add(new WeakReference<>(listener)); + LinkedList>> listeners = this.listeners == null ? new LinkedList<>() : this.listeners.stream() + .filter(listenerReference -> listenerReference.get() != null) + .collect(Collectors.toCollection(LinkedList::new)); + listeners.add(new WeakReference<>(listener)); + this.listeners = listeners; } public synchronized void invoke(A args) { @@ -40,11 +48,4 @@ public synchronized void invoke(A args) { } } } - - private List>> getListeners() { - if (listeners == null) { - listeners = new LinkedList<>(); - } - return listeners; - } } diff --git a/extension/src/main/java/synfron/reshaper/burp/core/events/message/LogMessage.java b/extension/src/main/java/synfron/reshaper/burp/core/events/message/LogMessage.java new file mode 100644 index 0000000..8c88b46 --- /dev/null +++ b/extension/src/main/java/synfron/reshaper/burp/core/events/message/LogMessage.java @@ -0,0 +1,21 @@ +package synfron.reshaper.burp.core.events.message; + +import lombok.Getter; + +import java.util.UUID; + +public class LogMessage extends Message { + + @Getter + private final String text; + + public LogMessage(String text) { + super(UUID.randomUUID().toString()); + this.text = text; + } + + @Override + public MessageType getMessageType() { + return MessageType.Log; + } +} diff --git a/extension/src/main/java/synfron/reshaper/burp/core/events/message/MessageType.java b/extension/src/main/java/synfron/reshaper/burp/core/events/message/MessageType.java index aca9f96..81f4a37 100644 --- a/extension/src/main/java/synfron/reshaper/burp/core/events/message/MessageType.java +++ b/extension/src/main/java/synfron/reshaper/burp/core/events/message/MessageType.java @@ -5,7 +5,8 @@ public enum MessageType { PromptRequest(PromptRequestMessage.class), PromptResponse(PromptResponseMessage.class), - PromptCancel(PromptCancelMessage.class); + PromptCancel(PromptCancelMessage.class), + Log(LogMessage.class); @Getter private final Class dataClass; diff --git a/extension/src/main/java/synfron/reshaper/burp/core/messages/EventInfo.java b/extension/src/main/java/synfron/reshaper/burp/core/messages/EventInfo.java index 3f6e14b..60cadc4 100644 --- a/extension/src/main/java/synfron/reshaper/burp/core/messages/EventInfo.java +++ b/extension/src/main/java/synfron/reshaper/burp/core/messages/EventInfo.java @@ -1,6 +1,5 @@ package synfron.reshaper.burp.core.messages; -import burp.BurpExtender; import burp.api.montoya.core.Annotations; import burp.api.montoya.http.message.HttpRequestResponse; import burp.api.montoya.http.message.requests.HttpRequest; @@ -12,6 +11,7 @@ import synfron.reshaper.burp.core.messages.entities.http.HttpRequestMessage; import synfron.reshaper.burp.core.rules.diagnostics.Diagnostics; import synfron.reshaper.burp.core.rules.diagnostics.IDiagnostics; +import synfron.reshaper.burp.core.settings.Workspace; import synfron.reshaper.burp.core.utils.UrlUtils; import synfron.reshaper.burp.core.vars.Variables; @@ -41,30 +41,38 @@ public abstract class EventInfo { @Getter protected final Variables variables = new Variables(); @Getter + protected final Workspace workspace; + @Getter private final Variables sessionVariables; @Getter - protected final Encoder encoder = new Encoder(BurpExtender.getGeneralSettings().getDefaultEncoding()); + protected final Encoder encoder; protected boolean changed; @Getter - protected final IDiagnostics diagnostics = new Diagnostics(); + protected final IDiagnostics diagnostics; - public EventInfo(BurpTool burpTool, HttpRequest httpRequest, Annotations annotations, Variables sessionVariables) { + public EventInfo(Workspace workspace, BurpTool burpTool, HttpRequest httpRequest, Annotations annotations, Variables sessionVariables) { + this.workspace = workspace; + encoder = new Encoder(workspace.getGeneralSettings().getDefaultEncoding()); + diagnostics = new Diagnostics(workspace); this.sessionVariables = sessionVariables; this.burpTool = burpTool; this.initialHttpRequest = httpRequest; this.annotations = annotations; - httpRequestMessage = new HttpRequestMessage(httpRequest, encoder); + httpRequestMessage = new HttpRequestMessage(workspace, httpRequest, encoder); destinationPort = httpRequest.httpService().port(); destinationAddress = httpRequest.httpService().host(); } protected EventInfo(EventInfo sourceEventInfo) { + this.workspace = sourceEventInfo.workspace; + encoder = new Encoder(workspace.getGeneralSettings().getDefaultEncoding()); + diagnostics = new Diagnostics(workspace); this.sessionVariables = sourceEventInfo.getSessionVariables(); this.burpTool = sourceEventInfo.getBurpTool(); this.initialHttpRequest = null; this.annotations = null; this.encoder.setEncoding(sourceEventInfo.getEncoder().getEncoding(), sourceEventInfo.getEncoder().isAutoSet()); - httpRequestMessage = new HttpRequestMessage(sourceEventInfo.getHttpRequestMessage().getValue(), encoder); + httpRequestMessage = new HttpRequestMessage(workspace, sourceEventInfo.getHttpRequestMessage().getValue(), encoder); httpProtocol = sourceEventInfo.getHttpProtocol(); destinationPort = sourceEventInfo.getDestinationPort(); destinationAddress = sourceEventInfo.getDestinationAddress(); diff --git a/extension/src/main/java/synfron/reshaper/burp/core/messages/HttpEventInfo.java b/extension/src/main/java/synfron/reshaper/burp/core/messages/HttpEventInfo.java index f96b6a6..24e087e 100644 --- a/extension/src/main/java/synfron/reshaper/burp/core/messages/HttpEventInfo.java +++ b/extension/src/main/java/synfron/reshaper/burp/core/messages/HttpEventInfo.java @@ -11,6 +11,7 @@ import synfron.reshaper.burp.core.ProtocolType; import synfron.reshaper.burp.core.messages.entities.http.HttpRequestMessage; import synfron.reshaper.burp.core.messages.entities.http.HttpResponseMessage; +import synfron.reshaper.burp.core.settings.Workspace; import synfron.reshaper.burp.core.utils.Url; import synfron.reshaper.burp.core.vars.Variables; @@ -31,20 +32,20 @@ public class HttpEventInfo extends EventInfo { private String messageId; private HttpRequest httpRequestOverride; - public HttpEventInfo(HttpDataDirection dataDirection, BurpTool burpTool, String messageId, HttpRequest httpRequest, HttpResponse httpResponse, Annotations annotations, String proxyName, String sourceAddress, Variables sessionVariables) { - super(burpTool, httpRequest, annotations, sessionVariables); + public HttpEventInfo(Workspace workspace, HttpDataDirection dataDirection, BurpTool burpTool, String messageId, HttpRequest httpRequest, HttpResponse httpResponse, Annotations annotations, String proxyName, String sourceAddress, Variables sessionVariables) { + super(workspace, burpTool, httpRequest, annotations, sessionVariables); this.initialDataDirection = dataDirection; this.dataDirection = dataDirection; this.messageId = messageId; this.httpProtocol = httpRequest.httpService().secure() ? "https" : "http"; this.initialHttpResponse = httpResponse; - this.httpResponseMessage = new HttpResponseMessage(httpResponse, encoder); + this.httpResponseMessage = new HttpResponseMessage(workspace, httpResponse, encoder); this.sourceAddress = sourceAddress; this.proxyName = proxyName; } - public HttpEventInfo(HttpDataDirection dataDirection, BurpTool burpTool, String messageId, HttpRequest httpRequest, HttpResponse httpResponse, Annotations annotations, Variables sessionVariables) { - this(dataDirection, burpTool, messageId, httpRequest, httpResponse, annotations, null, "burp::", sessionVariables); + public HttpEventInfo(Workspace workspace, HttpDataDirection dataDirection, BurpTool burpTool, String messageId, HttpRequest httpRequest, HttpResponse httpResponse, Annotations annotations, Variables sessionVariables) { + this(workspace, dataDirection, burpTool, messageId, httpRequest, httpResponse, annotations, null, "burp::", sessionVariables); } public HttpEventInfo(EventInfo sourceRequestEventInfo) { @@ -52,7 +53,7 @@ public HttpEventInfo(EventInfo sourceRequestEventInfo) { this.dataDirection = HttpDataDirection.Request; this.initialDataDirection = HttpDataDirection.Request; this.initialHttpResponse = null; - this.httpResponseMessage = new HttpResponseMessage((byte[]) null, encoder); + this.httpResponseMessage = new HttpResponseMessage(sourceRequestEventInfo.workspace, (byte[]) null, encoder); this.sourceAddress = "burp::"; this.proxyName = null; } @@ -68,12 +69,12 @@ public void setDataDirection(HttpDataDirection dataDirection) { } public void setHttpRequestMessage(byte[] request) { - httpRequestMessage = new HttpRequestMessage(request, encoder); + httpRequestMessage = new HttpRequestMessage(workspace, request, encoder); changed = true; } public void setHttpResponseMessage(byte[] response) { - httpResponseMessage = new HttpResponseMessage(response, encoder); + httpResponseMessage = new HttpResponseMessage(workspace, response, encoder); changed = true; } diff --git a/extension/src/main/java/synfron/reshaper/burp/core/messages/WebSocketEventInfo.java b/extension/src/main/java/synfron/reshaper/burp/core/messages/WebSocketEventInfo.java index 182b62f..399f09c 100644 --- a/extension/src/main/java/synfron/reshaper/burp/core/messages/WebSocketEventInfo.java +++ b/extension/src/main/java/synfron/reshaper/burp/core/messages/WebSocketEventInfo.java @@ -6,6 +6,7 @@ import org.apache.commons.lang3.StringUtils; import synfron.reshaper.burp.core.BurpTool; import synfron.reshaper.burp.core.ProtocolType; +import synfron.reshaper.burp.core.settings.Workspace; import synfron.reshaper.burp.core.vars.Variables; public class WebSocketEventInfo extends EventInfo { @@ -20,8 +21,8 @@ public class WebSocketEventInfo extends EventInfo { @Getter private T data; - public WebSocketEventInfo(WebSocketMessageType messageType, WebSocketDataDirection dataDirection, BurpTool burpTool, WebSocketMessageSender messageSender, HttpRequest httpRequest, Annotations annotations, T data, Variables sessionVariables) { - super(burpTool, httpRequest, annotations, sessionVariables); + public WebSocketEventInfo(Workspace workspace, WebSocketMessageType messageType, WebSocketDataDirection dataDirection, BurpTool burpTool, WebSocketMessageSender messageSender, HttpRequest httpRequest, Annotations annotations, T data, Variables sessionVariables) { + super(workspace, burpTool, httpRequest, annotations, sessionVariables); this.messageType = messageType; this.initialDataDirection = dataDirection; this.dataDirection = dataDirection; diff --git a/extension/src/main/java/synfron/reshaper/burp/core/messages/entities/http/HttpRequestMessage.java b/extension/src/main/java/synfron/reshaper/burp/core/messages/entities/http/HttpRequestMessage.java index bb9dbbc..7a9d7ba 100644 --- a/extension/src/main/java/synfron/reshaper/burp/core/messages/entities/http/HttpRequestMessage.java +++ b/extension/src/main/java/synfron/reshaper/burp/core/messages/entities/http/HttpRequestMessage.java @@ -1,6 +1,5 @@ package synfron.reshaper.burp.core.messages.entities.http; -import burp.BurpExtender; import burp.api.montoya.core.ByteArray; import burp.api.montoya.http.message.HttpHeader; import burp.api.montoya.http.message.requests.HttpRequest; @@ -8,6 +7,7 @@ import synfron.reshaper.burp.core.messages.ContentType; import synfron.reshaper.burp.core.messages.Encoder; import synfron.reshaper.burp.core.rules.SetItemPlacement; +import synfron.reshaper.burp.core.settings.Workspace; import synfron.reshaper.burp.core.utils.Log; import synfron.reshaper.burp.core.utils.ObjectUtils; import synfron.reshaper.burp.core.utils.Url; @@ -17,6 +17,7 @@ public class HttpRequestMessage extends HttpEntity { + private final Workspace workspace; private HttpRequest httpRequest; private final byte[] request; private final Encoder encoder; @@ -26,13 +27,15 @@ public class HttpRequestMessage extends HttpEntity { private HttpBody body; private boolean initialized; - public HttpRequestMessage(HttpRequest httpRequest, Encoder encoder) { + public HttpRequestMessage(Workspace workspace, HttpRequest httpRequest, Encoder encoder) { + this.workspace = workspace; this.httpRequest = httpRequest; this.request = httpRequest != null ? httpRequest.toByteArray().getBytes() : new byte[0]; this.encoder = encoder; } - public HttpRequestMessage(byte[] request, Encoder encoder) { + public HttpRequestMessage(Workspace workspace, byte[] request, Encoder encoder) { + this.workspace = workspace; this.request = request; this.encoder = encoder; } @@ -59,13 +62,13 @@ private void initialize() { } private void sanityCheckHeaders() { - if (BurpExtender.getGeneralSettings().isEnableSanityCheckWarnings()) { + if (workspace.getGeneralSettings().isEnableSanityCheckWarnings()) { for (int byteIndex = 0; byteIndex < request.length; byteIndex++) { if (request[byteIndex] == '\r') { break; } else if (request[byteIndex] == '\n') { String headersWarning = "Sanity Check - Warning: First line of a raw request with value '%s' has a line feed without a carriage return, and may result in missing headers."; - Log.get().withMessage(String.format(headersWarning, ByteArray.byteArray(Arrays.copyOfRange(request, 0, byteIndex)))).log(); + Log.get(workspace).withMessage(String.format(headersWarning, ByteArray.byteArray(Arrays.copyOfRange(request, 0, byteIndex)))).log(); break; } } diff --git a/extension/src/main/java/synfron/reshaper/burp/core/messages/entities/http/HttpResponseMessage.java b/extension/src/main/java/synfron/reshaper/burp/core/messages/entities/http/HttpResponseMessage.java index e5f6a1c..fc3b3e1 100644 --- a/extension/src/main/java/synfron/reshaper/burp/core/messages/entities/http/HttpResponseMessage.java +++ b/extension/src/main/java/synfron/reshaper/burp/core/messages/entities/http/HttpResponseMessage.java @@ -1,6 +1,5 @@ package synfron.reshaper.burp.core.messages.entities.http; -import burp.BurpExtender; import burp.api.montoya.core.ByteArray; import burp.api.montoya.http.message.HttpHeader; import burp.api.montoya.http.message.responses.HttpResponse; @@ -8,6 +7,7 @@ import org.apache.commons.lang3.StringUtils; import synfron.reshaper.burp.core.messages.Encoder; import synfron.reshaper.burp.core.messages.MimeType; +import synfron.reshaper.burp.core.settings.Workspace; import synfron.reshaper.burp.core.utils.Log; import java.util.Arrays; @@ -26,16 +26,19 @@ public class HttpResponseMessage extends HttpEntity { private HttpHeaders headers; private HttpBody body; private boolean initialized; + private final Workspace workspace; - public HttpResponseMessage(HttpResponse httpResponse, Encoder encoder) { + public HttpResponseMessage(Workspace workspace, HttpResponse httpResponse, Encoder encoder) { this.httpResponse = httpResponse; this.response = httpResponse != null ? httpResponse.toByteArray().getBytes() : new byte[0]; this.encoder = encoder; + this.workspace = workspace; } - public HttpResponseMessage(byte[] response, Encoder encoder) { + public HttpResponseMessage(Workspace workspace, byte[] response, Encoder encoder) { this.response = response; this.encoder = encoder; + this.workspace = workspace; } @Override @@ -60,13 +63,13 @@ private void initialize() { } private void sanityCheckHeaders() { - if (BurpExtender.getGeneralSettings().isEnableSanityCheckWarnings()) { + if (workspace.getGeneralSettings().isEnableSanityCheckWarnings()) { for (int byteIndex = 0; byteIndex < response.length; byteIndex++) { if (response[byteIndex] == '\r') { break; } else if (response[byteIndex] == '\n') { String headersWarning = "Sanity Check - Warning: First line of a raw response with value '%s' has a line feed without a carriage return, and may result in missing headers."; - Log.get().withMessage(String.format(headersWarning, ByteArray.byteArray(Arrays.copyOfRange(response, 0, byteIndex)))).log(); + Log.get(workspace).withMessage(String.format(headersWarning, ByteArray.byteArray(Arrays.copyOfRange(response, 0, byteIndex)))).log(); break; } } diff --git a/extension/src/main/java/synfron/reshaper/burp/core/rules/IRuleOperation.java b/extension/src/main/java/synfron/reshaper/burp/core/rules/IRuleOperation.java index 09b7ab9..5da8fde 100644 --- a/extension/src/main/java/synfron/reshaper/burp/core/rules/IRuleOperation.java +++ b/extension/src/main/java/synfron/reshaper/burp/core/rules/IRuleOperation.java @@ -1,7 +1,6 @@ package synfron.reshaper.burp.core.rules; import synfron.reshaper.burp.core.messages.EventInfo; -import synfron.reshaper.burp.core.vars.GlobalVariables; import synfron.reshaper.burp.core.vars.VariableSource; import synfron.reshaper.burp.core.vars.Variables; @@ -23,7 +22,7 @@ default int groupSize() { default Variables getVariables(VariableSource variableSource, EventInfo eventInfo) { return switch (variableSource) { case Event, EventList -> eventInfo.getVariables(); - case Global, GlobalList -> GlobalVariables.get(); + case Global, GlobalList -> eventInfo.getWorkspace().getGlobalVariables(); case Session, SessionList -> eventInfo.getSessionVariables(); default -> null; }; diff --git a/extension/src/main/java/synfron/reshaper/burp/core/rules/Rule.java b/extension/src/main/java/synfron/reshaper/burp/core/rules/Rule.java index 27f2277..2032c2b 100644 --- a/extension/src/main/java/synfron/reshaper/burp/core/rules/Rule.java +++ b/extension/src/main/java/synfron/reshaper/burp/core/rules/Rule.java @@ -1,7 +1,6 @@ package synfron.reshaper.burp.core.rules; import lombok.Getter; -import lombok.Setter; import synfron.reshaper.burp.core.events.IEventListener; import synfron.reshaper.burp.core.events.PropertyChangedArgs; import synfron.reshaper.burp.core.events.PropertyChangedEvent; @@ -15,9 +14,10 @@ public class Rule implements Serializable { @Getter private final transient PropertyChangedEvent propertyChangedEvent = new PropertyChangedEvent(); - @Getter @Setter + + @Getter private List> whens = List.of(); - @Getter @Setter + @Getter private List> thens = List.of(); @Getter private boolean enabled = true; @@ -43,6 +43,16 @@ public void setAutoRun(boolean autoRun) { propertyChangedEvent.invoke(new PropertyChangedArgs(this, "autoRun", autoRun)); } + public void setWhens(List> whens) { + this.whens = whens; + propertyChangedEvent.invoke(new PropertyChangedArgs(this, "whens", whens)); + } + + public void setThens(List> thens) { + this.thens = thens; + propertyChangedEvent.invoke(new PropertyChangedArgs(this, "thens", thens)); + } + public void setDiagnosticsEnabled(boolean diagnosticsEnabled) { this.diagnosticsEnabled = diagnosticsEnabled; propertyChangedEvent.invoke(new PropertyChangedArgs(this, "diagnosticsEnabled", diagnosticsEnabled)); diff --git a/extension/src/main/java/synfron/reshaper/burp/core/rules/RulesEngine.java b/extension/src/main/java/synfron/reshaper/burp/core/rules/RulesEngine.java index 888b14a..151d169 100644 --- a/extension/src/main/java/synfron/reshaper/burp/core/rules/RulesEngine.java +++ b/extension/src/main/java/synfron/reshaper/burp/core/rules/RulesEngine.java @@ -47,9 +47,9 @@ public RuleResponse run(EventInfo eventInfo, Rule rule) thenResult = thenResult.or(perform(rule.getThens(), eventInfo)); } } catch (RhinoException e) { - Log.get().withMessage("Failure running rule").withException(e).withPayload(e.getScriptStackTrace()).logErr(); + Log.get(eventInfo.getWorkspace()).withMessage("Failure running rule").withException(e).withPayload(e.getScriptStackTrace()).logErr(); } catch (Exception e) { - Log.get().withException(e).withMessage("Failure running rule").withPayload(rule).logErr(); + Log.get(eventInfo.getWorkspace()).withException(e).withMessage("Failure running rule").withPayload(rule).logErr(); } finally { if (rule.isEnabled() && eventInfo.getDiagnostics().isEnabled()) eventInfo.getDiagnostics().logEnd(rule); } diff --git a/extension/src/main/java/synfron/reshaper/burp/core/rules/diagnostics/Diagnostics.java b/extension/src/main/java/synfron/reshaper/burp/core/rules/diagnostics/Diagnostics.java index 5a2dcfb..36673f2 100644 --- a/extension/src/main/java/synfron/reshaper/burp/core/rules/diagnostics/Diagnostics.java +++ b/extension/src/main/java/synfron/reshaper/burp/core/rules/diagnostics/Diagnostics.java @@ -1,6 +1,5 @@ package synfron.reshaper.burp.core.rules.diagnostics; -import burp.BurpExtender; import lombok.Getter; import lombok.Setter; import org.apache.commons.lang3.StringUtils; @@ -14,12 +13,14 @@ import synfron.reshaper.burp.core.rules.Rule; import synfron.reshaper.burp.core.rules.thens.Then; import synfron.reshaper.burp.core.rules.whens.When; +import synfron.reshaper.burp.core.settings.Workspace; import java.io.Serializable; import java.util.*; import java.util.stream.Collectors; public class Diagnostics implements IDiagnostics { + private final Workspace workspace; private Integer diagnosticValueMaxLength; private List records; @@ -42,6 +43,10 @@ public class Diagnostics implements IDiagnostics { EscapeChars = new LookupTranslator(escapeCharsMap); } + public Diagnostics(Workspace workspace) { + this.workspace = workspace; + } + @Override public int size() { return records.size(); @@ -290,7 +295,7 @@ private String toResultPhrase(boolean result, boolean negated) { private int getDiagnosticValueMaxLength() { if (diagnosticValueMaxLength == null) { - diagnosticValueMaxLength = BurpExtender.getGeneralSettings().getDiagnosticValueMaxLength(); + diagnosticValueMaxLength = workspace.getGeneralSettings().getDiagnosticValueMaxLength(); } return diagnosticValueMaxLength; } diff --git a/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenBuildHttpMessage.java b/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenBuildHttpMessage.java index 5455eb1..1eed925 100644 --- a/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenBuildHttpMessage.java +++ b/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenBuildHttpMessage.java @@ -76,7 +76,7 @@ public RuleResponse perform(EventInfo eventInfo) { } private String buildRequestMessage(EventInfo eventInfo) { - HttpRequestMessage httpRequestMessage = new HttpRequestMessage(eventInfo.getEncoder().encode( + HttpRequestMessage httpRequestMessage = new HttpRequestMessage(eventInfo.getWorkspace(), eventInfo.getEncoder().encode( VariableString.getTextOrDefault(eventInfo, starterHttpMessage, "") ), eventInfo.getEncoder()); for (MessageValueSetter messageValueSetter : getMessageValueSetters()) { @@ -93,7 +93,7 @@ private String buildRequestMessage(EventInfo eventInfo) { } private String buildResponseMessage(EventInfo eventInfo) { - HttpResponseMessage httpResponseMessage = new HttpResponseMessage(eventInfo.getEncoder().encode( + HttpResponseMessage httpResponseMessage = new HttpResponseMessage(eventInfo.getWorkspace(), eventInfo.getEncoder().encode( VariableString.getTextOrDefault(eventInfo, starterHttpMessage, "") ), eventInfo.getEncoder()); for (MessageValueSetter messageValueSetter : getMessageValueSetters()) { diff --git a/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenExtract.java b/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenExtract.java index eebbbec..e098667 100644 --- a/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenExtract.java +++ b/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenExtract.java @@ -41,7 +41,7 @@ public RuleResponse perform(EventInfo eventInfo) { delimiterText = delimiter.getText(eventInfo); VariableSourceEntry variableSource = new VariableSourceEntry(listVariableSource, List.of(listVariableNameText)); variable = switch (variableSource.getVariableSource()) { - case GlobalList -> (ListVariable) GlobalVariables.get().add(Variables.asKey(variableSource.getParams().getFirst(), true)); + case GlobalList -> (ListVariable) eventInfo.getWorkspace().getGlobalVariables().add(Variables.asKey(variableSource.getParams().getFirst(), true)); case EventList -> (ListVariable) eventInfo.getVariables().add(Variables.asKey(variableSource.getParams().getFirst(), true)); case SessionList -> (ListVariable) eventInfo.getSessionVariables().add(Variables.asKey(variableSource.getParams().getFirst(), true)); default -> null; diff --git a/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenParseHttpMessage.java b/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenParseHttpMessage.java index 6748c23..571cc72 100644 --- a/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenParseHttpMessage.java +++ b/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenParseHttpMessage.java @@ -54,7 +54,7 @@ public RuleResponse perform(EventInfo eventInfo) { } private List> parseRequestMessage(EventInfo eventInfo) { - HttpRequestMessage httpRequestMessage = new HttpRequestMessage(eventInfo.getEncoder().encode( + HttpRequestMessage httpRequestMessage = new HttpRequestMessage(eventInfo.getWorkspace(), eventInfo.getEncoder().encode( VariableString.getTextOrDefault(eventInfo, httpMessage, "") ), eventInfo.getEncoder()); List> variables = new ArrayList<>(); @@ -83,7 +83,7 @@ private List> parseRequestMessage(EventInfo eventInfo) { } private List> parseResponseMessage(EventInfo eventInfo) { - HttpResponseMessage httpResponseMessage = new HttpResponseMessage(eventInfo.getEncoder().encode( + HttpResponseMessage httpResponseMessage = new HttpResponseMessage(eventInfo.getWorkspace(), eventInfo.getEncoder().encode( VariableString.getTextOrDefault(eventInfo, httpMessage, "") ), eventInfo.getEncoder()); List> variables = new ArrayList<>(); diff --git a/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenPrompt.java b/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenPrompt.java index cb766af..3b7780c 100644 --- a/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenPrompt.java +++ b/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenPrompt.java @@ -1,6 +1,5 @@ package synfron.reshaper.burp.core.rules.thens; -import burp.BurpExtender; import lombok.Getter; import lombok.Setter; import org.apache.commons.lang3.tuple.Pair; @@ -20,24 +19,25 @@ import java.util.UUID; import java.util.concurrent.TimeUnit; +@Getter public class ThenPrompt extends Then implements IHttpRuleOperation, IWebSocketRuleOperation { - @Getter @Setter + @Setter private VariableString description; - @Getter @Setter + @Setter private VariableString starterText; - @Getter @Setter + @Setter private VariableString failAfter; - @Getter @Setter + @Setter private boolean breakAfterFailure = true; - @Getter @Setter + @Setter private VariableSource captureVariableSource = VariableSource.Global; - @Getter @Setter + @Setter private VariableString captureVariableName; - @Getter @Setter + @Setter private SetListItemPlacement itemPlacement = SetListItemPlacement.Index; - @Getter @Setter + @Setter private VariableString delimiter; - @Getter @Setter + @Setter private VariableString index; @Override @@ -56,11 +56,11 @@ public RuleResponse perform(EventInfo eventInfo) { starterText = VariableString.getTextOrDefault(eventInfo, this.starterText, ""); PromptRequestMessage requestMessage = new PromptRequestMessage(UUID.randomUUID().toString(), description, starterText); MessageWaiter messageWaiter = new MessageWaiter<>( - BurpExtender.getMessageEvent(), + eventInfo.getWorkspace().getMessageEvent(), MessageType.PromptResponse, responseMessage -> responseMessage.getMessageId().equals(requestMessage.getMessageId()) ); - BurpExtender.getMessageEvent().invoke(new MessageArgs(this, requestMessage)); + eventInfo.getWorkspace().getMessageEvent().invoke(new MessageArgs(this, requestMessage)); if (messageWaiter.waitForMessage(failAfterInMilliseconds, TimeUnit.MILLISECONDS)) { output = messageWaiter.getMessage().getResponse(); if (output != null) { @@ -78,7 +78,7 @@ public RuleResponse perform(EventInfo eventInfo) { } complete = true; } else { - BurpExtender.getMessageEvent().invoke(new MessageArgs(this, new PromptCancelMessage(requestMessage.getMessageId()))); + eventInfo.getWorkspace().getMessageEvent().invoke(new MessageArgs(this, new PromptCancelMessage(requestMessage.getMessageId()))); failed = true; } } catch (InterruptedException e) { diff --git a/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenRepeat.java b/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenRepeat.java index afa98d3..8162759 100644 --- a/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenRepeat.java +++ b/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenRepeat.java @@ -154,7 +154,7 @@ private Iterator getHasNextItemIterator(EventInfo eventInfo, List (ListVariable) GlobalVariables.get().getOrDefault(Variables.asKey(variable1.getParams().getFirst(), true)); + case GlobalList -> (ListVariable) eventInfo.getWorkspace().getGlobalVariables().getOrDefault(Variables.asKey(variable1.getParams().getFirst(), true)); case EventList -> (ListVariable) eventInfo.getVariables().getOrDefault(Variables.asKey(variable1.getParams().getFirst(), true)); case SessionList -> (ListVariable) eventInfo.getSessionVariables().getOrDefault(Variables.asKey(variable1.getParams().getFirst(), true)); default -> null; diff --git a/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenRunRules.java b/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenRunRules.java index eb0c341..3476eb1 100644 --- a/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenRunRules.java +++ b/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenRunRules.java @@ -1,6 +1,5 @@ package synfron.reshaper.burp.core.rules.thens; -import burp.BurpExtender; import lombok.Getter; import lombok.Setter; import org.apache.commons.lang3.StringUtils; @@ -22,8 +21,8 @@ public class ThenRunRules extends Then implements IHttpRuleOperati private RulesEngine getRulesEngine(EventInfo eventInfo) { return (eventInfo instanceof HttpEventInfo) ? - BurpExtender.getHttpConnector().getRulesEngine() : - BurpExtender.getWebSocketConnector().getRulesEngine(); + eventInfo.getWorkspace().getHttpConnector().getRulesEngine() : + eventInfo.getWorkspace().getWebSocketConnector().getRulesEngine(); } public RuleResponse perform(EventInfo eventInfo) { diff --git a/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenSendRequest.java b/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenSendRequest.java index 3e85041..109d9e3 100644 --- a/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenSendRequest.java +++ b/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenSendRequest.java @@ -118,7 +118,7 @@ public RuleResponse perform(EventInfo eventInfo) { 0; failed = !complete || (failOnErrorStatusCode && (statusCode == 0 || (statusCode >= 400 && statusCode < 600))); if (captureOutput && (!failed || captureAfterFailure)) { - output = response.get() != null ? new HttpResponseMessage(response.get().toByteArray().getBytes(), eventInfo.getEncoder()).getText() : ""; + output = response.get() != null ? new HttpResponseMessage(eventInfo.getWorkspace(), response.get().toByteArray().getBytes(), eventInfo.getEncoder()).getText() : ""; setVariable( captureVariableSource, eventInfo, diff --git a/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/entities/script/ConsoleObj.java b/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/entities/script/ConsoleObj.java index 7bf7941..a1f6da1 100644 --- a/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/entities/script/ConsoleObj.java +++ b/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/entities/script/ConsoleObj.java @@ -1,6 +1,8 @@ package synfron.reshaper.burp.core.rules.thens.entities.script; import org.mozilla.javascript.ScriptableObject; +import synfron.reshaper.burp.core.messages.EventInfo; +import synfron.reshaper.burp.core.settings.Workspace; import synfron.reshaper.burp.core.utils.Log; import java.util.Arrays; @@ -9,14 +11,16 @@ import java.util.stream.Collectors; public class ConsoleObj { + private final Workspace workspace = ((EventInfo) Dispatcher.getCurrent().getDataBag().get("eventInfo")).getWorkspace(); + public void log(Object... args) { List values = getConsoleWritable(args); - Log.get().withMessage("Script Log").withPayload(values.size() == 1 ? values.getFirst() : values).log(); + Log.get(workspace).withMessage("Script Log").withPayload(values.size() == 1 ? values.getFirst() : values).log(); } public void error(Object... args) { List values = getConsoleWritable(args); - Log.get().withMessage("Script Log").withPayload(values.size() == 1 ? values.getFirst() : values).logErr(); + Log.get(workspace).withMessage("Script Log").withPayload(values.size() == 1 ? values.getFirst() : values).logErr(); } private List getConsoleWritable(Object[] values) { diff --git a/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/entities/script/Dispatcher.java b/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/entities/script/Dispatcher.java index 551fd19..1bada84 100644 --- a/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/entities/script/Dispatcher.java +++ b/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/entities/script/Dispatcher.java @@ -5,6 +5,7 @@ import org.mozilla.javascript.Context; import org.mozilla.javascript.RhinoException; import synfron.reshaper.burp.core.exceptions.WrappedException; +import synfron.reshaper.burp.core.messages.EventInfo; import synfron.reshaper.burp.core.utils.Log; import java.util.HashMap; @@ -45,6 +46,10 @@ private void setCurrent() { current.set(this); } + public EventInfo getEventInfo() { + return (EventInfo) dataBag.get("eventInfo"); + } + private Runnable getRunner(Consumer consumer, boolean shutdownOnException, boolean close) { numTasks.incrementAndGet(); return () -> { @@ -56,7 +61,7 @@ private Runnable getRunner(Consumer consumer, boolean shutdownOnExcepti setFirstException(e); executor.shutdownNow(); } else { - Log.get().withMessage("Script execution error") + Log.get(getEventInfo().getWorkspace()).withMessage("Script execution error") .withException(e) .withPayload(e instanceof RhinoException ? ((RhinoException)e).getScriptStackTrace() : null) .logErr(); @@ -112,7 +117,7 @@ public void start(Consumer consumer) { } } } catch (InterruptedException e) { - Log.get().withMessage("Unexpected script termination").withException(e).logErr(); + Log.get(getEventInfo().getWorkspace()).withMessage("Unexpected script termination").withException(e).logErr(); } } diff --git a/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/entities/script/ReshaperObj.java b/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/entities/script/ReshaperObj.java index 83dbf4e..09e2152 100644 --- a/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/entities/script/ReshaperObj.java +++ b/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/entities/script/ReshaperObj.java @@ -14,6 +14,8 @@ import synfron.reshaper.burp.core.rules.SetItemPlacement; import synfron.reshaper.burp.core.rules.thens.Then; import synfron.reshaper.burp.core.rules.thens.ThenType; +import synfron.reshaper.burp.core.settings.Workspace; +import synfron.reshaper.burp.core.settings.Workspaces; import synfron.reshaper.burp.core.utils.Serializer; import synfron.reshaper.burp.core.utils.TextUtils; import synfron.reshaper.burp.core.vars.*; @@ -29,28 +31,30 @@ public class ReshaperObj { public static class VariablesObj { + private final Workspace workspace = Dispatcher.getCurrent().getEventInfo().getWorkspace(); + public String getGlobalVariable(String name) { - return getVariable(GlobalVariables.get(), name); + return getVariable(workspace.getGlobalVariables(), name); } public String getEventVariable(String name) { - return getVariable(((EventInfo)Dispatcher.getCurrent().getDataBag().get("eventInfo")).getVariables(), name); + return getVariable(Dispatcher.getCurrent().getEventInfo().getVariables(), name); } public String getSessionVariable(String name) { - return getVariable(((EventInfo)Dispatcher.getCurrent().getDataBag().get("eventInfo")).getSessionVariables(), name); + return getVariable(Dispatcher.getCurrent().getEventInfo().getSessionVariables(), name); } public String[] getGlobalListVariable(String name) { - return getListVariable(GlobalVariables.get(), name); + return getListVariable(workspace.getGlobalVariables(), name); } public String[] getEventListVariable(String name) { - return getListVariable(((EventInfo)Dispatcher.getCurrent().getDataBag().get("eventInfo")).getVariables(), name); + return getListVariable(Dispatcher.getCurrent().getEventInfo().getVariables(), name); } public String[] getSessionListVariable(String name) { - return getListVariable(((EventInfo)Dispatcher.getCurrent().getDataBag().get("eventInfo")).getSessionVariables(), name); + return getListVariable(Dispatcher.getCurrent().getEventInfo().getSessionVariables(), name); } private String getVariable(Variables variables, String name) { @@ -77,28 +81,28 @@ public void setGlobalVariable(String name, String value) { if (!VariableString.isValidVariableName(name)) { throw new IllegalArgumentException(String.format("Invalid variable name '%s'", name)); } - GlobalVariables.get().add(Variables.asKey(name, false)).setValue(value); + workspace.getGlobalVariables().add(Variables.asKey(name, false)).setValue(value); } public void setEventVariable(String name, String value) { if (!VariableString.isValidVariableName(name)) { throw new IllegalArgumentException(String.format("Invalid variable name '%s'", name)); } - ((EventInfo)Dispatcher.getCurrent().getDataBag().get("eventInfo")).getVariables().add(Variables.asKey(name, false)).setValue(value); + Dispatcher.getCurrent().getEventInfo().getVariables().add(Variables.asKey(name, false)).setValue(value); } public void setSessionVariable(String name, String value) { if (!VariableString.isValidVariableName(name)) { throw new IllegalArgumentException(String.format("Invalid variable name '%s'", name)); } - ((EventInfo)Dispatcher.getCurrent().getDataBag().get("eventInfo")).getSessionVariables().add(Variables.asKey(name, false)).setValue(value); + Dispatcher.getCurrent().getEventInfo().getSessionVariables().add(Variables.asKey(name, false)).setValue(value); } public void setGlobalListVariable(String name, Object[] values, String delimiter) { if (!VariableString.isValidVariableName(name)) { throw new IllegalArgumentException(String.format("Invalid variable name '%s'", name)); } - ListVariable variable = (ListVariable) GlobalVariables.get().add(Variables.asKey(name, true)); + ListVariable variable = (ListVariable) workspace.getGlobalVariables().add(Variables.asKey(name, true)); variable.setValues(values, delimiter, SetListItemsPlacement.Overwrite); } @@ -106,7 +110,7 @@ public void setEventListVariable(String name, Object[] values, String delimiter) if (!VariableString.isValidVariableName(name)) { throw new IllegalArgumentException(String.format("Invalid variable name '%s'", name)); } - ListVariable variable = (ListVariable) ((EventInfo)Dispatcher.getCurrent().getDataBag().get("eventInfo")).getVariables().add(Variables.asKey(name, true)); + ListVariable variable = (ListVariable) Dispatcher.getCurrent().getEventInfo().getVariables().add(Variables.asKey(name, true)); variable.setValues(values, delimiter, SetListItemsPlacement.Overwrite); } @@ -114,37 +118,39 @@ public void setSessionListVariable(String name, Object[] values, String delimite if (!VariableString.isValidVariableName(name)) { throw new IllegalArgumentException(String.format("Invalid variable name '%s'", name)); } - ListVariable variable = (ListVariable) ((EventInfo)Dispatcher.getCurrent().getDataBag().get("eventInfo")).getSessionVariables().add(Variables.asKey(name, true)); + ListVariable variable = (ListVariable) Dispatcher.getCurrent().getEventInfo().getSessionVariables().add(Variables.asKey(name, true)); variable.setValues(values, delimiter, SetListItemsPlacement.Overwrite); } public void deleteGlobalVariable(String name) { - GlobalVariables.get().remove(Variables.asKey(name, false)); + workspace.getGlobalVariables().remove(Variables.asKey(name, false)); } public void deleteEventVariable(String name) { - ((EventInfo)Dispatcher.getCurrent().getDataBag().get("eventInfo")).getVariables().remove(Variables.asKey(name, false)); + Dispatcher.getCurrent().getEventInfo().getVariables().remove(Variables.asKey(name, false)); } public void deleteSessionVariable(String name) { - ((EventInfo)Dispatcher.getCurrent().getDataBag().get("eventInfo")).getSessionVariables().remove(Variables.asKey(name, false)); + Dispatcher.getCurrent().getEventInfo().getSessionVariables().remove(Variables.asKey(name, false)); } public void deleteGlobalListVariable(String name) { - GlobalVariables.get().remove(Variables.asKey(name, true)); + workspace.getGlobalVariables().remove(Variables.asKey(name, true)); } public void deleteEventListVariable(String name) { - ((EventInfo)Dispatcher.getCurrent().getDataBag().get("eventInfo")).getVariables().remove(Variables.asKey(name, true)); + Dispatcher.getCurrent().getEventInfo().getVariables().remove(Variables.asKey(name, true)); } public void deleteSessionListVariable(String name) { - ((EventInfo)Dispatcher.getCurrent().getDataBag().get("eventInfo")).getSessionVariables().remove(Variables.asKey(name, true)); + Dispatcher.getCurrent().getEventInfo().getSessionVariables().remove(Variables.asKey(name, true)); } } public static class EventObj { + private final EventInfo eventInfo = Dispatcher.getCurrent().getEventInfo(); + public List getMessageValueKeys() { EventInfo eventInfo = (EventInfo)Dispatcher.getCurrent().getDataBag().get("eventInfo"); return Arrays.stream(MessageValue.values()) @@ -155,7 +161,6 @@ public List getMessageValueKeys() { public String getMessageValue(String key, String identifier) { MessageValue messageValue = EnumUtils.getEnumIgnoreCase(MessageValue.class, key); - EventInfo eventInfo = (EventInfo)Dispatcher.getCurrent().getDataBag().get("eventInfo"); if (messageValue == null || !messageValue.hasProtocolType(eventInfo.getProtocolType())) { throw new IllegalArgumentException(String.format("Invalid message value key: '%s'", key)); } @@ -169,7 +174,6 @@ public String getMessageValue(String key, String identifier) { public void setMessageValue(String key, String identifier, String value) { MessageValue messageValue = EnumUtils.getEnumIgnoreCase(MessageValue.class, key); - EventInfo eventInfo = (EventInfo)Dispatcher.getCurrent().getDataBag().get("eventInfo"); if (messageValue == null || !messageValue.hasProtocolType(eventInfo.getProtocolType())) { throw new IllegalArgumentException(String.format("Invalid message value key: '%s'", key)); } @@ -185,6 +189,7 @@ public void setMessageValue(String key, String identifier, String value) { public String runThen(String thenType, NativeObject thenData) { Dispatcher dispatcher = Dispatcher.getCurrent(); EventInfo eventInfo = (EventInfo)Dispatcher.getCurrent().getDataBag().get("eventInfo"); + Workspaces.get().setCurrentWorkspace(eventInfo.getWorkspace()); String adjustedThenTypeName = StringUtils.prependIfMissing(thenType, "Then"); Stream> supportedThenTypes = Stream.of( ThenType.BuildHttpMessage, diff --git a/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/entities/script/XmlHttpRequestObj.java b/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/entities/script/XmlHttpRequestObj.java index 4cba9c2..67d0890 100644 --- a/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/entities/script/XmlHttpRequestObj.java +++ b/extension/src/main/java/synfron/reshaper/burp/core/rules/thens/entities/script/XmlHttpRequestObj.java @@ -79,8 +79,9 @@ public void open(String method, String url, boolean async) throws URISyntaxExcep uriBuilder.setParameters(inputUriBuilder.getQueryParams()); uriBuilder.setFragment(inputUriBuilder.getFragment()); String request = String.format(requestTemplate, method, uriBuilder, requestUrl.getAuthority()); - Encoder encoder = ((EventInfo)Dispatcher.getCurrent().getDataBag().get("eventInfo")).getEncoder(); - requestMessage = new HttpRequestMessage(encoder.encode(request), encoder); + EventInfo eventInfo = (EventInfo) Dispatcher.getCurrent().getDataBag().get("eventInfo"); + Encoder encoder = eventInfo.getEncoder(); + requestMessage = new HttpRequestMessage(eventInfo.getWorkspace(), encoder.encode(request), encoder); setReadyState(OPENED); } @@ -145,9 +146,10 @@ private void doSend(String body, Dispatcher.Task timeoutTask) { if (!Thread.interrupted()) { try { HttpRequestMessage requestMessage = this.requestMessage; - Encoder encoder = ((EventInfo)Dispatcher.getCurrent().getDataBag().get("eventInfo")).getEncoder(); + EventInfo eventInfo = (EventInfo) Dispatcher.getCurrent().getDataBag().get("eventInfo"); + Encoder encoder = eventInfo.getEncoder(); if (StringUtils.isNotEmpty(body)) { - requestMessage = new HttpRequestMessage(requestMessage.getValue(), encoder); + requestMessage = new HttpRequestMessage(eventInfo.getWorkspace(), requestMessage.getValue(), encoder); requestMessage.setBody(body); } boolean useHttps = !StringUtils.equalsIgnoreCase(requestUrl.getScheme(), "http"); @@ -164,7 +166,7 @@ private void doSend(String body, Dispatcher.Task timeoutTask) { dispatcher.execute(context -> { if (response != null) { responseURL = requestUrl.toString(); - responseMessage = new HttpResponseMessage(response, encoder); + responseMessage = new HttpResponseMessage(eventInfo.getWorkspace(), response, encoder); setReadyState(DONE); execute(onload); } else { diff --git a/extension/src/main/java/synfron/reshaper/burp/core/rules/whens/WhenRepeat.java b/extension/src/main/java/synfron/reshaper/burp/core/rules/whens/WhenRepeat.java index 153ddd4..51c5a40 100644 --- a/extension/src/main/java/synfron/reshaper/burp/core/rules/whens/WhenRepeat.java +++ b/extension/src/main/java/synfron/reshaper/burp/core/rules/whens/WhenRepeat.java @@ -44,7 +44,7 @@ public boolean isMatch(List> whensSubList, EventInfo eventInfo entryVariableNameText = entryVariableName.getText(eventInfo); VariableSourceEntry variable1 = new VariableSourceEntry(listVariableSource, List.of(listVariableNameText)); ListVariable variable = switch (variable1.getVariableSource()) { - case GlobalList -> (ListVariable) GlobalVariables.get().getOrDefault(Variables.asKey(variable1.getParams().getFirst(), true)); + case GlobalList -> (ListVariable) eventInfo.getWorkspace().getGlobalVariables().getOrDefault(Variables.asKey(variable1.getParams().getFirst(), true)); case EventList -> (ListVariable) eventInfo.getVariables().getOrDefault(Variables.asKey(variable1.getParams().getFirst(), true)); case SessionList -> (ListVariable) eventInfo.getSessionVariables().getOrDefault(Variables.asKey(variable1.getParams().getFirst(), true)); default -> null; diff --git a/extension/src/main/java/synfron/reshaper/burp/core/settings/ExportSettings.java b/extension/src/main/java/synfron/reshaper/burp/core/settings/ExportSettings.java deleted file mode 100644 index f38c28e..0000000 --- a/extension/src/main/java/synfron/reshaper/burp/core/settings/ExportSettings.java +++ /dev/null @@ -1,15 +0,0 @@ -package synfron.reshaper.burp.core.settings; - -import lombok.Data; -import synfron.reshaper.burp.core.rules.Rule; -import synfron.reshaper.burp.core.vars.Variable; - -import java.util.Collections; -import java.util.List; - -@Data -public class ExportSettings { - private List rules = Collections.emptyList(); - private List webSocketRules = Collections.emptyList(); - private List variables = Collections.emptyList(); -} diff --git a/extension/src/main/java/synfron/reshaper/burp/core/settings/GeneralSettings.java b/extension/src/main/java/synfron/reshaper/burp/core/settings/GeneralSettings.java index efd736f..411790b 100644 --- a/extension/src/main/java/synfron/reshaper/burp/core/settings/GeneralSettings.java +++ b/extension/src/main/java/synfron/reshaper/burp/core/settings/GeneralSettings.java @@ -4,7 +4,7 @@ import lombok.Data; import lombok.Getter; import synfron.reshaper.burp.core.BurpTool; -import synfron.reshaper.burp.core.Tab; +import synfron.reshaper.burp.core.WorkspaceTab; import synfron.reshaper.burp.core.events.IEventListener; import synfron.reshaper.burp.core.events.PropertyChangedArgs; import synfron.reshaper.burp.core.events.PropertyChangedEvent; @@ -20,11 +20,12 @@ public class GeneralSettings { private boolean captureRepeater; private boolean captureIntruder; private boolean captureExtender; + private boolean captureSession = true; private boolean captureWebSockets = true; private boolean enableEventDiagnostics; private int diagnosticValueMaxLength = 200; private boolean enableSanityCheckWarnings = true; - private boolean logInExtenderOutput = true; + private boolean logInExtenderOutput = false; private int logTabCharacterLimit = 1000000; private String defaultEncoding = Encoder.getDefaultEncoderName(); private ImportMethod importMethod = ImportMethod.File; @@ -34,7 +35,7 @@ public class GeneralSettings { private String lastExportFileName = "ReshaperBackup.json"; private HashSet hiddenThenTypes = new HashSet<>(); private HashSet hiddenWhenTypes = new HashSet<>(); - private HashSet hiddenTabs = new HashSet<>(); + private HashSet hiddenTabs = new HashSet<>(); @Getter @JsonIgnore private final PropertyChangedEvent propertyChangedEvent = new PropertyChangedEvent(); @@ -49,6 +50,7 @@ public void importSettings(GeneralSettings other) { this.captureIntruder = other.captureIntruder; this.captureExtender = other.captureExtender; this.captureWebSockets = other.captureWebSockets; + this.captureSession = other.captureSession; this.enableEventDiagnostics = other.enableEventDiagnostics; this.diagnosticValueMaxLength = other.diagnosticValueMaxLength; this.enableSanityCheckWarnings = other.enableSanityCheckWarnings; @@ -73,7 +75,7 @@ public boolean isCapture(BurpTool burpTool) { case Scanner -> isCaptureScanner(); case Intruder -> isCaptureIntruder(); case Extender -> isCaptureExtender(); - case Session -> true; + case Session -> isCaptureSession(); case WebSockets -> isCaptureWebSockets(); }; } @@ -117,6 +119,11 @@ public void setCaptureExtender(boolean captureExtender) { propertyChanged("captureExtender", captureExtender); } + public void setCaptureSession(boolean captureSession) { + this.captureSession = captureSession; + propertyChanged("captureSession", captureSession); + } + public void setCaptureWebSockets(boolean captureWebSockets) { this.captureWebSockets = captureWebSockets; propertyChanged("captureWebSockets", captureWebSockets); @@ -182,7 +189,7 @@ public void setHiddenWhenTypes(HashSet hiddenWhenTypes) { propertyChanged("hiddenWhenTypes", hiddenWhenTypes); } - public void setHiddenTabs(HashSet hiddenTabs) { + public void setHiddenTabs(HashSet hiddenTabs) { this.hiddenTabs = hiddenTabs; propertyChanged("hiddenTabs", hiddenTabs); } diff --git a/extension/src/main/java/synfron/reshaper/burp/core/settings/SettingsManager.java b/extension/src/main/java/synfron/reshaper/burp/core/settings/SettingsManager.java index 84fa7e6..99ac0b0 100644 --- a/extension/src/main/java/synfron/reshaper/burp/core/settings/SettingsManager.java +++ b/extension/src/main/java/synfron/reshaper/burp/core/settings/SettingsManager.java @@ -1,51 +1,40 @@ package synfron.reshaper.burp.core.settings; -import burp.BurpExtender; import com.fasterxml.jackson.core.type.TypeReference; -import synfron.reshaper.burp.core.ProtocolType; import synfron.reshaper.burp.core.exceptions.WrappedException; -import synfron.reshaper.burp.core.rules.Rule; import synfron.reshaper.burp.core.rules.RulesRegistry; import synfron.reshaper.burp.core.utils.Serializer; import synfron.reshaper.burp.core.vars.GlobalVariables; -import synfron.reshaper.burp.core.vars.Variable; import synfron.reshaper.burp.core.vars.Variables; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.util.Arrays; -import java.util.List; public class SettingsManager { - public static void importSettings(File file, boolean overwriteDuplicates) { + public static void importSettings(Workspace workspace, File file, boolean overwriteDuplicates) { try { - importSettings(Files.readString(file.toPath()), overwriteDuplicates); + importSettings(workspace, Files.readString(file.toPath()), overwriteDuplicates); } catch (IOException e) { throw new WrappedException(e); } } - public static void importSettings(String settingsJson, boolean overwriteDuplicates) { - ExportSettings exportSettings = Serializer.deserialize( + public static void importSettings(Workspace workspace, String settingsJson, boolean overwriteDuplicates) { + WorkspaceDataExport workspaceExport = Serializer.deserialize( settingsJson, new TypeReference<>() {} ); - getGlobalVariables().importVariables(exportSettings.getVariables(), overwriteDuplicates); - getHttpRulesRegistry().importRules(exportSettings.getRules(), overwriteDuplicates); - getWebSocketRulesRegistry().importRules(exportSettings.getWebSocketRules(), overwriteDuplicates); + workspaceExport.copyTo(workspace, overwriteDuplicates); } - public static void exportSettings(File file, List variables, List httpRules, List webSocketRules) { + public static void exportWorkspaceData(Workspace workspace, File file, WorkspaceDataExport workspaceExport) { try { - ExportSettings exportSettings = new ExportSettings(); - exportSettings.setVariables(variables); - exportSettings.setRules(httpRules); - exportSettings.setWebSocketRules(webSocketRules); - String data = switch (BurpExtender.getGeneralSettings().getExportMethod()) { - case Json -> Serializer.serialize(exportSettings, false); - case Yaml -> Serializer.serializeYaml(exportSettings, false); + String data = switch (workspace.getGeneralSettings().getExportMethod()) { + case Json -> Serializer.serialize(workspaceExport, false); + case Yaml -> Serializer.serializeYaml(workspaceExport, false); }; Files.writeString(file.toPath(), data); } catch (IOException e) { @@ -53,35 +42,35 @@ public static void exportSettings(File file, List variables, List() {})); - getGlobalVariables().importVariables(Storage.get("Reshaper.variables", new TypeReference<>() {}), false); - getHttpRulesRegistry().importRules(Storage.get("Reshaper.rules", new TypeReference<>() {}), false); - getWebSocketRulesRegistry().importRules(Storage.get("Reshaper.webSocketRules", new TypeReference<>() {}), false); - } - - public static void saveSettings() { - Storage.store("Reshaper.generalSettings", BurpExtender.getGeneralSettings()); - Storage.store("Reshaper.variables", getGlobalVariables().exportVariables()); - Storage.store("Reshaper.rules", getHttpRulesRegistry().exportRules()); - Storage.store("Reshaper.webSocketRules", getWebSocketRulesRegistry().exportRules()); - } + public static Workspaces loadPersistentWorkspaces() { + WorkspacesExport workspacesExport = Storage.get("Reshaper.workspaces", new TypeReference<>() {}); + if (workspacesExport != null) { + return workspacesExport.toWorkspaces(); + } - private static GlobalVariables getGlobalVariables() { - return GlobalVariables.get(); + Workspaces workspaces = new Workspaces(); + workspaces.initialize(); + loadLegacySettings(workspaces.getWorkspaces().getFirst()); + return workspaces; } - private static RulesRegistry getHttpRulesRegistry() { - return BurpExtender.getRulesRegistry(ProtocolType.Http); + private static void loadLegacySettings(Workspace workspace) { + workspace.getGeneralSettings().importSettings(Storage.get("Reshaper.generalSettings", new TypeReference<>() {})); + workspace.getGlobalVariables().importVariables(Storage.get("Reshaper.variables", new TypeReference<>() {}), false); + workspace.getHttpRulesRegistry().importRules(Storage.get("Reshaper.rules", new TypeReference<>() {}), false); + workspace.getWebSocketRulesRegistry().importRules(Storage.get("Reshaper.webSocketRules", new TypeReference<>() {}), false); } - private static RulesRegistry getWebSocketRulesRegistry() { - return BurpExtender.getRulesRegistry(ProtocolType.WebSocket); + public static void savePersistentWorkspaces(WorkspacesExport workspacesExport) { + Storage.store("Reshaper.workspaces", workspacesExport); } - public static void resetData() { - Arrays.stream(getHttpRulesRegistry().getRules()).forEach(rule -> getHttpRulesRegistry().deleteRule(rule)); - Arrays.stream(getWebSocketRulesRegistry().getRules()).forEach(rule -> getWebSocketRulesRegistry().deleteRule(rule)); - GlobalVariables.get().getValues().forEach(variable -> GlobalVariables.get().remove(Variables.asKey(variable.getName(), variable.isList()))); + public static void resetData(Workspace workspace) { + RulesRegistry httpRulesRegistry = workspace.getHttpRulesRegistry(); + RulesRegistry webSocketRulesRegistry = workspace.getWebSocketRulesRegistry(); + GlobalVariables globalVariables = workspace.getGlobalVariables(); + Arrays.stream(httpRulesRegistry.getRules()).forEach(httpRulesRegistry::deleteRule); + Arrays.stream(webSocketRulesRegistry.getRules()).forEach(webSocketRulesRegistry::deleteRule); + globalVariables.getValues().forEach(variable -> globalVariables.remove(Variables.asKey(variable.getName(), variable.isList()))); } } \ No newline at end of file diff --git a/extension/src/main/java/synfron/reshaper/burp/core/settings/Workspace.java b/extension/src/main/java/synfron/reshaper/burp/core/settings/Workspace.java new file mode 100644 index 0000000..02a1715 --- /dev/null +++ b/extension/src/main/java/synfron/reshaper/burp/core/settings/Workspace.java @@ -0,0 +1,85 @@ +package synfron.reshaper.burp.core.settings; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.Setter; +import synfron.reshaper.burp.core.HttpConnector; +import synfron.reshaper.burp.core.ProtocolType; +import synfron.reshaper.burp.core.WebSocketConnector; +import synfron.reshaper.burp.core.events.IEventListener; +import synfron.reshaper.burp.core.events.MessageEvent; +import synfron.reshaper.burp.core.events.PropertyChangedArgs; +import synfron.reshaper.burp.core.events.PropertyChangedEvent; +import synfron.reshaper.burp.core.rules.RulesRegistry; +import synfron.reshaper.burp.core.vars.GlobalVariables; + +import java.util.UUID; + +public class Workspace { + @Getter + private final GeneralSettings generalSettings = new GeneralSettings(); + @Getter + private final GlobalVariables globalVariables = new GlobalVariables(); + @Getter + private final HttpConnector httpConnector = new HttpConnector(this); + @Getter + private final WebSocketConnector webSocketConnector = new WebSocketConnector(this); + @Getter @Setter + private boolean defaultWorkspace = false; + @Getter + private int version = 1; + @Getter @Setter + private boolean legacyLoad = false; + @Getter + private String workspaceName; + @Getter + private final UUID workspaceUuid; + @Getter + private final PropertyChangedEvent propertyChangedEvent = new PropertyChangedEvent(); + @Getter + private final MessageEvent messageEvent = new MessageEvent(); + + public Workspace(@JsonProperty UUID workspaceUuid, @JsonProperty String workspaceName) { + this.workspaceUuid = workspaceUuid; + this.workspaceName = workspaceName; + } + + public RulesRegistry getRulesRegistry(ProtocolType protocolType) { + return switch (protocolType) { + case Http -> httpConnector.getRulesEngine().getRulesRegistry(); + case WebSocket -> webSocketConnector.getRulesEngine().getRulesRegistry(); + case Any -> throw new IllegalArgumentException(protocolType + " not valid in this context"); + }; + } + + public RulesRegistry getHttpRulesRegistry() { + return getRulesRegistry(ProtocolType.Http); + } + + public RulesRegistry getWebSocketRulesRegistry() { + return getRulesRegistry(ProtocolType.WebSocket); + } + + public void setWorkspaceName(String workspaceName) { + this.workspaceName = workspaceName; + propertyChanged("workspaceName", workspaceName); + } + + private void propertyChanged(String name, Object value) { + propertyChangedEvent.invoke(new PropertyChangedArgs(this, name, value)); + } + + public Workspace withListener(IEventListener listener) { + propertyChangedEvent.add(listener); + return this; + } + + public void unload() { + httpConnector.unload(); + webSocketConnector.unload(); + } + + public void load() { + httpConnector.init(); + } +} diff --git a/extension/src/main/java/synfron/reshaper/burp/core/settings/WorkspaceDataExport.java b/extension/src/main/java/synfron/reshaper/burp/core/settings/WorkspaceDataExport.java new file mode 100644 index 0000000..7d39a48 --- /dev/null +++ b/extension/src/main/java/synfron/reshaper/burp/core/settings/WorkspaceDataExport.java @@ -0,0 +1,41 @@ +package synfron.reshaper.burp.core.settings; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import synfron.reshaper.burp.core.rules.Rule; +import synfron.reshaper.burp.core.vars.Variable; + +import java.io.Serializable; +import java.util.Collections; +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class WorkspaceDataExport implements Serializable { + private int version = 0; + private List rules = Collections.emptyList(); + private List webSocketRules = Collections.emptyList(); + private List variables = Collections.emptyList(); + + public WorkspaceDataExport(Workspace workspace) { + version = 1; + rules = workspace.getHttpRulesRegistry().exportRules(); + webSocketRules = workspace.getWebSocketRulesRegistry().exportRules(); + variables = workspace.getGlobalVariables().exportVariables(); + } + + public WorkspaceDataExport(List rules, List webSocketRules, List variables) { + this.version = 1; + this.rules = rules; + this.webSocketRules = webSocketRules; + this.variables = variables; + } + + public void copyTo(Workspace workspace, boolean overwriteDuplicates) { + workspace.getGlobalVariables().importVariables(variables, overwriteDuplicates); + workspace.getHttpRulesRegistry().importRules(rules, overwriteDuplicates); + workspace.getWebSocketRulesRegistry().importRules(webSocketRules, overwriteDuplicates); + } +} diff --git a/extension/src/main/java/synfron/reshaper/burp/core/settings/WorkspaceExport.java b/extension/src/main/java/synfron/reshaper/burp/core/settings/WorkspaceExport.java new file mode 100644 index 0000000..426f6fb --- /dev/null +++ b/extension/src/main/java/synfron/reshaper/burp/core/settings/WorkspaceExport.java @@ -0,0 +1,34 @@ +package synfron.reshaper.burp.core.settings; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.util.UUID; + +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class WorkspaceExport extends WorkspaceDataExport { + private UUID workspaceUuid; + private String workspaceName; + private GeneralSettings generalSettings; + + public WorkspaceExport(Workspace workspace) { + super(workspace); + workspaceUuid = workspace.getWorkspaceUuid(); + workspaceName = workspace.getWorkspaceName(); + generalSettings = workspace.getGeneralSettings(); + } + + public void copyTo(Workspace workspace, boolean overwriteDuplicates) { + workspace.getGeneralSettings().importSettings(generalSettings); + super.copyTo(workspace, overwriteDuplicates); + } + + public Workspace toWorkspace() { + Workspace workspace = new Workspace(workspaceUuid, workspaceName); + copyTo(workspace, false); + return workspace; + } +} diff --git a/extension/src/main/java/synfron/reshaper/burp/core/settings/Workspaces.java b/extension/src/main/java/synfron/reshaper/burp/core/settings/Workspaces.java new file mode 100644 index 0000000..f0cb65c --- /dev/null +++ b/extension/src/main/java/synfron/reshaper/burp/core/settings/Workspaces.java @@ -0,0 +1,102 @@ +package synfron.reshaper.burp.core.settings; + +import lombok.Data; +import lombok.Getter; +import synfron.reshaper.burp.core.events.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@Data +public class Workspaces { + private UUID defaultWorkspaceUuid = null; + + private boolean enabled; + private List workspaces = new ArrayList<>(); + private ThreadLocal currentWorkspace = new ThreadLocal<>(); + private static Workspaces instance; + @Getter + private final PropertyChangedEvent propertyChangedEvent = new PropertyChangedEvent(); + @Getter + private final CollectionChangedEvent collectionChangedEvent = new CollectionChangedEvent(); + + public static Workspaces get() { + if (instance == null) { + instance = SettingsManager.loadPersistentWorkspaces(); + } + return instance; + } + + public void initialize() { + if (workspaces.isEmpty()) { + workspaces.add(new Workspace(UUID.randomUUID(), "Default Workspace")); + } + if (defaultWorkspaceUuid == null) { + defaultWorkspaceUuid = workspaces.getFirst().getWorkspaceUuid(); + } + } + + public void add(Workspace workspace) { + workspaces.add(workspace); + collectionChangedEvent.invoke(new CollectionChangedArgs(this, CollectionChangedAction.Add, workspace.getWorkspaceUuid(), workspace)); + } + + public void delete(Workspace workspace) { + if (workspaces.size() > 1) { + if (workspace.getWorkspaceUuid().equals(defaultWorkspaceUuid)) { + defaultWorkspaceUuid = workspaces.stream() + .map(Workspace::getWorkspaceUuid) + .filter(workspaceUuid -> !workspaceUuid.equals(workspace.getWorkspaceUuid())) + .findFirst() + .get(); + } + workspace.unload(); + workspaces.remove(workspace); + collectionChangedEvent.invoke(new CollectionChangedArgs(this, CollectionChangedAction.Remove, workspace.getWorkspaceUuid(), workspace)); + } + } + + public Workspace getDefault() { + return workspaces.stream() + .filter(workspace -> workspace.getWorkspaceUuid().equals(defaultWorkspaceUuid)) + .findFirst().orElse(workspaces.getFirst()); + } + + public Workspace get(UUID workspaceUuid) { + return workspaces.stream().filter(workspace -> workspace.getWorkspaceUuid().equals(workspaceUuid)).findFirst().orElse(null); + } + + public void setCurrentWorkspace(Workspace workspace) { + currentWorkspace.set(workspace); + } + + public Workspace getCurrentWorkspace() { + return currentWorkspace.get(); + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + propertyChanged("enabled", enabled); + } + + private void propertyChanged(String name, Object value) { + propertyChangedEvent.invoke(new PropertyChangedArgs(this, name, value)); + } + + public Workspaces withListener(IEventListener listener) { + propertyChangedEvent.add(listener); + return this; + } + + public Workspaces withCollectionListener(IEventListener listener) { + collectionChangedEvent.add(listener); + return this; + } + + public void unload() { + SettingsManager.savePersistentWorkspaces(new WorkspacesExport(get())); + get().getWorkspaces().forEach(Workspace::unload); + instance = null; + } +} diff --git a/extension/src/main/java/synfron/reshaper/burp/core/settings/WorkspacesExport.java b/extension/src/main/java/synfron/reshaper/burp/core/settings/WorkspacesExport.java new file mode 100644 index 0000000..98f5de0 --- /dev/null +++ b/extension/src/main/java/synfron/reshaper/burp/core/settings/WorkspacesExport.java @@ -0,0 +1,34 @@ +package synfron.reshaper.burp.core.settings; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.Collections; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +@Data +@NoArgsConstructor +public class WorkspacesExport implements Serializable { + private int version = 0; + private UUID defaultWorkspaceUuid; + private boolean enabled; + private List workspaces = Collections.emptyList(); + + public WorkspacesExport(Workspaces workspaces) { + version = 1; + defaultWorkspaceUuid = workspaces.getDefaultWorkspaceUuid(); + enabled = workspaces.isEnabled(); + this.workspaces = workspaces.getWorkspaces().stream().map(WorkspaceExport::new).toList(); + } + + public Workspaces toWorkspaces() { + Workspaces workspaces = new Workspaces(); + workspaces.setDefaultWorkspaceUuid(defaultWorkspaceUuid); + workspaces.setEnabled(enabled); + workspaces.setWorkspaces(this.workspaces.stream().map(WorkspaceExport::toWorkspace).collect(Collectors.toList())); + return workspaces; + } +} diff --git a/extension/src/main/java/synfron/reshaper/burp/core/utils/Log.java b/extension/src/main/java/synfron/reshaper/burp/core/utils/Log.java index 236ce86..ba42973 100644 --- a/extension/src/main/java/synfron/reshaper/burp/core/utils/Log.java +++ b/extension/src/main/java/synfron/reshaper/burp/core/utils/Log.java @@ -1,22 +1,40 @@ package synfron.reshaper.burp.core.utils; import burp.BurpExtender; -import burp.api.montoya.core.ByteArray; +import com.fasterxml.jackson.annotation.JsonIgnore; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; - -import java.nio.charset.StandardCharsets; +import synfron.reshaper.burp.core.events.MessageArgs; +import synfron.reshaper.burp.core.events.message.LogMessage; +import synfron.reshaper.burp.core.settings.Workspace; +import synfron.reshaper.burp.core.settings.Workspaces; @SuppressWarnings("FieldCanBeLocal") public class Log { private String message; private String exception; private Object payload; + @JsonIgnore + private final Workspace workspace; - private Log() {} + private Log(Workspace workspace) { + this.workspace = workspace; + } + + private Log() { + this.workspace = null; + } + + public static Log get(Workspace workspace) { + return new Log(workspace); + } public static Log get() { - return new Log(); + return get(Workspaces.get().getCurrentWorkspace()); + } + + public static Log getSystem() { + return get(null); } public Log withMessage(String message) { @@ -63,26 +81,21 @@ public void logErr() { } private void printOutput(String text, boolean isError) { - if (BurpExtender.getGeneralSettings().isLogInExtenderOutput()) { + if (workspace == null || workspace.getGeneralSettings().isLogInExtenderOutput()) { if (isError) { BurpExtender.getApi().logging().logToError(text); } else { BurpExtender.getApi().logging().logToOutput(text); } } - printToDisplay(text); + if (workspace != null) { + printToDisplay(text); + } } private void printToDisplay(String text) { - if (BurpExtender.getLogTextEditor() != null) { - BurpExtender.getLogTextEditor().setContents(BurpUtils.current.toByteArray( - TextUtils.bufferAppend( - new String(BurpExtender.getLogTextEditor().getContents().getBytes(), StandardCharsets.UTF_8), - text, - "\n", - BurpExtender.getGeneralSettings().getLogTabCharacterLimit() - ).getBytes(StandardCharsets.UTF_8) - )); + if (workspace != null) { + workspace.getMessageEvent().invoke(new MessageArgs(this, new LogMessage(text))); } } } diff --git a/extension/src/main/java/synfron/reshaper/burp/core/vars/GlobalVariables.java b/extension/src/main/java/synfron/reshaper/burp/core/vars/GlobalVariables.java index c0dc22a..373b9ab 100644 --- a/extension/src/main/java/synfron/reshaper/burp/core/vars/GlobalVariables.java +++ b/extension/src/main/java/synfron/reshaper/burp/core/vars/GlobalVariables.java @@ -10,14 +10,6 @@ public class GlobalVariables extends Variables { - private GlobalVariables() {} - - private static final GlobalVariables global = new GlobalVariables(); - - public static GlobalVariables get() { - return global; - } - public List exportVariables() { return variables.values().stream() .filter(Variable::isPersistent) diff --git a/extension/src/main/java/synfron/reshaper/burp/core/vars/VariableTag.java b/extension/src/main/java/synfron/reshaper/burp/core/vars/VariableTag.java index f9edeca..b8d825d 100644 --- a/extension/src/main/java/synfron/reshaper/burp/core/vars/VariableTag.java +++ b/extension/src/main/java/synfron/reshaper/burp/core/vars/VariableTag.java @@ -1,8 +1,8 @@ package synfron.reshaper.burp.core.vars; -import burp.BurpExtender; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; +import synfron.reshaper.burp.core.settings.Workspaces; import synfron.reshaper.burp.core.utils.Log; import synfron.reshaper.burp.core.utils.TextUtils; @@ -108,9 +108,7 @@ private static String getSpecialChar(String sequences) { try { return TextUtils.parseSpecialChars(sequences); } catch (Exception e) { - if (BurpExtender.getGeneralSettings().isEnableEventDiagnostics()) { - Log.get().withMessage(String.format("Invalid use of special character variable tag: %s", VariableTag.getTag(VariableSource.Special, sequences))).withException(e).logErr(); - } + Log.get(Workspaces.get().getCurrentWorkspace()).withMessage(String.format("Invalid use of special character variable tag: %s", VariableTag.getTag(VariableSource.Special, sequences))).withException(e).logErr(); } return null; } diff --git a/extension/src/main/java/synfron/reshaper/burp/core/vars/getters/AccessorVariableGetter.java b/extension/src/main/java/synfron/reshaper/burp/core/vars/getters/AccessorVariableGetter.java index 90b5a53..4b2f84f 100644 --- a/extension/src/main/java/synfron/reshaper/burp/core/vars/getters/AccessorVariableGetter.java +++ b/extension/src/main/java/synfron/reshaper/burp/core/vars/getters/AccessorVariableGetter.java @@ -27,7 +27,7 @@ public String getText(VariableSourceEntry variable, EventInfo eventInfo) { case Message -> getMessageVariable(eventInfo, variable.getParams()); case File -> getFileText(eventInfo, variable.getParams()); case Special -> variable.getParams().getFirst(); - case CookieJar -> getCookie(variable.getParams()); + case CookieJar -> getCookie(eventInfo, variable.getParams()); case Annotation -> getAnnotation(eventInfo, variable.getParams()); case Macro -> getMacro(eventInfo, variable.getParams()); case Generator -> generate(eventInfo, variable.getParams()); @@ -75,6 +75,7 @@ private String getMacro(EventInfo eventInfo, List variableNameParts) { if (macroItemIndex >= 0 && messageValue != null) { HttpRequestResponse requestResponse = CollectionUtils.elementAtOrDefault(eventInfo.getMacros(), macroItemIndex); HttpEventInfo macroEventInfo = new HttpEventInfo( + eventInfo.getWorkspace(), HttpDataDirection.Response, eventInfo.getBurpTool(), null, @@ -104,7 +105,7 @@ private String getAnnotation(EventInfo eventInfo, List name) { return null; } - private String getCookie(List parts) { + private String getCookie(EventInfo eventInfo, List parts) { try { String domain = parts.getFirst(); String name = parts.get(1); @@ -117,9 +118,7 @@ private String getCookie(List parts) { } } } catch (Exception e) { - if (BurpExtender.getGeneralSettings().isEnableEventDiagnostics()) { - Log.get().withMessage(String.format("Invalid use of cookie jar variable tag: %s", VariableTag.getTag(VariableSource.CookieJar, parts.toArray(String[]::new)))).withException(e).logErr(); - } + Log.get(eventInfo.getWorkspace()).withMessage(String.format("Invalid use of cookie jar variable tag: %s", VariableTag.getTag(VariableSource.CookieJar, parts.toArray(String[]::new)))).withException(e).logErr(); } return ""; } @@ -135,9 +134,7 @@ private String getFileText(EventInfo eventInfo, List variableNameParts) } return FileUtils.readFileToString(file, encoding); } catch (Exception e) { - if (eventInfo.getDiagnostics().isEnabled()) { - Log.get().withMessage(String.format("Error reading file with variable tag: %s", VariableTag.getTag(VariableSource.Special, variableNameParts.toArray(String[]::new)))).withException(e).logErr(); - } + Log.get(eventInfo.getWorkspace()).withMessage(String.format("Error reading file with variable tag: %s", VariableTag.getTag(VariableSource.Special, variableNameParts.toArray(String[]::new)))).withException(e).logErr(); } return null; } diff --git a/extension/src/main/java/synfron/reshaper/burp/core/vars/getters/CustomListVariableGetter.java b/extension/src/main/java/synfron/reshaper/burp/core/vars/getters/CustomListVariableGetter.java index 694f248..9573735 100644 --- a/extension/src/main/java/synfron/reshaper/burp/core/vars/getters/CustomListVariableGetter.java +++ b/extension/src/main/java/synfron/reshaper/burp/core/vars/getters/CustomListVariableGetter.java @@ -31,7 +31,7 @@ else if (StringUtils.isEmpty(place)) { private static ListVariable getVariable(VariableSourceEntry variable, String variableName, EventInfo eventInfo) { Variable value = switch (variable.getVariableSource()) { - case GlobalList -> (ListVariable) GlobalVariables.get().getOrDefault(Variables.asKey(variableName, true)); + case GlobalList -> (ListVariable) eventInfo.getWorkspace().getGlobalVariables().getOrDefault(Variables.asKey(variableName, true)); case EventList -> (ListVariable) eventInfo.getVariables().getOrDefault(Variables.asKey(variableName, true)); case SessionList -> (ListVariable) eventInfo.getSessionVariables().getOrDefault(Variables.asKey(variableName, true)); default -> null; diff --git a/extension/src/main/java/synfron/reshaper/burp/core/vars/getters/CustomVariableGetter.java b/extension/src/main/java/synfron/reshaper/burp/core/vars/getters/CustomVariableGetter.java index 3fb366c..c079c00 100644 --- a/extension/src/main/java/synfron/reshaper/burp/core/vars/getters/CustomVariableGetter.java +++ b/extension/src/main/java/synfron/reshaper/burp/core/vars/getters/CustomVariableGetter.java @@ -2,7 +2,6 @@ import synfron.reshaper.burp.core.messages.EventInfo; import synfron.reshaper.burp.core.utils.TextUtils; -import synfron.reshaper.burp.core.vars.GlobalVariables; import synfron.reshaper.burp.core.vars.Variable; import synfron.reshaper.burp.core.vars.VariableSourceEntry; import synfron.reshaper.burp.core.vars.Variables; @@ -12,7 +11,7 @@ public class CustomVariableGetter extends VariableGetter { @Override public String getText(VariableSourceEntry variable, EventInfo eventInfo) { Variable value = switch (variable.getVariableSource()) { - case Global -> GlobalVariables.get().getOrDefault(Variables.asKey(variable.getParams().getFirst(), false)); + case Global -> eventInfo.getWorkspace().getGlobalVariables().getOrDefault(Variables.asKey(variable.getParams().getFirst(), false)); case Event -> eventInfo.getVariables().getOrDefault(Variables.asKey(variable.getParams().getFirst(), false)); case Session -> eventInfo.getSessionVariables().getOrDefault(Variables.asKey(variable.getParams().getFirst(), false)); default -> null; diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/components/IFormComponent.java b/extension/src/main/java/synfron/reshaper/burp/ui/components/IFormComponent.java index 68e31d0..07003e3 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/components/IFormComponent.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/components/IFormComponent.java @@ -3,9 +3,14 @@ import net.miginfocom.swing.MigLayout; import org.apache.commons.lang3.StringUtils; import synfron.reshaper.burp.core.ProtocolType; +import synfron.reshaper.burp.core.settings.Workspace; import synfron.reshaper.burp.core.vars.VariableTag; import synfron.reshaper.burp.ui.components.rules.RuleOperationComponent; import synfron.reshaper.burp.ui.components.rules.wizard.vars.VariableTagWizardOptionPane; +import synfron.reshaper.burp.ui.components.shared.PromptTextField; +import synfron.reshaper.burp.ui.components.shared.TextPrompt; +import synfron.reshaper.burp.ui.components.workspaces.IWorkspaceDependentComponent; +import synfron.reshaper.burp.ui.components.workspaces.IWorkspaceHost; import synfron.reshaper.burp.ui.models.rules.wizard.vars.VariableTagWizardModel; import synfron.reshaper.burp.ui.utils.ActionPerformedListener; import synfron.reshaper.burp.ui.utils.ModalPrompter; @@ -20,13 +25,32 @@ import static java.awt.Component.LEFT_ALIGNMENT; import static java.awt.Component.TOP_ALIGNMENT; -public interface IFormComponent { +public interface IFormComponent extends IWorkspaceDependentComponent, IWorkspaceHost { + + @Override + default Workspace getWorkspace() { + return getHostedWorkspace(getComponent()); + } + + T getComponent(); + + default Workspace getHostedWorkspace() { + return getHostedWorkspace(getComponent()); + } default JPanel getLabeledField(String label, Component innerComponent) { return getLabeledField(label, innerComponent, true); } default JPanel getLabeledField(String label, Component innerComponent, boolean span) { + return getLabeledField(new JLabel(label), innerComponent, span); + } + + default JPanel getLabeledField(JLabel label, Component innerComponent) { + return getLabeledField(label, innerComponent, true); + } + + default JPanel getLabeledField(JLabel label, Component innerComponent, boolean span) { JPanel container = new JPanel(); container.setLayout(new MigLayout()); container.setBorder(null); @@ -35,7 +59,7 @@ default JPanel getLabeledField(String label, Component innerComponent, boolean s container.setBorder(BorderFactory.createEmptyBorder(0, -3, 0, 0)); } - container.add(new JLabel(label), "wrap"); + container.add(label, "wrap"); container.add(innerComponent, span ? "grow, push, span" : ""); return container; } @@ -80,6 +104,23 @@ default JTextField createTextField(boolean supportsVariableTags) { return addContextMenu(addUndo(textField), supportsVariableTags, getProtocolType()); } + private T createTextField(T textField, boolean supportsVariableTags) { + textField.setColumns(20); + textField.setMaximumSize(new Dimension(textField.getPreferredSize().width, textField.getPreferredSize().height)); + textField.setAlignmentX(LEFT_ALIGNMENT); + return addContextMenu(addUndo(textField), supportsVariableTags, getProtocolType()); + } + + default PromptTextField createPromptTextField(String placeholder, boolean supportsVariableTags) { + PromptTextField textField = new PromptTextField(placeholder); + textField.getTextPrompt().setShow(TextPrompt.Show.FOCUS_LOST); + textField.getTextPrompt().changeStyle(Font.ITALIC); + textField.setColumns(20); + textField.setMaximumSize(new Dimension(textField.getPreferredSize().width, textField.getPreferredSize().height)); + textField.setAlignmentX(LEFT_ALIGNMENT); + return addContextMenu(addUndo(textField), supportsVariableTags, getProtocolType()); + } + default Component getPaddedButton(JButton button) { JPanel outerContainer = new JPanel(new FlowLayout(FlowLayout.LEFT)); outerContainer.setAlignmentX(LEFT_ALIGNMENT); @@ -96,7 +137,7 @@ default T createTextComponent(T textComponent) { return addContextMenu(addUndo(textComponent), false, getProtocolType()); } - private static T addContextMenu(T textComponent, boolean supportsVariableTags, ProtocolType protocolType) { + private T addContextMenu(T textComponent, boolean supportsVariableTags, ProtocolType protocolType) { JPopupMenu popupMenu = new JPopupMenu(); Action cut = new DefaultEditorKit.CutAction(); @@ -115,7 +156,7 @@ private static T addContextMenu(T textComponent, bool popupMenu.add(selectAll); if (supportsVariableTags) { - Action addVariableTag = new ActionPerformedListener(event -> insertVariableTag(textComponent, protocolType)); + Action addVariableTag = new ActionPerformedListener(event -> createEntryPoint(() -> insertVariableTag(textComponent, protocolType))); addVariableTag.putValue(Action.NAME, "Insert Variable Tag"); popupMenu.addSeparator(); popupMenu.add(addVariableTag); diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/components/LogsComponent.java b/extension/src/main/java/synfron/reshaper/burp/ui/components/LogsComponent.java index 3d69d66..c964a5f 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/components/LogsComponent.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/components/LogsComponent.java @@ -1,19 +1,48 @@ package synfron.reshaper.burp.ui.components; import burp.BurpExtender; +import burp.api.montoya.ui.editor.EditorOptions; import burp.api.montoya.ui.editor.WebSocketMessageEditor; +import lombok.Getter; +import synfron.reshaper.burp.core.events.IEventListener; +import synfron.reshaper.burp.core.events.MessageArgs; +import synfron.reshaper.burp.core.events.message.LogMessage; +import synfron.reshaper.burp.core.events.message.MessageType; +import synfron.reshaper.burp.core.settings.Workspace; import synfron.reshaper.burp.core.utils.BurpUtils; +import synfron.reshaper.burp.core.utils.TextUtils; +import synfron.reshaper.burp.ui.components.workspaces.IWorkspaceDependent; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; +import java.nio.charset.StandardCharsets; -public class LogsComponent extends JPanel { +public class LogsComponent extends JPanel implements IWorkspaceDependent { - private final WebSocketMessageEditor textEditor = BurpExtender.getLogTextEditor(); + @Getter + private final WebSocketMessageEditor textEditor = BurpExtender.getApi().userInterface().createWebSocketMessageEditor(EditorOptions.READ_ONLY); + private final Workspace workspace; + private final IEventListener onMessageListener = this::onMessage; public LogsComponent() { + this.workspace = getHostedWorkspace(); initComponents(); + + workspace.getMessageEvent().add(onMessageListener); + } + + private void onMessage(MessageArgs messageArgs) { + if (messageArgs.getData().getMessageType() == MessageType.Log && messageArgs.getData() instanceof LogMessage logMessage) { + textEditor.setContents(BurpUtils.current.toByteArray( + TextUtils.bufferAppend( + new String(textEditor.getContents().getBytes(), StandardCharsets.UTF_8), + logMessage.getText(), + "\n", + workspace.getGeneralSettings().getLogTabCharacterLimit() + ).getBytes(StandardCharsets.UTF_8) + )); + } } private void initComponents() { diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/components/ReshaperComponent.java b/extension/src/main/java/synfron/reshaper/burp/ui/components/ReshaperComponent.java index f33862c..9163352 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/components/ReshaperComponent.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/components/ReshaperComponent.java @@ -5,74 +5,243 @@ */ package synfron.reshaper.burp.ui.components; -import burp.BurpExtender; -import synfron.reshaper.burp.core.Tab; -import synfron.reshaper.burp.core.ProtocolType; +import lombok.Getter; +import synfron.reshaper.burp.core.events.CollectionChangedArgs; import synfron.reshaper.burp.core.events.IEventListener; import synfron.reshaper.burp.core.events.PropertyChangedArgs; -import synfron.reshaper.burp.ui.components.rules.RulesTabComponent; -import synfron.reshaper.burp.ui.components.settings.SettingsTabComponent; -import synfron.reshaper.burp.ui.components.vars.VariablesTabComponent; +import synfron.reshaper.burp.core.settings.Workspace; +import synfron.reshaper.burp.core.settings.Workspaces; +import synfron.reshaper.burp.ui.components.workspaces.WorkspaceComponent; +import synfron.reshaper.burp.ui.components.workspaces.WorkspaceNameOptionPane; +import synfron.reshaper.burp.ui.models.workspaces.WorkspaceNameModel; +import synfron.reshaper.burp.ui.utils.ModalPrompter; import javax.swing.*; -import javax.swing.event.ChangeEvent; +import javax.swing.event.PopupMenuEvent; +import javax.swing.event.PopupMenuListener; import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; import java.util.HashMap; +import java.util.Objects; +import java.util.UUID; public class ReshaperComponent extends JPanel { - private final IEventListener generalSettingsChangedListener = this::onGeneralSettingsChanged; + @Getter + private final Workspaces workspaces; private JTabbedPane tabs; + private final IEventListener workspacesPropertyChangedListener = this::onWorkspacesPropertyChanged; + private final IEventListener workspacesChangedListener = this::onWorkspacesChangedListener; - public ReshaperComponent() { + public ReshaperComponent(Workspaces workspaces) { + this.workspaces = workspaces; initComponents(); } private void initComponents() { setLayout(new BorderLayout()); - add(getTabs()); + setBody(); - BurpExtender.getGeneralSettings().withListener(generalSettingsChangedListener); + workspaces.withListener(workspacesPropertyChangedListener) + .withCollectionListener(workspacesChangedListener); } - private JTabbedPane getTabs() { - tabs = new JTabbedPane(); + private void onWorkspacesChangedListener(CollectionChangedArgs collectionChangedArgs) { + switch (collectionChangedArgs.getAction()) { + case Add -> { + addTab(new WorkspaceComponent((Workspace) collectionChangedArgs.getItem())); + tabs.setSelectedComponent(tabs.getComponentAt(tabs.getTabCount() - 1)); + } + case Remove -> { + for (int tabIndex = 0; tabIndex < tabs.getTabCount(); tabIndex++) { + WorkspaceComponent workspaceComponent = (WorkspaceComponent) tabs.getComponentAt(tabIndex); + if (workspaceComponent.getWorkspace() == collectionChangedArgs.getItem()) { + tabs.remove(workspaceComponent); + } + } + } + } + } - addOrRemoveTabs(); - return tabs; + private void onWorkspacesPropertyChanged(PropertyChangedArgs propertyChangedArgs) { + if (propertyChangedArgs.getName().equals("enabled")) { + setBody(); + } else if (propertyChangedArgs.getName().equals("workspaces")) { + addOrRemoveTabs(null); + } } - private void onGeneralSettingsChanged(PropertyChangedArgs propertyChangedArgs) { - if (propertyChangedArgs.getName().equals("hiddenTabs")) { - addOrRemoveTabs(); + private void setBody() { + WorkspaceComponent workspaceComponent = null; + if (getComponentCount() > 0) { + workspaceComponent = getCurrentWorkspaceComponent(); + removeAll(); + } + if (workspaceComponent == null) { + add(workspaces.isEnabled() ? getTabs(null) : new WorkspaceComponent(workspaces.getDefault())); + } else { + add(workspaces.isEnabled() ? getTabs(workspaceComponent) : workspaceComponent); } } - private void addOrRemoveTabs() { - HashMap existingTabs = new HashMap<>(); + private WorkspaceComponent getCurrentWorkspaceComponent() { + Component currentComponent = getComponent(0); + if (currentComponent instanceof WorkspaceComponent workspaceComponent) { + return workspaceComponent; + } else if (currentComponent instanceof JTabbedPane tabbedPane) { + return (WorkspaceComponent)tabbedPane.getComponentAt(0); + } + return null; + } + + private JTabbedPane getTabs(WorkspaceComponent currentWorkspaceComponent) { + tabs = new JTabbedPane(); + addOrRemoveTabs(currentWorkspaceComponent); + return tabs; + } + private void addOrRemoveTabs(WorkspaceComponent currentWorkspaceComponent) { + HashMap existingTabs = new HashMap<>(); for (int tabIndex = 0; tabIndex < tabs.getTabCount(); tabIndex++) { - Tab tab = Tab.byName(tabs.getTitleAt(tabIndex)); - existingTabs.put(tab, tabs.getComponentAt(tabIndex)); + WorkspaceComponent workspaceComponent = (WorkspaceComponent) tabs.getComponentAt(tabIndex); + Workspace workspace = workspaces.get(workspaceComponent.getWorkspace().getWorkspaceUuid()); + if (workspace != null) { + existingTabs.put(workspace.getWorkspaceUuid(), workspaceComponent); + } + } + if (currentWorkspaceComponent != null) { + existingTabs.put(currentWorkspaceComponent.getWorkspace().getWorkspaceUuid(), currentWorkspaceComponent); } Component activeTab = tabs.getSelectedComponent(); tabs.removeAll(); - for (Tab tab : Tab.values()) { - if (!tab.isHideable() || !BurpExtender.getGeneralSettings().getHiddenTabs().contains(tab)) { - Component component = existingTabs.get(tab); - if (component == null) { - component = switch (tab) { - case HttpRules -> new RulesTabComponent(ProtocolType.Http); - case WebSocketRules -> new RulesTabComponent(ProtocolType.WebSocket); - case GlobalVariables -> new VariablesTabComponent(); - case Logs -> new LogsComponent(); - case Settings -> new SettingsTabComponent(); - }; - } - tabs.addTab(tab.toString(), component); - if (activeTab == component) { - tabs.setSelectedComponent(component); - } + for (Workspace workspace : workspaces.getWorkspaces()) { + WorkspaceComponent component = existingTabs.get(workspace.getWorkspaceUuid()); + if (component == null) { + component = new WorkspaceComponent(workspace); + } + addTab(component); + if ( + (activeTab == null && Objects.equals(workspace.getWorkspaceUuid(), workspaces.getDefaultWorkspaceUuid())) || + activeTab == component + ) { + tabs.setSelectedComponent(component); + } + } + } + + private void addTab(WorkspaceComponent component) { + tabs.addTab(component.getWorkspace().getWorkspaceName(), component); + JLabel label = new WorkspaceTabLabel(component).getComponent(); + tabs.setTabComponentAt(tabs.getTabCount() - 1, label); + } + + private class WorkspaceTabLabel { + private final WorkspaceComponent workspaceComponent; + private final Workspace workspace; + private JLabel label; + private final IEventListener workspacePropertyChangedListener = this::onWorkspacePropertyChanged; + + public WorkspaceTabLabel(WorkspaceComponent workspaceComponent) { + this.workspaceComponent = workspaceComponent; + this.workspace = workspaceComponent.getWorkspace(); + } + + private void onWorkspacePropertyChanged(PropertyChangedArgs propertyChangedArgs) { + if (propertyChangedArgs.getName().equals("workspaceName")) { + label.setText(workspace.getWorkspaceName()); + } + } + + public JLabel getComponent() { + if (label == null) { + label = new JLabel(workspace.getWorkspaceName()); + + JPopupMenu contextMenu = new JPopupMenu(); + JMenuItem rename = new JMenuItem("Rename"); + JMenuItem delete = new JMenuItem("Delete"); + JMenuItem defaultWorkspace = new JMenuItem("Set As Default"); + JMenuItem addNewWorkspace = new JMenuItem("Add New Workspace"); + JMenuItem disableWorkspaces = new JMenuItem("Disable Workspaces"); + + rename.addActionListener(this::onRename); + delete.addActionListener(this::onDelete); + defaultWorkspace.addActionListener(this::onDefaultWorkspace); + addNewWorkspace.addActionListener(this::onAddNewWorkspace); + disableWorkspaces.addActionListener(this::onDisableWorkspaces); + + contextMenu.add(rename); + contextMenu.add(delete); + contextMenu.add(defaultWorkspace); + contextMenu.add(addNewWorkspace); + contextMenu.add(disableWorkspaces); + + label.addMouseListener(new MouseListener() { + @Override + public void mouseClicked(MouseEvent e) { + if ((SwingUtilities.isLeftMouseButton(e))) { + tabs.setSelectedComponent(workspaceComponent); + } + } + @Override + public void mousePressed(MouseEvent e) {} + @Override + public void mouseReleased(MouseEvent e) {} + @Override + public void mouseEntered(MouseEvent e) {} + @Override + public void mouseExited(MouseEvent e) {} + }); + + workspace.withListener(workspacePropertyChangedListener); + + contextMenu.addPopupMenuListener(new PopupMenuListener() { + @Override + public void popupMenuWillBecomeVisible(PopupMenuEvent e) { + delete.setVisible(workspaces.getWorkspaces().size() > 1); + defaultWorkspace.setVisible(!Objects.equals(workspace.getWorkspaceUuid(), workspaces.getDefaultWorkspaceUuid())); + disableWorkspaces.setVisible(workspaces.getWorkspaces().size() == 1); + } + + @Override + public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { + } + + @Override + public void popupMenuCanceled(PopupMenuEvent e) { + } + }); + + label.setComponentPopupMenu(contextMenu); + } + return label; + } + + private void onDisableWorkspaces(ActionEvent actionEvent) { + workspaces.setEnabled(false); + } + + private void onAddNewWorkspace(ActionEvent actionEvent) { + Workspace workspace = new Workspace(UUID.randomUUID(), ""); + WorkspaceNameModel model = new WorkspaceNameModel(workspace, ignored -> Workspaces.get().add(workspace)); + ModalPrompter.open(model, ignored -> WorkspaceNameOptionPane.showDialog(model), true); + } + + private void onDefaultWorkspace(ActionEvent actionEvent) { + workspaces.setDefaultWorkspaceUuid(workspace.getWorkspaceUuid()); + } + + private void onRename(ActionEvent actionEvent) { + WorkspaceNameModel model = new WorkspaceNameModel(workspace); + ModalPrompter.open(model, ignored -> WorkspaceNameOptionPane.showDialog(model), true); + } + + private void onDelete(ActionEvent actionEvent) { + + int response = JOptionPane.showConfirmDialog(ReshaperComponent.this, String.format("Are you sure you want to delete workspace '%s'? All rules, variables, and settings in the workspace will be deleted.", workspace.getWorkspaceName()), "Delete Workspace", JOptionPane.YES_NO_OPTION); + if (response == JOptionPane.YES_OPTION) { + workspaces.delete(workspace); } } } diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/RuleComponent.java b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/RuleComponent.java index c9b93c4..8a7ef31 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/RuleComponent.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/RuleComponent.java @@ -1,12 +1,19 @@ package synfron.reshaper.burp.ui.components.rules; +import lombok.Getter; import lombok.SneakyThrows; +import net.miginfocom.swing.MigLayout; import synfron.reshaper.burp.core.ProtocolType; import synfron.reshaper.burp.core.events.IEventListener; import synfron.reshaper.burp.core.events.PropertyChangedArgs; +import synfron.reshaper.burp.core.settings.Workspace; +import synfron.reshaper.burp.core.utils.Log; import synfron.reshaper.burp.ui.components.IFormComponent; +import synfron.reshaper.burp.ui.components.rules.wizard.matchreplace.MatchAndReplaceWizardOptionPane; import synfron.reshaper.burp.ui.models.rules.RuleModel; +import synfron.reshaper.burp.ui.models.rules.wizard.matchreplace.MatchAndReplaceWizardModel; import synfron.reshaper.burp.ui.utils.DocumentActionListener; +import synfron.reshaper.burp.ui.utils.ModalPrompter; import javax.swing.*; import java.awt.*; @@ -20,6 +27,8 @@ public class RuleComponent extends JPanel implements IFormComponent { private final ProtocolType protocolType; private final RuleModel model; + @Getter + private final Workspace workspace; private JCheckBox isEnabled; private JCheckBox autoRun; private JTextField ruleName; @@ -27,6 +36,7 @@ public class RuleComponent extends JPanel implements IFormComponent { private final IEventListener modelPropertyChangedListener = this::onModelPropertyChanged; public RuleComponent(ProtocolType protocolType, RuleModel model) { + this.workspace = getHostedWorkspace(); this.protocolType = protocolType; this.model = model; @@ -38,7 +48,7 @@ public RuleComponent(ProtocolType protocolType, RuleModel model) { private void initComponent() { setLayout(new BorderLayout()); - add(getRuleNameBox(), BorderLayout.PAGE_START); + add(getHeaderBar(), BorderLayout.PAGE_START); add(getRuleOperations(), BorderLayout.CENTER); add(getActionBar(), BorderLayout.PAGE_END); } @@ -57,11 +67,34 @@ private void setSaveButtonState() { } } - private Component getRuleNameBox() { + public Component getHeaderBar() { JPanel container = new JPanel(); - container.setLayout(new BoxLayout(container, BoxLayout.Y_AXIS)); + container.setLayout(new BorderLayout()); container.setBorder(BorderFactory.createEmptyBorder(10, 5, 10, 5)); + JButton addMatchAndReplace = new JButton("Add Match & Replace"); + JPanel buttonContainer = new JPanel(new MigLayout()); + buttonContainer.add(addMatchAndReplace); + + addMatchAndReplace.addActionListener(this::onAddMatchAndReplace); + + container.add(getRuleNameBox(), BorderLayout.LINE_START); + container.add(buttonContainer, BorderLayout.LINE_END); + return container; + } + + private void onAddMatchAndReplace(ActionEvent actionEvent) { + try { + MatchAndReplaceWizardModel model = new MatchAndReplaceWizardModel(this.model); + ModalPrompter.open(model, ignored -> MatchAndReplaceWizardOptionPane.showDialog(model), true); + } catch (Exception e) { + Log.get(workspace).withMessage("Failed to create rule from content menu").withException(e).logErr(); + } + } + + private Component getRuleNameBox() { + JPanel container = new JPanel(new MigLayout()); + ruleName = createTextField(false); ruleName.setText(model.getName()); @@ -71,7 +104,7 @@ private Component getRuleNameBox() { ruleName.getDocument().addDocumentListener(new DocumentActionListener(this::onRuleNameChanged)); - container.add(new JLabel("Rule Name *")); + container.add(new JLabel("Rule Name *"), "wrap"); container.add(ruleName); return container; } @@ -179,4 +212,10 @@ private void onSave(ActionEvent actionEvent) { JOptionPane.ERROR_MESSAGE); } } + + @Override + @SuppressWarnings("unchecked") + public T getComponent() { + return (T) this; + } } diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/RuleListComponent.java b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/RuleListComponent.java index f2f64ab..7c8497d 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/RuleListComponent.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/RuleListComponent.java @@ -1,6 +1,6 @@ package synfron.reshaper.burp.ui.components.rules; -import burp.BurpExtender; +import lombok.Getter; import synfron.reshaper.burp.core.ProtocolType; import synfron.reshaper.burp.core.events.CollectionChangedArgs; import synfron.reshaper.burp.core.events.IEventListener; @@ -9,6 +9,9 @@ import synfron.reshaper.burp.core.rules.RulesRegistry; import synfron.reshaper.burp.core.rules.whens.WhenEventDirection; import synfron.reshaper.burp.core.rules.whens.WhenWebSocketEventDirection; +import synfron.reshaper.burp.core.settings.Workspace; +import synfron.reshaper.burp.ui.components.workspaces.IWorkspaceDependentComponent; +import synfron.reshaper.burp.ui.components.workspaces.IWorkspaceHost; import synfron.reshaper.burp.ui.models.rules.RuleModel; import synfron.reshaper.burp.ui.utils.ActionPerformedListener; import synfron.reshaper.burp.ui.utils.ForegroundColorListCellRenderer; @@ -26,9 +29,11 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -public class RuleListComponent extends JPanel { +public class RuleListComponent extends JPanel implements IWorkspaceHost, IWorkspaceDependentComponent { private final ProtocolType protocolType; private final RulesRegistry rulesRegistry; + @Getter + private final Workspace workspace; private JList rulesList; private DefaultListModel ruleListModel; private RuleContainerComponent ruleContainer; @@ -36,8 +41,9 @@ public class RuleListComponent extends JPanel { private final IEventListener rulesCollectionChangedListener = this::onRulesCollectionChanged; public RuleListComponent(ProtocolType protocolType) { + this.workspace = getHostedWorkspace(this); this.protocolType = protocolType; - this.rulesRegistry = BurpExtender.getRulesRegistry(protocolType); + this.rulesRegistry = workspace.getRulesRegistry(protocolType); initComponent(); } @@ -119,11 +125,13 @@ private int rgbScaler(int value, double divisor) { private void onSelectionChanged(ListSelectionEvent listSelectionEvent) { RuleModel rule = rulesList.getSelectedValue(); - if (rule != null) { - ruleContainer.setModel(rule); - } else if (!defaultSelect()) { - ruleContainer.setModel(null); - } + createEntryPoint(() -> { + if (rule != null) { + ruleContainer.setModel(rule); + } else if (!defaultSelect()) { + ruleContainer.setModel(null); + } + }); } private boolean defaultSelect() { diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/RuleOperationComponent.java b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/RuleOperationComponent.java index dd584a1..a62648d 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/RuleOperationComponent.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/RuleOperationComponent.java @@ -4,24 +4,36 @@ import net.miginfocom.swing.MigLayout; import synfron.reshaper.burp.core.ProtocolType; import synfron.reshaper.burp.core.rules.IRuleOperation; +import synfron.reshaper.burp.core.settings.Workspace; import synfron.reshaper.burp.ui.components.IFormComponent; +import synfron.reshaper.burp.ui.components.workspaces.IWorkspaceDependentComponent; import synfron.reshaper.burp.ui.models.rules.RuleOperationModel; import javax.swing.*; +import java.awt.*; -public abstract class RuleOperationComponent

, T extends IRuleOperation> extends JScrollPane implements IFormComponent { +public abstract class RuleOperationComponent

, T extends IRuleOperation> extends JScrollPane implements IFormComponent, IWorkspaceDependentComponent { @Getter protected final ProtocolType protocolType; @Getter protected final P model; protected final JPanel mainContainer; + @Getter + private final Workspace workspace; protected RuleOperationComponent(ProtocolType protocolType, P model) { + this.workspace = getHostedWorkspace(); this.protocolType = protocolType; this.model = model; mainContainer = new JPanel(new MigLayout()); setViewportView(mainContainer); } + @Override + @SuppressWarnings("unchecked") + public T getComponent() { + return (T) this; + } + } diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/RuleOperationListComponent.java b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/RuleOperationListComponent.java index baa8947..77df122 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/RuleOperationListComponent.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/RuleOperationListComponent.java @@ -1,13 +1,15 @@ package synfron.reshaper.burp.ui.components.rules; -import burp.BurpExtender; import lombok.Getter; import synfron.reshaper.burp.core.ProtocolType; import synfron.reshaper.burp.core.events.IEventListener; import synfron.reshaper.burp.core.events.PropertyChangedArgs; import synfron.reshaper.burp.core.rules.IRuleOperation; +import synfron.reshaper.burp.core.settings.Workspace; import synfron.reshaper.burp.core.utils.CollectionUtils; import synfron.reshaper.burp.ui.components.IFormComponent; +import synfron.reshaper.burp.ui.components.workspaces.IWorkspaceDependentComponent; +import synfron.reshaper.burp.ui.components.workspaces.IWorkspaceHost; import synfron.reshaper.burp.ui.models.rules.RuleModel; import synfron.reshaper.burp.ui.models.rules.RuleOperationModel; import synfron.reshaper.burp.ui.models.rules.RuleOperationModelType; @@ -20,9 +22,11 @@ import java.util.Comparator; import java.util.List; -public abstract class RuleOperationListComponent> extends JPanel implements IFormComponent { +public abstract class RuleOperationListComponent> extends JPanel implements IFormComponent, IWorkspaceDependentComponent, IWorkspaceHost { protected final ProtocolType protocolType; protected final RuleModel model; + @Getter + protected final Workspace workspace; protected JList operationsList; protected DefaultListModel operationsListModel; protected JComboBox> operationSelector; @@ -32,6 +36,7 @@ public abstract class RuleOperationListComponent ruleOperationChangedListener = this::onRuleOperationChanged; public RuleOperationListComponent(ProtocolType protocolType, RuleModel model) { + this.workspace = getHostedWorkspace(); this.protocolType = protocolType; this.model = model; setModelChangedListeners(); @@ -65,12 +70,13 @@ protected void initComponent() { private void onSelectionChanged(ListSelectionEvent listSelectionEvent) { T model = operationsList.getSelectedValue(); - if (model != null) { - ruleOperationContainer.setModel(model); - } - else if (!defaultSelect()) { - ruleOperationContainer.setModel(null); - } + createEntryPoint(() -> { + if (model != null) { + ruleOperationContainer.setModel(model); + } else if (!defaultSelect()) { + ruleOperationContainer.setModel(null); + } + }); } private boolean defaultSelect() { @@ -86,7 +92,7 @@ private Component getOperationsList() { container.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2)); operationsListModel = new DefaultListModel(); - operationsListModel.addAll(getRuleOperations()); + refreshOperationsList(); operationsList = new JList<>(operationsListModel); operationsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); @@ -100,6 +106,11 @@ private Component getOperationsList() { return container; } + protected void refreshOperationsList() { + operationsListModel.clear(); + operationsListModel.addAll(getRuleOperations()); + } + private Component getActionBar() { JPanel actionBar = new JPanel(new WrapLayout(FlowLayout.RIGHT)); @@ -219,4 +230,10 @@ public void setSelectionContainer(RuleOperationContainerComponent ruleOperationC protected abstract T getNewModel(RuleOperationModelType ruleOperationModelType); protected abstract > T getModel(R then); + + @Override + @SuppressWarnings("unchecked") + public T getComponent() { + return (T) this; + } } diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/thens/ThenListComponent.java b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/thens/ThenListComponent.java index 9c6bb1b..47b3b2b 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/thens/ThenListComponent.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/thens/ThenListComponent.java @@ -1,6 +1,5 @@ package synfron.reshaper.burp.ui.components.rules.thens; -import burp.BurpExtender; import synfron.reshaper.burp.core.ProtocolType; import synfron.reshaper.burp.core.events.IEventListener; import synfron.reshaper.burp.core.events.PropertyChangedArgs; @@ -11,19 +10,26 @@ import synfron.reshaper.burp.ui.models.rules.RuleOperationModelType; import synfron.reshaper.burp.ui.models.rules.thens.ThenModel; import synfron.reshaper.burp.ui.models.rules.thens.ThenModelType; -import synfron.reshaper.burp.ui.models.rules.whens.WhenModelType; -import java.util.Collections; import java.util.List; import java.util.stream.Collectors; public class ThenListComponent extends RuleOperationListComponent> { private final IEventListener generalSettingsChangedListener = this::onGeneralSettingsChanged; + private final IEventListener modelPropertyChangedListener = this::onModelPropertyChanged; public ThenListComponent(ProtocolType protocolType, RuleModel model) { super(protocolType, model); - BurpExtender.getGeneralSettings().withListener(generalSettingsChangedListener); + workspace.getGeneralSettings().withListener(generalSettingsChangedListener); + + model.getPropertyChangedEvent().add(modelPropertyChangedListener); + } + + private void onModelPropertyChanged(PropertyChangedArgs propertyChangedArgs) { + if (propertyChangedArgs.getName().equals("thens")) { + refreshOperationsList(); + } } @Override @@ -40,7 +46,7 @@ private void onGeneralSettingsChanged(PropertyChangedArgs propertyChangedArgs) { @Override protected List> getRuleOperationModelTypes() { return ThenModelType.getTypes(protocolType).stream() - .filter(type -> !BurpExtender.getGeneralSettings().getHiddenThenTypes().contains(type.getName())) + .filter(type -> !workspace.getGeneralSettings().getHiddenThenTypes().contains(type.getName())) .collect(Collectors.toList()); } diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/thens/ThenRunRulesComponent.java b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/thens/ThenRunRulesComponent.java index 4070e16..9ef4a2f 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/thens/ThenRunRulesComponent.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/thens/ThenRunRulesComponent.java @@ -1,6 +1,5 @@ package synfron.reshaper.burp.ui.components.rules.thens; -import burp.BurpExtender; import org.apache.commons.lang3.StringUtils; import synfron.reshaper.burp.core.ProtocolType; import synfron.reshaper.burp.core.rules.Rule; @@ -25,8 +24,8 @@ public ThenRunRulesComponent(ProtocolType protocolType, ThenRunRulesModel then) private RulesEngine getRulesEngine(ProtocolType protocolType) { return switch (protocolType) { - case Http -> BurpExtender.getHttpConnector().getRulesEngine(); - case WebSocket -> BurpExtender.getWebSocketConnector().getRulesEngine(); + case Http -> getHostedWorkspace(this).getHttpConnector().getRulesEngine(); + case WebSocket -> getHostedWorkspace(this).getWebSocketConnector().getRulesEngine(); default -> throw new UnsupportedOperationException("ProtocolType not supported here"); }; } diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/thens/buildhttpmessage/MessageValueSetterComponent.java b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/thens/buildhttpmessage/MessageValueSetterComponent.java index c89870c..e653130 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/thens/buildhttpmessage/MessageValueSetterComponent.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/thens/buildhttpmessage/MessageValueSetterComponent.java @@ -12,6 +12,7 @@ import javax.swing.*; import javax.swing.border.CompoundBorder; +import java.awt.*; import java.awt.event.ActionEvent; import java.util.List; import java.util.stream.Stream; @@ -97,4 +98,10 @@ private void onTextChanged(ActionEvent actionEvent) { private void onDelete(ActionEvent actionEvent) { model.setDeleted(true); } + + @Override + @SuppressWarnings("unchecked") + public T getComponent() { + return (T) this; + } } diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/thens/generate/BytesGeneratorComponent.java b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/thens/generate/BytesGeneratorComponent.java index 3539d80..45c48f3 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/thens/generate/BytesGeneratorComponent.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/thens/generate/BytesGeneratorComponent.java @@ -1,10 +1,12 @@ package synfron.reshaper.burp.ui.components.rules.thens.generate; import synfron.reshaper.burp.core.messages.Encoder; +import synfron.reshaper.burp.ui.components.IFormComponent; import synfron.reshaper.burp.ui.models.rules.thens.generate.IBytesGeneratorModel; import synfron.reshaper.burp.ui.utils.DocumentActionListener; import javax.swing.*; +import java.awt.*; import java.awt.event.ActionEvent; public class BytesGeneratorComponent extends GeneratorComponent { diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/thens/generate/GeneratorComponent.java b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/thens/generate/GeneratorComponent.java index b74b858..5d2058f 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/thens/generate/GeneratorComponent.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/thens/generate/GeneratorComponent.java @@ -5,6 +5,7 @@ import synfron.reshaper.burp.ui.models.rules.thens.generate.IGeneratorModel; import javax.swing.*; +import java.awt.*; public abstract class GeneratorComponent> extends JPanel implements IFormComponent { @@ -20,4 +21,10 @@ public GeneratorComponent(T model, boolean allowVariableTags) { } protected abstract void initComponent(); + + @Override + @SuppressWarnings("unchecked") + public T getComponent() { + return (T) this; + } } diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/thens/parsehttpmessage/MessageValueGetterComponent.java b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/thens/parsehttpmessage/MessageValueGetterComponent.java index f29f2e3..77b166c 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/thens/parsehttpmessage/MessageValueGetterComponent.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/thens/parsehttpmessage/MessageValueGetterComponent.java @@ -14,6 +14,7 @@ import javax.swing.*; import javax.swing.border.CompoundBorder; +import java.awt.*; import java.awt.event.ActionEvent; import java.util.List; import java.util.stream.Stream; @@ -149,4 +150,10 @@ private void onIndexChanged(ActionEvent actionEvent) { private void onDelete(ActionEvent actionEvent) { model.setDeleted(true); } + + @Override + @SuppressWarnings("unchecked") + public T getComponent() { + return (T) this; + } } diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/thens/transform/Base64TransformerComponent.java b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/thens/transform/Base64TransformerComponent.java index 9c106f1..dd9f1f7 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/thens/transform/Base64TransformerComponent.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/thens/transform/Base64TransformerComponent.java @@ -3,9 +3,11 @@ import synfron.reshaper.burp.core.messages.Encoder; import synfron.reshaper.burp.core.rules.thens.entities.transform.Base64Variant; import synfron.reshaper.burp.core.rules.thens.entities.transform.EncodeTransform; +import synfron.reshaper.burp.ui.components.IFormComponent; import synfron.reshaper.burp.ui.models.rules.thens.transform.Base64TransformerModel; import javax.swing.*; +import java.awt.*; import java.awt.event.ActionEvent; public class Base64TransformerComponent extends TransformerComponent { diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/thens/transform/TransformerComponent.java b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/thens/transform/TransformerComponent.java index 66c3874..b2748d9 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/thens/transform/TransformerComponent.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/thens/transform/TransformerComponent.java @@ -6,6 +6,7 @@ import synfron.reshaper.burp.ui.utils.DocumentActionListener; import javax.swing.*; +import java.awt.*; import java.awt.event.ActionEvent; public abstract class TransformerComponent> extends JPanel implements IFormComponent { @@ -35,4 +36,10 @@ public TransformerComponent(T model) { private void onInputChanged(ActionEvent actionEvent) { model.setInput(input.getText()); } + + @Override + @SuppressWarnings("unchecked") + public T getComponent() { + return (T) this; + } } diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/whens/WhenEventDirectionComponent.java b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/whens/WhenEventDirectionComponent.java index 58149ed..3054902 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/whens/WhenEventDirectionComponent.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/whens/WhenEventDirectionComponent.java @@ -1,19 +1,25 @@ package synfron.reshaper.burp.ui.components.rules.whens; import synfron.reshaper.burp.core.ProtocolType; +import synfron.reshaper.burp.core.events.IEventListener; +import synfron.reshaper.burp.core.events.PropertyChangedArgs; import synfron.reshaper.burp.core.messages.HttpDataDirection; import synfron.reshaper.burp.core.rules.whens.WhenEventDirection; import synfron.reshaper.burp.ui.models.rules.whens.WhenEventDirectionModel; import javax.swing.*; import java.awt.event.ActionEvent; +import java.util.Objects; public class WhenEventDirectionComponent extends WhenComponent { private JComboBox dataDirection; + private final IEventListener whenPropertyChangedListener = this::onWhenPropertyChanged; public WhenEventDirectionComponent(ProtocolType protocolType, WhenEventDirectionModel when) { super(protocolType, when); initComponent(); + + when.withListener(whenPropertyChangedListener); } private void initComponent() { @@ -30,4 +36,12 @@ private void initComponent() { private void onDataDirectionChanged(ActionEvent actionEvent) { model.setDataDirection((HttpDataDirection)dataDirection.getSelectedItem()); } + + private void onWhenPropertyChanged(PropertyChangedArgs propertyChangedArgs) { + if (propertyChangedArgs.getName().equals("dataDirection")) { + if (!Objects.equals(propertyChangedArgs.getValue(), dataDirection.getSelectedItem())) { + dataDirection.setSelectedItem(model.getDataDirection()); + } + } + } } diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/whens/WhenListComponent.java b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/whens/WhenListComponent.java index 8b54740..92d2a5a 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/whens/WhenListComponent.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/whens/WhenListComponent.java @@ -1,6 +1,5 @@ package synfron.reshaper.burp.ui.components.rules.whens; -import burp.BurpExtender; import synfron.reshaper.burp.core.ProtocolType; import synfron.reshaper.burp.core.events.IEventListener; import synfron.reshaper.burp.core.events.PropertyChangedArgs; @@ -12,17 +11,25 @@ import synfron.reshaper.burp.ui.models.rules.whens.WhenModel; import synfron.reshaper.burp.ui.models.rules.whens.WhenModelType; -import java.util.Collections; import java.util.List; import java.util.stream.Collectors; public class WhenListComponent extends RuleOperationListComponent> { private final IEventListener generalSettingsChangedListener = this::onGeneralSettingsChanged; + private final IEventListener modelPropertyChangedListener = this::onModelPropertyChanged; public WhenListComponent(ProtocolType protocolType, RuleModel model) { super(protocolType, model); - BurpExtender.getGeneralSettings().withListener(generalSettingsChangedListener); + workspace.getGeneralSettings().withListener(generalSettingsChangedListener); + + model.getPropertyChangedEvent().add(modelPropertyChangedListener); + } + + private void onModelPropertyChanged(PropertyChangedArgs propertyChangedArgs) { + if (propertyChangedArgs.getName().equals("whens")) { + refreshOperationsList(); + } } @Override @@ -39,7 +46,7 @@ private void onGeneralSettingsChanged(PropertyChangedArgs propertyChangedArgs) { @Override protected List> getRuleOperationModelTypes() { return WhenModelType.getTypes(protocolType).stream() - .filter(type -> !BurpExtender.getGeneralSettings().getHiddenWhenTypes().contains(type.getName())) + .filter(type -> !workspace.getGeneralSettings().getHiddenWhenTypes().contains(type.getName())) .collect(Collectors.toList()); } diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/matchreplace/MatchAndReplaceWizardOptionPane.java b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/matchreplace/MatchAndReplaceWizardOptionPane.java new file mode 100644 index 0000000..64deafe --- /dev/null +++ b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/matchreplace/MatchAndReplaceWizardOptionPane.java @@ -0,0 +1,155 @@ +package synfron.reshaper.burp.ui.components.rules.wizard.matchreplace; + +import net.miginfocom.swing.MigLayout; +import synfron.reshaper.burp.ui.components.IFormComponent; +import synfron.reshaper.burp.ui.components.shared.PromptTextField; +import synfron.reshaper.burp.ui.models.rules.wizard.matchreplace.MatchAndReplaceWizardModel; +import synfron.reshaper.burp.ui.models.rules.wizard.matchreplace.MatchType; +import synfron.reshaper.burp.ui.utils.ComponentVisibilityManager; +import synfron.reshaper.burp.ui.utils.DocumentActionListener; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.beans.PropertyChangeEvent; +import java.util.Objects; + +public class MatchAndReplaceWizardOptionPane extends JOptionPane implements IFormComponent { + + private final JPanel container; + private final MatchAndReplaceWizardModel model; + private JComboBox matchType; + private JTextField identifier; + private PromptTextField match; + private JLabel matchLabel; + private PromptTextField replace; + private JCheckBox regexMatch; + + private MatchAndReplaceWizardOptionPane(MatchAndReplaceWizardModel model) { + super(new JPanel(new BorderLayout()), JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION, null, new Object[]{ "OK", "Cancel" }, "OK"); + container = (JPanel)message; + this.model = model; + addPropertyChangeListener(JOptionPane.VALUE_PROPERTY, this::onPropertyChanged); + initComponent(); + } + + private void onPropertyChanged(PropertyChangeEvent event) { + if (Objects.equals(getValue(), "OK")) { + if (!model.updateRule()) { + JOptionPane.showMessageDialog(this, + String.join("\n", model.validate()), + "Validation Error", + JOptionPane.ERROR_MESSAGE); + } + } else { + model.setDismissed(true); + } + } + + public static void showDialog(MatchAndReplaceWizardModel model) { + MatchAndReplaceWizardOptionPane optionPane = new MatchAndReplaceWizardOptionPane(model); + JDialog dialog = optionPane.createDialog("Match & Replace"); + dialog.setResizable(true); + + dialog.setModal(false); + dialog.setVisible(true); + + optionPane.container.addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent e) { + dialog.pack(); + } + }); + } + + private void initComponent() { + container.add(getBody(), BorderLayout.CENTER); + } + + private Component getBody() { + JPanel container = new JPanel(new MigLayout()); + + matchType = createComboBox(MatchType.values()); + identifier = createTextField(true); + matchLabel = new JLabel(model.getMatchType().isAllowEmptyMatch() ? + "Match" : + "Match *" + ); + match = createPromptTextField(getMatchPlaceHolderText(model.getMatchType()), true); + replace = createPromptTextField(getReplacePlaceHolderText(model.getMatchType()),true); + regexMatch = new JCheckBox("Regex match"); + + matchType.setSelectedItem(model.getMatchType()); + identifier.setText(model.getIdentifier()); + match.setText(model.getMatch()); + replace.setText(model.getReplace()); + regexMatch.setSelected(model.isRegexMatch()); + + matchType.addActionListener(this::onMatchTypeChanged); + identifier.getDocument().addDocumentListener(new DocumentActionListener(this::onIdentifierChanged)); + match.getDocument().addDocumentListener(new DocumentActionListener(this::onMatchChanged)); + replace.getDocument().addDocumentListener(new DocumentActionListener(this::onReplaceChanged)); + regexMatch.addActionListener(this::onRegexMatchChanged); + + container.add(getLabeledField("Type", matchType), "wrap"); + container.add(ComponentVisibilityManager.withVisibilityFieldChangeDependency( + getLabeledField("Identifier *", identifier), + matchType, + () -> ((MatchType)matchType.getSelectedItem()).hasCustomIdentifier() + ), "wrap"); + container.add(getLabeledField(matchLabel, match), "wrap"); + container.add(getLabeledField("Replace", replace), "wrap"); + container.add(regexMatch, "wrap"); + + return container; + } + + private String getMatchPlaceHolderText(MatchType matchType) { + return switch (matchType) { + case RequestHeaderLine, ResponseHeaderLine -> "Leave blank to add a new header"; + default -> "Regex / text to match"; + }; + } + + private String getReplacePlaceHolderText(MatchType matchType) { + return switch (matchType) { + case RequestHeaderLine, ResponseHeaderLine -> "Leave blank to remove the header"; + default -> "Literal string to replace"; + }; + } + + private void onMatchTypeChanged(ActionEvent actionEvent) { + MatchType matchType = (MatchType) this.matchType.getSelectedItem(); + model.setMatchType(matchType); + matchLabel.setText(model.getMatchType().isAllowEmptyMatch() ? + "Match" : + "Match *" + ); + match.getTextPrompt().setText(getMatchPlaceHolderText(model.getMatchType())); + replace.getTextPrompt().setText(getReplacePlaceHolderText(model.getMatchType())); + } + + private void onIdentifierChanged(ActionEvent actionEvent) { + model.setIdentifier(identifier.getText()); + } + + private void onMatchChanged(ActionEvent actionEvent) { + model.setMatch(match.getText()); + } + + private void onReplaceChanged(ActionEvent actionEvent) { + model.setReplace(replace.getText()); + } + + private void onRegexMatchChanged(ActionEvent actionEvent) { + model.setRegexMatch(regexMatch.isSelected()); + } + + @Override + @SuppressWarnings("unchecked") + public T getComponent() { + return (T) this; + } +} diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/vars/AnnotationVariableTagWizardComponent.java b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/vars/AnnotationVariableTagWizardComponent.java index a4018cc..010c783 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/vars/AnnotationVariableTagWizardComponent.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/vars/AnnotationVariableTagWizardComponent.java @@ -6,6 +6,7 @@ import synfron.reshaper.burp.ui.models.rules.wizard.vars.AnnotationVariableTagWizardModel; import javax.swing.*; +import java.awt.*; import java.awt.event.ActionEvent; public class AnnotationVariableTagWizardComponent extends JPanel implements IFormComponent { @@ -32,4 +33,10 @@ private void initComponent() { private void onMessageAnnotationChanged(ActionEvent actionEvent) { model.setMessageAnnotation((MessageAnnotation)messageAnnotation.getSelectedItem()); } + + @Override + @SuppressWarnings("unchecked") + public T getComponent() { + return (T) this; + } } diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/vars/CookieJarVariableTagWizardComponent.java b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/vars/CookieJarVariableTagWizardComponent.java index 5122e51..ba49df3 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/vars/CookieJarVariableTagWizardComponent.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/vars/CookieJarVariableTagWizardComponent.java @@ -6,6 +6,7 @@ import synfron.reshaper.burp.ui.models.rules.wizard.vars.CookieJarVariableTagWizardModel; import javax.swing.*; +import java.awt.*; import java.awt.event.ActionEvent; public class CookieJarVariableTagWizardComponent extends JPanel implements IFormComponent { @@ -71,4 +72,10 @@ private void resetNames() { model.getNames().getOptions().forEach(option -> name.addItem(option)); name.setSelectedItem(model.getNames().getSelectedOption()); } + + @Override + @SuppressWarnings("unchecked") + public T getComponent() { + return (T) this; + } } diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/vars/CustomVariableTagWizardComponent.java b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/vars/CustomVariableTagWizardComponent.java index 46c18b0..79a6d67 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/vars/CustomVariableTagWizardComponent.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/vars/CustomVariableTagWizardComponent.java @@ -5,6 +5,7 @@ import synfron.reshaper.burp.ui.models.rules.wizard.vars.CustomVariableTagWizardModel; import javax.swing.*; +import java.awt.*; import java.awt.event.ActionEvent; public abstract class CustomVariableTagWizardComponent extends JPanel implements IFormComponent { @@ -31,4 +32,10 @@ protected void initComponent() { private void onVariableNameChanged(ActionEvent actionEvent) { model.setVariableName((String)variableName.getSelectedItem()); } + + @Override + @SuppressWarnings("unchecked") + public T getComponent() { + return (T) this; + } } diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/vars/FileVariableTagWizardComponent.java b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/vars/FileVariableTagWizardComponent.java index 96d50f8..e3d2280 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/vars/FileVariableTagWizardComponent.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/vars/FileVariableTagWizardComponent.java @@ -7,6 +7,7 @@ import synfron.reshaper.burp.ui.utils.DocumentActionListener; import javax.swing.*; +import java.awt.*; import java.awt.event.ActionEvent; public class FileVariableTagWizardComponent extends JPanel implements IFormComponent { @@ -36,7 +37,7 @@ private void initComponent() { private JPanel getFileBrowser() { JPanel container = new JPanel(new MigLayout()); - filePath = createTextField(true); + filePath = createTextField(false); JButton browse = new JButton("Browse"); filePath.setText(model.getFilePath()); @@ -76,4 +77,10 @@ private JFileChooser createFileChooser(String title) { fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); return fileChooser; } + + @Override + @SuppressWarnings("unchecked") + public T getComponent() { + return (T) this; + } } diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/vars/GeneratorVariableTagWizardComponent.java b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/vars/GeneratorVariableTagWizardComponent.java index c464aa1..97b1935 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/vars/GeneratorVariableTagWizardComponent.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/vars/GeneratorVariableTagWizardComponent.java @@ -8,6 +8,7 @@ import synfron.reshaper.burp.ui.models.rules.wizard.vars.generator.*; import javax.swing.*; +import java.awt.*; import java.awt.event.ActionEvent; public class GeneratorVariableTagWizardComponent extends JPanel implements IFormComponent { @@ -61,4 +62,10 @@ private void onSetGenerateOptionChanged(ActionEvent actionEvent) { model.setGenerateOption((GenerateOption) generateOption.getSelectedItem()); setGenerator(); } + + @Override + @SuppressWarnings("unchecked") + public T getComponent() { + return (T) this; + } } diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/vars/MacroVariableTagWizardComponent.java b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/vars/MacroVariableTagWizardComponent.java index 5a32247..1789890 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/vars/MacroVariableTagWizardComponent.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/vars/MacroVariableTagWizardComponent.java @@ -9,6 +9,7 @@ import synfron.reshaper.burp.ui.utils.DocumentActionListener; import javax.swing.*; +import java.awt.*; import java.awt.event.ActionEvent; import java.util.Arrays; @@ -62,4 +63,10 @@ private void onMessageValueChanged(ActionEvent actionEvent) { private void onIdentifierChanged(ActionEvent actionEvent) { model.setIdentifier(identifier.getText()); } + + @Override + @SuppressWarnings("unchecked") + public T getComponent() { + return (T) this; + } } diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/vars/MessageVariableTagWizardComponent.java b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/vars/MessageVariableTagWizardComponent.java index 69b8494..2dc3255 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/vars/MessageVariableTagWizardComponent.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/vars/MessageVariableTagWizardComponent.java @@ -9,6 +9,7 @@ import synfron.reshaper.burp.ui.utils.DocumentActionListener; import javax.swing.*; +import java.awt.*; import java.awt.event.ActionEvent; import java.util.Arrays; @@ -53,4 +54,10 @@ private void onMessageValueChanged(ActionEvent actionEvent) { private void onIdentifierChanged(ActionEvent actionEvent) { model.setIdentifier(identifier.getText()); } + + @Override + @SuppressWarnings("unchecked") + public T getComponent() { + return (T) this; + } } diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/vars/SpecialVariableTagWizardComponent.java b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/vars/SpecialVariableTagWizardComponent.java index 610fbdb..cda34cc 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/vars/SpecialVariableTagWizardComponent.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/vars/SpecialVariableTagWizardComponent.java @@ -7,6 +7,7 @@ import synfron.reshaper.burp.ui.utils.DocumentActionListener; import javax.swing.*; +import java.awt.*; import java.awt.event.ActionEvent; public class SpecialVariableTagWizardComponent extends JPanel implements IFormComponent { @@ -23,7 +24,7 @@ private void initComponent() { setLayout(new MigLayout()); specialChar = createComboBox(model.getSpecialChars().toArray(new SpecialVariableTagWizardModel.SpecialChar[0])); - value = createTextField(true); + value = createTextField(false); specialChar.setSelectedItem(model.getSpecialChar()); value.setText(model.getValue()); @@ -46,4 +47,10 @@ private void onSpecialCharChanged(ActionEvent actionEvent) { private void onValueChanged(ActionEvent actionEvent) { model.setValue(value.getText()); } + + @Override + @SuppressWarnings("unchecked") + public T getComponent() { + return (T) this; + } } diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/vars/VariableTagWizardOptionPane.java b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/vars/VariableTagWizardOptionPane.java index 7582697..d443b74 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/vars/VariableTagWizardOptionPane.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/vars/VariableTagWizardOptionPane.java @@ -12,6 +12,7 @@ import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.beans.PropertyChangeEvent; +import java.util.Objects; public class VariableTagWizardOptionPane extends JOptionPane implements IFormComponent { @@ -23,7 +24,7 @@ public class VariableTagWizardOptionPane extends JOptionPane implements IFormCom private final ProtocolType protocolType; private VariableTagWizardOptionPane(VariableTagWizardModel model, ProtocolType protocolType) { - super(new JPanel(new BorderLayout()), JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION); + super(new JPanel(new BorderLayout()), JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION, null, new Object[]{ "OK", "Cancel" }, "OK"); container = new VariableTagWizardContainerComponent(protocolType); outerContainer = (JPanel)message; this.model = model; @@ -75,7 +76,7 @@ private void onVariableSourceChanged(ActionEvent actionEvent) { } private void onPropertyChanged(PropertyChangeEvent event) { - if (getValue() != null && (int)getValue() == JOptionPane.OK_OPTION) { + if (Objects.equals(getValue(), "OK")) { if (!model.validate().isEmpty()) { JOptionPane.showMessageDialog(this, String.join("\n", model.validate()), @@ -97,4 +98,10 @@ public static void showDialog(VariableTagWizardModel model, ProtocolType protoco private void setCurrentDialog(JDialog dialog) { this.currentDialog = dialog; } + + @Override + @SuppressWarnings("unchecked") + public T getComponent() { + return (T) this; + } } diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/whens/WhenWizardItemComponent.java b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/whens/WhenWizardItemComponent.java index 9326f71..7c6ddda 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/whens/WhenWizardItemComponent.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/whens/WhenWizardItemComponent.java @@ -126,4 +126,10 @@ protected Component getRightJustifiedButton(JButton button) { outerContainer.add(button); return outerContainer; } + + @Override + @SuppressWarnings("unchecked") + public T getComponent() { + return (T) this; + } } diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/whens/WhenWizardOptionPane.java b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/whens/WhenWizardOptionPane.java index 6739cb5..57672d0 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/whens/WhenWizardOptionPane.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/whens/WhenWizardOptionPane.java @@ -13,6 +13,7 @@ import java.awt.*; import java.awt.event.ActionEvent; import java.beans.PropertyChangeEvent; +import java.util.Objects; public class WhenWizardOptionPane extends JOptionPane implements IFormComponent { @@ -21,9 +22,10 @@ public class WhenWizardOptionPane extends JOptionPane implements IFormComponent private JTextField ruleName; private JPanel whenWizardItemsComponent; private final IEventListener whenWizardItemChangedListener = this::onWhenWizardItemChanged; + private JScrollPane bodyScrollPane; private WhenWizardOptionPane(WhenWizardModel model) { - super(new JPanel(new BorderLayout()), JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION); + super(new JPanel(new BorderLayout()), JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION, null, new Object[]{ "OK", "Cancel" }, "OK"); container = (JPanel)message; this.model = model; addPropertyChangeListener(JOptionPane.VALUE_PROPERTY, this::onPropertyChanged); @@ -31,7 +33,7 @@ private WhenWizardOptionPane(WhenWizardModel model) { } private void onPropertyChanged(PropertyChangeEvent event) { - if (getValue() != null && (int)getValue() == JOptionPane.OK_OPTION) { + if (Objects.equals(getValue(), "OK")) { if (model.createRule()) { JOptionPane.showMessageDialog(this, "Rule created. Navigate to Reshaper to finish the rule.", @@ -58,10 +60,11 @@ public static void showDialog(WhenWizardModel model) { } private void initComponent() { - container.add(getBody(), BorderLayout.CENTER); + bodyScrollPane = getBodyScrollPane(); + container.add(bodyScrollPane, BorderLayout.CENTER); } - private Component getBody() { + private JScrollPane getBodyScrollPane() { JPanel container = new JPanel(new MigLayout()); ruleName = createTextField(false); @@ -79,6 +82,7 @@ private Component getBody() { container.add(getPaddedButton(addItem), "wrap"); scrollPane.setPreferredSize(container.getPreferredSize()); + scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); return scrollPane; } @@ -133,4 +137,10 @@ private void removeWhenWizardItem(WhenWizardItemModel whenWizardItemModel) { whenWizardItemsComponent.revalidate(); whenWizardItemsComponent.repaint(); } + + @Override + @SuppressWarnings("unchecked") + public T getComponent() { + return (T) this; + } } diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/components/settings/HideItemsOptionPane.java b/extension/src/main/java/synfron/reshaper/burp/ui/components/settings/HideItemsOptionPane.java index aa72187..bd57f3b 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/components/settings/HideItemsOptionPane.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/components/settings/HideItemsOptionPane.java @@ -1,7 +1,7 @@ package synfron.reshaper.burp.ui.components.settings; import net.miginfocom.swing.MigLayout; -import synfron.reshaper.burp.core.Tab; +import synfron.reshaper.burp.core.WorkspaceTab; import synfron.reshaper.burp.core.rules.thens.ThenType; import synfron.reshaper.burp.core.rules.whens.WhenType; import synfron.reshaper.burp.ui.components.IFormComponent; @@ -13,6 +13,7 @@ import java.awt.event.ActionListener; import java.beans.PropertyChangeEvent; import java.util.HashSet; +import java.util.Objects; import java.util.stream.Stream; public class HideItemsOptionPane extends JOptionPane implements IFormComponent { @@ -21,7 +22,7 @@ public class HideItemsOptionPane extends JOptionPane implements IFormComponent { private final HideItemsModel model; private HideItemsOptionPane(HideItemsModel model) { - super(new JPanel(new BorderLayout()), JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION); + super(new JPanel(new BorderLayout()), JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION, null, new Object[]{ "OK", "Cancel" }, "OK"); container = (JPanel)message; this.model = model; addPropertyChangeListener(JOptionPane.VALUE_PROPERTY, this::onPropertyChanged); @@ -29,7 +30,7 @@ private HideItemsOptionPane(HideItemsModel model) { } private void onPropertyChanged(PropertyChangeEvent event) { - if (getValue() != null && (int)getValue() == JOptionPane.OK_OPTION) { + if (Objects.equals(getValue(), "OK")) { model.save(); } else { model.setDismissed(true); @@ -73,7 +74,7 @@ private void addCheckboxes(JPanel container, int maxRowLength, java.util.Lis private void onHiddenTabChanged(ActionEvent actionEvent) { JCheckBox checkbox = (JCheckBox) actionEvent.getSource(); - Tab item = (Tab) checkbox.getClientProperty("item"); + WorkspaceTab item = (WorkspaceTab) checkbox.getClientProperty("item"); if (checkbox.isSelected()) { model.addHiddenTab(item); @@ -110,7 +111,7 @@ private Component getBody() { JPanel hideTabs = new JPanel(new MigLayout()); hideTabs.setBorder(BorderFactory.createEmptyBorder(4,4,4,4)); hideTabs.add(new JLabel("Hide Tabs:"), "wrap"); - addCheckboxes(hideTabs, 4, Stream.of(Tab.values()).filter(Tab::isHideable).toList(), model.getHiddenTabs(), this::onHiddenTabChanged); + addCheckboxes(hideTabs, 4, Stream.of(WorkspaceTab.values()).filter(WorkspaceTab::isHideable).toList(), model.getHiddenTabs(), this::onHiddenTabChanged); JPanel hideWhenTypes = new JPanel(new MigLayout()); hideWhenTypes.setBorder(BorderFactory.createEmptyBorder(4,4,4,4)); @@ -129,4 +130,10 @@ private Component getBody() { return container; } + + @Override + @SuppressWarnings("unchecked") + public T getComponent() { + return (T) this; + } } diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/components/settings/SettingsTabComponent.java b/extension/src/main/java/synfron/reshaper/burp/ui/components/settings/SettingsTabComponent.java index 6edb64f..9cd3274 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/components/settings/SettingsTabComponent.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/components/settings/SettingsTabComponent.java @@ -1,22 +1,25 @@ package synfron.reshaper.burp.ui.components.settings; -import burp.BurpExtender; import com.alexandriasoftware.swing.JSplitButton; +import lombok.Getter; import net.miginfocom.swing.MigLayout; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; -import synfron.reshaper.burp.core.ProtocolType; +import synfron.reshaper.burp.core.BurpTool; +import synfron.reshaper.burp.core.events.CollectionChangedArgs; +import synfron.reshaper.burp.core.events.IEventListener; +import synfron.reshaper.burp.core.events.PropertyChangedArgs; import synfron.reshaper.burp.core.messages.Encoder; import synfron.reshaper.burp.core.rules.Rule; -import synfron.reshaper.burp.core.settings.GeneralSettings; -import synfron.reshaper.burp.core.settings.SettingsManager; +import synfron.reshaper.burp.core.settings.*; import synfron.reshaper.burp.core.utils.Log; import synfron.reshaper.burp.core.utils.TextUtils; -import synfron.reshaper.burp.core.vars.GlobalVariables; import synfron.reshaper.burp.core.vars.Variable; import synfron.reshaper.burp.ui.components.IFormComponent; +import synfron.reshaper.burp.ui.components.workspaces.IWorkspaceDependentComponent; import synfron.reshaper.burp.ui.models.settings.HideItemsModel; +import synfron.reshaper.burp.ui.utils.ComponentVisibilityManager; import synfron.reshaper.burp.ui.utils.FocusActionListener; import synfron.reshaper.burp.ui.utils.ModalPrompter; @@ -34,13 +37,14 @@ import java.util.Objects; import java.util.stream.Collectors; -public class SettingsTabComponent extends JPanel implements IFormComponent, HierarchyListener { - +public class SettingsTabComponent extends JPanel implements IFormComponent, HierarchyListener, IWorkspaceDependentComponent { + @Getter + private final Workspace workspace; private JCheckBox overwriteDuplicates; private SelectionTable exportHttpRulesTable; private SelectionTable exportWebSocketRulesTable; private SelectionTable exportVariablesTable; - private final GeneralSettings generalSettings = BurpExtender.getGeneralSettings(); + private final GeneralSettings generalSettings; private JCheckBox enableEventDiagnostics; private JTextField diagnosticValueMaxLength; private JCheckBox enableSanityCheckWarnings; @@ -53,14 +57,37 @@ public class SettingsTabComponent extends JPanel implements IFormComponent, Hier private JCheckBox scanner; private JCheckBox target; private JCheckBox extender; + private JCheckBox session; private JCheckBox webSockets; private ButtonGroup importMethod; private ButtonGroup exportMethod; private JSplitButton exportData; private JSplitButton importData; + private final IEventListener workspacesPropertyChangedListener = this::onWorkspacesPropertyChanged; + private final IEventListener workspacesChangedListener = this::onWorkspacesChangedListener; + + private JButton enableWorkspaces; + private JPanel rightGeneralSettings; public SettingsTabComponent() { + workspace = getHostedWorkspace(this); + generalSettings = workspace.getGeneralSettings(); initComponent(); + Workspaces.get().withListener(workspacesPropertyChangedListener) + .withCollectionListener(workspacesChangedListener); + } + + private void onWorkspacesPropertyChanged(PropertyChangedArgs propertyChangedArgs) { + if (propertyChangedArgs.getName().equals("enabled")) { + enableWorkspaces.setText(Workspaces.get().isEnabled() ? "Disable Workspaces" : "Enable Workspaces"); + ComponentVisibilityManager.updateVisibility(rightGeneralSettings, enableWorkspaces, !Workspaces.get().isEnabled() || Workspaces.get().getWorkspaces().size() == 1); + } else if (propertyChangedArgs.getName().equals("workspaces")) { + ComponentVisibilityManager.updateVisibility(rightGeneralSettings, enableWorkspaces, !Workspaces.get().isEnabled() || Workspaces.get().getWorkspaces().size() == 1); + } + } + + private void onWorkspacesChangedListener(CollectionChangedArgs collectionChangedArgs) { + ComponentVisibilityManager.updateVisibility(rightGeneralSettings, enableWorkspaces, !Workspaces.get().isEnabled() || Workspaces.get().getWorkspaces().size() == 1); } private void initComponent() { @@ -83,14 +110,18 @@ private Component getGeneralSettings() { return container; } + private void onEnableWorkspaces(ActionEvent actionEvent) { + Workspaces.get().setEnabled(!Workspaces.get().isEnabled()); + } + private void onHideFeatures(ActionEvent actionEvent) { HideItemsModel model = new HideItemsModel(generalSettings); ModalPrompter.open(model, ignored -> HideItemsOptionPane.showDialog(model), true); } private Component RightGeneralOptions() { - JPanel container = new JPanel(new MigLayout()); - container.setBorder(BorderFactory.createEmptyBorder(0, 20, 0, 0)); + rightGeneralSettings = new JPanel(new MigLayout()); + rightGeneralSettings.setBorder(BorderFactory.createEmptyBorder(0, 20, 0, 0)); defaultEncoding = createComboBox(Encoder.getEncodings().toArray(new String[0])); @@ -98,10 +129,15 @@ private Component RightGeneralOptions() { defaultEncoding.addActionListener(this::onSetDefaultEncodingChanged); + enableWorkspaces = new JButton(Workspaces.get().isEnabled() ? "Disable Workspaces" : "Enable Workspaces"); + enableWorkspaces.addActionListener(this::onEnableWorkspaces); - container.add(getLabeledField("Default Encoding", defaultEncoding), "wrap"); - container.add(getCaptureTrafficOptions(), "wrap"); - return container; + rightGeneralSettings.add(getLabeledField("Default Encoding", defaultEncoding), "wrap"); + rightGeneralSettings.add(getCaptureTrafficOptions(), "wrap"); + rightGeneralSettings.add(enableWorkspaces, "wrap"); + + ComponentVisibilityManager.updateVisibility(rightGeneralSettings, enableWorkspaces, !Workspaces.get().isEnabled() || Workspaces.get().getWorkspaces().size() == 1); + return rightGeneralSettings; } private Component LeftGeneralOptions() { @@ -110,7 +146,7 @@ private Component LeftGeneralOptions() { enableEventDiagnostics = new JCheckBox("Enable Event Diagnostics"); diagnosticValueMaxLength = createTextField(false); enableSanityCheckWarnings = new JCheckBox("Enable Sanity Check Warnings"); - logInExtenderOutput = new JCheckBox("Replicate Logs in Extender Output"); + logInExtenderOutput = new JCheckBox("Replicate Logs to Extension Output"); logTabCharacterLimit = createTextField(false); JButton hideFeatures = new JButton("Hide Features"); JButton resetData = new JButton("Reset Data"); @@ -146,13 +182,14 @@ private Component LeftGeneralOptions() { private Component getCaptureTrafficOptions() { JPanel container = new JPanel(new MigLayout()); - proxy = new JCheckBox("Proxy"); - repeater = new JCheckBox("Repeater"); - intruder = new JCheckBox("Intruder"); - scanner = new JCheckBox("Scanner"); - target = new JCheckBox("Target"); - extender = new JCheckBox("Extender"); - webSockets = new JCheckBox("WebSockets"); + proxy = new JCheckBox(BurpTool.Proxy.toString()); + repeater = new JCheckBox(BurpTool.Repeater.toString()); + intruder = new JCheckBox(BurpTool.Intruder.toString()); + scanner = new JCheckBox(BurpTool.Scanner.toString()); + target = new JCheckBox(BurpTool.Target.toString()); + extender = new JCheckBox(BurpTool.Extender.toString()); + session = new JCheckBox(BurpTool.Session.toString()); + webSockets = new JCheckBox(BurpTool.WebSockets.toString()); proxy.setSelected(generalSettings.isCaptureProxy()); repeater.setSelected(generalSettings.isCaptureRepeater()); @@ -160,6 +197,7 @@ private Component getCaptureTrafficOptions() { scanner.setSelected(generalSettings.isCaptureScanner()); target.setSelected(generalSettings.isCaptureTarget()); extender.setSelected(generalSettings.isCaptureExtender()); + session.setSelected(generalSettings.isCaptureSession()); webSockets.setSelected(generalSettings.isCaptureWebSockets()); proxy.addActionListener(this::onProxyChanged); @@ -168,6 +206,7 @@ private Component getCaptureTrafficOptions() { scanner.addActionListener(this::onScannerChanged); target.addActionListener(this::onTargetChanged); extender.addActionListener(this::onExtenderChanged); + session.addActionListener(this::onSessionChanged); webSockets.addActionListener(this::onWebSocketsChanged); container.add(new JLabel("Capture Traffic From:"), "wrap"); @@ -177,7 +216,8 @@ private Component getCaptureTrafficOptions() { container.add(scanner, "wrap"); container.add(target); container.add(extender, "wrap"); - container.add(webSockets); + container.add(session); + container.add(webSockets, "wrap"); return container; } @@ -185,11 +225,11 @@ private void onResetData(ActionEvent actionEvent) { try { int response = JOptionPane.showConfirmDialog(this, "Are you sure you want to reset data? This will remove all rules and variables.", "Reset Data", JOptionPane.YES_NO_OPTION); if (response == JOptionPane.YES_OPTION) { - SettingsManager.resetData(); + SettingsManager.resetData(workspace); refreshLists(); } } catch (Exception e) { - Log.get().withMessage("Error resetting data").withException(e).logErr(); + Log.get(workspace).withMessage("Error resetting data").withException(e).logErr(); JOptionPane.showMessageDialog(this, "Error resetting data", @@ -251,6 +291,10 @@ private void onExtenderChanged(ActionEvent actionEvent) { generalSettings.setCaptureExtender(extender.isSelected()); } + private void onSessionChanged(ActionEvent actionEvent) { + generalSettings.setCaptureSession(session.isSelected()); + } + private void onWebSocketsChanged(ActionEvent actionEvent) { generalSettings.setCaptureWebSockets(webSockets.isSelected()); } @@ -414,11 +458,14 @@ private void onExportData(ActionEvent actionEvent) { if (result == JFileChooser.APPROVE_OPTION) { generalSettings.setLastExportPath(FilenameUtils.getFullPath(fileChooser.getSelectedFile().getAbsolutePath())); generalSettings.setLastExportFileName(FilenameUtils.getName(fileChooser.getSelectedFile().getAbsolutePath())); - SettingsManager.exportSettings( + SettingsManager.exportWorkspaceData( + workspace, fileChooser.getSelectedFile(), - exportVariablesTable.getSelectedValues(), - exportHttpRulesTable.getSelectedValues(), - exportWebSocketRulesTable.getSelectedValues() + new WorkspaceDataExport( + exportHttpRulesTable.getSelectedValues(), + exportWebSocketRulesTable.getSelectedValues(), + exportVariablesTable.getSelectedValues() + ) ); JOptionPane.showMessageDialog(this, @@ -428,7 +475,7 @@ private void onExportData(ActionEvent actionEvent) { ); } } catch (Exception e) { - Log.get().withMessage("Error exporting data").withException(e).logErr(); + Log.get(workspace).withMessage("Error exporting data").withException(e).logErr(); JOptionPane.showMessageDialog(this, "Error exporting data", @@ -454,7 +501,7 @@ private void onImportFromFile() { file = fileChooser.getSelectedFile().getAbsolutePath(); generalSettings.setLastExportPath(FilenameUtils.getFullPath(file)); generalSettings.setLastExportFileName(FilenameUtils.getName(file)); - SettingsManager.importSettings(fileChooser.getSelectedFile(), overwriteDuplicates.isSelected()); + SettingsManager.importSettings(workspace, fileChooser.getSelectedFile(), overwriteDuplicates.isSelected()); refreshLists(); JOptionPane.showMessageDialog(this, @@ -464,7 +511,7 @@ private void onImportFromFile() { ); } } catch (Exception e) { - Log.get().withMessage("Error importing data from file").withException(e).logErr(); + Log.get(workspace).withMessage("Error importing data from file").withException(e).logErr(); JOptionPane.showMessageDialog(this, String.format("Error importing data from file: %s", file), @@ -484,7 +531,7 @@ private void onImportFromUrl() { connection.setConnectTimeout(10000); connection.setReadTimeout(10000); String settingsJson = IOUtils.toString(connection.getInputStream(), Charset.defaultCharset()); - SettingsManager.importSettings(settingsJson, overwriteDuplicates.isSelected()); + SettingsManager.importSettings(workspace, settingsJson, overwriteDuplicates.isSelected()); refreshLists(); JOptionPane.showMessageDialog(this, @@ -494,7 +541,7 @@ private void onImportFromUrl() { ); } } catch (Exception e) { - Log.get().withMessage("Error importing data from URL").withException(e).logErr(); + Log.get(workspace).withMessage("Error importing data from URL").withException(e).logErr(); JOptionPane.showMessageDialog(this, String.format("Error importing data from URL: %s", url), @@ -530,15 +577,21 @@ private void refreshLists() { } private List getExportHttpRulesData() { - return BurpExtender.getRulesRegistry(ProtocolType.Http).exportRules(); + return workspace.getHttpRulesRegistry().exportRules(); } private List getExportWebSocketRulesData() { - return BurpExtender.getRulesRegistry(ProtocolType.WebSocket).exportRules(); + return workspace.getWebSocketRulesRegistry().exportRules(); } private List getExportVariablesData() { - return GlobalVariables.get().exportVariables(); + return workspace.getGlobalVariables().exportVariables(); + } + + @Override + @SuppressWarnings("unchecked") + public T getComponent() { + return (T) this; } } diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/components/shared/PromptTextField.java b/extension/src/main/java/synfron/reshaper/burp/ui/components/shared/PromptTextField.java new file mode 100644 index 0000000..8828722 --- /dev/null +++ b/extension/src/main/java/synfron/reshaper/burp/ui/components/shared/PromptTextField.java @@ -0,0 +1,15 @@ +package synfron.reshaper.burp.ui.components.shared; + +import lombok.Getter; + +import javax.swing.*; + +@Getter +public class PromptTextField extends JTextField { + + private final TextPrompt textPrompt; + + public PromptTextField(String promptText) { + textPrompt = new TextPrompt(promptText, this); + } +} diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/components/shared/TextPrompt.java b/extension/src/main/java/synfron/reshaper/burp/ui/components/shared/TextPrompt.java new file mode 100644 index 0000000..96d8d7b --- /dev/null +++ b/extension/src/main/java/synfron/reshaper/burp/ui/components/shared/TextPrompt.java @@ -0,0 +1,248 @@ +package synfron.reshaper.burp.ui.components.shared; + +/* +MIT License + +Copyright (c) 2023 Rob Camick + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.text.Document; +import javax.swing.text.JTextComponent; +import java.awt.*; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; + +/** + * The TextPrompt class will display a prompt over top of a text component when + * the Document of the text field is empty. The Show property is used to + * determine the visibility of the prompt. + * + * The Font and foreground Color of the prompt will default to those properties + * of the parent text component. You are free to change the properties after + * class construction. + */ +public class TextPrompt extends JLabel + implements FocusListener, DocumentListener +{ + public enum Show + { + ALWAYS, + FOCUS_GAINED, + FOCUS_LOST; + } + + private JTextComponent component; + private Document document; + + private Show show; + private boolean showPromptOnce; + private int focusLost; + + public TextPrompt(String text, JTextComponent component) + { + this(text, component, Show.ALWAYS); + } + + public TextPrompt(String text, JTextComponent component, Show show) + { + this.component = component; + setShow( show ); + document = component.getDocument(); + + setText( text ); + setFont( component.getFont() ); + setForeground( component.getForeground() ); + setBorder( new EmptyBorder(component.getInsets()) ); + setHorizontalAlignment(JLabel.LEADING); + + component.addFocusListener( this ); + document.addDocumentListener( this ); + + component.setLayout( new BorderLayout() ); + component.add( this ); + checkForPrompt(); + } + + /** + * Convenience method to change the alpha value of the current foreground + * Color to the specifice value. + * + * @param alpha value in the range of 0 - 1.0. + */ + public void changeAlpha(float alpha) + { + changeAlpha( (int)(alpha * 255) ); + } + + /** + * Convenience method to change the alpha value of the current foreground + * Color to the specifice value. + * + * @param alpha value in the range of 0 - 255. + */ + public void changeAlpha(int alpha) + { + alpha = alpha > 255 ? 255 : alpha < 0 ? 0 : alpha; + + Color foreground = getForeground(); + int red = foreground.getRed(); + int green = foreground.getGreen(); + int blue = foreground.getBlue(); + + Color withAlpha = new Color(red, green, blue, alpha); + super.setForeground( withAlpha ); + } + + /** + * Convenience method to change the style of the current Font. The style + * values are found in the Font class. Common values might be: + * Font.BOLD, Font.ITALIC and Font.BOLD + Font.ITALIC. + * + * @param style value representing the the new style of the Font. + */ + public void changeStyle(int style) + { + setFont( getFont().deriveFont( style ) ); + } + + /** + * Get the Show property + * + * @return the Show property. + */ + public Show getShow() + { + return show; + } + + /** + * Set the prompt Show property to control when the promt is shown. + * Valid values are: + * + * Show.AWLAYS (default) - always show the prompt + * Show.Focus_GAINED - show the prompt when the component gains focus + * (and hide the prompt when focus is lost) + * Show.Focus_LOST - show the prompt when the component loses focus + * (and hide the prompt when focus is gained) + * + * @param show a valid Show enum + */ + public void setShow(Show show) + { + this.show = show; + } + + /** + * Get the showPromptOnce property + * + * @return the showPromptOnce property. + */ + public boolean getShowPromptOnce() + { + return showPromptOnce; + } + + /** + * Show the prompt once. Once the component has gained/lost focus + * once, the prompt will not be shown again. + * + * @param showPromptOnce when true the prompt will only be shown once, + * otherwise it will be shown repeatedly. + */ + public void setShowPromptOnce(boolean showPromptOnce) + { + this.showPromptOnce = showPromptOnce; + } + + /** + * Check whether the prompt should be visible or not. The visibility + * will change on updates to the Document and on focus changes. + */ + private void checkForPrompt() + { + // Text has been entered, remove the prompt + + if (document.getLength() > 0) + { + setVisible( false ); + return; + } + + // Prompt has already been shown once, remove it + + if (showPromptOnce && focusLost > 0) + { + setVisible(false); + return; + } + + // Check the Show property and component focus to determine if the + // prompt should be displayed. + + if (component.hasFocus()) + { + if (show == Show.ALWAYS + || show == Show.FOCUS_GAINED) + setVisible( true ); + else + setVisible( false ); + } + else + { + if (show == Show.ALWAYS + || show == Show.FOCUS_LOST) + setVisible( true ); + else + setVisible( false ); + } + } + +// Implement FocusListener + + public void focusGained(FocusEvent e) + { + checkForPrompt(); + } + + public void focusLost(FocusEvent e) + { + focusLost++; + checkForPrompt(); + } + +// Implement DocumentListener + + public void insertUpdate(DocumentEvent e) + { + checkForPrompt(); + } + + public void removeUpdate(DocumentEvent e) + { + checkForPrompt(); + } + + public void changedUpdate(DocumentEvent e) {} +} \ No newline at end of file diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/components/vars/VariableComponent.java b/extension/src/main/java/synfron/reshaper/burp/ui/components/vars/VariableComponent.java index 11e183e..fa6adbd 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/components/vars/VariableComponent.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/components/vars/VariableComponent.java @@ -5,10 +5,12 @@ import burp.api.montoya.ui.editor.HttpRequestEditor; import burp.api.montoya.ui.editor.HttpResponseEditor; import burp.api.montoya.ui.editor.WebSocketMessageEditor; +import lombok.Getter; import lombok.SneakyThrows; import net.miginfocom.swing.MigLayout; import synfron.reshaper.burp.core.events.IEventListener; import synfron.reshaper.burp.core.events.PropertyChangedArgs; +import synfron.reshaper.burp.core.settings.Workspace; import synfron.reshaper.burp.core.utils.BurpUtils; import synfron.reshaper.burp.core.vars.VariableValueType; import synfron.reshaper.burp.ui.components.IFormComponent; @@ -28,6 +30,8 @@ public class VariableComponent extends JPanel implements IFormComponent { private final VariableModel model; + @Getter + private final Workspace workspace; private JTextField variableName; private Editor variableText; private JComboBox valueType; @@ -38,6 +42,7 @@ public class VariableComponent extends JPanel implements IFormComponent { private JPanel variableTextContainer; public VariableComponent(VariableModel model) { + this.workspace = getHostedWorkspace(); this.model = model; model.withListener(variablePropertyChangedListener); initComponent(); @@ -153,7 +158,7 @@ private void onValueTypeChanged(ActionEvent actionEvent) { } private void onVariableTextChanged(ActionEvent actionEvent) { - SwingUtilities.invokeLater(() -> { + createInvokeLaterEntryPoint(() -> { if (!model.isSaved() || !Objects.equals(getVariableText(), model.getValue())) { model.setValue(getVariableText()); } @@ -252,4 +257,10 @@ private void onSave(ActionEvent actionEvent) { JOptionPane.ERROR_MESSAGE); } } + + @Override + @SuppressWarnings("unchecked") + public T getComponent() { + return (T) this; + } } diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/components/vars/VariableListComponent.java b/extension/src/main/java/synfron/reshaper/burp/ui/components/vars/VariableListComponent.java index 477451b..119fc50 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/components/vars/VariableListComponent.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/components/vars/VariableListComponent.java @@ -1,13 +1,16 @@ package synfron.reshaper.burp.ui.components.vars; import com.alexandriasoftware.swing.JSplitButton; +import lombok.Getter; import synfron.reshaper.burp.core.events.CollectionChangedArgs; import synfron.reshaper.burp.core.events.IEventListener; import synfron.reshaper.burp.core.events.PropertyChangedArgs; import synfron.reshaper.burp.core.events.PropertyChangedEvent; -import synfron.reshaper.burp.core.vars.GlobalVariables; +import synfron.reshaper.burp.core.settings.Workspace; import synfron.reshaper.burp.core.vars.Variable; import synfron.reshaper.burp.core.vars.Variables; +import synfron.reshaper.burp.ui.components.workspaces.IWorkspaceDependentComponent; +import synfron.reshaper.burp.ui.components.workspaces.IWorkspaceHost; import synfron.reshaper.burp.ui.models.vars.VariableModel; import synfron.reshaper.burp.ui.utils.ListCellRenderer; @@ -25,7 +28,9 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -public class VariableListComponent extends JPanel implements HierarchyListener { +public class VariableListComponent extends JPanel implements HierarchyListener, IWorkspaceDependentComponent, IWorkspaceHost { + @Getter + private final Workspace workspace; private JList variableList; private DefaultListModel variableListModel; private VariableContainerComponent variableContainer; @@ -38,6 +43,7 @@ public class VariableListComponent extends JPanel implements HierarchyListener { private final PropertyChangedEvent activationChangedEvent = new PropertyChangedEvent(); public VariableListComponent() { + this.workspace = getHostedWorkspace(this); initComponent(); } @@ -45,7 +51,7 @@ private void initComponent() { setLayout(new BorderLayout()); variableListModel = new DefaultListModel<>(); - variableListModel.addAll(GlobalVariables.get().getValues().stream() + variableListModel.addAll(workspace.getGlobalVariables().getValues().stream() .map(variable -> new VariableModel(variable).withListener(variableModelChangedListener) .bindActivationChangedEvent(activationChangedEvent)) .collect(Collectors.toList())); @@ -57,7 +63,7 @@ private void initComponent() { JScrollPane scrollPane = new JScrollPane(); scrollPane.setViewportView(variableList); - GlobalVariables.get().getCollectionChangedEvent().add(variablesCollectionChangedListener); + workspace.getGlobalVariables().getCollectionChangedEvent().add(variablesCollectionChangedListener); variableList.addListSelectionListener(this::onSelectionChanged); add(scrollPane, BorderLayout.CENTER); @@ -67,7 +73,7 @@ private void initComponent() { private void onSelectionChanged(ListSelectionEvent listSelectionEvent) { VariableModel variable = variableList.getSelectedValue(); if (variable != null) { - variableContainer.setModel(variable); + createEntryPoint(() -> variableContainer.setModel(variable)); if (activated) { activationChangedEvent.invoke(new PropertyChangedArgs( this, @@ -155,7 +161,7 @@ private void onNewVariableModelChanged(PropertyChangedArgs propertyChangedArgs) private void onDelete(ActionEvent actionEvent) { VariableModel variable = variableList.getSelectedValue(); if (variable != null) { - if (!GlobalVariables.get().remove(Variables.asKey(variable.getName(), variable.isList()))) { + if (!workspace.getGlobalVariables().remove(Variables.asKey(variable.getName(), variable.isList()))) { variableListModel.removeElement(variable); } } @@ -170,7 +176,7 @@ private boolean defaultSelect() { } private void onVariablesCollectionChanged(CollectionChangedArgs collectionChangedArgs) { - SwingUtilities.invokeLater(() -> { + createInvokeLaterEntryPoint(() -> { Variable item = (Variable) collectionChangedArgs.getItem(); switch (collectionChangedArgs.getAction()) { case Add -> { @@ -199,7 +205,7 @@ private void onVariablesCollectionChanged(CollectionChangedArgs collectionChange .filter(model -> model.getVariable() == null); variableListModel.clear(); variableListModel.addAll(Stream.concat( - GlobalVariables.get().getValues().stream() + workspace.getGlobalVariables().getValues().stream() .map(variable -> variableModelMap.containsKey(variable) ? variableModelMap.get(variable) : new VariableModel(variable).withListener(variableModelChangedListener) diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/components/workspaces/IWorkspaceDependent.java b/extension/src/main/java/synfron/reshaper/burp/ui/components/workspaces/IWorkspaceDependent.java new file mode 100644 index 0000000..1c3c515 --- /dev/null +++ b/extension/src/main/java/synfron/reshaper/burp/ui/components/workspaces/IWorkspaceDependent.java @@ -0,0 +1,10 @@ +package synfron.reshaper.burp.ui.components.workspaces; + +import synfron.reshaper.burp.core.settings.Workspace; +import synfron.reshaper.burp.core.settings.Workspaces; + +public interface IWorkspaceDependent { + default Workspace getHostedWorkspace() { + return Workspaces.get().getCurrentWorkspace(); + } +} diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/components/workspaces/IWorkspaceDependentComponent.java b/extension/src/main/java/synfron/reshaper/burp/ui/components/workspaces/IWorkspaceDependentComponent.java new file mode 100644 index 0000000..d0ffd30 --- /dev/null +++ b/extension/src/main/java/synfron/reshaper/burp/ui/components/workspaces/IWorkspaceDependentComponent.java @@ -0,0 +1,20 @@ +package synfron.reshaper.burp.ui.components.workspaces; + +import synfron.reshaper.burp.core.settings.Workspace; +import synfron.reshaper.burp.core.settings.Workspaces; + +import javax.swing.*; +import java.awt.*; + +public interface IWorkspaceDependentComponent { + default Workspace getHostedWorkspace(T component) { + Workspace workspace = Workspaces.get().getCurrentWorkspace(); + if (workspace == null) { + IWorkspaceHost workspaceHost = (IWorkspaceHost) SwingUtilities.getAncestorOfClass(IWorkspaceHost.class, component); + if (workspaceHost != null) { + workspace = workspaceHost.getWorkspace(); + } + } + return workspace; + } +} diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/components/workspaces/IWorkspaceHost.java b/extension/src/main/java/synfron/reshaper/burp/ui/components/workspaces/IWorkspaceHost.java new file mode 100644 index 0000000..6f07ef6 --- /dev/null +++ b/extension/src/main/java/synfron/reshaper/burp/ui/components/workspaces/IWorkspaceHost.java @@ -0,0 +1,31 @@ +package synfron.reshaper.burp.ui.components.workspaces; + +import synfron.reshaper.burp.core.settings.Workspace; +import synfron.reshaper.burp.core.settings.Workspaces; + +import javax.swing.*; +import java.util.function.Supplier; + +public interface IWorkspaceHost { + Workspace getWorkspace(); + + default void createEntryPoint(Runnable job) { + Workspace workspace = getWorkspace(); + Workspaces.get().setCurrentWorkspace(workspace); + job.run(); + } + + default void createInvokeLaterEntryPoint(Runnable job) { + Workspace workspace = getWorkspace(); + SwingUtilities.invokeLater(() -> { + Workspaces.get().setCurrentWorkspace(workspace); + job.run(); + }); + } + + default T createEntryPoint(Supplier job) { + Workspace workspace = getWorkspace(); + Workspaces.get().setCurrentWorkspace(workspace); + return job.get(); + } +} diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/components/workspaces/WorkspaceComponent.java b/extension/src/main/java/synfron/reshaper/burp/ui/components/workspaces/WorkspaceComponent.java new file mode 100644 index 0000000..41a0ae8 --- /dev/null +++ b/extension/src/main/java/synfron/reshaper/burp/ui/components/workspaces/WorkspaceComponent.java @@ -0,0 +1,88 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package synfron.reshaper.burp.ui.components.workspaces; + +import lombok.Getter; +import synfron.reshaper.burp.core.ProtocolType; +import synfron.reshaper.burp.core.WorkspaceTab; +import synfron.reshaper.burp.core.events.IEventListener; +import synfron.reshaper.burp.core.events.PropertyChangedArgs; +import synfron.reshaper.burp.core.settings.Workspace; +import synfron.reshaper.burp.core.settings.Workspaces; +import synfron.reshaper.burp.ui.components.LogsComponent; +import synfron.reshaper.burp.ui.components.rules.RulesTabComponent; +import synfron.reshaper.burp.ui.components.settings.SettingsTabComponent; +import synfron.reshaper.burp.ui.components.vars.VariablesTabComponent; +import synfron.reshaper.burp.ui.utils.UiMessageHandler; + +import javax.swing.*; +import java.awt.*; +import java.util.HashMap; + +public class WorkspaceComponent extends JPanel implements IWorkspaceHost { + + private final IEventListener generalSettingsChangedListener = this::onGeneralSettingsChanged; + @Getter + private final Workspace workspace; + private JTabbedPane tabs; + private final UiMessageHandler uiMessageHandler; + + public WorkspaceComponent(Workspace workspace) { + this.workspace = workspace; + this.uiMessageHandler = new UiMessageHandler(workspace.getMessageEvent()); + initComponents(); + } + + private void initComponents() { + setLayout(new BorderLayout()); + add(getTabs()); + + workspace.getGeneralSettings().withListener(generalSettingsChangedListener); + } + + private JTabbedPane getTabs() { + tabs = new JTabbedPane(); + + addOrRemoveTabs(); + return tabs; + } + + private void onGeneralSettingsChanged(PropertyChangedArgs propertyChangedArgs) { + if (propertyChangedArgs.getName().equals("hiddenTabs")) { + addOrRemoveTabs(); + } + } + + private void addOrRemoveTabs() { + Workspaces.get().setCurrentWorkspace(workspace); + HashMap existingTabs = new HashMap<>(); + for (int tabIndex = 0; tabIndex < tabs.getTabCount(); tabIndex++) { + WorkspaceTab tab = WorkspaceTab.byName(tabs.getTitleAt(tabIndex)); + existingTabs.put(tab, tabs.getComponentAt(tabIndex)); + } + Component activeTab = tabs.getSelectedComponent(); + tabs.removeAll(); + + for (WorkspaceTab tab : WorkspaceTab.values()) { + if (!tab.isHideable() || !workspace.getGeneralSettings().getHiddenTabs().contains(tab)) { + Component component = existingTabs.get(tab); + if (component == null) { + component = createEntryPoint(() -> switch (tab) { + case HttpRules -> new RulesTabComponent(ProtocolType.Http); + case WebSocketRules -> new RulesTabComponent(ProtocolType.WebSocket); + case GlobalVariables -> new VariablesTabComponent(); + case Logs -> new LogsComponent(); + case Settings -> new SettingsTabComponent(); + }); + } + tabs.addTab(tab.toString(), component); + if (activeTab == component) { + tabs.setSelectedComponent(component); + } + } + } + } +} diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/components/workspaces/WorkspaceNameOptionPane.java b/extension/src/main/java/synfron/reshaper/burp/ui/components/workspaces/WorkspaceNameOptionPane.java new file mode 100644 index 0000000..a12c716 --- /dev/null +++ b/extension/src/main/java/synfron/reshaper/burp/ui/components/workspaces/WorkspaceNameOptionPane.java @@ -0,0 +1,77 @@ +package synfron.reshaper.burp.ui.components.workspaces; + +import net.miginfocom.swing.MigLayout; +import synfron.reshaper.burp.ui.components.IFormComponent; +import synfron.reshaper.burp.ui.models.workspaces.WorkspaceNameModel; +import synfron.reshaper.burp.ui.utils.DocumentActionListener; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.beans.PropertyChangeEvent; +import java.util.Objects; + +public class WorkspaceNameOptionPane extends JOptionPane implements IFormComponent { + + private final JPanel container; + private final WorkspaceNameModel model; + private JTextField workspaceName; + + private WorkspaceNameOptionPane(WorkspaceNameModel model) { + super(new JPanel(new BorderLayout()), JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION, null, new Object[]{ "OK", "Cancel" }, "OK"); + container = (JPanel)message; + this.model = model; + addPropertyChangeListener(JOptionPane.VALUE_PROPERTY, this::onPropertyChanged); + initComponent(); + } + + private void onPropertyChanged(PropertyChangeEvent event) { + if (Objects.equals(getValue(), "OK")) { + if (!model.save()) { + JOptionPane.showMessageDialog(this, + String.join("\n", model.validate()), + "Validation Error", + JOptionPane.ERROR_MESSAGE); + } + } else { + model.setDismissed(true); + } + } + + public static void showDialog(WorkspaceNameModel model) { + WorkspaceNameOptionPane optionPane = new WorkspaceNameOptionPane(model); + JDialog dialog = optionPane.createDialog("Workspace"); + dialog.setResizable(true); + + dialog.setModal(false); + dialog.setVisible(true); + } + + private void initComponent() { + container.add(getBody(), BorderLayout.CENTER); + } + + private Component getBody() { + JPanel container = new JPanel(new MigLayout()); + + workspaceName = createTextField(false); + + workspaceName.setText(model.getWorkspaceName()); + + workspaceName.getDocument().addDocumentListener(new DocumentActionListener(this::onWorkspaceNameChanged)); + + container.add(getLabeledField("Workspace Name", workspaceName), "wrap");; + + return container; + } + + private void onWorkspaceNameChanged(ActionEvent actionEvent) { + model.setWorkspaceName(workspaceName.getText()); + } + + @Override + @SuppressWarnings("unchecked") + public T getComponent() { + return (T) this; + } +} diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/models/rules/RuleModel.java b/extension/src/main/java/synfron/reshaper/burp/ui/models/rules/RuleModel.java index a3bb54c..ba8f389 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/models/rules/RuleModel.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/models/rules/RuleModel.java @@ -26,6 +26,8 @@ public class RuleModel { @Getter private boolean autoRun; @Getter + private final ProtocolType protocolType; + @Getter private boolean isNew; @Getter private String name; @@ -45,6 +47,7 @@ public RuleModel(ProtocolType protocolType, Rule rule) { } public RuleModel(ProtocolType protocolType, Rule rule, boolean isNew) { + this.protocolType = protocolType; this.isNew = isNew; this.rule = rule; this.whens = rule.getWhens().stream() @@ -95,6 +98,16 @@ public void setAutoRun(boolean autoRun) { propertyChanged("autoRun", autoRun); } + public void addWhen(WhenModel when) { + whens.add(when.withListener(ruleOperationChangedListener)); + propertyChanged("whens", whens); + } + + public void addThen(ThenModel then) { + thens.add(then.withListener(ruleOperationChangedListener)); + propertyChanged("thens", thens); + } + private void propertyChanged(String name, Object value) { setSaved(false); propertyChangedEvent.invoke(new PropertyChangedArgs(this, name, value)); diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/models/rules/whens/WhenFromToolModel.java b/extension/src/main/java/synfron/reshaper/burp/ui/models/rules/whens/WhenFromToolModel.java index 76dc900..477bcd2 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/models/rules/whens/WhenFromToolModel.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/models/rules/whens/WhenFromToolModel.java @@ -31,7 +31,7 @@ public boolean persist() { @Override protected String getTargetName() { - return tool.name(); + return tool.toString(); } @Override diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/models/rules/wizard/matchreplace/MatchAndReplaceWizardModel.java b/extension/src/main/java/synfron/reshaper/burp/ui/models/rules/wizard/matchreplace/MatchAndReplaceWizardModel.java new file mode 100644 index 0000000..55f2309 --- /dev/null +++ b/extension/src/main/java/synfron/reshaper/burp/ui/models/rules/wizard/matchreplace/MatchAndReplaceWizardModel.java @@ -0,0 +1,239 @@ +package synfron.reshaper.burp.ui.models.rules.wizard.matchreplace; + +import lombok.Getter; +import lombok.Setter; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import synfron.reshaper.burp.core.events.IEventListener; +import synfron.reshaper.burp.core.events.PropertyChangedArgs; +import synfron.reshaper.burp.core.events.PropertyChangedEvent; +import synfron.reshaper.burp.core.messages.HttpDataDirection; +import synfron.reshaper.burp.core.messages.MessageValue; +import synfron.reshaper.burp.core.rules.thens.ThenSetValue; +import synfron.reshaper.burp.core.rules.whens.WhenEventDirection; +import synfron.reshaper.burp.core.rules.whens.WhenMatchesText; +import synfron.reshaper.burp.core.vars.VariableString; +import synfron.reshaper.burp.ui.models.rules.RuleModel; +import synfron.reshaper.burp.ui.models.rules.thens.ThenSetValueModel; +import synfron.reshaper.burp.ui.models.rules.whens.WhenEventDirectionModel; +import synfron.reshaper.burp.ui.models.rules.whens.WhenMatchesTextModel; +import synfron.reshaper.burp.ui.models.rules.whens.WhenModel; +import synfron.reshaper.burp.ui.utils.IPrompterModel; +import synfron.reshaper.burp.ui.utils.ModalPrompter; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +@Getter +public class MatchAndReplaceWizardModel implements IPrompterModel { + private final RuleModel rule; + + private final PropertyChangedEvent propertyChangedEvent = new PropertyChangedEvent(); + + private boolean invalidated; + private boolean dismissed; + private MatchType matchType = MatchType.Url; + private String identifier = ""; + private String match = ""; + private String replace = ""; + private boolean regexMatch; + + + @Setter + private ModalPrompter modalPrompter; + + public MatchAndReplaceWizardModel(RuleModel rule) { + this.rule = rule; + } + + public void resetPropertyChangedListener() { + propertyChangedEvent.clearListeners(); + } + + private void propertyChanged(String name, Object value) { + propertyChangedEvent.invoke(new PropertyChangedArgs(this, name, value)); + } + + public void setMatchType(MatchType matchType) { + this.matchType = matchType; + propertyChanged("matchType", matchType); + } + + public void setIdentifier(String identifier) { + this.identifier = identifier; + propertyChanged("identifier", identifier); + } + + public void setMatch(String match) { + this.match = match; + propertyChanged("match", match); + } + + public void setReplace(String replace) { + this.replace = replace; + propertyChanged("replace", replace); + } + + public void setRegexMatch(boolean regexMatch) { + this.regexMatch = regexMatch; + propertyChanged("regexMatch", regexMatch); + } + + public MatchAndReplaceWizardModel withListener(IEventListener listener) { + propertyChangedEvent.add(listener); + return this; + } + + public List validate() { + List errors = new ArrayList<>(); + if (!matchType.isAllowEmptyMatch() && StringUtils.isEmpty(match)) { + errors.add("Match is required"); + } + return errors; + } + + public void setInvalidated(boolean invalidated) { + this.invalidated = invalidated; + propertyChanged("invalidated", invalidated); + } + + public void setDismissed(boolean dismissed) { + this.dismissed = dismissed; + propertyChanged("dismissed", dismissed); + } + + public boolean updateRule() { + if (validate().isEmpty()) { + boolean useContainsMatch = !regexMatch && !matchType.isRequiresRegex(); + String whenMatchText = regexMatch || useContainsMatch ? match : Pattern.quote(match); + String thenMatchText = regexMatch ? match : Pattern.quote(match); + String thenReplace = replace; + MessageValue messageValue = matchType.getMessageValue(); + + if (!match.isEmpty()) { + switch (matchType) { + case RequestHeaderLine, ResponseHeaderLine -> { + whenMatchText = "(?m)" + whenMatchText; + thenMatchText = "(?m)" + thenMatchText; + } + case RequestParamName -> { + whenMatchText = "(?<=^|&)([^=]*?)" + whenMatchText.replaceAll("(? { + whenMatchText = "(?<==)([^&]*?)" + whenMatchText.replaceAll("(? { + whenMatchText = "(?<=^|(;\\s?))([^=]*?)" + whenMatchText.replaceAll("(? { + whenMatchText = "(?<==)([^;]*?)" + whenMatchText.replaceAll("(? { + ThenSetValue removeExtraLines = new ThenSetValue(); + removeExtraLines.setSourceMessageValue(messageValue); + removeExtraLines.setUseMessageValue(true); + removeExtraLines.setDestinationMessageValue(messageValue); + removeExtraLines.setUseReplace(true); + removeExtraLines.setRegexPattern(VariableString.getAsVariableString("(\\r\\n)+", true)); + removeExtraLines.setReplacementText(VariableString.getAsVariableString("{{s:rn}}", true)); + rule.addThen(new ThenSetValueModel(rule.getProtocolType(), removeExtraLines, true)); + } + } + } else { + switch (matchType) { + case RequestHeaderLine, ResponseHeaderLine -> { + ThenSetValue addHeader = new ThenSetValue(); + addHeader.setText(VariableString.getAsVariableString(String.format("{{m:%s}}\r\n%s", + messageValue.name(), + thenReplace + ),true)); + addHeader.setUseMessageValue(false); + addHeader.setDestinationMessageValue(messageValue); + rule.addThen(new ThenSetValueModel(rule.getProtocolType(), addHeader, true)); + } + } + } + } + + private void addWhens(MessageValue messageValue, boolean useContainsMatch, String whenMatchText) { + boolean directionSet = false; + HttpDataDirection eventDirection = ObjectUtils.defaultIfNull(messageValue.getDataDirection(), HttpDataDirection.Request); + for (int ruleIndex = rule.getWhens().size() - 1; ruleIndex >= 0; ruleIndex--) { + WhenModel whenModel = rule.getWhens().get(ruleIndex); + if (whenModel instanceof WhenEventDirectionModel whenEventDirectionModel) { + if (whenEventDirectionModel.getDataDirection() != eventDirection) { + whenEventDirectionModel.setDataDirection(eventDirection); + } + directionSet = true; + } + } + if (!directionSet) { + WhenEventDirection whenEventDirection = new WhenEventDirection(); + whenEventDirection.setDataDirection(eventDirection); + rule.addWhen(new WhenEventDirectionModel(rule.getProtocolType(), whenEventDirection, true)); + } + WhenMatchesText whenMatchesText = new WhenMatchesText(); + whenMatchesText.setMatchType(useContainsMatch ? + synfron.reshaper.burp.core.rules.MatchType.Contains : + synfron.reshaper.burp.core.rules.MatchType.Regex + ); + whenMatchesText.setMatchText(VariableString.getAsVariableString(whenMatchText, true)); + whenMatchesText.setUseMessageValue(true); + whenMatchesText.setMessageValue(matchType.getMessageValue()); + if (matchType.hasIdentifier()) { + whenMatchesText.setIdentifier(matchType.hasCustomIdentifier() ? + VariableString.getAsVariableString(identifier, true) : + VariableString.getAsVariableString(matchType.getIdentifier(), false) + ); + } + + rule.addWhen(new WhenMatchesTextModel(rule.getProtocolType(), whenMatchesText, true)); + } +} diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/models/rules/wizard/matchreplace/MatchType.java b/extension/src/main/java/synfron/reshaper/burp/ui/models/rules/wizard/matchreplace/MatchType.java new file mode 100644 index 0000000..d4e7845 --- /dev/null +++ b/extension/src/main/java/synfron/reshaper/burp/ui/models/rules/wizard/matchreplace/MatchType.java @@ -0,0 +1,53 @@ +package synfron.reshaper.burp.ui.models.rules.wizard.matchreplace; + +import lombok.Getter; +import synfron.reshaper.burp.core.messages.MessageValue; + +@Getter +public enum MatchType { + Url("URL", MessageValue.Url, false, false), + RequestFirstLine("Request First Line", MessageValue.HttpRequestStatusLine, false, false), + RequestHeaderLine("Request Header Line", MessageValue.HttpRequestHeaders, true, true), + RequestParamName("Request Param Name", MessageValue.HttpRequestUriQueryParameters, false, true), + RequestParamValue("Request Param Value", MessageValue.HttpRequestUriQueryParameters, false, true), + RequestParamValueByName("Request Param Value By Name", MessageValue.HttpRequestUriQueryParameters, false, true), + RequestHeaderValueByName("Request Header Value By Name", MessageValue.HttpRequestHeader, false, false), + RequestCookieName("Request Cookie Name", MessageValue.HttpRequestHeader, "Cookie", false, true), + RequestCookieValue("Request Cookie Value", MessageValue.HttpRequestHeader, "Cookie", false, true), + RequestCookieValueByName("Request Cookie Value By Name", MessageValue.HttpRequestCookie, false, true), + RequestBody("Request Body", MessageValue.HttpRequestBody, false, false), + ResponseHeaderLine("Response Header Line", MessageValue.HttpResponseHeaders, true, true), + ResponseHeaderValueByName("Response Header Value By Name", MessageValue.HttpResponseHeader, false, false), + ResponseBody("Response Body", MessageValue.HttpResponseBody, false, false); + + private final String name; + private final MessageValue messageValue; + private final String identifier; + private final boolean allowEmptyMatch; + private final boolean requiresRegex; + + MatchType(String name, MessageValue messageValue, boolean allowEmptyMatch, boolean requiresRegex) { + this(name, messageValue, null, allowEmptyMatch, requiresRegex); + } + + MatchType(String name, MessageValue messageValue, String identifier, boolean allowEmptyMatch, boolean requiresRegex) { + this.name = name; + this.messageValue = messageValue; + this.identifier = identifier; + this.allowEmptyMatch = allowEmptyMatch; + this.requiresRegex = requiresRegex; + } + + @Override + public String toString() { + return name; + } + + public boolean hasIdentifier() { + return messageValue.isIdentifierRequired(); + } + + public boolean hasCustomIdentifier() { + return messageValue.isIdentifierRequired() && identifier == null; + } +} diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/models/rules/wizard/vars/GlobalListVariableTagWizardModel.java b/extension/src/main/java/synfron/reshaper/burp/ui/models/rules/wizard/vars/GlobalListVariableTagWizardModel.java index 4df2163..052af3d 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/models/rules/wizard/vars/GlobalListVariableTagWizardModel.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/models/rules/wizard/vars/GlobalListVariableTagWizardModel.java @@ -1,12 +1,16 @@ package synfron.reshaper.burp.ui.models.rules.wizard.vars; -import synfron.reshaper.burp.core.vars.*; +import synfron.reshaper.burp.core.vars.Variable; +import synfron.reshaper.burp.core.vars.VariableSource; +import synfron.reshaper.burp.core.vars.VariableSourceEntry; +import synfron.reshaper.burp.core.vars.VariableString; +import synfron.reshaper.burp.ui.components.workspaces.IWorkspaceDependent; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; -public class GlobalListVariableTagWizardModel extends CustomListVariableTagWizardModel { +public class GlobalListVariableTagWizardModel extends CustomListVariableTagWizardModel implements IWorkspaceDependent { public GlobalListVariableTagWizardModel(List entries) { super(entries); @@ -15,7 +19,7 @@ public GlobalListVariableTagWizardModel(List entries) { @Override public List getUpdatedVariableNames(List entries) { return Stream.concat( - GlobalVariables.get().getValues().stream().filter(Variable::isList).map(Variable::getName), + getHostedWorkspace().getGlobalVariables().getValues().stream().filter(Variable::isList).map(Variable::getName), entries.stream() .filter(entry -> entry.getVariableSource() == VariableSource.GlobalList) .map(entry -> entry.getParams().getFirst()) diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/models/rules/wizard/vars/GlobalVariableTagWizardModel.java b/extension/src/main/java/synfron/reshaper/burp/ui/models/rules/wizard/vars/GlobalVariableTagWizardModel.java index 4088b13..df89f90 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/models/rules/wizard/vars/GlobalVariableTagWizardModel.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/models/rules/wizard/vars/GlobalVariableTagWizardModel.java @@ -1,12 +1,16 @@ package synfron.reshaper.burp.ui.models.rules.wizard.vars; -import synfron.reshaper.burp.core.vars.*; +import synfron.reshaper.burp.core.vars.Variable; +import synfron.reshaper.burp.core.vars.VariableSource; +import synfron.reshaper.burp.core.vars.VariableSourceEntry; +import synfron.reshaper.burp.core.vars.VariableString; +import synfron.reshaper.burp.ui.components.workspaces.IWorkspaceDependent; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; -public class GlobalVariableTagWizardModel extends CustomVariableTagWizardModel { +public class GlobalVariableTagWizardModel extends CustomVariableTagWizardModel implements IWorkspaceDependent { public GlobalVariableTagWizardModel(List entries) { super(entries); @@ -15,7 +19,7 @@ public GlobalVariableTagWizardModel(List entries) { @Override public List getUpdatedVariableNames(List entries) { return Stream.concat( - GlobalVariables.get().getValues().stream().filter(variable -> !variable.isList()).map(Variable::getName), + getHostedWorkspace().getGlobalVariables().getValues().stream().filter(variable -> !variable.isList()).map(Variable::getName), entries.stream() .filter(entry -> entry.getVariableSource() == VariableSource.Global) .map(entry -> entry.getParams().getFirst()) diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/models/rules/wizard/whens/WhenWizardModel.java b/extension/src/main/java/synfron/reshaper/burp/ui/models/rules/wizard/whens/WhenWizardModel.java index 8071d1d..2c195d9 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/models/rules/wizard/whens/WhenWizardModel.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/models/rules/wizard/whens/WhenWizardModel.java @@ -1,6 +1,5 @@ package synfron.reshaper.burp.ui.models.rules.wizard.whens; -import burp.BurpExtender; import lombok.Getter; import lombok.Setter; import org.apache.commons.lang3.StringUtils; @@ -21,22 +20,17 @@ import java.util.LinkedList; import java.util.List; +@Getter public class WhenWizardModel implements IPrompterModel { - @Getter private final EventInfo eventInfo; - @Getter private String ruleName; - @Getter private final List items = new ArrayList<>(); - @Getter private final PropertyChangedEvent propertyChangedEvent = new PropertyChangedEvent(); - @Getter private boolean invalidated; - @Getter private boolean dismissed; - @Setter @Getter + @Setter private ModalPrompter modalPrompter; public WhenWizardModel(EventInfo eventInfo) { @@ -133,12 +127,12 @@ public boolean createRule() { WhenEventDirection direction = new WhenEventDirection(); direction.setDataDirection(requiresResponse ? HttpDataDirection.Response : HttpDataDirection.Request); whens.addFirst(direction); - rulesRegistry = BurpExtender.getHttpConnector().getRulesEngine().getRulesRegistry(); + rulesRegistry = eventInfo.getWorkspace().getHttpConnector().getRulesEngine().getRulesRegistry(); } else if (eventInfo instanceof WebSocketEventInfo webSocketEventInfo) { WhenWebSocketEventDirection direction = new WhenWebSocketEventDirection(); direction.setDataDirection(webSocketEventInfo.getDataDirection()); whens.addFirst(direction); - rulesRegistry = BurpExtender.getWebSocketConnector().getRulesEngine().getRulesRegistry(); + rulesRegistry = eventInfo.getWorkspace().getWebSocketConnector().getRulesEngine().getRulesRegistry(); } if (rulesRegistry != null) { diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/models/settings/HideItemsModel.java b/extension/src/main/java/synfron/reshaper/burp/ui/models/settings/HideItemsModel.java index d520efb..894e9ff 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/models/settings/HideItemsModel.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/models/settings/HideItemsModel.java @@ -2,7 +2,7 @@ import lombok.Getter; import lombok.Setter; -import synfron.reshaper.burp.core.Tab; +import synfron.reshaper.burp.core.WorkspaceTab; import synfron.reshaper.burp.core.events.IEventListener; import synfron.reshaper.burp.core.events.PropertyChangedArgs; import synfron.reshaper.burp.core.events.PropertyChangedEvent; @@ -17,7 +17,7 @@ public class HideItemsModel implements IPrompterModel { private final GeneralSettings generalSettings; private final HashSet hiddenThenTypes = new HashSet<>(); private final HashSet hiddenWhenTypes = new HashSet<>(); - private final HashSet hiddenTabs = new HashSet<>(); + private final HashSet hiddenTabs = new HashSet<>(); private final PropertyChangedEvent propertyChangedEvent = new PropertyChangedEvent(); @Setter private ModalPrompter modalPrompter; @@ -50,7 +50,7 @@ public void addHiddenThenType(String thenType) { propertyChanged("hiddenThenTypes", thenType); } - public void addHiddenTab(Tab tab) { + public void addHiddenTab(WorkspaceTab tab) { hiddenTabs.add(tab); propertyChanged("hiddenTabs", tab); } @@ -65,7 +65,7 @@ public void removeHiddenThenType(String thenType) { propertyChanged("hiddenThenTypes", thenType); } - public void removeHiddenTab(Tab tab) { + public void removeHiddenTab(WorkspaceTab tab) { hiddenTabs.remove(tab); propertyChanged("hiddenTabs", tab); } diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/models/vars/VariableModel.java b/extension/src/main/java/synfron/reshaper/burp/ui/models/vars/VariableModel.java index fddd03e..4b956ec 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/models/vars/VariableModel.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/models/vars/VariableModel.java @@ -6,15 +6,19 @@ import synfron.reshaper.burp.core.events.IEventListener; import synfron.reshaper.burp.core.events.PropertyChangedArgs; import synfron.reshaper.burp.core.events.PropertyChangedEvent; +import synfron.reshaper.burp.core.settings.Workspace; import synfron.reshaper.burp.core.utils.TextUtils; import synfron.reshaper.burp.core.vars.*; +import synfron.reshaper.burp.ui.components.workspaces.IWorkspaceDependent; +import synfron.reshaper.burp.ui.components.workspaces.IWorkspaceHost; -import javax.swing.*; import java.util.ArrayList; import java.util.List; import java.util.Objects; -public class VariableModel { +public class VariableModel implements IWorkspaceHost, IWorkspaceDependent { + @Getter + private final Workspace workspace; @Getter private Variable variable; @Getter @@ -40,12 +44,14 @@ public class VariableModel { private final IEventListener activationChanged = this::onActivationChanged; public VariableModel(boolean isList) { + this.workspace = getHostedWorkspace(); this.isList = isList; delimiter = StringEscapeUtils.escapeJava("\n"); saved = false; } public VariableModel(Variable variable) { + this.workspace = getHostedWorkspace(); this.variable = variable.withListener(variableChanged); name = variable.getName(); valueType = variable.getValueType(); @@ -66,7 +72,7 @@ private void onVariableChanged(PropertyChangedArgs propertyChangedArgs) { } private void syncProperties() { - SwingUtilities.invokeLater(() -> { + createInvokeLaterEntryPoint(() -> { value = StringUtils.defaultString(TextUtils.toString(variable.getValue())); if (isList) { delimiter = StringEscapeUtils.escapeJava(((ListVariable) variable).getDelimiter()); @@ -106,7 +112,7 @@ public List validate() { List errors = new ArrayList<>(); if (StringUtils.isEmpty(name)) { errors.add("Variable Name is required"); - } else if ((variable == null || !StringUtils.equals(variable.getName(), name)) && GlobalVariables.get().has(Variables.asKey(name, isList))) { + } else if ((variable == null || !StringUtils.equals(variable.getName(), name)) && workspace.getGlobalVariables().has(Variables.asKey(name, isList))) { errors.add("Variable Name must be unique"); } else if (!VariableString.isValidVariableName(name)) { errors.add("Variable Name is invalid"); @@ -157,10 +163,10 @@ public boolean save() { } Variable variable = this.variable; if (variable == null) { - variable = GlobalVariables.get().add(Variables.asKey(name, isList)); + variable = workspace.getGlobalVariables().add(Variables.asKey(name, isList)); } else if (!StringUtils.equals(variable.getName(), name)) { - GlobalVariables.get().remove(Variables.asKey(variable.getName(), isList)); - variable = GlobalVariables.get().add(Variables.asKey(name, isList)); + workspace.getGlobalVariables().remove(Variables.asKey(variable.getName(), isList)); + variable = workspace.getGlobalVariables().add(Variables.asKey(name, isList)); } if (isList) { ((ListVariable)variable).setDelimiter(StringEscapeUtils.unescapeJava(delimiter)); diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/models/workspaces/WorkspaceNameModel.java b/extension/src/main/java/synfron/reshaper/burp/ui/models/workspaces/WorkspaceNameModel.java new file mode 100644 index 0000000..8ce24c2 --- /dev/null +++ b/extension/src/main/java/synfron/reshaper/burp/ui/models/workspaces/WorkspaceNameModel.java @@ -0,0 +1,100 @@ +package synfron.reshaper.burp.ui.models.workspaces; + +import lombok.Getter; +import lombok.Setter; +import org.apache.commons.lang3.StringUtils; +import synfron.reshaper.burp.core.events.IEventListener; +import synfron.reshaper.burp.core.events.PropertyChangedArgs; +import synfron.reshaper.burp.core.events.PropertyChangedEvent; +import synfron.reshaper.burp.core.settings.Workspace; +import synfron.reshaper.burp.core.settings.Workspaces; +import synfron.reshaper.burp.ui.utils.IPrompterModel; +import synfron.reshaper.burp.ui.utils.ModalPrompter; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; + +@Getter +public class WorkspaceNameModel implements IPrompterModel { + private final Workspace workspace; + private String workspaceName; + private final Consumer onSuccess; + private final PropertyChangedEvent propertyChangedEvent = new PropertyChangedEvent(); + @Setter + private ModalPrompter modalPrompter; + private boolean dismissed; + private boolean invalidated; + + public WorkspaceNameModel(Workspace workspace) { + this(workspace, null); + } + + public WorkspaceNameModel(Workspace workspace, Consumer onSuccess) { + this.workspace = workspace; + this.workspaceName = workspace.getWorkspaceName(); + this.onSuccess = onSuccess; + } + + public WorkspaceNameModel withListener(IEventListener listener) { + getPropertyChangedEvent().add(listener); + return this; + } + + private void propertyChanged(String name, Object value) { + propertyChangedEvent.invoke(new PropertyChangedArgs(this, name, value)); + } + + public void setWorkspaceName(String workspaceName) { + this.workspaceName = workspaceName; + propertyChanged("workspaceName", workspaceName); + } + + public boolean save() { + if (validate().isEmpty()) { + workspace.setWorkspaceName(workspaceName); + + if (onSuccess != null) { + onSuccess.accept(workspace); + } + + setInvalidated(false); + return true; + } + setInvalidated(true); + return false; + } + + @Override + public void resetPropertyChangedListener() { + propertyChangedEvent.clearListeners(); + } + + @Override + public boolean isInvalidated() { + return false; + } + + public void setDismissed(boolean dismissed) { + this.dismissed = dismissed; + propertyChanged("dismissed", dismissed); + } + + public void setInvalidated(boolean invalidated) { + this.invalidated = invalidated; + propertyChanged("invalidated", invalidated); + } + + public List validate() { + List errors = new ArrayList<>(); + if (StringUtils.isEmpty(workspaceName)) { + errors.add("Workspace Name is required"); + } else if (!Objects.equals(getWorkspace().getWorkspaceName(), workspaceName) && + Workspaces.get().getWorkspaces().stream().anyMatch(workspace -> Objects.equals(workspace.getWorkspaceName(), workspaceName)) + ) { + errors.add("Workspace Name must be unique"); + } + return errors; + } +} diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/utils/ComponentVisibilityManager.java b/extension/src/main/java/synfron/reshaper/burp/ui/utils/ComponentVisibilityManager.java index b518ecd..4ee079b 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/utils/ComponentVisibilityManager.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/utils/ComponentVisibilityManager.java @@ -41,6 +41,10 @@ public static Component withVisibilityFieldChangeDependency( private static void updateVisibilityFieldChangeDependency(JPanel container, Component dependent, BooleanSupplier condition) { boolean isVisible = condition.getAsBoolean(); + updateVisibility(container, dependent, isVisible); + } + + public static void updateVisibility(JPanel container, Component dependent, boolean isVisible) { if (isVisible != dependent.isEnabled()) { if (isVisible) { container.add(dependent); diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/utils/ContextMenuHandler.java b/extension/src/main/java/synfron/reshaper/burp/ui/utils/ContextMenuHandler.java index 3ddf222..f031fcd 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/utils/ContextMenuHandler.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/utils/ContextMenuHandler.java @@ -9,6 +9,7 @@ import synfron.reshaper.burp.core.messages.WebSocketDataDirection; import synfron.reshaper.burp.core.messages.WebSocketEventInfo; import synfron.reshaper.burp.core.messages.WebSocketMessageType; +import synfron.reshaper.burp.core.settings.Workspaces; import synfron.reshaper.burp.core.utils.Log; import synfron.reshaper.burp.core.vars.Variables; import synfron.reshaper.burp.ui.components.rules.wizard.whens.WhenWizardOptionPane; @@ -38,19 +39,19 @@ public List provideMenuItems(WebSocketContextMenuEvent event) { private void onCreateWebSocketRule(List selectedItems, ActionEvent actionEvent) { WebSocketMessage webSocketMessage = selectedItems.getFirst(); - openWhenWizard(new WhenWizardModel(new WebSocketEventInfo<>(WebSocketMessageType.Binary, WebSocketDataDirection.from(webSocketMessage.direction()), null, null, webSocketMessage.upgradeRequest(), webSocketMessage.annotations(), webSocketMessage.payload().getBytes(), new Variables()))); + openWhenWizard(new WhenWizardModel(new WebSocketEventInfo<>(Workspaces.get().getDefault(), WebSocketMessageType.Binary, WebSocketDataDirection.from(webSocketMessage.direction()), null, null, webSocketMessage.upgradeRequest(), webSocketMessage.annotations(), webSocketMessage.payload().getBytes(), new Variables()))); } private void onCreateHttpRule(List selectedItems, ActionEvent actionEvent) { HttpRequestResponse httpRequestResponse = selectedItems.getFirst(); - openWhenWizard(new WhenWizardModel(new HttpEventInfo(null, null, null, httpRequestResponse.request(), httpRequestResponse.response(), httpRequestResponse.annotations(), new Variables()))); + openWhenWizard(new WhenWizardModel(new HttpEventInfo(Workspaces.get().getDefault(), null, null, null, httpRequestResponse.request(), httpRequestResponse.response(), httpRequestResponse.annotations(), new Variables()))); } private void openWhenWizard(WhenWizardModel model) { try { ModalPrompter.open(model, ignored -> WhenWizardOptionPane.showDialog(model), true); } catch (Exception e) { - Log.get().withMessage("Failed to create rule from content menu").withException(e).logErr(); + Log.get(Workspaces.get().getDefault()).withMessage("Failed to create rule from content menu").withException(e).logErr(); } } } diff --git a/extension/src/main/java/synfron/reshaper/burp/ui/utils/ModalPrompter.java b/extension/src/main/java/synfron/reshaper/burp/ui/utils/ModalPrompter.java index cd39803..4af1b81 100644 --- a/extension/src/main/java/synfron/reshaper/burp/ui/utils/ModalPrompter.java +++ b/extension/src/main/java/synfron/reshaper/burp/ui/utils/ModalPrompter.java @@ -8,10 +8,11 @@ import javax.swing.border.EmptyBorder; import java.awt.*; import java.util.Map; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; -public class ModalPrompter> implements IFormComponent { +public class ModalPrompter> { private static final Map dialogMap = new ConcurrentHashMap<>(); private final T model; private final IPrompter prompter; @@ -57,13 +58,13 @@ public static void createTextAreaDialog(String id, String title, String descript container.add(new JLabel(description), BorderLayout.PAGE_START); container.add(scrollPane, BorderLayout.CENTER); - JOptionPane optionPane = new JOptionPane(container, JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION); + JOptionPane optionPane = new JOptionPane(container, JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION, null, new Object[]{ "OK", "Cancel" }, "OK"); JDialog dialog = optionPane.createDialog(title); dialog.setResizable(true); dialogMap.put(id, dialog); optionPane.addPropertyChangeListener(JOptionPane.VALUE_PROPERTY, event -> { - if (optionPane.getValue() != null && (int)optionPane.getValue() == JOptionPane.OK_OPTION) { + if (Objects.equals(optionPane.getValue(), "OK")) { valueHandler.accept(inputText.getText()); } else { valueHandler.accept(null); diff --git a/runner/src/main/java/synfron/reshaper/burp/runner/Api.java b/runner/src/main/java/synfron/reshaper/burp/runner/Api.java index cc305ff..e47cf63 100644 --- a/runner/src/main/java/synfron/reshaper/burp/runner/Api.java +++ b/runner/src/main/java/synfron/reshaper/burp/runner/Api.java @@ -705,7 +705,7 @@ public void setResponse(HttpResponse response) { } } - private static class EditorImpl implements Editor, IFormComponent { + private static class EditorImpl extends Component implements Editor, IFormComponent { private final JPanel component; protected final JTextPane editorBox; @@ -746,6 +746,12 @@ public Optional selection() { public Component uiComponent() { return component; } + + @Override + @SuppressWarnings("unchecked") + public T getComponent() { + return (T) this; + } } private static class HttpResponseImpl implements HttpResponse { diff --git a/runner/src/main/java/synfron/reshaper/burp/runner/Window.java b/runner/src/main/java/synfron/reshaper/burp/runner/Window.java index e03ab2a..104d87e 100644 --- a/runner/src/main/java/synfron/reshaper/burp/runner/Window.java +++ b/runner/src/main/java/synfron/reshaper/burp/runner/Window.java @@ -6,6 +6,7 @@ package synfron.reshaper.burp.runner; import burp.BurpExtender; +import synfron.reshaper.burp.core.settings.Workspaces; import synfron.reshaper.burp.core.utils.BurpUtils; import synfron.reshaper.burp.ui.components.ReshaperComponent; @@ -30,7 +31,7 @@ public Window() { } private void initComponents() { - add(new ReshaperComponent()); + add(new ReshaperComponent(Workspaces.get())); } } From a3c69ab98bcae68e0514e6307908e3c3e3a14cb2 Mon Sep 17 00:00:00 2001 From: ddwightx Date: Sun, 18 Aug 2024 15:53:07 -0400 Subject: [PATCH 2/2] Updated to v2.5.0 docs. --- docs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs b/docs index 6166e08..e23745a 160000 --- a/docs +++ b/docs @@ -1 +1 @@ -Subproject commit 6166e08512159ee3317fafbe3b6961701261ff03 +Subproject commit e23745ab2148854c4f1a8f4ef384e6ed96a36f52